Skip to content

Instantly share code, notes, and snippets.

@dtinth
Created November 26, 2010 14:59
Show Gist options
  • Save dtinth/716814 to your computer and use it in GitHub Desktop.
Save dtinth/716814 to your computer and use it in GitHub Desktop.
Utility function to fix floating tone marks when outputting as PDF.
<?php
function thai($x) {
$back = array(
"\xE0\xB9\x88" => "\xEF\x9C\x85",
"\xE0\xB9\x89" => "\xEF\x9C\x86",
"\xE0\xB9\x8A" => "\xEF\x9C\x87",
"\xE0\xB9\x8B" => "\xEF\x9C\x88",
"\xE0\xB9\x8C" => "\xEF\x9C\x89"
);
$cross = array();
foreach (array("\xE0\xB8\xB4", "\xE0\xB8\xB5", "\xE0\xB8\xB6", "\xE0\xB8\xB7") as $p) {
for ($i = 0x85; $i <= 0x89; $i ++) {
$from = $p . "\xEF\x9C" . chr($i);
$to = $p . "\xE0\xB9" . chr($i + 3);
$cross[$from] = $to;
}
}
$x = strtr($x, $back);
$x = strtr($x, $cross);
return $x;
}
@lex0000
Copy link

lex0000 commented Dec 26, 2014

function thai($x) {
$back = array(
"\xE0\xB9\x88" => "\xEF\x9C\x85", //่(ไม้เอก)=>
"\xE0\xB9\x89" => "\xEF\x9C\x86", //้(ไม้โทร)=>
"\xE0\xB9\x8A" => "\xEF\x9C\x87", //๊(ไม้ตรี)=>
"\xE0\xB9\x8B" => "\xEF\x9C\x88", //+(ไม้จัตวา)=>
"\xE0\xB9\x8C" => "\xEF\x9C\x89" //์(ตัวการัน)=>
);
$cross = array();
$payanchana=array(
"\xE0\xB8\xB4", //สระอิ
"\xE0\xB8\xB5", //สระอี
"\xE0\xB8\xB6", //สระอึ
"\xE0\xB8\xB7", //สระอือ
"\xE0\xB8\xB1", //ไม้หันอากาศ
"ำ" //สระอำ \xE0\xB8\xB3
);
//http://www.endmemo.com/unicode/thai.php อ้างอิง unicode
foreach ($payanchana as $p) {

        if($p!='ำ'){
    for ($i = 0x85; $i <= 0x89; $i ++) {
        $from = $p . "\xEF\x9C" . chr($i);
        $to   = $p . "\xE0\xB9" . chr($i +3);
        $cross[$from] = $to;

    }
        }else{
            for ($i = 0x85; $i <= 0x89; $i ++) {
        $from = "\xEF\x9C" . chr($i).$p;
        $to   = "\xE0\xB9" . chr($i +3).$p;
        $cross[$from] = $to;
    }
        }

}
    $x = strtr($x, $back);
    $x = strtr($x, $cross);


return $x;

}

@ton212
Copy link

ton212 commented Sep 27, 2018

ปรับให้วรรณยุกต์ที่ซ้อนอยู่บน อิ อี อึ อือ และไม้หันอากาศ อยู่ชิดขวาของตัวอักษร ให้เป็นธรรมชาติมากขึ้น

