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
9 namespace FontLib\TrueType
;
11 use FontLib\AdobeFontMetrics
;
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
;
23 * @package php-font-lib
25 class File
extends BinaryStream
{
29 public $header = array();
31 private $tableOffset = 0; // Used for TTC
33 private static $raw = false;
35 protected $directory = array();
36 protected $data = array();
38 protected $glyph_subset = array();
40 public $glyph_all = array();
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"
104 function getTable() {
105 $this->parseTableEntries();
107 return $this->directory
;
110 function setTableOffset($offset) {
111 $this->tableOffset
= $offset;
115 $this->parseTableEntries();
117 $this->data
= array();
119 foreach ($this->directory
as $tag => $table) {
120 if (empty($this->data
[$tag])) {
121 $this->readTable($tag);
126 function utf8toUnicode($str) {
130 for ($i = 0; $i < $len; $i++
) {
137 elseif ($h >= 0xC2) {
138 if (($h <= 0xDF) && ($i < $len - 1)) {
139 $uni = ($h & 0x1F) << 6 |
(ord($str[++
$i]) & 0x3F);
141 elseif (($h <= 0xEF) && ($i < $len - 2)) {
142 $uni = ($h & 0x0F) << 12 |
(ord($str[++
$i]) & 0x3F) << 6 |
(ord($str[++
$i]) & 0x3F);
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);
157 function getUnicodeCharMap() {
159 foreach ($this->getData("cmap", "subtables") as $_subtable) {
160 if ($_subtable["platformID"] == 0 ||
$_subtable["platformID"] == 3 && $_subtable["platformSpecificID"] == 1) {
161 $subtable = $_subtable;
167 return $subtable["glyphIndexArray"];
173 function setSubset($subset) {
174 if (!is_array($subset)) {
175 $subset = $this->utf8toUnicode($subset);
178 $subset = array_unique($subset);
180 $glyphIndexArray = $this->getUnicodeCharMap();
182 if (!$glyphIndexArray) {
191 foreach ($subset as $code) {
192 if (!isset($glyphIndexArray[$code])) {
196 $gid = $glyphIndexArray[$code];
200 /** @var glyf $glyf */
201 $glyf = $this->getTableObject("glyf");
202 $gids = $glyf->getGlyphIDs($gids);
206 $this->glyph_subset
= $gids;
207 $this->glyph_all
= array_values($glyphIndexArray); // FIXME
210 function getSubset() {
211 if (empty($this->glyph_subset
)) {
212 return $this->glyph_all
;
215 return $this->glyph_subset
;
218 function encode($tags = array()) {
220 $tags = array_merge(array("head", "hhea", "cmap", "hmtx", "maxp", "glyf", "loca", "name", "post"), $tags);
223 $tags = array_keys($this->directory
);
226 $num_tables = count($tags);
229 Font
::d("Tables : " . implode(", ", $tags));
231 /** @var DirectoryEntry[] $entries */
233 foreach ($tags as $tag) {
234 if (!isset($this->directory
[$tag])) {
235 Font
::d(" >> '$tag' table doesn't exist");
239 $entries[$tag] = $this->directory
[$tag];
242 $this->header
->data
["numTables"] = $num_tables;
243 $this->header
->encode();
245 $directory_offset = $this->pos();
246 $offset = $directory_offset +
$num_tables * $n;
247 $this->seek($offset);
250 foreach ($entries as $entry) {
251 $entry->encode($directory_offset +
$i * $n);
256 function parseHeader() {
257 if (!empty($this->header
)) {
261 $this->seek($this->tableOffset
);
263 $this->header
= new Header($this);
264 $this->header
->parse();
267 function getFontType(){
268 $class_parts = explode("\\", get_class($this));
269 return $class_parts[1];
272 function parseTableEntries() {
273 $this->parseHeader();
275 if (!empty($this->directory
)) {
279 if (empty($this->header
->data
["numTables"])) {
284 $type = $this->getFontType();
285 $class = "FontLib\\$type\\TableDirectoryEntry";
287 for ($i = 0; $i < $this->header
->data
["numTables"]; $i++
) {
288 /** @var TableDirectoryEntry $entry */
289 $entry = new $class($this);
292 $this->directory
[$entry->tag
] = $entry;
296 function normalizeFUnit($value, $base = 1000) {
297 return round($value * ($base / $this->getData("head", "unitsPerEm")));
300 protected function readTable($tag) {
301 $this->parseTableEntries();
304 $name_canon = preg_replace("/[^a-z0-9]/", "", strtolower($tag));
306 $class = "FontLib\\Table\\Type\\$name_canon";
308 if (!isset($this->directory
[$tag]) ||
!class_exists($class)) {
313 $class = "FontLib\\Table\\Table";
316 /** @var Table $table */
317 $table = new $class($this->directory
[$tag]);
320 $this->data
[$tag] = $table;
328 public function getTableObject($name) {
329 return $this->data
[$name];
332 public function setTableObject($name, Table
$data) {
333 $this->data
[$name] = $data;
336 public function getData($name, $key = null) {
337 $this->parseTableEntries();
339 if (empty($this->data
[$name])) {
340 $this->readTable($name);
343 if (!isset($this->data
[$name])) {
348 return $this->data
[$name]->data
;
351 return $this->data
[$name]->data
[$key];
355 function addDirectoryEntry(DirectoryEntry
$entry) {
356 $this->directory
[$entry->tag
] = $entry;
359 function saveAdobeFontMetrics($file, $encoding = null) {
360 $afm = new AdobeFontMetrics($this);
361 $afm->write($file, $encoding);
365 * Get a specific name table string value from its ID
367 * @param int $nameID The name ID
369 * @return string|null
371 function getNameTableString($nameID) {
372 /** @var nameRecord[] $records */
373 $records = $this->getData("name", "records");
375 if (!isset($records[$nameID])) {
379 return $records[$nameID]->string;
385 * @return string|null
387 function getFontCopyright() {
388 return $this->getNameTableString(name
::NAME_COPYRIGHT
);
394 * @return string|null
396 function getFontName() {
397 return $this->getNameTableString(name
::NAME_NAME
);
403 * @return string|null
405 function getFontSubfamily() {
406 return $this->getNameTableString(name
::NAME_SUBFAMILY
);
410 * Get font subfamily ID
412 * @return string|null
414 function getFontSubfamilyID() {
415 return $this->getNameTableString(name
::NAME_SUBFAMILY_ID
);
421 * @return string|null
423 function getFontFullName() {
424 return $this->getNameTableString(name
::NAME_FULL_NAME
);
430 * @return string|null
432 function getFontVersion() {
433 return $this->getNameTableString(name
::NAME_VERSION
);
439 * @return string|null
441 function getFontWeight() {
442 return $this->getTableObject("OS/2")->data
["usWeightClass"];
446 * Get font Postscript name
448 * @return string|null
450 function getFontPostscriptName() {
451 return $this->getNameTableString(name
::NAME_POSTSCRIPT_NAME
);
455 $names_to_keep = array(
456 name
::NAME_COPYRIGHT
,
458 name
::NAME_SUBFAMILY
,
459 name
::NAME_SUBFAMILY_ID
,
460 name
::NAME_FULL_NAME
,
462 name
::NAME_POSTSCRIPT_NAME
,
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]);