in pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java [487:640]
private void insertGeneratedAppearance(PDAnnotationWidget widget,
PDAppearanceStream appearanceStream,
OutputStream output) throws IOException
{
try (PDAppearanceContentStream contents = new PDAppearanceContentStream(appearanceStream, output))
{
PDRectangle bbox = resolveBoundingBox(widget, appearanceStream);
// Acrobat calculates the left and right padding dependent on the offset of the border edge
// This calculation works for forms having been generated by Acrobat.
// The minimum distance is always 1f even if there is no rectangle being drawn around.
float borderWidth = 0;
if (widget.getBorderStyle() != null)
{
borderWidth = widget.getBorderStyle().getWidth();
}
float padding = Math.max(1f, borderWidth);
PDRectangle clipRect = applyPadding(bbox, padding);
PDRectangle contentRect = applyPadding(clipRect, padding);
contents.saveGraphicsState();
// Acrobat always adds a clipping path
contents.addRect(clipRect.getLowerLeftX(), clipRect.getLowerLeftY(),
clipRect.getWidth(), clipRect.getHeight());
contents.clip();
// get the font
PDFont font = defaultAppearance.getFont();
if (font == null)
{
throw new IllegalArgumentException("font is null, check whether /DA entry is incomplete or incorrect");
}
if (font.getName().contains("+"))
{
LOG.warn("Font '{}' of field '{}' contains subsetted font '{}'",
defaultAppearance.getFontName().getName(), field.getFullyQualifiedName(),
font.getName());
LOG.warn("This may bring trouble with PDField.setValue(), PDAcroForm.flatten() or " +
"PDAcroForm.refreshAppearances()");
LOG.warn("You should replace this font with a non-subsetted font:");
LOG.warn("PDFont font = PDType0Font.load(doc, new FileInputStream(fontfile), false);");
LOG.warn("acroForm.getDefaultResources().put(COSName.getPDFName(\"{}\", font);",
defaultAppearance.getFontName().getName());
}
// calculate the fontSize (because 0 = autosize)
float fontSize = defaultAppearance.getFontSize();
if (Float.compare(fontSize, 0) == 0)
{
fontSize = calculateFontSize(font, contentRect);
}
// for a listbox generate the highlight rectangle for the selected
// options
if (field instanceof PDListBox)
{
insertGeneratedListboxSelectionHighlight(contents, appearanceStream, font, fontSize);
}
// start the text output
contents.beginText();
// write font and color from the /DA string, with the calculated font size
defaultAppearance.writeTo(contents, fontSize);
// calculate the y-position of the baseline
float y;
// calculate font metrics at font size
float fontScaleY = fontSize / FONTSCALE;
float fontBoundingBoxAtSize = font.getBoundingBox().getHeight() * fontScaleY;
float fontCapAtSize;
float fontDescentAtSize;
if (font.getFontDescriptor() != null) {
fontCapAtSize = font.getFontDescriptor().getCapHeight() * fontScaleY;
fontDescentAtSize = font.getFontDescriptor().getDescent() * fontScaleY;
} else {
float fontCapHeight = resolveCapHeight(font);
float fontDescent = resolveDescent(font);
LOG.debug("missing font descriptor - resolved Cap/Descent to {}/{}", fontCapHeight,
fontDescent);
fontCapAtSize = fontCapHeight * fontScaleY;
fontDescentAtSize = fontDescent * fontScaleY;
}
if (field instanceof PDTextField && ((PDTextField) field).isMultiline())
{
y = contentRect.getUpperRightY() - fontBoundingBoxAtSize;
}
else
{
// Adobe shows the text 'shifted up' in case the caps don't fit into the clipping area
if (fontCapAtSize > clipRect.getHeight())
{
y = clipRect.getLowerLeftY() + -fontDescentAtSize;
}
else
{
// calculate the position based on the content rectangle
y = clipRect.getLowerLeftY() + (clipRect.getHeight() - fontCapAtSize) / 2;
// check to ensure that ascents and descents fit
if (y - clipRect.getLowerLeftY() < -fontDescentAtSize) {
float fontDescentBased = -fontDescentAtSize + contentRect.getLowerLeftY();
float fontCapBased = contentRect.getHeight() - contentRect.getLowerLeftY() - fontCapAtSize;
y = Math.min(fontDescentBased, Math.max(y, fontCapBased));
}
}
}
// show the text
float x = contentRect.getLowerLeftX();
// special handling for comb boxes as these are like table cells with individual
// chars
if (shallComb())
{
insertGeneratedCombAppearance(contents, appearanceStream, font, fontSize);
}
else if (field instanceof PDListBox)
{
insertGeneratedListboxAppearance(contents, appearanceStream, contentRect, font, fontSize);
}
else
{
PlainText textContent = new PlainText(value);
AppearanceStyle appearanceStyle = new AppearanceStyle();
appearanceStyle.setFont(font);
appearanceStyle.setFontSize(fontSize);
// Adobe Acrobat uses the font's bounding box for the leading between the lines
appearanceStyle.setLeading(font.getBoundingBox().getHeight() * fontScaleY);
PlainTextFormatter formatter = new PlainTextFormatter
.Builder(contents)
.style(appearanceStyle)
.text(textContent)
.width(contentRect.getWidth())
.wrapLines(isMultiLine())
.initialOffset(x, y)
.textAlign(getTextAlign(widget))
.build();
formatter.format();
}
contents.endText();
contents.restoreGraphicsState();
}
}