lineHeight($scale); foreach (explode("\n", $text) as $n => $row) { $w = $this->width($row, $scale); $cx = $align === 'center' ? $x - $w / 2 : ($align === 'right' ? $x - $w : $x); foreach (self::runs($row) as [$cjk, $run]) { $font = $cjk ? $this->fallback : $this->main; // runs share a vertical middle $dy = ($line - $font->lineHeight($scale)) / 2; $font->draw($r, $run, $cx, $y + $n * $line + $dy, $scale, $color, 'left', $shadow); $cx += $font->width($run, $scale); } } } public function width(string $text, float $scale = 1): int { $max = 0; foreach (explode("\n", $text) as $row) { $w = 0; foreach (self::runs($row) as [$cjk, $run]) { $w += ($cjk ? $this->fallback : $this->main)->width($run, $scale); } $max = max($max, $w); } return $max; } /** The main font's: the fallback only writes a few words in another script (the names of languages). */ public function height(float $scale = 1): int { return $this->main->height($scale); } public function lineHeight(float $scale = 1): int { return $this->main->lineHeight($scale); } public static function isCjk(string $char): bool { return mb_ord($char, 'UTF-8') >= 0x2E80; } /** @return list pieces of text of one font */ private static function runs(string $text): array { $runs = []; foreach (preg_split('//u', $text, -1, PREG_SPLIT_NO_EMPTY) ?: [] as $char) { $cjk = self::isCjk($char); $last = count($runs) - 1; if ($last >= 0 && $runs[$last][0] === $cjk) { $runs[$last][1] .= $char; } else { $runs[] = [$cjk, $char]; } } return $runs; } }