Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
90.00% |
27 / 30 |
|
33.33% |
1 / 3 |
CRAP | |
0.00% |
0 / 1 |
| TextTransform | |
90.00% |
27 / 30 |
|
33.33% |
1 / 3 |
20.40 | |
0.00% |
0 / 1 |
| apply | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
8 | |||
| capitalizeWords | |
91.67% |
11 / 12 |
|
0.00% |
0 / 1 |
5.01 | |||
| toFullWidth | |
80.00% |
8 / 10 |
|
0.00% |
0 / 1 |
7.39 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Phpdftk\Text; |
| 6 | |
| 7 | /** |
| 8 | * CSS Text 3 §2.1 — `text-transform`, applied to a text run before |
| 9 | * shaping. |
| 10 | * |
| 11 | * Shared because both text pipelines need it: HTML inline layout, and |
| 12 | * the SVG painter for `<text>`. They used to be the same algorithm in |
| 13 | * only one of them. |
| 14 | * |
| 15 | * - `uppercase` / `lowercase` are full Unicode case mappings. |
| 16 | * - `capitalize` upper-cases the first grapheme of each |
| 17 | * whitespace-separated word, preserving the original separators. |
| 18 | * - `full-width` (CSS Text 4 §2.1.4) maps ASCII U+0021..U+007E to the |
| 19 | * full-width forms U+FF01..U+FF5E, and ASCII space to the |
| 20 | * ideographic space U+3000. |
| 21 | * - Anything else — `none`, `full-size-kana`, an unknown keyword — |
| 22 | * passes through unchanged. |
| 23 | */ |
| 24 | final class TextTransform |
| 25 | { |
| 26 | public static function apply(string $text, ?string $keyword): string |
| 27 | { |
| 28 | if ($keyword === null || $text === '') { |
| 29 | return $text; |
| 30 | } |
| 31 | return match (strtolower(trim($keyword))) { |
| 32 | 'uppercase' => mb_strtoupper($text, 'UTF-8'), |
| 33 | 'lowercase' => mb_strtolower($text, 'UTF-8'), |
| 34 | 'capitalize' => self::capitalizeWords($text), |
| 35 | 'full-width' => self::toFullWidth($text), |
| 36 | default => $text, |
| 37 | }; |
| 38 | } |
| 39 | |
| 40 | private static function capitalizeWords(string $text): string |
| 41 | { |
| 42 | // Split on whitespace runs, capitalize the first codepoint of each |
| 43 | // non-empty word, and rejoin with the original separators. |
| 44 | $parts = preg_split('/(\s+)/u', $text, -1, PREG_SPLIT_DELIM_CAPTURE); |
| 45 | if ($parts === false) { |
| 46 | return $text; |
| 47 | } |
| 48 | $out = ''; |
| 49 | foreach ($parts as $part) { |
| 50 | if ($part === '' || preg_match('/^\s+$/u', $part) === 1) { |
| 51 | $out .= $part; |
| 52 | continue; |
| 53 | } |
| 54 | $first = mb_substr($part, 0, 1, 'UTF-8'); |
| 55 | $rest = mb_substr($part, 1, null, 'UTF-8'); |
| 56 | $out .= mb_strtoupper($first, 'UTF-8') . $rest; |
| 57 | } |
| 58 | return $out; |
| 59 | } |
| 60 | |
| 61 | private static function toFullWidth(string $text): string |
| 62 | { |
| 63 | $out = ''; |
| 64 | foreach (mb_str_split($text, 1, 'UTF-8') as $ch) { |
| 65 | $cp = mb_ord($ch, 'UTF-8'); |
| 66 | if ($cp === false) { |
| 67 | $out .= $ch; |
| 68 | continue; |
| 69 | } |
| 70 | $out .= match (true) { |
| 71 | $cp === 0x0020 => mb_chr(0x3000, 'UTF-8'), |
| 72 | $cp >= 0x0021 && $cp <= 0x007E => mb_chr($cp + 0xFEE0, 'UTF-8'), |
| 73 | default => $ch, |
| 74 | }; |
| 75 | } |
| 76 | return $out; |
| 77 | } |
| 78 | } |