fonts: บันทึกการทดลองทำ DejaVuSansThai รุ่นแรก

บันทึกการแปลงฟอนต์ Waree เป็น DejaVuSansThai รุ่นแรก

  • ใช้หลักการ instruct โดยพยายามยึดตามแนวของ DejaVu เท่าที่พอแกะออก
  • ยังขาดการทำ delta hint เพราะยังทำไม่เป็น ดังนั้นการออกแบบฟอนต์และการ instruct จึงพยายามปรับ เพื่อเลี่ยงการทำ delta hint ซึ่งต้องรอศึกษาต่อไป
    ทำ delta hint ได้แล้ว แต่เสียเวลาและสายตามากเกินไป จึงพยายามออกแบบโค๊ดให้ลดการทำ delta hint ให้มากที่สุด
    ล่าสุดเปลี่ยนแนวคิดการออกแบบเป็น กำหนดจุดให้น้อยที่สุดเพื่อให้ฟอนต์เพี้ยนน้อยที่สุด จึงจำเป็นต้องพึ่งการใช้ delta hint อย่างมาก
  • ลดความสูงของฟอนต์ไทย ให้เท่ากับอังกฤษตัวเล็ก แล้วพยายามย่นความสูงของสระและพยัญชนะลงมา แต่กระนั้นก็ยังล้นระยะห่างระหว่างบรรทัดของ DejaVu อยู่ดี
  • ค่า cvt ของ DejaVu Sans เต็ม 256 ค่าแล้ว แต่มีความจำเป็นต้องเพิ่มค่าของเราเข้าไป ทำให้ต้องอ้างค่าเป็น word แทน byte โค๊ดจึงอาจดูยากขึ้นเล็กน้อย
  • เขียนสคริปต์ไพธอน เล็ก ๆ ชื่อ dfont.py เอาไว้คลี่และเรียงสแต็ก ใช้ได้ผลดีพอควร แต่สคริปต์ยังขาดการตรวจสอบความถูกต้องของ instructing code ซึ่งถ้าใช้ด้วยพารามิเตอร์ --all อาจทำให้ fontforge หยุดการทำงานได้ - รอปรับปรุงต่อไป
  • ยังเหลือบั๊กอีกหลายจุด ตัว ค.ควาย จ.จาน ย.ยักษ์ ยังออกแบบไม่ดี ตัว ร.เรือ ธ.ธง ยัง instruct ได้ไม่ดี และยังไม่ได้ทำตัวหนา ตัวเอียง และตัวหนาเอียง แต่มีความจำเป็นต้องเว้นวรรคนาน จึงบันทึกไว้เพื่อให้จำได้

ความรู้ที่ได้

  • การทำ TrueType Hinting Instruction ตัวอักษรจะเพี้ยนเสมอ ทั้งนี้เนื่องมาจากการบังคับให้ลงจุดในจอมอนิเตอร์ ดังนั้น การ instruct ก็คือการบอกว่าจะให้เพี้ยนยังไง จึงจะทำให้ดูคมชัด
  • การเคลื่อนจุดด้วยวิธี indirect เช่นคำสั่ง MIRP (ซึ่งต้องอ้างค่าจาก cvt - Control Value Table) มีความแม่นยำแน่นอนกว่าการเคลื่อนค่าแบบ direct (เช่นคำสั่ง MDRP)
  • คำสั่ง ip (Interpolate) จะทำให้ฟอนต์ดูฟุ้ง แต่ระยะที่ได้ แน่นอนกว่าการเคลื่อนจุดแบบ direct (เว้นเสียแต่ถ้าไม่มีการทดลงจุด คืออาร์กิวเมนต์ rnd ควรใช้ MDRP เสมอ ไม่งั้นบางทีอาจเพี้ยน)
  • ถ้ามีความจำเป็นต้องใช้การ Interpolate อาจทำแค่แกนเดียว ส่วนอีกแกนนึงให้ใช้การเคลื่อนแบบ direct แทน
  • พยายามยึดแนวแกนซ้ายขวาและความสูงเฉลี่ยของตัวอักษรไว้ ดังนั้นจึงควรบรรจุค่าความสูงและความกว้างของตัวอักษรในตาราง cvt เสมอ
  • ควรออกแบบความกว้างของตัวอักษรให้ดูสม่ำเสมอ แล้วเขียนโค๊ดโดยดูจากตาราง cvt เป็นสำคัญ แต่จะมีข้อยกเว้นเป็นบางตัวอักษรที่มีหัวอักษรอยู่ตรงกลางฟอนต์ เช่น ค.ควาย หรือ ด.เด็ก อาจต้องยอมทิ้งค่าความกว้างจาก cvt โดยต้องไล่จุดไปตามแนวที่ผ่านหัวอักษรไปเรื่อย ๆ ไม่งั้นเวลาแสดงผลที่ขนาดปอยต์ต่าง ๆ หัวอักษรอาจเยื้องไปทางซ้ายบ้างขวาบ้าง บางครั้งอาจดูลีบติดไปทางแกนอักษรข้างใดข้างหนึ่งบ้าง - อาจแก้ได้ด้วย delta hint
  • การทำ delta hint หรือ grid fitting โดยกำหนดให้เป็น version 1 จะได้ฟอนต์ที่ดูนุ่มนวลดีกว่า version 0
  • ถ้าไม่ทดลงจุดด้วยอาร์กิวเมนต์ rnd ห้ามใช้คำสั่ง SHPIX ที่จุดนั้นเด็ดขาด จะมีปัญหาเลอะตอนพิมพ์ โดยเฉพาะในแนวแกน Y

ผลการทดลอง - บนลินุกซ์ ความละเอียดจอภาพ 85 dpi

  • ดูดีที่ 12 และ 13 ปอยต์
  • ตั้งแต่ 7 ปอยต์ลงไป อ่านไม่ได้
  • ตั้งแต่ 18 ปอยต์ขึ้นไป ไม่มีปัญหาเรื่องการ hint
  • แย่สุดที่ 14 ปอยต์ ที่เหลือนอกจากนี้ พอดูได้
  • รุ่นล่าสุด (510519) ปรับด้วย delta shift hint พยายามทำให้ดูดีในทุก ๆ ppem แต่ปรับละเอียดที่ 14,15 และ 16 ppem
  • ความกระชับของโค๊ด ขึ้นกับแนวคิดในการไล่เส้นทางเดินของฟอนต์
  • ความสวยของฟอนต์ขึ้นกับความเพียรในการทำ delta hint

ดาวน์โหลดไฟล์ฟอนต์ ttf ล่าสุด (รุ่น 520401)

ดาวน์โหลดไฟล์ซอร์ส

  • DejaVuSansThai-src-510331.tar.gz
  • ปรับปรุงช่องไฟ (ยังไม่ดี) ปรับปรุงเรื่องหัวเล็กน้อย (ดีขึ้น) และปรับหลัก instruction บางส่วน (ดีขึ้น)
    DejaVuSansThai-src-510408.tar.gz
  • รุ่นสุดท้ายก่อนทำ delta hint DejaVuSansThai-src-510415.tar.gz
  • เริ่มทำ delta hint (gridfit) ของตัว ค.ควาย ด.เด็ก และปรับปรุงตัว ร.เรือ DejaVuSansThai-src-510416.tar.gz
  • ทำตัวหนาเพิ่ม DejaVuSansThai-src-510420.tar.gz
  • ปรับวิธี hint ใหม่หมด เลียนแบบจาก Serif ซึ่งพยายามเลียนจาก Tahoma อีกที โดยปรับละเอียดที่ 16 ppem และปรับหัวให้ดูนุ่มขึ้น ที่ 14,15 และ 16 ppem
    DejaVuSansThai-src-510519.tar.gz
  • ทำตัวหนาเสร็จ ปรับวิธี hint อีกเล็กน้อย โดยใช้หลัก hint เท่าที่จำเป็น
    รุ่นนี้คิดว่าสมบูรณ์พอจะเอาไปใช้งานได้แล้ว DejaVuSansThai-src-510523.tar.gz
  • ทำรุ่นใหม่ โดยใช้ฟังก์ชั่น SHPIX บีบหัวให้ฟุ้งน้อยลง จึงทำให้ดูเหมือนกับคมขึ้น ตามไปช่วย hint ให้ภาษาลาวเล็กน้อย แต่ยังไม่เสร็จ DejaVuSansThai-src-510612.tar.gz
  • hint ภาษาลาวเสร็จ ปรับแก้เรื่องเส้นขีดฆ่า ตามคำแนะนำของคุณเทพ (แต่ตัวหนายังไม่ได้แก้ คิดว่าจะแก้ใหญ่ในภายหลังครับ) DejaVuSansThai-src-510618.tar.gz
  • เพิ่มตาราง open type (แก้ปัญหา OpenOffice-2.4 "สระอำ" เพี้ยน) และตรวจแก้ข้อมูลตาราง lookup gsub ขาดไป : DejaVuSansThai-src-510823.tar.gz
  • เริ่มการใช้ตาราง GPOS ลดสระหลบทั้งหมด ย้าย ฐ และ ญ ไม่มีฐานขึ้นข้างบน ดัดแปลงลายเส้นเล็กน้อย hint แบบให้กลมกลึง (คมชัดน้อยลง) : DejaVuSansThai-src-520218.tar.gz
  • hint ตัวหนา(เกือบ)เสร็จ (พักไปงานอื่นก่อน) รุ่นนี้พยายามทดลองเขียนโค๊ดไพธอนแบบนำมาใช้ใหม่ แต่เวลากระชั้นมาก โค๊ดข้างในจึงเลอะเทอะมาก (ตัวหนา) : DejaVuSansThai-src-520303.tar.gz
  • เกลา hint ตัวธรรมดาใหม่ โค๊ดเป็นฟังก์ชั่นเพื่อให้การ hint ไปในแนวเดียวกันทุก ๆ อักขระ แก้ปัญหาหัวบางที่ 9-12 ppem ทำให้อ่านง่ายขึ้น ลบการฟุ้งมากไปหน่อยจนทำให้ฟอนต์ดูแห้ง พยายามทำให้ขนาดเส้นตั้งกับเส้นนอนไม่ต่างกันมากนัก ที่ 17-19 ppem ยังไม่ได้แยกวรรณยุกต์เป็น 2 ระดับ (โค๊ดที่เป็นฟังก์ชั่นยังเลอะเทอะอยู่) : DejaVuSansThai-src-520322.tar.gz
  • ทำวรรณยุกต์ระดับสอง ปรับช่องไฟและความกว้างฟอนต์ขนาดเล็ก : DejaVuSansThai-src-520401.tar.gz

