source-class-Com.Tecnick.Pdf.Font.Stack

It appears that you are using AdBlocking software. The cost of running this website is covered by advertisements. If you like it please feel free to a small amount of money to secure the future of this website.
  1: <?php
  2: /**
  3:  * Stack.php
  4:  *
  5:  * @since       2011-05-23
  6:  * @category    Library
  7:  * @package     PdfFont
  8:  * @author      Nicola Asuni <info@tecnick.com>
  9:  * @copyright   2011-2015 Nicola Asuni - Tecnick.com LTD
 10:  * @license     http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
 11:  * @link        https://github.com/tecnickcom/tc-lib-pdf-font
 12:  *
 13:  * This file is part of tc-lib-pdf-font software library.
 14:  */
 15: 
 16: namespace Com\Tecnick\Pdf\Font;
 17: 
 18: use \Com\Tecnick\Pdf\Font\Font;
 19: use \Com\Tecnick\Pdf\Font\Exception as FontException;
 20: 
 21: /**
 22:  * Com\Tecnick\Pdf\Font\Stack
 23:  *
 24:  * @since       2011-05-23
 25:  * @category    Library
 26:  * @package     PdfFont
 27:  * @author      Nicola Asuni <info@tecnick.com>
 28:  * @copyright   2011-2015 Nicola Asuni - Tecnick.com LTD
 29:  * @license     http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
 30:  * @link        https://github.com/tecnickcom/tc-lib-pdf-font
 31:  */
 32: class Stack extends \Com\Tecnick\Pdf\Font\Buffer
 33: {
 34:     /**
 35:      * Default font size in points
 36:      */
 37:     const DEFAULT_SIZE = 10;
 38: 
 39:     /**
 40:      * Array (stack) containing fonts in order of insertion.
 41:      * The last item is the current font.
 42:      *
 43:      * @var array
 44:      */
 45:     protected $stack = array();
 46: 
 47:     /**
 48:      * Current font index
 49:      *
 50:      * @var int
 51:      */
 52:     protected $index = -1;
 53: 
 54:     /**
 55:      * Array containing font metrics for each fontkey-size combination.
 56:      *
 57:      * @var array
 58:      */
 59:     protected $metric = array();
 60: 
 61:     /**
 62:      * Insert a font into the stack
 63:      *
 64:      * The definition file (and the font file itself when embedding) must be present either in the current directory
 65:      * or in the one indicated by K_PATH_FONTS if the constant is defined.
 66:      *
 67:      * @param int    $objnum     Current PDF object number
 68:      * @param string $font       Font family, or comma separated list of font families
 69:      *                           If it is a standard family name, it will override the corresponding font.
 70:      * @param string $style      Font style.
 71:      *                           Possible values are (case insensitive):
 72:      *                             regular (default)
 73:      *                             B: bold
 74:      *                             I: italic
 75:      *                             U: underline
 76:      *                             D: strikeout (linethrough)
 77:      *                             O: overline
 78:      * @param int    $size       Font size in points (set to null to inherit the last font size).
 79:      * @param float  $spacing    Extra spacing between characters.
 80:      * @param float  $stretching Horizontal character stretching ratio.
 81:      * @param string $ifile      The font definition file (or empty for autodetect).
 82:      *                           By default, the name is built from the family and style, in lower case with no spaces.
 83:      * @param bool   $subset     If true embedd only a subset of the font
 84:      *                           (stores only the information related to the used characters);
 85:      *                           If false embedd full font;
 86:      *                           This option is valid only for TrueTypeUnicode fonts and it is disabled for PDF/A.
 87:      *                           If you want to enable users to modify the document, set this parameter to false.
 88:      *                           If you subset the font, the person who receives your PDF would need to have
 89:      *                           your same font in order to make changes to your PDF.
 90:      *                           The file size of the PDF would also be smaller because you are embedding only a subset.
 91:      *                           Set this to null to use the default value.
 92:      *                           NOTE: This option is computational and memory intensive.
 93:      *
 94:      * @return array Font data
 95:      *
 96:      * @throws FontException in case of error
 97:      */
 98:     public function insert(
 99:         &$objnum,
100:         $font,
101:         $style = '',
102:         $size = null,
103:         $spacing = null,
104:         $stretching = null,
105:         $ifile = '',
106:         $subset = null
107:     ) {
108:         if ($subset === null) {
109:             $subset = $this->subset;
110:         }
111:         $size = $this->getInputSize($size);
112:         $spacing = $this->getInputSpacing($spacing);
113:         $stretching = $this->getInputStretching($stretching);
114: 
115:         // try to load the corresponding imported font
116:         $err = null;
117:         $keys = $this->getNormalizedFontKeys($font);
118:         $fontkey = '';
119:         foreach ($keys as $fkey) {
120:             try {
121:                 $fontkey = $this->add($objnum, $fkey, $style, $ifile, $subset);
122:                 $err = null;
123:                 break;
124:             } catch (FontException $exc) {
125:                 $err = $exc;
126:             }
127:         }
128:         if ($err !== null) {
129:             throw new FontException($err->getMessage());
130:         }
131: 
132:         // add this font in the stack
133:         $data = $this->getFont($fontkey);
134: 
135:         $this->stack[++$this->index] = array(
136:             'key'        => $fontkey,
137:             'style'      => $data['style'],
138:             'size'       => $size,
139:             'spacing'    => $spacing,
140:             'stretching' => $stretching,
141:         );
142: 
143:         return $this->getFontMetric($this->stack[$this->index]);
144:     }
145: 
146:     /**
147:      * Returns the current font data array
148:      *
149:      * @return array
150:      */
151:     public function getCurrentFont()
152:     {
153:         return $this->getFontMetric($this->stack[$this->index]);
154:     }
155: 
156:     /**
157:      * Remove and return the last inserted font
158:      *
159:      * @return array
160:      */
161:     public function popLastFont()
162:     {
163:         if ($this->index < 0) {
164:             throw new FontException('The font stack is empty');
165:         }
166:         $font = array_pop($this->stack);
167:         --$this->index;
168:         return $this->getFontMetric($font);
169:     }
170: 
171:     /**
172:      * Replace missing characters with selected substitutions
173:      *
174:      * @param array $uniarr Array of character codepoints.
175:      * @param array $subs   Array of possible character substitutions.
176:      *                      The key is the character to check (integer value),
177:      *                      the value is an array of possible substitutes.
178:      *
179:      * @return array
180:      */
181:     public function replaceMissingChars(array $uniarr, array $subs = array())
182:     {
183:         $font = $this->getFontMetric($this->stack[$this->index]);
184:         foreach ($uniarr as $pos => $uni) {
185:             if (isset($font['cw'][$uni]) || !isset($subs[$uni])) {
186:                 continue;
187:             }
188:             foreach ($subs[$uni] as $alt) {
189:                 if (isset($font['cw'][$alt])) {
190:                     $uniarr[$pos] = $alt;
191:                     break;
192:                 }
193:             }
194:         }
195:         return $uniarr;
196:     }
197: 
198:     /**
199:      * Returns true if the specified unicode value is defined in the current font
200:      *
201:      * @param int $ord Unicode character value to convert
202:      *
203:      * @return bool
204:      */
205:     public function isCharDefined($ord)
206:     {
207:         $font = $this->getFontMetric($this->stack[$this->index]);
208:         return isset($font['cw'][$ord]);
209:     }
210: 
211:     /**
212:      * Returns true if the specified unicode value is defined in the current font
213:      *
214:      * @param int   $ord    Unicode character value.
215:      *
216:      * @return int
217:      */
218:     public function getCharWidth($ord)
219:     {
220:         if ($ord == 173) {
221:             // SHY character is not printed, as it is used for text hyphenation
222:             return 0;
223:         }
224:         $font = $this->getFontMetric($this->stack[$this->index]);
225:         if (isset($font['cw'][$ord])) {
226:             return $font['cw'][$ord];
227:         }
228:         return $font['dw'];
229:     }
230: 
231:     /**
232:      * Returns the lenght of the string specified using an array of codepoints.
233:      *
234:      * @param array $uniarr Array of character codepoints.
235:      *
236:      * @return float
237:      */
238:     public function getOrdArrWidth($uniarr)
239:     {
240:         $width = 0;
241:         foreach ($uniarr as $ord) {
242:             $width += $this->GetCharWidth($ord);
243:         }
244:         $width += ($this->stack[$this->index]['spacing']
245:             * $this->stack[$this->index]['stretching']
246:             * (count($uniarr) - 1)
247:         );
248:         return $width;
249:     }
250: 
251:     /**
252:      * Returns the glyph bounding box of the specified character in the current font in user units.
253:      *
254:      * @param int $ord Unicode character value.
255:      *
256:      * @return array (xMin, yMin, xMax, yMax)
257:      */
258:     public function getCharBBox($ord)
259:     {
260:         $font = $this->getFontMetric($this->stack[$this->index]);
261:         if (isset($font['cbbox'][$ord])) {
262:             return $font['cbbox'][$ord];
263:         }
264:         return array(0, 0, 0, 0); // glyph without outline
265:     }
266: 
267:     /**
268:      * Replace a char if it is defined on the current font.
269:      *
270:      * @param int $oldchar Integer code (unicode) of the character to replace.
271:      * @param int $newchar Integer code (unicode) of the new character.
272:      *
273:      * @return int the replaced char or the old char in case the new char i not defined
274:      */
275:     public function replaceChar($oldchar, $newchar)
276:     {
277:         if ($this->isCharDefined($newchar)) {
278:             // add the new char on the subset list
279:             $this->addSubsetChar($this->stack[$this->index]['key'], $newchar);
280:             // return the new character
281:             return $newchar;
282:         }
283:         // return the old char
284:         return $oldchar;
285:     }
286: 
287:     /**
288:      * Returns the font metrics associated to the input key.
289:      *
290:      * @param array $font Stack item
291:      *
292:      * @return array
293:      */
294:     protected function getFontMetric($font)
295:     {
296:         $mkey = md5(serialize($font));
297:         if (!empty($this->metric[$mkey])) {
298:             return $this->metric[$mkey];
299:         }
300: 
301:         $usize = ((float) $font['size'] / $this->kunit);
302:         $cratio = ($usize / 1000);
303:         $wratio = ($cratio * $font['stretching']); // horizontal ratio
304:         $data = $this->getFont($font['key']);
305: 
306:         // add this font in the stack wit metrics in internal units
307:         $this->metric[$mkey] = array(
308:             'out'          => sprintf('BT /F%d %F Tf ET', $data['i'], $font['size']), // PDF output string
309:             'key'          => $font['key'],
310:             'size'         => $font['size'],                                          // size in points
311:             'spacing'      => $font['spacing'],
312:             'stretching'   => $font['stretching'],
313:             'usize'        => $usize,                                                 // size in internal units
314:             'cratio'       => $cratio,                                                // conversion ratio
315:             'up'           => ($data['up'] * $cratio),
316:             'ut'           => ($data['ut'] * $cratio),
317:             'dw'           => ($data['dw'] * $cratio * $font['stretching']),
318:             'ascent'       => ($data['desc']['Ascent'] * $cratio),
319:             'descent'      => ($data['desc']['Descent'] * $cratio),
320:             'capheight'    => ($data['desc']['CapHeight'] * $cratio),
321:             'xheight'      => ($data['desc']['XHeight'] * $cratio),
322:             'avgwidth'     => ($data['desc']['AvgWidth'] * $cratio * $font['stretching']),
323:             'maxwidth'     => ($data['desc']['MaxWidth'] * $cratio * $font['stretching']),
324:             'missingwidth' => ($data['desc']['MissingWidth'] * $cratio * $font['stretching']),
325:             'cw'           => array(),
326:             'cbbox'        => array(),
327:         );
328:         $tbox = explode(' ', substr($data['desc']['FontBBox'], 1, -1));
329:         $this->metric[$mkey]['fbbox'] = array(
330:             ($tbox[0] * $wratio), // left
331:             ($tbox[1] * $cratio), // bottom
332:             ($tbox[2] * $wratio), // right
333:             ($tbox[3] * $cratio), // top
334:         );
335:         //left, bottom, right, and top edges
336:         foreach ($data['cw'] as $chr => $width) {
337:             $this->metric[$mkey]['cw'][$chr] = ($width * $wratio);
338:         }
339:         foreach ($data['cbbox'] as $chr => $val) {
340:             $this->metric[$mkey]['cbbox'][$chr] = array(
341:                 ($val[0] * $wratio), // left
342:                 ($val[1] * $cratio), // bottom
343:                 ($val[2] * $wratio), // right
344:                 ($val[3] * $cratio), // top
345:             );
346:         }
347: 
348:         return $this->metric[$mkey];
349:     }
350: 
351:     /**
352:      * Normalize the input size
353:      *
354:      * return float
355:      */
356:     protected function getInputSize($size = null)
357:     {
358:         if ($size === null) {
359:             if ($this->index >= 0) {
360:                 // inherit the size of the last inserted font
361:                 return $this->stack[$this->index]['size'];
362:             } else {
363:                 return self::DEFAULT_SIZE;
364:             }
365:         }
366:         return max(0, (float) $size);
367:     }
368: 
369:     /**
370:      * Normalize the input spacing
371:      *
372:      * @param float  $spacing  Extra spacing between characters.
373:      *
374:      * return float
375:      */
376:     protected function getInputSpacing($spacing = null)
377:     {
378:         if ($spacing === null) {
379:             if ($this->index >= 0) {
380:                 // inherit the size of the last inserted font
381:                 return $this->stack[$this->index]['spacing'];
382:             } else {
383:                 return 0;
384:             }
385:         }
386:         return ((float) $spacing);
387:     }
388: 
389:     /**
390:      * Normalize the input stretching
391:      *
392:      * @param float  $stretching Horizontal character stretching ratio.
393:      *
394:      * return float
395:      */
396:     protected function getInputStretching($stretching = null)
397:     {
398:         if ($stretching === null) {
399:             if ($this->index >= 0) {
400:                 // inherit the size of the last inserted font
401:                 return $this->stack[$this->index]['stretching'];
402:             } else {
403:                 return 1;
404:             }
405:         }
406:         return ((float) $stretching);
407:     }
408: 
409:     /**
410:      * Return normalized font keys
411:      *
412:      * @param string $fontfamily Property string containing comma-separated font family names
413:      *
414:      * @return array
415:      */
416:     protected function getNormalizedFontKeys($fontfamily)
417:     {
418:         $keys = array();
419:         // remove spaces and symbols
420:         $fontfamily = preg_replace('/[^a-z0-9_\,]/', '', strtolower($fontfamily));
421:         // extract all font names
422:         $fontslist = preg_split('/[,]/', $fontfamily);
423:         // replacement patterns
424:         $pattern = array('/^serif|^cursive|^fantasy|^timesnewroman/', '/^sansserif/', '/^monospace/');
425:         $replacement = array('times', 'helvetica', 'courier');
426:         // find first valid font name
427:         foreach ($fontslist as $font) {
428:             // replace font variations
429:             $font = preg_replace('/regular$/', '', $font);
430:             $font = preg_replace('/italic$/', 'I', $font);
431:             $font = preg_replace('/oblique$/', 'I', $font);
432:             $font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
433:             // replace common family names and core fonts
434:             $keys[] = preg_replace($pattern, $replacement, $font);
435:         }
436:         return $keys;
437:     }
438: }
439: 
 

© 2004-2017 – Nicola Asuni - Tecnick.com - All rights reserved.
about - disclaimer - privacy