Theppitak's blog

My personal blog.

25 กรกฎาคม 2568

Tai Tham Updated, Thai Noi Revisited

Update งานอักษรอีสาน

Khottabun กับอักษรธรรมแบบไร้ USE

หลังจากการวิเคราะห์ทางเลือกต่างๆ สำหรับอักษรธรรมไป 2 blog (Lao Tham Font vs USE และ To USE or Not to USE for Lao Tham) และได้ตัดสินใจ หลีกเลี่ยง USE โดยยอมทิ้ง MS Word ไว้ข้างหลัง ก็ได้ implement ใน fonts-khottabun ตามนั้น ตั้งแต่ commit a0389bd จนถึง commit 1c73c80 โดยปรับ major version ของฟอนต์ขึ้นเป็นรุ่น 003 สำหรับอักษรธรรมแบบไม่ใช้ USE

Keyboard Layout อักษรธรรมบน Windows

ถัดมาคือ ผังแป้นพิมพ์อักษรธรรมบน Windows ในรูปของซอร์ส KLC สำหรับสร้างเป็นผังแป้นพิมพ์ด้วย Microsoft Keyboard Layout Creator (MSKLC) 1.4 ซึ่งจะ build เป็น DLL พร้อมโปรแกรม setup

ประเด็นปลีกย่อยของผังนี้คือ Windows กำหนดให้ผังแป้นพิมพ์ต้องผูกติดกับโลแคล (locale) ซึ่งกำหนดด้วยภาษาและดินแดน ตรงนี้เป็นปัญหาโดยนิยามอยู่ เพราะอักษรธรรมสามารถใช้เขียนได้หลายภาษา ไม่ว่าจะไทย ลาว ไทลื้อ ไทขึน หรือคาถาบาลี-สันสกฤต แต่ Windows จะบังคับให้เราเลือกเพียงภาษาเดียวมาประกอบกับดินแดนที่เป็นถิ่นของภาษาที่ใช้ โดยห้ามใช้ซ้ำกับผังอื่นในโลแคลนั้นๆ ด้วย ประเด็นคือ:

  • โลแคลต่างๆ มักมีผังแป้นพิมพ์อยู่แล้ว ถ้าไม่ต้องการซ้ำก็ต้องสร้างโลแคลใหม่สำหรับอักษรธรรม แต่จะกำหนดโลแคลด้วยภาษาอะไร ในเมื่ออักษรธรรมสามารถใช้เขียนได้หลายภาษา?
  • สมมุติว่าเราเลือกสร้างโลแคลภาษาลาวถิ่นไทย (lo-TH) ด้วย Microsoft Locale Builder แต่ปัญหาคือ เราไม่สามารถเลือกบล็อคยูนิโคดของอักษรธรรมให้กับโลแคลใหม่นี้ได้ เนื่องจาก Microsoft กำหนดตัวเลข enum ให้กับบล็อคยูนิโคดต่างๆ แต่ยังไม่ได้กำหนดเลขสำหรับอ้างให้กับบล็อคอักษรธรรม!
  • ในเมื่อสร้างโลแคลเองไม่ได้ วิธีแก้ขัดจึงเป็นการเลือกโลแคลอะไรก็ได้ที่ไม่ซ้ำกับชุดผังแป้นพิมพ์ที่เราใช้ เช่น ใช้ th, en ไม่ได้ เพราะจะซ้ำกับสองผังหลักที่เราใช้ และผมก็จิ้มเอาโลแคลมลยาฬัมในอินเดีย (ml-IN) โดยไม่มีเหตุผลใดๆ มากไปกว่าการแก้ขัด เวลาจะป้อนอักษรธรรมบน Windows ผู้ใช้ก็สลับแป้นพิมพ์ไปที่ภาษามลยาฬัม
  • อันที่จริง สิ่งที่เราต้องการมากกว่าในกรณีนี้คือการอ้างถึง อักษร (script) ตรงๆ ไปเลย แต่การออกแบบของ Windows ยังไม่เอื้อขนาดนั้น

อย่างไรก็ดี เราก็ได้ผังแป้นพิมพ์อักษรธรรมที่ทำงานได้บน Windows โดยเป็นเพียงผังปุ่มจริงๆ ยังไม่มีการตรวจหรือสลับลำดับอะไร ผู้ใช้ต้องป้อนตามลำดับยูนิโคดเป๊ะๆ เช่น ᨸᩮᩢ᩠ᨶ (เป็น) ก็ต้องป้อนเป็น ป + เอ + ไม้ซัด + SAKOT + น

อักษรไทยน้อย

แม้อักษรไทยน้อยจะเรียบง่ายกว่าอักษรธรรม แต่กลับมีรายละเอียดปลีกย่อยมากกว่า ทั้งเรื่องความหลากหลายของชุดอักษรที่ใช้ การควบอักษรติดกัน การยืมตัวห้อยตัวเฟื้องจากอักษรธรรมตามแต่ผู้จารึกจะเลือกใช้ ดังที่ผมได้เคย สรุปประเด็น ไว้ โดยยังไม่ตกลงว่าจะเลือก implement แบบไหน

เมื่อหลายปีก่อน ในระหว่างที่ทำไปศึกษาไปนั้น ผมเลือกที่จะเติมอักขระลงในช่องที่ยังว่างอยู่ของบล็อคยูนิโคดอักษรลาว ไม่ว่าจะเป็นตัวเฟื้องหรือตัวควบต่างๆ แต่ยิ่งพบอักขระที่ต้องเพิ่มรหัสมากขึ้นจากการอ่านจารึก ก็ยิ่งเห็นว่ามันไม่ใช่วิธีที่ดีในระยะยาว

ตอนนี้ได้โอกาสมาปัดฝุ่น โดยบล็อคยูนิโคดลาวเองก็มีมีการเพิ่มอักขระสำหรับเขียนภาษาบาลีตามแบบพุทธบัณฑิตสภา จึงได้พิจารณาทางเลือกต่างๆ ใหม่

เริ่มจากตรวจสอบความคืบหน้าใน Unicode ของอักษรไทยน้อย ซึ่ง ข้อมูลใน ScriptSource ของ SIL ได้บันทึกรายละเอียดไว้ โดยสถานะล่าสุดคือ ยังไม่กำหนดใน Unicode ส่วนกระบวนการเท่าที่ผ่านมาคือ

2018-01-02 Request to Add Thai Characters — Nitaya Kanchanawan (WG2 N4927, L2/18-041)
เป็นข้อเสนอขอเพิ่มอักขระไทยน้อยลงในบล็อคอักษรไทย! โดยเป็นงานที่เกี่ยวเนื่องกับการกำหนดมาตรฐานการถอดอักษรไทยน้อยเป็นอักษรโรมันที่ราชบัณฑิตยสภาได้เสนอเข้าสู่ ISO/IEC โดยได้รับความเห็นว่าควรกำหนดรหัสอักขระก่อน แต่ไม่ทราบว่าด้วยเหตุผลใด ราชบัณฑิตยสภาถึงได้เสนอให้เพิ่มอักขระในบล็อคอักษรไทยแทนที่จะเป็นบล็อคอักษรลาวที่อยู่ในสายวิวัฒนาการโดยตรง
2018-01-19 Information and documentation - Transliteration of scripts in use in Thailand - Part 1: Transliteration of Akson-Thai-Noi — TC46 / WG3 (WG2 N4927A, L2/18-042)
เป็นร่างมาตรฐานการถอดอักษรไทยน้อยเป็นอักษรโรมันที่เป็นต้นเรื่อง ตัวเอกสารต้องใช้รหัสผ่านในการเข้าถึง และไม่เกี่ยวข้องกับเรื่องรหัสอักขระโดยตรง ผมจึงไม่ใส่ลิงก์ไว้
2018-01-19 Results on ISO CD 20674-1: Information and documentation - Transliteration of scripts in use in Thailand - Part 1: Transliteration of Akson-Thai-Noi — TC46 Secretatriat (WG2 N4927B, L2/18-043)
ผลการโหวตร่างมาตรฐานการถอดอักษรไทยน้อยของคณะกรรมการ เห็นชอบ 16, เห็นชอบโดยมีข้อคิดเห็น 2, ไม่เห็นชอบ 1, งดออกเสียง 19
2018-02-20 Thai-Noi Transliteration — Martin Hosken (WG2 N4939, L2/18-068)
เป็นความเห็นจากคุณ Martin Hosken ผู้เชี่ยวชาญจาก SIL โดยสรุปเห็นว่าอักษรไทยน้อยเข้ากันได้กับอักษรธรรมมากกว่าอักษรไทย
2018-02-28 Towards a comprehensive proposal for Thai Noi / Lao Buhan script — Ben Mitchell (L2/18-072)
เป็นความเห็นจากคุณ Ben Mitchell ผู้เชี่ยวชาญอีกท่านหนึ่ง โดยมีผู้ร่วมให้ข้อมูลคือ Patrick Chew, ผมเอง และ อ.ประพันธ์ เอี่ยมวิริยะกุล โดยสรุปเห็นว่าควรเพิ่มอักขระไทยน้อยในบล็อคอักษรลาว โดยเพิ่มเติมจากอักษรลาวบาลีของพุทธบัณฑิตสภาอีกที และได้เสนอทางเลือกต่างๆ ดังที่ผมได้สรุปไว้ แต่ยังไม่ระบุว่าจะเลือกวิธีการไหน
Recommendations to UTC #155 April-May 2018 on Script Proposals (L2/18-168) (อยู่ที่ข้อ 6.) และบันทึกการประชุมของ UTC #155 (L2/18-115) (อยู่ที่ข้อ D.8.1 และ D.8.3)
สรุปข้อแนะนำของคณะกรรมการว่าอักษรไทยน้อยไม่ใข่ส่วนขยายของอักษรไทย และผู้เสนอร่างอักษรไทยน้อย (หมายถึง อ.นิตยา กาญจนวรรณ จากราชบัณฑิตยสภา) ควรนำข้อมูลในเอกสารของคุณ Ben Mitchell ข้างต้นไปใช้ประกอบการร่างด้วย โดยมอบให้ Deborah Anderson ร่างเอกสารแจ้งเจ้าของร่าง ซึ่งก็คือเอกสาร L2/18-070

แล้วกระบวนการทั้งหมดก็หยุดอยู่ที่รอการดำเนินการของราชบัณฑิตยสภาต่อจากนั้น

