PageRenderTime 60ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/system/libraries/Zend/Pdf/FileParser/Font/OpenType.php

https://bitbucket.org/micromax/vox-fw
PHP | 1126 lines | 541 code | 144 blank | 441 comment | 102 complexity | f8b9bd8a4cce47a84a313d7e0ff4ee42 MD5 | raw file
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Pdf
  17. * @subpackage FileParser
  18. * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id: OpenType.php 23775 2011-03-01 17:25:24Z ralph $
  21. */
  22. /** Zend_Pdf_FileParser_Font */
  23. require_once 'Zend/Pdf/FileParser/Font.php';
  24. /**
  25. * Abstract base class for OpenType font file parsers.
  26. *
  27. * TrueType was originally developed by Apple and was adopted as the default
  28. * font format for the Microsoft Windows platform. OpenType is an extension of
  29. * TrueType, developed jointly by Microsoft and Adobe, which adds support for
  30. * PostScript font data.
  31. *
  32. * This abstract parser class forms the foundation for concrete subclasses which
  33. * extract either TrueType or PostScript font data from the file.
  34. *
  35. * All OpenType files use big-endian byte ordering.
  36. *
  37. * The full TrueType and OpenType specifications can be found at:
  38. * <ul>
  39. * <li>{@link http://developer.apple.com/textfonts/TTRefMan/}
  40. * <li>{@link http://www.microsoft.com/typography/OTSPEC/}
  41. * <li>{@link http://partners.adobe.com/public/developer/opentype/index_spec.html}
  42. * </ul>
  43. *
  44. * @package Zend_Pdf
  45. * @subpackage FileParser
  46. * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
  47. * @license http://framework.zend.com/license/new-bsd New BSD License
  48. */
  49. abstract class Zend_Pdf_FileParser_Font_OpenType extends Zend_Pdf_FileParser_Font
  50. {
  51. /**** Instance Variables ****/
  52. /**
  53. * Stores the scaler type (font type) for the font file. See
  54. * {@link _readScalerType()}.
  55. * @var integer
  56. */
  57. protected $_scalerType = 0;
  58. /**
  59. * Stores the byte offsets to the various information tables.
  60. * @var array
  61. */
  62. protected $_tableDirectory = array();
  63. /**** Public Interface ****/
  64. /* Semi-Concrete Class Implementation */
  65. /**
  66. * Verifies that the font file is in the expected format.
  67. *
  68. * NOTE: This method should be overridden in subclasses to check the
  69. * specific format and set $this->_isScreened!
  70. *
  71. * @throws Zend_Pdf_Exception
  72. */
  73. public function screen()
  74. {
  75. if ($this->_isScreened) {
  76. return;
  77. }
  78. $this->_readScalerType();
  79. }
  80. /**
  81. * Reads and parses the font data from the file on disk.
  82. *
  83. * NOTE: This method should be overridden in subclasses to add type-
  84. * specific parsing and set $this->isParsed.
  85. *
  86. * @throws Zend_Pdf_Exception
  87. */
  88. public function parse()
  89. {
  90. if ($this->_isParsed) {
  91. return;
  92. }
  93. /* Screen the font file first, if it hasn't been done yet.
  94. */
  95. $this->screen();
  96. /* Start by reading the table directory.
  97. */
  98. $this->_parseTableDirectory();
  99. /* Then parse all of the required tables.
  100. */
  101. $this->_parseHeadTable();
  102. $this->_parseNameTable();
  103. $this->_parsePostTable();
  104. $this->_parseHheaTable();
  105. $this->_parseMaxpTable();
  106. $this->_parseOs2Table();
  107. $this->_parseHmtxTable();
  108. $this->_parseCmapTable();
  109. /* If present, parse the optional tables.
  110. */
  111. /**
  112. * @todo Add parser for kerning pairs.
  113. * @todo Add parser for ligatures.
  114. * @todo Add parser for other useful hinting tables.
  115. */
  116. }
  117. /**** Internal Methods ****/
  118. /* Parser Methods */
  119. /**
  120. * Parses the OpenType table directory.
  121. *
  122. * The table directory contains the identifier, checksum, byte offset, and
  123. * length of each of the information tables housed in the font file.
  124. *
  125. * @throws Zend_Pdf_Exception
  126. */
  127. protected function _parseTableDirectory()
  128. {
  129. $this->moveToOffset(4);
  130. $tableCount = $this->readUInt(2);
  131. $this->_debugLog('%d tables', $tableCount);
  132. /* Sanity check, in case we're not actually reading a OpenType file and
  133. * the first four bytes coincidentally matched an OpenType signature in
  134. * screen() above.
  135. *
  136. * There are at minimum 7 required tables: cmap, head, hhea, hmtx, maxp,
  137. * name, and post. In the current OpenType standard, only 32 table types
  138. * are defined, so use 50 as a practical limit.
  139. */
  140. if (($tableCount < 7) || ($tableCount > 50)) {
  141. require_once 'Zend/Pdf/Exception.php';
  142. throw new Zend_Pdf_Exception('Table count not within expected range',
  143. Zend_Pdf_Exception::BAD_TABLE_COUNT);
  144. }
  145. /* Skip the next 6 bytes, which contain values to aid a binary search.
  146. */
  147. $this->skipBytes(6);
  148. /* The directory contains four values: the name of the table, checksum,
  149. * offset to the table from the beginning of the font, and actual data
  150. * length of the table.
  151. */
  152. for ($tableIndex = 0; $tableIndex < $tableCount; $tableIndex++) {
  153. $tableName = $this->readBytes(4);
  154. /* We ignore the checksum here for two reasons: First, the PDF viewer
  155. * will do this later anyway; Second, calculating the checksum would
  156. * require unsigned integers, which PHP does not currently provide.
  157. * We may revisit this in the future.
  158. */
  159. $this->skipBytes(4);
  160. $tableOffset = $this->readUInt(4);
  161. $tableLength = $this->readUInt(4);
  162. $this->_debugLog('%s offset: 0x%x; length: %d', $tableName, $tableOffset, $tableLength);
  163. /* Sanity checks for offset and length values.
  164. */
  165. $fileSize = $this->_dataSource->getSize();
  166. if (($tableOffset < 0) || ($tableOffset > $fileSize)) {
  167. require_once 'Zend/Pdf/Exception.php';
  168. throw new Zend_Pdf_Exception("Table offset ($tableOffset) not within expected range",
  169. Zend_Pdf_Exception::INDEX_OUT_OF_RANGE);
  170. }
  171. if (($tableLength < 0) || (($tableOffset + $tableLength) > $fileSize)) {
  172. require_once 'Zend/Pdf/Exception.php';
  173. throw new Zend_Pdf_Exception("Table length ($tableLength) not within expected range",
  174. Zend_Pdf_Exception::INDEX_OUT_OF_RANGE);
  175. }
  176. $this->_tableDirectory[$tableName]['offset'] = $tableOffset;
  177. $this->_tableDirectory[$tableName]['length'] = $tableLength;
  178. }
  179. }
  180. /**
  181. * Parses the OpenType head (Font Header) table.
  182. *
  183. * The head table contains global information about the font such as the
  184. * revision number and global metrics.
  185. *
  186. * @throws Zend_Pdf_Exception
  187. */
  188. protected function _parseHeadTable()
  189. {
  190. $this->_jumpToTable('head');
  191. /* We can read any version 1 table.
  192. */
  193. $tableVersion = $this->_readTableVersion(1, 1);
  194. /* Skip the font revision number and checksum adjustment.
  195. */
  196. $this->skipBytes(8);
  197. $magicNumber = $this->readUInt(4);
  198. if ($magicNumber != 0x5f0f3cf5) {
  199. require_once 'Zend/Pdf/Exception.php';
  200. throw new Zend_Pdf_Exception('Wrong magic number. Expected: 0x5f0f3cf5; actual: '
  201. . sprintf('%x', $magicNumber),
  202. Zend_Pdf_Exception::BAD_MAGIC_NUMBER);
  203. }
  204. /* Most of the flags we ignore, but there are a few values that are
  205. * useful for our layout routines.
  206. */
  207. $flags = $this->readUInt(2);
  208. $this->baselineAtZero = $this->isBitSet(0, $flags);
  209. $this->useIntegerScaling = $this->isBitSet(3, $flags);
  210. $this->unitsPerEm = $this->readUInt(2);
  211. $this->_debugLog('Units per em: %d', $this->unitsPerEm);
  212. /* Skip creation and modification date/time.
  213. */
  214. $this->skipBytes(16);
  215. $this->xMin = $this->readInt(2);
  216. $this->yMin = $this->readInt(2);
  217. $this->xMax = $this->readInt(2);
  218. $this->yMax = $this->readInt(2);
  219. $this->_debugLog('Font bounding box: %d %d %d %d',
  220. $this->xMin, $this->yMin, $this->xMax, $this->yMax);
  221. /* The style bits here must match the fsSelection bits in the OS/2
  222. * table, if present.
  223. */
  224. $macStyleBits = $this->readUInt(2);
  225. $this->isBold = $this->isBitSet(0, $macStyleBits);
  226. $this->isItalic = $this->isBitSet(1, $macStyleBits);
  227. /* We don't need the remainder of data in this table: smallest readable
  228. * size, font direction hint, indexToLocFormat, and glyphDataFormat.
  229. */
  230. }
  231. /**
  232. * Parses the OpenType name (Naming) table.
  233. *
  234. * The name table contains all of the identifying strings associated with
  235. * the font such as its name, copyright, trademark, license, etc.
  236. *
  237. * @throws Zend_Pdf_Exception
  238. */
  239. protected function _parseNameTable()
  240. {
  241. $this->_jumpToTable('name');
  242. $baseOffset = $this->_tableDirectory['name']['offset'];
  243. /* The name table begins with a short header, followed by each of the
  244. * fixed-length name records, followed by the variable-length strings.
  245. */
  246. /* We only understand version 0 tables.
  247. */
  248. $tableFormat = $this->readUInt(2);
  249. if ($tableFormat != 0) {
  250. require_once 'Zend/Pdf/Exception.php';
  251. throw new Zend_Pdf_Exception("Unable to read format $tableFormat table",
  252. Zend_Pdf_Exception::DONT_UNDERSTAND_TABLE_VERSION);
  253. }
  254. $this->_debugLog('Format %d table', $tableFormat);
  255. $nameCount = $this->readUInt(2);
  256. $this->_debugLog('%d name strings', $nameCount);
  257. $storageOffset = $this->readUInt(2) + $baseOffset;
  258. $this->_debugLog('Storage offset: 0x%x', $storageOffset);
  259. /* Scan the name records for those we're interested in. We'll skip over
  260. * encodings and languages we don't understand or support. Prefer the
  261. * Microsoft Unicode encoding for a given name/language combination, but
  262. * use Mac Roman if nothing else is available. We will extract the
  263. * actual strings later.
  264. */
  265. $nameRecords = array();
  266. for ($nameIndex = 0; $nameIndex < $nameCount; $nameIndex++) {
  267. $platformID = $this->readUInt(2);
  268. $encodingID = $this->readUInt(2);
  269. if (! ( (($platformID == 3) && ($encodingID == 1)) || // Microsoft Unicode
  270. (($platformID == 1) && ($encodingID == 0)) // Mac Roman
  271. ) ) {
  272. $this->skipBytes(8); // Not a supported encoding. Move on.
  273. continue;
  274. }
  275. $languageID = $this->readUInt(2);
  276. $nameID = $this->readUInt(2);
  277. $nameLength = $this->readUInt(2);
  278. $nameOffset = $this->readUInt(2);
  279. $languageCode = $this->_languageCodeForPlatform($platformID, $languageID);
  280. if ($languageCode === null) {
  281. $this->_debugLog('Skipping languageID: 0x%x; platformID %d', $languageID, $platformID);
  282. continue; // Not a supported language. Move on.
  283. }
  284. $this->_debugLog('Adding nameID: %d; languageID: 0x%x; platformID: %d; offset: 0x%x (0x%x); length: %d',
  285. $nameID, $languageID, $platformID, $baseOffset + $nameOffset, $nameOffset, $nameLength);
  286. /* Entries in the name table are sorted by platform ID. If an entry
  287. * exists for both Mac Roman and Microsoft Unicode, the Unicode entry
  288. * will prevail since it is processed last.
  289. */
  290. $nameRecords[$nameID][$languageCode] = array('platform' => $platformID,
  291. 'offset' => $nameOffset,
  292. 'length' => $nameLength );
  293. }
  294. /* Now go back and extract the interesting strings.
  295. */
  296. $fontNames = array();
  297. foreach ($nameRecords as $name => $languages) {
  298. foreach ($languages as $language => $attributes) {
  299. $stringOffset = $storageOffset + $attributes['offset'];
  300. $this->moveToOffset($stringOffset);
  301. if ($attributes['platform'] == 3) {
  302. $string = $this->readStringUTF16($attributes['length']);
  303. } else {
  304. $string = $this->readStringMacRoman($attributes['length']);
  305. }
  306. $fontNames[$name][$language] = $string;
  307. }
  308. }
  309. $this->names = $fontNames;
  310. }
  311. /**
  312. * Parses the OpenType post (PostScript Information) table.
  313. *
  314. * The post table contains additional information required for using the font
  315. * on PostScript printers. It also contains the preferred location and
  316. * thickness for an underline, which is used by our layout code.
  317. *
  318. * @throws Zend_Pdf_Exception
  319. */
  320. protected function _parsePostTable()
  321. {
  322. $this->_jumpToTable('post');
  323. /* We can read versions 1-4 tables.
  324. */
  325. $tableVersion = $this->_readTableVersion(1, 4);
  326. $this->italicAngle = $this->readFixed(16, 16);
  327. $this->underlinePosition = $this->readInt(2);
  328. $this->underlineThickness = $this->readInt(2);
  329. $fixedPitch = $this->readUInt(4);
  330. $this->isMonospaced = ($fixedPitch !== 0);
  331. /* Skip over PostScript virtual memory usage.
  332. */
  333. $this->skipBytes(16);
  334. /* The format of the remainder of this table is dependent on the table
  335. * version. However, since it contains glyph ordering information and
  336. * PostScript names which we don't use, move on. (This may change at
  337. * some point in the future though...)
  338. */
  339. }
  340. /**
  341. * Parses the OpenType hhea (Horizontal Header) table.
  342. *
  343. * The hhea table contains information used for horizontal layout. It also
  344. * contains some vertical layout information for Apple systems. The vertical
  345. * layout information for the PDF file is usually taken from the OS/2 table.
  346. *
  347. * @throws Zend_Pdf_Exception
  348. */
  349. protected function _parseHheaTable()
  350. {
  351. $this->_jumpToTable('hhea');
  352. /* We can read any version 1 table.
  353. */
  354. $tableVersion = $this->_readTableVersion(1, 1);
  355. /* The typographic ascent, descent, and line gap values are Apple-
  356. * specific. Similar values exist in the OS/2 table. We'll use these
  357. * values unless better values are found in OS/2.
  358. */
  359. $this->ascent = $this->readInt(2);
  360. $this->descent = $this->readInt(2);
  361. $this->lineGap = $this->readInt(2);
  362. /* The descent value is supposed to be negative--it's the distance
  363. * relative to the baseline. However, some fonts improperly store a
  364. * positive value in this field. If a positive value is found, flip the
  365. * sign and record a warning in the debug log that we did this.
  366. */
  367. if ($this->descent > 0) {
  368. $this->_debugLog('Warning: Font should specify negative descent. Actual: %d; Using %d',
  369. $this->descent, -$this->descent);
  370. $this->descent = -$this->descent;
  371. }
  372. /* Skip over advance width, left and right sidebearing, max x extent,
  373. * caret slope rise, run, and offset, and the four reserved fields.
  374. */
  375. $this->skipBytes(22);
  376. /* These values are needed to read the hmtx table.
  377. */
  378. $this->metricDataFormat = $this->readInt(2);
  379. $this->numberHMetrics = $this->readUInt(2);
  380. $this->_debugLog('hmtx data format: %d; number of metrics: %d',
  381. $this->metricDataFormat, $this->numberHMetrics);
  382. }
  383. /**
  384. * Parses the OpenType hhea (Horizontal Header) table.
  385. *
  386. * The hhea table contains information used for horizontal layout. It also
  387. * contains some vertical layout information for Apple systems. The vertical
  388. * layout information for the PDF file is usually taken from the OS/2 table.
  389. *
  390. * @throws Zend_Pdf_Exception
  391. */
  392. protected function _parseMaxpTable()
  393. {
  394. $this->_jumpToTable('maxp');
  395. /* We don't care about table version.
  396. */
  397. $this->_readTableVersion(0, 1);
  398. /* The number of glyphs in the font.
  399. */
  400. $this->numGlyphs = $this->readUInt(2);
  401. $this->_debugLog('number of glyphs: %d', $this->numGlyphs);
  402. // Skip other maxp table entries (if presented with table version 1.0)...
  403. }
  404. /**
  405. * Parses the OpenType OS/2 (OS/2 and Windows Metrics) table.
  406. *
  407. * The OS/2 table contains additional metrics data that is required to use
  408. * the font on the OS/2 or Microsoft Windows platforms. It is not required
  409. * for Macintosh fonts, so may not always be present. When available, we use
  410. * this table to determine most of the vertical layout and stylistic
  411. * information and for the font.
  412. *
  413. * @throws Zend_Pdf_Exception
  414. */
  415. protected function _parseOs2Table()
  416. {
  417. if (! $this->numberHMetrics) {
  418. require_once 'Zend/Pdf/Exception.php';
  419. throw new Zend_Pdf_Exception("hhea table must be parsed prior to calling this method",
  420. Zend_Pdf_Exception::PARSED_OUT_OF_ORDER);
  421. }
  422. try {
  423. $this->_jumpToTable('OS/2');
  424. } catch (Zend_Pdf_Exception $e) {
  425. /* This table is not always present. If missing, use default values.
  426. */
  427. require_once 'Zend/Pdf/Exception.php';
  428. if ($e->getCode() == Zend_Pdf_Exception::REQUIRED_TABLE_NOT_FOUND) {
  429. $this->_debugLog('No OS/2 table found. Using default values');
  430. $this->fontWeight = Zend_Pdf_Font::WEIGHT_NORMAL;
  431. $this->fontWidth = Zend_Pdf_Font::WIDTH_NORMAL;
  432. $this->isEmbeddable = true;
  433. $this->isSubsettable = true;
  434. $this->strikeThickness = $this->unitsPerEm * 0.05;
  435. $this->strikePosition = $this->unitsPerEm * 0.225;
  436. $this->isSerifFont = false; // the style of the font is unknown
  437. $this->isSansSerifFont = false;
  438. $this->isOrnamentalFont = false;
  439. $this->isScriptFont = false;
  440. $this->isSymbolicFont = false;
  441. $this->isAdobeLatinSubset = false;
  442. $this->vendorID = '';
  443. $this->xHeight = 0;
  444. $this->capitalHeight = 0;
  445. return;
  446. } else {
  447. /* Something else went wrong. Throw this exception higher up the chain.
  448. */
  449. throw $e;
  450. throw new Zend_Pdf_Exception($e->getMessage(), $e->getCode(), $e);
  451. }
  452. }
  453. /* Version 0 tables are becoming rarer these days. They are only found
  454. * in older fonts.
  455. *
  456. * Version 1 formally defines the Unicode character range bits and adds
  457. * two new fields to the end of the table.
  458. *
  459. * Version 2 defines several additional flags to the embedding bits
  460. * (fsType field) and five new fields to the end of the table.
  461. *
  462. * Versions 2 and 3 are structurally identical. There are only two
  463. * significant differences between the two: First, in version 3, the
  464. * average character width (xAvgCharWidth field) is calculated using all
  465. * non-zero width glyphs in the font instead of just the Latin lower-
  466. * case alphabetic characters; this doesn't affect us. Second, in
  467. * version 3, the embedding bits (fsType field) have been made mutually
  468. * exclusive; see additional discusson on this below.
  469. *
  470. * We can understand all four of these table versions.
  471. */
  472. $tableVersion = $this->readUInt(2);
  473. if (($tableVersion < 0) || ($tableVersion > 3)) {
  474. require_once 'Zend/Pdf/Exception.php';
  475. throw new Zend_Pdf_Exception("Unable to read version $tableVersion table",
  476. Zend_Pdf_Exception::DONT_UNDERSTAND_TABLE_VERSION);
  477. }
  478. $this->_debugLog('Version %d table', $tableVersion);
  479. $this->averageCharWidth = $this->readInt(2);
  480. /* Indicates the visual weight and aspect ratio of the characters. Used
  481. * primarily to logically sort fonts in lists. Also used to help choose
  482. * a more appropriate substitute font when necessary. See the WEIGHT_
  483. * and WIDTH_ constants defined in Zend_Pdf_Font.
  484. */
  485. $this->fontWeight = $this->readUInt(2);
  486. $this->fontWidth = $this->readUInt(2);
  487. /* Describes the font embedding licensing rights. We can only embed and
  488. * subset a font when given explicit permission.
  489. *
  490. * NOTE: We always interpret these bits according to the rules defined
  491. * in version 3 of this table, regardless of the actual version. This
  492. * means we will perform our checks in order from the most-restrictive
  493. * to the least.
  494. */
  495. $embeddingFlags = $this->readUInt(2);
  496. $this->_debugLog('Embedding flags: %d', $embeddingFlags);
  497. if ($this->isBitSet(9, $embeddingFlags)) {
  498. /* Only bitmaps may be embedded. We don't have the ability to strip
  499. * outlines from fonts yet, so this means no embed.
  500. */
  501. $this->isEmbeddable = false;
  502. } else if ($this->isBitSet(1, $embeddingFlags)) {
  503. /* Restricted license embedding. We currently don't have any way to
  504. * enforce this, so interpret this as no embed. This may be revised
  505. * in the future...
  506. */
  507. $this->isEmbeddable = false;
  508. } else {
  509. /* The remainder of the bit settings grant us permission to embed
  510. * the font. There may be additional usage rights granted or denied
  511. * but those only affect the PDF viewer application, not our code.
  512. */
  513. $this->isEmbeddable = true;
  514. }
  515. $this->_debugLog('Font ' . ($this->isEmbeddable ? 'may' : 'may not') . ' be embedded');
  516. $isSubsettable = $this->isBitSet($embeddingFlags, 8);
  517. /* Recommended size and offset for synthesized subscript characters.
  518. */
  519. $this->subscriptXSize = $this->readInt(2);
  520. $this->subscriptYSize = $this->readInt(2);
  521. $this->subscriptXOffset = $this->readInt(2);
  522. $this->subscriptYOffset = $this->readInt(2);
  523. /* Recommended size and offset for synthesized superscript characters.
  524. */
  525. $this->superscriptXSize = $this->readInt(2);
  526. $this->superscriptYSize = $this->readInt(2);
  527. $this->superscriptXOffset = $this->readInt(2);
  528. $this->superscriptYOffset = $this->readInt(2);
  529. /* Size and vertical offset for the strikethrough.
  530. */
  531. $this->strikeThickness = $this->readInt(2);
  532. $this->strikePosition = $this->readInt(2);
  533. /* Describes the class of font: serif, sans serif, script. etc. These
  534. * values are defined here:
  535. * http://www.microsoft.com/OpenType/OTSpec/ibmfc.htm
  536. */
  537. $familyClass = ($this->readUInt(2) >> 8); // don't care about subclass
  538. $this->_debugLog('Font family class: %d', $familyClass);
  539. $this->isSerifFont = ((($familyClass >= 1) && ($familyClass <= 5)) ||
  540. ($familyClass == 7));
  541. $this->isSansSerifFont = ($familyClass == 8);
  542. $this->isOrnamentalFont = ($familyClass == 9);
  543. $this->isScriptFont = ($familyClass == 10);
  544. $this->isSymbolicFont = ($familyClass == 12);
  545. /* Skip over the PANOSE number. The interesting values for us overlap
  546. * with the font family class defined above.
  547. */
  548. $this->skipBytes(10);
  549. /* The Unicode range is made up of four 4-byte unsigned long integers
  550. * which are used as bitfields covering a 128-bit range. Each bit
  551. * represents a Unicode code block. If the bit is set, this font at
  552. * least partially covers the characters in that block.
  553. */
  554. $unicodeRange1 = $this->readUInt(4);
  555. $unicodeRange2 = $this->readUInt(4);
  556. $unicodeRange3 = $this->readUInt(4);
  557. $unicodeRange4 = $this->readUInt(4);
  558. $this->_debugLog('Unicode ranges: 0x%x 0x%x 0x%x 0x%x',
  559. $unicodeRange1, $unicodeRange2, $unicodeRange3, $unicodeRange4);
  560. /* The Unicode range is currently only used to decide if the character
  561. * set covered by the font is a subset of the Adobe Latin set, meaning
  562. * it only has the basic latin set. If it covers any other characters,
  563. * even any of the extended latin characters, it is considered symbolic
  564. * to PDF and must be described differently in the Font Descriptor.
  565. */
  566. /**
  567. * @todo Font is recognized as Adobe Latin subset font if it only contains
  568. * Basic Latin characters (only bit 0 of Unicode range bits is set).
  569. * Actually, other Unicode subranges like General Punctuation (bit 31) also
  570. * fall into Adobe Latin characters. So this code has to be modified.
  571. */
  572. $this->isAdobeLatinSubset = (($unicodeRange1 == 1) && ($unicodeRange2 == 0) &&
  573. ($unicodeRange3 == 0) && ($unicodeRange4 == 0));
  574. $this->_debugLog(($this->isAdobeLatinSubset ? 'Is' : 'Is not') . ' a subset of Adobe Latin');
  575. $this->vendorID = $this->readBytes(4);
  576. /* Skip the font style bits. We use the values found in the 'head' table.
  577. * Also skip the first Unicode and last Unicode character indicies. Our
  578. * cmap implementation does not need these values.
  579. */
  580. $this->skipBytes(6);
  581. /* Typographic ascender, descender, and line gap. These values are
  582. * preferred to those in the 'hhea' table.
  583. */
  584. $this->ascent = $this->readInt(2);
  585. $this->descent = $this->readInt(2);
  586. $this->lineGap = $this->readInt(2);
  587. /* The descent value is supposed to be negative--it's the distance
  588. * relative to the baseline. However, some fonts improperly store a
  589. * positive value in this field. If a positive value is found, flip the
  590. * sign and record a warning in the debug log that we did this.
  591. */
  592. if ($this->descent > 0) {
  593. $this->_debugLog('Warning: Font should specify negative descent. Actual: %d; Using %d',
  594. $this->descent, -$this->descent);
  595. $this->descent = -$this->descent;
  596. }
  597. /* Skip over Windows-specific ascent and descent.
  598. */
  599. $this->skipBytes(4);
  600. /* Versions 0 and 1 tables do not contain the x or capital height
  601. * fields. Record zero for unknown.
  602. */
  603. if ($tableVersion < 2) {
  604. $this->xHeight = 0;
  605. $this->capitalHeight = 0;
  606. } else {
  607. /* Skip over the Windows code page coverages. We are only concerned
  608. * with Unicode coverage.
  609. */
  610. $this->skipBytes(8);
  611. $this->xHeight = $this->readInt(2);
  612. $this->capitalHeight = $this->readInt(2);
  613. /* Ignore the remaining fields in this table. They are Windows-specific.
  614. */
  615. }
  616. /**
  617. * @todo Obtain the x and capital heights from the 'glyf' table if they
  618. * haven't been supplied here instead of storing zero.
  619. */
  620. }
  621. /**
  622. * Parses the OpenType hmtx (Horizontal Metrics) table.
  623. *
  624. * The hmtx table contains the horizontal metrics for every glyph contained
  625. * within the font. These are the critical values for horizontal layout of
  626. * text.
  627. *
  628. * @throws Zend_Pdf_Exception
  629. */
  630. protected function _parseHmtxTable()
  631. {
  632. $this->_jumpToTable('hmtx');
  633. if (! $this->numberHMetrics) {
  634. require_once 'Zend/Pdf/Exception.php';
  635. throw new Zend_Pdf_Exception("hhea table must be parsed prior to calling this method",
  636. Zend_Pdf_Exception::PARSED_OUT_OF_ORDER);
  637. }
  638. /* We only understand version 0 tables.
  639. */
  640. if ($this->metricDataFormat != 0) {
  641. require_once 'Zend/Pdf/Exception.php';
  642. throw new Zend_Pdf_Exception("Unable to read format $this->metricDataFormat table.",
  643. Zend_Pdf_Exception::DONT_UNDERSTAND_TABLE_VERSION);
  644. }
  645. /* The hmtx table has no header. For each glpyh in the font, it contains
  646. * the glyph's advance width and its left side bearing. We don't use the
  647. * left side bearing.
  648. */
  649. $glyphWidths = array();
  650. for ($i = 0; $i < $this->numberHMetrics; $i++) {
  651. $glyphWidths[$i] = $this->readUInt(2);
  652. $this->skipBytes(2);
  653. }
  654. /* Populate last value for the rest of array
  655. */
  656. while (count($glyphWidths) < $this->numGlyphs) {
  657. $glyphWidths[] = end($glyphWidths);
  658. }
  659. $this->glyphWidths = $glyphWidths;
  660. /* There is an optional table of left side bearings which is sometimes
  661. * used for monospaced fonts. We don't use the left side bearing, so
  662. * we can safely ignore it.
  663. */
  664. }
  665. /**
  666. * Parses the OpenType cmap (Character to Glyph Mapping) table.
  667. *
  668. * The cmap table provides the maps from character codes to font glyphs.
  669. * There are usually at least two character maps in a font: Microsoft Unicode
  670. * and Macintosh Roman. For very complex fonts, there may also be mappings
  671. * for the characters in the Unicode Surrogates Area, which are UCS-4
  672. * characters.
  673. *
  674. * @todo Need to rework the selection logic for picking a subtable. We should
  675. * have an explicit list of preferences, followed by a list of those that
  676. * are tolerable. Most specifically, since everything above this layer deals
  677. * in Unicode, we need to be sure to only accept format 0 MacRoman tables.
  678. *
  679. * @throws Zend_Pdf_Exception
  680. */
  681. protected function _parseCmapTable()
  682. {
  683. $this->_jumpToTable('cmap');
  684. $baseOffset = $this->_tableDirectory['cmap']['offset'];
  685. /* We only understand version 0 tables.
  686. */
  687. $tableVersion = $this->readUInt(2);
  688. if ($tableVersion != 0) {
  689. require_once 'Zend/Pdf/Exception.php';
  690. throw new Zend_Pdf_Exception("Unable to read version $tableVersion table",
  691. Zend_Pdf_Exception::DONT_UNDERSTAND_TABLE_VERSION);
  692. }
  693. $this->_debugLog('Version %d table', $tableVersion);
  694. $subtableCount = $this->readUInt(2);
  695. $this->_debugLog('%d subtables', $subtableCount);
  696. /* Like the name table, there may be many different encoding subtables
  697. * present. Ideally, we are looking for an acceptable Unicode table.
  698. */
  699. $subtables = array();
  700. for ($subtableIndex = 0; $subtableIndex < $subtableCount; $subtableIndex++) {
  701. $platformID = $this->readUInt(2);
  702. $encodingID = $this->readUInt(2);
  703. if (! ( (($platformID == 0) && ($encodingID == 3)) || // Unicode 2.0 or later
  704. (($platformID == 0) && ($encodingID == 0)) || // Unicode
  705. (($platformID == 3) && ($encodingID == 1)) || // Microsoft Unicode
  706. (($platformID == 1) && ($encodingID == 0)) // Mac Roman
  707. ) ) {
  708. $this->_debugLog('Unsupported encoding: platformID: %d; encodingID: %d; skipping',
  709. $platformID, $encodingID);
  710. $this->skipBytes(4);
  711. continue;
  712. }
  713. $subtableOffset = $this->readUInt(4);
  714. if ($subtableOffset < 0) { // Sanity check for 4-byte unsigned on 32-bit platform
  715. $this->_debugLog('Offset 0x%x out of range for platformID: %d; skipping',
  716. $subtableOffset, $platformID);
  717. continue;
  718. }
  719. $this->_debugLog('Found subtable; platformID: %d; encodingID: %d; offset: 0x%x (0x%x)',
  720. $platformID, $encodingID, $baseOffset + $subtableOffset, $subtableOffset);
  721. $subtables[$platformID][$encodingID][] = $subtableOffset;
  722. }
  723. /* In preferred order, find a subtable to use.
  724. */
  725. $offsets = array();
  726. /* Unicode 2.0 or later semantics
  727. */
  728. if (isset($subtables[0][3])) {
  729. foreach ($subtables[0][3] as $offset) {
  730. $offsets[] = $offset;
  731. }
  732. }
  733. /* Unicode default semantics
  734. */
  735. if (isset($subtables[0][0])) {
  736. foreach ($subtables[0][0] as $offset) {
  737. $offsets[] = $offset;
  738. }
  739. }
  740. /* Microsoft Unicode
  741. */
  742. if (isset($subtables[3][1])) {
  743. foreach ($subtables[3][1] as $offset) {
  744. $offsets[] = $offset;
  745. }
  746. }
  747. /* Mac Roman.
  748. */
  749. if (isset($subtables[1][0])) {
  750. foreach ($subtables[1][0] as $offset) {
  751. $offsets[] = $offset;
  752. }
  753. }
  754. $cmapType = -1;
  755. foreach ($offsets as $offset) {
  756. $cmapOffset = $baseOffset + $offset;
  757. $this->moveToOffset($cmapOffset);
  758. $format = $this->readUInt(2);
  759. $language = -1;
  760. switch ($format) {
  761. case 0x0:
  762. $cmapLength = $this->readUInt(2);
  763. $language = $this->readUInt(2);
  764. if ($language != 0) {
  765. $this->_debugLog('Type 0 cmap tables must be language-independent;'
  766. . ' language: %d; skipping', $language);
  767. continue;
  768. }
  769. break;
  770. case 0x4: // break intentionally omitted
  771. case 0x6:
  772. $cmapLength = $this->readUInt(2);
  773. $language = $this->readUInt(2);
  774. if ($language != 0) {
  775. $this->_debugLog('Warning: cmap tables must be language-independent - this font'
  776. . ' may not work properly; language: %d', $language);
  777. }
  778. break;
  779. case 0x2: // break intentionally omitted
  780. case 0x8: // break intentionally omitted
  781. case 0xa: // break intentionally omitted
  782. case 0xc:
  783. $this->_debugLog('Format: 0x%x currently unsupported; skipping', $format);
  784. continue;
  785. //$this->skipBytes(2);
  786. //$cmapLength = $this->readUInt(4);
  787. //$language = $this->readUInt(4);
  788. //if ($language != 0) {
  789. // $this->_debugLog('Warning: cmap tables must be language-independent - this font'
  790. // . ' may not work properly; language: %d', $language);
  791. //}
  792. //break;
  793. default:
  794. $this->_debugLog('Unknown subtable format: 0x%x; skipping', $format);
  795. continue;
  796. }
  797. $cmapType = $format;
  798. break;
  799. }
  800. if ($cmapType == -1) {
  801. require_once 'Zend/Pdf/Exception.php';
  802. throw new Zend_Pdf_Exception('Unable to find usable cmap table',
  803. Zend_Pdf_Exception::CANT_FIND_GOOD_CMAP);
  804. }
  805. /* Now extract the subtable data and create a Zend_Pdf_FontCmap object.
  806. */
  807. $this->_debugLog('Using cmap type %d; offset: 0x%x; length: %d',
  808. $cmapType, $cmapOffset, $cmapLength);
  809. $this->moveToOffset($cmapOffset);
  810. $cmapData = $this->readBytes($cmapLength);
  811. require_once 'Zend/Pdf/Cmap.php';
  812. $this->cmap = Zend_Pdf_Cmap::cmapWithTypeData($cmapType, $cmapData);
  813. }
  814. /**
  815. * Reads the scaler type from the header of the OpenType font file and
  816. * returns it as an unsigned long integer.
  817. *
  818. * The scaler type defines the type of font: OpenType font files may contain
  819. * TrueType or PostScript outlines. Throws an exception if the scaler type
  820. * is not recognized.
  821. *
  822. * @return integer
  823. * @throws Zend_Pdf_Exception
  824. */
  825. protected function _readScalerType()
  826. {
  827. if ($this->_scalerType != 0) {
  828. return $this->_scalerType;
  829. }
  830. $this->moveToOffset(0);
  831. $this->_scalerType = $this->readUInt(4);
  832. switch ($this->_scalerType) {
  833. case 0x00010000: // version 1.0 - Windows TrueType signature
  834. $this->_debugLog('Windows TrueType signature');
  835. break;
  836. case 0x74727565: // 'true' - Macintosh TrueType signature
  837. $this->_debugLog('Macintosh TrueType signature');
  838. break;
  839. case 0x4f54544f: // 'OTTO' - the CFF signature
  840. $this->_debugLog('PostScript CFF signature');
  841. break;
  842. case 0x74797031: // 'typ1'
  843. require_once 'Zend/Pdf/Exception.php';
  844. throw new Zend_Pdf_Exception('Unsupported font type: PostScript in sfnt wrapper',
  845. Zend_Pdf_Exception::WRONG_FONT_TYPE);
  846. default:
  847. require_once 'Zend/Pdf/Exception.php';
  848. throw new Zend_Pdf_Exception('Not an OpenType font file',
  849. Zend_Pdf_Exception::WRONG_FONT_TYPE);
  850. }
  851. return $this->_scalerType;
  852. }
  853. /**
  854. * Validates a given table's existence, then sets the file pointer to the
  855. * start of that table.
  856. *
  857. * @param string $tableName
  858. * @throws Zend_Pdf_Exception
  859. */
  860. protected function _jumpToTable($tableName)
  861. {
  862. if (empty($this->_tableDirectory[$tableName])) { // do not allow NULL or zero
  863. require_once 'Zend/Pdf/Exception.php';
  864. throw new Zend_Pdf_Exception("Required table '$tableName' not found!",
  865. Zend_Pdf_Exception::REQUIRED_TABLE_NOT_FOUND);
  866. }
  867. $this->_debugLog("Parsing $tableName table...");
  868. $this->moveToOffset($this->_tableDirectory[$tableName]['offset']);
  869. }
  870. /**
  871. * Reads the fixed 16.16 table version number and checks for compatibility.
  872. * If the version is incompatible, throws an exception. If it is compatible,
  873. * returns the version number.
  874. *
  875. * @param float $minVersion Minimum compatible version number.
  876. * @param float $maxVertion Maximum compatible version number.
  877. * @return float Table version number.
  878. * @throws Zend_Pdf_Exception
  879. */
  880. protected function _readTableVersion($minVersion, $maxVersion)
  881. {
  882. $tableVersion = $this->readFixed(16, 16);
  883. if (($tableVersion < $minVersion) || ($tableVersion > $maxVersion)) {
  884. require_once 'Zend/Pdf/Exception.php';
  885. throw new Zend_Pdf_Exception("Unable to read version $tableVersion table",
  886. Zend_Pdf_Exception::DONT_UNDERSTAND_TABLE_VERSION);
  887. }
  888. $this->_debugLog('Version %.2f table', $tableVersion);
  889. return $tableVersion;
  890. }
  891. /**
  892. * Utility method that returns ISO 639 two-letter language codes from the
  893. * TrueType platform and language ID. Returns NULL for languages that are
  894. * not supported.
  895. *
  896. * @param integer $platformID
  897. * @param integer $encodingID
  898. * @return string | null
  899. */
  900. protected function _languageCodeForPlatform($platformID, $languageID)
  901. {
  902. if ($platformID == 3) { // Microsoft encoding.
  903. /* The low-order bytes specify the language, the high-order bytes
  904. * specify the dialect. We just care about the language. For the
  905. * complete list, see:
  906. * http://www.microsoft.com/globaldev/reference/lcid-all.mspx
  907. */
  908. $languageID &= 0xff;
  909. switch ($languageID) {
  910. case 0x09:
  911. return 'en';
  912. case 0x0c:
  913. return 'fr';
  914. case 0x07:
  915. return 'de';
  916. case 0x10:
  917. return 'it';
  918. case 0x13:
  919. return 'nl';
  920. case 0x1d:
  921. return 'sv';
  922. case 0x0a:
  923. return 'es';
  924. case 0x06:
  925. return 'da';
  926. case 0x16:
  927. return 'pt';
  928. case 0x14:
  929. return 'no';
  930. case 0x0d:
  931. return 'he';
  932. case 0x11:
  933. return 'ja';
  934. case 0x01:
  935. return 'ar';
  936. case 0x0b:
  937. return 'fi';
  938. case 0x08:
  939. return 'el';
  940. default:
  941. return null;
  942. }
  943. } else if ($platformID == 1) { // Macintosh encoding.
  944. switch ($languageID) {
  945. case 0:
  946. return 'en';
  947. case 1:
  948. return 'fr';
  949. case 2:
  950. return 'de';
  951. case 3:
  952. return 'it';
  953. case 4:
  954. return 'nl';
  955. case 5:
  956. return 'sv';
  957. case 6:
  958. return 'es';
  959. case 7:
  960. return 'da';
  961. case 8:
  962. return 'pt';
  963. case 9:
  964. return 'no';
  965. case 10:
  966. return 'he';
  967. case 11:
  968. return 'ja';
  969. case 12:
  970. return 'ar';
  971. case 13:
  972. return 'fi';
  973. case 14:
  974. return 'el';
  975. default:
  976. return null;
  977. }
  978. } else { // Unknown encoding.
  979. return null;
  980. }
  981. }
  982. }