diff --git a/src/Bidi.php b/src/Bidi.php index e4988e9be..967a17e5e 100644 --- a/src/Bidi.php +++ b/src/Bidi.php @@ -2,6 +2,7 @@ namespace Mpdf; +use Mpdf\Shaper\OtlData; use Mpdf\Utils\UtfString; /** @@ -11,8 +12,9 @@ * resolved and in string form. Nothing here reads a font table - this is the Unicode algorithm over * the classes Ucdn supplies, which is why it sits beside Ucdn rather than under Fonts. * - * prepare() takes an Otl because resolving the explicit embedding controls means deleting them from - * the text afterwards, and removing a character from text and its OTLdata together is Otl's job. + * prepare() takes the document's text encoding because resolving the explicit embedding controls + * means deleting them from the text afterwards, which Shaper\OtlData does to the text and its OTLdata + * together, counting characters in that encoding. * * @see https://www.unicode.org/reports/tr9/ * @@ -445,7 +447,7 @@ public static function sort($ta, $str, $dir, &$chunkOTLdata, $useGPOS) * from Mpdf::printbuffer(); reorder() then rearranges, from WriteFlowingBlock() and * finishFlowingBlock(), once line breaks have divided the paragraph into lines. */ - public static function prepare(&$para, $dir, Otl $otl) + public static function prepare(&$para, $dir, $encoding) { // Set the initial paragraph embedding level @@ -653,11 +655,11 @@ public static function prepare(&$para, $dir, Otl $otl) // Paragraph separators are not included in the embedding. // X9. Remove all RLE, LRE, RLO, LRO, and PDF codes. if ($controlchars) { - $otl->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x80\xaa"); - $otl->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x80\xab"); - $otl->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x80\xac"); - $otl->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x80\xad"); - $otl->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x80\xae"); + OtlData::removeChar($para[$nc][0], $para[$nc][18], "\xe2\x80\xaa", $encoding); + OtlData::removeChar($para[$nc][0], $para[$nc][18], "\xe2\x80\xab", $encoding); + OtlData::removeChar($para[$nc][0], $para[$nc][18], "\xe2\x80\xac", $encoding); + OtlData::removeChar($para[$nc][0], $para[$nc][18], "\xe2\x80\xad", $encoding); + OtlData::removeChar($para[$nc][0], $para[$nc][18], "\xe2\x80\xae", $encoding); preg_replace("/\x{202a}-\x{202e}/u", '', $para[$nc][0]); } } @@ -1011,10 +1013,10 @@ public static function prepare(&$para, $dir, Otl $otl) $numchunks = count($para); if ($controlchars) { for ($nc = 0; $nc < $numchunks; $nc++) { - $otl->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x81\xa6"); - $otl->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x81\xa7"); - $otl->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x81\xa8"); - $otl->removeChar($para[$nc][0], $para[$nc][18], "\xe2\x81\xa9"); + OtlData::removeChar($para[$nc][0], $para[$nc][18], "\xe2\x81\xa6", $encoding); + OtlData::removeChar($para[$nc][0], $para[$nc][18], "\xe2\x81\xa7", $encoding); + OtlData::removeChar($para[$nc][0], $para[$nc][18], "\xe2\x81\xa8", $encoding); + OtlData::removeChar($para[$nc][0], $para[$nc][18], "\xe2\x81\xa9", $encoding); preg_replace("/\x{2066}-\x{2069}/u", '', $para[$nc][0]); } // Remove any blank chunks made by removing directional codes diff --git a/src/Mpdf.php b/src/Mpdf.php index 8667fc107..694ee790f 100644 --- a/src/Mpdf.php +++ b/src/Mpdf.php @@ -13,6 +13,7 @@ use Mpdf\Output\Destination; use Mpdf\PsrLogAwareTrait\MpdfPsrLogAwareTrait; use Mpdf\QrCode; +use Mpdf\Shaper\OtlData; use Mpdf\Utils\Arrays; use Mpdf\Utils\NumericString; use Mpdf\Utils\UtfString; @@ -6141,8 +6142,8 @@ function MultiCell( /* -- OTL -- */ if (isset($OTLdata)) { - $tmpOTLdata = $this->otl->sliceOTLdata($OTLdata, $j, $i - $j); - $this->otl->trimOTLdata($tmpOTLdata, false, true); + $tmpOTLdata = OtlData::slice($OTLdata, $j, $i - $j); + OtlData::trim($tmpOTLdata, false, true); $this->magic_reverse_dir($tmp, $directionality, $tmpOTLdata); } /* -- END OTL -- */ @@ -6191,8 +6192,8 @@ function MultiCell( /* -- OTL -- */ if (isset($OTLdata)) { - $tmpOTLdata = $this->otl->sliceOTLdata($OTLdata, $j, $i - $j); - $this->otl->trimOTLdata($tmpOTLdata, false, true); + $tmpOTLdata = OtlData::slice($OTLdata, $j, $i - $j); + OtlData::trim($tmpOTLdata, false, true); $this->magic_reverse_dir($tmp, $directionality, $tmpOTLdata); } /* -- END OTL -- */ @@ -6206,8 +6207,8 @@ function MultiCell( /* -- OTL -- */ if (isset($OTLdata)) { - $tmpOTLdata = $this->otl->sliceOTLdata($OTLdata, $j, $sep - $j); - $this->otl->trimOTLdata($tmpOTLdata, false, true); + $tmpOTLdata = OtlData::slice($OTLdata, $j, $sep - $j); + OtlData::trim($tmpOTLdata, false, true); } /* -- END OTL -- */ @@ -6382,8 +6383,8 @@ function MultiCell( /* -- OTL -- */ if (isset($OTLdata)) { - $tmpOTLdata = $this->otl->sliceOTLdata($OTLdata, $j, $i - $j); - $this->otl->trimOTLdata($tmpOTLdata, false, true); + $tmpOTLdata = OtlData::slice($OTLdata, $j, $i - $j); + OtlData::trim($tmpOTLdata, false, true); $this->magic_reverse_dir($tmp, $directionality, $tmpOTLdata); } /* -- END OTL -- */ @@ -6646,7 +6647,7 @@ function finishFlowingBlock($endofblock = false, $next = '') $content[count($content) - 1] = substr($content[count($content) - 1], 0, (strlen($content[count($content) - 1]) - $strip)); /* -- OTL -- */ if (isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL']) { - $this->otl->trimOTLdata($cOTLdata[count($cOTLdata) - 1], false, true); + OtlData::trim($cOTLdata[count($cOTLdata) - 1], false, true); } /* -- END OTL -- */ } @@ -6735,9 +6736,9 @@ function finishFlowingBlock($endofblock = false, $next = '') /* -- OTL -- */ // mPDF 5.7.1 if (isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL']) { - $this->otl->removeChar($chunk, $cOTLdata[$k], "\xc2\xad"); - $this->otl->removeChar($chunk, $cOTLdata[$k], "\xe2\x80\x8b"); - $this->otl->replaceSpace($chunk, $cOTLdata[$k]); + OtlData::removeChar($chunk, $cOTLdata[$k], "\xc2\xad", $this->mb_enc); + OtlData::removeChar($chunk, $cOTLdata[$k], "\xe2\x80\x8b", $this->mb_enc); + OtlData::nbspToSpace($chunk, $cOTLdata[$k], $this->mb_enc); $content[$k] = $chunk; } /* -- END OTL -- */ else { // *OTL* $content[$k] = $chunk = str_replace("\xc2\xad", '', $chunk); @@ -8381,7 +8382,7 @@ function WriteFlowingBlock($s, $sOTLdata) $savedPreFont[] = $font[$cutcontentctr]; /* -- OTL -- */ if (!empty($sOTLdata)) { - $savedPreOTLdata[] = $this->otl->splitOTLdata($cOTLdata[$cutcontentctr], $cutcharctr, $cutcharctr); + $savedPreOTLdata[] = OtlData::split($cOTLdata[$cutcontentctr], $cutcharctr, $cutcharctr); } /* -- END OTL -- */ @@ -8402,7 +8403,7 @@ function WriteFlowingBlock($s, $sOTLdata) } if (!empty($sOTLdata)) { - $savedPreOTLdata[] = $this->otl->splitOTLdata($cOTLdata[(count($cOTLdata) - 1)], mb_strlen($currContent, $this->mb_enc)); + $savedPreOTLdata[] = OtlData::split($cOTLdata[(count($cOTLdata) - 1)], mb_strlen($currContent, $this->mb_enc)); } if (strpos($contentB[(count($contentB) - 1)], 'R') !== false) { // ??? @@ -8427,7 +8428,7 @@ function WriteFlowingBlock($s, $sOTLdata) $next = count($savedPreContent) - 1; $savedPreContent[$next] = '-' . $savedPreContent[$next]; if (!empty($savedPreOTLdata[$next])) { - $this->otl->prependOTLchar($savedPreOTLdata[$next], $charData, 'C'); + OtlData::prependChar($savedPreOTLdata[$next], $charData, 'C'); } } } @@ -8445,7 +8446,7 @@ function WriteFlowingBlock($s, $sOTLdata) if (!is_array($breakfound)) { $savedFont = $this->saveFont(); if (!empty($sOTLdata)) { - $savedOTLdata = $this->otl->splitOTLdata($cOTLdata[(count($cOTLdata) - 1)], mb_strlen($currContent, $this->mb_enc)); + $savedOTLdata = OtlData::split($cOTLdata[(count($cOTLdata) - 1)], mb_strlen($currContent, $this->mb_enc)); } } @@ -8465,7 +8466,7 @@ function WriteFlowingBlock($s, $sOTLdata) } // *CJK-FONTS* /* -- OTL -- */ if (isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL']) { - $this->otl->trimOTLdata($cOTLdata[count($cOTLdata) - 1], false, true); // NB also does U+3000 + OtlData::trim($cOTLdata[count($cOTLdata) - 1], false, true); // NB also does U+3000 } /* -- END OTL -- */ @@ -8515,9 +8516,9 @@ function WriteFlowingBlock($s, $sOTLdata) if (!$this->usingCoreFont) { /* -- OTL -- */ if ((isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL']) || !empty($sOTLdata)) { - $this->otl->removeChar($chunk, $cOTLdata[$k], "\xc2\xad"); - $this->otl->removeChar($chunk, $cOTLdata[$k], "\xe2\x80\x8b"); - $this->otl->replaceSpace($chunk, $cOTLdata[$k]); // NBSP -> space + OtlData::removeChar($chunk, $cOTLdata[$k], "\xc2\xad", $this->mb_enc); + OtlData::removeChar($chunk, $cOTLdata[$k], "\xe2\x80\x8b", $this->mb_enc); + OtlData::nbspToSpace($chunk, $cOTLdata[$k], $this->mb_enc); if (preg_match("/([" . $this->pregCURSchars . "])/u", $chunk)) { $inclCursive = true; } @@ -8938,7 +8939,7 @@ function WriteFlowingBlock($s, $sOTLdata) if ($this->checkCJK && $currContent == "\xe3\x80\x80") { $currContent = ''; if (isset($this->CurrentFont['useOTL']) && $this->CurrentFont['useOTL']) { - $this->otl->trimOTLdata($cOTLdata[count($cOTLdata) - 1], true, false); // left trim U+3000 + OtlData::trim($cOTLdata[count($cOTLdata) - 1], true, false); // left trim U+3000 } } /* -- END CJK-FONTS -- */ @@ -13734,7 +13735,7 @@ function WriteHTML($html, $mode = HTMLParserMode::DEFAULT_MODE, $init = true, $c } $e = $this->otl->applyOTL($e, $this->CurrentFont['useOTL']); $this->OTLdata = $this->otl->OTLdata; - $this->otl->removeChar($e, $this->OTLdata, "\xef\xbb\xbf"); // Remove ZWNBSP (also Byte order mark FEFF) + OtlData::removeChar($e, $this->OTLdata, "\xef\xbb\xbf", $this->mb_enc); // Remove ZWNBSP (also Byte order mark FEFF) } /* -- END OTL -- */ else { // removes U+200E/U+200F LTR and RTL mark and U+200C/U+200D Zero-width Joiner and Non-joiner @@ -13758,7 +13759,7 @@ function WriteHTML($html, $mode = HTMLParserMode::DEFAULT_MODE, $init = true, $c if ($this->specialcontent == "type=select") { $e = ltrim($e); if (!empty($this->OTLdata)) { - $this->otl->trimOTLdata($this->OTLdata, true, false); + OtlData::trim($this->OTLdata, true, false); } // *OTL* $stringwidth = $this->GetStringWidth($e); if (!isset($this->selectoption['MAXWIDTH']) || $stringwidth > $this->selectoption['MAXWIDTH']) { @@ -13796,7 +13797,7 @@ function WriteHTML($html, $mode = HTMLParserMode::DEFAULT_MODE, $init = true, $c if (($this->ignorefollowingspaces) && !$this->ispre) { $e = ltrim($e); if (!empty($this->OTLdata)) { - $this->otl->trimOTLdata($this->OTLdata, true, false); + OtlData::trim($this->OTLdata, true, false); } // *OTL* } if ($e || $e === '0') { @@ -13862,7 +13863,7 @@ function WriteHTML($html, $mode = HTMLParserMode::DEFAULT_MODE, $init = true, $c if ($this->ignorefollowingspaces && !$this->ispre) { $e = ltrim($e); if (!empty($this->OTLdata)) { - $this->otl->trimOTLdata($this->OTLdata, true, false); + OtlData::trim($this->OTLdata, true, false); } // *OTL* } if ($e || $e === '0') { @@ -16259,10 +16260,7 @@ function printbuffer($arrayaux, $blockstate = 0, $is_table = false, $table_draft // mPDF 6 // Process bidirectional text ready for bidi-re-ordering (which is done after line-breaks are established in WriteFlowingBlock etc.) if (($blockdir == 'rtl' || $this->biDirectional) && !$table_draft) { - if (empty($this->otl)) { - $this->otl = new Otl($this, $this->fontCache); - } - Bidi::prepare($arrayaux, $blockdir, $this->otl); + Bidi::prepare($arrayaux, $blockdir, $this->mb_enc); $array_size = count($arrayaux); } @@ -16310,7 +16308,7 @@ function printbuffer($arrayaux, $blockstate = 0, $is_table = false, $table_draft if ($i == 0 && $vetor[0] != "\n" && ! $this->ispre) { $vetor[0] = ltrim($vetor[0]); if (!empty($vetor[18])) { - $this->otl->trimOTLdata($vetor[18], true, false); + OtlData::trim($vetor[18], true, false); } // *OTL* } @@ -17055,7 +17053,7 @@ function PaintDivBB($divider = '', $blockstate = 0, $blvl = 0) $legend = $this->blk[$blvl]['border_legend']; // Same structure array as textbuffer $txt = $legend[0] = ltrim($legend[0]); if (!empty($legend[18])) { - $this->otl->trimOTLdata($legend[18], true, false); + OtlData::trim($legend[18], true, false); } // *OTL* // Set font, size, style, color $this->SetFont($legend[4], $legend[2], $legend[11]); @@ -19207,7 +19205,7 @@ function TableCheckMinWidth($maxwidth, $forcewrap = 0, $textbuffer = [], $checkl } $line = trim($line); if (!empty($OTLdata)) { - $this->otl->trimOTLdata($OTLdata, true, true); + OtlData::trim($OTLdata, true, true); } // *OTL* // SET FONT SIZE/STYLE from $chunk[n] // FONTSIZE diff --git a/src/Otl.php b/src/Otl.php index 2b3e36a87..b93ca3255 100644 --- a/src/Otl.php +++ b/src/Otl.php @@ -20,6 +20,8 @@ use Mpdf\Shaper\Indic; use Mpdf\Shaper\LineBreaking; use Mpdf\Shaper\Myanmar; +use Mpdf\Shaper\OtlData; +use Mpdf\Shaper\OtlTags; use Mpdf\Shaper\Sea; use Mpdf\Utils\UtfString; @@ -223,7 +225,7 @@ function applyOTL($str, $useOTL) $this->assocMarks = []; // assocMarks[$posarr mpos] => array(compID, ligPos) if ($this->debugOTL) { - $this->_dumpproc('BEGIN', '-', '-', '-', '-', -1, '-', 0); + echo OtlDump::shapingStep($this->OTLdata, 'BEGIN', '-', '-', '-', '-', -1, '-', 0); } $this->markWordBoundaries($scriptblock); @@ -244,7 +246,7 @@ function applyOTL($str, $useOTL) } if ($this->debugOTL) { - $this->_dumpproc('END', '-', '-', '-', '-', 0, '-', 0); + echo OtlDump::shapingStep($this->OTLdata, 'END', '-', '-', '-', '-', 0, '-', 0); exit; } @@ -1354,11 +1356,11 @@ private function selectScriptAndLanguage($scriptblock, $useOTL) $ScriptLang = $this->mpdf->CurrentFont['GSUBScriptLang']; if (count($ScriptLang)) { - list($GSUBscriptTag, $is_old_spec) = $this->_getOTLscriptTag($ScriptLang, $scripttag, $scriptblock, $this->shaper, $useOTL, 'GSUB'); + list($GSUBscriptTag, $is_old_spec) = OtlTags::script($ScriptLang, $scripttag, $scriptblock, $this->shaper, $useOTL); if ($this->mpdf->fontLanguageOverride && strpos($ScriptLang[$GSUBscriptTag], $this->mpdf->fontLanguageOverride) !== false) { $GSUBlangsys = str_pad($this->mpdf->fontLanguageOverride, 4); } elseif ($GSUBscriptTag && isset($ScriptLang[$GSUBscriptTag]) && $ScriptLang[$GSUBscriptTag] != '') { - $GSUBlangsys = $this->_getOTLLangTag($this->mpdf->currentLang, $ScriptLang[$GSUBscriptTag]); + $GSUBlangsys = OtlTags::language($this->mpdf->currentLang, $ScriptLang[$GSUBscriptTag]); } } $ScriptLang = $this->mpdf->CurrentFont['GPOSScriptLang']; @@ -1370,11 +1372,11 @@ private function selectScriptAndLanguage($scriptblock, $useOTL) } // else repeat for GPOS // [Font XBRiyaz has GSUB tables for latn, but not GPOS for latn] elseif (count($ScriptLang)) { - list($GPOSscriptTag, $dummy) = $this->_getOTLscriptTag($ScriptLang, $scripttag, $scriptblock, $this->shaper, $useOTL, 'GPOS'); + list($GPOSscriptTag, $dummy) = OtlTags::script($ScriptLang, $scripttag, $scriptblock, $this->shaper, $useOTL); if ($GPOSscriptTag && $this->mpdf->fontLanguageOverride && strpos($ScriptLang[$GPOSscriptTag], $this->mpdf->fontLanguageOverride) !== false) { $GPOSlangsys = str_pad($this->mpdf->fontLanguageOverride, 4); } elseif ($GPOSscriptTag && isset($ScriptLang[$GPOSscriptTag]) && $ScriptLang[$GPOSscriptTag] != '') { - $GPOSlangsys = $this->_getOTLLangTag($this->mpdf->currentLang, $ScriptLang[$GPOSscriptTag]); + $GPOSlangsys = OtlTags::language($this->mpdf->currentLang, $ScriptLang[$GPOSscriptTag]); } } @@ -2084,7 +2086,7 @@ private function _applyGSUBsingleSubst($lookupID, $subtable, $ptr, $currGlyph, $ $substitute = $this->glyphToChar($GlyphID); $this->GSUBsubstitute($ptr, $substitute, $Type); if ($this->debugOTL) { - $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); } return 1; @@ -2125,7 +2127,7 @@ private function _applyGSUBmultipleSubst($lookupID, $subtable, $ptr, $currGlyph, // What it puts there is what the cursor moves by, which for the empty sequence is nothing $shift = $this->GSUBsubstitute($ptr, $SubstituteGlyphs, $Type); if ($this->debugOTL) { - $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); } return $shift; @@ -2177,7 +2179,7 @@ private function _applyGSUBalternateSubst($lookupID, $subtable, $ptr, $currGlyph $substitute = $this->glyphToChar($GlyphID); $this->GSUBsubstitute($ptr, $substitute, $Type); if ($this->debugOTL) { - $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); } return 1; @@ -2246,7 +2248,7 @@ private function _applyGSUBligatureSubst($lookupID, $subtable, $ptr, $currGlyph, if ($match) { $shift = $this->GSUBsubstitute($ptr, $substitute, $Type, $GlyphPos); // GlyphPos contains positions to set null if ($this->debugOTL && $shift) { - $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); } if ($shift) { return ($spos - $ptr + 1 - ($CompCount - 1)); @@ -2301,7 +2303,7 @@ private function _applyGSUBcontextSubstFormat1($lookupID, $subtable, $ptr, $curr $matched = $this->checkContextMatch($Input, [], [], $ignore, $ptr); if ($matched) { if ($this->debugOTL) { - $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); } return $this->_applyGSUBlookupRecords($SubstCount, $matched, $currentTag, $is_old_spec, $tagInt); } @@ -2361,7 +2363,7 @@ private function _applyGSUBcontextSubstFormat2($lookupID, $subtable, $ptr, $curr $matched = $this->checkContextMatchMultiple($inputGlyphs, [], [], $ignore, $ptr, $class0excl); if ($matched) { if ($this->debugOTL) { - $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); } return $this->_applyGSUBlookupRecords($SubstCount, $matched, $currentTag, $is_old_spec, $tagInt); } @@ -2395,7 +2397,7 @@ private function _applyGSUBcontextSubstFormat3($lookupID, $subtable, $ptr, $curr $matched = $this->checkContextMatchMultiple($CoverageInputGlyphs, [], [], $ignore, $ptr); if ($matched) { if ($this->debugOTL) { - $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); } $this->reader->seek($save_pos); // Return to just after the Coverage table offsets @@ -2441,7 +2443,7 @@ private function _applyGSUBchainContextSubstFormat1($lookupID, $subtable, $ptr, $matched = $this->checkContextMatch($Input, $Backtrack, $Lookahead, $ignore, $ptr); if ($matched) { if ($this->debugOTL) { - $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); } $SubstCount = $this->reader->readUInt16(); return $this->_applyGSUBlookupRecords($SubstCount, $matched, $currentTag, $is_old_spec, $tagInt); @@ -2510,7 +2512,7 @@ private function _applyGSUBchainContextSubstFormat2($lookupID, $subtable, $ptr, $matched = $this->checkContextMatchMultiple($inputGlyphs, $backtrackGlyphs, $lookaheadGlyphs, $ignore, $ptr, $class0excl, $bclass0excl, $lclass0excl); if ($matched) { if ($this->debugOTL) { - $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); } $SubstCount = $this->reader->readUInt16(); return $this->_applyGSUBlookupRecords($SubstCount, $matched, $currentTag, $is_old_spec, $tagInt); @@ -2549,7 +2551,7 @@ private function _applyGSUBchainContextSubstFormat3($lookupID, $subtable, $ptr, $matched = $this->checkContextMatchMultiple($CoverageInputGlyphs, $CoverageBacktrackGlyphs, $CoverageLookaheadGlyphs, $ignore, $ptr); if ($matched) { if ($this->debugOTL) { - $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); } $this->reader->seek($save_pos); // Return to just after SubstCount @@ -2621,7 +2623,7 @@ private function _applyGSUBreverseChainSingleSubst($lookupID, $subtable, $ptr, $ $this->GSUBsubstitute($ptr, $substitute, $Type); if ($this->debugOTL) { - $this->_dumpproc('GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GSUB', $lookupID, $subtable, $Type, $SubstFormat, $ptr, $currGlyph, $level); } return 1; @@ -3303,7 +3305,7 @@ private function _applyGPOSsingleAdjustment($lookupID, $subtable, $ptr, $currGly } $this->_applyGPOSvaluerecord($ptr, $Value); if ($this->debugOTL) { - $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); } return 1; } @@ -3383,12 +3385,12 @@ private function _applyGPOSpairAdjustmentFormat1($lookupID, $subtable, $ptr, $cu if ($ValueFormat2) { $this->_applyGPOSvaluerecord($matchedpos, $Value2); if ($this->debugOTL) { - $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); } return $matchedpos - $ptr + 1; } if ($this->debugOTL) { - $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); } return $matchedpos - $ptr; } else { @@ -3463,12 +3465,12 @@ private function _applyGPOSpairAdjustmentFormat2($lookupID, $subtable, $ptr, $cu if ($ValueFormat2) { $this->_applyGPOSvaluerecord($matchedpos, $Value2); if ($this->debugOTL) { - $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); } return $matchedpos - $ptr + 1; } if ($this->debugOTL) { - $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); } return $matchedpos - $ptr; } @@ -3525,7 +3527,7 @@ private function _applyGPOScursiveAttachment($lookupID, $subtable, $ptr, $currGl $this->Exit[$ptr] = ['X' => $x, 'Y' => $y, 'dir' => $dir]; } if ($this->debugOTL) { - $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); } return 1; } @@ -3614,7 +3616,7 @@ private function _applyGPOSmarkToBase($lookupID, $subtable, $ptr, $currGlyph, $c $this->OTLdata[$ptr]['GPOSinfo']['XPlacement'] = $prevXPlacement + $BaseRecord['AnchorX'] - $MarkRecord['AnchorX']; $this->OTLdata[$ptr]['GPOSinfo']['YPlacement'] = $prevYPlacement + $BaseRecord['AnchorY'] - $MarkRecord['AnchorY']; if ($this->debugOTL) { - $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); } return 1; } @@ -3720,7 +3722,7 @@ private function _applyGPOSmarkToLigature($lookupID, $subtable, $ptr, $currGlyph $this->OTLdata[$ptr]['GPOSinfo']['XPlacement'] = $prevXPlacement + $LigatureRecord['AnchorX'] - $MarkRecord['AnchorX']; $this->OTLdata[$ptr]['GPOSinfo']['YPlacement'] = $prevYPlacement + $LigatureRecord['AnchorY'] - $MarkRecord['AnchorY']; if ($this->debugOTL) { - $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); } return 1; } @@ -3817,7 +3819,7 @@ private function _applyGPOSmarkToMark($lookupID, $subtable, $ptr, $currGlyph, $c $this->OTLdata[$ptr]['GPOSinfo']['XPlacement'] = $prevXPlacement + $Mark2Record['AnchorX'] - $Mark1Record['AnchorX']; $this->OTLdata[$ptr]['GPOSinfo']['YPlacement'] = $prevYPlacement + $Mark2Record['AnchorY'] - $Mark1Record['AnchorY']; if ($this->debugOTL) { - $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); } return 1; } @@ -3867,7 +3869,7 @@ private function _applyGPOScontextPosFormat1($lookupID, $subtable, $ptr, $currGl if ($matched) { $shift = $this->_applyGPOSlookupRecords($PosCount, $matched, $tag, $is_old_spec); if ($this->debugOTL) { - $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); } return $shift; @@ -3928,7 +3930,7 @@ private function _applyGPOScontextPosFormat2($lookupID, $subtable, $ptr, $currGl if ($matched) { $shift = $this->_applyGPOSlookupRecords($PosCount, $matched, $tag, $is_old_spec); if ($this->debugOTL) { - $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); } return $shift; @@ -3965,7 +3967,7 @@ private function _applyGPOScontextPosFormat3($lookupID, $subtable, $ptr, $currGl $this->reader->seek($save_pos); // Return to just after the Coverage table offsets $shift = $this->_applyGPOSlookupRecords($PosCount, $matched, $tag, $is_old_spec); if ($this->debugOTL) { - $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); } return $shift; @@ -4018,7 +4020,7 @@ private function _applyGPOSchainContextPosFormat1($lookupID, $subtable, $ptr, $c $PosCount = $this->reader->readUInt16(); $shift = $this->_applyGPOSlookupRecords($PosCount, $matched, $tag, $is_old_spec); if ($this->debugOTL) { - $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); } return $shift; @@ -4088,7 +4090,7 @@ private function _applyGPOSchainContextPosFormat2($lookupID, $subtable, $ptr, $c $PosCount = $this->reader->readUInt16(); $shift = $this->_applyGPOSlookupRecords($PosCount, $matched, $tag, $is_old_spec); if ($this->debugOTL) { - $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); } return $shift; @@ -4129,7 +4131,7 @@ private function _applyGPOSchainContextPosFormat3($lookupID, $subtable, $ptr, $c $this->reader->seek($save_pos); // Return to just after PosCount $shift = $this->_applyGPOSlookupRecords($PosCount, $matched, $tag, $is_old_spec); if ($this->debugOTL) { - $this->_dumpproc('GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); + echo OtlDump::shapingStep($this->OTLdata, 'GPOS', $lookupID, $subtable, $Type, $PosFormat, $ptr, $currGlyph, $level); } return $shift; @@ -4515,217 +4517,51 @@ private function getGCOMignoreSet($flag, $MarkFilteringSet) } /** - * Cut a laid-out run in two, at a line break. - * - * What is left of the run keeps everything up to the cut; what is returned starts at the restart - * position, which is past the cut where the break took a space with it. - * - * @param array $cOTLdata The run, truncated in place to the part before the break - * @param int $OTLcutoffpos Where the first part ends - * @param int|string $OTLrestartpos Where the second part begins, or '' for the cutoff - * - * @return array The part after the break + * @deprecated Use Mpdf\Shaper\OtlData::split() */ public function splitOTLdata(&$cOTLdata, $OTLcutoffpos, $OTLrestartpos = '') { - if (!$OTLrestartpos) { - $OTLrestartpos = $OTLcutoffpos; - } - $newOTLdata = ['GPOSinfo' => [], 'char_data' => []]; - $newOTLdata['group'] = substr($cOTLdata['group'], $OTLrestartpos); - $cOTLdata['group'] = substr($cOTLdata['group'], 0, $OTLcutoffpos); - - if (isset($cOTLdata['GPOSinfo']) && $cOTLdata['GPOSinfo']) { - foreach ($cOTLdata['GPOSinfo'] as $k => $val) { - if ($k >= $OTLrestartpos) { - $newOTLdata['GPOSinfo'][($k - $OTLrestartpos)] = $val; - } - if ($k >= $OTLcutoffpos) { - unset($cOTLdata['GPOSinfo'][$k]); - //$cOTLdata['GPOSinfo'][$k] = array(); - } - } - } - if (isset($cOTLdata['char_data'])) { - $newOTLdata['char_data'] = array_slice($cOTLdata['char_data'], $OTLrestartpos); - array_splice($cOTLdata['char_data'], $OTLcutoffpos); - } - - // Not necessary - easier to debug - if (isset($cOTLdata['GPOSinfo'])) { - ksort($cOTLdata['GPOSinfo']); - } - if (isset($newOTLdata['GPOSinfo'])) { - ksort($newOTLdata['GPOSinfo']); - } - - return $newOTLdata; + return OtlData::split($cOTLdata, $OTLcutoffpos, $OTLrestartpos); } /** - * A copy of part of a laid-out run, with the positioning renumbered to start at zero. - * - * @param array $OTLdata The run - * @param int $pos Where the part begins - * @param int $len How many characters of it to take - * - * @return array The part, as a run of its own + * @deprecated Use Mpdf\Shaper\OtlData::slice() */ public function sliceOTLdata($OTLdata, $pos, $len) { - // applyOTL() leaves OTLdata empty for a blank string, so every key here is optional - $newOTLdata = ['GPOSinfo' => [], 'char_data' => []]; - $newOTLdata['group'] = isset($OTLdata['group']) ? substr($OTLdata['group'], $pos, $len) : ''; - - if (!empty($OTLdata['GPOSinfo'])) { - foreach ($OTLdata['GPOSinfo'] as $k => $val) { - if ($k >= $pos && $k < ($pos + $len)) { - $newOTLdata['GPOSinfo'][($k - $pos)] = $val; - } - } - } - - if (isset($OTLdata['char_data'])) { - $newOTLdata['char_data'] = array_slice($OTLdata['char_data'], $pos, $len); - } - - // Not necessary - easier to debug - if ($newOTLdata['GPOSinfo']) { - ksort($newOTLdata['GPOSinfo']); - } - - return $newOTLdata; + return OtlData::slice($OTLdata, $pos, $len); } /** - * Put one character at the front of a laid-out run, moving the rest of it along by one. - * - * @param array $cOTLdata The run - * @param array $charData The character's entry, as Bidi::prepare() would have left it - * @param string $group Its class in the run's group string + * @deprecated Use Mpdf\Shaper\OtlData::prependChar() */ public function prependOTLchar(&$cOTLdata, $charData, $group) { - // applyOTL() leaves OTLdata empty for a blank string, so every key here is optional - $cOTLdata += ['group' => '', 'char_data' => [], 'GPOSinfo' => []]; - - $cOTLdata['group'] = $group . $cOTLdata['group']; - array_unshift($cOTLdata['char_data'], $charData); - - if ($cOTLdata['GPOSinfo']) { - $newGPOSinfo = []; - foreach ($cOTLdata['GPOSinfo'] as $k => $val) { - $newGPOSinfo[$k + 1] = $val; - } - $cOTLdata['GPOSinfo'] = $newGPOSinfo; - } + OtlData::prependChar($cOTLdata, $charData, $group); } /** - * Remove one or more occurrences of $char (single character) from $txt and adjust OTLdata + * @deprecated Use Mpdf\Shaper\OtlData::removeChar() */ public function removeChar(&$txt, &$cOTLdata, $char) { - while (mb_strpos($txt, $char, 0, $this->mpdf->mb_enc) !== false) { - $pos = mb_strpos($txt, $char, 0, $this->mpdf->mb_enc); - $newGPOSinfo = []; - $cOTLdata['group'] = substr_replace($cOTLdata['group'], '', $pos, 1); - if ($cOTLdata['GPOSinfo']) { - foreach ($cOTLdata['GPOSinfo'] as $k => $val) { - if ($k > $pos) { - $newGPOSinfo[($k - 1)] = $val; - } elseif ($k != $pos) { - $newGPOSinfo[$k] = $val; - } - } - $cOTLdata['GPOSinfo'] = $newGPOSinfo; - } - if (isset($cOTLdata['char_data'])) { - array_splice($cOTLdata['char_data'], $pos, 1); - } - - $txt = preg_replace("/" . $char . "/", '', $txt, 1); - } + OtlData::removeChar($txt, $cOTLdata, $char, $this->mpdf->mb_enc); } /** - * Remove one or more occurrences of $char (single character) from $txt and adjust OTLdata + * @deprecated Use Mpdf\Shaper\OtlData::nbspToSpace() */ public function replaceSpace(&$txt, &$cOTLdata) { - $char = chr(194) . chr(160); // NBSP - while (mb_strpos($txt, $char, 0, $this->mpdf->mb_enc) !== false) { - $pos = mb_strpos($txt, $char, 0, $this->mpdf->mb_enc); - if ($cOTLdata['char_data'][$pos]['uni'] == 160) { - $cOTLdata['char_data'][$pos]['uni'] = 32; - } - $txt = preg_replace("/" . $char . "/", ' ', $txt, 1); - } + OtlData::nbspToSpace($txt, $cOTLdata, $this->mpdf->mb_enc); } /** - * Drop the spaces from the ends of a laid-out run, and the positioning that went with them. - * - * @param array $cOTLdata The run, trimmed in place - * @param bool $Left Whether to trim the start - * @param bool $Right Whether to trim the end + * @deprecated Use Mpdf\Shaper\OtlData::trim() */ public function trimOTLdata(&$cOTLdata, $Left = true, $Right = true) { - $len = (!is_array($cOTLdata) || $cOTLdata['char_data'] === null) ? 0 : count($cOTLdata['char_data']); - $nLeft = 0; - $nRight = 0; - for ($i = 0; $i < $len; $i++) { - if ($cOTLdata['char_data'][$i]['uni'] == 32 || $cOTLdata['char_data'][$i]['uni'] == 12288) { - $nLeft++; - } // 12288 = 0x3000 = CJK space - else { - break; - } - } - for ($i = ($len - 1); $i >= 0; $i--) { - if ($cOTLdata['char_data'][$i]['uni'] == 32 || $cOTLdata['char_data'][$i]['uni'] == 12288) { - $nRight++; - } // 12288 = 0x3000 = CJK space - else { - break; - } - } - - // Trim Right - if ($Right && $nRight) { - $cOTLdata['group'] = substr($cOTLdata['group'], 0, strlen($cOTLdata['group']) - $nRight); - if ($cOTLdata['GPOSinfo']) { - foreach ($cOTLdata['GPOSinfo'] as $k => $val) { - if ($k >= $len - $nRight) { - unset($cOTLdata['GPOSinfo'][$k]); - } - } - } - if (isset($cOTLdata['char_data'])) { - for ($i = 0; $i < $nRight; $i++) { - array_pop($cOTLdata['char_data']); - } - } - } - // Trim Left - if ($Left && $nLeft) { - $cOTLdata['group'] = substr($cOTLdata['group'], $nLeft); - if ($cOTLdata['GPOSinfo']) { - $newPOSinfo = []; - foreach ($cOTLdata['GPOSinfo'] as $k => $val) { - if ($k >= $nLeft) { - $newPOSinfo[$k - $nLeft] = $cOTLdata['GPOSinfo'][$k]; - } - } - $cOTLdata['GPOSinfo'] = $newPOSinfo; - } - if (isset($cOTLdata['char_data'])) { - for ($i = 0; $i < $nLeft; $i++) { - array_shift($cOTLdata['char_data']); - } - } - } + OtlData::trim($cOTLdata, $Left, $Right); } /** @@ -4933,262 +4769,4 @@ private function getClassZeroExclusions($offset) return $this->LuDataCache[$this->otlCacheKey]['class0excl'][$offset]; } - - /** - * Pick the OpenType script tag to lay the text out under, from what the font offers. - * - * The tag Unicode implies is only a first choice: a font may offer the v2 Indic tag and not the - * old one or the other way round, may offer nothing for the script and still have a default - * entry, and may offer a script mPDF has no shaper for. This settles all of that, and says which - * Indic specification the chosen tag implies. - * - * @param array $ScriptLang The scripts this table offers, and the language systems under each - * @param string $scripttag The tag the text's Unicode script implies - * @param int $scriptblock The text's Unicode script - * @param string $shaper The shaper picked for it, where there is one - * @param int $useOTL Which script groups the document asked to be laid out this way - * @param string $mode 'GSUB' or 'GPOS', which may not offer the same scripts - * - * @return array The tag to use, or '' for none, and whether it implies the original Indic - * specification rather than the v2 one - */ - private function _getOTLscriptTag($ScriptLang, $scripttag, $scriptblock, $shaper, $useOTL, $mode) - { - // ScriptLang is the array of available script/lang tags supported by the font - // $scriptblock is the (number/code) for the script of the actual text string based on Unicode properties (Ucdn::$uni_scriptblock) - // $scripttag is the default tag derived from $scriptblock - /* - https://learn.microsoft.com/en-us/typography/opentype/spec/ttoreg - https://learn.microsoft.com/en-us/typography/opentype/spec/scripttags - - Values for useOTL - - Bit dn hn Value - 1 1 0x0001 GSUB/GPOS - Latin scripts - 2 2 0x0002 GSUB/GPOS - Cyrillic scripts - 3 4 0x0004 GSUB/GPOS - Greek scripts - 4 8 0x0008 GSUB/GPOS - CJK scripts (excluding Hangul-Jamo) - 5 16 0x0010 (Reserved) - 6 32 0x0020 (Reserved) - 7 64 0x0040 (Reserved) - 8 128 0x0080 GSUB/GPOS - All other scripts (including all RTL scripts, complex scripts with shapers etc) - - NB If change for RTL - cf. function magic_reverse_dir in mpdf.php to update - - */ - - if ($scriptblock == Ucdn::SCRIPT_LATIN) { - if (!($useOTL & 0x01)) { - return ['', false]; - } - } elseif ($scriptblock == Ucdn::SCRIPT_CYRILLIC) { - if (!($useOTL & 0x02)) { - return ['', false]; - } - } elseif ($scriptblock == Ucdn::SCRIPT_GREEK) { - if (!($useOTL & 0x04)) { - return ['', false]; - } - } elseif ($scriptblock >= Ucdn::SCRIPT_HIRAGANA && $scriptblock <= Ucdn::SCRIPT_YI) { - if (!($useOTL & 0x08)) { - return ['', false]; - } - } else { - if (!($useOTL & 0x80)) { - return ['', false]; - } - } - - // If availabletags includes scripttag - choose - if (isset($ScriptLang[$scripttag])) { - return [$scripttag, false]; - } - - // If INDIC (or Myanmar) and available tag not includes new version, check if includes old version & choose old version - if ($shaper) { - switch ($scripttag) { - case 'bng2': - if (isset($ScriptLang['beng'])) { - return ['beng', true]; - } - // fallthrough - case 'dev2': - if (isset($ScriptLang['deva'])) { - return ['deva', true]; - } - // fallthrough - case 'gjr2': - if (isset($ScriptLang['gujr'])) { - return ['gujr', true]; - } - // fallthrough - case 'gur2': - if (isset($ScriptLang['guru'])) { - return ['guru', true]; - } - // fallthrough - case 'knd2': - if (isset($ScriptLang['knda'])) { - return ['knda', true]; - } - // fallthrough - case 'mlm2': - if (isset($ScriptLang['mlym'])) { - return ['mlym', true]; - } - // fallthrough - case 'ory2': - if (isset($ScriptLang['orya'])) { - return ['orya', true]; - } - // fallthrough - case 'tml2': - if (isset($ScriptLang['taml'])) { - return ['taml', true]; - } - // fallthrough - case 'tel2': - if (isset($ScriptLang['telu'])) { - return ['telu', true]; - } - // fallthrough - case 'mym2': - if (isset($ScriptLang['mymr'])) { - return ['mymr', true]; - } - } - } - - // choose DFLT if present - if (isset($ScriptLang['DFLT'])) { - return ['DFLT', false]; - } - // else choose dflt if present - if (isset($ScriptLang['dflt'])) { - return ['dflt', false]; - } - // else return no scriptTag - if (isset($ScriptLang['latn'])) { - return ['latn', false]; - } - // else return no scriptTag - return ['', false]; - } - - /** - * Pick the OpenType language system tag from the document's language, out of what the script - * offers. - * - * An IETF tag is tried from the most specific part down - the language with its script or region, - * then the language alone - so that a font offering only the broader entry is still matched. - * - * @param string $ietf The language of the text, as an IETF tag, e.g. 'sr-Cyrl' - * @param string $available The language systems this script offers, space separated - * - * @return string The tag to use, or '' to fall back to the script's default - */ - private function _getOTLLangTag($ietf, $available) - { - // http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes - // https://learn.microsoft.com/en-us/typography/opentype/spec/languagetags - // IETF tag = e.g. en-US, und-Arab, sr-Cyrl cf. class LangToFont - if ($available == '') { - return ''; - } - - $tags = $ietf - ? preg_split('/-/', $ietf) - : []; - - $lang = ''; - $country = ''; - $script = ''; - - $lang = isset($tags[0]) - ? strtolower($tags[0]) - : ''; - - if (isset($tags[1]) && $tags[1]) { - if (strlen($tags[1]) == 2) { - $country = strtolower($tags[1]); - } - } - - if (isset($tags[2]) && $tags[2]) { - $country = strtolower($tags[2]); - } - - if ($lang != '' && isset(Ucdn::$ot_languages[$lang])) { - $langsys = Ucdn::$ot_languages[$lang]; - } elseif ($lang != '' && $country != '' && isset(Ucdn::$ot_languages[$lang . '' . $country])) { - $langsys = Ucdn::$ot_languages[$lang . '' . $country]; - } else { - $langsys = "DFLT"; - } - - if (strpos($available, $langsys) === false) { - if (strpos($available, "DFLT") !== false) { - return "DFLT"; - } else { - return ''; - } - } - - return $langsys; - } - - /** - * Echo the state of the run at one step of shaping, for the debugOTL trace. - * - * @param string $GPOSSUB 'GSUB' or 'GPOS', or a marker for the beginning or end of the run - * @param int $lookupID The lookup that applied - * @param int $subtable Which of its subtables - * @param int $Type The lookup's type - * @param int $Format The subtable's format - * @param int $ptr Where in the run it applied - * @param string $currGlyph The glyph it applied at, as hex - * @param int $level 0 for a lookup applied directly, 1 for one nested in a context rule - */ - private function _dumpproc($GPOSSUB, $lookupID, $subtable, $Type, $Format, $ptr, $currGlyph, $level) - { - echo '