generated from jric11/baseProject
Initial commit
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Devanagari.php
|
||||
*
|
||||
* @since 2026-04-30
|
||||
* @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)
|
||||
* @link https://github.com/tecnickcom/tc-lib-unicode
|
||||
*
|
||||
* This file is part of tc-lib-unicode software library.
|
||||
*/
|
||||
|
||||
namespace Com\Tecnick\Unicode\Substitution;
|
||||
|
||||
use Com\Tecnick\Unicode\Data\Devanagari as DevanagariData;
|
||||
|
||||
/**
|
||||
* Com\Tecnick\Unicode\Substitution\Devanagari
|
||||
*
|
||||
* Repositions Devanagari left-positional matras (vowel signs with Indic
|
||||
* Positional Category "Left", such as U+093F VOWEL SIGN I) to precede their
|
||||
* base consonant cluster in the codepoint array.
|
||||
*
|
||||
* In Unicode logical order a left matra is stored after the consonant (or
|
||||
* conjunct cluster) it modifies. For PDF glyph streams the matra glyph must
|
||||
* appear before the consonant, so each left matra is moved to immediately
|
||||
* before the cluster that precedes it.
|
||||
*
|
||||
* A consonant cluster is: base_consonant (VIRAMA base_consonant)*
|
||||
* Only base consonants in the range U+0915–U+0939 and U+0958–U+095F are
|
||||
* recognised as cluster heads. Orphaned matras (no preceding consonant) and
|
||||
* unknown codepoints are left unchanged.
|
||||
*
|
||||
* @since 2026-04-30
|
||||
* @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)
|
||||
* @link https://github.com/tecnickcom/tc-lib-unicode
|
||||
*/
|
||||
final class Devanagari
|
||||
{
|
||||
/**
|
||||
* Transformed codepoint array.
|
||||
*
|
||||
* @var list<int>
|
||||
*/
|
||||
private array $ordarr;
|
||||
|
||||
/**
|
||||
* @param array<int, int> $ordarr Array of Unicode codepoints.
|
||||
*/
|
||||
public function __construct(array $ordarr)
|
||||
{
|
||||
$this->ordarr = array_values($ordarr);
|
||||
$this->process();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transformed codepoint array.
|
||||
*
|
||||
* @return list<int>
|
||||
*/
|
||||
public function getOrdarr(): array
|
||||
{
|
||||
return $this->ordarr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over the codepoint array and repositions left matras that
|
||||
* follow a consonant cluster.
|
||||
*/
|
||||
private function process(): void
|
||||
{
|
||||
$len = count($this->ordarr);
|
||||
$result = [];
|
||||
$idx = 0;
|
||||
while ($idx < $len) {
|
||||
$cp = $this->ordarr[$idx] ?? null;
|
||||
if ($cp === null) {
|
||||
++$idx;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->isBaseConsonant($cp)) {
|
||||
$idx = $this->processConsonantCluster($idx, $len, $result);
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[] = $cp;
|
||||
++$idx;
|
||||
}
|
||||
|
||||
$this->ordarr = array_values($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the full conjunct cluster starting at $idx, then checks
|
||||
* whether the next codepoint is a left matra. If so, emits the matra
|
||||
* first and the cluster second; otherwise emits the cluster as-is.
|
||||
*
|
||||
* @param int $idx Current index in $this->ordarr.
|
||||
* @param int $len Length of $this->ordarr.
|
||||
* @param list<int> $result Result accumulator (passed by reference).
|
||||
*
|
||||
* @return int Updated index after all consumed codepoints.
|
||||
*/
|
||||
private function processConsonantCluster(int $idx, int $len, array &$result): int
|
||||
{
|
||||
$cluster = $this->collectCluster($idx, $len);
|
||||
$endIdx = $idx + count($cluster);
|
||||
|
||||
if ($endIdx < $len) {
|
||||
$matra = $this->ordarr[$endIdx] ?? null;
|
||||
if ($matra !== null && $this->isLeftMatra($matra)) {
|
||||
$result[] = $matra;
|
||||
foreach ($cluster as $codepoint) {
|
||||
$result[] = $codepoint;
|
||||
}
|
||||
|
||||
return $endIdx + 1;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($cluster as $codepoint) {
|
||||
$result[] = $codepoint;
|
||||
}
|
||||
|
||||
return $endIdx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the full conjunct cluster: base_consonant (VIRAMA base_consonant)*
|
||||
*
|
||||
* @param int $idx Starting index of the cluster head.
|
||||
* @param int $len Length of $this->ordarr.
|
||||
*
|
||||
* @return list<int> Collected cluster codepoints.
|
||||
*/
|
||||
private function collectCluster(int $idx, int $len): array
|
||||
{
|
||||
$first = $this->ordarr[$idx] ?? null;
|
||||
if ($first === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$cluster = [$first];
|
||||
$pos = $idx + 1;
|
||||
while (($pos + 1) < $len) {
|
||||
$virama = $this->ordarr[$pos] ?? null;
|
||||
$baseConsonant = $this->ordarr[$pos + 1] ?? null;
|
||||
assert($baseConsonant !== null, 'Expected Devanagari base consonant after virama candidate');
|
||||
if ($virama !== DevanagariData::VIRAMA || !$this->isBaseConsonant($baseConsonant)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$cluster[] = $virama;
|
||||
$cluster[] = $baseConsonant;
|
||||
$pos += 2;
|
||||
}
|
||||
|
||||
return $cluster;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when $codepoint is a Devanagari base consonant.
|
||||
*/
|
||||
private function isBaseConsonant(int $codepoint): bool
|
||||
{
|
||||
return $this->isInStandardRange($codepoint) || $this->isInExtendedRange($codepoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when $codepoint is in the standard consonant range
|
||||
* U+0915–U+0939.
|
||||
*/
|
||||
private function isInStandardRange(int $codepoint): bool
|
||||
{
|
||||
return $codepoint >= DevanagariData::BASE_CONSONANT_FIRST && $codepoint <= DevanagariData::BASE_CONSONANT_LAST;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when $codepoint is in the extended consonant range
|
||||
* U+0958–U+095F.
|
||||
*/
|
||||
private function isInExtendedRange(int $codepoint): bool
|
||||
{
|
||||
return (
|
||||
$codepoint >= DevanagariData::BASE_CONSONANT_EXT_FIRST
|
||||
&& $codepoint <= DevanagariData::BASE_CONSONANT_EXT_LAST
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when $codepoint is a Devanagari left matra.
|
||||
*/
|
||||
private function isLeftMatra(int $codepoint): bool
|
||||
{
|
||||
return array_key_exists($codepoint, DevanagariData::LEFT_MATRAS);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user