/* * 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.io.IOException; import java.util.HashMap; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; 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.conversion.CMapSubstitution; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.util.ResourceLoader; /** * This is implementation for the CIDFontType0/CIDFontType2 Fonts. * * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a> * @version $Revision: 1.11 $ */ public abstract class PDCIDFont extends PDSimpleFont { /** * Log instance. */ private static final Log log = LogFactory.getLog(PDCIDFont.class); private Map<Integer,Float> widthCache = null; private long defaultWidth = 0; /** * Constructor. */ public PDCIDFont() { super(); } /** * Constructor. * * @param fontDictionary The font dictionary according to the PDF specification. */ public PDCIDFont( COSDictionary fontDictionary ) { super( fontDictionary ); extractWidths(); } /** * This will get the fonts bouding box. * * @return The fonts bouding box. * * @throws IOException If there is an error getting the font bounding box. */ public PDRectangle getFontBoundingBox() throws IOException { throw new RuntimeException( "getFontBoundingBox(): Not yet implemented" ); } /** * This will get the default width. The default value for the default width is 1000. * * @return The default width for the glyphs in this font. */ public long getDefaultWidth() { if (defaultWidth == 0) { COSNumber number = (COSNumber)font.getDictionaryObject( COSName.DW); if( number != null ) { defaultWidth = number.intValue(); } else { defaultWidth = 1000; } } return defaultWidth; } /** * This will set the default width for the glyphs of this font. * * @param dw The default width. */ public void setDefaultWidth( long dw ) { defaultWidth = dw; font.setLong( COSName.DW, dw ); } /** * This will get the font width for a character. * * @param c The character code to get the width for. * @param offset The offset into the array. * @param length The length of the data. * * @return The width is in 1000 unit of text space, ie 333 or 777 * * @throws IOException If an error occurs while parsing. */ public float getFontWidth( byte[] c, int offset, int length ) throws IOException { float retval = getDefaultWidth(); int code = getCodeFromArray( c, offset, length ); Float widthFloat = widthCache.get( code ); if( widthFloat != null ) { retval = widthFloat.floatValue(); } return retval; } private void extractWidths() { if (widthCache == null) { widthCache = new HashMap<Integer,Float>(); COSArray widths = (COSArray)font.getDictionaryObject( COSName.W ); if( widths != null ) { int size = widths.size(); int counter = 0; while (counter < size) { COSNumber firstCode = (COSNumber)widths.getObject( counter++ ); COSBase next = widths.getObject( counter++ ); if( next instanceof COSArray ) { COSArray array = (COSArray)next; int startRange = firstCode.intValue(); int arraySize = array.size(); for (int i=0; i<arraySize; i++) { COSNumber width = (COSNumber)array.get(i); widthCache.put(startRange+i, width.floatValue()); } } else { COSNumber secondCode = (COSNumber)next; COSNumber rangeWidth = (COSNumber)widths.getObject( counter++ ); int startRange = firstCode.intValue(); int endRange = secondCode.intValue(); float width = rangeWidth.floatValue(); for (int i=startRange; i<=endRange; i++) { widthCache.put(i,width); } } } } } } /** * This will get the font height for a character. * * @param c The character code to get the height for. * @param offset The offset into the array. * @param length The length of the data. * * @return The width is in 1000 unit of text space, ie 333 or 777 * * @throws IOException If an error occurs while parsing. */ public float getFontHeight( byte[] c, int offset, int length ) throws IOException { float retval = 0; PDFontDescriptor desc = getFontDescriptor(); float xHeight = desc.getXHeight(); float capHeight = desc.getCapHeight(); if( xHeight != 0f && capHeight != 0 ) { //do an average of these two. Can we do better??? retval = (xHeight + capHeight)/2f; } else if( xHeight != 0 ) { retval = xHeight; } else if( capHeight != 0 ) { retval = capHeight; } else { retval = 0; } if( retval == 0 ) { retval = desc.getAscent(); } return retval; } /** * This will get the average font width for all characters. * * @return The width is in 1000 unit of text space, ie 333 or 777 * * @throws IOException If an error occurs while parsing. */ public float getAverageFontWidth() throws IOException { float totalWidths = 0.0f; float characterCount = 0.0f; float defaultWidth = getDefaultWidth(); COSArray widths = (COSArray)font.getDictionaryObject( COSName.W ); if( widths != null ) { for( int i=0; i<widths.size(); i++ ) { COSNumber firstCode = (COSNumber)widths.getObject( i++ ); COSBase next = widths.getObject( i ); if( next instanceof COSArray ) { COSArray array = (COSArray)next; for( int j=0; j<array.size(); j++ ) { COSNumber width = (COSNumber)array.get( j ); totalWidths+=width.floatValue(); characterCount += 1; } } else { i++; COSNumber rangeWidth = (COSNumber)widths.getObject( i ); if( rangeWidth.floatValue() > 0 ) { totalWidths += rangeWidth.floatValue(); characterCount += 1; } } } } float average = totalWidths / characterCount; if( average <= 0 ) { average = defaultWidth; } return average; } /** * {@inheritDoc} */ public float getFontWidth( int charCode ) { float width = -1; if (widthCache.containsKey(charCode)) { width = widthCache.get(charCode); } return width; } /** * Extract the CIDSystemInfo. * @return the CIDSystemInfo as String */ private String getCIDSystemInfo() { String cidSystemInfo = null; COSDictionary cidsysteminfo = (COSDictionary)font.getDictionaryObject(COSName.CIDSYSTEMINFO); if (cidsysteminfo != null) { String ordering = cidsysteminfo.getString(COSName.ORDERING); String registry = cidsysteminfo.getString(COSName.REGISTRY); int supplement = cidsysteminfo.getInt(COSName.SUPPLEMENT); cidSystemInfo = registry + "-" + ordering+ "-" + supplement; } return cidSystemInfo; } @Override protected void determineEncoding() { String cidSystemInfo = getCIDSystemInfo(); if (cidSystemInfo != null) { if (cidSystemInfo.contains("Identity")) { cidSystemInfo = "Identity-H"; } else if (cidSystemInfo.startsWith("Adobe-UCS-")) { cidSystemInfo = "Adobe-Identity-UCS"; } else { cidSystemInfo = cidSystemInfo.substring(0,cidSystemInfo.lastIndexOf("-"))+"-UCS2"; } cmap = cmapObjects.get( cidSystemInfo ); if (cmap == null) { String resourceName = resourceRootCMAP + cidSystemInfo; try { parseCmap( resourceRootCMAP, ResourceLoader.loadResource( resourceName ) ); if( cmap == null) { log.error("Error: Could not parse predefined CMAP file for '" + cidSystemInfo + "'" ); } } catch(IOException exception) { log.error("Error: Could not find predefined CMAP file for '" + cidSystemInfo + "'" ); } } } else { super.determineEncoding(); } } @Override public String encode(byte[] c, int offset, int length) throws IOException { String result = null; if (cmap != null) { result = cmapEncoding(getCodeFromArray( c, offset, length ), length, true); } else { result = super.encode(c, offset, length); } return result; } }