ข้ามไปยังเนื้อหา

ใช้การกู้คืนข้อผิดพลาดและกลยุทธ์การลองใหม่แบบกำหนดเอง

บริการเอกสารระดับโปรดักชันต้องทำมากกว่าการดักจับและบันทึกข้อยกเว้น บริการต้องตัดสินใจว่าจะดำเนินการอย่างไรต่อไป เช่นทำงานต่อด้วยผลลัพธ์ที่ลดทอนลง สลับไปใช้เส้นทางการเรนเดอร์สำรอง ลองใหม่ด้วยอินพุตที่เอนจินยอมรับได้ หรือส่งมอบหน้าที่สร้างไว้ก่อนเกิดความล้มเหลว สูตรนี้แสดงกลยุทธ์การกู้คืนสี่แบบที่สร้างบนลำดับชั้นข้อยกเว้นของ NextPDF และเมธอดสำหรับตรวจสอบสถานะเอกสาร:

  • การลดทอนอย่างนุ่มนวลเมื่อฟอนต์ล้มเหลว — ดักจับ NextPDF\Exception\FontNotFoundException สำรองไปยังฟอนต์ที่รับประกันว่าใช้งานได้ แล้วสร้างเอกสารต่อไป
  • ตัวเรนเดอร์สำรอง — เมื่อเส้นทางภายในกระบวนการ Document::writeHtml() ปฏิเสธอินพุต ให้ลองใหม่ผ่าน Document::writeHtmlChrome() ซึ่งเป็นบริดจ์ Chrome ของ nextpdf/artisan
  • ลองใหม่ด้วย HTML ทางเลือก — เมื่อเกิด NextPDF\Exception\HtmlParsingException หรือ NextPDF\Exception\CssResolutionBudgetExceededException ให้ลองใหม่ด้วย HTML รูปแบบที่ลดความซับซ้อนแล้วและทราบว่าใช้งานได้
  • การกู้คืนเอกสารบางส่วน — อ่าน Document::getNumPages() หลังเกิดความล้มเหลว และบันทึกสิ่งที่สร้างไว้แล้วแทนการทิ้งไป

เมื่อทราบวิธีดักจับในระดับที่เหมาะสมแล้ว หน้าคู่กัน จัดการข้อผิดพลาดด้วยลำดับชั้นข้อยกเว้นของ NextPDF จะอธิบายรายละเอียดของลำดับชั้น ส่วนหน้านี้แสดงสิ่งที่ควรทำ หลัง การดักจับ

สูตรนี้มุ่งไปที่รุ่นคอร์แบบโอเพนซอร์ส (OSS) API ทุกตัวที่ระบุไว้ที่นี่อยู่ใน nextpdf/core ดีเพนเดนซีทางเลือกเพียงตัวเดียวคือ nextpdf/artisan สำหรับการสำรองด้วย Chrome

Terminal window
composer require nextpdf/core:^3

กลยุทธ์ตัวเรนเดอร์สำรองยังใช้บริดจ์ Chrome เพิ่มเติม:

Terminal window
composer require nextpdf/artisan

เมื่อไม่มี nextpdf/artisan Document::writeHtmlChrome() จะโยน NextPDF\Exception\PageLayoutException แทนที่จะเรนเดอร์ กลยุทธ์การสำรองด้านล่างถือว่าการไม่มีบริดจ์เป็นอีกกรณีหนึ่งที่กู้คืนได้

การกู้คืนอาศัยข้อเท็จจริงสองประการเกี่ยวกับ NextPDF ซึ่งทั้งสองข้อได้รับการตรวจสอบกับซอร์สโค้ดแล้ว

ลำดับชั้นข้อยกเว้นบอกได้ว่าสิ่งใดกู้คืนได้ ข้อยกเว้นเชิงโดเมนทุกตัวสืบทอดจากคลาสฐานแบบนามธรรม NextPDF\Exception\NextPdfException ซึ่งสืบทอดจาก RuntimeException และนำ NextPDF\Contracts\ContextAwareExceptionInterface มาใช้ ดักจับชนิดย่อยที่เฉพาะเจาะจงเพื่อเลือกเส้นทางการกู้คืนให้ตรงกับความล้มเหลวนั้น:

  • FontNotFoundException มี getFontName() getSearchPaths() และ wasFallbackAttempted() — เพียงพอสำหรับการลองใหม่ด้วยฟอนต์อื่น
  • HtmlParsingException มี getRule() getPosition() และ getHtmlSnippet() — เพียงพอสำหรับตัดสินใจว่าการลองใหม่แบบเรียบง่ายคุ้มค่าหรือไม่
  • CssResolutionBudgetExceededException มี getVisits() และ getBudget() — เป็นสัญญาณว่าสไตล์ชีตแบบตัดทอนอาจแก้ปัญหาเซเลกเตอร์ที่ผิดปกติได้
  • ข้อจำกัดสำคัญประการหนึ่ง: NextPDF\Support\DegradedException สืบทอดจาก RuntimeException โดยตรง ไม่ใช่ NextPdfException ดังนั้น catch (NextPdfException $e) จึงไม่ดักจับการปฏิเสธจากนโยบายการลดทอน เมื่อ NextPDF\Contracts\DegradationPolicy ที่ใช้งานอยู่คือ Strict หรือ Balanced ให้ดักจับ DegradedException อย่างชัดเจนเพื่อกู้คืนจากข้อยกเว้นนี้

ตรวจสอบเอกสารได้ระหว่างสร้าง Document เปิดเผยสถานะการสร้างของเอกสารผ่านเมธอดอ่านอย่างเดียว getNumPages() คืนค่าจำนวนหน้าทั้งหมด รวมถึงหน้าที่กำลังทำงานและยังไม่ได้ฟลัช ส่วน getPage() คืนค่าดัชนีฐานศูนย์ของหน้าปัจจุบัน หลังเกิดความล้มเหลวระหว่างการสร้าง ให้อ่าน getNumPages() เพื่อดูว่ามีหน้าที่สมบูรณ์อยู่หรือไม่ จากนั้นเรียก save() หรือ getPdfData() เพื่อส่งออกหน้าเหล่านั้น เอนจินยังบันทึกเหตุการณ์การลดทอนที่ไม่ร้ายแรงด้วย: getWarnings() คืนค่า list<NextPDF\Support\Warning> hasWarnings() รายงานว่ามีการเก็บรวบรวมรายการใดไว้หรือไม่ และ hasDegradedParity() รายงานว่าความเที่ยงตรงของผลลัพธ์ได้รับผลกระทบหรือไม่ เมธอดเหล่านี้ช่วยให้รูทีนการกู้คืนแยกความแตกต่างระหว่าง “สำเร็จโดยไม่มีปัญหา” กับ “สำเร็จแต่ความเที่ยงตรงลดลง” โดยไม่ต้องแยกวิเคราะห์ข้อยกเว้น

นโยบายการลดทอนควบคุมว่าเหตุการณ์ใดควรจัดการเป็นข้อยกเว้น และเหตุการณ์ใดควรจัดการเป็นคำเตือน NextPDF\Core\Config มีค่าเริ่มต้นเป็น DegradationPolicy::Balanced ซึ่งจะเตือนและดำเนินการต่อสำหรับการลดทอนที่มีขอบเขตจำกัด แต่จะโยนข้อยกเว้นเมื่อผลกระทบทำให้ดำเนินต่อไม่ได้ DegradationPolicy::Permissive ไม่โยนข้อยกเว้นเลยและเก็บรวบรวมทุกอย่างไว้ในช่องทางคำเตือน DegradationPolicy::Strict โยนข้อยกเว้นเมื่อมีความเสี่ยงด้านการปฏิบัติตามข้อกำหนด การสูญเสียเชิงความหมาย หรือผลกระทบใดๆ ที่ทำให้ดำเนินต่อไม่ได้ เลือกนโยบายก่อน แล้วจึงเขียนตรรกะการกู้คืนสำหรับรูปแบบความล้มเหลวที่นโยบายนั้นสร้างขึ้น

โค้ดการกู้คืนด้านล่างใช้สมาชิกที่ตรวจสอบแล้วเหล่านี้:

  • NextPDF\Core\Document::createStandalone(?Config $config = null): self, addPage(), setFont(string $family, string $style = '', float $size = 12.0): static, cell(...), writeHtml(string $html): static, writeHtmlChrome(string $html, ?float $width = null, ?float $height = null): static, save(string $path): void, getPdfData(): string, getNumPages(): int, getPage(): int, getWarnings(): list<Warning>, hasWarnings(): bool, hasDegradedParity(): bool, addFontDirectory(string $directory): static.
  • NextPDF\Core\Config::withDegradationPolicy(DegradationPolicy $policy): self และ degradationPolicy มีค่าเริ่มต้นเป็น DegradationPolicy::Balanced โดยปริยาย
  • NextPDF\Contracts\DegradationPolicyStrict, Balanced, Permissive
  • NextPDF\Exception\NextPdfException (ฐานนามธรรม), NextPDF\Exception\FontNotFoundException, NextPDF\Exception\HtmlParsingException, NextPDF\Exception\CssResolutionBudgetExceededException, NextPDF\Exception\WriterException, NextPDF\Exception\PageLayoutException
  • NextPDF\Support\DegradedException (มี capability และ policy), NextPDF\Support\Capability (id, status, reason, isDegraded()), NextPDF\Support\Warning, NextPDF\Support\WarningSeverity

รูปแบบการกู้คืนขั้นต่ำที่ยังมีประโยชน์จะดักจับความล้มเหลวจากฟอนต์ที่ขาดหาย สำรองไปยังฟอนต์ที่รับประกันว่าใช้งานได้ แล้วดำเนินการต่อ สนิปเปตนี้ข้ามการจัดการแบบครอบคลุมจากตัวอย่างระดับโปรดักชัน สำหรับตัวจัดการที่สมบูรณ์พร้อมการบันทึกและขอบเขตของ DegradedException โปรดดูตัวอย่างระดับโปรดักชันด้านล่าง

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
use NextPDF\Exception\FontNotFoundException;
$doc = Document::createStandalone();
$doc->addPage();
try {
// A face that may not be installed on every host.
$doc->setFont('CorporateSans', '', 12);
} catch (FontNotFoundException $e) {
// Recover: fall back to a face the engine always resolves.
$doc->setFont('helvetica', '', 12);
}
$doc->cell(0, 10, 'Rendered with a recovered font.', newLine: true);
$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/out.pdf');

