| Server IP : 139.59.63.204 / Your IP : 216.73.217.62 Web Server : Apache/2.4.58 (Ubuntu) System : Linux ubuntu-s-1vcpu-1gb-blr1-01 6.8.0-110-generic #110-Ubuntu SMP PREEMPT_DYNAMIC Thu Mar 19 15:09:20 UTC 2026 x86_64 User : root ( 0) PHP Version : 8.3.6 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/abyogasms.com/vendor/tecnickcom/tc-lib-unicode/src/Bidi/ |
Upload File : |
<?php
declare(strict_types=1);
/**
* StepN.php
*
* @since 2011-05-23
* @category Library
* @package Unicode
* @author Nicola Asuni <info@tecnick.com>
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
* @link https://github.com/tecnickcom/tc-lib-unicode
*
* This file is part of tc-lib-unicode software library.
*/
namespace Com\Tecnick\Unicode\Bidi;
use Com\Tecnick\Unicode\Data\Bracket as UniBracket;
/**
* Com\Tecnick\Unicode\Bidi\StepN
*
* @since 2015-07-13
* @category Library
* @package Unicode
* @author Nicola Asuni <info@tecnick.com>
* @copyright 2011-2026 Nicola Asuni - Tecnick.com LTD
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
* @link https://github.com/tecnickcom/tc-lib-unicode
*/
class StepN extends \Com\Tecnick\Unicode\Bidi\StepBase
{
/**
* List or bracket pairs positions
*
* @var array<int, int>
*/
protected array $brackets = [];
/**
* Stack used to store bracket positions
*
* @var array<int, array{int, int}>
*/
protected array $bstack = [];
/**
* @return array{char: int, i: int, level: int, otype: string, pdimatch: int, pos: int, type: string, x: int}
*/
private function getItem(int $idx): array
{
$item = $this->seq['item'][$idx] ?? null;
assert($item !== null, 'Expected StepN sequence item at the requested index');
return $item;
}
private function setItemType(int $idx, string $type): void
{
$item = $this->getItem($idx);
$item['type'] = $type;
$this->seq['item'][$idx] = $item;
}
/**
* Process N steps
* Resolving Neutral and Isolate Formatting Types
*
* Neutral and isolate formatting (i.e. NI) characters are resolved one isolating run sequence at a time.
* Its results are that all NIs become either R or L. Generally, NIs take on the direction of the surrounding text.
* In case of a conflict, they take on the embedding direction.
* At isolating run sequence boundaries where the type of the character on the other side of the boundary
* is required, the type assigned to sos or eos is used.
*
* Bracket pairs within an isolating run sequence are processed as units so that both the opening and the closing
* paired bracket in a pair resolve to the same direction. Note that this rule is applied based on the current
* bidirectional character type of each paired bracket and not the original type, as this could have changed under
* X6. The current bidirectional character type may also have changed under a previous iteration of the for loop in
* N0 in the case of nested bracket pairs.
*/
protected function process(): void
{
$this->processStep($this->getBracketPairs(...));
$this->processN0();
$this->processStep($this->processN1(...));
$this->processStep($this->processN2(...));
}
/**
* BD16. Find all bracket pairs
*/
protected function getBracketPairs(int $idx): void
{
$char = $this->getItem($idx)['char'];
if (array_key_exists($char, UniBracket::OPEN)) {
// process open bracket
if ($char === 0x3008) {
$char = 0x2329;
}
$this->bstack[] = [$idx, (int) $char];
return;
}
if (array_key_exists($char, UniBracket::CLOSE)) {
// process closign bracket
if ($char === 0x3009) {
$char = 0x232A;
}
// Find the matching opening bracket: scan the stack from the top and stop at the
// first (nearest) match, popping through and including the matched entry. Per BD16
// a closing bracket pairs with the nearest opener; without stopping here, multiple
// openers of the same type would be paired to the same closer and the stack emptied.
$tmpstack = $this->bstack;
while ($tmpstack !== []) {
$item = \array_pop($tmpstack);
$openBracket = UniBracket::OPEN[$item[1]] ?? null;
if ($openBracket !== null && $char === $openBracket) {
$this->brackets[$item[0]] = $idx;
$this->bstack = $tmpstack;
break;
}
}
}
}
/**
* Return the normalized chat type for the N0 step
* Within this scope, bidirectional types EN and AN are treated as R.
*
* @param string $type Char type
*/
protected function getN0Type(string $type): string
{
return $type === 'AN' || $type === 'EN' ? 'R' : $type;
}
/**
* N0. Process bracket pairs in an isolating run sequence sequentially in the logical order of the text positions
* of the opening paired brackets.
*/
protected function processN0(): void
{
// Sort the list of bracket pairs in ascending order based on the text position
// of the opening paired bracket (done once, after all pairs have been collected).
\ksort($this->brackets);
$odir = $this->seq['edir'] === 'L' ? 'R' : 'L';
// For each bracket-pair element in the list of pairs of text positions
foreach ($this->brackets as $open => $close) {
if (!$this->processInsideBrackets($open, $close, $odir)) {
continue;
}
for ($jdx = $open - 1; $jdx >= 0; --$jdx) {
$btype = $this->getN0Type($this->getItem($jdx)['type']);
if ($btype === $odir) {
// 1. If the preceding strong type is also opposite the embedding direction,
// context is established, so set the type for both brackets in the pair to that direction.
$this->setBracketsType($open, $close, $odir);
break;
}
if ($btype === $this->seq['edir']) {
// 2. Otherwise set the type for both brackets in the pair to the embedding direction.
$this->setBracketsType($open, $close, $this->seq['edir']);
break;
}
}
if ($jdx < 0) {
$this->setBracketsType($open, $close, $this->seq['sos']);
}
// d. Otherwise, there are no strong types within the bracket pair. Therefore, do not set the type for that
// bracket pair. Note that if the enclosed text contains no strong types the bracket pairs will both
// resolve to the same level when resolved individually using rules N1 and N2.
}
}
/**
* Inspect the bidirectional types of the characters enclosed within the bracket pair.
*
* @param int $open Open bracket entry
* @param int $close Close bracket entry
* @param string $odir Opposite direction (L or R)
*
* @return bool True if type has not been found
*/
protected function processInsideBrackets(int $open, int $close, string $odir): bool
{
$opposite = false;
// a. Inspect the bidirectional types of the characters enclosed within the bracket pair.
for ($jdx = $open + 1; $jdx < $close; ++$jdx) {
$btype = $this->getN0Type($this->getItem($jdx)['type']);
// b. If any strong type (either L or R) matching the embedding direction is found,
// set the type for both brackets in the pair to match the embedding direction.
if ($btype === $this->seq['edir']) {
$this->setBracketsType($open, $close, $this->seq['edir']);
break;
}
if ($btype === $odir) {
// c. Otherwise, if there is a strong type it must be opposite the embedding direction.
$opposite = true;
}
}
// Therefore, test for an established context with a preceding strong type by checking backwards before
// the opening paired bracket until the first strong type (L, R, or sos) is found.
return $jdx === $close && $opposite;
}
/**
* Set the brackets type
*
* @param int $open Open bracket entry
* @param int $close Close bracket entry
* @param string $type Type
*/
protected function setBracketsType(int $open, int $close, string $type): void
{
$this->setItemType($open, $type);
$this->setItemType($close, $type);
// Any number of characters that had original bidirectional character type NSM
// prior to the application of W1 that immediately follow a paired bracket which
// changed to L or R under N0 should change to match the type of their preceding bracket.
$next = $close + 1;
while ($next < $this->seq['length']) {
$item = $this->getItem($next);
if ($item['otype'] !== 'NSM') {
break;
}
$this->setItemType($next, $type);
++$next;
}
}
/**
* N1. A sequence of NIs takes the direction of the surrounding strong text if the text on both sides has the same
* direction. European and Arabic numbers act as if they were R in terms of their influence on NIs.
* The start-of-sequence (sos) and end-of-sequence (eos) types are used at isolating run sequence boundaries.
*
* @param int $idx Current character position
*/
protected function processN1(int $idx): void
{
if ($this->getItem($idx)['type'] !== 'NI') {
return;
}
$bdx = $idx - 1;
$prev = $this->processN1prev($bdx);
if ($prev === '') {
return;
}
$jdx = $this->getNextN1Char($idx);
$next = $this->processN1next($jdx);
if ($next === '') {
return;
}
if ($next === $prev) {
for ($bdx = $idx; $bdx < $jdx && $bdx < $this->seq['length']; ++$bdx) {
$this->setItemType($bdx, $next);
}
}
}
/**
* Get the next direction
*
* @param int $bdx Position of the preceding character
*
* @return string Previous position
*/
protected function processN1prev(int &$bdx): string
{
if ($bdx < 0) {
$bdx = 0;
return $this->seq['sos'];
}
$item = $this->getItem($bdx);
if (\in_array($item['type'], ['R', 'AN', 'EN'], true)) {
return 'R';
}
if ($item['type'] === 'L') {
return 'L';
}
return '';
}
/**
* Get the next direction
*
* @param int $jdx Position of the next character
*
* @return string Previous position
*/
protected function processN1next(int &$jdx): string
{
if ($jdx >= $this->seq['length']) {
$jdx = $this->seq['length'];
return $this->seq['eos'];
}
$item = $this->getItem($jdx);
if (\in_array($item['type'], ['R', 'AN', 'EN'], true)) {
return 'R';
}
if ($item['type'] === 'L') {
return 'L';
}
return '';
}
/**
* Return the index of the next valid char for N1
*
* @param int $idx Start index
*/
protected function getNextN1Char(int $idx): int
{
$jdx = $idx + 1;
while ($jdx < $this->seq['length'] && $this->getItem($jdx)['type'] === 'NI') {
++$jdx;
}
return $jdx;
}
/**
* N2. Any remaining NIs take the embedding direction.
*
* @param int $idx Current character position
*/
protected function processN2(int $idx): void
{
if ($this->getItem($idx)['type'] === 'NI') {
$this->setItemType($idx, $this->seq['edir']);
}
}
}