You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

492 lines
19 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
  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 Fonts
  18. * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id$
  21. */
  22. /** Internally used classes */
  23. // require_once 'Zend/Pdf/Element/Array.php';
  24. // require_once 'Zend/Pdf/Element/Dictionary.php';
  25. // require_once 'Zend/Pdf/Element/Name.php';
  26. // require_once 'Zend/Pdf/Element/Numeric.php';
  27. // require_once 'Zend/Pdf/Element/String.php';
  28. /** Zend_Pdf_Resource_Font */
  29. // require_once 'Zend/Pdf/Resource/Font.php';
  30. /**
  31. * Adobe PDF CIDFont font object implementation
  32. *
  33. * A CIDFont program contains glyph descriptions that are accessed using a CID as
  34. * the character selector. There are two types of CIDFont. A Type 0 CIDFont contains
  35. * glyph descriptions based on Adobe’s Type 1 font format, whereas those in a
  36. * Type 2 CIDFont are based on the TrueType font format.
  37. *
  38. * A CIDFont dictionary is a PDF object that contains information about a CIDFont program.
  39. * Although its Type value is Font, a CIDFont is not actually a font. It does not have an Encoding
  40. * entry, it cannot be listed in the Font subdictionary of a resource dictionary, and it cannot be
  41. * used as the operand of the Tf operator. It is used only as a descendant of a Type 0 font.
  42. * The CMap in the Type 0 font is what defines the encoding that maps character codes to CIDs
  43. * in the CIDFont.
  44. *
  45. * Font objects should be normally be obtained from the factory methods
  46. * {@link Zend_Pdf_Font::fontWithName} and {@link Zend_Pdf_Font::fontWithPath}.
  47. *
  48. * @package Zend_Pdf
  49. * @subpackage Fonts
  50. * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
  51. * @license http://framework.zend.com/license/new-bsd New BSD License
  52. */
  53. abstract class Zend_Pdf_Resource_Font_CidFont extends Zend_Pdf_Resource_Font
  54. {
  55. /**
  56. * Object representing the font's cmap (character to glyph map).
  57. * @var Zend_Pdf_Cmap
  58. */
  59. protected $_cmap = null;
  60. /**
  61. * Array containing the widths of each character that have entries in used character map.
  62. *
  63. * @var array
  64. */
  65. protected $_charWidths = null;
  66. /**
  67. * Width for characters missed in the font
  68. *
  69. * @var integer
  70. */
  71. protected $_missingCharWidth = 0;
  72. /**
  73. * Object constructor
  74. *
  75. * @param Zend_Pdf_FileParser_Font_OpenType $fontParser Font parser object
  76. * containing OpenType file.
  77. * @param integer $embeddingOptions Options for font embedding.
  78. * @throws Zend_Pdf_Exception
  79. */
  80. public function __construct(Zend_Pdf_FileParser_Font_OpenType $fontParser)
  81. {
  82. parent::__construct();
  83. $fontParser->parse();
  84. /* Object properties */
  85. $this->_fontNames = $fontParser->names;
  86. $this->_isBold = $fontParser->isBold;
  87. $this->_isItalic = $fontParser->isItalic;
  88. $this->_isMonospaced = $fontParser->isMonospaced;
  89. $this->_underlinePosition = $fontParser->underlinePosition;
  90. $this->_underlineThickness = $fontParser->underlineThickness;
  91. $this->_strikePosition = $fontParser->strikePosition;
  92. $this->_strikeThickness = $fontParser->strikeThickness;
  93. $this->_unitsPerEm = $fontParser->unitsPerEm;
  94. $this->_ascent = $fontParser->ascent;
  95. $this->_descent = $fontParser->descent;
  96. $this->_lineGap = $fontParser->lineGap;
  97. $this->_cmap = $fontParser->cmap;
  98. /* Resource dictionary */
  99. $baseFont = $this->getFontName(Zend_Pdf_Font::NAME_POSTSCRIPT, 'en', 'UTF-8');
  100. $this->_resource->BaseFont = new Zend_Pdf_Element_Name($baseFont);
  101. /**
  102. * Prepare widths array.
  103. */
  104. /* Constract characters widths array using font CMap and glyphs widths array */
  105. $glyphWidths = $fontParser->glyphWidths;
  106. $charGlyphs = $this->_cmap->getCoveredCharactersGlyphs();
  107. $charWidths = array();
  108. foreach ($charGlyphs as $charCode => $glyph) {
  109. if(isset($glyphWidths[$glyph]) && !is_null($glyphWidths[$glyph])) {
  110. $charWidths[$charCode] = $glyphWidths[$glyph];
  111. }
  112. }
  113. $this->_charWidths = $charWidths;
  114. $this->_missingCharWidth = $glyphWidths[0];
  115. /* Width array optimization. Step1: extract default value */
  116. $widthFrequencies = array_count_values($charWidths);
  117. $defaultWidth = null;
  118. $defaultWidthFrequency = -1;
  119. foreach ($widthFrequencies as $width => $frequency) {
  120. if ($frequency > $defaultWidthFrequency) {
  121. $defaultWidth = $width;
  122. $defaultWidthFrequency = $frequency;
  123. }
  124. }
  125. // Store default value in the font dictionary
  126. $this->_resource->DW = new Zend_Pdf_Element_Numeric($this->toEmSpace($defaultWidth));
  127. // Remove characters which corresponds to default width from the widths array
  128. $defWidthChars = array_keys($charWidths, $defaultWidth);
  129. foreach ($defWidthChars as $charCode) {
  130. unset($charWidths[$charCode]);
  131. }
  132. // Order cheracter widths aray by character codes
  133. ksort($charWidths, SORT_NUMERIC);
  134. /* Width array optimization. Step2: Compact character codes sequences */
  135. $lastCharCode = -1;
  136. $widthsSequences = array();
  137. foreach ($charWidths as $charCode => $width) {
  138. if ($lastCharCode == -1) {
  139. $charCodesSequense = array();
  140. $sequenceStartCode = $charCode;
  141. } else if ($charCode != $lastCharCode + 1) {
  142. // New chracters sequence detected
  143. $widthsSequences[$sequenceStartCode] = $charCodesSequense;
  144. $charCodesSequense = array();
  145. $sequenceStartCode = $charCode;
  146. }
  147. $charCodesSequense[] = $width;
  148. $lastCharCode = $charCode;
  149. }
  150. // Save last sequence, if widths array is not empty (it may happens for monospaced fonts)
  151. if (count($charWidths) != 0) {
  152. $widthsSequences[$sequenceStartCode] = $charCodesSequense;
  153. }
  154. $pdfCharsWidths = array();
  155. foreach ($widthsSequences as $startCode => $widthsSequence) {
  156. /* Width array optimization. Step3: Compact widths sequences */
  157. $pdfWidths = array();
  158. $lastWidth = -1;
  159. $widthsInSequence = 0;
  160. foreach ($widthsSequence as $width) {
  161. if ($lastWidth != $width) {
  162. // New width is detected
  163. if ($widthsInSequence != 0) {
  164. // Previous width value was a part of the widths sequence. Save it as 'c_1st c_last w'.
  165. $pdfCharsWidths[] = new Zend_Pdf_Element_Numeric($startCode); // First character code
  166. $pdfCharsWidths[] = new Zend_Pdf_Element_Numeric($startCode + $widthsInSequence - 1); // Last character code
  167. $pdfCharsWidths[] = new Zend_Pdf_Element_Numeric($this->toEmSpace($lastWidth)); // Width
  168. // Reset widths sequence
  169. $startCode = $startCode + $widthsInSequence;
  170. $widthsInSequence = 0;
  171. }
  172. // Collect new width
  173. $pdfWidths[] = new Zend_Pdf_Element_Numeric($this->toEmSpace($width));
  174. $lastWidth = $width;
  175. } else {
  176. // Width is equal to previous
  177. if (count($pdfWidths) != 0) {
  178. // We already have some widths collected
  179. // So, we've just detected new widths sequence
  180. // Remove last element from widths list, since it's a part of widths sequence
  181. array_pop($pdfWidths);
  182. // and write the rest if it's not empty
  183. if (count($pdfWidths) != 0) {
  184. // Save it as 'c_1st [w1 w2 ... wn]'.
  185. $pdfCharsWidths[] = new Zend_Pdf_Element_Numeric($startCode); // First character code
  186. $pdfCharsWidths[] = new Zend_Pdf_Element_Array($pdfWidths); // Widths array
  187. // Reset widths collection
  188. $startCode += count($pdfWidths);
  189. $pdfWidths = array();
  190. }
  191. $widthsInSequence = 2;
  192. } else {
  193. // Continue widths sequence
  194. $widthsInSequence++;
  195. }
  196. }
  197. }
  198. // Check if we have widths collection or widths sequence to wite it down
  199. if (count($pdfWidths) != 0) {
  200. // We have some widths collected
  201. // Save it as 'c_1st [w1 w2 ... wn]'.
  202. $pdfCharsWidths[] = new Zend_Pdf_Element_Numeric($startCode); // First character code
  203. $pdfCharsWidths[] = new Zend_Pdf_Element_Array($pdfWidths); // Widths array
  204. } else if ($widthsInSequence != 0){
  205. // We have widths sequence
  206. // Save it as 'c_1st c_last w'.
  207. $pdfCharsWidths[] = new Zend_Pdf_Element_Numeric($startCode); // First character code
  208. $pdfCharsWidths[] = new Zend_Pdf_Element_Numeric($startCode + $widthsInSequence - 1); // Last character code
  209. $pdfCharsWidths[] = new Zend_Pdf_Element_Numeric($this->toEmSpace($lastWidth)); // Width
  210. }
  211. }
  212. /* Create the Zend_Pdf_Element_Array object and add it to the font's
  213. * object factory and resource dictionary.
  214. */
  215. $widthsArrayElement = new Zend_Pdf_Element_Array($pdfCharsWidths);
  216. $widthsObject = $this->_objectFactory->newObject($widthsArrayElement);
  217. $this->_resource->W = $widthsObject;
  218. /* CIDSystemInfo dictionary */
  219. $cidSystemInfo = new Zend_Pdf_Element_Dictionary();
  220. $cidSystemInfo->Registry = new Zend_Pdf_Element_String('Adobe');
  221. $cidSystemInfo->Ordering = new Zend_Pdf_Element_String('UCS');
  222. $cidSystemInfo->Supplement = new Zend_Pdf_Element_Numeric(0);
  223. $cidSystemInfoObject = $this->_objectFactory->newObject($cidSystemInfo);
  224. $this->_resource->CIDSystemInfo = $cidSystemInfoObject;
  225. }
  226. /**
  227. * Returns an array of glyph numbers corresponding to the Unicode characters.
  228. *
  229. * If a particular character doesn't exist in this font, the special 'missing
  230. * character glyph' will be substituted.
  231. *
  232. * See also {@link glyphNumberForCharacter()}.
  233. *
  234. * @param array $characterCodes Array of Unicode character codes (code points).
  235. * @return array Array of glyph numbers.
  236. */
  237. public function glyphNumbersForCharacters($characterCodes)
  238. {
  239. /**
  240. * CIDFont object is not actually a font. It does not have an Encoding entry,
  241. * it cannot be listed in the Font subdictionary of a resource dictionary, and
  242. * it cannot be used as the operand of the Tf operator.
  243. *
  244. * Throw an exception.
  245. */
  246. // require_once 'Zend/Pdf/Exception.php';
  247. throw new Zend_Pdf_Exception('CIDFont PDF objects could not be used as the operand of the text drawing operators');
  248. }
  249. /**
  250. * Returns the glyph number corresponding to the Unicode character.
  251. *
  252. * If a particular character doesn't exist in this font, the special 'missing
  253. * character glyph' will be substituted.
  254. *
  255. * See also {@link glyphNumbersForCharacters()} which is optimized for bulk
  256. * operations.
  257. *
  258. * @param integer $characterCode Unicode character code (code point).
  259. * @return integer Glyph number.
  260. */
  261. public function glyphNumberForCharacter($characterCode)
  262. {
  263. /**
  264. * CIDFont object is not actually a font. It does not have an Encoding entry,
  265. * it cannot be listed in the Font subdictionary of a resource dictionary, and
  266. * it cannot be used as the operand of the Tf operator.
  267. *
  268. * Throw an exception.
  269. */
  270. // require_once 'Zend/Pdf/Exception.php';
  271. throw new Zend_Pdf_Exception('CIDFont PDF objects could not be used as the operand of the text drawing operators');
  272. }
  273. /**
  274. * Returns a number between 0 and 1 inclusive that indicates the percentage
  275. * of characters in the string which are covered by glyphs in this font.
  276. *
  277. * Since no one font will contain glyphs for the entire Unicode character
  278. * range, this method can be used to help locate a suitable font when the
  279. * actual contents of the string are not known.
  280. *
  281. * Note that some fonts lie about the characters they support. Additionally,
  282. * fonts don't usually contain glyphs for control characters such as tabs
  283. * and line breaks, so it is rare that you will get back a full 1.0 score.
  284. * The resulting value should be considered informational only.
  285. *
  286. * @param string $string
  287. * @param string $charEncoding (optional) Character encoding of source text.
  288. * If omitted, uses 'current locale'.
  289. * @return float
  290. */
  291. public function getCoveredPercentage($string, $charEncoding = '')
  292. {
  293. /* Convert the string to UTF-16BE encoding so we can match the string's
  294. * character codes to those found in the cmap.
  295. */
  296. if ($charEncoding != 'UTF-16BE') {
  297. $string = iconv($charEncoding, 'UTF-16BE', $string);
  298. }
  299. $charCount = iconv_strlen($string, 'UTF-16BE');
  300. if ($charCount == 0) {
  301. return 0;
  302. }
  303. /* Calculate the score by doing a lookup for each character.
  304. */
  305. $score = 0;
  306. $maxIndex = strlen($string);
  307. for ($i = 0; $i < $maxIndex; $i++) {
  308. /**
  309. * @todo Properly handle characters encoded as surrogate pairs.
  310. */
  311. $charCode = (ord($string[$i]) << 8) | ord($string[++$i]);
  312. /* This could probably be optimized a bit with a binary search...
  313. */
  314. if (isset($this->_charWidths[$charCode])) {
  315. $score++;
  316. }
  317. }
  318. return $score / $charCount;
  319. }
  320. /**
  321. * Returns the widths of the Chars.
  322. *
  323. * The widths are expressed in the font's glyph space. You are responsible
  324. * for converting to user space as necessary. See {@link unitsPerEm()}.
  325. *
  326. * See also {@link widthForChar()}.
  327. *
  328. * @param array &$glyphNumbers Array of glyph numbers.
  329. * @return array Array of glyph widths (integers).
  330. */
  331. public function widthsForChars($charCodes)
  332. {
  333. $widths = array();
  334. foreach ($charCodes as $key => $charCode) {
  335. if (!isset($this->_charWidths[$charCode])) {
  336. $widths[$key] = $this->_missingCharWidth;
  337. } else {
  338. $widths[$key] = $this->_charWidths[$charCode];
  339. }
  340. }
  341. return $widths;
  342. }
  343. /**
  344. * Returns the width of the character.
  345. *
  346. * Like {@link widthsForChars()} but used for one char at a time.
  347. *
  348. * @param integer $charCode
  349. * @return integer
  350. */
  351. public function widthForChar($charCode)
  352. {
  353. if (!isset($this->_charWidths[$charCode])) {
  354. return $this->_missingCharWidth;
  355. }
  356. return $this->_charWidths[$charCode];
  357. }
  358. /**
  359. * Returns the widths of the glyphs.
  360. *
  361. * @param array &$glyphNumbers Array of glyph numbers.
  362. * @return array Array of glyph widths (integers).
  363. * @throws Zend_Pdf_Exception
  364. */
  365. public function widthsForGlyphs($glyphNumbers)
  366. {
  367. /**
  368. * CIDFont object is not actually a font. It does not have an Encoding entry,
  369. * it cannot be listed in the Font subdictionary of a resource dictionary, and
  370. * it cannot be used as the operand of the Tf operator.
  371. *
  372. * Throw an exception.
  373. */
  374. // require_once 'Zend/Pdf/Exception.php';
  375. throw new Zend_Pdf_Exception('CIDFont PDF objects could not be used as the operand of the text drawing operators');
  376. }
  377. /**
  378. * Returns the width of the glyph.
  379. *
  380. * Like {@link widthsForGlyphs()} but used for one glyph at a time.
  381. *
  382. * @param integer $glyphNumber
  383. * @return integer
  384. * @throws Zend_Pdf_Exception
  385. */
  386. public function widthForGlyph($glyphNumber)
  387. {
  388. /**
  389. * CIDFont object is not actually a font. It does not have an Encoding entry,
  390. * it cannot be listed in the Font subdictionary of a resource dictionary, and
  391. * it cannot be used as the operand of the Tf operator.
  392. *
  393. * Throw an exception.
  394. */
  395. // require_once 'Zend/Pdf/Exception.php';
  396. throw new Zend_Pdf_Exception('CIDFont PDF objects could not be used as the operand of the text drawing operators');
  397. }
  398. /**
  399. * Convert string to the font encoding.
  400. *
  401. * @param string $string
  402. * @param string $charEncoding Character encoding of source text.
  403. * @return string
  404. * @throws Zend_Pdf_Exception
  405. * */
  406. public function encodeString($string, $charEncoding)
  407. {
  408. /**
  409. * CIDFont object is not actually a font. It does not have an Encoding entry,
  410. * it cannot be listed in the Font subdictionary of a resource dictionary, and
  411. * it cannot be used as the operand of the Tf operator.
  412. *
  413. * Throw an exception.
  414. */
  415. // require_once 'Zend/Pdf/Exception.php';
  416. throw new Zend_Pdf_Exception('CIDFont PDF objects could not be used as the operand of the text drawing operators');
  417. }
  418. /**
  419. * Convert string from the font encoding.
  420. *
  421. * @param string $string
  422. * @param string $charEncoding Character encoding of resulting text.
  423. * @return string
  424. * @throws Zend_Pdf_Exception
  425. */
  426. public function decodeString($string, $charEncoding)
  427. {
  428. /**
  429. * CIDFont object is not actually a font. It does not have an Encoding entry,
  430. * it cannot be listed in the Font subdictionary of a resource dictionary, and
  431. * it cannot be used as the operand of the Tf operator.
  432. *
  433. * Throw an exception.
  434. */
  435. // require_once 'Zend/Pdf/Exception.php';
  436. throw new Zend_Pdf_Exception('CIDFont PDF objects could not be used as the operand of the text drawing operators');
  437. }
  438. }