| /* |
| * 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.io.UnsupportedEncodingException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.regex.Pattern; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| import org.apache.fontbox.cff.CFFCIDFont; |
| import org.apache.fontbox.cff.CFFCharset; |
| import org.apache.fontbox.cff.CFFEncoding; |
| import org.apache.fontbox.cff.CFFFont; |
| import org.apache.fontbox.ttf.CmapSubtable; |
| import org.apache.fontbox.ttf.TrueTypeFont; |
| import org.apache.pdfbox.contentstream.operator.Operator; |
| import org.apache.pdfbox.cos.COSBase; |
| import org.apache.pdfbox.cos.COSDictionary; |
| import org.apache.pdfbox.cos.COSName; |
| import org.apache.pdfbox.cos.COSString; |
| |
| 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.PDTrueTypeFont; |
| import org.apache.pdfbox.pdmodel.font.PDType0Font; |
| import org.apache.pdfbox.pdmodel.font.PDType1CFont; |
| import org.apache.pdfbox.pdmodel.font.PDType1Font; |
| |
| import org.apache.fop.fonts.FontInfo; |
| import org.apache.fop.fonts.Typeface; |
| import org.apache.fop.fonts.truetype.OTFSubSetFile; |
| |
| import org.apache.fop.pdf.PDFText; |
| |
| |
| public class MergeFontsPDFWriter extends PDFWriter { |
| protected static final Log log = LogFactory.getLog(MergeFontsPDFWriter.class); |
| private COSDictionary fonts; |
| private FontInfo fontInfo; |
| private Typeface font; |
| private FontContainer oldFont = null; |
| protected Map<COSName, String> fontsToRemove = new HashMap<COSName, String>(); |
| private final Map<COSDictionary, FontContainer> fontMap = new HashMap<COSDictionary, FontContainer>(); |
| private static final Pattern SUBSET_PATTERN = Pattern.compile("[A-Z][A-Z][A-Z][A-Z][A-Z][A-Z]\\+.+"); |
| private Collection<String> parentFonts; |
| |
| public MergeFontsPDFWriter(COSDictionary fonts, FontInfo fontInfo, UniqueName key, |
| Collection<String> parentFonts, int mcid) { |
| super(key, mcid); |
| this.fonts = fonts; |
| this.fontInfo = fontInfo; |
| this.parentFonts = parentFonts; |
| } |
| |
| public String writeText(PDStream pdStream) throws IOException { |
| String txt = super.writeText(pdStream); |
| if (fontsToRemove.isEmpty()) { |
| return null; |
| } |
| for (COSName cn : fontsToRemove.keySet()) { |
| fonts.removeItem(cn); |
| } |
| parentFonts.clear(); |
| parentFonts.addAll(fontsToRemove.values()); |
| return txt; |
| } |
| |
| protected void readPDFArguments(Operator op, Collection<COSBase> arguments) throws IOException { |
| for (COSBase c : arguments) { |
| if (c instanceof COSName) { |
| COSName cn = (COSName)c; |
| COSDictionary fontData = (COSDictionary)fonts.getDictionaryObject(cn.getName()); |
| String internalName = fontsToRemove.get(cn); |
| if (internalName == null && fontData != null) { |
| internalName = getNewFont(fontData, fontInfo, fontsToRemove.values()); |
| } |
| if (fontData == null || internalName == null) { |
| s.append("/" + key.getName(cn)); |
| if (op.getName().equals("Tf")) { |
| font = null; |
| oldFont = null; |
| } |
| } else { |
| s.append("/" + internalName); |
| fontsToRemove.put(cn, internalName); |
| font = fontInfo.getUsedFonts().get(internalName); |
| oldFont = getFont(fontData); |
| } |
| s.append(" "); |
| } else if (c instanceof COSString && font != null && ((FOPPDFFont)font).size() != 1) { |
| List<String> word = readCOSString((COSString)c, oldFont); |
| if (word == null) { |
| s.append(PDFText.escapeString(getString((COSString) c))); |
| } else { |
| String x = ((FOPPDFFont)font).getMappedWord(word, ((COSString) c).getBytes(), oldFont); |
| if (x == null) { |
| s.append(PDFText.escapeString(getString((COSString) c))); |
| } else { |
| s.append(x); |
| } |
| } |
| } else { |
| processArg(op, c); |
| } |
| } |
| } |
| |
| private String getNewFont(COSDictionary fontData, FontInfo fontinfo, Collection<String> usedFonts) |
| throws IOException { |
| String base = getUniqueFontName(fontData); |
| if (base == null || usedFonts.contains(base) || (parentFonts != null && parentFonts.contains(base))) { |
| return null; |
| } |
| try { |
| for (Typeface t : fontinfo.getUsedFonts().values()) { |
| if (t instanceof FOPPDFFont && base.equals(t.getFontName())) { |
| return ((FOPPDFFont)t).addFont(fontData); |
| } |
| } |
| if (base.endsWith("cid") || fontData.getItem(COSName.SUBTYPE) != COSName.TYPE1 |
| && fontData.getItem(COSName.SUBTYPE) != COSName.TRUE_TYPE) { |
| fontinfo.addMetrics(base, new FOPPDFMultiByteFont(fontData, base)); |
| } else { |
| fontinfo.addMetrics(base, new FOPPDFSingleByteFont(fontData, base)); |
| } |
| } catch (IOException e) { |
| log.warn(e.getMessage()); |
| return null; |
| } |
| fontinfo.useFont(base); |
| return base; |
| } |
| |
| private String getUniqueFontName(COSDictionary fontData) throws IOException { |
| FontContainer fontContainer = getFont(fontData); |
| PDFont font = fontContainer.font; |
| if (font.getName() != null) { |
| String extra = ""; |
| String name = getName(font.getName()) + "_" + ((COSName)fontData.getItem(COSName.SUBTYPE)).getName(); |
| if (font instanceof PDType0Font) { |
| PDCIDFont descendantFont = ((PDType0Font) font).getDescendantFont(); |
| if (descendantFont instanceof PDCIDFontType0) { |
| CFFFont cffFont = ((PDCIDFontType0) descendantFont).getCFFFont(); |
| if (cffFont instanceof CFFCIDFont |
| && ((CFFCIDFont) cffFont).getFdSelect().getClass().getName() |
| .equals("org.apache.fontbox.cff.CFFParser$Format0FDSelect")) { |
| extra += "format0"; |
| } |
| return name + extra + "cff"; |
| } else if (descendantFont instanceof PDCIDFontType2 && fontContainer.getToUnicode() != null) { |
| if (!isSubsetFont(font.getName())) { |
| extra = "f3"; |
| } |
| return name + extra; |
| } |
| } else if (font instanceof PDTrueTypeFont && isSubsetFont(font.getName())) { |
| TrueTypeFont tt = ((PDTrueTypeFont) font).getTrueTypeFont(); |
| for (CmapSubtable c : tt.getCmap().getCmaps()) { |
| if (c.getGlyphId(1) > 0) { |
| extra = "cid"; |
| } |
| } |
| return name + extra; |
| } else if (font instanceof PDType1CFont) { |
| return getNamePDType1Font(name, (PDType1CFont) font); |
| } else if (font instanceof PDType1Font) { |
| return name; |
| } |
| } |
| return null; |
| } |
| |
| private String getNamePDType1Font(String name, PDType1CFont font) throws IOException { |
| String extra = ""; |
| CFFEncoding encoding = font.getCFFType1Font().getEncoding(); |
| String eClass = encoding.getClass().getName(); |
| if (eClass.equals("org.apache.fontbox.cff.CFFParser$Format1Encoding")) { |
| extra = "f1enc"; |
| } else if (eClass.equals("org.apache.fontbox.cff.CFFParser$Format0Encoding")) { |
| extra = "f0enc"; |
| } |
| CFFCharset cs = font.getCFFType1Font().getCharset(); |
| List<Integer> sids = MergeCFFFonts.getSids(cs); |
| if (!sids.isEmpty() && sids.get(0) < OTFSubSetFile.NUM_STANDARD_STRINGS) { |
| extra += "stdcs"; |
| } |
| if (cs.getClass().getName().equals("org.apache.fontbox.cff.CFFParser$Format1Charset")) { |
| extra += "f1cs"; |
| } |
| if (font.getEncoding() != null) { |
| String enc = font.getEncoding().getClass().getSimpleName(); |
| if (!"DictionaryEncoding".equals(enc)) { |
| extra += enc; |
| } |
| } |
| return name + extra; |
| } |
| |
| private String getString(COSString s) throws UnsupportedEncodingException { |
| String encoding = "ISO-8859-1"; |
| byte[] data = s.getBytes(); |
| int start = 0; |
| if (data.length > 2) { |
| if (data[0] == (byte) 0xFF && data[1] == (byte) 0xFE) { |
| encoding = "UTF-16LE"; |
| start = 2; |
| } else if (data[0] == (byte) 0xFE && data[1] == (byte) 0xFF) { |
| encoding = "UTF-16BE"; |
| start = 2; |
| } |
| } |
| return new String(data, start, data.length - start, encoding); |
| } |
| |
| private List<String> readCOSString(COSString s, FontContainer oldFont) throws IOException { |
| List<String> word = new ArrayList<String>(); |
| byte[] string = s.getBytes(); |
| InputStream in = new ByteArrayInputStream(string); |
| while (in.available() > 0) { |
| int code = oldFont.font.readCode(in); |
| String unicode = oldFont.font.toUnicode(code); |
| if (unicode == null) { |
| return null; |
| } |
| word.add(unicode); |
| } |
| return word; |
| } |
| |
| 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); |
| } |
| |
| private static boolean isSubsetFont(String s) { |
| return SUBSET_PATTERN.matcher(s).matches(); |
| } |
| |
| protected static String getName(String name) { |
| if (isSubsetFont(name)) { |
| return name.split("\\+")[1].replace(" ", ""); |
| } |
| return name.replace(" ", ""); |
| } |
| } |