Pull merge.
[yaffs-website] / vendor / phenx / php-font-lib / src / FontLib / TrueType / File.php
1 <?php
2 /**
3  * @package php-font-lib
4  * @link    https://github.com/PhenX/php-font-lib
5  * @author  Fabien Ménager <fabien.menager@gmail.com>
6  * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7  */
8
9 namespace FontLib\TrueType;
10
11 use FontLib\AdobeFontMetrics;
12 use FontLib\Font;
13 use FontLib\BinaryStream;
14 use FontLib\Table\Table;
15 use FontLib\Table\DirectoryEntry;
16 use FontLib\Table\Type\glyf;
17 use FontLib\Table\Type\name;
18 use FontLib\Table\Type\nameRecord;
19
20 /**
21  * TrueType font file.
22  *
23  * @package php-font-lib
24  */
25 class File extends BinaryStream {
26   /**
27    * @var Header
28    */
29   public $header = array();
30
31   private $tableOffset = 0; // Used for TTC
32
33   private static $raw = false;
34
35   protected $directory = array();
36   protected $data = array();
37
38   protected $glyph_subset = array();
39
40   public $glyph_all = array();
41
42   static $macCharNames = array(
43     ".notdef", ".null", "CR",
44     "space", "exclam", "quotedbl", "numbersign",
45     "dollar", "percent", "ampersand", "quotesingle",
46     "parenleft", "parenright", "asterisk", "plus",
47     "comma", "hyphen", "period", "slash",
48     "zero", "one", "two", "three",
49     "four", "five", "six", "seven",
50     "eight", "nine", "colon", "semicolon",
51     "less", "equal", "greater", "question",
52     "at", "A", "B", "C", "D", "E", "F", "G",
53     "H", "I", "J", "K", "L", "M", "N", "O",
54     "P", "Q", "R", "S", "T", "U", "V", "W",
55     "X", "Y", "Z", "bracketleft",
56     "backslash", "bracketright", "asciicircum", "underscore",
57     "grave", "a", "b", "c", "d", "e", "f", "g",
58     "h", "i", "j", "k", "l", "m", "n", "o",
59     "p", "q", "r", "s", "t", "u", "v", "w",
60     "x", "y", "z", "braceleft",
61     "bar", "braceright", "asciitilde", "Adieresis",
62     "Aring", "Ccedilla", "Eacute", "Ntilde",
63     "Odieresis", "Udieresis", "aacute", "agrave",
64     "acircumflex", "adieresis", "atilde", "aring",
65     "ccedilla", "eacute", "egrave", "ecircumflex",
66     "edieresis", "iacute", "igrave", "icircumflex",
67     "idieresis", "ntilde", "oacute", "ograve",
68     "ocircumflex", "odieresis", "otilde", "uacute",
69     "ugrave", "ucircumflex", "udieresis", "dagger",
70     "degree", "cent", "sterling", "section",
71     "bullet", "paragraph", "germandbls", "registered",
72     "copyright", "trademark", "acute", "dieresis",
73     "notequal", "AE", "Oslash", "infinity",
74     "plusminus", "lessequal", "greaterequal", "yen",
75     "mu", "partialdiff", "summation", "product",
76     "pi", "integral", "ordfeminine", "ordmasculine",
77     "Omega", "ae", "oslash", "questiondown",
78     "exclamdown", "logicalnot", "radical", "florin",
79     "approxequal", "increment", "guillemotleft", "guillemotright",
80     "ellipsis", "nbspace", "Agrave", "Atilde",
81     "Otilde", "OE", "oe", "endash",
82     "emdash", "quotedblleft", "quotedblright", "quoteleft",
83     "quoteright", "divide", "lozenge", "ydieresis",
84     "Ydieresis", "fraction", "currency", "guilsinglleft",
85     "guilsinglright", "fi", "fl", "daggerdbl",
86     "periodcentered", "quotesinglbase", "quotedblbase", "perthousand",
87     "Acircumflex", "Ecircumflex", "Aacute", "Edieresis",
88     "Egrave", "Iacute", "Icircumflex", "Idieresis",
89     "Igrave", "Oacute", "Ocircumflex", "applelogo",
90     "Ograve", "Uacute", "Ucircumflex", "Ugrave",
91     "dotlessi", "circumflex", "tilde", "macron",
92     "breve", "dotaccent", "ring", "cedilla",
93     "hungarumlaut", "ogonek", "caron", "Lslash",
94     "lslash", "Scaron", "scaron", "Zcaron",
95     "zcaron", "brokenbar", "Eth", "eth",
96     "Yacute", "yacute", "Thorn", "thorn",
97     "minus", "multiply", "onesuperior", "twosuperior",
98     "threesuperior", "onehalf", "onequarter", "threequarters",
99     "franc", "Gbreve", "gbreve", "Idot",
100     "Scedilla", "scedilla", "Cacute", "cacute",
101     "Ccaron", "ccaron", "dmacron"
102   );
103
104   function getTable() {
105     $this->parseTableEntries();
106
107     return $this->directory;
108   }
109
110   function setTableOffset($offset) {
111     $this->tableOffset = $offset;
112   }
113
114   function parse() {
115     $this->parseTableEntries();
116
117     $this->data = array();
118
119     foreach ($this->directory as $tag => $table) {
120       if (empty($this->data[$tag])) {
121         $this->readTable($tag);
122       }
123     }
124   }
125
126   function utf8toUnicode($str) {
127     $len = strlen($str);
128     $out = array();
129
130     for ($i = 0; $i < $len; $i++) {
131       $uni = -1;
132       $h   = ord($str[$i]);
133
134       if ($h <= 0x7F) {
135         $uni = $h;
136       }
137       elseif ($h >= 0xC2) {
138         if (($h <= 0xDF) && ($i < $len - 1)) {
139           $uni = ($h & 0x1F) << 6 | (ord($str[++$i]) & 0x3F);
140         }
141         elseif (($h <= 0xEF) && ($i < $len - 2)) {
142           $uni = ($h & 0x0F) << 12 | (ord($str[++$i]) & 0x3F) << 6 | (ord($str[++$i]) & 0x3F);
143         }
144         elseif (($h <= 0xF4) && ($i < $len - 3)) {
145           $uni = ($h & 0x0F) << 18 | (ord($str[++$i]) & 0x3F) << 12 | (ord($str[++$i]) & 0x3F) << 6 | (ord($str[++$i]) & 0x3F);
146         }
147       }
148
149       if ($uni >= 0) {
150         $out[] = $uni;
151       }
152     }
153
154     return $out;
155   }
156
157   function getUnicodeCharMap() {
158     $subtable = null;
159     foreach ($this->getData("cmap", "subtables") as $_subtable) {
160       if ($_subtable["platformID"] == 0 || $_subtable["platformID"] == 3 && $_subtable["platformSpecificID"] == 1) {
161         $subtable = $_subtable;
162         break;
163       }
164     }
165
166     if ($subtable) {
167       return $subtable["glyphIndexArray"];
168     }
169
170     return null;
171   }
172
173   function setSubset($subset) {
174     if (!is_array($subset)) {
175       $subset = $this->utf8toUnicode($subset);
176     }
177
178     $subset = array_unique($subset);
179
180     $glyphIndexArray = $this->getUnicodeCharMap();
181
182     if (!$glyphIndexArray) {
183       return;
184     }
185
186     $gids = array(
187       0, // .notdef
188       1, // .null
189     );
190
191     foreach ($subset as $code) {
192       if (!isset($glyphIndexArray[$code])) {
193         continue;
194       }
195
196       $gid        = $glyphIndexArray[$code];
197       $gids[$gid] = $gid;
198     }
199
200     /** @var glyf $glyf */
201     $glyf = $this->getTableObject("glyf");
202     $gids = $glyf->getGlyphIDs($gids);
203
204     sort($gids);
205
206     $this->glyph_subset = $gids;
207     $this->glyph_all    = array_values($glyphIndexArray); // FIXME
208   }
209
210   function getSubset() {
211     if (empty($this->glyph_subset)) {
212       return $this->glyph_all;
213     }
214
215     return $this->glyph_subset;
216   }
217
218   function encode($tags = array()) {
219     if (!self::$raw) {
220       $tags = array_merge(array("head", "hhea", "cmap", "hmtx", "maxp", "glyf", "loca", "name", "post"), $tags);
221     }
222     else {
223       $tags = array_keys($this->directory);
224     }
225
226     $num_tables = count($tags);
227     $n          = 16; // @todo
228
229     Font::d("Tables : " . implode(", ", $tags));
230
231     /** @var DirectoryEntry[] $entries */
232     $entries = array();
233     foreach ($tags as $tag) {
234       if (!isset($this->directory[$tag])) {
235         Font::d("  >> '$tag' table doesn't exist");
236         continue;
237       }
238
239       $entries[$tag] = $this->directory[$tag];
240     }
241
242     $this->header->data["numTables"] = $num_tables;
243     $this->header->encode();
244
245     $directory_offset = $this->pos();
246     $offset           = $directory_offset + $num_tables * $n;
247     $this->seek($offset);
248
249     $i = 0;
250     foreach ($entries as $entry) {
251       $entry->encode($directory_offset + $i * $n);
252       $i++;
253     }
254   }
255
256   function parseHeader() {
257     if (!empty($this->header)) {
258       return;
259     }
260
261     $this->seek($this->tableOffset);
262
263     $this->header = new Header($this);
264     $this->header->parse();
265   }
266
267   function getFontType(){
268     $class_parts = explode("\\", get_class($this));
269     return $class_parts[1];
270   }
271
272   function parseTableEntries() {
273     $this->parseHeader();
274
275     if (!empty($this->directory)) {
276       return;
277     }
278
279     if (empty($this->header->data["numTables"])) {
280       return;
281     }
282
283
284     $type = $this->getFontType();
285     $class = "FontLib\\$type\\TableDirectoryEntry";
286
287     for ($i = 0; $i < $this->header->data["numTables"]; $i++) {
288       /** @var TableDirectoryEntry $entry */
289       $entry = new $class($this);
290       $entry->parse();
291
292       $this->directory[$entry->tag] = $entry;
293     }
294   }
295
296   function normalizeFUnit($value, $base = 1000) {
297     return round($value * ($base / $this->getData("head", "unitsPerEm")));
298   }
299
300   protected function readTable($tag) {
301     $this->parseTableEntries();
302
303     if (!self::$raw) {
304       $name_canon = preg_replace("/[^a-z0-9]/", "", strtolower($tag));
305
306       $class = "FontLib\\Table\\Type\\$name_canon";
307
308       if (!isset($this->directory[$tag]) || !class_exists($class)) {
309         return;
310       }
311     }
312     else {
313       $class = "FontLib\\Table\\Table";
314     }
315
316     /** @var Table $table */
317     $table = new $class($this->directory[$tag]);
318     $table->parse();
319
320     $this->data[$tag] = $table;
321   }
322
323   /**
324    * @param $name
325    *
326    * @return Table
327    */
328   public function getTableObject($name) {
329     return $this->data[$name];
330   }
331
332   public function setTableObject($name, Table $data) {
333     $this->data[$name] = $data;
334   }
335
336   public function getData($name, $key = null) {
337     $this->parseTableEntries();
338
339     if (empty($this->data[$name])) {
340       $this->readTable($name);
341     }
342
343     if (!isset($this->data[$name])) {
344       return null;
345     }
346
347     if (!$key) {
348       return $this->data[$name]->data;
349     }
350     else {
351       return $this->data[$name]->data[$key];
352     }
353   }
354
355   function addDirectoryEntry(DirectoryEntry $entry) {
356     $this->directory[$entry->tag] = $entry;
357   }
358
359   function saveAdobeFontMetrics($file, $encoding = null) {
360     $afm = new AdobeFontMetrics($this);
361     $afm->write($file, $encoding);
362   }
363
364   /**
365    * Get a specific name table string value from its ID
366    *
367    * @param int $nameID The name ID
368    *
369    * @return string|null
370    */
371   function getNameTableString($nameID) {
372     /** @var nameRecord[] $records */
373     $records = $this->getData("name", "records");
374
375     if (!isset($records[$nameID])) {
376       return null;
377     }
378
379     return $records[$nameID]->string;
380   }
381
382   /**
383    * Get font copyright
384    *
385    * @return string|null
386    */
387   function getFontCopyright() {
388     return $this->getNameTableString(name::NAME_COPYRIGHT);
389   }
390
391   /**
392    * Get font name
393    *
394    * @return string|null
395    */
396   function getFontName() {
397     return $this->getNameTableString(name::NAME_NAME);
398   }
399
400   /**
401    * Get font subfamily
402    *
403    * @return string|null
404    */
405   function getFontSubfamily() {
406     return $this->getNameTableString(name::NAME_SUBFAMILY);
407   }
408
409   /**
410    * Get font subfamily ID
411    *
412    * @return string|null
413    */
414   function getFontSubfamilyID() {
415     return $this->getNameTableString(name::NAME_SUBFAMILY_ID);
416   }
417
418   /**
419    * Get font full name
420    *
421    * @return string|null
422    */
423   function getFontFullName() {
424     return $this->getNameTableString(name::NAME_FULL_NAME);
425   }
426
427   /**
428    * Get font version
429    *
430    * @return string|null
431    */
432   function getFontVersion() {
433     return $this->getNameTableString(name::NAME_VERSION);
434   }
435
436   /**
437    * Get font weight
438    *
439    * @return string|null
440    */
441   function getFontWeight() {
442     return $this->getTableObject("OS/2")->data["usWeightClass"];
443   }
444
445   /**
446    * Get font Postscript name
447    *
448    * @return string|null
449    */
450   function getFontPostscriptName() {
451     return $this->getNameTableString(name::NAME_POSTSCRIPT_NAME);
452   }
453
454   function reduce() {
455     $names_to_keep = array(
456       name::NAME_COPYRIGHT,
457       name::NAME_NAME,
458       name::NAME_SUBFAMILY,
459       name::NAME_SUBFAMILY_ID,
460       name::NAME_FULL_NAME,
461       name::NAME_VERSION,
462       name::NAME_POSTSCRIPT_NAME,
463     );
464
465     foreach ($this->data["name"]->data["records"] as $id => $rec) {
466       if (!in_array($id, $names_to_keep)) {
467         unset($this->data["name"]->data["records"][$id]);
468       }
469     }
470   }
471 }