<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Derek</title>
    <description>The latest articles on DEV Community by Derek (@derek-compdf).</description>
    <link>https://dev.to/derek-compdf</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1420219%2Ffb7b9c21-dacb-47e0-aeff-4bdc4d17b5e6.png</url>
      <title>DEV Community: Derek</title>
      <link>https://dev.to/derek-compdf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/derek-compdf"/>
    <language>en</language>
    <item>
      <title>Can your RAG answer point back to the right PDF page?</title>
      <dc:creator>Derek</dc:creator>
      <pubDate>Wed, 04 Nov 2026 14:30:00 +0000</pubDate>
      <link>https://dev.to/derek-compdf/can-your-rag-answer-point-back-to-the-right-pdf-page-32nb</link>
      <guid>https://dev.to/derek-compdf/can-your-rag-answer-point-back-to-the-right-pdf-page-32nb</guid>
      <description>&lt;p&gt;A correct-looking answer is difficult to review if its citation opens the wrong PDF page. This can happen when several pages share the same heading, or when a pipeline discards page metadata before chunking.&lt;/p&gt;

&lt;p&gt;Here is a small check to run before adding a vector database. It uses a two-page teaching PDF and an actual ComPDF parsing result. It checks source mapping; it does not benchmark retrieval or prove that an LLM answers correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the two pages deliberately similar
&lt;/h2&gt;

&lt;p&gt;Use a heading of “Return policy” on both pages. Page 1 says “Domestic returns” and “Request a return within 14 days of delivery.” Page 2 says “International returns” and “Request a return within 30 days of delivery.” Export this as a text PDF.&lt;/p&gt;

&lt;p&gt;The shared heading matters: it cannot identify the right source on its own. You need the document version and page attached to the relevant text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspect the parser output before flattening it
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.compdf.com/guides/api-reference/v2/documentParsing?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=pdf_document_workflow_202610&amp;amp;utm_content=devto_page_provenance_20261105" rel="noopener noreferrer"&gt;ComPDF document parsing API&lt;/a&gt; returns structured document content. For this sample, the result archive contained a JSON file with two entries in result.pages. Each page had page_id and a structured list of blocks.&lt;/p&gt;

&lt;p&gt;The actual result preserved the 14-day sentence on page_id 1 and the 30-day sentence on page_id 2. It also preserved the repeated heading. Those observations apply to this sample; they are not an accuracy guarantee for other files.&lt;/p&gt;

&lt;p&gt;To repeat the request, keep your API key on the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--fail-with-body&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'https://api-server.compdf.com/server/v2/process/idp/documentParsing'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"x-api-key: &lt;/span&gt;&lt;span class="nv"&gt;$COMPDF_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--form&lt;/span&gt; &lt;span class="s1"&gt;'file=@returns-policy.pdf'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; response.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response is JSON, not the parsed file. Check the business code and task status, then download the returned result URL and extract result_json.json. Do not treat HTTP 200 as proof of successful parsing. Use the current API documentation for the response wrapper and processing status your account receives.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F190a5vujm5fezcjgktq6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F190a5vujm5fezcjgktq6.png" alt="Two parsed pages: domestic returns on page 1; international returns on page 2" width="800" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Figure: values reproduced from the actual JSON output, not a product UI screenshot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attach source identity while you still have it
&lt;/h2&gt;

&lt;p&gt;This Python example uses only the standard library. Place the original PDF and extracted JSON beside the script. ComPDF provides the page and block text here; the hash and chunk IDs below are application metadata.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;returns-policy.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;source_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_bytes&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result_json.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;structured&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source_sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;source_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;page_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;page_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chunk_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;source_hash&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:p&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;page_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:b&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chunks.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;within&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;page_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Actual output for the two-page sample:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 Request a return within 14 days of delivery.
2 Request a return within 30 days of delivery.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The block filter is intentionally narrow. This example checks text sentences, not table contents or all block types. If your task needs tables, inspect and process their structures explicitly rather than assuming they have become ordinary text chunks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a useful citation needs to retain
&lt;/h2&gt;

&lt;p&gt;Store the full source hash alongside each chunk, even though the example uses a shortened prefix in its readable ID. Keep an internal mapping from that hash to an immutable original file. The hash identifies file bytes, not access rights, and the chunk ID is only stable for the same extraction and block order. Re-parsing can change that order.&lt;/p&gt;

&lt;p&gt;When splitting long blocks, copy their source metadata to every child chunk. If you merge blocks across pages, store a page range or a list of sources; assigning the merged text to just the first page creates a misleading citation.&lt;/p&gt;

&lt;p&gt;The page_id in this result matched physical PDF pages 1 and 2. A viewer may expect a zero-based index, and printed page labels can differ from physical pages. Test that conversion in the viewer instead of subtracting one everywhere without checking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test failure cases before trusting a citation
&lt;/h2&gt;

&lt;p&gt;First, assert that the domestic sentence belongs to page 1 and the international sentence to page 2. Then deliberately swap the page metadata and make sure the check fails. Also replace the PDF with a changed version and confirm that your source mapping refuses to reuse the old hash.&lt;/p&gt;

&lt;p&gt;These are application checks. A parser returning page metadata does not automatically implement citation rendering, authorization, or version management for your system.&lt;/p&gt;

&lt;p&gt;If you later add retrieval, inspect the returned chunk metadata before asking an LLM to write an answer. A correct sentence with a missing source should remain a failed citation check. A clickable citation should open the authorized file version and the page containing the quoted sentence.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.compdf.com/workflows/rag-knowledge-base?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=pdf_document_workflow_202610&amp;amp;utm_content=devto_page_provenance_20261105" rel="noopener noreferrer"&gt;document parsing workflow for RAG&lt;/a&gt; is a useful next step when evaluating the input stage. Start with your own permitted PDFs: repeated headings, scanned pages, missing fields, and revised versions. Verify what survives parsing, then decide how to carry it through chunking and retrieval. A working citation is a property of the whole pipeline, not just of the text extractor.&lt;/p&gt;

</description>
      <category>pdf</category>
      <category>rag</category>
      <category>python</category>
    </item>
    <item>
      <title>Before changing embeddings, test what your PDF parser actually preserved</title>
      <dc:creator>Derek</dc:creator>
      <pubDate>Wed, 28 Oct 2026 14:15:00 +0000</pubDate>
      <link>https://dev.to/derek-compdf/before-changing-embeddings-test-what-your-pdf-parser-actually-preserved-4225</link>
      <guid>https://dev.to/derek-compdf/before-changing-embeddings-test-what-your-pdf-parser-actually-preserved-4225</guid>
      <description>&lt;p&gt;A RAG answer can cite the correct PDF and still use text in the wrong order. Before changing embeddings, inspect the evidence that reaches the index.&lt;/p&gt;

&lt;p&gt;This example uses two teaching PDFs: a table with prices and a two-column policy document. It compares a plain-text extraction baseline with actual ComPDF parsing output. It does not run an embedding model or claim a retrieval-accuracy improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create an input with a known reading order
&lt;/h2&gt;

&lt;p&gt;The two-column fixture has Policy A on the left and Policy B on the right. Policy A allows replacement within 14 days; Policy B allows 30 days. The PDF drawing commands deliberately alternate left and right lines, although a reader would read down each column.&lt;/p&gt;

&lt;p&gt;That makes it a useful diagnostic fixture, not a representative benchmark of all PDFs. The &lt;a href="https://drive.google.com/file/d/1WcAeFNELgaW7gCYNhwnjIZ5vGgD-EBgd/view" rel="noopener noreferrer"&gt;teaching sample pack&lt;/a&gt; contains the inputs, observed outputs and scripts. Unzip it and run commands from its root directory. Use Python 3.10+; install &lt;code&gt;examples/requirements.txt&lt;/code&gt; before inspecting PDF and DOCX outputs. You can inspect the saved results without an API key. A new parsing request requires your own server-side &lt;code&gt;COMPDF_API_KEY&lt;/code&gt; and may consume account usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve the parser output before chunking
&lt;/h2&gt;

&lt;p&gt;Use the &lt;a href="https://www.compdf.com/guides/api-reference/v2/documentParsing?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=pdf_document_workflow_202610&amp;amp;utm_content=devto_parser_checks_20261028" rel="noopener noreferrer"&gt;ComPDF document parsing API&lt;/a&gt; to obtain the result archive. The supplied Python script reads its key from &lt;code&gt;COMPDF_API_KEY&lt;/code&gt;, checks the business status, downloads the archive and verifies that &lt;code&gt;result_json.json&lt;/code&gt; exists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python examples/parse_file.py samples/rag-columns.pdf parsed.zip &lt;span class="nt"&gt;--operation&lt;/span&gt; parse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this result, &lt;code&gt;result.pages[0].structured&lt;/code&gt; contains the title and content blocks. The left-column policy is grouped before the right-column policy. Keep the original JSON so later chunking experiments do not overwrite the evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the exact fact association
&lt;/h2&gt;

&lt;p&gt;Plain-text extraction in this test returned both policy headings before either duration. The structured output grouped “Policy A: replacement period” with “is 14 days from delivery.” A narrow assertion demonstrates that difference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pypdf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PdfReader&lt;/span&gt;

&lt;span class="n"&gt;baseline_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PdfReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;samples/rag-columns.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;extract_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;results/rag-columns-parsed/result_json.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;parsed_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;structured&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Policy A: replacement period\s+is 14 days&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;baseline_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parsed_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Passing this assertion means that this association survived in this fixture. It says nothing about semantic correctness, hallucinations or overall answer accuracy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F09kx9ivonxz1uae9ncl3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F09kx9ivonxz1uae9ncl3.png" alt="Observed reading-order difference for the teaching two-column fixture; this is a parser inspection, not a retrieval benchmark" width="799" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Figure: Observed reading-order difference for the teaching two-column fixture; this is a parser inspection, not a retrieval benchmark&lt;/p&gt;

&lt;h2&gt;
  
  
  Check table cells separately
&lt;/h2&gt;

&lt;p&gt;The second fixture contains AX-271 at USD 19.40 and BX-272 at USD 0.00. The observed result retained a three-column HTML table. Inspect the cells rather than searching for isolated numbers: finding both a part number and a price somewhere in a document does not prove that they belong together.&lt;/p&gt;

&lt;p&gt;Zero is also different from missing data. Keep 0.00 as a value and represent an unavailable price separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then evaluate the downstream system
&lt;/h2&gt;

&lt;p&gt;After these checks, freeze the parser output and compare chunking or embeddings with the same questions, expected answers and retrieval settings. Report failed cases along with successful ones. This example stops before that evaluation; it cannot establish that one parser is best for all RAG pipelines.&lt;/p&gt;

&lt;p&gt;For a larger integration, the &lt;a href="https://www.compdf.com/workflows/rag-knowledge-base?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=pdf_document_workflow_202610&amp;amp;utm_content=devto_parser_checks_20261028" rel="noopener noreferrer"&gt;RAG document workflow&lt;/a&gt; connects document parsing to the later indexing steps. Start with your own authorized exception files and require evidence that the relevant facts survived before tuning the model.&lt;/p&gt;

</description>
      <category>python</category>
      <category>rag</category>
      <category>testing</category>
      <category>pdf</category>
    </item>
    <item>
      <title>PDF citations for RAG: keep the page and block behind each chunk</title>
      <dc:creator>Derek</dc:creator>
      <pubDate>Mon, 19 Oct 2026 11:47:25 +0000</pubDate>
      <link>https://dev.to/derek-compdf/pdf-citations-for-rag-keep-the-page-and-block-behind-each-chunk-8ck</link>
      <guid>https://dev.to/derek-compdf/pdf-citations-for-rag-keep-the-page-and-block-behind-each-chunk-8ck</guid>
      <description>&lt;p&gt;A RAG answer can quote the right sentence and still link to the wrong page. If the pipeline drops the source version or page reference, adding a citation button later will not recover that information.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://files.cnblogs.com/files/blogs/761778/compdf-teaching-checks-20261019.zip?t=1792408938&amp;amp;download=true" rel="noopener noreferrer"&gt;teaching input, outputs and example code&lt;/a&gt; are available for inspection. The archive includes no API key or SDK license.&lt;/p&gt;

&lt;p&gt;Start by keeping the parser output and a small source map. Test the map before changing the retrieval model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parse a document you can inspect
&lt;/h2&gt;

&lt;p&gt;Choose a short teaching PDF with a known heading, a paragraph and a table. Record which page contains each item. Keep the original file and its digest so a later upload cannot silently replace the cited version.&lt;/p&gt;

&lt;p&gt;ComPDF provides page-level structured parsing. This request uses the documented overseas endpoint; set &lt;code&gt;COMPDF_API_KEY&lt;/code&gt; on the server and provide &lt;code&gt;sample.pdf&lt;/code&gt; before running it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--fail-with-body&lt;/span&gt; &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'https://api-server.compdf.com/server/v2/process/idp/documentParsing'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"x-api-key: &lt;/span&gt;&lt;span class="nv"&gt;$COMPDF_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--form&lt;/span&gt; &lt;span class="s1"&gt;'file=@sample.pdf'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--form&lt;/span&gt; &lt;span class="s1"&gt;'pageRanges=1-3'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; response.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the request status and returned result before continuing. The &lt;a href="https://www.compdf.com/guides/api-reference/v2/ai/parsing-guide?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=pdf_parsing_workflow_202610&amp;amp;utm_content=devto_provenance_20261019" rel="noopener noreferrer"&gt;parsing guide&lt;/a&gt; covers the request and response; the command alone does not establish extraction quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Download and inspect the result first
&lt;/h2&gt;

&lt;p&gt;In this teaching run, the successful request returned a download URL. The downloaded ZIP contained &lt;code&gt;result_json.json&lt;/code&gt; and a Markdown file. The page data used below came from that JSON file inside the ZIP, not directly from the HTTP envelope.&lt;/p&gt;

&lt;p&gt;The three-page teaching input included a raster-only page. Its downloaded result contained three page records and 21 text-bearing blocks. Each mapped block had an eight-number polygon; table blocks contained HTML. Keep the table type and source reference when normalizing it rather than treating the markup as plain prose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Map the actual fields
&lt;/h2&gt;

&lt;p&gt;In the documented response, &lt;code&gt;result.pages&lt;/code&gt; contains page output. &lt;code&gt;page_id&lt;/code&gt; is one-based, and &lt;code&gt;structured&lt;/code&gt; contains blocks with fields such as &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;text&lt;/code&gt; and &lt;code&gt;pos&lt;/code&gt;. Confirm these against your response rather than replacing missing values with invented coordinates.&lt;/p&gt;

&lt;p&gt;The following code reads the downloaded archive and builds source records. Use the same input bytes that were submitted for parsing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zipfile&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;document_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;teaching-mixed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;document_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sample.pdf&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_bytes&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;zipfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ZipFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;result.zip&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;response_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;result_json.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;review&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;structured&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;review&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;page_number&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;page_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                       &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reason&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;No structured blocks&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;document_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;document_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;document_version&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;document_version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;page_number&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;page_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;block_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;source_polygon&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pos&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;page_width&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;width&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;page_height&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;height&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complete mapping script ran against this archive and produced 21 text-bearing records. Missing polygons also need a review entry before a citation renderer uses them. This checks source mapping; it does not prove that every extracted block is correct.&lt;/p&gt;

&lt;p&gt;These record names belong to the application. Keep the original response alongside them. Missing or failed pages need their own review record; this text-only mapping does not count empty blocks as proof that a page contained nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve references through chunking
&lt;/h2&gt;

&lt;p&gt;Each chunk should retain an ordered list of source records, including all pages it spans. Scope block IDs by document version and page; an integer block ID alone is not a global identifier.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.compdf.com/guides/api-reference/v2/ai/parsing-guide/page-details?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=pdf_parsing_workflow_202610&amp;amp;utm_content=devto_provenance_20261019" rel="noopener noreferrer"&gt;page structure reference&lt;/a&gt; defines &lt;code&gt;pos&lt;/code&gt; as a four-point polygon. A citation renderer must account for the source coordinate system, page dimensions, rotation and displayed size. Do not treat its first two numbers as a ready-made browser rectangle.&lt;/p&gt;

&lt;p&gt;ComPDF supplies extraction and location information. The application still owns chunking, retrieval, permissions and navigation to the original document.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F76pm1vbm72z30qupi9cg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F76pm1vbm72z30qupi9cg.png" alt="Teaching source checks" width="800" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Figure: teaching source and the mapped fields from the actual downloaded parser result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check a citation before trusting it
&lt;/h2&gt;

&lt;p&gt;Ask a question whose answer is known. Follow the returned citation and inspect the original page. Check the sentence, nearby heading and table relationship, then repeat with a paragraph that crosses pages and a newer version of the same filename.&lt;/p&gt;

&lt;p&gt;Also check a failed page. Keep it visible in the review list rather than silently indexing an empty result. Useful evaluation records show the input, returned blocks, citation target and remaining extraction errors; they do not need a claimed accuracy score.&lt;/p&gt;

&lt;p&gt;For a repeated ingestion workflow, use &lt;a href="https://www.compdf.com/compdf-portal/signin?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=pdf_parsing_workflow_202610&amp;amp;utm_content=devto_provenance_20261019" rel="noopener noreferrer"&gt;ComPDF developer access&lt;/a&gt; to evaluate authorized samples with the same checks. Continue to integration only when the output provides the structure and traceability your application needs. This mapping alone does not prove retrieval accuracy or a reduction in hallucinations.&lt;/p&gt;

</description>
      <category>python</category>
      <category>rag</category>
    </item>
    <item>
      <title>Testing HTML-to-PDF exports: fonts, page breaks, and missing text</title>
      <dc:creator>Derek</dc:creator>
      <pubDate>Tue, 13 Oct 2026 10:30:03 +0000</pubDate>
      <link>https://dev.to/derek-compdf/testing-html-to-pdf-exports-fonts-page-breaks-and-missing-text-1bch</link>
      <guid>https://dev.to/derek-compdf/testing-html-to-pdf-exports-fonts-page-breaks-and-missing-text-1bch</guid>
      <description>&lt;p&gt;A successful generation request gives you a file. It does not tell you whether a price symbol changed, a required label disappeared, or a table broke where you expected it to.&lt;/p&gt;

&lt;p&gt;This tutorial uses ComPDF to generate catalog PDFs with images, prices, and component tables, then uses PDF.js to check the output. Three smaller teaching samples show how to detect missing text, font problems, and unexpected page counts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generate a catalog with text, images, and tables
&lt;/h2&gt;

&lt;p&gt;This example uses &lt;a href="https://www.compdf.com/pdf-sdk/pdf-generation?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=pdf_generation_workflow_202610&amp;amp;utm_content=devto_pdf_export_checks_20261013" rel="noopener noreferrer"&gt;ComPDF PDF generation&lt;/a&gt; to turn a catalog layout into PDFs containing text, images, and tables. The same layout is filled with two teaching datasets: four component rows and 32 component rows.&lt;/p&gt;

&lt;p&gt;Node.js fills the values and rows into HTML. The request then sends &lt;code&gt;generationType=html&lt;/code&gt;, &lt;code&gt;html&lt;/code&gt;, &lt;code&gt;pageWidth=595&lt;/code&gt;, and &lt;code&gt;pageHeight=842&lt;/code&gt; to ComPDF. The API performs the HTML-to-PDF generation; the local data interpolation is separate application code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/b1bjfh1v4sgjq911b1po.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo3o4068qg5l7ctshssos.png" alt="Actual ComPDF catalog PDFs generated from the short and long HTML inputs" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure: the actual downloaded PDFs, rendered for inspection. The desk-stand artwork and catalog data are teaching examples. The long output is shown with both pages.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Actual output&lt;/th&gt;
&lt;th&gt;Checked in this run&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MODEL-Q7, four rows, €128.50&lt;/td&gt;
&lt;td&gt;One page&lt;/td&gt;
&lt;td&gt;Model, price, all four item IDs, headings, end marker, and visible images&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MODEL-T9, 32 rows, €206.40&lt;/td&gt;
&lt;td&gt;Two pages&lt;/td&gt;
&lt;td&gt;Model, price, all 32 item IDs, headings, end marker, and visible images&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every output page measured 595 × 842 pt, matching the request. The 32-row table continued onto a second page, with its final row and end marker intact. Visual review of all three pages confirmed that the images were present.&lt;/p&gt;

&lt;p&gt;These requests used HTML filled in Node.js. The API's separate server-side template mode is outside this example. For your own exports, check CSS rendering and repeated table headings against the required layout. The smaller samples below help isolate individual failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define the required text and page ranges
&lt;/h2&gt;