งานที่เหลือ และที่ตั้งใจจะทำ

  • เกลาช่องไฟละเอียด
  • ดูเส้นขาดฟอนต์ขาวพื้นหลังดำ
  • ทำตัวหนาให้มาแนวเดียวกัน

ภาพตัวอย่างของรุ่นล่าสุด

ตัวหนา

ภาพจาก OpenOffice.org รุ่นล่าสุด

oo ตัวหนา

ภาพตัวอย่าง รุ่นแรก (จากจอภาพขนาด 85 dpi)








Topic: 

fonts: โค๊ดไพธอนที่ใช้ช่วยทำงาน

สรุปรวมจาก python: เขียนโค๊ดคลี่แสต็กฟอนต์ และ python: โค๊ดฟอนต์ย้อนกลับ
รวมเป็นไฟล์เดียว ตั้งชื่อว่า dfont.py การทำงานแค่คลี่และเรียงแสต็กใหม่
เวลาใช้งาน จะต้องเขียนสคริปต์ไพธอนเพิ่มสำหรับฟอนต์แต่ละตัว เพื่อมาเรียกใช้ dfont เป็นมอดูล ปรับปรุง
  • 51-05-06 เพิ่มฟังก์ชั่นการทำ grid fitting ทำให้การเขียนโค๊ดง่ายขึ้น
    เช่นต้องการขยับจุด 20 ไป +8 ที่ฟอนต์ขนาด 9, 10 และ 11 ppem
    เดิมเขียนโค๊ดเป็น
    deltap1 3 20 15 20 31 20 47 เปลี่ยนเป็น
    f_deltap 20 9+8 20 10+8 20 11+8
โค๊ดมีดังนี้
$ vi dfont.py
#!/usr/bin/env python
"""
This file is only stack render engine.
Usage:
1. Decode from instucted code
    ./dfont.py -d INST_CODE.txt > PSEUDO_CODE.txt
2. Encode from pseudo_code
    ./dfont.py -e PSEUDO_CODE.txt > INST_CODE.txt


Implement:
1 Create FONTNAME.py contain code as followed:
    1.1 import dfont, sys, os
    1.2 cvt_dict = {"NAME": VALUE, ... }
    1.3 pseudo_code_dict = {"CHARACTER_NAME": "PSEUDO CODE TRUETYPE INSTRUCTION", ...}
    1.4 if __name__ == "__main__":
            if sys.argv[1] == "--all":
                #$0 --all = ENCODE ALL CHARS
                sfd_file = os.path.basename(sys.argv[0]).split('.',1)[0] + '.sfd'
                print "Encoding all character in %s" % (sfd_file)
                dfont.inst_encode_all( sfd_file, pseudo_code_dict, cvt_dict )
            else:
                #$0 uni0E01 > x.txt = PRINT INSTRUCTION CODE OF uni0E01 TO x.txt
                print '\n'.join(dfont.inst_encode( pseudo_code_dict[sys.argv[1]], cvt_dict ))

2 Encode:
    2.1 Process all character, this script will modify FONTNAME.sfd
        Usage: ./FONTNAME.py --all
    2.2 Encode each character, for example KO_KAI="uni0E01"
        Usage: ./FONTNAME.py uni0E01 > INSTRUCTED.TXT
"""

import sys
import os

# put cvt_dict and pseudo_code_dict varirable in FONTNAME.py
# cvt value example:
#cvt_dict = {
#    "base":             10,     #0
#    "hstem":            8,      #184
#    "vstem":            96,     #154 
#    "vstem_small":      27,     #135 
#    "headstem_small":   11,     #113    =kho
#    "headdia_small":    180,    #377    =kho
#    "headhole_small":   254,    #150    =kho
#    "headneck_kho":     39,     #180    =kho
#    "hwidth_ko":        164,    #987    =ko,tho 
#    "hwidth_kho":       14,     #690    =ko,tho 
#    "hwidth_sho":       108,    #772    =sho
#    "vheight":          49,     #1208 
#    "vheight_hi":       54,     #1208   =ko 
#    "vheight_hi_kho":   54,     #1229   =kho 
#    "vheight_lo":       88,     #20 
#    "vheight_lo_sho":   256,    #6  *** NEWVALUE
#    "head_diff":        131,    #254    =head of character: bo,po
#    "beak_diff":        9,      #102    =beak of ko,tho
#    "front_space_ko":   65,     #201    =front spacing of ko,tho
#    "front_space_kho":  38,     #340    =front spacing of kho
#}

# pseudo_code example:
#pseudo_code_dict = {
#    #ko_kai
#    "uni0E01": """
#srp0    26
#mirp[rp0,min,rnd,grey] front_kai 14
#mirp[min,rnd,grey]  hstem  12
#mirp[rp0,rnd,grey]  hwidth_14 25
#mirp[min,rnd,grey]  hstem 1
#mdrp[min,rnd,grey]  27
#srp0   14
#mdrp[rp0,rnd,grey]  18
#mdrp[min,rnd,grey]  8
#mdrp[rp0,rnd,grey]  17
#mdrp[min,rnd,grey]  10
#iup[x]
#svtca[y-axis]
#mdap[rnd]  1
#ALIGNRP    13
#mirp[rp0,rnd,grey]  vheight_shoot    22
#mirp[min,rnd,grey]  vstem_curve  5
#srp1    5
#srp2    13
#sloop   10
#ip  19 8 18 9 17 10 2 25 15 12
#iup[y]
#""",
#}


