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.afm.AFMParser;
033:
034: import org.fontbox.afm.FontMetric;
035:
036: import org.fontbox.cmap.CMapParser;
037:
038: import org.fontbox.cmap.CMap;
039:
040: import org.pdfbox.encoding.AFMEncoding;
041: import org.pdfbox.encoding.DictionaryEncoding;
042: import org.pdfbox.encoding.Encoding;
043: import org.pdfbox.encoding.EncodingManager;
044:
045: import org.pdfbox.cos.COSArray;
046: import org.pdfbox.cos.COSBase;
047: import org.pdfbox.cos.COSDictionary;
048: import org.pdfbox.cos.COSFloat;
049: import org.pdfbox.cos.COSName;
050: import org.pdfbox.cos.COSNumber;
051: import org.pdfbox.cos.COSStream;
052:
053: import org.pdfbox.pdmodel.common.COSArrayList;
054: import org.pdfbox.pdmodel.common.COSObjectable;
055: import org.pdfbox.pdmodel.common.PDMatrix;
056: import org.pdfbox.pdmodel.common.PDRectangle;
057:
058: import org.pdfbox.util.ResourceLoader;
059:
060: import java.awt.Graphics;
061:
062: import java.io.BufferedReader;
063: import java.io.InputStream;
064: import java.io.InputStreamReader;
065: import java.io.IOException;
066:
067: import java.util.Collections;
068: import java.util.HashMap;
069: import java.util.List;
070: import java.util.Map;
071: import java.util.StringTokenizer;
072:
073: /**
074: * This is the base class for all PDF fonts.
075: *
076: * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
077: * @version $Revision: 1.43 $
078: */
079: public abstract class PDFont implements COSObjectable {
080:
081: /**
082: * The cos dictionary for this font.
083: */
084: protected COSDictionary font;
085:
086: /**
087: * This is only used if this is a font object and it has an encoding.
088: */
089: private Encoding fontEncoding = null;
090: /**
091: * This is only used if this is a font object and it has an encoding and it is
092: * a type0 font with a cmap.
093: */
094: private CMap cmap = null;
095:
096: private static Map afmResources = null;
097: private static Map cmapObjects = null;
098: private static Map afmObjects = null;
099: private static Map cmapSubstitutions = null;
100:
101: static {
102: //these are read-only once they are created
103: afmResources = new HashMap();
104: cmapSubstitutions = new HashMap();
105:
106: //these are read-write
107: cmapObjects = Collections.synchronizedMap(new HashMap());
108: afmObjects = Collections.synchronizedMap(new HashMap());
109:
110: afmResources.put(COSName.getPDFName("Courier-Bold"),
111: "Resources/afm/Courier-Bold.afm");
112: afmResources.put(COSName.getPDFName("Courier-BoldOblique"),
113: "Resources/afm/Courier-BoldOblique.afm");
114: afmResources.put(COSName.getPDFName("Courier"),
115: "Resources/afm/Courier.afm");
116: afmResources.put(COSName.getPDFName("Courier-Oblique"),
117: "Resources/afm/Courier-Oblique.afm");
118: afmResources.put(COSName.getPDFName("Helvetica"),
119: "Resources/afm/Helvetica.afm");
120: afmResources.put(COSName.getPDFName("Helvetica-Bold"),
121: "Resources/afm/Helvetica-Bold.afm");
122: afmResources.put(COSName.getPDFName("Helvetica-BoldOblique"),
123: "Resources/afm/Helvetica-BoldOblique.afm");
124: afmResources.put(COSName.getPDFName("Helvetica-Oblique"),
125: "Resources/afm/Helvetica-Oblique.afm");
126: afmResources.put(COSName.getPDFName("Symbol"),
127: "Resources/afm/Symbol.afm");
128: afmResources.put(COSName.getPDFName("Times-Bold"),
129: "Resources/afm/Times-Bold.afm");
130: afmResources.put(COSName.getPDFName("Times-BoldItalic"),
131: "Resources/afm/Times-BoldItalic.afm");
132: afmResources.put(COSName.getPDFName("Times-Italic"),
133: "Resources/afm/Times-Italic.afm");
134: afmResources.put(COSName.getPDFName("Times-Roman"),
135: "Resources/afm/Times-Roman.afm");
136: afmResources.put(COSName.getPDFName("ZapfDingbats"),
137: "Resources/afm/ZapfDingbats.afm");
138:
139: cmapSubstitutions.put("ETen-B5-H", "ETen-B5-UCS2");
140: cmapSubstitutions.put("ETen-B5-V", "ETen-B5-UCS2");
141: cmapSubstitutions.put("ETenms-B5-H", "ETen-B5-UCS2");
142: cmapSubstitutions.put("ETenms-B5-V", "ETen-B5-UCS2");
143:
144: cmapSubstitutions.put("90ms-RKSJ-H", "90ms-RKSJ-UCS2");
145: cmapSubstitutions.put("90ms-RKSJ-V", "90ms-RKSJ-UCS2");
146: cmapSubstitutions.put("90msp-RKSJ-H", "90ms-RKSJ-UCS2");
147: cmapSubstitutions.put("90msp-RKSJ-V", "90ms-RKSJ-UCS2");
148: cmapSubstitutions.put("GBK-EUC-H", "GBK-EUC-UCS2");
149: cmapSubstitutions.put("GBK-EUC-V", "GBK-EUC-UCS2");
150: cmapSubstitutions.put("GBpc-EUC-H", "GBpc-EUC-UCS2C");
151: cmapSubstitutions.put("GBpc-EUC-V", "GBpc-EUC-UCS2C");
152:
153: cmapSubstitutions.put("UniJIS-UCS2-HW-H", "UniJIS-UCS2-H");
154: }
155:
156: /**
157: * This will clear AFM resources that are stored statically.
158: * This is usually not a problem unless you want to reclaim
159: * resources for a long running process.
160: *
161: * SPECIAL NOTE: The font calculations are currently in COSObject, which
162: * is where they will reside until PDFont is mature enough to take them over.
163: * PDFont is the appropriate place for them and not in COSObject but we need font
164: * calculations for text extractaion. THIS METHOD WILL BE MOVED OR REMOVED
165: * TO ANOTHER LOCATION IN A FUTURE VERSION OF PDFBOX.
166: */
167: public static void clearResources() {
168: afmObjects.clear();
169: cmapObjects.clear();
170: }
171:
172: /**
173: * Constructor.
174: */
175: public PDFont() {
176: font = new COSDictionary();
177: font.setItem(COSName.TYPE, COSName.FONT);
178: }
179:
180: /**
181: * Constructor.
182: *
183: * @param fontDictionary The font dictionary according to the PDF specification.
184: */
185: public PDFont(COSDictionary fontDictionary) {
186: font = fontDictionary;
187: }
188:
189: /**
190: * {@inheritDoc}
191: */
192: public COSBase getCOSObject() {
193: return font;
194: }
195:
196: /**
197: * This will get the font width for a character.
198: *
199: * @param c The character code to get the width for.
200: * @param offset The offset into the array.
201: * @param length The length of the data.
202: *
203: * @return The width is in 1000 unit of text space, ie 333 or 777
204: *
205: * @throws IOException If an error occurs while parsing.
206: */
207: public abstract float getFontWidth(byte[] c, int offset, int length)
208: throws IOException;
209:
210: /**
211: * This will get the font width for a character.
212: *
213: * @param c The character code to get the width for.
214: * @param offset The offset into the array.
215: * @param length The length of the data.
216: *
217: * @return The width is in 1000 unit of text space, ie 333 or 777
218: *
219: * @throws IOException If an error occurs while parsing.
220: */
221: public abstract float getFontHeight(byte[] c, int offset, int length)
222: throws IOException;
223:
224: /**
225: * This will get the width of this string for this font.
226: *
227: * @param string The string to get the width of.
228: *
229: * @return The width of the string in 1000 units of text space, ie 333 567...
230: *
231: * @throws IOException If there is an error getting the width information.
232: */
233: public float getStringWidth(String string) throws IOException {
234: byte[] data = string.getBytes();
235: float totalWidth = 0;
236: for (int i = 0; i < data.length; i++) {
237: totalWidth += getFontWidth(data, i, 1);
238: }
239: return totalWidth;
240: }
241:
242: /**
243: * This will get the average font width for all characters.
244: *
245: * @return The width is in 1000 unit of text space, ie 333 or 777
246: *
247: * @throws IOException If an error occurs while parsing.
248: */
249: public abstract float getAverageFontWidth() throws IOException;
250:
251: /**
252: * This will draw a string on a canvas using the font.
253: *
254: * @param string The string to draw.
255: * @param g The graphics to draw onto.
256: * @param fontSize The size of the font to draw.
257: * @param xScale The x scaling percent.
258: * @param yScale The y scaling percent.
259: * @param x The x coordinate to draw at.
260: * @param y The y coordinate to draw at.
261: *
262: * @throws IOException If there is an error drawing the specific string.
263: */
264: public abstract void drawString(String string, Graphics g,
265: float fontSize, float xScale, float yScale, float x, float y)
266: throws IOException;
267:
268: /**
269: * Used for multibyte encodings.
270: *
271: * @param data The array of data.
272: * @param offset The offset into the array.
273: * @param length The number of bytes to use.
274: *
275: * @return The int value of data from the array.
276: */
277: protected int getCodeFromArray(byte[] data, int offset, int length) {
278: int code = 0;
279: for (int i = 0; i < length; i++) {
280: code <<= 8;
281: code |= (data[offset + i] + 256) % 256;
282: }
283: return code;
284: }
285:
286: /**
287: * This will attempt to get the font width from an AFM file.
288: *
289: * @param code The character code we are trying to get.
290: *
291: * @return The font width from the AFM file.
292: *
293: * @throws IOException if we cannot find the width.
294: */
295: protected float getFontWidthFromAFMFile(int code)
296: throws IOException {
297: float retval = 0;
298: FontMetric metric = getAFM();
299: if (metric != null) {
300: Encoding encoding = getEncoding();
301: COSName characterName = encoding.getName(code);
302: retval = metric.getCharacterWidth(characterName.getName());
303: }
304: return retval;
305: }
306:
307: /**
308: * This will attempt to get the average font width from an AFM file.
309: *
310: * @return The average font width from the AFM file.
311: *
312: * @throws IOException if we cannot find the width.
313: */
314: protected float getAverageFontWidthFromAFMFile() throws IOException {
315: float retval = 0;
316: FontMetric metric = getAFM();
317: if (metric != null) {
318: retval = metric.getAverageCharacterWidth();
319: }
320: return retval;
321: }
322:
323: /**
324: * This will get an AFM object if one exists.
325: *
326: * @return The afm object from the name.
327: *
328: * @throws IOException If there is an error getting the AFM object.
329: */
330: protected FontMetric getAFM() throws IOException {
331: COSName name = (COSName) font
332: .getDictionaryObject(COSName.BASE_FONT);
333: FontMetric result = null;
334: if (name != null) {
335: result = (FontMetric) afmObjects.get(name);
336: if (result == null) {
337: String resource = (String) afmResources.get(name);
338: if (resource == null) {
339: //ok for now
340: //throw new IOException( "Unknown AFM font '" + name.getName() + "'" );
341: } else {
342: InputStream afmStream = ResourceLoader
343: .loadResource(resource);
344: if (afmStream == null) {
345: throw new IOException(
346: "Can't handle font width:" + resource);
347: }
348: AFMParser parser = new AFMParser(afmStream);
349: parser.parse();
350: result = parser.getResult();
351: afmObjects.put(name, result);
352: }
353: }
354: }
355: return result;
356: }
357:
358: /**
359: * This will perform the encoding of a character if needed.
360: *
361: * @param c The character to encode.
362: * @param offset The offset into the array to get the data
363: * @param length The number of bytes to read.
364: *
365: * @return The value of the encoded character.
366: *
367: * @throws IOException If there is an error during the encoding.
368: */
369: public String encode(byte[] c, int offset, int length)
370: throws IOException {
371: String retval = null;
372: COSName fontSubtype = (COSName) font
373: .getDictionaryObject(COSName.SUBTYPE);
374: String fontSubtypeName = fontSubtype.getName();
375: if (fontSubtypeName.equals("Type0")
376: || fontSubtypeName.equals("Type1")
377: || fontSubtypeName.equals("TrueType")) {
378: if (cmap == null) {
379: if (font.getDictionaryObject(COSName.TO_UNICODE) != null) {
380: COSStream toUnicode = (COSStream) font
381: .getDictionaryObject(COSName.TO_UNICODE);
382: if (toUnicode != null) {
383: parseCmap(toUnicode.getUnfilteredStream(), null);
384: }
385: } else {
386: COSBase encoding = font
387: .getDictionaryObject(COSName.ENCODING);
388: if (encoding instanceof COSStream) {
389: COSStream encodingStream = (COSStream) encoding;
390: parseCmap(encodingStream.getUnfilteredStream(),
391: null);
392: } else if (fontSubtypeName.equals("Type0")
393: && encoding instanceof COSName) {
394: COSName encodingName = (COSName) encoding;
395: cmap = (CMap) cmapObjects.get(encodingName);
396: if (cmap != null) {
397: cmap = (CMap) cmapObjects.get(encodingName);
398: } else {
399: String cmapName = encodingName.getName();
400: cmapName = performCMAPSubstitution(cmapName);
401: String resourceName = "Resources/cmap/"
402: + cmapName;
403: parseCmap(ResourceLoader
404: .loadResource(resourceName),
405: encodingName);
406: if (cmap == null
407: && !encodingName.getName().equals(
408: COSName.IDENTITY_H
409: .getName())) {
410: throw new IOException(
411: "Error: Could not find predefined "
412: + "CMAP file for '"
413: + encodingName
414: .getName()
415: + "'");
416: }
417: }
418: } else if (encoding instanceof COSName
419: || encoding instanceof COSDictionary) {
420: Encoding currentFontEncoding = getEncoding();
421: if (currentFontEncoding != null) {
422: retval = currentFontEncoding
423: .getCharacter(getCodeFromArray(c,
424: offset, length));
425: }
426: } else {
427: COSDictionary fontDescriptor = (COSDictionary) font
428: .getDictionaryObject(COSName.FONT_DESC);
429: if (fontSubtypeName.equals("TrueType")
430: && fontDescriptor != null
431: && (fontDescriptor
432: .getDictionaryObject(COSName.FONT_FILE) != null
433: || fontDescriptor
434: .getDictionaryObject(COSName.FONT_FILE2) != null || fontDescriptor
435: .getDictionaryObject(COSName.FONT_FILE3) != null)) {
436: //If we are using an embedded font then there is not much we can do besides
437: //return the same character codes.
438: //retval = new String( c,offset, length );
439: retval = getStringFromArray(c, offset,
440: length);
441: } else {
442: //this case will be handled below after checking the cmap
443: }
444: }
445: }
446:
447: }
448: }
449: if (retval == null && cmap != null) {
450: retval = cmap.lookup(c, offset, length);
451: }
452: //if we havn't found a value yet and
453: //we are still on the first byte and
454: //there is no cmap or the cmap does not have 2 byte mappings then try to encode
455: //using fallback methods.
456: if (retval == null && length == 1
457: && (cmap == null || !cmap.hasTwoByteMappings())) {
458: Encoding encoding = getEncoding();
459: if (encoding != null) {
460: retval = encoding.getCharacter(getCodeFromArray(c,
461: offset, length));
462: }
463: if (retval == null) {
464: retval = getStringFromArray(c, offset, length);
465: }
466: }
467: return retval;
468: }
469:
470: private static final String[] SINGLE_CHAR_STRING = new String[256];
471: private static final String[][] DOUBLE_CHAR_STRING = new String[256][256];
472: static {
473: for (int i = 0; i < 256; i++) {
474: SINGLE_CHAR_STRING[i] = new String(new byte[] { (byte) i });
475: for (int j = 0; j < 256; j++) {
476: DOUBLE_CHAR_STRING[i][j] = new String(new byte[] {
477: (byte) i, (byte) j });
478: }
479: }
480: }
481:
482: private static String getStringFromArray(byte[] c, int offset,
483: int length) throws IOException {
484: String retval = null;
485: if (length == 1) {
486: retval = SINGLE_CHAR_STRING[(c[offset] + 256) % 256];
487: } else if (length == 2) {
488: retval = DOUBLE_CHAR_STRING[(c[offset] + 256) % 256][(c[offset + 1] + 256) % 256];
489: } else {
490: throw new IOException("Error:Unknown character length:"
491: + length);
492: }
493: return retval;
494: }
495:
496: /**
497: * Some cmap names are synonyms for other CMAPs. If that is the case
498: * then this method will perform that substitution.
499: *
500: * @param cmapName The name of the cmap to attempt to look up.
501: *
502: * @return Either the original name or the substituted name.
503: */
504: private String performCMAPSubstitution(String cmapName) {
505: String retval = (String) cmapSubstitutions.get(cmapName);
506: if (retval == null) {
507: //if there is no substitution then just return the same value.
508: retval = cmapName;
509: }
510: return retval;
511: }
512:
513: private void parseCmap(InputStream cmapStream, COSName encodingName)
514: throws IOException {
515: if (cmapStream != null) {
516: CMapParser parser = new CMapParser();
517: cmap = parser.parse(cmapStream);
518: if (encodingName != null) {
519: cmapObjects.put(encodingName, cmap);
520: }
521: }
522: }
523:
524: /**
525: * The will set the encoding for this font.
526: *
527: * @param enc The font encoding.
528: */
529: public void setEncoding(Encoding enc) {
530: font.setItem(COSName.ENCODING, enc);
531: fontEncoding = enc;
532: }
533:
534: /**
535: * This will get or create the encoder.
536: *
537: * modified by Christophe Huault : DGBS Strasbourg huault@free.fr october 2004
538: *
539: * @return The encoding to use.
540: *
541: * @throws IOException If there is an error getting the encoding.
542: */
543: public Encoding getEncoding() throws IOException {
544: EncodingManager manager = new EncodingManager();
545: if (fontEncoding == null) {
546: COSBase encoding = font
547: .getDictionaryObject(COSName.ENCODING);
548: if (encoding == null) {
549: FontMetric metric = getAFM();
550: if (metric != null) {
551: fontEncoding = new AFMEncoding(metric);
552: }
553: if (fontEncoding == null) {
554: fontEncoding = manager.getStandardEncoding();
555: }
556: }
557: /**
558: * Si la clé /Encoding existe dans le dictionnaire fonte il y a deux possibilités :
559: * 1er cas : elle est associé à une reference contenant un dictionnaire de type encoding.
560: * Ce dictionnaire PDF est représenté par un DictionaryEncoding.
561: * If the /Encoding Key does exist in the font dictionary, there are two cases :
562: * case one : The value associated with /Encoding is a reference to a dictionary.
563: * This dictionary is represented by an instance of DictionaryEncoding class
564: */
565: else if (encoding instanceof COSDictionary) {
566: COSDictionary encodingDic = (COSDictionary) encoding;
567: //Let's see if the encoding dictionary has a base encoding
568: //If it does not then we will attempt to get it from the font
569: //file
570: COSName baseEncodingName = (COSName) encodingDic
571: .getDictionaryObject(COSName.BASE_ENCODING);
572: //on ajoute une entrée /BaseEncoding dans /Encoding uniquement si elle en est absente
573: //if not find in Encoding dictinary target, we try to find it from else where
574: if (baseEncodingName == null) {
575: COSName fontEncodingFromFile = getEncodingFromFont();
576: encodingDic.setItem(COSName.BASE_ENCODING,
577: fontEncodingFromFile);
578: }
579: fontEncoding = new DictionaryEncoding(encodingDic);
580: } else if (encoding instanceof COSName) {
581: if (!encoding.equals(COSName.IDENTITY_H)) {
582: fontEncoding = manager
583: .getEncoding((COSName) encoding);
584: }
585: } else {
586: throw new IOException("Unexpected encoding type:"
587: + encoding.getClass().getName());
588: }
589: }
590: return fontEncoding;
591: }
592:
593: /**
594: * This will always return "Font" for fonts.
595: *
596: * @return The type of object that this is.
597: */
598: public String getType() {
599: return font.getNameAsString(COSName.TYPE);
600: }
601:
602: /**
603: * This will get the subtype of font, Type1, Type3, ...
604: *
605: * @return The type of font that this is.
606: */
607: public String getSubType() {
608: return font.getNameAsString(COSName.SUBTYPE);
609: }
610:
611: /**
612: * The PostScript name of the font.
613: *
614: * @return The postscript name of the font.
615: */
616: public String getBaseFont() {
617: return font.getNameAsString(COSName.BASE_FONT);
618: }
619:
620: /**
621: * Set the PostScript name of the font.
622: *
623: * @param baseFont The postscript name for the font.
624: */
625: public void setBaseFont(String baseFont) {
626: font.setName(COSName.BASE_FONT, baseFont);
627: }
628:
629: /**
630: * The code for the first char or -1 if there is none.
631: *
632: * @return The code for the first character.
633: */
634: public int getFirstChar() {
635: return font.getInt(COSName.FIRST_CHAR, -1);
636: }
637:
638: /**
639: * Set the first character this font supports.
640: *
641: * @param firstChar The first character.
642: */
643: public void setFirstChar(int firstChar) {
644: font.setInt(COSName.FIRST_CHAR, firstChar);
645: }
646:
647: /**
648: * The code for the last char or -1 if there is none.
649: *
650: * @return The code for the last character.
651: */
652: public int getLastChar() {
653: return font.getInt(COSName.LAST_CHAR, -1);
654: }
655:
656: /**
657: * Set the last character this font supports.
658: *
659: * @param lastChar The last character.
660: */
661: public void setLastChar(int lastChar) {
662: font.setInt(COSName.LAST_CHAR, lastChar);
663: }
664:
665: /**
666: * The widths of the characters. This will be null for the standard 14 fonts.
667: *
668: * @return The widths of the characters.
669: */
670: public List getWidths() {
671: COSArray array = (COSArray) font
672: .getDictionaryObject(COSName.WIDTHS);
673: return COSArrayList.convertFloatCOSArrayToList(array);
674: }
675:
676: /**
677: * Set the widths of the characters code.
678: *
679: * @param widths The widths of the character codes.
680: */
681: public void setWidths(List widths) {
682: font.setItem(COSName.WIDTHS, COSArrayList
683: .converterToCOSArray(widths));
684: }
685:
686: /**
687: * This will get the matrix that is used to transform glyph space to
688: * text space. By default there are 1000 glyph units to 1 text space
689: * unit, but type3 fonts can use any value.
690: *
691: * Note:If this is a type3 font then it can be modified via the PDType3Font.setFontMatrix, otherwise this
692: * is a read-only property.
693: *
694: * @return The matrix to transform from glyph space to text space.
695: */
696: public PDMatrix getFontMatrix() {
697: PDMatrix matrix = null;
698: COSArray array = (COSArray) font
699: .getDictionaryObject(COSName.FONT_MATRIX);
700: if (array == null) {
701: array = new COSArray();
702: array.add(new COSFloat(0.001f));
703: array.add(COSNumber.ZERO);
704: array.add(COSNumber.ZERO);
705: array.add(new COSFloat(0.001f));
706: array.add(COSNumber.ZERO);
707: array.add(COSNumber.ZERO);
708: }
709: matrix = new PDMatrix(array);
710:
711: return matrix;
712: }
713:
714: /**
715: * Try to get the encoding for the font and add it to the target
716: * the target must be an an Encoding Dictionary.
717: *
718: * added by Christophe Huault : DGBS Strasbourg huault@free.fr october 2004
719: *
720: * @return The encoding from the font.
721: *
722: * @throws IOException If there is an error reading the file.
723: */
724: private COSName getEncodingFromFont() throws IOException {
725: //This whole section of code needs to be replaced with an actual
726: //type1 font parser!!
727:
728: COSName retvalue = null;
729: //recuperer le programme de fonte dans son stream qui doit se trouver
730: //dans le flux référencé par à la clé FileFont lui même situé dans
731: //le dictionnaire associé à /FontDescriptor du dictionnaire de type /Font courrant
732: //get the font program in the stream which should be located in
733: //the /FileFont Stream object himself in the /FontDescriptior of the current
734: //font dictionary
735: COSDictionary fontDescriptor = (COSDictionary) font
736: .getDictionaryObject(COSName.FONT_DESC);
737: if (fontDescriptor != null) {
738: COSStream fontFile = (COSStream) fontDescriptor
739: .getDictionaryObject(COSName.FONT_FILE);
740: if (fontFile != null) {
741: BufferedReader in = new BufferedReader(
742: new InputStreamReader(fontFile
743: .getUnfilteredStream()));
744: /**
745: * this section parse the FileProgram stream searching for a /Encoding entry
746: * the research stop if the entry "currentdict end" is reach or after 100 lignes
747: */
748: StringTokenizer st = null;
749: boolean found = false;
750: String line = "";
751: String key = null;
752: for (int i = 0; null != (line = in.readLine())
753: && i < 40 && !line.equals("currentdict end")
754: && !found; i++) {
755: st = new StringTokenizer(line);
756: if (st.hasMoreTokens()) {
757: key = st.nextToken();
758: if (key.equals("/Encoding")
759: && st.hasMoreTokens()) {
760: COSName value = COSName.getPDFName(st
761: .nextToken());
762: found = true;
763: if (value
764: .equals(COSName.MAC_ROMAN_ENCODING)
765: || value
766: .equals(COSName.PDF_DOC_ENCODING)
767: || value
768: .equals(COSName.STANDARD_ENCODING)
769: || value
770: .equals(COSName.WIN_ANSI_ENCODING)) {
771: //value is expected to be one of the encodings
772: //ie. StandardEncoding,WinAnsiEncoding,MacRomanEncoding,PDFDocEncoding
773: retvalue = value;
774: }
775: }
776: }
777: }
778: }
779: }
780: return retvalue;
781: }
782:
783: /**
784: * This will get the fonts bouding box.
785: *
786: * @return The fonts bouding box.
787: *
788: * @throws IOException If there is an error getting the bounding box.
789: */
790: public abstract PDRectangle getFontBoundingBox() throws IOException;
791:
792: /**
793: * {@inheritDoc}
794: */
795: public boolean equals(Object other) {
796: return other instanceof PDFont
797: && ((PDFont) other).getCOSObject() == this
798: .getCOSObject();
799: }
800:
801: /**
802: * {@inheritDoc}
803: */
804: public int hashCode() {
805: return this.getCOSObject().hashCode();
806: }
807: }
|