PDType3Font.java
/*
* 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.pdfbox.pdmodel.font;
import java.awt.geom.GeneralPath;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Objects;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fontbox.FontBoxFont;
import org.apache.fontbox.util.BoundingBox;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSStream;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.ResourceCache;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.encoding.DictionaryEncoding;
import org.apache.pdfbox.pdmodel.font.encoding.Encoding;
import org.apache.pdfbox.pdmodel.font.encoding.GlyphList;
import org.apache.pdfbox.util.Matrix;
import org.apache.pdfbox.util.Vector;
/**
* A PostScript Type 3 Font.
*
* @author Ben Litchfield
*/
public class PDType3Font extends PDSimpleFont
{
/**
* Log instance.
*/
private static final Log LOG = LogFactory.getLog(PDType3Font.class);
private PDResources resources;
private COSDictionary charProcs;
private Matrix fontMatrix;
private BoundingBox fontBBox;
private final ResourceCache resourceCache;
/**
* Constructor.
*
* @param fontDictionary The font dictionary according to the PDF specification.
*/
public PDType3Font(COSDictionary fontDictionary) throws IOException
{
this(fontDictionary, null);
}
/**
* Constructor.
*
* @param fontDictionary The font dictionary according to the PDF specification.
* @param resourceCache Resource cache, can be null.
*/
public PDType3Font(COSDictionary fontDictionary, ResourceCache resourceCache) throws IOException
{
super(fontDictionary);
this.resourceCache = resourceCache;
readEncoding();
}
@Override
public String getName()
{
return dict.getNameAsString(COSName.NAME);
}
@Override
protected final void readEncoding() throws IOException
{
COSBase encodingBase = dict.getDictionaryObject(COSName.ENCODING);
if (encodingBase instanceof COSName)
{
COSName encodingName = (COSName) encodingBase;
encoding = Encoding.getInstance(encodingName);
if (encoding == null)
{
LOG.warn("Unknown encoding: " + encodingName.getName());
}
}
else if (encodingBase instanceof COSDictionary)
{
encoding = new DictionaryEncoding((COSDictionary) encodingBase);
}
glyphList = GlyphList.getAdobeGlyphList();
}
@Override
protected Encoding readEncodingFromFont() throws IOException
{
// Type 3 fonts do not have a built-in encoding
throw new UnsupportedOperationException("not supported for Type 3 fonts");
}
@Override
protected Boolean isFontSymbolic()
{
return false;
}
@Override
public GeneralPath getPath(String name) throws IOException
{
// Type 3 fonts do not use vector paths
throw new UnsupportedOperationException("not supported for Type 3 fonts");
}
@Override
public boolean hasGlyph(String name) throws IOException
{
return getCharProcs() == null ? false
: getCharProcs().getCOSStream(COSName.getPDFName(name)) != null;
}
@Override
public FontBoxFont getFontBoxFont()
{
// Type 3 fonts do not use FontBox fonts
throw new UnsupportedOperationException("not supported for Type 3 fonts");
}
@Override
public Vector getDisplacement(int code) throws IOException
{
return getFontMatrix().transform(new Vector(getWidth(code), 0));
}
@Override
public float getWidth(int code) throws IOException
{
int firstChar = dict.getInt(COSName.FIRST_CHAR, -1);
int lastChar = dict.getInt(COSName.LAST_CHAR, -1);
List<Float> widths = getWidths();
if (!widths.isEmpty() && code >= firstChar && code <= lastChar)
{
if (code - firstChar >= widths.size())
{
return 0;
}
Float w = widths.get(code - firstChar);
return w == null ? 0 : w;
}
else
{
PDFontDescriptor fd = getFontDescriptor();
if (fd != null)
{
return fd.getMissingWidth();
}
else
{
return getWidthFromFont(code);
}
}
}
@Override
public float getWidthFromFont(int code) throws IOException
{
PDType3CharProc charProc = getCharProc(code);
if (charProc == null || charProc.getCOSObject().getLength() == 0)
{
return 0;
}
return charProc.getWidth();
}
@Override
public boolean isEmbedded()
{
return true;
}
@Override
public float getHeight(int code) throws IOException
{
PDFontDescriptor desc = getFontDescriptor();
if (desc != null)
{
// the following values are all more or less accurate at least all are average
// values. Maybe we'll find another way to get those value for every single glyph
// in the future if needed
PDRectangle bbox = desc.getFontBoundingBox();
float retval = 0;
if (bbox != null)
{
retval = bbox.getHeight() / 2;
}
if (Float.compare(retval, 0) == 0)
{
retval = desc.getCapHeight();
}
if (Float.compare(retval, 0) == 0)
{
retval = desc.getAscent();
}
if (Float.compare(retval, 0) == 0)
{
retval = desc.getXHeight();
if (retval > 0)
{
retval -= desc.getDescent();
}
}
return retval;
}
return 0;
}
@Override
protected byte[] encode(int unicode) throws IOException
{
throw new UnsupportedOperationException("Not implemented: Type3");
}
@Override
public int readCode(InputStream in) throws IOException
{
return in.read();
}
@Override
public Matrix getFontMatrix()
{
if (fontMatrix == null)
{
COSArray matrix = dict.getCOSArray(COSName.FONT_MATRIX);
fontMatrix = checkFontMatrixValues(matrix) ? Matrix.createMatrix(matrix)
: super.getFontMatrix();
}
return fontMatrix;
}
private boolean checkFontMatrixValues(COSArray matrix)
{
return matrix != null && matrix.size() == 6
&& matrix.toCOSNumberFloatList().stream().allMatch(Objects::nonNull);
}
@Override
public boolean isDamaged()
{
// there's no font file to load
return false;
}
@Override
public boolean isStandard14()
{
return false;
}
/**
* Returns the optional resources of the type3 stream.
*
* @return the resources bound to be used when parsing the type3 stream
*/
public PDResources getResources()
{
if (resources == null)
{
COSDictionary resDict = dict.getCOSDictionary(COSName.RESOURCES);
if (resDict != null)
{
resources = new PDResources(resDict, resourceCache);
}
}
return resources;
}
/**
* This will get the fonts bounding box from its dictionary.
*
* @return The fonts bounding box.
*/
public PDRectangle getFontBBox()
{
COSArray bBox = dict.getCOSArray(COSName.FONT_BBOX);
return bBox != null ? new PDRectangle(bBox) : null;
}
@Override
public BoundingBox getBoundingBox()
{
if (fontBBox == null)
{
fontBBox = generateBoundingBox();
}
return fontBBox;
}
private BoundingBox generateBoundingBox()
{
PDRectangle rect = getFontBBox();
if (rect == null)
{
LOG.warn("FontBBox missing, returning empty rectangle");
return new BoundingBox();
}
if (!isNonZeroBoundingBox(rect))
{
// Plan B: get the max bounding box of the glyphs
COSDictionary cp = getCharProcs();
if (cp != null)
{
for (COSName name : cp.keySet())
{
COSStream typ3CharProcStream = cp.getCOSStream(name);
if (typ3CharProcStream != null)
{
PDType3CharProc charProc = new PDType3CharProc(this, typ3CharProcStream);
try
{
PDRectangle glyphBBox = charProc.getGlyphBBox();
if (glyphBBox == null)
{
continue;
}
rect.setLowerLeftX(
Math.min(rect.getLowerLeftX(), glyphBBox.getLowerLeftX()));
rect.setLowerLeftY(
Math.min(rect.getLowerLeftY(), glyphBBox.getLowerLeftY()));
rect.setUpperRightX(
Math.max(rect.getUpperRightX(), glyphBBox.getUpperRightX()));
rect.setUpperRightY(
Math.max(rect.getUpperRightY(), glyphBBox.getUpperRightY()));
}
catch (IOException ex)
{
// ignore
LOG.debug(
"error getting the glyph bounding box - font bounding box will be used",
ex);
}
}
}
}
}
return new BoundingBox(rect.getLowerLeftX(), rect.getLowerLeftY(),
rect.getUpperRightX(), rect.getUpperRightY());
}
/**
* Returns the dictionary containing all streams to be used to render the glyphs.
*
* @return the dictionary containing all glyph streams.
*/
public COSDictionary getCharProcs()
{
if (charProcs == null)
{
charProcs = dict.getCOSDictionary(COSName.CHAR_PROCS);
}
return charProcs;
}
/**
* Returns the stream of the glyph for the given character code
*
* @param code character code
* @return the stream to be used to render the glyph
*/
public PDType3CharProc getCharProc(int code)
{
if (getEncoding() == null || getCharProcs() == null)
{
return null;
}
String name = getEncoding().getName(code);
COSStream stream = getCharProcs().getCOSStream(COSName.getPDFName(name));
return stream != null ? new PDType3CharProc(this, stream) : null;
}
}