Theppitak's blog

My personal blog.

04 กุมภาพันธ์ 2559

Thai Font Metrics

ประเด็นเล็ก ๆ ประเด็นหนึ่งที่ถูกอภิปรายกันในหลายโอกาสในหมู่คนทำฟอนต์ไทย หรือกระทั่งในหมู่ผู้ใช้ที่ต้องเตรียมเอกสารให้เข้ากับข้อกำหนด คือเรื่องขนาดของฟอนต์ไทยที่จะเล็กกว่าฟอนต์ตะวันตกที่ point size เดียวกัน เช่น บทความภาษาอังกฤษอาจกำหนดขนาดฟอนต์เป็น 10 หรือ 11 point แต่ถ้าใช้ฟอนต์ไทยขนาดเดียวกันจะเล็กจนอ่านไม่ออก ต้องปรับขนาดเพิ่มเป็น 14 หรือ 16 point ถึงจะเทียบเคียงกันได้ เรื่องนี้คุณเนยสดได้ เขียนอธิบายไว้แล้วเป็นอย่างดี

ฟอนต์จาก TLWG

สำหรับฟอนต์ชุดต่าง ๆ ที่ TLWG ผมได้ปรับขนาดตัวอักษรให้ใหญ่ขึ้น เพื่อให้เข้ากันกับฟอนต์ตะวันตกทั้งหมด ไม่ว่าจะเป็น Fonts-TLWG, Fonts-SIPA-Arundina หรือ ThaiFonts-Siampradesh ทำให้เกิดความไม่เข้ากันกับฟอนต์ไทยอื่น ๆ ที่มีอยู่ในตลาด

สำหรับที่มาที่ไป ผมตัดสินใจใช้ font metrics ที่เข้ากันกับฟอนต์ตะวันตกหลังจากที่ได้พูดคุยกับผู้ใช้และนักพัฒนาใน thread หนึ่งใน gtk-i18n list เมื่อปี 2547 โดยในขณะนั้น ฟอนต์ต่าง ๆ ที่พัฒนา ปรับปรุง และเผยแพร่โดย TLWG ยังใช้ขนาดเหมือนฟอนต์ไทยในตลาด และมีฝรั่งที่พยายามเรียนภาษาไทยบ่นเข้ามาว่าฟอนต์ไทยตัวเล็กมาก อ่านไม่ออก มีฟอนต์ที่ตัวใหญ่กว่านี้แนะนำไหม ผมพยายามอธิบายเหตุผลที่ฟอนต์ไทยต้องตัวเล็ก ว่าเราต้องเผื่อเนื้อที่ให้กับสระบน-ล่างและวรรณยุกต์ ก็ปรากฏว่านักพัฒนา Pango (Owen Taylor) ได้ แนะนำ ว่าสามารถใช้ขนาดตัวอักษรที่เท่ากับฟอนต์ตะวันตกได้ โดยเพียงแต่ขยายระยะระหว่างบรรทัดให้สูงขึ้น และได้รับการ สำทับ จาก Javier Sola นักพัฒนา Khmer OS ว่าฟอนต์ภาษาเขมรก็ใช้วิธีนี้ และได้ผลดี

ในขณะนั้น เรามีฟอนต์ Loma จาก NECTEC ที่เริ่มบุกเบิกทำฟอนต์ UI โดยใช้ metrics ที่สอดคล้องกับฟอนต์ตะวันตกเป็นตัวอย่างอยู่แล้ว หลังจากที่ในวินโดวส์มีฟอนต์ Tahoma ที่ทำงานในลักษณะนี้มาแล้วระยะหนึ่ง เมื่อนำมาประกอบกับข้อมูลที่ได้จากชุมชน GTK+ ดังกล่าว ผมจึงตัดสินใจปรับขนาดฟอนต์ไทยทั้งหมดในแหล่งของ TLWG ตามฟอนต์ตะวันตกตั้งแต่นั้นมา

Font Metrics แบบไทย

ย้อนกลับไปที่ที่มาของ font metrics แบบไทยที่มีการย่อส่วนลงมา เหตุผลเป็นที่เข้าใจได้ไม่ยาก ว่ามาจากการเผื่อเนื้อที่ให้กับสระบน-ล่างและวรรณยุกต์ ทำให้ต้องย่อขนาดของพยัญชนะลง และเพื่อให้เข้ากันกับตัวโรมัน ก็จำเป็นต้องย่อขนาดของตัวโรมันลงตามด้วย ทำให้ตัวโรมันของฟอนต์ไทยมีขนาดเหลือเพียงประมาณ 70% ของฟอนต์ตะวันตกที่มี point size เท่ากัน

เรื่องนี้มีตัวอย่างอย่างละเอียดในหนังสือ แบบตัวพิมพ์ไทย ที่จัดพิมพ์โดยโครงการฟอนต์แห่งชาติของ NECTEC เมื่อ พ.ศ. 2543

Thai font metrics recommendation

ปัญหา

การใช้ font metrics แบบไทยดูจะทำงานได้ดี และเราก็ใช้งานแบบนี้กันมานาน ตั้งแต่ยุค Windows 3.1 ที่ยังใช้รหัส สมอ. และมี Windows Thai Edition ใช้งานกันอยู่ มาจนถึงยุคเปลี่ยนผ่านสู่ I18N และ Unicode ใน Windows 95, Windows XP แล้วเราก็เริ่มเจออะไรทำนองนี้ในเว็บไซต์:

Mixed Thai/English text

ภาพนี้ผมจำลองขึ้นใหม่ เนื่องจากไม่มีระบบรุ่นเก่าให้จับภาพแล้ว แต่คงจะพอจำความรู้สึกนี้ได้ ที่เวลาอ่านเว็บไซต์ต่าง ๆ แล้ว เจอข้อความที่ภาษาไทยเล็กเท่ามด ภาษาอังกฤษใหญ่เท่าหม้อข้าว เพราะ text rendering engine แบบ multilingual ยุคแรก ๆ พยายามแสดงข้อความโดยแยกฟอนต์ตามภาษาเขียน แต่มันไม่แยกแยะว่าภาษาไทยต้องขยาย หรือภาษาอังกฤษต้องย่อ ส่วนข้อความภาษาเขมรนั้น ผมจำลองใส่เข้าไปด้วยเพื่อเปรียบเทียบให้ดูว่าเขา implement แบบไหน

rendering engine ยุคหลัง ๆ เริ่มฉลาดขึ้น โดยพยายามเลือกฟอนต์ตามภาษาหลักหรือตามโลแคล ถ้าฟอนต์นั้นมีอักษรโรมันให้ก็นำมาใช้เลย ถ้าไม่มีจึงจะไปหาจากฟอนต์อื่น ช่วยบรรเทาปัญหาข้อความผสมภาษาไทย-อังกฤษลงได้

แล้วโลกก็หมุนต่อไป สังคมไทยเริ่มเข้าสู่ยุคที่มีภาษาที่สามที่สี่ เด็ก ๆ เริ่มเรียนภาษาจีน ญี่ปุ่น เกาหลี ฯลฯ กันมากขึ้น ประชาคมเศรษฐกิจอาเซียนเริ่มรวมตัวกัน เริ่มมีข้อความภาษาลาว เขมร พม่า เข้ามามากขึ้น การใช้งานแบบ multilingual เริ่มเข้มข้นขึ้นกว่าแต่ก่อนที่เคยมีแค่ไทย-อังกฤษ

คำถามคือ เราจะจัดการระบบ multilingual นี้อย่างไร? เดิมมีแค่สองภาษา ฟอนต์อังกฤษมันโตเกินไป เราก็เพิ่มอักษรอังกฤษย่อส่วนลงในฟอนต์ไทยเสียก็สิ้นเรื่อง แต่เมื่อมีภาษาจีน ญี่ปุ่น เกาหลี ลาว เขมร พม่า เข้ามาร่วมด้วย เราจะเพิ่มอักษรของภาษาเหล่านั้นลงในฟอนต์ไทยหมดไหม? เอาแค่อักษรจีนอย่างต่ำ 5,000 ตัวก็อ่วมอรไทแล้ว ไหนจะอักษรเขมรที่มีอักษรตัวเชิง ทำให้ต้องเผื่อเนื้อที่บน-ล่างกว้างกว่าอักษรไทยเสียอีก เราจะได้ฟอนต์ตัวเล็กลงไปอีก และถ้าสักวันเราจะเพิ่ม อักษรทิเบต (ไม่แน่นะครับ ก็เราชอบประเทศภูฏานกันมากไม่ใช่หรือ) ที่ซ้อนกันสนุกสนานกว่าอักษรเขมรเสียอีก เราจะยิ่งได้ฟอนต์ที่เล็กกระจิ๋วหลิวเลยทีเดียว