สรุปว่ายังไม่มีมาตรฐาน Unicode อักษรไทยน้อยเกิดขึ้น ถ้าจะ implement ตอนนี้ก็เป็นเพียงตุ๊กตาเท่านั้น

จากทางเลือกต่างๆ ที่มี ผมพิจารณาเลือกเองไปก่อนอย่างนี้:

  • Conjunct หรือการสังโยคพยัญชนะด้วยตัวห้อย/ตัวเฟื้อง ใช้พินทุ (U+0EBA LAO SIGN PALI VIRAMA) ตามด้วยพยัญชนะที่จะสังโยค ยกเว้นกรณีที่มีรหัสอยู่แล้ว คือ ล ห้อย (U+0EBC) และ ย เฟื้อง (U+0EBD) ก็ใช้รหัสนั้นๆ ไปเลย
  • Ligature หรือตัวแฝด หรืออักษรควบ ใช้ U+200D ZERO WIDTH JOINER (ZWJ) เชื่อมพยัญชนะ ยกเว้นกรณีที่มีรหัสอยู่แล้ว คือ U+0EDC (ໜ), U+0EDD (ໝ) ก็ใช้รหัสนั้นๆ ไปเลย
  • สระออย กำหนดอักขระใหม่ในช่องที่ยังว่างอยู่ คือ U+0EBE เพื่อให้เป็นวิธีที่สอดคล้องกับอักษรธรรม (อีกวิธีที่ไม่ต้องกำหนดอักขระใหม่คือใช้ พินทุ + ຢ แต่จะเป็นวิธีที่แปลกแยกจากอักษรธรรม)

เมื่อเลือกวิธีนี้แล้ว ก็ปรับเปลี่ยนทั้งในฟอนต์ Khottabun และในระบบป้อนข้อความ Lanxang ดังนี้:

  • fonts-khottabun: detach glyph ตัวห้อย/ตัวเฟื้องและตัวควบทั้งหมดจากรหัสอักขระ แล้วสร้างกฎ 'ccmp' TN conjuncts และ 'ccmp' TN subjoins เพื่อเข้าถึง glyph เหล่านี้ผ่านพินทุและ ZWJ ตามลำดับ พร้อมกับปรับรุ่น major ของฟอนต์เป็นรุ่น 004 (commit cc4935a)
  • lanxang: ตัดกระบวนการแปลง พินทุ + พยัญชนะ เป็นอักขระตัวห้อย/ตัวเฟื้อง และ พยัญชนะ + ZWJ + พยัญชนะ เป็นอักขระตัวควบ ทั้งหมด (ทิ้ง sequence ประกอบไว้อย่างนั้นในข้อความเลย แล้วให้ฟอนต์จัดการตอน render) ยกเว้นการควบ ໜ,ໝ เพื่ออำนวยความสะดวก (commit f0645b1) แต่ก็ยังป้อน ໜ, ໝ โดยตรงได้ด้วย level 3 เช่นกัน
  • lanxang: เพิ่มผังแป้นพิมพ์อักษรไทยน้อยสำหรับ Windows ด้วย (commit cce81cd)

พร้อมกันนี้ก็แปลงวิธีลงรหัสอักขระไทยน้อยในตัวอย่างการปริวรรตทั้งหลายด้วย (พญาคันคาก, ฮีตคองคะลำ, บูชาพญานาค, นกกระจอก)

ป้ายกำกับ: , , , ,

06 เมษายน 2568

To USE or Not to USE for Lao Tham

จาก blog ที่แล้ว ผมได้เล่าถึงการรองรับอักษรธรรมในปัจจุบันที่ text shaping engine ต่างๆ หันมาใช้ Universal Shaping Engine (USE) ตามข้อกำหนดของไมโครซอฟท์ โดย USE เองเป็น engine ครอบจักรวาลที่มีการจัดการภายในตามคุณสมบัติของอักขระ Unicode เช่น การสลับสระหน้ากับพยัญชนะต้นสำหรับอักษรตระกูลพราหมี และเรียกใช้ OpenType feature ต่างๆ ในฟอนต์ตามลำดับที่กำหนดไว้

และการจัดการภายในของ USE ก็ทำให้มีข้อเสนอที่จะปรับโครงสร้างการลงรหัสข้อความอักษรธรรมเพื่อให้ทำงานกับ USE ได้ แต่มันก็ยังไม่เข้าที่เข้าทางนัก จึงเกิดแนวคิดที่จะหลบเลี่ยง USE แล้วทำทุกอย่างเองในฟอนต์ แต่ไปติดปัญหาที่ MS Word ที่ไม่ยอมให้หลบได้ง่ายๆ ทำให้เราอยู่บนทางแยกที่ต้องเลือกว่าจะใช้ USE ที่ยังไม่เข้าที่ หรือจะเลี่ยง USE ไปทำทุกอย่างเองโดยทิ้ง MS Word ไว้ข้างหลัง

blog นี้ก็จะวิเคราะห์ต่อ ว่าทางเลือกแต่ละทางมีข้อดีข้อเสียอย่างไร

Encoding แบบใหม่

ตามข้อเสนอในเอกสาร Structuring Tai Tham Unicode เมื่อตัดรายละเอียดที่อักษรธรรมลาว/อีสานไม่ใช้ออกไป ก็พอจะสรุปลำดับอักขระในข้อความได้เป็น:

  S ::= CC (Vs | Vf)?

โดยที่ CC คือ consonant cluster พร้อมสระหน้า/ล่าง/บน วรรณยุกต์ สระหรือตัวสะกดปิดท้าย

  CC ::= C M? V? T? F?

  C ::= [<ก>-<ฮ><อิลอย>-<โอลอย><แล><ส_สองห้อง><ตัวเลข>] | <ห><SAKOT>[<ก>-<ม>]

  M ::= <ระวง>? <ล_ล่าง>? (<SAKOT><ว>)? (<SAKOT><ย>)?

  V ::= Vp? Vb? Va? 
  Vp ::= [<เอ>-<ไม้ม้วน>] // สระหน้า
  Vb ::= [<อุ>-<อู><ออ_ล่าง>] // สระล่าง
  Va ::= [<อิ>-<อือ><ไม้กง><ไม้เก๋าห่อนึ่ง>][<ไม้ซัด><ไม้กั๋ง>]? // สระบน

  T ::= [<ไม้เอก>-<ไม้โท>]

  F ::= Fs? [<ละทั้งหลาย><ไม้กั๋งไหล><ง_บน><ม_ล่าง><บ_ล่าง><ส_ล่าง><ไม้กั๋ง>]? (<SAKOT> S)?
  Fs ::= [<อ><ส_สองห้อง><ออย>] | <SAKOT>[<ก>-<ฬ>] // อ ของสระเอือ หรือ ตัวสะกด

สังเกตว่ามี recursion ใน F เพื่อแทนลูกโซ่ของพยางค์ในคำบาลีด้วย

ต่อจาก CC ก็จะตามด้วยสระกินที่ (spacing vowels) ซึ่งแบ่งเป็นสระมีตัวสะกด (Vs) และสระไม่มีตัวสะกด (Vf)

สำหรับสระมีตัวสะกด Vs คือสระอาหรือสระอำ (สระอำในอักษรธรรมไม่ได้มีรหัสอักขระเฉพาะเหมือนอักษรไทย แต่แทนด้วยสระอาตามด้วยไม้กั๋ง (นิคหิต) ความจริงอาจนับไม้กั๋งเป็นตัวสะกดในสระอำก็ได้ แต่กฎนี้พยายามจัดการสระอากับสระอำไปด้วยกัน กลายเป็นว่าสระอำก็ยังมีตัวสะกดได้อีก) พร้อมตัวสะกด Fs (ถ้ามี)

  Vs ::= AV Fs?
  AV ::= [<อา><อาสูง>] <ไม้กั๋ง>? // สระอา สระอำ

ส่วนสระไม่มีตัวสะกด Vf ในตัวเนื้อหาของร่างฯ ได้บรรยายถึงสระเอาะและสระเอือะ แต่ในสรุปส่วนท้ายดูจะลืมสระเอาะไป และยังจัดการ อ ของสระเอือะซ้ำซ้อนกับใน Fs อีก

  Vf ::= <อ>? <อะ> // วิสรรชนีย์ของสระเอือะ

Encoding แบบ USE

แม้ encoding แบบใหม่ที่เสนอมีจุดมุ่งหมายเพื่อให้วาดแสดงด้วย USE แต่เมื่อลองใช้กับ USE จริงกลับยังมี dotted circle เกิดขึ้นในหลายกรณี ซึ่งจากการทดลองก็พอจะสังเกตลักษณะของ USE ที่ต่างจากโครงสร้างที่เสนอในร่างดังนี้:

  • ลำดับสระใน V สระบนมาก่อนสระล่าง ไม่ใช่ตามหลังสระล่างอย่างในร่างฯ ดังนั้นจึงอาจปรับกฎเป็น:
      V ::= Vp? Va? Vb?
    
  • วรรณยุกต์อยู่หน้าสระกินที่ไม่ได้ แต่ตามหลังได้ และต้องอยู่ก่อนตัวสะกด ดูเหมือน USE จะนับ Vs เป็นส่วนหนึ่งของ V:
      V ::= Vp? Va? Vb? Vs?
    
    ซึ่งถ้าเป็นเช่นนั้น ก็หมายความว่า USE ก็ยังต้องการการ reorder ให้วรรณยุกต์ไปอยู่ในตำแหน่งก่อนหน้าสระกินที่เพื่อให้สามารถซ้อนบนพยัญชนะได้ด้วย มันจะกลายเป็นความซับซ้อนเกินจำเป็นน่ะสิ

หลักการสำหรับการป้อนข้อความ

สมมติว่าเราใช้ encoding แบบ USE เราจะมีหลักในใจอย่างไร? เราจะใช้ลำดับการพิมพ์แบบที่เราเคยใช้กับอักษรไทยไม่ได้อีกแล้ว เพราะมันคือการ encode แบบกึ่ง visual ไม่ว่าเขาจะยืนยันที่จะเรียกว่า logical order ยังไงก็ตาม แต่ลำดับการ encode นี้จะไม่ตรงกับลำดับการสะกดคำเสมอไป มันเน้นให้เรียงพิมพ์ได้เป็นหลัก! โดยมีลำดับแบบที่เรียกว่า logical order มาหลอกให้งงเล่น