#BEGIN
# inst_dict = { "COMMAND" : ("Description",diff,pops,push), ... }
# diff: 0=NOOP, 1=1BYTE, 2=2BYTE, ...
#       -1=FIRST BYTE IS ONE BYTE COUNTER,
#       -2=FIRST BYTE IS TWO BYTE COUNTER, 
#       -3=CLEAR STACK
#       -4=REQUIRE SOME PROCESSING
inst_dict = {
    "AA" :       ("Adjust Angle", 1, 1, 0),
    "ABS" :      ("ABSolute value", 0, 1, 1),
    "ADD" :      ("ADD", 1, 2, 1),
    "ALIGNPTS" : ("ALIGN Points", 2, 2, 0),
    "ALIGNRP" :  ("ALIGN to Reference Point", 1, 1, 0),
    "AND" :      ("logical AND", 1, 2, 1),
    "CALL" :     ("CALL function", 1, 1, 0),
    "CEILING" :  ("CEILING", 0, 1, 1),
    "CINDEX" :   ("Copy the INDEXed element to the top of the stack", 1, 1, -4),
    "CLEAR" :    ("CLEAR the stack", -3, 0, 0),
    "DEBUG" :    ("DEBUG call", 1, 1, 0),
    "DELTAC1" :  ("DELTA exception C1", -2, 0, 0),
    "DELTAC2" :  ("DELTA exception C2", -2, 0, 0),
    "DELTAC3" :  ("DELTA exception C3", -2, 0, 0),
    "DELTAP1" :  ("DELTA exception P1", -2, 0, 0),
    "DELTAP2" :  ("DELTA exception P2", -2, 0, 0),
    "DELTAP3" :  ("DELTA exception P3", -2, 0, 0),
    "DEPTH" :    ("DEPTH of the stack", 0, 0, 1),
    "DIV" :      ("DIVide", 1, 2, 1),
    "DUP" :      ("DUPlicate top stack element", 0, 1, 1),
    "EIF" :      ("End IF", 0, 0, 0),
    "ELSE" :     ("ELSE clause", 0, 0, 0),
    "ENDF" :     ("END Function definition", 0, 0, 0),
    "EQ" :       ("EQual", 1, 2, 1),
    "EVEN" :     ("EVEN", 0, 1, 1),
    "FDEF" :     ("Function DEFinition", 1, 1, 0),
    "FLIPOFF" :  ("set the auto FLIP Boolean to OFF", 0, 0, 0),
    "FLIPON" :   ("set the auto FLIP Boolean to ON", 0, 0, 0),
    "FLIPPT" :   ("FLIP PoinT", 1, 1, 0),
    "FLIPRGOFF" :    ("FLIP RanGe OFF", 2, 2, 0),
    "FLIPRGON" : ("FLIP RanGe ON", 2, 2, 0),
    "FLOOR" :    ("FLOOR", 2, 2, 0),
    "GC" :       ("Get Coordinate projected onto the projection vector", 0, 1, 1),
    "GETINFO" :  ("GET INFOrmation", 0, 1, 1),
    "GFV" :      ("Get Freedom Vector", 0, 0, 2),
    "GPV" :      ("Get Projection Vector", 0, 0, 2),
    "GT" :       ("Greater Than", 1, 2, 1),
    "GTEQ" :     ("Greater Than or EQual", 1, 2, 1),
    "IDEF" :     ("Instruction DEFinition", 1, 1, 0),
    "IF" :       ("IF test", 1, 1, 0),
    "INSTCTRL" : ("INSTRuction execution ConTRoL", 2, 2, 0),
    "IP" :       ("Interpolate Point", 1, 1, 0),
    "ISECT" :    ("moves point p to the InterSECTion of two lines", 5, 5, 0),
    "IUP" :      ("Interpolate Untouched Points through the outline", 0, 0, 0),
    "JMPR" :     ("JuMP Relative", 1, 1, 0),
    "JROF" :     ("Jump Relative On False", 1, 1, 0),
    "JROT" :     ("Jump Relative On True", 3, 3, 0),
    "LOOPCALL" : ("LOOP and CALL function", 2, 2, 0),
    "LT" :       ("Less Than", 1, 2, 1),
    "LTEQ" :     ("Less Than or Equal", 1, 2, 1),
    "MAX" :      ("MAXimum of top two stack elements", 1, 2, 1),
    "MD" :       ("Measure Distance", 1, 2, 1),
    "MDAP" :     ("Move Direct Absolute Point", 1, 1, 0),
    "MDRP" :     ("Move Direct Relative Point", 1, 1, 0),
    "MIAP" :     ("Move Indirect Absolute Point", 2, 2, 0),
    "MIN" :      ("MINimum of top two stack elements", 1, 2, 1),
    "MINDEX" :   ("Move the INDEXed element to the top of the stack", 1, 1, 3),
    "MIRP" :     ("Move Indirect Relative Point", 2, 2, 0),
    "MPPEM" :    ("Measure Pixels Per EM", 0, 0, 1),
    "MPS" :      ("Measure Point Size", 0, 0, 1),
    "MSIRP" :    ("Move Stack Indirect Relative Point", 1, 1, 0),
    "MUL" :      ("MULtiply", 1, 2, 1),
    "NEG" :      ("NEGate", 0, 1, 1),
    "NEQ" :      ("Not EQual", 1, 2, 1),
    "NOT" :      ("logical NOT", 0, 1, 1),
    "NPUSHB" :   ("PUSH N Bytes", -1, 0, 0),
    "NPUSHW" :   ("PUSH N Words", -1, 0, 0),
    "NROUND" :   ("No ROUNDing of value", 0, 1, 1),
    "ODD" :      ("ODD", 0, 1, 1),
    "OR" :       ("logical OR", 1, 2, 1),
    "POP" :      ("POP top stack element", 1, 1, 0),
    "PUSHB" :    ("PUSH Bytes", -4, 0, 0),
    "PUSHW" :    ("PUSH Words", -4, 0, 0),
    "RCVT" :     ("Read Control Value Table entry", 0, 1, 1),
    "RDTG" :     ("Round Down To Grid", 0, 0, 0),
    "ROFF" :     ("Round OFF", 0, 0, 0),
    "ROLL" :     ("ROLL the top three stack elements", 0, 3, 3),
    "ROUND" :    ("ROUND value", 0, 1, 1),
    "RS" :       ("Read Store", 0, 1, 1),
    "RTDG" :     ("Round To Double Grid", 0, 0, 0),
    "RTG" :      ("Round To Grid", 0, 0, 0),
    "RTHG" :     ("Round To Half Grid", 0, 0, 0),
    "RUTG" :     ("Round Up To Grid", 0, 0, 0),
    "S45ROUND" : ("Super ROUND 45 degrees", 1, 1, 0),
    "SANGW" :    ("Set Angle Weight", 1, 1, 0),
    "SCANCTRL" : ("SCAN conversion ConTRoL", 1, 1, 0),
    "SCANTYPE" : ("SCANTYPE", 1, 1, 0),
    "SCFS" : ("Sets Coordinate From the Stack using projection vector and freedom vector", 2, 2, 0),
    "SCVTCI" :   ("Set Control Value Table Cut-In", 1, 1, 0),
    "SDB" :      ("Set Delta Base in the graphics state", 1, 1, 0),
    "SDPVTL" :   ("Set Dual Projection Vector To Line", 2, 2, 0),
    "SDS" :      ("Set Delta Shift in the graphics state", 1, 1, 0),
    "SFVFS" :    ("Set Freedom Vector From Stack", 2, 2, 0),
    "SFVTCA" :   ("Set Freedom Vector To Coordinate Axis", 0, 0, 0),
    "SFVTL" :    ("Set Freedom Vector To Line", 2, 2, 0),
    "SFVTP" :    ("Set Freedom Vector To Projection Vector", 0, 0, 0),
    "SHC" :      ("SHift Contour using reference point", 1, 1, 0),
    "SHP" :      ("SHift Point using reference point", 1, 1, 0),
    "SHPIX" :    ("SHift point by a PIXel amount", 2, 2, 0),
    "SHZ" :      ("SHift Zone using reference point", 1, 1, 0),
    "SLOOP" :    ("Set LOOP variable", 1, 1, 0),
    "SMD" :      ("Set Minimum Distance", 1, 1, 0),
    "SPVFS" :    ("Set Projection Vector From Stack", 2, 2, 0),
    "SPVTCA" :   ("Set Projection Vector To Coordinate Axis", 0, 0, 0),
    "SPVTL" :    ("Set Projection Vector To Line", 2, 2, 0),
    "SROUND" :   ("Super ROUND", 1, 1, 0),
    "SRP0" :     ("Set Reference Point 0", 1, 1, 0),
    "SRP1" :     ("Set Reference Point 1", 1, 1, 0),
    "SRP2" :     ("Set Reference Point 2", 1, 1, 0),
    "SSW" :      ("Set Single Width", 1, 1, 0),
    "SSWCI" :    ("Set Single Width Cut-In", 1, 1, 0),
    "SUB" :      ("SUBtract", 1, 2, 1),
    "SVTCA" :    ("Set freedom and projection Vectors To Coordinate Axis", 0, 0, 0),
    "SWAP" :     ("SWAP the top two elements on the stack", 0, 2, 2),
    "SZP0" :     ("Set Zone Pointer 0", 1, 1, 0),
    "SZP1" :     ("Set Zone Pointer 1", 1, 1, 0),
    "SZP2" :     ("Set Zone Pointer 2", 1, 1, 0),
    "SZPS" :     ("Set Zone PointerS", 1, 1, 0),
    "UTP" :      ("UnTouch Point", 1, 1, 0),
    "WCVTF" :    ("Write Control Value Table in Funits", 1, 1, 0),
    "WCVTP" :    ("Write Control Value Table in Pixel units", 2, 2, 0),
    "WS" :       ("Write Store", 2, 2, 0),
}

