private void insertGeneratedAppearance()

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();
        }
    }