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
24 changes: 14 additions & 10 deletions src/Mpdf.php
Original file line number Diff line number Diff line change
Expand Up @@ -25776,7 +25776,7 @@ function SubstituteCharsNonCore(&$writehtml_a, &$writehtml_i, &$writehtml_e)

foreach ($unicode as $c => $char) {

if (($char > 127 || ($flag == 1 && $char == 32)) && $char != 173 && (!isset($this->subArrMB['a'][$char]) || ($flag == 1 && $char == 32)) && ($char < 1536 || ($char > 1791 && $char < 2304) || $char > 3455)) {
if ($char > 127 && $char != 173 && !isset($this->subArrMB['a'][$char]) && ($char < 1536 || ($char > 1791 && $char < 2304) || $char > 3455)) {
if ($flag == 0) {
$start = $c;
}
Expand All @@ -25803,7 +25803,8 @@ function SubstituteCharsNonCore(&$writehtml_a, &$writehtml_i, &$writehtml_e)

foreach ($this->backupSubsFont as $bsfctr => $bsf) {

if ($this->fonttrans[$bsf] == 'chelvetica' || $this->fonttrans[$bsf] == 'ctimes' || $this->fonttrans[$bsf] == 'ccourier') {
// A backup font that is a core font's name draws nothing a core font does not
if (isset($this->fonttrans[$bsf]) && in_array($this->fonttrans[$bsf], ['chelvetica', 'ctimes', 'ccourier'], true)) {
continue;
}

Expand Down Expand Up @@ -25880,25 +25881,30 @@ function SubstituteCharsMB(&$writehtml_a, &$writehtml_i, &$writehtml_e)
$ftype = '';
$u = [];
// Every emoji is outside ASCII, a keycap's U+20E3 included
$emojiClusters = preg_match('/[^\x00-\x7F]/', $writehtml_e) ? Emoji::clusters($unicode) : [];
$mayHaveEmoji = preg_match('/[^\x00-\x7F]/', $writehtml_e) === 1;
// Each emoji up to where the run ends, by where it starts: found as the scan reaches it, since the
// rest of the text is scanned again on the next pass
$emojiClusters = [];
$codepointCount = count($unicode);

// Not foreach, so the loop can jump past the rest of a multi-codepoint sequence once it has been handled
for ($c = 0; $c < $codepointCount; $c++) {
$char = $unicode[$c];
$cluster = $mayHaveEmoji ? Emoji::clusterAt($unicode, $c, $codepointCount) : null;

// An emoji moves whole or not at all, so its joiners and selectors go with it to the font
// whose GSUB joins them
if (isset($emojiClusters[$c])) {
if ($cluster !== null) {
// An emoji ends a SIP run; it is handled on the next pass
if ($flag == 2) {
break;
}

$length = $emojiClusters[$c][0];
$emojiClusters[$c] = $cluster;
$length = $cluster[0];
$emoji = array_slice($unicode, $c, $length);

if ($this->fontSubstitution->emojiWantsAnotherFont($emoji, $emojiClusters[$c][1])) {
if ($this->fontSubstitution->emojiWantsAnotherFont($emoji, $cluster[1])) {
if ($flag == 0) {
$start = $c;
}
Expand All @@ -25912,7 +25918,7 @@ function SubstituteCharsMB(&$writehtml_a, &$writehtml_i, &$writehtml_e)
continue;
}

if (($flag == 0 || $flag == 2) && (!$this->_charDefined($cw, $char) || ($flag == 2 && $char == 32)) && $this->checkSIP && $char > 131071) { // Unicode Plane 2 (SIP)
if (($flag == 0 || $flag == 2) && !$this->_charDefined($cw, $char) && $this->checkSIP && $char > 131071) { // Unicode Plane 2 (SIP)

if (in_array($this->FontFamily, $this->available_CJK_fonts)) {
return 0;
Expand All @@ -25925,9 +25931,7 @@ function SubstituteCharsMB(&$writehtml_a, &$writehtml_i, &$writehtml_e)
$flag = 2;
$u[] = $char;

// elseif (($flag == 0 || $flag==1) && $char != 173 && !$this->_charDefined($cw,$char) && ($char<1423 || ($char>3583 && $char < 11263))) {

} elseif (($flag == 0 || $flag == 1) && $char != 173 && (!$this->_charDefined($cw, $char) || ($flag == 1 && $char == 32)) && ($char < 1536 || ($char > 1791 && $char < 2304) || $char > 3455)) {
} elseif (($flag == 0 || $flag == 1) && $char != 173 && !$this->_charDefined($cw, $char) && ($char < 1536 || ($char > 1791 && $char < 2304) || $char > 3455)) {

if ($flag == 0) {
$start = $c;
Expand Down
7 changes: 5 additions & 2 deletions src/Unicode/Emoji.php
Original file line number Diff line number Diff line change
Expand Up @@ -253,13 +253,16 @@ public static function isTag($codepoint)
}

/**
* The emoji at one place in a run, for a caller walking the run itself that would otherwise find
* every emoji in it with clusters() before it knows how far it will look
*
* @param int[] $codepoints The run
* @param int $i Where in it to look
* @param int $i Where in it to look, which is not inside an emoji that starts earlier
* @param int $count How long it is
*
* @return array|null [length, presentation] of the emoji starting at $i, or null where none does
*/
private static function clusterAt(array $codepoints, $i, $count)
public static function clusterAt(array $codepoints, $i, $count)
{
$first = $codepoints[$i];

Expand Down
31 changes: 29 additions & 2 deletions tests/Mpdf/EmojiSubstitutionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,37 @@ public function testAKeycapGoesToTheFontThatHasAllOfIt()
*/
public function testAFlagAndASkinToneStayWithTheEmojiTheyQualify()
{
$pieces = $this->drawn([0x1F1E6, 0x1F1FA, 0x20, 0x1F44D, 0x1F3FD], ['coloremoji']);
$pieces = $this->drawn([0x1F1E6, 0x1F1FA, 0x1F44D, 0x1F3FD], ['coloremoji']);

$this->assertCount(1, $pieces);
$this->assertSame('coloremoji', $pieces[0][0]);
$this->assertCount(2, $pieces[0][1], 'the flag and the thumb');
}

/**
* A space between two emoji is the document font's, which has one, and not carried into the run
* of the font the emoji go to: an emoji font's space can be as wide as an emoji, as Noto Color
* Emoji's is. The recorder leaves out a piece that is only a space, so the space shows as the two
* runs either side of it.
*/
public function testASpaceBetweenEmojiStaysInTheDocumentFont()
{
$pieces = $this->drawn([0x1F1E6, 0x1F1FA, 0x20, 0x1F44D], ['coloremoji']);

$this->assertCount(2, $pieces, 'a run either side of the space');
$this->assertSame('coloremoji', $pieces[0][0]);
$this->assertCount(3, $pieces[0][1], 'the flag, the space the run carries, and the thumb');
$this->assertCount(1, $pieces[0][1], 'the flag, without the space');
$this->assertSame(['coloremoji', [0x1F44D]], $pieces[1]);
}

/**
* A space in a core font's text is the core font's too, and ends the run a backup font draws
*/
public function testASpaceBetweenWordsACoreFontLacksStaysInTheCoreFont()
{
$pieces = $this->drawn([0x61, 0x416, 0x416, 0x20, 0x416, 0x62], ['dejavusans'], ['default_font' => 'chelvetica']);

$this->assertSame([['chelvetica', [0x61]], ['dejavusans', [0x416, 0x416]], ['dejavusans', [0x416]], ['chelvetica', [0x62]]], $pieces);
}

/**
Expand Down
Binary file modified tests/data/snapshots/color-emoji-cbdt.pdf
Binary file not shown.
Binary file modified tests/data/snapshots/color-emoji-colrv0.pdf
Binary file not shown.
Binary file modified tests/data/snapshots/color-emoji-sbix.pdf
Binary file not shown.
Loading