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.pdfbox.pdmodel.font; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.NoninvertibleTransformException; import java.io.IOException; import java.util.HashMap; import org.apache.fontbox.afm.FontMetric; 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.cos.COSStream; import org.apache.pdfbox.encoding.DictionaryEncoding; import org.apache.pdfbox.encoding.Encoding; import org.apache.pdfbox.encoding.EncodingManager; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.util.ResourceLoader; /** * This class contains implementation details of the simple pdf fonts. * * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a> * @version $Revision: 1.18 $ */ public abstract class PDSimpleFont extends PDFont { private final HashMap<Integer, Float> mFontSizes = new HashMap<Integer, Float>(128); private float avgFontWidth = 0.0f; private float avgFontHeight = 0.0f; /** * Log instance. */ private static final Log LOG = LogFactory.getLog(PDSimpleFont.class); /** * Constructor. */ public PDSimpleFont() { super(); } /** * Constructor. * * @param fontDictionary The font dictionary according to the PDF specification. */ public PDSimpleFont(COSDictionary fontDictionary) { super(fontDictionary); } /** * Looks up, creates, returns the AWT Font. * * @return returns the awt font to bes used for rendering * @throws IOException if something went wrong. */ public Font getawtFont() throws IOException { LOG.error("Not yet implemented:" + getClass().getName()); return null; } /** * {@inheritDoc} */ public void drawString(String string, int[] codePoints, Graphics g, float fontSize, AffineTransform at, float x, float y) throws IOException { Font awtFont = getawtFont(); FontRenderContext frc = new FontRenderContext(new AffineTransform(), true, true); GlyphVector glyphs = null; boolean useCodepoints = codePoints != null && isType0Font(); PDFont descendantFont = useCodepoints ? ((PDType0Font) this).getDescendantFont() : null; // symbolic fonts may trigger the same fontmanager.so/dll error as described below if (useCodepoints && !descendantFont.getFontDescriptor().isSymbolic()) { PDCIDFontType2Font cid2Font = null; if (descendantFont instanceof PDCIDFontType2Font) { cid2Font = (PDCIDFontType2Font) descendantFont; } if ((cid2Font != null && cid2Font.hasCIDToGIDMap()) || isFontSubstituted) { // we still have to use the string if a CIDToGIDMap is used glyphs = awtFont.createGlyphVector(frc, string); } else { glyphs = awtFont.createGlyphVector(frc, codePoints); } } else { // mdavis - fix fontmanager.so/dll on sun.font.FileFont.getGlyphImage // for font with bad cmaps? // Type1 fonts are not affected as they don't have cmaps if (!isType1Font() && awtFont.canDisplayUpTo(string) != -1) { LOG.warn("Changing font on <" + string + "> from <" + awtFont.getName() + "> to the default font"); awtFont = Font.decode(null).deriveFont(1f); } glyphs = awtFont.createGlyphVector(frc, string); } Graphics2D g2d = (Graphics2D) g; g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); writeFont(g2d, at, x, y, glyphs); } /** * This will get the font height 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 getFontHeight(byte[] c, int offset, int length) throws IOException { // maybe there is already a precalculated value if (avgFontHeight > 0) { return avgFontHeight; } float retval = 0; FontMetric metric = getAFM(); if (metric != null) { int code = getCodeFromArray(c, offset, length); Encoding encoding = getFontEncoding(); String characterName = encoding.getName(code); retval = metric.getCharacterHeight(characterName); } else { PDFontDescriptor desc = getFontDescriptor(); if (desc != null) { // the following values are all more or less accurate // at least all are average values. Maybe we'll find // another way to get those value for every single glyph // in the future if needed PDRectangle fontBBox = desc.getFontBoundingBox(); if (fontBBox != null) { retval = fontBBox.getHeight() / 2; } if (retval == 0) { retval = desc.getCapHeight(); } if (retval == 0) { retval = desc.getAscent(); } if (retval == 0) { retval = desc.getXHeight(); if (retval > 0) { retval -= desc.getDescent(); } } avgFontHeight = retval; } } return retval; } /** * 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 { int code = getCodeFromArray(c, offset, length); Float fontWidth = mFontSizes.get(code); if (fontWidth == null) { fontWidth = getFontWidth(code); if (fontWidth <= 0) { //hmm should this be in PDType1Font?? fontWidth = getFontWidthFromAFMFile(code); } mFontSizes.put(code, fontWidth); } return fontWidth; } /** * 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 average = 0.0f; //AJW if (avgFontWidth != 0.0f) { average = avgFontWidth; } else { float totalWidth = 0.0f; float characterCount = 0.0f; COSArray widths = (COSArray) font.getDictionaryObject(COSName.WIDTHS); if (widths != null) { for (int i = 0; i < widths.size(); i++) { COSNumber fontWidth = (COSNumber) widths.getObject(i); if (fontWidth.floatValue() > 0) { totalWidth += fontWidth.floatValue(); characterCount += 1; } } } if (totalWidth > 0) { average = totalWidth / characterCount; } else { average = getAverageFontWidthFromAFMFile(); } avgFontWidth = average; } return average; } /** * This will get the ToUnicode object. * * @return The ToUnicode object. */ public COSBase getToUnicode() { return font.getDictionaryObject(COSName.TO_UNICODE); } /** * This will set the ToUnicode object. * * @param unicode The unicode object. */ public void setToUnicode(COSBase unicode) { font.setItem(COSName.TO_UNICODE, unicode); } /** * This will get the fonts bounding box. * * @return The fonts bouding box. * * @throws IOException If there is an error getting the bounding box. */ public PDRectangle getFontBoundingBox() throws IOException { return getFontDescriptor().getFontBoundingBox(); } /** * This will draw a string on a canvas using the font. * * @param g2d The graphics to draw onto. * @param at The transformation matrix with all information for scaling and shearing of the font. * @param x The x coordinate to draw at. * @param y The y coordinate to draw at. * @param glyphs The GlyphVector containing the glyphs to be drawn. * */ protected void writeFont(final Graphics2D g2d, final AffineTransform at, final float x, final float y, final GlyphVector glyphs) { // check if we have a rotation if (!at.isIdentity()) { try { AffineTransform atInv = at.createInverse(); // do only apply the size of the transform, rotation will be realized by rotating the graphics, // otherwise the hp printers will not render the font // apply the transformation to the graphics, which should be the same as applying the // transformation itself to the text g2d.transform(at); // translate the coordinates Point2D.Float newXy = new Point2D.Float(x, y); atInv.transform(new Point2D.Float(x, y), newXy); g2d.drawGlyphVector(glyphs, (float) newXy.getX(), (float) newXy.getY()); // restore the original transformation g2d.transform(atInv); } catch (NoninvertibleTransformException e) { LOG.error("Error in " + getClass().getName() + ".writeFont", e); } } else { g2d.drawGlyphVector(glyphs, x, y); } } /** * {@inheritDoc} */ protected void determineEncoding() { String cmapName = null; COSName encodingName = null; COSBase encoding = getEncoding(); Encoding fontEncoding = null; if (encoding != null) { if (encoding instanceof COSName) { if (cmap == null) { encodingName = (COSName) encoding; cmap = cmapObjects.get(encodingName.getName()); if (cmap == null) { cmapName = encodingName.getName(); } } if (cmap == null && cmapName != null) { try { fontEncoding = EncodingManager.INSTANCE.getEncoding(encodingName); } catch (IOException exception) { LOG.debug("Debug: Could not find encoding for " + encodingName); } } } else if (encoding instanceof COSStream) { if (cmap == null) { COSStream encodingStream = (COSStream) encoding; try { cmap = parseCmap(null, encodingStream.getUnfilteredStream()); } catch (IOException exception) { LOG.error("Error: Could not parse the embedded CMAP"); } } } else if (encoding instanceof COSDictionary) { try { fontEncoding = new DictionaryEncoding((COSDictionary) encoding); } catch (IOException exception) { LOG.error("Error: Could not create the DictionaryEncoding"); } } } setFontEncoding(fontEncoding); extractToUnicodeEncoding(); if (cmap == null && cmapName != null) { String resourceName = resourceRootCMAP + cmapName; try { cmap = parseCmap(resourceRootCMAP, ResourceLoader.loadResource(resourceName)); if (cmap == null && encodingName == null) { LOG.error("Error: Could not parse predefined CMAP file for '" + cmapName + "'"); } } catch (IOException exception) { LOG.error("Error: Could not find predefined CMAP file for '" + cmapName + "'"); } } } private void extractToUnicodeEncoding() { COSName encodingName = null; String cmapName = null; COSBase toUnicode = getToUnicode(); if (toUnicode != null) { setHasToUnicode(true); if (toUnicode instanceof COSStream) { try { toUnicodeCmap = parseCmap(resourceRootCMAP, ((COSStream) toUnicode).getUnfilteredStream()); } catch (IOException exception) { LOG.error("Error: Could not load embedded ToUnicode CMap"); } } else if (toUnicode instanceof COSName) { encodingName = (COSName) toUnicode; toUnicodeCmap = cmapObjects.get(encodingName.getName()); if (toUnicodeCmap == null) { cmapName = encodingName.getName(); String resourceName = resourceRootCMAP + cmapName; try { toUnicodeCmap = parseCmap(resourceRootCMAP, ResourceLoader.loadResource(resourceName)); } catch (IOException exception) { LOG.error("Error: Could not find predefined ToUnicode CMap file for '" + cmapName + "'"); } if (toUnicodeCmap == null) { LOG.error("Error: Could not parse predefined ToUnicode CMap file for '" + cmapName + "'"); } } } } } private boolean isFontSubstituted = false; protected boolean isFontSubstituted() { return isFontSubstituted; } protected void setIsFontSubstituted(boolean isSubstituted) { isFontSubstituted = isSubstituted; } }