บทความเทคนิค

PDFium FPDF_LIBRARY_CONFIG: จังหวะ Brotli สลับ Skia เป็น AGG

ใน PDFium Component สำหรับ Delphi การเปิด BrotliEnabled หรือ IsolatePerDocument ใน TPdfLibraryConfiguration เคยพาบิลด์ Skia ที่แถมมาสลับไปเป็นตัวเรนเดอร์ AGG โดยไม่มี error สักคำ เพราะทั้งสองตัวเลือกยก FPDF_LIBRARY_CONFIG ขึ้นไปถึงเวอร์ชันที่ PDFium อ่าน m_RendererType ตรงตามตัวหนังสือ ตั้งแต่ v3.123.0 ตัวเรนเดอร์ค่าเริ่มต้นคงเป็น default ของตัว DLL เอง และตั้งแต่ v3.125.0 การขอ Skia หรือ Fontations ที่ DLL หยิบยื่นไม่ได้จะขึ้น EPdfError ที่จับได้ แทนที่จะฆ่า process ทิ้ง

ทั้งสองบั๊กไม่มีตัวไหนประกาศตัวเอง อันแรกผลิตหน้าที่ดูปกติดี เพียงแต่ถูกเรนเดอร์โดย rasterizer ต่างคนละตัว ขอบลดรอยหยักกับขอบตัวอักษรเพี้ยนนิด ๆ จากบิลด์ที่คุณแถมไปให้ลูกค้าพร้อมเทสต์มาแล้ว อันที่สองประกาศตัวเสียอย่างดังก้อง ด้วยการพา process ของ host ลงทั้งตัวจากข้างใน initialization ระดับ native ทั้งคู่มาจากที่เดียวกัน: โครงสร้าง C ที่มีเวอร์ชันกำกับ ซึ่งฟิลด์จะมีค่าก็ต่อเมื่อเลขเวอร์ชันบอกว่ามีเท่านั้น และค่าศูนย์ในนั้นไม่ได้แปลว่า “ยังไม่ได้ตั้ง” แต่เป็นทางเลือกจริง ๆ หนึ่งทาง

FPDF_LIBRARY_CONFIG ตัดสินว่า PDFium จะใช้ตัวเรนเดอร์ตัวไหนอย่างไร

FPDF_InitLibraryWithConfig จะถามถึง m_RendererType ก็ต่อเมื่อฟิลด์ Version ของโครงสร้างเป็น 4 ขึ้นไป และตั้งแต่เวอร์ชันนั้นเป็นต้นไปมันใช้ค่าตามที่เขียนมาเป๊ะ ๆ ใต้เวอร์ชัน 4 PDFium เมินฟิลด์นี้แล้วหยิบ default ของบิลด์ ซึ่งคือ Skia ในบิลด์ที่คอมไพล์ด้วย PDF_USE_SKIA และเป็น AGG ในทุกกรณีอื่น

ฟิลด์ที่มาทีหลังทุกตัวเดินตามแพทเทิร์นเดียวกัน โครงสร้างโตขึ้นทีละความสามารถ และแต่ละความสามารถมาพร้อมเลขเวอร์ชันใหม่ PDFium Component ประกอบโครงสร้าง native ใน LoadLibrary จาก TPdfLibraryConfiguration ของคุณ แล้วยกเวอร์ชันขึ้นเท่าที่ตัวเลือกที่เซ็ตไว้บังคับเท่านั้น

เวอร์ชันโครงสร้างฟิลด์ที่เพิ่มเซ็ตเมื่อไร
2m_pIsolate, m_v8EmbedderSlotเขียนเสมอ; V8Isolate, V8EmbedderSlot
3m_pPlatformV8Platform ไม่ใช่ nil
4m_RendererTypeRenderer ที่ไม่ใช่ prpDefault
5m_FontLibraryTypeFontBackend ที่ไม่ใช่ pfbpDefault
6m_BrotliEnabledBrotliEnabled = True
7m_IsolatePerDocumentIsolatePerDocument = True