stack1_command = ("NPUSHB", "NPUSHW")
stack2_command = ("PUSHB", "PUSHW")

arg_1_command = ("MDAP", "MDRP")
arg_2_command = ("MIAP", "MIRP")

delta_command = ("F_DELTA",)
#step_list USE IN SPECIAL PSUEDO FUNCTION f_delta
step_list = [-8, -7, -6, -5, -4, -3, -2, -1, 1, 2, 3, 4, 5, 6, 7, 8]

normal_command = []
for i in range(6):
    normal_command.append( [ j for j in inst_dict.keys() if inst_dict[j][1]==i ] )

#           -1=FIRST BYTE IS ONE BYTE COUNTER,
#           -2=FIRST BYTE IS TWO BYTE COUNTER, 
#           -3=CLEAR STACK
#           -4=REQUIRE PROCESS
pop_command = []
pop_command.append([])
for i in range(-1, -4, -1):
    pop_command.append( [ j for j in inst_dict.keys() if inst_dict[j][1]==i ] )


def usage(prog_name):
    print """\
Usage: %s [-d CODE | [-e] PSUEDO_CODE]
""" % (prog_name)
    return

def line_format(cmd,stack,desc):
    #STRING, LIST, STRING
    return "%-24s %-10s ;%10s" % (cmd, "  ".join(stack), desc,)

def f_delta(first_cmd, l):
    #PSUEDO COMMAND TO PROCESS DELTA FUNCTION
    #
    #first_cmd = PSUEDO COMMAND, ex: f_deltap1 = deltap1
    #l = LIST OF ARGUMENT (INCLUDE SELF UN-UPPERCASE COMMAND IN FIRST ARGUMENT)
    #
    #usage: 
    #   f_deltap 0 16+8     #deltap1 MOVE POINT 0 AT 16 PPEM +8
    #real code will be:
    #   DELTAP1  1 0 127
    #
    d1_list = []
    d2_list = []
    d3_list = []
    first_cmd = first_cmd[2:]   #TRIM F_ OUT
    l.pop(0)
    while len(l) > 0:
        point = l.pop(0)
        pstep = l.pop(0)
        if '+' in pstep or '-' in pstep:
            if '+' in pstep:
                ppem, no_of_step = pstep.split('+')
                ppem = int(ppem)
                no_of_step = int(no_of_step)
            else:
                ppem, no_of_step = pstep.split('-')
                ppem = int(ppem)
                no_of_step = -int(no_of_step)
            if ppem < 9:
                raise ValueError('ppem must greater than 9, %s: %s %s' % (first_cmd, point, pstep,))
            if abs(no_of_step) > 8:
                raise ValueError('no_of_step must be between +8 and -8, %s: %s %s' % (first_cmd, point, pstep,))
            if no_of_step == 0:
                raise ValueError('no_of_step must not be 0, %s: %s %s' % (first_cmd, point, pstep,))
            num = (ppem-9)*16 + step_list.index(no_of_step)
            if num < 256:
                d1_list.extend([point,str(num)])
            elif num < 512:
                d2_list.extend([point,str(num)])
            elif num < 1024:
                d3_list.extend([point,str(num)])
            else:
                raise ValueError('Number of delta point exceed 1024, %s' % (num,))
        else:
            raise SyntaxError('Please use + or - in f_delta second argument, %s %s' % (point, pstep,))
    new_l = []
    if d1_list:
        new_l.append(first_cmd+'1')
        new_l.append(str(len(d1_list)/2))
        new_l.extend(d1_list)
    if d2_list:
        new_l.append(first_cmd+'2')
        new_l.append(str(len(d2_list)/2))
        new_l.extend(d2_list)
    if d3_list:
        new_l.append(first_cmd+'3')
        new_l.append(str(len(d3_list)/2))
        new_l.extend(d3_list)
    return  new_l[0], new_l

def inst_decode(txt):
    """Decode TrueType Instruction code into simpler code"""
    txt_list = txt.split("\n")
    new_list = []
    commentlist = []
    stack_list = []
    sloop = 0
    i = 0
    while i < len(txt_list):
        if "[" in txt_list[i]:
            c1,c2 = txt_list[i].strip().split("[",1)
        else:
            c1,c2 = txt_list[i].strip(), ""
        #SKIP EMPTY LINE
        if c1 == "":
            i += 1
            continue
        #STACK: NPUSHB, NPUSHW
        if c1 in stack1_command:
            i += 1
            n = int(txt_list[i].strip())
            i += 1
            while n > 0:
                stack_list.append(txt_list[i].strip())
                i += 1
                n -= 1
            continue
        #STACK2: PUSHB, PUSHW
        if c1[:5] in stack2_command:
            n = int(txt_list[i].strip()[6:])
            i += 1
            while n > 0:
                stack_list.append(txt_list[i].strip())
                i += 1
                n -= 1
            continue
        temp_list = []
        #POP ONE
        if c1 in pop_command[1]:
            idx = stack_list.pop()
            temp_list = [idx]
            for j in range(int(idx)):
                temp_list.append(stack_list.pop()) 
            new_list.append(line_format(txt_list[i],temp_list,inst_dict[c1][0]))
            i += 1
            continue
        #POP PAIR
        elif c1 in pop_command[2]:
            idx = stack_list.pop()
            temp_list = [idx]
            for j in range(int(idx)):
                temp_list.append(stack_list.pop()) 
                temp_list.append(stack_list.pop()) 
            new_list.append(line_format(txt_list[i],temp_list,inst_dict[c1][0]))
            i += 1
            continue
        #POP ALL (CLEAR STACK)
        elif c1 in pop_command[3]:
            stack_list = []
            new_list.append(line_format(txt_list[i],temp_list,inst_dict[c1][0]))
            i += 1
            continue

        #NORMAL COMMAND
        if c1 in normal_command[0]:
            count = 0
        elif c1 in normal_command[1]:
            count = 1 
        elif c1 in normal_command[2]:
            count = 2 
        elif c1 in normal_command[3]:
            count = 3 
        elif c1 in normal_command[4]:
            count = 4 
        elif c1 in normal_command[5]:
            count = 5 
        else:
            count = -1
            #print 'count-1:',c1
            new_list.append('count-1:%s' % (c1,))
        if count > 0:
            if sloop > 0:
                count += sloop-1
                sloop = 0
        while count > 0:
            if stack_list == []:
                #print temp_list
                new_list.extend(temp_list)
                break
            cnum = stack_list.pop()
            temp_list.append(cnum)
            if c1 == 'SLOOP':
                sloop = int(cnum)
            count -= 1
        new_list.append(line_format(txt_list[i],temp_list,inst_dict[c1][0]))
        i += 1
    return new_list
    
def stack_format(stack_list):

    def flush(new_list, cmd_list, ind, cur_ind):
        #if len(cmd_list)==0: return [], [], cur_ind
        if ind == 0:
            temp = ['PUSHB_','NPUSHB']
        else:
            temp = ['PUSHW_','NPUSHW']
        if len(cmd_list) < 8:
            new_list.append('%s%s' % (temp[0],len(cmd_list),))
        else:
            new_list.append(temp[1])
            new_list.append(' %s' % (len(cmd_list),))
        new_list.extend(cmd_list)
        return new_list, [], cur_ind

    if len(stack_list)==0: return []
    n = 0
    new_list = []
    cmd_list = []
    stack_list.reverse()
    if int(stack_list[n]) < 256:
        ind = 0     #BYTE
    else:
        ind = 1     #WORD
    while n < len(stack_list):
        if int(stack_list[n]) < 256:
            cur_ind = 0     #BYTE
        else:
            cur_ind = 1     #WORD
        if ind == cur_ind:
            cmd_list.append(' %s' % (stack_list[n],))
        else:
            new_list, cmd_list, ind = flush(new_list, cmd_list, ind, cur_ind)    #FLUSH
            cmd_list.append(' %s' % (stack_list[n],))
        n += 1
    new_list, cmd_list, ind = flush(new_list, cmd_list, ind, cur_ind)    #FLUSH
    return new_list 

