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
71 changes: 66 additions & 5 deletions src/Fonts/GlyphString.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
/**
* The fixed-width hexadecimal strings the OTL code writes characters as.
*
* Coverage tables, mark and ligature classes and substitution rules are all carried as text and
* compared as text: GlyphClassMarks is one run of them that membership is tested against with
* strpos(), and a substitution of one character by several is their concatenation. Both only work
* while every character takes up the same number of characters, which is what this is for.
* Coverage tables, mark and ligature classes and substitution rules are all carried as text, and a
* substitution of one character by several is their concatenation, which only works while every
* character takes up the same number of characters. That is what this is for.
*
* Five digits is that width. It covers everything up to U+FFFFF; a character above that - plane 16,
* the second private use area - comes back six digits wide and lines up with nothing.
* the second private use area - comes back six digits wide and lines up with nothing. A six-digit
* glyph holds two five-digit ones, U+100300 both U+10030 and U+0300, so a list of glyphs is never
* searched for one with strpos(): set() and inList() test it glyph by glyph.
*/
class GlyphString
{
Expand All @@ -26,4 +27,64 @@ public static function of($codepoint)
return str_pad(strtoupper(dechex($codepoint)), 5, '0', STR_PAD_LEFT);
}

/**
* A list of glyphs as a set, for a list asked about glyph after glyph.
*
* @param string $glyphs "|"-separated, with or without the space GDEF's classes put before each
* glyph: " 00300| 00301"
*
* @return true[] Glyph, as hex => true
*/
public static function set($glyphs)
{
$set = [];
foreach (explode('|', $glyphs) as $glyph) {
$glyph = trim($glyph);
if ($glyph !== '') {
$set[$glyph] = true;
}
}

return $set;
}

/**
* Whether a list of glyphs names this one, for a list asked about too seldom to be worth a set.
*
* The glyph has to stand on its own: the list may separate glyphs with anything but a hex digit,
* so the one test serves GDEF's " 00300| 00301", a Coverage table's "00300|00301", the space-ended
* "0FE8E 0FE94 " of `finals`, and an ignore pattern's "((?:(?: 00300| 00301))*)".
*
* @param string $glyphs
* @param string $glyph As hex
*
* @return bool
*/
public static function inList($glyphs, $glyph)
{
$length = strlen($glyph);

for ($at = strpos($glyphs, $glyph); $at !== false; $at = strpos($glyphs, $glyph, $at + 1)) {
if (!self::isHexDigitAt($glyphs, $at - 1) && !self::isHexDigitAt($glyphs, $at + $length)) {
return true;
}
}

return false;
}

/**
* Whether a match in a list is flanked by another hex digit, which makes it part of a longer glyph
* rather than a glyph of its own.
*
* @param string $string The list being searched
* @param int $at A position either side of a match, which may fall outside the string
*
* @return bool False before the start or past the end, where nothing flanks the match
*/
private static function isHexDigitAt($string, $at)
{
return $at >= 0 && isset($string[$at]) && strpos('0123456789ABCDEFabcdef', $string[$at]) !== false;
}

}
54 changes: 48 additions & 6 deletions src/Fonts/Table/LookupFlag.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Mpdf\Fonts\Table;

use Mpdf\Exception\FontException;
use Mpdf\Fonts\GlyphString;

/**
* The glyphs a lookup passes over, from its LookupFlag, its markFilteringSet and GDEF.
Expand Down Expand Up @@ -48,6 +49,11 @@ class LookupFlag
*/
private $marksOutsideFilteringSets = [];

/**
* @var true[][] Each class skips() has been asked about, as GlyphString::set() gives it
*/
private $sets = [];

/**
* @param string $fontkey
* @param array $gdef
Expand Down Expand Up @@ -132,7 +138,8 @@ public function glyphs($flag, $markFilteringSet)
*
* The same answer as looking for the glyph in glyphs(), a class at a time, without joining the
* classes: the shaper asks once per glyph a subtable is offered, and a font's marks can run to tens
* of kilobytes of text.
* of kilobytes of text. Each class is looked up in a set rather than searched, so a glyph is not
* found inside a longer one's hex.
*
* @param int $flag The lookup's LookupFlag
* @param string $glyph The glyph, as hex
Expand All @@ -145,7 +152,8 @@ public function skips($flag, $glyph, $markFilteringSet)
$this->checkMarkFilteringSet($flag, $markFilteringSet);

foreach (self::skipped($flag) as $class) {
if (strpos($this->glyphsOf($class, $flag, $markFilteringSet), $glyph)) {
$set = $this->setOf($class, $flag, $markFilteringSet);
if (isset($set[$glyph])) {
return true;
}
}
Expand Down Expand Up @@ -183,6 +191,43 @@ private function glyphsOf($class, $flag, $markFilteringSet)
}
}

/**
* @return true[] GDEF's marks, as GlyphString::set() gives them
*/
public function marks()
{
return $this->setOf(self::MARKS, 0, '');
}

/**
* A class skips() tests glyphs against, built once and kept, since it is asked glyph after glyph.
*
* The marks outside a filtering set or an attachment class depend on which set or class the flag
* names, so those are kept per set or class; every other class is the same for any flag.
*
* @param string $class One of the class constants, as skipped() gives it
* @param int $flag The lookup's LookupFlag, which names the attachment class
* @param int|string $markFilteringSet The mark glyph set it names, or '' where it names none
*
* @return true[] The class, as GlyphString::set() gives it
*/
private function setOf($class, $flag, $markFilteringSet)
{
if ($class === self::MARKS_OUTSIDE_FILTERING_SET) {
$key = $class . $markFilteringSet;
} elseif ($class === self::MARKS_OUTSIDE_ATTACHMENT_CLASS) {
$key = $class . self::attachmentClass($flag);
} else {
$key = $class;
}

if (!isset($this->sets[$key])) {
$this->sets[$key] = GlyphString::set($this->glyphsOf($class, $flag, $markFilteringSet));
}

return $this->sets[$key];
}

/**
* UseMarkFilteringSet means "skip every mark except those in the given mark glyph set", so the
* glyphs to skip are the marks minus that set - not the set itself.
Expand All @@ -196,10 +241,7 @@ private function marksOutsideFilteringSet($markFilteringSet)
}

$keep = [];
$inSet = [];
foreach (explode('|', $this->gdef['MarkGlyphSets'][$markFilteringSet]) as $glyph) {
$inSet[trim($glyph)] = true;
}
$inSet = GlyphString::set($this->gdef['MarkGlyphSets'][$markFilteringSet]);

foreach (explode('|', $this->gdef['GlyphClassMarks']) as $glyph) {
$glyph = trim($glyph);
Expand Down
Loading
Loading