กับดักอยู่ในสองแถวสุดท้าย เวอร์ชันเป็นแบบสะสม: โครงสร้างเวอร์ชัน 6 ก็คือโครงสร้างเวอร์ชัน 4 พร้อมเวอร์ชัน 5 ไปด้วย PDFium จึงอ่าน m_RendererType กับ m_FontLibraryType ทั้งที่คุณขอแค่ Brotli สิ่งที่นั่งอยู่ในสองฟิลด์นั้นขณะนั้นจะกลายเป็นตัวเรนเดอร์กับ font backend ไปเต็ม ๆ ไม่ว่าคุณจะตั้งใจเลือกมันหรือไม่

แผนภาพบันไดเวอร์ชัน FPDF_LIBRARY_CONFIG ของ PDFium Component จากเวอร์ชัน 2 ถึง 7 แสดงว่าตัวเลือก TPdfLibraryConfiguration ตัวไหนเติม m_RendererType, m_FontLibraryType, m_BrotliEnabled กับ m_IsolatePerDocument และเพราะเวอร์ชันเป็นแบบสะสม ฟิลด์ตัวเรนเดอร์ที่ถูกล้างเป็นศูนย์จึงไปถึง PDFium เป็นคำขอ AGG อย่างชัดเจน ไม่ใช่ค่าที่ยังไม่ได้ตั้ง บนบิลด์ใดก็ตาม
แต่ละตัวเลือกยกเวอร์ชันโครงสร้างขึ้นและฟิลด์ก่อนหน้าทุกตัวยังมีชีวิตอยู่ ศูนย์ใน m_RendererType จึงเดินทางไปถึง PDFium ในฐานะคำขอ AGG ที่ชัดเจน

ทำไมเปิด Brotli แล้วตัวเรนเดอร์ถึงสลับไป AGG

ก่อน v3.123.0 PDFium Component เขียน FPDF_RENDERERTYPE_AGG ลง m_RendererType สำหรับ prpDefault การตั้งค่าใดก็ตามที่ดันโครงสร้างขึ้นไปเวอร์ชัน 6 หรือ 7 จึงบังคับ AGG ลงบนบิลด์ Skia ทั้งที่ pdfium.dll กับ pdfium.v8.dll ที่แถมมากับ component เป็นบิลด์ Skia ปัญหาจึงโจมตี deployment ค่าเริ่มต้น ไม่ใช่ deployment แปลก ๆ

การแมปนี้ตอนเขียนดูไม่มีพิษภัย ที่เวอร์ชัน 2 หรือ 3 ฟิลด์นี้ไม่เคยถูกอ่าน prpDefault จึงแปลว่า “DLL จะทำอะไรก็ตามใจ” จริง ๆ แต่วินาทีที่ BrotliEnabled (เวอร์ชัน 6) หรือ IsolatePerDocument (เวอร์ชัน 7) เข้ามาในภาพ โค้ดชุดเดิมเปลี่ยน “ไม่มีความเห็น” ให้กลายเป็นคำขอ AGG อย่างชัดเจน ไม่มีอะไรพัง PDFium initialize ปกติ เรนเดอร์ทุกหน้า และคืน error code กลับมาศูนย์ เพราะจากมุมมองของมัน ผู้เรียกขอ AGG มาเองและก็ได้ AGG ไป

pixel hash ทำให้การสลับนี้มองเห็นในที่ที่ screenshot มองไม่เห็น เรนเดอร์หน้าแรกของเอกสารตัวอย่างเดียวกันใต้สามการตั้งค่าได้แบบนี้:

  • ค่าเริ่มต้น: hash 502D77C3711B4ACF
  • BrotliEnabled = True โดยปล่อย Renderer เป็น prpDefault: hash F75B5EB4728ADE87
  • สั่ง prpAgg ตรง ๆ: hash F75B5EB4728ADE87 ตรงกับรอบที่เปิด Brotli เป๊ะ

การแก้ใน v3.123.0 คือฟังก์ชันสาธารณะ PdfNativeRendererType ซึ่งแปล TPdfRendererPreference เป็นค่าที่จะเขียนลง m_RendererType prpAgg กับ prpSkia แมปตรงตัวต่อตัว prpDefault ตอนนี้แมปไป Skia เมื่อ DLL ที่โหลดแล้ว export FPDF_RenderPageSkia และไป AGG ในกรณีอื่น export นี้ถูกคอมไพล์ภายใต้เงื่อนไข PDF_USE_SKIA เดียวกับค่า default Skia เอง จึงกลายเป็นคุณสมบัติบิลด์ตัวเดียวที่มองเห็นได้จากนอกตัว DLL หลังแก้แล้วการตั้งค่าแบบเปิด Brotli ให้ hash เดียวกับค่าเริ่มต้น