หลักการคือ

  1. ละทิ้งลำดับการสะกดในใจไว้ก่อน พิจารณารูปร่างของข้อความที่ต้องการ แล้ว encode ตามกฎในข้อถัดๆ ไป
  2. ผสมตัวห้อย ตัวเฟื้อง หรือระวง กับพยัญชนะต้นก่อน โดยมากเป็นตัวควบกล้ำ
  3. ตามด้วยสระ โดยถ้ามีสระหลายตัว ให้วางสระตามลำดับ หน้า, บน, ล่าง, ขวา
  4. ตามด้วยวรรณยุกต์
  5. ตามด้วยตัวสะกด ซึ่งอาจเป็นตัวห้อย, ตัวเฟื้อง, ไม้กั๋ง, ง สะกดบน, หรือไม้กั๋งไหล
  6. ถ้ามีการเชื่อมพยางค์เป็นลูกโซ่แบบบาลี ก็นับตัวสะกดเป็นพยัญชนะต้นของพยางค์ถัดไปแล้วใส่สระตามได้เลย

ความไม่ปกติของลำดับ

หลักการที่ว่าไปก็ดูปกติดีนี่? แต่ความซับซ้อนของอักษรธรรมทำให้มันไม่ตรงไปตรงมาอย่างนั้น ต่อไปนี้คือกรณีต่างๆ ที่ฝืนความรู้สึก อย่างน้อยก็ในระยะแรก

การสะกดแม่กกด้วยไม้ซัด

ไม้ซัดถือว่าเป็นสระบน (Va) ในกฎ ซึ่ง encoding ในร่างฯ สามารถตามหลังสระล่าง (Vb) ได้ แต่ตาม encoding ของ USE ต้องมาก่อนสระล่าง ดังนั้น เมื่อไม้ซัดทำหน้าที่เป็นตัวสะกดแม่กก จึงต้องใช้ลำดับที่ตัวสะกดมาก่อนสระ ไม่ใช่สระมาก่อนตัวสะกด

แต่ก็จะมีกรณีที่ไม่สามารถ encode ได้ ไม่ว่าจะตามร่างฯ หรือตาม USE คือเมื่อเป็นตัวสะกดของสระอา

อาจจะยกเว้นคำว่า นาค ที่สามารถซ่อนลำดับไว้ภายใต้รูปเขียนพิเศษได้

เมื่อมีตัวห้อย/ตัวเฟื้องหรือสระระหว่าง cluster

การเขียนอักษรธรรมในหลายกรณีมีการใช้ตัวห้อย/ตัวเฟื้องเป็นพยัญชนะต้นของพยางค์ถัดไป แทนที่จะใช้ตัวเต็ม ซึ่งในกรณีเหล่านี้ ถ้าวางตัวห้อย/ตัวเฟื้องในตำแหน่งของตัวสะกด ก็จะไม่สามารถวางสระต่อได้อีก เพราะดูเหมือน USE ไม่ได้รองรับ recursion ใน F ตามกฎในร่างฯ และเมื่อแจงต่อไป CC ของพยางค์ถัดไปก็ขาดพยัญชนะต้นตัวเต็ม (C) มาขึ้นต้น CC แต่ถ้า encode โดยเลี่ยงให้ตัวห้อย/ตัวเฟื้องดังกล่าวเป็นส่วนหนึ่งของ CC ของพยางค์ก่อนหน้าก็จะสามารถวางสระต่อได้ แต่ลำดับก็จะดูขัดสามัญสำนึกสักหน่อย

แต่ในบางกรณีก็ไม่เอื้อให้ทำเช่นนั้น เช่น เมื่อมีสระมาคั่นในแบบที่ไม่สามารถสลับลำดับได้

ในคำย่อที่มีการยืมพยัญชนะต้น

บ่อยครั้งที่อักษรธรรมมีการใช้รูปเขียนย่อในลักษณะที่ใช้พยัญชนะต้นตัวเดียวซ้ำในพยางค์มากกว่าหนึ่งพยางค์ ในจำนวนนั้น บางคำอาจสามารถยังจัดลำดับอักขระจนเข้ากับกฎเกณฑ์ของ USE ได้ แม้จะดูเหมือนการเปลี่ยนหน้าที่อักขระ เช่น เปลี่ยนตัวสะกดมาเป็นตัวควบ หรืออาจมีการสลับลำดับสระของพยางค์ต่างๆ

แต่บางกรณีก็ไม่สามารถจัดลำดับให้เข้าเกณฑ์ได้

วิธีหลบเลี่ยง?

อย่างไรก็ดี ปัญหาทั้งหมดนี้สามารถหลบเลี่ยงได้ โดยลบ glyph DOTTED CIRCLE (uni25CC) ออกจากฟอนต์เสีย แล้ว USE ก็จะไม่แสดง dotted circle อีกเลย!

ที่กล่าวมาทั้งหมดนี้คือการทดสอบกับแอปที่ใช้ Harfbuzz แต่สุดท้าย เมื่อนำไปทดสอบกับ MS Word ปรากฏว่ามันก็ยังไม่ยอมง่ายๆ อยู่ดี โดยปัญหาหลักที่พบมีสองข้อ ข้อแรกคือกฎของตัวห้อย/ตัวเฟื้องจะไม่ทำงานถ้าไม่ได้ตามหลังพยัญชนะต้นทันที เช่น เป็นตัวสะกดของสระอา หรือมีสระอื่นมาคั่นกลาง กล่าวคือ blwf ดูจะถูกเรียกเฉพาะในตำแหน่งพยัญชนะซ้อนกันเท่านั้น ไม่เรียกในตำแหน่งตัวสะกด และอีกข้อหนึ่งคือกฎ ligature สำหรับ นา ไม่ทำงานเมื่อมีอักขระซ้อนบน/ล่าง

สรุป

ประเด็นต่างๆ ที่พบ พอจะสรุปได้ตังตาราง

ประเด็น ใช้ USE เลี่ยง USE
วิธีการ
  • lana script tag
  • GSUB ตาม USE
  • ตัด dotted circle
  • DFLT script tag
  • GSUB อย่างละเอียด
  • อาจมี dotted circle ได้
การ encode ข้อความ กึ่ง visual (ขัดสามัญสำนึกนิดหน่อย) ตามหลักการสะกดคำ
แอปที่รองรับ Harfbuzz app*, MS Word (บางส่วน) Harfbuzz app*, Notepad
อาการเมื่อไม่รองรับ
  • MS Word
    • กฎ blwf ในตำแหน่งตัวสะกดไม่ทำงาน
    • กฎ ligature นา ไม่ทำงานถ้ามีอักขระบน/ล่างแทรก
  • Notepad และแอปที่รองรับ OpenType แบบผิวเผิน
    • ไม่มีกฎไหนทำงานเลย
  • ทุกแอป
    • ไม้กั๋งไหลไม่ไหล
  • MS Word
    • ccmp: การ reorder สระหน้า, ระวง ไม่ทำงาน
    • liga: reorder สระหน้าซ้ำสองตำแหน่ง

*Harfbuzz app เช่น LibreOffice, Firefox, Gedit, Mousepad ฯลฯ

จึงพอสรุปได้ว่ายังมีความแตกต่างระหว่างข้อกำหนดในร่างฯ กับสิ่งที่ USE รองรับจริง ทำให้ไม่ว่าจะพยายามอย่างไรก็จะมีข้อบกพร่องเกิดขึ้นเสมอ และแอปที่มีปัญหาเสมอๆ ก็คือ MS Word

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

ในขณะที่หากเลือกหลีกเลี่ยง USE แอปส่วนใหญ่ยกเว้น MS Word ก็จะสามารถจัดแสดงอักษรธรรมได้สมบูรณ์ (ดังกล่าวไว้ใน blog ที่แล้ว) โดยที่ผู้ใช้ก็ยังคงใช้ encoding แบบเก่าได้เช่นเดิม และเมื่อไรที่ USE พร้อม ก็เพียงแปลง encoding ไปเป็นแบบใหม่ (ซึ่งอาจจะอัตโนมัติหรือ manual ก็ค่อยว่ากัน) โดยไม่ต้องมี encoding ชั่วคราวของ USE มาแทรกกลางเป็นชนิดที่สามให้เกิดความสับสนเพิ่ม โดยแลกกับการทิ้ง MS Word ที่ยังไงก็ไม่สมบูรณ์อยู่แล้ว และแนะนำให้ผู้ใช้อักษรธรรมใช้ LibreOffice หรือ Notepad (ซึ่งเป็นผลพลอยได้) ในการเตรียมเอกสารแทน ส่วนเว็บเบราว์เซอร์นั้นไม่เป็นปัญหา เพราะเบราว์เซอร์ส่วนใหญ่ก็ใช้ Harfbuzz เป็นฐานกันอยู่แล้ว การรองรับก็จะเหมือนๆ กับใน LibreOffice นั่นแล

ด้วยเหตุนี้ ผมจึงเลือกที่จะสร้างฟอนต์อักษรธรรมที่ หลีกเลี่ยง USE ต่อไป จนกว่า USE จะพร้อมจริงๆ

ป้ายกำกับ: , ,

01 เมษายน 2568

Lao Tham Font vs USE

บันทึกการกลับมาทำฟอนต์อักษรธรรมอีกครั้ง หลังจาก commit ล่าสุด 4 ปีที่แล้ว โดยในช่วงหลังของการทำงานรอบนั้น มีความเปลี่ยนแปลงสำคัญใน Harfbuzz ก่อนที่ผมจะวางมือไปทำอย่างอื่น คือ มีการใช้ Universal Shaping Engine (USE) ตามข้อกำหนดของไมโครซอฟท์ ทำให้กฎบางข้อหยุดทำงาน เช่น การจัดการไม้กั๋งไหล (ลาวเรียก ไม้อังแล่น) แต่ผมก็ไม่ได้ไปติดตาม

ในเอกสาร Creating and supporting OpenType fonts for the Universal Shaping Engine ของไมโครซอฟท์ ได้อธิบายขั้นตอนต่างๆ ที่ USE จะเรียกใช้กฎในฟอนต์ โดยที่ USE เองก็มีการประมวลผลภายในเองบางส่วนด้วย ตามแต่อักษรที่รองรับ โดยสำหรับอักษรธรรมก็มีเชิงอรรถให้ปวดใจว่า

Note: Tai Tham support is currently limited. Additional encoding work is required for full text representation to be possible.

การรองรับอักษรธรรมยังคงจำกัดอยู่ในตอนนี้ ยังต้องทำงานด้านการกำหนดรหัสกันต่อไปเพื่อจะให้ได้รหัสที่สามารถแทนข้อความได้เต็มที่

