001: /**
002: * Copyright (c) 2003-2006, www.pdfbox.org
003: * All rights reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions are met:
007: *
008: * 1. Redistributions of source code must retain the above copyright notice,
009: * this list of conditions and the following disclaimer.
010: * 2. Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: * 3. Neither the name of pdfbox; nor the names of its
014: * contributors may be used to endorse or promote products derived from this
015: * software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
018: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
019: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
020: * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
021: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
024: * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027: *
028: * http://www.pdfbox.org
029: *
030: */package org.pdfbox.pdmodel.font;
031:
032: import org.fontbox.ttf.CMAPEncodingEntry;
033: import org.fontbox.ttf.CMAPTable;
034: import org.fontbox.ttf.GlyphData;
035: import org.fontbox.ttf.GlyphTable;
036: import org.fontbox.ttf.HeaderTable;
037: import org.fontbox.ttf.HorizontalHeaderTable;
038: import org.fontbox.ttf.HorizontalMetricsTable;
039: import org.fontbox.ttf.NamingTable;
040: import org.fontbox.ttf.NameRecord;
041: import org.fontbox.ttf.OS2WindowsMetricsTable;
042: import org.fontbox.ttf.PostScriptTable;
043: import org.fontbox.ttf.TTFParser;
044: import org.fontbox.ttf.TrueTypeFont;
045:
046: import org.pdfbox.cos.COSDictionary;
047: import org.pdfbox.cos.COSName;
048:
049: import org.pdfbox.pdmodel.PDDocument;
050:
051: import org.pdfbox.pdmodel.common.PDRectangle;
052: import org.pdfbox.pdmodel.common.PDStream;
053:
054: import org.pdfbox.encoding.WinAnsiEncoding;
055: import org.pdfbox.exceptions.WrappedIOException;
056:
057: import org.pdfbox.util.ResourceLoader;
058:
059: import java.awt.Font;
060: import java.awt.FontFormatException;
061: import java.awt.Graphics;
062: import java.awt.Graphics2D;
063: import java.awt.RenderingHints;
064: import java.awt.geom.AffineTransform;
065:
066: import java.util.ArrayList;
067: import java.util.HashMap;
068: import java.util.List;
069: import java.util.Map;
070: import java.util.Properties;
071:
072: import java.io.File;
073: import java.io.FileInputStream;
074: import java.io.IOException;
075: import java.io.InputStream;
076:
077: /**
078: * This is the TrueType implementation of fonts.
079: *
080: * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
081: * @version $Revision: 1.17 $
082: */
083: public class PDTrueTypeFont extends PDSimpleFont {
084: /**
085: * This is the key to a property in the Resources/PDFBox_External_Fonts.properties file
086: * to load a Font when a mapping does not exist for the current font.
087: */
088: public static final String UNKNOWN_FONT = "UNKNOWN_FONT";
089:
090: private Font awtFont = null;
091:
092: private static Properties externalFonts = new Properties();
093: private static Map loadedExternalFonts = new HashMap();
094:
095: static {
096: try {
097: ResourceLoader.loadProperties(
098: "Resources/PDFBox_External_Fonts.properties",
099: externalFonts);
100: } catch (IOException io) {
101: io.printStackTrace();
102: throw new RuntimeException("Error loading font resources");
103: }
104: }
105:
106: /**
107: * Constructor.
108: */
109: public PDTrueTypeFont() {
110: super ();
111: font.setItem(COSName.SUBTYPE, COSName.TRUE_TYPE);
112: }
113:
114: /**
115: * Constructor.
116: *
117: * @param fontDictionary The font dictionary according to the PDF specification.
118: */
119: public PDTrueTypeFont(COSDictionary fontDictionary) {
120: super (fontDictionary);
121: }
122:
123: /**
124: * This will load a TTF font from a font file.
125: *
126: * @param doc The PDF document that will hold the embedded font.
127: * @param file The file on the filesystem that holds the font file.
128: * @return A true type font.
129: * @throws IOException If there is an error loading the file data.
130: */
131: public static PDTrueTypeFont loadTTF(PDDocument doc, String file)
132: throws IOException {
133: return loadTTF(doc, new File(file));
134: }
135:
136: /**
137: * This will load a TTF to be embedding into a document.
138: *
139: * @param doc The PDF document that will hold the embedded font.
140: * @param file A TTF file stream.
141: * @return A PDF TTF.
142: * @throws IOException If there is an error loading the data.
143: */
144: public static PDTrueTypeFont loadTTF(PDDocument doc, File file)
145: throws IOException {
146: PDTrueTypeFont retval = new PDTrueTypeFont();
147: PDFontDescriptorDictionary fd = new PDFontDescriptorDictionary();
148: PDStream fontStream = new PDStream(doc, new FileInputStream(
149: file), false);
150: fontStream.getStream().setInt(COSName.LENGTH1,
151: (int) file.length());
152: fontStream.addCompression();
153: fd.setFontFile2(fontStream);
154: retval.setFontDescriptor(fd);
155: //only support winansi encoding right now, should really
156: //just use Identity-H with unicode mapping
157: retval.setEncoding(new WinAnsiEncoding());
158: TrueTypeFont ttf = null;
159: try {
160: TTFParser parser = new TTFParser();
161: ttf = parser.parseTTF(file);
162: NamingTable naming = ttf.getNaming();
163: List records = naming.getNameRecords();
164: for (int i = 0; i < records.size(); i++) {
165: NameRecord nr = (NameRecord) records.get(i);
166: if (nr.getNameId() == NameRecord.NAME_POSTSCRIPT_NAME) {
167: retval.setBaseFont(nr.getString());
168: fd.setFontName(nr.getString());
169: } else if (nr.getNameId() == NameRecord.NAME_FONT_FAMILY_NAME) {
170: fd.setFontFamily(nr.getString());
171: }
172: }
173:
174: OS2WindowsMetricsTable os2 = ttf.getOS2Windows();
175: fd.setNonSymbolic(true);
176: switch (os2.getFamilyClass()) {
177: case OS2WindowsMetricsTable.FAMILY_CLASS_SYMBOLIC:
178: fd.setSymbolic(true);
179: fd.setNonSymbolic(false);
180: break;
181: case OS2WindowsMetricsTable.FAMILY_CLASS_SCRIPTS:
182: fd.setScript(true);
183: break;
184: case OS2WindowsMetricsTable.FAMILY_CLASS_CLAREDON_SERIFS:
185: case OS2WindowsMetricsTable.FAMILY_CLASS_FREEFORM_SERIFS:
186: case OS2WindowsMetricsTable.FAMILY_CLASS_MODERN_SERIFS:
187: case OS2WindowsMetricsTable.FAMILY_CLASS_OLDSTYLE_SERIFS:
188: case OS2WindowsMetricsTable.FAMILY_CLASS_SLAB_SERIFS:
189: fd.setSerif(true);
190: break;
191: default:
192: //do nothing
193: }
194: switch (os2.getWidthClass()) {
195: case OS2WindowsMetricsTable.WIDTH_CLASS_ULTRA_CONDENSED:
196: fd.setFontStretch("UltraCondensed");
197: break;
198: case OS2WindowsMetricsTable.WIDTH_CLASS_EXTRA_CONDENSED:
199: fd.setFontStretch("ExtraCondensed");
200: break;
201: case OS2WindowsMetricsTable.WIDTH_CLASS_CONDENSED:
202: fd.setFontStretch("Condensed");
203: break;
204: case OS2WindowsMetricsTable.WIDTH_CLASS_SEMI_CONDENSED:
205: fd.setFontStretch("SemiCondensed");
206: break;
207: case OS2WindowsMetricsTable.WIDTH_CLASS_MEDIUM:
208: fd.setFontStretch("Normal");
209: break;
210: case OS2WindowsMetricsTable.WIDTH_CLASS_SEMI_EXPANDED:
211: fd.setFontStretch("SemiExpanded");
212: break;
213: case OS2WindowsMetricsTable.WIDTH_CLASS_EXPANDED:
214: fd.setFontStretch("Expanded");
215: break;
216: case OS2WindowsMetricsTable.WIDTH_CLASS_EXTRA_EXPANDED:
217: fd.setFontStretch("ExtraExpanded");
218: break;
219: case OS2WindowsMetricsTable.WIDTH_CLASS_ULTRA_EXPANDED:
220: fd.setFontStretch("UltraExpanded");
221: break;
222: default:
223: //do nothing
224: }
225: fd.setFontWeight(os2.getWeightClass());
226:
227: //todo retval.setFixedPitch
228: //todo retval.setNonSymbolic
229: //todo retval.setItalic
230: //todo retval.setAllCap
231: //todo retval.setSmallCap
232: //todo retval.setForceBold
233:
234: HeaderTable header = ttf.getHeader();
235: PDRectangle rect = new PDRectangle();
236: rect.setLowerLeftX(header.getXMin() * 1000f
237: / header.getUnitsPerEm());
238: rect.setLowerLeftY(header.getYMin() * 1000f
239: / header.getUnitsPerEm());
240: rect.setUpperRightX(header.getXMax() * 1000f
241: / header.getUnitsPerEm());
242: rect.setUpperRightY(header.getYMax() * 1000f
243: / header.getUnitsPerEm());
244: fd.setFontBoundingBox(rect);
245:
246: HorizontalHeaderTable hHeader = ttf.getHorizontalHeader();
247: fd.setAscent(hHeader.getAscender() * 1000f
248: / header.getUnitsPerEm());
249: fd.setDescent(hHeader.getDescender() * 1000f
250: / header.getUnitsPerEm());
251:
252: GlyphTable glyphTable = ttf.getGlyph();
253: GlyphData[] glyphs = glyphTable.getGlyphs();
254:
255: PostScriptTable ps = ttf.getPostScript();
256: fd.setFixedPitch(ps.getIsFixedPitch() > 0);
257: fd.setItalicAngle(ps.getItalicAngle());
258:
259: String[] names = ps.getGlyphNames();
260: if (names != null) {
261: for (int i = 0; i < names.length; i++) {
262: //if we have a capital H then use that, otherwise use the
263: //tallest letter
264: if (names[i].equals("H")) {
265: fd.setCapHeight((glyphs[i].getBoundingBox()
266: .getUpperRightY() * 1000f)
267: / header.getUnitsPerEm());
268: }
269: if (names[i].equals("x")) {
270: fd.setXHeight((glyphs[i].getBoundingBox()
271: .getUpperRightY() * 1000f)
272: / header.getUnitsPerEm());
273: }
274: }
275: }
276:
277: //hmm there does not seem to be a clear definition for StemV,
278: //this is close enough and I am told it doesn't usually get used.
279: fd.setStemV((fd.getFontBoundingBox().getWidth() * .13f));
280:
281: CMAPTable cmapTable = ttf.getCMAP();
282: CMAPEncodingEntry[] cmaps = cmapTable.getCmaps();
283: int[] glyphToCCode = null;
284: for (int i = 0; i < cmaps.length; i++) {
285: if (cmaps[i].getPlatformId() == CMAPTable.PLATFORM_WINDOWS
286: && cmaps[i].getPlatformEncodingId() == CMAPTable.ENCODING_UNICODE) {
287: glyphToCCode = cmaps[i].getGlyphIdToCharacterCode();
288: }
289: }
290: int firstChar = 0;
291: /**
292: for( int i=0; i<glyphToCCode.length; i++ )
293: {
294: if( glyphToCCode[i] != 0 )
295: {
296: firstChar = Math.min( glyphToCCode[i], firstChar );
297: }
298: }*/
299:
300: int maxWidths = 256;
301: HorizontalMetricsTable hMet = ttf.getHorizontalMetrics();
302: int[] widthValues = hMet.getAdvanceWidth();
303: List widths = new ArrayList(widthValues.length);
304: Integer zero = new Integer(250);
305: for (int i = 0; i < widthValues.length && i < maxWidths; i++) {
306: widths.add(zero);
307: }
308: for (int i = 0; i < widthValues.length; i++) {
309: if (glyphToCCode[i] - firstChar < widths.size()
310: && glyphToCCode[i] - firstChar >= 0
311: && widths.get(glyphToCCode[i] - firstChar) == zero) {
312: widths.set(glyphToCCode[i] - firstChar,
313: new Integer((int) (widthValues[i] * 1000f)
314: / header.getUnitsPerEm()));
315: }
316: }
317: retval.setWidths(widths);
318:
319: retval.setFirstChar(firstChar);
320: retval.setLastChar(firstChar + widths.size() - 1);
321:
322: } finally {
323: if (ttf != null) {
324: ttf.close();
325: }
326: }
327:
328: return retval;
329: }
330:
331: /**
332: * {@inheritDoc}
333: */
334: public void drawString(String string, Graphics g, float fontSize,
335: float xScale, float yScale, float x, float y)
336: throws IOException {
337: PDFontDescriptorDictionary fd = (PDFontDescriptorDictionary) getFontDescriptor();
338: if (awtFont == null) {
339: try {
340: PDStream ff2Stream = fd.getFontFile2();
341: String fontName = fd.getFontName();
342: if (ff2Stream != null) {
343: awtFont = Font.createFont(Font.TRUETYPE_FONT,
344: ff2Stream.createInputStream());
345: } else {
346: //throw new IOException( "Error:TTF Stream is null");
347: // Embedded true type programs are optional,
348: // if there is no stream, we must use an external
349: // file.
350: TrueTypeFont ttf = getExternalFontFile2(fd);
351: if (ttf != null) {
352: awtFont = Font.createFont(Font.TRUETYPE_FONT,
353: ttf.getOriginalData());
354: } else {
355: awtFont = Font.getFont(fontName, null);
356: }
357: }
358: } catch (FontFormatException f) {
359: throw new WrappedIOException(f);
360: }
361: }
362: AffineTransform at = new AffineTransform();
363: at.scale(xScale, yScale);
364: Graphics2D g2d = (Graphics2D) g;
365: g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
366: RenderingHints.VALUE_ANTIALIAS_ON);
367: g2d.setFont(awtFont.deriveFont(at).deriveFont(fontSize));
368: g2d.drawString(string, (int) x, (int) y);
369: }
370:
371: /**
372: * Permit to load an external TTF Font program file
373: *
374: * Created by Pascal Allain
375: * Vertical7 Inc.
376: *
377: * @param fd The font descriptor currently used
378: *
379: * @return A PDStream with the Font File program, null if fd is null
380: *
381: * @throws IOException If the font is not found
382: */
383: private TrueTypeFont getExternalFontFile2(
384: PDFontDescriptorDictionary fd) throws IOException {
385: TrueTypeFont retval = null;
386:
387: if (fd != null) {
388: String baseFont = getBaseFont();
389: String fontResource = externalFonts
390: .getProperty(UNKNOWN_FONT);
391: if ((baseFont != null)
392: && (externalFonts.containsKey(baseFont))) {
393: fontResource = externalFonts.getProperty(baseFont);
394: }
395: if (fontResource != null) {
396: retval = (TrueTypeFont) loadedExternalFonts
397: .get(baseFont);
398: if (retval == null) {
399: TTFParser ttfParser = new TTFParser();
400: InputStream fontStream = ResourceLoader
401: .loadResource(fontResource);
402: if (fontStream == null) {
403: throw new IOException(
404: "Error missing font resource '"
405: + externalFonts.get(baseFont)
406: + "'");
407: }
408: retval = ttfParser.parseTTF(fontStream);
409: loadedExternalFonts.put(baseFont, retval);
410: }
411: }
412: }
413:
414: return retval;
415: }
416: }
|