com.openhtmltopdf.pdfboxout.PdfBoxPerDocumentFormState Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openhtmltopdf-pdfbox Show documentation
Show all versions of openhtmltopdf-pdfbox Show documentation
Openhtmltopdf is a CSS 2.1 renderer written in Java. This artifact supports PDF output with Apache PDF-BOX 2.
The newest version!
package com.openhtmltopdf.pdfboxout;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import com.openhtmltopdf.util.LogMessageId;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import com.openhtmltopdf.css.constants.CSSName;
import com.openhtmltopdf.layout.SharedContext;
import com.openhtmltopdf.pdfboxout.PdfBoxForm.CheckboxStyle;
import com.openhtmltopdf.render.Box;
import com.openhtmltopdf.render.RenderingContext;
import com.openhtmltopdf.util.ArrayUtil;
import com.openhtmltopdf.util.XRLog;
/**
* The per document container for form and form control state.
*/
public class PdfBoxPerDocumentFormState {
// We keep a map of forms for the document so we can add controls to the correct form as they are seen.
private final Map forms = new HashMap<>();
// The list of controls in the document. Control class contains all the info we need to output a control.
private final List controls = new ArrayList<>();
// A set of controls, so we don't double process a control.
private final Set seenControls = new HashSet<>();
// We keep a map of fonts to font resource name so we don't double add fonts needed for form controls.
private final Map controlFonts = new HashMap<>();
// The checkbox style to appearance stream map. We only create appearance streams on demand and once for a specific
// style so we store appearance streams created here.
private final Map checkboxAppearances = new EnumMap<>(CheckboxStyle.class);
// Again, we only create these appearance streams as needed.
private PDAppearanceStream checkboxOffAppearance;
private PDAppearanceStream radioBoxOffAppearance;
private PDAppearanceStream radioBoxOnAppearance;
// The ZapfDingbats font resource needed by checkbox and radio box appearance streams.
private PDResources checkBoxFontResource;
public PDAppearanceStream getCheckboxStyle(CheckboxStyle style) {
return checkboxAppearances.get(style);
}
public PDAppearanceStream getCheckboxOffStream() {
return this.checkboxOffAppearance;
}
public PDAppearanceStream getRadioOffStream() {
return this.radioBoxOffAppearance;
}
public PDAppearanceStream getRadioOnStream() {
return this.radioBoxOnAppearance;
}
/**
* Adds a form to a map to be used later by processControls
.
*/
public void addFormIfRequired(Box box, PdfBoxOutputDevice od) {
if (!forms.containsKey(box.getElement())) {
PdfBoxForm frm = PdfBoxForm.createForm(box.getElement(), this, od);
forms.put(box.getElement(), frm);
}
}
/**
* Adds a control to a list to be used later by processControls
.
*/
public void addControlIfRequired(Box box, PDPage page, AffineTransform transform, RenderingContext c, float pageHeight) {
if (!seenControls.contains(box.getElement())) {
controls.add(new PdfBoxForm.Control(box, page, new AffineTransform(transform), c, pageHeight));
seenControls.add(box.getElement());
}
}
private String getControlFont(SharedContext sharedContext, PdfBoxForm.Control ctrl) {
PDFont fnt = ((PdfBoxFSFont) sharedContext.getFont(ctrl.box.getStyle().getFontSpecification())).getFontDescription().get(0).getFont();
String fontName;
if (!controlFonts.containsKey(fnt)) {
fontName = "OpenHTMLFont" + controlFonts.size();
controlFonts.put(fnt, fontName);
} else {
fontName = controlFonts.get(fnt);
}
return fontName;
}
private void createCheckboxAppearanceStreams(PDDocument writer, PdfBoxForm.Control ctrl) {
CheckboxStyle style = CheckboxStyle.fromIdent(ctrl.box.getStyle().getIdent(CSSName.FS_CHECKBOX_STYLE));
if (!checkboxAppearances.containsKey(style)) {
PDAppearanceStream strm = PdfBoxForm.createCheckboxAppearance(style, writer, checkBoxFontResource);
checkboxAppearances.put(style, strm);
}
if (checkboxOffAppearance == null) {
checkboxOffAppearance = PdfBoxForm.createCheckboxAppearance("q\nQ\n", writer, checkBoxFontResource);
}
}
private void createRadioboxAppearanceStream(PDDocument writer, PdfBoxForm.Control ctrl) {
if (radioBoxOffAppearance == null) {
radioBoxOffAppearance = PdfBoxForm.createCheckboxAppearance("q\nQ\n", writer, checkBoxFontResource);
}
if (radioBoxOnAppearance == null) {
radioBoxOnAppearance = PdfBoxForm.createCheckboxAppearance(CheckboxStyle.DIAMOND, writer, checkBoxFontResource);
}
}
private void createCheckboxFontResource() {
if (checkBoxFontResource == null) {
checkBoxFontResource = new PDResources();
checkBoxFontResource.put(COSName.getPDFName("OpenHTMLZap"), new PDType1Font(Standard14Fonts.FontName.ZAPF_DINGBATS));
}
}
public void processControls(SharedContext sharedContext, PDDocument writer, Box root) {
for (PdfBoxForm.Control ctrl : controls) {
PdfBoxForm frm = findEnclosingForm(ctrl.box.getElement());
String fontName = null;
if (!ArrayUtil.isOneOf(ctrl.box.getElement().getAttribute("type"), "checkbox", "radio", "hidden")) {
// Need to embed a font for every control other than checkbox, radio and hidden.
fontName = getControlFont(sharedContext, ctrl);
} else if (ctrl.box.getElement().getAttribute("type").equals("checkbox")) {
createCheckboxFontResource();
createCheckboxAppearanceStreams(writer, ctrl);
} else if (ctrl.box.getElement().getAttribute("type").equals("radio")) {
createCheckboxFontResource();
createRadioboxAppearanceStream(writer, ctrl);
}
if (frm != null) {
frm.addControl(ctrl, fontName);
}
}
PDResources resources = new PDResources();
/* Defaultfonts */
resources.put(COSName.HELV, new PDType1Font(Standard14Fonts.FontName.HELVETICA));
resources.put(COSName.ZA_DB, new PDType1Font(Standard14Fonts.FontName.ZAPF_DINGBATS));
for (Map.Entry fnt : controlFonts.entrySet()) {
resources.put(COSName.getPDFName(fnt.getValue()), fnt.getKey());
}
if (forms.size() != 0) {
int start = 0;
PDAcroForm acro = new PDAcroForm(writer);
acro.setDefaultAppearance("/Helv 0 Tf 0 g");
acro.setNeedAppearances(Boolean.TRUE);
acro.setDefaultResources(resources);
writer.getDocumentCatalog().setAcroForm(acro);
for (PdfBoxForm frm : forms.values()) {
try {
start = 1 + frm.process(acro, start, root);
} catch (IOException e) {
throw new PdfContentStreamAdapter.PdfException("processControls", e);
}
}
}
}
/**
* Helper function to find an enclosing PdfBoxForm given a control element.
*/
private PdfBoxForm findEnclosingForm(Node e) {
Element frmElement = DOMUtil.findClosestEnclosingElementWithNodeName(e, "form");
if (frmElement != null &&
forms.containsKey(frmElement)) {
return forms.get(frmElement);
}
XRLog.log(Level.WARNING, LogMessageId.LogMessageId1Param.GENERAL_PDF_FOUND_FORM_CONTROL_WITH_NO_ENCLOSING_FORM, e.getNodeName());
return null;
}
}