Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
 
3
define('OT_CMAP_PLATFORM_WINDOWS', 3);
4
define('OT_CMAP_PLATFORM_WINDOWS_UNICODE', 1);
5
 
6
define('OT_PLATFORM_ID_MICROSOFT', 3);
7
 
8
define('OT_NAME_ID_SUBFAMILY_NAME', 2);
9
define('OT_NAME_ID_UNIQUE_ID', 3);
10
define('OT_NAME_ID_FULL_NAME', 4);
11
define('OT_NAME_ID_POSTSCRIPT_NAME', 6);
12
 
13
define('OT_CMAP_LANGUAGE_WINDOWS_ENGLISH_AMERICAN', 0x0409);
14
 
15
/**
16
 * This class allows parsing TrueType/OpenType font files
17
 */
18
class OpenTypeFile {
19
  var $_filehandle;
20
  var $_sfnt;
21
 
22
  function OpenTypeFile() {
23
    $this->_filehandle = null;
24
    $this->_sfnt = new OpenTypeFileSFNT();
25
  }
26
 
27
  function open($filename) {
28
    $this->_filehandle = fopen($filename, 'rb');
29
    $this->_read($this->_filehandle);
30
  }
31
 
32
  function close() {
33
    fclose($this->_filehandle);
34
  }
35
 
36
  function _delete() {
37
    $this->close();
38
    $this->_sfnt->_delete();
39
  }
40
 
41
  function getFileHandle() {
42
    return $this->_filehandle;
43
  }
44
 
45
  function &getTable($tag) {
46
    $table =& $this->_sfnt->_getTable($tag, $this->_filehandle, $this);
47
    return $table;
48
  }
49
 
50
  function &_getCMAPSubtable($offset) {
51
    $table =& $this->_sfnt->_getCMAPSubtable($offset, $this->_filehandle, $this);
52
    return $table;
53
  }
54
 
55
  function _read($filehandle) {
56
    $this->_sfnt->_read($filehandle);
57
  }
58
}
59
 
60
/**
61
 * A key  characteristic of the  OpenType format is the  TrueType sfnt
62
 * "wrapper", which  provides organization for a  collection of tables
63
 * in a general and extensible manner.
64
 */
65
class OpenTypeFileSFNT {
66
  var $_offsetTable;
67
  var $_tableDirectory;
68
 
69
  var $_tables;
70
 
71
  function _delete() {
72
    foreach ($this->_tables as $key => $value) {
73
      $this->_tables[$key]->_delete();
74
      unset($this->_tables[$key]);
75
    };
76
    $this->_tables = array();
77
  }
78
 
79
  function OpenTypeFileSFNT() {
80
    $this->_offsetTable = new OpenTypeFileOffsetTable();
81
    $this->_tableDirectory = array();
82
  }
83
 
84
  function _read($filehandle) {
85
    $this->_offsetTable->_read($filehandle);
86
 
87
    for ($i=0; $i<$this->_offsetTable->_numTables; $i++) {
88
      $tableDirectory = new OpenTypeFileTableDirectory();
89
      $tableDirectory->_read($filehandle);
90
      $this->_tableDirectory[] = $tableDirectory;
91
    };
92
  }
93
 
94
  function &_getCMAPSubtable($offset, $filehandle, $file) {
95
    $dir = $this->_getDirectory('cmap');
96
    if (is_null($dir)) { $dummy = null; return $dummy; };
97
 
98
    /**
99
     * Store  current  file  position,  as _getCMAPSubtable  could  be
100
     * called from another file-related operation
101
     */
102
    $old_pos = ftell($filehandle);
103
 
104
    fseek($filehandle, $dir->_offset, SEEK_SET);
105
    fseek($filehandle, $offset, SEEK_CUR);
106
    $subtable = new OpenTypeFileCMAPSubtable();
107
    $subtable->_read($filehandle);
108
 
109
    /**
110
     * Restore current file position
111
     */
112
    fseek($filehandle, $old_pos, SEEK_SET);
113
 
114
    return $subtable;
115
  }
116
 
117
  function &_getTable($tag, $filehandle, $file) {
118
    if (!isset($this->_tables[$tag])) {
119
      $table = $this->_createTableByTag($tag);
120
      if (is_null($table)) { $dummy = null; return $dummy; };
121
      $table->setFontFile($file);
122
 
123
      $dir = $this->_getDirectory($tag);
124
      if (is_null($dir)) { $dummy = null; return $dummy; };
125
 
126
      /**
127
       * Store  current file  position, as  _getTable could  be called
128
       * from another _getTable
129
       */
130
      $old_pos = ftell($filehandle);
131
 
132
      fseek($filehandle, $dir->_offset, SEEK_SET);
133
      $table->_read($filehandle);
134
 
135
      /**
136
       * Restore current file position
137
       */
138
      fseek($filehandle, $old_pos, SEEK_SET);
139
 
140
      $this->_tables[$tag] =& $table;
141
    };
142
 
143
    return $this->_tables[$tag];
144
  }
145
 
146
  function _getDirectory($tag) {
147
    foreach ($this->_tableDirectory as $directoryEntry) {
148
      if ($directoryEntry->_tag == $tag) {
149
        return $directoryEntry;
150
      };
151
    };
152
 
153
    return null;
154
  }
155
 
156
  function _createTableByTag($tag) {
157
    switch ($tag) {
158
    case 'hhea':
159
      return new OpenTypeFileHHEA();
160
    case 'maxp':
161
      return new OpenTypeFileMAXP();
162
    case 'cmap':
163
      return new OpenTypeFileCMAP();
164
    case 'hmtx':
165
      return new OpenTypeFileHMTX();
166
    case 'post':
167
      return new OpenTypeFilePOST();
168
    case 'head':
169
      return new OpenTypeFileHEAD();
170
    case 'name':
171
      return new OpenTypeFileNAME();
172
    default:
173
      return null;
174
    }
175
  }
176
}
177
 
