Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 170 |
|
0.00% |
0 / 16 |
CRAP | |
0.00% |
0 / 1 |
WikiHiero | |
0.00% |
0 / 170 |
|
0.00% |
0 / 16 |
3540 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
loadData | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
parserHook | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getScale | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setScale | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
renderGlyph | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
42 | |||
renderGlyphImage | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
30 | |||
renderVoidBlock | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
getImageUrl | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
isMirrored | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
extractCode | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
resizeGlyph | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
56 | |||
render | |
0.00% |
0 / 94 |
|
0.00% |
0 / 1 |
756 | |||
getFiles | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getImagePath | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getCode | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * WikiHiero - A PHP convert from text using "Manual for the encoding of |
4 | * hieroglyphic texts for computer input" syntax to HTML entities (table and |
5 | * images). |
6 | * |
7 | * Copyright (C) 2004 Guillaume Blanchard (Aoineko) |
8 | * |
9 | * This program is free software; you can redistribute it and/or modify |
10 | * it under the terms of the GNU General Public License as published by |
11 | * the Free Software Foundation; either version 2 of the License, or |
12 | * (at your option) any later version. |
13 | * |
14 | * This program is distributed in the hope that it will be useful, |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | * GNU General Public License for more details. |
18 | * |
19 | * You should have received a copy of the GNU General Public License along |
20 | * with this program; if not, write to the Free Software Foundation, Inc., |
21 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
22 | * http://www.gnu.org/copyleft/gpl.html |
23 | */ |
24 | |
25 | namespace WikiHiero; |
26 | |
27 | use MediaWiki\Config\Config; |
28 | use MediaWiki\Context\RequestContext; |
29 | use MediaWiki\Html\Html; |
30 | use MediaWiki\Output\OutputPage; |
31 | use MediaWiki\Parser\Parser; |
32 | |
33 | class WikiHiero { |
34 | public const IMAGE_EXT = 'png'; |
35 | private const IMAGE_PREFIX = 'hiero_'; |
36 | |
37 | /** Use default scale */ |
38 | private const DEFAULT_SCALE = -1; |
39 | private const CARTOUCHE_WIDTH = 2; |
40 | private const IMAGE_MARGIN = 1; |
41 | private const MAX_HEIGHT = 44; |
42 | |
43 | private const TABLE_START = '<table class="mw-hiero-table">'; |
44 | |
45 | private $scale = 100; |
46 | private $config; |
47 | |
48 | /** @var string[] */ |
49 | private static $phonemes; |
50 | /** @var string[] */ |
51 | private static $prefabs; |
52 | /** @var int[][] */ |
53 | private static $files; |
54 | |
55 | /** |
56 | * @param Config|null $config |
57 | */ |
58 | public function __construct( Config $config = null ) { |
59 | $this->config = $config ?: RequestContext::getMain()->getConfig(); |
60 | self::loadData(); |
61 | } |
62 | |
63 | /** |
64 | * Loads hieroglyph information |
65 | * @suppress PhanUndeclaredVariable,PhanTypeMismatchPropertyProbablyReal Phan doesn't understand require_once |
66 | */ |
67 | private static function loadData() { |
68 | if ( self::$phonemes ) { |
69 | return; |
70 | } |
71 | require_once dirname( __DIR__ ) . '/data/tables.php'; |
72 | self::$phonemes = $wh_phonemes; |
73 | self::$prefabs = $wh_prefabs; |
74 | self::$files = $wh_files; |
75 | } |
76 | |
77 | /** |
78 | * Parser callback for <hiero> tag |
79 | * @param string $input |
80 | * @param array $args |
81 | * @param Parser $parser |
82 | * @return string |
83 | */ |
84 | public static function parserHook( $input, $args, $parser ) { |
85 | $hiero = new WikiHiero(); |
86 | $parser->getOutput()->addModuleStyles( [ 'ext.wikihiero' ] ); |
87 | $parser->addTrackingCategory( 'wikihiero-usage-tracking-category' ); |
88 | // Strip newlines to avoid breakage in the wiki parser block pass |
89 | return str_replace( "\n", " ", $hiero->render( $input ) ); |
90 | } |
91 | |
92 | /** |
93 | * @return int Scale of overall hieroglyphic block in percents |
94 | */ |
95 | public function getScale() { |
96 | return $this->scale; |
97 | } |
98 | |
99 | /** |
100 | * Sets the scale of overall hieroglyphic block in percents |
101 | * |
102 | * @param int $scale |
103 | */ |
104 | public function setScale( $scale ) { |
105 | $this->scale = $scale; |
106 | } |
107 | |
108 | /** |
109 | * Renders a glyph |
110 | * |
111 | * @param string $glyph glyph's code to render |
112 | * @param int|null $height glyph size in pixels or null to omit |
113 | * @return string a string to add to the stream |
114 | */ |
115 | private function renderGlyph( $glyph, $height = null ) { |
116 | // Support skins with night theme. |
117 | $imageClass = 'skin-invert'; |
118 | if ( $this->isMirrored( $glyph ) ) { |
119 | $imageClass .= ' mw-mirrored'; |
120 | } |
121 | $glyph = $this->extractCode( $glyph ); |
122 | |
123 | if ( $glyph == '..' ) { |
124 | // Render void block |
125 | return $this->renderVoidBlock( self::MAX_HEIGHT ); |
126 | } |
127 | if ( $glyph == '.' ) { |
128 | // Render half-width void block |
129 | return $this->renderVoidBlock( self::MAX_HEIGHT / 2 ); |
130 | } |
131 | |
132 | if ( $glyph == '<' || $glyph == '>' ) { |
133 | // Render cartouches |
134 | return $this->renderGlyphImage( $glyph, self::MAX_HEIGHT, null, $imageClass ); |
135 | } |
136 | |
137 | return $this->renderGlyphImage( $glyph, $height, self::IMAGE_MARGIN, $imageClass ); |
138 | } |
139 | |
140 | /** |
141 | * Renders a glyph into an <img> tag |
142 | * |
143 | * @param string $glyph Glyph to render |
144 | * @param int|null $height Image height, if null don't set explicitly |
145 | * @param int|null $margin Margin, if null don't set |
146 | * @param string|null $class Class for <img> tag |
147 | * @return string Rendered HTML |
148 | */ |
149 | private function renderGlyphImage( $glyph, $height = null, $margin = null, $class = null ) { |
150 | if ( array_key_exists( $glyph, self::$phonemes ) ) { |
151 | $code = self::$phonemes[$glyph]; |
152 | $fileName = $code; |
153 | // Don't show image name for cartouches and such |
154 | $title = preg_match( '/^[A-Za-z0-9]+$/', $glyph ) ? "{$code} [{$glyph}]" : $glyph; |
155 | } else { |
156 | $fileName = $title = $glyph; |
157 | } |
158 | if ( !array_key_exists( $fileName, self::$files ) ) { |
159 | return htmlspecialchars( $glyph ); |
160 | } |
161 | |
162 | $style = $margin === null ? null : "margin: {$margin}px;"; |
163 | $attribs = [ |
164 | 'class' => $class, |
165 | 'style' => $style, |
166 | 'src' => $this->getImageUrl( $fileName ), |
167 | 'height' => $height, |
168 | 'title' => $title, |
169 | 'alt' => $glyph, |
170 | ]; |
171 | return Html::element( 'img', $attribs ); |
172 | } |
173 | |
174 | /** |
175 | * Returns HTML for a void block |
176 | * @param int $width |
177 | * @return string |
178 | */ |
179 | private function renderVoidBlock( $width ) { |
180 | $width = intval( $width ); |
181 | return Html::rawElement( |
182 | 'table', |
183 | [ |
184 | 'class' => 'mw-hiero-table', |
185 | 'style' => "width: {$width}px;", |
186 | ], |
187 | '<tr><td> </td></tr>' |
188 | ); |
189 | } |
190 | |
191 | private function getImageUrl( $fileName ) { |
192 | $url = self::getImagePath() . self::IMAGE_PREFIX . $fileName . '.' . self::IMAGE_EXT; |
193 | return OutputPage::transformResourcePath( $this->config, $url ); |
194 | } |
195 | |
196 | private function isMirrored( $glyph ) { |
197 | return substr( $glyph, -1 ) == '\\'; |
198 | } |
199 | |
200 | /** |
201 | * Extracts hieroglyph code from glyph, e.g. A1\ --> A1 |
202 | * |
203 | * @param string $glyph |
204 | * @return string |
205 | */ |
206 | private function extractCode( $glyph ) { |
207 | return preg_replace( '/\\\\.*$/', '', $glyph ); |
208 | } |
209 | |
210 | /** |
211 | * Resize a glyph |
212 | * |
213 | * @param string $item glyph code |
214 | * @param bool $is_cartouche true if glyph is inside a cartouche |
215 | * @param int $total total size of a group for multi-glyph block |
216 | * @return int size |
217 | */ |
218 | private function resizeGlyph( $item, $is_cartouche = false, $total = 0 ) { |
219 | $item = $this->extractCode( $item ); |
220 | if ( array_key_exists( $item, self::$phonemes ) ) { |
221 | $glyph = self::$phonemes[$item]; |
222 | } else { |
223 | $glyph = $item; |
224 | } |
225 | |
226 | $margin = 2 * self::IMAGE_MARGIN; |
227 | if ( $is_cartouche ) { |
228 | $margin += 2 * self::CARTOUCHE_WIDTH; |
229 | } |
230 | |
231 | if ( array_key_exists( $glyph, self::$files ) ) { |
232 | $height = $margin + self::$files[$glyph][1]; |
233 | if ( $total ) { |
234 | if ( $total > self::MAX_HEIGHT ) { |
235 | return intval( $height * self::MAX_HEIGHT / $total ) - $margin; |
236 | } else { |
237 | return $height - $margin; |
238 | } |
239 | } else { |
240 | if ( $height > self::MAX_HEIGHT ) { |
241 | return intval( self::MAX_HEIGHT * self::MAX_HEIGHT / $height ) - $margin; |
242 | } else { |
243 | return $height - $margin; |
244 | } |
245 | } |
246 | } |
247 | |
248 | return self::MAX_HEIGHT - $margin; |
249 | } |
250 | |
251 | /** |
252 | * Render hieroglyph text |
253 | * |
254 | * @param string $hiero text to convert |
255 | * @param int $scale global scale in percentage (default = 100%) |
256 | * @param bool $line use line (default = false) |
257 | * @return string converted code |
258 | */ |
259 | public function render( $hiero, $scale = self::DEFAULT_SCALE, $line = false ) { |
260 | if ( $scale != self::DEFAULT_SCALE ) { |
261 | $this->setScale( $scale ); |
262 | } |
263 | |
264 | $html = ""; |
265 | |
266 | if ( $line ) { |
267 | $html .= "<hr />\n"; |
268 | } |
269 | |
270 | $tokenizer = new HieroTokenizer( $hiero ); |
271 | $blocks = $tokenizer->tokenize(); |
272 | $contentHtml = $tableHtml = $tableContentHtml = ""; |
273 | $is_cartouche = false; |
274 | |
275 | // ------------------------------------------------------------------------ |
276 | // Loop into all blocks |
277 | foreach ( $blocks as $code ) { |
278 | // simplest case, the block contain only 1 code -> render |
279 | if ( count( $code ) == 1 ) { |
280 | if ( $code[0] == '!' ) { |
281 | // end of line |
282 | $tableHtml = '</tr></table>' . self::TABLE_START . "<tr>\n"; |
283 | if ( $line ) { |
284 | $contentHtml .= "<hr />\n"; |
285 | } |
286 | |
287 | } elseif ( strstr( $code[0], '<' ) ) { |
288 | // start cartouche |
289 | $contentHtml .= '<td>' . $this->renderGlyph( $code[0] ) . '</td>'; |
290 | $is_cartouche = true; |
291 | $contentHtml .= '<td>' . |
292 | self::TABLE_START . "<tr><td class=\"mw-hiero-box\" style=\"height: " . |
293 | self::CARTOUCHE_WIDTH . "px;\"></td></tr><tr><td>" . self::TABLE_START . |
294 | "<tr>"; |
295 | |
296 | } elseif ( strstr( $code[0], '>' ) ) { |
297 | // end cartouche |
298 | $contentHtml .= "</tr></table></td></tr><tr><td class=\"mw-hiero-box\" " . |
299 | "style=\"height: " . self::CARTOUCHE_WIDTH . |
300 | 'px;"></td></tr></table></td>'; |
301 | $is_cartouche = false; |
302 | $contentHtml .= '<td>' . $this->renderGlyph( $code[0] ) . '</td>'; |
303 | |
304 | } elseif ( $code[0] != "" ) { |
305 | // assume it's a glyph or '..' or '.' |
306 | $contentHtml .= '<td>' . $this->renderGlyph( |
307 | $code[0], |
308 | $this->resizeGlyph( $code[0], $is_cartouche ) |
309 | ) . '</td>'; |
310 | } |
311 | |
312 | // block contains more than 1 glyph |
313 | } else { |
314 | // convert all codes into '&' to test prefabs glyph |
315 | $prefabs = ""; |
316 | foreach ( $code as $t ) { |
317 | if ( preg_match( "/[*:!()]/", $t[0] ) ) { |
318 | $prefabs .= "&"; |
319 | } else { |
320 | $prefabs .= $t; |
321 | } |
322 | } |
323 | |
324 | // test if block exists in the prefabs list |
325 | if ( in_array( $prefabs, self::$prefabs, true ) ) { |
326 | $contentHtml .= '<td>' . $this->renderGlyph( |
327 | $prefabs, |
328 | $this->resizeGlyph( $prefabs, $is_cartouche ) |
329 | ) . '</td>'; |
330 | |
331 | // block must be manually computed |
332 | } else { |
333 | // get block total height |
334 | $line_max = 0; |
335 | $total = 0; |
336 | $height = 0; |
337 | |
338 | foreach ( $code as $t ) { |
339 | if ( $t == ":" ) { |
340 | if ( $height > $line_max ) { |
341 | $line_max = $height; |
342 | } |
343 | $total += $line_max; |
344 | $line_max = 0; |
345 | |
346 | } elseif ( $t == "*" ) { |
347 | if ( $height > $line_max ) { |
348 | $line_max = $height; |
349 | } |
350 | } else { |
351 | if ( array_key_exists( $t, self::$phonemes ) ) { |
352 | $glyph = self::$phonemes[$t]; |
353 | } else { |
354 | $glyph = $t; |
355 | } |
356 | if ( array_key_exists( $glyph, self::$files ) ) { |
357 | $height = 2 + self::$files[$glyph][1]; |
358 | } |
359 | } |
360 | } |
361 | |
362 | if ( $height > $line_max ) { |
363 | $line_max = $height; |
364 | } |
365 | |
366 | $total += $line_max; |
367 | |
368 | // render all glyph into the block |
369 | $block = ""; |
370 | foreach ( $code as $t ) { |
371 | if ( $t == ":" ) { |
372 | $block .= "<br />"; |
373 | |
374 | } elseif ( $t == "*" ) { |
375 | $block .= " "; |
376 | |
377 | } else { |
378 | // resize the glyph according to the block total height |
379 | $block .= $this->renderGlyph( |
380 | $t, |
381 | $this->resizeGlyph( $t, $is_cartouche, $total ) |
382 | ); |
383 | } |
384 | } |
385 | |
386 | $contentHtml .= '<td>' . $block . '</td>'; |
387 | } |
388 | $contentHtml .= "\n"; |
389 | } |
390 | |
391 | if ( strlen( $contentHtml ) > 0 ) { |
392 | $tableContentHtml .= $tableHtml . $contentHtml; |
393 | $contentHtml = $tableHtml = ""; |
394 | } |
395 | } |
396 | |
397 | if ( strlen( $tableContentHtml ) > 0 ) { |
398 | $html .= self::TABLE_START . "<tr>\n" . $tableContentHtml . '</tr></table>'; |
399 | } |
400 | |
401 | $style = null; |
402 | if ( $this->scale != 100 ) { |
403 | $ratio = floatval( $this->scale ) / 100; |
404 | $style = "transform: scale($ratio,$ratio);"; |
405 | } |
406 | |
407 | return Html::rawElement( |
408 | 'table', |
409 | [ |
410 | 'class' => 'mw-hiero-table mw-hiero-outer', |
411 | 'dir' => 'ltr', |
412 | 'style' => $style, |
413 | ], |
414 | "<tr><td>\n$html\n</td></tr>" |
415 | ); |
416 | } |
417 | |
418 | /** |
419 | * Returns a list of image files used by this extension |
420 | * |
421 | * @return array list of files in format 'file' => [ width, height ] |
422 | */ |
423 | public function getFiles() { |
424 | return self::$files; |
425 | } |
426 | |
427 | /** |
428 | * @return string URL of images directory |
429 | */ |
430 | public static function getImagePath() { |
431 | global $wgExtensionAssetsPath; |
432 | return "$wgExtensionAssetsPath/wikihiero/img/"; |
433 | } |
434 | |
435 | /** |
436 | * Get glyph code from file name |
437 | * |
438 | * @param string $file file name |
439 | * @return string converted code |
440 | */ |
441 | public static function getCode( $file ) { |
442 | return substr( $file, strlen( self::IMAGE_PREFIX ), -( 1 + strlen( self::IMAGE_EXT ) ) ); |
443 | } |
444 | } |