package qz.printer.rendering; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.pdfbox.pdmodel.common.PDStream; import org.apache.pdfbox.pdmodel.font.*; import org.apache.pdfbox.rendering.PageDrawer; import org.apache.pdfbox.rendering.PageDrawerParameters; import org.apache.pdfbox.util.Matrix; import org.apache.pdfbox.util.Vector; import java.awt.*; import java.awt.geom.AffineTransform; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * PageDrawer overrides derived from PDFBOX 1.8 * with the help of Alexander Scherbatiy */ public class PdfFontPageDrawer extends PageDrawer { private static final Logger log = LogManager.getLogger(PdfFontPageDrawer.class); private String fallbackFont = "helvetica"; //todo - definable parameter? private final Map fonts = new HashMap<>(); public PdfFontPageDrawer(PageDrawerParameters parameters, boolean ignoresTransparency) throws IOException { super(parameters); if (ignoresTransparency) { // Note: These must match ParamPdfRenderer's OpaquePageDrawer addOperator(new OpaqueDrawObject()); addOperator(new OpaqueGraphicStateParameters()); } } @Override protected void showGlyph(Matrix textRenderingMatrix, PDFont font, int code, Vector displacement) throws IOException { // fall-back to draw Glyph when awt font has not been found AffineTransform at = textRenderingMatrix.createAffineTransform(); at.concatenate(font.getFontMatrix().createAffineTransform()); Graphics2D graphics = getGraphics(); setClip(); AffineTransform prevTx = graphics.getTransform(); stretchNonEmbeddedFont(at, font, code, displacement); // Probably relates to DEFAULT_FONT_MATRIX transform from PDFont at.scale(100, -100); graphics.transform(at); graphics.setComposite(getGraphicsState().getNonStrokingJavaComposite()); graphics.setPaint(getNonStrokingPaint()); Font prevFont = graphics.getFont(); Font awtFont = getAwtFont(font); graphics.setFont(awtFont); graphics.drawString(font.toUnicode(code), 0, 0); graphics.setFont(prevFont); graphics.setTransform(prevTx); } private void stretchNonEmbeddedFont(AffineTransform at, PDFont font, int code, Vector displacement) throws IOException { // Stretch non-embedded glyph if it does not match the height/width contained in the PDF. // Vertical fonts have zero X displacement, so the following code scales to 0 if we don't skip it. if (!font.isEmbedded() && !font.isVertical() && !font.isStandard14() && font.hasExplicitWidth(code)) { float fontWidth = font.getWidthFromFont(code); if (fontWidth > 0 && Math.abs(fontWidth - displacement.getX() * 1000) > 0.0001) { float pdfWidth = displacement.getX() * 1000; at.scale(pdfWidth / fontWidth, 1); } } } private Font cacheFont(PDFont font, Font awtFont) { fonts.put(font, awtFont); return awtFont; } private Font getAwtFont(PDFont font) throws IOException { Font awtFont = fonts.get(font); if (awtFont != null) { return awtFont; } if (font instanceof PDType0Font) { return cacheFont(font, getPDType0AwtFont((PDType0Font)font)); } if (font instanceof PDType1Font) { return cacheFont(font, getPDType1AwtFont((PDType1Font)font)); } String msg = String.format("Not yet implemented: %s", font.getClass().getName()); throw new UnsupportedOperationException(msg); } public Font getPDType0AwtFont(PDType0Font font) throws IOException { Font awtFont = null; PDCIDFont descendantFont = font.getDescendantFont(); if (descendantFont != null) { if (descendantFont instanceof PDCIDFontType2) { awtFont = getPDCIDAwtFontType2((PDCIDFontType2)descendantFont); } if (awtFont != null) { /* * Fix Oracle JVM Crashes. * Tested with Oracle JRE 6.0_45-b06 and 7.0_21-b11 */ awtFont.canDisplay(1); } } if (awtFont == null) { awtFont = FontManager.getAwtFont(fallbackFont); log.debug("Using font {} instead of {}", awtFont.getName(), descendantFont.getFontDescriptor().getFontName()); } return awtFont.deriveFont(10f); } private Font getPDType1AwtFont(PDType1Font font) throws IOException { Font awtFont = null; String baseFont = font.getBaseFont(); PDFontDescriptor fd = font.getFontDescriptor(); if (fd != null) { if (fd.getFontFile() != null) { try { // create a type1 font with the embedded data awtFont = Font.createFont(Font.TYPE1_FONT, fd.getFontFile().createInputStream()); } catch(java.awt.FontFormatException e) { log.debug("Can't read the embedded type1 font {}", fd.getFontName()); } } if (awtFont == null) { // check if the font is part of our environment if (fd.getFontName() != null) { awtFont = FontManager.getAwtFont(fd.getFontName()); } if (awtFont == null) { log.debug("Can't find the specified font {}", fd.getFontName()); } } } else { // check if the font is part of our environment awtFont = FontManager.getAwtFont(baseFont); if (awtFont == null) { log.debug("Can't find the specified basefont {}", baseFont); } } if (awtFont == null) { // we can't find anything, so we have to use the standard font awtFont = FontManager.getAwtFont(fallbackFont); log.debug("Using font {} instead", awtFont.getName()); } return awtFont.deriveFont(20f); } public Font getPDCIDAwtFontType2(PDCIDFontType2 font) throws IOException { Font awtFont = null; PDFontDescriptor fd = font.getFontDescriptor(); PDStream ff2Stream = fd.getFontFile2(); if (ff2Stream != null) { try { // create a font with the embedded data awtFont = Font.createFont(Font.TRUETYPE_FONT, ff2Stream.createInputStream()); } catch(java.awt.FontFormatException f) { log.debug("Can't read the embedded font {}", fd.getFontName()); } if (awtFont == null) { if (fd.getFontName() != null) { awtFont = FontManager.getAwtFont(fd.getFontName()); } if (awtFont != null) { log.debug("Using font {} instead", awtFont.getName()); } } } return awtFont; } }