178
/**
179
 * The OpenType font with the Offset Table. If the font file contains only one font, the Offset Table will begin at byte 0 of the file. If the font file is a TrueType collection, the beginning point of the Offset Table for each font is indicated in the TTCHeader.
180
 *
181
 * Offset Table Type 	Name 	Description
182
 * Fixed 	sfnt version 	0x00010000 for version 1.0.
183
 * USHORT 	numTables 	Number of tables.
184
 * USHORT 	searchRange 	(Maximum power of 2 <= numTables) x 16.
185
 * USHORT 	entrySelector 	Log2(maximum power of 2 <= numTables).
186
 * USHORT 	rangeShift 	NumTables x 16-searchRange.
187
 *
188
 * OpenType fonts that contain  TrueType outlines should use the value
189
 * of 1.0  for the  sfnt version. OpenType  fonts containing  CFF data
190
 * should use the tag 'OTTO' as the sfnt version number.
191
 *
192
 * NOTE: The Apple specification  for TrueType fonts allows for 'true'
193
 * and 'typ1' for sfnt version.  These version tags should not be used
194
 * for fonts which contain OpenType tables.
195
 */
196
class OpenTypeFileOffsetTable {
197
  var $_numTables;
198
  var $_searchRange;
199
  var $_entrySelector;
200
  var $_rangeShift;
201
 
202
  function OpenTypeFileOffsetTable() {
203
    $this->_numTables     = 0;
204
    $this->_searchRange   = 0;
205
    $this->_entrySelector = 0;
206
    $this->_rangeShift    = 0;
207
  }
208
 
209
  function _read($filehandle) {
210
    $content = fread($filehandle, 4+4*2);
211
 
212
    $unpacked = unpack("Nversion/nnumTables/nsearchRange/nentrySelector/nrangeShift", $content);
213
 
214
    $fixed                = $unpacked['version'];
215
    $this->_numTables     = $unpacked['numTables'];
216
    $this->_searchRange   = $unpacked['searchRange'];
217
    $this->_entrySelector = $unpacked['entrySelector'];
218
    $this->_rangeShift    = $unpacked['rangeShift'];
219
  }
220
}
221
 
222
/**
223
 * The  Offset Table is  followed immediately  by the  Table Directory
224
 * entries. Entries in the Table Directory must be sorted in ascending
225
 * order by  tag. Offset  values in the  Table Directory  are measured
226
 * from the start of the font file.
227
 *
228
 * Table Directory Type 	Name 	Description
229
 * ULONG 	tag 	4 -byte identifier.
230
 * ULONG 	checkSum 	CheckSum for this table.
231
 * ULONG 	offset 	Offset from beginning of TrueType font file.
232
 * ULONG 	length 	Length of this table.
233
 *
234
 * The Table Directory  makes it possible for a  given font to contain
235
 * only  those tables  it  actually needs.  As  a result  there is  no
236
 * standard value for numTables.
237
 *
238
 * Tags are the  names given to tables in the  OpenType font file. All
239
 * tag names  consist of  four characters. Names  with less  than four
240
 * letters  are   allowed  if  followed  by   the  necessary  trailing
241
 * spaces. All  tag names  defined within a  font (e.g.,  table names,
242
 * feature tags, language tags) must be built from printing characters
243
 * represented by ASCII values 32-126.
244
 */