แผนภาพเทียบ pixel hash ของ PDFium Component แสดง hash การเรนเดอร์ Skia ค่าเริ่มต้น 502D77C3711B4ACF, การตั้งค่า BrotliEnabled ก่อน v3.123.0 ที่ให้ผลตรงกับการรัน prpAgg ตรง ๆ ด้วย hash F75B5EB4728ADE87 และ wrapper ที่แก้แล้วแก้ prpDefault ผ่าน export FPDF_RenderPageSkia กลับไปสู่ hash Skia เดิม
pixel hash จับสิ่งที่ screenshot ซ่อนไว้: การเปิด Brotli เคยเรนเดอร์ทุกหน้าด้วย AGG และค่าเริ่มต้นที่แก้แล้วตอนนี้ตรงกับการตั้งค่าที่ไม่แตะต้องอะไร

font backend ไม่เคยมีปัญหาแบบเดียวกัน m_FontLibraryType ถูกอ่านตั้งแต่เวอร์ชัน 5 และค่าศูนย์ของมัน คือ FPDF_FONTBACKENDTYPE_FREETYPE ก็เป็น default ของ PDFium ด้วยเมื่อฟิลด์ไม่ถูกอ่านเลย การเขียน FreeType สำหรับ pfbpDefault จึงทำซ้ำ default ของ native ได้เป๊ะ ค่าศูนย์ไม่ได้ผิดเสมอไป มันแค่ไม่ถูกโดยอัตโนมัติเสมอเท่านั้น

เมื่อมี v3.123.0 ขึ้นไป โค้ดตอนสตาร์ทที่คุณจะเขียนตามธรรมชาติก็ทำตามที่มันพูดจริง ๆ แล้ว:

uses
  PDFium;

procedure ConfigurePdfiumAtStartup;
var
  Config: TPdfLibraryConfiguration;
begin
  // ต้องรันก่อนอะไรก็แล้วแต่จะไปโหลดไลบรารี native
  Config := TPdfLibraryConfiguration.Default;
  Config.BrotliEnabled := True;   // ยก FPDF_LIBRARY_CONFIG ขึ้นเป็นเวอร์ชัน 6
  // Renderer คง prpDefault: บนบิลด์ที่ export จะ resolve เป็น Skia
  // เป็น FPDF_RenderPageSkia และเป็น AGG บนบิลด์ที่มีแต่ AGG
  SetLength(Config.UserFontPaths, 1);
  Config.UserFontPaths[0] := 'C:\ProgramData\MyApp\Fonts';
  ConfigurePdfLibrary(Config);
end;

เตือนตัวเองไว้ว่า BrotliEnabled ทำให้ stream /BrotliDecode ของ PDF 2.0 ถอดรหัสได้ก็ต่อเมื่อตัว DLL เองถูกบิลด์ด้วย PDF_ENABLE_BROTLI flag นี้เป็นเพียงคำขอ บนบิลด์ที่ไม่มีรองรับ Brotli มันไม่มีผลอะไรเลย TPdfLibraryConfiguration.Hardened เหมือน Default ทุกอย่างยกเว้น AllowMachineTime ที่เป็น False ซึ่งกัน JavaScript ของเอกสารไว้ไม่ให้อ่านนาฬิกาจริง เป็นจุดเริ่มต้นที่สมเหตุผลสำหรับการประมวลผลไฟล์ไม่น่าเชื่อถือฝั่งเซิร์ฟเวอร์

เกิดอะไรขึ้นเมื่อคุณขอ backend ที่ DLL ไม่มีอยู่

PDFium ไม่คืน error ให้ตัวเรนเดอร์หรือ font backend ที่บิลด์ไม่มี FPDF_InitLibraryWithConfig จะโจมตี native CHECK ให้พัง ซึ่งบน Windows โผล่มาเป็น breakpoint exception และถ้าไม่มี structured exception handler ล้อม call ไว้ process ก็จบ ส่วนหัวของไฟล์ header ก็บอกไว้แบบนั้นจริง ๆ เตือนว่าค่าที่ไม่รองรับ “will similarly fail with an immediate crash”