ตัวอย่างฉบับเต็มเชื่อมกลยุทธ์ทั้งสี่แบบเข้าด้วยกันในไปป์ไลน์การเรนเดอร์เดียว ได้แก่ การสำรองฟอนต์ การสำรองตัวเรนเดอร์จากเส้นทางภายในกระบวนการไปยัง Chrome การลองใหม่ด้วย HTML ทางเลือก และการกู้คืนเอกสารบางส่วนที่ขับเคลื่อนโดย getNumPages() ตัวอย่างนี้เคารพช่องทางผลลัพธ์ของฮาร์เนส และไม่ดักจับ Exception แบบเปล่าหรือปล่อยให้บล็อก catch ว่างเปล่า

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Contracts\ContextAwareExceptionInterface;
use NextPDF\Contracts\DegradationPolicy;
use NextPDF\Core\Config;
use NextPDF\Core\Document;
use NextPDF\Exception\CssResolutionBudgetExceededException;
use NextPDF\Exception\FontNotFoundException;
use NextPDF\Exception\HtmlParsingException;
use NextPDF\Exception\NextPdfException;
use NextPDF\Exception\PageLayoutException;
use NextPDF\Exception\WriterException;
use NextPDF\Support\DegradedException;
/**
* A minimal structured sink. In production this is your PSR-3 logger; the
* exception class and its structured context become log fields.
*
* @param array<string, mixed> $context
*/
function logRecovery(string $message, array $context): void
{
fwrite(STDERR, $message . ' ' . json_encode($context, JSON_THROW_ON_ERROR) . "\n");
}
/**
* Resolve a usable font, degrading from the requested face to a guaranteed
* fallback. Returns the face actually applied so the caller can record it.
*
* @param non-empty-string $requested
* @param non-empty-string $fallback
*
* @return non-empty-string
*/
function applyFontWithFallback(Document $doc, string $requested, string $fallback): string
{
try {
$doc->setFont($requested, '', 12);
return $requested;
} catch (FontNotFoundException $e) {
// STRATEGY 1 — graceful degradation on a font failure.
logRecovery('Font unavailable; degrading to a guaranteed face', [
'exception' => $e::class,
'font_name' => $e->getFontName(),
'searched' => $e->getSearchPaths(),
'fallback' => $fallback,
]);
$doc->setFont($fallback, '', 12);
return $fallback;
}
}
/**
* Render HTML through the in-process pipeline, then through the Chrome bridge,
* then through a simplified HTML variant. Each layer recovers a more specific
* failure than the last.
*/
function renderHtmlWithRecovery(Document $doc, string $primaryHtml, string $simplifiedHtml): void
{
try {
// Primary path: the in-process HTML/CSS pipeline.
$doc->writeHtml($primaryHtml);
return;
} catch (CssResolutionBudgetExceededException $e) {
// STRATEGY 3 — retry with alternative HTML for a pathological selector.
logRecovery('CSS resolution budget exceeded; retrying with simplified HTML', [
'exception' => $e::class,
'visits' => $e->getVisits(),
'budget' => $e->getBudget(),
]);
$doc->writeHtml($simplifiedHtml);
return;
} catch (HtmlParsingException $e) {
// STRATEGY 2 — fall back to the Chrome renderer for input the
// in-process parser rejects. The Chrome bridge uses a browser CSS
// engine, so it may accept what the in-process parser would not.
logRecovery('In-process HTML parse failed; trying the Chrome fallback renderer', [
'exception' => $e::class,
'rule' => $e->getRule(),
'position' => $e->getPosition(),
]);
try {
$doc->writeHtmlChrome($primaryHtml);
return;
} catch (PageLayoutException $chromeError) {
// The Chrome bridge is absent (nextpdf/artisan not installed) or
// rejected the input. Last resort: the simplified HTML variant
// through the in-process pipeline.
logRecovery('Chrome fallback unavailable; retrying with simplified HTML', [
'exception' => $chromeError::class,
]);
$doc->writeHtml($simplifiedHtml);
return;
}
}
}
// --- Configure the degradation policy up front ---------------------------
// Balanced (the default) warns on bounded degradation and throws only on a
// blocking impact. A regulated workflow would choose DegradationPolicy::Strict.
$config = (new Config())->withDegradationPolicy(DegradationPolicy::Balanced);
$doc = Document::createStandalone($config);
$primaryHtml = '<h1>Quarterly report</h1><p>Body paragraph with rich styling.</p>';
$simplifiedHtml = '<h1>Quarterly report</h1><p>Body paragraph (simplified).</p>';
$outputPath = getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/out.pdf';
try {
$doc->addPage();
$applied = applyFontWithFallback($doc, 'CorporateSans', 'helvetica');
$doc->cell(0, 12, 'Custom error recovery patterns', newLine: true);
renderHtmlWithRecovery($doc, $primaryHtml, $simplifiedHtml);
$doc->save($outputPath);
logRecovery('Document built', [
'font_applied' => $applied,
'pages' => $doc->getNumPages(),
'has_warnings' => $doc->hasWarnings(),
'degraded_parity' => $doc->hasDegradedParity(),
]);
} catch (DegradedException $e) {
// BOUNDARY: DegradedException extends RuntimeException directly, NOT
// NextPdfException, so the catch-all below would not have caught it.
// Under Strict/Balanced policy a blocking degradation lands here.
logRecovery('Capability degraded under the active policy; emitting a built partial', [
'exception' => $e::class,
'capability' => $e->capability->id,
'status' => $e->capability->status->value,
'reason' => $e->capability->reason ?? 'unknown',
'policy' => $e->policy->value,
]);
// STRATEGY 4 — partial-document recovery: save whatever pages exist.
if ($doc->getNumPages() > 0) {
$doc->save($outputPath);
}
} catch (WriterException $e) {
// Serialization or I/O failure: the in-memory document is valid but could
// not be written. Surface the stage so infrastructure can act on it.
logRecovery('PDF write failed; document was valid in memory', [
'exception' => $e::class,
'writer_state' => $e->getWriterState(),
'output_path' => $e->getOutputPath(),
]);
} catch (NextPdfException $e) {
// Catch-all for every other NextPDF\Exception\*. STRATEGY 4 again: if any
// complete pages were built before the failure, emit them rather than
// discarding the work.
$context = ['exception' => $e::class, 'pages' => $doc->getNumPages()];
if ($e instanceof ContextAwareExceptionInterface) {
$context += $e->getContext();
}
logRecovery('Unrecovered NextPDF failure; attempting a partial save', $context);
if ($doc->getNumPages() > 0) {
$doc->save($outputPath);
}
}
fwrite(STDERR, "Recovery pipeline complete.\n");