245
class OpenTypeFileTableDirectory {
246
  var $_tag;
247
  var $_checkSum;
248
  var $_offset;
249
  var $_length;
250
 
251
  function OpenTypeFileTableDirectory() {
252
    $this->_tag      = null;
253
    $this->_checkSum = 0;
254
    $this->_offset   = 0;
255
    $this->_length   = 0;
256
  }
257
 
258
  function _read($filehandle) {
259
    $content = fread($filehandle, 4*4);
260
 
261
    $unpacked = unpack("c4tag/NcheckSum/Noffset/Nlength", $content);
262
 
263
    $this->_tag      = chr($unpacked['tag1']).chr($unpacked['tag2']).chr($unpacked['tag3']).chr($unpacked['tag4']);
264
    $this->_checkSum = $unpacked['checkSum'];
265
    $this->_offset   = $unpacked['offset'];
266
    $this->_length   = $unpacked['length'];
267
  }
268
}
269
 
270
/* -------------- */
271
 
272
class OpenTypeFileTable {
273
  var $_fontFile;
274
 
275
  function _delete() {
276
  }
277
 
278
  function OpenTypeFileTable() {
279
    $this->_fontFile = null;
280
  }
281
 
282
  function setFontFile(&$fontFile) {
283
    $this->_fontFile =& $fontFile;
284
  }
285
 
286
  function &getFontFile() {
287
    return $this->_fontFile;
288
  }
289
 
290
  function _fixFWord($value) {
291
    if ($value > 65536/2) {
292
      return $value - 65536;
293
    } else {
294
      return $value;
295
    };
296
  }
297
 
298
  function _fixShort($value) {
299
    if ($value > 65536/2) {
300
      return $value - 65536;
301
    } else {
302
      return $value;
303
    };
304
  }
305
}
306
 
307
class OpenTypeFilePOST extends OpenTypeFileTable {
308
  var $_version;
309
  var $_italicAngle;
310
  var $_underlinePosition;
311
  var $_underlineThickness;
312
  var $_isFixedPitch;
313
  var $_minMemType42;
314
  var $_maxMemType42;
315
  var $_minMemType1;
316
  var $_maxMemType1;
317
 
318
  function OpenTypeFilePOST() {
319
    $this->OpenTypeFileTable();
320
  }
321
 
322
  function _read($filehandle) {
323
    $content  = fread($filehandle, 2*2 + 7*4);
324
    $unpacked = unpack("Nversion/NitalicAngle/nunderlinePosition/nunderlineThickness/NisFixedPitch/NminMemType42/NmaxMemType42/NminMemType1/NmaxMemType1", $content);
325
    $this->_version            = $unpacked['version'];
326
    $this->_italicAngle        = $unpacked['italicAngle'];
327
    $this->_underlinePosition  = $this->_fixFWord($unpacked['underlinePosition']);
328
    $this->_underlineThickness = $this->_fixFWord($unpacked['underlineThickness']);
329
    $this->_isFixedPitch       = $unpacked['isFixedPitch'];
330
    $this->_minMemType42       = $unpacked['minMemType42'];
331
    $this->_maxMemType42       = $unpacked['maxMemType42'];
332
    $this->_minMemType1        = $unpacked['minMemType1'];
333
    $this->_maxMemType1        = $unpacked['maxMemType1'];
334
  }
335
}
336
 
337
class OpenTypeFileNAME extends OpenTypeFileTable {
338
  var $_format;
339
  var $_count;
340
  var $_stringOffset;
341
  var $_nameRecord;
342
 
343
  function OpenTypeFileNAME() {
344
    $this->OpenTypeFileTable();
345
    $this->_nameRecord = array();
346
  }
347
 
348
  function _read($filehandle) {
349
    $content  = fread($filehandle, 2*3);
350
    $unpacked = unpack("nformat/ncount/nstringOffset", $content);
351
 
352
    $this->_format       = $unpacked['format'];
353
    $this->_count        = $unpacked['count'];
354
    $this->_stringOffset = $unpacked['stringOffset'];
355
 
356
    $baseOffset = ftell($filehandle) + OpenTypeFileNAMERecord::sizeof()*$this->_count;
357
 
358
    for ($i=0; $i<$this->_count; $i++) {
359
      $record =& new OpenTypeFileNAMERecord();
360
      $record->setBaseOffset($baseOffset);
361
      $record->setFontFile($this->getFontFile());
362
      $record->_read($filehandle);
363
      $this->_nameRecord[] =& $record;
364
    };
365
  }
366
 
367
  /**
368
   * Note that this function can perform "wildcard" lookups when one or more
369
   * parameters is set to null value; in this case the first encountered name
370
   * will be returned
371
   *
372
   * @return String corresponding name content or null is this name is
373
   * not defined in the font file
374
   */
375
  function lookup($platformId, $encodingId, $languageId, $nameId) {
376
    $size = count($this->_nameRecord);
377
 
378
    for ($i=0; $i<$size; $i++) {
379
      if ($this->_nameRecord[$i]->match($platformId, $encodingId, $languageId, $nameId)) {
380
        return $this->_nameRecord[$i]->getName();
381
      };
382
    }
383
 
384
    return null;
385
  }
386
}
387
 
