001: /*
002: * Copyright (c) 2007, intarsys consulting GmbH
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * - Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * - 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: *
014: * - Neither the name of intarsys nor the names of its contributors may be used
015: * to endorse or promote products derived from this software without specific
016: * prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
022: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
023: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
024: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
025: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
026: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
027: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
028: * POSSIBILITY OF SUCH DAMAGE.
029: */
030: package de.intarsys.pdf.font;
031:
032: import java.util.Arrays;
033: import java.util.Iterator;
034: import de.intarsys.font.FontStyle;
035: import de.intarsys.font.IFont;
036: import de.intarsys.pdf.cos.COSArray;
037: import de.intarsys.pdf.cos.COSBasedObject;
038: import de.intarsys.pdf.cos.COSDictionary;
039: import de.intarsys.pdf.cos.COSName;
040: import de.intarsys.pdf.cos.COSNumber;
041: import de.intarsys.pdf.cos.COSObject;
042: import de.intarsys.pdf.encoding.Encoding;
043: import de.intarsys.pdf.encoding.MacOSRomanEncoding;
044: import de.intarsys.pdf.encoding.StandardEncoding;
045: import de.intarsys.pdf.pd.PDObject;
046:
047: /**
048: * A PDF font object.
049: */
050: public abstract class PDFont extends PDObject implements IFont {
051: /**
052: * The meta class implementation
053: */
054: public static class MetaClass extends PDObject.MetaClass {
055: protected MetaClass(Class instanceClass) {
056: super (instanceClass);
057: }
058:
059: /*
060: * (non-Javadoc)
061: *
062: * @see de.intarsys.pdf.cos.COSBasedObject.MetaClass#doDetermineClass(de.intarsys.pdf.cos.COSObject)
063: */
064: protected COSBasedObject.MetaClass doDetermineClass(
065: COSObject object) {
066: COSDictionary dict;
067:
068: dict = object.asDictionary();
069: COSName type = dict.get(DK_Type).asName();
070: if (type == null) {
071: throw new IllegalArgumentException(
072: "Dictionary has no type");
073: }
074: if (!type.equals(CN_Type_Font)) {
075: throw new IllegalArgumentException("type <" + type
076: + "> is not a valid font type");
077: }
078: COSName subtype = dict.get(DK_Subtype).asName();
079: if (subtype == null) {
080: throw new IllegalArgumentException(
081: "font not identified by subtype");
082: }
083: if (subtype.equals(CN_Subtype_Type1)) {
084: return PDFontType1.META;
085: } else if (subtype.equals(CN_Subtype_TrueType)) {
086: if (dict.get(DK_FontDescriptor).isNull()) {
087: /*
088: * treat as if Type1 was specified, because that's probably
089: * what the creator meant; further processing would yield
090: * wrong results anyway as FontDescriptor is a required
091: * entry for TrueType fonts
092: */
093: return PDFontType1.META;
094: }
095: return PDFontTrueType.META;
096: } else if (subtype.equals(CN_Subtype_MMType1)) {
097: return PDFontMMType1.META;
098: } else if (subtype.equals(CN_Subtype_Type0)) {
099: return PDFontType0.META;
100: } else if (subtype.equals(CN_Subtype_Type3)) {
101: return PDFontType3.META;
102: } else if (subtype.equals(CN_Subtype_CIDFontType0)) {
103: return CIDFontType0.META;
104: } else if (subtype.equals(CN_Subtype_CIDFontType2)) {
105: return CIDFontType2.META;
106: }
107: throw new IllegalArgumentException("font subtype <"
108: + subtype + "> not supported");
109: }
110:
111: public Class getRootClass() {
112: return PDFont.class;
113: }
114: }
115:
116: public static final COSName CN_Subtype_CIDFontType0 = COSName
117: .constant("CIDFontType0"); //$NON-NLS-1$
118:
119: public static final COSName CN_Subtype_CIDFontType2 = COSName
120: .constant("CIDFontType2"); //$NON-NLS-1$
121:
122: public static final COSName CN_Subtype_MMType1 = COSName
123: .constant("MMType1"); //$NON-NLS-1$
124:
125: public static final COSName CN_Subtype_TrueType = COSName
126: .constant("TrueType"); //$NON-NLS-1$
127:
128: public static final COSName CN_Subtype_Type0 = COSName
129: .constant("Type0"); //$NON-NLS-1$
130:
131: public static final COSName CN_Subtype_Type1 = COSName
132: .constant("Type1"); //$NON-NLS-1$
133:
134: public static final COSName CN_Subtype_Type3 = COSName
135: .constant("Type3"); //$NON-NLS-1$
136:
137: public static final COSName CN_Type_Font = COSName.constant("Font"); //$NON-NLS-1$
138:
139: public static final COSName DK_BaseFont = COSName
140: .constant("BaseFont"); //$NON-NLS-1$
141:
142: public static final COSName DK_Encoding = COSName
143: .constant("Encoding"); //$NON-NLS-1$
144:
145: public static final COSName DK_FirstChar = COSName
146: .constant("FirstChar"); //$NON-NLS-1$
147:
148: public static final COSName DK_FontDescriptor = COSName
149: .constant("FontDescriptor"); //$NON-NLS-1$
150:
151: public static final COSName DK_LastChar = COSName
152: .constant("LastChar"); //$NON-NLS-1$
153:
154: public static final COSName DK_ToUnicode = COSName
155: .constant("ToUnicode"); //$NON-NLS-1$
156:
157: public static final COSName DK_Widths = COSName.constant("Widths"); //$NON-NLS-1$
158:
159: public static final COSName DK_Name = COSName.constant("Name"); //$NON-NLS-1$
160:
161: /** The meta class instance */
162: public static final MetaClass META = new MetaClass(MetaClass.class
163: .getDeclaringClass());
164:
165: public static String getFontFamilyName(String name) {
166: if (name == null) {
167: return null;
168: }
169: int posPlus = name.indexOf('+');
170: if (posPlus > 0) {
171: name = name.substring(posPlus + 1);
172: }
173: int posMinus = name.lastIndexOf('-');
174: if (posMinus > 0) {
175: name = name.substring(0, posMinus);
176: }
177: return name;
178: }
179:
180: /**
181: * extracts the "name" portion from the given font name string
182: *
183: * @param name
184: * a font name
185: *
186: * @return font name's "name" portion
187: */
188: public static String getFontName(String name) {
189: if (name == null) {
190: return null;
191: }
192: int posPlus = name.indexOf('+');
193: if (posPlus > 0) {
194: name = name.substring(posPlus + 1);
195: }
196: return name;
197: }
198:
199: /**
200: * extracts the "style" portion from the given font name
201: *
202: * @param name
203: * a font name
204: *
205: * @return font name's "style" portion
206: */
207: public static FontStyle getFontStyle(String name) {
208: if (name == null) {
209: return FontStyle.REGULAR;
210: }
211: int posMinus = name.lastIndexOf('-');
212: if (posMinus > 0) {
213: name = name.substring(posMinus + 1);
214: }
215: return FontStyle.getFontStyle(name);
216: }
217:
218: /**
219: * Flags for every codepoint if the corresponding character is used in the
220: * document.
221: *
222: * <p>
223: * If not, a serializer could decide to embedd a font partially.
224: * </p>
225: */
226: private boolean[] charUsed = new boolean[256];
227:
228: // the encoding used for this font
229: private Encoding encoding;
230:
231: // some detail information about the font
232: private PDFontDescriptor fontDescriptor;
233:
234: // an array for the width of each glyph used
235: private int[] widths;
236:
237: /**
238: * Create the receiver class from an already defined {@link COSDictionary}.
239: * NEVER use the constructor directly.
240: *
241: * @param object
242: * the PDDocument containing the new object
243: */
244: protected PDFont(COSObject object) {
245: super (object);
246: Arrays.fill(charUsed, true);
247: }
248:
249: public void compress() {
250: // nothing to do
251: }
252:
253: /**
254: * The font descriptor for a builtin font.
255: *
256: * @return The font descriptor for a builtin font
257: *
258: * @throws IllegalStateException
259: */
260: protected PDFontDescriptor createBuiltinFontDescriptor() {
261: // this may happen, there are strange documents around that depend on
262: // certain TrueTypes to be present.
263: // Note from EHK: I'm treating some strange TrueTypes as Type1 now, so
264: // maybe doesn't happen anymore
265: return null;
266: }
267:
268: /**
269: * Fill the correct width values into an array of glyph widths for a builtin
270: * font. This is a valid implementation for type1 builtin fonts only.
271: *
272: * @param result
273: * The array to hold the glyph widths.
274: *
275: * @return The array of widths for the defined range of chars in the font
276: */
277: protected int[] createBuiltInWidths(int[] result) {
278: return result;
279: }
280:
281: /**
282: * Fill an array of glyph widths from the definition prepared by the font
283: * dictionary. The widths in the font are declared in the range from the
284: * first supported code point to the last code point. The code point selects
285: * a glyph out of the font depending on the encoding by the font, the
286: * corresponding entry in the widt array defines its width.
287: *
288: * @param result
289: * The array to hold the correct widths.
290: * @param array
291: * The COSArray defining the widths.
292: *
293: * @return The array of widths for the defined range of chars in the font
294: */
295: protected int[] createDeclaredWidths(int[] result, COSArray array) {
296: int i = getFirstChar();
297: for (Iterator it = array.iterator(); it.hasNext();) {
298: COSNumber width = ((COSObject) it.next()).asNumber();
299: if (width != null) {
300: result[i] = width.intValue();
301: }
302: i++;
303: }
304: return result;
305: }
306:
307: /**
308: * get an encoding object that describes this fonts NATIVE encoding (if any)
309: *
310: * @return an encoding
311: */
312: protected Encoding createDefaultEncoding() {
313: return StandardEncoding.UNIQUE;
314: }
315:
316: /**
317: * Create the encoding for the font. The encoding is specified either "by
318: * default", as a known encoding name or a completely user defined
319: * difference encoding.
320: *
321: * @return The encoding object for the font.
322: *
323: * @throws IllegalArgumentException
324: * When the encoding defined in the font is not supported.
325: */
326: protected Encoding createEncoding() {
327: COSObject base = cosGetField(PDFont.DK_Encoding);
328: if (base.isNull()) {
329: return createDefaultEncoding();
330: }
331: if (base instanceof COSName) {
332: return Encoding.create((COSName) base);
333: }
334: if (base instanceof COSDictionary) {
335: return DifferenceEncoding
336: .create((COSDictionary) base, this );
337: }
338:
339: // todo 1 cmap support stream CMaps for Type 0 fonts
340: throw new IllegalArgumentException("encoding not supported");
341: }
342:
343: protected int createFirstChar() {
344: return 0;
345: }
346:
347: /**
348: * @return the lazily created font descriptor of this font
349: */
350: protected PDFontDescriptor createFontDescriptor() {
351: COSObject base = cosGetField(DK_FontDescriptor);
352: if (base.isNull()) {
353: return createBuiltinFontDescriptor();
354: }
355: return (PDFontDescriptorEmbedded) PDFontDescriptorEmbedded.META
356: .createFromCos(base);
357: }
358:
359: protected int createLastChar() {
360: return 255;
361: }
362:
363: /**
364: * construct a array of glyph widths for the current font the widths may be
365: * defined in the /Widths entry of the pdf font or in the font metric (afm)
366: * of a builtin font
367: *
368: * @return the array of widths for the defined range of chars in the font
369: */
370: protected int[] createWidths() {
371: int[] result = new int[256];
372: int missing = getMissingWidth();
373: for (int i = 0; i < 256; i++) {
374: result[i] = missing;
375: }
376:
377: COSArray base = cosGetField(DK_Widths).asArray();
378: if (base == null) {
379: return createBuiltInWidths(result);
380: }
381: return createDeclaredWidths(result, base);
382: }
383:
384: protected void dump() {
385: // do nothing
386: }
387:
388: /**
389: * @return the base font for this font dictionary
390: */
391: public COSName getBaseFont() {
392: return cosGetField(DK_BaseFont).asName();
393: }
394:
395: /**
396: * The encoding of the glyphs in the font
397: *
398: * @return The encoding of the glyphs in the font
399: */
400: public Encoding getEncoding() {
401: if (encoding == null) {
402: encoding = createEncoding();
403: }
404: return encoding;
405: }
406:
407: /**
408: * The first codepoint defined in the font.
409: *
410: * @return The first codepoint defined in the font
411: */
412: public int getFirstChar() {
413: COSNumber base = cosGetField(DK_FirstChar).asInteger();
414: if (base == null) {
415: return createFirstChar();
416: }
417: return base.intValue();
418: }
419:
420: /**
421: * @return the font descriptor object for this font
422: */
423: public PDFontDescriptor getFontDescriptor() {
424: if (fontDescriptor == null) {
425: fontDescriptor = createFontDescriptor();
426: }
427: return fontDescriptor;
428: }
429:
430: /**
431: * return the glyph width of a codepoint in the receiver font
432: *
433: * @param codePoint
434: * the index of the glyph in the font
435: *
436: * @return the width of the glyph
437: */
438: public int getGlyphWidth(int codePoint) {
439: return getGlyphWidths()[codePoint];
440: }
441:
442: /**
443: *
444: * @return the array of defined widths for the font
445: */
446: public int[] getGlyphWidths() {
447: if (widths == null) {
448: widths = createWidths();
449: }
450: return widths;
451: }
452:
453: /**
454: * @return the last codepoint defined in the font
455: */
456: public int getLastChar() {
457: COSNumber base = cosGetField(DK_LastChar).asInteger();
458: if (base == null) {
459: return createLastChar();
460: }
461: return base.intValue();
462: }
463:
464: /**
465: * This is a special mapping that is used if we have a font on the physical
466: * device using a Macintosh Roman encoding character map.
467: *
468: * <p>
469: * See PDF docs, "Encodings for True Type fonts".
470: * </p>
471: *
472: * @param codePoint
473: *
474: * @return The unicode value for <code>codePoint</code>
475: */
476: public int getMacintoshRomanCode(int codePoint) {
477: String glyphName = getEncoding().getGlyphName(codePoint);
478: return MacOSRomanEncoding.UNIQUE.getByteCode(glyphName);
479: }
480:
481: /**
482: * @return the width we should use for a missing/undefined glyph width
483: */
484: public int getMissingWidth() {
485: if (getFontDescriptor() == null) {
486: return 0;
487: }
488: return getFontDescriptor().getMissingWidth();
489: }
490:
491: public abstract CID getNextCID(byte[] bytes, int offset);
492:
493: public CMap getToUnicode() {
494: return (CMap) CMap.META
495: .createFromCos(cosGetField(DK_ToUnicode));
496: }
497:
498: /*
499: * (non-Javadoc)
500: *
501: * @see de.intarsys.pdf.pd.PDObject#cosGetExpectedType()
502: */
503: protected COSName cosGetExpectedType() {
504: return CN_Type_Font;
505: }
506:
507: public abstract float getUnderlinePosition();
508:
509: public abstract int getUnderlineThickness();
510:
511: public int getUnicode(int byteCode) {
512: CMap toUnicode = getToUnicode();
513: if (toUnicode == null) {
514: return getEncoding().getUnicode(byteCode);
515: }
516: CID cid = toUnicode.lookup(new byte[] { (byte) byteCode });
517: return cid.getValue();
518: }
519:
520: public boolean isCharUsed(int c) {
521: if ((c < 0) || (c > 255)) {
522: return false;
523: }
524: return charUsed[c];
525: }
526:
527: /**
528: * Answer true if this font's program is embedded within the document.
529: *
530: * @return Answer true if this font's program is embedded within the
531: * document.
532: */
533: public boolean isEmbedded() {
534: // standard fonts can be embedded
535: // if (isStandardFont()) {
536: // return false;
537: // }
538: if (getFontDescriptor() == null) {
539: return false;
540: }
541: if (getFontDescriptor().getFontFile() != null) {
542: return true;
543: }
544: if (getFontDescriptor().getFontFile2() != null) {
545: return true;
546: }
547: if (getFontDescriptor().getFontFile3() != null) {
548: return true;
549: }
550: return false;
551: }
552:
553: /**
554: * Answer true if this is one of the 14 standard fonts. TODO 2 implement
555: *
556: * @return Answer true if this is one of the 14 standard fonts.
557: */
558: public boolean isStandardFont() {
559: return false;
560: }
561:
562: /**
563: * Answer true if this font is partially embedded in the document.
564: *
565: * @return Answer true if this font is partially embedded in the document.
566: */
567: public boolean isSubset() {
568: byte[] name = getBaseFont().byteValue();
569: if (name.length > 7) {
570: return name[6] == '+';
571: }
572: return false;
573: }
574:
575: public void setBaseFont(String name) {
576: setFieldName(DK_BaseFont, name);
577: }
578:
579: public void setCharUsed(int c) {
580: if ((c < 0) || (c > 255)) {
581: return;
582: }
583: charUsed[c] = true;
584: }
585:
586: /**
587: * set an encoding for the font
588: *
589: * @param newFontEncoding
590: * the new encoding to use
591: */
592: public void setEncoding(Encoding newFontEncoding) {
593: encoding = newFontEncoding;
594: if (newFontEncoding != null) {
595: COSObject ref = encoding.getCosObject();
596: if (ref != null) {
597: cosSetField(DK_Encoding, ref);
598: }
599: } else {
600: cosRemoveField(DK_Encoding);
601: }
602: }
603:
604: public void setFontDescriptor(PDFontDescriptor descriptor) {
605: fontDescriptor = descriptor;
606: setFieldObject(DK_FontDescriptor, fontDescriptor);
607: }
608:
609: public String toString() {
610: return cosGetSubtype().stringValue() + "-Font "
611: + getBaseFont().toString() + " (" + getEncoding() + ")";
612: }
613: }
|