blob: a7fca245fe508e27a900fe16e37e53aeda7d21db [file] [log] [blame]
/*
* 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.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fontbox.FontBoxFont;
import org.apache.fontbox.cff.CFFCIDFont;
import org.apache.fontbox.cff.CFFFont;
import org.apache.fontbox.cff.CFFParser;
import org.apache.fontbox.cff.CFFType1Font;
import org.apache.fontbox.cff.Type2CharString;
import org.apache.fontbox.util.BoundingBox;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.util.Matrix;
import static org.apache.pdfbox.pdmodel.font.UniUtil.getUniNameOfCodePoint;
/**
* Type 0 CIDFont (CFF).
*
* @author Ben Litchfield
* @author John Hewson
*/
public class PDCIDFontType0 extends PDCIDFont
{
private static final Log LOG = LogFactory.getLog(PDCIDFontType0.class);
private final CFFCIDFont cidFont; // Top DICT that uses CIDFont operators
private final FontBoxFont t1Font; // Top DICT that does not use CIDFont operators
private final Map<Integer, Float> glyphHeights = new HashMap<>();
private final boolean isEmbedded;
private final boolean isDamaged;
private final AffineTransform fontMatrixTransform;
private Float avgWidth = null;
private Matrix fontMatrix;
private BoundingBox fontBBox;
private int[] cid2gid = null;
/**
* Constructor.
*
* @param fontDictionary The font dictionary according to the PDF specification.
* @param parent The parent font.
*/
public PDCIDFontType0(COSDictionary fontDictionary, PDType0Font parent) throws IOException
{
super(fontDictionary, parent);
PDFontDescriptor fd = getFontDescriptor();
byte[] bytes = null;
if (fd != null)
{
PDStream ff3Stream = fd.getFontFile3();
if (ff3Stream != null)
{
bytes = IOUtils.toByteArray(ff3Stream.createInputStream());
}
}
boolean fontIsDamaged = false;
CFFFont cffFont = null;
if (bytes != null && bytes.length > 0 && (bytes[0] & 0xff) == '%')
{
// PDFBOX-2642 contains a corrupt PFB font instead of a CFF
LOG.warn("Found PFB but expected embedded CFF font " + fd.getFontName());
fontIsDamaged = true;
}
else if (bytes != null)
{
CFFParser cffParser = new CFFParser();
try
{
cffFont = cffParser.parse(bytes, new FF3ByteSource()).get(0);
}
catch (IOException e)
{
LOG.error("Can't read the embedded CFF font " + fd.getFontName(), e);
fontIsDamaged = true;
}
}
if (cffFont != null)
{
// embedded
if (cffFont instanceof CFFCIDFont)
{
cidFont = (CFFCIDFont)cffFont;
t1Font = null;
}
else
{
cidFont = null;
t1Font = cffFont;
}
cid2gid = readCIDToGIDMap();
isEmbedded = true;
isDamaged = false;
}
else
{
// find font or substitute
CIDFontMapping mapping = FontMappers.instance()
.getCIDFont(getBaseFont(), getFontDescriptor(),
getCIDSystemInfo());
FontBoxFont font;
if (mapping.isCIDFont())
{
cffFont = mapping.getFont().getCFF().getFont();
if (cffFont instanceof CFFCIDFont)
{
cidFont = (CFFCIDFont) cffFont;
t1Font = null;
font = cidFont;
}
else
{
// PDFBOX-3515: OpenType fonts are loaded as CFFType1Font
CFFType1Font f = (CFFType1Font) cffFont;
cidFont = null;
t1Font = f;
font = f;
}
}
else
{
cidFont = null;
t1Font = mapping.getTrueTypeFont();
font = t1Font;
}
if (mapping.isFallback())
{
LOG.warn("Using fallback " + font.getName() + " for CID-keyed font " +
getBaseFont());
}
isEmbedded = false;
isDamaged = fontIsDamaged;
}
fontMatrixTransform = getFontMatrix().createAffineTransform();
fontMatrixTransform.scale(1000, 1000);
}
@Override
public final Matrix getFontMatrix()
{
if (fontMatrix == null)
{
List<Number> numbers;
if (cidFont != null)
{
numbers = cidFont.getFontMatrix();
}
else
{
try
{
numbers = t1Font.getFontMatrix();
}
catch (IOException e)
{
LOG.debug("Couldn't get font matrix - returning default value", e);
return new Matrix(0.001f, 0, 0, 0.001f, 0, 0);
}
}
if (numbers != null && numbers.size() == 6)
{
fontMatrix = new Matrix(numbers.get(0).floatValue(), numbers.get(1).floatValue(),
numbers.get(2).floatValue(), numbers.get(3).floatValue(),
numbers.get(4).floatValue(), numbers.get(5).floatValue());
}
else
{
fontMatrix = new Matrix(0.001f, 0, 0, 0.001f, 0, 0);
}
}
return fontMatrix;
}
@Override
public BoundingBox getBoundingBox()
{
if (fontBBox == null)
{
fontBBox = generateBoundingBox();
}
return fontBBox;
}
private BoundingBox generateBoundingBox()
{
if (getFontDescriptor() != null) {
PDRectangle bbox = getFontDescriptor().getFontBoundingBox();
if (bbox != null && (Float.compare(bbox.getLowerLeftX(),0) != 0 ||
Float.compare(bbox.getLowerLeftY(),0) != 0 ||
Float.compare(bbox.getUpperRightX(),0) != 0 ||
Float.compare(bbox.getUpperRightY(),0) != 0))
{
return new BoundingBox(bbox.getLowerLeftX(), bbox.getLowerLeftY(),
bbox.getUpperRightX(), bbox.getUpperRightY());
}
}
if (cidFont != null)
{
return cidFont.getFontBBox();
}
else
{
try
{
return t1Font.getFontBBox();
}
catch (IOException e)
{
LOG.debug("Couldn't get font bounding box - returning default value", e);
return new BoundingBox();
}
}
}
/**
* Returns the embedded CFF CIDFont, or null if the substitute is not a CFF font.
*/
public CFFFont getCFFFont()
{
if (cidFont != null)
{
return cidFont;
}
else if (t1Font instanceof CFFType1Font)
{
return (CFFType1Font)t1Font;
}
else
{
return null;
}
}
/**
* Returns the embedded or substituted font.
*/
public FontBoxFont getFontBoxFont()
{
if (cidFont != null)
{
return cidFont;
}
else
{
return t1Font;
}
}
/**
* Returns the Type 2 charstring for the given CID, or null if the substituted font does not
* contain Type 2 charstrings.
*
* @param cid CID
* @throws IOException if the charstring could not be read
*/
public Type2CharString getType2CharString(int cid) throws IOException
{
if (cidFont != null)
{
return cidFont.getType2CharString(cid);
}
else if (t1Font instanceof CFFType1Font)
{
return ((CFFType1Font)t1Font).getType2CharString(cid);
}
else
{
return null;
}
}
/**
* Returns the name of the glyph with the given character code. This is done by looking up the
* code in the parent font's ToUnicode map and generating a glyph name from that.
*/
private String getGlyphName(int code) throws IOException
{
String unicodes = parent.toUnicode(code);
if (unicodes == null)
{
return ".notdef";
}
return getUniNameOfCodePoint(unicodes.codePointAt(0));
}
@Override
public GeneralPath getPath(int code) throws IOException
{
int cid = codeToCID(code);
if (cid2gid != null && isEmbedded)
{
// PDFBOX-4093: despite being a type 0 font, there is a CIDToGIDMap
cid = cid2gid[cid];
}
Type2CharString charstring = getType2CharString(cid);
if (charstring != null)
{
return charstring.getPath();
}
else if (isEmbedded && t1Font instanceof CFFType1Font)
{
return ((CFFType1Font)t1Font).getType2CharString(cid).getPath();
}
else
{
return t1Font.getPath(getGlyphName(code));
}
}
@Override
public GeneralPath getNormalizedPath(int code) throws IOException
{
return getPath(code);
}
@Override
public boolean hasGlyph(int code) throws IOException
{
int cid = codeToCID(code);
Type2CharString charstring = getType2CharString(cid);
if (charstring != null)
{
return charstring.getGID() != 0;
}
else if (isEmbedded && t1Font instanceof CFFType1Font)
{
return ((CFFType1Font)t1Font).getType2CharString(cid).getGID() != 0;
}
else
{
return t1Font.hasGlyph(getGlyphName(code));
}
}
/**
* Returns the CID for the given character code. If not found then CID 0 is returned.
*
* @param code character code
* @return CID
*/
@Override
public int codeToCID(int code)
{
return parent.getCMap().toCID(code);
}
@Override
public int codeToGID(int code)
{
int cid = codeToCID(code);
if (cidFont != null)
{
// The CIDs shall be used to determine the GID value for the glyph procedure using the
// charset table in the CFF program
return cidFont.getCharset().getGIDForCID(cid);
}
else
{
// The CIDs shall be used directly as GID values
return cid;
}
}
@Override
public byte[] encode(int unicode)
{
// todo: we can use a known character collection CMap for a CIDFont
// and an Encoding for Type 1-equivalent
throw new UnsupportedOperationException();
}
@Override
public byte[] encodeGlyphId(int glyphId)
{
throw new UnsupportedOperationException();
}
@Override
public float getWidthFromFont(int code) throws IOException
{
int cid = codeToCID(code);
float width;
if (cidFont != null)
{
width = getType2CharString(cid).getWidth();
}
else if (isEmbedded && t1Font instanceof CFFType1Font)
{
width = ((CFFType1Font)t1Font).getType2CharString(cid).getWidth();
}
else
{
width = t1Font.getWidth(getGlyphName(code));
}
Point2D p = new Point2D.Float(width, 0);
fontMatrixTransform.transform(p, p);
return (float)p.getX();
}
@Override
public boolean isEmbedded()
{
return isEmbedded;
}
@Override
public boolean isDamaged()
{
return isDamaged;
}
@Override
public float getHeight(int code) throws IOException
{
int cid = codeToCID(code);
float height;
if (!glyphHeights.containsKey(cid))
{
height = (float) getType2CharString(cid).getBounds().getHeight();
glyphHeights.put(cid, height);
}
else
{
height = glyphHeights.get(cid);
}
return height;
}
@Override
public float getAverageFontWidth()
{
if (avgWidth == null)
{
avgWidth = getAverageCharacterWidth();
}
return avgWidth;
}
// todo: this is a replacement for FontMetrics method
private float getAverageCharacterWidth()
{
// todo: not implemented, highly suspect
return 500;
}
private class FF3ByteSource implements CFFParser.ByteSource
{
@Override
public byte[] getBytes() throws IOException
{
return getFontDescriptor().getFontFile3().toByteArray();
}
}
}