&lt;p&gt;For the short sheet, keep &lt;code&gt;MODEL-Q7&lt;/code&gt;, &lt;code&gt;24 V&lt;/code&gt;, and &lt;code&gt;128.50 USD&lt;/code&gt;. For multilingual labels, retain the Chinese text, &lt;code&gt;Français&lt;/code&gt;, &lt;code&gt;23 °C&lt;/code&gt;, and &lt;code&gt;€128.50&lt;/code&gt;. For the parts list, check the beginning, middle, end, column headings, and final marker.&lt;/p&gt;

&lt;p&gt;Page limits belong to these samples. A 2–6 page range catches an implausible result for this parts list; it is not a rule for every export.&lt;/p&gt;

&lt;p&gt;Tested locally: Node.js 24.18.0 and &lt;code&gt;pdfjs-dist&lt;/code&gt; 5.6.205. Create a directory with &lt;code&gt;samples/&lt;/code&gt; and &lt;code&gt;results/&lt;/code&gt;, then install the reader:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-exact&lt;/span&gt; pdfjs-dist@5.6.205
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; samples results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;.mjs&lt;/code&gt; filenames for the scripts below. Follow &lt;a href="https://www.compdf.com/guides/api-reference/v2/authentication?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=pdf_generation_workflow_202610&amp;amp;utm_content=devto_pdf_export_checks_20261013" rel="noopener noreferrer"&gt;ComPDF API authentication&lt;/a&gt; to get a public API key, then store it in a server-side environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;COMPDF_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'YOUR_PUBLIC_API_KEY'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Build three small HTML samples
&lt;/h2&gt;

&lt;p&gt;Each file starts with this document structure. Save the first as &lt;code&gt;samples/simple.html&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Product sheet teaching sample&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#232b43&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12pt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;25pt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;table&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border-collapse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;collapse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;td&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#cbd4e1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;tr&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;break-inside&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;avoid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Model Q7 product sheet&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;MODEL-Q7&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Supply voltage: 24 V&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Unit price: 128.50 USD&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Teaching sample. No product performance claims.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;samples/multilingual.html&lt;/code&gt;, use this body and set the body font family to &lt;code&gt;"DejaVu Sans", "Noto Sans CJK SC", sans-serif&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Language labels&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;MODEL-M2&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;中文：产品说明 · 教学示例&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Français : température 23 °C&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Price: €128.50&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This font selection produced the required text and visible glyphs in the sample. Check font availability and the rendered output in your own environment; a CSS font name alone does not guarantee the expected result.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;samples/long-table.html&lt;/code&gt;, keep the document structure and replace the body with a title, a table, and an end marker. This small setup script creates the rows without hand-copying 60 entries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// make-table.mjs&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:fs/promises&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;samples/simple.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;PART-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;Teaching component &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;h1&amp;gt;Model T9 parts list&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;MODEL-T9 — teaching sample&amp;lt;/p&amp;gt;
&amp;lt;table&amp;gt;&amp;lt;tbody&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;Part&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;Description&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;Quantity&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;
&amp;lt;p&amp;gt;END-OF-PARTS&amp;lt;/p&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;samples/long-table.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;body&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;[\s\S]&lt;/span&gt;&lt;span class="sr"&gt;*&amp;lt;&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;body&amp;gt;/u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;body&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/body&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;node make-table.mjs&lt;/code&gt; to create the 60-row sample. The illustrated catalog exports also embed the ComPDF logo as a data URI, which avoids an external image download. The smaller samples here focus on text and pagination.&lt;/p&gt;

&lt;p&gt;Save these requirements as &lt;code&gt;cases.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"simple"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"expect"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"MODEL-Q7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"24 V"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"128.50 USD"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"minPages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"maxPages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"multilingual"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"expect"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"MODEL-M2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"产品说明"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"教学示例"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"Français"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"23 °C"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"€128.50"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"minPages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"maxPages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"long-table"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"expect"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"MODEL-T9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"PART-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"PART-030"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"PART-060"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"END-OF-PARTS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"Part"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"Description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"Quantity"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"minPages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"maxPages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Generate and download the files
&lt;/h2&gt;

&lt;p&gt;Send the HTML to the &lt;a href="https://www.compdf.com/guides/api-reference/v2/pdf-generate?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=pdf_generation_workflow_202610&amp;amp;utm_content=devto_pdf_export_checks_20261013" rel="noopener noreferrer"&gt;PDF Generation API&lt;/a&gt;. The script below checks the HTTP response, the business code, the returned status, and the downloaded file header. Save it as &lt;code&gt;generate.mjs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:fs/promises&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fileURLToPath&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fileURLToPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cases&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cases.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COMPDF_API_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Set COMPDF_API_KEY to your public API key.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;results&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),{&lt;/span&gt;&lt;span class="na"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cases&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;!&lt;/span&gt;&lt;span class="nx"&gt;cases&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;negative&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;cases&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Unknown case: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
 &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;generationType&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;samples&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.html`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
 &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;outputFileName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.pdf`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api-server.compdf.com/server/v2/process/pdf/generate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-api-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: generation HTTP &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Not automatically resubmitting.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;200&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;||!&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;downloadUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: generation not successful (code &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, status &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;).`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;download&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;downloadUrl&lt;/span&gt;&lt;span class="p"&gt;,{&lt;/span&gt;&lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;)});&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;download&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: download HTTP &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;download&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;download&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;!==&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;%PDF-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: response is not a PDF.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;results&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.pdf`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: saved PDF (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; bytes)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;node generate.mjs&lt;/code&gt;. The API key is sent only to the generation endpoint, not forwarded to the result-download URL. The script saves the files promptly and does not print the download URLs or key.&lt;/p&gt;

&lt;p&gt;If a submission times out, investigate the request before resubmitting it. A missing client response does not prove that generation never started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check text and page counts
&lt;/h2&gt;

&lt;p&gt;Save this as &lt;code&gt;check.mjs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:fs/promises&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;fileURLToPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;getDocument&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pdfjs-dist/legacy/build/pdf.mjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fileURLToPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;normalize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NFKC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+/gu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getDocument&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;useSystemFonts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;disableFontFace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;numPages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTextContent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;str&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;!&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pagesOK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;numPages&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minPages&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;numPages&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxPages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;pagesOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;numPages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;pagesOK&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="nf"&gt;fileURLToPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cases&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cases.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;failed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cases&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;negative&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;results&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.pdf`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;);}&lt;/span&gt;
  &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;};}&lt;/span&gt;
  &lt;span class="nx"&gt;failed&lt;/span&gt;&lt;span class="o"&gt;||=!&lt;/span&gt;&lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;results&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;report.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
 &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
 &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exitCode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;code&gt;node check.mjs&lt;/code&gt;. It writes &lt;code&gt;results/report.json&lt;/code&gt; and exits with code 1 if a file cannot be read or a requirement fails.&lt;/p&gt;

&lt;p&gt;The text normalization removes whitespace and applies Unicode NFKC normalization. That makes the check tolerant of line wrapping and some compatibility characters. It also means this check does not verify exact typography, whitespace, reading order, or every line of a table.&lt;/p&gt;

&lt;p&gt;For stricter rules, change the normalization and add checks that match the document's purpose. Do not compare PDF binary hashes as a layout test: metadata changes can alter the bytes without altering the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix missing accents and symbols
&lt;/h2&gt;

&lt;p&gt;The first multilingual export used an Arial-based fallback stack. It failed the requirements for &lt;code&gt;Français&lt;/code&gt;, &lt;code&gt;23 °C&lt;/code&gt;, and &lt;code&gt;€128.50&lt;/code&gt;. Its rendered output also showed incorrect characters. Changing the font selection to &lt;code&gt;DejaVu Sans&lt;/code&gt; and generating again produced the expected text and visible glyphs in this sample.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/0qzelktuqfqi248x7mmp.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frbowstl51pow7mo71gbh.png" alt="Actual PDF excerpts before and after the font selection change" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure: excerpts from the generated PDFs before and after changing the font selection. The comparison shows the effect on this sample; it does not identify the underlying rendering cause.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A text check alone would not distinguish a PDF text-mapping issue from a visual glyph issue. Inspecting the rendered page gave a second piece of evidence. Conversely, a page can look readable while its extractable text is wrong, which matters when users copy, search, or further process the PDF.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspect page breaks and headings separately
&lt;/h2&gt;

&lt;p&gt;The corrected parts list contained 60 rows across 3 pages, including its final marker. Review every page to see where rows end, whether characters are clipped, and whether the next page still makes sense.&lt;/p&gt;

&lt;p&gt;The first version placed the column headings in &lt;code&gt;&amp;lt;thead&amp;gt;&lt;/code&gt; and requested &lt;code&gt;display: table-header-group&lt;/code&gt;. Those headings did not appear in the tested output. Moving the initial labels into a &lt;code&gt;&amp;lt;tbody&amp;gt;&lt;/code&gt; row restored the first heading row; this example has &lt;strong&gt;not&lt;/strong&gt; established repeated headings on later pages.&lt;/p&gt;

&lt;p&gt;If repeating headings or a particular page margin is required, treat it as a separate acceptance condition. Do not infer it from the fact that the PDF has three pages. CSS rules are input intentions until the generated document shows that they took effect.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/6jihe5qugeampplej8s4.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7oi3eqpoyq73vrb2grk1.png" alt="Results and the distinction between text and visual checks" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure: checks after correcting the font selection and initial table headings. Passing text checks does not certify the entire layout.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sample&lt;/th&gt;
&lt;th&gt;Pages in this run&lt;/th&gt;
&lt;th&gt;Required text&lt;/th&gt;
&lt;th&gt;Further review&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Short sheet&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Passed&lt;/td&gt;
&lt;td&gt;Look for clipping and check the price&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multilingual labels&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Passed after font change&lt;/td&gt;
&lt;td&gt;Inspect accents, Chinese glyphs, degree and euro symbols&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parts list&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Passed after heading change&lt;/td&gt;
&lt;td&gt;Check all rows and page boundaries; repeated headings remain a separate requirement&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Check that invalid exports fail
&lt;/h2&gt;

&lt;p&gt;Remove &lt;code&gt;&amp;lt;p&amp;gt;MODEL-Q7&amp;lt;/p&amp;gt;&lt;/code&gt; from a copy of &lt;code&gt;simple.html&lt;/code&gt; and save it as &lt;code&gt;missing-model.html&lt;/code&gt;. Add this entry to &lt;code&gt;cases.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"missing-model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"expect"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"MODEL-Q7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"24 V"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"128.50 USD"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"minPages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"maxPages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"negative"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate the altered input explicitly with &lt;code&gt;node generate.mjs missing-model&lt;/code&gt;. The normal report excludes cases marked &lt;code&gt;negative&lt;/code&gt;; check the altered PDF against the original requirements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// test-negative.mjs&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:assert/strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:fs/promises&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;inspect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./check.mjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cases.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;results/missing-model.pdf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;simple&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MODEL-Q7&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;results/long-table.pdf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;long-table&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;maxPages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pagesOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PASS: altered input and an invalid page range are both rejected.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;node test-negative.mjs&lt;/code&gt;. It should reject both the PDF with its model label removed and the three-page PDF checked against a one-page limit. These cases test the checker's failure paths.&lt;/p&gt;

&lt;p&gt;The scripts were tested locally. To use them in CI, have the job fail when the checker returns a nonzero exit status, and keep visual review as a separate acceptance step.&lt;/p&gt;

&lt;p&gt;Before accepting an export, require the generation response to succeed, the content checks to pass, and the rendered pages to meet the actual delivery requirements. Keep the sample HTML, downloaded PDFs, rules, and report together so the next change can be checked against the same expectations.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>testing</category>
      <category>pdf</category>
    </item>
    <item>
      <title>2026 PDF Generation API Comprehensive Comparison Review: 13 Mainstream Solutions Benchmarked (HTML to PDF)</title>
      <dc:creator>Derek</dc:creator>
      <pubDate>Fri, 26 Jun 2026 03:05:20 +0000</pubDate>
      <link>https://dev.to/derek-compdf/2026-pdf-generation-api-comprehensive-comparison-review-13-mainstream-solutions-benchmarked-html-2l38</link>
      <guid>https://dev.to/derek-compdf/2026-pdf-generation-api-comprehensive-comparison-review-13-mainstream-solutions-benchmarked-html-2l38</guid>
      <description>&lt;p&gt;By 2026, the PDF generation API market has evolved from "can it generate" to "does it generate well, fast, and securely." There are over 20 solutions on the market, ranging from a few euros per month for lightweight APIs to enterprise-grade SDKs, with price differences exceeding 100x.&lt;/p&gt;

&lt;p&gt;This article provides a horizontal comparison of 13 mainstream PDF Generation APIs across &lt;strong&gt;six core dimensions — rendering quality, developer experience, performance &amp;amp; stability, data security, pricing, and additional features&lt;/strong&gt; — to help technical teams make optimal selections.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 If you're evaluating PDF generation solutions, check out &lt;a href="https://www.compdf.com/pdf-sdk/pdf-generation?utm_source=dev.to&amp;amp;utm_medium=dev.to_generate_api_20260626&amp;amp;utm_campaign=dev.to_generate_api_20260626&amp;amp;ref_platform_id=dev.to"&gt;&lt;strong&gt;ComPDF Generation API&lt;/strong&gt;&lt;/a&gt; for an enterprise-grade PDF SDK that integrates viewing, editing, generation, and conversion in one package.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Participating Products Overview
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;Company/Background&lt;/th&gt;
&lt;th&gt;Core Positioning&lt;/th&gt;
&lt;th&gt;Starting Price (Official)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ComPDF Generation API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PDF Technologies (KDAN)&lt;/td&gt;
&lt;td&gt;Enterprise PDF Generation SDK + API&lt;/td&gt;
&lt;td&gt;Free 200 requests/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDFGeneratorAPI&lt;/td&gt;
&lt;td&gt;Actual Reports (Estonia)&lt;/td&gt;
&lt;td&gt;Enterprise Document Automation&lt;/td&gt;
&lt;td&gt;€80/year (50 credits)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CraftMyPDF&lt;/td&gt;
&lt;td&gt;Independent Team (Singapore)&lt;/td&gt;
&lt;td&gt;Drag-and-Drop Template Editor&lt;/td&gt;
&lt;td&gt;$0/month (50 PDFs)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DocRaptor&lt;/td&gt;
&lt;td&gt;Expected Behavior (USA)&lt;/td&gt;
&lt;td&gt;Highest CSS Fidelity (PrinceXML)&lt;/td&gt;
&lt;td&gt;Free (5 watermarked docs/month), $15/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orshot&lt;/td&gt;
&lt;td&gt;Independent Team&lt;/td&gt;
&lt;td&gt;Templates + API, supports images &amp;amp; video&lt;/td&gt;
&lt;td&gt;30 free, $39/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;APITemplate.io&lt;/td&gt;
&lt;td&gt;Independent Team&lt;/td&gt;
&lt;td&gt;Visual + HTML Dual Editor&lt;/td&gt;
&lt;td&gt;$0/month (50 PDFs), $19/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDFMonkey&lt;/td&gt;
&lt;td&gt;Independent Team (France)&lt;/td&gt;
&lt;td&gt;Lightweight HTML Templates&lt;/td&gt;
&lt;td&gt;€0/month (20 docs), €5/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDFShift&lt;/td&gt;
&lt;td&gt;Independent Team&lt;/td&gt;
&lt;td&gt;Minimalist HTML-to-PDF&lt;/td&gt;
&lt;td&gt;50 free requests/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Api2Pdf&lt;/td&gt;
&lt;td&gt;Independent Team&lt;/td&gt;
&lt;td&gt;Pay-per-use, no monthly fee&lt;/td&gt;
&lt;td&gt;$1/month + usage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IronPDF&lt;/td&gt;
&lt;td&gt;Iron Software (USA)&lt;/td&gt;
&lt;td&gt;.NET Ecosystem PDF Library&lt;/td&gt;
&lt;td&gt;$749/year&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nutrient DWS&lt;/td&gt;
&lt;td&gt;Nutrient (formerly PSPDFKit)&lt;/td&gt;
&lt;td&gt;PDF Generation API&lt;/td&gt;
&lt;td&gt;50 free requests/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apryse&lt;/td&gt;
&lt;td&gt;Apryse (formerly PDFTron)&lt;/td&gt;
&lt;td&gt;Enterprise PDF SDK&lt;/td&gt;
&lt;td&gt;Contact sales (starting from $1,500)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adobe Document Generation API&lt;/td&gt;
&lt;td&gt;Adobe&lt;/td&gt;
&lt;td&gt;Cloud Document Generation&lt;/td&gt;
&lt;td&gt;Usage-based pricing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Six-Dimension In-Depth Comparison
&lt;/h2&gt;

&lt;h3&gt;
  
  
  I. Rendering Quality &amp;amp; Layout Control
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;Rendering Engine&lt;/th&gt;
&lt;th&gt;Pagination Control&lt;/th&gt;
&lt;th&gt;CJK Fonts&lt;/th&gt;
&lt;th&gt;Headers/Footers&lt;/th&gt;
&lt;th&gt;Page Numbers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ComPDF Generation API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTML/CSS Rendering&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;✓&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Customizable&lt;/td&gt;
&lt;td&gt;Dynamic page numbers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDFGeneratorAPI&lt;/td&gt;
&lt;td&gt;Chrome-based&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CraftMyPDF&lt;/td&gt;
&lt;td&gt;Drag-and-Drop Template + Browser Render&lt;/td&gt;
&lt;td&gt;Configured in templates&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Configured in templates&lt;/td&gt;
&lt;td&gt;Configured in templates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DocRaptor&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;PrinceXML Engine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full support (forced breaks, footnotes, etc.)&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orshot&lt;/td&gt;
&lt;td&gt;Drag-and-Drop Template + Browser Render&lt;/td&gt;
&lt;td&gt;Configured in templates&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;APITemplate.io&lt;/td&gt;
&lt;td&gt;HTML/CSS/JS&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDFMonkey&lt;/td&gt;
&lt;td&gt;Chrome Rendering Engine&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Depends on font installation&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDFShift&lt;/td&gt;
&lt;td&gt;Chromium Rendering&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Api2Pdf&lt;/td&gt;
&lt;td&gt;Chrome / wkhtmltopdf / LibreOffice&lt;/td&gt;
&lt;td&gt;Varies by engine&lt;/td&gt;
&lt;td&gt;Varies by engine&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IronPDF&lt;/td&gt;
&lt;td&gt;Chrome Rendering&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nutrient DWS&lt;/td&gt;
&lt;td&gt;HTML/CSS Rendering&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apryse&lt;/td&gt;
&lt;td&gt;Proprietary Rendering Engine&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Native CJK support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adobe API&lt;/td&gt;
&lt;td&gt;Adobe Proprietary Engine&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: "—" indicates the information was not explicitly listed on the official website.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  II. Developer Experience &amp;amp; Ease of Use
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;Visual Editor&lt;/th&gt;
&lt;th&gt;HTML Templates&lt;/th&gt;
&lt;th&gt;Official SDK / Libraries&lt;/th&gt;
&lt;th&gt;Free Testing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ComPDF Generation API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Java, .NET, C++, PHP, Python, Web, etc.&lt;/td&gt;
&lt;td&gt;30-day trial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDFGeneratorAPI&lt;/td&gt;
&lt;td&gt;WYSIWYG Drag-and-Drop&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Python, PHP, Java, Ruby, Node&lt;/td&gt;
&lt;td&gt;Unlimited sandbox testing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CraftMyPDF&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Mature Drag-and-Drop Editor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;REST API&lt;/td&gt;
&lt;td&gt;50 PDFs/month free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DocRaptor&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;HTML/CSS/JS&lt;/td&gt;
&lt;td&gt;Python, Ruby, PHP, Java, .NET, etc.&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Unlimited watermarked testing&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orshot&lt;/td&gt;
&lt;td&gt;Canva-style Editor&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Node, Python, Ruby, PHP&lt;/td&gt;
&lt;td&gt;30 free requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;APITemplate.io&lt;/td&gt;
&lt;td&gt;WYSIWYG + HTML&lt;/td&gt;
&lt;td&gt;Supported (Jinja2)&lt;/td&gt;
&lt;td&gt;Python, PHP, Java, C#, JS&lt;/td&gt;
&lt;td&gt;50 PDFs/month free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDFMonkey&lt;/td&gt;
&lt;td&gt;Basic Editor&lt;/td&gt;
&lt;td&gt;HTML/CSS&lt;/td&gt;
&lt;td&gt;REST API&lt;/td&gt;
&lt;td&gt;20 docs/month free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDFShift&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Pure HTML&lt;/td&gt;
&lt;td&gt;REST API&lt;/td&gt;
&lt;td&gt;50 requests/month free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Api2Pdf&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Pure HTML&lt;/td&gt;
&lt;td&gt;REST API&lt;/td&gt;
&lt;td&gt;Pay-per-use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IronPDF&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;HTML/CSS&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;.NET (C#, F#, VB.NET)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;30-day trial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nutrient DWS&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;HTML/CSS&lt;/td&gt;
&lt;td&gt;Python, TypeScript, Java, C#, PHP&lt;/td&gt;
&lt;td&gt;50 requests/month free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apryse&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;HTML/CSS&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Full-platform SDK&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Contact sales&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adobe API&lt;/td&gt;
&lt;td&gt;Adobe Editor&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Node, Java, Python&lt;/td&gt;
&lt;td&gt;Usage-based pricing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: DocRaptor offers unlimited watermarked test documents, ideal for large-scale debugging during development. ComPDF's SDK coverage is the broadest (including Web/Desktop/Mobile + Server-side) and supports on-premises deployment. CraftMyPDF and Orshot's visual editors are most friendly for non-technical teams.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  III. Performance &amp;amp; Stability
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;Deployment&lt;/th&gt;
&lt;th&gt;SLA&lt;/th&gt;
&lt;th&gt;Regional Nodes&lt;/th&gt;
&lt;th&gt;Batch Generation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ComPDF Generation API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;On-Premises / Cloud API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Enterprise-grade&lt;/td&gt;
&lt;td&gt;On-premises or Cloud&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDFGeneratorAPI&lt;/td&gt;
&lt;td&gt;SaaS&lt;/td&gt;
&lt;td&gt;Enterprise-grade&lt;/td&gt;
&lt;td&gt;EU&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CraftMyPDF&lt;/td&gt;
&lt;td&gt;SaaS&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;US/EU/SG/AU&lt;/td&gt;
&lt;td&gt;Async supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DocRaptor&lt;/td&gt;
&lt;td&gt;SaaS&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;99.99%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;US&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orshot&lt;/td&gt;
&lt;td&gt;SaaS&lt;/td&gt;
&lt;td&gt;99.98%&lt;/td&gt;
&lt;td&gt;US/EU&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;APITemplate.io&lt;/td&gt;
&lt;td&gt;SaaS&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;US/EU/SG/AU&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Parallel supported&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDFMonkey&lt;/td&gt;
&lt;td&gt;SaaS&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;EU&lt;/td&gt;
&lt;td&gt;Webhook supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDFShift&lt;/td&gt;
&lt;td&gt;SaaS&lt;/td&gt;
&lt;td&gt;99.99%&lt;/td&gt;
&lt;td&gt;US/EU&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Api2Pdf&lt;/td&gt;
&lt;td&gt;SaaS&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;US/EU&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IronPDF&lt;/td&gt;
&lt;td&gt;On-Premises&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Local&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nutrient DWS&lt;/td&gt;
&lt;td&gt;SaaS + On-Premises&lt;/td&gt;
&lt;td&gt;Enterprise-grade&lt;/td&gt;
&lt;td&gt;US/EU&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apryse&lt;/td&gt;
&lt;td&gt;On-Premises / SaaS&lt;/td&gt;
&lt;td&gt;Enterprise-grade&lt;/td&gt;
&lt;td&gt;On-premises or Cloud&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adobe API&lt;/td&gt;
&lt;td&gt;SaaS&lt;/td&gt;
&lt;td&gt;Adobe SLA&lt;/td&gt;
&lt;td&gt;Global&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key Insight&lt;/strong&gt;: ComPDF, IronPDF, Nutrient DWS, and Apryse support on-premises deployment, suitable for enterprises with extreme performance and data security requirements. DocRaptor and PDFShift publicly commit to 99.99% SLA, making them the most reliable among SaaS solutions.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  IV. Data Security &amp;amp; Compliance
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;Data Retention&lt;/th&gt;
&lt;th&gt;GDPR&lt;/th&gt;
&lt;th&gt;HIPAA&lt;/th&gt;
&lt;th&gt;SOC 2&lt;/th&gt;
&lt;th&gt;ISO 27001&lt;/th&gt;
&lt;th&gt;On-Premises&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ComPDF Generation API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Full user control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Supported&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDFGeneratorAPI&lt;/td&gt;
&lt;td&gt;Configurable&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;ISO 27001&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CraftMyPDF&lt;/td&gt;
&lt;td&gt;Configurable&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;SOC 2 Type II&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DocRaptor&lt;/td&gt;
&lt;td&gt;Encrypted transit &amp;amp; storage&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;SOC 2&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orshot&lt;/td&gt;
&lt;td&gt;Configurable&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;APITemplate.io&lt;/td&gt;
&lt;td&gt;Custom storage (BYOS)&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;SOC 2&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDFMonkey&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Auto-deleted after days&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GDPR compliant&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDFShift&lt;/td&gt;
&lt;td&gt;Generated and immediately destroyed&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Api2Pdf&lt;/td&gt;
&lt;td&gt;Deleted after generation&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IronPDF&lt;/td&gt;
&lt;td&gt;Processed locally&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Supported&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nutrient DWS&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Deleted after request completion&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;SOC 2&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Supported&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apryse&lt;/td&gt;
&lt;td&gt;User-controlled&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Supported&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adobe API&lt;/td&gt;
&lt;td&gt;Adobe-controlled&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Adobe Compliant&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Data Source&lt;/strong&gt;: Official security &amp;amp; compliance pages of each product. PDFMonkey's website explicitly states "no special compliance certifications"; Nutrient DWS officially states "does not store any documents."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  V. Pricing &amp;amp; Cost Structure
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;Billing Model&lt;/th&gt;
&lt;th&gt;Entry Price&lt;/th&gt;
&lt;th&gt;Medium Scale/Month&lt;/th&gt;
&lt;th&gt;Large Scale/Month&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ComPDF Generation API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;SDK License (perpetual/annual)&lt;/strong&gt; + &lt;strong&gt;API Call Packages&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Free 200 requests/month&lt;/td&gt;
&lt;td&gt;Contact sales&lt;/td&gt;
&lt;td&gt;Contact sales&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDFGeneratorAPI&lt;/td&gt;
&lt;td&gt;Credits (1 credit = 5 pages)&lt;/td&gt;
&lt;td&gt;€80/year (50 credits)&lt;/td&gt;
&lt;td&gt;€1,770/year (15K credits)&lt;/td&gt;
&lt;td&gt;€6,208/year (250K credits)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CraftMyPDF&lt;/td&gt;
&lt;td&gt;Per PDF/image&lt;/td&gt;
&lt;td&gt;$0/month (50)&lt;/td&gt;
&lt;td&gt;$99/month (12K)&lt;/td&gt;
&lt;td&gt;$499/month (150K)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DocRaptor&lt;/td&gt;
&lt;td&gt;Per document&lt;/td&gt;
&lt;td&gt;Free (5 watermarked) / $15 (125)&lt;/td&gt;
&lt;td&gt;$75/month (1,250)&lt;/td&gt;
&lt;td&gt;$1,000/month (40K)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orshot&lt;/td&gt;
&lt;td&gt;Credits (1 credit = 1 page/1s video)&lt;/td&gt;
&lt;td&gt;30 free / $39 (1.5K)&lt;/td&gt;
&lt;td&gt;$160/month (20K)&lt;/td&gt;
&lt;td&gt;$349/month (75K)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;APITemplate.io&lt;/td&gt;
&lt;td&gt;Per PDF/image&lt;/td&gt;
&lt;td&gt;$0/month (50)&lt;/td&gt;
&lt;td&gt;$69/month (12K PDFs)&lt;/td&gt;
&lt;td&gt;$139/month (25K PDFs)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDFMonkey&lt;/td&gt;
&lt;td&gt;Per document&lt;/td&gt;
&lt;td&gt;€0/month (20)&lt;/td&gt;
&lt;td&gt;€15/month (3K)&lt;/td&gt;
&lt;td&gt;€300/month (60K)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDFShift&lt;/td&gt;
&lt;td&gt;Credits&lt;/td&gt;
&lt;td&gt;50 free/month&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Contact sales&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Api2Pdf&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Pay-per-use ($1/month + usage)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~$1.49/month (1,000 PDFs)&lt;/td&gt;
&lt;td&gt;Contact sales&lt;/td&gt;
&lt;td&gt;Contact sales&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IronPDF&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Annual subscription (per developer)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$749/year&lt;/td&gt;
&lt;td&gt;$1,499/year&lt;/td&gt;
&lt;td&gt;Contact sales&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nutrient DWS&lt;/td&gt;
&lt;td&gt;Credits&lt;/td&gt;
&lt;td&gt;50 free/month&lt;/td&gt;
&lt;td&gt;Contact sales&lt;/td&gt;
&lt;td&gt;Contact sales&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apryse&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;SDK License (modular)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Starting from $1,500&lt;/td&gt;
&lt;td&gt;Contact sales&lt;/td&gt;
&lt;td&gt;Contact sales&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adobe API&lt;/td&gt;
&lt;td&gt;Per document/page&lt;/td&gt;
&lt;td&gt;Usage-based&lt;/td&gt;
&lt;td&gt;Contact Adobe&lt;/td&gt;
&lt;td&gt;Contact Adobe&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: All prices above are publicly available on each product's official website (collected June 2026). ComPDF and Apryse use SDK licensing models, which become significantly more cost-effective than pay-per-use plans at scale.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Comprehensive Recommendations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  By Use Case
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Recommended Solution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;On-Premises Deployment + Enterprise Full-Stack PDF&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;ComPDF Generation API&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Finance, Healthcare, Government (On-Premises)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;ComPDF Generation API&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Highest CSS Fidelity / Print-Grade Output&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DocRaptor / ComPDF Generation API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Non-Technical Teams: Drag-and-Drop Template Design&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CraftMyPDF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Low-Cost Entry / Small Scale&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PDFMonkey&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;End-to-End Document Processing (Generation + OCR + Signatures)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ComPDF Generation API / Apryse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deep .NET Ecosystem Integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ComPDF Generation API / IronPDF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Adobe Ecosystem Users&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Adobe Document Generation API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Flexible Pay-Per-Use&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ComPDF Generation API / Api2Pdf&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Why ComPDF Generation API Deserves Special Attention?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;On-Premises Deployment&lt;/strong&gt;: Full on-premises support — data never leaves the enterprise network, meeting the strictest compliance requirements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full-Platform SDK&lt;/strong&gt;: Java / .NET / C++ / PHP / Python / Web / Windows / Mac / Android / iOS / Flutter / React Native —全覆盖&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full-Stack Coverage&lt;/strong&gt;: Beyond PDF generation, provides viewing, annotation, editing, conversion, forms, e-signatures, AI parsing, and more — a complete PDF solution in one SDK&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost Advantage at Scale&lt;/strong&gt;: SDK licensing becomes significantly more economical than pay-per-use SaaS APIs as volume grows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local Rendering, Low Latency&lt;/strong&gt;: PDF rendering is entirely local with on-premises deployment, ideal for latency-sensitive scenarios&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;There is no one-size-fits-all answer for PDF Generation API selection. The decision depends on four key factors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data Sensitivity&lt;/strong&gt; → Determines whether on-premises deployment is needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technical Team Capability&lt;/strong&gt; → Determines preference for visual editors vs. code-based templates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generation Volume&lt;/strong&gt; → Determines whether pay-per-use or SDK licensing is more cost-effective&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature Requirements&lt;/strong&gt; → Determines whether a full PDF suite or generation-only is needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For enterprises with high data security requirements, full-platform SDK coverage, or large-scale stable PDF generation needs, &lt;a href="https://www.compdf.com/pdf-sdk/pdf-generation?utm_source=dev.to&amp;amp;utm_medium=dev.to_generate_api_20260626&amp;amp;utm_campaign=dev.to_generate_api_20260626&amp;amp;ref_platform_id=dev.to"&gt;&lt;strong&gt;ComPDF Generation API&lt;/strong&gt;&lt;/a&gt; is one of the strongest all-around choices currently available.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Data in this article is sourced from official product websites, collected in June 2026. Prices may change with product updates; please refer to each official website for the latest pricing.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>html</category>
      <category>pdf</category>
    </item>
    <item>
      <title>AI Document Parsing in Practice: A Guide to Extracting Information from Complex PDFs</title>
      <dc:creator>Derek</dc:creator>
      <pubDate>Tue, 16 Jun 2026 03:44:55 +0000</pubDate>
      <link>https://dev.to/derek-compdf/ai-document-parsing-in-practice-a-guide-to-extracting-information-from-complex-pdfs-2p53</link>
      <guid>https://dev.to/derek-compdf/ai-document-parsing-in-practice-a-guide-to-extracting-information-from-complex-pdfs-2p53</guid>
      <description>&lt;p&gt;Traditional PDF parsing tools often struggle when faced with multi-column layouts, merged tables, or scanned documents. They can only "see" pixels and text fragments, but cannot "understand" the logical structure of a document. With breakthroughs in AI technology—especially in layout analysis and semantic understanding—this challenge is being completely rewritten. This article first analyzes common challenges of complex PDFs, then reveals a brand-new AI-driven parsing workflow.&lt;/p&gt;

&lt;p&gt;Try ComPDF AI &lt;a href="https://www.compdf.com/demo/idp/document-parsing?utm_source=dev.to&amp;amp;utm_medium=dev.to_ai_parsing_20260616&amp;amp;utm_campaign=dev.to_ai_parsing_20260616&amp;amp;ref_platform_id=dev.to"&gt;Online PDF Document Parsing Tool&lt;/a&gt; to experience the precision restoration enabled by intelligent parsing.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Common Types and Challenges of Complex PDF Documents
&lt;/h2&gt;

&lt;p&gt;Not all PDFs can have their content easily extracted. Based on real-world scenarios, complex PDFs typically fall into the following categories:&lt;/p&gt;

&lt;h3&gt;
  
  
  1.1 Scanned/Image-Based PDFs
&lt;/h3&gt;

&lt;p&gt;These PDFs are essentially collections of images. Page content is generated by scanners or cameras, making text unselectable and unsearchable. While traditional OCR can recognize text, its accuracy drops significantly when dealing with low resolution, skewed angles, or watermark interference.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 PDFs with Complex Tables
&lt;/h3&gt;

&lt;p&gt;Table data represents a high-difficulty scenario in information extraction. Merged cells, continued跨页 tables, borderless tables, and nested tables—these structures are highly prone to misalignment when converted to Word or Excel, completely altering the meaning of the data.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.3 Multi-Column/Mixed Layout PDFs
&lt;/h3&gt;

&lt;p&gt;Academic papers, newspapers, and product manuals often use multi-column layouts, where text flows from the bottom of the left column to the top of the right column. Traditional extraction tools cannot understand reading order, often producing scrambled output.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.4 Form-Based PDFs
&lt;/h3&gt;

&lt;p&gt;Forms containing text fields, checkboxes, and dropdown menus require not only text content recognition but also an understanding of the meaning and state of interactive controls.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.5 Encrypted/Restricted PDFs
&lt;/h3&gt;

&lt;p&gt;Some PDFs have printing or copying permissions set, requiring restrictions to be lifted before content can be extracted.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Traditional vs AI Solutions: What's the Fundamental Difference?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Traditional OCR/Rule-Based Extraction&lt;/th&gt;
&lt;th&gt;AI-Driven Parsing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Approach&lt;/td&gt;
&lt;td&gt;Pixel recognition + fixed template matching&lt;/td&gt;
&lt;td&gt;Semantic understanding + layout analysis + structure restoration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Layout Adaptability&lt;/td&gt;
&lt;td&gt;Depends on fixed templates, breaks with layout changes&lt;/td&gt;
&lt;td&gt;Self-adapts to different layouts, no preset templates needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output Quality&lt;/td&gt;
&lt;td&gt;Plain text strings, loses structure and hierarchy&lt;/td&gt;
&lt;td&gt;Fully restores heading hierarchy, tables, lists and other structures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Table Handling&lt;/td&gt;
&lt;td&gt;Prone to misalignment, lost merged cells&lt;/td&gt;
&lt;td&gt;Accurately identifies cell merging and跨页 continued tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output Formats&lt;/td&gt;
&lt;td&gt;Primarily TXT&lt;/td&gt;
&lt;td&gt;Structured output in Markdown / JSON / Excel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Downstream Integration&lt;/td&gt;
&lt;td&gt;Requires extensive secondary development for data cleaning&lt;/td&gt;
&lt;td&gt;Direct connection to RAG systems, LLM training, and other downstream tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In short: Traditional OCR "sees" text, AI parsing "understands" the document.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. In Practice: A Universal AI Workflow for Complex PDF Parsing
&lt;/h2&gt;

&lt;p&gt;Regardless of the tool used, information extraction from complex PDFs typically follows this standardized process:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Document Ingestion
&lt;/h3&gt;

&lt;p&gt;Supports batch upload of multiple formats including PDFs, images, and scanned documents. In enterprise scenarios, processing hundreds of documents at a time is the norm, making batch capability and processing speed especially important.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Layout Analysis and Structural Restoration
&lt;/h3&gt;

&lt;p&gt;This is the core of AI parsing. The system automatically identifies heading levels, paragraphs, tables, images, headers, footers, and other elements within the page, reconstructs the document's logical reading order, and outputs structured data.&lt;/p&gt;

&lt;p&gt;Key technical aspects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Layout Analysis&lt;/strong&gt;: Identifies regions such as text blocks, tables, images, and formulas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reading Order Restoration&lt;/strong&gt;: Understands the correct reading order for multi-column and mixed text-image layouts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Table Structure Restoration&lt;/strong&gt;: Identifies cell boundaries, merge relationships, and跨页 continued tables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mathematical Formula Recognition&lt;/strong&gt;: Converts formula images into editable LaTeX format&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3: Data Validation
&lt;/h3&gt;

&lt;p&gt;Parsing results typically provide a visual comparison interface, with the original document on the left and parsed results on the right, synchronized with highlighting. Supports manual verification and real-time corrections to ensure zero-error critical information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Output and Application
&lt;/h3&gt;

&lt;p&gt;Structured data can be exported in Markdown, JSON, Excel, and other formats, directly used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RAG Knowledge Base Construction&lt;/strong&gt;: Import parsed documents into vector databases to build queryable enterprise knowledge bases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM Training Corpora&lt;/strong&gt;: High-quality PDF parsing results provide clean data sources for model fine-tuning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Middle Platform Input&lt;/strong&gt;: Integrate with ERP, CRM, and other business systems for automated data flow&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Recommended Tool: ComPDF AI Intelligent Document Parsing
&lt;/h2&gt;

&lt;p&gt;Among numerous PDF parsing tools, &lt;strong&gt;ComPDF AI&lt;/strong&gt;'s &lt;a href="https://www.compdf.com/pdf-sdk/data-extraction?utm_source=dev.to&amp;amp;utm_medium=dev.to_ai_parsing_20260616&amp;amp;utm_campaign=dev.to_ai_parsing_20260616&amp;amp;ref_platform_id=dev.to"&gt;Intelligent Document Parsing&lt;/a&gt; feature stands out as an efficient choice for handling complex PDFs, thanks to its deep optimization in layout restoration and semantic understanding. The following uses ComPDF AI as an example to demonstrate the actual complex PDF parsing workflow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5zyv5qjuduxh24cs1c2c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5zyv5qjuduxh24cs1c2c.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 1: Scanned Contract Parsing
&lt;/h3&gt;

&lt;p&gt;A company received a scanned PDF contract (50 pages) containing handwritten annotations, company seals, and dual-column clauses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Traditional approach&lt;/strong&gt;: Manual reading and entry of key clauses, approximately 3 hours, with high risk of missing details.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ComPDF AI approach&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enter the "Intelligent Document Parsing" page, upload the scanned contract PDF/image&lt;/li&gt;
&lt;li&gt;The system automatically performs OCR + AI layout analysis, identifying all text regions and restoring logical structure&lt;/li&gt;
&lt;li&gt;Within seconds, the left side displays the original PDF, the right side shows the parsed structured Markdown content&lt;/li&gt;
&lt;li&gt;Click anywhere on the original text, and the right-side parsed result synchronously highlights the corresponding paragraph for easy verification&lt;/li&gt;
&lt;li&gt;Download the parsing results for direct use in subsequent clause analysis&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Scenario 2: Financial Report PDF with Complex Tables
&lt;/h3&gt;

&lt;p&gt;An annual financial report PDF contains dozens of financial tables—multi-level headers, merged cells,跨页 continued tables, and numerical alignment formats—demanding extremely high parsing accuracy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ComPDF AI processing results&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initiates AI table recognition&lt;/li&gt;
&lt;li&gt;Automatically identifies header hierarchy and merge relationships&lt;/li&gt;
&lt;li&gt;Automatically splices跨页 tables with no data loss&lt;/li&gt;
&lt;li&gt;Outputs JSON format, with numerical fields retaining original precision, ready for direct import into analysis systems&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Scenario 3: Batch Parsing of Multi-Column Academic Papers
&lt;/h3&gt;

&lt;p&gt;A research team needs to batch parse 200 PDF papers to build a literature knowledge base.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ComPDF AI solution&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Batch upload 200 PDFs, system automatically queues processing&lt;/li&gt;
&lt;li&gt;AI layout analysis accurately identifies and restores multi-column text&lt;/li&gt;
&lt;li&gt;Each paper is parsed into Markdown format, preserving heading hierarchy, references, and figure captions, accurately recognizing 30+ document tags&lt;/li&gt;
&lt;li&gt;Parsing results are imported into RAG systems (e.g., LlamaIndex/LangChain) to build a queryable literature knowledge base&lt;/li&gt;
&lt;li&gt;Researchers can directly ask questions, and AI provides citation-backed answers based on the parsed original text&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Scenario 4: Mixed Layout Product Manual Processing
&lt;/h3&gt;

&lt;p&gt;A product manual contains text descriptions, product specification tables, installation diagrams, and flowcharts—multiple elements interwoven with high layout flexibility.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ComPDF AI advantages&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatic separation of text and images, with tables independently outputting structured data&lt;/li&gt;
&lt;li&gt;Precise recognition of text labels within flowcharts&lt;/li&gt;
&lt;li&gt;Supports exporting multiple formats (Markdown/JSON/TXT) to suit different downstream needs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. Advanced: From Document Parsing to Intelligent Knowledge Base
&lt;/h2&gt;

&lt;p&gt;The ultimate goal of PDF parsing is often not just "getting the text," but making the knowledge within documents fully usable.&lt;/p&gt;

&lt;p&gt;ComPDF AI provides end-to-end capabilities from document parsing to knowledge base application:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Document Upload → AI Layout Parsing → Semantic Chunking → Store in Knowledge Base → AI Q&amp;amp;A&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Building an Enterprise Private Knowledge Base&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;Import parsed document data into the &lt;strong&gt;ComPDF AI Intelligent Knowledge Base&lt;/strong&gt;, supporting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;10 Chunking Strategies&lt;/strong&gt;: General, Q&amp;amp;A, Legal Documents, Papers, Books, etc., optimized for different document types&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Model Integration&lt;/strong&gt;: Seamlessly connect with ChatGPT, DeepSeek, Gemini, Qwen, Llama, and other mainstream LLMs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permission Management&lt;/strong&gt;: Granular control over team members' viewing and management permissions to ensure data security&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Precise Key Information Extraction
&lt;/h3&gt;

&lt;p&gt;For business documents such as invoices, contracts, and insurance policies, ComPDF AI's &lt;strong&gt;Intelligent Document Extraction&lt;/strong&gt; feature, based on NLP and KVP (Key-Value Pair) technology, can directly output JSON/Excel/CSV structured data, connecting with RPA, ERP, CRM, and other systems for automated information entry.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Conclusion
&lt;/h2&gt;

&lt;p&gt;From traditional OCR that could only "see" text, to AI parsing that can "understand" document structure and semantics—PDF information extraction technology has entered a new era.&lt;/p&gt;

&lt;p&gt;Whether it's scanned contracts, complex tables, multi-column papers, or mixed-layout manuals, intelligent document parsing tools represented by &lt;strong&gt;ComPDF AI&lt;/strong&gt; are transforming "manual word-by-word entry" into "one-click structured output":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High layout restoration accuracy, preserving the original document's logical hierarchy&lt;/li&gt;
&lt;li&gt;Precise table recognition, no跨页 merge misalignment&lt;/li&gt;
&lt;li&gt;Strong batch processing capability, suitable for enterprise scenarios&lt;/li&gt;
&lt;li&gt;Rich output formats, seamless integration with RAG and LLM training&lt;/li&gt;
&lt;li&gt;From parsing to knowledge base construction, forming a complete closed loop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're still struggling with the efficiency of extracting information from complex PDFs, why not try an AI-driven approach—leave the repetitive work to the tools, and give your time back to the work that truly needs thought.&lt;/p&gt;

</description>
      <category>pdf</category>
      <category>ai</category>
      <category>database</category>
      <category>development</category>
    </item>
    <item>
      <title>How to Extracte Accurate Unstructured Document Data: Smart Extraction &amp; Custom Extraction</title>
      <dc:creator>Derek</dc:creator>
      <pubDate>Fri, 12 Jun 2026 02:07:51 +0000</pubDate>
      <link>https://dev.to/derek-compdf/how-to-extracte-accurate-unstructured-document-data-smart-extraction-custom-extraction-9ng</link>
      <guid>https://dev.to/derek-compdf/how-to-extracte-accurate-unstructured-document-data-smart-extraction-custom-extraction-9ng</guid>
      <description>&lt;p&gt;In daily work, do you often encounter these scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Receiving dozens of invoices and manually entering each invoice number, amount, and date&lt;/li&gt;
&lt;li&gt;Piles of client contracts that require flipping through each one to extract key clauses&lt;/li&gt;
&lt;li&gt;Customs declarations, orders, insurance policies, etc. in varying formats — manual extraction is time-consuming and error-prone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These repetitive data entry tasks consume significant manpower and are highly prone to errors due to fatigue. ComPDF AI's &lt;a href="https://www.compdf.com/demo/idp/document-extraction?utm_source=dev.to&amp;amp;utm_medium=dev.to_ai_extract_20260612&amp;amp;utm_campaign=dev.to_ai_extract_20260612&amp;amp;ref_platform_id=dev.to"&gt;Smart Document Extraction&lt;/a&gt; feature is designed to solve precisely these pain points — leveraging semantic understanding, NLP, and Key-Value Pair (KVP) technology to accurately identify and capture key document information, efficiently transforming it into structured data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Extract Data from Unstructured Documents?
&lt;/h2&gt;

&lt;p&gt;According to IBM, approximately &lt;strong&gt;80%–90%&lt;/strong&gt; of enterprise-generated data is unstructured — PDF files, Word documents, emails, scanned documents, images, and more. While rich in information, this data &lt;strong&gt;lacks a predefined format and schema&lt;/strong&gt;, making it impossible to directly analyze and process like structured data in a database.&lt;/p&gt;

&lt;p&gt;The traditional approach is manual entry — inefficient and error-prone. While &lt;strong&gt;OCR (Optical Character Recognition)&lt;/strong&gt; can recognize text in images, it can only "see" characters without understanding the meaning or context.&lt;/p&gt;

&lt;p&gt;The core difference &lt;strong&gt;from traditional OCR to AI-driven Intelligent Document Processing (IDP)&lt;/strong&gt; :&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Traditional OCR&lt;/th&gt;
&lt;th&gt;AI Smart Extraction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Approach&lt;/td&gt;
&lt;td&gt;Text recognition&lt;/td&gt;
&lt;td&gt;Semantic understanding + key information localization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;Plain text / searchable PDF&lt;/td&gt;
&lt;td&gt;Structured Key-Value Pairs (KVP)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context Understanding&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;NLP-based document context understanding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Layout Adaptation&lt;/td&gt;
&lt;td&gt;Fixed template dependent&lt;/td&gt;
&lt;td&gt;Flexible adaptation to different layouts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output Format&lt;/td&gt;
&lt;td&gt;TXT / Word&lt;/td&gt;
&lt;td&gt;JSON / Excel / CSV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System Integration&lt;/td&gt;
&lt;td&gt;Requires secondary development&lt;/td&gt;
&lt;td&gt;Direct integration with RPA / ERP / CRM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ComPDF AI's Smart Document Extraction is an &lt;strong&gt;&lt;a href="https://www.compdf.com/demo/idp/document-extraction?utm_source=dev.to&amp;amp;utm_medium=dev.to_ai_extract_20260612&amp;amp;utm_campaign=dev.to_ai_extract_20260612&amp;amp;ref_platform_id=dev.to"&gt;AI-driven IDP solution&lt;/a&gt;&lt;/strong&gt; , not a simple OCR tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Extraction Methods for Standard and Special Documents
&lt;/h2&gt;

&lt;p&gt;AI-driven precise document data extraction typically follows these standardized steps to ensure accuracy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Document Input&lt;/strong&gt;: Upload PDFs, images, scanned documents, and other formats&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-Classification&lt;/strong&gt;: AI identifies the document type (invoice, contract, order, etc.) and automatically matches or recommends templates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart Extraction&lt;/strong&gt;: Based on NLP + KVP technology, accurately locates and extracts key fields&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human Verification&lt;/strong&gt;: Provides a visual review interface where users can edit and correct extraction results&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Output&lt;/strong&gt;: Export as JSON / Excel / CSV, or push directly to business systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ComPDF AI's Smart Document Extraction fully covers the above workflow, from upload to structured data output — an efficient closed loop.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Smart Extraction: Upload and Go, AI Auto-Recognizes
&lt;/h3&gt;

&lt;p&gt;The core of Smart Document Extraction is &lt;strong&gt;out-of-the-box usability&lt;/strong&gt;. You simply:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Enter Smart Document Extraction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;From the ComPDF AI homepage or left sidebar, click "Smart Document Extraction" to enter the feature page. In the template list on the left, the system includes built-in &lt;strong&gt;Order&lt;/strong&gt; and &lt;strong&gt;Invoice&lt;/strong&gt; templates covering most business scenarios.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Upload Files and Auto-Extract&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After uploading one or more files, the system automatically performs extraction based on your selected template. If no template is selected, the system intelligently identifies the file type and matches the most suitable template — no manual configuration needed, truly "upload and go."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Review and Confirm&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After extraction, click "Review" to enter the verification page. The original file is on the left and the extracted structured data is on the right — easy side-by-side comparison. You can directly edit, correct, or add new fields. Once confirmed, download as &lt;strong&gt;JSON, Excel, or CSV&lt;/strong&gt; to integrate directly with enterprise systems.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use cases: Automated data processing for standardized documents such as invoice recognition, order information archiving, insurance policy key field extraction, and identity document data collection.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp0wcdy6o39xv29a26knd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp0wcdy6o39xv29a26knd.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Custom Extraction: Flexible Configuration for Non-Standard Documents
&lt;/h3&gt;

&lt;p&gt;For special document types (e.g., internal reports, specific contract formats, industry-specific forms), ComPDF AI also supports &lt;strong&gt;custom templates&lt;/strong&gt; — click "Select Template" → "New Template" to configure extraction fields based on your needs.&lt;/p&gt;

&lt;p&gt;With custom templates, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Specify Key-Value Pair fields&lt;/strong&gt;: such as contract number, signing date, party A name, amount, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibly adapt different layouts&lt;/strong&gt;: accurately extract even when the same type of document has different layouts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team sharing&lt;/strong&gt;: created templates are reusable and team members can use them with one click&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Custom templates make ComPDF AI more than just a "standard document extractor" — it adapts to the special needs of various industries, from &lt;strong&gt;logistics bills of lading, financial account statements, medical record summaries, to legal case files&lt;/strong&gt;, precisely extracting needed information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvurc7q3pnpjekjl9ukr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvurc7q3pnpjekjl9ukr.png" alt=" " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Can Do with Extracted Data
&lt;/h2&gt;

&lt;p&gt;The extracted structured data (JSON / Excel / CSV) can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Seamlessly integrated with RPA, ERP, CRM systems&lt;/strong&gt; for automated data entry&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Used as a data middle-platform input source&lt;/strong&gt; to support analysis and decision-making&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch exported for archiving&lt;/strong&gt; to build a searchable structured database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Used as high-quality training data for AI large models&lt;/strong&gt; to support RAG (Retrieval-Augmented Generation) and improve knowledge base Q&amp;amp;A accuracy&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Choose ComPDF AI? — Traditional OCR vs. AI Smart Extraction
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Traditional OCR&lt;/th&gt;
&lt;th&gt;ComPDF AI Smart Extraction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Approach&lt;/td&gt;
&lt;td&gt;Text recognition (only "sees" characters)&lt;/td&gt;
&lt;td&gt;Semantic understanding + key information localization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;Plain text / searchable PDF&lt;/td&gt;
&lt;td&gt;Structured Key-Value Pairs (KVP)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context Understanding&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;NLP-based document context understanding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Layout Adaptation&lt;/td&gt;
&lt;td&gt;Fixed template dependent&lt;/td&gt;
&lt;td&gt;Flexible adaptation to different layouts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output Format&lt;/td&gt;
&lt;td&gt;TXT / Word&lt;/td&gt;
&lt;td&gt;JSON / Excel / CSV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System Integration&lt;/td&gt;
&lt;td&gt;Requires secondary development&lt;/td&gt;
&lt;td&gt;Easy integration with RPA / ERP / CRM&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;From traditional OCR to AI-driven intelligent document processing, from manual data entry to automated machine extraction, from standardized templates to custom configuration — ComPDF AI makes enterprise unstructured document data extraction simple, accurate, and efficient. In this data-driven era, leave repetitive work to AI and give your time back to more valuable tasks.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>pdf</category>
      <category>ocr</category>
    </item>
    <item>
      <title>2026 PDF Table Extraction Tools Review: 15 Tools Benchmarked</title>
      <dc:creator>Derek</dc:creator>
      <pubDate>Wed, 10 Jun 2026 11:01:45 +0000</pubDate>
      <link>https://dev.to/derek-compdf/2026-pdf-table-extraction-tools-review-15-tools-benchmarked-pl</link>
      <guid>https://dev.to/derek-compdf/2026-pdf-table-extraction-tools-review-15-tools-benchmarked-pl</guid>
      <description>&lt;p&gt;PDF was designed for "what you see is what you get" document exchange, not structured data — this creates fundamental barriers for table extraction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PDF lacks a table semantic model&lt;/strong&gt;: Tables in PDFs are merely collections of lines and characters, with no abstract definition of rows, columns, or cells&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex table structures pose typical challenges&lt;/strong&gt;: Merged cells, rotated text, cross-page tables, borderless tables — each of these scenarios causes most tools to produce severely degraded output&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scanned vs. text-based PDFs are fundamentally different&lt;/strong&gt;: The former relies on OCR engines to convert images to text, while the latter can directly parse the character encoding layer — each demands very different tool capabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article presents a horizontal benchmark of mainstream commercial SDKs/APIs, open-source tools, and free online tools for PDF table extraction, producing quantifiable comparison results based on unified test samples.&lt;/p&gt;




&lt;h2&gt;
  
  
  Test PDF Description
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Test File&lt;/strong&gt;: Scanned PDF (SEO: The Art of SEO 3rd Edition, English, Page 114)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fewn066lznl2yue3fui8a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fewn066lznl2yue3fui8a.png" alt=" " width="601" height="794"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Output note: Screenshot of original scanned test file (tools that cannot process scanned files were tested on other text-based PDF tables instead)&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Type&lt;/td&gt;
&lt;td&gt;Scanned (image-only, no text layer)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Page Size&lt;/td&gt;
&lt;td&gt;504 x 661.5 pt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embedded Image&lt;/td&gt;
&lt;td&gt;1008 x 1323 px, RGB, 8bit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chars/Lines/Rects&lt;/td&gt;
&lt;td&gt;0 / 0 / 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Actual Content&lt;/td&gt;
&lt;td&gt;1 table with 4 columns and multiple rows&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This sample represents a &lt;strong&gt;high-difficulty test scenario&lt;/strong&gt;: a scanned PDF with no text layer, semi-bordered table structure, and hierarchical headers. It requires tools to handle both OCR recognition and table structure reconstruction — most pure-parsing Python libraries cannot process it directly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Online PDF Table Extraction Tools
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Quick Selection Guide
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Recommended Tool&lt;/th&gt;
&lt;th&gt;Alternative&lt;/th&gt;
&lt;th&gt;Rationale&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Daily simple table to Excel&lt;/td&gt;
&lt;td&gt;SmallPDF&lt;/td&gt;
&lt;td&gt;iLovePDF&lt;/td&gt;
&lt;td&gt;Shortest workflow, upload and get results&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Privacy-sensitive documents&lt;/td&gt;
&lt;td&gt;PDF24&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Completely free, strong privacy protection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Developer API integration&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://www.compdf.com/demo/idp/table-extraction?utm_source=dev.to&amp;amp;utm_medium=dev.to_extract_pdftable_20260610&amp;amp;utm_campaign=dev.to_extract_pdftable_20260610&amp;amp;ref_platform_id=dev.to"&gt;ComPDF&lt;/a&gt; etc.&lt;/td&gt;
&lt;td&gt;PDFTables&lt;/td&gt;
&lt;td&gt;Provides REST API for programmable calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex tables (merged cells/hierarchical headers)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;ComPDF or other commercial SDKs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Online tools generally perform poorly on complex tables; professional SDKs with structure reconstruction are recommended&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  1. ExtractTable
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;URL&lt;/td&gt;
&lt;td&gt;&lt;a href="https://extracttable.com" rel="noopener noreferrer"&gt;extracttable.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type&lt;/td&gt;
&lt;td&gt;Cloud API + Web Demo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scanned Support&lt;/td&gt;
&lt;td&gt;Yes (OCR)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing&lt;/td&gt;
&lt;td&gt;Credit-based, from 50 credits/$3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Web demo supports images only (JPG/PNG); paid version supports PDF. Output format: CSV/Excel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test Results&lt;/strong&gt;: Due to demo limitations (images only, 2/day), full scanned PDF testing was not possible. Tested with image input instead — basic continuous text extraction was usable, but &lt;code&gt;=&lt;/code&gt; was misrecognized as &lt;code&gt;—&lt;/code&gt;, and bold styling, cell sizing, unordered lists, and other formatting were all lost.&lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://medium.com/@kramermark/i-tested-12-best-in-class-pdf-table-extraction-tools-and-the-results-were-appalling-f8a9991d972e" rel="noopener noreferrer"&gt;Mark Kramer's benchmark&lt;/a&gt;, ExtractTable has issues with merged cell content misalignment and missing data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtzpxvgap056o6na9ny5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtzpxvgap056o6na9ny5.png" alt=" " width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  2. SmallPDF
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;URL&lt;/td&gt;
&lt;td&gt;&lt;a href="https://smallpdf.com" rel="noopener noreferrer"&gt;smallpdf.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type&lt;/td&gt;
&lt;td&gt;Online web tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scanned Support&lt;/td&gt;
&lt;td&gt;Yes (OCR, Pro version, 7-day free trial)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free Limit&lt;/td&gt;
&lt;td&gt;2/day free, Pro $12/month&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Offers PDF to Excel conversion with easy workflow. However, recognition capability is limited with complex structures like merged cells and rotated text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test Results&lt;/strong&gt;: Performed well among online tools — table structure, merged cells, vertical text, and superscript/subscript were all effectively recognized.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fttu8f89sjt9oui1lo5xq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fttu8f89sjt9oui1lo5xq.png" alt=" " width="799" height="260"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  3. iLovePDF
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;URL&lt;/td&gt;
&lt;td&gt;&lt;a href="https://ilovepdf.com" rel="noopener noreferrer"&gt;ilovepdf.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type&lt;/td&gt;
&lt;td&gt;Online web tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scanned Support&lt;/td&gt;
&lt;td&gt;Yes (OCR, paid version)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free Limit&lt;/td&gt;
&lt;td&gt;2/hour free, Pro $6/month&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Offers PDF to Excel, PDF to Word, and multi-format conversion. Free version does not include OCR.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test Results&lt;/strong&gt;: OCR recognition accuracy was insufficient — some text areas were converted successfully, but significant content remained embedded as raw image slices within tables, failing to achieve true structured output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fypfgljoh3xce0y6z3cnm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fypfgljoh3xce0y6z3cnm.png" alt=" " width="511" height="564"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  4. PDF24 Tools
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;URL&lt;/td&gt;
&lt;td&gt;&lt;a href="https://tools.pdf24.org" rel="noopener noreferrer"&gt;tools.pdf24.org&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type&lt;/td&gt;
&lt;td&gt;Online + Desktop client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scanned Support&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Completely free&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;German-developed free PDF toolset offering PDF to Excel conversion with no file size limits and strong privacy protection. Limited support for complex table structures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test Results&lt;/strong&gt;: Scanned documents were not OCR-processed — images were embedded directly into Excel output. Table structure recognition failed, with text loss and incorrect cell merge logic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flag2ulkbzfdyocuq40sj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flag2ulkbzfdyocuq40sj.png" alt=" " width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  5. PDFTables.com (Online)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;URL&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.pdftables.com" rel="noopener noreferrer"&gt;pdftables.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type&lt;/td&gt;
&lt;td&gt;Online web + API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scanned Support&lt;/td&gt;
&lt;td&gt;No OCR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing&lt;/td&gt;
&lt;td&gt;Credit-based, $50/1000 pages&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Supports drag-and-drop upload and conversion. Good conversion quality for standard bordered tables, but &lt;strong&gt;does not support scanned documents&lt;/strong&gt; and has no free trial.&lt;/p&gt;




&lt;h2&gt;
  
  
  Commercial PDF Table Extraction Tools
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Quick Selection Guide
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Recommended Tool&lt;/th&gt;
&lt;th&gt;Rationale&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Complex merged cells/hierarchical headers/style preservation&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;ComPDF&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Only commercial SDK verified across hierarchical headers, merged cells, and style preservation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloud-native/high throughput/AWS ecosystem&lt;/td&gt;
&lt;td&gt;AWS Textract&lt;/td&gt;
&lt;td&gt;Deep AWS integration, pay-per-use, suitable for elastic throughput&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-platform SDK integration (Web/Mobile)&lt;/td&gt;
&lt;td&gt;ComPDF&lt;/td&gt;
&lt;td&gt;Native cross-platform SDK, suitable for embedding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Desktop occasional use&lt;/td&gt;
&lt;td&gt;Adobe Acrobat&lt;/td&gt;
&lt;td&gt;Most widely used PDF desktop tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enterprise full-stack document processing&lt;/td&gt;
&lt;td&gt;ComPDF&lt;/td&gt;
&lt;td&gt;Full-platform SDK, enterprise-grade private deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  1. ComPDF (Recommended)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Product&lt;/td&gt;
&lt;td&gt;ComPDF SDK / API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SDK Languages&lt;/td&gt;
&lt;td&gt;Python, Java, Go, iOS, Android, C#&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing&lt;/td&gt;
&lt;td&gt;Contact sales&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Core Capabilities&lt;/strong&gt; (Source: &lt;a href="https://www.compdf.com/demo/idp/table-extraction?utm_source=dev.to&amp;amp;utm_medium=dev.to_extract_pdftable_20260610&amp;amp;utm_campaign=dev.to_extract_pdftable_20260610&amp;amp;ref_platform_id=dev.to"&gt;ComPDF Official&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;ComPDF is one of the few commercial table extraction SDKs covering all three of the following capabilities:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability Dimension&lt;/th&gt;
&lt;th&gt;Support Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Table Type Coverage&lt;/td&gt;
&lt;td&gt;Bordered, irregular border, borderless tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex Merged Cells&lt;/td&gt;
&lt;td&gt;Cross-row/column merged cell structure reconstruction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content Preservation&lt;/td&gt;
&lt;td&gt;Simultaneous text and image extraction from cells&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Style Preservation&lt;/td&gt;
&lt;td&gt;Font/typeface/size/color/bold-italic full preservation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Third-Party Benchmark Context&lt;/strong&gt;: Mark Kramer from MITRE tested 12 mainstream tools in a horizontal benchmark, with the following conclusion (Source: &lt;a href="https://medium.com/@kramermark/i-tested-12-best-in-class-pdf-table-extraction-tools-and-the-results-were-appalling-f8a9991d972e" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Among all the commercial solutions, ComPDF was the only tool to correctly capture the hierarchical column headers."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Our Test Results:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5olsins7qvnpyni7h75l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5olsins7qvnpyni7h75l.png" alt=" " width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evaluation Item&lt;/th&gt;
&lt;th&gt;Conclusion&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hierarchical Merged Headers&lt;/td&gt;
&lt;td&gt;Correctly captured, best performance among all commercial tools tested&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Row/Column Merging&lt;/td&gt;
&lt;td&gt;Cross-row/column merge logic fully reconstructed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text Style&lt;/td&gt;
&lt;td&gt;Font, size, bold/italic largely preserved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Table Borders&lt;/td&gt;
&lt;td&gt;Correctly identified and reconstructed border positions and line styles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Row/Column Dimensions&lt;/td&gt;
&lt;td&gt;Column widths and row heights consistent with original PDF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Known Limitations&lt;/td&gt;
&lt;td&gt;Footnote attribution, superscript text, and rotated text recognition still need improvement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SDK Coverage&lt;/td&gt;
&lt;td&gt;6 languages (Python / Java / Go / iOS / Android / C#)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  2. AWS Textract
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Product&lt;/td&gt;
&lt;td&gt;Amazon Textract API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free Tier&lt;/td&gt;
&lt;td&gt;100 pages/month for new users (3 months) &lt;a href="https://aws.amazon.com/textract/pricing/" rel="noopener noreferrer"&gt;Pricing&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing&lt;/td&gt;
&lt;td&gt;$0.015/page (Table mode)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import boto3
client = boto3.client('textract')
response = client.analyze_document(
    Document={'S3Object': {'Bucket': 'my-bucket', 'Name': 'invoice.pdf'}},
    FeatureTypes=['TABLES', 'FORMS']
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Evaluation&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Basic Table Recognition&lt;/strong&gt;: Good performance on text-based PDF bordered tables, accurate structure reconstruction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scanned OCR&lt;/strong&gt;: Leveraging AWS's underlying OCR engine, scanned document processing is at the top tier of commercial APIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merged Cells&lt;/strong&gt;: Limited support; hierarchical header scenarios show deviations from individual cell outputs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ecosystem Integration&lt;/strong&gt;: Native connectivity with AWS services (S3/Lambda/SageMaker), suitable for teams with existing AWS infrastructure&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. Nanonets
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Product&lt;/td&gt;
&lt;td&gt;Nanonets API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing&lt;/td&gt;
&lt;td&gt;$0.10-0.30/run &lt;a href="https://www.nanonets.com/pricing/" rel="noopener noreferrer"&gt;Pricing&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Third-Party Reference&lt;/strong&gt;: &lt;a href="https://medium.com/@kramermark/i-tested-12-best-in-class-pdf-table-extraction-tools-and-the-results-were-appalling-f8a9991d972e" rel="noopener noreferrer"&gt;Mark Kramer's benchmark&lt;/a&gt; shows lower omission rates than ExtractTable, but footnote content is output as garbled text, and merged cells cannot correctly express hierarchical relationships.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our Test&lt;/strong&gt;: Continuous text recognition is basically usable; basic text styles (font/typeface/size) are preserved; text color, table border line styles, and other formatting are not reconstructed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3rdef5bo9cnayelzks6u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3rdef5bo9cnayelzks6u.png" alt=" " width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Nutrient (formerly PSPDFKit)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Product&lt;/td&gt;
&lt;td&gt;Nutrient SDK / API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;URL&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nutrient.io" rel="noopener noreferrer"&gt;nutrient.io&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Positioning&lt;/td&gt;
&lt;td&gt;Enterprise PDF SDK (cross-platform)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platforms&lt;/td&gt;
&lt;td&gt;Web, iOS, Android, Windows, macOS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing&lt;/td&gt;
&lt;td&gt;Contact sales (enterprise)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nutrient (formerly PSPDFKit) is a well-known cross-platform PDF SDK vendor, with core capabilities focused on PDF rendering, annotation, and editing. Table extraction is provided as an API module.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Product Positioning&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cross-platform native SDK with outstanding PDF rendering and interaction performance&lt;/li&gt;
&lt;li&gt;Table extraction is not its core scenario; custom development and integration are required&lt;/li&gt;
&lt;li&gt;Suitable for teams with existing Nutrient deployments needing supplementary table capabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Test Results&lt;/strong&gt;: Text content recognition accuracy was insufficient, with issues including positional drift, spacing distortion, and partial text loss. Hierarchical relationships between headers and content were not correctly reconstructed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd59qespnm406wat1dm8l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd59qespnm406wat1dm8l.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Adobe Acrobat
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Product&lt;/td&gt;
&lt;td&gt;Adobe Acrobat Pro DC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing&lt;/td&gt;
&lt;td&gt;Subscription ~$19.99/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scanned Support&lt;/td&gt;
&lt;td&gt;Built-in OCR (Pro version)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use Case&lt;/td&gt;
&lt;td&gt;Desktop occasional, small-scale processing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Product Features&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strengths&lt;/strong&gt;: Low barrier to entry, no coding required; Pro version includes OCR for direct scanned document export&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limitations&lt;/strong&gt;: No batch automation API; output quality is unstable with merged cells&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Test Results&lt;/strong&gt;: Overall table structure was reconstructed, but text loss and individual character recognition errors were present. Table border line styles and cell formatting were not preserved.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsuwqhetvew4gw5eh6crt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsuwqhetvew4gw5eh6crt.png" alt=" " width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  6. iText (iText 8 Core / iText 7 Community)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Product&lt;/td&gt;
&lt;td&gt;iText 8 Core / iText 7 Community&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;URL&lt;/td&gt;
&lt;td&gt;&lt;a href="https://itextpdf.com" rel="noopener noreferrer"&gt;itextpdf.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;AGPL (free open-source) / Commercial license&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platforms&lt;/td&gt;
&lt;td&gt;Java, .NET (C#)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing&lt;/td&gt;
&lt;td&gt;AGPL free / Commercial contact sales&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;iText is one of the oldest PDF processing libraries (founded in 1998). &lt;strong&gt;iText does not provide a dedicated table extraction API&lt;/strong&gt; — you must use &lt;code&gt;LocationTextExtractionStrategy&lt;/code&gt; to parse text positions and infer table structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test Results Across Three PDFs:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test File&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Extraction Result&lt;/th&gt;
&lt;th&gt;Table Structure&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SEO Book Page 114&lt;/td&gt;
&lt;td&gt;Scanned&lt;/td&gt;
&lt;td&gt;0 chars&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transcript (1).pdf&lt;/td&gt;
&lt;td&gt;Text-based transcript&lt;/td&gt;
&lt;td&gt;2218+866 chars&lt;/td&gt;
&lt;td&gt;Continuous text stream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prot_000 8.pdf&lt;/td&gt;
&lt;td&gt;Text-based clinical protocol&lt;/td&gt;
&lt;td&gt;1062 chars&lt;/td&gt;
&lt;td&gt;Continuous text stream&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgzgkp5eus89clak1xn97.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgzgkp5eus89clak1xn97.png" alt=" " width="799" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Capability Summary&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Text-based PDF text extraction&lt;/td&gt;
&lt;td&gt;4/5 — mature text layer extraction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automatic table recognition&lt;/td&gt;
&lt;td&gt;Requires custom development (coordinate-based inference)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scanned OCR&lt;/td&gt;
&lt;td&gt;No built-in OCR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDF creation/editing&lt;/td&gt;
&lt;td&gt;5/5 — industry benchmark&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaway&lt;/strong&gt;: iText is mature in the domain of low-level PDF operations, but it is not an out-of-the-box table extraction tool. Text content extraction (cumulative 4,146 characters) is complete and reliable, but table structure (column alignment, merged cells, rotated text) is entirely lost. For structured table output, use dedicated table reconstruction tools like ComPDF (commercial), Camelot, or Docling (open-source).&lt;/p&gt;




&lt;h2&gt;
  
  
  Open-Source PDF Table Extraction Tools
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Quick Selection Guide
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Recommended Tool&lt;/th&gt;
&lt;th&gt;Rationale&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scanned/image PDF table extraction&lt;/td&gt;
&lt;td&gt;Docling&lt;/td&gt;
&lt;td&gt;9.39s实测完成扫描件表格提取，AI pipeline integration ready&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standard bordered text-based PDF tables&lt;/td&gt;
&lt;td&gt;Camelot&lt;/td&gt;
&lt;td&gt;Simple lattice mode setup, extraction in a few lines of code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex tables (merged cells/rotated text)&lt;/td&gt;
&lt;td&gt;pdfplumber + custom code&lt;/td&gt;
&lt;td&gt;Requires fine-tuning and custom post-processing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java ecosystem/existing Java projects&lt;/td&gt;
&lt;td&gt;tabula-py / iText&lt;/td&gt;
&lt;td&gt;Naturally compatible with Java tech stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full document understanding (AI pipeline)&lt;/td&gt;
&lt;td&gt;Docling&lt;/td&gt;
&lt;td&gt;Native integration with LangChain/LlamaIndex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero budget&lt;/td&gt;
&lt;td&gt;Any open-source tool&lt;/td&gt;
&lt;td&gt;All MIT licensed, no licensing fees&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  1. Docling (IBM)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Version&lt;/td&gt;
&lt;td&gt;v2.99.0 (June 8, 2026) &lt;a href="https://github.com/docling-project/docling" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Stars&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;61,200+&lt;/strong&gt; — fastest growing PDF open-source project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependencies&lt;/td&gt;
&lt;td&gt;Python 3.10+, models require initial download (~2-5GB)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Core Features&lt;/strong&gt; (Source: GitHub README + &lt;a href="https://arxiv.org/abs/2408.09869" rel="noopener noreferrer"&gt;Technical Report&lt;/a&gt;)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-format support (PDF/DOCX/PPTX/XLSX/HTML/images/audio/email etc.)&lt;/li&gt;
&lt;li&gt;Built-in TableFormer (claims 93.6% accuracy vs Tabula 67.9%, Camelot 73.0%)&lt;/li&gt;
&lt;li&gt;Built-in OCR (via RapidOCR for scanned documents)&lt;/li&gt;
&lt;li&gt;Integrations: LangChain / LlamaIndex / Crew AI / Haystack&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Test Results (June 2026)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Environment: Models downloaded successfully via &lt;code&gt;HF_ENDPOINT=https://hf-mirror.com&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Conversion time: 9.39s&lt;/li&gt;
&lt;li&gt;Markdown length: 2573 chars&lt;/li&gt;
&lt;li&gt;Heron layout model: loaded (770/770 weights)&lt;/li&gt;
&lt;li&gt;OCR engine: RapidOCR&lt;/li&gt;
&lt;li&gt;Table detection: successful&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhkl1vwj8kdax60y1c762.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhkl1vwj8kdax60y1c762.png" alt=" " width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusions&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Table Structure Recognition&lt;/strong&gt;: 4-column multi-row table structure fully preserved, row-column correspondence correct&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scanned Document Processing&lt;/strong&gt;: Successfully extracted a structured table from a pure image PDF, validating AI model feasibility for scanned documents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OCR English Accuracy&lt;/strong&gt;: Low due to RapidOCR's optimization for Chinese characters; English recognition drift (e.g., "Google" misrecognized as "Googfe") is the current version's main bottleneck&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Improvement Direction&lt;/strong&gt;: Pairing with an English-specialized OCR engine (e.g., Tesseract English model) would significantly improve English table recognition accuracy on scanned documents.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. pdfplumber
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Version&lt;/td&gt;
&lt;td&gt;v0.11.9 (January 2026) &lt;a href="https://pypi.org/project/pdfplumber/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT, based on pdfminer.six&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Positioning&lt;/td&gt;
&lt;td&gt;Character-level low-level PDF parsing engine&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Test Results (June 2026)&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chars: 0, Lines: 0, Rects: 0, Tables: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Cannot process scanned files — 0 characters, 0 lines, 0 tables. Consistent with &lt;a href="https://pypi.org/project/pdfplumber/" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;: &lt;em&gt;"Works best on machine-generated, rather than scanned, PDFs"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Text-based PDF Tests (New):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transcript (1).pdf&lt;/strong&gt; (Student transcript, 2 pages):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chars: 2905 | Tables: 0 | Time: 0.13s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;pdfplumber successfully extracted 2905 characters of text but &lt;strong&gt;detected zero tables&lt;/strong&gt; (0 tables). This is because pdfplumber's table detection depends on graphical lines (lines/rects), while this transcript uses borderless tables ("ghost tables") where the visual table structure is formed purely by text alignment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prot_000 8.pdf&lt;/strong&gt; (Clinical protocol schedule, 1 page):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chars: 1017 | Tables: 1 | Time: 1.19s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Successfully detected 1 table (Schedule of Events), benefiting from the table's complete border lines. However, merged cell information (e.g., cross-column time axis headers) was lost, and rotated text could not be recognized.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;: pdfplumber detects &lt;strong&gt;bordered tables&lt;/strong&gt; accurately (consistent with Mark Kramer's findings), but is &lt;strong&gt;completely ineffective on borderless tables&lt;/strong&gt;. As a low-level library, significant custom code is required for complex scenarios.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5jlpch9qpf4o9n3lpwe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5jlpch9qpf4o9n3lpwe.png" alt=" " width="800" height="264"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Camelot
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Version&lt;/td&gt;
&lt;td&gt;v2.0.0 (June 4, 2026) &lt;a href="https://pypi.org/project/camelot-py/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependencies&lt;/td&gt;
&lt;td&gt;Python 3.10+, PyTorch optional for ML mode&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;5 Parsers&lt;/strong&gt;: lattice (bordered) / stream (borderless) / network (text alignment) / ml (Table Transformer) / auto (automatic)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test Results&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lattice Mode&lt;/strong&gt;: Cannot process scanned files; parser explicitly rejects image-based page input&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Text-based PDF Tests (New):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transcript (1).pdf&lt;/strong&gt; (transcript, borderless table):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;lattice&lt;/td&gt;
&lt;td&gt;❌ 0 tables — no table lines detected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stream&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;1 table, 95.8% accuracy&lt;/strong&gt; — 0.07s, successfully identified borderless table&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Prot_000 8.pdf&lt;/strong&gt; (clinical protocol, bordered table):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;lattice&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;1 table, 97.97% accuracy&lt;/strong&gt; — 0.36s, complete structure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stream&lt;/td&gt;
&lt;td&gt;✅ &lt;strong&gt;2 tables, 100% accuracy&lt;/strong&gt; — 0.08s, fastest speed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt; The extracted Excel files showed disorganized structure and text-to-cell mapping misalignment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flj3xyns0w3cciglwa14l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flj3xyns0w3cciglwa14l.png" alt=" " width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  4. tabula-py
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Version&lt;/td&gt;
&lt;td&gt;v2.10.0 (October 2024) &lt;a href="https://pypi.org/project/tabula-py/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Java 8+&lt;/strong&gt; + Python 3.9+&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Test Results (Java 21 + tabula-py 2.10.0)&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tables found: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Cannot process scanned files. Even with Java installed, tabula-py cannot parse image-based PDFs without a text layer. &lt;a href="https://tabula.technology/" rel="noopener noreferrer"&gt;Tabula's official documentation&lt;/a&gt; states: "Tabula only works on text-based PDFs, not scanned documents."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Text-based PDF Tests (New):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transcript (1).pdf&lt;/strong&gt; (Student transcript, 2 pages):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tables found: 2 | Time: 1.54s
Table 1: 29 rows x 7 cols (transfer credits)
Table 2: 8 rows x 7 cols (GE 2022 semester)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;tabula-py successfully identified the two semester course tables as separate DataFrames with complete column structure (Course Code / Description / Credits / Grade / Quality Points). All 29 transfer credit course records were correctly captured.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prot_000 8.pdf&lt;/strong&gt; (Clinical protocol schedule, 1 page):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR: 'utf-8' codec can't decode byte 0xa1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;tabula-py threw an encoding exception when processing this PDF. The PDF contains special characters (e.g., registered trademark symbol ®, ±), and tabula-py's Java subprocess output decoding failed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;: tabula-py's text recognition for standard tables (e.g., transcripts) is basically correct, but the structure reconstruction is disorganized, text-to-row/column mapping is misaligned, and table borders were not correctly identified.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgxpm4jrn9lpwoh9bt7tp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgxpm4jrn9lpwoh9bt7tp.png" alt=" " width="799" height="488"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This article presents a horizontal benchmark of &lt;strong&gt;15 tools&lt;/strong&gt; using &lt;strong&gt;three test files of varying difficulty&lt;/strong&gt; (scanned no-text-layer PDF / text-based transcript PDF / text-based clinical protocol PDF). The key findings are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Scanned Documents Remain the Biggest Differentiator&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Of the 15 tools tested, only &lt;strong&gt;Docling, ComPDF, AWS Textract, Nanonets, Nutrient, and Adobe Acrobat&lt;/strong&gt; can process scanned documents&lt;/li&gt;
&lt;li&gt;Pure-parsing tools like pdfplumber, Camelot(lattice), tabula-py, and iText &lt;strong&gt;completely fail&lt;/strong&gt; on scanned documents (0 chars/0 tables)&lt;/li&gt;
&lt;li&gt;OCR capability is the first differentiator for PDF table extraction tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Table Structure Reconstruction on Text-Based PDFs Is the Real Test&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Even among tools that successfully extract text from text-based PDFs, very few can &lt;strong&gt;fully preserve table structure&lt;/strong&gt; (row-column correspondence, merged cells, border styles)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docling&lt;/strong&gt; produced 27 Markdown tables (18,048 chars) from the text transcript and 21 tables (12,922 chars) from the clinical protocol — its AI-driven layout analysis significantly outperforms other open-source tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Camelot&lt;/strong&gt; achieves the highest accuracy on bordered tables (lattice 97.97%), and its stream mode works on borderless tables (95.8%) — the best choice among pure-parsing tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;tabula-py&lt;/strong&gt; correctly identifies columns on standard transcript tables but has encoding compatibility issues&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iText&lt;/strong&gt; extracts text completely (4,146 characters cumulative), but table structure is entirely lost — only continuous text stream output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. The Gap Between Commercial and Open-Source Tools Is Largest for Complex Tables&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For merged cells/hierarchical headers, &lt;strong&gt;ComPDF&lt;/strong&gt; is the only commercially available SDK that passed verification&lt;/li&gt;
&lt;li&gt;Open-source tools are adequate for simple bordered tables, but the gap widens significantly for complex table scenarios&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>pdf</category>
      <category>table</category>
      <category>pdftoexcel</category>
      <category>extractpdf</category>
    </item>
    <item>
      <title>AI Agent for Order/Invoice/Contract Generation: From Natural Language to PDF — An All-in-One Solution</title>
      <dc:creator>Derek</dc:creator>
      <pubDate>Fri, 29 May 2026 08:43:52 +0000</pubDate>
      <link>https://dev.to/derek-compdf/ai-agent-for-orderinvoicecontract-generation-from-natural-language-to-pdf-an-all-in-one-1k1j</link>
      <guid>https://dev.to/derek-compdf/ai-agent-for-orderinvoicecontract-generation-from-natural-language-to-pdf-an-all-in-one-1k1j</guid>
      <description>&lt;p&gt;Generating business documents like purchase orders, invoices, and contracts used to be either a tedious manual layout job or a technically demanding coding task involving API integration. Now, with the &lt;strong&gt;AI Agent + ComPDF Generation API&lt;/strong&gt; combo, you simply describe your needs in natural language. The AI Agent automatically creates the HTML template and JSON data file — then you can preview and verify the output in the ComPDF online Demo, or even have it generate a full visual application that calls the &lt;strong&gt;ComPDF Generation API&lt;/strong&gt;. Zero code required throughout the entire process. Sign up for ComPDF Cloud and get 200+ free processing credits every month.&lt;/p&gt;




&lt;h2&gt;
  
  
  Workflow Overview: From Natural Language to PDF
&lt;/h2&gt;

&lt;p&gt;The entire workflow consists of just four steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Step 1 →&lt;/strong&gt; Tell the AI Agent your document requirements — it auto-generates the HTML template and JSON data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 2 →&lt;/strong&gt; Validate the output in the ComPDF online Demo — iterate instantly by asking the AI to make changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 3 →&lt;/strong&gt; Have the AI Agent (any AI Agent works; this example uses OpenCode) generate a visual application that calls the API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step 4 →&lt;/strong&gt; Sign up for ComPDF Cloud to get your API Key — 200+ free credits every month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below, we walk through the full flow using a cross-border trade scenario.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Use Natural Language to Have the AI Agent Generate Templates and Sample Data
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Send Your Requirements to the AI Agent
&lt;/h3&gt;

&lt;p&gt;Open any AI Agent (such as OpenCode, Claude, ChatGPT, Copilot, etc.) and send the following instruction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I run a foreign trading company. I need an HTML template and JSON data for a purchase order in English. The template should include: company logo placeholder, customer information, item list (name, unit price, quantity, subtotal), shipping cost, tax, and grand total. The JSON data should simulate a real international trade order.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI Agent will immediately return two files: &lt;code&gt;order_template.html&lt;/code&gt; and &lt;code&gt;order_data.json&lt;/code&gt;. You can open both files to check if they meet your business needs, and ask the AI to make any adjustments — such as inserting your company logo in the top-right corner.&lt;/p&gt;

&lt;h3&gt;
  
  
  Order Template Generated by the AI Agent
&lt;/h3&gt;

&lt;p&gt;Below is the core HTML template code generated by the AI Agent:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta charset="utf-8"&amp;gt;
&amp;lt;style&amp;gt;
@page { size: A4; margin: 20mm 15mm; }
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Helvetica Neue', Arial, sans-serif; color: #1a202c; font-size: 13px; line-height: 1.5; }
.header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 30px; border-bottom: 3px solid #1a365d; padding-bottom: 20px; }
.logo { width: 120px; height: 60px; background: #1a365d; color: #fff; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 14px; border-radius: 4px; }
.doc-title { text-align: right; }
.doc-title h1 { font-size: 26px; color: #1a365d; letter-spacing: 2px; }
.doc-title p { color: #718096; font-size: 12px; }
.info-grid { display: flex; justify-content: space-between; margin-bottom: 25px; }
.info-box { width: 48%; }
.info-box h3 { font-size: 11px; color: #1a365d; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px; }
.info-box p { font-size: 12px; color: #4a5568; margin: 2px 0; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
th { background: #1a365d; color: #fff; padding: 10px 8px; font-size: 12px; text-align: left; letter-spacing: 0.5px; }
td { padding: 9px 8px; border-bottom: 1px solid #e2e8f0; font-size: 12px; }
td.num { text-align: right; }
.totals { width: 320px; margin-left: auto; margin-top: 10px; }
.totals table { margin: 0; }
.totals td { padding: 6px 10px; border: none; font-size: 12px; }
.totals .label { font-weight: bold; color: #4a5568; }
.totals .value { text-align: right; }
.totals .grand-total td { font-size: 16px; font-weight: bold; color: #1a365d; border-top: 2px solid #1a365d; padding-top: 8px; }
.footer { margin-top: 40px; padding-top: 15px; border-top: 1px solid #e2e8f0; font-size: 11px; color: #a0aec0; text-align: center; }
&amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;div class="header"&amp;gt;
&amp;lt;div class="logo"&amp;gt;ABC TRADING&amp;lt;/div&amp;gt;
&amp;lt;div class="doc-title"&amp;gt;
&amp;lt;h1&amp;gt;PURCHASE ORDER&amp;lt;/h1&amp;gt;
&amp;lt;p&amp;gt;Order No: {{order_number}} | Date: {{order_date}}&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class="info-grid"&amp;gt;
&amp;lt;div class="info-box"&amp;gt;
&amp;lt;h3&amp;gt;Seller&amp;lt;/h3&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;{{seller.name}}&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;{{seller.address}}&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;{{seller.city}}, {{seller.country}}&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;Tel: {{seller.phone}} | Email: {{seller.email}}&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class="info-box"&amp;gt;
&amp;lt;h3&amp;gt;Ship To&amp;lt;/h3&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;{{buyer.name}}&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;{{buyer.address}}&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;{{buyer.city}}, {{buyer.country}}&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;Tel: {{buyer.phone}}&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th style="width:40px"&amp;gt;#&amp;lt;/th&amp;gt;
&amp;lt;th&amp;gt;Item Description&amp;lt;/th&amp;gt;
&amp;lt;th style="width:60px"&amp;gt;Qty&amp;lt;/th&amp;gt;
&amp;lt;th style="width:50px"&amp;gt;Unit&amp;lt;/th&amp;gt;
&amp;lt;th style="width:90px" class="num"&amp;gt;Unit Price (USD)&amp;lt;/th&amp;gt;
&amp;lt;th style="width:100px" class="num"&amp;gt;Total (USD)&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
{{#each items}}
&amp;lt;tr&amp;gt;
&amp;lt;td&amp;gt;{{inc @index}}&amp;lt;/td&amp;gt;
&amp;lt;td&amp;gt;&amp;lt;strong&amp;gt;{{name}}&amp;lt;/strong&amp;gt;&amp;lt;br&amp;gt;&amp;lt;span style="color:#718096;font-size:11px"&amp;gt;{{sku}}&amp;lt;/span&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td&amp;gt;{{qty}}&amp;lt;/td&amp;gt;
&amp;lt;td&amp;gt;{{unit}}&amp;lt;/td&amp;gt;
&amp;lt;td class="num"&amp;gt;{{format_price unit_price}}&amp;lt;/td&amp;gt;
&amp;lt;td class="num"&amp;gt;&amp;lt;strong&amp;gt;{{format_price total}}&amp;lt;/strong&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
{{/each}}
&amp;lt;/table&amp;gt;

&amp;lt;div class="totals"&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;&amp;lt;td class="label"&amp;gt;Subtotal&amp;lt;/td&amp;gt;&amp;lt;td class="value"&amp;gt;${{format_price subtotal}}&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;&amp;lt;td class="label"&amp;gt;Shipping ({{shipping.method}})&amp;lt;/td&amp;gt;&amp;lt;td class="value"&amp;gt;${{format_price shipping.cost}}&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;&amp;lt;td class="label"&amp;gt;Insurance&amp;lt;/td&amp;gt;&amp;lt;td class="value"&amp;gt;${{format_price insurance}}&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
&amp;lt;tr class="grand-total"&amp;gt;&amp;lt;td&amp;gt;TOTAL AMOUNT&amp;lt;/td&amp;gt;&amp;lt;td class="value"&amp;gt;${{format_price grand_total}}&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;p style="margin-top:15px;font-size:11px;color:#718096;"&amp;gt;
&amp;lt;strong&amp;gt;Terms:&amp;lt;/strong&amp;gt; {{payment_terms}}&amp;lt;br&amp;gt;
&amp;lt;strong&amp;gt;Delivery:&amp;lt;/strong&amp;gt; {{delivery_date}} via {{shipping.method}}&amp;lt;br&amp;gt;
&amp;lt;strong&amp;gt;Incoterms:&amp;lt;/strong&amp;gt; {{incoterms}}
&amp;lt;/p&amp;gt;

&amp;lt;div class="footer"&amp;gt;
ABC Trading Co., Ltd. | 123 Trade Street, Los Angeles, CA 90001, USA | www.abctrading.com
&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Template highlights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses Mustache-style variable placeholders (&lt;code&gt;{{order_number}}&lt;/code&gt;, &lt;code&gt;{{seller.name}}&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;li&gt;Supports &lt;code&gt;{{#each items}}&lt;/code&gt; loop rendering for the item list&lt;/li&gt;
&lt;li&gt;CSS styles fully cover print layout (&lt;code&gt;@page size: A4&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Includes company brand color (deep blue &lt;code&gt;#1a365d&lt;/code&gt;) and professional fonts&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Corresponding JSON Data Sample
&lt;/h3&gt;

&lt;p&gt;Simulates a real electronic components international trade order containing 4 items with a grand total of \$113,590.00.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "order_number": "PO-2026-0589",
  "order_date": "May 25, 2026",
  "seller": {
    "name": "ABC Trading Co., Ltd.",
    "address": "123 Trade Street, Suite 400",
    "city": "Los Angeles",
    "country": "United States",
    "phone": "+1 (213) 555-0198",
    "email": "orders@abctrading.com"
  },
  "buyer": {
    "name": "Shenzhen ElecTech Co., Ltd.",
    "address": "No. 88 Tech Road, Nanshan District",
    "city": "Shenzhen",
    "country": "China",
    "phone": "+86 755 8888 6666"
  },
  "items": [
    { "name": "Industrial Touch Screen Display 10.1\"", "sku": "TSD-101-IPS", "qty": 500, "unit": "pcs", "unit_price": 85.50, "total": 42750.00 },
    { "name": "Raspberry Pi Compute Module 4 (8GB)", "sku": "RPI-CM4-8G", "qty": 1000, "unit": "pcs", "unit_price": 45.00, "total": 45000.00 },
    { "name": "USB-C Power Supply 65W", "sku": "PS-65W-USBC", "qty": 800, "unit": "pcs", "unit_price": 12.80, "total": 10240.00 },
    { "name": "Industrial Enclosure IP65", "sku": "ENC-IP65-304", "qty": 300, "unit": "pcs", "unit_price": 38.20, "total": 11460.00 }
  ],
  "subtotal": 109450.00,
  "shipping": { "method": "Sea Freight (FOB Los Angeles)", "cost": 3250.00 },
  "insurance": 890.00,
  "grand_total": 113590.00,
  "payment_terms": "T/T 30% deposit, 70% against copy of B/L",
  "delivery_date": "July 15, 2026",
  "incoterms": "FOB Los Angeles"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Continue Generating Invoice and Contract Templates
&lt;/h3&gt;

&lt;p&gt;Keep the same conversation going and send the next instruction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Based on the same trade scenario, generate an HTML template and JSON data for a Proforma Invoice, including invoice number, invoice date, payment terms, and bank information.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI Agent will generate the invoice template:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta charset="utf-8"&amp;gt;
&amp;lt;style&amp;gt;
@page { size: A4; margin: 18mm 15mm; }
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Helvetica Neue', Arial, sans-serif; color: #1a202c; font-size: 13px; }
.header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 25px; }
.logo-area { display: flex; align-items: center; gap: 12px; }
.logo { width: 55px; height: 55px; background: #1a365d; color: #fff; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 10px; border-radius: 50%; text-align: center; line-height: 1.2; }
.company-info h2 { font-size: 16px; color: #1a365d; }
.company-info p { font-size: 11px; color: #718096; }
.doc-badge { background: #1a365d; color: #fff; padding: 8px 25px; text-align: center; border-radius: 4px; }
.doc-badge h1 { font-size: 20px; letter-spacing: 3px; }
.doc-badge p { font-size: 11px; opacity: 0.8; }
.ribbon { background: #ebf4ff; padding: 12px 15px; border-left: 4px solid #1a365d; margin: 15px 0; display: flex; justify-content: space-between; font-size: 12px; color: #2d3748; }
.info-grid { display: flex; justify-content: space-between; margin-bottom: 20px; }
.info-box { width: 48%; padding: 12px; background: #f7fafc; border-radius: 4px; }
.info-box h3 { font-size: 10px; color: #1a365d; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 5px; }
.info-box p { font-size: 11px; color: #4a5568; margin: 1px 0; }
table { width: 100%; border-collapse: collapse; margin: 15px 0; }
th { background: #1a365d; color: #fff; padding: 9px 8px; font-size: 11px; text-align: left; }
th.num { text-align: right; }
td { padding: 8px; border-bottom: 1px solid #e2e8f0; font-size: 12px; }
td.num { text-align: right; }
tr:nth-child(even) td { background: #f7fafc; }
.totals-box { width: 350px; margin-left: auto; margin-top: 10px; border: 1px solid #e2e8f0; border-radius: 4px; overflow: hidden; }
.totals-box table { margin: 0; }
.totals-box td { padding: 7px 12px; border-bottom: 1px solid #e2e8f0; font-size: 12px; }
.totals-box tr:last-child td { border-bottom: none; }
.totals-box .grand-total { background: #1a365d; color: #fff; }
.totals-box .grand-total td { font-size: 15px; font-weight: bold; border-bottom: none; }
.bank-info { margin-top: 25px; padding: 12px 15px; background: #fffff0; border: 1px solid #ecc94b; border-radius: 4px; font-size: 11px; color: #744210; }
.bank-info h4 { font-size: 11px; margin-bottom: 4px; }
.footer { margin-top: 25px; padding-top: 12px; border-top: 1px solid #e2e8f0; font-size: 10px; color: #a0aec0; text-align: center; }
&amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;div class="header"&amp;gt;
&amp;lt;div class="logo-area"&amp;gt;
&amp;lt;div class="logo"&amp;gt;ABC&amp;lt;br&amp;gt;TRADING&amp;lt;/div&amp;gt;
&amp;lt;div class="company-info"&amp;gt;
&amp;lt;h2&amp;gt;ABC Trading Co., Ltd.&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;123 Trade Street, Los Angeles, CA 90001, USA&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;Tax ID: US-XX-XXXXXXX | Tel: +1 (213) 555-0198&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class="doc-badge"&amp;gt;
&amp;lt;h1&amp;gt;INVOICE&amp;lt;/h1&amp;gt;
&amp;lt;p&amp;gt;{{invoice_number}}&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class="ribbon"&amp;gt;
&amp;lt;span&amp;gt;&amp;lt;strong&amp;gt;Invoice Date:&amp;lt;/strong&amp;gt; {{invoice_date}}&amp;lt;/span&amp;gt;
&amp;lt;span&amp;gt;&amp;lt;strong&amp;gt;Due Date:&amp;lt;/strong&amp;gt; {{due_date}}&amp;lt;/span&amp;gt;
&amp;lt;span&amp;gt;&amp;lt;strong&amp;gt;PO Reference:&amp;lt;/strong&amp;gt; {{po_reference}}&amp;lt;/span&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class="info-grid"&amp;gt;
&amp;lt;div class="info-box"&amp;gt;
&amp;lt;h3&amp;gt;Bill To&amp;lt;/h3&amp;gt;
&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;{{buyer.name}}&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;{{buyer.address}}&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;{{buyer.city}}, {{buyer.country}}&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;Attn: {{buyer.contact}}&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class="info-box"&amp;gt;
&amp;lt;h3&amp;gt;Ship To&amp;lt;/h3&amp;gt;
&amp;lt;p&amp;gt;{{shipping.consignee}}&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;{{shipping.port_of_loading}} → {{shipping.port_of_discharge}}&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;Via: {{shipping.method}}&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;ETD: {{shipping.etd}}&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th style="width:35px"&amp;gt;#&amp;lt;/th&amp;gt;
&amp;lt;th&amp;gt;Description&amp;lt;/th&amp;gt;
&amp;lt;th style="width:55px"&amp;gt;Qty&amp;lt;/th&amp;gt;
&amp;lt;th style="width:85px" class="num"&amp;gt;Unit Price&amp;lt;/th&amp;gt;
&amp;lt;th style="width:95px" class="num"&amp;gt;Amount (USD)&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
{{#each items}}
&amp;lt;tr&amp;gt;
&amp;lt;td&amp;gt;{{inc @index}}&amp;lt;/td&amp;gt;
&amp;lt;td&amp;gt;&amp;lt;strong&amp;gt;{{name}}&amp;lt;/strong&amp;gt;&amp;lt;br&amp;gt;&amp;lt;span style="color:#718096;font-size:10px"&amp;gt;HS Code: {{hs_code}} | {{sku}}&amp;lt;/span&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td class="num"&amp;gt;{{qty}}&amp;lt;/td&amp;gt;
&amp;lt;td class="num"&amp;gt;{{format_price unit_price}}&amp;lt;/td&amp;gt;
&amp;lt;td class="num"&amp;gt;&amp;lt;strong&amp;gt;{{format_price total}}&amp;lt;/strong&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
{{/each}}
&amp;lt;/table&amp;gt;

&amp;lt;div class="totals-box"&amp;gt;
&amp;lt;table&amp;gt;
&amp;lt;tr&amp;gt;&amp;lt;td style="width:180px"&amp;gt;Subtotal&amp;lt;/td&amp;gt;&amp;lt;td class="num"&amp;gt;${{format_price subtotal}}&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;Freight ({{shipping.method}})&amp;lt;/td&amp;gt;&amp;lt;td class="num"&amp;gt;${{format_price shipping_cost}}&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;Insurance (0.8%)&amp;lt;/td&amp;gt;&amp;lt;td class="num"&amp;gt;${{format_price insurance}}&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;Total Amount Due&amp;lt;/td&amp;gt;&amp;lt;td class="num" style="font-weight:bold"&amp;gt;${{format_price grand_total}}&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
&amp;lt;tr class="grand-total"&amp;gt;&amp;lt;td&amp;gt;AMOUNT IN WORDS&amp;lt;/td&amp;gt;&amp;lt;td class="num" style="font-size:11px"&amp;gt;{{amount_in_words}}&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class="bank-info"&amp;gt;
&amp;lt;h4&amp;gt;Banking Details&amp;lt;/h4&amp;gt;
&amp;lt;p&amp;gt;Bank of America | Account: 1234-5678-9012-3456 | SWIFT: BOFAUS3N | Routing: 026009593&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class="footer"&amp;gt;
This invoice is valid for payment within 30 days. Please quote invoice number for all correspondence.
&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then generate the contract template:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Generate an HTML template and JSON data for an international sales contract, including contract terms, both parties' information, signature fields, and trade terms.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In less than 5 minutes, all three sets of templates and data are ready — completed entirely through natural language.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Validate the Output in the ComPDF Online Demo
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Access the ComPDF Generation API Online Demo
&lt;/h3&gt;

&lt;p&gt;Open your browser and visit the free online &lt;a href="https://www.compdf.com/demo/pdf-generation/generate-from-html?utm_source=dev.to&amp;amp;utm_medium=dev.to_ai_document_generation&amp;amp;utm_campaign=dev.to_ai_document_generation&amp;amp;ref_platform_id=dev.to"&gt;Automated PDF Document Generation&lt;/a&gt; demo page provided by ComPDF. It features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTML Source Editor&lt;/strong&gt; — paste or modify templates directly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON Data Editor&lt;/strong&gt; — paste data to auto-bind template variables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Generate PDF" Button&lt;/strong&gt; — one-click API call to generate the PDF&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDF Preview &amp;amp; Download&lt;/strong&gt; — view online or save locally&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Upload Templates and Data for Validation
&lt;/h3&gt;

&lt;p&gt;The operation is very simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy the entire &lt;code&gt;order_template.html&lt;/code&gt; content generated by the AI Agent and paste it into the Demo's HTML editor&lt;/li&gt;
&lt;li&gt;Copy the entire &lt;code&gt;order_data.json&lt;/code&gt; content and paste it into the JSON data area&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;"Generate PDF"&lt;/strong&gt; button&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The ComPDF Generation API renders the template in real time, fills in the data, and produces a standard PDF file.&lt;/p&gt;

&lt;p&gt;The generated output — Purchase Order PDF example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7qrm7tyaiix69vbxle4y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7qrm7tyaiix69vbxle4y.png" alt="renderedorderpng" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Invoice PDF example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcrmu874yqohilttx0asd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcrmu874yqohilttx0asd.png" alt="renderedinvoicepng" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Contract PDF example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F10n0lkddn1t2ppzgz781.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F10n0lkddn1t2ppzgz781.png" alt="renderedcontractpng" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, all styles — fonts, colors, tables, borders — are perfectly preserved. The output quality fully meets business document standards.&lt;/p&gt;

&lt;h3&gt;
  
  
  Iterative Refinement: Edit Directly or Ask the AI Agent
&lt;/h3&gt;

&lt;p&gt;Not satisfied with the output? The ComPDF Demo page offers two ways to make changes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Method 1&lt;/strong&gt;: Edit directly. Modify the CSS or structure directly in the Demo's HTML editor, then click Generate again to see the changes in real time. For example, adjust font sizes, change table column widths, or swap the color scheme.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Method 2&lt;/strong&gt;: Ask the AI Agent to modify (recommended). Describe your changes in natural language, for instance:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please insert my company logo in the top-right corner of the order template. The company name is ABC Trading Co., Ltd. Change the theme color to deep blue #1a365d. Add a sequence number column to the item table.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI Agent will immediately understand your requirements, update the template, and return the new code. Simply copy the new code into the Demo page and regenerate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Have the AI Agent Generate a Visual Application That Calls the ComPDF API for Automated Document Generation
&lt;/h2&gt;

&lt;p&gt;This is the highlight of the workflow — the AI Agent doesn't just generate files; it creates a complete, runnable application for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Send the Final Instruction
&lt;/h3&gt;

&lt;p&gt;Send the following instruction to the AI Agent (when the AI asks for authentication credentials, you can &lt;a href="https://api.compdf.com/signup?utm_source=dev.to&amp;amp;utm_medium=dev.to_ai_document_generation&amp;amp;utm_campaign=dev.to_ai_document_generation&amp;amp;ref_platform_id=dev.to"&gt;sign up for ComPDF Cloud&lt;/a&gt; to obtain them):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build a program that calls the ComPDF Generation API to automatically create invoices/orders/contracts, and generate a visual interface for it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Visual Interface Generated by the AI Agent
&lt;/h3&gt;

&lt;p&gt;The AI Agent will generate a complete web application interface, as shown below (this interface was generated by the AI — you can simply ask the AI to create any page you want; the entire process is as easy as sending a message):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2d8mgehj9rb2j19fo3gg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2d8mgehj9rb2j19fo3gg.png" alt="visualappinterfacepng" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The interface consists of three core areas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Document Type Switcher&lt;/strong&gt; — one-click toggle between Purchase Order / Invoice / Sales Contract&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTML Template + JSON Data Editor&lt;/strong&gt; — split-pane layout, supports direct editing or file upload&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result Area&lt;/strong&gt; — displays the generated filename with a download button&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fill in your ComPDF API Key, click the "Generate PDF" button, and the API call progress will be displayed in real time:&lt;/p&gt;

&lt;h3&gt;
  
  
  Program Logic
&lt;/h3&gt;

&lt;p&gt;The application's logic is straightforward:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You input HTML template + JSON data
            ↓
    Click "Generate PDF" button
            ↓
    Front-end sends data to ComPDF Generation API
            ↓
    API generates PDF file in real time
            ↓
    Page displays filename with download button
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The entire process completes in seconds. It supports one-click switching between three document types — Order, Invoice, and Contract — with pre-loaded templates and data for each, ready to use out of the box.&lt;/p&gt;

&lt;h3&gt;
  
  
  API Key Configuration
&lt;/h3&gt;

&lt;p&gt;In the generated application, you'll need to configure your own API Key. Here's how to get one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;&lt;a href="https://api.compdf.com/signup?utm_source=dev.to&amp;amp;utm_medium=dev.to_ai_document_generation&amp;amp;utm_campaign=dev.to_ai_document_generation&amp;amp;ref_platform_id=dev.to"&gt;https://api.compdf.com/signup&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Register for a ComPDF Cloud account (just your email)&lt;/li&gt;
&lt;li&gt;Retrieve your API Key from the console&lt;/li&gt;
&lt;li&gt;Paste it into the API Key input field in the application&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Free users get 200+ file processing credits per month&lt;/strong&gt;, which is more than enough for personal testing and daily use. If you need higher throughput, cost-effective paid plans are available.&lt;/p&gt;




&lt;h2&gt;
  
  
  ComPDF Generation API Core Capabilities
&lt;/h2&gt;

&lt;p&gt;Understanding the technology behind the scenes will help you get the most out of this solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  High-Fidelity HTML/CSS to PDF Rendering
&lt;/h3&gt;

&lt;p&gt;The core capability of the ComPDF Generation API is generating PDFs from HTML/CSS templates. It faithfully preserves all style attributes — fonts, colors, layouts, tables, images, and more — and supports CSS &lt;code&gt;@page&lt;/code&gt; rules for print layout control. This means every design detail in the AI Agent-generated template is accurately reproduced in the PDF output.&lt;/p&gt;

&lt;h3&gt;
  
  
  Template Variables and Data Binding
&lt;/h3&gt;

&lt;p&gt;Supports using &lt;code&gt;{{variable_name}}&lt;/code&gt; placeholders in HTML templates, dynamically filled with JSON data. One template paired with different JSON data can generate thousands of differentiated documents — ideal for batch-generating invoices or order confirmations for different customers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Batch Processing and Asynchronous Tasks
&lt;/h3&gt;

&lt;p&gt;Supports submitting up to 500 document generation tasks in a single request. Results are retrieved via callback or polling after asynchronous processing completes. For business scenarios requiring large-scale invoice or contract generation, this significantly improves processing efficiency.&lt;/p&gt;




&lt;h2&gt;
  
  
  Effectiveness Comparison: Natural Language + AI Agent vs. Traditional Approach
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Traditional Approach&lt;/th&gt;
&lt;th&gt;AI Agent Approach&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Writing HTML Template&lt;/td&gt;
&lt;td&gt;Manual coding, 30-60 min&lt;/td&gt;
&lt;td&gt;Natural language description, generated in seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Constructing JSON Data&lt;/td&gt;
&lt;td&gt;Manual construction, 15-30 min&lt;/td&gt;
&lt;td&gt;AI auto-generates sample data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Writing API Call Code&lt;/td&gt;
&lt;td&gt;1-2 hours&lt;/td&gt;
&lt;td&gt;AI Agent completes automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Building Front-end UI&lt;/td&gt;
&lt;td&gt;2-4 hours&lt;/td&gt;
&lt;td&gt;AI Agent generates automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4-8 hours&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;15-30 minutes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Technical Barrier&lt;/td&gt;
&lt;td&gt;Requires full-stack dev skills&lt;/td&gt;
&lt;td&gt;Zero code, natural language only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Free Credits Value
&lt;/h3&gt;

&lt;p&gt;200+ free processing credits per month — calculated at one PDF per credit — is sufficient for personal testing and small team daily use. Building the same capability the traditional way requires purchasing servers, deploying services, and maintaining systems — with costs and technical barriers far exceeding this approach.&lt;/p&gt;




&lt;h2&gt;
  
  
  Best Practices &amp;amp; Advanced Tips
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How to Write Effective AI Agent Instructions
&lt;/h3&gt;

&lt;p&gt;The more specific your instructions, the more precise the generated template. We recommend including the following elements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Document Type&lt;/strong&gt; — Order/Invoice/Contract/Quotation, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business Scenario&lt;/strong&gt; — International trade/E-commerce/Domestic trade/Service contract, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Field Checklist&lt;/strong&gt; — Which data fields should be included&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual Preferences&lt;/strong&gt; — Colors/Fonts/LOGO position, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language Requirement&lt;/strong&gt; — Chinese/English/Bilingual&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Example: "Generate an English proforma invoice template for a Shenzhen-based tech company, including product name, HS code, quantity, unit price, and total. Theme color is blue, logo in the top-left."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Template Reuse Tips
&lt;/h3&gt;

&lt;p&gt;When designing templates, hard-code fixed information like company name, logo, and address directly into the HTML, and use variable placeholders for dynamic content like order number, customer name, and item list. This way, the same template paired with different JSON data can serve different customers and orders.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migrating from Demo Validation to Production
&lt;/h3&gt;

&lt;p&gt;After validation in the ComPDF online Demo, deploy the AI Agent-generated visual application to your own server, or use platforms like Vercel / Netlify for one-click hosting. In production, we recommend storing templates in a database or object storage and linking them with your business system's order data to create a fully automated document generation pipeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  I have no programming experience. Can I use this solution?
&lt;/h3&gt;

&lt;p&gt;Absolutely. The entire workflow revolves around interacting with the AI Agent using natural language. You only need to describe what you want, and the AI Agent handles all the technical work. The ComPDF online Demo also requires zero programming knowledge.&lt;/p&gt;

&lt;h3&gt;
  
  
  How exactly are the 200+ free credits calculated?
&lt;/h3&gt;

&lt;p&gt;After registering for ComPDF Cloud, you automatically receive 200+ free file processing credits every month. Each call to the Generation API to produce one PDF file counts as one credit. If you need more, cost-effective paid plans are available.&lt;/p&gt;

&lt;h3&gt;
  
  
  What deployment methods does the AI Agent-generated program support?
&lt;/h3&gt;

&lt;p&gt;The AI Agent typically generates a backend based on Python Flask/FastAPI or Node.js Express, paired with an HTML/React front-end. It can run locally or be deployed to cloud platforms like Vercel, Netlify, or Railway.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does it support generating PDFs with Chinese content?
&lt;/h3&gt;

&lt;p&gt;Yes. The ComPDF Generation API fully supports Chinese font rendering. Simply specify a Chinese font (such as SimSun, Microsoft YaHei) or use a generic font-family in your HTML template to generate high-quality Chinese documents.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: Start Your AI Document Automation Journey
&lt;/h2&gt;

&lt;p&gt;Three steps to get started:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sign up for ComPDF Cloud&lt;/strong&gt; — get your API Key for free&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Describe your needs to the AI Agent in natural language&lt;/strong&gt; — generate templates and data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate in the ComPDF online Demo&lt;/strong&gt; — or let the AI Agent generate a visual application directly&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From zero to your first PDF — just a few minutes.&lt;/p&gt;

&lt;p&gt;Beyond orders, invoices, and contracts, this solution is equally suitable for quotations, packing lists, bills of lading, customs declarations, and other international trade documents. You can also try using the AI Agent to generate more complex templates — such as multilingual templates, multi-currency invoices, or data reports with charts. The flexibility of the ComPDF Generation API combined with the creativity of the AI Agent opens up possibilities far beyond your imagination.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>automation</category>
      <category>invoice</category>
      <category>pdf</category>
    </item>
    <item>
      <title>How to Generate PDF Invoices? A Complete Guide from Zero to Service-Oriented Implementation</title>
      <dc:creator>Derek</dc:creator>
      <pubDate>Wed, 20 May 2026 06:34:00 +0000</pubDate>
      <link>https://dev.to/derek-compdf/how-to-generate-pdf-invoices-a-complete-guide-from-zero-to-service-oriented-implementation-4ph5</link>
      <guid>https://dev.to/derek-compdf/how-to-generate-pdf-invoices-a-complete-guide-from-zero-to-service-oriented-implementation-4ph5</guid>
      <description>&lt;p&gt;&lt;a href="https://www.compdf.com/pdf-sdk/pdf-generation?utm_source=dev.to_invoice_20260520&amp;amp;utm_medium=referral&amp;amp;utm_campaign=dev.to_invoice_20260520&amp;amp;ref_platform_id=dev.to"&gt;Generating a PDF invoice&lt;/a&gt; may seem as simple as "exporting a file," but in real business scenarios, it connects order systems, finance systems, tax rules, customer delivery, and audit trails. Many teams manage to get by with manual work or simple tools in the early stage, but later frequently run into the following issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Inconsistent field definitions lead to mismatched bills.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Frequent template changes make historical documents unrecoverable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No alerting when batch tasks fail – customers don't receive invoices on time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unstable output quality – misaligned printing or garbled fonts.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the factor that defines your ceiling is not "whether a certain tool is easy to use," but whether you have built an evolvable engineering system for invoice generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  I. First, align on the concept: Invoice generation is data engineering, not just a layout problem
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Field standardization is the top priority
&lt;/h3&gt;

&lt;p&gt;We recommend defining an invoice domain model before touching the template:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Header fields: invoice_no, issue_date, due_date, currency, locale&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Entity fields: seller, buyer, line_items&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Settlement fields: subtotal, discount, tax_lines, grand_total, paid_amount, balance_due&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Compliance fields: tax_id, invoice_type, jurisdiction, notes&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the field layer is unstable, any PDF tool will become a "chaotic formatting output device".&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Amount calculation must be "single source of truth" on the backend
&lt;/h3&gt;

&lt;p&gt;Do not recalculate amounts in the frontend or template. Recommended approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Backend calculates using smallest currency units or fixed-point arithmetic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Templates only display values, never perform business calculations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Apply locale formatting only at the display layer.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This avoids high‑risk incidents like "amounts look correct on screen but are wrong in accounting."&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Traceability is a core acceptance criterion
&lt;/h3&gt;

&lt;p&gt;Every invoice should support "reconstructable history". Record at least:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Template version&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Input data snapshot (after desensitization)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rendering engine version&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Task ID and generation timestamp&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is crucial for audits, dispute resolution, and historical replay.&lt;/p&gt;

&lt;h2&gt;
  
  
  II. Invoice generation solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Solution 1: Online invoice tools for quick generation (suitable for low volume)
&lt;/h3&gt;

&lt;p&gt;Use cases: Individuals, freelancers, early‑stage teams; low invoice volume, goal is to get invoices out fast.&lt;/p&gt;

&lt;p&gt;Pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Zero development, live in minutes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Good for verifying whether your business fields are complete&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Limited control over templates&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Weak system integration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Insufficient audit, permission, and data governance capabilities&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Solution 2: Template + data binding (mainstream for small/mid‑sized teams)
&lt;/h3&gt;

&lt;p&gt;Use cases: You already have ERP/CRM/order data and are starting to pursue brand consistency and reliable batch output.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Recommended architecture
&lt;/h4&gt;

&lt;p&gt;Adopt a three‑stage pattern: Template + Payload + Renderer&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Template: defines visual structure (headers, tables, terms, pagination rules)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Payload: unified JSON input (output from business layer)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Renderer: performs rendering and outputs PDF&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is far more maintainable than "hard‑coding PDFs" and facilitates team collaboration.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Key points of template governance
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Versioning: invoice_v1, invoice_v2&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Change review: field changes and visual changes approved separately&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Regression samples: keep at least simple line items, multi‑tax‑rate items, long item lists, multi‑currency invoices&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3. How ComPDF fits naturally at this stage
&lt;/h4&gt;

&lt;p&gt;If you are already in the "template governance + batch output" stage and want both output consistency and on‑premise deployment, you can integrate &lt;a href="https://www.compdf.com/pdf-sdk/pdf-generation?utm_source=dev.to_invoice_20260520&amp;amp;utm_medium=referral&amp;amp;utm_campaign=dev.to_invoice_20260520&amp;amp;ref_platform_id=dev.to"&gt;ComPDF Generation SDK&lt;/a&gt; as one implementation of the renderer layer in your existing architecture.&lt;/p&gt;

&lt;p&gt;The goal is not to "replace everything" but to place it inside the Renderer abstraction, allowing you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Keep your existing business data structure&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Smoothly swap rendering implementations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gradually scale as quality and performance requirements increase&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Solution 3: Google Sheets / Form automation (low‑code validation)
&lt;/h3&gt;

&lt;p&gt;Use cases: Business teams want to move first, development resources are limited, or you need a quick POC.&lt;/p&gt;

&lt;p&gt;Typical pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Maintain order rows in a sheet&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Assemble payload with scripts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Call generation API&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write back the URL and send&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Recommendations for professionalization:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add idempotency keys to avoid duplicate generation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add retries on failure and a dead‑letter queue&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a task status dashboard (success rate, failure reasons, percentile latency)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When daily volume continues to grow, migrate to a backend service – don't let critical paths rely on manual triggers for too long.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution 4: Integrate invoice generation API inside business systems (enterprise grade)
&lt;/h3&gt;

&lt;p&gt;Use cases: SaaS, e‑commerce platforms, cross‑regional businesses – require high concurrency, high availability, and auditability.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. API layer design
&lt;/h4&gt;

&lt;p&gt;At a minimum, include these endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;POST /invoices/generate – submit task&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GET /invoices/{task_id} – query status&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;POST /webhooks/invoice-generated – callback notification&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Recommended fields: template_id, template_version, invoice_data, locale, currency, idempotency_key, callback_url.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Reliability and observability metrics
&lt;/h4&gt;

&lt;p&gt;Include these in your SLOs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Success rate&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;P95 / P99 generation latency&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Callback arrival rate&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Retry success rate&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Segment alerts by failure type: template errors, data errors, engine errors, storage errors, callback errors.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. ComPDF's role in enterprise service‑orientation
&lt;/h4&gt;

&lt;p&gt;When you want to turn "invoice generation" into a platform capability, &lt;a href="https://api.compdf.com/api-reference/pdf-generate?utm_source=dev.to_invoice_20260520&amp;amp;utm_medium=referral&amp;amp;utm_campaign=dev.to_invoice_20260520&amp;amp;ref_platform_id=dev.to"&gt;ComPDF Generation API&lt;/a&gt; can be integrated into a shared middleware layer as part of your generation service. A natural approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Keep a unified invoice_data model in the business layer&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Access ComPDF via an adapter in the engine layer&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Handle authentication, audit, monitoring, and rate limiting uniformly at the platform level&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes ComPDF a "rendering capability node" inside your engineering system, not an isolated independent flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  III. 8 control points most easily overlooked during engineering implementation
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Idempotency control  When the same order is triggered repeatedly, only the same result or same task should be produced – no duplicate charges or duplicate sending.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Template/data decoupling  Templates should not directly depend on business database field names. Use a DTO mapping layer to isolate changes and reduce impact when templates are modified.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pagination and long‑table strategy  Define rules for "max row height, repeated table headers on continuation pages, fixed summary area on last page" so finance reviewers won't struggle to read.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Font and locale management  Use a consistent font package and language coverage – especially for mixed Chinese/English text, amount in words, and special symbols.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tax rule versioning  Tax rates, exemption policies, and tax ID display rules should be versionable, supporting switching by region and effective date.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;File lifecycle management  Define clear storage strategies: hot storage, cold storage, deletion period, access expiration, download authentication.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Security and compliance  Desensitize sensitive information, encrypt data in transit, enforce least‑privilege access, and keep immutable audit logs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Regression testing system  Build a "template regression set + data regression set + rendering regression set". Automatically compare key layouts and amount fields on every release.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;What are the minimum required fields for a PDF invoice?  Invoice number, date, seller/buyer information, line items, tax rate, total amount due, and payment terms are the basics. For cross‑border business, also include tax ID and currency rules.&lt;/p&gt;

&lt;p&gt;When must I migrate from an online tool?  Migrate when any of these happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You issue invoices in batches every day.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multiple templates are in use concurrently.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You need auditing and access control.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Customers complain about inconsistent output.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How to avoid template upgrades affecting historical invoices?  Freeze template versions + bind a version number to each historical task. Reconstruct historical invoices using only the original version.&lt;/p&gt;

&lt;p&gt;How to handle generation congestion during peak hours?  Use queue‑based peak shaving, asynchronous tasks, sharded concurrency, and priority strategies – and plan capacity around P95/P99 latency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Invoice generation is a classic "business document engineering" problem. A truly professional solution is not about how quickly you can produce a PDF, but whether you can continue to output reliably as volume grows, rules change, and audit pressure increases.&lt;/p&gt;

&lt;p&gt;By following the roadmap of data standardization → template governance → task reliability → service‑oriented platform, your invoicing system will evolve from "just usable" to controllable, auditable, and sustainable.&lt;/p&gt;

</description>
      <category>invoice</category>
      <category>pdf</category>
    </item>
    <item>
      <title>Best Hospital Information System (HIS) Document Processing: Comparison &amp; Optimization</title>
      <dc:creator>Derek</dc:creator>
      <pubDate>Thu, 07 May 2026 03:48:49 +0000</pubDate>
      <link>https://dev.to/derek-compdf/best-hospital-information-system-his-document-processing-comparison-optimization-35f</link>
      <guid>https://dev.to/derek-compdf/best-hospital-information-system-his-document-processing-comparison-optimization-35f</guid>
      <description>&lt;p&gt;In the field of medical informatization, selecting the “Best Hospital Information System (HIS)” has always been a multi‑dimensional challenge. Traditional evaluations tend to focus on the number of functional modules, response speed, or user interface friendliness. &lt;a href="https://www.sciencedirect.com/topics/nursing-and-health-professions/hospital-information-system" rel="noopener noreferrer"&gt;Research&lt;/a&gt; shows that with growing demands for cross‑institutional collaboration, medical evidence compliance, and legal recognition of electronic documents, document processing capability is becoming a core hidden indicator of HIS maturity.&lt;/p&gt;

&lt;p&gt;This article compares the document/data processing capabilities of widely used HIS solutions and explains how to build a smarter, more flexible medical document and data processing platform using &lt;a href="https://www.compdf.com/solutions/healthcare?utm_source=dev.to_healthcare_20260507&amp;amp;utm_medium=referral&amp;amp;utm_campaign=dev.to_healthcare_20260507"&gt;ComPDF&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side‑by‑Side Comparison of Document Processing Capabilities in Mainstream HIS Software
&lt;/h2&gt;

&lt;p&gt;Based on publicly available customer case studies, product documentation, and industry reports, we compare the current state, pain points, and optimization paths of 12 mainstream HIS systems regarding document processing.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Software&lt;/th&gt;
&lt;th&gt;Country&lt;/th&gt;
&lt;th&gt;Public User Clues (Summary)&lt;/th&gt;
&lt;th&gt;Current Document Processing Status&lt;/th&gt;
&lt;th&gt;Main Pain Points&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Epic&lt;/td&gt;
&lt;td&gt;USA&lt;/td&gt;
&lt;td&gt;Common in large global hospitals; deeply used in complex clinical workflows&lt;/td&gt;
&lt;td&gt;Mature export of medical records, discharge summaries, reports; cross‑institutional governance requires additional build&lt;/td&gt;
&lt;td&gt;High cost of cross‑system document standards, version control, and external sharing compliance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oracle Health (Cerner)&lt;/td&gt;
&lt;td&gt;USA&lt;/td&gt;
&lt;td&gt;Visible in Oracle customer cases (e.g., NU‑MED) integrating HIS&lt;/td&gt;
&lt;td&gt;Enterprise document management capabilities present; multi‑module interfaces may be inconsistent&lt;/td&gt;
&lt;td&gt;Complex document standards and interface governance during post‑merger integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MEDITECH Expanse&lt;/td&gt;
&lt;td&gt;USA&lt;/td&gt;
&lt;td&gt;Customer success pages show reduced A/R days, saved nurse documentation time&lt;/td&gt;
&lt;td&gt;Complete clinical documentation and operational reporting; clear improvement data available&lt;/td&gt;
&lt;td&gt;Multi‑campus template consistency and external sharing anonymization require secondary build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;InterSystems TrakCare&lt;/td&gt;
&lt;td&gt;USA&lt;/td&gt;
&lt;td&gt;Customer testimonials mention "fewer clicks, improved documentation efficiency, support for local compliance documents"&lt;/td&gt;
&lt;td&gt;Strong interoperability and unified data foundation; close coupling between documents and workflows&lt;/td&gt;
&lt;td&gt;Large differences in local document standards across countries; cross‑border compliance complex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dedalus&lt;/td&gt;
&lt;td&gt;Italy&lt;/td&gt;
&lt;td&gt;Common in European public hospital networks; shows regional deployment scenarios&lt;/td&gt;
&lt;td&gt;Strong synergy with regional health platforms&lt;/td&gt;
&lt;td&gt;Long implementation cycles due to differences in cross‑institutional document exchange standards, languages, and encoding systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Altera Digital Health&lt;/td&gt;
&lt;td&gt;USA&lt;/td&gt;
&lt;td&gt;Resource center publishes customer practices focusing on clinical and operational efficiency&lt;/td&gt;
&lt;td&gt;Traditional document workflow capabilities present; undergoing modernization&lt;/td&gt;
&lt;td&gt;Difficulties with document consistency and historical traceability after legacy system migration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPSI / TruBridge&lt;/td&gt;
&lt;td&gt;USA&lt;/td&gt;
&lt;td&gt;Case pages focus on community hospitals – "small team, fast implementation" model&lt;/td&gt;
&lt;td&gt;Basic export/archiving meets daily needs of small and medium hospitals&lt;/td&gt;
&lt;td&gt;Weak advanced document governance (judicial evidence chain, cross‑institution exchange)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;athenahealth&lt;/td&gt;
&lt;td&gt;USA&lt;/td&gt;
&lt;td&gt;Emphasizes outpatient efficiency and revenue cycle improvements; large user base&lt;/td&gt;
&lt;td&gt;Strong document circulation in outpatient scenarios&lt;/td&gt;
&lt;td&gt;Insufficient depth in complex inpatient document authoring and multi‑system compliance archiving&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NextGen Healthcare&lt;/td&gt;
&lt;td&gt;USA&lt;/td&gt;
&lt;td&gt;Common in specialty clinics and ACO scenarios&lt;/td&gt;
&lt;td&gt;Complete outpatient document chain; good specialty‑specific templating&lt;/td&gt;
&lt;td&gt;High coordination cost for cross‑specialty, cross‑institution document standards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;eClinicalWorks&lt;/td&gt;
&lt;td&gt;USA&lt;/td&gt;
&lt;td&gt;Public case studies; user base of 180,000+ physicians&lt;/td&gt;
&lt;td&gt;Comprehensive coverage of clinical documents, patient communication, telehealth documents&lt;/td&gt;
&lt;td&gt;Document quality consistency and audit granularity need improvement in large organizations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wining Health WiNEX HIS&lt;/td&gt;
&lt;td&gt;China&lt;/td&gt;
&lt;td&gt;Present in Chinese top‑tier hospitals and regional medical projects – group + internet hospital scenarios&lt;/td&gt;
&lt;td&gt;Strong localization of medical documents and integration with medical insurance&lt;/td&gt;
&lt;td&gt;Interoperability across vendors and unification of document exchange standards remain challenging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Donghua Medical HIS&lt;/td&gt;
&lt;td&gt;China&lt;/td&gt;
&lt;td&gt;Active in large hospital informatization projects; emphasizes integrated platform&lt;/td&gt;
&lt;td&gt;Comprehensive inpatient/outpatient documentation workflows&lt;/td&gt;
&lt;td&gt;Common issues with digital archiving of legacy medical records and cross‑institution exchange efficiency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chuangye Huikang HIS&lt;/td&gt;
&lt;td&gt;China&lt;/td&gt;
&lt;td&gt;Many cases in regional health and hospital informatization; common in municipal healthcare systems&lt;/td&gt;
&lt;td&gt;Complete basic document management, compliant with local policies&lt;/td&gt;
&lt;td&gt;Document versioning and data fragmentation due to multiple coexisting systems&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note: Some user usage clues are derived from publicly available customer case studies/testimonials, as noted in the table. For formal procurement evaluations, we recommend supplementing with third‑party assessments (e.g., KLAS, local tender acceptance reports, or in‑depth hospital interviews).&lt;/p&gt;

&lt;h2&gt;
  
  
  A Document‑Centered “Best HIS” Evaluation Framework (Ready for System Selection)
&lt;/h2&gt;

&lt;p&gt;Traditional HIS selection often falls into a “feature list competition”. We suggest upgrading it to a “document closed‑loop capability” competition. The core evaluation dimensions are as follows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evaluation Dimension&lt;/th&gt;
&lt;th&gt;Core Question&lt;/th&gt;
&lt;th&gt;Key Indicator Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Document Completeness&lt;/td&gt;
&lt;td&gt;Does it cover the entire chain?&lt;/td&gt;
&lt;td&gt;Can orders, progress notes, lab tests, images, prescriptions, settlements, and external sharing be automatically generated as standard documents?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Document Trustworthiness&lt;/td&gt;
&lt;td&gt;Is it admissible as judicial evidence?&lt;/td&gt;
&lt;td&gt;Supports digital signatures, signature verification, trusted timestamps, and immutable audit logs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Document Interoperability&lt;/td&gt;
&lt;td&gt;Can it reduce duplicate data entry?&lt;/td&gt;
&lt;td&gt;Supports HL7/FHIR, standard metadata mapping; bidirectional synchronization with RIS/LIS/PACS.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Document Efficiency&lt;/td&gt;
&lt;td&gt;How well does it handle batch operations?&lt;/td&gt;
&lt;td&gt;Batch generation, template governance, automatic archiving, full‑text search, tracked secondary edits.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Document Compliance&lt;/td&gt;
&lt;td&gt;Does it comply with privacy and sharing regulations?&lt;/td&gt;
&lt;td&gt;Privacy anonymization, dynamic watermarks, “minimum necessary” sharing principle, external sharing approval workflows.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  ComPDF Implementation Roadmap for HIS Scenarios
&lt;/h2&gt;

&lt;p&gt;To address the pain points above, ComPDF can be deployed in parallel with existing HIS systems – no core system replacement is required. We recommend a three‑phase approach:&lt;/p&gt;

&lt;h4&gt;
  
  
  Phase 1: Assessment &amp;amp; Standardization
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  Identify high‑frequency document types: discharge summaries, lab reports, medical record loan packages, medical insurance claim packages.&lt;/li&gt;
&lt;li&gt;  Standardize templates: fonts, margins, metadata fields (patient ID, department, attending physician, timestamp).&lt;/li&gt;
&lt;li&gt;  Establish mapping between document types and HIS business tables.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Phase 2: Capability Integration
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  Integrate &lt;a href="https://www.compdf.com/pdf-sdk/digital-signatures?utm_source=dev.to_healthcare_20260507&amp;amp;utm_medium=referral&amp;amp;utm_campaign=dev.to_healthcare_20260507"&gt;digital signature&lt;/a&gt; / verification services (supports SM, RSA, and other algorithms).&lt;/li&gt;
&lt;li&gt;  Configure r&lt;a href="https://www.compdf.com/pdf-sdk/security?utm_source=dev.to_healthcare_20260507&amp;amp;utm_medium=referral&amp;amp;utm_campaign=dev.to_healthcare_20260507"&gt;edaction&lt;/a&gt; rules (e.g., auto‑masking of name, ID number, contact info) and dynamic watermarks.&lt;/li&gt;
&lt;li&gt;  Deploy &lt;a href="https://www.compdf.com/pdf-sdk/pdf-generation?utm_source=dev.to_healthcare_20260507&amp;amp;utm_medium=referral&amp;amp;utm_campaign=dev.to_healthcare_20260507"&gt;batch generation&lt;/a&gt; queues and automatic table of contents / bookmark functions.&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://www.compdf.com/solutions/intelligent-document-processing?utm_source=dev.to_healthcare_20260507&amp;amp;utm_medium=referral&amp;amp;utm_campaign=dev.to_healthcare_20260507"&gt;Document parsing and key data extraction&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;  Other document processing: basic page editing, content editing, document viewing &amp;amp; annotation, format conversion, encryption/decryption, PDF/A archiving, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Phase 3: Operations &amp;amp; Continuous Optimization
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  Launch a quality dashboard to monitor generation latency, failure rate, anonymization hit rate, and external sharing traceability.&lt;/li&gt;
&lt;li&gt;  Conduct problem attribution and template iteration by department or document type.&lt;/li&gt;
&lt;li&gt;  Regularly export audit reports to meet Grade‑A security (Classified Protection) and EMR rating requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As the value of medical data becomes increasingly prominent, “best” in HIS should not be defined solely by the number of features, but by the system’s ability to produce trustworthy, traceable, and interoperable documents. By introducing a document middle‑platform like ComPDF, healthcare institutions can, with minimal cost and while preserving existing investments, cross the threshold of document governance and truly move from “go‑live success” to “clinical value success”.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Adobe vs. ComPDF Conversion SDK V4.0.0 – PDF to Word Conversion Performance Comparison</title>
      <dc:creator>Derek</dc:creator>
      <pubDate>Thu, 30 Apr 2026 07:01:25 +0000</pubDate>
      <link>https://dev.to/derek-compdf/adobe-vs-compdf-conversion-sdk-v400-pdf-to-word-conversion-performance-comparison-cci</link>
      <guid>https://dev.to/derek-compdf/adobe-vs-compdf-conversion-sdk-v400-pdf-to-word-conversion-performance-comparison-cci</guid>
      <description>&lt;p&gt;For real‑world PDF‑to‑Word conversion scenarios, this article selects three representative test documents to compare the conversion results of&amp;nbsp;&lt;strong&gt;&lt;a href="https://acrobat.adobe.com/link/acrobat/pdf-to-word" rel="noopener noreferrer"&gt;Adobe Online Tools&lt;/a&gt;&lt;/strong&gt;&amp;nbsp;and&amp;nbsp;&lt;strong&gt;&lt;a href="https://www.compdf.com/conversion/office-files?utm_source=adobe_vs_conversion4.0_dev.to&amp;amp;utm_medium=referral&amp;amp;utm_campaign=adobe_vs_conversion4.0_dev.to"&gt;ComPDF Conversion SDK V4.0.0&lt;/a&gt;&lt;/strong&gt;. The comparison focuses on paragraph integrity, table reconstruction, image embedding, font &amp;amp; color preservation, multi‑column layout recognition, and file size, helping teams quickly determine the suitability of each tool for different business contexts.&lt;/p&gt;

&lt;p&gt;Overall, the two tools do not produce a clear winner in every aspect. Adobe performs more stably in heading recognition and certain format retention, while ComPDF holds an advantage in font and color preservation. In other words, if document structure is more important, Adobe provides more consistent results; if retaining fonts and visual elements is the priority, ComPDF is the better choice.&lt;/p&gt;




&lt;h3&gt;
  
  
  I. Test Scope &amp;amp; Evaluation Method
&lt;/h3&gt;

&lt;p&gt;Three complex PDF samples were selected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A brochure‑style page rich in fonts and images,&lt;/li&gt;
&lt;li&gt;  A complex document with multiple columns and tables,&lt;/li&gt;
&lt;li&gt;  A well‑structured technical form.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each PDF was converted to DOCX. The resulting files were analyzed using automated scripts to count:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Font types&lt;/li&gt;
&lt;li&gt;  Color types&lt;/li&gt;
&lt;li&gt;  Heading‑style paragraphs&lt;/li&gt;
&lt;li&gt;  Bold and italic runs&lt;/li&gt;
&lt;li&gt;  Multi‑column sections&lt;/li&gt;
&lt;li&gt;  Number of tables&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &amp;nbsp;Automated metrics are useful for observing “structural preservation” and “object segmentation,” but they do not fully replace human visual judgment. For instance, a higher number of embedded images does not guarantee a better visual experience. Therefore, &lt;a href="https://drive.google.com/drive/folders/1s3UUNTN3GANMY3-VfE94rMsfGOatbQ1E" rel="noopener noreferrer"&gt;here are links to the converted Word files and original PDFs&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  II. Sample 1 – Rich Fonts, Colors, and Images
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Original PDF:&lt;/strong&gt; &amp;nbsp;7 pages, 10 fonts, 4 non‑black text colors, 2 images, multi‑column layout.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Adobe Online Tools&lt;/th&gt;
&lt;th&gt;ComPDF SDK V4.0.0&lt;/th&gt;
&lt;th&gt;Winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Embedded images&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;152&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Font types&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;ComPDF ✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Color types&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;ComPDF ✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bold runs&lt;/td&gt;
&lt;td&gt;110&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;Adobe ✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Italic runs&lt;/td&gt;
&lt;td&gt;170&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;Adobe ✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi‑col sections&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Tie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File size (KB)&lt;/td&gt;
&lt;td&gt;527.7&lt;/td&gt;
&lt;td&gt;870.5&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heading‑style paragraphs&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Adobe ✓&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Observations:&lt;/strong&gt; &amp;nbsp;ComPDF preserves fonts and colors more faithfully, identifying 5 fonts and 3 non‑black text colors (vs. Adobe’s 4 and 1). However, Adobe handles formatting details better: it recognizes more bold/italic runs and retains 12 heading‑style paragraphs, making the output easier to edit and navigate.&lt;/p&gt;




&lt;h3&gt;
  
  
  III. Sample 2 – Complex Document with Multiple Columns, Tables, and Text Attributes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Original PDF:&lt;/strong&gt; &amp;nbsp;11 pages, 28 fonts, 6 non‑black text colors, no images, multi‑column layout.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Adobe Online Tools&lt;/th&gt;
&lt;th&gt;ComPDF SDK V4.0.0&lt;/th&gt;
&lt;th&gt;Winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tables restored&lt;/td&gt;
&lt;td&gt;4 (Table 1 borderless table failed)&lt;/td&gt;
&lt;td&gt;4 (Table 4 layout failed)&lt;/td&gt;
&lt;td&gt;Tie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embedded images&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;145&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Font types&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;ComPDF ✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Color types&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Tie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bold runs&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;108&lt;/td&gt;
&lt;td&gt;ComPDF ✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Italic runs&lt;/td&gt;
&lt;td&gt;224&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Adobe ✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi‑col sections&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;ComPDF ✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File size (KB)&lt;/td&gt;
&lt;td&gt;137.9&lt;/td&gt;
&lt;td&gt;222.3&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heading‑style paragraphs&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Adobe ✓&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Observations:&lt;/strong&gt; &amp;nbsp;Both tools reconstruct a similar number of tables, but each has shortcomings: Adobe fails on a borderless table (Table 1), while ComPDF has layout issues with a specially formatted table (Table 4). For complex tables, manual adjustment is still needed. ComPDF is more aggressive in preserving complex page structures and font differences, and it captures more bold runs, indicating better sensitivity to local emphasis formatting.&lt;/p&gt;




&lt;h3&gt;
  
  
  IV. Sample 3 – Technical Form Document
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Original PDF:&lt;/strong&gt; &amp;nbsp;2 pages, 9 fonts, 1 non‑black text color, no images, multi‑column layout.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Adobe Online Tools&lt;/th&gt;
&lt;th&gt;ComPDF SDK V4.0.0&lt;/th&gt;
&lt;th&gt;Winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tables restored&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Tie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embedded images&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Font types&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;ComPDF ✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Color types&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Tie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bold runs&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Adobe ✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Italic runs&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Adobe ✓&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi‑col sections&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Tie&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File size (KB)&lt;/td&gt;
&lt;td&gt;12.5&lt;/td&gt;
&lt;td&gt;35.6&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heading‑style paragraphs&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Adobe ✓&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Observations:&lt;/strong&gt; &amp;nbsp;Both tools perform consistently on table reconstruction, color retention, and multi‑column recognition when dealing with well‑structured technical forms. Differences persist in fonts and formatting details: ComPDF preserves more font types, while Adobe continues to lead in bold/italic and heading‑style retention.&lt;/p&gt;




&lt;h3&gt;
  
  
  V. Overall Results – Two Distinct Objectives
&lt;/h3&gt;

&lt;p&gt;Aggregating results from the three test documents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;ComPDF&lt;/strong&gt;&amp;nbsp;wins all three tests in&amp;nbsp;&lt;strong&gt;font preservation&lt;/strong&gt;, and wins one test and ties two in&amp;nbsp;&lt;strong&gt;color preservation&lt;/strong&gt;&amp;nbsp;→ stronger visual attribute retention.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Adobe&lt;/strong&gt;&amp;nbsp;leads in&amp;nbsp;&lt;strong&gt;heading preservation&lt;/strong&gt;&amp;nbsp;across all three tests, and is more competitive in&amp;nbsp;&lt;strong&gt;format retention (bold)&lt;/strong&gt; &amp;nbsp;and&amp;nbsp;&lt;strong&gt;paragraph integrity&lt;/strong&gt;&amp;nbsp;→ more consistent structured output.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Choose Adobe Online Tools if your business requires:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Easily editable Word documents with clear heading structures, good paragraph continuity, and suitability for office collaboration, review, and content repurposing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose ComPDF Conversion SDK V4.0.0 if your business requires:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Maximum retention of original PDF font variations, color information, and complex layout characteristics, especially when visual fidelity is critical for presentation or further technical processing.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