def inst_encode(txt,cvt_dict={}):
    """Encode simple code into TrueType Instruction code"""
    if cvt_dict == {}:
        print "WARNING: cvt_dict is empty, please supply cvt_dict argument"
    comment_list = ['#',';']    #COMMENT CHARACTER
    flush_list = ['SVTCA']      #FLUSH STACK
    stack_list = []
    new_list = []
    cmd_list = []
    txt_list = txt.split('\n')
    for line in txt_list:
        line = line.replace("\t"," ")
        if " " in line:
            l = line.split(" ")
        else:
            l = [line] 
        l = [ j for j in l if j != "" ]
        if l == []:
            continue
        if l[0][0] in comment_list: #BYPASS COMMENT
            continue
        for f in flush_list:    #?FLUSH STACK
            if f == l[0][:len(f)]:
                new_list.extend(stack_format(stack_list))
                stack_list = []
                new_list.extend(cmd_list)
                cmd_list = []
                break
        if '[' in l[0]:         #TO UPPERCASE
            ltemp = l[0].split('[',1)
            first_cmd = ltemp[0].upper()
            first_word = first_cmd+'['+ltemp[1]
        else:
            first_word = l[0].upper()
            first_cmd = first_word
        #CHECK SPECIAL COMMAND ---------------------------------------
        # f_delta -> f_deltap1 = deltap1, ...
        for temp in delta_command:
            if temp in first_word:
                first_word, l = f_delta(first_word, l)
    
        #END CHECK SPECIAL COMMAND -----------------------------------
        cmd_list.append(first_word)     #KEEP COMMAND
        l.pop(0)                        #PROCESS REST STACK NUM
#
        temp_arg_list = []
        for element in l:
            if element[0] in comment_list:
                break 
            else:
                temp_list = element.split('[',1)
                if element in cvt_dict.keys():                  #CHECK CVT VALUE
                    stack_list.append(cvt_dict[element])
                    temp_arg_list.append(element)
                elif temp_list[0].upper() in inst_dict.keys():  #TRAP DELTA COMMAND
                    temp_list[0] = temp_list[0].upper()
                    element = '['.join(temp_list)
                    cmd_list.append(element)
                else:
                    stack_list.append(element)
                    temp_arg_list.append(element)
        #TEST NUMBER OF PARAMETER
        if first_cmd in arg_1_command:
            if len(temp_arg_list) != 1:
                raise ValueError('%s must take 1 argument, %s' % (first_word, temp_arg_list,))
        elif first_cmd in arg_2_command:
            if len(temp_arg_list) != 2:
                raise ValueError('%s must take 2 arguments, %s' % (first_word, temp_arg_list,))
        #
    if stack_list != []:
        new_list.extend(stack_format(stack_list))
    new_list.extend(cmd_list)
    return new_list


def inst_encode_all(sfd_file, pseudo_code_dict={}, cvt_dict={}):
    """Encode all characters in pseudo_code_dict, modify FONTNAME.sfd."""
    if os.path.isfile(sfd_file):
        sfd = open(sfd_file).read().split('\n')
        sfd_bak = sfd[:]
    else:
        print "File %s.sfd is not existed, script aborted." % (sfd_file,)
        sys.exit[1]
    for chr,txt in pseudo_code_dict.iteritems():
        search_line = 'StartChar: %s' % (chr,)
        if search_line in sfd:
            line = sfd.index(search_line)
            while line < len(sfd):
                if sfd[line] == "TtInstrs:":
                    temp_sfd = sfd[:line+1]
                    #print temp_sfd[-2:]
                    temp_sfd += inst_encode(txt,cvt_dict)
                    line += 1
                    while sfd[line] != "EndTTInstrs":
                        line += 1
                    temp_sfd += sfd[line:]
                    sfd = temp_sfd[:]
                    break
                elif sfd[line] in ["Back","Fore"]:
                    temp_sfd = sfd[:line] \
                               + ["TtInstrs:"] \
                               + inst_encode(txt,cvt_dict) \
                               + ["EndTTInstrs"]+sfd[line:]
                    sfd = temp_sfd[:]
                    break
                elif sfd[line] == "EndChar":
                    print "sfd file format error at %s" % (chr,)
                    break
                else:
                    line += 1
        else:
            print "Character %s not found, script aborted." % (chr,)
            sys.exit(1)
    if sfd != sfd_bak:
        os.rename(sfd_file, "%s.bak" % (sfd_file,))
        f = open(sfd_file,"w")
        f.write("\n".join(sfd))
        f.close()
        print "%s modified." % (sfd_file,)
    else:
        print "No modified."


if __name__ == "__main__":
    if sys.argv[1] == "-d":
        #DECODE
        #$0 -d x.txt > d.txt = PRINT PSEUDO_CODE FROM x.txt TO d.txt
        f = open(sys.argv[2])
        txt = f.read()
        f.close()
        print '\n'.join(inst_decode(txt))
    elif sys.argv[1] == "-e":
        #ENCODE
        #$0 -e d.txt > x.txt = PRINT INSTRUCTED CODE FROM PSEUDO_CODE d.txt TO x.txt
        f = open(sys.argv[2])
        txt = f.read()
        f.close()
        print '\n'.join(inst_encode(txt))
    else:
        #DEFAULT IS ENCODING
        #$0 -e d.txt > x.txt = PRINT INSTRUCTED CODE FROM PSEUDO_CODE d.txt TO x.txt
        f = open(sys.argv[1])
        txt = f.read()
        f.close()
        print '\n'.join(inst_encode(txt))
Topic: 

บันทึกการ instruct ฟอนต์

เที่ยวนี้ไม่มีอะไร บันทึกตัวอย่างการ instruct เอาไว้ดูเพื่ออ้างอิงเฉย ๆ

ตัวอย่าง DejaVuSans อักขระ n

โค๊ด

NPUSHB
 25
 3
 9
 0
 3
 14
 1
 6
 135
 14
 17
 184
 12
 188
 10
 1
 2
 8
 0
 78
 13
 9
 8
 11
 70
 20
SRP0
MIRP[rp0,min,rnd,grey]
MIRP[min,rnd,grey]
SHP[rp2]
MIRP[rp0,rnd,grey]
MIRP[min,rnd,grey]
IUP[x]
SVTCA[y-axis]
MDAP[rnd]
ALIGNRP
MIRP[rnd,grey]
MIRP[rp0,rnd,grey]
MDRP[rnd,grey]
MIRP[min,rnd,grey]
SRP1
SRP2
SLOOP
IP
IUP[y]
PUSHB_5
 96
 21
 207
 21
 2
SVTCA[x-axis]
DELTAP1

outline

x-direction instruction

y-direction instruction

คลี่สแต็กให้พออ่านง่าย

CODE                   RP0     STACK      DESCRIPTION
                                           (x-axis is default direction)
SRP0                           20         Set Reference Point 0
MIRP[rp0,min,rnd,grey] (20)    70 11      Move Indirect Relative Point
MIRP[min,rnd,grey]     (11)    8 9
SHP[rp2]               (11)    13         SHift Point using reference point
MIRP[rp0,rnd,grey]     (11)    78 0
MIRP[min,rnd,grey]     (0)     8 2
IUP[x]                                    Interpolate Untouched Points through the outline

SVTCA[y-axis]                             Set freedom and projection Vectors To Coordinate Axis
MDAP[rnd]                      1          Move Direct Absolute Point
ALIGNRP                (1)     10         ALIGN to Reference Point
MIRP[rnd,grey]         (1)     188 12
MIRP[rp0,rnd,grey]     (1)     184 17
MDRP[rnd,grey]         (17)    14
MIRP[min,rnd,grey]     (17)    135 6
SRP1                           1          Set Reference Point 1
SRP2                           14         Set Reference Point 2
SLOOP                          3          Set LOOP variable
IP                             0 9 3      Interpolate Point
IUP[y]                                    Interpolate Untouched Points through the outline

SVTCA[x-axis]
DELTAP1                       2 21 207 21 96    DELTA exception P1


อธิบาย deltap

The 8 bit arg component of the DELTA instructions decomposes into two parts. 
The first 4 bits represent the relative number of pixels per em at which the exception is applied. 
The second 4 bits represent the magnitude of the change to be made. The structure of the arg is shown in FIGURE 26.
ppem = pointSize * dpi / 72 

207 = 1100 1111
1100=12 (9+12=21ppem)
1111=15 = 8step = 8/64 of pixel    (0=-8, 1=-7, ... 14=+7, 15=+8)

96 = 0110 0000
0110=6 (9+6=15ppem)
0000=0 = -8step = -8/64 of pixel

แปลว่า
1.เลื่อนจุด 21 ไป 8step ที่ขนาดฟอนต์ 21 ppem (จอ Mac75dpi=21pt Windows96dpi~16pt Linux(จอผม85dpi)~18pt)
2. เลื่อนจุด 21 ไป -8step ที่ขนาดฟอนต์ 15 ppem (จอ Mac75dpi=15pt Windows96dpi~11pt Linux(จอผม85dpi)~13pt)

ตาราง cvt

