blob: 6dcd95f2fae4a65c37e67e7fd08006c78b06418f [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.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.apache.fontbox.cmap.CMap;
import org.apache.fontbox.ttf.CMAPEncodingEntry;
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.COSNumber;
import org.apache.pdfbox.encoding.Encoding;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDFontDescriptorDictionary;
import org.apache.pdfbox.pdmodel.font.PDFontFactory;
import org.apache.pdfbox.pdmodel.font.PDTrueTypeFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.fop.fonts.EmbeddingMode;
import org.apache.fop.fonts.FontType;
import org.apache.fop.fonts.SingleByteEncoding;
import org.apache.fop.fonts.SingleByteFont;
import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.pdf.PDFDictionary;
public class FOPPDFSingleByteFont extends SingleByteFont implements FOPPDFFont {
private int fontCount;
private PDFont font;
protected PDFDictionary ref;
protected Map<String, Integer> charMapGlobal = new LinkedHashMap<String, Integer>();
private Map<Integer, Integer> newWidth = new HashMap<Integer, Integer>();
private Map<String, byte[]> charStringsDict;
private List<MergeTTFonts.Cmap> newCmap = new ArrayList<MergeTTFonts.Cmap>();
private Map<Integer, String> encodingMap = new TreeMap<Integer, String>();
private int encodingSkip;
private MergeTTFonts mergeTTFonts = new MergeTTFonts();
private MergeCFFFonts mergeCFFFonts = new MergeCFFFonts();
private MergeType1Fonts mergeType1Fonts = new MergeType1Fonts();
private String shortFontName;
private final Map<COSDictionary, PDFont> fontMap = new HashMap<COSDictionary, PDFont>();
public FOPPDFSingleByteFont(COSDictionary fontData, String name) throws IOException {
super(null, EmbeddingMode.FULL);
if (fontData.getItem(COSName.SUBTYPE) == COSName.TRUE_TYPE) {
setFontType(FontType.TRUETYPE);
}
width = new int[0];
font = getFont(fontData);
setFirstChar(font.getFirstChar());
setLastChar(font.getLastChar());
shortFontName = MergeFontsPDFWriter.getName(font.getBaseFont());
loadFontFile(font);
float[] bBoxF = font.getFontBoundingBox().getCOSArray().toFloatArray();
int[] bBox = new int[bBoxF.length];
for (int i = 0; i < bBox.length; i++) {
bBox[i] = (int)bBoxF[i];
}
setFontBBox(bBox);
setFontName(name);
Object cmap = getCmap(font);
for (int i = font.getFirstChar(); i <= font.getLastChar(); i++) {
String mappedChar = getChar(cmap, i);
if (mappedChar != null && !charMapGlobal.containsKey(mappedChar)) {
charMapGlobal.put(mappedChar, i);
}
}
//mark font as used
notifyMapOperation();
FOPPDFMultiByteFont.setProperties(this, font);
if (font.getWidths() != null) {
//if width contains 0 we cant rely on codeToNameMap
boolean usesZero = font.getWidths().contains(0);
Set<Integer> codeToName = getCodeToName(font.getFontEncoding()).keySet();
for (int i = getFirstChar();
i <= Math.min(getLastChar(), getFirstChar() + font.getWidths().size()); i++) {
if (usesZero || codeToName.contains(i)) {
int w = font.getWidths().get(i - getFirstChar());
newWidth.put(i, w);
} else {
newWidth.put(i, 0);
}
}
}
mapping = new FOPPDFEncoding();
encodingSkip = font.getLastChar() + 1;
addEncoding(font);
}
private Map<Integer, String> getCodeToName(Encoding encoding) throws IOException {
Map<Integer, String> codeToName = new HashMap<Integer, String>();
if (encoding != null) {
COSBase cos = encoding.getCOSObject();
if (cos instanceof COSDictionary) {
COSDictionary enc = (COSDictionary) cos;
COSName baseEncodingName = (COSName) enc.getDictionaryObject(COSName.BASE_ENCODING);
if (baseEncodingName != null) {
Encoding baseEncoding = Encoding.getInstance(baseEncodingName);
codeToName.putAll(baseEncoding.getCodeToNameMap());
}
COSArray differences = (COSArray)enc.getDictionaryObject(COSName.DIFFERENCES);
int currentIndex = -1;
for (int i = 0; differences != null && i < differences.size(); i++) {
COSBase next = differences.getObject(i);
if (next instanceof COSNumber) {
currentIndex = ((COSNumber)next).intValue();
} else if (next instanceof COSName) {
COSName name = (COSName)next;
codeToName.put(currentIndex++, name.getName());
}
}
} else {
return encoding.getCodeToNameMap();
}
}
return codeToName;
}
private Object getCmap(PDFont font) throws IOException {
if (font.getFontEncoding() != null) {
return font.getFontEncoding();
}
return font.getToUnicodeCMap();
}
private PDStream readFontFile(PDFont font) throws IOException {
PDFontDescriptorDictionary fd = (PDFontDescriptorDictionary) font.getFontDescriptor();
setFlags(fd.getFlags());
PDStream ff = fd.getFontFile3();
if (ff == null) {
ff = fd.getFontFile2();
if (ff == null) {
ff = fd.getFontFile();
}
} else {
setFontType(FontType.TYPE1C);
}
if (ff == null) {
throw new IOException(font.getBaseFont() + " no font file");
}
return ff;
}
private void loadFontFile(PDFont font) throws IOException {
PDStream ff = readFontFile(font);
mergeFontFile(ff.createInputStream(), font);
if (font instanceof PDTrueTypeFont) {
TrueTypeFont ttfont = ((PDTrueTypeFont) font).getTTFFont();
CMAPEncodingEntry[] cmapList = ttfont.getCMAP().getCmaps();
for (CMAPEncodingEntry c : cmapList) {
MergeTTFonts.Cmap tempCmap = getNewCmap(c.getPlatformId(), c.getPlatformEncodingId());
for (int i = 0; i < 256 * 256; i++) {
if (c.getGlyphId(i) != 0) {
tempCmap.glyphIdToCharacterCode.put(i, c.getGlyphId(i));
}
}
newCmap.add(tempCmap);
}
FOPPDFMultiByteFont.mergeMaxp(ttfont, mergeTTFonts.maxp);
}
}
private MergeTTFonts.Cmap getNewCmap(int platformID, int platformEncodingID) {
for (MergeTTFonts.Cmap cmap : newCmap) {
if (cmap.platformId == platformID && cmap.platformEncodingId == platformEncodingID) {
return cmap;
}
}
return new MergeTTFonts.Cmap(platformID, platformEncodingID);
}
@Override
public boolean hasChar(char c) {
return charMapGlobal.containsKey(String.valueOf(c));
}
@Override
public char mapChar(char c) {
return mapping.mapChar(c);
}
public String getEmbedFontName() {
return shortFontName;
}
public int[] getWidths() {
width = new int[getLastChar() - getFirstChar() + 1];
for (int i = getFirstChar(); i <= getLastChar(); i++) {
if (newWidth.containsKey(i)) {
width[i - getFirstChar()] = newWidth.get(i);
} else {
width[i - getFirstChar()] = 0;
}
}
return width.clone();
}
public String addFont(COSDictionary fontData) throws IOException {
PDFont font = getFont(fontData);
if (font instanceof PDType1Font && differentGlyphData((PDType1Font) font)) {
return null;
}
mergeWidths(font);
if (font.getFirstChar() < getFirstChar()) {
setFirstChar(font.getFirstChar());
}
for (int w : newWidth.keySet()) {
if (w > getLastChar()) {
setLastChar(w);
}
}
loadFontFile(font);
addEncoding(font);
return getFontName();
}
public int size() {
return fontCount;
}
private Map<String, byte[]> getCharStringsDict(PDType1Font font) throws IOException {
if (getFontType() == FontType.TYPE1) {
return font.getType1Font().getCharStringsDict();
}
return font.getType1CFont().getCFFFont().getCharStringsDict();
}
private boolean differentGlyphData(PDType1Font otherFont) throws IOException {
if (charStringsDict == null) {
charStringsDict = getCharStringsDict((PDType1Font) font);
}
for (Map.Entry<String, byte[]> s : getCharStringsDict(otherFont).entrySet()) {
if (charStringsDict.containsKey(s.getKey())) {
int numberDiff = 0;
byte[] b1 = charStringsDict.get(s.getKey());
byte[] b2 = s.getValue();
int b1Index = b1.length - 1;
int b2Index = b2.length - 1;
while (b1Index >= 0 && b2Index >= 0) {
if (b1[b1Index] != b2[b2Index]) {
numberDiff++;
if (numberDiff > 2) {
break;
}
}
b1Index--;
b2Index--;
}
if (numberDiff > 2) {
// log.info(getFontName() + " " + s.getKey() + " not equal " + numberdiff);
return true;
}
}
}
return false;
}
private void mergeWidths(PDFont font) throws IOException {
int w = 0;
int skipGlyphIndex = getLastChar() + 1;
Object cmap = getCmap(font);
Set<Integer> codeToName = getCodeToName(font.getFontEncoding()).keySet();
for (int i = font.getFirstChar(); i <= font.getLastChar(); i++) {
boolean addedWidth = false;
int glyphIndexPos = skipGlyphIndex;
if (font instanceof PDTrueTypeFont) {
glyphIndexPos = i;
}
int neww = 0;
if (font.getWidths() != null) {
neww = font.getWidths().get(i - font.getFirstChar());
if (!newWidth.containsKey(i) || newWidth.get(i) == 0) {
if (getFontType() == FontType.TYPE1
|| font instanceof PDTrueTypeFont
|| codeToName.contains(i)) {
newWidth.put(i, neww);
glyphIndexPos = i;
} else {
newWidth.put(i, 0);
}
addedWidth = true;
}
}
String mappedChar = getChar(cmap, i);
if (mappedChar != null && !charMapGlobal.containsKey(mappedChar)) {
charMapGlobal.put(mappedChar, glyphIndexPos);
if (!addedWidth && w < font.getWidths().size()) {
newWidth.put(newWidth.size() + getFirstChar(), neww);
}
skipGlyphIndex++;
}
w++;
}
}
private String getChar(Object cmap, int i) throws IOException {
if (cmap instanceof CMap) {
CMap c = (CMap)cmap;
int size = 1;
if (c.hasTwoByteMappings()) {
size = 2;
}
return c.lookup(i, size);
}
Encoding enc = (Encoding)cmap;
return enc.getName(i);
}
public String getEncodingName() {
if (font.getFontEncoding() != null) {
COSBase cosObject = font.getFontEncoding().getCOSObject();
if (cosObject != null) {
if (cosObject instanceof COSDictionary) {
COSBase item = ((COSDictionary) cosObject).getItem(COSName.BASE_ENCODING);
if (item != null) {
return ((COSName)item).getName();
}
} else if (cosObject instanceof COSName) {
return ((COSName) cosObject).getName();
} else {
throw new RuntimeException(cosObject.toString() + " not supported");
}
}
}
return null;
}
private void addEncoding(PDFont fontForEnc) throws IOException {
List<String> added = new ArrayList<String>(encodingMap.values());
Map<Integer, String> codeToName = getCodeToName(fontForEnc.getFontEncoding());
for (int i = fontForEnc.getFirstChar(); i <= fontForEnc.getLastChar(); i++) {
if (codeToName.keySet().contains(i)) {
String s = codeToName.get(i);
if (!added.contains(s) || (added.contains(s) && !encodingMap.containsKey(i))) {
if (!encodingMap.containsKey(i)) {
encodingMap.put(i, s);
} else {
encodingMap.put(encodingSkip, s);
encodingSkip++;
}
}
}
}
}
class FOPPDFEncoding implements SingleByteEncoding {
private boolean cmap;
public String getName() {
return "FOPPDFEncoding";
}
public char mapChar(char c) {
if (charMapGlobal.containsKey(String.valueOf(c))) {
return (char)charMapGlobal.get(String.valueOf(c)).intValue();
}
return 0;
}
public String[] getCharNameMap() {
Collection<String> v = encodingMap.values();
return v.toArray(new String[v.size()]);
}
public char[] getUnicodeCharMap() {
if (cmap) {
if (MergeFontsPDFWriter.getToUnicode(font) == null) {
return new char[0];
}
List<String> cmapStrings = new ArrayList<String>();
Map<Integer, String> cm = new HashMap<Integer, String>();
for (Map.Entry<String, Integer> o : charMapGlobal.entrySet()) {
cm.put(o.getValue(), o.getKey());
}
for (int i = 0; i < getLastChar() + 1; i++) {
if (cm.containsKey(i)) {
cmapStrings.add(cm.get(i));
} else {
cmapStrings.add(" ");
}
}
return fromStringToCharArray(cmapStrings);
}
cmap = true;
return toCharArray(encodingMap.keySet());
}
private char[] fromStringToCharArray(Collection<String> list) {
char[] ret = new char[list.size()];
int i = 0;
for (String e : list) {
if (e.length() > 0) {
ret[i++] = e.charAt(0);
}
}
return ret;
}
private char[] toCharArray(Collection<Integer> list) {
char[] ret = new char[list.size()];
int i = 0;
for (int e : list) {
ret[i++] = (char)e;
}
return ret;
}
}
public PDFDictionary getRef() {
return ref;
}
public void setRef(PDFDictionary d) {
ref = d;
}
public boolean isEmbeddable() {
return true;
}
public boolean isSymbolicFont() {
return false;
}
private void mergeFontFile(InputStream ff, PDFont pdFont) throws IOException {
if (getFontType() == FontType.TRUETYPE) {
Map<Integer, Integer> chars = new HashMap<Integer, Integer>();
chars.put(0, 0);
mergeTTFonts.readFont(new FontFileReader(ff), chars, false);
} else if (getFontType() == FontType.TYPE1) {
mergeType1Fonts.readFont(ff, (PDType1Font) pdFont);
} else {
mergeCFFFonts.readType1CFont(ff, shortFontName);
}
fontCount++;
}
public InputStream getInputStream() throws IOException {
if (getFontType() == FontType.TYPE1C) {
mergeCFFFonts.writeFont();
return new ByteArrayInputStream(mergeCFFFonts.getFontSubset());
}
if (getFontType() == FontType.TRUETYPE) {
mergeTTFonts.writeFont(newCmap);
return new ByteArrayInputStream(mergeTTFonts.getFontSubset());
}
if (getFontType() == FontType.TYPE1) {
return new ByteArrayInputStream(mergeType1Fonts.writeFont());
}
return null;
}
protected PDFont getFont(COSDictionary fontData) throws IOException {
if (!fontMap.containsKey(fontData)) {
if (fontMap.size() > 10) {
fontMap.clear();
}
fontMap.put(fontData, PDFontFactory.createFont(fontData));
}
return fontMap.get(fontData);
}
}