นักออกแบบเลือกฟอนต์ที่มีอักษร a แบบชั้นเดียวสำหรับส่วนหัว หรือเลขศูนย์ที่มีเส้นขีดขวางสำหรับตาราง หรือชุดอักษรตัวพิมพ์ใหญ่แบบตวัดปลายหรูหราสำหรับหน้าปก สัญลักษณ์เหล่านี้มีอยู่ในฟอนต์อยู่แล้ว พวกมันแค่ไม่ใช่ค่าเริ่มต้น อักษร a เริ่มต้นจะทำการจับคู่จากตัวอักษรผ่านตาราง cmap ไปยังสัญลักษณ์ (glyph) ตัวหนึ่ง และสัญลักษณ์ทางเลือกจะอยู่ในตำแหน่งที่ห่างออกไปไม่กี่รหัสสัญลักษณ์ ซึ่งเข้าถึงได้ผ่านกฎการแทนที่เท่านั้น การแสดงสัญลักษณ์ทางเลือกในไฟล์ PDF หมายถึงการอ่านกฎเหล่านั้นและส่งออกสัญลักษณ์ทดแทนในกระแสเนื้อหา บทความนี้เกี่ยวกับวิธีการอ่านกฎเหล่านั้น ซึ่งเป็นกฎประเภทการแทนที่เดี่ยว (single-substitution) ใน Object Pascal โดยไม่มีคลังการจัดรูปร่างที่เป็นเนทีฟอยู่เบื้องหลัง
ขอบเขตการทำงานถูกกำหนดให้แคบลงโดยตั้งใจ ชุดสไตล์และสัญลักษณ์ทางเลือกเป็นฟังก์ชันการแทนที่แบบหนึ่งสัญลักษณ์เข้าและหนึ่งสัญลักษณ์ออกเท่านั้น พวกมันเป็นส่วนหนึ่งของโครงสร้าง OpenType ที่คุณสามารถจัดการได้ด้วยการตรวจสอบตารางสั้นๆ ที่แน่นอน ซึ่งทำให้เหมาะสำหรับเอ็นจิน Pascal ที่ไม่ต้องการพึ่งพาไลบรารีภาษา C ภายนอก
ทำไมต้องเป็น Delphi แท้แทนที่จะใช้ HarfBuzz
HarfBuzz เป็นคำตอบที่ชัดเจนสำหรับคำสั่ง "จัดรูปร่างข้อความนี้" และสำหรับข้อความแบบสองทิศทาง ภาษาตระกูลอินเดีย หรือภาษาอาหรับ มันคือคำตอบที่ถูกต้อง แต่มันก็คือไลบรารีภาษา C การเชื่อมโยงมันเข้ากับผลิตภัณฑ์ Delphi หรือ C++Builder หมายความว่าคุณต้องจัดส่งวัตถุเนทีฟสำหรับแต่ละแพลตฟอร์มปลายทางและสถาปัตยกรรม ต้องจัดการรูปแบบการเรียกใช้ ติดตามรอบการอัปเดต และอ่านเงื่อนไขใบอนุญาตเพื่อความถูกต้อง ไม่มีส่วนใดที่ทำยากเมื่อพิจารณาแยกต่างหาก แต่มันสร้างภาระงานที่ต้องคอยดูแลจัดการตลอดเวลา และแทบไม่มีประโยชน์เลยหากความต้องการใช้งานจริงของคุณคือเพียงแค่ "ขออักษรในรูปแบบ ss01 ของตัวอักษรนี้ให้ฉัน"
การแทนที่เดี่ยวไม่จำเป็นต้องใช้เอ็นจินการจัดรูปร่างอักษร มันต้องการเพียงตัววิเคราะห์ไวยากรณ์ (parser) สำหรับรูปแบบตารางย่อย GSUB และการค้นหาแบบทวิภาค (binary search) หนึ่งหรือสองครั้ง การเขียนสิ่งนี้ใน Pascal ช่วยให้กระบวนการสร้างโค้ดทั้งหมดอยู่ภายในตัวรวบรวมตัวเดียว ข้อจำกัดที่แท้จริงคือวิธีการนี้จะจัดการเฉพาะการค้นหาเพื่อแทนที่สัญลักษณ์อักษรเท่านั้นและไม่ได้ทำอย่างอื่น มันไม่ใช่การแยกแยะข้อความสองทิศทาง ไม่ใช่การจัดเรียงลำดับภาษาอินเดียใหม่ และไม่ใช่การจัดรูปร่างตามบริบทโดยอัตโนมัติ เมื่อสิ่งเหล่านั้นมีความจำเป็นต้องใช้งาน การดำเนินการสืบค้นข้อมูลแบบแทนที่เดี่ยวจะไม่สามารถนำมาใช้ทำงานแทนได้
ลำดับชั้นของ GSUB จากบนลงล่าง
ตารางการแทนที่สัญลักษณ์อักษร (Glyph Substitution table) จัดระเบียบในลักษณะของการอ้างอิงเป็นทอดๆ และการสืบค้นการแทนที่จะไล่ตามลำดับจากบนสุด ที่จุดสูงสุดคือ ScriptList แท็กสคริปต์เช่น latn จะเลือกรายการหนึ่ง และแท็กพิเศษ DFLT คือสคริปต์เริ่มต้นที่จะนำมาใช้เมื่อไม่มีสคริปต์เฉพาะเจาะจงอื่นๆ ที่ตรงกัน รายการสคริปต์จะชี้ไปยัง LangSys ซึ่งเป็นระบบภาษา โดยมี LangSys เริ่มต้นสำหรับกรณีทั่วไป และมีตัวเลือกที่ตั้งชื่อไว้เฉพาะสำหรับภาษาที่ต้องการพฤติกรรมที่แตกต่างกัน ภาษาตุรกีเป็นตัวอย่างทั่วไปที่อักษร i แบบมีจุดและไม่มีจุดต้องการการจัดการเป็นพิเศษในแบบของตนเอง
LangSys จะระบุชุดของดัชนีฟีเจอร์ แต่ละดัชนีจะชี้เข้าไปใน FeatureList ซึ่งเรกคอร์ดฟีเจอร์จะมีแท็กขนาดสี่ไบต์ เช่น แท็ก ss01 และรายการของดัชนีการค้นหา ดัชนีเหล่านั้นจะชี้ไปยัง LookupList ซึ่งเป็นจุดที่ตารางย่อยการแทนที่จริงบันทึกอยู่ ดังนั้นการแก้ไข ss01 หมายถึง: ค้นหาสคริปต์ ค้นหา LangSys ของมัน ค้นหาฟีเจอร์ที่มีแท็กระบุเป็น ss01 รวบรวมการค้นหาที่ระบุชื่อไว้ และนำมาใช้งานจริง HotPDF จะใช้ค่าเริ่มต้นสคริปต์เป็น DFLT และใช้ LangSys เริ่มต้น ซึ่งเป็นค่าที่ฟอนต์อักษรละตินส่วนใหญ่ใช้จัดส่ง และระบบยังเปิดเผยวิธีการแทนที่แท็กสคริปต์เมื่อฟอนต์เชื่อมต่อฟีเจอร์ของตนภายใต้สคริปต์เฉพาะแทน
ตารางขอบเขต (Coverage tables) เป็นตัวตัดสินว่าสัญลักษณ์ใดมีส่วนร่วม
ตารางย่อยการแทนที่ทุกตารางจะเริ่มต้นด้วยคำถามเดียวกัน: สัญลักษณ์อินพุตนี้มีส่วนร่วมในกฎข้อนี้หรือไม่ และหากเป็นเช่นนั้น มันจะอยู่ในตำแหน่งใดในการทำดัชนีของกฎนั้น คำตอบสำหรับคำถามนี้ได้มาจากตารางขอบเขต (Coverage table) และผลลัพธ์ที่ได้คือดัชนีขอบเขต (coverage index) ซึ่งเป็นลำดับขนาดเล็กที่ตารางย่อยที่เหลือจะใช้เพื่อค้นหาว่าสัญลักษณ์นั้นจะเปลี่ยนเป็นอะไร
ตารางขอบเขตมีสองรูปแบบ รูปแบบที่ 1 (Format 1) คือรายการรหัสสัญลักษณ์อักษรที่จัดเรียงลำดับจากน้อยไปมาก คุณค้นหาตำแหน่งของสัญลักษณ์ด้วยการค้นหาแบบทวิภาค และตำแหน่งในรายการจะเป็นดัชนีขอบเขต รูปแบบที่ 2 (Format 2) คือรายการเรกคอร์ดช่วง ซึ่งแต่ละรายการระบุสัญลักษณ์เริ่มต้น สัญลักษณ์สิ้นสุด และดัชนีขอบเขตที่สัญลักษณ์เริ่มต้นจับคู่ไป สัญลักษณ์ที่อยู่ภายในช่วงจะได้ดัชนีขอบเขตโดยการคำนวณออฟเซ็ตจากจุดเริ่มต้นของช่วงนั้น รูปแบบที่ 1 จะมีความกระชับเมื่อสัญลักษณ์ที่เกี่ยวข้องกระจายอยู่ทั่วไป รูปแบบที่ 2 จะมีประสิทธิภาพเมื่อข้อมูลอยู่ในรูปของช่วงที่ต่อเนื่องกัน ทั้งสองรูปแบบถูกจัดเรียงไว้แล้วจึงค้นหาได้ในเวลาลอการิทึม และทั้งคู่จะคืนค่าดัชนีขอบเขตหรือระบุว่า "ไม่อยู่ในขอบเขต" เพื่อให้เอ็นจินปล่อยสัญลักษณ์นั้นไว้ตามเดิม
การแทนที่เดี่ยวและสองรูปแบบโครงสร้าง
การแทนที่เดี่ยวเป็น LookupType 1 และมันจะแมปสัญลักษณ์หนึ่งตัวไปยังสัญลักษณ์ทดแทนตัวเดียวพอดี โครงสร้างนี้มีสองรูปแบบเช่นกัน และการแบ่งรูปแบบทำเพื่อเพิ่มประสิทธิภาพพื้นที่ รูปแบบที่ 1 (Format 1) จะจัดเก็บค่าผลต่างแบบมีเครื่องหมายตัวเดียว รหัสสัญลักษณ์เอาต์พุตคือรหัสสัญลักษณ์อินพุตบวกกับค่าผลต่างนั้น หารเอาเศษด้วย 65536 นี่คือวิธีที่ฟอนต์อักษรเข้ารหัสการแทนที่เมื่อสัญลักษณ์ที่เกี่ยวข้องทั้งหมดมีออฟเซ็ตคงที่เท่ากันจากสัญลักษณ์ทางเลือก เช่น กลุ่มของตัวเลขแบบเส้นตรงที่วางอยู่ในระยะทางคงที่จากตัวเลขแบบดั้งเดิม ตารางขอบเขตระบุว่ามีสัญลักษณ์ใดบ้างที่เข้าเกณฑ์ และค่าผลต่างตัวเดียวดังกล่าวจะทำงานร่วมกับสัญลักษณ์ทั้งหมดเหล่านั้น
รูปแบบที่ 2 (Format 2) จะเก็บอาร์เรย์ของรหัสสัญลักษณ์ทดแทนอย่างชัดเจน ดัชนีขอบเขตจากตารางขอบเขตคือดัชนีเข้าสู่อาร์เรย์นั้น ดังนั้นสัญลักษณ์ที่ดัชนีขอบเขต 0 จะเปลี่ยนเป็นองค์ประกอบอาร์เรย์แรก ดัชนีขอบเขต 1 เป็นตัวที่สอง และตามลำดับ รูปแบบที่ 2 จะนำมาใช้เมื่อสัญลักษณ์ทางเลือกไม่ได้อยู่ในออฟเซ็ตที่เท่ากัน ซึ่งเป็นกรณีทั่วไปสำหรับชุดสไตล์ที่สร้างขึ้นด้วยมือ การสอบถามจะเหมือนกันสำหรับผู้เรียกใช้ทั้งสองรูปแบบ นำสัญลักษณ์อินพุตมาตรวจสอบผ่านตารางขอบเขต และหากอยู่ในขอบเขต ให้ใช้ค่าผลต่างหรืออ่านค่าจากช่องอาร์เรย์นั้น
var
Pdf: THotPDF;
BaseGID, AltGID: Word;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.BeginDoc;
Pdf.RegisterUnicodeTTF('C:\Fonts\MyStylisticFace.ttf');
Pdf.SetFont('My Stylistic Face', 12, []);
// Default glyph for 'a' through the font's cmap.
BaseGID := Pdf.GetUnicodeGlyphForCodepoint(Ord('a'));
// Stylistic Set 1: resolve the alternate via GSUB LookupType 1.
AltGID := Pdf.GetSingleSubstituteGlyph(BaseGID, 'ss01');
// AltGID = BaseGID means the feature did not touch this glyph.
if AltGID <> BaseGID then
{ emit AltGID in the content stream };
finally
Pdf.Free;
end;
end;ข้อตกลงที่น่าสังเกตคือการส่งผ่านค่า GetSingleSubstituteGlyph จะส่งคืนค่ารหัสสัญลักษณ์อินพุตโดยไม่มีการเปลี่ยนแปลงในกรณีที่ไม่พบข้อมูล: ไม่มีฟอนต์ ไม่มีตาราง GSUB ไม่มีฟีเจอร์ที่ตรงกัน หรือไม่อยู่ในขอบเขต นั่นหมายความว่าการเรียกใช้งานจะปลอดภัยในการใช้งานโดยไม่มีเงื่อนไข คุณร้องขอสัญลักษณ์ทางเลือก และหากไม่มีการระบุไว้ คุณจะได้สิ่งเดิมที่คุณส่งเข้าไปกลับมา โค้ดที่เรียกใช้งานจึงไม่จำเป็นต้องมีกรณีพิเศษเพื่อรองรับฟอนต์อักษรที่ไม่มีฟีเจอร์นั้น
ความหมายของแท็กฟีเจอร์สไตล์อักษร
แท็กฟีเจอร์คือคำศัพท์ทั้งหมดที่คุณร้องขอตัวเลือกทางเลือก และแท็กที่เกี่ยวข้องกับสไตล์อักษรมีจำนวนสั้นๆ คู่หลักคือ salt ซึ่งเป็นสัญลักษณ์ทางเลือกทั่วไปที่เข้าถึงสัญลักษณ์ทางเลือกของอักษรได้ทั้งหมด และแท็ก ss01 ถึง ss20 ซึ่งเป็นชุดรูปแบบสไตล์ยี่สิบชุดที่ฟอนต์สามารถกำหนดได้ โดยแต่ละชุดคือชุดการแทนที่ที่มีชื่อซึ่งนักออกแบบจัดกลุ่มไว้ด้วยกัน ตัวอย่างเช่น ฟอนต์อาจใส่อักษร a แบบชั้นเดียวและตัวอักษร R ขาตรงไว้ภายใต้ ss03 ดังนั้นการเปิดใช้งานชุดรูปแบบนั้นเพียงชุดเดียวจะเปลี่ยนสไตล์อักษรทั้งสองตัวได้พร้อมกัน
นอกจากนี้ยังมีแท็กการแทนที่เดี่ยวอื่นๆ ที่อยู่รอบข้าง เช่น aalt ซึ่งเป็นสิทธิ์เข้าถึงสัญลักษณ์ทางเลือกทั้งหมด โดยรวมตัวเลือกสัญลักษณ์ทางเลือกทั้งหมดที่สัญลักษณ์นั้นมี ซึ่งมักแสดงเป็นฟีเจอร์พาเลทสัญลักษณ์ titl เลือกอักษรตัวพิมพ์ใหญ่สำหรับหัวข้อเรื่องที่เหมาะสำหรับขนาดใหญ่ subs และ sups จะสลับเป็นตัวห้อยและตัวยกจริงแทนที่จะเป็นการย่อขนาดฟอนต์เริ่มต้น ordn ผลิตรูปแบบตัวเลขลำดับที่ เช่น อักษรตัวยกใน 1st และ 2nd แท็ก frac สร้างเศษส่วน แม้ว่าเศษส่วนทแยงมุมเต็มรูปแบบจะพึ่งพาตรรกะของอักษรควบและบริบทที่มากกว่าการแทนที่เดี่ยวทั่วไป สำหรับกรณีสัญลักษณ์เดียว กลไกการทำงานจะเหมือนกับ ss01 ทุกประการ: ส่งแท็กไปยังการสอบถามการแทนที่และอ่านค่าสัญลักษณ์ทางเลือกกลับมา
// Try a stylistic-set feature, then fall back to plain alternates.
function ResolveAlternate(Pdf: THotPDF; BaseGID: Word;
const PreferredTag: AnsiString): Word;
begin
Result := Pdf.GetSingleSubstituteGlyph(BaseGID, PreferredTag);
if Result = BaseGID then
Result := Pdf.GetSingleSubstituteGlyph(BaseGID, 'salt');
// Still BaseGID if neither feature covers this glyph.
end;cmap รูปแบบ 12 และระนาบเสริม
ก่อนที่การแทนที่จะทำงานได้ อักขระจะต้องถูกแปลงเป็นสัญลักษณ์ก่อน ซึ่งนั่นคือหน้าที่ของตาราง cmap การสืบค้นการแทนที่จะเริ่มต้นจากรหัสสัญลักษณ์อักษร ดังนั้นเส้นทางจึงเป็นการแปลงอักขระเป็นสัญลักษณ์ผ่าน cmap แล้วจึงแปลงสัญลักษณ์เป็นทางเลือกผ่าน GSUB ส่วนที่น่าสนใจของ cmap คือขอบเขตการทำงาน ตารางย่อยรูปแบบที่ 4 ครอบคลุมระนาบพหุภาษาพื้นฐาน (Basic Multilingual Plane) หรือจุดรหัส 65536 จุดแรก ซึ่งเพียงพอสำหรับข้อความละตินส่วนใหญ่ แต่มันไม่เพียงพอสำหรับจุดรหัสตั้งแต่ U+10000 ขึ้นไป ซึ่งเป็นระนาบเสริม (supplementary planes) ที่เป็นจุดบันทึกของอักขระเลขคณิตคณิตศาสตร์ สัญลักษณ์ต่างๆ และสคริปต์ภาษาที่ใช้งานอยู่ในปัจจุบัน
รูปแบบที่ 12 คือตารางย่อยที่ครอบคลุมช่วง U+0000 ถึง U+10FFFF ทั้งหมด มันคือรายการกลุ่มที่จัดเรียงลำดับ โดยแต่ละกลุ่มประกอบด้วยจุดรหัสเริ่มต้น จุดรหัสสิ้นสุด และรหัสสัญลักษณ์เริ่มต้น ดังนั้นจุดรหัสที่เรียงต่อเนื่องกันจะแมปไปยังกลุ่มของสัญลักษณ์ที่เรียงต่อเนื่องกัน HotPDF จัดการจุดรหัสด้วยกลยุทธ์แบบผสมผสานที่สอดคล้องกับโครงสร้างของข้อมูล จุดรหัสใน BMP จะประมวลผลผ่านอาร์เรย์โดยตรงซึ่งจัดทำดัชนีโดยจุดรหัส เป็นการค้นหาครั้งเดียวโดยไม่ต้องค้นหาข้อมูลซ้ำ ส่วนจุดรหัสในระนาบเสริมจะประมวลผลผ่านตารางแบบเบาบางที่จัดเรียงตามจุดรหัสและค้นหาด้วยการค้นหาแบบทวิภาค ผลลัพธ์ที่ได้คือฟังก์ชัน GetUnicodeGlyphForCodepoint จะรับค่า Cardinal และตอบกลับอย่างถูกต้องตลอดช่วง โดยจะส่งคืนค่ารหัสสัญลักษณ์เป็น 0 ซึ่งก็คือสัญลักษณ์ .notdef สำหรับจุดรหัสใดๆ ที่ฟอนต์ไม่ได้ทำการจับคู่ไว้
var
Pdf: THotPDF;
Cp: Cardinal;
GID, StyledGID: Word;
begin
// A supplementary-plane code point: U+1D49C MATHEMATICAL SCRIPT CAPITAL A.
Cp := $1D49C;
GID := Pdf.GetUnicodeGlyphForCodepoint(Cp); // format 12 lookup
if GID <> 0 then
StyledGID := Pdf.GetSingleSubstituteGlyph(GID, 'ss01')
else
StyledGID := 0; // font has no glyph for this code point
end;ขอบเขตที่การสอบถามเหล่านี้สิ้นสุดลง
API การแทนที่เดี่ยวจะตอบคำถามรูปทรงแบบเดียว และสิ่งสำคัญคือต้องระบุให้ชัดเจนว่าสิ่งใดที่พวกมันไม่สามารถตอบได้ LookupType 1 เป็นหนึ่งในประเภทการแทนที่แปดประเภท การสืบค้นจะไม่รองรับการแทนที่หลายตัวของ LookupType 2 ซึ่งสัญลักษณ์ตัวหนึ่งจะเปลี่ยนเป็นหลายตัว และไม่รองรับการแทนที่แบบเชื่อมตัวอักษรของ LookupType 4 ซึ่งสัญลักษณ์หลายตัวจะรวมเป็นตัวเดียว นอกจากนี้ยังไม่จัดการกับประเภทตามบริบทและตามบริบทแบบลูกโซ่ (LookupType 5 และ 6) ที่จะทำงานเมื่อสัญลักษณ์ปรากฏในตำแหน่งเฉพาะข้างเคียงเท่านั้น รวมถึงประเภทส่วนขยายและประเภทการเชื่องย้อนกลับ เศษส่วนทแยงมุม ตัวอักษรรวมของภาษาเทวนาครี หรือโครงสร้างต้น-กลาง-ท้ายของภาษาอาหรับ เป็นปัญหาด้านลำดับขั้นตอนการจัดวาง และการค้นหาการแทนที่เดี่ยวแบบรายสัญลักษณ์ไม่สามารถนำเสนอการทำงานนี้ได้
นอกจากนี้ยังไม่ได้ทำการจัดรูปร่างอักษรโดยอัตโนมัติ ไม่มีส่วนใดที่นี่ที่ทำหน้าที่ตรวจสอบข้อความ ตัดสินใจว่าจะเปิดใช้งานฟีเจอร์ใด และนำมาใช้ในลำดับที่สคริปต์ต้องการ ผู้เรียกใช้จะเป็นผู้เลือกแท็กฟีเจอร์และนำมาใช้กับสัญลักษณ์ทีละตัว นั่นคือเครื่องมือที่ถูกต้องสำหรับชุดสไตล์และตัวเลือกทางเลือกซึ่งเป็นแบบเลือกใช้เฉพาะจุด และเป็นเครื่องมือที่ไม่ถูกต้องสำหรับสคริปต์ที่ต้องการการจัดเรียงลำดับใหม่ การรักษาขอบเขตการทำงานนี้ให้ชัดเจนคือสิ่งที่ช่วยให้เส้นทางการแทนที่มีขนาดเล็กและคาดการณ์ผลลัพธ์ได้ง่าย
สำหรับกรณีที่ต้องทำงานในระดับลำดับขั้นตอน เรื่องราวของสคริปต์ที่ซับซ้อนจะมีอธิบายไว้ในบทความของเราเกี่ยวกับการจัดรูปร่างข้อความสคริปต์ที่ซับซ้อนใน Delphi หากการแทนที่เป็นส่วนหนึ่งของงานรายงานขนาดใหญ่ที่มีการวางรูปภาพและฟอนต์อื่นๆ บนหน้ากระดาษ คู่มือสำหรับเอาต์พุตรายงานด้วยฟอนต์และรูปภาพจะอธิบายการเชื่อมต่อส่วนต่างๆ เข้าด้วยกัน ทั้งหมดนี้รันบนเอ็นจินเดียวกันคือ HotPDF Component สำหรับ Delphi และ C++Builder ซึ่งเก็บการสืบค้นการแทนที่ GSUB ควบคู่ไปกับเครื่องมือฝังฟอนต์ การจัดชุดย่อย และ API ข้อความที่มีอธิบายในส่วนอื่นของบล็อกนี้