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