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.io.ByteArrayOutputStream;
033: import java.io.FileOutputStream;
034: import java.io.IOException;
035: import java.util.HashSet;
036: import java.util.Map;
037: import java.util.Set;
038: import de.intarsys.font.FontStyle;
039: import de.intarsys.font.truetype.TTFont;
040: import de.intarsys.font.truetype.TTFontHeader;
041: import de.intarsys.font.truetype.TTFontParser;
042: import de.intarsys.font.truetype.TTFontSerializer;
043: import de.intarsys.font.truetype.TTHorizontalHeader;
044: import de.intarsys.font.truetype.TTMetrics;
045: import de.intarsys.font.truetype.TTRegistry;
046: import de.intarsys.font.truetype.TrueTypeException;
047: import de.intarsys.pdf.cds.CDSRectangle;
048: import de.intarsys.pdf.cos.COSArray;
049: import de.intarsys.pdf.cos.COSBasedObject;
050: import de.intarsys.pdf.cos.COSDictionary;
051: import de.intarsys.pdf.cos.COSInteger;
052: import de.intarsys.pdf.cos.COSName;
053: import de.intarsys.pdf.cos.COSObject;
054: import de.intarsys.pdf.cos.COSRuntimeException;
055: import de.intarsys.pdf.cos.COSStream;
056: import de.intarsys.pdf.encoding.Encoding;
057: import de.intarsys.pdf.encoding.SymbolEncoding;
058: import de.intarsys.pdf.encoding.WinAnsiEncoding;
059: import de.intarsys.tools.locator.ByteArrayLocator;
060: import de.intarsys.tools.locator.ILocator;
061:
062: /**
063: * basic implementation for true type support
064: *
065: * <p>
066: * todo 2 review initialization (sequence problems)
067: * </p>
068: */
069: public class PDFontTrueType extends PDFont {
070: /**
071: * The meta class implementation
072: */
073: public static class MetaClass extends PDFont.MetaClass {
074: protected MetaClass(Class instanceClass) {
075: super (instanceClass);
076: }
077:
078: protected COSBasedObject doCreateCOSBasedObject(COSObject object) {
079: return new PDFontTrueType(object);
080: }
081: }
082:
083: /** The meta class instance */
084: public static final MetaClass META = new MetaClass(MetaClass.class
085: .getDeclaringClass());
086:
087: // the underlying TrueType font object
088: private TTFont ttFont;
089:
090: // serialized form of the font programm (compressed)
091: private ByteArrayOutputStream serializedBOS = null;
092:
093: /**
094: * Create the receiver class from an already defined {@link COSDictionary}.
095: * NEVER use the constructor directly.
096: *
097: * @param object
098: * the PDDocument containing the new object
099: */
100: protected PDFontTrueType(COSObject object) {
101: super (object);
102: }
103:
104: public static String getFontFamilyName(String name) {
105: if (name == null) {
106: return null;
107: }
108: int posPlus = name.indexOf('+');
109: if (posPlus > 0) {
110: name = name.substring(posPlus + 1);
111: }
112: int posMinus = name.indexOf('-');
113: if (posMinus > 0) {
114: name = name.substring(0, posMinus);
115: }
116: int posComma = name.indexOf(',');
117: if (posComma > 0) {
118: name = name.substring(0, posComma);
119: }
120: return name;
121: }
122:
123: public static FontStyle getFontStyle(String name) {
124: if (name == null) {
125: return FontStyle.REGULAR;
126: }
127: int posMinus = name.indexOf('-');
128: if (posMinus > 0) {
129: name = name.substring(posMinus + 1);
130: }
131: int posComma = name.indexOf(',');
132: if (posComma > 0) {
133: name = name.substring(posComma + 1).trim();
134: }
135: return FontStyle.getFontStyle(name);
136: }
137:
138: /**
139: * create a TrueType font object to be used in the pdf document
140: *
141: * @param ttFont
142: * the baseFontName of the font to use
143: *
144: * @return the new font created
145: */
146: public static PDFontTrueType createNew(TTFont ttFont) {
147: PDFontTrueType font = null;
148: font = (PDFontTrueType) PDFontTrueType.META.createNew();
149: font.setTTFont(ttFont);
150: font.initializeFromTTFont();
151: return font;
152: }
153:
154: public String getFontFamilyName() {
155: if (getTTFont() != null) {
156: // the embedded name must be parsed, too!
157: String embeddedName = getTTFont().getFontFamilyName();
158: return PDFontTrueType.getFontFamilyName(embeddedName);
159: }
160: return PDFontTrueType.getFontFamilyName(getBaseFont()
161: .stringValue());
162: }
163:
164: public String getFontName() {
165: if (getTTFont() != null) {
166: // the embedded name must be parsed, too!
167: String embeddedName = getTTFont().getFontName();
168: if (embeddedName != null) {
169: return PDFont.getFontName(embeddedName);
170: }
171: }
172:
173: // todo 1 this may not happen since TrueType fonts require no basefont
174: // key
175: // instead its derived from a host postscript name
176: return PDFont.getFontName(getBaseFont().stringValue());
177: }
178:
179: public FontStyle getFontStyle() {
180: if (getTTFont() != null) {
181: // the embedded name must be parsed, too!
182: String embeddedName = getTTFont().getFontFamilyName();
183: return PDFontTrueType.getFontStyle(embeddedName);
184: }
185: return PDFontTrueType.getFontStyle(getBaseFont().stringValue());
186: }
187:
188: /*
189: * (non-Javadoc)
190: *
191: * @see de.intarsys.pdf.font.PDFont#getNextCID(byte[], int)
192: */
193: public CID getNextCID(byte[] bytes, int offset) {
194: return new CIDSelectorCode((bytes[offset] & 0xff));
195: }
196:
197: /*
198: * (non-Javadoc)
199: *
200: * @see de.intarsys.pdf.pd.PDObject#cosGetExpectedSubtype()
201: */
202: protected COSName cosGetExpectedSubtype() {
203: return CN_Subtype_TrueType;
204: }
205:
206: public float getUnderlinePosition() {
207: if (getTTFont() != null) {
208: try {
209: return getTTFont().getPostScriptInformation()
210: .getUnderlinePosition();
211: } catch (TrueTypeException e) {
212: // ignore
213: return -100;
214: }
215: }
216:
217: return -100;
218: }
219:
220: public int getUnderlineThickness() {
221: if (getTTFont() != null) {
222: try {
223: return getTTFont().getPostScriptInformation()
224: .getUnderlineThickness();
225: } catch (TrueTypeException e) {
226: // ignore
227: return 100;
228: }
229: }
230: return 100;
231: }
232:
233: /**
234: * returns the glyph width from the TT font with units and cmap to glyph
235: * indexing resolved
236: */
237: public int getTTGlyphWidth(int codePoint) {
238: int result = 0;
239: Map cmap = null;
240: int unitsPerEm = 0;
241:
242: try {
243: cmap = getTTFontCMap();
244: unitsPerEm = getTTFont().getFontHeader().getUnitsPerEm();
245: } catch (TrueTypeException e) {
246: return result;
247: }
248: int unicode = getEncoding().getUnicode(codePoint);
249: if (getFontDescriptor().getFlags().isSymbolic()) {
250: unicode = 0xF000 | codePoint;
251: }
252: Integer glyphIndex = (Integer) cmap.get(new Integer(unicode));
253: if (glyphIndex != null) {
254: try {
255: // well rounded...
256: result = ((getTTFont().getGlyphWidth(
257: glyphIndex.intValue()) * 1000) + 500)
258: / unitsPerEm;
259: } catch (TrueTypeException e) {
260: cosGetObject().handleException(
261: new COSRuntimeException(e));
262: }
263: }
264: return result;
265: }
266:
267: public void compress() {
268: super .compress();
269:
270: int first = 255;
271: int last = 0;
272: Map cmap = null;
273: int unitsPerEm = 0;
274:
275: try {
276: cmap = getTTFontCMap();
277: unitsPerEm = getTTFont().getFontHeader().getUnitsPerEm();
278: } catch (TrueTypeException e) {
279: cosGetObject()
280: .handleException(
281: new COSRuntimeException(
282: "error parsing TrueType", e)); //$NON-NLS-1$
283: return;
284: }
285:
286: for (int i = 0; i <= 255; i++) {
287: // for each codepoint
288: if (isCharUsed(i)) {
289: if (i < first) {
290: first = i;
291: }
292:
293: if (i > last) {
294: last = i;
295: }
296: }
297: }
298:
299: COSArray widths = COSArray.create(last - first + 1);
300: Set glyphs = new HashSet();
301:
302: for (int i = first; i <= last; i++) {
303: // for each codepoint
304: int width = 0;
305:
306: if (isCharUsed(i)) {
307: int unicode = getEncoding().getUnicode(i);
308: Integer glyphIndex = (Integer) cmap.get(new Integer(
309: unicode));
310:
311: if (glyphIndex != null) {
312: glyphs.add(glyphIndex);
313:
314: try {
315: width = (getTTFont().getGlyphWidth(
316: glyphIndex.intValue()) * 1000)
317: / unitsPerEm;
318: } catch (TrueTypeException e) {
319: cosGetObject().handleException(
320: new COSRuntimeException(e));
321: }
322: }
323: }
324:
325: widths.add(COSInteger.create(width));
326: }
327:
328: cosSetField(DK_FirstChar, COSInteger.create(first));
329: cosSetField(DK_LastChar, COSInteger.create(last));
330: cosSetField(DK_Widths, widths);
331:
332: try {
333: TTFont newFont = getTTFont().createSubset(glyphs);
334: ByteArrayOutputStream bos = new ByteArrayOutputStream();
335: TTFontSerializer serializer = new TTFontSerializer(bos);
336: serializer.write(newFont);
337: setSerializedBOS(bos);
338: } catch (IOException e) {
339: cosGetObject().handleException(new COSRuntimeException(e));
340: } catch (TrueTypeException e) {
341: cosGetObject().handleException(new COSRuntimeException(e));
342: }
343: }
344:
345: /*
346: * (non-Javadoc)
347: *
348: * @see de.intarsys.pdf.font.PDFont#dump()
349: */
350: protected void dump() {
351: PDFontDescriptorEmbedded fd = (PDFontDescriptorEmbedded) getFontDescriptor();
352: if (fd == null) {
353: return;
354: }
355: byte[] data = fd.getFontFile2();
356: if (data == null) {
357: return;
358: }
359: try {
360: String filename = getBaseFont().stringValue();
361: filename = filename.replace('+', '_');
362: filename = filename.replace(',', '_');
363: FileOutputStream os = new FileOutputStream(
364: "fontdump_" + filename //$NON-NLS-1$
365: + ".ttf"); //$NON-NLS-1$
366: os.write(data);
367: os.close();
368: } catch (Exception e) {
369: // ignore and use fallback
370: }
371: }
372:
373: public void embedFontProgramm() {
374: if (getTTFont() == null) {
375: return;
376: }
377: compress();
378: ((PDFontDescriptorEmbedded) getFontDescriptor())
379: .setFontFile2(getSerializedBOS().toByteArray());
380: }
381:
382: public void removeFontProgramm() {
383: ((PDFontDescriptorEmbedded) getFontDescriptor())
384: .removeFontFile2();
385: }
386:
387: protected void setSerializedBOS(ByteArrayOutputStream serializedBOS) {
388: this .serializedBOS = serializedBOS;
389: }
390:
391: protected ByteArrayOutputStream getSerializedBOS() {
392: return serializedBOS;
393: }
394:
395: public void setTTFont(TTFont font) {
396: ttFont = font;
397: }
398:
399: public TTFont getTTFont() {
400: return ttFont;
401: }
402:
403: protected Map getTTFontCMap() throws TrueTypeException {
404: // try different maps
405: Map result = getTTFont().getCMapsAt(3, 3);
406: if (result == null) {
407: result = getTTFont().getCMapsAt(3, 1);
408: }
409: if (result == null) {
410: result = getTTFont().getCMapsAt(3, 0);
411: }
412: if (result == null) {
413: result = getTTFont().getCMapsAt(1, 0);
414: }
415: if (result == null) {
416: result = getTTFont().getCMapsAt(0, 3);
417: }
418: if (result == null) {
419: result = getTTFont().getCMapsAt(0, 1);
420: }
421: if (result == null) {
422: result = getTTFont().getCMapsAt(0, 0);
423: }
424: if (result == null) {
425: throw new TrueTypeException("No CMap found"); //$NON-NLS-1$
426: }
427: return result;
428: }
429:
430: /**
431: * Get an encoding object that describes this fonts NATIVE encoding (if
432: * any).
433: *
434: * <p>
435: * With a true type font the /Encoding entry should select one of the fonts
436: * available encodings. If no such encoding is defined "an implementation
437: * dependent encoding is chosen" (PDF Reference 1.4, p332)
438: * </p>
439: *
440: * @return Get an encoding object that describes this fonts NATIVE encoding
441: * (if any).
442: */
443: protected Encoding createDefaultEncoding() {
444: if ((getTTFont() != null) && getTTFont().isSymbolFont()) {
445: return SymbolEncoding.UNIQUE;
446: }
447: return WinAnsiEncoding.UNIQUE;
448: }
449:
450: /*
451: * (non-Javadoc)
452: *
453: * @see de.intarsys.pdf.pd.PDObject#initializeFromCos()
454: */
455: protected void initializeFromCos() {
456: super .initializeFromCos();
457: COSDictionary descriptor = cosGetField(DK_FontDescriptor)
458: .asDictionary();
459: if (descriptor != null) {
460: COSStream fontFile = descriptor.get(
461: PDFontDescriptorEmbedded.DK_FontFile2).asStream();
462: if (fontFile != null) { // embedded TTFont
463: TTFontParser p = new TTFontParser();
464: try {
465: byte[] bytes = fontFile.getDecodedBytes();
466: ILocator locator = new ByteArrayLocator(bytes,
467: getFontName(), "ttf");
468: TTFont ttFont = TTFont.createFromLocator(locator);
469: setTTFont(ttFont);
470: } catch (IOException e) {
471: // todo 2 handle corrupt tt font program
472: e.printStackTrace();
473: }
474: } else { // linked TTFont
475: // try to find the font from the registry
476: TTFont font = (TTFont) TTRegistry.get().getFont(
477: getBaseFont().stringValue());
478:
479: if (font != null) {
480: setTTFont(font);
481: } else {
482: // TODO 2 handle missing font program
483: }
484: }
485: }
486: }
487:
488: public void initializeFromScratchFontDescriptor() {
489: PDFontDescriptorEmbedded result = (PDFontDescriptorEmbedded) PDFontDescriptorEmbedded.META
490: .createNew();
491: TTMetrics m;
492:
493: try {
494: m = getTTFont().getMetrics();
495:
496: TTFontHeader fh = getTTFont().getFontHeader();
497: TTHorizontalHeader hh = getTTFont().getHorizontalHeader();
498:
499: //
500: result.setAscent((m.getSTypoAscender() * 1000)
501: / fh.getUnitsPerEm());
502:
503: result.setCapHeight(m.getSCapHeight());
504: result.setDescent((m.getSTypoDescender() * 1000)
505: / fh.getUnitsPerEm());
506:
507: CDSRectangle rect = new CDSRectangle((fh.getXMin() * 1000)
508: / fh.getUnitsPerEm(), (fh.getYMin() * 1000)
509: / fh.getUnitsPerEm(), (fh.getXMax() * 1000)
510: / fh.getUnitsPerEm(), (fh.getYMax() * 1000)
511: / fh.getUnitsPerEm());
512: result.setFontBB(rect);
513:
514: // todo 3 font name
515: result.setFontName(getBaseFont().stringValue());
516:
517: double italicAngle = (-Math.atan2(hh.getCaretSlopeRun(), hh
518: .getCaretSlopeRise()) * 180)
519: / Math.PI;
520:
521: result.setItalicAngle((float) italicAngle);
522:
523: // todo 3 constant value
524: // Comment on TT Fonts: "The value is not in TrueType fonts. You
525: // have to calculate it
526: // by analysis of, say, the cap I glyph. Don't worry too much about
527: // putting in a precise
528: // value: the value will only ever be used if the font is not
529: // present with the PDF file,
530: // when a vaguely similar font will be used instead."
531: result.setStemV(80);
532:
533: result.setFlags(0);
534: result.getFlags().setSymbolic(getTTFont().isSymbolFont());
535: result
536: .getFlags()
537: .setItalic(
538: ((fh.getMacStyle() & TTFontHeader.MACSTYLE_ITALIC) != 0));
539: result
540: .getFlags()
541: .setForceBold(
542: ((fh.getMacStyle() & TTFontHeader.MACSTYLE_BOLD) != 0));
543: if (m.getPanose() != null) {
544: result.getFlags().setFixedPitch(
545: m.getPanose().isProportionMonospaced());
546: }
547:
548: // result.setFontFile2(getTTFont().getData().getBytes());
549: setFontDescriptor(result);
550: } catch (TrueTypeException e) {
551: cosGetObject()
552: .handleException(
553: new COSRuntimeException(
554: "error parsing TrueType", e));
555: }
556: }
557:
558: protected void initializeFromTTFont() {
559: super.initializeFromScratch();
560: setBaseFont(getTTFont().getPsName());
561: setEncoding(createDefaultEncoding());
562: initializeFromScratchFontDescriptor();
563: embedFontProgramm();
564: }
565: }
|