สองกรณีที่จับต้องได้คือบิลด์ที่มีแต่ AGG ไปรับ FPDF_RENDERERTYPE_SKIA และบิลด์ที่ไม่มี Fontations ไปรับ FPDF_FONTBACKENDTYPE_FONTATIONS runtime Skia ที่แถมมาอยู่ในกลุ่มที่สอง เรนเดอร์ด้วย Skia แต่ใช้ FreeType กับฟอนต์ การขอ prpSkia คู่กับ pfbpFontations ใส่มันผลิต External exception 80000003 ฝั่ง Delphi มาให้ เมื่อ debugger หรือ exception handler บังเอิญจับมันได้ สถานการณ์ก็ยังฟื้นไม่ได้อยู่ดี:

  • PDFium ถูกทิ้งค้างครึ่ง initialize
  • configuration ระดับ process ถูกผนึกไปแล้ว ConfigurePdfLibrary จึงปฏิเสธ configuration ที่แก้แล้ว
  • การลองใหม่ด้วย configuration อื่นใน process เดิมเป็นไปไม่ได้อีกต่อไป

นี่คือความล้มเหลวหัวตรงข้ามกับบั๊ก Brotli ที่นั่นฟิลด์ถือค่าที่ไม่มีใครเลือกและ PDFium ก็รับมันมาอย่างเงียบ ๆ ที่นี่ฟิลด์ถือค่าที่ผู้เรียกเลือกตั้งใจไว้และ PDFium ก็ไม่ยอมรับการต่อรองอะไรกับมันเลย ทั้งคู่เป็นปัญหาที่ wrapper ต้องเก็บให้จบก่อนคำสั่ง call ระดับ native เพราะพ้นจุดนั้นไปก็ไม่เหลืออะไรให้จับแล้ว

PDFium Component พรีเช็ก Skia กับ Fontations อย่างไร

ตั้งแต่ v3.125.0 LoadLibrary ตรวจ configuration หลังผูก export ของ DLL และก่อนเรียก FPDF_InitLibraryWithConfig แล้วเปลี่ยนตัวเรนเดอร์หรือ font backend ที่ไม่รองรับให้กลายเป็น EPdfError พร้อมข้อความที่เอ่ยชื่อการตั้งค่าที่มีปัญหากับทางเลือกอื่น DLL ถูกยูโหลดและ configuration ถูกเปิดผนึก ผู้เรียกจึงเลือกการตั้งค่าอื่นแล้วโหลดใหม่ได้

การตัดสินเองนั่งอยู่ในฟังก์ชันบริสุทธิ์ PdfLibraryConfigurationSupportError ที่รับ configuration บวก boolean สองตัวที่บรรยายบิลด์ แล้วคืนสตริงว่างเมื่อคุณสมบัติผสมนั้นปลอดภัย เพราะมันไม่แตะ state ระดับ native เลย คุณจึงเรียกมันจากเทสต์ของตัวเองกับคู่ความสามารถแบบไหนก็ได้ ข้างใน LoadLibrary boolean สองตัวนั้นมาจากหลักฐานคนละชนิด และควรได้รับความเชื่อต่างระดับกัน:

  • Skiaตรวจพบจากการมีอยู่ของ export FPDF_RenderPageSkia ซึ่งเป็นสัญญาณเดียวกับที่ PdfNativeRendererType ใช้ export กับตัวเรนเดอร์ Skia ถูกคอมไพล์ใต้เงื่อนไขเดียวกัน เช็กนี้จึงเป๊ะ
  • Fontationsไม่มี export ของตัวเอง ร่องรอยเดียวที่มันทิ้งไว้คือ font crates ภาษา Rust ที่มันดึงเข้าไฟล์ไบนารี PDFium Component จึงสแกนไฟล์ไลบรารีที่โหลดแล้วหาชื่อ crate skrifa กับ read-fonts (รวม read_fonts) การสแกนรันเฉพาะเมื่อถูกขอ pfbpFontations และไฟล์ที่อ่านไม่ได้นับเป็น “ไม่มี Fontations”