388
class OpenTypeFileNAMERecord extends OpenTypeFileTable {
389
  var $_platformId;
390
  var $_encodingId;
391
  var $_languageId;
392
  var $_nameId;
393
  var $_length;
394
  var $_offset;
395
 
396
  var $_content;
397
  var $_baseOffset;
398
 
399
  function OpenTypeFileNAMERecord() {
400
    $this->OpenTypeFileTable();
401
    $this->_content = null;
402
  }
403
 
404
  function sizeof() {
405
    return 6*2;
406
  }
407
 
408
  function setBaseOffset($offset) {
409
    $this->_baseOffset = $offset;
410
  }
411
 
412
  function match($platformId, $encodingId, $languageId, $nameId) {
413
    return
414
      (is_null($platformId) || $platformId == $this->_platformId) &&
415
      (is_null($encodingId) || $encodingId == $this->_encodingId) &&
416
      (is_null($languageId) || $languageId == $this->_languageId) &&
417
      (is_null($nameId)     || $nameId     == $this->_nameId);
418
  }
419
 
420
  function getBaseOffset() {
421
    return $this->_baseOffset;
422
  }
423
 
424
  function getName() {
425
    if (is_null($this->_content)) {
426
      $file =& $this->getFontFile();
427
      $filehandle = $file->getFileHandle();
428
      $old_offset = ftell($filehandle);
429
 
430
      fseek($filehandle, $this->getBaseOffset() + $this->_offset, SEEK_SET);
431
      $this->_content = fread($filehandle, $this->_length);
432
 
433
      fseek($filehandle, $old_offset, SEEK_SET);
434
    };
435
 
436
    return $this->_content;
437
  }
438
 
439
  function _read($filehandle) {
440
    $content = fread($filehandle, 6*2);
441
 
442
    $unpacked = unpack("nplatformId/nencodingId/nlanguageId/nnameId/nlength/noffset", $content);
443
 
444
    $this->_platformId    = $unpacked['platformId'];
445
    $this->_encodingId    = $unpacked['encodingId'];
446
    $this->_languageId    = $unpacked['languageId'];
447
    $this->_nameId        = $unpacked['nameId'];
448
    $this->_length        = $unpacked['length'];
449
    $this->_offset        = $unpacked['offset'];
450
  }
451
}
452
 