function thai($x)
{
    $back = array(
        "\xE0\xB9\x88" => "\xEF\x9C\x8A", // ไม้เอก
        "\xE0\xB9\x89" => "\xEF\x9C\x8B", // ไม้โท
        "\xE0\xB9\x8A" => "\xEF\x9C\x8C", // ไม้ตรี
        "\xE0\xB9\x8B" => "\xEF\x9C\x8D", // ไม้จัตวา
        "\xE0\xB9\x8C" => "\xEF\x9C\x8E" // ตัวการันต์
    );
    $cross = array();

    $vowels = array(
        "\xE0\xB8\xB4", // สระอิ
        "\xE0\xB8\xB5", // สระอี
        "\xE0\xB8\xB6", // สระอึ
        "\xE0\xB8\xB7", // สระอือ
        "\xE0\xB8\xB1", // ไม้หันอากาศ
        "" //สระอำ \xE0\xB8\xB3
    );
    //http://www.endmemo.com/unicode/thai.php อ้างอิง unicode

    foreach ($vowels as $p) {

        if ($p != '') {
            for ($i = 0x8A; $i <= 0x8E; $i++) {
                $from = $p . "\xEF\x9C" . chr($i);
                $to = $p . "\xE0\xB9" . chr($i - 2);
                $cross[$from] = $to;

            }
        } else {
            for ($i = 0x8A; $i <= 0x8E; $i++) {
                $from = "\xEF\x9C" . chr($i) . $p;
                $to = "\xE0\xB9" . chr($i - 2) . $p;
                $cross[$from] = $to;
            }
        }

    }
    $x = strtr($x, $back);
    $x = strtr($x, $cross);
    
    return $x;
}

@ve3
Copy link

ve3 commented Feb 26, 2025

สำหรับผู้ที่สงสัยว่า \xEF\x9C\x... ทั้งหลายมันคืออะไร เพราะไปหาในหน้าอักขระไทย unicode ก็ไม่พบ
เฉลย: มันคือตัวอักษรไทยในตำแหน่งต่างๆหรือตัดเอาบางส่วนออกไป อยู่ใน private area ซึ่งปกติแสดงผลผ่านเบราว์เซอร์มักจะไม่ปรากฏ เป็นสัญญลักษณ์อะไรก็ไม่รู้ดูไม่รู้เรื่อง. ต้องทำผ่านโปรแกรมสร้าง pdf อย่างเช่น mpdf มันจึงจะแสดงออกมารู้เรื่อง.

เอาโค้ดต่อไปนี้ไปเพื่อดูว่ามันมีอะไรกันบ้าง แล้วจะเข้าใจเองครับ.

<?php


/**
 * Get default Mpdf fonts.
 *
 * @return array Return associative array with keys: `fontDirs`, `fontData`.
 */
function mpdfGetDefaultFonts(): array
{
    $defaultConfig = (new \Mpdf\Config\ConfigVariables())->getDefaults();
    $fontDirs = $defaultConfig['fontDir'];
    $defaultFontConfig = (new \Mpdf\Config\FontVariables())->getDefaults();
    $fontData = $defaultFontConfig['fontdata'];
    // remove not exists font.
    unset($fontData['eeyekunicode']);
    unset($defaultConfig, $defaultFontConfig);

    return [
        'fontDirs' => $fontDirs,
        'fontData' => $fontData,
    ];
}// mpdfGetDefaultFonts

.mpdf-functions.php

<?php

require 'vendor/autoload.php';

ini_set('memory_limit', '64M');

include_once '.mpdf-functions.php';
list('fontDirs' => $fontDirs, 'fontData' => $fontData) = mpdfGetDefaultFonts();
$config = [
    'default_font' => 'sans-serif',
    'default_font_size' => 22,
    'fontdata' => $fontData,
    'fontDir' => $fontDirs,
];
unset($fontDirs);

$mpdf = new \Mpdf\Mpdf($config);
$fontsToTest = ['garuda', 'zawgyi-one'];

/**
 * Private use area of unicode block.
 * @link https://utf8-chartable.de/unicode-utf8-table.pl?start=63168&utf8=string-literal&unicodeinhtml=hex Reference.
 */