ซึ่งเมื่อไปตรวจสอบเอกสารที่เกี่ยวข้อง ก็พบเอกสาร Structuring Tai Tham Unicode (L2/19-365) โดย Martin Hosken ซึ่งเป็นร่างข้อเสนอที่จะปรับลำดับการลงรหัสอักขระอักษรธรรมจาก แบบเริ่มแรก (L2/05-095R) เพื่อการวาดแสดงด้วย USE ซึ่งลำดับแบบใหม่ค่อนข้างแตกต่างจากลำดับการสะกดในใจพอสมควร และข้อเสนอใหม่ก็ยังคงเป็นฉบับร่าง จึงเกิดความไม่แน่นอนว่าแนวทางการลงรหัสปัจจุบันจะเป็นแบบไหน

เนื่องจากพระอาจารย์ชยสิริ ชยสาโร ประสงค์จะให้ผมเพิ่มอักษรธรรมลงในฟอนต์ Laobuhan ของท่าน ผมจึงถือโอกาสทดลองทำตามข้อกำหนดของ USE อันใหม่เสียเลย โดยพยายามทดสอบบน Windows 11 ด้วย นอกเหนือจากที่เคยทดสอบเฉพาะบนลินุกซ์ โดยในข้อกำหนดใหม่ USE มีการ reorder สระหน้าให้ จึงไม่ต้องทำในฟอนต์เองอีกต่อไป สิ่งที่ควรทำ (เรียงตามลำดับการเรียกใช้โดย USE) จึงเป็น:

  • pref (Pre-base forms) เพื่อระบุตำแหน่งของระวง เพื่อที่ USE จะ reorder ไปไว้หน้าพยัญชนะต้นให้ในภายหลัง
  • rphf (Reph forms) ความจริงเป็นกฎสำหรับ reorder เรผะ ของอักษรอินเดีย ที่ดูแล้วน่าจะใช้กับ ไม้กั๋งไหล ของอักษรธรรมได้ แต่เมื่อทดลองทำจริงแล้วไม่เป็นผล ซึ่งในเอกสารของไมโครซอฟท์เองก็ระบุไว้ว่า:
    Note that the category REPHA is not currently supported by USE.
    ซึ่งหมายความว่า rphf เองก็จะยังไม่ทำงาน
  • blwf (Below-base forms) เพื่อทำตัวห้อย ตัวเฟื้อง
  • liga (Standard ligatures) เพื่อทำรูปเขียนพิเศษ เช่น นา

ผลการทดสอบคือ แอปที่ใช้ Harfbuzz (GTK, LibreOffice, Firefox) และ MS Word วาดแสดงอักษรธรรมได้ดี ยกเว้นในบางกรณี เช่น สระอาที่ตามหลังวรรณยุกต์จะมี dotted circle แทรกเข้ามา และตัว ล ห้อย (ที่ไม่ใช้ SAKOT) ที่ใช้ร่วมกับสระหน้าจะแสดงถูกต้องเฉพาะเมื่อลงรหัสด้วยลำดับที่เหมาะสมเท่านั้น กล่าวคือ ถ้าใช้ structure ข้อความแบบใหม่ที่ออกแบบให้รองรับ USE ก็ น่าจะ แสดงได้ถูกต้องเป็นส่วนมาก

อักษรธรรมบน LibreOffice Writer แบบใช้ USE
อักษรธรรมบน MS Word แบบใช้ USE

แต่กับ Notepad บน Windows ดูเหมือนกฎต่างๆ จะไม่ทำงานเลย! สถานการณ์อาจต่างจากลินุกซ์ที่แอปแทบทุกตัวใช้ Harfbuzz กันหมด แต่บน Windows ยังแยก implement กันอยู่

อักษรธรรมบน Windows Notepad แบบใช้ USE
อักษรธรรมบน Linux Mousepad แบบใช้ USE

แต่ปัญหา dotted circle ในบางกรณีของ USE ก็ยังทำให้ผมต้องค้นหาข้อมูลต่อ ทั้งไล่ซอร์สโค้ดของ Harfbuzz ทั้งค้นเว็บ จนไปเจอ โพสต์ของ Richard Wordingham เล่าปัญหาเดียวกันที่เขาเจอ โดยได้พูดถึงวิธีหลบเลี่ยง USE ด้วย โดยในทุกกฎ GSUB, GPOS ไม่ต้องระบุรหัสอักษรเป็น lana เลย เพียงใช้รหัสอักษร DFLT ก็พอ ซึ่งถ้าเป็น Harfbuzz ก็จะไปเรียก default engine แทน USE ซึ่งฟอนต์ก็ต้องเตรียมกฎ GSUB ให้มันทำงานทุกอย่างแทน USE ทั้งหมด ผ่าน OpenType feature เท่าที่ใช้รองรับ Standard Scripts ซึ่งก็จะมี feature จำนวนจำกัดให้ใช้ เช่น ccmp และ liga เป็นต้น

เรื่องกฎไม่ใช่เรื่องยาก เพราะฟอนต์ Khottabun ที่ผมสร้างตั้งแต่ก่อนที่ Harfbuzz จะใช้ USE ก็ทำทุกอย่างเองหมดอยู่แล้ว ก็เลยหยิบมาทดลองได้ทันที โดยตัดรหัสอักษร lana ออกจากกฎ ccmp ที่ใช้ทั้งหมด

ผลปรากฏว่า มันยังคงวาดแสดงได้อย่างสมบูรณ์บน Harfbuzz แต่คราวนี้มันทำงานบน Notepad บน Windows ด้วย! โดยในทั้งสองกรณี กฎที่เขียนไว้สำหรับไม้กั๋งไหลที่เคยไม่ทำงานบน Harfbuzz ก็กลับทำงานเรียบร้อย! (ดูตัวอย่างคำว่า มงฺคล แบบแรก)

อักษรธรรมบน LibreOffice Writer แบบใช้รหัสอักษร DFLT
อักษรธรรมบน Windows Notepad แบบใช้รหัสอักษร DFLT

แต่ข่าวร้ายคือ มันเละบน MS Word

อักษรธรรมบน MS Word แบบใช้รหัสอักษร DFLT

ซึ่งจากการทดลองก็สรุปได้ว่ามันเกิดจาก:

  • ข้อจำกัดของ ccmp บน engine ของ MS Word ที่ดูจะไม่รองรับ contextual substitution ที่ซับซ้อน (เข้าใจว่าทำได้แค่ multiple substitution และ ligature substitution อย่างง่ายเท่านั้น)
  • engine ของ MS Word ยังคงใช้ USE เช่นเดิม ทำให้ถึงแม้จะเปลี่ยนไปใช้ liga ที่สามารถทำ contextual substitution ซับซ้อนได้ ก็จะเกิดการ reorder สระหน้าซ้ำซ้อนกันระหว่างโดยตัว engine เองก้บโดยกฎในฟอนต์จนสระหน้าสามารถเลื่อนข้ามพยัญชนะไปได้ถึง 2 ตำแหน่ง

ถ้าคิดตามหลักเหตุผลแล้ว ถ้า ccmp ที่ USE เรียกใช้ในขั้น preprocessing สามารถทำงานได้อย่างถูกต้อง โดยให้กฎแทรก glyph ผีสักตัว (เช่น ZWNJ) ไว้หน้าสระหน้าที่สลับลำดับแล้ว เพื่อให้มันคั่นระหว่างสระหน้ากับพยัญชนะที่อยู่ก่อนหน้าไว้ ก็ควรจะสามารถป้องกัน USE ไม่ให้ reorder ซ้ำอีกได้ แต่ในเมื่อ DirectWrite engine ที่ MS Word ใช้ไม่รองรับ ccmp ที่ซับซ้อน (แบบที่ Harfbuzz ทำ) มันก็จบเห่ตั้งแต่ต้น ส่วน liga หรือ clig ที่รองรับ contextual substitution แบบซับซ้อนได้ ก็ถูกเรียกทำงานหลังการ reorder ภายในของ USE ไปแล้ว จึงไม่สามารถควบคุมอะไรได้ทันการณ์

ในเมื่อ USE ก็ไม่สมบูรณ์ จะเลี่ยง USE ก็ติดปัญหากับ MS Word ผมจึงอยู่บนทางเลือก:

  1. ใช้ USE ไปเลย แล้วรอให้มีการแก้ปัญหาของ USE อีกที
  2. เลี่ยง USE ต่อไป โดยได้การทำงานของไม้กั๋งไหลเพิ่มมาด้วย แต่ยอมทิ้งการรองรับบน MS Word
  3. เลี่ยง USE แบบต้องปรับฟอนต์ขนานใหญ่ให้ใช้ ccmp ในการ reorder ให้ได้ ผ่าน ligature glyph แล้วค่อยรื้อกลับใหม่อีกทีเมื่อ USE พร้อม

การเลือกครั้งนี้จะไม่ยากเลยถ้าการรองรับอักษรธรรมใน Unicode มีทิศทางที่ชัดเจน แต่ปัญหาคือมันไม่ขยับมา 6 ปีแล้ว

แอปที่ใช้ Harfbuzz นั้นทำงานได้กับทุกทางเลือก ปัญหาอยู่ที่แอปนอกเหนือจากนั้น โดยในที่นี้คือ MS Word กับ Notepad ซึ่งเราต้องเลือกเอาอย่างใดอย่างหนึ่ง ถ้าใช้ USE ก็จะได้ MS Word ที่ทำงานได้เท่าที่ USE รองรับ ถ้าไม่ใช้ USE ก็จะได้ Notepad ที่ทำงานเต็มรูปแบบ

โดย Notepad เองก็ถือเป็นตัวแทนของแอปอื่นๆ ที่รองรับ OpenType แบบผิวเผินด้วย ในขณะที่ MS Word ก็น่าจะเป็นแอปหลักแอปหนึ่งที่ผู้ใช้ฟอนต์จะใช้เตรียมเอกสาร (แต่จะให้ดี LibreOffice เป็นทางเลือกที่เปิดกว้างต่อ solution ต่างๆ มากกว่า)

ผมจะวิเคราะห์ใน blog หน้าว่าจะพิจารณาชั่งน้ำหนักอย่างไรต่อไป

ป้ายกำกับ: ,

29 ธันวาคม 2565

From C++ to Rust So Far

บันทึกเมื่อเรียน Rust ได้ครึ่งทาง