453
/**
454
 * This table  gives global information  about the font.  The bounding
455
 * box  values  should  be   computed  using  only  glyphs  that  have
456
 * contours.  Glyphs  with  no  contours  should be  ignored  for  the
457
 * purposes of these calculations.
458
 *
459
 * Type 	Name 	Description
460
 * Fixed 	Table version number 	0x00010000 for version 1.0.
461
 * Fixed 	fontRevision 	Set by font manufacturer.
462
 * ULONG 	checkSumAdjustment 	To compute: set it to 0, sum the entire font as ULONG, then store 0xB1B0AFBA - sum.
463
 * ULONG 	magicNumber 	Set to 0x5F0F3CF5.
464
 * USHORT 	flags 	Bit 0: Baseline for font at y=0;
465
 * Bit 1: Left sidebearing point at x=0;
466
 * Bit 2: Instructions may depend on point size;
467
 * Bit 3: Force ppem to integer values for all internal scaler math; may use fractional ppem sizes if this bit is clear;
468
 * Bit 4: Instructions may alter advance width (the advance widths might not scale linearly);
469
 * Bits 5-10: These should be set according to Apple's specification . However, they are not implemented in OpenType.
470
 * Bit 11: Font data is 'lossless,' as a result of having been compressed and decompressed with the Agfa MicroType Express engine.
471
 * Bit 12: Font converted (produce compatible metrics)
472
 * Bit 13: Font optimised for ClearType
473
 * Bit 14: Reserved, set to 0
474
 * Bit 15: Reserved, set to 0
475
 * USHORT 	unitsPerEm 	Valid range is from 16 to 16384. This value should be a power of 2 for fonts that have TrueType outlines.
476
 * LONGDATETIME 	created 	Number of seconds since 12:00 midnight, January 1, 1904. 64-bit integer
477
 * LONGDATETIME 	modified 	Number of seconds since 12:00 midnight, January 1, 1904. 64-bit integer
478
 * SHORT 	xMin 	For all glyph bounding boxes.
479
 * SHORT 	yMin 	For all glyph bounding boxes.
480
 * SHORT 	xMax 	For all glyph bounding boxes.
481
 * SHORT 	yMax 	For all glyph bounding boxes.
482
 * USHORT 	macStyle 	Bit 0: Bold (if set to 1);
483
 * Bit 1: Italic (if set to 1)
484
 * Bit 2: Underline (if set to 1)
485
 * Bit 3: Outline (if set to 1)
486
 * Bit 4: Shadow (if set to 1)
487
 * Bit 5: Condensed (if set to 1)
488
 * Bit 6: Extended (if set to 1)
489
 * Bits 7-15: Reserved (set to 0).
490
 * USHORT 	lowestRecPPEM 	Smallest readable size in pixels.
491
 * SHORT 	fontDirectionHint 	0: Fully mixed directional glyphs;
492
 * 1: Only strongly left to right;
493
 * 2: Like 1 but also contains neutrals;
494
 * -1: Only strongly right to left;
495
 * -2: Like -1 but also contains neutrals. 1
496
 * SHORT 	indexToLocFormat 	0 for short offsets, 1 for long.
497
 * SHORT 	glyphDataFormat 	0 for current format.
498
 */
499
class OpenTypeFileHEAD extends OpenTypeFileTable {
500
  var $_version;
501
  var $_fontRevision;
502
  var $_checkSumAdjustment;
503
  var $_magicNumber;
504
  var $_flags;
505
  var $_unitsPerEm;
506
  var $_created;
507
  var $_modified;
508
  var $_xMin;
509
  var $_yMin;
510
  var $_xMax;
511
  var $_yMax;
512
  var $_macStyle;
513
  var $_lowestRecPPEM;
514
  var $_fontDirectionHint;
515
  var $_indexToLocFormat;
516
  var $_glyphDataFormat;
517
 
518
  function OpenTypeFileHEAD() {
519
    $this->OpenTypeFileTable();
520
  }
521
 
522
  function _read($filehandle) {
523
    $content = fread($filehandle, 4*4 + 11*2 + 2*8);
524
 
525
    $unpacked = unpack("Nversion/NfontRevision/NcheckSumAdjustment/NmagicNumber/nflags/nunitsPerEm/N2created/N2modified/nxMin/nyMin/nxMax/nyMax/nmacStyle/nlowestRecPPEM/nfontDirectionHint/nindexToLocFormat/nglyphDataFormat", $content);
526
    $this->_version            = $unpacked['version'];
527
    $this->_fontRevision       = $unpacked['fontRevision'];
528
    $this->_checkSumAdjustment = $unpacked['checkSumAdjustment'];
529
    $this->_magicNumber        = $unpacked['magicNumber'];
530
    $this->_flags              = $unpacked['flags'];
531
    $this->_unitsPerEm         = $unpacked['unitsPerEm'];
532
    $this->_created            = $unpacked['created1']  << 32 | $unpacked['created2'];
533
    $this->_modified           = $unpacked['modified1'] << 32 | $unpacked['modified2'];
534
    $this->_xMin               = $this->_fixShort($unpacked['xMin']);
535
    $this->_yMin               = $this->_fixShort($unpacked['yMin']);
536
    $this->_xMax               = $this->_fixShort($unpacked['xMax']);
537
    $this->_yMax               = $this->_fixShort($unpacked['yMax']);
538
    $this->_macStyle           = $unpacked['macStyle'];
539
    $this->_lowestRecPPEM      = $unpacked['lowestRecPPEM'];
540
    $this->_fontDirectionHint  = $this->_fixShort($unpacked['fontDirectionHint']);
541
    $this->_indexToLocFormat   = $this->_fixShort($unpacked['indexToLocFormat']);
542
    $this->_glyphDataFormat    = $this->_fixShort($unpacked['glyphDataFormat']);
543
  }
544
}
545
 