ITEM            VALUE           DESCRIPTION
8               184             stem width
70              186             left spacing
78              938             outer width
135             156             top stem thick
184             1147            straight vertical height
188             1120            outer height
Topic: 

fonts: ตัวอย่างการ hint ก.ไก่

มีค่า cvt ของฟอนต์ DejaVu Sans ที่เกี่ยวข้อง ทำเป็นตัวแปรแบบดิกชันนารีในไพธอน คือ
cvt_dict = {
    ...
    "front_kai":      11,     #113    =front spacing of ko_kai 
    "hstem":          8,      #184    =horizontal stem width
    "w_881":          163,    #881    =width of ko kai
    "vheight_shoot":  184,    #1147   =overshoot height
    "vstem_curve":    185,    #156    =curve vertical stem width
    ...
}
ค่า cvt ของฟอนต์ ดูได้จากเมนูของ FontForge คือ Hints -> Edit 'cvt' ... โดยจะเรียงตั้งแต่ลำดับที่ 0 เป็นต้นไป
ค่า cvt ของ ก.ไก่ข้างต้น คือ กำหนดให้ลำดับที่ 11 (ซึ่งมีค่า 113) เป็นระยะช่องไฟด้านหน้า เป็นต้น โครง ก.ไก่ ตัวอย่างจะเริ่มด้วยการ hint ในแนวแกน X ก่อน (เป็นค่าปริยาย) จะแสดงเป็นโค๊ด instruction แบบจำลอง เพื่อให้ดูง่าย เวลาใช้งานจริง ก็นำไปเขียนเป็นโค๊ดในไพธอน โปรแกรม dfont.py จะคลี่แสต็กและเรียงออกมาเป็นโค๊ดจริง ๆ อีกที แนวแกน X
เส้นทาง hint ในแนวแกน X จุดเริ่มต้นคือจุดที่ 26 (จุดสุดท้ายคือ 25 + 1)
srp0                26
จากจุดเริ่มต้น จะเคลื่อนจุดอ้างอิง rp0 ไปยังจุดที่ใกล้ขอบซ้ายที่สุด คือจุด 18 เพื่อกั้นเป็นระยะช่องไฟด้านหน้า ด้วยค่า cvt คือ front_kai
การเคลื่อนด้วยการอ้างอิงค่า คำสั่งคือ MIRP
การเคลื่อนจุดอ้างอิงไปด้วย ใช้อาร์กิวเมนต์ rp0
การปัดเศษให้ลงจุด (round) ใช้อาร์กิวเมนต์ rnd
การแรเงาพื้นที่ ใช้อาร์กิวเมนต์ grey (ปกติควรเป็น white แต่ DejaVu ใฃ้ grey เสมอ จึงใช้ตาม เพื่อกันสับสน)
ค่า cvt ที่ใช้ คือ front_kai
จุดที่จะเคลื่อนไป คือ 18
mirp[rp0,min,rnd,grey]  front_kai 18
จุดที่จะไปต่อคือ จุด 8 แต่เนื่องจากเมื่อเคลื่อนไปแล้ว ไม่มีจุดที่จะไปต่อ เราจึงไม่ต้องเคลื่อนจุด rp0 ไปด้วย จึงไม่ต้องใส่อาร์กิวเมนต์ rp0
ระยะของปาก ก.ไก่ ไม่จำเป็นต้องเป็นค่าที่แน่นอนนัก เราจึงไม่ต้องอ้างค่า cvt จึงใช้คำสั่ง MDRP
ปัดเศษด้วย ใช้ rnd
แรเงา ใช้ grey
จุดที่เคลื่อนไปคือ 8
mdrp[min,rnd,grey]  8
ตอนนี้ rp0 ยังอยู่ที่เดิม คือ จุด 18 เราต้องเคลื่อนไปต่อที่จุด 17 และ 10 ตามลำดับ แต่หากเราเคลื่อนไปจุด 17 ก่อน แล้วต่อไปที่ 10 จะทำให้ระยะที่ถูกทด (round) แล้ว มีค่ามากเกินไป ทำให้ปาก ก.ไก่ ไม่สวยงาม เราจึงใช้การเคลื่อนไปที่จุด 10 ก่อน แล้วจึงย้อนมาที่ 17 อีกครั้งหนึ่ง ทำให้ได้ภาพที่สวยงามกว่า
mdrp[rp0,rnd,grey]  10
mdrp[min,rnd,grey]  17
จุดที่จะไปต่อคือจุด 14 แต่ตอนนี้จุดอ้างอิง rp0 มาอยู่ที่จุด 10 แล้ว หากเราเคลื่อนจากจุดนี้ไปเลย จะทำให้ระยะทดไม่แน่นอน จึงควรย้อนกลับไปที่จุดแรกคือ 18 จะดีกว่า
srp0                18
mdrp[rp0,rnd,grey]  14
ต่อไปเป็นการกำหนดความหนาของเส้นตั้ง (hstem) ต้องใช้ค่า cvt เสมอ
ต้องอ้างอิงจาก cvt คำสั่งคือ MIRP
ไม่ต้องย้าย rp0 ไปด้วย เพราะไม่มีจุดที่จะไปต่อ จึงไม่ต้องใช้อาร์กิวเมนต์ rp0
ถึงแม้ขนาดเส้นจะบางอย่างไร การแสดงผลต้องกำหนดค่าให้อย่างน้อย 1 จุดเสมอ ใช้อาร์กิวเมนต์ min
แรเงาใช้ grey
mirp[min,rnd,grey]  hstem  12
ต่อไปเป็นการกำหนดความกว้างของตัวอักษร ต้องใช้ cvt เสมอ โดยต้องย้ายจุด rp0 ไปด้วย เพื่อไปทำงานต่อ โดยจะกำหนดขนาดให้คลุมความกว้างรวมก่อน แล้วจึงย้อนมากำหนดความหนาของเส้นตั้งตัวหลังอีกครั้งหนึ่ง
mirp[rp0,rnd,grey]  w_881 25
mirp[min,rnd,grey]  hstem  1
กั้นเป็นฃ่องไฟด้านหลัง โดยไม่ต้องกำหนดระยะ min และเผื่อการแรเงาเป็น white เพื่อให้มีช่องว่างอย่างน้อย 1 จุดคั่นกับอักษรตัวถัดไป มีประโยชน์สำหรับฟอนต์ที่มีการทดลงจุดในแนวแกน X มาก ๆ ซึ่งจะทำให้ล้นความกว้างที่มีอยู่ (คำสั่งนี้คิดเอง เพื่อให้ไม่ต้องทำ delta hint ตามแบบของ DejaVu)
mdrp[rnd,white]     27
เกลี่ยจุดที่เรายังไม่ได้อ้างอิงในแนวแกน X ซึ่งจะใช้คำสั่งสี้ปิดท้ายเสมอ (IUP คือ Interpolate Untouched Point)
IUP[x]
แนวแกน Y
เส้นทาง hint ในแนวแกน Y เริ่มย้ายมาแกน Y
SVTCA[y-axis]
จะเริ่มต้นที่จุด 1 ในแนวแกน Y นี้ เรายังไม่ได้ทำอะไรเลย จึงควรปัดให้ลงจุดเสียก่อน
mdap[rnd]           1
บอกว่าจุด 13 อยู่ในระดับเดียวกับจุด 1
alignrp             13
กำหนดความสูงของฟอนต์
mirp[rp0,rnd,grey]  vheight_shoot    22
กำหนดความหนาของเส้นในแนวนอน
mirp[min,rnd,grey]  vstem_curve  5
กำหนดให้ rp1 เป็นจุด 5 และ rp2 เป็นจุด 13 เพื่อจะเกลี่ยจุดที่สำคัญอื่น ๆ โดยใช้ rp1 และ rp2 เป็นจุดอ้างอิงในการเฉลี่ยค่า
srp1                5
srp2                13
sloop               10
ip      19 8 18 9 17 10 2 25 15 12
เกลี่ยจุดที่เหลือ ในแนวแกน Y
IUP[y]
เมื่อนำโค๊ดที่เขียนมาถอดด้วยสคริปต์ dfont.py จะได้โค๊ด instruction ดังนี้
NPUSHB
 15
 27
 1
 8
 25
 163
 12
 8
 14
 18
 17
 10
 8
 18
 11
 26
SRP0
MIRP[rp0,min,rnd,grey]
MDRP[min,rnd,grey]
MDRP[rp0,rnd,grey]
MDRP[min,rnd,grey]
SRP0
MDRP[rp0,rnd,grey]
MIRP[min,rnd,grey]
MIRP[rp0,rnd,grey]
MIRP[min,rnd,grey]
MDRP[rnd,white]
IUP[x]
NPUSHB
 19
 12
 15
 25
 2
 10
 17
 9
 18
 8
 19
 10
 13
 5
 5
 185
 22
 184
 13
 1