ผมเคยเรียน Rust ไปแล้วรอบหนึ่ง ตอนนั้นเรียนจากเว็บไหนก็จำไม่ได้ เขียนบนเว็บ คอมไพล์บนเว็บ ไม่เหลือร่องรอยในเครื่องให้ทบทวน แถมเนื้อหาก็ไม่ได้ลงลึกอะไร เรียนเหมือนไม่ได้เรียน ความมึนไม่ปรากฏ และไม่นานก็ลืม รอบนี้จึงขอเรียนแบบจริงจังเพื่อหวังให้นำไปใช้ได้จริง หลังจากที่ได้เห็นการใช้งานที่หลากหลายของภาษาในที่ต่างๆ จนดูเหมือนพยายามจะแทนที่ C/C++ ให้ได้

ครั้งนี้ผมเลือกที่จะเรียนแบบมี quiz จากแหล่งซึ่งมีเนื้อหาเดียวกับหนังสือ The Rust Programming Language (the book) แต่แทรก quiz เป็นระยะ

ซึ่งผมขอวิจารณ์ว่าเนื้อหายังอธิบายประเด็นสำคัญไม่ละเอียดเท่าที่ควร บางเรื่อง เช่น syntax ของ reference อ่านแล้วยังไม่เกิดความมั่นใจว่าเมื่อไรควรใช้แบบไหน ในขณะที่ quiz ซึ่งควรจะช่วยตรวจสอบความเข้าใจได้กลับมีหลายข้อที่ถามเกินเนื้อหาปัจจุบัน ทำให้วัดอะไรไม่ได้ และยังทำให้เกิดความไม่มั่นใจในสิ่งที่เรียน แต่หลายข้อที่ถามไม่เกินเนื้อหาก็ช่วยทบทวนได้ดีเหมือนกัน และบางข้อยังบ่งชี้ได้ว่าเนื้อหาที่อธิบายไปนั้นยังขาดรายละเอียดบางอย่าง จึงขอแนะนำผู้ที่จะเรียนจากแหล่งนี้ว่าพยายามทำ quiz เท่าที่ทำได้ แต่ถ้าทำไม่ได้เพราะข้อมูลยังไม่เพียงพอก็ไม่ต้องเสียกำลังใจ ข้ามๆ ไปบ้างก็ได้ แล้วค่อยกลับมาดูทีหลังเมื่อได้เรียนเพิ่มเติม

หลังจากเรียนมาได้ประมาณครึ่งหนึ่งของจำนวนบททั้งหมด ก็ขอเขียนบันทึกระหว่างทางสักหน่อยเกี่ยวกับประเด็นของ Rust ที่คิดว่าน่าสนใจ

Ownership

ด้วยความรู้ครึ่งทางนี้ ผมได้เห็นความพยายามของ Rust ที่จะจัดการ memory แบบไม่ใช้ garbage collection โดยขจัดปัญหา memory leak หรือ memory corruption ต่างๆ ที่พบใน C/C++ โดยเฉพาะใน C โดยเครื่องมือหลักที่ใช้คือ ownership ที่ฝังไว้ในตัวภาษาเลย

เวลาเขียน C เราจะสร้าง contract ต่างๆ ของ API ว่า object ต่างๆ ที่สร้างขึ้น เป็นหน้าที่ของใครในการทำลาย โดยเขียนไว้ใน API documentation และเป็นเรื่องของนักพัฒนาที่ ควร พยายามทำตาม บางครั้งมีการถ่ายโอนความรับผิดชอบในการทำลายไปให้ object อื่นก็ควรทำให้ชัดเจน โครงการไหนที่มีการจัดการเรื่องนี้ดีก็จะปลอดปัญหา แต่ถ้ามีจุดผิดพลาด ก็กลายเป็นการเขมือบทรัพยากรระบบ หรือทำให้โปรแกรมตาย หรือกระทั่งโดนจารกรรมข้อมูลได้ โดยมีการพัฒนาเครื่องมือต่างๆ มาช่วยตรวจสอบ เช่น valgrind

ถ้าเป็น C++ ก็มี constructor/destructor รวมถึง smart pointer ต่างๆ มาช่วยจัดการให้ ก็สะดวกกว่า C ขึ้นมาหน่อย แต่ก็ยังอาศัย วินัย ของโปรแกรมเมอร์บ้างอยู่ดี การแยก composition/aggregation ออกจาก association ใน class diagram ก็เพื่อสื่อสารเรื่องนี้

แต่ Rust กำหนดเรื่องนี้ไว้ในตัวภาษาเลย เรียกว่า ownership และใช้ move semantics เป็นหลักใน assignment และการ pass argument ทำให้มีการถ่ายโอน ownership เพื่อลดการ clone โดยไม่จำเป็น โดยที่ยังมี reference คล้ายของ C++ ให้ใช้ในกรณีที่แค่ ยืมใช้ (borrow) โดยไม่ take ownership โดยทั้งหมดนี้มีการตรวจสอบให้ตั้งแต่ตอนคอมไพล์!

ตอนที่เรียนเรื่องนี้ก็รู้สึกว่าเป็นความกล้าหาญมากที่ Rust พยายามจัดการ memory ด้วยคอมไพเลอร์ และถ้าทำได้จริงโดยไม่เพิ่มความยุ่งยากเกินไปก็เป็นเรื่องดี แต่ในใจก็สงสัยว่าจะทำได้แค่ไหน มีอะไรต้องแลกแน่ๆ ซึ่งก็เป็นไปตามนั้น เยอะเสียด้วย มันเหมือนการเลือก program design แบบหนึ่งที่อาจมีในโครงการ C/C++ บางโครงการ แล้วบังคับให้ใช้แบบนั้นไปเลย โดยบัญญัติเป็น syntax และ semantics ของภาษา โปรแกรมเมอร์ภาษา Rust ซึ่งเรียกว่า Rustacean (มันคือ Crustacean หรือสัตว์พวกกุ้งกั้งปูที่ไม่เขียน C? :P) ต้องใช้โหมดความคิดอีกโหมดหนึ่งในการเขียนโปรแกรมไปเลย

และขอบอกว่าสมัยที่เรียน C นั้น ผมไม่เคยมึนกับ pointer ของ C เท่ากับที่มึน ownership ของ Rust เลย! ตอนที่เขียน C นั้น ownership ต่างๆ เกิดจากวินัยที่เราสร้างขึ้นเองจากความเข้าใจ แต่พอมันกลายเป็นข้อบังคับของภาษา มันจะเหมือนการเดินที่ต้องคอยระวังไม่ให้เหยียบมดหรือกระทั่งทำให้มดตายทางอ้อม ขนาดนั้นเลย

Safe Borrows

บ่อเกิดของความมึนคือ Rust มีการสับรางการยืมไม่ให้เกิด race condition โดยอนุญาตให้มีการยืมแบบอ่านอย่างเดียว (immutable borrow) ได้พร้อมกันหลายทาง แต่ทันทีที่มีการยืมแบบเขียนได้ (mutable borrow) เกิดขึ้น การยืมอื่นทุกทางจะหมดอายุทันที ไม่ว่าจะยืมแบบเขียนได้หรือไม่ได้ และถ้ามีการใช้การยืมที่หมดอายุแล้ว ก็จะเกิด error ทันที โดยเป็น error ที่ตรวจพบตั้งแต่ตอนคอมไพล์!

การตรวจสอบนี้ ช่วยป้องกันบั๊กอย่างเช่นมีการลบ element ในลูปที่ iterate ใน vector เพราะการลบ element ต้องยืม vector แบบ mutable ทำให้ iterator ที่กำลังยืม vector อยู่เหมือนกันไม่สามารถ iterate ต่อไปได้ หากต้องการลบ element ในลูปจริงๆ ก็คงต้องเลี่ยงไปใช้ indexing แทน ซึ่งปลอดภัยกว่า เป็นต้น

ฟังดูเหมือนไม่มีอะไร แต่เอาเข้าจริงเวลาอ่านโค้ดมันซับซ้อนว่ามีการยืมซ่อนอยู่ที่ไหน ใครหมดอายุตอนไหน เวลาทำ quiz จะมึนมาก แต่เวลาคอมไพล์จริง error message ช่วยได้เยอะมาก

Lifetime

ใน C/C++ มีบั๊กประเภท dangling pointer หรือ use after free ที่เกิดจากการ dereference pointer ชี้ไปยัง object ที่ถูกทำลายไปแล้ว Rust ป้องกันปัญหานี้ด้วยการกำหนด lifetime ของ object ทำให้การใช้ reference ที่นอกจากไม่อนุญาตให้เป็น null แล้ว ยังไม่อนุญาตให้ reference ไปยัง object เกินอายุขัยของ object ด้วย ถ้าตรวจพบก็จะมี compiler error เช่นกัน!

จากสามเรื่องข้างต้น น่าทึ่งที่ Rust พยายามขนาดนี้เพื่อป้องกันปัญหา memory ตั้งแต่ตอนคอมไพล์ สิ่งที่แลกมาคือคุณต้องใช้ความระมัดระวังอย่างหนักในการเขียนโค้ด ซึ่งบางครั้งกฎเหล่านี้ยังครอบคลุมไปถึงกรณีที่พิสูจน์ได้ว่าปลอดภัยด้วย แต่ถ้ามันแหกกฎของ Rust ก็ถูกตีตกได้

ยังมีเรื่องอื่นที่ขัดกับความรู้สึกของคนที่มาจากภาษา C/C++

The Default Immutability

ในขณะที่ C/C++ และภาษาอื่นจำนวนมากใช้ ตัวแปร เพื่อการคำนวณ โดยปกติตัวแปรจึงสามารถเปลี่ยนค่าได้ ยกเว้นเมื่อต้องการห้ามเปลี่ยนจึงใส่ qualifier const กำกับ แล้วคอมไพเลอร์จะช่วยโวยวายให้ถ้ามีความพยายามจะเปลี่ยนค่าที่กำหนดเป็น const ไว้ แต่ Rust จะให้ตัวแปรเป็น const หรือ immutable โดยปริยาย เมื่อต้องการเปลี่ยนค่าจึงใส่ keyword mut (mutable) กำกับ นัยว่าเป็นการเอื้อต่อ functional programming และ concurrency กระมัง? แต่ในแง่หนึ่งก็เป็นการเน้นให้เห็นชัดเจนว่าค่าไหนมีโอกาสถูกเปลี่ยน เช่นตอนที่ให้ฟังก์ชัน ยืม object ไป อาจมีการเปลี่ยนค่าใน object นั้นกลับมา ซึ่งโค้ดที่เขียนด้วย C/C++ จำนวนมากที่ไม่ค่อยเคร่งครัดการใช้ const ก็จะเห็นเรื่องนี้ได้ไม่ชัด แต่กับ Rust ถ้าไม่เคร่งครัดคุณก็หมดสิทธิ์เปลี่ยนค่า ซึ่งก็อาจจะดี แต่คุณจะเผลอสับสนกับโหมดความคิดอยู่บ่อยๆ

