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