การเช็ก Fontations เป็น heuristic และมันผิดได้ฝั่งเดียว บิลด์ Fontations ที่ถูก strip สตริงพวกนั้นจนหมดจะถูกปฏิเสธ ทั้งที่ตัวมันใช้งานได้ การถ่วงน้ำหนักแบบนี้ตั้งใจไว้ การปฏิเสธที่ผิดต้องจ่ายด้วย exception หนึ่งตัวที่จับได้กับการ fallback ไป FreeType การยอมรับที่ผิดต้องจ่ายด้วย process ทั้งตัว

การเปิดผนึกสำคัญพอกับการเช็ก LoadLibrary ผนึก configuration ตั้งแต่ต้นการโหลด ไม่มีการรีเซ็ตก็เลยเกิดเหตุที่การปฏิเสธความสามารถทิ้งให้ ConfigurePdfLibrary ตอบทุกการลองใหม่ด้วย EPdfError “PDFium library configuration is already sealed” เส้นทางการปฏิเสธจึงเรียก UnloadLibrary ก่อน call FPDF_DestroyLibrary ของมันปลอดภัยในจังหวะนั้น เพราะ PDFium ยังไม่ถูก initialize และคืนค่าทันที ความล้มเหลวอื่นของการโหลด เช่น DLL หายไม่เจอหรือสถาปัตยกรรมไม่ตรง คงผนึกไว้ ลูปลองใหม่จึงต้องแยกสองกรณีนี้ออกจากกันให้ได้:

uses
  SysUtils, PDFium;

function StartPdfiumPreferringSkia: TPdfRendererPreference;
var
  Config: TPdfLibraryConfiguration;
begin
  Config := TPdfLibraryConfiguration.Default;
  Config.Renderer := prpSkia;
  ConfigurePdfLibrary(Config);
  try
    PDFium.LoadLibrary;   // ระบุยูนิต: Windows.LoadLibrary ใช้ชื่อเดียวกัน
    Result := prpSkia;
  except
    on E: EPdfError do
    begin
      // การปฏิเสธความสามารถจะยูโหลด DLL แล้วเปิดผนึก configuration
      // DLL ที่โหลดไม่ได้เลยยังผนึกอยู่: ลองซ้ำก็ไม่ช่วย
      if PdfLibraryConfigurationSealed then
        raise;
      Config.Renderer := prpAgg;
      ConfigurePdfLibrary(Config);
      PDFium.LoadLibrary;
      Result := prpAgg;
    end;
  end;
end;

สังเกต PDFium.LoadLibrary ที่ระบุยูนิตชัด ๆ ในยูนิตที่ใช้ Windows หรือ Winapi.Windows ด้วย LoadLibrary ที่ไม่ระบุยูนิตจะ resolve ไปยูนิตที่ปรากฏท้ายสุดใน uses clause ถ้าผู้ชนะคือฟังก์ชัน Win32 call แบบไร้พารามิเตอร์จะคอมไพล์ไม่ผ่านด้วย error เรื่องจำนวนอาร์กิวเมนต์ ซึ่งไม่บอกอะไรเกี่ยวกับ PDFium เลย

แผนภาพ flow พรีเช็กของ LoadLibrary ใน PDFium Component ที่ ConfigurePdfLibrary ผนึก configuration การเช็กความสามารถทดสอบ export FPDF_RenderPageSkia กับหลักฐานสตริง skrifa คำขอที่ไม่รองรับขึ้น EPdfError ที่จับได้แล้วเปิดผนึกให้ลองใหม่ ขณะที่ DLL ที่ไม่เคยโหลดได้คง PdfLibraryConfigurationSealed เป็น true
การตรวจรันหลัง export ถูกผูกและก่อน initialize จึงทำให้ backend ที่หายไปพังเป็น EPdfError ที่คุณจับได้ แทน native CHECK ที่ฆ่า process ทิ้ง

การตรวจที่เกิดเร็วกว่านั้นอีก