SVTCA[y-axis]
MDAP[rnd]
ALIGNRP
MIRP[rp0,rnd,grey]
MIRP[min,rnd,grey]
SRP1
SRP2
SLOOP
IP
IUP[y]
จบแล้วครับ
Topic: 

fonts: ตัวอย่างการ hint ข.ไข่

ค่า cvt ที่เกี่ยวข้องคือ
cvt_dict = {
    ...
    "w_690":            14,     #690    =width of kho_khai
    "hstem":            8,      #184    =horizontal stem
    "vstem":            96,     #154    =vertical stem
    "headstem":         258,    #116    =head stem
    "headstem_plus_hole":   259, #268   =head stem + hole width
    "headdia":          257,    #384    =head diameter
    "vheight":          188,    #1120   =normal height 
    "vheight_shoot":    184,    #1147   =overshoot height
    "front_khai":       32,     #51     =front spacing of kho_khai
    ...
}
ภาพลายเส้น
โครง ข.ไข่ แนวแกน X
เส้นทาง hint ในแนวแกน X ตัวอย่างนี้เริ่มซับซ้อน เนื่องจากช่วงหัวของ ข.ไข่ มีทางเดินที่ต้องการทดลงจุดมาก ทำให้ระยะที่ได้มีค่าไม่แน่นอนตามขนาดฟอนต์ที่เปลี่ยนไป ผลที่ได้คือ
ถ้าเราทำให้ฟอนต์มีขนาดคงที่ จะทำให้บางครั้งหัวดูลีบหรือใหญ่เกินไป
ถ้าเราทำให้หัวดูสวยงาม บางครั้งฟอนต์จะดูกว้างหรือลีบเกินไป
ทางแก้คือเราจะ hint ให้ช่วงหัวให้ดูสวยงามก่อน แล้วโยงไปเส้น stem ด้านหลัง แล้วจึงโยงกลับมาที่ stem ด้านหน้าอีกครั้ง โดยใช้ตรงโค้งช่วงคอของอักษรเป็นช่วงที่รับระยะการเปลี่ยนแปลง วิธีนี้ทำให้เกิดผลข้างเคียงน้อยที่สุด (แต่ก็ยังมีอยู่ดี ต้องแก้ต่อไปด้วย delta hint สำหรับขนาดฟอนต์ที่ดูไม่งาม) เริ่มต้นที่จุด 53
srp0                53
เคลื่อนไปจุดใกล้ทีสุดด้วยระยะ front_khai
mirp[rp0,min,rnd,grey]  front_khai 21
จัดการหัวก่อน ด้วยการกำหนดขนาดเส้นหัว เคลือนไปอีกด้านหนึ่ง แล้วกำหนดขนาดย้อนกลับอีกครั้ง
mirp[min,rnd,grey]  headstem 50
mirp[rp0,rnd,grey]  headdia 15
mirp[min,rnd,grey]  headstem 44
กำหนดจุดคอดที่จุด 12
mdrp[rnd,grey]      12
เคลื่อนไปที่จุด 7 ด้วยการแรเงาแบบ white เพื่อแยกหัวออกจากคออักษรให้ชัดเจน
mdrp[rp0,rnd,white] 7
เคลื่อนไปต่อ เพื่อกำหนดความกว้างของคอ ให้เท่ากับระยะ vstem (ยืมค่า vstem ซึ่งน้อยกว่า hstem มาใช้สำหรับคออักษร)
mirp[rp0,rnd,grey]  vstem 27
เคลื่อนไปต้อ ด้วยการแรเงาแบบ white เพื่อแยกเส้นหน้าและเส้นหลังจากกัน
mdrp[rp0,rnd,white] 36
เคลื่อนต่อ กำหนดขนาดของเส้นหลัง
mirp[rp0,min,rnd,grey] hstem 38
กำหนดระยะกั้นหลัง
mdrp[rnd,white]     54
เคลื่อนย้อนกลับมาที่จุด 1 เพื่อกำหนดความกว้างหลักของอักษร ให้เท่ากับระยะ w_690
mirp[rp0,rnd,grey]  w_690 1
กำหนดความกว้างของเส้นหน้า
mirp[min,rnd,grey]  hstem 32
เกลี่ยจุดที่เหลือ จบการทำงานในแกน X
IUP[x]
แนวแกน Y
เส้นทาง hint ในแนวแกน Y ทำงานในแกน Y
SVTCA[y-axis]
ปัดเศษลงจุด 1
mdap[rnd]           1
กำหนดความสูงปกติของเส้นหลัง
mirp[rnd,grey]      vheight 38
กำหนดความหนาของเส้นแนวนอนให้เท่ากับระยะ vstem
mirp[min,rnd,grey]  vstem 33
เคลื่อนไปเพื่อกำหนดความสูงหลอกตา (overshoot) ของหัว
mirp[rp0,rnd,grey]  vheight_shoot 24
เคลื่อนจุด กำหนดความหนาของคอแนวนอน โดยกำหนดให้เท่ากับความหนาของหัว คือ headstem
mirp[rp0,rnd,grey]  headstem 10
ย้ายมากำหนดส่วนคอด
mdrp[rp0,rnd,white] 12
ตรงนี้ เราจะเคลื่อนลงไปใต้สุดของหัวเลย เพื่ออาศัยพื้นที่ส่วนบนของหัว เป็นระยะรองรับการทดจุด (มีพื้นที่ดำมากกว่าส่วนอื่น) แล้วจึงกำหนดจุดของความหนาหัวและรูของหัวอักษรจากด้านล่าง
mdrp[rp0,rnd,grey]  18
mirp[min,rnd,grey]  headstem 41
mirp[rnd,grey]      headstem_plus_hole 47
เกลี่ยจุดเป็นคำสั่งสุดท้าย
IUP[y]
เสร็จแล้ว
ถอดด้วย dfont.py จะได้โค๊ด instruction ดังนี้
NPUSHB
 12
 32
 8
 1
 14
 54
 38
 8
 36
 27
 96
 7
 44
PUSHW_1
 258
PUSHB_2
 12
 15
PUSHW_1
 257
PUSHB_1
 50
PUSHW_1
 258
PUSHB_3
 21
 32
 53
SRP0
MIRP[rp0,min,rnd,grey]
MIRP[min,rnd,grey]
MIRP[rp0,rnd,grey]
MDRP[rnd,grey]
MIRP[min,rnd,grey]
MDRP[rp0,rnd,white]
MIRP[rp0,rnd,grey]
MDRP[rp0,rnd,white]
MIRP[rp0,min,rnd,grey]
MDRP[rnd,white]
MIRP[rp0,rnd,grey]
MIRP[min,rnd,grey]
IUP[x]
PUSHB_1
 47
PUSHW_1
 259
PUSHB_1
 41
PUSHW_1
 258
PUSHB_3
 18
 12
 10
PUSHW_1
 258
PUSHB_7
 24
 184
 33
 96
 38
 188
 1
SVTCA[y-axis]
MDAP[rnd]
MIRP[rnd,grey]
MIRP[min,rnd,grey]
MIRP[rp0,rnd,grey]
MIRP[rp0,rnd,grey]
MDRP[rp0,rnd,white]
MDRP[rp0,rnd,grey]
MIRP[min,rnd,grey]
MIRP[rnd,grey]
IUP[y]
Topic: 

fonts: ตัวอย่างการ hint ค.ควาย

