1<?php 2 3namespace Mpdf\Writer; 4 5use Mpdf\Strict; 6 7use Mpdf\Fonts\FontCache; 8use Mpdf\Mpdf; 9use Mpdf\TTFontFile; 10 11class FontWriter 12{ 13 14 use Strict; 15 16 /** 17 * @var \Mpdf\Mpdf 18 */ 19 private $mpdf; 20 21 /** 22 * @var \Mpdf\Writer\BaseWriter 23 */ 24 private $writer; 25 26 /** 27 * @var \Mpdf\Fonts\FontCache 28 */ 29 private $fontCache; 30 31 /** 32 * @var string 33 */ 34 private $fontDescriptor; 35 36 public function __construct(Mpdf $mpdf, BaseWriter $writer, FontCache $fontCache, $fontDescriptor) 37 { 38 $this->mpdf = $mpdf; 39 $this->writer = $writer; 40 $this->fontCache = $fontCache; 41 $this->fontDescriptor = $fontDescriptor; 42 } 43 44 public function writeFonts() 45 { 46 foreach ($this->mpdf->FontFiles as $fontkey => $info) { 47 // TrueType embedded 48 if (isset($info['type']) && $info['type'] === 'TTF' && !$info['sip'] && !$info['smp']) { 49 $used = true; 50 $asSubset = false; 51 foreach ($this->mpdf->fonts as $k => $f) { 52 if (isset($f['fontkey']) && $f['fontkey'] === $fontkey && $f['type'] === 'TTF') { 53 $used = $f['used']; 54 if ($used) { 55 $nChars = (ord($f['cw'][0]) << 8) + ord($f['cw'][1]); 56 $usage = (int) (count($f['subset']) * 100 / $nChars); 57 $fsize = $info['length1']; 58 // Always subset the very large TTF files 59 if ($fsize > ($this->mpdf->maxTTFFilesize * 1024)) { 60 $asSubset = true; 61 } elseif ($usage < $this->mpdf->percentSubset) { 62 $asSubset = true; 63 } 64 } 65 if ($this->mpdf->PDFA || $this->mpdf->PDFX) { 66 $asSubset = false; 67 } 68 $this->mpdf->fonts[$k]['asSubset'] = $asSubset; 69 break; 70 } 71 } 72 if ($used && !$asSubset) { 73 // Font file embedding 74 $this->writer->object(); 75 $this->mpdf->FontFiles[$fontkey]['n'] = $this->mpdf->n; 76 $originalsize = $info['length1']; 77 if ($this->mpdf->repackageTTF || $this->mpdf->fonts[$fontkey]['TTCfontID'] > 0 || $this->mpdf->fonts[$fontkey]['useOTL'] > 0) { // mPDF 5.7.1 78 // First see if there is a cached compressed file 79 if ($this->fontCache->has($fontkey . '.ps.z') && $this->fontCache->jsonHas($fontkey . '.ps.json')) { 80 $font = $this->fontCache->load($fontkey . '.ps.z'); 81 $originalsize = $this->fontCache->jsonLoad($fontkey . '.ps.json'); // sets $originalsize (of repackaged font) 82 } else { 83 $ttf = new TTFontFile($this->fontCache, $this->fontDescriptor); 84 $font = $ttf->repackageTTF($this->mpdf->FontFiles[$fontkey]['ttffile'], $this->mpdf->fonts[$fontkey]['TTCfontID'], $this->mpdf->debugfonts, $this->mpdf->fonts[$fontkey]['useOTL']); // mPDF 5.7.1 85 86 $originalsize = strlen($font); 87 $font = gzcompress($font); 88 unset($ttf); 89 90 $this->fontCache->binaryWrite($fontkey . '.ps.z', $font); 91 $this->fontCache->jsonWrite($fontkey . '.ps.json', $originalsize); 92 } 93 } elseif ($this->fontCache->has($fontkey . '.z')) { 94 $font = $this->fontCache->load($fontkey . '.z'); 95 } else { 96 $font = file_get_contents($this->mpdf->FontFiles[$fontkey]['ttffile']); 97 $font = gzcompress($font); 98 $this->fontCache->binaryWrite($fontkey . '.z', $font); 99 } 100 101 $this->writer->write('<</Length ' . strlen($font)); 102 $this->writer->write('/Filter /FlateDecode'); 103 $this->writer->write('/Length1 ' . $originalsize); 104 $this->writer->write('>>'); 105 $this->writer->stream($font); 106 $this->writer->write('endobj'); 107 } 108 } 109 } 110 111 foreach ($this->mpdf->fonts as $k => $font) { 112 113 // Font objects 114 $type = $font['type']; 115 $name = $font['name']; 116 117 if ($type === 'TTF' && (!isset($font['used']) || !$font['used'])) { 118 continue; 119 } 120 121 // @log Writing fonts 122 123 if (isset($font['asSubset'])) { 124 $asSubset = $font['asSubset']; 125 } else { 126 $asSubset = ''; 127 } 128 129 if ($type === 'Type0') { // Adobe CJK Fonts 130 131 $this->mpdf->fonts[$k]['n'] = $this->mpdf->n + 1; 132 $this->writer->object(); 133 $this->writer->write('<</Type /Font'); 134 $this->writeType0($font); 135 136 } elseif ($type === 'core') { 137 138 // Standard font 139 $this->mpdf->fonts[$k]['n'] = $this->mpdf->n + 1; 140 141 if ($this->mpdf->PDFA || $this->mpdf->PDFX) { 142 throw new \Mpdf\MpdfException('Core fonts are not allowed in PDF/A1-b or PDFX/1-a files (Times, Helvetica, Courier etc.)'); 143 } 144 145 $this->writer->object(); 146 $this->writer->write('<</Type /Font'); 147 $this->writer->write('/BaseFont /' . $name); 148 $this->writer->write('/Subtype /Type1'); 149 150 if ($name !== 'Symbol' && $name !== 'ZapfDingbats') { 151 $this->writer->write('/Encoding /WinAnsiEncoding'); 152 } 153 154 $this->writer->write('>>'); 155 $this->writer->write('endobj'); 156 157 } elseif ($type === 'TTF' && ($font['sip'] || $font['smp'])) { 158 159 // TrueType embedded SUBSETS for SIP (CJK extB containing Supplementary Ideographic Plane 2) 160 // Or Unicode Plane 1 - Supplementary Multilingual Plane 161 162 if (!$font['used']) { 163 continue; 164 } 165 166 $ssfaid = 'AA'; 167 $ttf = new TTFontFile($this->fontCache, $this->fontDescriptor); 168 $subsetCount = count($font['subsetfontids']); 169 for ($sfid = 0; $sfid < $subsetCount; $sfid++) { 170 $this->mpdf->fonts[$k]['n'][$sfid] = $this->mpdf->n + 1; // NB an array for subset 171 $subsetname = 'MPDF' . $ssfaid . '+' . $font['name']; 172 $ssfaid++; 173 174 /* For some strange reason a subset ($sfid > 0) containing less than 97 characters causes an error 175 so fill up the array */ 176 for ($j = count($font['subsets'][$sfid]); $j < 98; $j++) { 177 $font['subsets'][$sfid][$j] = 0; 178 } 179 180 $subset = $font['subsets'][$sfid]; 181 unset($subset[0]); 182 $ttfontstream = $ttf->makeSubsetSIP($font['ttffile'], $subset, $font['TTCfontID'], $this->mpdf->debugfonts, $font['useOTL']); // mPDF 5.7.1 183 $ttfontsize = strlen($ttfontstream); 184 $fontstream = gzcompress($ttfontstream); 185 $widthstring = ''; 186 $toUnistring = ''; 187 188 foreach ($font['subsets'][$sfid] as $cp => $u) { 189 $w = $this->mpdf->_getCharWidth($font['cw'], $u); 190 if ($w !== false) { 191 $widthstring .= $w . ' '; 192 } else { 193 $widthstring .= round($ttf->defaultWidth) . ' '; 194 } 195 if ($u > 65535) { 196 $utf8 = chr(($u >> 18) + 240) . chr((($u >> 12) & 63) + 128) . chr((($u >> 6) & 63) + 128) . chr(($u & 63) + 128); 197 $utf16 = mb_convert_encoding($utf8, 'UTF-16BE', 'UTF-8'); 198 $l1 = ord($utf16[0]); 199 $h1 = ord($utf16[1]); 200 $l2 = ord($utf16[2]); 201 $h2 = ord($utf16[3]); 202 $toUnistring .= sprintf("<%02s> <%02s%02s%02s%02s>\n", strtoupper(dechex($cp)), strtoupper(dechex($l1)), strtoupper(dechex($h1)), strtoupper(dechex($l2)), strtoupper(dechex($h2))); 203 } else { 204 $toUnistring .= sprintf("<%02s> <%04s>\n", strtoupper(dechex($cp)), strtoupper(dechex($u))); 205 } 206 } 207 208 // Additional Type1 or TrueType font 209 $this->writer->object(); 210 $this->writer->write('<</Type /Font'); 211 $this->writer->write('/BaseFont /' . $subsetname); 212 $this->writer->write('/Subtype /TrueType'); 213 $this->writer->write('/FirstChar 0 /LastChar ' . (count($font['subsets'][$sfid]) - 1)); 214 $this->writer->write('/Widths ' . ($this->mpdf->n + 1) . ' 0 R'); 215 $this->writer->write('/FontDescriptor ' . ($this->mpdf->n + 2) . ' 0 R'); 216 $this->writer->write('/ToUnicode ' . ($this->mpdf->n + 3) . ' 0 R'); 217 $this->writer->write('>>'); 218 $this->writer->write('endobj'); 219 220 // Widths 221 $this->writer->object(); 222 $this->writer->write('[' . $widthstring . ']'); 223 $this->writer->write('endobj'); 224 225 // Descriptor 226 $this->writer->object(); 227 $s = '<</Type /FontDescriptor /FontName /' . $subsetname . "\n"; 228 foreach ($font['desc'] as $kd => $v) { 229 if ($kd === 'Flags') { 230 $v |= 4; 231 $v &= ~32; 232 } // SYMBOLIC font flag 233 $s .= ' /' . $kd . ' ' . $v . "\n"; 234 } 235 $s .= '/FontFile2 ' . ($this->mpdf->n + 2) . ' 0 R'; 236 $this->writer->write($s . '>>'); 237 $this->writer->write('endobj'); 238 239 // ToUnicode 240 $this->writer->object(); 241 $toUni = "/CIDInit /ProcSet findresource begin\n"; 242 $toUni .= "12 dict begin\n"; 243 $toUni .= "begincmap\n"; 244 $toUni .= "/CIDSystemInfo\n"; 245 $toUni .= "<</Registry (Adobe)\n"; 246 $toUni .= "/Ordering (UCS)\n"; 247 $toUni .= "/Supplement 0\n"; 248 $toUni .= ">> def\n"; 249 $toUni .= "/CMapName /Adobe-Identity-UCS def\n"; 250 $toUni .= "/CMapType 2 def\n"; 251 $toUni .= "1 begincodespacerange\n"; 252 $toUni .= "<00> <FF>\n"; 253 // $toUni .= sprintf("<00> <%02s>\n", strtoupper(dechex(count($font['subsets'][$sfid])-1))); 254 $toUni .= "endcodespacerange\n"; 255 $toUni .= count($font['subsets'][$sfid]) . " beginbfchar\n"; 256 $toUni .= $toUnistring; 257 $toUni .= "endbfchar\n"; 258 $toUni .= "endcmap\n"; 259 $toUni .= "CMapName currentdict /CMap defineresource pop\n"; 260 $toUni .= "end\n"; 261 $toUni .= "end\n"; 262 $this->writer->write('<</Length ' . strlen($toUni) . '>>'); 263 $this->writer->stream($toUni); 264 $this->writer->write('endobj'); 265 266 // Font file 267 $this->writer->object(); 268 $this->writer->write('<</Length ' . strlen($fontstream)); 269 $this->writer->write('/Filter /FlateDecode'); 270 $this->writer->write('/Length1 ' . $ttfontsize); 271 $this->writer->write('>>'); 272 $this->writer->stream($fontstream); 273 $this->writer->write('endobj'); 274 } // foreach subset 275 unset($ttf); 276 277 } elseif ($type === 'TTF') { // TrueType embedded SUBSETS or FULL 278 279 $this->mpdf->fonts[$k]['n'] = $this->mpdf->n + 1; 280 281 if ($asSubset) { 282 $ssfaid = 'A'; 283 $ttf = new TTFontFile($this->fontCache, $this->fontDescriptor); 284 $fontname = 'MPDFA' . $ssfaid . '+' . $font['name']; 285 $subset = $font['subset']; 286 unset($subset[0]); 287 $ttfontstream = $ttf->makeSubset($font['ttffile'], $subset, $font['TTCfontID'], $this->mpdf->debugfonts, $font['useOTL']); 288 $ttfontsize = strlen($ttfontstream); 289 $fontstream = gzcompress($ttfontstream); 290 $codeToGlyph = $ttf->codeToGlyph; 291 unset($codeToGlyph[0]); 292 } else { 293 $fontname = $font['name']; 294 } 295 296 // Type0 Font 297 // A composite font - a font composed of other fonts, organized hierarchically 298 $this->writer->object(); 299 $this->writer->write('<</Type /Font'); 300 $this->writer->write('/Subtype /Type0'); 301 $this->writer->write('/BaseFont /' . $fontname . ''); 302 $this->writer->write('/Encoding /Identity-H'); 303 $this->writer->write('/DescendantFonts [' . ($this->mpdf->n + 1) . ' 0 R]'); 304 $this->writer->write('/ToUnicode ' . ($this->mpdf->n + 2) . ' 0 R'); 305 $this->writer->write('>>'); 306 $this->writer->write('endobj'); 307 308 // CIDFontType2 309 // A CIDFont whose glyph descriptions are based on TrueType font technology 310 $this->writer->object(); 311 $this->writer->write('<</Type /Font'); 312 $this->writer->write('/Subtype /CIDFontType2'); 313 $this->writer->write('/BaseFont /' . $fontname . ''); 314 $this->writer->write('/CIDSystemInfo ' . ($this->mpdf->n + 2) . ' 0 R'); 315 $this->writer->write('/FontDescriptor ' . ($this->mpdf->n + 3) . ' 0 R'); 316 317 if (isset($font['desc']['MissingWidth'])) { 318 $this->writer->write('/DW ' . $font['desc']['MissingWidth'] . ''); 319 } 320 321 if (!$asSubset && $this->fontCache->has($font['fontkey'] . '.cw')) { 322 $w = $this->fontCache->load($font['fontkey'] . '.cw'); 323 $this->writer->write($w); 324 } else { 325 $this->writeTTFontWidths($font, $asSubset, ($asSubset ? $ttf->maxUni : 0)); 326 } 327 328 $this->writer->write('/CIDToGIDMap ' . ($this->mpdf->n + 4) . ' 0 R'); 329 $this->writer->write('>>'); 330 $this->writer->write('endobj'); 331 332 // ToUnicode 333 $this->writer->object(); 334 $toUni = "/CIDInit /ProcSet findresource begin\n"; 335 $toUni .= "12 dict begin\n"; 336 $toUni .= "begincmap\n"; 337 $toUni .= "/CIDSystemInfo\n"; 338 $toUni .= "<</Registry (Adobe)\n"; 339 $toUni .= "/Ordering (UCS)\n"; 340 $toUni .= "/Supplement 0\n"; 341 $toUni .= ">> def\n"; 342 $toUni .= "/CMapName /Adobe-Identity-UCS def\n"; 343 $toUni .= "/CMapType 2 def\n"; 344 $toUni .= "1 begincodespacerange\n"; 345 $toUni .= "<0000> <FFFF>\n"; 346 $toUni .= "endcodespacerange\n"; 347 $toUni .= "1 beginbfrange\n"; 348 $toUni .= "<0000> <FFFF> <0000>\n"; 349 $toUni .= "endbfrange\n"; 350 $toUni .= "endcmap\n"; 351 $toUni .= "CMapName currentdict /CMap defineresource pop\n"; 352 $toUni .= "end\n"; 353 $toUni .= "end\n"; 354 355 $this->writer->write('<</Length ' . strlen($toUni) . '>>'); 356 $this->writer->stream($toUni); 357 $this->writer->write('endobj'); 358 359 // CIDSystemInfo dictionary 360 $this->writer->object(); 361 $this->writer->write('<</Registry (Adobe)'); 362 $this->writer->write('/Ordering (UCS)'); 363 $this->writer->write('/Supplement 0'); 364 $this->writer->write('>>'); 365 $this->writer->write('endobj'); 366 367 // Font descriptor 368 $this->writer->object(); 369 $this->writer->write('<</Type /FontDescriptor'); 370 $this->writer->write('/FontName /' . $fontname); 371 372 foreach ($font['desc'] as $kd => $v) { 373 if ($asSubset && $kd === 'Flags') { 374 $v |= 4; 375 $v &= ~32; 376 } // SYMBOLIC font flag 377 $this->writer->write(' /' . $kd . ' ' . $v); 378 } 379 380 if ($font['panose']) { 381 $this->writer->write(' /Style << /Panose <' . $font['panose'] . '> >>'); 382 } 383 384 if ($asSubset) { 385 $this->writer->write('/FontFile2 ' . ($this->mpdf->n + 2) . ' 0 R'); 386 } elseif ($font['fontkey']) { 387 // obj ID of a stream containing a TrueType font program 388 $this->writer->write('/FontFile2 ' . $this->mpdf->FontFiles[$font['fontkey']]['n'] . ' 0 R'); 389 } 390 391 $this->writer->write('>>'); 392 $this->writer->write('endobj'); 393 394 // Embed CIDToGIDMap 395 // A specification of the mapping from CIDs to glyph indices 396 if ($asSubset) { 397 $cidtogidmap = str_pad('', 256 * 256 * 2, "\x00"); 398 foreach ($codeToGlyph as $cc => $glyph) { 399 $cidtogidmap[$cc * 2] = chr($glyph >> 8); 400 $cidtogidmap[$cc * 2 + 1] = chr($glyph & 0xFF); 401 } 402 $cidtogidmap = gzcompress($cidtogidmap); 403 } else { 404 // First see if there is a cached CIDToGIDMapfile 405 if ($this->fontCache->has($font['fontkey'] . '.cgm')) { 406 $cidtogidmap = $this->fontCache->load($font['fontkey'] . '.cgm'); 407 } else { 408 $ttf = new TTFontFile($this->fontCache, $this->fontDescriptor); 409 $charToGlyph = $ttf->getCTG($font['ttffile'], $font['TTCfontID'], $this->mpdf->debugfonts, $font['useOTL']); 410 $cidtogidmap = str_pad('', 256 * 256 * 2, "\x00"); 411 foreach ($charToGlyph as $cc => $glyph) { 412 $cidtogidmap[$cc * 2] = chr($glyph >> 8); 413 $cidtogidmap[$cc * 2 + 1] = chr($glyph & 0xFF); 414 } 415 unset($ttf); 416 $cidtogidmap = gzcompress($cidtogidmap); 417 $this->fontCache->binaryWrite($font['fontkey'] . '.cgm', $cidtogidmap); 418 } 419 } 420 $this->writer->object(); 421 $this->writer->write('<</Length ' . strlen($cidtogidmap) . ''); 422 $this->writer->write('/Filter /FlateDecode'); 423 $this->writer->write('>>'); 424 $this->writer->stream($cidtogidmap); 425 $this->writer->write('endobj'); 426 427 // Font file 428 if ($asSubset) { 429 $this->writer->object(); 430 $this->writer->write('<</Length ' . strlen($fontstream)); 431 $this->writer->write('/Filter /FlateDecode'); 432 $this->writer->write('/Length1 ' . $ttfontsize); 433 $this->writer->write('>>'); 434 $this->writer->stream($fontstream); 435 $this->writer->write('endobj'); 436 unset($ttf); 437 } 438 } else { 439 throw new \Mpdf\MpdfException(sprintf('Unsupported font type: %s (%s)', $type, $name)); 440 } 441 } 442 } 443 444 private function writeTTFontWidths(&$font, $asSubset, $maxUni) // _putTTfontwidths 445 { 446 $character = [ 447 'startcid' => 1, 448 'rangeid' => 0, 449 'prevcid' => -2, 450 'prevwidth' => -1, 451 'interval' => false, 452 'range' => [], 453 ]; 454 455 $fontCacheFilename = $font['fontkey'] . '.cw127.json'; 456 if ($asSubset && $this->fontCache->jsonHas($fontCacheFilename)) { 457 $character = $this->fontCache->jsonLoad($fontCacheFilename); 458 $character['startcid'] = 128; 459 } 460 461 // for each character 462 $cwlen = ($asSubset) ? $maxUni + 1 : (strlen($font['cw']) / 2); 463 for ($cid = $character['startcid']; $cid < $cwlen; $cid++) { 464 if ($cid == 128 && $asSubset && (!$this->fontCache->has($fontCacheFilename))) { 465 $character = [ 466 'rangeid' => $character['rangeid'], 467 'prevcid' => $character['prevcid'], 468 'prevwidth' => $character['prevwidth'], 469 'interval' => $character['interval'], 470 'range' => $character['range'], 471 ]; 472 473 $this->fontCache->jsonWrite($fontCacheFilename, $character); 474 } 475 476 $character1 = isset($font['cw'][$cid * 2]) ? $font['cw'][$cid * 2] : ''; 477 $character2 = isset($font['cw'][$cid * 2 + 1]) ? $font['cw'][$cid * 2 + 1] : ''; 478 479 if ($character1 === "\00" && $character2 === "\00") { 480 continue; 481 } 482 483 $width = (ord($character1) << 8) + ord($character2); 484 485 if ($width === 65535) { 486 $width = 0; 487 } 488 489 if ($asSubset && $cid > 255 && (!isset($font['subset'][$cid]) || !$font['subset'][$cid])) { 490 continue; 491 } 492 493 if ($asSubset && $cid > 0xFFFF) { 494 continue; 495 } // mPDF 6 496 497 if (!isset($font['dw']) || (isset($font['dw']) && $width != $font['dw'])) { 498 if ($cid === ($character['prevcid'] + 1)) { 499 // consecutive CID 500 if ($width === $character['prevwidth']) { 501 if (isset($character['range'][$character['rangeid']][0]) && $width === $character['range'][$character['rangeid']][0]) { 502 $character['range'][$character['rangeid']][] = $width; 503 } else { 504 array_pop($character['range'][$character['rangeid']]); 505 // new range 506 $character['rangeid'] = $character['prevcid']; 507 $character['range'][$character['rangeid']] = []; 508 $character['range'][$character['rangeid']][] = $character['prevwidth']; 509 $character['range'][$character['rangeid']][] = $width; 510 } 511 $character['interval'] = true; 512 $character['range'][$character['rangeid']]['interval'] = true; 513 } else { 514 if ($character['interval']) { 515 // new range 516 $character['rangeid'] = $cid; 517 $character['range'][$character['rangeid']] = []; 518 $character['range'][$character['rangeid']][] = $width; 519 } else { 520 $character['range'][$character['rangeid']][] = $width; 521 } 522 $character['interval'] = false; 523 } 524 } else { 525 // new range 526 $character['rangeid'] = $cid; 527 $character['range'][$character['rangeid']] = []; 528 $character['range'][$character['rangeid']][] = $width; 529 $character['interval'] = false; 530 } 531 $character['prevcid'] = $cid; 532 $character['prevwidth'] = $width; 533 } 534 } 535 $w = $this->writeFontRanges($character['range']); 536 $this->writer->write($w); 537 if (!$asSubset) { 538 $this->fontCache->binaryWrite($font['fontkey'] . '.cw', $w); 539 } 540 } 541 542 private function writeFontRanges(&$range) // _putfontranges 543 { 544 // optimize ranges 545 $prevk = -1; 546 $nextk = -1; 547 $prevint = false; 548 foreach ($range as $k => $ws) { 549 $cws = count($ws); 550 if (($k == $nextk) and ( !$prevint) and ( (!isset($ws['interval'])) or ( $cws < 4))) { 551 if (isset($range[$k]['interval'])) { 552 unset($range[$k]['interval']); 553 } 554 $range[$prevk] = array_merge($range[$prevk], $range[$k]); 555 unset($range[$k]); 556 } else { 557 $prevk = $k; 558 } 559 $nextk = $k + $cws; 560 if (isset($ws['interval'])) { 561 if ($cws > 3) { 562 $prevint = true; 563 } else { 564 $prevint = false; 565 } 566 unset($range[$k]['interval']); 567 --$nextk; 568 } else { 569 $prevint = false; 570 } 571 } 572 // output data 573 $w = ''; 574 foreach ($range as $k => $ws) { 575 if (count(array_count_values($ws)) === 1) { 576 // interval mode is more compact 577 $w .= ' ' . $k . ' ' . ($k + count($ws) - 1) . ' ' . $ws[0]; 578 } else { 579 // range mode 580 $w .= ' ' . $k . ' [ ' . implode(' ', $ws) . ' ]' . "\n"; 581 } 582 } 583 return '/W [' . $w . ' ]'; 584 } 585 586 private function writeFontWidths(&$font, $cidoffset = 0) // _putfontwidths 587 { 588 ksort($font['cw']); 589 unset($font['cw'][65535]); 590 $rangeid = 0; 591 $range = []; 592 $prevcid = -2; 593 $prevwidth = -1; 594 $interval = false; 595 // for each character 596 foreach ($font['cw'] as $cid => $width) { 597 $cid -= $cidoffset; 598 if (!isset($font['dw']) || (isset($font['dw']) && $width != $font['dw'])) { 599 if ($cid === ($prevcid + 1)) { 600 // consecutive CID 601 if ($width === $prevwidth) { 602 if ($width === $range[$rangeid][0]) { 603 $range[$rangeid][] = $width; 604 } else { 605 array_pop($range[$rangeid]); 606 // new range 607 $rangeid = $prevcid; 608 $range[$rangeid] = []; 609 $range[$rangeid][] = $prevwidth; 610 $range[$rangeid][] = $width; 611 } 612 $interval = true; 613 $range[$rangeid]['interval'] = true; 614 } else { 615 if ($interval) { 616 // new range 617 $rangeid = $cid; 618 $range[$rangeid] = []; 619 $range[$rangeid][] = $width; 620 } else { 621 $range[$rangeid][] = $width; 622 } 623 $interval = false; 624 } 625 } else { 626 // new range 627 $rangeid = $cid; 628 $range[$rangeid] = []; 629 $range[$rangeid][] = $width; 630 $interval = false; 631 } 632 $prevcid = $cid; 633 $prevwidth = $width; 634 } 635 } 636 $this->writer->write($this->writeFontRanges($range)); 637 } 638 639 // from class PDF_Chinese CJK EXTENSIONS 640 public function writeType0(&$font) // _putType0 641 { 642 // Type0 643 $this->writer->write('/Subtype /Type0'); 644 $this->writer->write('/BaseFont /' . $font['name'] . '-' . $font['CMap']); 645 $this->writer->write('/Encoding /' . $font['CMap']); 646 $this->writer->write('/DescendantFonts [' . ($this->mpdf->n + 1) . ' 0 R]'); 647 $this->writer->write('>>'); 648 $this->writer->write('endobj'); 649 // CIDFont 650 $this->writer->object(); 651 $this->writer->write('<</Type /Font'); 652 $this->writer->write('/Subtype /CIDFontType0'); 653 $this->writer->write('/BaseFont /' . $font['name']); 654 655 $cidinfo = '/Registry ' . $this->writer->string('Adobe'); 656 $cidinfo .= ' /Ordering ' . $this->writer->string($font['registry']['ordering']); 657 $cidinfo .= ' /Supplement ' . $font['registry']['supplement']; 658 $this->writer->write('/CIDSystemInfo <<' . $cidinfo . '>>'); 659 660 $this->writer->write('/FontDescriptor ' . ($this->mpdf->n + 1) . ' 0 R'); 661 if (isset($font['MissingWidth'])) { 662 $this->writer->write('/DW ' . $font['MissingWidth'] . ''); 663 } 664 $this->writeFontWidths($font, 31); 665 $this->writer->write('>>'); 666 $this->writer->write('endobj'); 667 668 // Font descriptor 669 $this->writer->object(); 670 $s = '<</Type /FontDescriptor /FontName /' . $font['name']; 671 foreach ($font['desc'] as $k => $v) { 672 if ($k !== 'Style') { 673 $s .= ' /' . $k . ' ' . $v . ''; 674 } 675 } 676 $this->writer->write($s . '>>'); 677 $this->writer->write('endobj'); 678 } 679 680} 681