ConfigurePdfLibrary ปฏิเสธบางคู่คุณสมบัติก่อนมี DLL เข้ามาเกี่ยวข้องเลย ทั้งหมดขึ้น EPdfError การระบุ FontBackend ชัด ๆ รวมถึง pfbpFreeType บังคับให้ Renderer = prpSkia เพราะ PDFium ถามหา font backend เฉพาะกับตัวเรนเดอร์ Skia IsolatePerDocument บังคับให้ V8Isolate เป็น nil เพราะ PDFium สร้าง isolate ของตัวเองต่อเอกสาร แล้วก็โจมตี native CHECK ให้พังถ้าคุณยังส่งอันอื่นเข้าไปให้ด้วย สตริงว่างใน UserFontPaths ถูกปฏิเสธ และการ call หลังความพยายามโหลดครั้งแรกพังด้วย “PDFium library configuration is already sealed”

กฎสุดท้ายมีผลทางปฏิบัติ: คุณแอบพรีเช็ก DLL ก่อนแล้วค่อยตั้งค่ามันไม่ได้ GetSkiaRenderCapabilities, V8FeaturesAvailable การเปิดเอกสาร และ entry point อื่นส่วนใหญ่เรียก LoadLibrary ภายใน ซึ่งผนึก configuration ตรงนั้นทันที การ call UnloadLibrary ทีหลังก็ไม่ได้เปิดมันกลับมา ตั้งค่าก่อน โหลดต่อ แล้วค่อยถามคำถาม ซึ่งเป๊ะกับลำดับที่ routine วินิจฉัยควรเดิน:

uses
  SysUtils, PDFium, FPdfView;

function DescribePdfiumState: string;
var
  Config: TPdfLibraryConfiguration;
  Renderer: string;
begin
  Config := GetPdfLibraryConfiguration;   // เป็นสำเนา ส่องดูได้ปลอดภัย
  if not PDFium.Loaded then
  begin
    if PdfLibraryConfigurationSealed then
      Exit('PDFium failed to load; configuration is sealed');
    Exit('PDFium not loaded; configuration can still change');
  end;
  // resolution เดียวกับที่ LoadLibrary ใช้ตอนประกอบ FPDF_LIBRARY_CONFIG
  if PdfNativeRendererType(Config.Renderer,
    GetSkiaRenderCapabilities.PageRender) = FPDF_RENDERERTYPE_SKIA then
    Renderer := 'Skia'
  else
    Renderer := 'AGG';
  Result := Format('Renderer=%s Brotli=%s IsolatePerDocument=%s',
    [Renderer, BoolToStr(Config.BrotliEnabled, True),
     BoolToStr(Config.IsolatePerDocument, True)]);
end;

การ log บรรทัดนี้หนึ่งครั้งตอนสตาร์ทแสนถูก และเป็นสิ่งแรกที่คุณอยากเห็นในตั๋วแจ้งปัญหาที่พูดว่า “ข้อความบนเซิร์ฟเวอร์ดูต่างออกไป” PDFium.Loaded ระบุยูนิตด้วยเหตุผลเดียวกับ LoadLibrary: ข้างในเมธอดของฟอร์มหรือ component Loaded เปล่า ๆ จะผูกไปที่ TComponent.Loaded

สองวิธีที่โครงสร้าง C แบบมีเวอร์ชันพังได้

โครงสร้าง configuration แบบมีเวอร์ชันทุกตัว ไม่ว่าจะเป็น FPDF_LIBRARY_CONFIG, record cbSize ของ Win32 หรือ ABI ของ plugin ล้วนพังได้สองแบบสมมาตรกัน และ wrapper ต้องกันทั้งคู่ แบบแรกคือเติมฟิลด์ไปแต่คงเวอร์ชันต่ำเกินไป แบบที่สองคือยกเวอร์ชันไปแต่ทิ้งฟิลด์ไว้ที่ค่าศูนย์ซึ่งไลบรารีอ่านว่าเป็นทางเลือกที่ตั้งใจไว้

  1. เซ็ตฟิลด์แต่เวอร์ชันต่ำเกินไป เขียน m_BrotliEnabled = 1 ลงโครงสร้างเวอร์ชัน 2 แล้ว PDFium ไม่เคยหันมามองเลย call สำเร็จและ stream Brotli ก็ยังถอดรหัสไม่ได้ การป้องกันคือ derive เวอร์ชันจากฟิลด์ที่ใช้จริง ซึ่งเป็นสิ่งที่ LoadLibrary ทำ ไม่ใช่ hard-code เวอร์ชันตายตัว
  2. เวอร์ชันพอแล้วแต่ค่าศูนย์มีความหมาย ยกเวอร์ชันขึ้น 6 แล้วทุกฟิลด์จนถึงเวอร์ชัน 6 กลายเป็นมีชีวิต FillChar ล้าง m_RendererType เป็นศูนย์ซึ่งก็คือ FPDF_RENDERERTYPE_AGG ตัวเรนเดอร์ตัวจริง ไม่ใช่ “ยังไม่ได้ตั้ง” การป้องกันคือเขียนทุกฟิลด์ที่เวอร์ชันที่เลือกคลุมด้วยค่าที่ตั้งใจไว้ และ resolve คำว่า “default” กับบิลด์จริงแทนการสมมติ

