libthai thbrk() redesign
กลับมาดู libthai ต่อ ดอง GNOME Bug #382437 มานานละ เข้าถ้ำทำ libthai ละกัน
ตามแผนเดิม คือเปลี่ยนรูทีน th_brk() ของ libthai ซึ่งเป็นตัวแยกข้อความภาษาไทย-อังกฤษ ก่อนแยกส่งเฉพาะส่วนภาษาไทยให้รูทีนตัดคำด้วยพจนานุกรม จากเดิมที่เขียนโค้ดวิเคราะห์แบบ ad hoc พอให้ใช้ได้ไปก่อน มาเป็น finite state machine ซึ่งแบบร่างแรกที่ได้ คืออย่างนี้:
Alphabet:
- thai = พยัญชนะ-สระไทย
- nbb (no break before) = อักขระที่ห้ามตัดบรรทัดก่อนหน้า แม้จะมีอักขระเว้นวรรค ได้แก่ ๆ ฯ ! ? ) ] } , ; .
- nba (no break after) = อักขระที่ห้ามตัดบรรทัดต่อท้าย ถ้าตามด้วยข้อความทันที แต่อาจตัดได้ถ้ามีเว้นวรรคต่อท้าย ได้แก่ ( [ { (มีอันไหนอีกก็ list เข้าไป)
- sp = white space (ซึ่งถ้าสังเกตพฤติกรรม จะถือเป็น nbb subset ก็ได้)
- eng = อักขระอื่น ๆ ที่ไม่อยู่ในกลุ่มข้างต้น
Action:
- start-th = เริ่มจำว่าต้องตัดคำไทย แทรก line break ด้วยถ้าไม่มีพารามิเตอร์ nb (no break)
- start-en = เริ่มจำว่าเป็นส่วนไม่ใช่ภาษาไทย แทรก line break ด้วยถ้าไม่มีพารามิเตอร์ nb (no break)
- coll (collect) = เก็บอักขระเข้าบัฟเฟอร์ตัดคำ
- break = ตัดคำในบัฟเฟอร์
- end = จบคำสำหรับก้อนข้อความที่ไม่ใช่ภาษาไทย
- lbr = แทรก line break หน้าอักขระ
แต่พอจะเพิ่มชนิด alphabet ก็จะกลายเป็น state machine ที่จำนวน state กับจำนวน transition พอ ๆ กัน สงสัยเรื่องกรณีอักขระต่าง ๆ ที่นึกไม่ออก ก็ไปอ่าน Unicode Standard Annex #14 เรื่อง Line Breaking Algorithm แล้วสรุปย่อลงมาในขอบเขต TIS-620 ได้เป็น state machine แบบที่สอง คือ
THAI | ALPHA | NUM | NBB | NBA | NB | MB | SPACE | QUOTE | HYPHEN | NUM_NBB | NUM_CUR | NUM_NB | TERM | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
THAI | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
ALPHA | 1 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
NUM | 1 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
NBB | 1 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
NBA | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
NB | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
MB | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
SPACE | 1 | 1 | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 1 |
QUOTE | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
HYPHEN | 1 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
NUM_NBB | 1 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 |
NUM_CUR | 1 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
NUM_NB | 1 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
TERM | 1 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
โดยที่:
- THAI = อักขระไทย
- ALPHA = อักขระอื่นที่ไม่ใช่ภาษาไทย
- NUM = ตัวเลข
- NBB (No Break Before) = อักขระที่ห้ามตัดบรรทัดก่อนหน้า แม้จะมีอักขระเว้นวรรค ได้แก่ ๆ ฯ ! ? ) ] } , ; .
- NBA (No Break After) = อักขระที่ห้ามตัดบรรทัดต่อท้าย ถ้าตามด้วยข้อความทันที แต่อาจตัดได้ถ้ามีเว้นวรรคต่อท้าย ได้แก่ ( [ {
- NB (No Break) = อักขระที่ห้ามตัดบรรทัดทั้งก่อนหน้าและต่อท้าย ได้แก่ NO BREAK SPACE
- MB (Mandatory Break) = อักขระบังคับตัดบรรทัด ได้แก่ NEW LINE
- SPACE = White Space
- QUOTE = เครื่องหมายคำพูดที่กำกวมว่าปิดหรือเปิด คือ ' "
- HYPHEN = เครื่องหมาย - (ลบ/ยัติภังค์)
- NUM_NBB = อักขระห้ามตัดคำก่อนหน้า เมื่อเขียนกับตัวเลข ได้แก่ %
- NUM_CUR = เครื่องหมายหน่วยเงินตรา มีผลให้ไม่ตัดบรรทัดเมื่อใช้กับตัวเลข แม้จะมีเว้นวรรค ได้แก่ $ ฿
- NUM_NB = เครื่องหมายประกอบตัวเลข ไม่ตัดบรรทัดเมื่อใช้กับตัวเลข แต่ถ้ามีเว้นวรรคไม่เกี่ยว ได้แก่ , . : เช่น "100,000", "12:00"
- TERM = อักขระที่ใช้จบเรื่อง ได้แก่ ๚ ๛
ไอ้ตรงเครื่องหมายหน่วยเงินนี่แหละ ที่มีปัญหา จากตารางข้างบน มีความเชื่อมโยงแค่กับอักขระที่ติดกันเท่านั้น ไม่ได้ยิงสถานะผ่านข้ามช่องว่าง ถ้าช่องว่างเจอตัวเลข ก็ต้องตัดเท่านั้น จะห้ามตัดอย่างเครื่องหมายหน่วยเงินไม่ได้ กลายเป็นว่า ผม over-simplify Unicode ไป สุดท้ายก็ต้องเพิ่ม state ที่สาม เพื่อแยกความแตกต่างระหว่าง prohibited คือห้ามตัดแม้จะมีช่องว่าง กับ indirect break ที่ห้ามตัด แต่ถ้ามีช่องว่างคั่นก็ตัดได้
กลายเป็นแบบที่ 3
THAI | ALPHA | NUM | NBB | NBA | NB | MB | SPACE | QUOTE | HYPHEN | NUM_NBB | NUM_CUR | NUM_NB | TERM | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
THAI | I | A | A | P | A | I | P | P | I | I | A | A | P | I |
ALPHA | A | I | A | P | A | I | P | P | I | I | A | A | P | I |
NUM | A | A | I | P | A | I | P | P | I | I | I | P | P | I |
NBB | A | A | A | P | A | I | P | P | I | I | A | A | P | I |
NBA | P | P | P | P | P | P | P | P | P | P | P | P | P | P |
NB | I | I | I | P | I | I | P | P | I | I | I | I | P | I |
MB | A | A | A | A | A | A | A | A | A | A | A | A | A | A |
SPACE | A | A | A | P | A | A | P | P | A | A | A | A | P | A |
QUOTE | I | I | I | P | I | I | P | P | I | I | I | I | P | I |
HYPHEN | A | A | I | P | A | I | P | P | I | I | A | A | P | I |
NUM_NBB | A | A | A | P | A | I | P | P | I | A | A | A | P | I |
NUM_CUR | A | A | P | P | A | I | P | P | I | P | A | A | P | I |
NUM_NB | A | A | I | P | A | I | P | P | I | I | A | A | P | I |
TERM | A | A | A | P | A | I | P | P | I | I | A | A | P | I |
- A = Allowed คือตัดบรรทัดได้ทันที
- P = Prohibited คือห้ามตัดบรรทัด แม้จะมี space คั่น
- I = Indirect break คือห้ามตัดบรรทัดทันที แต่ถ้ามี space คั่นก็ยอมได้
ได้แบบที่ 3 นี่แหละ คงเพียงพอแล้ว.. Unicode เขาศึกษา + ออกแบบครบดีแฮะ
commit เข้า libthai cvs แล้วครับ
ป้ายกำกับ: libthai
4 ความเห็น:
ณ 11 มิถุนายน 2550 เวลา 17:26 , Lancaster แถลง…
ถึงจะอ่านไม่ค่อยเข้าใจ แต่ก็พอทราบว่าเป็นงานที่มีผลสำคัญต่อวงการตัดคำไทยมาก ยังไงก็ขอขอบคุณด้วยรคับ
ป.ล. เหมือนกับ stat สักตัวที่คุณเทพฯลงไว้จะแอบโฆษณานะครับ
ณ 11 มิถุนายน 2550 เวลา 18:17 , bact' แถลง…
วาว
ณ 11 มิถุนายน 2550 เวลา 18:23 , bact' แถลง…
ดูจากคำอธิบายแล้ว
(เช่น "อักขระที่ห้ามตัดบรรทัดก่อนหน้า แม้จะมีอักขระเว้นวรรค")
อาจจะต้องระบุว่า function นี้ มีจุดประสงค์สำหรับทำ line wrap
ไม่ใช่ tokenization (เวลาทำ word highlight (ดับเบิ้ลคลิก เป็นต้น) เขาจะใช้จุดตัดแบบไหน ?)
ณ 11 มิถุนายน 2550 เวลา 19:41 , Thep แถลง…
Lancaster, เรื่องโฆษณา MrChoke ก็บอกผมหลายทีละ ตกลงตัดออกแล้วครับ ขอบคุณที่บอกให้ทราบครับ (ผมเองไม่เคยเจอ เพราะ pop-up blocker ของ epiphany มันบล็อคเงียบเลย)
bact', ใน pango จะมี logical attribute ระหว่าง word start/end กับ line break แยกกันครับ และ API ของ libthai ไม่ละเอียดพอจะจัดการได้ เลยอาศัย default routine ของ pango ในเรื่อง word boundary แล้วให้ libthai เติม line break และ word boundary เฉพาะในส่วนภาษาไทยเอา
สรุปว่า th_brk() ใช้สำหรับ line break เท่านั้น ความจริงก็คิดอยู่ ว่าจะเพิ่ม API สำหรับหา word boundary ต่างหาก
ถ้าสังเกตใน diagram แรก จะเห็น action ชื่อ "end" เอาไว้มาร์ค word end ซึ่งอาจตรงหรือไม่ตรงกับ line break ก็ได้ (ในขณะที่ word start จะตรงเสมอ) อันนั้นแหละครับ ที่คิดเผื่อไว้
แสดงความเห็น (มีการกลั่นกรองสำหรับ blog ที่เก่ากว่า 14 วัน)
<< กลับหน้าแรก