ปล่อย STDOUT ว่างไว้สำหรับฮาร์เนส การวินิจฉัยการกู้คืนจะไปที่ STDERR และไฟล์ Portable Document Format (PDF) จะถูกเขียนไปยัง NEXTPDF_COOKBOOK_OUTPUT เท่านั้น

  • จัดเรียงบล็อก catch จากเฉพาะเจาะจงไปยังทั่วไป PHP จับคู่กับ catch ที่เข้ากันได้ตัวแรก การวาง catch (NextPdfException $e) ไว้ก่อน catch (WriterException $e) จะทำให้บล็อกที่เฉพาะเจาะจงกลายเป็นโค้ดที่ไม่มีทางทำงาน เพราะ WriterException สืบทอดจาก NextPdfException
  • DegradedException อยู่นอกลำดับชั้น ข้อยกเว้นนี้สืบทอดจาก RuntimeException ไม่ใช่ NextPdfException ไปป์ไลน์ที่ดักจับเฉพาะ NextPdfException จะปล่อยให้การปฏิเสธจากนโยบายแบบเข้มงวดแพร่กระจายไปโดยไม่ถูกดักจับ ให้ดักจับ DegradedException (หรือ RuntimeException ที่กว้างกว่า) เมื่อมีนโยบายการลดทอนที่ไม่ใช่ค่าเริ่มต้นทำงานอยู่
  • การสำรองฟอนต์ก็ล้มเหลวได้เช่นกัน หากฟอนต์สำรองของคุณไม่ได้ลงทะเบียนไว้ setFont() ครั้งที่สองจะโยนข้อยกเว้นอีกครั้ง ใช้ชื่อแทนแบบ Base14 เช่น helvetica ซึ่งเอนจินแก้ไขได้โดยไม่ต้องค้นหาในระบบไฟล์ หรือลงทะเบียนฟอนต์ที่มาพร้อมกันผ่าน addFontDirectory() ตอนเริ่มต้นเพื่อให้การสำรองได้รับการรับประกัน
  • getNumPages() นับหน้าที่กำลังทำงานซึ่งยังไม่ได้ฟลัช เมธอดนี้คืนค่าจำนวนหน้าที่ฟลัชแล้วบวกหนึ่งเมื่อมีหน้าเปิดอยู่ในขณะนั้น “การบันทึกบางส่วน” รวมหน้าที่กำลังสร้างเมื่อเกิดความล้มเหลว ซึ่งโดยปกติแล้วเป็นสิ่งที่ต้องการ หากต้องการเฉพาะหน้าที่เสร็จสมบูรณ์เต็มที่ ให้แยกเส้นทางตาม getPage() ด้วย
  • การสำรองด้วย Chrome เปลี่ยนความเที่ยงตรง ไม่ใช่เพียงความพร้อมใช้งาน ไปป์ไลน์ภายในกระบวนการและบริดจ์ Chrome ใช้เอนจินการจัดวางที่ต่างกัน ดังนั้นเอกสารที่สำรองไปยัง Chrome จึงอาจให้ผลลัพธ์แตกต่างออกไป ถือว่าการสำรองเป็นการกู้คืน ไม่ใช่สิ่งทดแทนแบบโปร่งใส และบันทึกว่าเส้นทางใดสร้างผลลัพธ์
  • การลองใหม่ต้องใช้อินพุตที่ทราบว่าใช้งานได้ การลองใหม่ด้วย HTML แบบเรียบง่ายจะช่วยได้ก็ต่อเมื่อรูปแบบที่เรียบง่ายนั้นลดความซับซ้อนลงจริง เช่นมีเซเลกเตอร์ซ้อนกันน้อยลง และไม่มีเชน :has() ที่ใช้งบประมาณการแก้ไขจนหมด การลองใหม่ด้วยอินพุตเดิมที่ล้มเหลวไปแล้วจะวนกลับไปยังข้อยกเว้นเดิม
  • ตรวจสอบคำเตือนหลังการรันที่สะอาด การเรนเดอร์ที่คืนค่าโดยไม่โยนข้อยกเว้นก็ยังอาจมีการลดทอนได้ ตรวจสอบ hasDegradedParity() และอ่าน getWarnings() ก่อนถือว่าผลลัพธ์เที่ยงตรงในระดับพิกเซล ภายใต้ DegradationPolicy::Permissive การลดทอนทุกอย่างเป็นคำเตือน ไม่ใช่ข้อยกเว้น
  • การกู้คืนเพิ่มต้นทุนเฉพาะบนเส้นทางความล้มเหลวเท่านั้น NextPDF โยนข้อยกเว้นในสถานะที่ผิดปกติ ดังนั้นการเรนเดอร์ที่ไม่มีปัญหาจึงไม่เสียต้นทุนเพิ่มเติมจาก try/catch ที่ครอบไว้
  • การสำรองตัวเรนเดอร์จะรันการเรนเดอร์ซ้ำ ความพยายามภายในกระบวนการจะถูกทิ้งไป และความพยายามด้วย Chrome จะเริ่มต้นใหม่ ดังนั้นในกรณีเลวร้ายที่สุด การเรนเดอร์แบบสำรองจึงมีต้นทุนเท่ากับเวลาการเรนเดอร์ทั้งสองครั้งบวกกับการสื่อสารไปกลับข้ามกระบวนการกับ Chrome เผื่องบประมาณส่วนนี้ไว้เมื่อตั้งค่าการหมดเวลาของคำขอ
  • การลองใหม่ด้วย HTML ทางเลือกจะแยกวิเคราะห์เอกสารชุดที่สอง รักษารูปแบบที่เรียบง่ายให้มีขนาดเล็ก เพื่อให้การลองใหม่มีต้นทุนต่ำเมื่อเทียบกับความพยายามหลัก
  • การบันทึกบางส่วนจะซีเรียลไลซ์หน้าที่สร้างไว้แล้ว ต้นทุนของการบันทึกบางส่วนขยายตามจำนวนหน้าที่เหลืออยู่ ไม่ใช่ตามงานที่ล้มเหลว
  • อย่าแสดงข้อความข้อยกเว้นดิบหรือเส้นทางระบบไฟล์แก่ผู้ใช้ปลายทาง ข้อความของ FontNotFoundException มีไดเรกทอรีที่ค้นหา และ WriterException มีเส้นทางผลลัพธ์ ทั้งสองอย่างเปิดเผยโครงสร้างของเซิร์ฟเวอร์ บันทึกบริบทเชิงโครงสร้างไว้ฝั่งเซิร์ฟเวอร์ และคืนค่าข้อความทั่วไปให้ผู้เรียก
  • ถือว่า HTML ที่ใช้ลองใหม่เป็นอินพุตที่ไม่น่าเชื่อถือในทุกความพยายาม การสำรองและการลองใหม่ด้วย HTML แบบเรียบง่ายยังผ่านขอบเขตอินพุตเดียวกัน ไปป์ไลน์ภายในกระบวนการและบริดจ์ Chrome ต่างใช้นโยบายความปลอดภัยของ HTML ของตนเอง และการลองใหม่ไม่ได้ผ่อนปรนการตรวจสอบความถูกต้องนั้น อย่าสันนิษฐานว่ารูปแบบ “ที่เรียบง่าย” ปลอดภัยกว่าเพียงเพราะคุณเป็นผู้เขียนเอง
  • การบันทึกบางส่วนยังคงเป็นการเขียนไฟล์ ใช้กฎการตรวจสอบความถูกต้องของเส้นทาง สิทธิ์ และตำแหน่งจัดเก็บกับผลลัพธ์บางส่วนเช่นเดียวกับผลลัพธ์ที่สมบูรณ์ Document::save() ปฏิเสธสตรีมแร็ปเปอร์และไบต์ว่าง (null byte) และแก้ไขไดเรกทอรีหลักเพื่อปิดกั้นการท่องผ่านเส้นทาง แต่ปลายทางที่คุณส่งเข้ามาเป็นความรับผิดชอบของคุณ

สูตรนี้ไม่ได้อ้างมาตรฐานเชิงบรรทัดฐานใดๆ สูตรนี้ประกอบ API ข้อยกเว้นและการตรวจสอบเอกสารสาธารณะของ NextPDF ให้เป็นโฟลว์ควบคุมการกู้คืน ไม่ได้ยืนยันพฤติกรรมที่กำหนดโดย ISO 32000-2 หรือมาตรฐานอื่นใด จึงไม่มีบล็อก citations:

หน้านี้ได้รับการตรวจสอบด้วยโปรไฟล์การทำซ้ำได้แบบ semantic เอกสารที่กู้คืนมี /ID ของเทรลเลอร์และวันที่แก้ไขที่สร้างใหม่ทุกครั้งที่บันทึก ดังนั้นจึงไม่สามารถทำให้เหมือนกันในระดับไบต์ได้ การเปรียบเทียบแบบ abstract syntax tree (AST) เชิงโครงสร้างร่วมกับเมทาดาทาเท่านั้นจึงมีความเสถียรในทุกการรัน