• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dg / texy / 21345344909

26 Jan 2026 03:32AM UTC coverage: 92.382% (-0.4%) from 92.744%
21345344909

push

github

dg
HtmlElement: removed toHtml() & toText()

18 of 19 new or added lines in 5 files covered. (94.74%)

149 existing lines in 21 files now uncovered.

2401 of 2599 relevant lines covered (92.38%)

0.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.43
/src/Texy/Modules/LongWordsModule.php
1
<?php
2

3
/**
4
 * This file is part of the Texy! (https://texy.nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
declare(strict_types=1);
9

10
namespace Texy\Modules;
11

12
use Texy;
13
use function array_flip, array_pop, array_splice, count, end, iconv_strlen, implode, ord, preg_match_all;
14

15

16
/**
17
 * Breaks long words with soft hyphens for better line wrapping.
18
 */
19
final class LongWordsModule extends Texy\Module
20
{
21
        private const
22
                Dont = 0, // don't hyphenate
23
                Here = 1, // hyphenate here
24
                After = 2; // hyphenate after
25

26
        private const SafeLimit = 1000;
27

28
        public int $wordLimit = 20;
29

30
        /** @var array<string, int>|string[] */
31
        private array $consonants = [
32
                'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'z',
33
                'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Z',
34
                "\u{10D}", "\u{10F}", "\u{148}", "\u{159}", "\u{161}", "\u{165}", "\u{17E}", // Czech UTF-8
35
                "\u{10C}", "\u{10E}", "\u{147}", "\u{158}", "\u{160}", "\u{164}", "\u{17D}",
36
        ];
37

38
        /** @var array<string, int>|string[] */
39
        private array $vowels = [
40
                'a', 'e', 'i', 'o', 'u', 'y',
41
                'A', 'E', 'I', 'O', 'U', 'Y',
42
                "\u{E1}", "\u{E9}", "\u{11B}", "\u{ED}", "\u{F3}", "\u{FA}", "\u{16F}", "\u{FD}", // Czech UTF-8
43
                "\u{C1}", "\u{C9}", "\u{11A}", "\u{CD}", "\u{D3}", "\u{DA}", "\u{16E}", "\u{DD}",
44
        ];
45

46
        /** @var array<string, int>|string[] */
47
        private array $before_r = [
48
                'b', 'B', 'c', 'C', 'd', 'D', 'f', 'F', 'g', 'G', 'k', 'K', 'p', 'P', 'r', 'R', 't', 'T', 'v', 'V',
49
                "\u{10D}", "\u{10C}", "\u{10F}", "\u{10E}", "\u{159}", "\u{158}", "\u{165}", "\u{164}", // Czech UTF-8
50
        ];
51

52
        /** @var array<string, int>|string[] */
53
        private array $before_l = [
54
                'b', 'B', 'c', 'C', 'd', 'D', 'f', 'F', 'g', 'G', 'k', 'K', 'l', 'L', 'p', 'P', 't', 'T', 'v', 'V',
55
                "\u{10D}", "\u{10C}", "\u{10F}", "\u{10E}", "\u{165}", "\u{164}", // Czech UTF-8
56
        ];
57

58
        /** @var array<string, int>|string[] */
59
        private array $before_h = ['c', 'C', 's', 'S'];
60

61
        /** @var array<string, int>|string[] */
62
        private array $doubleVowels = ['a', 'A', 'o', 'O'];
63

64

65
        public function __construct(Texy\Texy $texy)
1✔
66
        {
67
                $this->texy = $texy;
1✔
68

69
                $this->consonants = array_flip($this->consonants);
1✔
70
                $this->vowels = array_flip($this->vowels);
1✔
71
                $this->before_r = array_flip($this->before_r);
1✔
72
                $this->before_l = array_flip($this->before_l);
1✔
73
                $this->before_h = array_flip($this->before_h);
1✔
74
                $this->doubleVowels = array_flip($this->doubleVowels);
1✔
75

76
                $texy->registerPostLine($this->postLine(...), 'longwords');
1✔
77
        }
1✔
78

79

80
        public function postLine(string $text): string
1✔
81
        {
82
                return Texy\Regexp::replace(
1✔
83
                        $text,
1✔
84
                        '~[^ \n\t\x14\x15\x16\x{2013}\x{2014}\x{ad}-]{' . $this->wordLimit . ',}~',
1✔
85
                        $this->parse(...),
1✔
86
                );
87
        }
88

89

90
        /**
91
         * Callback for long words.
92
         * @param  string[]  $matches
93
         */
94
        private function parse(array $matches): string
1✔
95
        {
96
                [$mWord] = $matches;
1✔
97
                // [0] => lllloooonnnnggggwwwoorrdddd
98

99
                if (iconv_strlen($mWord, 'UTF-8') > self::SafeLimit) {
1✔
100
                        return $mWord;
1✔
101
                }
102

103
                $chars = Texy\Regexp::matchAll(
1✔
104
                        $mWord,
1✔
105
                        '~[' . Texy\Patterns::MARK . ']+|.~',
1✔
106
                );
107

108
                $chars = array_column($chars, 0);
1✔
109
                if (count($chars) < $this->wordLimit) {
1✔
110
                        return $mWord;
1✔
111
                }
112

113
                $s = [''];
1✔
114
                $trans = [-1];
1✔
115
                foreach ($chars as $key => $char) {
1✔
116
                        if (ord($char[0]) < 32) {
1✔
117
                                continue;
1✔
118
                        }
119

120
                        $s[] = $char;
1✔
121
                        $trans[] = $key;
1✔
122
                }
123

124
                $s[] = '';
1✔
125
                $len = count($s) - 2;
1✔
126

127
                $positions = [];
1✔
128
                $a = 0;
1✔
129
                $last = 1;
1✔
130

131
                while (++$a < $len) {
1✔
132
                        if ($s[$a] === "\u{A0}") {
1✔
UNCOV
133
                                $a++;
×
UNCOV
134
                                continue;  // here and after never
×
135
                        }
136

137
                        $hyphen = $this->getHyphen($s[$a], $s[$a - 1], $s[$a + 1]);
1✔
138

139
                        if ($hyphen === self::Dont && ($a - $last > $this->wordLimit * 0.6)) {
1✔
140
                                $positions[] = $last = $a - 1; // Hyphenate here
1✔
141
                        }
142

143
                        if ($hyphen === self::Here) {
1✔
144
                                $positions[] = $last = $a - 1; // Hyphenate here
1✔
145
                        }
146

147
                        if ($hyphen === self::After) {
1✔
148
                                $positions[] = $last = $a;
1✔
149
                                $a++; // Hyphenate after
1✔
150
                        }
151
                }
152

153
                $a = end($positions);
1✔
154
                if (($a === $len - 1) && isset($this->consonants[$s[$len]])) {
1✔
155
                        array_pop($positions);
1✔
156
                }
157

158
                $syllables = [];
1✔
159
                $last = 0;
1✔
160
                foreach ($positions as $pos) {
1✔
161
                        if ($pos - $last > $this->wordLimit * 0.6) {
1✔
162
                                $syllables[] = implode('', array_splice($chars, 0, $trans[$pos] - $trans[$last]));
1✔
163
                                $last = $pos;
1✔
164
                        }
165
                }
166

167
                $syllables[] = implode('', $chars);
1✔
168
                return implode("\u{AD}", $syllables);
1✔
169
        }
170

171

172
        private function getHyphen(string $ch, string $prev, string $next): int
1✔
173
        {
174
                if ($ch === '.') {
1✔
175
                        return self::Here;
1✔
176

177
                } elseif (isset($this->consonants[$ch])) { // consonants
1✔
178
                        if (isset($this->vowels[$next])) {
1✔
179
                                return isset($this->vowels[$prev]) ? self::Here : self::Dont;
1✔
180

181
                        } elseif (($ch === 's') && ($prev === 'n') && isset($this->consonants[$next])) {
1✔
UNCOV
182
                                return self::After;
×
183

184
                        } elseif (isset($this->consonants[$next], $this->vowels[$prev])) {
1✔
185
                                if ($next === 'r') {
1✔
186
                                        return isset($this->before_r[$ch]) ? self::Here : self::After;
1✔
187

188
                                } elseif ($next === 'l') {
1✔
189
                                        return isset($this->before_l[$ch]) ? self::Here : self::After;
1✔
190

191
                                } elseif ($next === 'h') { // CH
1✔
192
                                        return isset($this->before_h[$ch])
1✔
193
                                                ? self::Dont
1✔
194
                                                : self::After;
1✔
195
                                }
196

197
                                return self::After;
1✔
198
                        }
199

200
                        return self::Dont;
1✔
201

202
                } elseif (($ch === 'u') && isset($this->doubleVowels[$prev])) {
1✔
203
                        return self::After;
1✔
204

205
                } elseif (isset($this->vowels[$ch], $this->vowels[$prev])) {
1✔
206
                        return self::Here;
1✔
207
                }
208

209
                return self::Dont; // Do not hyphenate
1✔
210
        }
211
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc