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