ค่า cvt ที่เกี่ยวข้องคือ
cvt_dict = {
    ...
    "w_1001":           71,     #1001   =width of kho_kwai
    "w_690":            14,     #690    =
    "front_kai":        11,     #113    =front spacing of ko_kai
    "hstem":            8,      #184    =horizontal stem thick
    "headstem":         258,    #116    =head stem thick
    "headstem_plus_hole":   259, #268   =head stem thick + head hole width
    "vheight_shoot":    184,    #1147   =overshoot height
    "vstem_curve":      185,    #156    =curve range vertical stem
    ...
}
ภาพลายเส้น
โครง ค.ควาย แนวแกน X
เส้นทาง hint ในแนวแกน X เริ่มที่จุด 52
srp0                52
เคลื่อนไปที่จุดใกล้สุด 33 ด้วยระยะ front_kai
mirp[rp0,min,rnd,grey] front_kai 33
กำหนดความหนาของเส้นหน้า ที่จุด 8
mirp[min,rnd,grey]  hstem 8
เคลื่อนไปกำหนดความกว้างของตัวอักษรที่จุดขวาสุด 39 ด้วยความกว้าง w_1001
mirp[rp0,rnd,grey]  w_1001  39
กำหนดช่องไฟหลัง
mdrp[rnd,white]     53
กำหนดความหนาของเส้นหลัง ที่จุด 1
mirp[min,rnd,grey]  hstem 1
มาเริ่มกันใหม่ที่จุด 8
srp0                8
เคลื่อนลงข้างล่าง สู่จุด 11 (เพื่อจะนำไปสู่ฐานของเส้นหน้า) ให้การแรเงาเป็น white เพื่อแยกเส้นหน้ากับคอของหัวอักษร
mdrp[rp0,rnd,white] 11
เนื่องจากเส้นตรงนี้บางมาก จึงกำหนดจุดเพื่อคงระยะอย่างน้อย ที่จุด 27 กำหนดเป็น min
mdrp[min,rnd,grey]  27
จากจุด 11 เคลื่อนไปลงจุด 30
mdrp[rp0,rnd,grey]  30
กำหนดความหนาของฐาน ที่จุด 29
mirp[min,rnd,grey]  hstem 29
กลับไปที่จุด 11
srp0                11
เคลื่อนไปที่จุด 14 ด้วยการแรเงา white อีกครั้ง เพื่อแยกเส้นให้เด็ดขาด
mdrp[rp0,rnd,white] 14
คงความหนาของส่วนคอดไว้
mdrp[min,rnd,grey]  25
ย้อนกลับไปตั้งต้นที่จุด 33 อีกครั้งหนึ่ง เพื่อจะสร้างหัวอักษร โดยให้ระยะ จาก 14-49 เป็นตัวรองรับการทดจุด
srp0                33
เพื่อความแน่นอนแม่นยำ เราจะเคลื่อนจุดไปหาจุดขวาสุดของหัวอักษร โดยกำหนดค่าเป็น w_690
(จำเป็นต้องให้แม่นยำ เนื่องจากช่องว่างตรงหัวอักษรมีจำกัดมาก) แล้วจึงกำหนดขนาดความหนาหัวอักษรและรูย้อนกลับอีกที
mirp[rp0,rnd,grey]  w_690   20
mirp[min,rnd,grey]  headstem 43
mirp[rnd,grey]      headstem_plus_hole 49
เกลี่ยจุดเป็นคำสั่งสุดท้าย
IUP[x]
แนวแกน Y
เส้นทาง hint ในแนวแกน Y ทำงานในแกน Y
SVTCA[y-axis]
เริ่มที่จุด 0
MDAP[rnd]           0
บอกว่าจุด 29 อยู่ในระดับเดียวกัน
ALIGNRP             29
เคลื่อนไปกำหนดจุดสูงสุดด้วยค่า vheight_shoot
mirp[rp0,rnd,grey]  vheight_shoot  36
กำหนดความหนาตรงส่วนนี้ด้วยค่า vstem_curve
mirp[min,rnd,grey]  vstem_curve  5
จากจุด 36 เดิม เคลื่อนไปส่วนบนของหัวอักษรที่จุด 17 เนื่องจากไม่กังวลระยะมากนัก จึงใช้การเคลื่อนแบบไม่อ้างค่า cvt คือคำสั่ง MDRP
(เหตุที่ใช้จุด 36 แทนที่จะเป็นจุด 1 หรือ จุด 0 ในการอ้างอิงสำหรับหัวอักษร ค.ควาย เนื่องจากความสวยงามในการวาดหัวอักษร จะขึ้นกับโค้งด้านบนมากว่าฐานด้านล่าง)
mdrp[rp0,rnd,grey]  17
กำหนดขนาดหัวโดยใช้เทคนิกเหมือนเดิม คือกำหนดความหนาด้านบน เคลื่อนไปกำหนดขนาดรวม และกำหนดความหนาด้านล่าง
mirp[min,rnd,grey]  headstem  46
mirp[rp0,rnd,grey]  headdia  23
mirp[min,rnd,grey]  headstem  40
ย้ายมาที่จุด 29 เพื่อมากำหนดความหนาตรงจุดนั้น
srp0                29
mdrp[min,rnd,grey]  11
เกลี่ยจุดสำคัญ โดยให้ท้องตัวอักษรเป็นจุดเริ่มต้น คือจุด 5 และความหนาของฐานเป็นจุดสิ้นสุด คือจุด 11
srp1                5
srp2                11
sloop               4
ip                  33 8 2 39
เกลี่ยจุดที่เหลือ
IUP[y]
เสร็จแล้ว
แต่ถึงแม้จะป้องกันอย่างไร รูปร่างฟอนต์แบบตัว ค.ควาย จะยังมีปัญหาในตอนทดจุดอยู่ดี
คือที่ขนาด 17 ppem หัวอักษรจะชิดติดกับแกนขวาของอักษร
(สรุปเป็นปอยต์ตามความละเอียดของจอภาพคือ 72dpi=17pt, 85dpi=14.5pt, 96dpi=13pt)
ดูจากเมนู View -> Grid Fit -> Show Grid Fit -> 17pt 72dpi ได้ภาพดังนี้
17ppem gridfit แก้ด้วยการขยับจุดจำนวน 8 จุด ในแนวแกน X คือ
จุด 2 ขยับไปทางขวา 8px คือ +8px ที่ 17ppem สูตรคือ
+8px ค่าในตาราง delta hint คือ 15
No.of step -8 -7 -6 -5 -4 -3 -2 -1 +1 +2 +3 +4 +5 +6 +7 +8
Selector 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
17ppem นำมาลบด้วย 9 (ลบด้วย 9 เสมอ) คือ 6
ค่า 6 จะเป็น 4 บิตบน และค่า 15 คือ 4 บิตล่าง รวมกันคือ 6*16+15=143
จะได้คู่ของการทำ delta hint คือ จุด 2 และค่าผลรวม 143 เขียนสรุปย่อดังนี้
ที่ 17 ppem
จุด 2 ขยับ +8 = 2 143
จุด 39 ขยับ +8 = 39 143
จุด 0 ขยับ +8 = 0 143
จุด 1 ขยับ +8 = 1 143
จุด 3 ขยับ +6 = 1 141
จุด 38 ขยับ +6 = 38 141
จุด 37 ขยับ +4 = 37 139
จุด 4 ขยับ +4 = 4 139
นับรวมได้ 8 คู่ จะได้โค๊ดจำลองดังนี้ กำหนดการทำงานในแกน X
SVTCA[x-axis]
ใส่ delta hint โดยใส่จำนวนคู่ไว้เป็นค่าแรก
deltap1             8 2 143 39 143 0 143 1 143 3 141 38 141 37 139 4 139
ภาพหลังการขยับแล้วจะป็นดังนี้
17ppem after gridfit เสร็จแล้ว
เมื่อนำมาถอดด้วย dfont.py จะได้โค๊ด instruction ดังนี้
PUSHB_1
 49
PUSHW_1
 259
PUSHB_1
 43
PUSHW_1
 258
NPUSHB
 22
 20
 14
 33
 25
 14
 11
 29
 8
 30
 27
 11
 8
 1
 8
 53
 39
 71
 8
 8
 33
 11
 52
SRP0
MIRP[rp0,min,rnd,grey]
MIRP[min,rnd,grey]
MIRP[rp0,rnd,grey]
MDRP[rnd,white]
MIRP[min,rnd,grey]
SRP0
MDRP[rp0,rnd,white]
MDRP[min,rnd,grey]
MDRP[rp0,rnd,grey]
MIRP[min,rnd,grey]
SRP0
MDRP[rp0,rnd,white]
MDRP[min,rnd,grey]
SRP0
MIRP[rp0,rnd,grey]
MIRP[min,rnd,grey]
MIRP[rnd,grey]
IUP[x]
NPUSHB
 10
 39
 2
 8
 33
 4
 11
 5
 11
 29
 40
PUSHW_1
 258
PUSHB_1
 23
PUSHW_1
 257
PUSHB_1
 46
PUSHW_1
 258
PUSHB_7
 17
 5
 185
 36
 184
 29
 0
SVTCA[y-axis]
MDAP[rnd]
ALIGNRP
MIRP[rp0,rnd,grey]
MIRP[min,rnd,grey]
MDRP[rp0,rnd,grey]
MIRP[min,rnd,grey]
MIRP[rp0,rnd,grey]
MIRP[min,rnd,grey]
SRP0
MDRP[min,rnd,grey]
SRP1
SRP2
SLOOP
IP
IUP[y]
NPUSHB
 17
 139
 4
 139
 37
 141
 38
 141
 3
 143
 1
 143
 0
 143
 39
 143
 2
 8
SVTCA[x-axis]
DELTAP1
Topic: