Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions src/Otl.php
Original file line number Diff line number Diff line change
Expand Up @@ -4474,6 +4474,33 @@ private function _getMarkRecord($offset, $MarkPos)
return $MarkRecord;
}

/**
* UseMarkFilteringSet means "skip every mark except those in the given mark glyph set", so the glyphs to
* ignore are GlyphClassMarks minus that set - not the set itself.
*
* @param string $marks Space-prefixed, "|"-separated glyph list, e.g. " 00DCA| 00DD2"
* @param string $set The mark glyph set, in the same format
*
* @return string
*/
private function marksOutsideFilteringSet($marks, $set)
{
$keep = [];
$inSet = [];
foreach (explode('|', $set) as $glyph) {
$inSet[trim($glyph)] = true;
}

foreach (explode('|', $marks) as $glyph) {
$glyph = trim($glyph);
if ($glyph !== '' && !isset($inSet[$glyph])) {
$keep[] = $glyph;
}
}

return $keep ? ' ' . implode('| ', $keep) : '';
}

private function _getGCOMignoreString($flag, $MarkFilteringSet)
{
// If ignoreFlag set, combine all ignore glyphs into -> "(?:( 0FBA1| 0FBA2| 0FBA3)*)"
Expand All @@ -4496,7 +4523,8 @@ private function _getGCOMignoreString($flag, $MarkFilteringSet)
if ($MarkFilteringSet === '' || !isset($this->MarkGlyphSets[$MarkFilteringSet])) {
throw new \Mpdf\MpdfException("This font [" . $this->fontkey . "] contains MarkGlyphSets - but MarkFilteringSet not set");
}
$str = $this->MarkGlyphSets[$MarkFilteringSet];
$ignoreflag = $flag;
$str = $this->marksOutsideFilteringSet($this->GlyphClassMarks, $this->MarkGlyphSets[$MarkFilteringSet]);
}

// If Ignore Marks set, supercedes any above
Expand Down Expand Up @@ -4550,8 +4578,9 @@ private function _checkGCOMignore($flag, $glyph, $MarkFilteringSet)
$ignore = true;
}
}
// Flag & 0x0010 = UseMarkFilteringSet
if (($flag & 0x0010) && strpos($this->MarkGlyphSets[$MarkFilteringSet], $glyph)) {
// Flag & 0x0010 = UseMarkFilteringSet: skip every mark *except* those in the set
if (($flag & 0x0010) && strpos($this->GlyphClassMarks, $glyph)
&& !strpos($this->MarkGlyphSets[$MarkFilteringSet], $glyph)) {
$ignore = true;
}
return $ignore;
Expand Down
59 changes: 51 additions & 8 deletions src/TTFontFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -1344,7 +1344,8 @@ function _getGDEFtables()
$MarkSetOffset[] = $this->read_ulong();
}
for ($i = 0; $i < $MarkSetCount; $i++) {
$this->seek($MarkSetOffset[$i]);
// Coverage offsets are relative to the MarkGlyphSetsDef table, not the file
$this->seek($gdef_offset + $MarkGlyphSetsDef_offset + $MarkSetOffset[$i]);
$glyphs = $this->_getCoverage();
$this->MarkGlyphSets[$i] = ' ' . implode('| ', $glyphs);
}
Expand Down Expand Up @@ -2441,6 +2442,10 @@ function _getGSUBarray(&$Lookup, &$lul, $scripttag)
}
} // LookupType 2: Multiple Substitution Subtable
elseif ($Lookup[$i]['Type'] == 2) {
if (!isset($Lookup[$i]['Subtable'][$c]['subs'])) {
continue; // every entry was filtered out by the Ignore flags
}

for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) {
$inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace'];
$substitute = implode(" ", $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute']);
Expand All @@ -2451,6 +2456,10 @@ function _getGSUBarray(&$Lookup, &$lul, $scripttag)
}
} // LookupType 3: Alternate Forms
elseif ($Lookup[$i]['Type'] == 3) {
if (!isset($Lookup[$i]['Subtable'][$c]['subs'])) {
continue; // every entry was filtered out by the Ignore flags
}

for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) {
$inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace'];
$substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0];
Expand All @@ -2461,6 +2470,10 @@ function _getGSUBarray(&$Lookup, &$lul, $scripttag)
}
} // LookupType 4: Ligature Substitution Subtable
elseif ($Lookup[$i]['Type'] == 4) {
if (!isset($Lookup[$i]['Subtable'][$c]['subs'])) {
continue; // every entry was filtered out by the Ignore flags
}

for ($s = 0; $s < count($Lookup[$i]['Subtable'][$c]['subs']); $s++) {
$inputGlyphs = $Lookup[$i]['Subtable'][$c]['subs'][$s]['Replace'];
$substitute = $Lookup[$i]['Subtable'][$c]['subs'][$s]['substitute'][0];
Expand Down Expand Up @@ -2497,7 +2510,7 @@ function _getGSUBarray(&$Lookup, &$lul, $scripttag)

// $Lookup[$lup] = secondary Lookup
for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) {
if (count($Lookup[$lup]['Subtable'][$lus]['subs'])) {
if (!empty($Lookup[$lup]['Subtable'][$lus]['subs'])) {
foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] as $luss) {
$lookupGlyphs = $luss['Replace'];
$mLen = count($lookupGlyphs);
Expand Down Expand Up @@ -2628,7 +2641,7 @@ function _getGSUBarray(&$Lookup, &$lul, $scripttag)
$lup = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['LookupListIndex'];
$seqIndex = $Lookup[$i]['Subtable'][$c]['SubstLookupRecord'][$b]['SequenceIndex'];
for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) {
if (count($Lookup[$lup]['Subtable'][$lus]['subs'])) {
if (!empty($Lookup[$lup]['Subtable'][$lus]['subs'])) {
foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] as $luss) {
$lookupGlyphs = $luss['Replace'];
$mLen = count($lookupGlyphs);
Expand Down Expand Up @@ -2707,7 +2720,7 @@ function _getGSUBarray(&$Lookup, &$lul, $scripttag)

// $Lookup[$lup] = secondary Lookup
for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) {
if (count($Lookup[$lup]['Subtable'][$lus]['subs'])) {
if (!empty($Lookup[$lup]['Subtable'][$lus]['subs'])) {
foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] as $luss) {
$lookupGlyphs = $luss['Replace'];
$mLen = count($lookupGlyphs);
Expand Down Expand Up @@ -2823,7 +2836,7 @@ function _getGSUBarray(&$Lookup, &$lul, $scripttag)

// $Lookup[$lup] = secondary Lookup
for ($lus = 0; $lus < $Lookup[$lup]['SubtableCount']; $lus++) {
if (count($Lookup[$lup]['Subtable'][$lus]['subs'])) {
if (!empty($Lookup[$lup]['Subtable'][$lus]['subs'])) {
foreach ($Lookup[$lup]['Subtable'][$lus]['subs'] as $luss) {
$lookupGlyphs = $luss['Replace'];
$mLen = count($lookupGlyphs);
Expand Down Expand Up @@ -2946,14 +2959,43 @@ function _checkGSUBignore($flag, $glyph, $MarkFilteringSet)
$ignore = true;
}
}
// Flag & 0x0010 = UseMarkFilteringSet
if (($flag & 0x0010) && strpos($this->MarkGlyphSets[$MarkFilteringSet], $glyph)) {
// Flag & 0x0010 = UseMarkFilteringSet: skip every mark *except* those in the set
if (($flag & 0x0010) && strpos($this->GlyphClassMarks, $glyph)
&& !strpos($this->MarkGlyphSets[$MarkFilteringSet], $glyph)) {
$ignore = true;
}

return $ignore;
}


/**
* UseMarkFilteringSet means "skip every mark except those in the given mark glyph set", so the glyphs to
* ignore are GlyphClassMarks minus that set - not the set itself.
*
* @param string $marks Space-prefixed, "|"-separated glyph list, e.g. " 00DCA| 00DD2"
* @param string $set The mark glyph set, in the same format
*
* @return string
*/
private function marksOutsideFilteringSet($marks, $set)
{
$keep = [];
$inSet = [];
foreach (explode('|', $set) as $glyph) {
$inSet[trim($glyph)] = true;
}

foreach (explode('|', $marks) as $glyph) {
$glyph = trim($glyph);
if ($glyph !== '' && !isset($inSet[$glyph])) {
$keep[] = $glyph;
}
}

return $keep ? ' ' . implode('| ', $keep) : '';
}

function _getGSUBignoreString($flag, $MarkFilteringSet)
{
// If ignoreFlag set, combine all ignore glyphs into -> "((?:(?: FBA1| FBA2| FBA3))*)"
Expand All @@ -2976,7 +3018,8 @@ function _getGSUBignoreString($flag, $MarkFilteringSet)
if (!isset($this->MarkGlyphSets[$MarkFilteringSet])) {
throw new \Mpdf\Exception\FontException(sprintf('Font "%s" uses mark filtering set %s, which GDEF does not define', $this->fontkey, $MarkFilteringSet));
}
$str = $this->MarkGlyphSets[$MarkFilteringSet];
$ignoreflag = $flag;
$str = $this->marksOutsideFilteringSet($this->GlyphClassMarks, $this->MarkGlyphSets[$MarkFilteringSet]);
}

// If Ignore Marks set, supercedes any above
Expand Down
13 changes: 13 additions & 0 deletions tests/Mpdf/TTFontFileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,17 @@ public function testGetMetricsWithMarkGlyphSets()
$this->assertSame('NotoSansSinhala-Regular', $this->ttf->fullName);
}

/**
* MarkGlyphSetsDef coverage offsets are relative to that table, not to the file. Seeking to them as
* absolute offsets lands in the table directory and yields empty sets, which silently disables every
* UseMarkFilteringSet lookup. U+0DCA/U+0DD2/U+0DD3 are the subset's Sinhala marks; the second set's
* glyph has no cmap entry, so it is mapped into the Private Use Area.
*/
public function testGetMetricsReadsMarkGlyphSetsCoverage()
{
$this->ttf->getMetrics(__DIR__ . '/../data/ttf/NotoSansSinhala-Subset.ttf', (string) time(), 0, false, false, 0xFF);

$this->assertSame([' 00DCA| 00DD2| 00DD3', ' 0E00A'], $this->ttf->MarkGlyphSets);
}

}
51 changes: 51 additions & 0 deletions tests/Snapshots/MarkGlyphSetsSnapshotTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace Snapshots;

/**
* Renders the sequences that exercise GSUB lookups flagged UseMarkFilteringSet.
*
* The subset's mark filtering set is {U+0DCA, U+0DD2, U+0DD3}, and two ligature lookups are filtered by it:
* U+0DBB with each of those three, and U+0DCF + U+0DCA. Getting the filtering wrong in either direction
* silently drops those ligatures back to base + floating mark, which no other test would notice.
*
* @group snapshot
*/
class MarkGlyphSetsSnapshotTest extends Snapshot
{
/**
* @return string A unique identifier / name for the snapshot
*/
public function getId()
{
return 'markglyphsets';
}

/**
* Generate a PDF document by initializing the Mpdf object on $this->mpdf and
* loading it with content
*
* @return void
* @internal Don't call any $this->mpdf->Output*() method
*/
public function generatePdf()
{
$this->mpdf = new \Mpdf\Mpdf([
'fontDir' => [__DIR__ . '/../data/ttf'],
'fontdata' => [
'sinhalasubset' => [
'R' => 'NotoSansSinhala-Subset.ttf',
'useOTL' => 0xFF,
],
],
'default_font' => 'sinhalasubset',
'default_font_size' => 30,
]);

$this->mpdf->WriteHTML(
'<div>&#x0DBB;&#x0DD2; &#x0DBB;&#x0DD3; &#x0DBB;&#x0DCA; &#x0DCF;&#x0DCA;</div>'
. '<div>&#x0DC1;&#x0DCA;&#x200D;&#x0DBB;&#x0DD3; &#x0DBD;&#x0D82;&#x0D9A;&#x0DCF;</div>'
. '<div>&#x0DC3;&#x0DD2;&#x0D82;&#x0DC4;&#x0DBD; &#x0D85;&#x0D9A;&#x0DD4;&#x0DBB;&#x0DD4;</div>'
);
}
}
Binary file added tests/data/snapshots/markglyphsets.pdf
Binary file not shown.
Loading