Tibetan sample text

เริ่มเห็นภาพกันไหมครับ ว่าการเผื่อช่องว่างสำหรับการซ้อนอักขระมันไม่ scale ในระบบ multilingual

หรือหากผลักภาระไปให้ rendering engine ที่จะต้องจดจำว่าภาษาแต่ละภาษาต้องย่อ-ขยายด้วยอัตราเท่าไรแล้วปรับขนาดฟอนต์เอา มันก็เป็น workaround ที่ไปเพิ่มความยุ่งยากให้กับการออกแบบฟอนต์คู่สองภาษาอื่น ๆ เช่น การสร้างฟอนต์ที่มีอักษรไทย-ลาว, ไทย-เขมร, ไทย-พม่า, ไทย-ยาวี เพื่อใช้ในบริบทของกลุ่มผู้ใช้ทวิภาษา จะต้องออกแบบฟอนต์ให้อักษรแต่ละภาษามีขนาดไม่เท่ากันในฟอนต์เดียวกัน และถ้าเกิดว่า rendering engine ของแต่ละระบบใช้อัตราย่อ-ขยายของแต่ละภาษาต่างกันอีกล่ะ? คงจะพอจินตนาการถึงความยุ่งยากกันได้ และโชคดีที่ไม่มี rendering engine ไหนคิดทำอะไรทำนองนี้

ถ้าเช่นนั้น แบบไหนล่ะถึงจะ scale?

ทางออก

ระบบที่ดูสมเหตุสมผลกว่าก็คือ ทุกภาษาควรอิงบรรทัดฐานเดียวกัน กล่าวคือ

กำหนดให้ point size คือความสูงของตัวอักษร ไม่ใช่ความสูงของบรรทัด

เมื่อกำหนดอย่างนี้ แล้วไปขยายขนาดตัวอักษรไทยทั้งหมดให้สูงเท่า point size ซึ่งจะทำให้วรรณยุกต์เขยิบขึ้นสูงจนตกขอบด้านบนของ em-box นักพัฒนาฟอนต์ก็อาจเกิดประเด็นคำถามต่อไปนี้:

  • จะกำหนดความสูงของบรรทัดให้สูงกว่า em-box ได้อย่างไร?
  • การที่ glyph ของวรรณยุกต์ตกขอบ em-box ซึ่งเป็น bounding box ออกไป จะไม่ผิดหลักการหรือ?
  • font metrics แบบใหม่ จะขัดกันกับแบบเดิมที่ใช้ในเอกสารต่าง ๆ ไหม?

ก็จะตอบทีละประเด็นนะครับ

การกำหนดความสูงของบรรทัด

โดยปกติที่ผ่านมา ฟอนต์ไทยเคร่งครัดกับเรื่องความสูงของบรรทัดที่จะไม่ให้เกิน em-box ทำให้เราต้องย่อขนาดตัวอักษรทั้งหมดให้เล็กลงเพื่อให้สามารถบรรจุสระบน-ล่างและวรรณยุกต์ลงไปใน em-box ได้

แต่ในการคำนวณระยะระหว่างบรรทัดของโปรแกรมต่าง ๆ ที่ใช้ฟอนต์ TrueType หรือ OpenType จะไม่ได้ใช้ ascender/descender จาก em-box นี้โดยตรง แต่จะใช้ค่า sTypoAscender, sTypoDescender และ sTypoLineGap จาก ตาราง OS/2 & Windows Metrics ในตัวฟอนต์ ซึ่งเราสามารถกำหนดค่า sTypoAscender และ sTypoDescender ให้เลย em-box ออกไปได้ และอาจกำหนดค่า usWinAscent และ usWinDescent ด้วย เพื่อไม่ให้บิตแม็ปของตัวอักษรที่วาดถูกขลิบออก