The Default Move Semantics

ใน C++11 มีสิ่งที่เรียกว่า move semantics ซึ่งใช้ย้ายข้อมูลจาก object หนึ่งไปยังอีก object หนึ่งที่เป็น class เดียวกันพร้อมกับล้างค่าใน object ต้นทาง ทำให้เกิดการย้าย ownership โดยไม่มีสำเนาเกิดขึ้น ซึ่งนอกจากจะช่วยจัดการ ownership แล้ว ยังช่วยลดการ clone และ destruct โดยไม่จำเป็นอีกด้วย เพราะ move constructor สามารถย้ายเฉพาะ pointer ที่ชี้ data ได้เลย ไม่ต้องสำเนาตัว data ใน object ใหม่ และทำลาย data ใน object เก่า โปรแกรม C ที่ออกแบบดีๆ ก็สามารถ move data ในลักษณะนี้ได้เช่นกัน แต่เรื่องนี้ก็ยังไม่ใช่ default ของทั้ง C และ C++ ต้องมีการทำอะไรบางอย่างเพิ่มเติมเพื่อจะใช้ เช่น ใน C++ ก็ใช้ std::move()

แต่ Rust ใช้ move semantics โดยปริยาย ทั้งใน assignment และ function call ยกเว้นว่าต้องการทำสำเนาจึง clone เอา หรือแค่ให้ยืมผ่าน reference เอา ซึ่งก็อาจทำให้โค้ดโดยรวมมีประสิทธิภาพขึ้น แต่คุณก็ต้องระวังผลของการเปลี่ยนแปลง ownership ที่เกิดขึ้นด้วย

นอกจากนี้ ก็มีหลายสิ่งที่เป็นประโยชน์ใน Rust

Enum as Tagged Union

เวลาใช้ union ใน C/C++ เรามักใช้ร่วมกับ field ที่ระบุว่าเราใช้ข้อมูลชนิดไหนใน union นั้น ซึ่งเคยเห็นบางตำราเรียกว่า tagged union

Rust เอาสิ่งนี้มารวมเข้ากับ enum โดยกำหนดให้ enum สามารถมีข้อมูลประกอบได้ ซึ่งหาก implement ด้วย C/C++ ก็คือการใส่ union ประกอบกับ enum เป็น tagged union นั่นเอง ทำให้สร้าง tagged union ใน Rust ได้สะดวกสบาย ไม่ต้องเขียนโค้ดในการอ่าน/เขียนเอง

Option, Result

Rust ลงทุนสร้าง enum แบบมีข้อมูลประกอบ ก็นำมาใช้ประโยชน์กับค่า return ของฟังก์ชันที่สามารถ return ทั้งสถานะ success/failure และผลลัพธ์ที่ได้ในคราวเดียว

Option คือ enum ที่มีสองค่า คือ None กับ Some โดยค่า Some สามารถมีข้อมูลประกอบได้ ใช้กับฟังก์ชันที่ทำอะไรสักอย่างให้ได้ผลลัพธ์โดยอาจล้มเหลวได้ เช่น การค้นหาข้อมูล ในภาษา C/C++ เราอาจให้ฟังก์ชัน return สถานะ success/failure และถ้า success ก็เขียนข้อมูลลงใน parameter ที่ call by reference มา เช่น

bool Table::FindData(const char* key, Record& result) const;

หรืออีกแบบคือ return pointer ไปยัง object ที่สร้างขึ้นใหม่ โดยถ้าล้มเหลวก็ return NULL

Record* Table::FindData(const char* key) const;

แต่ Rust ไม่มี null pointer/reference แต่จะ return เป็น Option มาเลย

impl Table {
    pub fn find_data(&self, key: &str) -> Option<Record> {
        ...
    }
}

โดยผู้เรียกจะตรวจสอบค่า enum ที่ return มาก่อนดึงค่าออกมาใช้ก็ได้ หรือจะใช้ method ของ Option ในการแกะห่อเอาค่ามาใช้ก็ได้ ซึ่งเป็นวิธีที่ถือว่าดูดี

Result ก็คล้ายกัน คือเป็น enum ที่มีสองค่า คือ Ok กับ Err โดยค่า Ok สามารถมีข้อมูลผลลัพธ์ประกอบได้ และค่า Err ก็มีข้อมูล error ประกอบได้ (ต่างกับ None ของ Option ที่ไม่มีข้อมูลประกอบใดๆ)

Trait-Bound Generic

trait คือสิ่งที่คล้ายกับ interface ในภาษา OOP ต่างๆ แต่ไม่เหมือนกันเสียทีเดียว

generic ก็เหมือนกับ template ของ C++ ซึ่งหากใครเคยใช้ STL ของ C++ จะรู้ว่า type ที่จะมาเป็น parameter ของ template หนึ่งๆ มักมีข้อแม้บางอย่าง แล้วแต่ template ที่ใช้ เช่น ต้องเปรียบเทียบค่ากันได้ หรือต้องรองรับการ move ฯลฯ แต่ไม่เคยมีการประกาศชัดเจน จะรู้ได้ก็ตอนที่คอมไพเลอร์ฟ้องว่าขาดคุณสมบัติ แล้วค่อยไปหาทางอุดทีละเรื่องเอา (ผมว่านี่แหละคือสิ่งที่คล้ายกับ duck typing ใน C++)

แต่ generic ของ Rust สามารถประกาศคุณสมบัติของ type ที่เป็น parameter ได้ว่าต้องรองรับ trait ใดบ้าง ทำให้รู้แต่ต้นว่าต้องเตรียมคุณสมบัติอะไรไว้

Rust ยังมีรายละเอียดให้เรียนรู้อีกเยอะ นี่ผมเพิ่งมาได้ครึ่งทาง ก็ยังต้องเรียนรู้กันต่อไป

ป้ายกำกับ: , ,

23 ตุลาคม 2564

IBus-LibThai 0.1.5

IBus-LibThai 0.1.5 ออกไปแล้วเมื่อวันพุธที่ผ่านมา หลังจากไม่มี release มาเกือบ 5 ปี โดยมีเงื่อนไขหลักที่ทำให้จำเป็นต้องออกรุ่นใหม่คือปัญหาในระบบตั้งค่า อาการคือตั้งค่าเสร็จแล้วไม่มีผลต่อตัว engine ตั้งยังไงก็ได้ค่า default

ผมไม่ได้ติดตามการพัฒนาของ IBus มาระยะหนึ่ง จึงไม่ทราบว่ามีความเปลี่ยนแปลงอะไรบ้างจนทำให้วิธีตั้งค่าแบบเดิมใช้ไม่ได้ จึงเริ่มจากตรวจสอบ engine อื่นว่าปัจจุบันเขาทำยังไงกัน ก็ได้พบว่าหลาย engine ได้ย้ายจากการใช้ IBusConfig ซึ่งเป็น API ของ IBus เองสำหรับการจัดการค่าตั้งไปใช้ระบบอื่น บาง engine ถึงกับสร้างระบบ config ของตัวเองขึ้นมาเลย

แต่ผมก็ยังคงพยายามปรับแก้ภายใต้ API ของ IBus ต่อไป โดยเปลี่ยนจากการ restart IBus เพื่อ reload config มาเป็นการ watch ค่า config แล้วปรับใช้ค่าที่เปลี่ยนทันทีแทน ซึ่งก็ทำให้ engine เปลี่ยนพฤติกรรมตามค่าที่ผู้ใช้ตั้งได้ ภายใน session เดิม แต่ไม่สามารถ save ค่าไว้ให้มีผลใน session ถัดไปได้

ผมแน่ใจว่าทำทุกอย่างเท่าที่ document บอกแล้ว ถ้าจะมีอะไรที่ undocumented ก็ต้องแกะซอร์สของ IBus ซึ่งพอลงมือแกะก็พบว่า แม้แต่ IBus แกนหลักเองก็ไม่ใช้ IBusConfig แต่ไปใช้ GSettings ของ GLib!

จะรออะไรล่ะครับ ถึงแกะจนเจอที่ผิด แต่เขาจะสนใจแก้โค้ดที่เขาเองก็ไม่ใช้ไหมล่ะ? ผมจึงตัดสินใจเปลี่ยนไปใช้ GSettings เหมือนกัน

ด้วยระบบ GSettings ซึ่งใช้ dconf เป็น backend ค่าตั้งต่างๆ จะสามารถเข้าถึงได้ด้วยโปรแกรม dconf-editor

ค่า config ของ IBus-LibThai ใน dconf editor

โดยค่า config แต่ละค่าจะมีคำอธิบายจาก GSchema ที่เตรียมไว้

คำบรรยายคีย์ kb-layout ของ IBus-LibThai ใน dconf editor

และผู้ใช้ IBus ก็ยังคงสามารถปรับแต่ง IBus-LibThai ได้จาก preferences ของ engine ใน IBus Preferences ตามปกติ

Preferences ของ IBus-LibThai

พร้อมกันนี้ ยังมีความเปลี่ยนแปลงอื่นในส่วนของผังแป้นพิมพ์:

  • มีการปรับโครงสร้างของข้อมูลผังแป้นพิมพ์เพื่อให้เข้าใจง่าย และสะดวกต่อการปรับแก้
  • ปรับแก้ผังปัตตะโชติที่ยังคลาดเคลื่อนจากแพลตฟอร์มอื่น โดย ขอหารือ กับคุณ @sirn ซึ่งเป็นผู้ใช้ผังปัตตะโชติ ซึ่งคุณ @sirn ก็ได้เสนอแนะเพิ่มเติม จนกระทั่งได้ตำแหน่งปุ่มสำหรับอักษรไทยเพิ่ม คือ ฃ ขวด, ฅ คน, ลากข้างยาว, ยามักการ
  • เพิ่มผัง มนูญชัย ซึ่งเป็นผังแป้นพิมพ์ใหม่ล่าสุดที่เกิดจากการออปติไมซ์ด้วย AI จากตัวอย่างเอกสารร่วมสมัย ผู้ใช้ลินุกซ์ที่สนใจทดสอบหรือใช้งานผังใหม่นี้จึงสามารถใช้งานได้ผ่าน IBus-LibThai

ป้ายกำกับ: ,

16 กันยายน 2564

Red-Black Trees

Red-black tree เป็น self-balancing binary search tree ที่ใช้ในโครงการต่างๆ มากมาย เช่น ใน C++ STL, ใน symbol table ของ compiler/interpreter ต่างๆ และใน Linux kernel ด้วยข้อได้เปรียบ AVL tree เรื่องการเพิ่มข้อมูลเพื่อการ balance ในแต่ละโหนดเพียงบิตเดียว ในขณะที่ AVL tree ต้องการอย่างน้อย 2 บิต รวมถึงการ insert และ delete ที่เร็วกว่า แม้จะใช้เวลา search มากกว่าสักหน่อย แต่ก็ยังเป็น O(log n) เหมือนกัน

ฟังดูน่าศึกษาใช่ไหมล่ะ? แต่พอลองค้นดูจริงๆ มันยุ่งกว่าที่คิดมาก

red-black tree เสนอโดย Leonidas J. Guibas และ Robert Sedgewick (PDF) โดยต่อยอดมาจาก symmetric binary B-tree ของ Rudolf Bayer

symmetric binary B-Tree เป็นการสร้าง perfectly-balanced 2-3-4 tree (B-tree ที่มีทางแยกไม่เกิน 4 และทุก leaf มีความลึกเท่ากันหมด) ด้วย binary tree แบบพิเศษ โดยแทนโหนด B-tree ที่มีทางแยก 3 และ 4 ทางด้วย binary node 2 และ 3 โหนดที่เชื่อมกันด้วยลิงก์แนวนอน (เรียกว่า ρ-arc) ส่วนลิงก์ที่เชื่อมระหว่างโหนดของ B-tree ก็เป็นลิงก์แนวตั้ง (เรียกว่า δ-arc)

Guibas และ Sedgewick ได้นำ symmetric binary B-tree มาสร้างคำอธิบายแบบใหม่ โดยให้ทุกลิงก์เป็นลิงก์แนวตั้งทั้งหมด และเรียก ρ-arc ของ Bayer ว่า red link และเรียก δ-arc ว่า black link แล้วเรียบเรียงนิยามและอัลกอริทึมต่างๆ เสียใหม่ กลายเป็น red-black tree

Red-black representation of a 2-3-4 tree
(ภาพจาก Left-leaning Red-Black Trees โดย Robert Sedgewick)

ส่วนเหตุผลว่าทำไมต้องเป็นสองสีนี้ Sedgewick ตอบไว้ในตอนท้ายของการบรรยายครั้งหนึ่งว่า เป็นเพราะขณะที่พัฒนาเรื่องนี้กันที่ Xerox PARC นั้น เริ่มมีการสร้าง laser printer ที่สามารถพิมพ์สีได้ และสีที่พิมพ์ออกมาดูดีที่สุดในขณะนั้นคือสีแดง

อัลกอริทึมสำหรับ insert ของ red-black tree ค่อนข้างจะเทียบเคียงกับของ 2-3-4 tree ได้อย่างตรงไปตรงมา และใช้ operation พื้นฐานในการ rebalance คล้ายกับของ AVL tree แต่อัลกอริทึมสำหรับ delete นั้น Guibas และ Sedgewick บรรยายไว้ไม่ยาวมาก และยังดูมีขั้นตอนเกินจำเป็น โดยมีการ rebalance ทั้งขาลงและขาขึ้น

หลายตำราจึงคิดโครงสร้างของตัวเองขึ้นมาเพื่อปรับปรุงการ delete ซึ่งหลายกรณีส่งผลถึงวิธีอธิบายโครงสร้างทั้งหมดที่ไม่เชื่อมโยงกับ 2-3-4 tree อีกต่อไป เช่น เรียก red/black node แทน red/black link และอธิบายโครงสร้างในเทอมของ parent/sibling/uncle node ไปเลย ซึ่งทำให้จำเป็นต้องมี parent pointer และไม่อาศัย recursion และพอมาถึงเรื่องการ delete หลายตำราก็อธิบายได้น่าปวดหัวมาก บางฉบับถึงกับจั่วหัวไว้ก่อนเลยว่า You will hate it! บางฉบับข้ามเรื่อง delete ไปเสียดื้อๆ เลยด้วยซ้ำ

Red-black tree with red/black nodes
Red-black tree ที่ใช้ red/black node
(ภาพจาก Wikipedia)

หลังจากอ่านมาหลายแหล่ง รวมถึงฉบับที่ Sedgewick กลับมาเองด้วยโครงสร้าง Left-leaning red-black tree (slide, Coursera) พร้อมกับโค้ดสุด simple แต่ก็มีปัญหาเรื่องการ delete ที่ช้ากว่าของตำราอื่น ดังบทวิจารณ์ Left-Leaning Red-Black Trees Considered Harmful

แต่สุดท้ายก็เจอคำอธิบายที่ผมคิดว่าลงตัวที่สุดจากคอร์สหนึ่งของมหาวิทยาลัย Purdue ซึ่งอธิบาย Red-black tree แบบไม่มี leaning โดยอิง 2-3-4 tree ตั้งแต่ต้นจนจบ อ่านได้ตามลำดับเลยครับ:

  1. (2,4) Trees
  2. 2-3-4 Trees and Red-Black Trees
  3. Deletion from Red-Black Trees

ป้ายกำกับ: ,

23 มิถุนายน 2564

Norasi Small Caps and Old Style Figures

ฟอนต์ Norasi เพิ่มการรองรับการใช้ small caps และ old style figures (ตัวเลขอารบิกแบบหนังสือยุคเก่า) แล้ว ทั้งใน OpenType และ LaTeX

PUA Glyphs

ฟอนต์ Norasi (ฟช ๓ จากโครงการฟอนต์แห่งชาติของเนคเทค) มี PUA glyph สำหรับตัว small caps และตัวเลขอารบิกในหนังสือยุคเก่า (old style figures) มานานแล้ว ผมไม่แน่ใจว่ามีมาตังแต่แรกเริ่มที่นำฟอนต์มาจากโครงการ Omega เลย หรือว่ามาเพิ่มเอาทีหลังใน TLWG เนื่องจากผู้ที่ทำงานกับ Norasi ในช่วงต้นจะเป็นคุณทิม (ชนพ ศิลปอนันต์) เป็นหลัก แต่มันก็อยู่ในรูป glyph ที่มีรหัสยูนิโค้ดในช่วง Private Use Area (PUA) พร้อมกับชื่อ glyph แบบ Postscript ตามอย่าง Adobe Glyph List เช่น เลขศูนย์แบบเก่ามีรหัสยูนิโค้ด U+F730 และมีชื่อ glyph เป็น zerooldstyle และตัว A small caps มีรหัสยูนิโค้ด U+F761 และมีชื่อ glyph เป็น Asmall เป็นต้น

PUA glyph เหล่านี้ โดยปกติจะไม่ใช้ในข้อความ แต่จะใช้เป็นการภายในของตัววาดข้อความ ในทำนองเดียวกับวรรณยุกต์ตัวต่ำ สระบนหลบหาง ป ฝ ฟ และสระอุ อู หลบหาง ฎ ฏ ในอักษรไทยนั่นเอง เท่ากับว่า PUA glyph เหล่านี้มีอยู่ แต่ยังไม่พร้อมใช้ ยกเว้นในระบบที่พยายามจะใช้ PUA เหล่านี้จริงๆ

OpenType

ขณะเดียวกัน เราจะเห็นแอปพลิเคชันสมัยใหม่พยายามรองรับ OpenType feature ต่างๆ ใน UI มากขึ้น จากเดิมที่มีแต่ใน desktop publishing หรูๆ ตอนนี้แม้แต่ word processor อย่าง LibreOffice Writer และ MS Word ก็เริ่มมี UI สำหรับเลือกเปิด/ปิด discretionary feature (ฟีเจอร์ที่กำหนดใน mark up ของเอกสาร และอาจจะผ่าน UI ให้ผู้ใช้เลือก) ของ OpenType แล้ว ซึ่ง small caps (smcp) และ old style figures (onum) ก็เป็นฟีเจอร์ชนิดนี้

หลังจากเพิ่มฟีเจอร์ดังกล่าวในฟอนต์ Norasi แล้วทดลองใช้กับ LibreOffice Writer ก็ได้ผลดังนี้:

  • บรรทัดแรก เป็นตัวเลขแบบเรียงบนบรรทัด (lining figures)
  • บรรทัดที่ 2 เป็นตัวเลขแบบหนังสือยุคเก่า (old style figures)
  • บรรทัดที่ 3 เพิ่มการใช้ small caps จาก glyph ในฟอนต์เอง ผ่าน scmp โดยเลือกฟีเจอร์ Lower case to small capitals ใน character format
  • บรรทัดที่ 4 ใช้ Small capitals จาก text format ของ LibreOffice เอง ซึ่งน่าจะเป็นการสังเคราะห์ small caps ด้วยการย่อส่วนจากตัว capital ปกติ ซึ่งทำให้ได้เส้นที่บางลง และ LibreOffice ก็ได้พยายามลดผลกระทบนี้ด้วยการย่อส่วนแต่เพียงเล็กน้อย ทำให้ได้ตัว small caps ที่ยังสูงกว่าตัว lower case อยู่

สำหรับการใช้ในเว็บ ผมได้ลองทำ หน้าทดสอบ โดยใช้ font-variant: small-caps และ font-variant-numeric: oldstyle-nums ใน CSS

LaTeX

สำหรับผู้ใช้ XeLaTeX และ LuaLaTeX ก็สามารถใช้ OpenType feature ได้โดยตรง แต่สำหรับ pdfLaTeX ซึ่งยังใช้เทคโนโลยี PostScript ธรรมดา ก็จำเป็นต้องมีการรองรับเพิ่มเติมในแพกเกจฟอนต์

Small Caps

LaTeX2e รองรับ small caps ผ่านคำสั่ง \scshape และ \textsc{...} โดยตัว TeX engine จะไปหาการประกาศ sc shape ใน font description (lthnorasi.fd สำหรับฟอนต์ Norasi) เพื่อเชื่อมโยงไปหาไฟล์ TFM (TeX Font Metrics) ของ shape ดังกล่าว

และตรง TFM นี่แหละ ที่เราสามารถ remap อักขระ lower case ไปหา glyph ที่เป็น small caps ได้ ทำให้ TeX เรียงพิมพ์ตัว lower case ด้วยตัว small caps แทน

เท่ากับว่า จากฟอนต์ Norasi ที่เรามีอยู่แล้ว 6 type face (regular, slanted, italic, bold, bold slated, bold italic) เราจะต้องสร้าง face ใหม่อีกหนึ่งชุดที่ remap สำหรับ small caps กลายเป็น 12 type face

แต่เรายังมีรายละเอียดที่ต้องพิจารณาเพิ่มจากการ remap คือ:

  • ต้องตัดกฎ ligature สำหรับ ff, fi, fl, ffi, ffl ออกด้วย เนื่องจากรูป small caps ไม่มี ligature ดังกล่าว
  • ฟอนต์ไทยใช้กฎ ligature ในการจัดเรียงสระ-วรรณยุกต์ที่ไม่ลอยและหลบหางพยัญชนะ (shaping) ซึ่งยังจำเป็นต้องสร้างกฎเหล่านี้สำหรับ small caps อยู่

นั่นจึงนำไปสู่การสร้างไฟล์ .enc ต่างหากที่ remap small caps, ตัดกฎ Latin ligature, คงกฎ ligature สำหรับอักษรไทย พร้อมทั้งเขียน make rules สำหรับสร้าง TFM (สำหรับ TeX) และ VF (virtual font ซึ่งใช้ในขั้น dvi) สำหรับ small caps เพิ่มอีกชุดหนึ่งด้วย

สรุปรวมอยู่ใน GitHub commit นั่นแล

และเมื่อใช้งานผ่านคำสั่ง \textsc{...} ก็ได้ผลดังภาพ:

Small caps in pdfLaTeX

Old Style Figures

ด้วยเทคโนโลยี PostScript ธรรมดา การรองรับตัวเลขแบบหนังสือยุคเก่า (old style figures) ก็ยังคงต้องใช้วิธี remap อักขระตัวเลขไปเป็น PUA glyph ที่เป็น old style ไม่ต่างกับ small caps เพียงแต่ old style figures จะมีประเด็นการใช้งานในทางปฏิบัติที่ต้องพิจารณาเพิ่มเติมด้วย

LaTeX2e มีคำสั่ง \oldstylenums{ตัวเลข) เพื่อแสดง ตัวเลข เป็นแบบ old style ได้ โดยจะใช้ glyph จากฟอนต์ของ Knuth เสมอ แต่ในกรณีที่ต้องการให้ผู้ใช้สามารถใช้ตัวเลข old style จากฟอนต์ของเราได้ ก็จะต้องใช้ช่องทางอื่น

use case ที่น่าจะพบบ่อยในชีวิตจริง คือการใช้ตัวเลข old style ทั้งเอกสาร ทั้งเลขบท เลขหัวข้อ เลขหน้า เลขในข้อความ ฯลฯ ซึ่งตรงนี้จะต่างจาก small caps และแพกเกจฟอนต์มักจะรองรับในรูปของ option ของแพกเกจ

อีก use case หนึ่งคือการใช้ตัวเลข old style เฉพาะที่ ซึ่งมีแพกเกจอย่างน้อยสองตัวที่เตรียมคำสั่งไว้ให้ คือ nfssext-cfr และ fontaxes ซึ่งทั้งสองแพกเกจจัดเตรียมคำสั่งไว้คนละชุด ซึ่งก็ถือเป็นเรื่องปกติ แต่ที่ไม่ปกติคือทั้งสองแพกเกจเรียกใช้ฟอนต์ต่างกันด้วย!

old style figures ไม่ได้ถูกจัดให้เป็น shape หนึ่งของฟอนต์เหมือน small caps เพราะมันมีผลแค่กับตัวเลข ไม่ใช่ทั้งฟอนต์ วิธี implement จึงไม่ใช่การประกาศ shape ใน font description แต่เป็นการสร้าง font family ใหม่ไปเลย! โดยจะมีข้อตกลงบางอย่างในการตั้งชื่อ family ใหม่ที่ว่านั้น และเมื่อผนวกกับคุณสมบัติ proportional/tabular ของตัวเลขเข้าไปด้วยก็กลายเป็นข้อตกลงที่มีรายละเอียดอีกหน่อย

ปัญหาคือ แพกเกจ nfssext-cfr และ fontaxes ใช้ข้อตกลงคนละชุดกัน!

  • nfssext-cfr ใช้ font naming ของ Karl Berry ซึ่งออกแบบมาเพื่อแทนชื่อฟอนต์ให้ได้ใน 8 ตัวอักษร case-insensitive ซึ่งหมายถึงระบบแฟ้มของ DOS นั่นเอง โดยใน 8 ตัวอักษรจะแทนทั้งผู้ผลิต (foundry), ชื่อฟอนต์, ความหนา, variant (เช่น italic, oblique, sans serif, monospace, small caps, old style figures ฯลฯ), encoding, ความกว้าง, ขนาด ซึ่งน่าอัศจรรย์มากกับความพยายามบีบอัดขนาดนั้น แต่ดูไม่ practical เท่าไรกับโลกที่มีฟอนต์มากมายมหาศาล อีกทั้งระบบแฟ้มส่วนใหญ่ในปัจจุบันก็รองรับความยาวเกิน 8 ตัวอักษรกันแทบทั้งสิ้นแล้ว และจะว่าไป ชื่อฟอนต์บางชื่อที่ลงรหัสแบบนี้ก็ยาวเกิน 8 ตัวอักษรไปแล้วด้วย แต่อย่างไรก็ดี มันก็ยังถือเป็นข้อตกลงที่ใช้กันเป็นมาตรฐานของ LaTeX อยู่ และฟอนต์ Norasi ที่ใช้ old style figures ก็จะลงรหัสชื่อ font family (แบบไม่ได้ตรงหลักการเป๊ะ) ได้เป็น norj และ font family แบบปกติก็จะถูกย่อลงเป็น norx (ว่าตามนัยประวัติ ฟอนต์ Norasi ใน ThaiLaTeX ยุคเริ่มแรกก็เคยใช้ชื่อว่า nf3x ก่อนที่จะเปลี่ยนเป็น norasi ในภายหลัง)
  • fontaxes ใช้ข้อตกลงชื่อฟอนต์ของตัวเอง โดยปล่อยชื่อฟอนต์ให้ยาวตามปกติ แล้วเติมท้ายด้วยรหัส variant เช่น Norasi-OsF โดย variant ที่เกี่ยวกับตัวเลขได้แก่
    • OsF = old style figures
    • LF = lining figures
    • TOsF = tabular old style figures
    • TLF = tabular lining figures
    โดยที่ก็ยังรองรับ font naming ของ Karl Berry ด้วย เพียงแต่ไม่ได้บีบความยาวลงเท่านั้น ดังนั้น ตามข้อตกลงนี้ ฟอนต์ Norasi ที่ใช้ old style figures ก็จะใช้ชื่อ font family เป็น norasi-TOsF, norasi-OsF หรือ norasij ก็ได้ เพราะอันที่จริง ตัวเลขในฟอนต์ Norasi เป็น tabular อยู่แล้ว คือกว้างเท่ากันหมด เรียงตรงกันในตารางได้ แต่การใช้ norasi-TOsF จะใช้ได้กับการ mark up ตัวเลขเป็น tabular figures เท่านั้น ใช้ในข้อความธรรมดาไม่ได้ ส่วน norasi-OsF จะใช้ได้ในข้อความธรรมดาและการ mark up เป็น proportional figures เท่านั้น ใช้แบบ tabular ไม่ได้ แต่ norasij จะใช้ได้หมดทุกกรณี ดังนั้น ในกรณีของ Norasi จึงเลือกใช้ชื่อ norasij

จากหลักการทำงานและจากการทดลอง fontaxes ดูจะใช้ได้ในทางปฏิบัติมากกว่า จึงเลือกรองรับ fontaxes เป็นหลัก แต่ก็พยายามรองรับ nfssext-cfr ด้วยตามสมควร

จาก 12 type face ที่ได้จากการทำ small caps มาแล้ว เราก็จะสร้าง font family ใหม่ที่ประกอบด้วย 12 type face นี้ แต่ remap glyph ของตัวเลขไปเป็นแบบ old style เท่ากับว่าเราจะมีทั้งหมด 24 type face แบ่งเป็น 2 family, family ละ 12 type face

เมื่อเขียน make rules เพื่อสร้าง TFM/VF ของ 12 type face ฉบับ old style figures แล้ว เราก็สร้าง LTHnorasij.fd เพื่อประกาศ font family norasij

เพื่อการรองรับ nfssext-cfr เราก็สร้าง LTHnorj.fd ในทำนองเดียวกัน และสร้าง LTHnorx.fd ที่มีเนื้อหาหลักเหมือน LTHnorasi.fd ทุกประการด้วย

ทั้งหมดนี้ก็จะสามารถรองรับใช้ตัวเลขแบบ old style ผ่าน fontaxes และ nfssext-cfr (บางส่วน) ได้แล้ว

Old style figures via fontaxes

Old style figures via nfssext-cfr

สำหรับ use case การใช้ตัวเลข old style ทั้งเอกสาร นั้น เราก็เพิ่ม option norasi-osf และ rmnorasi-osf ใน fonts-tlwg.sty โดยหากเลือกตัวเลือกนี้ก็จะกำหนดฟอนต์ปริยายเป็น norasij แทน norasi เท่านั้นเอง

Old style figures with 'norasi-osf' option

ทั้งหมดนี้อยู่ใน Git แล้ว มีตัวอย่างเอกสารในไดเรกทอรี latex/examples/ คือ:

  • oldnum.tex ใช้ตัวเลขแบบ old style ทั้งเอกสาร และใช้ small caps ในชื่อ section
  • digits-axes.tex ใช้ตัวเลขแบบ old style ผสมกับแบบ lining ผ่าน fontaxes พร้อมทั้งสาธิตการใช้ตัวเลขแบบ tabular/proportional ในตาราง
  • digits-cfr.tex ใช้ตัวเลขแบบ old style ผสมกับแบบ lining ผ่าน nfssext-cfr ซึ่งการใช้ตัวเลขแบบ tabular old style จะยังไม่ทำงาน

หลังจาก make install แล้ว สามารถใช้คำสั่ง make กับไฟล์ PDF ปลายทางได้เลย เช่น make oldnum.pdf

งานนี้สำเร็จลุล่วงได้ ขอให้เครดิตแก่พี่เลี้ยงที่มาช่วยดูแลพ่อ ทำให้ผมสามารถปลีกเวลามานั่งทำงานได้บ้าง

ป้ายกำกับ: , ,

hacker emblem