blob: db8ba37cc7af0cdf60c2243636715179b3131935 [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.fop.render.pdf.pdfbox;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import org.apache.commons.io.IOUtils;
import org.apache.fontbox.cff.CFFCharset;
import org.apache.fontbox.cff.CFFFont;
import org.apache.fontbox.cff.CFFStandardString;
import org.apache.fontbox.cmap.CMap;
import org.apache.fontbox.ttf.CmapSubtable;
import org.apache.fontbox.ttf.GlyphData;
import org.apache.fontbox.ttf.TrueTypeFont;
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.COSObject;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.pdmodel.font.PDCIDFont;
import org.apache.pdfbox.pdmodel.font.PDCIDFontType0;
import org.apache.pdfbox.pdmodel.font.PDCIDFontType2;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDFontDescriptor;
import org.apache.pdfbox.pdmodel.font.PDTrueTypeFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.font.encoding.GlyphList;
import org.apache.fop.fonts.CIDFontType;
import org.apache.fop.fonts.CustomFont;
import org.apache.fop.fonts.EmbeddingMode;
import org.apache.fop.fonts.FontType;
import org.apache.fop.fonts.MultiByteFont;
import org.apache.fop.pdf.PDFDictionary;
import org.apache.fop.util.CharUtilities;
public class FOPPDFMultiByteFont extends MultiByteFont implements FOPPDFFont {
protected PDFDictionary ref;
private Map<Integer, Integer> newWidth = new TreeMap<Integer, Integer>();
private Map<String, Integer> charMapGlobal = new LinkedHashMap<String, Integer>();
private MergeFonts mergeFonts;
//private Map<String, GlyphData> glyphs = new HashMap<String, GlyphData>();
private final Map<COSDictionary, FontContainer> fontMap = new HashMap<COSDictionary, FontContainer>();
public FOPPDFMultiByteFont(COSDictionary fontData, String name) throws IOException {
super(null, EmbeddingMode.SUBSET);
//this stops fop modifying font later on
setEmbeddingMode(EmbeddingMode.FULL);
readFontBBox(fontData);
setFontName(name);
addFont(fontData);
notifyMapOperation();
}
public String addFont(COSDictionary fontData) throws IOException {
FontContainer font = getFont(fontData);
setProperties(this, font.font);
PDCIDFont mainFont = null;
TrueTypeFont ttf = null;
if (font.font instanceof PDType0Font) {
PDCIDFont cidFont = ((PDType0Font) font.font).getDescendantFont();
int dw = cidFont.getCOSObject().getInt(COSName.DW);
setDefaultWidth(dw);
mainFont = cidFont;
if (cidFont instanceof PDCIDFontType0) {
setCIDType(CIDFontType.CIDTYPE0);
setFontType(FontType.CIDTYPE0);
} else {
ttf = ((PDCIDFontType2) cidFont).getTrueTypeFont();
}
} else {
ttf = ((PDTrueTypeFont) font.font).getTrueTypeFont();
setDefaultWidth(1000);
}
GlyphData[] glyphData = new GlyphData[0];
if (ttf != null) {
glyphData = ttf.getGlyph().getGlyphs();
}
Map<Integer, Integer> oldToNewGIMap = new HashMap<Integer, Integer>();
if (charMapGlobal.isEmpty()) {
oldToNewGIMap.put(0, 0); // .notdef glyph
}
CMap c = font.getToUnicodeCMap();
Map<Integer, String> mapping = getMapping(font, c, glyphData.length);
if (glyphData.length > 0) {
differentGlyphData(glyphData, mapping);
}
Map<Integer, String> gidToGlyph = new TreeMap<Integer, String>(mapping);
if (font.font instanceof PDTrueTypeFont) {
CmapSubtable cmap = ttf.getCmap().getCmaps()[0];
gidToGlyph.clear();
for (int i = 1; i < glyphData.length; i++) {
String mappedChar = mapping.get(cmap.getCharacterCode(i));
gidToGlyph.put(i, mappedChar);
}
}
readCharMap(font, gidToGlyph, glyphData, mainFont, oldToNewGIMap);
InputStream ffr = readFontFile(font.font);
if (mergeFonts == null) {
if (ttf != null) {
mergeFonts = new MergeTTFonts(null);
} else {
mergeFonts = new MergeCFFFonts();
}
}
if (mergeFonts instanceof MergeTTFonts) {
mergeMaxp(ttf, ((MergeTTFonts)mergeFonts).maxp);
int sizeNoCompGlyphs = oldToNewGIMap.size();
mergeFonts.readFont(ffr, null, null, oldToNewGIMap, true);
if (oldToNewGIMap.size() > sizeNoCompGlyphs) {
cidSet.mapChar(256 * 256, (char) 0);
}
} else {
mergeFonts.readFont(ffr, getEmbedFontName(), null, null, true);
}
return getFontName();
}
private void readCharMap(FontContainer font, Map<Integer, String> gidToGlyph, GlyphData[] glyphData,
PDCIDFont mainFont, Map<Integer, Integer> oldToNewGIMap) throws IOException {
int widthPos = font.getFirstChar() + 1;
for (Map.Entry<Integer, String> i : gidToGlyph.entrySet()) {
String mappedChar = i.getValue();
int key = i.getKey();
boolean skipWidth = (mappedChar == null) || mappedChar.length() == 0;
if (skipWidth) {
mappedChar = (char)charMapGlobal.size() + "tmp";
} else if (mappedChar.length() > 1) {
mappedChar = "" + (char)mappedChar.hashCode();
}
if (!charMapGlobal.containsKey(mappedChar)) {
if (glyphData.length > 0
&& glyphData[key] == null
&& !CharUtilities.isAdjustableSpace(mappedChar.charAt(0))) {
continue;
}
boolean addToEnd = charMapGlobal.containsValue(key);
if (addToEnd) {
addPrivateUseMapping(mappedChar.charAt(0), charMapGlobal.size() + 1);
charMapGlobal.put(mappedChar, charMapGlobal.size() + 1);
} else {
addPrivateUseMapping(mappedChar.charAt(0), key);
charMapGlobal.put(mappedChar, key);
}
int glyph = 0;
if (hasChar(mappedChar.charAt(0))) {
glyph = (int) mapChar(mappedChar.charAt(0));
}
oldToNewGIMap.put(key, glyph);
if (!skipWidth) {
if (!(font.font instanceof PDTrueTypeFont)) {
widthPos = key;
}
float w = font.font.getWidth(widthPos);
if (w >= 0) {
if (mainFont instanceof PDCIDFontType0) {
newWidth.put(key, (int)w);
} else {
newWidth.put(glyph, (int)w);
}
}
}
}
if (!skipWidth) {
widthPos++;
}
}
}
private Map<Integer, String> getMapping(FontContainer font, CMap c, int len) throws IOException {
Map<Integer, String> mapping = new HashMap<Integer, String>();
if (font.font instanceof PDType0Font) {
PDCIDFont cidFont = ((PDType0Font) font.font).getDescendantFont();
if (cidFont instanceof PDCIDFontType0) {
mapping = getStrings(((PDCIDFontType0) cidFont).getCFFFont());
}
}
if (c != null) {
int last = font.getLastChar();
if (last == -1) {
last = len;
}
for (int i = font.getFirstChar(); i <= last; i++) {
String l = c.toUnicode(i);
if (l != null) {
mapping.put(i, l);
}
}
}
return mapping;
}
private Map<Integer, String> getStrings(CFFFont ff) throws IOException {
CFFCharset cs = ff.getCharset();
Map<Integer, String> strings = new LinkedHashMap<Integer, String>();
for (int gid = 0; gid < 256; gid++) {
int sid = cs.getCIDForGID(gid);
if (sid != 0) {
strings.put(sid, GlyphList.getAdobeGlyphList().toUnicode(readString(sid)));
}
}
return strings;
}
private String readString(int index) throws IOException {
if (index >= 0 && index <= 390) {
return CFFStandardString.getName(index);
}
// technically this maps to .notdef, but we need a unique glyph name
return "SID" + index;
}
private boolean differentGlyphData(GlyphData[] data, Map<Integer, String> mapping) throws IOException {
Map<String, Integer> tmpMap = new HashMap<String, Integer>();
for (Map.Entry<Integer, String> entry : mapping.entrySet()) {
if (!tmpMap.containsKey(entry.getValue())) {
tmpMap.put(entry.getValue(), entry.getKey());
}
}
mapping.clear();
for (Map.Entry<String, Integer> entry : tmpMap.entrySet()) {
mapping.put(entry.getValue(), entry.getKey());
}
for (Map.Entry<Integer, String> n : mapping.entrySet()) {
if (n.getKey() >= data.length) {
throw new IOException("Mapping not found in glyphData");
}
// if (data[n.getKey()] != null) {
// if (glyphs.containsKey(n.getValue()) && !glyphs.get(n.getValue()).equals(data[n.getKey()])) {
// return true;
// }
// glyphs.put(n.getValue(), data[n.getKey()]);
// }
}
return false;
}
private InputStream readFontFile(PDFont font) throws IOException {
PDFontDescriptor fd = font.getFontDescriptor();
if (font instanceof PDType0Font) {
PDCIDFont cidFont = ((PDType0Font) font).getDescendantFont();
fd = cidFont.getFontDescriptor();
}
PDStream ff = fd.getFontFile3();
if (ff == null) {
ff = fd.getFontFile2();
if (ff == null) {
ff = fd.getFontFile();
}
}
if (ff == null) {
throw new IOException(font.getName() + " no fontfile");
}
InputStream is = ff.createInputStream();
return new ByteArrayInputStream(IOUtils.toByteArray(is));
}
public Map<Integer, Integer> getWidthsMap() {
return newWidth;
}
public PDFDictionary getRef() {
return ref;
}
public void setRef(PDFDictionary d) {
ref = d;
}
public int size() {
if (getFontType() == FontType.CIDTYPE0) {
return 1;
}
return 0;
}
private void readFontBBox(COSBase b) throws IOException {
if (b instanceof COSDictionary) {
COSDictionary dict = (COSDictionary)b;
for (Map.Entry<COSName, COSBase> n : dict.entrySet()) {
readFontBBox(n.getValue());
if (n.getKey() == COSName.FONT_BBOX) {
COSArray w = (COSArray)n.getValue();
float[] bboxf = w.toFloatArray();
int[] bbox = new int[bboxf.length];
for (int i = 0; i < bbox.length; i++) {
bbox[i] = (int)bboxf[i];
}
setFontBBox(bbox);
}
}
} else if (b instanceof COSObject) {
COSObject o = (COSObject)b;
readFontBBox(o.getObject());
} else if (b instanceof COSArray) {
COSArray o = (COSArray)b;
for (int i = 0; i < o.size(); i++) {
readFontBBox(o.get(i));
}
}
}
public boolean isEmbeddable() {
return true;
}
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(mergeFonts.getMergedFontSubset());
}
protected FontContainer getFont(COSDictionary fontData) throws IOException {
if (!fontMap.containsKey(fontData)) {
if (fontMap.size() > 10) {
fontMap.clear();
}
fontMap.put(fontData, new FontContainer(fontData));
}
return fontMap.get(fontData);
}
protected static void setProperties(CustomFont cFont, PDFont font) {
if (font.getFontDescriptor() != null) {
cFont.setCapHeight((int) font.getFontDescriptor().getCapHeight());
cFont.setAscender((int)font.getFontDescriptor().getAscent());
cFont.setDescender((int)font.getFontDescriptor().getDescent());
cFont.setXHeight((int)font.getFontDescriptor().getXHeight());
cFont.setStemV((int)font.getFontDescriptor().getStemV());
}
}
protected static void mergeMaxp(TrueTypeFont ttf, MaximumProfileTable outMaxp) throws IOException {
org.apache.fontbox.ttf.MaximumProfileTable mp = ttf.getMaximumProfile();
outMaxp.setVersion(mp.getVersion());
outMaxp.setNumGlyphs(outMaxp.getNumGlyphs() + mp.getNumGlyphs());
outMaxp.setMaxPoints(outMaxp.getMaxPoints() + mp.getMaxPoints());
outMaxp.setMaxContours(outMaxp.getMaxContours() + mp.getMaxContours());
outMaxp.setMaxCompositePoints(outMaxp.getMaxCompositePoints() + mp.getMaxCompositePoints());
outMaxp.setMaxCompositeContours(outMaxp.getMaxCompositeContours() + mp.getMaxCompositeContours());
outMaxp.setMaxZones(outMaxp.getMaxZones() + mp.getMaxZones());
outMaxp.setMaxTwilightPoints(outMaxp.getMaxTwilightPoints() + mp.getMaxTwilightPoints());
outMaxp.setMaxStorage(outMaxp.getMaxStorage() + mp.getMaxStorage());
outMaxp.setMaxFunctionDefs(outMaxp.getMaxFunctionDefs() + mp.getMaxFunctionDefs());
outMaxp.setMaxInstructionDefs(outMaxp.getMaxInstructionDefs() + mp.getMaxInstructionDefs());
outMaxp.setMaxStackElements(outMaxp.getMaxStackElements() + mp.getMaxStackElements());
outMaxp.setMaxSizeOfInstructions(outMaxp.getMaxSizeOfInstructions() + mp.getMaxSizeOfInstructions());
outMaxp.setMaxComponentElements(outMaxp.getMaxComponentElements() + mp.getMaxComponentElements());
outMaxp.setMaxComponentDepth(outMaxp.getMaxComponentDepth() + mp.getMaxComponentDepth());
}
}