mirror of https://github.com/apache/poi.git
#64867 - Provide PDF rendering with PPTX2PNG
render text as text, i.e. not as shapes git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1883212 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
578b71b127
commit
1d3ef4d81b
|
@ -32,8 +32,10 @@ import java.text.AttributedCharacterIterator.Attribute;
|
||||||
import java.text.AttributedString;
|
import java.text.AttributedString;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.poi.common.usermodel.fonts.FontGroup;
|
import org.apache.poi.common.usermodel.fonts.FontGroup;
|
||||||
import org.apache.poi.common.usermodel.fonts.FontGroup.FontGroupRange;
|
import org.apache.poi.common.usermodel.fonts.FontGroup.FontGroupRange;
|
||||||
|
@ -257,9 +259,18 @@ public class DrawTextParagraph implements Drawable {
|
||||||
|
|
||||||
DrawFactory fact = DrawFactory.getInstance(graphics);
|
DrawFactory fact = DrawFactory.getInstance(graphics);
|
||||||
StringBuilder text = new StringBuilder();
|
StringBuilder text = new StringBuilder();
|
||||||
AttributedString at = getAttributedString(graphics, text);
|
|
||||||
|
|
||||||
AttributedCharacterIterator it = at.getIterator();
|
List<AttributedStringData> attList = getAttributedString(graphics, text);
|
||||||
|
AttributedString as = new AttributedString(text.toString());
|
||||||
|
AttributedString asNoCR = new AttributedString(text.toString().replaceAll("[\\r\\n]", " "));
|
||||||
|
|
||||||
|
for (AttributedStringData asd : attList) {
|
||||||
|
as.addAttribute(asd.attribute, asd.value, asd.beginIndex, asd.endIndex);
|
||||||
|
asNoCR.addAttribute(asd.attribute, asd.value, asd.beginIndex, asd.endIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
AttributedCharacterIterator it = as.getIterator();
|
||||||
|
AttributedCharacterIterator itNoCR = asNoCR.getIterator();
|
||||||
LineBreakMeasurer measurer = new LineBreakMeasurer(it, graphics.getFontRenderContext());
|
LineBreakMeasurer measurer = new LineBreakMeasurer(it, graphics.getFontRenderContext());
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int startIndex = measurer.getPosition();
|
int startIndex = measurer.getPosition();
|
||||||
|
@ -308,7 +319,7 @@ public class DrawTextParagraph implements Drawable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AttributedString str = new AttributedString(it, startIndex, endIndex);
|
AttributedString str = new AttributedString(itNoCR, startIndex, endIndex);
|
||||||
DrawTextFragment line = fact.getTextFragment(layout, str);
|
DrawTextFragment line = fact.getTextFragment(layout, str);
|
||||||
lines.add(line);
|
lines.add(line);
|
||||||
|
|
||||||
|
@ -369,10 +380,14 @@ public class DrawTextParagraph implements Drawable {
|
||||||
// TODO: check font group defaulting to Symbol
|
// TODO: check font group defaulting to Symbol
|
||||||
buFont = dfm.getMappedFont(graphics, buFont);
|
buFont = dfm.getMappedFont(graphics, buFont);
|
||||||
|
|
||||||
|
Map<TextAttribute,Object> att = new HashMap<>();
|
||||||
|
att.put(TextAttribute.FOREGROUND, fgPaint);
|
||||||
|
att.put(TextAttribute.FAMILY, buFont.getTypeface());
|
||||||
|
att.put(TextAttribute.SIZE, fontSize);
|
||||||
|
att.put(TextAttribute.FONT, new Font(att));
|
||||||
|
|
||||||
AttributedString str = new AttributedString(dfm.mapFontCharset(graphics,buFont,buCharacter));
|
AttributedString str = new AttributedString(dfm.mapFontCharset(graphics,buFont,buCharacter));
|
||||||
str.addAttribute(TextAttribute.FOREGROUND, fgPaint);
|
att.forEach(str::addAttribute);
|
||||||
str.addAttribute(TextAttribute.FAMILY, buFont.getTypeface());
|
|
||||||
str.addAttribute(TextAttribute.SIZE, fontSize);
|
|
||||||
|
|
||||||
TextLayout layout = new TextLayout(str.getIterator(), graphics.getFontRenderContext());
|
TextLayout layout = new TextLayout(str.getIterator(), graphics.getFontRenderContext());
|
||||||
DrawFactory fact = DrawFactory.getInstance(graphics);
|
DrawFactory fact = DrawFactory.getInstance(graphics);
|
||||||
|
@ -559,8 +574,7 @@ public class DrawTextParagraph implements Drawable {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AttributedString getAttributedString(Graphics2D graphics, StringBuilder text){
|
protected List<AttributedStringData> getAttributedString(Graphics2D graphics, StringBuilder text) {
|
||||||
List<AttributedStringData> attList = new ArrayList<>();
|
|
||||||
if (text == null) {
|
if (text == null) {
|
||||||
text = new StringBuilder();
|
text = new StringBuilder();
|
||||||
}
|
}
|
||||||
|
@ -569,6 +583,9 @@ public class DrawTextParagraph implements Drawable {
|
||||||
DrawFontManager dfm = DrawFactory.getInstance(graphics).getFontManager(graphics);
|
DrawFontManager dfm = DrawFactory.getInstance(graphics).getFontManager(graphics);
|
||||||
assert(dfm != null);
|
assert(dfm != null);
|
||||||
|
|
||||||
|
final Map<Attribute,Object> att = new HashMap<>();
|
||||||
|
final List<AttributedStringData> attList = new ArrayList<>();
|
||||||
|
|
||||||
for (TextRun run : paragraph){
|
for (TextRun run : paragraph){
|
||||||
String runText = getRenderableText(graphics, run);
|
String runText = getRenderableText(graphics, run);
|
||||||
// skip empty runs
|
// skip empty runs
|
||||||
|
@ -576,66 +593,79 @@ public class DrawTextParagraph implements Drawable {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// user can pass an custom object to convert fonts
|
att.clear();
|
||||||
|
|
||||||
runText = dfm.mapFontCharset(graphics, run.getFontInfo(null), runText);
|
// user can pass an custom object to convert fonts
|
||||||
int beginIndex = text.length();
|
FontInfo fontInfo = run.getFontInfo(null);
|
||||||
|
runText = dfm.mapFontCharset(graphics, fontInfo, runText);
|
||||||
|
final int beginIndex = text.length();
|
||||||
text.append(runText);
|
text.append(runText);
|
||||||
int endIndex = text.length();
|
final int endIndex = text.length();
|
||||||
|
|
||||||
PaintStyle fgPaintStyle = run.getFontColor();
|
PaintStyle fgPaintStyle = run.getFontColor();
|
||||||
Paint fgPaint = dp.getPaint(graphics, fgPaintStyle);
|
Paint fgPaint = dp.getPaint(graphics, fgPaintStyle);
|
||||||
attList.add(new AttributedStringData(TextAttribute.FOREGROUND, fgPaint, beginIndex, endIndex));
|
|
||||||
|
att.put(TextAttribute.FOREGROUND, fgPaint);
|
||||||
|
|
||||||
Double fontSz = run.getFontSize();
|
Double fontSz = run.getFontSize();
|
||||||
if (fontSz == null) {
|
if (fontSz == null) {
|
||||||
fontSz = paragraph.getDefaultFontSize();
|
fontSz = paragraph.getDefaultFontSize();
|
||||||
}
|
}
|
||||||
attList.add(new AttributedStringData(TextAttribute.SIZE, fontSz.floatValue(), beginIndex, endIndex));
|
att.put(TextAttribute.SIZE, fontSz.floatValue());
|
||||||
|
|
||||||
if(run.isBold()) {
|
if(run.isBold()) {
|
||||||
attList.add(new AttributedStringData(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD, beginIndex, endIndex));
|
att.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
|
||||||
}
|
}
|
||||||
if(run.isItalic()) {
|
if(run.isItalic()) {
|
||||||
attList.add(new AttributedStringData(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE, beginIndex, endIndex));
|
att.put(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
|
||||||
}
|
}
|
||||||
if(run.isUnderlined()) {
|
if(run.isUnderlined()) {
|
||||||
attList.add(new AttributedStringData(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON, beginIndex, endIndex));
|
att.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
|
||||||
attList.add(new AttributedStringData(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_TWO_PIXEL, beginIndex, endIndex));
|
att.put(TextAttribute.INPUT_METHOD_UNDERLINE, TextAttribute.UNDERLINE_LOW_TWO_PIXEL);
|
||||||
}
|
}
|
||||||
if(run.isStrikethrough()) {
|
if(run.isStrikethrough()) {
|
||||||
attList.add(new AttributedStringData(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON, beginIndex, endIndex));
|
att.put(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
|
||||||
}
|
}
|
||||||
if(run.isSubscript()) {
|
if(run.isSubscript()) {
|
||||||
attList.add(new AttributedStringData(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUB, beginIndex, endIndex));
|
att.put(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUB);
|
||||||
}
|
}
|
||||||
if(run.isSuperscript()) {
|
if(run.isSuperscript()) {
|
||||||
attList.add(new AttributedStringData(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUPER, beginIndex, endIndex));
|
att.put(TextAttribute.SUPERSCRIPT, TextAttribute.SUPERSCRIPT_SUPER);
|
||||||
}
|
}
|
||||||
|
|
||||||
Hyperlink<?,?> hl = run.getHyperlink();
|
Hyperlink<?,?> hl = run.getHyperlink();
|
||||||
if (hl != null) {
|
if (hl != null) {
|
||||||
attList.add(new AttributedStringData(HYPERLINK_HREF, hl.getAddress(), beginIndex, endIndex));
|
att.put(HYPERLINK_HREF, hl.getAddress());
|
||||||
attList.add(new AttributedStringData(HYPERLINK_LABEL, hl.getLabel(), beginIndex, endIndex));
|
att.put(HYPERLINK_LABEL, hl.getLabel());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fontInfo != null) {
|
||||||
|
att.put(TextAttribute.FAMILY, fontInfo.getTypeface());
|
||||||
|
} else {
|
||||||
|
att.put(TextAttribute.FAMILY, paragraph.getDefaultFontFamily());
|
||||||
|
}
|
||||||
|
|
||||||
|
att.put(TextAttribute.FONT, new Font(att));
|
||||||
|
|
||||||
|
att.forEach((k,v) -> attList.add(new AttributedStringData(k,v,beginIndex,endIndex)));
|
||||||
|
|
||||||
processGlyphs(graphics, dfm, attList, beginIndex, run, runText);
|
processGlyphs(graphics, dfm, attList, beginIndex, run, runText);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure that the paragraph contains at least one character
|
// ensure that the paragraph contains at least one character
|
||||||
// We need this trick to correctly measure text
|
// We need this trick to correctly measure text
|
||||||
if (text.length() == 0) {
|
if (text.length() == 0) {
|
||||||
Double fontSz = paragraph.getDefaultFontSize();
|
|
||||||
text.append(" ");
|
text.append(" ");
|
||||||
attList.add(new AttributedStringData(TextAttribute.SIZE, fontSz.floatValue(), 0, 1));
|
|
||||||
|
Double fontSz = paragraph.getDefaultFontSize();
|
||||||
|
att.put(TextAttribute.SIZE, fontSz.floatValue());
|
||||||
|
att.put(TextAttribute.FAMILY, paragraph.getDefaultFontFamily());
|
||||||
|
att.put(TextAttribute.FONT, new Font(att));
|
||||||
|
|
||||||
|
att.forEach((k,v) -> attList.add(new AttributedStringData(k,v,0,1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
AttributedString string = new AttributedString(text.toString());
|
return attList;
|
||||||
for (AttributedStringData asd : attList) {
|
|
||||||
string.addAttribute(asd.attribute, asd.value, asd.beginIndex, asd.endIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Binary file not shown.
|
@ -87,4 +87,11 @@ module org.apache.poi.ooxml {
|
||||||
requires static org.apache.santuario.xmlsec;
|
requires static org.apache.santuario.xmlsec;
|
||||||
requires static org.bouncycastle.provider;
|
requires static org.bouncycastle.provider;
|
||||||
requires static org.bouncycastle.pkix;
|
requires static org.bouncycastle.pkix;
|
||||||
|
|
||||||
|
/* optional dependencies for slideshow rendering via PPTX2PNG */
|
||||||
|
requires static batik.all;
|
||||||
|
requires static org.apache.pdfbox;
|
||||||
|
requires static org.apache.fontbox;
|
||||||
|
requires static de.rototor.pdfbox.graphics2d;
|
||||||
|
requires static xmlgraphics.commons;
|
||||||
}
|
}
|
Binary file not shown.
|
@ -89,6 +89,14 @@ module org.apache.poi.ooxml {
|
||||||
requires org.bouncycastle.pkix;
|
requires org.bouncycastle.pkix;
|
||||||
|
|
||||||
|
|
||||||
|
/* optional dependencies for slideshow rendering via PPTX2PNG */
|
||||||
|
requires batik.all;
|
||||||
|
requires org.apache.pdfbox;
|
||||||
|
requires org.apache.fontbox;
|
||||||
|
requires de.rototor.pdfbox.graphics2d;
|
||||||
|
requires xmlgraphics.commons;
|
||||||
|
|
||||||
|
|
||||||
// test specific exports
|
// test specific exports
|
||||||
requires junit;
|
requires junit;
|
||||||
requires com.google.common;
|
requires com.google.common;
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
package org.apache.poi.xslf.util;
|
||||||
|
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.FontFormatException;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||||
|
|
||||||
|
public class PDFFontMapper extends PDFFontMapper2 /* PdfBoxGraphics2DFontTextDrawer */ {
|
||||||
|
|
||||||
|
private static final String DEFAULT_TTF_PATTERN = ".*\\.tt[fc]";
|
||||||
|
|
||||||
|
private static final String FONTDIRS_MAC =
|
||||||
|
"$HOME/Library/Fonts;" +
|
||||||
|
"/Library/Fonts;" +
|
||||||
|
"/Network/Library/Fonts;" +
|
||||||
|
"/System/Library/Fonts;" +
|
||||||
|
"/System Folder/Fonts";
|
||||||
|
|
||||||
|
private static final String FONTDIRS_WIN =
|
||||||
|
"C:\\Windows\\Fonts";
|
||||||
|
|
||||||
|
private static final String FONTDIRS_UNX =
|
||||||
|
"/usr/share/fonts;" +
|
||||||
|
"/usr/local/share/fonts;" +
|
||||||
|
"$HOME/.fonts";
|
||||||
|
|
||||||
|
|
||||||
|
private final Map<String,File> fonts = new HashMap<>();
|
||||||
|
private final Set<String> registered = new HashSet<>();
|
||||||
|
|
||||||
|
public PDFFontMapper(String fontDir, String fontTtf) {
|
||||||
|
registerFonts(fontDir, fontTtf);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void registerFonts(String fontDir, String fontTtf) {
|
||||||
|
if (fontDir == null) {
|
||||||
|
String OS = System.getProperty("os.name", "generic").toLowerCase(Locale.ROOT);
|
||||||
|
if (OS.contains("mac") || OS.contains("darwin")) {
|
||||||
|
fontDir = FONTDIRS_MAC;
|
||||||
|
} else if (OS.contains("win")) {
|
||||||
|
fontDir = FONTDIRS_WIN;
|
||||||
|
} else {
|
||||||
|
fontDir = FONTDIRS_UNX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String fd = fontDir.replace("$HOME", System.getProperty("user.home"));
|
||||||
|
final LinkedList<File> dirs = new LinkedList<>();
|
||||||
|
Stream.of(fd.split(";")).map(File::new).filter(File::isDirectory).forEach(dirs::add);
|
||||||
|
|
||||||
|
Pattern p = Pattern.compile(fontTtf == null ? DEFAULT_TTF_PATTERN : fontTtf);
|
||||||
|
|
||||||
|
while (!dirs.isEmpty()) {
|
||||||
|
File[] ttfs = dirs.removeFirst().listFiles((f, n) -> {
|
||||||
|
File f2 = new File(f, n);
|
||||||
|
if (f2.isDirectory()) {
|
||||||
|
dirs.add(f2);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return p.matcher(n).matches();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ttfs == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (File f : ttfs) {
|
||||||
|
try {
|
||||||
|
Font font = Font.createFont(Font.TRUETYPE_FONT, f);
|
||||||
|
fonts.put(font.getFontName(Locale.ROOT), f);
|
||||||
|
} catch (IOException|FontFormatException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PDFont mapFont(Font font, IFontTextDrawerEnv env) throws IOException, FontFormatException {
|
||||||
|
String name = font.getFontName(Locale.ROOT);
|
||||||
|
if (!registered.contains(name)) {
|
||||||
|
registered.add(name);
|
||||||
|
File f = fonts.get(name);
|
||||||
|
if (f != null) {
|
||||||
|
super.registerFont(name, f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.mapFont(font, env);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,681 @@
|
||||||
|
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.
|
||||||
|
* <p>
|
||||||
|
* 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<FontEntry> fontFiles = new ArrayList<FontEntry>();
|
||||||
|
private final List<File> tempFiles = new ArrayList<File>();
|
||||||
|
private final Map<String, PDFont> fontMap = new HashMap<String, PDFont>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import de.rototor.pdfbox.graphics2d.PdfBoxGraphics2D;
|
import de.rototor.pdfbox.graphics2d.PdfBoxGraphics2D;
|
||||||
|
import de.rototor.pdfbox.graphics2d.PdfBoxGraphics2DFontTextDrawer;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
|
@ -36,8 +37,13 @@ public class PDFFormat implements OutputFormat {
|
||||||
private final PDDocument document;
|
private final PDDocument document;
|
||||||
private PDPageContentStream contentStream;
|
private PDPageContentStream contentStream;
|
||||||
private PdfBoxGraphics2D pdfBoxGraphics2D;
|
private PdfBoxGraphics2D pdfBoxGraphics2D;
|
||||||
|
private PdfBoxGraphics2DFontTextDrawer fontTextDrawer;
|
||||||
|
|
||||||
|
public PDFFormat(boolean textAsShapes, String fontDir, String fontTtf) {
|
||||||
|
if (!textAsShapes) {
|
||||||
|
fontTextDrawer = new PDFFontMapper(fontDir, fontTtf);
|
||||||
|
}
|
||||||
|
|
||||||
public PDFFormat() {
|
|
||||||
document = new PDDocument();
|
document = new PDDocument();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +52,10 @@ public class PDFFormat implements OutputFormat {
|
||||||
PDPage page = new PDPage(new PDRectangle((float) width, (float) height));
|
PDPage page = new PDPage(new PDRectangle((float) width, (float) height));
|
||||||
document.addPage(page);
|
document.addPage(page);
|
||||||
contentStream = new PDPageContentStream(document, page);
|
contentStream = new PDPageContentStream(document, page);
|
||||||
pdfBoxGraphics2D = new PdfBoxGraphics2D(document, (float)width, (float)height);
|
pdfBoxGraphics2D = new PdfBoxGraphics2D(document, (float) width, (float) height);
|
||||||
|
if (fontTextDrawer != null) {
|
||||||
|
pdfBoxGraphics2D.setFontTextDrawer(fontTextDrawer);
|
||||||
|
}
|
||||||
return pdfBoxGraphics2D;
|
return pdfBoxGraphics2D;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,5 +76,9 @@ public class PDFFormat implements OutputFormat {
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
document.close();
|
document.close();
|
||||||
|
if (fontTextDrawer != null) {
|
||||||
|
fontTextDrawer.close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,10 @@ public final class PPTX2PNG {
|
||||||
" -textAsShapes text elements are saved as shapes in SVG, necessary for variable spacing\n" +
|
" -textAsShapes text elements are saved as shapes in SVG, necessary for variable spacing\n" +
|
||||||
" often found in math formulas\n" +
|
" often found in math formulas\n" +
|
||||||
" -charset <cs> sets the default charset to be used, defaults to Windows-1252\n" +
|
" -charset <cs> sets the default charset to be used, defaults to Windows-1252\n" +
|
||||||
" -emfHeaderBounds force the usage of the emf header bounds to calculate the bounding box";
|
" -emfHeaderBounds force the usage of the emf header bounds to calculate the bounding box\n" +
|
||||||
|
" -fontdir <dir> (PDF only) font directories separated by \";\" - use $HOME for current users home dir\n" +
|
||||||
|
" defaults to the usual plattform directories\n" +
|
||||||
|
" -fontTtf <regex> (PDF only) regex to match the .ttf filenames";
|
||||||
|
|
||||||
System.out.println(msg);
|
System.out.println(msg);
|
||||||
// no System.exit here, as we also run in junit tests!
|
// no System.exit here, as we also run in junit tests!
|
||||||
|
@ -104,6 +107,8 @@ public final class PPTX2PNG {
|
||||||
private boolean textAsShapes = false;
|
private boolean textAsShapes = false;
|
||||||
private Charset charset = LocaleUtil.CHARSET_1252;
|
private Charset charset = LocaleUtil.CHARSET_1252;
|
||||||
private boolean emfHeaderBounds = false;
|
private boolean emfHeaderBounds = false;
|
||||||
|
private String fontDir = null;
|
||||||
|
private String fontTtf = null;
|
||||||
|
|
||||||
private PPTX2PNG() {
|
private PPTX2PNG() {
|
||||||
}
|
}
|
||||||
|
@ -192,6 +197,22 @@ public final class PPTX2PNG {
|
||||||
case "-emfheaderbounds":
|
case "-emfheaderbounds":
|
||||||
emfHeaderBounds = true;
|
emfHeaderBounds = true;
|
||||||
break;
|
break;
|
||||||
|
case "-fontdir":
|
||||||
|
if (opt != null) {
|
||||||
|
fontDir = opt;
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
fontDir = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "-fontttf":
|
||||||
|
if (opt != null) {
|
||||||
|
fontTtf = opt;
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
fontTtf = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
file = new File(args[i]);
|
file = new File(args[i]);
|
||||||
break;
|
break;
|
||||||
|
@ -313,7 +334,7 @@ public final class PPTX2PNG {
|
||||||
case "svg":
|
case "svg":
|
||||||
return new SVGFormat(textAsShapes);
|
return new SVGFormat(textAsShapes);
|
||||||
case "pdf":
|
case "pdf":
|
||||||
return new PDFFormat();
|
return new PDFFormat(textAsShapes,fontDir,fontTtf);
|
||||||
default:
|
default:
|
||||||
return new BitmapFormat(format);
|
return new BitmapFormat(format);
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ public class TestPPTX2PNG {
|
||||||
private static boolean xslfOnly;
|
private static boolean xslfOnly;
|
||||||
private static final POIDataSamples samples = POIDataSamples.getSlideShowInstance();
|
private static final POIDataSamples samples = POIDataSamples.getSlideShowInstance();
|
||||||
private static final File basedir = null;
|
private static final File basedir = null;
|
||||||
|
|
||||||
private static final String files =
|
private static final String files =
|
||||||
"bug64693.pptx, 53446.ppt, alterman_security.ppt, alterman_security.pptx, KEY02.pptx, themes.pptx, " +
|
"bug64693.pptx, 53446.ppt, alterman_security.ppt, alterman_security.pptx, KEY02.pptx, themes.pptx, " +
|
||||||
"backgrounds.pptx, layouts.pptx, sample.pptx, shapes.pptx, 54880_chinese.ppt, keyframes.pptx," +
|
"backgrounds.pptx, layouts.pptx, sample.pptx, shapes.pptx, 54880_chinese.ppt, keyframes.pptx," +
|
||||||
|
@ -74,6 +75,8 @@ public class TestPPTX2PNG {
|
||||||
@Parameter
|
@Parameter
|
||||||
public String pptFile;
|
public String pptFile;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Parameters(name="{0}")
|
@Parameters(name="{0}")
|
||||||
public static Collection<String> data() {
|
public static Collection<String> data() {
|
||||||
Function<String, Stream<String>> fun = (basedir == null) ? Stream::of :
|
Function<String, Stream<String>> fun = (basedir == null) ? Stream::of :
|
||||||
|
@ -105,6 +108,7 @@ public class TestPPTX2PNG {
|
||||||
"-quiet",
|
"-quiet",
|
||||||
// "-charset", "GBK",
|
// "-charset", "GBK",
|
||||||
// "-emfHeaderBounds",
|
// "-emfHeaderBounds",
|
||||||
|
// "-textAsShapes",
|
||||||
"-fixside", "long",
|
"-fixside", "long",
|
||||||
"-scale", "800"
|
"-scale", "800"
|
||||||
));
|
));
|
||||||
|
|
|
@ -575,16 +575,18 @@ public class HwmfGraphics implements HwmfCharsetAware {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addAttributes(BiConsumer<TextAttribute,Object> attributes, HwmfFont font, String typeface) {
|
private void addAttributes(BiConsumer<TextAttribute,Object> attributes, HwmfFont font, String typeface) {
|
||||||
attributes.accept(TextAttribute.FAMILY, typeface);
|
Map<TextAttribute,Object> att = new HashMap<>();
|
||||||
attributes.accept(TextAttribute.SIZE, getFontHeight(font));
|
att.put(TextAttribute.FAMILY, typeface);
|
||||||
|
att.put(TextAttribute.SIZE, getFontHeight(font));
|
||||||
|
|
||||||
if (font.isStrikeOut()) {
|
if (font.isStrikeOut()) {
|
||||||
attributes.accept(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
|
att.put(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
|
||||||
}
|
}
|
||||||
if (font.isUnderline()) {
|
if (font.isUnderline()) {
|
||||||
attributes.accept(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
|
att.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
|
||||||
}
|
}
|
||||||
if (font.isItalic()) {
|
if (font.isItalic()) {
|
||||||
attributes.accept(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
|
att.put(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
|
||||||
}
|
}
|
||||||
// convert font weight to awt font weight - usually a font weight of 400 is regarded as regular
|
// convert font weight to awt font weight - usually a font weight of 400 is regarded as regular
|
||||||
final int fw = font.getWeight();
|
final int fw = font.getWeight();
|
||||||
|
@ -595,7 +597,10 @@ public class HwmfGraphics implements HwmfCharsetAware {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
attributes.accept(TextAttribute.WEIGHT, awtFW);
|
att.put(TextAttribute.WEIGHT, awtFW);
|
||||||
|
att.put(TextAttribute.FONT, new Font(att));
|
||||||
|
|
||||||
|
att.forEach(attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double getFontHeight(HwmfFont font) {
|
private double getFontHeight(HwmfFont font) {
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
|
||||||
|
log4j.configuration=log4j.properties
|
|
@ -18,4 +18,6 @@ log4j.rootLogger=ALL,CONSOLE
|
||||||
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
|
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
|
||||||
log4j.appender.CONSOLE.target=System.out
|
log4j.appender.CONSOLE.target=System.out
|
||||||
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
|
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
|
||||||
log4j.appender.CONSOLE.layout.ConversionPattern=%d{dd.MM HH:mm:ss} %-30.30c %5p %m%n
|
log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %c %5p %m%n
|
||||||
|
|
||||||
|
log4j.logger.org.apache.fontbox.ttf=INFO
|
||||||
|
|
Loading…
Reference in New Issue