From 134b4ba762f33a2575092653ab73e416af1e0034 Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Mon, 9 Nov 2020 20:11:11 +0000 Subject: [PATCH] #64867 - Provide PDF rendering with PPTX2PNG remove pdfbox-graphics2d workaround and update its version git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1883236 13f79535-47bb-0310-9956-ffa450edef68 --- build.gradle | 2 +- build.xml | 2 +- .../apache/poi/xslf/util/PDFFontMapper.java | 3 +- .../apache/poi/xslf/util/PDFFontMapper2.java | 700 ------------------ 4 files changed, 4 insertions(+), 703 deletions(-) delete mode 100644 src/ooxml/java/org/apache/poi/xslf/util/PDFFontMapper2.java diff --git a/build.gradle b/build.gradle index 47aa68d6b3..87ed66d6ff 100644 --- a/build.gradle +++ b/build.gradle @@ -295,7 +295,7 @@ project('ooxml') { compile 'org.apache.pdfbox:pdfbox:2.0.19' compile 'org.apache.pdfbox:fontbox:2.0.19' - compile 'de.rototor.pdfbox:graphics2d:0.27' + compile 'de.rototor.pdfbox:graphics2d:0.28' // for ooxml-lite, should we move this somewhere else? compile "junit:junit:${junitVersion}" diff --git a/build.xml b/build.xml index 6ac84afbd1..ed892e98f8 100644 --- a/build.xml +++ b/build.xml @@ -300,7 +300,7 @@ under the License. - + diff --git a/src/ooxml/java/org/apache/poi/xslf/util/PDFFontMapper.java b/src/ooxml/java/org/apache/poi/xslf/util/PDFFontMapper.java index 4db8fbcd96..73c48ed049 100644 --- a/src/ooxml/java/org/apache/poi/xslf/util/PDFFontMapper.java +++ b/src/ooxml/java/org/apache/poi/xslf/util/PDFFontMapper.java @@ -32,9 +32,10 @@ import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Stream; +import de.rototor.pdfbox.graphics2d.PdfBoxGraphics2DFontTextDrawer; import org.apache.pdfbox.pdmodel.font.PDFont; -public class PDFFontMapper extends PDFFontMapper2 /* PdfBoxGraphics2DFontTextDrawer */ { +public class PDFFontMapper extends PdfBoxGraphics2DFontTextDrawer { private static final String DEFAULT_TTF_PATTERN = ".*\\.tt[fc]"; diff --git a/src/ooxml/java/org/apache/poi/xslf/util/PDFFontMapper2.java b/src/ooxml/java/org/apache/poi/xslf/util/PDFFontMapper2.java deleted file mode 100644 index b8b0a22ea0..0000000000 --- a/src/ooxml/java/org/apache/poi/xslf/util/PDFFontMapper2.java +++ /dev/null @@ -1,700 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ==================================================================== - */ - -package org.apache.poi.xslf.util; - -import java.awt.Font; -import java.awt.FontFormatException; -import java.awt.FontMetrics; -import java.awt.Graphics; -import java.awt.Paint; -import java.awt.font.FontRenderContext; -import java.awt.font.LineMetrics; -import java.awt.font.TextAttribute; -import java.awt.geom.Rectangle2D; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.text.AttributedCharacterIterator; -import java.text.CharacterIterator; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import de.rototor.pdfbox.graphics2d.PdfBoxGraphics2DFontTextDrawer; -import de.rototor.pdfbox.graphics2d.PdfBoxGraphics2DFontTextDrawerDefaultFonts; -import org.apache.fontbox.ttf.TrueTypeCollection; -import org.apache.fontbox.ttf.TrueTypeFont; -import org.apache.pdfbox.io.IOUtils; -import org.apache.pdfbox.pdmodel.PDPageContentStream; -import org.apache.pdfbox.pdmodel.font.PDFont; -import org.apache.pdfbox.pdmodel.font.PDType0Font; -import org.apache.pdfbox.pdmodel.font.PDType1Font; -import org.apache.pdfbox.util.Matrix; -import org.apache.poi.util.Internal; - -/** - * Workaround class until PdfBoxGraphics2DFontTextDrawer is fixed - */ -@Internal -public class PDFFontMapper2 extends PdfBoxGraphics2DFontTextDrawer -{ - /** - * Close / delete all resources associated with this drawer. This mainly means - * deleting all temporary files. You can not use this object after a call to - * close. - *