546
class OpenTypeFileCMAP extends OpenTypeFileTable {
547
  var $_header;
548
  var $_encodings;
549
  var $_subtables;
550
 
551
  function OpenTypeFileCMAP() {
552
    $this->OpenTypeFileTable();
553
    $this->_header = new OpenTypeFileCMAPHeader();
554
    $this->_encodings = array();
555
    $this->_subtables = array();
556
  }
557
 
558
  function _read($filehandle) {
559
    $this->_header->_read($filehandle);
560
 
561
    for ($i=0; $i<$this->_header->_numTables; $i++) {
562
      $encoding = new OpenTypeFileCMAPEncoding();
563
      $encoding->_read($filehandle);
564
      $this->_encodings[] =& $encoding;
565
    };
566
  }
567
 
568
  /**
569
   * It is assumed that current  file position is set to the beginning
570
   * of CMAP table
571
   */
572
  function _getSubtable($filehandle, $offset) {
573
    fseek($filehandle, $offset, SEEK_CUR);
574
 
575
    $subtable = new OpenTypeFileCMAPSubtable();
576
    $subtable->_read($filehandle);
577
 
578
    return $subtable;
579
  }
580
 
581
  function &findSubtable($platformId, $encodingId) {
582
    $file = $this->getFontFile();
583
 
584
    $index = 0;
585
    foreach ($this->_encodings as $encoding) {
586
      if ($encoding->_platformId == $platformId &&
587
          $encoding->_encodingId == $encodingId) {
588
        return $this->getSubtable($index);
589
      };
590
    };
591
 
592
    $dummy = null; return $dummy;
593
  }
594
 
595
  function &getSubtable($index) {
596
    if (!isset($this->_subtables[$index])) {
597
      $file =& $this->getFontFile();
598
      $subtable =& $file->_getCMAPSubtable($this->_encodings[$index]->_offset);
599
      $this->_subtables[$index] =& $subtable;
600
      return $subtable;
601
    } else {
602
      return $this->_subtables[$index];
603
    };
604
  }
605
}
606
 
607
/**
608
 * TODO: support for CMAP subtable formats other than 4
609
 */
610
class OpenTypeFileCMAPSubtable {
611
  var $_format;
612
  var $_content;
613
 
614
  function OpenTypeFileCMAPSubtable() {
615
    $this->_content = null;
616
  }
617
 
618
  function lookup($unicode) {
619
    return $this->_content->lookup($unicode);
620
  }
621
 
622
  function _read($filehandle) {
623
    $content = fread($filehandle, 2);
624
 
625
    $unpacked = unpack("nformat", $content);
626
    $this->_format = $unpacked['format'];
627
 
628
    switch ($this->_format) {
629
    case 4:
630
      $this->_content = new OpenTypeFileCMAPSubtable4();
631
      $this->_content->_read($filehandle);
632
      return;
633
 
634
    default:
635
      die(sprintf("Unsupported CMAP subtable format: %i", $this->_format));
636
    }
637
  }
638
}
639
 
