1d5e02c3aSGreg Roach<?php 2d5e02c3aSGreg Roach 3d5e02c3aSGreg Roach/** 4d5e02c3aSGreg Roach * webtrees: online genealogy 5d5e02c3aSGreg Roach * Copyright (C) 2021 webtrees development team 6d5e02c3aSGreg Roach * This program is free software: you can redistribute it and/or modify 7d5e02c3aSGreg Roach * it under the terms of the GNU General Public License as published by 8d5e02c3aSGreg Roach * the Free Software Foundation, either version 3 of the License, or 9d5e02c3aSGreg Roach * (at your option) any later version. 10d5e02c3aSGreg Roach * This program is distributed in the hope that it will be useful, 11d5e02c3aSGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 12d5e02c3aSGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13d5e02c3aSGreg Roach * GNU General Public License for more details. 14d5e02c3aSGreg Roach * You should have received a copy of the GNU General Public License 15d5e02c3aSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>. 16d5e02c3aSGreg Roach */ 17d5e02c3aSGreg Roach 18d5e02c3aSGreg Roachdeclare(strict_types=1); 19d5e02c3aSGreg Roach 20d5e02c3aSGreg Roachnamespace Fisharebest\Webtrees\Report; 21d5e02c3aSGreg Roach 22d5e02c3aSGreg Roachuse Fisharebest\Webtrees\I18N; 23d5e02c3aSGreg Roach 24d5e02c3aSGreg Roachuse function str_contains; 25d5e02c3aSGreg Roach 26d5e02c3aSGreg Roach/** 27d5e02c3aSGreg Roach * RTL Functions for use in the PDF reports 28d5e02c3aSGreg Roach */ 29d5e02c3aSGreg Roachclass RightToLeftSupport 30d5e02c3aSGreg Roach{ 31d5e02c3aSGreg Roach private const UTF8_LRM = "\xE2\x80\x8E"; // U+200E (Left to Right mark: zero-width character with LTR directionality) 32d5e02c3aSGreg Roach private const UTF8_RLM = "\xE2\x80\x8F"; // U+200F (Right to Left mark: zero-width character with RTL directionality) 33d5e02c3aSGreg Roach private const UTF8_LRO = "\xE2\x80\xAD"; // U+202D (Left to Right override: force everything following to LTR mode) 34d5e02c3aSGreg Roach private const UTF8_RLO = "\xE2\x80\xAE"; // U+202E (Right to Left override: force everything following to RTL mode) 35d5e02c3aSGreg Roach private const UTF8_LRE = "\xE2\x80\xAA"; // U+202A (Left to Right embedding: treat everything following as LTR text) 36d5e02c3aSGreg Roach private const UTF8_RLE = "\xE2\x80\xAB"; // U+202B (Right to Left embedding: treat everything following as RTL text) 37d5e02c3aSGreg Roach private const UTF8_PDF = "\xE2\x80\xAC"; // U+202C (Pop directional formatting: restore state prior to last LRO, RLO, LRE, RLE) 38d5e02c3aSGreg Roach 39d5e02c3aSGreg Roach private const OPEN_PARENTHESES = '([{'; 40d5e02c3aSGreg Roach 41d5e02c3aSGreg Roach private const CLOSE_PARENTHESES = ')]}'; 42d5e02c3aSGreg Roach 43d5e02c3aSGreg Roach private const NUMBERS = '0123456789'; 44d5e02c3aSGreg Roach 45d5e02c3aSGreg Roach private const NUMBER_PREFIX = '+-'; // Treat these like numbers when at beginning or end of numeric strings 46d5e02c3aSGreg Roach 47d5e02c3aSGreg Roach private const NUMBER_PUNCTUATION = '- ,.:/'; // Treat these like numbers when inside numeric strings 48d5e02c3aSGreg Roach 49d5e02c3aSGreg Roach private const PUNCTUATION = ',.:;?!'; 50d5e02c3aSGreg Roach 51d5e02c3aSGreg Roach // Markup 52d5e02c3aSGreg Roach private const START_LTR = '<LTR>'; 53d5e02c3aSGreg Roach private const END_LTR = '</LTR>'; 54d5e02c3aSGreg Roach private const START_RTL = '<RTL>'; 55d5e02c3aSGreg Roach private const END_RTL = '</RTL>'; 56d5e02c3aSGreg Roach private const LENGTH_START = 5; 57d5e02c3aSGreg Roach private const LENGTH_END = 6; 58d5e02c3aSGreg Roach 59*7fa97a69SGreg Roach /* Were we previously processing LTR or RTL. */ 60*7fa97a69SGreg Roach private static string $previousState; 61d5e02c3aSGreg Roach 62*7fa97a69SGreg Roach /* Are we currently processing LTR or RTL. */ 63*7fa97a69SGreg Roach private static string $currentState; 64d5e02c3aSGreg Roach 65*7fa97a69SGreg Roach /* Text waiting to be processed. */ 66*7fa97a69SGreg Roach private static string $waitingText; 67d5e02c3aSGreg Roach 68*7fa97a69SGreg Roach /* Offset into the text. */ 69*7fa97a69SGreg Roach private static int $posSpanStart; 70d5e02c3aSGreg Roach 71d5e02c3aSGreg Roach /** 72d5e02c3aSGreg Roach * This function strips ‎ and ‏ from the input string. It should be used for all 73d5e02c3aSGreg Roach * text that has been passed through the PrintReady() function before that text is stored 74d5e02c3aSGreg Roach * in the database. The database should NEVER contain these characters. 75d5e02c3aSGreg Roach * 76d5e02c3aSGreg Roach * @param string $inputText The string from which the ‎ and ‏ characters should be stripped 77d5e02c3aSGreg Roach * 78d5e02c3aSGreg Roach * @return string The input string, with ‎ and ‏ stripped 79d5e02c3aSGreg Roach */ 80d5e02c3aSGreg Roach private static function stripLrmRlm(string $inputText): string 81d5e02c3aSGreg Roach { 82d5e02c3aSGreg Roach return str_replace([ 83d5e02c3aSGreg Roach self::UTF8_LRM, 84d5e02c3aSGreg Roach self::UTF8_RLM, 85d5e02c3aSGreg Roach self::UTF8_LRO, 86d5e02c3aSGreg Roach self::UTF8_RLO, 87d5e02c3aSGreg Roach self::UTF8_LRE, 88d5e02c3aSGreg Roach self::UTF8_RLE, 89d5e02c3aSGreg Roach self::UTF8_PDF, 90d5e02c3aSGreg Roach '‎', 91d5e02c3aSGreg Roach '‏', 92d5e02c3aSGreg Roach '&LRM;', 93d5e02c3aSGreg Roach '&RLM;', 94d5e02c3aSGreg Roach ], '', $inputText); 95d5e02c3aSGreg Roach } 96d5e02c3aSGreg Roach 97d5e02c3aSGreg Roach /** 98d5e02c3aSGreg Roach * This function encapsulates all texts in the input with <span dir='xxx'> and </span> 99d5e02c3aSGreg Roach * according to the directionality specified. 100d5e02c3aSGreg Roach * 101d5e02c3aSGreg Roach * @param string $inputText Raw input 102d5e02c3aSGreg Roach * 103d5e02c3aSGreg Roach * @return string The string with all texts encapsulated as required 104d5e02c3aSGreg Roach */ 105d5e02c3aSGreg Roach public static function spanLtrRtl(string $inputText): string 106d5e02c3aSGreg Roach { 107d5e02c3aSGreg Roach if ($inputText === '') { 108d5e02c3aSGreg Roach // Nothing to do 109d5e02c3aSGreg Roach return ''; 110d5e02c3aSGreg Roach } 111d5e02c3aSGreg Roach 112d5e02c3aSGreg Roach $workingText = str_replace("\n", '<br>', $inputText); 113d5e02c3aSGreg Roach $workingText = str_replace([ 114d5e02c3aSGreg Roach '<span class="starredname"><br>', 115d5e02c3aSGreg Roach '<span<br>class="starredname">', 116d5e02c3aSGreg Roach ], '<br><span class="starredname">', $workingText); // Reposition some incorrectly placed line breaks 117d5e02c3aSGreg Roach $workingText = self::stripLrmRlm($workingText); // Get rid of any existing UTF8 control codes 118d5e02c3aSGreg Roach 119d5e02c3aSGreg Roach self::$previousState = ''; 120d5e02c3aSGreg Roach self::$currentState = strtoupper(I18N::direction()); 121d5e02c3aSGreg Roach $numberState = false; // Set when we're inside a numeric string 122d5e02c3aSGreg Roach $result = ''; 123d5e02c3aSGreg Roach self::$waitingText = ''; 124d5e02c3aSGreg Roach $openParDirection = []; 125d5e02c3aSGreg Roach 126d5e02c3aSGreg Roach self::beginCurrentSpan($result); 127d5e02c3aSGreg Roach 128d5e02c3aSGreg Roach while ($workingText !== '') { 129d5e02c3aSGreg Roach $charArray = self::getChar($workingText, 0); // Get the next ASCII or UTF-8 character 130d5e02c3aSGreg Roach $currentLetter = $charArray['letter']; 131d5e02c3aSGreg Roach $currentLen = $charArray['length']; 132d5e02c3aSGreg Roach 133d5e02c3aSGreg Roach $openParIndex = strpos(self::OPEN_PARENTHESES, $currentLetter); // Which opening parenthesis is this? 134d5e02c3aSGreg Roach $closeParIndex = strpos(self::CLOSE_PARENTHESES, $currentLetter); // Which closing parenthesis is this? 135d5e02c3aSGreg Roach 136d5e02c3aSGreg Roach switch ($currentLetter) { 137d5e02c3aSGreg Roach case '<': 138d5e02c3aSGreg Roach // Assume this '<' starts an HTML element 139d5e02c3aSGreg Roach $endPos = strpos($workingText, '>'); // look for the terminating '>' 140d5e02c3aSGreg Roach if ($endPos === false) { 141d5e02c3aSGreg Roach $endPos = 0; 142d5e02c3aSGreg Roach } 143d5e02c3aSGreg Roach $currentLen += $endPos; 144d5e02c3aSGreg Roach $element = substr($workingText, 0, $currentLen); 145d5e02c3aSGreg Roach $temp = strtolower(substr($element, 0, 3)); 146d5e02c3aSGreg Roach if (strlen($element) < 7 && $temp === '<br') { 147d5e02c3aSGreg Roach if ($numberState) { 148d5e02c3aSGreg Roach $numberState = false; 149d5e02c3aSGreg Roach if (self::$currentState === 'RTL') { 150d5e02c3aSGreg Roach self::$waitingText .= self::UTF8_PDF; 151d5e02c3aSGreg Roach } 152d5e02c3aSGreg Roach } 153d5e02c3aSGreg Roach self::breakCurrentSpan($result); 154d5e02c3aSGreg Roach } elseif (self::$waitingText === '') { 155d5e02c3aSGreg Roach $result .= $element; 156d5e02c3aSGreg Roach } else { 157d5e02c3aSGreg Roach self::$waitingText .= $element; 158d5e02c3aSGreg Roach } 159d5e02c3aSGreg Roach $workingText = substr($workingText, $currentLen); 160d5e02c3aSGreg Roach break; 161d5e02c3aSGreg Roach case '&': 162d5e02c3aSGreg Roach // Assume this '&' starts an HTML entity 163d5e02c3aSGreg Roach $endPos = strpos($workingText, ';'); // look for the terminating ';' 164d5e02c3aSGreg Roach if ($endPos === false) { 165d5e02c3aSGreg Roach $endPos = 0; 166d5e02c3aSGreg Roach } 167d5e02c3aSGreg Roach $currentLen += $endPos; 168d5e02c3aSGreg Roach $entity = substr($workingText, 0, $currentLen); 169d5e02c3aSGreg Roach if (strtolower($entity) === ' ') { 170d5e02c3aSGreg Roach $entity = ' '; // Ensure consistent case for this entity 171d5e02c3aSGreg Roach } 172d5e02c3aSGreg Roach if (self::$waitingText === '') { 173d5e02c3aSGreg Roach $result .= $entity; 174d5e02c3aSGreg Roach } else { 175d5e02c3aSGreg Roach self::$waitingText .= $entity; 176d5e02c3aSGreg Roach } 177d5e02c3aSGreg Roach $workingText = substr($workingText, $currentLen); 178d5e02c3aSGreg Roach break; 179d5e02c3aSGreg Roach case '{': 180d5e02c3aSGreg Roach if (substr($workingText, 1, 1) === '{') { 181d5e02c3aSGreg Roach // Assume this '{{' starts a TCPDF directive 182d5e02c3aSGreg Roach $endPos = strpos($workingText, '}}'); // look for the terminating '}}' 183d5e02c3aSGreg Roach if ($endPos === false) { 184d5e02c3aSGreg Roach $endPos = 0; 185d5e02c3aSGreg Roach } 186d5e02c3aSGreg Roach $currentLen = $endPos + 2; 187d5e02c3aSGreg Roach $directive = substr($workingText, 0, $currentLen); 188d5e02c3aSGreg Roach $workingText = substr($workingText, $currentLen); 189d5e02c3aSGreg Roach $result .= self::$waitingText . $directive; 190d5e02c3aSGreg Roach self::$waitingText = ''; 191d5e02c3aSGreg Roach break; 192d5e02c3aSGreg Roach } 193d5e02c3aSGreg Roach // no break 194d5e02c3aSGreg Roach default: 195d5e02c3aSGreg Roach // Look for strings of numbers with optional leading or trailing + or - 196d5e02c3aSGreg Roach // and with optional embedded numeric punctuation 197d5e02c3aSGreg Roach if ($numberState) { 198d5e02c3aSGreg Roach // If we're inside a numeric string, look for reasons to end it 199d5e02c3aSGreg Roach $offset = 0; // Be sure to look at the current character first 200d5e02c3aSGreg Roach $charArray = self::getChar($workingText . "\n", $offset); 201d5e02c3aSGreg Roach if (!str_contains(self::NUMBERS, $charArray['letter'])) { 202d5e02c3aSGreg Roach // This is not a digit. Is it numeric punctuation? 203d5e02c3aSGreg Roach if (substr($workingText . "\n", $offset, 6) === ' ') { 204d5e02c3aSGreg Roach $offset += 6; // This could be numeric punctuation 205d5e02c3aSGreg Roach } elseif (str_contains(self::NUMBER_PUNCTUATION, $charArray['letter'])) { 206d5e02c3aSGreg Roach $offset += $charArray['length']; // This could be numeric punctuation 207d5e02c3aSGreg Roach } 208d5e02c3aSGreg Roach // If the next character is a digit, the current character is numeric punctuation 209d5e02c3aSGreg Roach $charArray = self::getChar($workingText . "\n", $offset); 210d5e02c3aSGreg Roach if (!str_contains(self::NUMBERS, $charArray['letter'])) { 211d5e02c3aSGreg Roach // This is not a digit. End the run of digits and punctuation. 212d5e02c3aSGreg Roach $numberState = false; 213d5e02c3aSGreg Roach if (self::$currentState === 'RTL') { 214d5e02c3aSGreg Roach if (!str_contains(self::NUMBER_PREFIX, $currentLetter)) { 215d5e02c3aSGreg Roach $currentLetter = self::UTF8_PDF . $currentLetter; 216d5e02c3aSGreg Roach } else { 217d5e02c3aSGreg Roach $currentLetter .= self::UTF8_PDF; // Include a trailing + or - in the run 218d5e02c3aSGreg Roach } 219d5e02c3aSGreg Roach } 220d5e02c3aSGreg Roach } 221d5e02c3aSGreg Roach } 222d5e02c3aSGreg Roach } else { 223d5e02c3aSGreg Roach // If we're outside a numeric string, look for reasons to start it 224d5e02c3aSGreg Roach if (str_contains(self::NUMBER_PREFIX, $currentLetter)) { 225d5e02c3aSGreg Roach // This might be a number lead-in 226d5e02c3aSGreg Roach $offset = $currentLen; 227d5e02c3aSGreg Roach $nextChar = substr($workingText . "\n", $offset, 1); 228d5e02c3aSGreg Roach if (str_contains(self::NUMBERS, $nextChar)) { 229d5e02c3aSGreg Roach $numberState = true; // We found a digit: the lead-in is therefore numeric 230d5e02c3aSGreg Roach if (self::$currentState === 'RTL') { 231d5e02c3aSGreg Roach $currentLetter = self::UTF8_LRE . $currentLetter; 232d5e02c3aSGreg Roach } 233d5e02c3aSGreg Roach } 234d5e02c3aSGreg Roach } elseif (str_contains(self::NUMBERS, $currentLetter)) { 235d5e02c3aSGreg Roach $numberState = true; // The current letter is a digit 236d5e02c3aSGreg Roach if (self::$currentState === 'RTL') { 237d5e02c3aSGreg Roach $currentLetter = self::UTF8_LRE . $currentLetter; 238d5e02c3aSGreg Roach } 239d5e02c3aSGreg Roach } 240d5e02c3aSGreg Roach } 241d5e02c3aSGreg Roach 242d5e02c3aSGreg Roach // Determine the directionality of the current UTF-8 character 243d5e02c3aSGreg Roach $newState = self::$currentState; 244d5e02c3aSGreg Roach 245d5e02c3aSGreg Roach while (true) { 246d5e02c3aSGreg Roach if (I18N::scriptDirection(I18N::textScript($currentLetter)) === 'rtl') { 247d5e02c3aSGreg Roach if (self::$currentState === '') { 248d5e02c3aSGreg Roach $newState = 'RTL'; 249d5e02c3aSGreg Roach break; 250d5e02c3aSGreg Roach } 251d5e02c3aSGreg Roach 252d5e02c3aSGreg Roach if (self::$currentState === 'RTL') { 253d5e02c3aSGreg Roach break; 254d5e02c3aSGreg Roach } 255d5e02c3aSGreg Roach // Switch to RTL only if this isn't a solitary RTL letter 256d5e02c3aSGreg Roach $tempText = substr($workingText, $currentLen); 257d5e02c3aSGreg Roach while ($tempText !== '') { 258d5e02c3aSGreg Roach $nextCharArray = self::getChar($tempText, 0); 259d5e02c3aSGreg Roach $nextLetter = $nextCharArray['letter']; 260d5e02c3aSGreg Roach $nextLen = $nextCharArray['length']; 261d5e02c3aSGreg Roach $tempText = substr($tempText, $nextLen); 262d5e02c3aSGreg Roach 263d5e02c3aSGreg Roach if (I18N::scriptDirection(I18N::textScript($nextLetter)) === 'rtl') { 264d5e02c3aSGreg Roach $newState = 'RTL'; 265d5e02c3aSGreg Roach break 2; 266d5e02c3aSGreg Roach } 267d5e02c3aSGreg Roach 268d5e02c3aSGreg Roach if (str_contains(self::PUNCTUATION, $nextLetter) || str_contains(self::OPEN_PARENTHESES, $nextLetter)) { 269d5e02c3aSGreg Roach $newState = 'RTL'; 270d5e02c3aSGreg Roach break 2; 271d5e02c3aSGreg Roach } 272d5e02c3aSGreg Roach 273d5e02c3aSGreg Roach if ($nextLetter === ' ') { 274d5e02c3aSGreg Roach break; 275d5e02c3aSGreg Roach } 276d5e02c3aSGreg Roach $nextLetter .= substr($tempText . "\n", 0, 5); 277d5e02c3aSGreg Roach if ($nextLetter === ' ') { 278d5e02c3aSGreg Roach break; 279d5e02c3aSGreg Roach } 280d5e02c3aSGreg Roach } 281d5e02c3aSGreg Roach // This is a solitary RTL letter : wrap it in UTF8 control codes to force LTR directionality 282d5e02c3aSGreg Roach $currentLetter = self::UTF8_LRO . $currentLetter . self::UTF8_PDF; 283d5e02c3aSGreg Roach $newState = 'LTR'; 284d5e02c3aSGreg Roach break; 285d5e02c3aSGreg Roach } 286d5e02c3aSGreg Roach if ($currentLen !== 1 || $currentLetter >= 'A' && $currentLetter <= 'Z' || $currentLetter >= 'a' && $currentLetter <= 'z') { 287d5e02c3aSGreg Roach // Since it’s neither Hebrew nor Arabic, this UTF-8 character or ASCII letter must be LTR 288d5e02c3aSGreg Roach $newState = 'LTR'; 289d5e02c3aSGreg Roach break; 290d5e02c3aSGreg Roach } 291d5e02c3aSGreg Roach if ($closeParIndex !== false) { 292d5e02c3aSGreg Roach // This closing parenthesis has to inherit the matching opening parenthesis' directionality 293d5e02c3aSGreg Roach if (!empty($openParDirection[$closeParIndex]) && $openParDirection[$closeParIndex] !== '?') { 294d5e02c3aSGreg Roach $newState = $openParDirection[$closeParIndex]; 295d5e02c3aSGreg Roach } 296d5e02c3aSGreg Roach $openParDirection[$closeParIndex] = ''; 297d5e02c3aSGreg Roach break; 298d5e02c3aSGreg Roach } 299d5e02c3aSGreg Roach if ($openParIndex !== false) { 300d5e02c3aSGreg Roach // Opening parentheses always inherit the following directionality 301d5e02c3aSGreg Roach self::$waitingText .= $currentLetter; 302d5e02c3aSGreg Roach $workingText = substr($workingText, $currentLen); 303d5e02c3aSGreg Roach while (true) { 304d5e02c3aSGreg Roach if ($workingText === '') { 305d5e02c3aSGreg Roach break; 306d5e02c3aSGreg Roach } 307d5e02c3aSGreg Roach if (substr($workingText, 0, 1) === ' ') { 308d5e02c3aSGreg Roach // Spaces following this left parenthesis inherit the following directionality too 309d5e02c3aSGreg Roach self::$waitingText .= ' '; 310d5e02c3aSGreg Roach $workingText = substr($workingText, 1); 311d5e02c3aSGreg Roach continue; 312d5e02c3aSGreg Roach } 313d5e02c3aSGreg Roach if (substr($workingText, 0, 6) === ' ') { 314d5e02c3aSGreg Roach // Spaces following this left parenthesis inherit the following directionality too 315d5e02c3aSGreg Roach self::$waitingText .= ' '; 316d5e02c3aSGreg Roach $workingText = substr($workingText, 6); 317d5e02c3aSGreg Roach continue; 318d5e02c3aSGreg Roach } 319d5e02c3aSGreg Roach break; 320d5e02c3aSGreg Roach } 321d5e02c3aSGreg Roach $openParDirection[$openParIndex] = '?'; 322d5e02c3aSGreg Roach break 2; // double break because we're waiting for more information 323d5e02c3aSGreg Roach } 324d5e02c3aSGreg Roach 325d5e02c3aSGreg Roach // We have a digit or a "normal" special character. 326d5e02c3aSGreg Roach // 327d5e02c3aSGreg Roach // When this character is not at the start of the input string, it inherits the preceding directionality; 328d5e02c3aSGreg Roach // at the start of the input string, it assumes the following directionality. 329d5e02c3aSGreg Roach // 330d5e02c3aSGreg Roach // Exceptions to this rule will be handled later during final clean-up. 331d5e02c3aSGreg Roach // 332d5e02c3aSGreg Roach self::$waitingText .= $currentLetter; 333d5e02c3aSGreg Roach $workingText = substr($workingText, $currentLen); 334d5e02c3aSGreg Roach if (self::$currentState !== '') { 335d5e02c3aSGreg Roach $result .= self::$waitingText; 336d5e02c3aSGreg Roach self::$waitingText = ''; 337d5e02c3aSGreg Roach } 338d5e02c3aSGreg Roach break 2; // double break because we're waiting for more information 339d5e02c3aSGreg Roach } 340d5e02c3aSGreg Roach if ($newState !== self::$currentState) { 341d5e02c3aSGreg Roach // A direction change has occurred 342d5e02c3aSGreg Roach self::finishCurrentSpan($result); 343d5e02c3aSGreg Roach self::$previousState = self::$currentState; 344d5e02c3aSGreg Roach self::$currentState = $newState; 345d5e02c3aSGreg Roach self::beginCurrentSpan($result); 346d5e02c3aSGreg Roach } 347d5e02c3aSGreg Roach self::$waitingText .= $currentLetter; 348d5e02c3aSGreg Roach $workingText = substr($workingText, $currentLen); 349d5e02c3aSGreg Roach $result .= self::$waitingText; 350d5e02c3aSGreg Roach self::$waitingText = ''; 351d5e02c3aSGreg Roach 352d5e02c3aSGreg Roach foreach ($openParDirection as $index => $value) { 353d5e02c3aSGreg Roach // Since we now know the proper direction, remember it for all waiting opening parentheses 354d5e02c3aSGreg Roach if ($value === '?') { 355d5e02c3aSGreg Roach $openParDirection[$index] = self::$currentState; 356d5e02c3aSGreg Roach } 357d5e02c3aSGreg Roach } 358d5e02c3aSGreg Roach 359d5e02c3aSGreg Roach break; 360d5e02c3aSGreg Roach } 361d5e02c3aSGreg Roach } 362d5e02c3aSGreg Roach 363d5e02c3aSGreg Roach // We're done. Finish last <span> if necessary 364d5e02c3aSGreg Roach if ($numberState) { 365d5e02c3aSGreg Roach if (self::$waitingText === '') { 366d5e02c3aSGreg Roach if (self::$currentState === 'RTL') { 367d5e02c3aSGreg Roach $result .= self::UTF8_PDF; 368d5e02c3aSGreg Roach } 369d5e02c3aSGreg Roach } else { 370d5e02c3aSGreg Roach if (self::$currentState === 'RTL') { 371d5e02c3aSGreg Roach self::$waitingText .= self::UTF8_PDF; 372d5e02c3aSGreg Roach } 373d5e02c3aSGreg Roach } 374d5e02c3aSGreg Roach } 375d5e02c3aSGreg Roach self::finishCurrentSpan($result, true); 376d5e02c3aSGreg Roach 377d5e02c3aSGreg Roach // Get rid of any waiting text 378d5e02c3aSGreg Roach if (self::$waitingText !== '') { 379d5e02c3aSGreg Roach if (I18N::direction() === 'rtl' && self::$currentState === 'LTR') { 380d5e02c3aSGreg Roach $result .= self::START_RTL; 381d5e02c3aSGreg Roach $result .= self::$waitingText; 382d5e02c3aSGreg Roach $result .= self::END_RTL; 383d5e02c3aSGreg Roach } else { 384d5e02c3aSGreg Roach $result .= self::START_LTR; 385d5e02c3aSGreg Roach $result .= self::$waitingText; 386d5e02c3aSGreg Roach $result .= self::END_LTR; 387d5e02c3aSGreg Roach } 388d5e02c3aSGreg Roach self::$waitingText = ''; 389d5e02c3aSGreg Roach } 390d5e02c3aSGreg Roach 391d5e02c3aSGreg Roach // Lastly, do some more cleanups 392d5e02c3aSGreg Roach 393d5e02c3aSGreg Roach // Move leading RTL numeric strings to following LTR text 394d5e02c3aSGreg Roach // (this happens when the page direction is RTL and the original text begins with a number and is followed by LTR text) 395d5e02c3aSGreg Roach while (substr($result, 0, self::LENGTH_START + 3) === self::START_RTL . self::UTF8_LRE) { 396d5e02c3aSGreg Roach $spanEnd = strpos($result, self::END_RTL . self::START_LTR); 397d5e02c3aSGreg Roach if ($spanEnd === false) { 398d5e02c3aSGreg Roach break; 399d5e02c3aSGreg Roach } 400d5e02c3aSGreg Roach $textSpan = self::stripLrmRlm(substr($result, self::LENGTH_START + 3, $spanEnd - self::LENGTH_START - 3)); 401d5e02c3aSGreg Roach if (I18N::scriptDirection(I18N::textScript($textSpan)) === 'rtl') { 402d5e02c3aSGreg Roach break; 403d5e02c3aSGreg Roach } 404d5e02c3aSGreg Roach $result = self::START_LTR . substr($result, self::LENGTH_START, $spanEnd - self::LENGTH_START) . substr($result, $spanEnd + self::LENGTH_START + self::LENGTH_END); 405d5e02c3aSGreg Roach break; 406d5e02c3aSGreg Roach } 407d5e02c3aSGreg Roach 408d5e02c3aSGreg Roach // On RTL pages, put trailing "." in RTL numeric strings into its own RTL span 409d5e02c3aSGreg Roach if (I18N::direction() === 'rtl') { 410d5e02c3aSGreg Roach $result = str_replace(self::UTF8_PDF . '.' . self::END_RTL, self::UTF8_PDF . self::END_RTL . self::START_RTL . '.' . self::END_RTL, $result); 411d5e02c3aSGreg Roach } 412d5e02c3aSGreg Roach 413d5e02c3aSGreg Roach // Trim trailing blanks preceding <br> in LTR text 414d5e02c3aSGreg Roach while (self::$previousState !== 'RTL') { 415d5e02c3aSGreg Roach if (str_contains($result, ' <LTRbr>')) { 416d5e02c3aSGreg Roach $result = str_replace(' <LTRbr>', '<LTRbr>', $result); 417d5e02c3aSGreg Roach continue; 418d5e02c3aSGreg Roach } 419d5e02c3aSGreg Roach if (str_contains($result, ' <LTRbr>')) { 420d5e02c3aSGreg Roach $result = str_replace(' <LTRbr>', '<LTRbr>', $result); 421d5e02c3aSGreg Roach continue; 422d5e02c3aSGreg Roach } 423d5e02c3aSGreg Roach if (str_contains($result, ' <br>')) { 424d5e02c3aSGreg Roach $result = str_replace(' <br>', '<br>', $result); 425d5e02c3aSGreg Roach continue; 426d5e02c3aSGreg Roach } 427d5e02c3aSGreg Roach if (str_contains($result, ' <br>')) { 428d5e02c3aSGreg Roach $result = str_replace(' <br>', '<br>', $result); 429d5e02c3aSGreg Roach continue; 430d5e02c3aSGreg Roach } 431d5e02c3aSGreg Roach break; // Neither space nor : we're done 432d5e02c3aSGreg Roach } 433d5e02c3aSGreg Roach 434d5e02c3aSGreg Roach // Trim trailing blanks preceding <br> in RTL text 435d5e02c3aSGreg Roach while (true) { 436d5e02c3aSGreg Roach if (str_contains($result, ' <RTLbr>')) { 437d5e02c3aSGreg Roach $result = str_replace(' <RTLbr>', '<RTLbr>', $result); 438d5e02c3aSGreg Roach continue; 439d5e02c3aSGreg Roach } 440d5e02c3aSGreg Roach if (str_contains($result, ' <RTLbr>')) { 441d5e02c3aSGreg Roach $result = str_replace(' <RTLbr>', '<RTLbr>', $result); 442d5e02c3aSGreg Roach continue; 443d5e02c3aSGreg Roach } 444d5e02c3aSGreg Roach break; // Neither space nor : we're done 445d5e02c3aSGreg Roach } 446d5e02c3aSGreg Roach 447d5e02c3aSGreg Roach // Convert '<LTRbr>' and '<RTLbr' 448d5e02c3aSGreg Roach $result = str_replace([ 449d5e02c3aSGreg Roach '<LTRbr>', 450d5e02c3aSGreg Roach '<RTLbr>', 451d5e02c3aSGreg Roach ], [ 452d5e02c3aSGreg Roach self::END_LTR . '<br>' . self::START_LTR, 453d5e02c3aSGreg Roach self::END_RTL . '<br>' . self::START_RTL, 454d5e02c3aSGreg Roach ], $result); 455d5e02c3aSGreg Roach 456d5e02c3aSGreg Roach // Include leading indeterminate directional text in whatever follows 457d5e02c3aSGreg Roach if (substr($result . "\n", 0, self::LENGTH_START) !== self::START_LTR && substr($result . "\n", 0, self::LENGTH_START) !== self::START_RTL && substr($result . "\n", 0, 4) !== '<br>') { 458d5e02c3aSGreg Roach $leadingText = ''; 459d5e02c3aSGreg Roach while (true) { 460d5e02c3aSGreg Roach if ($result === '') { 461d5e02c3aSGreg Roach $result = $leadingText; 462d5e02c3aSGreg Roach break; 463d5e02c3aSGreg Roach } 464d5e02c3aSGreg Roach if (substr($result . "\n", 0, self::LENGTH_START) !== self::START_LTR && substr($result . "\n", 0, self::LENGTH_START) !== self::START_RTL) { 465d5e02c3aSGreg Roach $leadingText .= substr($result, 0, 1); 466d5e02c3aSGreg Roach $result = substr($result, 1); 467d5e02c3aSGreg Roach continue; 468d5e02c3aSGreg Roach } 469d5e02c3aSGreg Roach $result = substr($result, 0, self::LENGTH_START) . $leadingText . substr($result, self::LENGTH_START); 470d5e02c3aSGreg Roach break; 471d5e02c3aSGreg Roach } 472d5e02c3aSGreg Roach } 473d5e02c3aSGreg Roach 474d5e02c3aSGreg Roach // Include solitary "-" and "+" in surrounding RTL text 475d5e02c3aSGreg Roach $result = str_replace([ 476d5e02c3aSGreg Roach self::END_RTL . self::START_LTR . '-' . self::END_LTR . self::START_RTL, 477d5e02c3aSGreg Roach self::END_RTL . self::START_LTR . '+' . self::END_LTR . self::START_RTL, 478d5e02c3aSGreg Roach ], [ 479d5e02c3aSGreg Roach '-', 480d5e02c3aSGreg Roach '+', 481d5e02c3aSGreg Roach ], $result); 482d5e02c3aSGreg Roach 483d5e02c3aSGreg Roach //$result = strtr($result, [ 484d5e02c3aSGreg Roach // self::END_RTL . self::START_LTR . '-' . self::END_LTR . self::START_RTL => '-', 485d5e02c3aSGreg Roach // self::END_RTL . self::START_LTR . '+' . self::END_LTR . self::START_RTL => '+', 486d5e02c3aSGreg Roach //]); 487d5e02c3aSGreg Roach 488d5e02c3aSGreg Roach // Remove empty spans 489d5e02c3aSGreg Roach $result = str_replace([ 490d5e02c3aSGreg Roach self::START_LTR . self::END_LTR, 491d5e02c3aSGreg Roach self::START_RTL . self::END_RTL, 492d5e02c3aSGreg Roach ], '', $result); 493d5e02c3aSGreg Roach 494d5e02c3aSGreg Roach // Finally, correct '<LTR>', '</LTR>', '<RTL>', and '</RTL>' 495d5e02c3aSGreg Roach // LTR text: <span dir="ltr"> text </span> 496d5e02c3aSGreg Roach // RTL text: <span dir="rtl"> text </span> 497d5e02c3aSGreg Roach 498d5e02c3aSGreg Roach $result = str_replace([ 499d5e02c3aSGreg Roach self::START_LTR, 500d5e02c3aSGreg Roach self::END_LTR, 501d5e02c3aSGreg Roach self::START_RTL, 502d5e02c3aSGreg Roach self::END_RTL, 503d5e02c3aSGreg Roach ], [ 504d5e02c3aSGreg Roach '<span dir="ltr">', 505d5e02c3aSGreg Roach '</span>', 506d5e02c3aSGreg Roach '<span dir="rtl">', 507d5e02c3aSGreg Roach '</span>', 508d5e02c3aSGreg Roach ], $result); 509d5e02c3aSGreg Roach 510d5e02c3aSGreg Roach return $result; 511d5e02c3aSGreg Roach } 512d5e02c3aSGreg Roach 513d5e02c3aSGreg Roach /** 514d5e02c3aSGreg Roach * Wrap words that have an asterisk suffix in <u> and </u> tags. 515d5e02c3aSGreg Roach * This should underline starred names to show the preferred name. 516d5e02c3aSGreg Roach * 517d5e02c3aSGreg Roach * @param string $textSpan 518d5e02c3aSGreg Roach * @param string $direction 519d5e02c3aSGreg Roach * 520d5e02c3aSGreg Roach * @return string 521d5e02c3aSGreg Roach */ 522d5e02c3aSGreg Roach private static function starredName(string $textSpan, string $direction): string 523d5e02c3aSGreg Roach { 524d5e02c3aSGreg Roach // To avoid a TCPDF bug that mixes up the word order, insert those <u> and </u> tags 525d5e02c3aSGreg Roach // only when page and span directions are identical. 526d5e02c3aSGreg Roach if ($direction === strtoupper(I18N::direction())) { 527d5e02c3aSGreg Roach while (true) { 528d5e02c3aSGreg Roach $starPos = strpos($textSpan, '*'); 529d5e02c3aSGreg Roach if ($starPos === false) { 530d5e02c3aSGreg Roach break; 531d5e02c3aSGreg Roach } 532d5e02c3aSGreg Roach $trailingText = substr($textSpan, $starPos + 1); 533d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, $starPos); 534d5e02c3aSGreg Roach $wordStart = strrpos($textSpan, ' '); // Find the start of the word 535d5e02c3aSGreg Roach if ($wordStart !== false) { 536d5e02c3aSGreg Roach $leadingText = substr($textSpan, 0, $wordStart + 1); 537d5e02c3aSGreg Roach $wordText = substr($textSpan, $wordStart + 1); 538d5e02c3aSGreg Roach } else { 539d5e02c3aSGreg Roach $leadingText = ''; 540d5e02c3aSGreg Roach $wordText = $textSpan; 541d5e02c3aSGreg Roach } 542d5e02c3aSGreg Roach $textSpan = $leadingText . '<u>' . $wordText . '</u>' . $trailingText; 543d5e02c3aSGreg Roach } 544d5e02c3aSGreg Roach $textSpan = preg_replace('~<span class="starredname">(.*)</span>~', '<u>\1</u>', $textSpan); 545d5e02c3aSGreg Roach // The is a work-around for a TCPDF bug eating blanks. 546d5e02c3aSGreg Roach $textSpan = str_replace([ 547d5e02c3aSGreg Roach ' <u>', 548d5e02c3aSGreg Roach '</u> ', 549d5e02c3aSGreg Roach ], [ 550d5e02c3aSGreg Roach ' <u>', 551d5e02c3aSGreg Roach '</u> ', 552d5e02c3aSGreg Roach ], $textSpan); 553d5e02c3aSGreg Roach } else { 554d5e02c3aSGreg Roach // Text and page directions differ: remove the <span> and </span> 555d5e02c3aSGreg Roach $textSpan = preg_replace('~(.*)\*~', '\1', $textSpan); 556d5e02c3aSGreg Roach $textSpan = preg_replace('~<span class="starredname">(.*)</span>~', '\1', $textSpan); 557d5e02c3aSGreg Roach } 558d5e02c3aSGreg Roach 559d5e02c3aSGreg Roach return $textSpan; 560d5e02c3aSGreg Roach } 561d5e02c3aSGreg Roach 562d5e02c3aSGreg Roach /** 563d5e02c3aSGreg Roach * Get the next character from an input string 564d5e02c3aSGreg Roach * 565d5e02c3aSGreg Roach * @param string $text 566d5e02c3aSGreg Roach * @param int $offset 567d5e02c3aSGreg Roach * 568d5e02c3aSGreg Roach * @return array{'letter':string,'length':int} 569d5e02c3aSGreg Roach */ 570d5e02c3aSGreg Roach private static function getChar(string $text, int $offset): array 571d5e02c3aSGreg Roach { 572d5e02c3aSGreg Roach if ($text === '') { 573d5e02c3aSGreg Roach return [ 574d5e02c3aSGreg Roach 'letter' => '', 575d5e02c3aSGreg Roach 'length' => 0, 576d5e02c3aSGreg Roach ]; 577d5e02c3aSGreg Roach } 578d5e02c3aSGreg Roach 579d5e02c3aSGreg Roach $char = substr($text, $offset, 1); 580d5e02c3aSGreg Roach $length = 1; 581d5e02c3aSGreg Roach if ((ord($char) & 0xE0) === 0xC0) { 582d5e02c3aSGreg Roach $length = 2; 583d5e02c3aSGreg Roach } 584d5e02c3aSGreg Roach if ((ord($char) & 0xF0) === 0xE0) { 585d5e02c3aSGreg Roach $length = 3; 586d5e02c3aSGreg Roach } 587d5e02c3aSGreg Roach if ((ord($char) & 0xF8) === 0xF0) { 588d5e02c3aSGreg Roach $length = 4; 589d5e02c3aSGreg Roach } 590d5e02c3aSGreg Roach $letter = substr($text, $offset, $length); 591d5e02c3aSGreg Roach 592d5e02c3aSGreg Roach return [ 593d5e02c3aSGreg Roach 'letter' => $letter, 594d5e02c3aSGreg Roach 'length' => $length, 595d5e02c3aSGreg Roach ]; 596d5e02c3aSGreg Roach } 597d5e02c3aSGreg Roach 598d5e02c3aSGreg Roach /** 599d5e02c3aSGreg Roach * Insert <br> into current span 600d5e02c3aSGreg Roach * 601d5e02c3aSGreg Roach * @param string $result 602d5e02c3aSGreg Roach * 603d5e02c3aSGreg Roach * @return void 604d5e02c3aSGreg Roach */ 605d5e02c3aSGreg Roach private static function breakCurrentSpan(string &$result): void 606d5e02c3aSGreg Roach { 607d5e02c3aSGreg Roach // Interrupt the current span, insert that <br>, and then continue the current span 608d5e02c3aSGreg Roach $result .= self::$waitingText; 609d5e02c3aSGreg Roach self::$waitingText = ''; 610d5e02c3aSGreg Roach 611d5e02c3aSGreg Roach $breakString = '<' . self::$currentState . 'br>'; 612d5e02c3aSGreg Roach $result .= $breakString; 613d5e02c3aSGreg Roach } 614d5e02c3aSGreg Roach 615d5e02c3aSGreg Roach /** 616d5e02c3aSGreg Roach * Begin current span 617d5e02c3aSGreg Roach * 618d5e02c3aSGreg Roach * @param string $result 619d5e02c3aSGreg Roach * 620d5e02c3aSGreg Roach * @return void 621d5e02c3aSGreg Roach */ 622d5e02c3aSGreg Roach private static function beginCurrentSpan(string &$result): void 623d5e02c3aSGreg Roach { 624d5e02c3aSGreg Roach if (self::$currentState === 'LTR') { 625d5e02c3aSGreg Roach $result .= self::START_LTR; 626d5e02c3aSGreg Roach } 627d5e02c3aSGreg Roach if (self::$currentState === 'RTL') { 628d5e02c3aSGreg Roach $result .= self::START_RTL; 629d5e02c3aSGreg Roach } 630d5e02c3aSGreg Roach 631d5e02c3aSGreg Roach self::$posSpanStart = strlen($result); 632d5e02c3aSGreg Roach } 633d5e02c3aSGreg Roach 634d5e02c3aSGreg Roach /** 635d5e02c3aSGreg Roach * Finish current span 636d5e02c3aSGreg Roach * 637d5e02c3aSGreg Roach * @param string $result 638d5e02c3aSGreg Roach * @param bool $theEnd 639d5e02c3aSGreg Roach * 640d5e02c3aSGreg Roach * @return void 641d5e02c3aSGreg Roach */ 642d5e02c3aSGreg Roach private static function finishCurrentSpan(string &$result, bool $theEnd = false): void 643d5e02c3aSGreg Roach { 644d5e02c3aSGreg Roach $textSpan = substr($result, self::$posSpanStart); 645d5e02c3aSGreg Roach $result = substr($result, 0, self::$posSpanStart); 646d5e02c3aSGreg Roach 647d5e02c3aSGreg Roach // Get rid of empty spans, so that our check for presence of RTL will work 648d5e02c3aSGreg Roach $result = str_replace([ 649d5e02c3aSGreg Roach self::START_LTR . self::END_LTR, 650d5e02c3aSGreg Roach self::START_RTL . self::END_RTL, 651d5e02c3aSGreg Roach ], '', $result); 652d5e02c3aSGreg Roach 653d5e02c3aSGreg Roach // Look for numeric strings that are times (hh:mm:ss). These have to be separated from surrounding numbers. 654d5e02c3aSGreg Roach $tempResult = ''; 655d5e02c3aSGreg Roach while ($textSpan !== '') { 656d5e02c3aSGreg Roach $posColon = strpos($textSpan, ':'); 657d5e02c3aSGreg Roach if ($posColon === false) { 658d5e02c3aSGreg Roach break; 659d5e02c3aSGreg Roach } // No more possible time strings 660d5e02c3aSGreg Roach $posLRE = strpos($textSpan, self::UTF8_LRE); 661d5e02c3aSGreg Roach if ($posLRE === false) { 662d5e02c3aSGreg Roach break; 663d5e02c3aSGreg Roach } // No more numeric strings 664d5e02c3aSGreg Roach $posPDF = strpos($textSpan, self::UTF8_PDF, $posLRE); 665d5e02c3aSGreg Roach if ($posPDF === false) { 666d5e02c3aSGreg Roach break; 667d5e02c3aSGreg Roach } // No more numeric strings 668d5e02c3aSGreg Roach 669d5e02c3aSGreg Roach $tempResult .= substr($textSpan, 0, $posLRE + 3); // Copy everything preceding the numeric string 670d5e02c3aSGreg Roach $numericString = substr($textSpan, $posLRE + 3, $posPDF - $posLRE); // Separate the entire numeric string 671d5e02c3aSGreg Roach $textSpan = substr($textSpan, $posPDF + 3); 672d5e02c3aSGreg Roach $posColon = strpos($numericString, ':'); 673d5e02c3aSGreg Roach if ($posColon === false) { 674d5e02c3aSGreg Roach // Nothing that looks like a time here 675d5e02c3aSGreg Roach $tempResult .= $numericString; 676d5e02c3aSGreg Roach continue; 677d5e02c3aSGreg Roach } 678d5e02c3aSGreg Roach $posBlank = strpos($numericString . ' ', ' '); 679d5e02c3aSGreg Roach $posNbsp = strpos($numericString . ' ', ' '); 680d5e02c3aSGreg Roach if ($posBlank < $posNbsp) { 681d5e02c3aSGreg Roach $posSeparator = $posBlank; 682d5e02c3aSGreg Roach $lengthSeparator = 1; 683d5e02c3aSGreg Roach } else { 684d5e02c3aSGreg Roach $posSeparator = $posNbsp; 685d5e02c3aSGreg Roach $lengthSeparator = 6; 686d5e02c3aSGreg Roach } 687d5e02c3aSGreg Roach if ($posColon > $posSeparator) { 688d5e02c3aSGreg Roach // We have a time string preceded by a blank: Exclude that blank from the numeric string 689d5e02c3aSGreg Roach $tempResult .= substr($numericString, 0, $posSeparator); 690d5e02c3aSGreg Roach $tempResult .= self::UTF8_PDF; 691d5e02c3aSGreg Roach $tempResult .= substr($numericString, $posSeparator, $lengthSeparator); 692d5e02c3aSGreg Roach $tempResult .= self::UTF8_LRE; 693d5e02c3aSGreg Roach $numericString = substr($numericString, $posSeparator + $lengthSeparator); 694d5e02c3aSGreg Roach } 695d5e02c3aSGreg Roach 696d5e02c3aSGreg Roach $posBlank = strpos($numericString, ' '); 697d5e02c3aSGreg Roach $posNbsp = strpos($numericString, ' '); 698d5e02c3aSGreg Roach if ($posBlank === false && $posNbsp === false) { 699d5e02c3aSGreg Roach // The time string isn't followed by a blank 700d5e02c3aSGreg Roach $textSpan = $numericString . $textSpan; 701d5e02c3aSGreg Roach continue; 702d5e02c3aSGreg Roach } 703d5e02c3aSGreg Roach 704d5e02c3aSGreg Roach // We have a time string followed by a blank: Exclude that blank from the numeric string 705d5e02c3aSGreg Roach if ($posBlank === false) { 706d5e02c3aSGreg Roach $posSeparator = $posNbsp; 707d5e02c3aSGreg Roach $lengthSeparator = 6; 708d5e02c3aSGreg Roach } elseif ($posNbsp === false) { 709d5e02c3aSGreg Roach $posSeparator = $posBlank; 710d5e02c3aSGreg Roach $lengthSeparator = 1; 711d5e02c3aSGreg Roach } elseif ($posBlank < $posNbsp) { 712d5e02c3aSGreg Roach $posSeparator = $posBlank; 713d5e02c3aSGreg Roach $lengthSeparator = 1; 714d5e02c3aSGreg Roach } else { 715d5e02c3aSGreg Roach $posSeparator = $posNbsp; 716d5e02c3aSGreg Roach $lengthSeparator = 6; 717d5e02c3aSGreg Roach } 718d5e02c3aSGreg Roach $tempResult .= substr($numericString, 0, $posSeparator); 719d5e02c3aSGreg Roach $tempResult .= self::UTF8_PDF; 720d5e02c3aSGreg Roach $tempResult .= substr($numericString, $posSeparator, $lengthSeparator); 721d5e02c3aSGreg Roach $posSeparator += $lengthSeparator; 722d5e02c3aSGreg Roach $numericString = substr($numericString, $posSeparator); 723d5e02c3aSGreg Roach $textSpan = self::UTF8_LRE . $numericString . $textSpan; 724d5e02c3aSGreg Roach } 725d5e02c3aSGreg Roach $textSpan = $tempResult . $textSpan; 726d5e02c3aSGreg Roach $trailingBlanks = ''; 727d5e02c3aSGreg Roach $trailingBreaks = ''; 728d5e02c3aSGreg Roach 729d5e02c3aSGreg Roach /* ****************************** LTR text handling ******************************** */ 730d5e02c3aSGreg Roach 731d5e02c3aSGreg Roach if (self::$currentState === 'LTR') { 732d5e02c3aSGreg Roach // Move trailing numeric strings to the following RTL text. Include any blanks preceding or following the numeric text too. 733d5e02c3aSGreg Roach if (I18N::direction() === 'rtl' && self::$previousState === 'RTL' && !$theEnd) { 734d5e02c3aSGreg Roach $trailingString = ''; 735d5e02c3aSGreg Roach $savedSpan = $textSpan; 736d5e02c3aSGreg Roach while ($textSpan !== '') { 737d5e02c3aSGreg Roach // Look for trailing spaces and tentatively move them 738d5e02c3aSGreg Roach if (substr($textSpan, -1) === ' ') { 739d5e02c3aSGreg Roach $trailingString = ' ' . $trailingString; 740d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -1); 741d5e02c3aSGreg Roach continue; 742d5e02c3aSGreg Roach } 743d5e02c3aSGreg Roach if (substr($textSpan, -6) === ' ') { 744d5e02c3aSGreg Roach $trailingString = ' ' . $trailingString; 745d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -1); 746d5e02c3aSGreg Roach continue; 747d5e02c3aSGreg Roach } 748d5e02c3aSGreg Roach if (substr($textSpan, -3) !== self::UTF8_PDF) { 749d5e02c3aSGreg Roach // There is no trailing numeric string 750d5e02c3aSGreg Roach $textSpan = $savedSpan; 751d5e02c3aSGreg Roach break; 752d5e02c3aSGreg Roach } 753d5e02c3aSGreg Roach 754d5e02c3aSGreg Roach // We have a numeric string 755d5e02c3aSGreg Roach $posStartNumber = strrpos($textSpan, self::UTF8_LRE); 756d5e02c3aSGreg Roach if ($posStartNumber === false) { 757d5e02c3aSGreg Roach $posStartNumber = 0; 758d5e02c3aSGreg Roach } 759d5e02c3aSGreg Roach $trailingString = substr($textSpan, $posStartNumber) . $trailingString; 760d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, $posStartNumber); 761d5e02c3aSGreg Roach 762d5e02c3aSGreg Roach // Look for more spaces and move them too 763d5e02c3aSGreg Roach while ($textSpan !== '') { 764d5e02c3aSGreg Roach if (substr($textSpan, -1) === ' ') { 765d5e02c3aSGreg Roach $trailingString = ' ' . $trailingString; 766d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -1); 767d5e02c3aSGreg Roach continue; 768d5e02c3aSGreg Roach } 769d5e02c3aSGreg Roach if (substr($textSpan, -6) === ' ') { 770d5e02c3aSGreg Roach $trailingString = ' ' . $trailingString; 771d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -1); 772d5e02c3aSGreg Roach continue; 773d5e02c3aSGreg Roach } 774d5e02c3aSGreg Roach break; 775d5e02c3aSGreg Roach } 776d5e02c3aSGreg Roach 777d5e02c3aSGreg Roach self::$waitingText = $trailingString . self::$waitingText; 778d5e02c3aSGreg Roach break; 779d5e02c3aSGreg Roach } 780d5e02c3aSGreg Roach } 781d5e02c3aSGreg Roach 782d5e02c3aSGreg Roach $savedSpan = $textSpan; 783d5e02c3aSGreg Roach // Move any trailing <br>, optionally preceded or followed by blanks, outside this LTR span 784d5e02c3aSGreg Roach while ($textSpan !== '') { 785d5e02c3aSGreg Roach if (substr($textSpan, -1) === ' ') { 786d5e02c3aSGreg Roach $trailingBlanks = ' ' . $trailingBlanks; 787d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -1); 788d5e02c3aSGreg Roach continue; 789d5e02c3aSGreg Roach } 790d5e02c3aSGreg Roach if (substr('......' . $textSpan, -6) === ' ') { 791d5e02c3aSGreg Roach $trailingBlanks = ' ' . $trailingBlanks; 792d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -6); 793d5e02c3aSGreg Roach continue; 794d5e02c3aSGreg Roach } 795d5e02c3aSGreg Roach break; 796d5e02c3aSGreg Roach } 797d5e02c3aSGreg Roach while (substr($textSpan, -7) === '<LTRbr>') { 798d5e02c3aSGreg Roach $trailingBreaks = '<br>' . $trailingBreaks; // Plain <br> because it’s outside a span 799d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -7); 800d5e02c3aSGreg Roach } 801d5e02c3aSGreg Roach if ($trailingBreaks !== '') { 802d5e02c3aSGreg Roach while ($textSpan !== '') { 803d5e02c3aSGreg Roach if (substr($textSpan, -1) === ' ') { 804d5e02c3aSGreg Roach $trailingBreaks = ' ' . $trailingBreaks; 805d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -1); 806d5e02c3aSGreg Roach continue; 807d5e02c3aSGreg Roach } 808d5e02c3aSGreg Roach if (substr($textSpan, -6) === ' ') { 809d5e02c3aSGreg Roach $trailingBreaks = ' ' . $trailingBreaks; 810d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -6); 811d5e02c3aSGreg Roach continue; 812d5e02c3aSGreg Roach } 813d5e02c3aSGreg Roach break; 814d5e02c3aSGreg Roach } 815d5e02c3aSGreg Roach self::$waitingText = $trailingBlanks . self::$waitingText; // Put those trailing blanks inside the following span 816d5e02c3aSGreg Roach } else { 817d5e02c3aSGreg Roach $textSpan = $savedSpan; 818d5e02c3aSGreg Roach } 819d5e02c3aSGreg Roach 820d5e02c3aSGreg Roach $trailingBlanks = ''; 821d5e02c3aSGreg Roach $trailingPunctuation = ''; 822d5e02c3aSGreg Roach $trailingID = ''; 823d5e02c3aSGreg Roach $trailingSeparator = ''; 824d5e02c3aSGreg Roach $leadingSeparator = ''; 825d5e02c3aSGreg Roach 826d5e02c3aSGreg Roach while (I18N::direction() === 'rtl') { 827d5e02c3aSGreg Roach if (str_contains($result, self::START_RTL)) { 828d5e02c3aSGreg Roach // Remove trailing blanks for inclusion in a separate LTR span 829d5e02c3aSGreg Roach while ($textSpan !== '') { 830d5e02c3aSGreg Roach if (substr($textSpan, -1) === ' ') { 831d5e02c3aSGreg Roach $trailingBlanks = ' ' . $trailingBlanks; 832d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -1); 833d5e02c3aSGreg Roach continue; 834d5e02c3aSGreg Roach } 835d5e02c3aSGreg Roach if (substr($textSpan, -6) === ' ') { 836d5e02c3aSGreg Roach $trailingBlanks = ' ' . $trailingBlanks; 837d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -1); 838d5e02c3aSGreg Roach continue; 839d5e02c3aSGreg Roach } 840d5e02c3aSGreg Roach break; 841d5e02c3aSGreg Roach } 842d5e02c3aSGreg Roach 843d5e02c3aSGreg Roach // Remove trailing punctuation for inclusion in a separate LTR span 844d5e02c3aSGreg Roach if ($textSpan === '') { 845d5e02c3aSGreg Roach $trailingChar = "\n"; 846d5e02c3aSGreg Roach } else { 847d5e02c3aSGreg Roach $trailingChar = substr($textSpan, -1); 848d5e02c3aSGreg Roach } 849d5e02c3aSGreg Roach if (str_contains(self::PUNCTUATION, $trailingChar)) { 850d5e02c3aSGreg Roach $trailingPunctuation = $trailingChar; 851d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -1); 852d5e02c3aSGreg Roach } 853d5e02c3aSGreg Roach } 854d5e02c3aSGreg Roach 855d5e02c3aSGreg Roach // Remove trailing ID numbers that look like "(xnnn)" for inclusion in a separate LTR span 856d5e02c3aSGreg Roach while (true) { 857d5e02c3aSGreg Roach if (substr($textSpan, -1) !== ')') { 858d5e02c3aSGreg Roach break; 859d5e02c3aSGreg Roach } // There is no trailing ')' 860d5e02c3aSGreg Roach $posLeftParen = strrpos($textSpan, '('); 861d5e02c3aSGreg Roach if ($posLeftParen === false) { 862d5e02c3aSGreg Roach break; 863d5e02c3aSGreg Roach } // There is no leading '(' 864d5e02c3aSGreg Roach $temp = self::stripLrmRlm(substr($textSpan, $posLeftParen)); // Get rid of UTF8 control codes 865d5e02c3aSGreg Roach 866d5e02c3aSGreg Roach // If the parenthesized text doesn't look like an ID number, 867d5e02c3aSGreg Roach // we don't want to touch it. 868d5e02c3aSGreg Roach // This check won’t work if somebody uses ID numbers with an unusual format. 869d5e02c3aSGreg Roach $offset = 1; 870d5e02c3aSGreg Roach $charArray = self::getChar($temp, $offset); // Get 1st character of parenthesized text 871d5e02c3aSGreg Roach if (str_contains(self::NUMBERS, $charArray['letter'])) { 872d5e02c3aSGreg Roach break; 873d5e02c3aSGreg Roach } 874d5e02c3aSGreg Roach $offset += $charArray['length']; // Point at 2nd character of parenthesized text 875d5e02c3aSGreg Roach if (!str_contains(self::NUMBERS, substr($temp, $offset, 1))) { 876d5e02c3aSGreg Roach break; 877d5e02c3aSGreg Roach } 878d5e02c3aSGreg Roach // 1st character of parenthesized text is alpha, 2nd character is a digit; last has to be a digit too 879d5e02c3aSGreg Roach if (!str_contains(self::NUMBERS, substr($temp, -2, 1))) { 880d5e02c3aSGreg Roach break; 881d5e02c3aSGreg Roach } 882d5e02c3aSGreg Roach 883d5e02c3aSGreg Roach $trailingID = substr($textSpan, $posLeftParen); 884d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, $posLeftParen); 885d5e02c3aSGreg Roach break; 886d5e02c3aSGreg Roach } 887d5e02c3aSGreg Roach 888d5e02c3aSGreg Roach // Look for " - " or blank preceding the ID number and remove it for inclusion in a separate LTR span 889d5e02c3aSGreg Roach if ($trailingID !== '') { 890d5e02c3aSGreg Roach while ($textSpan !== '') { 891d5e02c3aSGreg Roach if (substr($textSpan, -1) === ' ') { 892d5e02c3aSGreg Roach $trailingSeparator = ' ' . $trailingSeparator; 893d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -1); 894d5e02c3aSGreg Roach continue; 895d5e02c3aSGreg Roach } 896d5e02c3aSGreg Roach if (substr($textSpan, -6) === ' ') { 897d5e02c3aSGreg Roach $trailingSeparator = ' ' . $trailingSeparator; 898d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -6); 899d5e02c3aSGreg Roach continue; 900d5e02c3aSGreg Roach } 901d5e02c3aSGreg Roach if (substr($textSpan, -1) === '-') { 902d5e02c3aSGreg Roach $trailingSeparator = '-' . $trailingSeparator; 903d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -1); 904d5e02c3aSGreg Roach continue; 905d5e02c3aSGreg Roach } 906d5e02c3aSGreg Roach break; 907d5e02c3aSGreg Roach } 908d5e02c3aSGreg Roach } 909d5e02c3aSGreg Roach 910d5e02c3aSGreg Roach // Look for " - " preceding the text and remove it for inclusion in a separate LTR span 911d5e02c3aSGreg Roach $foundSeparator = false; 912d5e02c3aSGreg Roach $savedSpan = $textSpan; 913d5e02c3aSGreg Roach while ($textSpan !== '') { 914d5e02c3aSGreg Roach if (substr($textSpan, 0, 1) === ' ') { 915d5e02c3aSGreg Roach $leadingSeparator = ' ' . $leadingSeparator; 916d5e02c3aSGreg Roach $textSpan = substr($textSpan, 1); 917d5e02c3aSGreg Roach continue; 918d5e02c3aSGreg Roach } 919d5e02c3aSGreg Roach if (substr($textSpan, 0, 6) === ' ') { 920d5e02c3aSGreg Roach $leadingSeparator = ' ' . $leadingSeparator; 921d5e02c3aSGreg Roach $textSpan = substr($textSpan, 6); 922d5e02c3aSGreg Roach continue; 923d5e02c3aSGreg Roach } 924d5e02c3aSGreg Roach if (substr($textSpan, 0, 1) === '-') { 925d5e02c3aSGreg Roach $leadingSeparator = '-' . $leadingSeparator; 926d5e02c3aSGreg Roach $textSpan = substr($textSpan, 1); 927d5e02c3aSGreg Roach $foundSeparator = true; 928d5e02c3aSGreg Roach continue; 929d5e02c3aSGreg Roach } 930d5e02c3aSGreg Roach break; 931d5e02c3aSGreg Roach } 932d5e02c3aSGreg Roach if (!$foundSeparator) { 933d5e02c3aSGreg Roach $textSpan = $savedSpan; 934d5e02c3aSGreg Roach $leadingSeparator = ''; 935d5e02c3aSGreg Roach } 936d5e02c3aSGreg Roach break; 937d5e02c3aSGreg Roach } 938d5e02c3aSGreg Roach 939d5e02c3aSGreg Roach // We're done: finish the span 940d5e02c3aSGreg Roach $textSpan = self::starredName($textSpan, 'LTR'); // Wrap starred name in <u> and </u> tags 941d5e02c3aSGreg Roach while (true) { 942d5e02c3aSGreg Roach // Remove blanks that precede <LTRbr> 943d5e02c3aSGreg Roach if (str_contains($textSpan, ' <LTRbr>')) { 944d5e02c3aSGreg Roach $textSpan = str_replace(' <LTRbr>', '<LTRbr>', $textSpan); 945d5e02c3aSGreg Roach continue; 946d5e02c3aSGreg Roach } 947d5e02c3aSGreg Roach if (str_contains($textSpan, ' <LTRbr>')) { 948d5e02c3aSGreg Roach $textSpan = str_replace(' <LTRbr>', '<LTRbr>', $textSpan); 949d5e02c3aSGreg Roach continue; 950d5e02c3aSGreg Roach } 951d5e02c3aSGreg Roach break; 952d5e02c3aSGreg Roach } 953d5e02c3aSGreg Roach if ($leadingSeparator !== '') { 954d5e02c3aSGreg Roach $result .= self::START_LTR . $leadingSeparator . self::END_LTR; 955d5e02c3aSGreg Roach } 956d5e02c3aSGreg Roach $result .= $textSpan . self::END_LTR; 957d5e02c3aSGreg Roach if ($trailingSeparator !== '') { 958d5e02c3aSGreg Roach $result .= self::START_LTR . $trailingSeparator . self::END_LTR; 959d5e02c3aSGreg Roach } 960d5e02c3aSGreg Roach if ($trailingID !== '') { 961d5e02c3aSGreg Roach $result .= self::START_LTR . $trailingID . self::END_LTR; 962d5e02c3aSGreg Roach } 963d5e02c3aSGreg Roach if ($trailingPunctuation !== '') { 964d5e02c3aSGreg Roach $result .= self::START_LTR . $trailingPunctuation . self::END_LTR; 965d5e02c3aSGreg Roach } 966d5e02c3aSGreg Roach if ($trailingBlanks !== '') { 967d5e02c3aSGreg Roach $result .= self::START_LTR . $trailingBlanks . self::END_LTR; 968d5e02c3aSGreg Roach } 969d5e02c3aSGreg Roach } 970d5e02c3aSGreg Roach 971d5e02c3aSGreg Roach /* ****************************** RTL text handling ******************************** */ 972d5e02c3aSGreg Roach 973d5e02c3aSGreg Roach if (self::$currentState === 'RTL') { 974d5e02c3aSGreg Roach $savedSpan = $textSpan; 975d5e02c3aSGreg Roach 976d5e02c3aSGreg Roach // Move any trailing <br>, optionally followed by blanks, outside this RTL span 977d5e02c3aSGreg Roach while ($textSpan !== '') { 978d5e02c3aSGreg Roach if (substr($textSpan, -1) === ' ') { 979d5e02c3aSGreg Roach $trailingBlanks = ' ' . $trailingBlanks; 980d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -1); 981d5e02c3aSGreg Roach continue; 982d5e02c3aSGreg Roach } 983d5e02c3aSGreg Roach if (substr('......' . $textSpan, -6) === ' ') { 984d5e02c3aSGreg Roach $trailingBlanks = ' ' . $trailingBlanks; 985d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -6); 986d5e02c3aSGreg Roach continue; 987d5e02c3aSGreg Roach } 988d5e02c3aSGreg Roach break; 989d5e02c3aSGreg Roach } 990d5e02c3aSGreg Roach while (substr($textSpan, -7) === '<RTLbr>') { 991d5e02c3aSGreg Roach $trailingBreaks = '<br>' . $trailingBreaks; // Plain <br> because it’s outside a span 992d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -7); 993d5e02c3aSGreg Roach } 994d5e02c3aSGreg Roach if ($trailingBreaks !== '') { 995d5e02c3aSGreg Roach self::$waitingText = $trailingBlanks . self::$waitingText; // Put those trailing blanks inside the following span 996d5e02c3aSGreg Roach } else { 997d5e02c3aSGreg Roach $textSpan = $savedSpan; 998d5e02c3aSGreg Roach } 999d5e02c3aSGreg Roach 1000d5e02c3aSGreg Roach // Move trailing numeric strings to the following LTR text. Include any blanks preceding or following the numeric text too. 1001d5e02c3aSGreg Roach if (!$theEnd && I18N::direction() !== 'rtl') { 1002d5e02c3aSGreg Roach $trailingString = ''; 1003d5e02c3aSGreg Roach $savedSpan = $textSpan; 1004d5e02c3aSGreg Roach while ($textSpan !== '') { 1005d5e02c3aSGreg Roach // Look for trailing spaces and tentatively move them 1006d5e02c3aSGreg Roach if (substr($textSpan, -1) === ' ') { 1007d5e02c3aSGreg Roach $trailingString = ' ' . $trailingString; 1008d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -1); 1009d5e02c3aSGreg Roach continue; 1010d5e02c3aSGreg Roach } 1011d5e02c3aSGreg Roach if (substr($textSpan, -6) === ' ') { 1012d5e02c3aSGreg Roach $trailingString = ' ' . $trailingString; 1013d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -1); 1014d5e02c3aSGreg Roach continue; 1015d5e02c3aSGreg Roach } 1016d5e02c3aSGreg Roach if (substr($textSpan, -3) !== self::UTF8_PDF) { 1017d5e02c3aSGreg Roach // There is no trailing numeric string 1018d5e02c3aSGreg Roach $textSpan = $savedSpan; 1019d5e02c3aSGreg Roach break; 1020d5e02c3aSGreg Roach } 1021d5e02c3aSGreg Roach 1022d5e02c3aSGreg Roach // We have a numeric string 1023d5e02c3aSGreg Roach $posStartNumber = strrpos($textSpan, self::UTF8_LRE); 1024d5e02c3aSGreg Roach if ($posStartNumber === false) { 1025d5e02c3aSGreg Roach $posStartNumber = 0; 1026d5e02c3aSGreg Roach } 1027d5e02c3aSGreg Roach $trailingString = substr($textSpan, $posStartNumber) . $trailingString; 1028d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, $posStartNumber); 1029d5e02c3aSGreg Roach 1030d5e02c3aSGreg Roach // Look for more spaces and move them too 1031d5e02c3aSGreg Roach while ($textSpan !== '') { 1032d5e02c3aSGreg Roach if (substr($textSpan, -1) === ' ') { 1033d5e02c3aSGreg Roach $trailingString = ' ' . $trailingString; 1034d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -1); 1035d5e02c3aSGreg Roach continue; 1036d5e02c3aSGreg Roach } 1037d5e02c3aSGreg Roach if (substr($textSpan, -6) === ' ') { 1038d5e02c3aSGreg Roach $trailingString = ' ' . $trailingString; 1039d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -1); 1040d5e02c3aSGreg Roach continue; 1041d5e02c3aSGreg Roach } 1042d5e02c3aSGreg Roach break; 1043d5e02c3aSGreg Roach } 1044d5e02c3aSGreg Roach 1045d5e02c3aSGreg Roach self::$waitingText = $trailingString . self::$waitingText; 1046d5e02c3aSGreg Roach break; 1047d5e02c3aSGreg Roach } 1048d5e02c3aSGreg Roach } 1049d5e02c3aSGreg Roach 1050d5e02c3aSGreg Roach // Trailing " - " needs to be prefixed to the following span 1051d5e02c3aSGreg Roach if (!$theEnd && substr('...' . $textSpan, -3) === ' - ') { 1052d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -3); 1053d5e02c3aSGreg Roach self::$waitingText = ' - ' . self::$waitingText; 1054d5e02c3aSGreg Roach } 1055d5e02c3aSGreg Roach 1056d5e02c3aSGreg Roach while (I18N::direction() === 'rtl') { 1057d5e02c3aSGreg Roach // Look for " - " preceding <RTLbr> and relocate it to the front of the string 1058d5e02c3aSGreg Roach $posDashString = strpos($textSpan, ' - <RTLbr>'); 1059d5e02c3aSGreg Roach if ($posDashString === false) { 1060d5e02c3aSGreg Roach break; 1061d5e02c3aSGreg Roach } 1062d5e02c3aSGreg Roach $posStringStart = strrpos(substr($textSpan, 0, $posDashString), '<RTLbr>'); 1063d5e02c3aSGreg Roach if ($posStringStart === false) { 1064d5e02c3aSGreg Roach $posStringStart = 0; 1065d5e02c3aSGreg Roach } else { 1066d5e02c3aSGreg Roach $posStringStart += 9; 1067d5e02c3aSGreg Roach } // Point to the first char following the last <RTLbr> 1068d5e02c3aSGreg Roach 1069d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, $posStringStart) . ' - ' . substr($textSpan, $posStringStart, $posDashString - $posStringStart) . substr($textSpan, $posDashString + 3); 1070d5e02c3aSGreg Roach } 1071d5e02c3aSGreg Roach 1072d5e02c3aSGreg Roach // Strip leading spaces from the RTL text 1073d5e02c3aSGreg Roach $countLeadingSpaces = 0; 1074d5e02c3aSGreg Roach while ($textSpan !== '') { 1075d5e02c3aSGreg Roach if (substr($textSpan, 0, 1) === ' ') { 1076d5e02c3aSGreg Roach $countLeadingSpaces++; 1077d5e02c3aSGreg Roach $textSpan = substr($textSpan, 1); 1078d5e02c3aSGreg Roach continue; 1079d5e02c3aSGreg Roach } 1080d5e02c3aSGreg Roach if (substr($textSpan, 0, 6) === ' ') { 1081d5e02c3aSGreg Roach $countLeadingSpaces++; 1082d5e02c3aSGreg Roach $textSpan = substr($textSpan, 6); 1083d5e02c3aSGreg Roach continue; 1084d5e02c3aSGreg Roach } 1085d5e02c3aSGreg Roach break; 1086d5e02c3aSGreg Roach } 1087d5e02c3aSGreg Roach 1088d5e02c3aSGreg Roach // Strip trailing spaces from the RTL text 1089d5e02c3aSGreg Roach $countTrailingSpaces = 0; 1090d5e02c3aSGreg Roach while ($textSpan !== '') { 1091d5e02c3aSGreg Roach if (substr($textSpan, -1) === ' ') { 1092d5e02c3aSGreg Roach $countTrailingSpaces++; 1093d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -1); 1094d5e02c3aSGreg Roach continue; 1095d5e02c3aSGreg Roach } 1096d5e02c3aSGreg Roach if (substr($textSpan, -6) === ' ') { 1097d5e02c3aSGreg Roach $countTrailingSpaces++; 1098d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, -6); 1099d5e02c3aSGreg Roach continue; 1100d5e02c3aSGreg Roach } 1101d5e02c3aSGreg Roach break; 1102d5e02c3aSGreg Roach } 1103d5e02c3aSGreg Roach 1104d5e02c3aSGreg Roach // Look for trailing " -", reverse it, and relocate it to the front of the string 1105d5e02c3aSGreg Roach if (substr($textSpan, -2) === ' -') { 1106d5e02c3aSGreg Roach $posDashString = strlen($textSpan) - 2; 1107d5e02c3aSGreg Roach $posStringStart = strrpos(substr($textSpan, 0, $posDashString), '<RTLbr>'); 1108d5e02c3aSGreg Roach if ($posStringStart === false) { 1109d5e02c3aSGreg Roach $posStringStart = 0; 1110d5e02c3aSGreg Roach } else { 1111d5e02c3aSGreg Roach $posStringStart += 9; 1112d5e02c3aSGreg Roach } // Point to the first char following the last <RTLbr> 1113d5e02c3aSGreg Roach 1114d5e02c3aSGreg Roach $textSpan = substr($textSpan, 0, $posStringStart) . '- ' . substr($textSpan, $posStringStart, $posDashString - $posStringStart) . substr($textSpan, $posDashString + 2); 1115d5e02c3aSGreg Roach } 1116d5e02c3aSGreg Roach 1117d5e02c3aSGreg Roach if ($countLeadingSpaces !== 0) { 1118d5e02c3aSGreg Roach $newLength = strlen($textSpan) + $countLeadingSpaces; 1119d5e02c3aSGreg Roach $textSpan = str_pad($textSpan, $newLength, ' ', I18N::direction() === 'rtl' ? STR_PAD_LEFT : STR_PAD_RIGHT); 1120d5e02c3aSGreg Roach } 1121d5e02c3aSGreg Roach if ($countTrailingSpaces !== 0) { 1122d5e02c3aSGreg Roach if (I18N::direction() === 'ltr') { 1123d5e02c3aSGreg Roach if ($trailingBreaks === '') { 1124d5e02c3aSGreg Roach // Move trailing RTL spaces to front of following LTR span 1125d5e02c3aSGreg Roach $newLength = strlen(self::$waitingText) + $countTrailingSpaces; 1126d5e02c3aSGreg Roach self::$waitingText = str_pad(self::$waitingText, $newLength, ' ', STR_PAD_LEFT); 1127d5e02c3aSGreg Roach } 1128d5e02c3aSGreg Roach } else { 1129d5e02c3aSGreg Roach $newLength = strlen($textSpan) + $countTrailingSpaces; 1130d5e02c3aSGreg Roach $textSpan = str_pad($textSpan, $newLength); 1131d5e02c3aSGreg Roach } 1132d5e02c3aSGreg Roach } 1133d5e02c3aSGreg Roach 1134d5e02c3aSGreg Roach // We're done: finish the span 1135d5e02c3aSGreg Roach $textSpan = self::starredName($textSpan, 'RTL'); // Wrap starred name in <u> and </u> tags 1136d5e02c3aSGreg Roach $result .= $textSpan . self::END_RTL; 1137d5e02c3aSGreg Roach } 1138d5e02c3aSGreg Roach 1139d5e02c3aSGreg Roach if (self::$currentState !== 'LTR' && self::$currentState !== 'RTL') { 1140d5e02c3aSGreg Roach $result .= $textSpan; 1141d5e02c3aSGreg Roach } 1142d5e02c3aSGreg Roach 1143d5e02c3aSGreg Roach $result .= $trailingBreaks; // Get rid of any waiting <br> 1144d5e02c3aSGreg Roach } 1145d5e02c3aSGreg Roach} 1146