PDFium Component ให้แอปพลิเคชัน Delphi ตัดสินใจได้ว่าจะใช้ไบต์ฟอนต์ใดเมื่อ PDF อ้างอิงถึงฟอนต์ที่มันไม่ได้ฝังไว้ ConfigureSystemFontProvider ติดตั้งการนำไปใช้งานจริงของ IPdfSystemFontProvider ซึ่งรับคำขอแม็ปฟอนต์ทุกคำขอที่ PDFium สร้างขึ้น ครบถ้วนพร้อมชื่อฟอนต์ น้ำหนัก แฟล็กตัวเอียง ชุดอักขระ และตระกูลระยะพิมพ์ และตอบกลับด้วยไบต์ TrueType, TrueType Collection หรือ OpenType ที่จะใช้
สิ่งนี้มีอยู่เพราะฟอนต์ที่ไม่ได้ฝังไว้คือลอตเตอรีของการเรนเดอร์ PDF ที่ระบุชื่อ Arial แต่ไม่ฝังอะไรเลยจะเรนเดอร์ด้วย Arial บนเวิร์กสเตชัน ด้วยตัวแทนที่เข้ากันได้ด้านเมตริกบนเซิร์ฟเวอร์ Linux และด้วยอะไรก็ตามที่ตัวแม็ปของโฮสต์พบบนอิมเมจคอนเทนเนอร์ที่ถูกล็อกไว้ ใบแจ้งหนี้เดียวกันมีหน้าตาต่างกันในแต่ละที่ การขึ้นบรรทัดใหม่ขยับ และลูกค้าได้รับเอกสารที่ไม่ตรงกับสำเนาที่เก็บถาวรไว้
ทำไมไม่ติดตั้งฟอนต์บนเซิร์ฟเวอร์ไปเลย
บางครั้งนั่นคือคำตอบ และเมื่อเป็นเช่นนั้น ก็ให้ทำแบบนั้น แต่มันล้มเหลวในสามสถานการณ์ทั่วไป การอนุญาตสิทธิ์อาจห้ามการติดตั้งฟอนต์บนเซิร์ฟเวอร์เพื่อการเรนเดอร์อัตโนมัติ อิมเมจคอนเทนเนอร์ถูกสร้างขึ้นใหม่บ่อยครั้ง และฟอนต์ที่ติดตั้งด้วยมือจะหายไปเมื่อมีการปรับใช้ครั้งถัดไป และเวิร์กโฟลว์ที่อยู่ภายใต้กฎระเบียบต้องการให้สแต็กการเรนเดอร์สร้างซ้ำได้จากสิ่งประดิษฐ์ภายใต้การควบคุมเวอร์ชัน ซึ่งการติดตั้งฟอนต์ระดับเครื่องทั้งเครื่องไม่ใช่เช่นนั้น
ผู้ให้บริการแก้ปัญหาทั้งสามข้อด้วยการย้ายการตัดสินใจเข้าไปในแอปพลิเคชันของคุณ ฟอนต์มาในรูปทรัพยากรที่คุณควบคุมได้ นโยบายการแม็ปเป็นโค้ดที่คุณตรวจทานได้ และไบนารีเดียวกันเรนเดอร์เหมือนกันทุกที่ เพราะไม่มีอะไรขึ้นอยู่กับสิ่งที่บังเอิญติดตั้งไว้
การติดตั้งผู้ให้บริการ
การกำหนดค่าต้องเกิดขึ้นก่อนที่ไลบรารีจะถูกโหลด PDFium รับโครงสร้างข้อมูลฟอนต์ระบบตอนเริ่มต้น และเก็บแฮนเดิลที่มันแจกออกไปในภายหลัง ดังนั้นการสลับผู้ให้บริการขณะที่เอกสารเปิดอยู่จะทำให้แฮนเดิลฟอนต์ที่ PDFium ยังถืออยู่ใช้ไม่ได้ คอมโพเนนต์จึงปฏิเสธสิ่งนี้โดยสิ้นเชิง แทนที่จะปล่อยให้มันทำลายการเรนเดอร์:
uses
PDFium;
type
TAppFontProvider = class(TInterfacedObject, IPdfSystemFontProvider)
public
function ResolveFont(const Request: TPdfSystemFontRequest;
out Font: TPdfSystemFontData): Boolean;
end;
function TAppFontProvider.ResolveFont(const Request: TPdfSystemFontRequest;
out Font: TPdfSystemFontData): Boolean;
var
Path: string;
begin
// การแม็ปที่กำหนดผลลัพธ์แน่นอน: ชื่อฟอนต์บวกกับน้ำหนักและตัวเอียงตัดสินใจ
// ว่าจะส่งไฟล์ใดสำหรับคำขอนี้
Path := MapFaceToBundledFile(Request.FaceName, Request.Weight,
Request.Italic, Request.Charset);
Result := Path <> '';
if not Result then
Exit;
Font.FaceName := Request.FaceName;
Font.FontData := LoadFileBytes(Path); // ไบต์ sfnt หรือ TTC แบบครบชุด
Font.Charset := Request.Charset;
Font.TTCIndex := 0; // ดัชนีภายในคอลเลกชัน
end;
var
Policy: TPdfSystemFontPolicy;
begin
Policy := TPdfSystemFontPolicy.Default;
Policy.AllowDefaultFallback := False; // โฮสต์เป็นผู้ตัดสินใจทุกอย่าง
Policy.AllowFaceSubstitution := False; // ปฏิเสธชื่อฟอนต์ที่ต่างออกไป
Policy.MaxFontBytes := 32 * 1024 * 1024;
Policy.MaxCacheEntries := 64;
ConfigureSystemFontProvider(TAppFontProvider.Create, Policy);
// เพิ่งจะโหลดไลบรารีและเปิดเอกสารได้ตอนนี้เท่านั้น
end;
การรื้อถอนทำงานในลำดับตรงกันข้าม ผู้ให้บริการจะถูกถอดออกจาก PDFium ก่อน แล้วจึงยกเลิกการโหลดไลบรารี การข้ามขั้นตอนถอดออกจะทิ้งแฮนเดิลฟอนต์ดั้งเดิมให้ชี้ไปยังอ็อบเจกต์ Pascal ที่กำลังจะถูกปลดปล่อย ซึ่งเป็น access violation ตอนปิดระบบแบบคลาสสิกในโค้ดที่ผสมอินเทอร์เฟซแบบนับการอ้างอิงเข้ากับไลบรารี C
แฟล็กนโยบายตัดสินใจอะไรจริงๆ
AllowDefaultFallback คือสวิตช์ระหว่างสองโหมดการทำงาน เมื่อปิดไว้ คำขอที่ผู้ให้บริการปฏิเสธจะล้มเหลวเฉยๆ ซึ่งเป็นสิ่งที่คุณต้องการในขณะพิสูจน์ว่าทุกฟอนต์ในชุดข้อมูลถูกครอบคลุมไว้ ช่องว่างใดก็ตามจะปรากฏชัดทันทีแทนที่จะถูกปิดบังไว้ เมื่อเปิดไว้ คำขอที่ยังไม่คลี่คลายจะถูกมอบหมายให้ตัวแม็ปที่คืนค่าโดย FPDF_GetDefaultSystemFontInfo ในขณะที่โลกภายนอกยังคงเห็นตัวห่อหุ้มแฮนเดิลแบบเดียวกัน โดยชื่อฟอนต์ ชุดอักขระ ข้อมูลตาราง และการลบฟอนต์ถูกส่งต่ออย่างถูกต้องตามแหล่งกำเนิด
AllowFaceSubstitution ควบคุมว่าผู้ให้บริการสามารถตอบกลับด้วยชื่อฟอนต์ที่ต่างจากที่ร้องขอได้หรือไม่ การปิดไว้ทำให้การแทนที่เป็นการตัดสินใจที่ชัดเจนแทนที่จะเป็นอุบัติเหตุ ซึ่งสำคัญเมื่อเอกสารระบุชื่อฟอนต์ที่เมตริกต่างกันมากพอที่จะเปลี่ยนการแบ่งหน้า
คอมโพเนนต์ตรวจสอบความถูกต้องของทุกคำตอบจากผู้ให้บริการก่อนที่จะไปถึง PDFium ข้อมูลว่างเปล่าจะถูกปฏิเสธ ฟอนต์ที่ใหญ่เกินไปจะถูกปฏิเสธเทียบกับ MaxFontBytes ดัชนี TTC ถูกตรวจสอบ และตาราง sfnt แต่ละตัวถูกให้บริการจากไดเรกทอรีฟอนต์เมื่อ PDFium ขอตารางแทนที่จะขอทั้งไฟล์ ความสามารถสุดท้ายนี้หมายความว่าผู้ให้บริการสามารถส่งมอบไฟล์ฟอนต์ที่สมบูรณ์และให้คอมโพเนนต์ตอบคำถามระดับตารางแทนได้ แทนที่จะเปิดเผยอ็อบเจกต์ Pascal ดิบข้าม C ABI
การแคชโดยไม่มีข้อมูลฟอนต์ค้างอยู่
คำขอแม็ปฟอนต์เกิดซ้ำอยู่ตลอดเวลาระหว่างการเรนเดอร์ ดังนั้นคำตอบจึงถูกแคชไว้ด้วยคีย์ที่ครอบคลุมพารามิเตอร์การเลือกฟอนต์ทุกตัว และถูกขับออกด้วยลำดับ least-recently-used ที่มีขอบเขตจำกัด ความละเอียดอ่อนอยู่ที่อายุการใช้งาน PDFium อาจยังคงอ่านไบต์ของฟอนต์ที่รายการแคชของมันเพิ่งถูกขับออกไป
แคชเก็บอาร์เรย์ไดนามิกแบบนับการอ้างอิง และแฮนเดิลดั้งเดิมแต่ละตัวถือสแนปช็อตของตัวเอง ดังนั้นการขับออกจึงเป็นการลดการอ้างอิงลง ไม่ใช่การปลดปล่อยหน่วยความจำที่กำลังใช้งานอยู่ คอลแบ็กการลบจะปลดปล่อยแฮนเดิลและรักษาจำนวนที่ใช้งานอยู่ไว้ ในทางปฏิบัติ นั่นหมายความว่า MaxCacheEntries สามารถปรับได้เพื่อหน่วยความจำโดยไม่มีความเสี่ยงใดๆ ที่จะดึงข้อมูลออกจากใต้การเรนเดอร์ที่กำลังทำงานอยู่
ผู้ให้บริการถูกเรียกบนเธรดของฉันหรือไม่
ไม่จำเป็นเสมอไป PDFium อาจเรียกตัวแม็ปจากเวิร์กเกอร์เธรดของตัวเอง ดังนั้นการนำไปใช้งานจริงต้องปลอดภัยต่อเธรด ตัวนับที่ใช้ร่วมกัน แคช และการสังเกตการกำหนดค่า ต่างได้รับการป้องกันภายในคอมโพเนนต์ด้วย critical section ของตัวเอง แต่โค้ดภายใน ResolveFont เป็นหน้าที่ของคุณที่จะทำให้ปลอดภัย
รูปแบบที่ปลอดภัยที่สุดคือผู้ให้บริการที่ไม่แตะต้องสถานะที่ใช้ร่วมกันซึ่งเปลี่ยนแปลงได้เลย อ่านจากตารางที่สร้างไว้ตอนเริ่มต้น โหลดไบต์จากไฟล์หรือทรัพยากร แล้วคืนค่า หากการค้นหาต้องใช้แคชที่ใช้ร่วมกันของคุณเอง ให้ป้องกันมันไว้ และเก็บข้อยกเว้นไว้ภายในการนำไปใช้งานจริงของคุณ เพราะข้อยกเว้นของ Pascal ต้องไม่มีวันคลี่กลับผ่านสแตกของ PDFium คอมโพเนนต์ดักจับที่ขอบเขต C ABI และแปลงเป็นความล้มเหลวหรือค่าสำรองเริ่มต้นที่เป็นตัวเลือก แต่การพึ่งพาสิ่งนั้นเป็นลำดับควบคุมปกติมีต้นทุนด้านประสิทธิภาพและซ่อนบั๊ก กฎการทำงานแบบเธรดสำหรับส่วนที่เหลือของคอมโพเนนต์เป็นไปตามหลักการเดียวกับที่อยู่ในวินัยการล็อกการเรนเดอร์
การพิสูจน์การแม็ปในระบบใช้งานจริง
สถิติเปลี่ยนการแทนที่ฟอนต์จากการเดาให้กลายเป็นสิ่งที่คุณสามารถยืนยันได้ GetSystemFontProviderStatistics รายงานว่ามีการกำหนดค่าและติดตั้งผู้ให้บริการหรือไม่ มีคำขอแม็ปกี่ครั้ง และคำขอเหล่านั้นได้รับการตอบสนองอย่างไร แบ่งเป็นการชนแคช การชนของผู้ให้บริการ และการชนค่าสำรองเริ่มต้น พร้อมกับคำตอบที่ถูกปฏิเสธ คำขอที่ล้มเหลว แฮนเดิลที่ยังใช้งานอยู่ และฟอนต์ที่แคชไว้:
var
Stats: TPdfSystemFontStatistics;
begin
Stats := GetSystemFontProviderStatistics;
Writeln(Format('requests=%d cache=%d provider=%d fallback=%d',
[Stats.MapRequests, Stats.CacheHits, Stats.ProviderHits,
Stats.DefaultFallbackHits]));
Writeln(Format('rejected=%d failed=%d handles=%d cached=%d',
[Stats.RejectedProviderResponses, Stats.FailedRequests,
Stats.ActiveHandles, Stats.CachedFonts]));
// In a conformance run with fallback disabled, any fallback hit or
// failed request means a document referenced a font we do not ship
if (Stats.DefaultFallbackHits > 0) or (Stats.FailedRequests > 0) then
raise Exception.Create('unmapped font encountered - update the font set');
end;
จำนวน RejectedProviderResponses ที่เพิ่มขึ้นเป็นสัญญาณว่าผู้ให้บริการกำลังตอบด้วยข้อมูลที่นโยบายปฏิเสธ ปกติแล้วคือไฟล์ที่ใหญ่เกินไปหรือฟอนต์ที่ถูกแทนที่ และควรตั้งการแจ้งเตือนไว้ เพราะคำขอเหล่านั้นจะเสื่อมสภาพลงไปเป็นค่าสำรองหรือความล้มเหลวอย่างเงียบๆ สำหรับการวินิจฉัยว่าเอกสารต้องการฟอนต์ใดจริงๆ ก่อนที่คุณจะสร้างตารางการแม็ป เส้นทางการตรวจสอบในการวิเคราะห์คุณสมบัติฟอนต์ PDF จะแสดงรายการฟอนต์ที่ฝังไว้และไม่ได้ฝังไว้ต่อเอกสาร
การจัดหาฟอนต์ การเรนเดอร์ และการสกัดข้อความ ใช้อินสแตนซ์ไลบรารีเดียวกันร่วมกันข้าม Delphi, C++Builder และ Lazarus รายละเอียดการปรับใช้อธิบายไว้ที่หน้าคอมโพเนนต์ PDFium สำหรับ Delphi