KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > it > stefanochizzolini > clown > documents > contents > fonts > OpenTypeFont


1 /*
2   Copyright © 2007 Stefano Chizzolini. http://clown.stefanochizzolini.it
3
4   Contributors:
5     * Stefano Chizzolini (original code developer, info@stefanochizzolini.it):
6       contributed code is Copyright © 2007 by Stefano Chizzolini.
7
8   This file should be part of the source code distribution of "PDF Clown library"
9   (the Program): see the accompanying README files for more info.
10
11   This Program is free software; you can redistribute it and/or modify it under
12   the terms of the GNU General Public License as published by the Free Software
13   Foundation; either version 2 of the License, or (at your option) any later version.
14
15   This Program is distributed in the hope that it will be useful, but WITHOUT ANY
16   WARRANTY, either expressed or implied; without even the implied warranty of
17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more details.
18
19   You should have received a copy of the GNU General Public License along with this
20   Program (see README files); if not, go to the GNU website (http://www.gnu.org/).
21
22   Redistribution and use, with or without modification, are permitted provided that such
23   redistributions retain the above copyright notice, license and disclaimer, along with
24   this list of conditions.
25 */

26
27 package it.stefanochizzolini.clown.documents.contents.fonts;
28
29 import it.stefanochizzolini.clown.bytes.Buffer;
30 import it.stefanochizzolini.clown.bytes.IInputStream;
31 import it.stefanochizzolini.clown.documents.Document;
32 import it.stefanochizzolini.clown.objects.PdfArray;
33 import it.stefanochizzolini.clown.objects.PdfDictionary;
34 import it.stefanochizzolini.clown.objects.PdfDirectObject;
35 import it.stefanochizzolini.clown.objects.PdfIndirectObject;
36 import it.stefanochizzolini.clown.objects.PdfInteger;
37 import it.stefanochizzolini.clown.objects.PdfName;
38 import it.stefanochizzolini.clown.objects.PdfReal;
39 import it.stefanochizzolini.clown.objects.PdfRectangle;
40 import it.stefanochizzolini.clown.objects.PdfStream;
41 import it.stefanochizzolini.clown.util.NotImplementedException;
42
43 import java.util.EnumSet JavaDoc;
44 import java.util.Hashtable JavaDoc;
45
46 /**
47   OpenType font [PDF:1.6:5;OTF:1.4].
48 */

49 public class OpenTypeFont
50   extends Font
51 {
52   // <class>
53
// <classes>
54
/**
55     Font metrics.
56   */

57   private static final class FontMetrics
58   {
59     /**
60       Whether the encoding is custom (symbolic font).
61     */

62     public boolean isCustomEncoding;
63     /**
64       Unit normalization coefficient.
65     */

66     public float unitNorm;
67     /*
68       Font Header ('head' table).
69     */

70     public int flags; // USHORT.
71
public int unitsPerEm; // USHORT.
72
public short xMin;
73     public short yMin;
74     public short xMax;
75     public short yMax;
76     public int macStyle; // USHORT.
77
/*
78       Horizontal Header ('hhea' table).
79     */

80     public short ascender;
81     public short descender;
82     public short lineGap;
83     public int advanceWidthMax; // UFWORD.
84
public short minLeftSideBearing;
85     public short minRightSideBearing;
86     public short xMaxExtent;
87     public short caretSlopeRise;
88     public short caretSlopeRun;
89     public int numberOfHMetrics; // USHORT.
90
/*
91       OS/2 table ('OS/2' table).
92     */

93     public short sTypoAscender;
94     public short sTypoDescender;
95     public short sTypoLineGap;
96     public short sxHeight;
97     public short sCapHeight;
98     /*
99       PostScript table ('post' table).
100     */

101     public float italicAngle;
102     public short underlinePosition;
103     public short underlineThickness;
104     public boolean isFixedPitch;
105   }
106
107   /**
108     Font data parser.
109   */

110   private final class Parser
111   {
112     private static final String JavaDoc Encoding_Latin1 = "ISO-8859-1";
113     private static final String JavaDoc Encoding_Unicode = "UTF-16";
114     private static final int MicrosoftLanguage_UsEnglish = 0x409;
115     private static final int NameID_FontPostscriptName = 6;
116     private static final int PlatformID_Microsoft = 3;
117
118     public String JavaDoc fontName;
119     public PdfName subType;
120
121     private IInputStream fontData;
122
123     private Hashtable JavaDoc<String JavaDoc,Integer JavaDoc> tableOffsets;
124
125     private Parser(
126       IInputStream fontData
127       )
128     {
129       this.fontData = fontData;
130
131       load();
132     }
133
134     /**
135       Loads the font data.
136     */

137     private void load(
138       )
139     {
140       OpenTypeFont.this.metrics = new FontMetrics();
141
142       try
143       {
144         // 1. Offset Table.
145
// Which font file format ('sfnt') version?
146
switch(fontData.readInt())
147         {
148           case(0x00010000): // TrueType.
149
this.subType = PdfName.TrueType;
150             break;
151           case(0x4F54544F): // CFF (Type 1).
152
this.subType = PdfName.Type1;
153             break;
154           default:
155             throw new FontFileFormatException("Not a valid OpenType font file.");
156         }
157         // Get the number of tables!
158
int tableCount = fontData.readUnsignedShort();
159         this.tableOffsets = new Hashtable JavaDoc<String JavaDoc,Integer JavaDoc>(tableCount);
160
161         // Skip to the beginning of the table directory!
162
fontData.skip(6);
163         // 2. Table Directory.
164
// Collecting the table offsets...
165
for(
166           int index = 0;
167           index < tableCount;
168           index++
169           )
170         {
171           // Get the table tag!
172
String JavaDoc tag = readString_ascii(4);
173           // Skip to the table offset!
174
fontData.skip(4);
175           // Get the table offset!
176
int offset = fontData.readInt();
177           // Collect the table offset!
178
tableOffsets.put(tag,offset);
179
180           // Skip to the next entry!
181
fontData.skip(4);
182         }
183
184         this.fontName = load_getName(NameID_FontPostscriptName);
185
186         load_tables();
187         load_cMap();
188         load_glyphWidths();
189         load_glyphKerning();
190       }
191       catch(Exception JavaDoc e)
192       {throw new RuntimeException JavaDoc(e);}
193     }
194
195     /**
196       Loads the character to glyph index mapping table.
197     */

198     private void load_cMap(
199       )
200       throws FontFileFormatException
201     {
202       /*
203         NOTE: A 'cmap' table may contain one or more subtables that represent multiple encodings
204         intended for use on different platforms (such as Mac OS and Windows).
205         Each subtable is identified by the two numbers, such as (3,1), that represent a combination
206         of a platform ID and a platform-specific encoding ID, respectively.
207         A symbolic font (used to display glyphs that do not use standard encodings, i.e. neither
208         MacRomanEncoding nor WinAnsiEncoding) program's "cmap" table should contain a (1,0) subtable.
209         It may also contain a (3,0) subtable; if present, this subtable should map from character
210         codes in the range 0xF000 to 0xF0FF by prepending the single-byte codes in the (1,0) subtable
211         with 0xF0 and mapping to the corresponding glyph descriptions.
212       */

213       // Character To Glyph Index Mapping Table ('cmap' table).
214
// Retrieve the location info!
215
int tableOffset = tableOffsets.get("cmap");
216       if(tableOffset == 0)
217         throw new FontFileFormatException("'cmap' table does NOT exist.");
218
219       int map10Offset = 0;
220       int map31Offset = 0;
221       try
222       {
223         // Header.
224
// Go to the number of tables!
225
fontData.seek(tableOffset + 2);
226         int tableCount = fontData.readUnsignedShort();
227
228         // Encoding records.
229
/*
230           NOTE: Symbolic fonts use specific (non-standard, i.e. neither Unicode nor platform-standard)
231           font encodings.
232         */

233         metrics.isCustomEncoding = false;
234         // Iterating through the entries...
235
for(
236           int tableIndex = 0;
237           tableIndex < tableCount;
238           tableIndex++
239           )
240         {
241           // Platform ID.
242
int platformID = fontData.readUnsignedShort();
243           // Encoding ID.
244
int encodingID = fontData.readUnsignedShort();
245           // Subtable offset.
246
int offset = fontData.readInt();
247     //TODO:DEBUG verify whether to use mac 1,0 or 3,0 tables for symbolic fonts!!!
248
switch(platformID)
249           {
250             case 1: // Macintosh.
251
if(encodingID == 0)
252                 map10Offset = offset;
253               break;
254             case PlatformID_Microsoft:
255               /*
256                 NOTE: When building a symbol font for Windows, the platform ID should be 3 and the
257                 encoding ID should be 0.
258                 When building a Unicode font for Windows, the platform ID should be 3 and the
259                 encoding ID should be 1.
260               */

261               switch(encodingID)
262               {
263                 case 0: // Symbolic font.
264
metrics.isCustomEncoding = true;
265                   break;
266                 case 1: // Nonsymbolic font.
267
map31Offset = offset;
268                   break;
269               }
270               break;
271           }
272         }
273       }
274       catch(Exception JavaDoc e)
275       {throw new RuntimeException JavaDoc(e);}
276
277       // Is it symbolic?
278
if(metrics.isCustomEncoding) // Symbolic.
279
{
280         // Does map(1,0) exist?
281
if(map10Offset > 0)
282         {
283           // Go to the beginning of the subtable!
284
fontData.seek(tableOffset + map10Offset);
285         }
286         else
287         {throw new FontFileFormatException("(1,0) symbolic table does NOT exist.");}
288       }
289       else // Nonsymbolic.
290
{
291         // Does map(3,1) exist?
292
if(map31Offset > 0)
293         {
294           // Go to the beginning of the subtable!
295
fontData.seek(tableOffset + map31Offset);
296         }
297         else
298         {throw new FontFileFormatException("(3,1) nonsymbolic table does NOT exist.");}
299       }
300
301       int format;
302       try
303       {format = fontData.readUnsignedShort();}
304       catch(Exception JavaDoc e)
305       {throw new RuntimeException JavaDoc(e);}
306       // Which cmap table format?
307
switch(format)
308       {
309         case 0: // Byte encoding table.
310
load_cMap_format0();
311           break;
312         case 4: // Segment mapping to delta values.
313
load_cMap_format4();
314           break;
315         case 6: // Trimmed table mapping.
316
load_cMap_format6();
317           break;
318         default:
319           throw new FontFileFormatException("Format " + format + " cmap table NOT supported.");
320       }
321     }
322
323     /**
324       Loads format-0 cmap subtable (Byte encoding table, i.e. Apple standard
325       character-to-glyph index mapping table).
326       @return Map of character codes to glyph indexes.
327     */

328     private void load_cMap_format0(
329       )
330     {
331       /*
332         NOTE: This is a simple 1-to-1 mapping of character codes to glyph indices.
333         The glyph collection is limited to 256 entries.
334       */

335       OpenTypeFont.this.glyphIndexes = new Hashtable JavaDoc<Integer JavaDoc,Integer JavaDoc>(256);
336
337       try
338       {
339         // Skip to the mapping array!
340
fontData.skip(4);
341         // Glyph index array.
342
// Iterating through the glyph indexes...
343
for(
344           int code = 0;
345           code < 256;
346           code++
347           )
348         {
349           glyphIndexes.put(
350             code, // Character code.
351
fontData.readUnsignedByte() // Glyph index.
352
);
353         }
354       }
355       catch(Exception JavaDoc e)
356       {throw new RuntimeException JavaDoc(e);}
357     }
358
359     /**
360       Loads format-4 cmap subtable (Segment mapping to delta values, i.e. Microsoft standard
361       character to glyph index mapping table for fonts that support Unicode ranges other than the
362       range [U+D800 - U+DFFF] (defined as Surrogates Area, in Unicode v 3.0)).
363       @return Map of character codes to glyph indexes.
364     */

365     private void load_cMap_format4(
366       )
367     {
368       /*
369         NOTE: This format is used when the character codes for the characters represented by a font
370         fall into several contiguous ranges, possibly with holes in some or all of the ranges (i.e.
371         some of the codes in a range may not have a representation in the font).
372         The format-dependent data is divided into three parts, which must occur in the following
373         order:
374           1. A header gives parameters for an optimized search of the segment list;
375           2. Four parallel arrays (end characters, start characters, deltas and range offsets)
376           describe the segments (one segment for each contiguous range of codes);
377           3. A variable-length array of glyph IDs.
378       */

379       try
380       {
381         // 1. Header.
382
// Get the table length!
383
int tableLength = fontData.readUnsignedShort(); // USHORT.
384

385         // Skip to the segment count!
386
fontData.skip(2);
387         // Get the segment count!
388
int segmentCount = fontData.readUnsignedShort() / 2;
389
390         // 2. Arrays describing the segments.
391
// Skip to the array of end character code for each segment!
392
fontData.skip(6);
393         // End character code for each segment.
394
int[] endCodes = new int[segmentCount]; // USHORT.
395
for(
396           int index = 0;
397           index < segmentCount;
398           index++
399           )
400         {endCodes[index] = fontData.readUnsignedShort();}
401
402         // Skip to the array of start character code for each segment!
403
fontData.skip(2);
404         // Start character code for each segment.
405
int[] startCodes = new int[segmentCount]; // USHORT.
406
for(
407           int index = 0;
408           index < segmentCount;
409           index++
410           )
411         {startCodes[index] = fontData.readUnsignedShort();}
412
413         // Delta for all character codes in segment.
414
short[] deltas = new short[segmentCount];
415         for(
416           int index = 0;
417           index < segmentCount;
418           index++
419           )
420         {deltas[index] = fontData.readShort();}
421
422         // Offsets into glyph index array.
423
int[] rangeOffsets = new int[segmentCount]; // USHORT.
424
for(
425           int index = 0;
426           index < segmentCount;
427           index++
428           )
429         {rangeOffsets[index] = fontData.readUnsignedShort();}
430
431         // 3. Glyph id array.
432
/*
433           NOTE: There's no explicit field defining the array length; it must be inferred from
434           the space left by the known fields.
435         */

436         int glyphIndexCount = tableLength / 2 // Number of 16-bit words inside the table.
437
- 8 // Number of single-word header fields (8 fields: format, length, language, segCountX2, searchRange, entrySelector, rangeShift, reservedPad).
438
- segmentCount * 4; // Number of single-word items in the arrays describing the segments (4 arrays of segmentCount items).
439
int[] glyphIds = new int[glyphIndexCount]; // USHORT.
440
for(
441           int index = 0;
442           index < glyphIds.length;
443           index++
444           )
445         {glyphIds[index] = fontData.readUnsignedShort();}
446
447         OpenTypeFont.this.glyphIndexes = new Hashtable JavaDoc<Integer JavaDoc,Integer JavaDoc>(glyphIndexCount);
448         // Iterating through the segments...
449
for(
450           int segmentIndex = 0;
451           segmentIndex < segmentCount;
452           segmentIndex++
453           )
454         {
455           int endCode = endCodes[segmentIndex];
456           // Is it NOT the last end character code?
457
/*
458             NOTE: The final segment's endCode MUST be 0xFFFF. This segment need not (but MAY)
459             contain any valid mappings (it can just map the single character code 0xFFFF to
460             missing glyph). However, the segment MUST be present.
461           */

462           if(endCode != 0xFFFF)
463           {endCode++;}
464           // Iterating inside the current segment...
465
for(
466             int code = startCodes[segmentIndex];
467             code < endCode;
468             code++
469             )
470           {
471             int glyphIndex;
472             // Doesn't the mapping of character codes rely on glyph id?
473
if(rangeOffsets[segmentIndex] == 0) // No glyph-id reliance.
474
{
475               /*
476                 NOTE: If the range offset is 0, the delta value is added directly to the character
477                 code to get the corresponding glyph index. The delta arithmetic is modulo 65536.
478               */

479               glyphIndex = (code + deltas[segmentIndex]) & 0xFFFF;
480             }
481             else // Glyph-id reliance.
482
{
483               /*
484                 NOTE: If the range offset is NOT 0, the mapping of character codes relies on glyph
485                 id.
486                 The character code offset from start code is added to the range offset. This sum is
487                 used as an offset from the current location within range offset itself to index out
488                 the correct glyph id. This obscure indexing trick (sic!) works because glyph id
489                 immediately follows range offset in the font file. The C expression that yields the
490                 address to the glyph id is:
491                   *(rangeOffsets[segmentIndex]/2
492                   + (code - startCodes[segmentIndex])
493                   + &idRangeOffset[segmentIndex])
494                 As Java language semantics don't deal directly with pointers, we have to further
495                 exploit such a trick reasoning with 16-bit displacements in order to yield an index
496                 instead of an address (sooo-good!).
497               */

498               // Retrieve the glyph index!
499
int glyphIdIndex = rangeOffsets[segmentIndex] / 2 // 16-bit word range offset.
500
+ (code - startCodes[segmentIndex]) // Character code offset from start code.
501
- (segmentCount - segmentIndex); // Physical offset between the offsets into glyph index array and the glyph index array.
502
// Is it a missing glyph?
503
/*
504                 NOTE: If the value obtained from the indexing operation is not 0 (which indicates
505                 missing glyph), deltas[segmentIndex] is added to it to get the glyph index. The delta
506                 arithmetic is modulo 65536.
507               */

508               if(glyphIdIndex == 0) // Missing glyph.
509
{glyphIndex = 0;}
510               else // Available glyph.
511
{glyphIndex = (glyphIds[glyphIdIndex] + deltas[segmentIndex]) & 0xFFFF;}
512             }
513
514             glyphIndexes.put(
515               code, // Character code.
516
glyphIndex // Glyph index.
517
);
518           }
519         }
520       }
521       catch(Exception JavaDoc e)
522       {throw new RuntimeException JavaDoc(e);}
523     }
524
525     /**
526       Loads format-6 cmap subtable (Trimmed table mapping).
527       @return Map of character codes to glyph indexes.
528     */

529     private void load_cMap_format6(
530       )
531     {
532       try
533       {
534         // Skip to the first character code!
535
fontData.skip(4);
536         int firstCode = fontData.readUnsignedShort();
537         int codeCount = fontData.readUnsignedShort();
538         OpenTypeFont.this.glyphIndexes = new Hashtable JavaDoc<Integer JavaDoc,Integer JavaDoc>(codeCount);
539         for(
540           int code = firstCode,
541             lastCode = firstCode + codeCount;
542           code < lastCode;
543           code++
544           )
545         {
546           glyphIndexes.put(
547             code, // Character code.
548
fontData.readUnsignedShort() // Glyph index.
549
);
550         }
551       }
552       catch(Exception JavaDoc e)
553       {throw new RuntimeException JavaDoc(e);}
554     }
555
556     /**
557       Gets a name.
558       @param id Name identifier.
559     */

560     private String JavaDoc load_getName(
561       int id
562       )
563       throws FontFileFormatException
564     {
565       // Naming Table ('name' table).
566
// Retrieve the location info!
567
int tableOffset = tableOffsets.get("name");
568       if(tableOffset == 0)
569         throw new FontFileFormatException("'name' table does NOT exist.");
570
571       try
572       {
573         // Go to the number of name records!
574
fontData.seek(tableOffset + 2);
575
576         int recordCount = fontData.readUnsignedShort(); // USHORT.
577
int storageOffset = fontData.readUnsignedShort(); // USHORT.
578
// Iterating through the name records...
579
for(
580           int recordIndex = 0;
581           recordIndex < recordCount;
582           recordIndex++
583           )
584         {
585           int platformID = fontData.readUnsignedShort(); // USHORT.
586
// Is it the default platform?
587
if(platformID == PlatformID_Microsoft)
588           {
589             fontData.skip(2);
590             int languageID = fontData.readUnsignedShort(); // USHORT.
591
// Is it the default language?
592
if(languageID == MicrosoftLanguage_UsEnglish)
593             {
594               int nameID = fontData.readUnsignedShort(); // USHORT.
595
// Does the name ID equal the searched one?
596
if(nameID == id)
597               {
598                 int length = fontData.readUnsignedShort(); // USHORT.
599
int offset = fontData.readUnsignedShort(); // USHORT.
600

601                 // Go to the name string!
602
fontData.seek(tableOffset + storageOffset + offset);
603
604                 return readString(length,platformID);
605               }
606               else
607               {fontData.skip(4);}
608             }
609             else
610             {fontData.skip(6);}
611           }
612           else
613           {fontData.skip(10);}
614         }
615
616         return null; // Not found.
617
}
618       catch(Exception JavaDoc e)
619       {throw new RuntimeException JavaDoc(e);}
620     }
621
622     /**
623       Loads the glyph kerning.
624     */

625     private void load_glyphKerning(
626       )
627       throws FontFileFormatException
628     {
629       // Kerning ('kern' table).
630
// Retrieve the location info!
631
int tableOffset;
632       try
633       {tableOffset = tableOffsets.get("kern");}
634       catch(Exception JavaDoc e)
635       {return;}
636
637       try
638       {
639         // Go to the table count!
640
fontData.seek(tableOffset + 2);
641         int subtableCount = fontData.readUnsignedShort(); // USHORT.
642

643         OpenTypeFont.this.glyphKernings = new Hashtable JavaDoc<Integer JavaDoc,Integer JavaDoc>();
644         int subtableOffset = (int)fontData.getPosition();
645         // Iterating through the subtables...
646
for(
647           int subtableIndex = 0;
648           subtableIndex < subtableCount;
649           subtableIndex++
650           )
651         {
652           // Go to the subtable length!
653
fontData.seek(subtableOffset + 2);
654           // Get the subtable length!
655
int length = fontData.readUnsignedShort(); // USHORT.
656

657           // Get the type of information contained in the subtable!
658
int coverage = fontData.readUnsignedShort(); // USHORT.
659
// Is it a format-0 subtable?
660
/*
661             NOTE: coverage bits 8-15 (format of the subtable) MUST be all zeros
662             (representing format 0).
663           */

664           //
665
if((coverage & 0xff00) == 0x0000)
666           {
667             int pairCount = fontData.readUnsignedShort(); // USHORT.
668

669             // Skip to the beginning of the list!
670
fontData.skip(6);
671             // List of kerning pairs and values.
672
for(
673               int pairIndex = 0;
674               pairIndex < pairCount;
675               pairIndex++
676               )
677             {
678               // Get the glyph index pair (left-hand and right-hand)!
679
int pair = fontData.readInt(); // USHORT USHORT.
680
// Get the normalized kerning value!
681
int value = (int)(fontData.readShort() * metrics.unitNorm);
682
683               glyphKernings.put(pair,value);
684             }
685           }
686
687           subtableOffset += length;
688         }
689       }
690       catch(Exception JavaDoc e)
691       {throw new RuntimeException JavaDoc(e);}
692     }
693
694     /**
695       Loads the glyph widths.
696     */

697     private void load_glyphWidths(
698       )
699       throws FontFileFormatException
700     {
701       // Horizontal Metrics ('hmtx' table).
702
// Retrieve the location info!
703
int tableOffset = tableOffsets.get("hmtx");
704       if(tableOffset == 0)
705         throw new FontFileFormatException("'hmtx' table does NOT exist.");
706
707       try
708       {
709         // Go to the glyph horizontal-metrics entries!
710
fontData.seek(tableOffset);
711         OpenTypeFont.this.glyphWidths = new int[metrics.numberOfHMetrics]; // USHORT.
712
for(
713           int index = 0;
714           index < metrics.numberOfHMetrics;
715           index++
716           )
717         {
718           // Get the glyph advance width!
719
glyphWidths[index] = (int)(fontData.readUnsignedShort() * metrics.unitNorm);
720           // Skip the left side bearing!
721
fontData.skip(2);
722         }
723       }
724       catch(Exception JavaDoc e)
725       {throw new RuntimeException JavaDoc(e);}
726     }
727
728     /**
729       Loads general tables.
730     */

731     private void load_tables(
732       )
733       throws FontFileFormatException
734     {
735       // Font Header ('head' table).
736
// Retrieve the location info!
737
int tableOffset = tableOffsets.get("head");
738       if(tableOffset == 0)
739         throw new FontFileFormatException("'head' table does NOT exist.");
740
741       try
742       {
743         // Go to the font flags!
744
fontData.seek(tableOffset + 16);
745         metrics.flags = fontData.readUnsignedShort();
746         metrics.unitsPerEm = fontData.readUnsignedShort();
747         metrics.unitNorm = 1000f / metrics.unitsPerEm;
748         // Go to the bounding box limits!
749
fontData.skip(16);
750         metrics.xMin = fontData.readShort();
751         metrics.yMin = fontData.readShort();
752         metrics.xMax = fontData.readShort();
753         metrics.yMax = fontData.readShort();
754         metrics.macStyle = fontData.readUnsignedShort();
755       }
756       catch(Exception JavaDoc e)
757       {throw new RuntimeException JavaDoc(e);}
758
759       // Font Header ('head' table).
760
// Retrieve the location info!
761
tableOffset = tableOffsets.get("OS/2");
762       if(tableOffset == 0)
763         throw new FontFileFormatException("'OS/2' table does NOT exist.");
764
765       try
766       {
767         fontData.seek(tableOffset);
768         int version = fontData.readUnsignedShort();
769         // Go to the ascender!
770
fontData.skip(66);
771         metrics.sTypoAscender = fontData.readShort();
772         metrics.sTypoDescender = fontData.readShort();
773         metrics.sTypoLineGap = fontData.readShort();
774         if(version >= 2)
775         {
776           fontData.skip(12);
777           metrics.sxHeight = fontData.readShort();
778           metrics.sCapHeight = fontData.readShort();
779         }
780         else
781         {
782           /*
783             NOTE: These are just rule-of-thumb values, in case the xHeight and CapHeight fields
784             aren't available.
785           */

786           metrics.sxHeight = (short)(.5 * metrics.unitsPerEm);
787           metrics.sCapHeight = (short)(.7 * metrics.unitsPerEm);
788         }
789       }
790       catch(Exception JavaDoc e)
791       {throw new RuntimeException JavaDoc(e);}
792
793       // Horizontal Header ('hhea' table).
794
// Retrieve the location info!
795
tableOffset = tableOffsets.get("hhea");
796       if(tableOffset == 0)
797         throw new FontFileFormatException("'hhea' table does NOT exist.");
798
799       try
800       {
801         // Go to the ascender!
802
fontData.seek(tableOffset + 4);
803         metrics.ascender = fontData.readShort();
804         metrics.descender = fontData.readShort();
805         metrics.lineGap = fontData.readShort();
806         metrics.advanceWidthMax = fontData.readUnsignedShort();
807         metrics.minLeftSideBearing = fontData.readShort();
808         metrics.minRightSideBearing = fontData.readShort();
809         metrics.xMaxExtent = fontData.readShort();
810         metrics.caretSlopeRise = fontData.readShort();
811         metrics.caretSlopeRun = fontData.readShort();
812         // Go to the horizontal metrics count!
813
fontData.skip(12);
814         metrics.numberOfHMetrics = fontData.readUnsignedShort();
815       }
816       catch(Exception JavaDoc e)
817       {throw new RuntimeException JavaDoc(e);}
818
819       // PostScript ('post' table).
820
// Retrieve the location info!
821
tableOffset = tableOffsets.get("post");
822       if(tableOffset == 0)
823         throw new FontFileFormatException("'post' table does NOT exist.");
824
825       try
826       {
827         // Go to the italic angle!
828
fontData.seek(tableOffset + 4);
829         metrics.italicAngle =
830           fontData.readShort() // Fixed-point mantissa (16 bits).
831
+ (float)fontData.readUnsignedShort() / 16384; // Fixed-point fraction (16 bits).
832
metrics.underlinePosition = fontData.readShort();
833         metrics.underlineThickness = fontData.readShort();
834         metrics.isFixedPitch = (fontData.readInt() != 0);
835       }
836       catch(Exception JavaDoc e)
837       {throw new RuntimeException JavaDoc(e);}
838     }
839
840     /**
841       Reads a string.
842     */

843     private String JavaDoc readString(
844       int length,
845       int platformID
846       )
847     {
848       // Which platform?
849
switch(platformID)
850       {
851         case 0: // Unicode.
852
case PlatformID_Microsoft:
853           return readString_unicode(length);
854         default:
855           return readString_ascii(length);
856       }
857     }
858
859     /**
860       Reads a string from the font file using the extended ASCII encoding.
861     */

862     private String JavaDoc readString_ascii(
863       int length
864       )
865     {
866       /*
867         NOTE: Extended ASCII (Latin1) is a single-byte encoding
868         (I know you already knew that!).
869       */

870       try
871       {
872         byte[] data = new byte[length];
873         fontData.read(data);
874
875         return new String JavaDoc(data,Encoding_Latin1);
876       }
877       catch(Exception JavaDoc e)
878       {throw new RuntimeException JavaDoc(e);}
879     }
880
881     /**
882       Reads a string from the font file using the Unicode encoding.
883     */

884     private String JavaDoc readString_unicode(
885       int length
886       )
887     {
888       /*
889         NOTE: Unicode is a double-byte encoding
890         (I know you already knew that!).
891       */

892       try
893       {
894         byte[] data = new byte[length];
895         fontData.read(data);
896
897         return new String JavaDoc(data,Encoding_Unicode);
898       }
899       catch(Exception JavaDoc e)
900       {throw new RuntimeException JavaDoc(e);}
901     }
902   }
903   // </classes>
904

905   // <dynamic>
906
// <fields>
907
private FontMetrics metrics;
908
909   private Hashtable JavaDoc<Integer JavaDoc,Integer JavaDoc> glyphIndexes;
910   private Hashtable JavaDoc<Integer JavaDoc,Integer JavaDoc> glyphKernings;
911   private int[] glyphWidths;
912   // </fields>
913

914   // <constructors>
915
public OpenTypeFont(
916     Document context,
917     IInputStream fontData
918     )
919   {
920     super(context);
921
922     load(fontData);
923   }
924
925   /**
926     <h3>Remarks</h3>
927     <p>For internal use only.</p>
928   */

929 //TODO:IMPL manage loading of already embedded font metrics to allow editing!!!
930
public OpenTypeFont(
931     PdfDirectObject baseObject
932     )
933   {super(baseObject);}
934   // </constructors>
935

936   // <interface>
937
// <public>
938
public Object JavaDoc clone(
939     Document context
940     )
941   {throw new NotImplementedException();}
942
943   @Override JavaDoc
944   public int getKerning(
945     char textChar1,
946     char textChar2
947     )
948   {
949     try
950     {
951       return glyphKernings.get(
952         glyphIndexes.get((int)textChar1) << 16 // Left-hand glyph index.
953
+ glyphIndexes.get((int)textChar2) // Right-hand glyph index.
954
);
955     }
956     catch(Exception JavaDoc e)
957     {return 0;}
958   }
959
960   @Override JavaDoc
961   public int getKerning(
962     String JavaDoc text
963     )
964   {
965     int kerning = 0;
966     // Are kerning pairs available?
967
if(glyphKernings != null)
968     {
969       char textChars[] = text.toCharArray();
970       for(
971         int index = 0,
972           length = text.length() - 1;
973         index < length;
974         index++
975         )
976       {
977         kerning += getKerning(
978           textChars[index],
979           textChars[index + 1]
980           );
981       }
982     }
983
984     return kerning;
985   }
986
987   @Override JavaDoc
988   public int getWidth(
989     char textChar
990     )
991   {return glyphWidths[glyphIndexes.get((int)textChar)];}
992
993   @Override JavaDoc
994   public int getWidth(
995     String JavaDoc text
996     )
997   {
998     int width = 0;
999     char textChars[] = text.toCharArray();
1000    for(
1001      int index = 0,
1002        length = text.length();
1003      index < length;
1004      index++
1005      )
1006    {width += getWidth(textChars[index]);}
1007
1008    return width;
1009  }
1010  // </public>
1011

1012  /**
1013    Loads the font data.
1014  */

1015  private void load(
1016    IInputStream fontData
1017    )
1018  {
1019    try
1020    {
1021      Parser parser = new Parser(fontData);
1022
1023      // Subtype.
1024
getBaseDataObject().put(
1025        PdfName.Subtype,
1026        parser.subType
1027        );
1028      // BaseFont.
1029
getBaseDataObject().put(
1030        PdfName.BaseFont,
1031        new PdfName(parser.fontName)
1032        );
1033      // Encoding.
1034
/*
1035        NOTE: PDF font dictionary's Encoding entry is used in conjunction with a "cmap" to map
1036        from a character code in a string to a glyph description in an OpenType font program.
1037        * A nonsymbolic font SHOULD specify MacRomanEncoding or WinAnsiEncoding as the value of its
1038        Encoding entry, with no Differences array.
1039        * A symbolic font (used to display glyphs that do not use MacRomanEncoding or
1040        WinAnsiEncoding) SHOULD NOT specify an Encoding entry. The font descriptor's Symbolic flag
1041        should be set.
1042      */

1043      // Is it symbolic?
1044
if(metrics.isCustomEncoding) // Symbolic.
1045
{
1046        /*
1047          NOTE: Encoding entry MUST be null,
1048          whilst the font descriptor's Symbolic flag MUST be set.
1049        */

1050      }
1051      else // Nonsymbolic.
1052
{
1053        getBaseDataObject().put(PdfName.Encoding,PdfName.WinAnsiEncoding);
1054      }
1055      // TODO:IMPL subsetting to be managed!!!
1056
int firstChar = 0;
1057      int lastChar = 255;
1058      // FirstChar.
1059
getBaseDataObject().put(
1060        PdfName.FirstChar,
1061        new PdfInteger(firstChar)
1062        );
1063      // LastChar.
1064
getBaseDataObject().put(
1065        PdfName.LastChar,
1066        new PdfInteger(lastChar)
1067        );
1068      // Widths.
1069
PdfArray widthsObject = new PdfArray(lastChar - firstChar + 1);
1070      for(
1071        int code = firstChar;
1072        code <= lastChar;
1073        code++
1074        )
1075      {
1076        int width;
1077        try
1078        {width = glyphWidths[glyphIndexes.get(code)];}
1079        catch(Exception JavaDoc e)
1080        {width = 0;}
1081
1082        widthsObject.add(new PdfInteger(width));
1083      }
1084      getBaseDataObject().put(
1085        PdfName.Widths,
1086        widthsObject
1087        );
1088      // FontDescriptor.
1089
getBaseDataObject().put(
1090        PdfName.FontDescriptor,
1091        load_createFontDescriptor(fontData).getReference()
1092        );
1093    }
1094    catch(Exception JavaDoc e)
1095    {throw new RuntimeException JavaDoc(e);}
1096  }
1097
1098  /**
1099    Creates the font descriptor.
1100  */

1101  private PdfIndirectObject load_createFontDescriptor(
1102    IInputStream fontData
1103    )
1104  {
1105    PdfDictionary fontDescriptor = new PdfDictionary();
1106    // Type.
1107
fontDescriptor.put(
1108      PdfName.Type,
1109      PdfName.FontDescriptor
1110      );
1111    // FontName.
1112
fontDescriptor.put(
1113      PdfName.FontName,
1114      getBaseDataObject().get(PdfName.BaseFont)
1115      );
1116    // Flags [PDF:1.6:5.7.1].
1117
int flags = 0;
1118    if(metrics.isFixedPitch)
1119    {flags |= FlagsEnum.FixedPitch.getCode();}
1120    if(metrics.isCustomEncoding)
1121    {flags |= FlagsEnum.Symbolic.getCode();}
1122    else
1123    {flags |= FlagsEnum.Nonsymbolic.getCode();}
1124    fontDescriptor.put(
1125      PdfName.Flags,
1126      new PdfInteger(flags)
1127      );
1128    // FontBBox.
1129
fontDescriptor.put(
1130      PdfName.FontBBox,
1131      new PdfRectangle(
1132        metrics.xMin * metrics.unitNorm,
1133        metrics.yMin * metrics.unitNorm,
1134        metrics.xMax * metrics.unitNorm,
1135        metrics.yMax * metrics.unitNorm
1136        )
1137      );
1138    // ItalicAngle.
1139
fontDescriptor.put(
1140      PdfName.ItalicAngle,
1141      new PdfReal(metrics.italicAngle)
1142      );
1143    // Ascent.
1144
if(metrics.ascender == 0)
1145    {
1146        fontDescriptor.put(
1147          PdfName.Ascent,
1148          new PdfReal(metrics.sTypoAscender * metrics.unitNorm)
1149          );
1150    }
1151    else
1152    {
1153        fontDescriptor.put(
1154          PdfName.Ascent,
1155          new PdfReal(metrics.ascender * metrics.unitNorm)
1156          );
1157    }
1158    // Descent.
1159
if(metrics.descender == 0)
1160    {
1161        fontDescriptor.put(
1162          PdfName.Descent,
1163          new PdfReal(metrics.sTypoDescender * metrics.unitNorm)
1164          );
1165    }
1166    else
1167    {
1168        fontDescriptor.put(
1169          PdfName.Descent,
1170          new PdfReal(metrics.descender * metrics.unitNorm)
1171          );
1172    }
1173    // Leading.
1174
fontDescriptor.put(
1175      PdfName.Leading,
1176      new PdfReal(metrics.sTypoLineGap * metrics.unitNorm)
1177      );
1178    // CapHeight.
1179
fontDescriptor.put(
1180      PdfName.CapHeight,
1181      new PdfReal(metrics.sCapHeight * metrics.unitNorm)
1182      );
1183    // StemV.
1184
/*
1185      NOTE: '100' is just a rule-of-thumb value, 'cause I've still to solve the
1186      'cvt' table puzzle (such a harsh headache!) for TrueType fonts...
1187      TODO:IMPL TrueType and CFF stemv real value to extract!!!
1188    */

1189    fontDescriptor.put(
1190      PdfName.StemV,
1191      new PdfInteger(100)
1192      );
1193    // FontFile.
1194
//TODO:IMPL distinguish between truetype (FontDescriptor.FontFile2) and opentype (FontDescriptor.FontFile3 and FontStream.subtype=OpenType)!!!
1195
PdfIndirectObject fontFile = getFile().getIndirectObjects().add(
1196      new PdfStream(
1197        new PdfDictionary(
1198          new PdfName[]{PdfName.Subtype},
1199          new PdfDirectObject[]{PdfName.OpenType}
1200          ),
1201        new Buffer(fontData.toByteArray())
1202        )
1203      );
1204    fontDescriptor.put(
1205      PdfName.FontFile3,
1206      fontFile.getReference()
1207      );
1208
1209    return getFile().getIndirectObjects().add(fontDescriptor);
1210  }
1211  // </interface>
1212
// </dynamic>
1213
// </class>
1214
}
Popular Tags