<?php
// includes/arabic_pdf_shaper.php
// Liftek Iraq — Arabic text reshaping for Dompdf-rendered PDF output only.
//
// WHY THIS EXISTS:
// Dompdf does not implement a real text-shaping engine (no HarfHarfBuzz-
// style contextual shaping, no Unicode Bidi Algorithm). It draws whatever
// codepoints it is given, left-to-right, one isolated glyph at a time. A
// browser's native text stack does all of this automatically, which is why
// the exact same Arabic string looks correct in the browser View but comes
// out disconnected and in the wrong (mirrored) order in the PDF — this is
// the root cause behind "الكلمات لا تضهر بصورة كاملة" (words don't render
// whole) and the reversed word order seen in the footer band.
//
// FIX: pre-process Arabic text in PHP, before it ever reaches Dompdf, into
// the exact sequence of PRESENTATION-FORM glyphs (contextual initial /
// medial / final / isolated shapes, with Lam-Alef ligatures) already laid
// out in final left-to-right VISUAL order. Dompdf then just prints these
// glyphs verbatim, left to right, and — because they are already the
// correct connected shapes in the correct order — the result looks right
// without Dompdf needing to understand Arabic at all.
//
// SCOPE: this is a simplified two-level (Arabic-RTL / other-LTR) reordering
// — it is not a full Unicode Bidi Algorithm implementation. It is designed
// for short certificate/footer strings (names, taglines, ISO references)
// which is everything this system ever prints in Arabic. It is applied
// ONLY in PDF mode; the browser View is left untouched (it already renders
// correctly natively) — applying this to HTML view output would double-
// reverse the text and break it.

if (!defined('ARABIC_PDF_SHAPER_LOADED')) {
    define('ARABIC_PDF_SHAPER_LOADED', true);

    /**
     * Joining behaviour of each base Arabic letter:
     *   'D' = dual-joining  (connects to both the previous AND next letter)
     *   'R' = right-joining (connects only FROM the previous letter; never
     *         extends a connection onward to the next letter)
     *   'U' = non-joining   (never connects on either side — hamza alone)
     * This is standard Unicode Arabic Shaping behaviour (ArabicShaping.txt).
     */
    function _arabicJoiningType(string $ch): string
    {
        static $rightJoining = [
            "\u{0622}", "\u{0623}", "\u{0624}", "\u{0625}", "\u{0627}", // آ أ ؤ إ ا
            "\u{062F}", "\u{0630}", "\u{0631}", "\u{0632}",             // د ذ ر ز
            "\u{0648}", "\u{0649}", "\u{0629}",                         // و ى ة
        ];
        static $dualJoining = [
            "\u{0626}", "\u{0628}", "\u{062A}", "\u{062B}", "\u{062C}", // ئ ب ت ث ج
            "\u{062D}", "\u{062E}", "\u{0633}", "\u{0634}", "\u{0635}", // ح خ س ش ص
            "\u{0636}", "\u{0637}", "\u{0638}", "\u{0639}", "\u{063A}", // ض ط ظ ع غ
            "\u{0641}", "\u{0642}", "\u{0643}", "\u{0644}", "\u{0645}", // ف ق ك ل م
            "\u{0646}", "\u{0647}", "\u{064A}",                         // ن ه ي
        ];
        if (in_array($ch, $dualJoining, true)) return 'D';
        if (in_array($ch, $rightJoining, true)) return 'R';
        return 'U'; // hamza (U+0621) and anything else non-Arabic-joining
    }

    /** Arabic combining diacritics (harakat) — transparent to joining context. */
    function _isArabicDiacritic(string $ch): bool
    {
        $cp = mb_ord($ch, 'UTF-8');
        return $cp !== false && $cp >= 0x064B && $cp <= 0x0652;
    }

    function _isArabicLetter(string $ch): bool
    {
        $cp = mb_ord($ch, 'UTF-8');
        return $cp !== false && $cp >= 0x0600 && $cp <= 0x06FF && !_isArabicDiacritic($ch);
    }

    /**
     * Presentation-form codepoints per base letter: [isolated, final, initial, medial].
     * R-joining and U-joining letters only ever use slots 0 (isolated) and 1 (final).
     */
    function _arabicPresentationForms(string $ch): array
    {
        static $map = [
            "\u{0621}" => ["\u{FE80}", "\u{FE80}", "\u{FE80}", "\u{FE80}"], // ء hamza (isolated only)
            "\u{0622}" => ["\u{FE81}", "\u{FE82}", "\u{FE81}", "\u{FE82}"], // آ
            "\u{0623}" => ["\u{FE83}", "\u{FE84}", "\u{FE83}", "\u{FE84}"], // أ
            "\u{0624}" => ["\u{FE85}", "\u{FE86}", "\u{FE85}", "\u{FE86}"], // ؤ
            "\u{0625}" => ["\u{FE87}", "\u{FE88}", "\u{FE87}", "\u{FE88}"], // إ
            "\u{0626}" => ["\u{FE89}", "\u{FE8A}", "\u{FE8B}", "\u{FE8C}"], // ئ
            "\u{0627}" => ["\u{FE8D}", "\u{FE8E}", "\u{FE8D}", "\u{FE8E}"], // ا
            "\u{0628}" => ["\u{FE8F}", "\u{FE90}", "\u{FE91}", "\u{FE92}"], // ب
            "\u{0629}" => ["\u{FE93}", "\u{FE94}", "\u{FE93}", "\u{FE94}"], // ة
            "\u{062A}" => ["\u{FE95}", "\u{FE96}", "\u{FE97}", "\u{FE98}"], // ت
            "\u{062B}" => ["\u{FE99}", "\u{FE9A}", "\u{FE9B}", "\u{FE9C}"], // ث
            "\u{062C}" => ["\u{FE9D}", "\u{FE9E}", "\u{FE9F}", "\u{FEA0}"], // ج
            "\u{062D}" => ["\u{FEA1}", "\u{FEA2}", "\u{FEA3}", "\u{FEA4}"], // ح
            "\u{062E}" => ["\u{FEA5}", "\u{FEA6}", "\u{FEA7}", "\u{FEA8}"], // خ
            "\u{062F}" => ["\u{FEA9}", "\u{FEAA}", "\u{FEA9}", "\u{FEAA}"], // د
            "\u{0630}" => ["\u{FEAB}", "\u{FEAC}", "\u{FEAB}", "\u{FEAC}"], // ذ
            "\u{0631}" => ["\u{FEAD}", "\u{FEAE}", "\u{FEAD}", "\u{FEAE}"], // ر
            "\u{0632}" => ["\u{FEAF}", "\u{FEB0}", "\u{FEAF}", "\u{FEB0}"], // ز
            "\u{0633}" => ["\u{FEB1}", "\u{FEB2}", "\u{FEB3}", "\u{FEB4}"], // س
            "\u{0634}" => ["\u{FEB5}", "\u{FEB6}", "\u{FEB7}", "\u{FEB8}"], // ش
            "\u{0635}" => ["\u{FEB9}", "\u{FEBA}", "\u{FEBB}", "\u{FEBC}"], // ص
            "\u{0636}" => ["\u{FEBD}", "\u{FEBE}", "\u{FEBF}", "\u{FEC0}"], // ض
            "\u{0637}" => ["\u{FEC1}", "\u{FEC2}", "\u{FEC3}", "\u{FEC4}"], // ط
            "\u{0638}" => ["\u{FEC5}", "\u{FEC6}", "\u{FEC7}", "\u{FEC8}"], // ظ
            "\u{0639}" => ["\u{FEC9}", "\u{FECA}", "\u{FECB}", "\u{FECC}"], // ع
            "\u{063A}" => ["\u{FECD}", "\u{FECE}", "\u{FECF}", "\u{FED0}"], // غ
            "\u{0641}" => ["\u{FED1}", "\u{FED2}", "\u{FED3}", "\u{FED4}"], // ف
            "\u{0642}" => ["\u{FED5}", "\u{FED6}", "\u{FED7}", "\u{FED8}"], // ق
            "\u{0643}" => ["\u{FED9}", "\u{FEDA}", "\u{FEDB}", "\u{FEDC}"], // ك
            "\u{0644}" => ["\u{FEDD}", "\u{FEDE}", "\u{FEDF}", "\u{FEE0}"], // ل
            "\u{0645}" => ["\u{FEE1}", "\u{FEE2}", "\u{FEE3}", "\u{FEE4}"], // م
            "\u{0646}" => ["\u{FEE5}", "\u{FEE6}", "\u{FEE7}", "\u{FEE8}"], // ن
            "\u{0647}" => ["\u{FEE9}", "\u{FEEA}", "\u{FEEB}", "\u{FEEC}"], // ه
            "\u{0648}" => ["\u{FEED}", "\u{FEEE}", "\u{FEED}", "\u{FEEE}"], // و
            "\u{0649}" => ["\u{FEEF}", "\u{FEF0}", "\u{FEEF}", "\u{FEF0}"], // ى
            "\u{064A}" => ["\u{FEF1}", "\u{FEF2}", "\u{FEF3}", "\u{FEF4}"], // ي
        ];
        return $map[$ch] ?? [$ch, $ch, $ch, $ch]; // unknown Arabic-block char: pass through unshaped
    }

    /** Lam-Alef ligature forms: [isolated, final] keyed by the Alef variant that follows LAM. */
    function _lamAlefLigature(string $alefChar): ?array
    {
        static $map = [
            "\u{0622}" => ["\u{FEF5}", "\u{FEF6}"], // لآ
            "\u{0623}" => ["\u{FEF7}", "\u{FEF8}"], // لأ
            "\u{0625}" => ["\u{FEF9}", "\u{FEFA}"], // لإ
            "\u{0627}" => ["\u{FEFB}", "\u{FEFC}"], // لا
        ];
        return $map[$alefChar] ?? null;
    }

    /**
     * Reshapes + visually reorders Arabic text for Dompdf's PDF output.
     * DO NOT use this on browser-View HTML — only in $pdf_mode.
     */
    function arabicShapeForPdf(string $text): string
    {
        if ($text === '') return $text;

        // Split into an array of single Unicode characters (multibyte-safe).
        $chars = preg_split('//u', $text, -1, PREG_SPLIT_NO_EMPTY);
        if ($chars === false) return $text; // malformed UTF-8: leave untouched, never fatal

        // --- Pass 1: collapse LAM + (Alef variant) into a single ligature "unit". ---
        $units = []; // each unit: ['base' => single-char-or-null-for-ligature, 'ligature' => [iso,final]|null, 'raw' => original char(s), 'type' => D/R/U]
        $n = count($chars);
        for ($i = 0; $i < $n; $i++) {
            $ch = $chars[$i];
            if ($ch === "\u{0644}" && $i + 1 < $n) { // LAM
                $lig = _lamAlefLigature($chars[$i + 1]);
                if ($lig !== null) {
                    $units[] = ['ligature' => $lig, 'type' => 'R', 'raw' => $ch . $chars[$i + 1]];
                    $i++; // consume the Alef too
                    continue;
                }
            }
            $units[] = ['ligature' => null, 'type' => _isArabicLetter($ch) ? _arabicJoiningType($ch) : null, 'raw' => $ch];
        }

        // --- Pass 2: contextual shaping (skip diacritics for context, keep them in place). ---
        $shaped = [];
        $count = count($units);
        for ($i = 0; $i < $count; $i++) {
            $u = $units[$i];
            $raw = $u['raw'];

            if (_isArabicDiacritic($raw)) { $shaped[] = $raw; continue; }

            if ($u['type'] === null) { $shaped[] = $raw; continue; } // non-Arabic char: unchanged

            // Find previous / next non-diacritic unit for joining context.
            $prevType = null;
            for ($p = $i - 1; $p >= 0; $p--) {
                if (_isArabicDiacritic($units[$p]['raw'])) continue;
                $prevType = $units[$p]['type'];
                break;
            }
            $nextType = null;
            for ($q = $i + 1; $q < $count; $q++) {
                if (_isArabicDiacritic($units[$q]['raw'])) continue;
                $nextType = $units[$q]['type'];
                break;
            }

            $joinsPrev = ($prevType === 'D');
            $joinsNext = ($u['type'] === 'D') && ($nextType === 'D' || $nextType === 'R');

            if ($u['ligature'] !== null) {
                // Ligature only has isolated/final forms (Alef never connects onward).
                $shaped[] = $joinsPrev ? $u['ligature'][1] : $u['ligature'][0];
                continue;
            }

            $forms = _arabicPresentationForms($raw); // [isolated, final, initial, medial]
            if ($joinsPrev && $joinsNext)      { $shaped[] = $forms[3]; }
            elseif ($joinsPrev && !$joinsNext) { $shaped[] = $forms[1]; }
            elseif (!$joinsPrev && $joinsNext) { $shaped[] = $forms[2]; }
            else                                { $shaped[] = $forms[0]; }
        }

        // --- Pass 3: simplified bidi — split into RTL (Arabic) vs LTR (other) runs,
        // reverse the character order within each Arabic run, keep non-Arabic runs
        // (numbers, Latin words) in their own original internal order, then output
        // the runs in reverse sequence (rightmost logical run first). ---
        $runs = [];
        $currentRun = '';
        $currentIsArabic = null;
        foreach ($shaped as $glyph) {
            $isArabicGlyph = (bool) preg_match('/[\x{0590}-\x{08FF}\x{FB50}-\x{FEFF}]/u', $glyph);
            if ($currentIsArabic === null) { $currentIsArabic = $isArabicGlyph; }
            if ($isArabicGlyph !== $currentIsArabic && trim($glyph) !== '') {
                $runs[] = ['text' => $currentRun, 'arabic' => $currentIsArabic];
                $currentRun = '';
                $currentIsArabic = $isArabicGlyph;
            }
            $currentRun .= $glyph;
        }
        if ($currentRun !== '') { $runs[] = ['text' => $currentRun, 'arabic' => $currentIsArabic]; }

        $runs = array_reverse($runs);
        $out = '';
        foreach ($runs as $run) {
            if ($run['arabic']) {
                $runChars = preg_split('//u', $run['text'], -1, PREG_SPLIT_NO_EMPTY);
                $out .= implode('', array_reverse($runChars));
            } else {
                $out .= $run['text'];
            }
        }
        return $out;
    }
}
