Java tutorial
/* * 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.cff.CFFType1Font; import org.apache.fontbox.cmap.CMap; import org.apache.fontbox.ttf.CmapSubtable; 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.pdmodel.common.PDStream; 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.PDType1CFont; import org.apache.pdfbox.pdmodel.font.PDType1Font; import org.apache.pdfbox.pdmodel.font.encoding.BuiltInEncoding; import org.apache.pdfbox.pdmodel.font.encoding.Encoding; 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.pdf.PDFDictionary; public class FOPPDFSingleByteFont extends SingleByteFont implements FOPPDFFont { private int fontCount; private FontContainer 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 MergeFonts mergeFonts; private String shortFontName; private final Map<COSDictionary, FontContainer> fontMap = new HashMap<COSDictionary, FontContainer>(); 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.font.getName()); loadFontFile(font); float[] bBoxF = font.getBoundingBox(); 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.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.getEncoding()).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) { Map<Integer, String> codeToName = new HashMap<Integer, String>(); if (encoding != null) { COSBase cos = null; if (!(encoding instanceof BuiltInEncoding)) { 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(FontContainer font) throws IOException { if (font.getEncoding() != null) { return font.getEncoding(); } if (font.getToUnicodeCMap() == null) { throw new IOException("No cmap found in " + font.font.getName()); } return font.getToUnicodeCMap(); } private PDStream readFontFile(PDFont font) throws IOException { PDFontDescriptor fd = 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.getName() + " no font file"); } return ff; } private void loadFontFile(FontContainer font) throws IOException { PDStream ff = readFontFile(font.font); mergeFontFile(ff.createInputStream(), font); if (font.font instanceof PDTrueTypeFont) { TrueTypeFont ttfont = ((PDTrueTypeFont) font.font).getTrueTypeFont(); CmapSubtable[] cmapList = ttfont.getCmap().getCmaps(); for (CmapSubtable c : cmapList) { MergeTTFonts.Cmap tempCmap = getNewCmap(c.getPlatformId(), c.getPlatformEncodingId()); for (int i = 0; i < 256 * 256; i++) { int gid = c.getGlyphId(i); if (gid != 0) { tempCmap.glyphIdToCharacterCode.put(i, gid); } } } FOPPDFMultiByteFont.mergeMaxp(ttfont, ((MergeTTFonts) mergeFonts).maxp); } } private MergeTTFonts.Cmap getNewCmap(int platformID, int platformEncodingID) { for (MergeTTFonts.Cmap cmap : newCmap) { if (cmap.platformId == platformID && cmap.platformEncodingId == platformEncodingID) { return cmap; } } MergeTTFonts.Cmap cmap = new MergeTTFonts.Cmap(platformID, platformEncodingID); newCmap.add(cmap); return cmap; } @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 { FontContainer font = getFont(fontData); if ((font.font instanceof PDType1Font || font.font instanceof PDType1CFont) && differentGlyphData(font.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(PDFont font) throws IOException { if (font instanceof PDType1Font) { return ((PDType1Font) font).getType1Font().getCharStringsDict(); } CFFType1Font cffFont = ((PDType1CFont) font).getCFFType1Font(); List<byte[]> bytes = cffFont.getCharStringBytes(); Map<String, byte[]> map = new HashMap<String, byte[]>(); for (int i = 0; i < bytes.size(); i++) { map.put(cffFont.getCharset().getNameForGID(i), bytes.get(i)); } return map; } private boolean differentGlyphData(PDFont otherFont) throws IOException { if (charStringsDict == null) { charStringsDict = getCharStringsDict(font.font); } Map<String, byte[]> otherFontMap = getCharStringsDict(otherFont); for (Map.Entry<String, byte[]> s : otherFontMap.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(FontContainer font) throws IOException { int w = 0; int skipGlyphIndex = getLastChar() + 1; Object cmap = getCmap(font); Set<Integer> codeToName = getCodeToName(font.getEncoding()).keySet(); for (int i = font.getFirstChar(); i <= font.getLastChar(); i++) { boolean addedWidth = false; int glyphIndexPos = skipGlyphIndex; if (font.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.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; return c.toUnicode(i); } Encoding enc = (Encoding) cmap; return enc.getName(i); } public String getEncodingName() { return font.getBaseEncodingName(); } private void addEncoding(FontContainer fontForEnc) { List<String> added = new ArrayList<String>(encodingMap.values()); Map<Integer, String> codeToName = getCodeToName(fontForEnc.getEncoding()); 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 (font.getToUnicode() == 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, FontContainer pdFont) throws IOException { if (mergeFonts == null) { if (getFontType() == FontType.TRUETYPE) { mergeFonts = new MergeTTFonts(newCmap); } else if (getFontType() == FontType.TYPE1) { mergeFonts = new MergeType1Fonts(); } else { mergeFonts = new MergeCFFFonts(); } } Map<Integer, Integer> chars = new HashMap<Integer, Integer>(); chars.put(0, 0); mergeFonts.readFont(ff, shortFontName, pdFont, chars, false); fontCount++; } 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); } }