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