PageRenderTime 52ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/schrift/schrift.php

https://github.com/markraddatz/schrift
PHP | 1060 lines | 877 code | 159 blank | 24 comment | 107 complexity | 9ecc8fec0ac7aafc115513370d7cdafb MD5 | raw file
  1. <?php
  2. # Copyright (c) 2011 Mark Raddatz <mraddatz@gmail.com>
  3. #
  4. # Schrift is a font library that reads true type fonts and creates subset
  5. # fonts containing only the needed characters. It is released under
  6. # the MIT license.
  7. class Schrift {
  8. static $default_options = array(
  9. "debug" => false,
  10. "platform_id" => 3,
  11. );
  12. function __construct($file_name, $options=array()) {
  13. $this->file_name = $file_name;
  14. $this->options = array_merge(self::$default_options, $options);
  15. $this->debug = $this->options["debug"];
  16. $this->glyph_ids = null;
  17. $this->maxp = array();
  18. $this->head = array();
  19. $this->loca = array();
  20. $this->file = null;
  21. $this->open_file();
  22. $this->font_index = $this->read_font_index();
  23. $this->table_index = $this->read_table_index();
  24. }
  25. function subset($text) {
  26. $unicodes = gettype($text) == "string"
  27. ? $this->to_unicode($text)
  28. : $text;
  29. sort($unicodes);
  30. $unicodes = array_unique($unicodes);
  31. $charmap = $this->glyph_subset($unicodes);
  32. $this->read_head_table();
  33. $this->read_maxp_table();
  34. $this->read_loca_table();
  35. $glyf = $this->write_glyph_table($charmap);
  36. $cmap = $this->write_cmap_table($charmap, $glyf["mapping"]);
  37. $format = end($glyf["loca"]) > 0xffff ? 1 : 0;
  38. $num_glyphs = count($glyf["loca"]) - 1;
  39. $tables = array();
  40. $tables[] = $cmap;
  41. $tables[] = $glyf;
  42. $tables[] = $head = $this->write_head_table($format);
  43. $tables[] = $hhea = $this->write_hhea_table();
  44. $tables[] = $hmtx = $this->write_hmtx_table();
  45. $tables[] = $loca = $this->write_loca_table($glyf["loca"]);
  46. $tables[] = $maxp = $this->write_maxp_table($num_glyphs);
  47. $tables[] = $name = $this->write_name_table();
  48. $tables[] = $post = $this->write_post_table();
  49. $font = $this->write_font_directory($tables);
  50. $tabl = $this->write_table_directory($tables);
  51. $output = $font["output"] . $tabl["output"];
  52. foreach($tables as $table) {
  53. if ($table["tag"] == "head") {
  54. $checksum = $this->font_checksum($font, $tabl, $tables);
  55. $table = $this->write_head_table($format, $checksum);
  56. }
  57. $output .= $table["output"];
  58. }
  59. return $output;
  60. }
  61. function supported_charcodes() {
  62. if ($this->glyph_ids === null) {
  63. $this->read_cmap_index();
  64. }
  65. return array_keys($this->glyph_ids);
  66. }
  67. function supported_chars() {
  68. $unicodes = $this->supported_charcodes();
  69. $codepoints = array();
  70. foreach($unicodes as $codepoint) {
  71. $codepoints[] = pack("N", $codepoint);
  72. }
  73. return iconv("ucs-4be", "utf-8", implode($codepoints));
  74. }
  75. function to_unicode($text, $encoding="utf-8") {
  76. return array_values(unpack("N*", iconv($encoding, "ucs-4be", $text)));
  77. }
  78. function open_file() {
  79. if ($this->debug) {
  80. print("parse file: " . $this->file_name . "\n");
  81. }
  82. $this->file = fopen($this->file_name, "r");
  83. }
  84. function read_font_index() {
  85. fseek($this->file, 0, SEEK_SET);
  86. $font = unpack(
  87. "Nscalar_type/" .
  88. "nnum_tables/" .
  89. "nsearch_range/" .
  90. "nentry_selector/" .
  91. "nrange_shift",
  92. fread($this->file, 12)
  93. );
  94. if ($this->debug) {
  95. print_r(array("font" => $font));
  96. }
  97. return $font;
  98. }
  99. function read_table_index() {
  100. fseek($this->file, 12, SEEK_SET);
  101. $table = array();
  102. for($i = 0; $i < $this->font_index["num_tables"]; $i++) {
  103. $table = array_merge($table, $this->read_table($i));
  104. }
  105. if ($this->debug) {
  106. print_r(array("table" => $table));
  107. }
  108. return $table;
  109. }
  110. function read_table($index) {
  111. fseek($this->file, 12 + $index * 16, SEEK_SET);
  112. $tag = fread($this->file, 4);
  113. $table = unpack(
  114. "Nchecksum/" .
  115. "Noffset/" .
  116. "Nlength",
  117. fread($this->file, 12)
  118. );
  119. $table["name"] = self::$table_name[$tag];
  120. return array($tag => $table);
  121. }
  122. function read_head_table() {
  123. fseek($this->file, $this->table_index["head"]["offset"], SEEK_SET);
  124. $table = unpack(
  125. "nversion1/" .
  126. "nversion2/" .
  127. "nfont_revision1/" .
  128. "nfont_revision2/" .
  129. "Ncheck_sum_adjustment/" .
  130. "Nmagic_number/" .
  131. "nflags/" .
  132. "nunits_per_em/" .
  133. "Ncreated1/" .
  134. "Ncreated2/" .
  135. "Nmodified1/" .
  136. "Nmodified2/" .
  137. "nx_min/" .
  138. "ny_min/" .
  139. "nx_max/" .
  140. "ny_max/" .
  141. "nmac_style/" .
  142. "nlowest_rec_ppem/" .
  143. "nfont_direction_hint/" .
  144. "nindex_to_loc_format/" .
  145. "nglyph_data_format" ,
  146. fread($this->file, 54)
  147. );
  148. if ($this->debug) {
  149. print_r(array("table" => $table));
  150. }
  151. $this->head = $table;
  152. }
  153. function read_maxp_table() {
  154. fseek($this->file, $this->table_index["maxp"]["offset"], SEEK_SET);
  155. $table = unpack(
  156. "nversion1/" .
  157. "nversion2/" .
  158. "nnum_glyphs/" .
  159. "nmax_points/" .
  160. "nmax_contours/" .
  161. "nmax_component_points/" .
  162. "nmax_component_contours/" .
  163. "nmax_zones/" .
  164. "nmax_twilight_points/".
  165. "nmax_storage/" .
  166. "nmax_function_defs/" .
  167. "nmax_instruction_defs/" .
  168. "nmax_stack_elements/" .
  169. "nmax_size_of_instructons/" .
  170. "nmax_component_elements/" .
  171. "nmax_component_depth",
  172. fread($this->file, 32)
  173. );
  174. if ($this->debug) {
  175. print_r(array("table" => $table));
  176. }
  177. $this->maxp = $table;
  178. }
  179. function read_loca_table() {
  180. fseek($this->file, $this->table_index["loca"]["offset"], SEEK_SET);
  181. $long_format = $this->head["index_to_loc_format"] == 1;
  182. $loca = array_values(unpack(
  183. $long_format ? "N*" : "n*",
  184. fread($this->file, $this->table_index["loca"]["length"])
  185. ));
  186. if (!$long_format) {
  187. foreach($loca as $i => $offset) {
  188. $loca[$i] = $offset * 2;
  189. }
  190. }
  191. $this->loca = $loca;
  192. }
  193. function glyph_subset($unicodes) {
  194. $map = array();
  195. if ($this->glyph_ids === null) {
  196. $this->read_cmap_index();
  197. }
  198. foreach($unicodes as $charcode) {
  199. $glyph_id = $this->get_glyph_ids($charcode);
  200. if ($glyph_id != 0) {
  201. $map[$charcode] = $glyph_id;
  202. }
  203. }
  204. return $map;
  205. }
  206. function set_glyph_ids($charcode, $glyph_id) {
  207. if ($glyph_id == 0) {
  208. return;
  209. }
  210. if (isset($this->glyph_ids[$charcode]) && $this->glyph_ids[$charcode] != $glyph_id) {
  211. if ($this->debug) {
  212. $old_glyph_id = $this->glyph_ids[$charcode];
  213. print("glyph for char " . $charcode . ": " . $old_glyph_id . " " . $glyph_id . "\n");
  214. }
  215. return;
  216. }
  217. $this->glyph_ids[$charcode] = $glyph_id;
  218. }
  219. function get_glyph_ids($charcode) {
  220. if (gettype($charcode) == "string") {
  221. list($charcode) = $this->to_unicode($charcode);
  222. }
  223. if (isset($this->glyph_ids[$charcode])) {
  224. return $this->glyph_ids[$charcode];
  225. }
  226. # no glyph for this char
  227. return 0;
  228. }
  229. function read_cmap_index() {
  230. fseek($this->file, $this->table_index["cmap"]["offset"], SEEK_SET);
  231. $cmap = unpack(
  232. "nversion/" .
  233. "nnumber_subtables",
  234. fread($this->file, 4)
  235. );
  236. if ($this->debug) {
  237. print_r(array("cmap" => $cmap));
  238. }
  239. for ($i = 0; $i < $cmap["number_subtables"]; $i++) {
  240. $header = $this->read_cmap_subtable_header($i);
  241. if ($header["platform_id"] == $this->options["platform_id"]) {
  242. $this->read_cmap_subtable($header["offset"]);
  243. }
  244. }
  245. ksort($this->glyph_ids);
  246. if ($this->debug) {
  247. print_r(array("glyph_ids" => $this->glyph_ids));
  248. }
  249. }
  250. function read_cmap_subtable_header($index) {
  251. fseek($this->file, $this->table_index["cmap"]["offset"] + 4 + $index * 8, SEEK_SET);
  252. $header = unpack(
  253. "nplatform_id/" .
  254. "nencoding_id/" .
  255. "Noffset",
  256. fread($this->file, 8)
  257. );
  258. $header["platform_name"] = self::$platform_name[$header["platform_id"]];
  259. $header["encoding_name"] = self::$encoding_name[$header["platform_id"]][$header["encoding_id"]];
  260. if ($this->debug) {
  261. print_r($header);
  262. }
  263. return $header;
  264. }
  265. function read_cmap_subtable($offset) {
  266. fseek($this->file, $this->table_index["cmap"]["offset"] + $offset, SEEK_SET);
  267. $cmap = unpack(
  268. "nformat",
  269. fread($this->file, 2)
  270. );
  271. if ($this->debug) {
  272. print_r($cmap);
  273. }
  274. if ($cmap["format"] == 0) {
  275. $this->read_cmap_format0($offset);
  276. }
  277. else if ($cmap["format"] == 4) {
  278. $this->read_cmap_format4($offset);
  279. }
  280. else if ($cmap["format"] == 6) {
  281. $this->read_cmap_format6($offset);
  282. }
  283. else if ($cmap["format"] == 12) {
  284. $this->read_cmap_format12($offset);
  285. }
  286. else {
  287. throw new Exception("Cmap format " . $cmap["format"] . " not supported yet.");
  288. }
  289. }
  290. function read_cmap_format0($offset) {
  291. fseek($this->file, $this->table_index["cmap"]["offset"] + $offset + 2, SEEK_SET);
  292. $cmap = unpack(
  293. "nlength/" .
  294. "nlanguage/" .
  295. "C256glyph_ids_array",
  296. fread($this->file, 260)
  297. );
  298. for($charcode = 0; $charcode < 256; $charcode++) {
  299. $glyph_id = $cmap["glyph_ids_array" . ($i + 1)];
  300. $this->set_glyph_ids($charcode, $glyph_id);
  301. }
  302. if ($this->debug) {
  303. print_r(array("format0" => $this->glyph_ids));
  304. }
  305. }
  306. function read_cmap_format4($offset) {
  307. fseek($this->file, $this->table_index["cmap"]["offset"] + $offset + 2, SEEK_SET);
  308. $header = unpack(
  309. "nlength/" .
  310. "nlanguage/" .
  311. "nseg_count_x2/" .
  312. "nsearch_range/" .
  313. "nentry_selector/" .
  314. "nrange_shift",
  315. fread($this->file, 12)
  316. );
  317. $seg_count = $header["seg_count_x2"] / 2;
  318. $cmap = unpack(
  319. "n" . $seg_count . "end_code/" .
  320. "nreserved_pad/" .
  321. "n" . $seg_count . "start_code/" .
  322. "n" . $seg_count . "id_delta/" .
  323. "n" . $seg_count . "id_range_offset",
  324. fread($this->file, $seg_count * 2 + 2 + $seg_count * 2 * 3)
  325. );
  326. unset($cmap["reserved_pad"]);
  327. # 0: end, 1: start, 2: delta, 3: rangeoffset
  328. $cmap = array_chunk($cmap, $seg_count);
  329. if ($this->debug) {
  330. print_r(array("cmap" => $cmap));
  331. }
  332. $glyph_ids_length = $header["length"] - (14 + $seg_count * 2 + 2 + $seg_count * 2 * 3);
  333. $glyph_ids = $glyph_ids_length > 0
  334. ? unpack("n*", fread($this->file, $glyph_ids_length))
  335. : array();
  336. if ($this->debug) {
  337. print_r(array(
  338. "glyph_ids_length" => $glyph_ids_length,
  339. "glyph_ids" => $glyph_ids,
  340. ));
  341. }
  342. foreach($cmap[0] as $segment => $end) {
  343. $start = $cmap[1][$segment];
  344. $delta = $cmap[2][$segment];
  345. $range_offset = $cmap[3][$segment];
  346. if ($range_offset == 0) {
  347. for($i = 0; $i <= ($end - $start); $i++) {
  348. $charcode = $start + $i;
  349. $glyph_id = ($delta + $charcode) % 0x10000;
  350. $this->set_glyph_ids($charcode, $glyph_id);
  351. }
  352. }
  353. else {
  354. $glyph_offset = ($range_offset - ($seg_count * 2 - $segment * 2)) / 2 + 1;
  355. for($i = 0; $i <= ($end - $start); $i++) {
  356. $charcode = $start + $i;
  357. $glyph_id = $glyph_ids[$glyph_offset + $i];
  358. $this->set_glyph_ids($charcode, $glyph_id);
  359. }
  360. }
  361. }
  362. }
  363. function read_cmap_format6($offset) {
  364. fseek($this->file, $this->table_index["cmap"]["offset"] + $offset + 2, SEEK_SET);
  365. $header = unpack(
  366. "nlength/" .
  367. "nlanguage/" .
  368. "nfirst_code/" .
  369. "nentry_count",
  370. fread($this->file, 8)
  371. );
  372. $cmap = unpack(
  373. "n" . $header["entry_count"],
  374. fread($this->file, 2 * $header["entry_count"])
  375. );
  376. if ($this->debug) {
  377. print_r(array("format6 cmap" => $cmap));
  378. }
  379. $start = $header["first_code"];
  380. $end = $start + $header["entry_count"];
  381. for ($i = 0; $i < ($end - $start); $i++) {
  382. $charcode = $start + $i;
  383. $glyph_id = $cmap[$i + 1];
  384. $this->set_glyph_ids($charcode, $glyph_id);
  385. if ($this->debug) {
  386. print("start ".$start." end " . $end . " i " . $i . " -> " . $glyph_id . "\n");
  387. }
  388. }
  389. }
  390. function read_cmap_format12($offset) {
  391. fseek($this->file, $this->table_index["cmap"]["offset"] + $offset + 2, SEEK_SET);
  392. $header = unpack(
  393. "nreserved/" .
  394. "Nlength/" .
  395. "Nlanguage/" .
  396. "Nn_groups",
  397. fread($this->file, 14)
  398. );
  399. for ($i = 0; $i < $header["n_groups"]; $i++) {
  400. $this->read_cmap12_group($this->table_index["cmap"]["offset"] + $offset , $i);
  401. }
  402. }
  403. function read_cmap12_group($offset, $index) {
  404. fseek($this->file, $offset + 16 + $index * 12, SEEK_SET);
  405. $cmap = unpack(
  406. "Nstart_charcode/" .
  407. "Nend_charcode/" .
  408. "Nstart_glyphcode",
  409. fread($this->file, 12)
  410. );
  411. $start = $cmap["start_charcode"];
  412. $end = $cmap["end_charcode"];
  413. $glyph = $cmap["start_glyphcode"];
  414. for($i = 0; $i <= ($end - $start); $i++) {
  415. $charcode = $start + $i;
  416. $glyph_id = $glyph + $i;
  417. $this->set_glyph_ids($charcode, $glyph_id);
  418. }
  419. }
  420. function write_cmap_table($charmap, $mapping) {
  421. $output = pack("nn", 0, 1); # version 0, 1 table
  422. $output .= pack("nn", 3, 1);# Microsoft, Unicode BMP (UCS-2)
  423. $output .= pack("N", 12); # offset
  424. $output .= $this->write_cmap_table_format4($charmap, $mapping);
  425. $length = strlen($output);
  426. $output = $this->table_padding($output);
  427. $checksum = $this->table_checksum($output);
  428. return array(
  429. "tag" => "cmap",
  430. "output" => $output,
  431. "length" => $length,
  432. "checksum" => $checksum,
  433. );
  434. }
  435. function write_cmap_table_format4($charmap, $mapping) {
  436. $end_code = "";
  437. $start_code = "";
  438. $id_delta = "";
  439. $id_range_offset = "";
  440. $last_charcode = null;
  441. $last_delta = null;
  442. $final_code = false;
  443. foreach($charmap as $charcode => $glyph_id) {
  444. $delta = $mapping[$glyph_id] - $charcode;
  445. if ($last_charcode === null || $delta != $last_delta) {
  446. if ($last_charcode !== null) {
  447. $end_code .= pack("n", $last_charcode);
  448. }
  449. $start_code .= pack("n", $charcode);
  450. $id_delta .= pack("n", (0x10000 + $delta) % 0x10000);
  451. $id_range_offset .= pack("n", 0);
  452. }
  453. $last_delta = $delta;
  454. $last_charcode = $charcode;
  455. if ($charcode == 0xffff) {
  456. $final_code = true;
  457. }
  458. }
  459. if ($last_charcode !== null) {
  460. $end_code .= pack("n", $last_charcode);
  461. }
  462. if (!$final_code) {
  463. $end_code .= pack("n", 0xffff);
  464. $start_code .= pack("n", 0xffff);
  465. $id_delta .= pack("n", 1);
  466. $id_range_offset .= pack("n", 0);
  467. }
  468. $cmap = $end_code . pack("n", 0) . $start_code . $id_delta . $id_range_offset;
  469. $length = 14 + strlen($cmap);
  470. $seg_count = strlen($end_code) / 2;
  471. $seg_count_x2 = 2 * $seg_count;
  472. $log2 = intval(log($seg_count, 2));
  473. $search_range = pow(2, $log2) * 2;
  474. $entry_selector = $log2;
  475. $range_shift = 2 * $seg_count - $search_range;
  476. $output = pack("nnnnnnn", 4, $length, 0, $seg_count_x2, $search_range,
  477. $entry_selector, $range_shift);
  478. $output .= $cmap;
  479. return $output;
  480. }
  481. function write_glyph_table($charmap) {
  482. $output = "";
  483. $loca = array();
  484. $mapping = array();
  485. $offsets = array();
  486. # undef glyph should be index 0
  487. foreach(range(0, 3) as $i) {
  488. if ($this->debug) {
  489. print("write glyp " . $i . "\n");
  490. }
  491. $this->write_glyph($i, $output, $loca, $mapping, $offsets);
  492. }
  493. # map chars and it glyphs
  494. foreach($charmap as $charcode => $glyph_id) {
  495. if ($this->debug) {
  496. print("write char " . $charcode . "\n");
  497. }
  498. $this->write_glyph($glyph_id, $output, $loca, $mapping, $offsets);
  499. }
  500. # last loca entry
  501. $loca[] = $length = strlen($output);
  502. $output = $this->table_padding($output);
  503. $checksum = $this->table_checksum($output);
  504. return array(
  505. "tag" => "glyf",
  506. "output" => $output,
  507. "length" => $length,
  508. "loca" => $loca,
  509. "mapping" => $mapping,
  510. "offsets" => $offsets,
  511. "checksum" => $checksum,
  512. );
  513. }
  514. function write_glyph($glyph_id, &$output, &$loca, &$mapping, &$offsets) {
  515. if (isset($mapping[$glyph_id])) {
  516. if ($this->debug) {
  517. print("glyph " . $glyph_id . " already mapped -> " . $mapping[$glyph_id] . "\n");
  518. }
  519. return;
  520. }
  521. if (!isset($this->loca[$glyph_id])) {
  522. throw new Exception("Glyph " . $glyph_id . " not found in loca.");
  523. }
  524. $offset = $this->loca[$glyph_id];
  525. $length = $this->loca[$glyph_id + 1] - $offset;
  526. $glyph = "";
  527. if ($length > 0) {
  528. fseek($this->file, $this->table_index["glyf"]["offset"] + $offset, SEEK_SET);
  529. $header = unpack(
  530. "snumber_of_contours",
  531. fread($this->file, 2)
  532. );
  533. $num_contours = $header["number_of_contours"];
  534. if ($num_contours >= 0) {
  535. $glyph = $this->write_simple_glyph($offset, $length, $num_contours);
  536. }
  537. else if ($num_contours == -1) {
  538. $glyph = $this->write_compound_glyph($offset, $length, $num_contours,
  539. $output, $loca, $mapping, $offsets);
  540. }
  541. }
  542. if (isset($mapping[$glyph_id])) {
  543. if ($this->debug) {
  544. print("glyph " . $glyph_id . " already mapped -> " . $mapping[$glyph_id] . "\n");
  545. }
  546. return;
  547. }
  548. $mapping[$glyph_id] = count($loca);
  549. $offsets[$glyph_id] = strlen($output);
  550. $loca[] = strlen($output);
  551. $output .= $glyph;
  552. if ($this->debug) {
  553. print("write glyph " . $glyph_id . " -> " . $mapping[$glyph_id] .
  554. " offset " . $offsets[$glyph_id] . " length " . $length . "\n");
  555. }
  556. }
  557. function write_simple_glyph($offset, $length, $number_of_contours) {
  558. $glyph = pack("s", $number_of_contours);
  559. $glyph .= fread($this->file, $length - 2);
  560. return $glyph;
  561. }
  562. function write_compound_glyph($offset, $length, $number_of_contours,
  563. &$output, &$loca, &$mapping, &$offsets) {
  564. $glyph = pack("s", $number_of_contours);
  565. $glyph .= fread($this->file, 8);
  566. $read = 2 + 8;
  567. do {
  568. $header = unpack(
  569. "nflags/" .
  570. "nglyph_id",
  571. fread($this->file, 4)
  572. );
  573. $read += 4;
  574. $flags = $header["flags"];
  575. $glyph_id = $header["glyph_id"];
  576. if ($this->debug) {
  577. print("flags " . decbin($flags) . "\n");
  578. if ($flags & 0x0001) {print(" ARG_1_AND_2_ARE_WORDS\n");}
  579. if ($flags & 0x0002) {print(" ARGS_ARE_XY_VALUES\n");}
  580. if ($flags & 0x0004) {print(" ROUND_XY_TO_GRID\n");}
  581. if ($flags & 0x0008) {print(" WE_HAVE_A_SCALE\n");}
  582. if ($flags & 0x0010) {print(" obsolet\n");}
  583. if ($flags & 0x0020) {print(" MORE_COMPONENTS\n");}
  584. if ($flags & 0x0040) {print(" WE_HAVE_AN_X_AND_Y_SCALE\n");}
  585. if ($flags & 0x0080) {print(" WE_HAVE_A_TWO_BY_TWO\n");}
  586. if ($flags & 0x0100) {print(" WE_HAVE_INSTRUCTIONS\n");}
  587. if ($flags & 0x0200) {print(" USE_MY_METRICS\n");}
  588. if ($flags & 0x0400) {print(" OVERLAP_COMPOUND\n");}
  589. print("child glyph " . $glyph_id . "\n");
  590. }
  591. $pos = ftell($this->file);
  592. $this->write_glyph($glyph_id, $output, $loca, $mapping, $offsets);
  593. fseek($this->file, $pos, SEEK_SET);
  594. $glyph .= pack("nn", $flags, $mapping[$glyph_id]);
  595. # ARG_1_AND_2_ARE_WORDS
  596. $glyph .= fread($this->file, $flags & 0x0001 ? 4 : 2);
  597. $read += $flags & 0x0001 ? 4 : 2;
  598. if ($flags & 0x0080) {
  599. # WE_HAVE_A_TWO_BY_TWO
  600. $glyph .= fread($this->file, 8);
  601. $read += 8;
  602. }
  603. else if ($flags & 0x0040) {
  604. # WE_HAVE_AN_X_AND_Y_SCALE
  605. $glyph .= fread($this->file, 4);
  606. $read += 4;
  607. }
  608. else if ($flags & 0x0008) {
  609. # WE_HAVE_A_SCALE
  610. $glyph .= fread($this->file, 2);
  611. $read += 2;
  612. }
  613. # MORE_COMPONENTS
  614. } while ($flags & 0x0020);
  615. # WE_HAVE_INSTRUCTIONS
  616. if ($flags & 0x0100) {
  617. $header = unpack(
  618. "ninstruction_length",
  619. fread($this->file, 2)
  620. );
  621. $read += 2;
  622. $instruction_length = $header["instruction_length"];
  623. $glyph .= pack("n", $instruction_length);
  624. if ($instruction_length > 0) {
  625. $glyph .= fread($this->file, $instruction_length);
  626. $read += $instruction_length;
  627. }
  628. }
  629. # add padding
  630. for($diff = $length - $read; $diff > 0; $diff--) {
  631. $glyph .= "\0";
  632. }
  633. return $glyph;
  634. }
  635. function write_head_table($format, $checksum=0) {
  636. fseek($this->file, $this->table_index["head"]["offset"], SEEK_SET);
  637. $output = fread($this->file, 4 + 4); # copy verson and fond revision
  638. # skip check_sum_adjustment
  639. fread($this->file, 4);
  640. $output .= pack("N", $checksum);
  641. $output .= fread($this->file, $this->table_index["head"]["length"] - 12 - 4);
  642. $output .= pack("nn", $format, 0);
  643. $length = strlen($output);
  644. if ($length != $this->table_index["head"]["length"]) {
  645. throw new Exception("head length differ.");
  646. }
  647. $output = $this->table_padding($output);
  648. $checksum = $this->table_checksum($output);
  649. return array(
  650. "tag" => "head",
  651. "output" => $output,
  652. "length" => $length,
  653. "checksum" => $checksum,
  654. );
  655. }
  656. function write_hhea_table() {
  657. fseek($this->file, $this->table_index["hhea"]["offset"], SEEK_SET);
  658. $output = fread($this->file, $this->table_index["hhea"]["length"]);
  659. $length = strlen($output);
  660. $output = $this->table_padding($output);
  661. $checksum = $this->table_checksum($output);
  662. return array(
  663. "tag" => "hhea",
  664. "output" => $output,
  665. "length" => $length,
  666. "checksum" => $checksum,
  667. );
  668. }
  669. function write_hmtx_table() {
  670. fseek($this->file, $this->table_index["hmtx"]["offset"], SEEK_SET);
  671. $output = fread($this->file, $this->table_index["hmtx"]["length"]);
  672. $length = strlen($output);
  673. $output = $this->table_padding($output);
  674. $checksum = $this->table_checksum($output);
  675. return array(
  676. "tag" => "hmtx",
  677. "output" => $output,
  678. "length" => $length,
  679. "checksum" => $checksum,
  680. );
  681. }
  682. function write_loca_table($loca) {
  683. $output = "";
  684. $long_format = end($loca) > 0xffff ? true : false;
  685. $format = $long_format ? "N" : "n";
  686. $multi = $long_format ? 1 : 0.5;
  687. foreach($loca as $index => $offset) {
  688. $output .= pack($format, $offset * $multi);
  689. if ($this->debug) {
  690. print("write loca " . $index . " -> " . ($offset * $multi) . "\n");
  691. }
  692. }
  693. $length = strlen($output);
  694. $output = $this->table_padding($output);
  695. $checksum = $this->table_checksum($output);
  696. return array(
  697. "tag" => "loca",
  698. "output" => $output,
  699. "length" => $length,
  700. "checksum" => $checksum,
  701. );
  702. }
  703. function write_maxp_table($num_glyphs) {
  704. fseek($this->file, $this->table_index["maxp"]["offset"], SEEK_SET);
  705. $output = fread($this->file, 4);
  706. fread($this->file, 2); # skip
  707. $output .= pack("n", $num_glyphs);
  708. $output .= fread($this->file, $this->table_index["maxp"]["length"] - 6);
  709. $length = strlen($output);
  710. if ($length != $this->table_index["maxp"]["length"]) {
  711. throw new Exception("maxp length not equal");
  712. }
  713. $output = $this->table_padding($output);
  714. $checksum = $this->table_checksum($output);
  715. return array(
  716. "tag" => "maxp",
  717. "output" => $output,
  718. "length" => $length,
  719. "checksum" => $checksum,
  720. );
  721. }
  722. function write_name_table() {
  723. fseek($this->file, $this->table_index["name"]["offset"], SEEK_SET);
  724. $output = fread($this->file, $this->table_index["name"]["length"]);
  725. $length = strlen($output);
  726. $output = $this->table_padding($output);
  727. $checksum = $this->table_checksum($output);
  728. return array(
  729. "tag" => "name",
  730. "output" => $output,
  731. "length" => $length,
  732. "checksum" => $checksum,
  733. );
  734. }
  735. function write_post_table() {
  736. fseek($this->file, $this->table_index["post"]["offset"] + 4, SEEK_SET);
  737. $output = pack("nn", 3, 0); # format 3.0
  738. $output .= fread($this->file, $this->table_index["post"]["length"] - 4);
  739. $length = strlen($output);
  740. $output = $this->table_padding($output);
  741. $checksum = $this->table_checksum($output);
  742. return array(
  743. "tag" => "post",
  744. "output" => $output,
  745. "length" => $length,
  746. "checksum" => $checksum,
  747. );
  748. }
  749. function write_font_directory($tables) {
  750. $num_tables = count($tables);
  751. $log2 = intval(log($num_tables, 2));
  752. $search_range = pow(2, $log2) * 16;
  753. $entry_selector = $log2;
  754. $range_shift = $num_tables*16 - $search_range;
  755. $output = pack("nnnnnn", 1, 0, $num_tables, $search_range, $entry_selector, $range_shift);
  756. $output = $this->table_padding($output);
  757. $checksum = $this->table_checksum($output);
  758. return array(
  759. "output" => $output,
  760. "checksum" => $checksum,
  761. );
  762. }
  763. function write_table_directory($tables) {
  764. $offset = 12 + count($tables) * 16;
  765. $table_offset = array();
  766. $output = "";
  767. foreach ($tables as $table) {
  768. $output .= $table["tag"];
  769. $output .= pack("NNN", $table["checksum"], $offset, $table["length"]);
  770. $table_offset[$table["tag"]] = $offset;
  771. $offset += strlen($table["output"]);
  772. }
  773. $output = $this->table_padding($output);
  774. $checksum = $this->table_checksum($output);
  775. return array(
  776. "output" => $output,
  777. "offsets" => $table_offset,
  778. "length" => $offset,
  779. "checksum" => $checksum,
  780. );
  781. }
  782. function table_padding($output) {
  783. # long aligning and padding with zeors
  784. while ((strlen($output) % 4) != 0) {
  785. $output .= "\0";
  786. }
  787. return $output;
  788. }
  789. function table_checksum($output) {
  790. $checksum = 0;
  791. foreach(unpack("N*", $output) as $long) {
  792. $checksum += $long;
  793. }
  794. return $checksum;
  795. }
  796. function font_checksum($font, $tabl, $tables) {
  797. $checksum = $font["checksum"] + $tabl["checksum"];
  798. foreach($tables as $table) {
  799. $checksum += $table["checksum"];
  800. }
  801. return $checksum;
  802. }
  803. static $table_name = array(
  804. "acnt" => "accent attachment",
  805. "aivar" => "axis variation",
  806. "bdat" => "bitmap data",
  807. "bheb" => "bitmap fond header",
  808. "bloc" => "bitmap location",
  809. "bsln" => "baseline",
  810. "cmap" /* ttf, otf */ => "character code mapping",
  811. "cvar" => "CVT variation",
  812. "EBSC" => "embedded bitmap scaling control",
  813. "fdsc" => "font descriptor",
  814. "feat" => "layout feature",
  815. "fmtx" => "font metrics",
  816. "fpgm" => "font program",
  817. "fvar" => "font variation",
  818. "gasp" => "grid-fitting and scan-conversion procedure",
  819. "glyf" /* ttf */ => "glyph outline",
  820. "gvar" => "glyph variation",
  821. "hdmx" => "horizontal device metrics",
  822. "head" /* ttf, otf */ => "font header",
  823. "hhea" /* ttf, otf */ => "horizontal header",
  824. "hmtx" /* ttf, otf */ => "horizontal metrics",
  825. "hsty" => "horizontal style",
  826. "just" => "justification",
  827. "kern" => "kerning",
  828. "lcar" => "ligature caret",
  829. "loca" /* ttf */ => "glyph location",
  830. "maxp" /* ttf, otf */ => "maximum profile",
  831. "mort" => "metamorphosis",
  832. "name" /* ttf */ => "name",
  833. "opbd" => "optical bounds",
  834. "OS/2" /* otf */ => "compatibility",
  835. "post" /* ttf, otf */ => "glyph name and PostScript compatibility",
  836. "prep" => "control value program",
  837. "prop" => "properties",
  838. "trak" => "tracking",
  839. "vhea" => "vertical header",
  840. "vmtx" => "vertical metrics",
  841. "Zapf" => "glyph reference",
  842. /* TrueType Outlines */
  843. "cvt " => "control value table",
  844. /* Post Script Outlines */
  845. "CFF " => "compact font format",
  846. "VORG" => "vertical origin",
  847. /* Advanced Typographic Tables */
  848. "BASE" => "baseline data",
  849. "GDEF" => "glyph definition data",
  850. "GPOS" => "glyph positioning data",
  851. "GSUB" => "glyph substituion data",
  852. "JSTF" => "justification data",
  853. /* Other OpenType Tables */
  854. "DSIG" => "digital signature",
  855. "LTSH" => "linear threshold data",
  856. "PCLT" => "PCL 5 data",
  857. "VDMX" => "Vertical device metrics",
  858. /* FontForge Tables */
  859. "FFTM" => "fontforge timestamp"
  860. );
  861. static $platform_name = array(
  862. "Unicode",
  863. "Macintosh",
  864. "(reserved; do not use)",
  865. "Microsoft",
  866. );
  867. static $encoding_name = array(
  868. array(
  869. "Unicode 1.0 semantics",
  870. "Unicode 1.1 semantics",
  871. "ISO 10646 semantics",
  872. "Unicode 2.0 or later semantics (cmap format 0, 4, 6)",
  873. "Unicode 2.0 or later semantics (cmap format 0, 4, 6, 10, 12)",
  874. "Unicode Variation Sequences (cmap format 14)",
  875. "Unicode full repetoire (cmap format 0, 4, 6, 10, 12, 13)",
  876. ),
  877. array("Roman", "Japanese", "Korean", "Arabic", "Hebrew", "Greek",
  878. "Russian", "RSymbol", "Devanagari", "Gujarati", "Oriya",
  879. "Bengali", "Tamil", "Telugu", "Kannada", "Malayalam",
  880. "Sinhalese", "Burmese", "Khmer", "Thai", "Laotian",
  881. "Georgian", "Armenian", "SimplifiedChinese", "Tibetan",
  882. "Mongolian", "Geez", "Slavic", "Vietnamese", "Sindhi",
  883. "(Uninterpreted)",
  884. ),
  885. array(),
  886. array(
  887. "Symbol",
  888. "Unicode BMP (UCS-2)",
  889. "ShiftJIS",
  890. "PRC",
  891. "Big5",
  892. "Wansung",
  893. "Johab",
  894. "Reserved",
  895. "Reserved",
  896. "Reserved",
  897. "Unicode UCS-4",
  898. ),
  899. );
  900. }
  901. ?>