แก้สระลอยภาษาไทยใน PHP PDF

ผู้เขียน PHP ที่ต้องการสร้างเอกสาร PDF อาจพบเจอปัญหาที่แก้ได้ยากบ่อยครั้งซึ่งเป็นปัญหายอดนิยม นั่นคือเสียงวรรณยุกต์ลอย, หรือสระลอย. ปัญหาดังกล่าวเกิดจากฟอนต์เป็นหลัก เพราะฟอนต์บางตัวก็ทำงานได้ดี แต่บางตัวก็ไม่เป็นเช่นนั้น. การแก้สระลอยในโค้ดต่อไปนี้อาจแก้ได้เฉพาะที่มีเสียงวรรณยุกต์ลอยเท่านั้น (เช่น ไม้เอก, ไม้โท) แต่ไม่สามารถแก้เสียงวรรณยุกต์จม, หรือสระจมได้ เนื่องจากเป็นปัญหาที่ฟอนต์นั้นๆไม่ได้ออกแบบมาให้มีอักขระในส่วนนี้เลย จึงไม่สามารถนำมาใช้ทดแทนแก้ปัญหาได้.

/**
 * Fix Thai vowels and characters. (สระลอย, เสียงวรรณยุกต์ลอย)
 *
 * @link https://gist.github.com/dtinth/716814 Original source code
 * @param string $text
 * @return string
 */
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

ที่มาของโค้ดด้านบนนี้ แรกเริ่มเดิมทีมาจาก URL https://gist.github.com/dtinth/716814 แต่ได้มีการปรับแต่งเรื่อยมาจนกระทั่งผู้เขียนได้เพิ่มการแก้ปัญหา สระด้านล่างไปชนกับตัวอักษรที่ลากยาวลงมาด้านล่างด้วย เช่น , เป็นต้น.

ทดสอบ

ในการทดสอบ ผู้เขียนจะใช้โค้ดของ mpdf ซึ่งคุณผู้อ่านจะนำไปใช้กับอะไรอย่างอื่นก็ได้ทั้งหมด. ตัวอย่างโค้ดทดสอบ.

require 'vendor/autoload.php';

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,
    ];
}

list('fontDirs' => $fontDirs, 'fontData' => $fontData) = mpdfGetDefaultFonts();
$fontDirs = array_merge($fontDirs, [__DIR__ . DIRECTORY_SEPARATOR . 'custom-fonts']);// แก้ไขของคุณ
$fontData = $fontData + [
    'sarabunnew' => [
        'R' => 'THSarabunNew.ttf',
    ],
];// แก้ไขของคุณ
$config = [
    'default_font' => 'sans-serif',
    'default_font_size' => 22,
    'fontdata' => $fontData,
    'fontDir' => $fontDirs,
];
unset($fontDirs);

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

$html = '<!DOCTYPE html><html>' . PHP_EOL;
$html .= '<head><meta charset="utf-8"></head>' . PHP_EOL;
$html .= '<body>' . PHP_EOL;
$html .= '<h1>Fix Thai vowels</h1>' . PHP_EOL;
foreach ($fontsToTest as $font) {
    $html .= '<h3>Font: ' . $font . '</h3>' . PHP_EOL;
    $html .= '<p style="font-family:' . $font . ';">' . fixThaiVowels('อ่า อิ่ อี่ อึ่ อื่ อ้า อิ้ อี้ อึ้ อื้ อ๊า อิ๊ อี๊ อึ๊ อื๊ อ๋า อิ๋ อี๋ อึ๋ อื๋');
    $html .= '</p>' . PHP_EOL;
}
$html .= '</body>' . PHP_EOL;
$html .= '</html>' . PHP_EOL;

$mpdf->WriteHTML($html);
$mpdf->Output();

ผลลัพธ์ควรจะได้คล้ายภาพต่อไปนี้

screenshot of fixed Thai vowels

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องข้อมูลจำเป็นถูกทำเครื่องหมาย *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>