640
class OpenTypeFileCMAPSubtable4 extends OpenTypeFileTable {
641
  var $_length;
642
  var $_language;
643
  var $_segCountX2;
644
  var $_searchRange;
645
  var $_entrySelector;
646
  var $_rangeShift;
647
  var $_endCount;
648
  var $_startCount;
649
  var $_idDelta;
650
  var $_idRangeOffset;
651
  var $_glyphIdArray;
652
 
653
  function OpenTypeFileCMAPSubtable4() {
654
    $this->_endCount      = array();
655
    $this->_startCount    = array();
656
    $this->_idDelta       = array();
657
    $this->_idRangeOffset = array();
658
    $this->_glyphIdArray  = array();
659
  }
660
 
661
  function lookup($unicode) {
662
    $index = $this->_lookupSegment($unicode);
663
    if (is_null($index)) { return null; };
664
 
665
    if ($this->_idRangeOffset[$index] != 0) {
666
      /**
667
       * If  the idRangeOffset  value for  the segment  is not  0, the
668
       * mapping  of  character  codes  relies  on  glyphIdArray.  The
669
       * character  code  offset  from   startCode  is  added  to  the
670
       * idRangeOffset value. This  sum is used as an  offset from the
671
       * current location within idRangeOffset itself to index out the
672
       * correct glyphIdArray value. This obscure indexing trick works
673
       * because glyphIdArray immediately follows idRangeOffset in the
674
       * font file. The C expression that yields the glyph index is:
675
       *
676
       * *(idRangeOffset[i]/2 + (c - startCount[i]) + &idRangeOffset[i])
677
       *
678
       * The value c  is the character code in question,  and i is the
679
       * segment index in which c  appears. If the value obtained from
680
       * the   indexing   operation   is   not  0   (which   indicates
681
       * missingGlyph),  idDelta[i] is added  to it  to get  the glyph
682
       * index. The idDelta arithmetic is modulo 65536.
683
       */
684
      $value = $this->_glyphIdArray[$unicode - $this->_startCount[$index]];
685
      return ($value + $this->_idDelta[$index]) % 65536;
686
 
687
    } else {
688
      /**
689
       * If  the  idRangeOffset  is  0,  the idDelta  value  is  added
690
       * directly to  the character code offset (i.e.  idDelta[i] + c)
691
       * to  get the  corresponding  glyph index.  Again, the  idDelta
692
       * arithmetic is modulo 65536.
693
       */
694
      return ($this->_idDelta[$index] + $unicode) % 65536;
695
    };
696
  }
697
 
698
  /**
699
   * The segments  are sorted in  order of increasing  endCode values,
700
   * and the segment values are specified in four parallel arrays. You
701
   * search for the first endCode that is greater than or equal to the
702
   * character code you want to map.
703
   */
704
  function _lookupSegment($unicode) {
705
    for ($i=0; $i<$this->_segCountX2/2; $i++) {
706
      if ($unicode <= $this->_endCount[$i]) {
707
        /**
708
         * If the corresponding startCode is less than or equal to the
709
         * character code, then you  use the corresponding idDelta and
710
         * idRangeOffset to  map the character  code to a  glyph index
711
         * (otherwise, the missingGlyph is returned).
712
         */
713
        if ($this->_startCount[$i] <= $unicode) {
714
          return $i;
715
        } else {
716
          return null;
717
        };
718
      };
719
    };
720
    return null;
721
  }
722
 
723
  function _read($filehandle) {
724
    $content = fread($filehandle, 6*2);
725
    $unpacked = unpack("nlength/nlanguage/nsegCountX2/nsearchRange/nentrySelector/nrangeShift", $content);
726
    $this->_length        = $unpacked['length'];
727
    $this->_language      = $unpacked['language'];
728
    $this->_segCountX2    = $unpacked['segCountX2'];
729
    $this->_searchRange   = $unpacked['searchRange'];
730
    $this->_entrySelector = $unpacked['entrySelector'];
731
    $this->_rangeShift    = $unpacked['rangeShift'];
732
 
733
    for ($i=0; $i<floor($this->_segCountX2/2); $i++) {
734
      $content = fread($filehandle, 2);
735
      $unpacked = unpack("nendCount", $content);
736
      $this->_endCount[] = $unpacked['endCount'];
737
    };
738
 
739
    // Skip 'reservedPad' field
740
    $content = fread($filehandle, 2);
741
 
742
    for ($i=0; $i<$this->_segCountX2/2; $i++) {
743
      $content = fread($filehandle, 2);
744
      $unpacked = unpack("nstartCount", $content);
745
      $this->_startCount[] = $unpacked['startCount'];
746
    };
747
 
748
    for ($i=0; $i<$this->_segCountX2/2; $i++) {
749
      $content = fread($filehandle, 2);
750
      $unpacked = unpack("nidDelta", $content);
751
      $this->_idDelta[] = $this->_fixShort($unpacked['idDelta']);
752
    };
753
 
754
    for ($i=0; $i<$this->_segCountX2/2; $i++) {
755
      $content = fread($filehandle, 2);
756
      $unpacked = unpack("nidRangeOffset", $content);
757
      $this->_idRangeOffset[] = $unpacked['idRangeOffset'];
758
    };
759
 
760
    for ($i=0; $i<$this->_length - 2*12; $i+=2) {
761
      $content = fread($filehandle, 2);
762
      $unpacked = unpack("nglyphId", $content);
763
      $this->_glyphIdArray[] = $unpacked['glyphId'];
764
    };
765
  }
766
}
767
 
768
class OpenTypeFileCMAPEncoding {
769
  var $_platformId;
770
  var $_encodingId;
771
  var $_offset;
772
 
773
  function _read($filehandle) {
774
    $content = fread($filehandle, 2*2+4);
775
 
776
    $unpacked = unpack("nplatformId/nencodingId/Noffset", $content);
777
    $this->_platformId = $unpacked['platformId'];
778
    $this->_encodingId = $unpacked['encodingId'];
779
    $this->_offset     = $unpacked['offset'];
780
  }
781
}
782
 
783
class OpenTypeFileCMAPHeader {
784
  var $_version;
785
  var $_numTables;
786
 
787
  function _read($filehandle) {
788
    $content = fread($filehandle, 2*2);
789
 
790
    $unpacked = unpack("nversion/nnumTables", $content);
791
    $this->_version   = $unpacked['version'];
792
    $this->_numTables = $unpacked['numTables'];
793
  }
794
}
795
 
796
// @TODO: v 1.0 support
797
class OpenTypeFileMAXP extends OpenTypeFileTable {
798
  var $_numGlyphs;
799
 
800
  function OpenTypeFileMAXP() {
801
    $this->OpenTypeFileTable();
802
  }
803
 
804
  function _read($filehandle) {
805
    $content = fread($filehandle, 4+2*1);
806
 
807
    $unpacked = unpack("Nversion/nnumGlyphs", $content);
808
 
809
    $version          = $unpacked['version'];
810
    $this->_numGlyphs = $unpacked['numGlyphs'];
811
  }
812
}
813
 
814
class OpenTypeFileHHEA extends OpenTypeFileTable {
815
  var $_ascender;
816
  var $_descender;
817
  var $_lineGap;
818
  var $_advanceWidthMax;
819
  var $_minLeftSideBearing;
820
  var $_minRightSideBearing;
821
  var $_xMaxExtent;
822
  var $_caretSlopeRise;
823
  var $_caretSlopeRun;
824
  var $_caretOffset;
825
  var $_metricDataFormat;
826
  var $_numberOfHMetrics;
827
 
828
  function OpenTypeFileHHEA() {
829
    $this->OpenTypeFileTable();
830
  }
831
 
832
  function _read($filehandle) {
833
    $content = fread($filehandle, 4+16*2);
834
 
835
    $unpacked = unpack("Nversion/nascender/ndescender/nlineGap/nadvanceWidthMax/nminLeftSideBearing/".
836
                       "nminRightSideBearing/nxMaxExtent/ncaretSlopeRise/ncaretSlopeRun/ncaretOffset/n4reserved/".
837
                       "nmetricDataFormat/nnumberOfHMetrics", $content);
838
 
839
    $version                    = $unpacked['version'];
840
    $this->_ascender            = $this->_fixFWord($unpacked['ascender']);
841
    $this->_descender           = $this->_fixFWord($unpacked['descender']);
842
    $this->_lineGap             = $this->_fixFWord($unpacked['lineGap']);
843
    $this->_advanceWidthMax     = $unpacked['advanceWidthMax'];
844
    $this->_minLeftSideBearing  = $this->_fixFWord($unpacked['minLeftSideBearing']);
845
    $this->_minRightSideBearing = $this->_fixFWord($unpacked['minRightSideBearing']);
846
    $this->_xMaxExtent          = $this->_fixFWord($unpacked['xMaxExtent']);
847
    $this->_caretSlopeRise      = $this->_fixShort($unpacked['caretSlopeRise']);
848
    $this->_caretSlopeRun       = $this->_fixShort($unpacked['caretSlopeRun']);
849
    $this->_caretOffset         = $this->_fixShort($unpacked['caretOffset']);
850
    $this->_metricDataFormat    = $this->_fixShort($unpacked['metricDataFormat']);
851
    $this->_numberOfHMetrics    = $unpacked['numberOfHMetrics'];
852
  }
853
}
854
 
855
class OpenTypeFileHMTX extends OpenTypeFileTable {
856
  var $_hMetrics;
857
  var $_leftSideBearing;
858
 
859
  function _delete() {
860
    unset($this->_hMetrics);
861
    unset($this->_leftSideBearing);
862
  }
863
 
864
  function OpenTypeFileHMTX() {
865
    $this->OpenTypeFileTable();
866
 
867
    $this->_hMetrics        = array();
868
    $this->_leftSideBearing = array();
869
  }
870
 
871
  function _read($filehandle) {
872
    $fontFile =& $this->getFontFile();
873
    $hhea =& $fontFile->getTable('hhea');
874
    $maxp =& $fontFile->getTable('maxp');
875
 
876
    for ($i=0; $i<$hhea->_numberOfHMetrics; $i++) {
877
      $content = fread($filehandle, 2*2);
878
      $unpacked = unpack("nadvanceWidth/nlsb", $content);
879
      $this->_hMetrics[] = array('advanceWidth' => $unpacked['advanceWidth'],
880
                                 'lsb'          => $this->_fixShort($unpacked['lsb']));
881
    };
882
 
883
    for ($i=0; $i<$maxp->_numGlyphs; $i++) {
884
      $content = fread($filehandle, 2);
885
      $unpacked = unpack("nitem", $content);
886
      $this->_leftSideBearing[] = $unpacked['item'];
887
    };
888
  }
889
}
890
 
891
 
892
?>