กฎข้อที่สามตามมาสำหรับค่าที่พาให้ตัวถูกเรียกพังได้: ตรวจมันกับสิ่งที่ไบนารีทำได้ก่อนคำสั่ง call ด้วยหลักฐานที่แข็งแรงที่สุดเท่าที่มี และพูดความจริงในโค้ดกับเอกสารเมื่อหลักฐานนั้นเป็นแค่ heuristic สัญลักษณ์ที่ export มาคือหลักฐาน ชื่อ crate ใน string table คือการเดาที่ดีเท่านั้น

สรุป: configuration ไลบรารีของ PDFium Component

  • call ConfigurePdfLibrary ครั้งเดียว ก่อนอะไรก็แล้วแต่จะไปโหลด DLL การถามความสามารถใด ๆ หรือการโหลดเอกสารจะผนึกมันทันที
  • อัปเกรดเป็น v3.123.0 ขึ้นไปถ้าคุณเซ็ต BrotliEnabled หรือ IsolatePerDocument แล้วคาดหวัง output Skia จาก runtime ที่แถมมา
  • ปล่อย Renderer เป็น prpDefault เว้นแต่คุณต้องการ rasterizer ตัวใดตัวหนึ่ง ตอนนี้มัน resolve เป็น default ของบิลด์ในทุกเวอร์ชันโครงสร้าง
  • ใช้ PdfNativeRendererType คู่กับ GetSkiaRenderCapabilities.PageRender เพื่อ log ว่าตัวเรนเดอร์ที่ทำงานจริงคือตัวไหน
  • คาดหวัง EPdfError ไม่ใช่ crash สำหรับ prpSkia บน DLL ที่มีแต่ AGG หรือ pfbpFontations บน DLL ที่ไม่มี Fontations ใน v3.125.0 ขึ้นไป
  • หลังการปฏิเสธความสามารถ PdfLibraryConfigurationSealed จะเป็น False และคุณตั้งค่าใหม่ได้ หลังการโหลด DLL พังมันคงเป็น True
  • ถือการตรวจจับ Fontations เป็น heuristic และเตรียม fallback เป็น FreeType ไว้
  • เขียน PDFium.LoadLibrary กับ PDFium.Loaded พร้อมชื่อยูนิต กันชนชื่อกับ Win32 และ TComponent

ถ้า DLL พังก่อนที่ configuration จะมีโอกาสมีความหมายด้วยซ้ำ เริ่มจากการวินิจฉัยความล้มเหลวของการโหลด PDFium DLL ใน Delphi และวิธีที่ component หาไบนารีที่ถูกต้องบนแต่ละแพลตฟอร์ม ดูที่การโหลดไลบรารี native ของ PDFium บนทุกเป้าหมาย พอเรื่องตัวเรนเดอร์ลุล่งแล้ว render cache กับกลยุทธ์ซูมลื่น เล่าวิธีทำให้การเรนเดอร์หน้ายังไวใน viewer

PDFium Component ห่อเอนจิน PDFium ไว้ให้ Delphi กับ C++Builder พร้อมการเช็ก configuration อย่างที่เล่าไว้ในนี้ การ initialize ระดับ native จึงพังเป็น exception ของ Pascal ที่จับจัดการได้ ไม่ใช่การจบ process รายละเอียดผลิตภัณฑ์กับดาวน์โหลดอยู่ที่หน้าผลิตภัณฑ์ PDFium Component for Delphi