de.intarsys.pdf.font.PDFontTools Maven / Gradle / Ivy
/*
* Copyright (c) 2007, intarsys consulting GmbH
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of intarsys nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package de.intarsys.pdf.font;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import de.intarsys.pdf.cds.CDSRectangle;
import de.intarsys.pdf.content.CSContent;
import de.intarsys.pdf.content.CSError;
import de.intarsys.pdf.content.CSException;
import de.intarsys.pdf.content.CSInterpreter;
import de.intarsys.pdf.content.CSOperation;
import de.intarsys.pdf.content.CSWarning;
import de.intarsys.pdf.cos.COSDictionary;
import de.intarsys.pdf.cos.COSInteger;
import de.intarsys.pdf.cos.COSName;
import de.intarsys.pdf.cos.COSObject;
import de.intarsys.pdf.cos.COSObjectWalkerDeep;
import de.intarsys.pdf.cos.COSStream;
import de.intarsys.pdf.cos.COSTools;
import de.intarsys.pdf.cos.COSVisitorException;
import de.intarsys.pdf.cos.ICOSObjectVisitor;
import de.intarsys.pdf.encoding.Encoding;
import de.intarsys.pdf.encoding.WinAnsiEncoding;
import de.intarsys.pdf.font.outlet.FontFactoryException;
import de.intarsys.pdf.font.outlet.FontOutlet;
import de.intarsys.pdf.font.outlet.FontQuery;
import de.intarsys.pdf.font.outlet.IFontFactory;
import de.intarsys.pdf.font.outlet.IFontOutlet;
import de.intarsys.pdf.font.outlet.IFontQuery;
import de.intarsys.pdf.pd.PDDocument;
import de.intarsys.pdf.pd.PDForm;
import de.intarsys.pdf.pd.PDObject;
import de.intarsys.pdf.pd.PDPage;
import de.intarsys.pdf.pd.PDPattern;
import de.intarsys.pdf.pd.PDResources;
import de.intarsys.tools.attribute.Attribute;
/**
* Tool class for handling PDF fonts.
*
* For more sophisticated help, see {@link IFontOutlet}.
*
*/
public class PDFontTools {
private static final Attribute ATTR_CIDFONT = new Attribute("cidfont");
private static final Attribute ATTR_CREATEDFONTS = new Attribute(
"createdfonts");
private static final Attribute ATTR_TYPE0FONT = new Attribute("type0font");
static protected void collectFonts(Set fonts, CSContent content,
PDResources resources, final boolean considerTR) {
final Set fontNames = new HashSet();
CSInterpreter collector = new CSInterpreter(new HashMap()) {
private COSName fontName;
private int renderingMode = 0;
@Override
protected void handleError(CSError error) throws CSException {
//
}
protected void handleText() {
if (renderingMode != 3 || !considerTR) {
fontNames.add(fontName);
}
}
@Override
protected void handleWarning(CSWarning warning) throws CSException {
//
}
@Override
protected void notSupported(CSOperation operation)
throws CSException {
//
}
@Override
protected void render_DoubleQuote(CSOperation operation)
throws CSException {
handleText();
}
@Override
protected void render_Quote(CSOperation operation)
throws CSException {
handleText();
}
@Override
protected void render_Tf(CSOperation operation) throws CSException {
if (operation.operandSize() == 2) {
fontName = operation.getOperand(0).asName();
}
}
@Override
protected void render_Tj(CSOperation operation) throws CSException {
handleText();
}
@Override
protected void render_TJ(CSOperation operation) throws CSException {
handleText();
}
@Override
protected void render_Tr(CSOperation operation) throws CSException {
COSInteger mode = (COSInteger) operation.getOperand(0);
renderingMode = mode.intValue();
}
};
collector.process(content, resources);
for (Iterator it = fontNames.iterator(); it.hasNext();) {
COSName fontName = (COSName) it.next();
fonts.add(resources.getFontResource(fontName));
}
}
/**
* Create a well known Type1 font.
*
* @param name
* The font name.
*
* @return The font we found or a new instance created on the fly.
*/
public static PDFont createBuiltinFont(String name) {
PDFont font = PDFontType1.createNew(name);
if (font.getFontDescriptor().isNonsymbolic()) {
font.setEncoding(WinAnsiEncoding.UNIQUE);
}
return font;
}
protected static CIDFont createCIDFont(PDFont font) {
if (font instanceof PDFontTrueType) {
CIDFontType2 result = (CIDFontType2) CIDFontType2.META.createNew();
result.cosSetField(PDFont.DK_BaseFont, font.getBaseFont()
.copyShallow());
CIDSystemInfo info = (CIDSystemInfo) CIDSystemInfo.META.createNew();
int width = font.getMissingWidth();
if (width == 0) {
width = 1000;
}
result.setDefaultGlyphWidth(width);
result.setCIDSystemInfo(info);
// PDFontDescriptorEmbedded descriptor =
// (PDFontDescriptorEmbedded)
// PDFontDescriptorEmbedded.META
// .createNew();
result.setFontDescriptor(font.getFontDescriptor());
return result;
} else if (font instanceof PDFontType1) {
//
} else {
//
}
return null;
}
protected static PDFont createType0Font(PDFont font) {
PDFont result;
if (font instanceof PDFontType0) {
result = font;
} else {
CIDFont cidFont = getCIDFont(font);
if (cidFont == null) {
result = font;
} else {
PDFontType0 type0Font = (PDFontType0) PDFontType0.META
.createNew();
type0Font.cosSetField(PDFont.DK_BaseFont, font.getBaseFont()
.copyShallow());
// /Identity-H encoding is set by default
type0Font.setDescendantFont(cidFont);
result = type0Font;
}
}
return result;
}
static protected CIDFont getCIDFont(PDFont font) {
CIDFont cidFont = (CIDFont) font.getAttribute(ATTR_CIDFONT);
if (cidFont == null) {
cidFont = createCIDFont(font);
font.setAttribute(ATTR_CIDFONT, cidFont);
}
return cidFont;
}
/**
* The font name
, looked up in resources
.
*
* When no matching resource is found, a builtin font is created on the fly.
*
* @param document
* @param resources
* @param name
* @return The font name
, looked up in resources
.
*/
public static PDFont getFont(PDDocument document, PDResources resources,
COSName name) {
if (resources != null) {
PDFont font = resources.getFontResource(name);
if (font != null) {
return font;
}
}
if (name != null) {
// bad PDF but try to be nice; treat as builtin font anyway
return lookupFont(document, name.stringValue());
}
return null;
}
/**
* The font name
, looked up in resources
.
*
* @param resources
* @param name
* @return The font name
, looked up in resources
.
*/
public static PDFont getFont(PDResources resources, COSName name) {
return getFont(resources.getDoc(), resources, name);
}
/**
* Determine the fonts contained as objects within the document.
*
* @param doc
* The PDDocument to parse
* @return Collection of all {@link PDFont} objects in the document.
*/
public static List getFonts(PDDocument doc) {
final List result = new ArrayList();
try {
COSDictionary trailer = doc.cosGetDoc().stGetDoc().cosGetTrailer();
ICOSObjectVisitor visitor = new COSObjectWalkerDeep() {
@Override
protected void handleException(RuntimeException e)
throws COSVisitorException {
// ignore swapping exceptions
}
@Override
public Object visitFromDictionary(COSDictionary dict)
throws COSVisitorException {
try {
PDFont font = (PDFont) PDFont.META.createFromCos(dict);
if (font != null) {
result.add(font);
return null;
}
} catch (Exception e) {
//
}
return super.visitFromDictionary(dict);
}
};
trailer.accept(visitor);
} catch (Exception e) {
// ignore any exception (especially COSSwapException)
}
return result;
}
/**
* The font height in user space coordinates.
*
* @param font
* The font to be used.
* @return The scaled font height in user space coordinates.
*/
public static float getGlyphHeight(PDFont font) {
CDSRectangle rect = font.getFontDescriptor().getFontBB();
float scaledHeight = rect.getUpperRightY() - rect.getLowerLeftY();
scaledHeight = scaledHeight / 1000f;
return scaledHeight;
}
/**
* The scaled font height in user space coordinates.
*
* @param font
* The font to be used.
* @param size
* The font size
* @return The scaled font height in user space coordinates.
*/
public static float getGlyphHeightScaled(PDFont font, float size) {
CDSRectangle rect = font.getFontDescriptor().getFontBB();
float scaledHeight = rect.getUpperRightY() - rect.getLowerLeftY();
scaledHeight = (size * scaledHeight) / 1000f;
return scaledHeight;
}
/**
* The sum of the length of all glyphs referenced by length
* bytes from codepoints
starting at offset
.
*
* @param font
* @param codepoints
* @param offset
* @param length
* @return The sum of the length of all glyphs referenced by
* length
bytes from codepoints
starting
* at offset
.
*/
public static int getGlyphWidthEncoded(PDFont font, byte[] codepoints,
int offset, int length) {
InputStream is = new ByteArrayInputStream(codepoints, offset, length);
Encoding encoding = font.getEncoding();
int result = 0;
try {
int codepoint = encoding.getNextEncoded(is);
while (codepoint != -1) {
result = result + font.getGlyphWidthEncoded(codepoint);
codepoint = encoding.getNextEncoded(is);
}
} catch (IOException e) {
// no io exception with byte arrays
}
return result;
}
/**
* The scaled sum of the length of all glyphs referenced by
* length
bytes from codepoints
starting at
* offset
.
*
* @param font
* @param size
* @param codepoints
* @param offset
* @param length
* @return The scaled sum of the length of all glyphs referenced by
* length
bytes from codepoints
starting
* at offset
.
*/
public static float getGlyphWidthEncodedScaled(PDFont font, float size,
byte[] codepoints, int offset, int length) {
float width = getGlyphWidthEncoded(font, codepoints, offset, length);
return (size * width) / 1000f;
}
/**
* "Scale up" font
to a multibyte font.
*
* @param font
* @return The new font
*/
static public PDFont getType0Font(PDFont font) {
PDFont result = (PDFont) font.getAttribute(ATTR_TYPE0FONT);
if (result == null) {
result = createType0Font(font);
font.setAttribute(ATTR_TYPE0FONT, result);
}
return result;
}
/**
* Tries to determine which fonts are really used within the document
*
* The following criteria are used to determine usage of a font.
*
* Any glyph of the font is referenced in
* - the Contents stream of a page object
* - the stream of a Form XObject
* - the appearance stream of an annotation, including form fields
* - the content stream of a Type 3 font glyph
* - the stream of a tiling pattern
*
*
* @param doc
* The PDDocument to parse
* @param considerTR
* If true, considers font references with Text rendering mode 3
* as unused
* @return Set of all used fonts
*/
public static List getUsedFonts(PDDocument doc, boolean considerTR) {
Set fonts = new HashSet();
// get the fonts from the page objects / contentstream
PDPage page = doc.getPageTree().getFirstPage();
while (true) {
if (page == null) {
break;
}
CSContent contentstream = page.getContentStream();
if (contentstream != null) {
collectFonts(fonts, contentstream, page.getResources(),
considerTR);
}
page = page.getNextPage();
}
// get the fonts from all XObjects, also from tiling patterns
for (Iterator it = doc.cosGetDoc().objects(); it.hasNext();) {
try {
COSObject object = (COSObject) it.next();
COSDictionary dict = COSTools.toDictionary(object);
if (dict == null) {
continue;
}
// Form XObjects
if (dict.get(PDObject.DK_Subtype)
.equals(PDForm.CN_Subtype_Form)) {
PDForm form = (PDForm) PDForm.META.createFromCos(object);
if (form != null) {
collectFonts(fonts, form.getContentStream(),
form.getResources(), considerTR);
}
}
// Pattern tiling
if (dict.get(PDPattern.DK_PatternType).equals(
COSInteger.create(PDPattern.PATTERN_TYPE_TILING))) {
CSContent patternCS = CSContent
.createFromCos((COSStream) object);
COSDictionary r = dict.get(PDForm.DK_Resources)
.asDictionary();
PDResources resources = (PDResources) PDResources.META
.createFromCos(r);
collectFonts(fonts, patternCS, resources, considerTR);
}
} catch (RuntimeException e) {
// ignore exception from diving into objects, go on...
}
}
return new ArrayList(fonts);
}
/**
* Lookup a font anywhere in the document. If the font is not found, we
* assume a well known Type1 font was requested.
*
* This method reads and scans the whole document - so this may be to slow
* for some scenarios. In this case you should simply create a new one.
*
* @param document
* The document where we search.
* @param name
* The font name.
* @return The font we found or a new instance created on the fly.
*/
protected static PDFont lookupFont(PDDocument document, String name) {
PDFont font = null;
IFontFactory factory = FontOutlet.get().lookupFontFactory(document);
IFontQuery query = new FontQuery(name);
try {
font = factory.getFont(query);
} catch (FontFactoryException e) {
//
}
if (font == null) {
font = createBuiltinFont(name);
factory.registerFont(font);
}
return font;
}
}