$html = '<!DOCTYPE html><html>' . PHP_EOL;
$html .= '<head><meta charset="utf-8"></head>' . PHP_EOL;
$html .= '<body>' . PHP_EOL;
$html .= '<h1><a href="https://utf8-chartable.de/unicode-utf8-table.pl?start=63168&utf8=string-literal&unicodeinhtml=hex" target="_blank">Private use area of unicode block.</a></h1>' . PHP_EOL;
foreach ($fontsToTest as $index => $font) {
    $html .= '<h3>' . $font . '</h3>' . PHP_EOL;
    $html .= '<p style="font-family:' . $font . ';">' . PHP_EOL;
    for ($i = 0x80; $i <= 0xBF; ++$i) {
        $html .= "\xEF\x9B" . chr($i) . 'า (\xEF\x9B\x' . dechex($i) . ')<br>';
    }
    for ($i = 0x80; $i <= 0xBF; ++$i) {
        $html .= "\xEF\x9C" . chr($i) . 'า (\xEF\x9C\x' . dechex($i) . ')<br>';
    }
    for ($i = 0x80; $i <= 0xBF; ++$i) {
        $html .= "\xEF\x9D" . chr($i) . 'า (\xEF\x9D\x' . dechex($i) . ')<br>';
    }
    $html .= '</p>' . PHP_EOL;
    if (array_key_last($fontsToTest) !== $index) {
        $html .= '<pagebreak>';
    }
}// endforeach;
unset($font, $index);
unset($fontsToTest);
$html .= '</body>' . PHP_EOL;
$html .= '</html>' . PHP_EOL;

// Write some HTML code:
$mpdf->WriteHTML($html);
unset($html);

// Output a PDF file directly to the browser
$mpdf->Output();

test.php

ตัวอย่าง
\xEF\x9C\x85 จะได้ไม้เอกที่อยู่ตรงกลางๆ
\xEF\x9C\x8a จะได้ไม้เอกที่เยื้องอยู่ข้างหลัง
\xEF\x9C\x93 จะได้ไม้เอกที่ลอยๆ

@ve3
Copy link

ve3 commented Feb 26, 2025

อัปเดทใหม่ นอกจากจะแก้เสียงวรรณยุกต์ลอยแล้ว (ไม้เอก เป็นต้น) ยังแก้เรื่องสระด้านล่างไปชนกับตัวอักษรที่ลากยาวลงมาด้านล่างด้วย เช่น ญ, ฎ เป็นต้น.

function fixThaiVowels(string $text): string
{
    // เสียงวรรณยุกต์ และทัณฑฆาต (การันต์)
    $back = [
        "\xE0\xB9\x88" => "\xEF\x9C\x8A", //่(ไม้เอก)
        "\xE0\xB9\x89" => "\xEF\x9C\x8B", //(ไม้โท)
        "\xE0\xB9\x8A" => "\xEF\x9C\x8C", //(ไม้ตรี)
        "\xE0\xB9\x8B" => "\xEF\x9C\x8D", //(ไม้จัตวา)
        "\xE0\xB9\x8C" => "\xEF\x9C\x8E" //(ตัวการันต์)
    ];
    $bottomVowels = [
        "\xE0\xB8\xB8" => "\xEF\x9C\x98", // สระอุ
        "\xE0\xB8\xB9" => "\xEF\x9C\x99", // สระอู
        "\xE0\xB8\xBA" => "\xEF\x9C\x9A", // พินธุ
    ];
    $replaces = [];
    // ตัวอักษรที่มีความสูงที่จะต้องไม่ให้ชนกับเสียงวรรณยุกต์
    $highChars = [
        "", // สระอิ
        "", // สระอี
        "", // สระอึ
        "", // สระอือ
        "", // ไม้หันอากาศ
        "", // สระอำ
        "", // สระอำ ที่ไม่มีสระอา
        "",
        "",
        "",
        "",
    ];
    // ตัวอักษรที่ลากลงด้านล่าง
    $lowChars = [
        "",
        "",
        "",
        "",
    ];
    $lowReplaceChars = [
        '' => "\xEF\x9C\x8F",
        '' => "\xEF\x9C\x80",
    ];

    // loop กำหนดค่าคืนกลับ (replace) เสียงวรรณยุกต์ที่จะต้องลอยสูง
    foreach ($highChars as $p) {
        if ($p !== '' && $p !== '') {
            for ($i = 0x8A; $i <= 0x8E; ++$i) {
                $from = $p . "\xEF\x9C" . chr($i);
                $to = $p . "\xE0\xB9" . chr($i - 2);
                $replaces[$from] = $to;
            }// endfor;
            unset($i);
        } else {
            for ($i = 0x8A; $i <= 0x8E; ++$i) {
                $from = "\xEF\x9C" . chr($i) . $p;
                $to = "\xE0\xB9" . chr($i - 2) . $p;
                $replaces[$from] = $to;
            }// endfor;
            unset($i);
        }// endif;
    }// endforeach;
    unset($highChars, $p);

    // loop กำหนดค่าสระด้านล่าง ที่จะต้องเปลี่ยน (replace) เมื่อเจอตัวอักษรที่ลากลงด้านล่าง
    foreach ($lowChars as $p) {
        foreach ($bottomVowels as $orig => $replace) {
            $from = $p . $orig;
            $to = $p . $replace;
            $replaces[$from] = $to;
        }// endforeach;
        unset($orig, $replace);
    }// endforeach;
    unset($lowChars, $p);

    // loop กำหนดตัวอักษรที่มีฐานด้านล่าง ที่จะต้องเปลี่ยน (replace) เมื่อมีสระด้านล่างตามมาด้วย
    foreach ($lowReplaceChars as $origChar => $replaceChar) {
        foreach ($bottomVowels as $orig => $replace) {
            $from = $origChar . $orig;
            $to = $replaceChar . $orig;
            $replaces[$from] = $to;
        }// endforeach;
        unset($orig, $replace);
    }// endforeach;
    unset($lowReplaceChars, $origChar, $replaceChar);

    $text = strtr($text, $back);
	$text = strtr($text, $replaces);
    unset($back, $replaces);
    return $text;
}// fixThaiVowels