อย่างไรก็ดี ในทางปฏิบัติแล้ว ค่า usWinAscent และ usWinDescent กลับเป็นค่าที่มีผลต่อการคำนวณระยะระหว่างบรรทัดมากกว่า (app ต่าง ๆ ไม่ได้ทำตาม spec ของไมโครซอฟท์นัก แม้ใน spec ของไมโครซอฟท์จะเขียนไว้ว่า This is strongly discouraged. ก็ตาม

โดยสรุปก็คือ ควรกำหนดค่าทั้ง sTypoAscender, sTypoDescender, usWinAscent, usWinDescent ทั้งหมดไว้ก่อน

หากใช้ Fontforge ก็กำหนดได้ในแท็บ OS/2 Metrics (Element > Font Info > OS/2 > Metrics)

Fontforge OS/2 Metrics dialog

ตำแหน่งของวรรณยุกต์

ในยุคก่อน เรามีการจัดตำแหน่งสระบน-ล่างและวรรณยุกต์เพื่อหลบหางพยัญชนะโดยใช้วิธีสร้าง glyph ชุดพิเศษที่มีการเลื่อนตำแหน่งรอไว้ แล้ว rendering engine จะเลือกใช้ glyph ชุดพิเศษเหล่านั้นตามความเหมาะสม เป็นเทคนิคที่เขาเรียกกันว่า positioning by substitution

glyph ชุดพิเศษต่าง ๆ เหล่านี้ จะเลื่อนระยะไว้เรียบร้อยเพื่อให้นำไปวางซ้อนได้พอดีโดยไม่มีการเลื่อนที่อีก ดังนั้น หากจะใช้เทคนิคนี้กับฟอนต์ที่ขยายขนาดขึ้น glyph ของวรรณยุกต์ที่เลื่อนระยะรอไว้ก็จะต้องตกขอบ em-box อย่างเลี่ยงไม่ได้ (แต่ยังอยู่ภายในความสูงของบรรทัด)

แต่นั่นเป็นข้อจำกัดของระบบเก่าก่อนที่จะมี OpenType

ด้วยเทคโนโลยี OpenType เรามีเครื่องมือจัดตำแหน่งการวางซ้อนอักขระ โดยใช้ข้อมูล GPOS ซึ่งอาศัยการกำหนด anchor สำหรับวาง glyph ซ้อนกันเหมือนการต่อชิ้นส่วนเลโก้ โดย glyph ที่เป็นฐานจะมี base anchor เป็นเหมือนเบ้าเสียบรอไว้ ส่วน glyph ที่จะมาวางซ้อนก็จะมี mark anchor เป็นเหมือนเดือยสำหรับเสียบเข้ากับเบ้า

ในการวาง glyph ที่มาซ้อนนั้น ตำแหน่ง glyph ตัวสวมจะถูกเลื่อนที่ด้วยเวกเตอร์ระหว่าง anchor เสียบกับ anchor เบ้า เพื่อให้ anchor สวมกันได้พอดี ดังนั้น ตำแหน่งเดิมของ glyph จะอยู่ที่ไหนก็ไม่สำคัญ เพราะยังไงก็สามารถคำนวณเวกเตอร์ดังกล่าวได้อยู่แล้ว ทำให้เราสามารถวาง glyph ของวรรณยุกต์ไว้ภายใน em-box ก็ได้ถ้าต้องการ นักพัฒนาฟอนต์ที่เคร่งครัดกับ em-box จึงวางใจได้

Thai composition example

สำหรับผู้ที่สนใจ ไมโครซอฟท์มีเอกสารแนะนำ การสร้างฟอนต์ OpenType สำหรับอักษรไทย โดยเฉพาะ

ความเข้ากันได้กับฟอนต์เดิม

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

  • ฟอนต์นานาภาษา ที่มีอักษรของภาษาต่าง ๆ ทั่วโลกเริ่มมีมากขึ้น หลังจากเทคโนโลยี multilingual เริ่มอยู่ตัวและใช้กันเป็นปกติ เช่น
    • Freefont จาก GNU Project (มีอักษรไทย)
    • Noto Fonts จาก Google (มีอักษรไทย)
    • DejaVu fork จาก Bitstream Vera (ยังไม่มีอักษรไทย)
    เฉพาะฟอนต์ที่มีอักษรไทย แน่นอนว่าใช้ font metrics ที่ point size = ขนาดตัวอักษร และใช้ GPOS ในการเลื่อนวรรณยุกต์ขึ้นสูงจาก em-box หรือถ้ายังไม่มีอักษรไทย ดูอักษรภาษาอื่น ๆ ที่มีแล้วต่างก็ใช้แนวทางนี้
  • web font ไทย
    • คัดสรร ดีมาก (Cadson Demak) เท่าที่ตรวจสอบเฉพาะ 3 ฟอนต์ใน Google Fonts พบว่าใช้ font metrics ที่ point size = ขนาดตัวอักษร และใช้ GPOS ในการเลื่อนวรรณยุกต์ขึ้นสูงจาก em-box

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

และสุดท้ายก็อยู่ที่ผู้พัฒนาฟอนต์ทั้งหลายนั่นแหละครับ ว่าจะขยับไปสู่แนวทางนี้กันมากน้อยแค่ไหน

ป้ายกำกับ: ,

02 กุมภาพันธ์ 2559

Thanks

ขอขอบคุณย้อนหลัง สำหรับผู้สนับสนุนงานพัฒนาซอฟต์แวร์เสรีของผมในช่วงตุลาคม 2558 ถึงมกราคม 2559 ที่ผ่านมาครับ คือ:

  • เดือนตุลาคม 2558
    • อ.พฤษภ์ บุญมา
    • ผู้ไม่ประสงค์จะออกนาม
    • คุณธนาธิป ศรีวิรุฬห์ชัย
    • ผู้ไม่แสดงตน 1 ท่าน
  • เดือนพฤศจิกายน 2558
  • เดือนธันวาคม 2558
  • เดือนมกราคม 2559
    • อ.พฤษภ์ บุญมา
    • คุณธนาธิป ศรีวิรุฬห์ชัย
    • คุณ RERNG-RIT
    • ผู้ไม่ประสงค์จะออกนาม

ขอให้ทุกท่านเจริญด้วยอายุ วรรณะ สุขะ พละ การงานเจริญก้าวหน้า คิดหวังสิ่งใดก็ขอให้สมดังปรารถนาครับ

สี่เดือนผ่านไป งานที่ได้ทำไปในช่วงนี้ก็คือ:

  • ทำ libthai ให้ thread-safe พร้อมทั้ง optimize libthai อีกนิดหน่อย ได้เป็น libthai 0.1.23 ตามด้วยการแก้บั๊กใน libthai 0.1.24
  • เตรียม libthai udeb เพื่อใช้ใน debian-installer
  • พัฒนา Fonts-TLWG ให้รองรับการเขียนภาษามลายูปาตานี และออกรุ่น Fonts-TLWG 0.6.2
  • เตรียมแพกเกจ OTF และ WOFF สำหรับ Fonts-TLWG บน Debian
  • งานแปล: Xfce, GNOME, Debian Installer ตามปกติ
  • ปรับแก้ Debian package ของ libdatrie และ libthai โดยประเด็นหลักคือ:
    • แก้ให้ libdatrie-doc และ libthai-doc ใช้ไฟล์ jquery.js ของระบบ (จากแพกเกจ libjs-jquery) แทนฉบับที่ doxygen embed ให้ ช่วยประหยัดเนื้อที่ได้ถึง 146 KB และการใช้สคริปต์ร่วมกันยังช่วยให้สามารถปรับรุ่นทั้งระบบได้สะดวก
    • เขียน debian/rules ใหม่ จากการเรียก dh_* ตรง ๆ มาเป็นการใช้ dh ซึ่งทำให้กฎกระชับลงอย่างมาก และยังได้แพกเกจ -dbgsym สำหรับการดีบั๊กโดยอัตโนมัติอีกด้วย
    • แก้ปัญหา FTBFS ด้วย GCC 6 ที่เกิดจาก libthai (หลังจากย้ายมา GCC 5 ใน Jessie ครั้งหนึ่งแล้ว Debian Stretch กำลังจะใช้ GCC 6 เป็นรุ่น default)

ช่วงเทอมที่ผ่านมา (ส.ค. - ธ.ค. 2558) ผมรับงานสอน เป็นอาจารย์พิเศษที่มหาวิทยาลัยแห่งหนึ่ง ต้องบริหารเวลาอย่างหนัก ทั้งกิจกรรมของครอบครัวเล็ก ครอบครัวใหญ่ ทั้งงานสอนที่ต้องเตรียมสอน ออกข้อสอบ วัดผล แล้วก็สลับมาทำงานซอฟต์แวร์เสรีไปด้วย ทำให้หายไปจาก social network พักใหญ่ บางช่วงไม่ได้เข้าไปเช็กอะไรเลยเกือบทั้งอาทิตย์ บางช่วงได้แต่เช็กอะไรในช่วงสั้น ๆ ไม่สามารถโต้ตอบได้ ตอนนี้ก็พยายามกลับมาทำงานต่อ กลับมาคุยกับชาวบ้านได้มากขึ้น (นิดหน่อย) ล่ะครับ ^_^'

ป้ายกำกับ:

13 มกราคม 2559

Fonts-TLWG OTF and WOFF in Debian

ดังที่ได้กล่าวไว้ในท้าย blog ที่แล้ว ว่าผมได้ตัดสินใจที่จะผลักดันการใช้ฟอนต์รูปแบบอื่นนอกจาก TTF ใน Debian ซึ่งล่าสุดก็ได้เตรียมแพกเกจรุ่น 1:0.6.2-2 และได้ ผ่าน NEW queue เข้าสู่ sid แล้ว เมื่อคืนนี้ ก็ขอบันทึกแนวคิดเบื้องหลังไว้สักหน่อย

โครงการ Fonts-TLWG ได้รวบรวมฟอนต์ต่าง ๆ ที่เจ้าของอนุญาตให้เผยแพร่แบบโอเพนซอร์สได้ เพื่อนำมาพัฒนาต่อให้เข้ากับความต้องการและเทคโนโลยีใหม่ ๆ โดยต้นฉบับก็มักจะมาในรูป TrueType (TTF) ซึ่งใช้เส้นโค้ง quadratic Bézier แต่เมื่อ import เข้าสู่โครงการจะถูกแปลงเป็น cubic Bézier ทั้งหมด ด้วยเหตุผลคือ:

  • การใช้งานกับ LaTeX ซึ่งเน้นการรองรับ e-TeX และ pdfTeX engine นั้น การใช้ฟอนต์ Postscript (ซึ่งใช้เส้นโค้ง cubic Bézier) จะจัดการได้ง่ายกว่า
  • เส้นโค้ง cubic Bézier สามารถปรับแก้ไขได้ง่ายกว่า quadratic Bézier (เคยอธิบายไว้ใน blog เก่า)

การใช้ cubic Bézier ทำให้เราสามารถปรับแต่งฟอนต์ได้เต็มที่ โดยที่ยังสามารถ generate ฟอนต์เป็น TrueType ได้เหมือนเดิม โดยใช้สคริปต์แปลงโค้ง cubic เป็น quadratic พร้อมกับ apply auto instruction ด้วย ซึ่งวิธีนี้ก็มีข้อดีข้อเสียเมื่อเทียบกับการทำงานกับ TrueType โดยตรง คือ

ข้อดี:

  • โค้ง cubic ปรับแก้ได้สะดวกกว่ามาก
  • generate ฟอนต์ได้ทั้งฟอร์แมตที่ใช้ cubic และ quadratic Bézier โดยไม่เกิดจุดต่อโค้งส่วนเกินที่เกินความจำเป็น อันจะทำให้ข้อมูลฟอนต์มีขนาดใหญ่ขึ้น (โดยปกติ โค้ง quadratic จะต้องใช้จุดควบคุมมากกว่าโค้ง cubic ในการแทนเส้นโค้งเดียวกัน และเมื่อแปลงโค้ง quadratic เป็น cubic ก็จะเกิดจุดต่อโค้งระหว่างกลางเพิ่มขึ้นอีก ทำให้ข้อมูลของโค้ง cubic เกิดการขยายตัวเกินความจำเป็นถึง 2 ชั้น ในขณะที่การใช้โค้ง cubic เป็นต้นทาง จะได้ข้อมูลของโค้ง cubic ขนาดเล็ก และจะเกิดการขยายตัวของข้อมูลเพียงชั้นเดียวขณะแปลงเป็น quadratic)
  • ทำ hinting ได้ง่าย เนื่องจากฟอนต์ Postscript อาศัย global hint ควบคุมเส้นอักษรเป็นหลัก (ดู blog เก่า เกี่ยวกับ blue zones และ stem hints) ในขณะที่ TrueType ใช้ instruction ควบคุมจุดต่าง ๆ ซึ่งแทบจะไม่ต่างอะไรกับภาษาแอสเซมบลี

ข้อเสีย:

  • การแปลงโค้ง cubic เป็น quadratic จะต้องเกิดการ interpolate จุดเพิ่มเติม เนื่องจากโค้ง quadratic มีความสามารถในการบรรยายโค้งต่ำกว่า ต้องใช้จุดมากกว่า ในขณะที่การแปลงจาก quadratic เป็น cubic จะได้จุดแบบแม่นตรง (ไม่ใช่ interpolate) และเนื่องจากการ interpolate เป็นไปแบบอัตโนมัติ จึงไม่อาจควบคุมจำนวนจุดที่ interpolate ได้เต็มที่นัก
  • คุณภาพของ TrueType instruction ที่สร้างแบบอัตโนมัติอาจไม่สูงนักเมื่อเทียบกับการเขียน instruction ด้วยมือ

เมื่อเทียบข้อดี-ข้อเสียแล้ว ผมยังคงเลือกเอาโค้งแบบ cubic แม้ข้อเสียจะเกิดกับฟอนต์ TrueType ที่มีผู้ใช้มากที่สุด เพราะข้อเสียต่าง ๆ ถือว่ายอมรับได้ ความคลาดเคลื่อนจากการ interpolate ขณะแปลงเส้นโค้งไม่ได้มีนัยสำคัญอะไร ส่วนเรื่องคุณภาพของ TrueType instruction นั้น เราก็ทำอะไรไม่ได้มาก เนื่องจากเป็นเรื่องที่ถ้าจะทำจริงจะต้องอาศัยแรงงานมหาศาล โดยที่ไม่ได้ช่วยเพิ่มคุณภาพให้กับฟอนต์ที่ใช้ cubic Bézier แต่อย่างใด (ในขณะที่การปรับแต่ง hint ของ Postscript สามารถช่วยเพิ่มคุณภาพของ TrueType instruction ที่สร้างแบบอัตโนมัติได้บ้าง)

กล่าวโดยสรุป โค้ง cubic Bézier คือโค้งที่เป็น native ที่ใช้ในการพัฒนา ถ้าสามารถใช้โค้งนี้ได้โดยตรงก็ย่อมควบคุมอะไรต่าง ๆ ได้ดีกว่า อย่างน้อยก็ในทางทฤษฎี

ที่ผ่านมา การใช้งานฟอนต์ที่ดูจะเหมาะสมในทางปฏิบัติก็คือ

  • เดสก์ท็อป ใช้ TrueType (quadratic Bézier)
  • LaTeX ใช้ Type 1 (cubic Bézier)

ที่ผมยังไม่กล้าผลักดันการใช้ฟอนต์ที่ใช้โค้ง cubic บนเดสก์ท็อปในช่วงที่ผ่านมา ก็เนื่องจากรูปแบบการ hint ของ Postscript นั้น อิงอาศัยความฉลาดของ rasterizer ในการใช้ hint ในฟอนต์มาปรับเส้นโค้งต่าง ๆ (ไม่เหมือนกับ TrueType instruction ที่มีคำสั่งครบสำหรับ rasterizer ไม่ว่าใช้ rasterizer ตัวไหนก็ได้คุณภาพใกล้เคียงกัน) ซึ่งในช่วงแรกนั้น Postscript rasterizer ที่มากับ FreeType วาดตัวอักษรได้ดีเฉพาะบางขนาดเท่านั้น แต่พอใช้กับขนาดอื่น เส้นนอนจะเริ่มหนาและเบลอ (เสียดายที่ไม่ได้จับภาพไว้เป็นเรื่องเป็นราว) จนเมื่อ Adobe contribute CFF rasterizer ให้กับ FreeType คุณภาพที่ได้ก็ดีขึ้นอย่างเห็นได้ชัด ซึ่งเริ่มเปิดใช้จริงใน FreeType 2.5.0.1

ในรอบ Debian Jessie นั้น ผมถึงกับเปลี่ยนฟอร์แมตฟอนต์จาก TTF เป็น OTF ในรุ่น 1:0.6.0-2 ซึ่งทำให้ได้ฟอนต์ที่กินเนื้อที่น้อยลงแทบจะครึ่งต่อครึ่งโดยที่คุณภาพก็ไม่ได้ด้อยกว่า TTF เลย แต่ก็ต้องชะงักเมื่อเจอ Debian #730742 ที่มีผู้ร้องเรียนว่า rasterizer ตัวใหม่ทำให้ฟอนต์ Cantarell ไม่สวย ทำให้ผู้ดูแลแพกเกจ FreeType ของ Debian ต้อง disable CFF rasterizer ของ Adobe แล้วกลับไปใช้ rasterizer ตัวเก่า ผมจึงต้องถอยกลับมาใช้ TTF ในรุ่น 1:0.6.1-2

จนกระทั่งเข้าสู่รอบพัฒนา Stretch ฟอนต์ Cantarell ได้รับการแก้ปัญหาแล้ว จึงได้มีผู้ร้องขอเปิดใช้ CFF rasterizer ของ Adobe อีกครั้ง (Debian #795653) และมีผลตั้งแต่รุ่น 2.6-1 หลังจากรอดูอยู่พักหนึ่งจนแน่ใจว่าเขาไม่ disable กลับอีก ผมจึงตั้งใจไว้ว่าจะเริ่มผลักดันฟอนต์ OTF อีกครั้ง แต่ครั้งนี้ตัดสินใจเลือกการเปลี่ยนแปลงที่ใหญ่กว่านั้น ด้วยการ build ทั้ง TTF และ OTF ให้ผู้ใช้เลือกติดตั้งตามชอบ โดยมีโครงสร้างดังนี้:

  • fonts-thai-tlwg (metapackage)
    • fonts-tlwg-kinnari (dependency package + fontconfig stuffs)
      • fonts-tlwg-kinnari-ttf (TTF files), OR
      • fonts-tlwg-kinnari-otf (OTF files)
    • fonts-tlwg-garuda (dependency package + fontconfig stuffs)
      • fonts-tlwg-garuda-ttf (TTF files), OR
      • fonts-tlwg-garuda-otf (OTF files)
    • ...
  • fonts-thai-tlwg-ttf (metapackage สำหรับติดตั้ง fonts-tlwg-*-ttf ทั้งหมด)
  • fonts-thai-tlwg-otf (metapackage สำหรับติดตั้ง fonts-tlwg-*-otf ทั้งหมด)

คราวนี้ถ้าจะ disable rasterizer ของ Adobe อีก ผู้ใช้ก็สามารถเปลี่ยนกลับไปใช้ TTF ได้ทันที โหะ ๆ

พร้อมกันนี้ ก็ได้เพิ่มแพกเกจ fonts-thai-tlwg-web สำหรับติดตั้ง web font ในรูปแบบ WOFF สำหรับเซิร์ฟเวอร์ที่ต้องการใช้ web font ในเว็บไซต์ต่าง ๆ ด้วย

หากคุณใช้ Debian Sid ก็ติดตั้งได้เลยตั้งแต่วันนี้ หากคุณใช้ Debian testing ก็รออีกสักพัก

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

01 มกราคม 2559

Fonts-TLWG 0.6.2

Fonts-TLWG 0.6.2 ได้ออกไปแล้ว หลังจากใช้เวลาพัฒนาจากรุ่น 0.6.1 อยู่เกือบปีครึ่ง ความเปลี่ยนแปลงหลัก ๆ ของรุ่นนี้คือการรองรับการเขียนภาษามลายูปาตานีด้วยอักษรไทยในฟอนต์ต่าง ๆ แต่ก็มีรายการอื่น ๆ อีกพอประมาณ

สรุปการเปลี่ยนแปลงในรุ่นนี้คือ

  • เพิ่ม Preferred Family/Subfamily (Name ID 16, 17) ในทุกฟอนต์ เพื่อให้ Windows สามารถรองรับ style ได้มากกว่า 4 style ซึ่งจำเป็นสำหรับฟอนต์ Kinnari และ Norasi ซึ่งมีทั้ง Oblique และ Italic และฟอนต์ Umpush ที่มี Light เพิ่มเติมด้วย (ตามคำแนะนำของคุณ Martin Hosken) นอกจากนี้ บางโปรแกรมบนลินุกซ์อย่าง GNOME Software ยังใช้ข้อมูลนี้ในการจัดกลุ่มแพกเกจฟอนต์ให้เป็นกลุ่มเดียวกันด้วย (ตาม รายงานของคุณ Richard Hughes สำหรับฟอนต์ Arundina)
  • กำหนด weight ของฟอนต์น้ำหนักปกติเป็น Regular จากเดิมที่เป็น Regular บ้าง Medium บ้าง Book บ้าง เพื่อให้เป็นไปตามข้อกำหนดของ Name ID 2 เหมือนกันทั้งหมด
  • validate ทุกฟอนต์ พร้อมแก้ปัญหาที่ตรวจพบ ตัวอย่างของปัญหาที่พบก็เช่น จุดต่อโค้งมีความชันเกือบอยู่ในแนวดิ่งหรือราบแต่ไม่ดิ่งหรือราบพอดี (ก็แก้ให้ดิ่งหรือราบพอดี), จุดมีพิกัดไม่เป็นจำนวนเต็ม (ก็ปัดเศษให้เป็นจำนวนเต็ม), Blue zone แคบหรือกว้างเกินไป ฯลฯ
  • เซ็ต OS/2 Version เป็น 4 ทุกฟอนต์ เดิมนั้นกำหนดเป็นค่า Auto ซึ่ง Fontforge จะให้ค่าเป็น Version 1 ตาราง OS/2 & Windows Metrics เป็นตาราง metrics ของฟอนต์ TrueType ซึ่งไมโครซอฟท์ได้พัฒนาเพิ่มจาก spec ของ Apple เพื่อใช้กับ OS/2 และ Windows (อ่านรายละเอียดเพิ่มเติม) ซึ่งมีการปรับปรุงมาเรื่อย ๆ โดยรุ่นล่าสุดคือ version 5 แต่รุ่นที่ Fontforge รองรับสูงสุดคือ version 4 จึงกำหนดเลขรุ่นเพื่อให้ฟอนต์มีข้อมูลตาม spec รุ่นใหม่ ๆ ตามคำแนะนำของคุณ Martin Hosken
  • รองรับการเขียนภาษามลายูปาตานีด้วยอักษรไทย ดังที่เคย บันทึกรายละเอียดไว้ ซึ่งงานส่วนนี้ถือว่ากินเวลาพัฒนานานที่สุด
  • รองรับการสร้าง web font แบบ WOFF ใน configure script เพิ่มเติมจาก Type 1, TTF, OTF
  • เพิ่มบริการ on-line web font เพื่อให้เว็บต่าง ๆ สามารถใช้ฟอนต์ชุด TLWG ในเว็บของตนเองได้ โดยได้เตรียม CSS stylesheet ไว้ ดังรายละเอียดในหน้า TLWG Web Fonts

เนื่องจากรุ่นนี้ไม่ได้มีความเปลี่ยนแปลงในส่วนของ LaTeX มากนัก จึงไม่อัปเดตรุ่นใน CTAN

รุ่นนี้ผมตัดสินใจเริ่มผลักดันการใช้ฟอนต์รูปแบบต่าง ๆ จึงได้เตรียม generated fonts ในรูป OTF และ WOFF เพิ่มเติมจากแบบ TTF ที่เคยทำตามปกติ และมีแผนที่จะเพิ่มรูปแบบ OTF (หรืออาจจะ WOFF ด้วย) ใน Debian ให้ผู้ใช้ได้เลือกใช้ด้วยเร็ว ๆ นี้ หลังจากที่ libfreetype6 ใน Debian ได้ enable CFF rasterizer ของ Adobe ใน FreeType อีกครั้งใน Stretch มาระยะหนึ่งแล้ว (Debian #795653) ซึ่งทำให้คุณภาพการ render ฟอนต์ OTF ดีขึ้นมาก (rasterizer ตัวนี้ควรจะได้ใช้กันตั้งแต่รุ่น Jessie แต่เพราะผู้ใช้บางส่วนไม่ชอบ จึงถูก disable ไป [Debian #730742] จนกระทั่งเริ่มรอบพัฒนา Stretch จึง enable ใหม่อีกครั้ง)

ป้ายกำกับ: ,

16 ตุลาคม 2558

LibThai Thread-safety

อันเนื่องมาจากการที่ Pango 1.38.0 ที่ออกมาพร้อมกับ GNOME 3.18 ได้ตัดระบบ dynamic module ทิ้ง แล้วใช้วิธีรวมเข้าในซอร์สโค้ดเลย (GNOME #733882) ทำให้ผู้ดูแลหลัก คือ Behdad Esfahbod ได้มีโอกาสรื้อมอดูลภาษาต่าง ๆ รวมถึงภาษาไทย และพบประเด็นของ libthai ที่รองรับการตัดคำของมอดูลภาษาไทยอยู่ จึงได้ติดต่อมาที่ผมเพื่อให้แก้ไข โดยประเด็นสำคัญคือเรื่อง thread-safety

โค้ดใน libthai มีส่วนหนึ่งที่ทำให้ไม่ปลอดภัยเมื่อเรียกใช้ผ่าน thread หลาย thread พร้อมกัน ซึ่งเป็นส่วนที่ผมใช้ลดปริมาณการเรียก malloc() โดยยังไม่ free() โหนดทางเลือกต่าง ๆ ของการตัดคำที่เลิกใช้แล้วในทันที แต่เอาไปฝากไว้ใน free list เพื่อนำกลับมาใช้ใหม่ และไอ้เจ้า free list ที่เป็นเสมือนโกดังเก็บของเก่ารอ reuse นี่แหละ ที่ implement แบบ static โดยไม่มีการจองก่อนเข้าใช้ ทำให้เมื่อมีหลาย thread เข้าใช้พร้อมกันจะเกิด race condition ขึ้นได้

ประเด็นนี้ pango รุ่นนี้แก้ปัญหาเฉพาะหน้าด้วยการ ล็อคก่อนเรียก th_brk() เพื่อให้ thread ต่าง ๆ เข้าใช้ฟังก์ชันนี้ทีละ thread ซึ่งอาจกลายเป็นคอขวดของระบบหลาย thread ได้ ทางที่ดีคือทำให้ libthai threadsafe เสีย

ทางเลือกที่เป็นไปได้จึงมี 3 ทาง

  1. ยกเลิก free list ไปเสีย (ซึ่ง Behdad สนับสนุน โดยให้เหตุผลว่า malloc() ใน libc รุ่นหลัง ๆ ทำงานเร็วขึ้นมากแล้ว และ CPU สมัยใหม่ก็ทำงานเร็วขึ้นมากแล้วด้วย จะไปนั่งออปติไมซ์ทำไมให้เมื่อยตุ้ม)
  2. ใช้ free list 1 ชุดต่อ 1 thread แทนที่จะใช้ร่วมกันหมด (แต่ละ thread เก็บโหนดที่เลิกใช้ไว้ในกระเป๋า recycle ของใครของมัน ไม่ต้องใช้ร่วมกัน)
  3. ใช้ free list กลางเหมือนเดิม แต่ให้มีระบบ lock (แต่ละ thread ต้องล็อคโกดังขณะเข้าใช้ thread อื่นต้องรอให้ thread ที่ใช้งานอยู่ใช้ให้เสร็จเสียก่อนจึงจะเข้าใช้ได้)

วิธียกเลิก free list อาจจะง่าย แต่ก่อนจะเชื่อก็ต้องวัดดูก่อน ซึ่งจากการทดลองของผมก็พบว่า th_brk() จะทำงานนานขึ้นถึง 18% ถ้าใช้ malloc() ตรง ๆ ทุกครั้ง หรือถ้าเทียบกลับ การใช้ free list สามารถประหยัดเวลาจากการใช้ malloc() ได้ถึง 15% ซึ่งผมถือว่ามีนัยสำคัญ ฉะนั้นจึงไม่เลือกวิธีนี้

จากนั้นจึงมาทดลองวิธีใช้ free list 1 ชุดต่อ 1 thread ซึ่งวิธีการก็ไม่ได้มีอะไรมาก เพียงแค่เปลี่ยนจากการใช้ตัวแปร static มาเป็นการสร้าง free list ใหม่ในการเรียก th_brk() แต่ละครั้งเท่านั้นเอง เนื่องจากตัว libthai เองไม่ได้สร้าง thread ใหม่อยู่แล้ว การสร้าง thread เกิดจากโค้ดผู้เรียกอย่าง pango หรือผู้ที่เรียก pango อีกทีทั้งสิ้น การสร้าง free list ต่อ 1 call ก็เท่ากับการสร้างต่อ 1 thread นั่นเอง

ผลการทดลองคือ free list แบบต่อ thread ก็ยังสามารถประหยัดเวลาได้ถึง 13% (2% ที่หายไปพบภายหลังว่าเกิดจากการสร้าง free list ผิดจากจุดเดิมในโค้ด ทำให้เกิดการสร้างและทำลาย free list เพิ่มขึ้น เมื่อแก้ให้ตรงกับจุดเดิมก็ปรากฏว่าประหยัดเวลาได้เท่าๆ ของเดิม) ซึ่งถือว่าน่าพอใจ

วิธีสุดท้ายที่ใช้ระบบล็อคนั้น จะทำให้ libthai ต้องมีการแทรกโค้ดการเรียก pthread เข้ามา ซึ่งทำให้โค้ดส่วนนี้ดูแปลกแยกไม่สวยงาม และในเมื่อวิธีที่ใช้ free list ต่อ thread ได้ผลเป็นที่น่าพอใจอยู่แล้ว ผมจึงไม่พิจารณาวิธีนี้อีก

จึงได้เป็น rev 577 (และแก้ไขเพิ่มเติมใน rev 583 หลังจากพบสาเหตุที่ทำให้การประหยัดเวลาลดลง 2%)

นอกจากนั้น ก็เป็นความพยายาม optimize การตัดคำของ libthai ต่อ ดังรายการต่อไปนี้:

  • optimize libdatrie ในส่วน AlphaMap (การแม็ปอักขระให้เป็นดัชนีสำหรับ trie transition ซึ่งจะเกิด ทุกครั้ง ที่มี transition) จากการคำนวณหาลำดับของอักขระจากช่วงต่าง ๆ ที่กำหนด มาเป็นการเปิดตารางที่สร้างไว้ล่วงหน้า ฟังดูอาจจะนึกว่ามันคงเร็วขึ้นอย่างมโหฬาร แต่สำหรับ libthai แล้วไม่ใช่ เนื่องจากเรากำหนดช่วงอักขระภาษาไทยเป็นช่วงต่อเนื่องเพียงช่วงเดียว การคำนวณลำดับอักขระจึงไม่ซับซ้อน เพียงแค่ลบด้วยอักขระแรกของช่วงแล้วบวกด้วย 1 ก็จบแล้ว การทำงานที่ประหยัดได้จากการเปิดตารางจึงเป็นเรื่องของโสหุ้ยของการวนลูปเล็ก ๆ น้อย ๆ เท่านั้น ซี่งปรากฏว่าทำให้ตัวฟังก์ชันใช้เวลาทำงานลดลง 14.6% และทำให้การตัดคำของ libthai ใช้เวลาลดลงโดยรวม 0.2% เท่านั้น (libdatrie rev 277) แต่สำหรับ use case ที่อักขระอินพุตมีหลายช่วงไม่ต่อเนื่อง การเปิดตารางนี้จะช่วยประหยัดเวลาได้มาก
  • optimize libthai ในส่วนของการยุบรวมกรณีตัดคำที่ตกตำแหน่งเดียวกัน (การตัดคำของ libthai ใช้วิธีลองตัดคำแบบต่าง ๆ เทียบกับพจนานุกรม แล้วเลือกเอาวิธีที่ได้จำนวนคำน้อยที่สุด โดยใช้การค้นแบบ best-first search ซึ่งคล้ายกับ breadth-first search แต่มีการคำนวณ heuristic ของกรณีต่าง ๆ และยุบรวมกรณีที่คืบหน้าเท่ากันเป็นระยะ ๆ) ซึ่งปรากฏว่าฟังก์ชัน brk_pool_match() ขึ้นชาร์ตฟังก์ชันที่กินเวลานานในรายงานของ Callgrind จึงพยายาม optimize ด้วยการทำให้ตัวฟังก์ชันเร็วขึ้นด้วยการแยกลูปเพื่อลด branching (rev 579 ตัวฟังก์ชันใช้เวลาลดลง 9.3%, runtime โดยรวมลดลง 0.067%) และด้วยการลดปริมาณงานของฟังก์ชันโดยค้นต่อจากจุดเดิมแทนการเริ่มที่ต้นลิสต์เสมอ (rev 582 ตัวฟังก์ชันใช้เวลาลดลง 8.85%, อันดับในชาร์ตเลื่อนลง 2 ขั้น, runtime โดยรวมลดลง 0.0388%) รวมแล้ว ตัวฟังก์ชันใช้เวลาลดลง 17.3% runtime โดยรวมลดลงประมาณ 0.1%

สรุปแล้ว การตัดตำของ libthai โดยรวมใช้เวลาลดลงประมาณ 0.28% (รวมเวลาโหลดพจนานุกรม) พร้อมกับปลอดภัยสำหรับการทำงานหลาย thread โดยไม่ต้องล็อคด้วย

ถ้าไม่มีไอเดียใหม่อีก ก็คงจะออกรุ่นใหม่เร็ว ๆ นี้ครับ

ในอีกด้านหนึ่ง ทีม debian-cd ได้กระทุ้งมาอีกครั้งว่าช่วยทำ udeb ของ libthai ให้หน่อย จะได้ใช้ในโปรแกรมติดตั้งของเดเบียน (Debian #800369) ก็ทยอยทำตั้งแต่ libdatrie1-udeb (0.2.9-3) รอจนผ่าน NEW queue แล้วก็ทำ libthai-data-udeb และ libthai0-udeb ต่อ (ขณะที่เขียน blog ยังรออยู่ใน NEW)

ป้ายกำกับ: ,

03 กันยายน 2558

Thanks

ขอขอบคุณย้อนหลัง สำหรับผู้สนับสนุนงานพัฒนาซอฟต์แวร์เสรีของผมในช่วงปลายเมษายน 2558 ถึงต้นกันยายน 2558 ที่ผ่านมาครับ คือ:

  • ปลายเดือนเมษายน 2558
    • ผู้ไม่แสดงตน 1 ท่าน
  • เดือนพฤษภาคม 2558
  • เดือนมิถุนายน 2558
    • คุณธนาธิป ศรีวิรุฬห์ชัย
    • ผู้ไม่แสดงตน 1 ท่าน
  • เดือนกรกฎาคม 2558
    • คุณธนาธิป ศรีวิรุฬห์ชัย
    • ผู้ไม่ประสงค์จะออกนาม
  • เดือนสิงหาคม 2558
  • ต้นเดือนกันยายน 2558

ขอให้ทุกท่านมีความเจริญก้าวหน้าในหน้าที่การงาน คิดสิ่งใดก็ขอให้สมปรารถนานะครับ

สี่เดือนที่ผ่านมา นับจาก บันทึกขอบคุณครั้งที่แล้ว งานพัฒนาที่ทำไปพอสรุปได้ดังนี้:

  • Optimize LibThai/LibDATrie เพิ่มเติม ตรวจสอบความเรียบร้อยทั่วไป และออกรุ่น libdatrie 0.2.9 และ libthai 0.1.22 พร้อมอัปโหลดเข้า Debian (libdatrie 0.2.9-1 และ libthai 0.1.22-1)
  • ตามแก้ปัญหาในแพกเกจ libdatrie-dev และ libthai-dev ที่อัปโหลดไว้ ดังที่มีผู้รายงานบั๊ก Debian #788163 และ Debian #788164
  • ออกรุ่น thaixfonts 1.2.7 ซึ่งได้เตรียมการแก้ปัญหาข้อมูล copyright ที่ขาดไว้ พร้อมทั้งเรื่อง reproducibility ที่ระบบของ Debian ตรวจพบ และแก้ปัญหาเล็ก ๆ น้อย ๆ ในตัว Debian package แล้วอัปโหลดเข้า Debian (thaixfonts 1.2.7-1)
  • ปรับแก้ฟอนต์ในชุด Fonts-TLWG ต่อ เพื่อรองรับภาษามลายูปาตานีเต็มรูปแบบ พร้อมกับเตรียม fallback โดยใช้ภาษาแต้จิ๋วเป็นโจทย์ ดังที่ได้ บันทีกไว้ ซึ่งขณะนี้ยังอยู่ระหว่างดำเนินการต่อ

  • อัปเดตแพกเกจ scim-thai ใน Debian เล็กน้อย เพื่อเป็นส่วนหนึ่งของการย้ายไป GCC5 ของ Debian

  • อัปเดตคำแปลชื่อภาษาใน ISO 639 และ ISO 639-3 ในแพกเกจ iso-codes (มีผลใน iso-codes 3.61)
  • ตรวจทานคำแปล GNOME ตามที่มีผู้ส่งเข้ามา

ขณะเดียวกัน ช่วงนี้งานที่ผมรับเพื่อ support ตัวเองเป็นงานสอน ซึ่งต้องใช้เวลาค่อนข้างมากในการเตรียมเนื้อหา จึงอาจเหลือเวลามาทำงานพัฒนาน้อยลง แต่ก็พยายามเจียดเวลาเท่าที่จะทำได้ครับ

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

14 สิงหาคม 2558

Patani Malay Support in Fonts-TLWG with Teochew Experiment

โครงการ fonts-tlwg กำลังอยู่ระหว่างพัฒนาเพิ่มเติมให้รองรับภาษาชาติพันธุ์ที่เขียนด้วยอักษรไทยได้สมบูรณ์ยิ่งขึ้น โดยรอบนี้เจาะไปที่ภาษามลายูปาตานีที่นอกจากจะต้องผ่อนคลายกฎการซ้อนอักขระต่าง ๆ แล้ว ยังต้องการอักขระเพิ่มเติมจากบล็อคยูนิโค้ดภาษาไทยอีก 4 ตัว เพื่อเป็นเครื่องหมายกำกับเสียงอ่าน กล่าวคือ:

  • U+0303 COMBINING TILDE คือสัญลักษณ์ตัวหนอนที่เขียนกำกับเหนือพยัญชนะเพื่อให้ออกเสียงขึ้นจมูก (เสียงนาสิก) คงได้แนวคิดจากตัว ñ ในภาษาสเปน
  • U+0331 COMBINING MACRON BELOW คือสัญลักษณ์ขีดเส้นใต้พยัญชนะเพื่อตัดเสียงนาสิกออกจากพยัญชนะนาสิกของไทย 4 ตัว คือ ง ญ น ม
  • U+02BC MODIFIER LETTER APOSTROPHE ใช้ในการลดรูปคำให้เหลือจำนวนพยางค์น้อยลง
  • U+02D7 MODIFIER LETTER MINUS ใช้ในการกล้ำสองพยางค์ให้ต่อเนื่องเป็นพยางค์เดียว

สองตัวหลังดูไม่มีอะไรมาก แค่เพิ่ม glyph ให้ก็ดูจะเพียงพอ แต่สองคัวแรกซึ่งเป็นอักขระประกอบ (combining character) ก็เลยมีปฏิสัมพันธ์กับสระบน-ล่าง วรรณยุกต์ และเครื่องหมายกำกับเสียงของไทย ทำให้ต้องมาจัดระเบียบใหม่ ซึ่งปรากฏว่าต้องทั้งขยับอักษรไทย ทั้งเปลี่ยนลำดับ normalization ของยูนิโค้ดด้วย

ตัวหนอนบอกเสียงนาสิกดูจะไม่มีประเด็นอะไรสำหรับภาษามลายูปาตานี เนื่องจากคำที่มีเครื่องหมายนี้จะไม่มีสระบนและวรรณยุกต์กำกับอยู่แล้ว ส่วนตัวขีดเส้นใต้ก็ควรขีดเส้นใต้พยัญชนะที่ความกว้างไม่เท่ากันให้สวยงามทั้งสี่ตัวด้วย (กล่าวคือ ไม่ใช่ใช้อักขระเดียวขีดเส้นใต้อักขระทุกตัว แต่ความยาวของขีดควรปรับตามความกว้างของพยัญชนะด้วย) รวมทั้งต้องตัดเชิง ญ เมื่อขีดเส้นใต้ด้วย

ก็ดูไม่ยุ่งยากอะไร ตัวหนอนก็แค่ทำประหนึ่งมันเป็นสระอิอีอึอือเสียก็จบ ส่วนตัวขีดเส้นใต้ก็สร้าง ligature glyph สำหรับ ง ญ น ม ขีดเส้นใต้สำเร็จรูปเท่านั้นเอง ผลออกมาก็งามน่าแล

Patani Malay text using Garuda

แต่ช้าก่อน ฟีเจอร์ของภาษามลายูปาตานีฟังดูน่าสนใจไม่น้อยสำหรับคนพูดภาษาแต้จิ๋วอย่างผม

  • เสียงนาสิกเป็นตัวแยกความหมายของคำในภาษาแต้จิ๋ว ภาษาไทยมีเสียงนาสิกแค่ไม่กี่เสียง คือ ง ญ ณ น ม ถ้าพยัญชนะต้นไม่ใช่เสียงพวกนี้ก็จะไม่ออกเสียงขึ้นจมูก (ญ ไม่มีเสียงนาสิกในไทยกลาง แต่ยังมีในไทยอีสาน [ดู blog เก่าประกอบ] และอาจจะมี ห ฮ ที่ยังขึ้นจมูกบ้างไม่ขึ้นบ้างแล้วแต่สำเนียง) แต่ภาษาแต้จิ๋วสามารถขึ้นจมูกได้ทุกพยางค์ และยังเป็นตัวแยกความหมายอีกด้วย เช่น คำว่า 碗 ที่แปลว่า ชาม จะออกเสียงเป็น อั้ว พร้อมเสียงขึ้นจมูก ซึ่งจะแตกต่างจากคำว่า 我 ที่เป็นสรรพนามบุรุษที่หนึ่งเอกพจน์ ซึ่งออกเสียงเป็น อั้ว เหมือนกันแต่ไม่ขึ้นจมูก ในเมื่อเสียงนาสิกมีความสำคัญถึงขนาดแยกความหมายของคำในภาษาแต้จิ๋วเช่นนี้ ตัวหนอนกำกับเสียงนาสิกของมลายูปาตานีจึงน่าจะนำมาใช้กับแต้จิ๋วได้
  • พยัญชนะที่ตัดเสียงนาสิกในภาษาแต้จิ๋วก็มี เช่นในคำว่า 牛 ที่แปลว่า วัว จะออกเสียงเป็น ก๎งู๊ คือเป็น ง ที่กล้ำกับเสียง ก ที่คอและไม่ขึ้นจมูก เสียงพยัญชนะนี้ไม่มีในภาษาไทย และมีความยากลำบากในการเขียนด้วยอักษรไทยมาก ตัวขีดเส้นใต้ตัดเสียงนาสิกของมลายูปาตานีก็น่าจะนำมาใช้ได้เช่นกัน

แต่การนำเครื่องหมายทั้งสองมาใช้กับภาษาแต้จิ๋วก็ทำให้พบกับ requirement ที่สูงขึ้น เพราะพยางค์ในภาษาแต้จิ๋วสามารถผสมกับสระและวรรณยุกต์ของไทยได้อย่างอิสระกว่ามาก ทำให้ตัวหนอนบอกเสียงนาสิกต้องสามารถผสมกับไม้หันอากาศและสระอิอีอึอือและใช้วรรณยุกต์ได้ด้วย ส่วนตัวขีดเส้นใต้ก็ต้องผสมกับสระล่างได้ด้วยเช่นกัน

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

ประเด็นของโจทย์ก็คือ:

  • ตัวหนอนนาสิก ฐานข้อมูลอักขระยูนิโค้ดกำหนดให้มี combining class 220 ในขณะที่วรรณยุกต์ไทยเป็น class 107 ตามหลัก normalization ของยูนิโค้ด จะเรียงลำดับอักขระที่อยู่ระหว่างอักขระ class 0 (ได้แก่พยัญชนะบนบรรทัด) ตามลำดับ combining class จากน้อยไปหามาก (เรียกว่า canonical order) ผลคือ เมื่อตัวหนอนนาสิกผสมกับวรรณยุกต์ไทย rendering engine จะสลับมันไปอยู่หลังวรรณยุกต์ก่อนส่งให้ฟอนต์แสดงผลเสมอ แต่สิ่งที่เราต้องการคือวางตัวหนอนก่อนวรรณยุกต์ เพื่อให้อยู่ชิดพยัญชนะ แล้วจึงซ้อนวรรณยุกต์ทับทีหลัง ดังนั้น ฟอนต์จะต้องรู้จักกลับลำดับคืนด้วย แต่ถ้ามีสระบนมาคั่นกลางก็ไม่ต้องทำอะไร เพราะสระบน, ไม้ไต่คู้, ทัณฑฆาต, นิคหิต, ยามักการ ต่างถูกกำหนด combining class เป็น 0 เหมือนพยัญชนะบนเส้นบรรทัด จึงเป็นจุดกั้นแบ่งโซนการเรียงลำดับ combining character โดยปริยาย
  • ตัวขีดเส้นใต้ ฐานข้อมูลอักขระยูนิโค้ดกำหนดให้มี combining class 220 ในขณะที่สระอุ สระอู มี combining class เป็น 103 (แต่ Uniscribe และ Harfbuzz แก้เองให้เป็น 3 ด้วยเหตุผลใดไม่อาจทราบได้) และพินทุมี combining class เป็น 9 ทำให้ตัวขีดเส้นใต้จะถูก rendering engine normalize ให้ไปอยู่หลังสระล่างและพินทุเสมอ และถ้ามีวรรณยุกต์ก็จะเลื่อนไปอยู่หลังวรรณยุกต์ด้วย แต่สิ่งที่เราต้องการคือวางตัวขีดเส้นใต้ก่อนเพื่อน เพื่อให้อยู่ชิดพยัญชนะ ดังนั้น ฟอนต์จะต้องรู้จักกลับลำดับคืนด้วย
  • พินทุกับสระล่าง ตาม combining class ในฐานข้อมูลอักขระยูนิโค้ดแล้ว พินทุ (class 9) จะต้องมาก่อนสระล่าง (class 103) ใน canonical order ซึ่งเป็นสิ่งที่ต้องการอยู่แล้วสำหรับภาษามลายูปาตานี แต่ด้วยเหตุผลกลใดไม่ทราบได้ Uniscribe ของไมโครซอฟท์ได้แก้ทับคลาสของสระล่างจาก 103 เป็น 3 ทำให้สระล่างมาก่อนพินทุ และเพื่อความเข้ากันได้กับ Uniscribe ทำให้ Harfbuzz ก็ทำอย่างเดียวกันด้วย สิ่งที่ฟอนต์ต้องทำคือกลับลำดับนี้คืนอีกชั้นหนึ่ง

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

แต่ปัญหาของตัวขีดเส้นใต้ซับซ้อนกว่าที่คาดไว้มาก เพราะมีประเด็นอื่นที่ต้องพิจารณาร่วมด้วยอีก 2 ประเด็น คือ

  • การขีดเส้นใต้ ง ญ น ม ซึ่งความกว้างไม่เท่ากัน จัดการได้ด้วยการสร้าง precomposed glyph ที่ขีดเส้นใต้พยัญชนะดังกล่าวเสร็จสรรพ (ตัดเชิงสำหรับ ญ ไปในตัว) พร้อมกฎ ligature สำหรับเปลี่ยนคู่อักขระให้เป็นตัว precomposed กฎนี้ต้องทำงานในขณะที่พยัญชนะดังกล่าวกับตัวขีดเส้นใต้อยู่ติดกัน
  • กฎเดิมที่จัดการกรณีคำว่า ปู่ ซึ่งจะเปลี่ยนวรรณยุกต์ให้เป็นตัวต่ำพร้อมกับสลับสระล่างกับวรรณยุกต์ให้วรรณยุกต์มาก่อน เพื่อจะได้วางหลบหาง ป ฝ ฟ (และอาจจะ ฬ) ด้วย GPOS ได้ กฎนี้ต้องทำงานในขณะที่สระล่างตามหลังด้วยวรรณยุกต์ทันที

การเพิ่มกฎสำหรับกลับลำดับตัวขีดเส้นใต้จึงต้องระวังที่จะไม่ไปกีดขวางการทำงานของกฎดังกล่าวด้วย โดยถ้าทำก่อน ตัวขีดเส้นใต้ก็จะมาขวางกฎ ปู่ ให้ไม่ทำงาน และถ้าทำทีหลัง วรรณยุกต์จากกฎ ปู่ ก็จะมาขวางกฎสร้าง precomposed glyph ให้ไม่ทำงาน

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

สตริงเริ่มแรก: C + Macron + BV + T
Uniscribe/Harfbuzz: C + BV + T + Macron
1: สลับ T กับ Macron C + BV + Macron + T
2: สลับ BV กับ Macron C + Macron + BV + T
3: ligature C-Macron-lig + BV + T || C + Macron + BV + T
3: สลับ Macron กับ BV C + BV + Macron + T
4: สลับ Macron กับ T C + BV + T + Macron
general composition C + T.low + BV + Macron
5: สลับ BV กับ Macron C + T.low + Macron + BV

กล่าวคือ เริ่มจากเลื่อนตัวขีดเส้นใต้ไปชิดกับพยัญชนะก่อนเพื่อให้กฎ ligature ทำงาน และถ้าตัวขีดเส้นใต้ไม่ได้ทำให้เกิด ligature ก็เลื่อนกลับที่เดิมเพื่อให้กฎ ปู่ ใน general composition ทำงาน จากนั้นจึงสลับตัวขีดเส้นใต้ให้มาก่อนสระล่าง ก็เป็นอันได้ลำดับที่เราต้องการ

กรณีลำดับของพินทุกับสระล่างที่ Uniscribe/Harfbuzz แปลงสารมาก็ทำนองเดียวกัน มีความซับซ้อนเล็กน้อย แต่ก็แค่กับกฎ ปู่ เท่านั้น :-

สตริงเริ่มแรก: C + Ph + BV + T
Uniscribe/Harfbuzz: C + BV + Ph + T
1: สลับพินทุกับ BV C + Ph + BV + T
general composition: C + Ph + T.low + BV
2: สลับพินทุกับ T.low C + T.low + Ph + BV

กล่าวคือ สลับพินทุกับสระล่างกลับให้เป็นเหมือนเดิม แล้วปล่อยให้กฎ ปู่ ใน general composition ทำงาน เสร็จแล้วก็เลื่อนวรรณยุกต์ตัวต่ำให้เข้าไปชิดกับพยัญชนะ

ผลที่ได้ดูจะทำงานได้ดีเมื่อทำ stress test ด้วยภาษาแต้จิ๋ว:

Experimental Teochew text using Garuda

และเนื่องจาก requirement ในเรื่องตัวหนอนนาสิกและตัวขีดเส้นใต้ของภาษาแต้จิ๋ว เป็น superset ของมลายูปาตานี การผ่านการทดสอบด้วยภาษาแต้จิ๋วย่อมถือว่าครอบคลุมภาษามลายูปาตานีโดยปริยาย

สังเกตว่า ในขณะที่ทำภาษามลายูปาตานีในขั้นแรกนั้น ผมลืมกรณีที่พยัญชนะขีดเส้นใต้มีสระล่างไปเสียสนิทด้วยซ้ำ การเล่นกับภาษาแต้จิ๋วยังช่วยจับบั๊กนี้ให้ด้วย

เมื่อทดลองกับฟอนต์ Garuda เป็นที่พอใจแล้ว ก็มาสรุปขั้นตอนทุกอย่างอีกครั้งกับฟอนต์ Kinnari:

Experimental Teochew text using Kinnari

แล้วก็ใช้ฟอนต์ Kinnari เป็นแบบสำหรับฟอนต์อื่น ๆ ต่อไป โดยเริ่มจากฟอนต์ Norasi:

Experimental Teochew text using Norasi

ตอนนี้ทำถึงขั้นนี้เท่านั้นครับ ยังมีฟอนต์อื่น ๆ อีกสิบกว่าฟอนต์ในชุด fonts-tlwg ที่ผมจะค่อยทยอยทำต่อไปเมื่อมีเวลาว่าง คาดว่าคงใช้เวลาอีกหลายเดือน :-P

ผมสร้าง หน้าทดสอบ โดยใช้ web font ที่สร้างจากตัวล่าสุดที่อยู่ระหว่างพัฒนาไว้ด้วย คงจะอัปเดตไปเรื่อย ๆ ตามความคืบหน้าครับ

ในอีกด้านหนึ่ง ก็ได้ อภิปรายและเสนอแพตช์ สำหรับ Harfbuzz เพื่อขอตัดการ override combining class ของสระล่างของไทยเสียด้วย ดูจะได้รับความเห็นชอบ แต่ยังไม่ apply เสียที อาจจะเกรงความไม่เข้ากันกับ Uniscribe หรือเปล่าก็ไม่ทราบได้ แต่การแก้ในฟอนต์ที่ผมทำไปก็เท่ากับทำให้มันทำงานบนวินโดวส์ได้ด้วย

ป้ายกำกับ: ,

hacker emblem