- * Calling close multiple times does nothing. - */ - @SuppressWarnings("ResultOfMethodCallIgnored") - @Override - public void close() - { - for (File tempFile : tempFiles) - tempFile.delete(); - tempFiles.clear(); - fontFiles.clear(); - fontMap.clear(); - } - - private static class FontEntry - { - String overrideName; - File file; - } - - private final List fontFiles = new ArrayList(); - private final List tempFiles = new ArrayList(); - private final Map fontMap = new HashMap(); - - /** - * Register a font. If possible, try to use a font file, i.e. - * {@link #registerFont(String, File)}. This method will lead to the creation of - * a temporary file which stores the font data. - * - * @param fontName the name of the font to use. If null, the name is taken from the - * font. - * @param fontStream the input stream of the font. This file must be a ttf/otf file! - * You have to close the stream outside, this method will not close - * the stream. - * @throws IOException when something goes wrong with reading the font or writing the - * font to the content stream of the PDF: - */ - @SuppressWarnings("WeakerAccess") - public void registerFont(String fontName, InputStream fontStream) throws IOException - { - File fontFile = File.createTempFile("pdfboxgfx2dfont", ".ttf"); - FileOutputStream out = new FileOutputStream(fontFile); - try - { - IOUtils.copy(fontStream, out); - } - finally - { - out.close(); - } - fontFile.deleteOnExit(); - tempFiles.add(fontFile); - registerFont(fontName, fontFile); - } - - /** - * Register a font. - * - * @param fontName the name of the font to use. If null, the name is taken from the - * font. - * @param fontFile the font file. This file must exist for the live time of this - * object, as the font data will be read lazy on demand - */ - @SuppressWarnings("WeakerAccess") - public void registerFont(String fontName, File fontFile) - { - if (!fontFile.exists()) - throw new IllegalArgumentException("Font " + fontFile + " does not exist!"); - FontEntry entry = new FontEntry(); - entry.overrideName = fontName; - entry.file = fontFile; - fontFiles.add(entry); - } - - /** - * Override for registerFont(null,fontFile) - * - * @param fontFile the font file - */ - @SuppressWarnings("WeakerAccess") - public void registerFont(File fontFile) - { - registerFont(null, fontFile); - } - - /** - * Override for registerFont(null,fontStream) - * - * @param fontStream the font file - * @throws IOException when something goes wrong with reading the font or writing the - * font to the content stream of the PDF: - */ - @SuppressWarnings("WeakerAccess") - public void registerFont(InputStream fontStream) throws IOException - { - registerFont(null, fontStream); - } - - /** - * Register a font which is already associated with the PDDocument - * - * @param name the name of the font as returned by - * {@link java.awt.Font#getFontName()}. This name is used for the - * mapping the java.awt.Font to this PDFont. - * @param font the PDFont to use. This font must be loaded in the current - * document. - */ - @SuppressWarnings("WeakerAccess") - public void registerFont(String name, PDFont font) - { - fontMap.put(name, font); - } - - /** - * @return true if the font mapping is populated on demand. This is usually only - * the case if this class has been derived. The default implementation - * just checks for this. - */ - @SuppressWarnings("WeakerAccess") - protected boolean hasDynamicFontMapping() - { - return true; - } - - @Override - public boolean canDrawText(AttributedCharacterIterator iterator, IFontTextDrawerEnv env) - throws IOException, FontFormatException - { - /* - * When no font is registered we can not display the text using a font... - */ - if (fontMap.size() == 0 && fontFiles.size() == 0 && !hasDynamicFontMapping()) - return false; - - boolean run = true; - StringBuilder sb = new StringBuilder(); - while (run) - { - - Font attributeFont = (Font) iterator.getAttribute(TextAttribute.FONT); - if (attributeFont == null) - attributeFont = env.getFont(); - if (mapFont(attributeFont, env) == null) - return false; - - /* - * We can not do a Background on the text currently. - */ - if (iterator.getAttribute(TextAttribute.BACKGROUND) != null) - return false; - - boolean isStrikeThrough = TextAttribute.STRIKETHROUGH_ON - .equals(iterator.getAttribute(TextAttribute.STRIKETHROUGH)); - boolean isUnderline = TextAttribute.UNDERLINE_ON - .equals(iterator.getAttribute(TextAttribute.UNDERLINE)); - boolean isLigatures = TextAttribute.LIGATURES_ON - .equals(iterator.getAttribute(TextAttribute.LIGATURES)); - if (isStrikeThrough || isUnderline || isLigatures) - return false; - - run = iterateRun(iterator, sb); - String s = sb.toString(); - int l = s.length(); - for (int i = 0; i < l; ) - { - int codePoint = s.codePointAt(i); - switch (Character.getDirectionality(codePoint)) - { - /* - * We can handle normal LTR. - */ - case Character.DIRECTIONALITY_LEFT_TO_RIGHT: - case Character.DIRECTIONALITY_EUROPEAN_NUMBER: - case Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR: - case Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR: - case Character.DIRECTIONALITY_WHITESPACE: - case Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR: - case Character.DIRECTIONALITY_NONSPACING_MARK: - case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL: - case Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR: - case Character.DIRECTIONALITY_SEGMENT_SEPARATOR: - case Character.DIRECTIONALITY_OTHER_NEUTRALS: - case Character.DIRECTIONALITY_ARABIC_NUMBER: - break; - case Character.DIRECTIONALITY_RIGHT_TO_LEFT: - case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: - case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING: - case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE: - case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT: - /* - * We can not handle this - */ - return false; - default: - /* - * Default: We can not handle this - */ - return false; - } - - if (!attributeFont.canDisplay(codePoint)) - return false; - - i += Character.charCount(codePoint); - } - } - return true; - } - - @Override - public void drawText(AttributedCharacterIterator iterator, IFontTextDrawerEnv env) - throws IOException, FontFormatException - { - PDPageContentStream contentStream = env.getContentStream(); - - contentStream.beginText(); - - Matrix textMatrix = new Matrix(); - textMatrix.scale(1, -1); - contentStream.setTextMatrix(textMatrix); - - StringBuilder sb = new StringBuilder(); - boolean run = true; - while (run) - { - - Font attributeFont = (Font) iterator.getAttribute(TextAttribute.FONT); - if (attributeFont == null) - attributeFont = env.getFont(); - - Number fontSize = ((Number) iterator.getAttribute(TextAttribute.SIZE)); - if (fontSize != null) - attributeFont = attributeFont.deriveFont(fontSize.floatValue()); - PDFont font = applyFont(attributeFont, env); - - Paint paint = (Paint) iterator.getAttribute(TextAttribute.FOREGROUND); - if (paint == null) - paint = env.getPaint(); - - boolean isStrikeThrough = TextAttribute.STRIKETHROUGH_ON - .equals(iterator.getAttribute(TextAttribute.STRIKETHROUGH)); - boolean isUnderline = TextAttribute.UNDERLINE_ON - .equals(iterator.getAttribute(TextAttribute.UNDERLINE)); - boolean isLigatures = TextAttribute.LIGATURES_ON - .equals(iterator.getAttribute(TextAttribute.LIGATURES)); - - run = iterateRun(iterator, sb); - String text = sb.toString(); - - /* - * Apply the paint - */ - env.applyPaint(paint, null); - - /* - * If we force the text write we may encounter situations where the font can not - * display the characters. PDFBox will throw an exception in this case. We will - * just silently ignore the text and not display it instead. - */ - try - { - showTextOnStream(env, contentStream, attributeFont, font, isStrikeThrough, - isUnderline, isLigatures, text); - } - catch (IllegalArgumentException e) - { - if (font instanceof PDType1Font && !font.isEmbedded()) - { - /* - * We tried to use a builtin default font, but it does not have the needed - * characters. So we use a embedded font as fallback. - */ - try - { - if (fallbackFontUnknownEncodings == null) - fallbackFontUnknownEncodings = findFallbackFont(env); - if (fallbackFontUnknownEncodings != null) - { - env.getContentStream().setFont(fallbackFontUnknownEncodings, - attributeFont.getSize2D()); - showTextOnStream(env, contentStream, attributeFont, - fallbackFontUnknownEncodings, isStrikeThrough, isUnderline, - isLigatures, text); - e = null; - } - } - catch (IllegalArgumentException e1) - { - e = e1; - } - } - - if (e != null) - System.err.println("PDFBoxGraphics: Can not map text " + text + " with font " - + attributeFont.getFontName() + ": " + e.getMessage()); - } - } - contentStream.endText(); - } - - @Override - public FontMetrics getFontMetrics(final Font f, IFontTextDrawerEnv env) - throws IOException, FontFormatException - { - final FontMetrics defaultMetrics = env.getCalculationGraphics().getFontMetrics(f); - final PDFont pdFont = mapFont(f, env); - /* - * By default we delegate to the buffered image based calculation. This is wrong - * as soon as we use the native PDF Box font, as those have sometimes different widths. - * - * But it is correct and fine as long as we use vector shapes. - */ - if (pdFont == null) - return defaultMetrics; - return new FontMetrics(f) - { - public int getDescent() - { - return defaultMetrics.getDescent(); - } - - public int getHeight() - { - return defaultMetrics.getHeight(); - } - - public int getMaxAscent() - { - return defaultMetrics.getMaxAscent(); - } - - public int getMaxDescent() - { - return defaultMetrics.getMaxDescent(); - } - - public boolean hasUniformLineMetrics() - { - return defaultMetrics.hasUniformLineMetrics(); - } - - public LineMetrics getLineMetrics(String str, Graphics context) - { - return defaultMetrics.getLineMetrics(str, context); - } - - public LineMetrics getLineMetrics(String str, int beginIndex, int limit, - Graphics context) - { - return defaultMetrics.getLineMetrics(str, beginIndex, limit, context); - } - - public LineMetrics getLineMetrics(char[] chars, int beginIndex, int limit, - Graphics context) - { - return defaultMetrics.getLineMetrics(chars, beginIndex, limit, context); - } - - public LineMetrics getLineMetrics(CharacterIterator ci, int beginIndex, int limit, - Graphics context) - { - return defaultMetrics.getLineMetrics(ci, beginIndex, limit, context); - } - - public Rectangle2D getStringBounds(String str, Graphics context) - { - return defaultMetrics.getStringBounds(str, context); - } - - public Rectangle2D getStringBounds(String str, int beginIndex, int limit, - Graphics context) - { - return defaultMetrics.getStringBounds(str, beginIndex, limit, context); - } - - public Rectangle2D getStringBounds(char[] chars, int beginIndex, int limit, - Graphics context) - { - return defaultMetrics.getStringBounds(chars, beginIndex, limit, context); - } - - public Rectangle2D getStringBounds(CharacterIterator ci, int beginIndex, int limit, - Graphics context) - { - return defaultMetrics.getStringBounds(ci, beginIndex, limit, context); - } - - public Rectangle2D getMaxCharBounds(Graphics context) - { - return defaultMetrics.getMaxCharBounds(context); - } - - @Override - public int getAscent() - { - return defaultMetrics.getAscent(); - } - - @Override - public int getMaxAdvance() - { - return defaultMetrics.getMaxAdvance(); - } - - @Override - public int getLeading() - { - return defaultMetrics.getLeading(); - } - - @Override - public FontRenderContext getFontRenderContext() - { - return defaultMetrics.getFontRenderContext(); - } - - @Override - public int charWidth(char ch) - { - char[] chars = { ch }; - return charsWidth(chars, 0, chars.length); - } - - @Override - public int charWidth(int codePoint) - { - char[] data = Character.toChars(codePoint); - return charsWidth(data, 0, data.length); - } - - @Override - public int charsWidth(char[] data, int off, int len) - { - return stringWidth(new String(data, off, len)); - } - - @Override - public int stringWidth(String str) - { - try - { - return (int) (pdFont.getStringWidth(str) / 1000 * f.getSize()); - } - catch (IOException e) - { - throw new RuntimeException(e); - } - catch (IllegalArgumentException e) - { - /* - * We let unknown chars be handled with - */ - return defaultMetrics.stringWidth(str); - } - } - - @Override - public int[] getWidths() - { - try - { - int[] first256Widths = new int[256]; - for (int i = 0; i < first256Widths.length; i++) - first256Widths[i] = (int) (pdFont.getWidth(i) / 1000 * f.getSize()); - return first256Widths; - } - catch (IOException e) - { - throw new RuntimeException(e); - } - } - - }; - } - - private PDFont fallbackFontUnknownEncodings; - - private PDFont findFallbackFont(IFontTextDrawerEnv env) throws IOException - { - /* - * We search for the right font in the system folders... We try to use - * LucidaSansRegular and if not found Arial, because this fonts often exists. We - * use the Java default font as fallback. - * - * Normally this method is only used and called if a default font misses some - * special characters, e.g. Hebrew or Arabic characters. - */ - String javaHome = System.getProperty("java.home", "."); - String javaFontDir = javaHome + "/lib/fonts"; - String windir = System.getenv("WINDIR"); - if (windir == null) - windir = javaFontDir; - File[] paths = new File[] { new File(new File(windir), "fonts"), - new File(System.getProperty("user.dir", ".")), new File("/Library/Fonts"), - new File("/usr/share/fonts/truetype"), new File("/usr/share/fonts/truetype/dejavu"), - new File("/usr/share/fonts/truetype/liberation"), - new File("/usr/share/fonts/truetype/noto"), new File(javaFontDir) }; - File foundFontFile = null; - for (String fontFileName : new String[] { "LucidaSansRegular.ttf", "arial.ttf", "Arial.ttf", - "DejaVuSans.ttf", "LiberationMono-Regular.ttf", "NotoSerif-Regular.ttf" }) - { - for (File path : paths) - { - File arialFile = new File(path, fontFileName); - if (arialFile.exists()) - { - foundFontFile = arialFile; - break; - } - } - if (foundFontFile != null) - break; - } - /* - * If we did not find any font, we can't do anything :( - */ - if (foundFontFile == null) - return null; - return PDType0Font.load(env.getDocument(), foundFontFile); - } - - private void showTextOnStream(IFontTextDrawerEnv env, PDPageContentStream contentStream, - Font attributeFont, PDFont font, boolean isStrikeThrough, boolean isUnderline, - boolean isLigatures, String text) throws IOException - { - if (isStrikeThrough || isUnderline) - { - // noinspection unused - float stringWidth = font.getStringWidth(text); - // noinspection unused - LineMetrics lineMetrics = attributeFont - .getLineMetrics(text, env.getFontRenderContext()); - /* - * TODO: We can not draw that yet, we must do that later. While in textmode its - * not possible to draw lines... - */ - } - // noinspection StatementWithEmptyBody - if (isLigatures) - { - /* - * No idea how to map this ... - */ - } - contentStream.showText(text); - } - - private PDFont applyFont(Font font, IFontTextDrawerEnv env) - throws IOException, FontFormatException - { - PDFont fontToUse = mapFont(font, env); - if (fontToUse == null) - { - /* - * If we have no font but are forced to apply a font, we just use the default - * builtin PDF font... - */ - fontToUse = PdfBoxGraphics2DFontTextDrawerDefaultFonts.chooseMatchingHelvetica(font); - } - env.getContentStream().setFont(fontToUse, font.getSize2D()); - return fontToUse; - } - - /** - * Try to map the java.awt.Font to a PDFont. - * - * @param font the java.awt.Font for which a mapping should be found - * @param env environment of the font mapper - * @return the PDFont or null if none can be found. - * @throws IOException when the font can not be loaded - * @throws FontFormatException when the font file can not be loaded - */ - @SuppressWarnings("WeakerAccess") - protected PDFont mapFont(final Font font, final IFontTextDrawerEnv env) - throws IOException, FontFormatException - { - /* - * If we have any font registering's, we must perform them now - */ - for (final FontEntry fontEntry : fontFiles) - { - if (fontEntry.overrideName == null) - { - Font javaFont = Font.createFont(Font.TRUETYPE_FONT, fontEntry.file); - fontEntry.overrideName = javaFont.getFontName(); - } - if (fontEntry.file.getName().toLowerCase(Locale.US).endsWith(".ttc")) - { - TrueTypeCollection collection = new TrueTypeCollection(fontEntry.file); - collection.processAllFonts(new TrueTypeCollection.TrueTypeFontProcessor() - { - @Override - public void process(TrueTypeFont ttf) throws IOException - { - PDFont pdFont = PDType0Font.load(env.getDocument(), ttf, true); - fontMap.put(fontEntry.overrideName, pdFont); - fontMap.put(pdFont.getName(), pdFont); - } - }); - } - else - { - /* - * We load the font using the file. - */ - PDFont pdFont = PDType0Font.load(env.getDocument(), fontEntry.file); - fontMap.put(fontEntry.overrideName, pdFont); - } - } - fontFiles.clear(); - - return fontMap.get(font.getFontName()); - } - - private boolean iterateRun(AttributedCharacterIterator iterator, StringBuilder sb) - { - sb.setLength(0); - - int charCount = iterator.getRunLimit() - iterator.getRunStart(); - while (charCount-- > 0) - { - char c = iterator.current(); - iterator.next(); - if (c == AttributedCharacterIterator.DONE) - { - return false; - } - else - { - sb.append(c); - } - } - return (iterator.getIndex() < iterator.getRunLimit()); - } - -}