ทำสอบโดยใช้โค้ดสร้าง PDF ใดๆก็ได้แล้วแก้ไขชื่อฟอนต์เอาให้ถูกต้อง

$font = 'sarabunnew';// ตย. กำหนดชื่อฟอนต์ให้ถูกต้องเอาเองนะครับ
$html .= '<h3>' . $font . '</h3>' . PHP_EOL;
$html .= '<p style="font-family:' . $font . ';">orig: อ่า อิ่ อี่ อึ่ อื่ อ้า อิ้ อี้ อึ้ อื้ อ๊า อิ๊ อี๊ อึ๊ อื๊ อ๋า อิ๋ อี๋ อึ๋ อื๋<br>' . PHP_EOL;
$html .= ' &nbsp; อ์ อิ์ อี์ อึ์ อื์ อั่น อั้น อั๊น อั๋น อ่ำ อ้ำ อ๊ำ อ๋ำ อ่ํ อ้ํ อ๊ํ อ๋ํ<br>' . PHP_EOL;
$html .= ' &nbsp; ป่า ป้า ป๊า ป๋า ปิ่ ปิ้ ปิ๊ ปิ๋ ปื่ ปื้ ปื๊ ปื๋ ปั่น ป่ำ ป่ํ<br>';
$html .= ' &nbsp; ฝ่า ฝ้า ฝ๊า ฝ๋า ฝิ่ ฝิ้ ฝิ๊ ฝิ๋ ฝื่ ฝื้ ฝื๊ ฝื๋ ฝั่น ฝ่ำ ฝ่ํ<br>';
$html .= ' &nbsp; ฟ่า ฟ้า ฟ๊า ฟ๋า ฟิ่ ฟิ้ ฟิ๊ ฟิ๋ ฟื่ ฟื้ ฟื๊ ฟื๋ ฟั่น ฟ่ำ ฟ่ํ<br>';
$html .= ' &nbsp; ฬ่า ฬ้า ฬ๊า ฬ๋า ฬิ่ ฬิ้ ฬิ๊ ฬิ๋ ฬื่ ฬื้ ฬื๊ ฬื๋ ฬั่น ฬ่ำ ฬ่ํ<br>';
$html .= ' &nbsp; อุ อู อฺ ฎุ ฎู ฎฺ ฏุ ฏู ฏฺ ฤุ ฤู ฤฺ ฦุ ฦู ฦฺ<br>';
$html .= ' &nbsp; ญุ ญู ญฺ ฐุ ฐู ฐฺ<br>';
$html .= "custom: อ\xEF\x9C\x8Aา อิ\xEF\x9C\x93 อี\xEF\x9C\x93 อึ\xEF\x9C\x93 อื\xEF\x9C\x93 ";
$html .= "\xEF\x9C\x8Bา อิ\xEF\x9C\x94 อี\xEF\x9C\x94 อึ\xEF\x9C\x94 อื\xEF\x9C\x94 ";
$html .= "\xEF\x9C\x8Cา อิ\xEF\x9C\x95 อี\xEF\x9C\x95 อึ\xEF\x9C\x95 อื\xEF\x9C\x95 ";
$html .= "\xEF\x9C\x8Dา อิ\xEF\x9C\x96 อี\xEF\x9C\x96 อึ\xEF\x9C\x96 อื\xEF\x9C\x96 ";
$html .= '<br>' . PHP_EOL;
$html .= " &nbsp; อ\xEF\x9C\x8E อิ\xEF\x9C\x97 อี\xEF\x9C\x97 อึ\xEF\x9C\x97 อื\xEF\x9C\x97 ";
$html .= "อั\xEF\x9C\x93น อั\xEF\x9C\x94น อั\xEF\x9C\x95น อั\xEF\x9C\x96";
$html .= "\xEF\x9C\x93ำ อ\xEF\x9C\x94ำ อ\xEF\x9C\x95ำ อ\xEF\x9C\x96";
$html .= "\xEF\x9C\x93ํ อ\xEF\x9C\x94ํ อ\xEF\x9C\x95ํ อ\xEF\x9C\x96";
$html .= '<br>' . PHP_EOL;
$html .= " &nbsp; ป\xEF\x9C\x93า ป\xEF\x9C\x94า ป\xEF\x9C\x95า ป\xEF\x9C\x96";
$html .= "ปิ\xEF\x9C\x93 ปิ\xEF\x9C\x94 ปิ\xEF\x9C\x95 ปิ\xEF\x9C\x96 ";
$html .= "ปื\xEF\x9C\x93 ปื\xEF\x9C\x94 ปื\xEF\x9C\x95 ปื\xEF\x9C\x96 ";
$html .= "ปั\xEF\x9C\x93น ป\xEF\x9C\x93ำ ป\xEF\x9C\x93";
$html .= '<br>' . PHP_EOL;
$html .= " &nbsp; ฝ\xEF\x9C\x93า ฝ\xEF\x9C\x94า ฝ\xEF\x9C\x95า ฝ\xEF\x9C\x96";
$html .= "ฝิ\xEF\x9C\x93 ฝิ\xEF\x9C\x94 ฝิ\xEF\x9C\x95 ฝิ\xEF\x9C\x96 ";
$html .= "ฝื\xEF\x9C\x93 ฝื\xEF\x9C\x94 ฝื\xEF\x9C\x95 ฝื\xEF\x9C\x96 ";
$html .= "ฝั\xEF\x9C\x93น ฝ\xEF\x9C\x93ำ ฝ\xEF\x9C\x93";
$html .= '<br>' . PHP_EOL;
$html .= " &nbsp; ฟ\xEF\x9C\x93า ฟ\xEF\x9C\x94า ฟ\xEF\x9C\x95า ฟ\xEF\x9C\x96";
$html .= "ฟิ\xEF\x9C\x93 ฟิ\xEF\x9C\x94 ฟิ\xEF\x9C\x95 ฟิ\xEF\x9C\x96 ";
$html .= "ฟื\xEF\x9C\x93 ฟื\xEF\x9C\x94 ฟื\xEF\x9C\x95 ฟื\xEF\x9C\x96 ";
$html .= "ฟั\xEF\x9C\x93น ฟ\xEF\x9C\x93ำ ฟ\xEF\x9C\x93";
$html .= '<br>' . PHP_EOL;
$html .= " &nbsp; ฬ\xEF\x9C\x93า ฬ\xEF\x9C\x94า ฬ\xEF\x9C\x95า ฬ\xEF\x9C\x96";
$html .= "ฬิ\xEF\x9C\x93 ฬิ\xEF\x9C\x94 ฬิ\xEF\x9C\x95 ฬิ\xEF\x9C\x96 ";
$html .= "ฬื\xEF\x9C\x93 ฬื\xEF\x9C\x94 ฬื\xEF\x9C\x95 ฬื\xEF\x9C\x96 ";
$html .= "ฬั\xEF\x9C\x93น ฬ\xEF\x9C\x93ำ ฬ\xEF\x9C\x93";
$html .= '<br>' . PHP_EOL;
$html .= " &nbsp; อุ อู อฺ ฎ\xEF\x9C\x98\xEF\x9C\x99\xEF\x9C\x9A ";
$html .= "\xEF\x9C\x98\xEF\x9C\x99\xEF\x9C\x9A ";
$html .= "\xEF\x9C\x98\xEF\x9C\x99\xEF\x9C\x9A ";
$html .= '<br>' . PHP_EOL;
$html .= " &nbsp; \xEF\x9C\x8F\xEF\x9C\x8F\xEF\x9C\x8F\xEF\x9C\x80\xEF\x9C\x80\xEF\x9C\x80";
$html .= '<br>' . PHP_EOL;
$html .= 'test function: ' . fixThaiVowels('อ่า อิ่ อี่ อึ่ อื่ อ้า อิ้ อี้ อึ้ อื้ อ๊า อิ๊ อี๊ อึ๊ อื๊ อ๋า อิ๋ อี๋ อึ๋ อื๋');
$html .= '<br>' . PHP_EOL;
$html .= ' &nbsp; ' . fixThaiVowels('อ์ อิ์ อี์ อึ์ อื์ อั่น อั้น อั๊น อั๋น อ่ำ อ้ำ อ๊ำ อ๋ำ อ่ํ อ้ํ อ๊ํ อ๋ํ');
$html .= '<br>' . PHP_EOL;
$html .= ' &nbsp; ' . fixThaiVowels('ป่า ป้า ป๊า ป๋า ปิ่ ปิ้ ปิ๊ ปิ๋ ปื่ ปื้ ปื๊ ปื๋ ปั่น ป่ำ ป่ํ');
$html .= '<br>' . PHP_EOL;
$html .= ' &nbsp; ' . fixThaiVowels('ฝ่า ฝ้า ฝ๊า ฝ๋า ฝิ่ ฝิ้ ฝิ๊ ฝิ๋ ฝื่ ฝื้ ฝื๊ ฝื๋ ฝั่น ฝ่ำ ฝ่ํ');
$html .= '<br>' . PHP_EOL;
$html .= ' &nbsp; ' . fixThaiVowels('ฟ่า ฟ้า ฟ๊า ฟ๋า ฟิ่ ฟิ้ ฟิ๊ ฟิ๋ ฟื่ ฟื้ ฟื๊ ฟื๋ ฟั่น ฟ่ำ ฟ่ํ');
$html .= '<br>' . PHP_EOL;
$html .= ' &nbsp; ' . fixThaiVowels('ฬ่า ฬ้า ฬ๊า ฬ๋า ฬิ่ ฬิ้ ฬิ๊ ฬิ๋ ฬื่ ฬื้ ฬื๊ ฬื๋ ฬั่น ฬ่ำ ฬ่ํ');
$html .= '<br>' . PHP_EOL;
$html .= ' &nbsp; ' . fixThaiVowels('อุ อู อฺ ฎุ ฎู ฎฺ ฏุ ฏู ฏฺ ฤุ ฤู ฤฺ ฦุ ฦู ฦฺ');
$html .= '<br>' . PHP_EOL;
$html .= ' &nbsp; ' . fixThaiVowels('ญุ ญู ญฺ ฐุ ฐู ฐฺ');
$html .= '</p>' . PHP_EOL;

หมายเหตุ: ไม่สามารถแก้ไขได้กับทุกฟอนต์ บางฟอนต์ที่เสียงวรรณยุกต์จม (สระจม, ไม้เอกจม เป็นต้น) จะไม่สามารถแก้ได้เลย เนื่องจากเขาทำมาไม่ครบ. ดังนั้นตรวจสอบกับฟอนต์ที่จะใช้ให้ดีก่อนใช้จริง.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment