<?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: IronSoftware</title>
    <description>The latest articles on DEV Community by IronSoftware (@ironsoftware).</description>
    <link>https://dev.to/ironsoftware</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%2F714737%2F30e70529-1628-476f-9a14-3b157c0f5c82.png</url>
      <title>DEV Community: IronSoftware</title>
      <link>https://dev.to/ironsoftware</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ironsoftware"/>
    <language>en</language>
    <item>
      <title>xhtml2pdf in Python: The Dependency You Inherit</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Thu, 03 Sep 2026 21:59:48 +0000</pubDate>
      <link>https://dev.to/ironsoftware/xhtml2pdf-in-python-the-dependency-you-inherit-11dk</link>
      <guid>https://dev.to/ironsoftware/xhtml2pdf-in-python-the-dependency-you-inherit-11dk</guid>
      <description>&lt;p&gt;One line of xhtml2pdf's dependency list is the whole architecture, and it is &lt;code&gt;reportlab&amp;lt;5,&amp;gt;=4.0.4&lt;/code&gt;. The package translates parsed markup onto ReportLab's drawing model, so the fidelity ceiling of every document it produces is set by what that model can express rather than by what a browser would draw, and the version of the model it can express is decided by somebody else's release policy. Two calendars govern one feature from here on, with the lower of the two fixing the ceiling, which is a different commitment from &lt;a href="https://ironpdf.com/python/tutorials/html-to-pdf/" rel="noopener noreferrer"&gt;rendering the markup as a browser renders it&lt;/a&gt; or from &lt;a href="https://ironpdf.com/python/how-to/python-create-pdf/" rel="noopener noreferrer"&gt;producing the document from one package&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full disclosure. IronPDF comes from our team at Iron Software. What follows weighs what xhtml2pdf's pinned ReportLab ceiling and documented layout errors cost a build, and how IronPDF renders the same markup through a browser engine.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Deep Does the ReportLab Pin Go?
&lt;/h2&gt;

&lt;p&gt;Incoming markup is parsed with html5lib and mapped onto ReportLab's Platypus document model to produce the final file, which is the pipeline the project documents. Nothing in that path evaluates CSS the way a layout engine does, so what reaches the page is whatever Platypus can be asked to draw. Raising the ceiling therefore means waiting for ReportLab's model to gain a capability and then waiting for xhtml2pdf to translate onto it, which is two upstream projects deep for a change that a browser engine would already have.&lt;/p&gt;

&lt;p&gt;One useful side effect comes with that pin. ReportLab's serious historical CVEs, including remote code execution and injection findings, are scoped to its 3.x series, and &lt;code&gt;reportlab&amp;lt;5,&amp;gt;=4.0.4&lt;/code&gt; sits outside that range, so none of those advisories applies to a current xhtml2pdf install. It also means the 5.x line, and the security review that release was built around, sits on the far side of a pin that has not moved. Watching two projects to know where one library stands is the maintenance shape being adopted here, where a single &lt;a href="https://ironpdf.com/python/docs/" rel="noopener noreferrer"&gt;documented API surface&lt;/a&gt; puts that dependency question inside one package.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does a Scanner Still Flag 0.2.17?
&lt;/h2&gt;

&lt;p&gt;A single CVE sits against xhtml2pdf's own code. CVE-2024-25885 is a moderate-severity regular-expression denial-of-service issue in the &lt;code&gt;getcolor&lt;/code&gt; function, affecting versions up to and including 0.2.16. The wrinkle is in the record rather than the code, because both the GitHub Advisory Database and the NVD list it with no fixed version recorded, which surfaces in a naive dependency scan as an open, unpatched issue. The project's own 0.2.17 release notes list a fix for the reDoS issue in getColor among the bug fixes, and Snyk's scan of 0.2.17 reports nothing outstanding, so a scan that stops at the advisory database's fixed-version field is reading a stale record rather than the state of the package. That distinction is worth carrying into the same review as &lt;a href="https://ironpdf.com/python/examples/security-and-metadata/" rel="noopener noreferrer"&gt;document metadata and permissions&lt;/a&gt;, because somebody has to answer for it every audit.&lt;/p&gt;

&lt;p&gt;Release cadence is slow and the commit record is steadier than it. Version 0.2.17 was published on 23 February 2025 and is still the current release eighteen months later, while the repository is neither archived nor disabled and took commits as recently as 30 August 2026. That is a community-run project still taking contributions between releases, and it is also a project whose next release date nobody can plan around.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Narrow Case for a Pure-Python Install
&lt;/h2&gt;

&lt;p&gt;Every required dependency is pure Python, covering html5lib, Pillow, pypdf, python-bidi, arabic-reshaper, pyHanko, pyhanko-certvalidator, svglib, and ReportLab itself, so the install needs nothing on the system path, meaning no Pango, no browser binary, no apt-get layer, and no root access. That is decisive inside a slim Docker image, a Lambda deployment package, or a CI runner where system packages are not available, and only an optional &lt;code&gt;pycairo&lt;/code&gt; extra tied to one SVG backend pulls in a system graphics library at all. The document furniture is better than the category average too, with a working table-of-contents tag, numbered pages, barcode generation, &lt;a href="https://ironpdf.com/python/examples/unicode/" rel="noopener noreferrer"&gt;right-to-left and bidirectional text&lt;/a&gt; through required rather than optional dependencies, and &lt;a href="https://ironpdf.com/python/examples/digitally-sign-a-pdf/" rel="noopener noreferrer"&gt;digital signatures&lt;/a&gt; through pyHanko, all under the Apache License 2.0 with the patent grant that MIT and BSD terms do not carry. Where a build cannot install anything outside pip and the documents are simple and tabular, that install is the one case for a translation layer over a rendering engine, though the constraint that forces it is rarer each year, and IronPDF answers the browser-authored half of the same problem from one package.&lt;/p&gt;

&lt;p&gt;The integration surface for a basic conversion is one function.&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;from&lt;/span&gt; &lt;span class="n"&gt;xhtml2pdf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pisa&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;html_to_pdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source_html&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="n"&gt;output_path&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_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;wb&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;output_file&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;pisa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CreatePDF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source_html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;output_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;

&lt;span class="n"&gt;invoice_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;Invoice #1042&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;Total due: $412.00&amp;lt;/p&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="nf"&gt;html_to_pdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice_html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice.pdf&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;That writes the invoice to disk and returns &lt;code&gt;True&lt;/code&gt; when the conversion reports no errors. The &lt;code&gt;pisa&lt;/code&gt; module name is a holdover from the project's original name before it was renamed and relicensed around 2010, rather than a sign that an older unrelated package has been installed by mistake. Page numbering and the table-of-contents tag come from xhtml2pdf's own tag set, where a browser engine takes &lt;a href="https://ironpdf.com/python/examples/page-numbers-and-page-breaks/" rel="noopener noreferrer"&gt;page numbers and breaks&lt;/a&gt; from the stylesheet.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;xhtml2pdf &lt;code&gt;0.2.17&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;IronPDF for Python&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rendering path&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pisa.CreatePDF&lt;/code&gt; onto ReportLab's Platypus model&lt;/td&gt;
&lt;td&gt;Chromium through &lt;code&gt;ChromePdfRenderer&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CSS coverage&lt;/td&gt;
&lt;td&gt;Page, frame, and vendor properties, no flexbox or grid&lt;/td&gt;
&lt;td&gt;Modern CSS as the browser implements it, flexbox and grid included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JavaScript on the page&lt;/td&gt;
&lt;td&gt;No scripting stage in the pipeline&lt;/td&gt;
&lt;td&gt;Runs before &lt;code&gt;RenderHtmlAsPdf&lt;/code&gt; captures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System libraries to install&lt;/td&gt;
&lt;td&gt;None for the core conversion path&lt;/td&gt;
&lt;td&gt;Handled inside the packaged runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A table cell spanning a page break&lt;/td&gt;
&lt;td&gt;Documented as an error&lt;/td&gt;
&lt;td&gt;Paginated as the stylesheet asks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Floated or inline images&lt;/td&gt;
&lt;td&gt;Rendered in their own paragraph&lt;/td&gt;
&lt;td&gt;Positioned where the layout puts them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Right-to-left and bidirectional text&lt;/td&gt;
&lt;td&gt;python-bidi and arabic-reshaper, required&lt;/td&gt;
&lt;td&gt;Rendered through the browser text stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Digital signatures&lt;/td&gt;
&lt;td&gt;pyHanko, a separate dependency&lt;/td&gt;
&lt;td&gt;Signing on the same &lt;code&gt;PdfDocument&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Licence&lt;/td&gt;
&lt;td&gt;Apache License 2.0&lt;/td&gt;
&lt;td&gt;Commercial, one tier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python versions accepted&lt;/td&gt;
&lt;td&gt;3.8 to 3.12&lt;/td&gt;
&lt;td&gt;3.7 and later&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Table 1. Conversion path and document output only, xhtml2pdf against IronPDF for Python, as each project documents itself.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The second, fifth, and sixth rows are where a design and its output stop matching, because flexbox, a table crossing a page, and a floated image are all ordinary in &lt;a href="https://ironpdf.com/python/examples/using-html-to-create-a-pdf/" rel="noopener noreferrer"&gt;markup written for a browser&lt;/a&gt;. The rest is a question of what the build environment allows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Does the Translation Layer Stop?
&lt;/h2&gt;

&lt;p&gt;Documented CSS support here is page-shaped rather than layout-shaped. What the reference covers is page size and margins, frame position and dimensions, and vendor extensions such as &lt;code&gt;-pdf-frame-content&lt;/code&gt;, &lt;code&gt;-pdf-keep-with-next&lt;/code&gt;, and the &lt;code&gt;-pdf-outline&lt;/code&gt; family, and nothing in it names flexbox, grid, &lt;code&gt;position&lt;/code&gt;, &lt;code&gt;float&lt;/code&gt;, &lt;code&gt;box-shadow&lt;/code&gt;, &lt;code&gt;background-image&lt;/code&gt;, or &lt;code&gt;z-index&lt;/code&gt;. Markup written for a browser layout does not degrade politely against that list, so a card layout, a sticky header, or a grid dashboard comes out as whatever the flowable model can approximate, which is often nothing like the design it was built from.&lt;/p&gt;

&lt;p&gt;No scripting stage appears anywhere in the xhtml2pdf pipeline and no JavaScript engine appears in its dependency tree, so a page that assembles its content client-side, whether through a charting library, a templating framework injecting DOM after load, or a table populated by a later call, has to be pre-rendered to static markup before conversion. That pre-render step is a service to write and operate, and it is the point where &lt;a href="https://ironpdf.com/python/examples/js-charts-to-pdf/" rel="noopener noreferrer"&gt;charts drawn in the browser&lt;/a&gt; stop being a template detail and become infrastructure.&lt;/p&gt;

&lt;p&gt;Three limits are documented in the reference in those words. The main restriction, as the docs put it, is that table cells longer than one page lead to an error, tables cannot float left or right and cannot be inlined, and images always render in a separate paragraph because ReportLab does not support images inside paragraphs, so floating is not available. An invoice with a long line-item table or a report that wraps text around a figure meets a stated limit, not a bug that a workaround can route around, which makes it a design constraint on every template the pipeline will ever &lt;a href="https://ironpdf.com/python/examples/using-html-to-create-a-pdf/" rel="noopener noreferrer"&gt;convert from existing markup&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Chromium Renders That a Mapping Cannot
&lt;/h2&gt;

&lt;p&gt;The template usually outgrows the translation layer one panel at a time. An invoice that started as a plain table picks up a summary panel built with flexbox, and the layout that made the pure-Python install possible stops keeping up.&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;from&lt;/span&gt; &lt;span class="n"&gt;ironpdf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="n"&gt;renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChromePdfRenderer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RenderHtmlAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
&amp;lt;html&amp;gt;
&amp;lt;body style=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;font-family: sans-serif; display: flex; gap: 16px;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;
    &amp;lt;div style=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flex: 1; background: #f4f4f4; padding: 12px;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;Line items&amp;lt;/div&amp;gt;
    &amp;lt;div style=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flex: 1; background: #eef; padding: 12px;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;Totals&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SaveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice.pdf&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;Both panels come out side by side with their spacing and background colours intact, because the flexbox rules are evaluated by the same engine a browser uses rather than mapped onto a drawing model first. Once the document exists, &lt;a href="https://ironpdf.com/python/examples/merge-pdfs/" rel="noopener noreferrer"&gt;assembling it with other files&lt;/a&gt; happens on the same object.&lt;/p&gt;

&lt;p&gt;IronPDF renders that markup through a browser engine, so flexbox and grid arrive as designed, a long table breaks across pages instead of raising an error, and an image floats where the stylesheet puts it. The narrow case that stays with xhtml2pdf is a build that cannot install anything outside pip, and fewer teams are held to that every year. IronPDF has a &lt;a href="https://ironpdf.com/python/licensing/" rel="noopener noreferrer"&gt;free trial&lt;/a&gt; if you want to run the template that broke against a rendering engine before rewriting it to fit the property list.&lt;/p&gt;

&lt;p&gt;What comes with xhtml2pdf either way is the line at the top of this piece. &lt;code&gt;reportlab&amp;lt;5,&amp;gt;=4.0.4&lt;/code&gt; sits in another project's dependency list, and it decides when the layout ceiling moves, which ReportLab release a security review is actually reviewing, and how long a template waits for a property that browsers shipped years ago. Reading that line first tells you more about the next three years of the pipeline than any feature table will.&lt;/p&gt;

&lt;p&gt;Is your own template on the near or the far side of that ceiling? Tell us in the comments, especially if you have hit the long-table or floated-image constraint in production and found a route around it that held up.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;xhtml2pdf and ReportLab are the property of their respective maintainers, and we have no affiliation with either project. The dependency, licence, and advisory details above are drawn from xhtml2pdf's own README, PyPI metadata, and release notes at the time of writing. If a detail has changed since, the comments are open.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>pdf</category>
      <category>django</category>
      <category>architecture</category>
    </item>
    <item>
      <title>fpdf2 in Python: What the Code-First Path Costs Later</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Thu, 03 Sep 2026 18:43:16 +0000</pubDate>
      <link>https://dev.to/ironsoftware/fpdf2-in-python-what-the-code-first-path-costs-later-1221</link>
      <guid>https://dev.to/ironsoftware/fpdf2-in-python-what-the-code-first-path-costs-later-1221</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;fpdf2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fpdf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FPDF&lt;/span&gt;

&lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FPDF&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Helvetica&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cell&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;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invoice #1042&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ln&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice.pdf&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;One command pulling a 329 KB pure-Python wheel and three dependencies behind it, then seven lines putting a one-page PDF on disk at 14 point, with no compiled extension anywhere in the install and no browser process at run time. Read the two blocks together and they are the whole argument for fpdf2, which is also where the argument against it lives, because every element on that page costs another method call, written in the order the page is drawn. An invoice that already exists as HTML gets rebuilt field by field before fpdf2 can produce it, rather than &lt;a href="https://ironpdf.com/python/how-to/python-create-pdf/" rel="noopener noreferrer"&gt;going to a renderer as it stands&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full disclosure. IronPDF is what our team at Iron Software builds. This piece examines what fpdf2's absent CSS path and Python 3.10 floor cost later, and how IronPDF turns an existing template into a PDF without a rebuild.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does a Code-First Path Cost Later?
&lt;/h2&gt;

&lt;p&gt;The first boundary is markup. The &lt;code&gt;write_html()&lt;/code&gt; method covers a fixed tag set, covering headings, paragraphs, basic tables, lists, links, and a handful of inline styles, with no general CSS support beyond page-break hints. The fpdf2 documentation states that the whole HTML 5 specification is not supported and neither is CSS, and it points readers toward other libraries for that job. A design that already exists as a web page, an email template, or a stylesheet-driven layout gets rebuilt by hand through the cell and layout API rather than &lt;a href="https://ironpdf.com/python/examples/using-html-to-create-a-pdf/" rel="noopener noreferrer"&gt;handed to a renderer as it stands&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The second is round-tripping. fpdf2 generates documents rather than reading them, and the project ships a dedicated Combine with pypdf page for exactly that reason. Any workflow that opens, stamps, or &lt;a href="https://ironpdf.com/python/how-to/python-merge-pdf/" rel="noopener noreferrer"&gt;merges a document that already exists&lt;/a&gt; therefore carries two dependencies with two release cadences and two security surfaces, which is a maintenance cost that shows up years after the choice was made.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Little Does the Install Actually Need?
&lt;/h2&gt;

&lt;p&gt;Nothing about fpdf2 is stronger than its install, and that is not a small point. The current release is a 329 KB pure-Python wheel with three required dependencies, namely defusedxml, Pillow, and fonttools, and no compiled extensions, so it installs the same way on a locked-down Windows box, an Alpine container, and a Lambda function. Underneath sit more than 1,300 unit tests running on Linux and Windows with qpdf-based PDF diffing plus timing and memory checks, validation against PDF Checker and VeraPDF, and real depth for the size, covering Unicode font embedding, SVG import, barcodes, tables, &lt;a href="https://ironpdf.com/python/examples/encryption-and-decryption/" rel="noopener noreferrer"&gt;encryption&lt;/a&gt;, digital signing, and &lt;a href="https://ironpdf.com/python/examples/pdfa/" rel="noopener noreferrer"&gt;PDF/A output&lt;/a&gt;. For a document that never leaves the code that generates it, that footprint is the one case for keeping a constructor rather than a renderer in the dependency list, though few pipelines stay that self-contained for long, and IronPDF answers the markup half of the same question from one package.&lt;/p&gt;

&lt;p&gt;None of that is bought with dependency weight, which is the trade the whole library is built around, and the same page setup arrives as &lt;a href="https://ironpdf.com/python/examples/pdf-generation-settings/" rel="noopener noreferrer"&gt;render options&lt;/a&gt; on a markup-first path.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;fpdf2 &lt;code&gt;2.8.8&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;IronPDF for Python&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Existing HTML and CSS as input&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;write_html()&lt;/code&gt; tag subset, no CSS beyond break hints&lt;/td&gt;
&lt;td&gt;Chromium through &lt;code&gt;ChromePdfRenderer&lt;/code&gt;, CSS and JavaScript included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documents built from code&lt;/td&gt;
&lt;td&gt;Procedural placement, &lt;code&gt;cell&lt;/code&gt; and &lt;code&gt;output&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Data merged into a template, then &lt;code&gt;RenderHtmlAsPdf&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Opening a PDF that already exists&lt;/td&gt;
&lt;td&gt;Cannot parse existing files, pypdf recommended alongside&lt;/td&gt;
&lt;td&gt;Load, stamp, and merge on the same &lt;code&gt;PdfDocument&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Install footprint&lt;/td&gt;
&lt;td&gt;329 KB pure-Python wheel, 3 required dependencies&lt;/td&gt;
&lt;td&gt;1 package, 0 system dependencies, engine included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python versions accepted&lt;/td&gt;
&lt;td&gt;3.10 and later&lt;/td&gt;
&lt;td&gt;3.7 and later&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Licence&lt;/td&gt;
&lt;td&gt;LGPL-3.0-only&lt;/td&gt;
&lt;td&gt;Commercial, one tier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Release record&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;2.8.8&lt;/code&gt; on 9 August 2026, 4 releases in 12 months&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;2026.9.0.2&lt;/code&gt; on 1 September 2026, 11 in 12 months&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Document features&lt;/td&gt;
&lt;td&gt;Tables, barcodes, SVG import, encryption, signing, PDF/A-1, PDF/A-2, PDF/A-3&lt;/td&gt;
&lt;td&gt;Browser-accurate layout, page control, forms, and signing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Table 1. Document input and output paths only, fpdf2 against IronPDF for Python, as each project documents itself.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Migrations are decided by the markup row and the round-tripping row, since &lt;a href="https://ironpdf.com/python/examples/stamping-new-content/" rel="noopener noreferrer"&gt;stamping a document that already exists&lt;/a&gt; is where the second dependency joins. The others describe two defensible starting points rather than a gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Constraints That Come From Outside Your Code
&lt;/h2&gt;

&lt;p&gt;The interpreter floor is the first. fpdf2 requires Python 3.10 or later, so a codebase still running 3.8 or 3.9, which is ordinary in estates that upgrade on a slower cycle, cannot install the current release until the runtime itself moves. That turns a library upgrade into a platform upgrade, and it lands on exactly the older services least likely to be scheduled for one.&lt;/p&gt;

&lt;p&gt;The licence is the second. fpdf2 ships under the LGPL, stated in the README badge and reported as LGPL-3.0 by GitHub's licence detector. That is a different family from the MIT and BSD terms common across the other Python PDF libraries, and the licence text defines an Application as any work that makes use of an interface provided by the Library but which is not otherwise based on the Library, which is the language that governs code merely importing the package. What that means for a specific codebase is a question for counsel rather than a comparison article, and the text is public for anyone who needs the close read. IronPDF is licensed commercially, so the terms sit in the purchase rather than in the obligations attached to the code that imports it.&lt;/p&gt;

&lt;p&gt;The security record is clean. The GitHub Advisory Database, Snyk, the NVD, and OSV.dev return nothing against fpdf2's own code, and the long-dormant PyFPDF it forked from is equally clear. CVEs do surface against the similarly named PHP libraries FPDF and FPDI, which share ancestry but not a codebase, so a scanner matching on the name alone produces noise somebody has to answer for, which is worth settling in the same review as &lt;a href="https://ironpdf.com/python/examples/security-and-metadata/" rel="noopener noreferrer"&gt;the metadata and permissions&lt;/a&gt; on the output. The project sits in the py-pdf GitHub organization alongside pypdf, and its CI workflow runs zizmor, pylint, bandit, semgrep, grype, and guarddog on every change, which is more security tooling than most volunteer-maintained libraries carry.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens When the Source Is Already a Page?
&lt;/h2&gt;

&lt;p&gt;What moves a team is the first boundary made concrete. A report template that already exists as HTML with a stylesheet, an invoice designed in a browser rather than assembled field by field, or a page that needs its script to run before the layout is complete.&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;from&lt;/span&gt; &lt;span class="n"&gt;ironpdf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="n"&gt;renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChromePdfRenderer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RenderHtmlFileAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice_template.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SaveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice.pdf&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;That renders the template file exactly as a browser would paint it, stylesheet included, and writes it to disk. The same &lt;code&gt;ChromePdfRenderer&lt;/code&gt; takes a string in memory through &lt;code&gt;RenderHtmlAsPdf&lt;/code&gt; or &lt;a href="https://ironpdf.com/python/examples/converting-a-url-to-a-pdf/" rel="noopener noreferrer"&gt;a live page through its URL&lt;/a&gt;, so the entry point does not change when the markup moves.&lt;/p&gt;

&lt;p&gt;IronPDF turns the template a designer already owns into the finished PDF without rebuilding it field by field, runs on Python 3.7 and later so the interpreter floor never gates the upgrade, and opens the documents that already exist rather than handing that job to a second library. The narrow case that stays with fpdf2 is a document assembled entirely in code on a runtime already at 3.10. IronPDF has a &lt;a href="https://ironpdf.com/python/licensing/" rel="noopener noreferrer"&gt;free trial&lt;/a&gt; if you want to put one of your existing templates through it before rebuilding it by hand.&lt;/p&gt;

&lt;p&gt;So which does your document actually start as, code or a page? Tell us in the comments which one your last project needed, especially if you ended up carrying fpdf2 and a second library side by side to cover both.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;fpdf2 and PyFPDF are the property of their maintainers, and we have no affiliation with the py-pdf organization. The release, licence, and security details above rest on the project's own documentation, repository, and PyPI metadata at the time of writing. If a detail has changed since, please let us know below.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>pdf</category>
      <category>opensource</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Aspose.PDF for Python: Which Tier Ships to Production?</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Thu, 03 Sep 2026 17:20:15 +0000</pubDate>
      <link>https://dev.to/ironsoftware/asposepdf-for-python-which-tier-ships-to-production-j8i</link>
      <guid>https://dev.to/ironsoftware/asposepdf-for-python-which-tier-ships-to-production-j8i</guid>
      <description>&lt;p&gt;The prototype works, the invoices render, and somebody asks what the licence costs. The purchase page answers with two numbers. Developer Small Business is $1,199, the tier the prototype was built under. Developer OEM is $3,597, the first tier Aspose's own license-types policy allows on a public-facing website or inside anything shipped to a third party. Nothing in the code changes between them. What changes is who may see the output, a question worth settling before the prototype rather than after, and one that never arises when &lt;a href="https://ironpdf.com/python/tutorials/html-to-pdf/" rel="noopener noreferrer"&gt;the same HTML goes through a bundled browser engine&lt;/a&gt; on a single tier.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full disclosure. Our team at Iron Software builds IronPDF. This read traces what Aspose.PDF's entry-tier deployment ban and moving wheel matrix cost a team, and what IronPDF renders from a single tier on every platform.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tier Ladder, Rung by Rung
&lt;/h2&gt;

&lt;p&gt;Start at the bottom of the ladder and climb it. Developer Small Business at $1,199 licenses one developer at one location and excludes public-facing websites and third-party redistribution. Developer OEM at $3,597 removes both restrictions. Site licences, for up to ten developers in one organization, run from $5,995 for Small Business to $16,786 for OEM, and a Site SDK tier at $59,950 adds the right to embed the library inside your own API or SDK. A metered, pay-as-you-use option starts at $1,999 a month, which suits a team that cannot forecast deployment volume better than a per-developer fee does.&lt;/p&gt;

&lt;p&gt;Until a purchased licence is applied, the library runs in evaluation mode and stamps an Evaluation Only notice across the top of every generated page, naming Aspose.PDF and a 2002-2020 copyright line. Aspose documents a 30-day temporary licence that lifts the stamp, so every page an unlicensed build produces carries that notice until a key is bought and applied. Applying the real licence is three lines.&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;aspose.pdf&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ap&lt;/span&gt;

&lt;span class="c1"&gt;# The licence is applied per process, before any document is created
&lt;/span&gt;&lt;span class="n"&gt;license_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Aspose.PDF.Python.lic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;license&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;License&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;license&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_license&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;license_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that call the watermark is gone and pages render clean. IronPDF handles the same step through a &lt;a href="https://ironpdf.com/python/get-started/license-keys/" rel="noopener noreferrer"&gt;license key&lt;/a&gt; set once at startup, and the tier that key belongs to does not change with the deployment target.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Breadth Covers, and Where It Stops
&lt;/h2&gt;

&lt;p&gt;Aspose.PDF is a wide package. It converts between more than twenty document formats, covering Word, Excel, PowerPoint, HTML, images, PDF/A, and OFD, it validates PDF/A, PDF/X, PDF/E, and PDF/UA conformance in the library itself, and it ships signing, AcroForm extraction, encryption, annotation handling, and table creation in the same wheel. For a pipeline built on heavy Word and Excel conversion or &lt;a href="https://ironpdf.com/python/examples/pdfa/" rel="noopener noreferrer"&gt;archival conformance&lt;/a&gt; where HTML is one input among many, that breadth is the ceiling of what one dependency can cover, and IronPDF answers the HTML half of the same pipeline from its only tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Wheel Matrix Changes Between Releases
&lt;/h2&gt;

&lt;p&gt;Each platform wheel runs 122 to 159 MB, and the matrix of platforms is not fixed from one release to the next. Version 26.6.0, uploaded 3 July 2026, shipped five wheels including native Intel and Apple Silicon macOS builds. Version 26.7.0, uploaded 30 July 2026, shipped three, namely Windows 32-bit, Windows 64-bit, and Linux &lt;code&gt;manylinux1_x86_64&lt;/code&gt;, with both macOS builds absent. Version 26.8.0, uploaded 28 August 2026, restored all five. A macOS team that pinned to the latest Aspose.PDF release during those four weeks had an install that could not resolve, and the answer at the time was to pin back rather than apply a workaround.&lt;/p&gt;

&lt;p&gt;Linux adds its own step. The Aspose.PDF system-requirements page calls for GCC-6 runtime libraries or later plus the &lt;code&gt;libpython&lt;/code&gt; shared library, which is not present by default on distributions where &lt;code&gt;--enable-shared&lt;/code&gt; is not Python's build setting. A container that skips it fails at import rather than at render time, so the failure is fast, and it is still one more platform-specific line in a Dockerfile that a &lt;a href="https://ironpdf.com/python/get-started/use-ironpdfengine/" rel="noopener noreferrer"&gt;single bundled runtime&lt;/a&gt; does not ask for. PyPI enforces Python 3.9 through 3.13 for the current release, so an older interpreter in a legacy service is a resolver failure rather than a warning.&lt;/p&gt;

&lt;p&gt;The record is quiet. A search of the GitHub Advisory Database returns no advisory for the &lt;code&gt;aspose-pdf&lt;/code&gt; pip package, and Snyk reports no direct vulnerability against any version, including 26.8.0. Entries that surface under the Aspose.PDF name are scoped by CPE to a separate C++ build with no PyPI package, so they are not the Python wheel's record and are not counted here. What decides this choice sits earlier than the advisory feed, in the tier the deployment needs and the wheel the target platform gets, which is also where a team should be looking when it reviews &lt;a href="https://ironpdf.com/python/examples/security-and-metadata/" rel="noopener noreferrer"&gt;document security and metadata&lt;/a&gt; at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does the HTML Path Run Client-Side JavaScript?
&lt;/h2&gt;

&lt;p&gt;Aspose.PDF documents nine members on &lt;code&gt;HtmlLoadOptions&lt;/code&gt;, covering warning handling, font embedding, single-page layout, media type, encoding, and page information, and no member names a browser engine, a JavaScript toggle, or a page-load timeout. Loading and converting markup is short.&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;aspose.pdf&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ap&lt;/span&gt;

&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HtmlLoadOptions&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;ap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&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;invoice.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice.pdf&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;That produces a PDF built from the static markup as Aspose's internal parser reads it. A template that assembles its layout after page load, which is most dashboards and anything drawing &lt;a href="https://ironpdf.com/python/examples/js-charts-to-pdf/" rel="noopener noreferrer"&gt;charts in the browser&lt;/a&gt;, reaches the parser as an empty shell, and there is no documented option to wait for the script that would have filled it. IronPDF renders that same template through Chromium, so &lt;a href="https://ironpdf.com/python/examples/javascript-html-to-pdf/" rel="noopener noreferrer"&gt;client-side JavaScript runs before the page is captured&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;Capability&lt;/th&gt;
&lt;th&gt;Aspose.PDF for Python &lt;code&gt;26.8.0&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;IronPDF for Python&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HTML rendering engine&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;HtmlLoadOptions&lt;/code&gt;, engine not named in the reference&lt;/td&gt;
&lt;td&gt;Chromium, bundled in the package&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client-side script on HTML input&lt;/td&gt;
&lt;td&gt;No toggle among the 9 documented members&lt;/td&gt;
&lt;td&gt;Runs before capture, through &lt;code&gt;RenderHtmlAsPdf&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platform wheels per release&lt;/td&gt;
&lt;td&gt;5 in &lt;code&gt;26.8.0&lt;/code&gt;, 3 in &lt;code&gt;26.7.0&lt;/code&gt;, 5 in &lt;code&gt;26.6.0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1 wheel set, unchanged from release to release&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python versions accepted&lt;/td&gt;
&lt;td&gt;3.9 to 3.13, enforced by PyPI&lt;/td&gt;
&lt;td&gt;3.7 and later, one wheel set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Install footprint&lt;/td&gt;
&lt;td&gt;122 to 159 MB per platform wheel&lt;/td&gt;
&lt;td&gt;1 package, 0 system libraries to add&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public-facing deployment&lt;/td&gt;
&lt;td&gt;Developer OEM at $3,597 or above&lt;/td&gt;
&lt;td&gt;1 tier, no deployment restriction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output before a licence is applied&lt;/td&gt;
&lt;td&gt;Watermarked on every page, 30-day temporary key&lt;/td&gt;
&lt;td&gt;Clean pages once the key is applied&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Table 1. Document output path only, Aspose.PDF for Python against IronPDF for Python, with licence tier shown where Aspose gates it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two of those rows move a budget rather than a backlog. The deployment row and the licensing row are what the purchase plan has to answer before the capability rows matter at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  What IronPDF Asks of the Deployment Plan
&lt;/h2&gt;

&lt;p&gt;For the narrower job, an HTML or CSS template that has to become a PDF with no format conversion or conformance validation in the path, the renderer is the whole dependency.&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;from&lt;/span&gt; &lt;span class="n"&gt;ironpdf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="n"&gt;renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChromePdfRenderer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RenderHtmlAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice_html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SaveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice.pdf&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;That writes a searchable PDF from the markup exactly as a browser tab would have drawn it, on Windows, macOS, Linux, and in a container, from one install on Python 3.7 and later. The same object &lt;a href="https://ironpdf.com/python/how-to/python-merge-pdf/" rel="noopener noreferrer"&gt;merges the result with an existing file&lt;/a&gt; when the output has to be assembled rather than just rendered.&lt;/p&gt;

&lt;p&gt;IronPDF renders that markup on Windows, macOS, Linux, and in a container from one tier, with no wheel matrix to re-check on each upgrade and no second licence to buy before the deployment faces the public. The narrow case that stays with Aspose.PDF is bulk Word, Excel, and PowerPoint conversion with archival conformance written into a contract. IronPDF has a &lt;a href="https://ironpdf.com/python/licensing/" rel="noopener noreferrer"&gt;free trial&lt;/a&gt; if you want to run one of your existing templates through both before the tier question comes up.&lt;/p&gt;

&lt;p&gt;Has anyone here budgeted Aspose.PDF past the entry tier, or hit the 26.7.0 wheel gap on a macOS build machine? Tell us in the comments what the deployment cost turned out to be once the evaluation was over, because that gap is the part a purchase page never shows.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Aspose and Aspose.PDF are trademarks of Aspose Pty Ltd, and we have no affiliation with the company. The pricing, wheel, and platform details above are drawn from Aspose's own PyPI listing, documentation, and purchase pages at the time of writing. If a detail has moved since, correct us in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>pdf</category>
      <category>licensing</category>
      <category>architecture</category>
    </item>
    <item>
      <title>borb for Python: The Licence Decides Before You Ship</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Thu, 03 Sep 2026 17:19:20 +0000</pubDate>
      <link>https://dev.to/ironsoftware/borb-for-python-the-licence-decides-before-you-ship-29o4</link>
      <guid>https://dev.to/ironsoftware/borb-for-python-the-licence-decides-before-you-ship-29o4</guid>
      <description>&lt;p&gt;borb has two prices, and only one of them is money. The library ships under AGPL-3.0-or-later, where the fee is paid in source code that reaches the application importing it, with a commercial licence sold beside that for teams who would rather pay cash. The README names three situations that move a team onto the paid side, covering paid PDF services such as generation inside a cloud application, use in a closed-source project, and distribution inside one. Most pipelines are at least one of those, so the choice arrives before a project ships, unlike &lt;a href="https://ironpdf.com/python/examples/using-html-to-create-a-pdf/" rel="noopener noreferrer"&gt;handing a template to a renderer&lt;/a&gt; under one commercial licence.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full disclosure. We work on IronPDF at Iron Software. What follows weighs what borb's AGPL terms and single-maintainer model commit a project to, and how IronPDF renders markup a team already owns under one commercial licence.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Paid Tier, the Add-Ons, and the Telemetry
&lt;/h2&gt;

&lt;p&gt;The three cases above are not the whole of the paid side. Four capabilities sit outside the core library as separately sold add-ons, namely &lt;a href="https://ironpdf.com/python/examples/markdown-to-pdf/" rel="noopener noreferrer"&gt;Markdown-to-PDF conversion&lt;/a&gt;, redaction, find-and-replace, and OCR, so a pipeline that needs any of them is buying twice. What follows here is what the project states about its own terms rather than a reading of what the AGPL requires for a given deployment, which is a question for your own counsel.&lt;/p&gt;

&lt;p&gt;The library also ships licence verification and usage reporting of its own. A signed licence file registers at runtime to enable the commercial tier, and a separate usage-statistics module reports by default, sending licence state, page and document counts, platform, and library version, with a documented &lt;code&gt;opt_out()&lt;/code&gt; call to stop it. That reporting only fires when &lt;code&gt;requests&lt;/code&gt; is installed, which a bare install does not add. Projects running without a registered licence get a console reminder every 64 documents processed, and nothing else changes, since output is not watermarked, throttled, or blocked. IronPDF keeps the equivalent step to a &lt;a href="https://ironpdf.com/python/get-started/license-keys/" rel="noopener noreferrer"&gt;licence key&lt;/a&gt; applied once at startup, with no runtime reporting attached to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Does the Object Model Stop?
&lt;/h2&gt;

&lt;p&gt;Behind the licence sits more than a recent rewrite suggests. The project declares no required runtime dependencies, and image handling, barcodes, avatars, fonts, and outbound web requests all sit behind optional extras, which is a small surface for a security review to sign off. Behind that sit read, validate, and write pipelines built on close to seventy content-stream operators, a Source, Filter, and Sink pipeline that &lt;a href="https://ironpdf.com/python/examples/extract-pdf-text/" rel="noopener noreferrer"&gt;pulls text&lt;/a&gt;, images, colours, and keyword rankings by font, page, or spatial position, multi-column layouts, eighteen annotation types, &lt;a href="https://ironpdf.com/python/examples/form-data/" rel="noopener noreferrer"&gt;form fields&lt;/a&gt;, and barcodes, QR codes, and charts as generated content. The companion examples repository, maintained by the same author, runs to hundreds of worked samples. For a pipeline whose documents are assembled entirely from structured data, that object model is the ceiling of what a code-first toolkit needs to reach, and IronPDF answers the markup half of the same pipeline from one commercial licence.&lt;/p&gt;

&lt;p&gt;Composing a document means building a tree of Python objects.&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;from&lt;/span&gt; &lt;span class="n"&gt;borb.pdf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Document&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="n"&gt;PageLayout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SingleColumnLayout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PDF&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;borb.pdf.layout_element.text.paragraph&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Paragraph&lt;/span&gt;

&lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append_page&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="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PageLayout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SingleColumnLayout&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="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append_layout_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Paragraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello World&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;PDF&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;what&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;where_to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output.pdf&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;That writes a one-page PDF with a single paragraph on it, and every further element arrives the same way, one constructor at a time. There is no markup-to-layout conversion anywhere in the codebase, and the only markup-shaped input borb accepts is a dedicated Markdown paragraph element, with full Markdown conversion sold as one of the paid add-ons. An invoice that already exists as an HTML template has to be rebuilt element by element through that API before borb can produce it.&lt;/p&gt;

&lt;p&gt;Set the two scopes side by side and the split is clean, one library answering the half that starts in data and the other the half that starts in markup.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;borb&lt;/th&gt;
&lt;th&gt;IronPDF for Python&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Existing HTML and CSS as input&lt;/td&gt;
&lt;td&gt;No HTML parser and no CSS support in the codebase&lt;/td&gt;
&lt;td&gt;Chromium engine through &lt;code&gt;ChromePdfRenderer&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documents assembled from structured data&lt;/td&gt;
&lt;td&gt;Object composition, &lt;code&gt;Paragraph&lt;/code&gt; into &lt;code&gt;SingleColumnLayout&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Data merged into a template, then &lt;code&gt;RenderHtmlAsPdf&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reading and modifying existing PDFs&lt;/td&gt;
&lt;td&gt;Read, validate, and write over close to 70 operators&lt;/td&gt;
&lt;td&gt;Load, edit, merge, and stamp on 1 &lt;code&gt;PdfDocument&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text and image extraction&lt;/td&gt;
&lt;td&gt;Source, Filter, and Sink pipeline&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ExtractAllText&lt;/code&gt; plus image extraction, built in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forms and annotations&lt;/td&gt;
&lt;td&gt;18 annotation types and form fields&lt;/td&gt;
&lt;td&gt;Form fill, annotations, and encryption in 1 API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Install surface&lt;/td&gt;
&lt;td&gt;Core install, with images, barcodes, and fonts as extras&lt;/td&gt;
&lt;td&gt;One package, browser engine already inside&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Licence&lt;/td&gt;
&lt;td&gt;AGPL-3.0-or-later, dual-licensed with paid commercial bands&lt;/td&gt;
&lt;td&gt;Commercial, one licence for every deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python versions&lt;/td&gt;
&lt;td&gt;3.6 and later per packaging metadata&lt;/td&gt;
&lt;td&gt;3.7 and later, one wheel&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Table 1. Document input and output paths only, borb against IronPDF for Python, with licence terms as each project states them.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The first row is the one that decides most migrations, because markup that already exists can go straight to &lt;a href="https://ironpdf.com/python/how-to/python-create-pdf/" rel="noopener noreferrer"&gt;a call that produces the document&lt;/a&gt; rather than through a constructor for every element on the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Ships the Next Release?
&lt;/h2&gt;

&lt;p&gt;The README describes borb as created and maintained as a solo project, and the contributor list is one human account plus an automated dependency bot. Output has not slowed, with a release roughly every month to every quarter since 2021, including a full rewrite for the 3.x line and a release weeks before this comparison, and the packaging classifier lists development status as Mature. Depending on borb in production still means depending on the continuity of one person, which is a planning question for a document pipeline that has to outlive the people who set it up.&lt;/p&gt;

&lt;p&gt;Advisories are not where the risk sits here. The GitHub Advisory Database's PyPI filter, Snyk, and the NVD carry no issue in borb's own code. A keyword search for the name surfaces two CVEs that belong to an unrelated WordPress plugin from Borbis Media rather than this library, which matters only when an automated scanner flags the string and someone has to explain it. What decides this choice sits in the licence band and the maintenance model rather than the advisory feed, and the &lt;a href="https://ironpdf.com/python/docs/" rel="noopener noreferrer"&gt;documented API surface&lt;/a&gt; is where a like-for-like comparison actually happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Job Outgrows an Object API
&lt;/h2&gt;

&lt;p&gt;Most code-first pipelines end on a small request. A team starts in borb because the output is pure data, covering order confirmations, certificates, and internal reports, and then someone asks for the marketing team's branded cover page or the existing HTML email template. At that point an object-composition API and a browser engine stop being interchangeable.&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;from&lt;/span&gt; &lt;span class="n"&gt;ironpdf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="n"&gt;renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChromePdfRenderer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RenderHtmlAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
&amp;lt;html&amp;gt;
&amp;lt;body style=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;font-family: sans-serif;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;
    &amp;lt;h1&amp;gt;Invoice #4471&amp;lt;/h1&amp;gt;
    &amp;lt;table style=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;width: 100%; border-collapse: collapse;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;
        &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;Consulting services&amp;lt;/td&amp;gt;&amp;lt;td style=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text-align: right;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;$2,400.00&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
    &amp;lt;/table&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SaveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice.pdf&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;That produces the invoice with its CSS applied exactly as a browser would paint it, without any of the markup being rebuilt through an object API first. IronPDF renders HTML and CSS from one package, so a template a designer already owns reaches PDF unchanged, and the same package covers the neighbouring jobs, including &lt;a href="https://ironpdf.com/python/how-to/python-fill-pdf-form/" rel="noopener noreferrer"&gt;populating an existing PDF form&lt;/a&gt; rather than regenerating the document around it.&lt;/p&gt;

&lt;p&gt;IronPDF renders the markup a team already owns under one commercial licence, with no copyleft obligation reaching into your source, no add-on to buy for a capability the pipeline already needed, and no single maintainer between the project and its next release. The narrow case that stays with borb is a document assembled entirely from structured data with no markup anywhere near it. IronPDF has a &lt;a href="https://ironpdf.com/python/licensing/" rel="noopener noreferrer"&gt;free trial&lt;/a&gt; if you want to run one of your existing templates through it before that licence conversation starts.&lt;/p&gt;

&lt;p&gt;Where does your pipeline actually start, in code or in markup? If you are already running borb commercially, tell us in the comments how the licensing call went internally, because that conversation is the part nobody documents.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;borb is the property of its author, and we have no affiliation with the project. The licence, pricing, and maintenance details above are taken from borb's own README, PyPI listing, and pricing page at the time of writing. If a detail has moved since, say so in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>pdf</category>
      <category>opensource</category>
      <category>architecture</category>
    </item>
    <item>
      <title>ReportLab in Python: The Build-Versus-Buy Call Made Twice</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Wed, 02 Sep 2026 16:44:29 +0000</pubDate>
      <link>https://dev.to/ironsoftware/reportlab-in-python-the-build-versus-buy-call-made-twice-5hkd</link>
      <guid>https://dev.to/ironsoftware/reportlab-in-python-the-build-versus-buy-call-made-twice-5hkd</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;reportlab.platypus&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SimpleDocTemplate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Paragraph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Spacer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;reportlab.lib.styles&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;getSampleStyleSheet&lt;/span&gt;

&lt;span class="n"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getSampleStyleSheet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;story&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;Paragraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Quarterly Statement&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="nc"&gt;Spacer&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;12&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;Paragraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Account balance and transaction summary follow.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Normal&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;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SimpleDocTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statement.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;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;story&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ten lines, and a quarterly statement comes out of them with a title, a twelve-point gap, and a body paragraph, each one a Python object taking its formatting from a shared stylesheet rather than from a coordinate. That is the build-versus-buy question being asked the first time, and hand-coding the layout is a defensible answer to it.&lt;/p&gt;

&lt;p&gt;The question gets asked a second time later, and that time it costs money. When the source material turns out to be a web page, the options are re-authoring the layout as flowables, licensing the commercial tier that reads markup, or &lt;a href="https://ironpdf.com/python/tutorials/html-to-pdf/" rel="noopener noreferrer"&gt;rendering the markup that already exists&lt;/a&gt; under a single licence. ReportLab prices that tier by monthly output volume rather than by seat, and teams tend to budget for the first question and meet the second one mid-project.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full disclosure. Our team at Iron Software makes IronPDF. This read examines what ReportLab's markup gap and separate commercial product cost a team, and what IronPDF renders from the licence already in the build.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Open-Source Line Falls
&lt;/h2&gt;

&lt;p&gt;Markup support in the open-source package is scoped to formatting inside a single &lt;code&gt;Paragraph&lt;/code&gt;, covering bold, italic, links, and inline images, and there is no document-level HTML or CSS parser anywhere in it. Turning a web page into a ReportLab document therefore means re-authoring its layout as Python flowables, one element at a time, or moving to the commercial tier for the markup path.&lt;/p&gt;

&lt;p&gt;Reading a PDF the library did not generate sits on the far side of the same line. PageCatcher, the component that imports pages from documents produced elsewhere, ships only with the commercial tier, so an open-source-only pipeline can produce PDFs and cannot &lt;a href="https://ironpdf.com/python/how-to/python-merge-pdf/" rel="noopener noreferrer"&gt;pull one back in to merge or restructure it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Support stops at that line too. The package README asks users not to raise issues against it and points them at the reportlab-users mailing list instead, so a Platypus layout bug or a paragraph-markup edge case is a mailing-list-and-source-code problem rather than a support ticket. For a document pipeline expected to run for years, that is the maintenance model being chosen, not a detail of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does ReportLab PLUS Add, and How Is It Priced?
&lt;/h2&gt;

&lt;p&gt;The commercial side is a separate package, rlextra, built around Report Markup Language. The open-source README states it plainly, describing the library as the foundation for the commercial RML product available in the ReportLab PLUS package, which offers a template-based style of document development and more features than the open-source line carries. The dependency runs deeper than a sales note, because &lt;code&gt;reportlab.lib.pdfencrypt&lt;/code&gt; in the installed open-source package imports &lt;code&gt;storeFormsInMemory&lt;/code&gt; and &lt;code&gt;restoreFormsInMemory&lt;/code&gt; from &lt;code&gt;rlextra.pageCatcher.pageCatcher&lt;/code&gt;, so the code path exists in the package a team already has and the module it needs sits on the other side of the purchase.&lt;/p&gt;

&lt;p&gt;What teams underestimate is the shape of that purchase. The markup path is not an option inside the package already installed, it is a second product with its own package, its own document language, and its own commercial terms, quoted separately by the vendor. A pipeline that starts in the open-source line and later needs markup is therefore making a procurement decision rather than a version bump. IronPDF keeps that step to a &lt;a href="https://ironpdf.com/python/get-started/license-keys/" rel="noopener noreferrer"&gt;licence key&lt;/a&gt; applied to the package already in the build.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Far Does the Flowable Model Carry a Report?
&lt;/h2&gt;

&lt;p&gt;Age is the case for ReportLab rather than a mark against it. The project has shipped 100 releases on PyPI since February 2009, 10 of them in the past twelve months, with 5.0.1 landing on 20 August 2026, which is a quarter century of edge cases newer libraries have not met yet. Platypus, the Page Layout and Typography Using Scripts model, is built from five layers, namely DocTemplates, PageTemplates, Frames, Flowables, and the underlying Canvas, so a document is assembled as flowable objects and reformatted globally through shared styles rather than page by page. The canvas API underneath gives point-level control over font, position, and stroke, and the open-source package ships a real vector graphics and &lt;a href="https://ironpdf.com/python/examples/js-charts-to-pdf/" rel="noopener noreferrer"&gt;charting subsystem&lt;/a&gt; rather than a stub, so bar and pie charts need no separate plotting library. The dependency footprint stays small with it, requiring only Pillow for images and charset-normalizer for text encoding, with &lt;code&gt;rl_accel&lt;/code&gt;, rlPyCairo, and uharfbuzz text shaping as opt-in extras, on Python 3.9 and later per the packaging metadata. Where a report must match an exact printed layout and every glyph position is part of the specification, that model is the ceiling of what a code-first document toolkit needs to reach, and IronPDF answers the markup half of the same question from one package with &lt;a href="https://ironpdf.com/python/examples/custom-pdf-paper-size/" rel="noopener noreferrer"&gt;the same page controls&lt;/a&gt; exposed through CSS.&lt;/p&gt;

&lt;p&gt;The statement at the top of this piece is that model at its smallest, a &lt;code&gt;SimpleDocTemplate&lt;/code&gt; choosing the page and a story deciding what lands on it. Scale the same code to a thousand-page run and the property worth having does not change, because the layout still lives in the stylesheet rather than in the loop emitting the rows, and restyling the run is one edit in one place.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;ReportLab open source&lt;/th&gt;
&lt;th&gt;IronPDF for Python&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Existing HTML and CSS as input&lt;/td&gt;
&lt;td&gt;Formatting inside a &lt;code&gt;Paragraph&lt;/code&gt; only, no document parser&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RenderHtmlAsPdf&lt;/code&gt;, &lt;code&gt;RenderUrlAsPdf&lt;/code&gt;, and &lt;code&gt;RenderHtmlFileAsPdf&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documents built from code&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;SimpleDocTemplate&lt;/code&gt; and flowables, or canvas drawing&lt;/td&gt;
&lt;td&gt;Data merged into a template, then rendered through Chromium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typography control&lt;/td&gt;
&lt;td&gt;Point-level, code-driven through the canvas API&lt;/td&gt;
&lt;td&gt;CSS and standard browser layout rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Charts and vector graphics&lt;/td&gt;
&lt;td&gt;Drawing and charting subsystem in the package&lt;/td&gt;
&lt;td&gt;Whatever the source page already draws, script included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reading a PDF produced elsewhere&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;rlextra.pageCatcher&lt;/code&gt;, commercial package only&lt;/td&gt;
&lt;td&gt;Load, merge, and stamp on the same &lt;code&gt;PdfDocument&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vendor support&lt;/td&gt;
&lt;td&gt;Not provided for the open-source library&lt;/td&gt;
&lt;td&gt;Included with every licence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Licence&lt;/td&gt;
&lt;td&gt;BSD licence on the core, rlextra sold commercially&lt;/td&gt;
&lt;td&gt;Commercial, one licence for every deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python versions accepted&lt;/td&gt;
&lt;td&gt;3.9 and later&lt;/td&gt;
&lt;td&gt;3.7 and later&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Table 1. Document input and output paths only, ReportLab's open-source package against IronPDF for Python, with tier gating shown where ReportLab applies it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two rows here become invoices later, namely the markup row and the row about reading a PDF from elsewhere. The others are design taste.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 5.x Line Changed a Security Default
&lt;/h2&gt;

&lt;p&gt;The 5.x line changed a security default rather than a feature. The changelog for 5.0.0a1, dated 21 May 2026, records the decision to make a &lt;code&gt;trustedHosts&lt;/code&gt; value of &lt;code&gt;None&lt;/code&gt; mean no hosts are trusted in &lt;code&gt;open_for_read&lt;/code&gt;, reversing the earlier no-restriction reading of the same setting, and that change shipped in 5.0.0 on 18 June 2026. Release 5.0.1, on 20 August 2026, went further and applied the &lt;code&gt;trustedHosts&lt;/code&gt; and &lt;code&gt;trustedSchemes&lt;/code&gt; checks to redirected hosts. No CVE was assigned to either, and any pipeline that pulls &lt;a href="https://ironpdf.com/python/examples/image-to-pdf/" rel="noopener noreferrer"&gt;images from outside the build&lt;/a&gt; needs its allow-list written before that upgrade lands.&lt;/p&gt;

&lt;p&gt;Four vulnerabilities sit against ReportLab's own code, all fixed years ago, namely CVE-2019-17626 and CVE-2019-19450, both critical at 9.8 for remote code execution in the XML-like paragraph markup parser and fixed in 3.5.28 and 3.5.31, CVE-2020-28463 for server-side request forgery through &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags, fixed in 3.5.55 and paired with the &lt;code&gt;trustedHosts&lt;/code&gt; and &lt;code&gt;trustedSchemes&lt;/code&gt; mitigation that 5.0 later hardened, and CVE-2023-33733 for code execution through a crafted input, fixed in 3.6.13. The most recent of those landed more than three years before the current 5.0.1, so this is a record rather than a live exposure, and it is what happens when one parser handles both trusted and less-trusted input, which belongs in the same review as &lt;a href="https://ironpdf.com/python/examples/security-and-metadata/" rel="noopener noreferrer"&gt;the metadata and permissions&lt;/a&gt; on whatever produces the output.&lt;/p&gt;

&lt;p&gt;One adjacent project decides the version plan. The tool most often reached for by teams wanting HTML with ReportLab underneath, xhtml2pdf, is maintained independently, and its current release pins &lt;code&gt;reportlab&amp;lt;5,&amp;gt;=4.0.4&lt;/code&gt;, so it has not moved onto the 5.x line. Pairing the two therefore pins the ReportLab version as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Markup-First Path Starts
&lt;/h2&gt;

&lt;p&gt;The markup-first path starts from a page or a template that already exists rather than from a story of flowables.&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;from&lt;/span&gt; &lt;span class="n"&gt;ironpdf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="n"&gt;renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChromePdfRenderer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RenderUrlAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://internal.example.com/reports/q3-statement&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SaveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q3-statement.pdf&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;That writes the statement exactly as the reporting app already draws it in a browser, stylesheet and script included, with no report definition in between. The same object takes a template file through &lt;code&gt;RenderHtmlFileAsPdf&lt;/code&gt; and a string through &lt;code&gt;RenderHtmlAsPdf&lt;/code&gt;, so &lt;a href="https://ironpdf.com/python/examples/converting-a-url-to-a-pdf/" rel="noopener noreferrer"&gt;pointing it at an internal URL&lt;/a&gt; and pointing it at a file are the same call with a different argument.&lt;/p&gt;

&lt;p&gt;IronPDF renders the page a front-end team already maintains from the licence already in the build, and opens a PDF produced elsewhere on the same object, so neither capability arrives as a second product with its own commercial terms. The narrow case that stays with ReportLab is a document computed entirely from data where every glyph position belongs to the specification. IronPDF has a &lt;a href="https://ironpdf.com/python/licensing/" rel="noopener noreferrer"&gt;free trial&lt;/a&gt; if you want to run your existing template through it before pricing the markup tier.&lt;/p&gt;

&lt;p&gt;Which category does your own pipeline fall into, computed pages or an HTML source of truth? Tell us in the comments, especially if you have quoted ReportLab PLUS for the markup path or found another route to it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ReportLab and ReportLab PLUS are trademarks of ReportLab Inc, and we have no affiliation with the company. The release, pricing, and advisory details above are drawn from ReportLab's own documentation, FAQ, PyPI listing, and the published CVE records at the time of writing. If a detail has moved since, correct us below.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>pdf</category>
      <category>opensource</category>
      <category>architecture</category>
    </item>
    <item>
      <title>pypdf and the Pin That Never Gets Its Fix</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Wed, 02 Sep 2026 15:14:00 +0000</pubDate>
      <link>https://dev.to/ironsoftware/pypdf-and-the-pin-that-never-gets-its-fix-18id</link>
      <guid>https://dev.to/ironsoftware/pypdf-and-the-pin-that-never-gets-its-fix-18id</guid>
      <description>&lt;p&gt;A supplier emails over a signed contract. Three pages of it belong in the archive, the intake form stapled to the back needs its fields flattened before anything downstream reads them, and the whole bundle has to come back out as one document. That is pypdf's work, and a requirements file pinned to PyPDF2 3.0.1 will never receive another fix for any of it, because that release of 31 December 2022 is the last the project shipped and every patch since has landed under the pypdf name. What produces the new document at the end of that bundle is &lt;a href="https://ironpdf.com/python/tutorials/html-to-pdf/" rel="noopener noreferrer"&gt;a separate question&lt;/a&gt; with a separate answer.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full disclosure. The team behind IronPDF is ours at Iron Software. This read weighs what pypdf's absent renderer and monthly patch cadence ask of a team, and what IronPDF writes when the document does not exist yet.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Do the Maintainers Say Is Out of Scope?
&lt;/h2&gt;

&lt;p&gt;Start with the boundary, because the maintainers state it themselves rather than leaving it to be inferred. Asked in a GitHub discussion whether the library could act as a viewer, maintainer pubpub-zz answered that pypdf is a library to work on PDF elements and not a virtual viewer, pointing the asker toward &lt;code&gt;pdftocairo&lt;/code&gt; or the &lt;code&gt;pdf2image&lt;/code&gt; wrapper. &lt;a href="https://ironpdf.com/python/how-to/python-pdf-to-image/" rel="noopener noreferrer"&gt;Turning a page into pixels&lt;/a&gt; and turning markup into a document both sit outside the library by design, which is a scope decision rather than a missing feature.&lt;/p&gt;

&lt;p&gt;Text extraction is bounded by the format as well, and the documentation says so. An image-only page, usually a scan, yields text that is minimal or effectively empty, tables usually reduce to positioned text with no reliable column and row structure, and pulling text from a complex layout means parsing the page's entire content stream, which costs memory on large files. Each of those is the point where a second tool joins the diagram, and &lt;a href="https://ironpdf.com/python/examples/digitally-sign-a-pdf/" rel="noopener noreferrer"&gt;signing or stamping the finished document&lt;/a&gt; usually joins it at the same seam.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Jobs Does the Manipulation Surface Still Own?
&lt;/h2&gt;

&lt;p&gt;Within the category of PDFs that already exist, the scope is wide. The documentation lists merging, splitting, cropping and rotating pages, reading and writing metadata and outlines, encrypting and decrypting, editing viewer preferences, working with attachments and annotations, &lt;a href="https://ironpdf.com/python/examples/pdf-watermarking/" rel="noopener noreferrer"&gt;stamping watermarks&lt;/a&gt;, and reading, filling, and flattening AcroForm fields, all in one package rather than three stitched together. The install stays light with it, since the core needs nothing beyond &lt;code&gt;typing_extensions&lt;/code&gt; below Python 3.11, with extras for AES encryption, image handling, and font work. Development runs under the py-pdf organization on GitHub at a fast cadence, with 41 releases in the past twelve months and at least two in every one of those months, and 6.16.2 current as of 23 August 2026. For a workflow that takes apart and reassembles documents it did not create, that surface is the ceiling of what a manipulation library needs to reach, and IronPDF answers the producing half of the same pipeline from one package.&lt;/p&gt;

&lt;p&gt;Filling a form is one pass through a reader and a writer.&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;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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PdfWriter&lt;/span&gt;

&lt;span class="n"&gt;reader&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;intake-form.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;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PdfWriter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;writer&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="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# auto_regenerate=False stops the viewer recomputing field appearances,
# which is what triggers a save-changes prompt in some readers
&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update_page_form_field_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;writer&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;applicant_name&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;Jordan Rivera&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;date&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;2026-08-25&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;auto_regenerate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;intake-form-filled.pdf&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;wb&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;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That writes a filled copy of the intake form with the two values in place and the field appearances already baked in. The equivalent step on a document a renderer has just produced is &lt;a href="https://ironpdf.com/python/how-to/python-fill-pdf-form/" rel="noopener noreferrer"&gt;filling the form on the same object&lt;/a&gt; that rendered it, with no second package in the path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Package Name Does the Fix Land In?
&lt;/h2&gt;

&lt;p&gt;The rename is not a fork and not an abandonment. The PyPDF2 listing on PyPI states the project went back to its roots, names 3.0.1 as its final release, and points all further development at pypdf from 3.1.0 onward. Three advisories are PyPDF2-only and closed on that side, namely CVE-2022-24859 for an inline-image infinite loop fixed in 1.27.5, CVE-2023-36810 for quadratic-runtime parsing of a malformed xref fixed in 1.27.9, and CVE-2023-36807 for an infinite loop on malformed objects fixed in 2.10.6.&lt;/p&gt;

&lt;p&gt;CVE-2023-36464 is the one that spans the rename, and it is the clearest reason a stale pin is a standing risk. PyPDF2 2.2.0 through 3.0.1 is listed as affected with no PyPDF2 fix available, while the equivalent pypdf range, 3.1.0 through 3.9.0, was patched in pypdf 3.9.0. An old PyPDF2 pin therefore has no upgrade path inside its own name, and the remediation is a package change rather than a version bump, which is a migration ticket rather than a dependency bump on whatever sprint discovers it. Sitting on one &lt;a href="https://ironpdf.com/python/docs/" rel="noopener noreferrer"&gt;documented API surface&lt;/a&gt; that does not change names is worth something at exactly that moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Fix Lands Here Most Months
&lt;/h2&gt;

&lt;p&gt;The GitHub Advisory Database's PyPI filter returns dozens of published advisories against pypdf's own code, and the volume reads correctly only with the class attached. Nearly all of them are availability findings, meaning infinite loops or memory exhaustion triggered by malformed input, rather than remote code execution or data disclosure. The highest score on the record is CVE-2026-59935 at CVSS 8.7, an infinite loop on improperly terminated inline images, patched in 6.14.2, with CVE-2026-59936 the same class of finding against the same code path, patched in 6.14.1. The same shape recurs through the record with CVE-2026-54531 for an infinite loop parsing outlines in the writer, fixed in 6.13.0, CVE-2026-54651 for an infinite loop processing threads and articles, fixed in 6.13.1, CVE-2026-33699 for an infinite loop during stream recovery, fixed in 6.9.2, and manipulated-stream memory-exhaustion findings including CVE-2026-48735, CVE-2026-41314, and CVE-2026-41312.&lt;/p&gt;

&lt;p&gt;Every one of those carries a fixed version, so the record describes maintainers who patch quickly. The architectural consequence is a standing upgrade budget rather than a one-time integration, because a service parsing documents from outside sources has to track a package that publishes fixes most months, and each bump is a regression test against every document shape the pipeline handles. That obligation belongs in the same review as &lt;a href="https://ironpdf.com/python/examples/security-and-metadata/" rel="noopener noreferrer"&gt;the encryption and metadata settings&lt;/a&gt; on the output side, and it is the recurring cost a comparison of feature lists never shows.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;pypdf &lt;code&gt;6.16.2&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;IronPDF for Python&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Merge, split, rotate, and crop pages&lt;/td&gt;
&lt;td&gt;Supported through &lt;code&gt;PdfWriter&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The same object that rendered the document&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metadata and outlines&lt;/td&gt;
&lt;td&gt;Read and write&lt;/td&gt;
&lt;td&gt;Read and write without a second package&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encryption&lt;/td&gt;
&lt;td&gt;Encrypt and decrypt&lt;/td&gt;
&lt;td&gt;Encrypt, decrypt, set permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AcroForm fields&lt;/td&gt;
&lt;td&gt;Read, fill, and flatten&lt;/td&gt;
&lt;td&gt;Read, fill, and flatten on the rendered file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text out of an existing PDF&lt;/td&gt;
&lt;td&gt;Supported, with documented structural limits&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ExtractAllText&lt;/code&gt; across every page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTML or CSS into a new PDF&lt;/td&gt;
&lt;td&gt;No rendering engine in the library&lt;/td&gt;
&lt;td&gt;Chromium through &lt;code&gt;RenderHtmlAsPdf&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A PDF page as an image&lt;/td&gt;
&lt;td&gt;Outside scope, maintainers point at &lt;code&gt;pdf2image&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Rasterising built into the same API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Licence&lt;/td&gt;
&lt;td&gt;BSD-3-Clause License&lt;/td&gt;
&lt;td&gt;Commercial, one tier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python versions accepted&lt;/td&gt;
&lt;td&gt;3.9 to 3.14&lt;/td&gt;
&lt;td&gt;3.7 and later&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Table 1. Operations on existing and new documents, pypdf against IronPDF for Python, as each project documents itself.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The sixth and seventh rows are where a pipeline gains a second dependency. Everything above them is work both packages already do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where IronPDF Takes the Outbound Half
&lt;/h2&gt;

&lt;p&gt;Producing a new document starts from markup or a URL rather than from a file that already exists.&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;from&lt;/span&gt; &lt;span class="n"&gt;ironpdf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="n"&gt;renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChromePdfRenderer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# A page the reporting app already serves, rendered as the browser draws it
&lt;/span&gt;&lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RenderUrlAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://internal.example.com/reports/monthly-summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SaveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;monthly-summary.pdf&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;That writes the rendered report to disk with its stylesheet applied and its script already run. The same object takes a template file through &lt;code&gt;RenderHtmlFileAsPdf&lt;/code&gt; and a string through &lt;code&gt;RenderHtmlAsPdf&lt;/code&gt;, and once the document exists, &lt;a href="https://ironpdf.com/python/how-to/python-merge-pdf/" rel="noopener noreferrer"&gt;merging it with other files&lt;/a&gt; is part of the same API rather than a second install.&lt;/p&gt;

&lt;p&gt;IronPDF writes the document that does not exist yet and then merges, stamps, encrypts, and signs it on the same object, so the outbound half is one package rather than a renderer bolted onto a manipulation library on a monthly patch cycle. The narrow case that stays with pypdf is page surgery on documents somebody else produced. IronPDF has a &lt;a href="https://ironpdf.com/python/licensing/" rel="noopener noreferrer"&gt;free trial&lt;/a&gt; if you want to run your existing template through the generation half while pypdf keeps doing the inbound work.&lt;/p&gt;

&lt;p&gt;Which side does most of your work land on, taking apart documents that already exist or producing ones that do not exist yet? Tell us in the comments, especially if you are running pypdf and a renderer together and have opinions about where the seam belongs.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;pypdf and PyPDF2 are the property of their maintainers, and we have no affiliation with the py-pdf organization. The release, licence, and advisory details above are based on the projects' own repositories, PyPI listings, and the published CVE records at the time of writing. If a detail has moved since, the comments are open.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>pdf</category>
      <category>opensource</category>
      <category>architecture</category>
    </item>
    <item>
      <title>WeasyPrint in Python: The Install Is the Deployment Story</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Wed, 02 Sep 2026 15:13:49 +0000</pubDate>
      <link>https://dev.to/ironsoftware/weasyprint-in-python-the-install-is-the-deployment-story-38eh</link>
      <guid>https://dev.to/ironsoftware/weasyprint-in-python-the-install-is-the-deployment-story-38eh</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; python:3.12-slim python &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import weasyprint"&lt;/span&gt;
&lt;span class="go"&gt;Traceback (most recent call last):
&lt;/span&gt;&lt;span class="c"&gt;  ...
&lt;/span&gt;&lt;span class="go"&gt;OSError: cannot load library 'libgobject-2.0-0': libgobject-2.0-0: cannot open
shared object file: No such file or directory
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script that worked on the laptop stops at the import line in the container, and nothing in that message says Pango. WeasyPrint renders through a purpose-built layout engine that reaches for Pango at the system level, so a bare &lt;code&gt;python:slim&lt;/code&gt; base image cannot get as far as &lt;code&gt;write_pdf()&lt;/code&gt; until that library is layered in by hand, and the same step repeats on every target the pipeline touches. That is a deployment commitment rather than a package install, and it is worth pricing next to &lt;a href="https://ironpdf.com/python/tutorials/html-to-pdf/" rel="noopener noreferrer"&gt;rendering the same markup from one package&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full disclosure. IronPDF is built by our team at Iron Software. This read traces what WeasyPrint's per-target Pango install and absent script engine cost a deployment, and what IronPDF renders with its engine already inside the package.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pango Installs Per Target, Not Per Project
&lt;/h2&gt;

&lt;p&gt;Pango has to be added by hand wherever the code runs, arriving as &lt;code&gt;libpango-1.0-0&lt;/code&gt; on Debian and Ubuntu, &lt;code&gt;pango&lt;/code&gt; through Homebrew on macOS, and &lt;code&gt;pango&lt;/code&gt; via MSYS2 on Windows. A serverless function needs it baked into a custom layer before deployment rather than resolved by &lt;code&gt;pip install&lt;/code&gt;, which puts the renderer's requirements in the build pipeline instead of the requirements file. Fonts follow the same system-level model, since WeasyPrint always uses Fontconfig to reach fonts even on Windows and macOS, so anything installed system-wide appears automatically and is checkable with &lt;code&gt;fc-list&lt;/code&gt; and &lt;code&gt;fc-match&lt;/code&gt;, while a custom &lt;code&gt;@font-face&lt;/code&gt; rule needs a &lt;code&gt;weasyprint.text.fonts.FontConfiguration&lt;/code&gt; object passed alongside the CSS and reused across every CSS source, where IronPDF resolves the same font from the stylesheet the browser engine already reads.&lt;/p&gt;

&lt;p&gt;The surface has shrunk rather than grown, which is worth knowing before copying an old runbook. The current Python dependency list covers pydyf, cffi, tinyhtml5, tinycss2, cssselect2, Pyphen, Pillow, and fonttools, with no Cairo and no GDK-PixBuf anywhere in it. Version 53.0, released on 31 July 2021, is where that changed, adding pydyf as a dependency and deprecating every output format except PDF. Installation guides and forum answers written before that change still tell readers to install Cairo and GDK-PixBuf beside Pango, which now wastes a build step rather than fixing anything. IronPDF keeps the equivalent decision inside &lt;a href="https://ironpdf.com/python/get-started/use-ironpdfengine/" rel="noopener noreferrer"&gt;one packaged runtime&lt;/a&gt;, so the deployment target does not change what has to be installed first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nothing on the Page Ever Runs
&lt;/h2&gt;

&lt;p&gt;Content that appears only after client-side script has run never reaches the page. WeasyPrint does not embed a browser engine, so anything that exists only after client-side script runs is absent from the output, whether that is a single-page application rendered without a server-side pass, a chart drawn by a JavaScript charting library, or a widget populated by an API call after load. There is no delay flag to wait on and no fallback browser to hand the page to, because there is no script engine in the design at all.&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;from&lt;/span&gt; &lt;span class="n"&gt;weasyprint&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HTML&lt;/span&gt;

&lt;span class="nc"&gt;HTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice.html&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_pdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice.pdf&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;That reads the file, applies its stylesheet, and writes the PDF in one call, with no browser process to launch and no async loop to manage, which is the whole job for an invoice or a catalogue built from static markup. The same template with a client-side chart in it produces a document with an empty space where the chart belongs, and closing that gap means either a server-side rendering pass or &lt;a href="https://ironpdf.com/python/examples/javascript-html-to-pdf/" rel="noopener noreferrer"&gt;a renderer that runs the script first&lt;/a&gt; before capturing &lt;a href="https://ironpdf.com/python/examples/js-charts-to-pdf/" rel="noopener noreferrer"&gt;what the charting library drew&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Far Print-First CSS Reaches
&lt;/h2&gt;

&lt;p&gt;Paged media is the scope the engine was built for. The documentation lists support across close to twenty W3C CSS modules, including CSS Paged Media 3, Generated Content for Paged Media, Fragmentation 3 and 4, and Multi-column Layout, which are the specifications governing page boxes, running headers and footers, page counters, and content that has to break cleanly across pages. The project is equally precise about its own edges, noting that Flexbox works for simple use cases but is not deeply tested and that Grid works for simple cases with limitations, so a team reading the CSS support page knows which patterns to test first. Maintenance is current rather than nostalgic, with 9,546 stars, 865 forks, and 137 open issues on a repository that took commits the day this was written, 119 releases since 2011 and six to nine of them in each of 2023, 2024, and 2025, and a LICENSE file naming BSD 3-Clause terms precisely where the PyPI classifier only says BSD. Where a document is authored for print from the start and every element of it is static, that engine is the ceiling of what a paged-CSS renderer needs to reach, and IronPDF answers the browser-authored half of the same problem with &lt;a href="https://ironpdf.com/python/examples/pdfa/" rel="noopener noreferrer"&gt;PDF/A output&lt;/a&gt; and &lt;a href="https://ironpdf.com/python/examples/add-attachment-to-pdf/" rel="noopener noreferrer"&gt;file attachments&lt;/a&gt; on the same document object.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;WeasyPrint &lt;code&gt;69.0&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;IronPDF for Python&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rendering engine&lt;/td&gt;
&lt;td&gt;Purpose-built engine for paged CSS&lt;/td&gt;
&lt;td&gt;Chromium, driven by &lt;code&gt;ChromePdfRenderer&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JavaScript on the page&lt;/td&gt;
&lt;td&gt;Not executed, by design&lt;/td&gt;
&lt;td&gt;Executed before the page is captured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CSS coverage&lt;/td&gt;
&lt;td&gt;About 20 W3C modules, partial on Flexbox and Grid&lt;/td&gt;
&lt;td&gt;Whatever the bundled Chromium build supports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System library to install&lt;/td&gt;
&lt;td&gt;Pango 1.44 or later, per target&lt;/td&gt;
&lt;td&gt;0, the package carries its own engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDF forms&lt;/td&gt;
&lt;td&gt;Supported through &lt;code&gt;--pdf-forms&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Create, fill, and flatten on the same document&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Archival output&lt;/td&gt;
&lt;td&gt;PDF/A, PDF/UA, PDF/X, and e-invoicing profiles&lt;/td&gt;
&lt;td&gt;PDF/A from &lt;code&gt;RenderHtmlAsPdf&lt;/code&gt;, then &lt;code&gt;SaveAs&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Watermarks, stamps, and signatures&lt;/td&gt;
&lt;td&gt;Not a documented feature&lt;/td&gt;
&lt;td&gt;Stamping, watermarking, and signing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python versions accepted&lt;/td&gt;
&lt;td&gt;3.10 to 3.14&lt;/td&gt;
&lt;td&gt;3.7 and later, one wheel&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Table 1. Rendering and document output only, WeasyPrint against IronPDF for Python, as each project documents itself.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A deployment is decided by the script row and the system-library row. How the rest reads depends entirely on how the source markup was authored.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where IronPDF Fits Instead
&lt;/h2&gt;

&lt;p&gt;The case that moves a team is usually that missing script engine made concrete, meaning a dashboard, a report drawn by a client-side charting library, or any page whose real content appears only after script execution finishes.&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;from&lt;/span&gt; &lt;span class="n"&gt;ironpdf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="n"&gt;renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChromePdfRenderer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RenderUrlAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://dashboard.internal.example.com/report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SaveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dashboard-report.pdf&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;That writes the dashboard as the browser draws it, with the charts already rendered, because the script has run before the page is captured. Static files and raw strings go through the same object via &lt;code&gt;RenderHtmlFileAsPdf&lt;/code&gt; and &lt;code&gt;RenderHtmlAsPdf&lt;/code&gt;, and once the document exists, &lt;a href="https://ironpdf.com/python/examples/pdf-watermarking/" rel="noopener noreferrer"&gt;watermarking or stamping it&lt;/a&gt; is part of the same API rather than a second package.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three CVEs and a Documented Throughput Limit
&lt;/h2&gt;

&lt;p&gt;Three CVEs sit against WeasyPrint's own code, and all three have fixes. CVE-2024-28184, tracked as GHSA-35jj-wx47-4w8r at CVSS 7.4, let a crafted document attach an arbitrary local file or URL past the intended fetcher restrictions, patched in 61.2. CVE-2025-68616, GHSA-983w-rhvv-gwmv at CVSS 7.5, let a server-side request forgery protection be bypassed through an HTTP redirect in the default URL fetcher, patched in 68.0. CVE-2026-49452, GHSA-jhhc-3hcp-qhm5 at CVSS 6.5, was a CSS injection through unescaped attribute values with presentational hints enabled, patched in 69.0, and the project's own release notes for 69.0 name it as a security update. A scanner showing that number with no patched version listed is reading a stale advisory field rather than an open issue. Running 69.0 closes all three, and the fetcher settings belong in the same review as &lt;a href="https://ironpdf.com/python/examples/security-and-metadata/" rel="noopener noreferrer"&gt;the metadata and permissions&lt;/a&gt; on the output.&lt;/p&gt;

&lt;p&gt;Throughput is the other recurring cost, and the documentation states it rather than leaving it to a benchmark. WeasyPrint is often slower than other web engines, per its own docs, which recommend avoiding large CSS frameworks, replacing tables with block layouts, and caching images to keep run times reasonable. For a batch generating thousands of documents a day, that guidance is an infrastructure line item and a set of constraints on how the templates may be written, where a renderer with &lt;a href="https://ironpdf.com/python/examples/async/" rel="noopener noreferrer"&gt;documented parallel and asynchronous paths&lt;/a&gt; spends the same budget on concurrency instead.&lt;/p&gt;

&lt;p&gt;IronPDF renders the same page with its engine already inside the package, so no per-target system library joins the build, the script that assembles the layout runs before capture, and throughput is a concurrency setting rather than a documented limit. The narrow case that stays with WeasyPrint is a static document authored for print where archival profiles are contractual. IronPDF has a &lt;a href="https://ironpdf.com/python/licensing/" rel="noopener noreferrer"&gt;free trial&lt;/a&gt; if you want to put one of your existing templates through it before that Pango layer gets written into another image.&lt;/p&gt;

&lt;p&gt;Which one are you running today, and did the install behave the way you expected? Tell us in the comments, especially if you hit the Pango step on a container image or a serverless target and had to work out where it belonged in the build.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;WeasyPrint is the property of its maintainers, and we have no affiliation with CourtBouillon. The dependency, licence, and advisory details above are drawn from the project's own documentation, repository, and the published CVE records at the time of writing. If a detail has moved since, say so below.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>pdf</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>pdfminer.six: What the Reading Half of a Pipeline Costs</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Tue, 01 Sep 2026 21:09:49 +0000</pubDate>
      <link>https://dev.to/ironsoftware/pdfminersix-what-the-reading-half-of-a-pipeline-costs-292p</link>
      <guid>https://dev.to/ironsoftware/pdfminersix-what-the-reading-half-of-a-pipeline-costs-292p</guid>
      <description>&lt;p&gt;CVE-2025-64512, GHSA-wf5f-4jwr-ppcp, CVSS 8.6. &lt;code&gt;CMapDB._load_data()&lt;/code&gt; deserialises CMap cache files with Python's &lt;code&gt;pickle.loads()&lt;/code&gt;, so a crafted PDF could point that loader at an attacker-controlled &lt;code&gt;.pickle.gz&lt;/code&gt; file and execute code while the document was being read. Patched in release 20251107. Three weeks later came CVE-2025-70559, GHSA-f83h-ghpp-7wcc, CVSS 7.8, whose advisory states that the patch introduced in the earlier commit does not address the vulnerability reported there, describing a local privilege-escalation path through the same pickle mechanism and closing it in 20251230.&lt;/p&gt;

&lt;p&gt;Both are arbitrary-code-execution findings rather than availability issues, so nothing parsing outside documents can run below 20251230. That obligation sits on the reading half of a pipeline whose other half, &lt;a href="https://ironpdf.com/python/tutorials/html-to-pdf/" rel="noopener noreferrer"&gt;turning markup into a finished PDF&lt;/a&gt;, is a separate dependency with its own release cadence, advisory feed, and upgrade schedule.&lt;/p&gt;

&lt;p&gt;The library reads documents and never writes one, so the seam between the halves is not a gap in the library, it is a second entry on the architecture diagram, and the upgrade it implies belongs on a schedule rather than at the next convenient sprint.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full disclosure. We build IronPDF at Iron Software. This read follows what pdfminer.six's missing write path and pickle advisories add to a pipeline, and what IronPDF produces from markup in a single call.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does That Upgrade Obligation Ask of a Schedule?
&lt;/h2&gt;

&lt;p&gt;The advisory work belongs in the same review as &lt;a href="https://ironpdf.com/python/examples/security-and-metadata/" rel="noopener noreferrer"&gt;the document security and metadata settings&lt;/a&gt; on whatever produces the output, and the release rhythm makes it harder to plan around than a monthly cadence would. Releases arrive in bursts rather than a steady drip, with four landing inside four days in late December 2025, preceded by single releases in November 2025, May 2025, and April 2025, so roughly five or six a year. The most recent commit visible on the repository is dated 13 March 2026, with no release following it as of late August 2026. Version numbers are date-based rather than semantic, which means a pin communicates when a build was cut rather than what changed in it. Supported Python runs 3.10 through 3.14 per the published classifiers, and pure Python does not mean dependency-light here, since pdfminer.six requires &lt;code&gt;charset-normalizer&lt;/code&gt; and &lt;code&gt;cryptography&lt;/code&gt; at runtime, with Pillow as an optional extra for &lt;a href="https://ironpdf.com/python/examples/extract-pdf-image/" rel="noopener noreferrer"&gt;image handling&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Far Does Character-Level Extraction Go?
&lt;/h2&gt;

&lt;p&gt;Layout analysis is the hard part, and pdfminer.six documents it rather than dodging it, stating that a PDF file does not contain anything resembling paragraphs, sentences, or even words, and the library's whole job is regrouping characters back into words, lines, and boxes from their positions on the page. The object model follows that directly, with &lt;code&gt;LTChar&lt;/code&gt; objects each carrying a bounding box, font name, and size, rolling up into &lt;code&gt;LTTextLine&lt;/code&gt;, then &lt;code&gt;LTTextBox&lt;/code&gt;, then &lt;code&gt;LTPage&lt;/code&gt;. On top of that sit four output formats from the same parse, covering plain text, HTML, XML, and hOCR, plus AcroForm field extraction, table-of-contents resolution, and CJK and vertical-writing text. The lineage matters as much as the feature list, because pdfplumber and other extraction libraries build on that layout engine rather than writing their own, which forces the object model to stay stable release over release. For a workflow whose entire job is understanding what sits on a page and precisely where, that model is the ceiling of what an extraction library needs to reach, and IronPDF answers the producing half of the same pipeline from one package.&lt;/p&gt;

&lt;p&gt;Reaching the coordinates means walking the tree.&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;from&lt;/span&gt; &lt;span class="n"&gt;pdfminer.high_level&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;extract_pages&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pdfminer.layout&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LTTextContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LTChar&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;page_layout&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;extract_pages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice.pdf&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;element&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;page_layout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&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;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LTTextContainer&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;text_line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;element&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;character&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;text_line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;if&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;character&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LTChar&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;character&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bbox&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fontname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;character&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That prints one line per character, each with its bounding box, font name, and point size, which is the level of detail a field-location or signature-block problem actually needs. The equivalent starting point on the producing side is &lt;a href="https://ironpdf.com/python/examples/extract-pdf-text/" rel="noopener noreferrer"&gt;pulling the text back out of a document&lt;/a&gt; that a renderer has just written.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Boundary Sits
&lt;/h2&gt;

&lt;p&gt;Every converter produces something other than a PDF. The four options pdfminer.six offers are plain text, HTML, XML, and hOCR, and not one of them writes a document back out, so a workflow that has to return an actual PDF, even one that merely restates content it just extracted, brings a second library in to &lt;a href="https://ironpdf.com/python/how-to/python-merge-pdf/" rel="noopener noreferrer"&gt;write and assemble it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Grouping accuracy is a tuning job rather than a default. Characters become lines and boxes through the &lt;code&gt;LAParams&lt;/code&gt; class, and the documentation states that the output of the layout analysis heavily depends on those parameters. A multi-column layout or a dense table is not guaranteed to group correctly on the first attempt, and it groups correctly once somebody tunes &lt;code&gt;LAParams&lt;/code&gt; against that specific document shape, which is per-shape work that arrives with every new supplier format.&lt;/p&gt;

&lt;p&gt;A page with no text layer gives the parser nothing to work with. What pdfminer.six reads is character and font objects already encoded in the file, so a scanned page saved as an image returns nothing until an OCR pass puts a text layer there, and that OCR pass is another tool again. By that point the diagram carries three dependencies for a job that started as one, which is the part worth pricing before &lt;a href="https://ironpdf.com/python/how-to/python-pdf-to-image/" rel="noopener noreferrer"&gt;rasterising or re-rendering the page&lt;/a&gt; gets added on top.&lt;/p&gt;

&lt;p&gt;The whole split sits in one table.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;pdfminer.six &lt;code&gt;20260107&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;IronPDF for Python&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Text out of an existing PDF&lt;/td&gt;
&lt;td&gt;Character coordinates through &lt;code&gt;LTChar&lt;/code&gt; and &lt;code&gt;LTTextBox&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ExtractAllText&lt;/code&gt; on a loaded &lt;code&gt;PdfDocument&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Producing a PDF file&lt;/td&gt;
&lt;td&gt;No write path in any converter&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RenderHtmlAsPdf&lt;/code&gt; and &lt;code&gt;SaveAs&lt;/code&gt;, through Chromium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Form fields&lt;/td&gt;
&lt;td&gt;AcroForm field extraction&lt;/td&gt;
&lt;td&gt;Reading and filling fields on the same document&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output formats from one pass&lt;/td&gt;
&lt;td&gt;Text, HTML, XML, and hOCR&lt;/td&gt;
&lt;td&gt;PDF, plus page images through rasterising&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime dependencies&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;charset-normalizer&lt;/code&gt; and &lt;code&gt;cryptography&lt;/code&gt; required&lt;/td&gt;
&lt;td&gt;1 package, 0 system dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Licence&lt;/td&gt;
&lt;td&gt;MIT License&lt;/td&gt;
&lt;td&gt;Commercial, one tier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python versions accepted&lt;/td&gt;
&lt;td&gt;3.10 to 3.14&lt;/td&gt;
&lt;td&gt;3.7 and later&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Current release&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;20260107&lt;/code&gt;, date-based, 5 or 6 a year&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;2026.9.0.2&lt;/code&gt; on 1 September 2026, 11 in 12 months&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Table 1. Where each library operates in a document pipeline, pdfminer.six against IronPDF for Python, as each project documents itself.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The second row is the seam, since &lt;a href="https://ironpdf.com/python/examples/using-html-to-create-a-pdf/" rel="noopener noreferrer"&gt;turning markup into the finished document&lt;/a&gt; is the half with no entry point here at all, and everything else in the table is what the reading half brings with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where IronPDF Picks Up
&lt;/h2&gt;

&lt;p&gt;The producing half is a three-line job when the input is markup rather than a parsed page.&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;from&lt;/span&gt; &lt;span class="n"&gt;ironpdf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="n"&gt;renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChromePdfRenderer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RenderHtmlAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;h1&amp;gt;Extracted Summary&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;Generated from parsed invoice data.&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SaveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;summary-report.pdf&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;That writes a finished PDF from the markup, rendered through Chromium so the stylesheet behaves as it does in a browser. Static template files and live URLs go through the same object via &lt;code&gt;RenderHtmlFileAsPdf&lt;/code&gt; and &lt;code&gt;RenderUrlAsPdf&lt;/code&gt;, so the entry point stays the same wherever the markup lives, and the &lt;a href="https://ironpdf.com/python/docs/" rel="noopener noreferrer"&gt;Python API reference&lt;/a&gt; covers the surface beyond rendering.&lt;/p&gt;

&lt;p&gt;IronPDF produces the document the pipeline has to hand back, from markup, in a single call, which keeps the outbound half off a second dependency with its own advisory feed and its own upgrade schedule. The narrow case that stays with pdfminer.six is character-level extraction where coordinates and font data are the deliverable. IronPDF has a &lt;a href="https://ironpdf.com/python/licensing/" rel="noopener noreferrer"&gt;free trial&lt;/a&gt; if you want to test the generation half against your own template while the extraction step keeps running as it is.&lt;/p&gt;

&lt;p&gt;What sits on the other end of your pipeline once the extraction finishes, a template renderer, a second PDF library, or a step still held together by hand? Tell us in the comments, because that seam is where most of these architectures actually get decided.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;pdfminer.six is the property of its maintainers, and we have no affiliation with the project. The release, dependency, and advisory details above are drawn from the project's own repository, PyPI metadata, and the published CVE records at the time of writing. If a detail has moved since, correct us in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>pdf</category>
      <category>opensource</category>
      <category>architecture</category>
    </item>
    <item>
      <title>ReportBro in Python: What Embedding a Designer Costs</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Tue, 01 Sep 2026 21:08:49 +0000</pubDate>
      <link>https://dev.to/ironsoftware/reportbro-in-python-what-embedding-a-designer-costs-130a</link>
      <guid>https://dev.to/ironsoftware/reportbro-in-python-what-embedding-a-designer-costs-130a</guid>
      <description>&lt;p&gt;Ask who is going to maintain the invoice layout in a year, and a ReportBro adoption answers with two package managers. &lt;code&gt;reportbro-lib&lt;/code&gt; is the Python renderer a service runs, installed from PyPI. &lt;code&gt;reportbro-designer&lt;/code&gt; is the JavaScript component the business user opens in a browser, installed from npm. They are versioned and upgraded separately, so a front-end build joins the dependency diagram of a feature Python renders, and both halves are AGPL-3.0, so the licence review has to clear both before anything ships. None of that is asked of a library that &lt;a href="https://ironpdf.com/python/tutorials/html-to-pdf/" rel="noopener noreferrer"&gt;turns existing markup into a PDF&lt;/a&gt; and is &lt;a href="https://ironpdf.com/python/how-to/python-create-pdf/" rel="noopener noreferrer"&gt;installed once&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full disclosure. We are the team at Iron Software behind IronPDF. This piece looks at what ReportBro's AGPL halves and PLUS-gated text shaping commit a team to, and how IronPDF renders a developer-owned template from one pip install.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does Embedding a Designer Commit You To?
&lt;/h2&gt;

&lt;p&gt;Both components are licensed AGPL-3.0, so this is not an open renderer paired with a paid designer. Any team shipping a closed-source product needs the licence review to clear both halves, and the commercial alternative is sold as a bundle with ReportBro PLUS rather than as a standalone upgrade to either component. Text formatting sits inside that same decision. The AGPL build of &lt;code&gt;reportbro-lib&lt;/code&gt; raises an explicit &lt;code&gt;errorMsgPlusVersionRequired&lt;/code&gt; the moment an element sets its &lt;code&gt;richText&lt;/code&gt; field or asks for text shaping, which gates both behind PLUS with no intermediate option, so a layout requirement that looks like formatting turns into a purchasing conversation. IronPDF keeps that step to a &lt;a href="https://ironpdf.com/python/get-started/license-keys/" rel="noopener noreferrer"&gt;licence key&lt;/a&gt; applied once, with the same &lt;a href="https://ironpdf.com/python/docs/" rel="noopener noreferrer"&gt;documented surface&lt;/a&gt; available to every deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Is the Designer Actually For?
&lt;/h2&gt;

&lt;p&gt;A non-developer opens the designer in a browser, drags fields onto a page, binds them to data, and previews the result live, without touching the Python that renders the file. Wiring it into a page takes a stylesheet and script include, a container &lt;code&gt;div&lt;/code&gt;, and a &lt;code&gt;new ReportBro(...)&lt;/code&gt; call against it. What comes out is not a binary or a proprietary blob but a plain Python dict, so a layout a business user builds can be committed to source control, diffed in a pull request, and reviewed the way code is reviewed. The same definition produces either a PDF or an XLSX file through &lt;code&gt;generate_pdf()&lt;/code&gt; or &lt;code&gt;generate_xlsx()&lt;/code&gt;, with no second template to maintain, and a full parameter, expression, and data-binding model configured visually rather than in a script. Nine years of work sits behind it, with roughly fifty &lt;code&gt;reportbro-lib&lt;/code&gt; releases since the first PyPI upload in 2017 and 3.12.2 on 29 April 2026. Where a business user owns the layout and maintains it without a developer in the loop, that designer is the ceiling of what a reporting product can hand a non-technical author, and IronPDF answers the developer-owned half of the same problem from one package.&lt;/p&gt;

&lt;p&gt;Rendering the definition is two lines once the data is in hand.&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;from&lt;/span&gt; &lt;span class="n"&gt;reportbro&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Report&lt;/span&gt;

&lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report_definition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pdf_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_pdf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That returns the finished PDF as bytes, ready to write to disk or stream back through a web response. Swapping &lt;code&gt;generate_pdf()&lt;/code&gt; for &lt;code&gt;generate_xlsx()&lt;/code&gt; produces the spreadsheet from the same definition, which is the part of the model that saves a second template, and the equivalent starting point on a markup-first path is &lt;a href="https://ironpdf.com/python/examples/using-html-to-create-a-pdf/" rel="noopener noreferrer"&gt;pointing a renderer at the HTML that already exists&lt;/a&gt; rather than rebuilding it, with &lt;a href="https://ironpdf.com/python/examples/pdf-generation-settings/" rel="noopener noreferrer"&gt;page setup handled in the render options&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Inputs Will the Renderer Take?
&lt;/h2&gt;

&lt;p&gt;A report-definition dict and a data object, and nothing else. ReportBro exposes no method that takes an HTML string, a URL, or an existing template and returns a PDF, so content that already lives as markup, covering a marketing template, a live web page, or an email layout, gets rebuilt inside the designer's object model before it can be produced. That rebuild is the cost that lands on teams whose source material was authored somewhere else, and it recurs every time the design does.&lt;/p&gt;

&lt;p&gt;Underneath, the PDF path runs through &lt;code&gt;reportbro-fpdf2&lt;/code&gt;, a maintainer-controlled fork of fpdf2, with Babel, Pillow, XlsxWriter, python-barcode, qrcode, and simpleeval alongside it, all pure Python, and &lt;code&gt;reportbro-lib&lt;/code&gt; declares support for Python 3.10 and above. Three constructor defaults are worth knowing before deployment, since &lt;code&gt;Report()&lt;/code&gt; allows local images by default through &lt;code&gt;allow_local_image=True&lt;/code&gt;, blocks external image URLs unless a team opts in through &lt;code&gt;allow_external_image=False&lt;/code&gt;, and caps output at 10,000 pages through &lt;code&gt;page_limit=10000&lt;/code&gt; until that is raised explicitly. Those are sensible defaults for a tool rendering user-supplied data, and they are also three settings to carry into a deployment review next to whatever handles &lt;a href="https://ironpdf.com/python/examples/image-to-pdf/" rel="noopener noreferrer"&gt;images&lt;/a&gt; and &lt;a href="https://ironpdf.com/python/examples/headers-and-footers/" rel="noopener noreferrer"&gt;page furniture&lt;/a&gt; on the output side. Nothing else in that review is outstanding, since the GitHub Advisory Database under both the PyPI and npm filters, Snyk, and the NVD carry nothing against either package, which puts the whole of this decision in the licence and the authoring model.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;ReportBro &lt;code&gt;3.12.2&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;IronPDF for Python&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PDF output&lt;/td&gt;
&lt;td&gt;Through a maintainer-controlled fpdf2 fork&lt;/td&gt;
&lt;td&gt;Through a bundled Chromium engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTML or CSS as input&lt;/td&gt;
&lt;td&gt;Report-definition dict plus data, no markup path&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RenderHtmlAsPdf&lt;/code&gt;, &lt;code&gt;RenderUrlAsPdf&lt;/code&gt;, and &lt;code&gt;RenderHtmlFileAsPdf&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who authors the layout&lt;/td&gt;
&lt;td&gt;A non-developer in the browser designer&lt;/td&gt;
&lt;td&gt;A developer, in HTML and CSS the team already owns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;richText&lt;/code&gt; and text shaping&lt;/td&gt;
&lt;td&gt;Raises &lt;code&gt;errorMsgPlusVersionRequired&lt;/code&gt; without PLUS&lt;/td&gt;
&lt;td&gt;Rendered as the browser renders it, every licence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Packages to install&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;reportbro-lib&lt;/code&gt; on PyPI plus &lt;code&gt;reportbro-designer&lt;/code&gt; on npm&lt;/td&gt;
&lt;td&gt;One package from pip, Python only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Licence&lt;/td&gt;
&lt;td&gt;AGPL-3.0 on both components, or a commercial bundle&lt;/td&gt;
&lt;td&gt;Commercial, one licence for every deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python versions accepted&lt;/td&gt;
&lt;td&gt;3.10 and later&lt;/td&gt;
&lt;td&gt;3.7 and later&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Table 1. Document output path only, ReportBro against IronPDF for Python, with tier gating shown where ReportBro applies it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A rebuild is priced by the markup row, and a purchase order is triggered by the text-shaping row, where &lt;a href="https://ironpdf.com/python/examples/unicode/" rel="noopener noreferrer"&gt;Unicode and text rendering&lt;/a&gt; sit inside the engine rather than behind a tier. Everything else answers the question of who sits in front of the layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Renderer Asks of the Team Instead
&lt;/h2&gt;

&lt;p&gt;The other half of this problem starts with markup that already exists, whether a template, a live page, or a report some other system already renders.&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;from&lt;/span&gt; &lt;span class="n"&gt;ironpdf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="n"&gt;renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChromePdfRenderer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RenderHtmlAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;h1&amp;gt;Invoice #4471&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;Total due: $1,240.00&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SaveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invoice.pdf&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;That writes the invoice with its CSS applied as a browser would paint it, on Windows, macOS, Linux, and in a container, on Python 3.7 and later. Where an internal Flask or Django view already renders the report as a page, &lt;a href="https://ironpdf.com/python/examples/converting-a-url-to-a-pdf/" rel="noopener noreferrer"&gt;handing the renderer that URL&lt;/a&gt; skips the report-definition step completely.&lt;/p&gt;

&lt;p&gt;IronPDF renders a developer-owned template from one pip install, with no npm component in the build, no AGPL review to clear before production, and no tier gate standing between a formatting requirement and a purchase order. The narrow case that stays with ReportBro is a business user maintaining the layout in a browser with no developer in the loop. IronPDF has a &lt;a href="https://ironpdf.com/python/licensing/" rel="noopener noreferrer"&gt;free trial&lt;/a&gt; if you want to put your existing template through it before the licence review starts.&lt;/p&gt;

&lt;p&gt;So who is actually staffing the next report on your side, a business user with a browser or a developer with an HTML template? Tell us in the comments which one your last project needed, because that answer usually settles the tooling before anyone opens a feature table.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ReportBro is the property of its maintainers, and we have no affiliation with the project. The licence, release, and default-configuration details above are taken from ReportBro's own documentation, PyPI listing, and npm package at the time of writing. If a detail has changed since, please let us know below.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>pdf</category>
      <category>reporting</category>
      <category>architecture</category>
    </item>
    <item>
      <title>List &amp; Label 31: Linux Costs €1,990 to Find Out</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Mon, 31 Aug 2026 18:45:59 +0000</pubDate>
      <link>https://dev.to/ironsoftware/list-label-31-linux-costs-eu1990-to-find-out-125n</link>
      <guid>https://dev.to/ironsoftware/list-label-31-linux-costs-eu1990-to-find-out-125n</guid>
      <description>&lt;p&gt;You cannot find out whether List &amp;amp; Label works in your Linux container without buying the top tier first. Everything that makes it deployable off Windows, meaning the Cross Platform engine, container hosting, the Web Report Designer, and the combit Report Server, sits behind Enterprise at €1,990 per developer in year one. On Standard or Professional, asking whether this runs in Docker is a purchase order rather than a feature flag, and the answer arrives after the money does. IronPDF answers the same question on its only tier, before any purchase order, in an afternoon of &lt;a href="https://ironpdf.com/tutorials/html-to-pdf/" rel="noopener noreferrer"&gt;turning markup into a PDF&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;IronPDF is ours. We build it at Iron Software, and what follows explores the Enterprise-only tier that gates List &amp;amp; Label on Linux, next to IronPDF &lt;a href="https://ironpdf.com/how-to/html-string-to-pdf/" rel="noopener noreferrer"&gt;running the same render call&lt;/a&gt; from its only tier.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does the Tier Ladder Gate?
&lt;/h2&gt;

&lt;p&gt;The vendor has shipped this component inside .NET applications since 1992, and version 31 reached general availability on 16 October 2025. The tier ladder is where a deployment question turns into a purchasing one.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability (document output only)&lt;/th&gt;
&lt;th&gt;List &amp;amp; Label 31&lt;/th&gt;
&lt;th&gt;IronPDF&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PDF export of a designed report&lt;/td&gt;
&lt;td&gt;Native, from a &lt;code&gt;.lst&lt;/code&gt; or &lt;code&gt;.lbl&lt;/code&gt; definition&lt;/td&gt;
&lt;td&gt;Renders markup, a file, or a URL through &lt;code&gt;ChromePdfRenderer&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTML and CSS in the document body&lt;/td&gt;
&lt;td&gt;Custom component, restricted CSS subset, no browser engine&lt;/td&gt;
&lt;td&gt;Chromium rendering of HTML, CSS, and JavaScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Direct rendering of a URL or Razor view&lt;/td&gt;
&lt;td&gt;Bound into a report definition first&lt;/td&gt;
&lt;td&gt;Direct, via &lt;code&gt;RenderUrlAsPdf&lt;/code&gt; or a &lt;code&gt;.cshtml&lt;/code&gt; view&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manipulating an existing PDF&lt;/td&gt;
&lt;td&gt;Outside the export pipeline&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;PdfDocument.Merge&lt;/code&gt;, &lt;a href="https://ironpdf.com/how-to/merge-or-split-pdfs/" rel="noopener noreferrer"&gt;split&lt;/a&gt;, stamp, encrypt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tier required for Linux and &lt;a href="https://ironpdf.com/get-started/ironpdf-docker/" rel="noopener noreferrer"&gt;container hosting&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;3rd tier, Enterprise&lt;/td&gt;
&lt;td&gt;1st tier, and the only tier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entry price per developer, first year&lt;/td&gt;
&lt;td&gt;€740 Standard, €1,390 Professional, €1,990 Enterprise&lt;/td&gt;
&lt;td&gt;1 licence, 1 tier&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Table 1. Document output only, List &amp;amp; Label 31 against IronPDF, with tier gating shown where combit applies it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The last two rows keep this comparison honest. Everything above them is a document-output question, while those two are what the tier ladder costs to answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  €1,990 Buys the Answer to a Linux Question
&lt;/h2&gt;

&lt;p&gt;That ladder is not arbitrary, and the vendor is transparent about what sits where. Standard and Professional are Windows desktop reporting. Enterprise adds List &amp;amp; Label Cross Platform for Windows, Linux, and macOS, unlimited use in web and server applications, the Web Report Designer and Web Report Viewer, the browser-based Report Server, premium support with a 14-day bug-fix commitment on even-numbered versions, and additional designer languages. Priced as a package, that is defensible. Priced as the cost of finding out whether a container deployment is viable, it front-loads the commitment before the evidence exists.&lt;/p&gt;

&lt;p&gt;A second constraint inside the cross-platform story survives the tier upgrade. The migration documentation states plainly that the designer is not yet available for List &amp;amp; Label Cross Platform, and that templates must therefore still be created using the classic version on Windows and then converted to the new JSON format. A Linux-hosted deployment on the Enterprise tier therefore keeps a Windows machine in the authoring pipeline. The runtime went cross-platform ahead of the tool that feeds it, which is a reasonable order to build things in, and it means a team with &lt;a href="https://ironpdf.com/how-to/macos/" rel="noopener noreferrer"&gt;developers on macOS&lt;/a&gt; is planning around a Windows box for report editing regardless of what the runtime supports.&lt;/p&gt;

&lt;p&gt;The HTML path carries its own boundary. The documentation states that List &amp;amp; Label uses a custom component for HTML rendering supporting a restricted CSS subset, and that correct rendering of entire web pages is not that component's main intent, while the designer docs warn against complex scripts, redirects, and unusual CSS. That is an accurate self-description rather than a hidden limitation, and it means a layout built for a browser reaches PDF through IronPDF without a rebuild and through combit's component only with one.&lt;/p&gt;

&lt;p&gt;On security, a search of NVD, the GitHub Advisory Database, and Snyk turns up no advisory naming combit or List &amp;amp; Label as the affected vendor or product. Two things follow from that. It is not the same as an absence of vulnerabilities, because the vendor is not a CVE Numbering Authority, so a missing public record carries less evidential weight than it would for a company filing its own advisories, and a plain keyword search on the product name is a poor filter since the phrase is ordinary English, so a real check confirms the CPE vendor string reads &lt;code&gt;combit&lt;/code&gt;. Having granted that, the risk that actually decides this product is elsewhere, because the deployment surface, the Windows authoring dependency, and the tier gate are all fixed at purchase time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Does That Breadth Stop Short?
&lt;/h2&gt;

&lt;p&gt;The surface area is wide. The product pages list roughly 30 export formats, over 250 formula functions, more than 40 built-in .NET data providers, and chart, map, crosstab, barcode, and gauge objects. Version 31 added something concrete on top of that, namely out-of-the-box ZUGFeRD and Factur-X 2.3 e-invoicing in the PDF export path, covering the BASIC, EN 16931, EXTENDED, and XRECHNUNG profiles. That produces a conformant &lt;a href="https://ironpdf.com/how-to/pdfa/" rel="noopener noreferrer"&gt;PDF/A-3&lt;/a&gt; invoice with the structured XML &lt;a href="https://ironpdf.com/how-to/add-remove-attachments/" rel="noopener noreferrer"&gt;embedded as an attachment&lt;/a&gt; straight from a report definition, with no separate compliance library in the build. For anyone shipping into European markets where e-invoicing is becoming mandatory, that covers an integration the build would otherwise carry on its own. That matrix is Enterprise-tier work, and the Linux question it leaves open is the one IronPDF answers on its first tier.&lt;/p&gt;

&lt;p&gt;Licensing is per developer, not per deployment. End users and projects are unlimited under every tier, and the report designer is redistributable with no royalty, so handing report authoring to your own customers adds no per-deployment line item. That is a materially different commercial shape from tools metering by server, end user, or deployment target, and it is the ceiling of what the licence covers, because IronPDF licenses per developer on the same basis with no tier ladder to climb for a container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Tier Does IronPDF Ask You to Reach?
&lt;/h2&gt;

&lt;p&gt;The narrower job is a fixed layout or an HTML template that has to become a PDF, with no report project or bound data source in the path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IronPdf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;statementRenderer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChromePdfRenderer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;statementRenderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RenderingOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MarginTop&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;statementRenderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RenderingOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MarginBottom&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statementRenderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RenderHtmlAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statementHtml&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// The assembly step a report definition would need a subreport for&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;packet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;appendix&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;packet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"statement-package.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;IronPDF writes a single searchable PDF from two sources, and the same call runs on Windows, on Linux, and in a container from one package with no cross-platform tier to reach first. IronPDF hands the stylesheet to a full browser engine, so the &lt;a href="https://ironpdf.com/how-to/html-to-pdf-responsive-css/" rel="noopener noreferrer"&gt;responsive CSS a design team already wrote&lt;/a&gt; survives the trip, and where the markup starts life as an MVC template it &lt;a href="https://ironpdf.com/how-to/cshtml-to-pdf-razor/" rel="noopener noreferrer"&gt;takes the Razor view directly&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Upstream of that call belongs to List &amp;amp; Label and should stay there. A rendering library has none of it, not a business analyst designing their own layout, not a parameter-driven query against a live data source, not a crosstab or a subreport, and not scheduled distribution through a report server. The ceiling on this particular path is that the tier unlocking Linux also prices in a designer the workload may never open, and that the authoring half of cross-platform has not shipped yet. Coexistence is the usual outcome for teams with real use for both, and the &lt;a href="https://ironpdf.com/tutorials/crystal-reports-alternative-csharp/" rel="noopener noreferrer"&gt;renderer-side pattern is written up in full&lt;/a&gt; if that split is on the table. IronPDF has a &lt;a href="https://ironpdf.com/licensing/" rel="noopener noreferrer"&gt;free trial&lt;/a&gt; if you want to answer the container question against your own template before signing anything.&lt;/p&gt;

&lt;p&gt;Has anyone here run List &amp;amp; Label Cross Platform in production yet, or is report authoring still routed through a Windows machine because the designer has not caught up? Let us know in the comments, because the gap between runtime support and tooling support is the part that decides how this feels day to day.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;List &amp;amp; Label is a trademark of its owner and this piece is not affiliated with the company. The pricing, tier gating, cross-platform, and HTML rendering details above are drawn from combit's own pricing pages and documentation, and from NVD, the GitHub Advisory Database, and Snyk, as they stood at the time of writing. If a detail has changed since, correct us in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>pdf</category>
      <category>productivity</category>
    </item>
    <item>
      <title>RDLC and ReportViewer on .NET 10: Who Owns the Package?</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Mon, 31 Aug 2026 18:45:51 +0000</pubDate>
      <link>https://dev.to/ironsoftware/rdlc-and-reportviewer-on-net-10-who-owns-the-package-o8c</link>
      <guid>https://dev.to/ironsoftware/rdlc-and-reportviewer-on-net-10-who-owns-the-package-o8c</guid>
      <description>&lt;p&gt;Search nuget.org for reportviewer and you get 104 packages. Two carry the verified-owner badge and are published by Microsoft, both version 150.1652.0, both republished 3 June 2024, and both targeting &lt;code&gt;net40&lt;/code&gt; through &lt;code&gt;net481&lt;/code&gt; with no .NET Core or .NET 5 target in either. The most-downloaded results are not those two. Package provenance is a boring thing to audit until it is the only thing between a report pipeline and a dependency nobody supports, which is a different position from &lt;a href="https://ironpdf.com/tutorials/html-to-pdf/" rel="noopener noreferrer"&gt;IronPDF turning the same data into a PDF&lt;/a&gt; under a named publisher.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Our team at Iron Software builds IronPDF, and this read examines what RDLC's missing modern-.NET ReportViewer costs a project, and how IronPDF &lt;a href="https://ironpdf.com/how-to/html-string-to-pdf/" rel="noopener noreferrer"&gt;renders the same markup to a PDF&lt;/a&gt; from one supported package.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Far Does Local Report Processing Reach?
&lt;/h2&gt;

&lt;p&gt;Local report processing covers one job, and it covers it entirely inside the application.&lt;/p&gt;

&lt;p&gt;It ships under Microsoft's own redistribution terms, which is part of why so many .NET Framework codebases already have it. It is also fully embedded, because local processing mode means, in Microsoft's own phrasing, that the ReportViewer control processes the report only and the host application must retrieve the data and supply it. No report server, no catalog database, no web portal, nothing to install or patch beyond the application itself. IronPDF matches that with no server either, on the .NET versions ReportViewer never reached.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Reporting.WinForms&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;invoiceReport&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;LocalReport&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;ReportPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Invoice.rdlc"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="n"&gt;invoiceReport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataSources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ReportDataSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"InvoiceDataSet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invoiceRows&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;pdfBytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;invoiceReport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PDF"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteAllBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invoice.pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pdfBytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That returns a PDF byte array with no network call in the path, which is what an offline line-of-business application wants. For &lt;a href="https://ironpdf.com/how-to/windows/" rel="noopener noreferrer"&gt;a WinForms or WPF application on Windows&lt;/a&gt; against .NET Framework, this remains a reasonable fit, and Microsoft has not stopped maintaining it. Both owned packages were republished in June 2024, and the RDLC Report Designer extension on the Visual Studio Marketplace is published under a domain-verified Microsoft publisher with over 1.3 million installs. Those updates are the ceiling of what the packages offer, because they stop at .NET Framework while IronPDF's current build targets .NET 10.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which .NET Targets Still Get an Official ReportViewer?
&lt;/h2&gt;

&lt;p&gt;With no report server, scheduler, or analyst-facing portal in the picture, the comparison narrows to how a PDF gets produced.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability (PDF output path only)&lt;/th&gt;
&lt;th&gt;RDLC via ReportViewer, local processing&lt;/th&gt;
&lt;th&gt;IronPDF&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Renders to a PDF file&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LocalReport.Render("PDF")&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ChromePdfRenderer.RenderHtmlAsPdf&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Input types the call accepts&lt;/td&gt;
&lt;td&gt;1, an &lt;code&gt;.rdlc&lt;/code&gt; compiled into the project&lt;/td&gt;
&lt;td&gt;4, markup, a file, a URL, or a Razor view&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTML rendering engine&lt;/td&gt;
&lt;td&gt;Internal RDL renderer, fixed tag and CSS subset&lt;/td&gt;
&lt;td&gt;Chromium, full CSS and JavaScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Officially supported targets&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;net40&lt;/code&gt; through &lt;code&gt;net481&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://ironpdf.com/tutorials/dotnet-core-pdf-generating/" rel="noopener noreferrer"&gt;.NET Framework 4.6.2 through .NET 10&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Headless rendering with no viewer control&lt;/td&gt;
&lt;td&gt;Community port only&lt;/td&gt;
&lt;td&gt;Native, &lt;code&gt;PdfDocument&lt;/code&gt; returned directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://ironpdf.com/get-started/linux/" rel="noopener noreferrer"&gt;Linux and container support&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Community port only, and images need a workaround&lt;/td&gt;
&lt;td&gt;Native, images included&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Table 1. PDF output path only, local RDLC processing against IronPDF, excluding the in-app viewer and report designer.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Three pieces of evidence sit behind the middle rows.&lt;/p&gt;

&lt;p&gt;First, no officially supported ASP.NET Core ReportViewer exists at all, and a team that only needs the PDF still cannot process an &lt;code&gt;.rdlc&lt;/code&gt; on .NET 6 or later using a package Microsoft publishes. The working option, &lt;code&gt;ReportViewerCore.NETCore&lt;/code&gt;, is maintained by an individual developer, currently at version 15.1.33 published 20 January 2026 with 3.6 million downloads, and its NuGet description opens by stating the project is not supported or developed by Microsoft. Its GitHub README is more specific still, explaining that the source comes from decompiling Report Viewer for WinForms version 15.0.1404.0 using ILSpy, and adding that while decompiling and modifying it for compatibility reasons is legal in the maintainer's local jurisdiction, redistributing a modified version most likely is not, so it should be used at your own risk. The maintainer is being straight with people. The forum answers recommending the package rarely quote that passage at all.&lt;/p&gt;

&lt;p&gt;Second, images break off Windows. Local report rendering depends on &lt;code&gt;System.Drawing.Common&lt;/code&gt; for anything past plain text, and that has been Windows-only since .NET 6, throwing &lt;code&gt;PlatformNotSupportedException&lt;/code&gt; elsewhere unless a compatibility switch is set. Microsoft's own breaking-change documentation states that the switch, &lt;code&gt;System.Drawing.EnableUnixSupport&lt;/code&gt;, is available only in .NET 6 and was removed in .NET 7. A report with a logo that renders on a Windows build agent fails the moment that pipeline moves into a Linux container on .NET 7 or later, where IronPDF keeps &lt;a href="https://ironpdf.com/how-to/add-images-to-pdfs/" rel="noopener noreferrer"&gt;image handling inside the same render call&lt;/a&gt; rather than a platform dependency.&lt;/p&gt;

&lt;p&gt;Third, two package names circulating as the fix do not hold up. &lt;code&gt;Microsoft.ReportingServices.ReportViewer.NetCore&lt;/code&gt;, recommended in forum answers including on Microsoft's own Q&amp;amp;A site, does not exist on nuget.org. &lt;code&gt;AspNetCore.Reporting&lt;/code&gt; was last published in August 2018 with a description still saying the package is not finished yet. Separately, the standalone Microsoft Report Viewer 2015 Runtime redistributable that older deployments install outside NuGet reached the end of extended support on 14 October 2025, with no newer standalone runtime announced. On security the record is quiet, because an NVD keyword search for ReportViewer turns up no CVE naming the control or either owned package. Older write-ups citing GDI+ vulnerabilities against this control are pointing at the Windows graphics stack of that era, and a clean CVE record is not the exposure that matters here anyway. The exposure is a production PDF path whose only modern-.NET implementation is a decompiled binary carrying an unresolved redistribution question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does One Package Cover Every Target You Ship?
&lt;/h2&gt;

&lt;p&gt;If what the project owes its users is a PDF built from data and a template the application controls, the report definition and the viewer control both drop out of the path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IronPdf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;invoiceRenderer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChromePdfRenderer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;invoiceRenderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RenderHtmlAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoiceHtml&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Same object handles the assembly step an .rdlc subreport would have covered&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;packet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coverSheet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;packet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invoice-packet.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;IronPDF runs on .NET Framework 4.6.2 through .NET 10, on Windows, Linux, and macOS, in a container or not, through one package with a named publisher behind it. Where an MVC view already produces the markup, the same renderer &lt;a href="https://ironpdf.com/how-to/cshtml-to-pdf-razor/" rel="noopener noreferrer"&gt;takes the Razor view directly&lt;/a&gt;, and the merging, splitting, encrypting, and stamping surface is documented in the &lt;a href="https://ironpdf.com/object-reference/api/" rel="noopener noreferrer"&gt;API reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The boundary is plain. This is not a replacement for ReportViewer when the requirement is an interactive report a user pages through inside a desktop application, with parameter prompts, a viewer control, and drill-down navigation the user drives. There is no viewer control here and none planned. RDLC's ceiling is the framework it was built for rather than its quality, because on .NET Framework it stays offline, maintained, and fit for that codebase, and the honest recommendation there is to leave it alone. What changes is the day a project moves off .NET Framework, since migrating the reports later then means adopting a decompiled community port whose own maintainer flags redistribution as unresolved. That decision is worth making deliberately, with the provenance in front of you. The &lt;a href="https://ironpdf.com/tutorials/crystal-reports-alternative-csharp/" rel="noopener noreferrer"&gt;pattern for moving the export step to a renderer&lt;/a&gt; is written up if that is where you land, and IronPDF has a &lt;a href="https://ironpdf.com/licensing/" rel="noopener noreferrer"&gt;free trial&lt;/a&gt; for running one of your existing &lt;code&gt;.rdlc&lt;/code&gt; outputs against an HTML template first.&lt;/p&gt;

&lt;p&gt;Have you had to make this call on a project leaving .NET Framework? Tell us in the comments how it went, especially if you ended up shipping &lt;code&gt;ReportViewerCore.NETCore&lt;/code&gt; to production and how the licensing question got answered internally.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Microsoft, ReportViewer, and RDLC are Microsoft trademarks and this piece is not affiliated with Microsoft. The package ownership, version, lifecycle, and platform details above are drawn from nuget.org, Microsoft Learn, the Visual Studio Marketplace, and the ReportViewerCore repository as they stood at the time of writing. If a detail has changed since, correct us in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>pdf</category>
      <category>dotnetcore</category>
    </item>
    <item>
      <title>Stimulsoft's AllowHtmlTags Is Not a Rendering Engine</title>
      <dc:creator>IronSoftware</dc:creator>
      <pubDate>Sun, 30 Aug 2026 16:06:20 +0000</pubDate>
      <link>https://dev.to/ironsoftware/stimulsofts-allowhtmltags-is-not-a-rendering-engine-1f3h</link>
      <guid>https://dev.to/ironsoftware/stimulsofts-allowhtmltags-is-not-a-rendering-engine-1f3h</guid>
      <description>&lt;p&gt;Two ways to get markup into a document, and only one of them is what most people mean by the phrase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Report engine: a boolean on a text field, and an interpreter behind it&lt;/span&gt;
&lt;span class="n"&gt;invoiceHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AllowHtmlTags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;invoiceHeader&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="s"&gt;"&amp;lt;b&amp;gt;Invoice&amp;lt;/b&amp;gt; &amp;lt;font-size=\"14\"&amp;gt;2026-08&amp;lt;/font-size&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Rendering engine: the page itself, stylesheet and all&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChromePdfRenderer&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;RenderHtmlAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoiceHtml&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both lines put styled content on a page. The first accepts a fixed list of inline tags inside one field. The second hands the whole document to a browser. Confusing them turns an existing library of &lt;a href="https://ironpdf.com/how-to/html-file-to-pdf/" rel="noopener noreferrer"&gt;HTML templates&lt;/a&gt; into a redesign nobody scheduled.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full disclosure. We build IronPDF at Iron Software, and this read looks at where Stimulsoft's 21-tag interpreter and per-runtime SKUs cost a team, and where IronPDF renders &lt;a href="https://ironpdf.com/tutorials/html-to-pdf/" rel="noopener noreferrer"&gt;the whole stylesheet&lt;/a&gt; from one licence.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Tag Property Is Not a Rendering Engine
&lt;/h2&gt;

&lt;p&gt;Stimulsoft's &lt;code&gt;AllowHtmlTags&lt;/code&gt; documentation lists what a text component accepts, namely &lt;code&gt;b&lt;/code&gt;, &lt;code&gt;i&lt;/code&gt;, &lt;code&gt;u&lt;/code&gt;, &lt;code&gt;s&lt;/code&gt;, &lt;code&gt;sub&lt;/code&gt;, &lt;code&gt;sup&lt;/code&gt;, &lt;code&gt;strong&lt;/code&gt;, &lt;code&gt;em&lt;/code&gt;, &lt;code&gt;strike&lt;/code&gt;, &lt;code&gt;color&lt;/code&gt;, &lt;code&gt;background-color&lt;/code&gt;, &lt;code&gt;font&lt;/code&gt;, &lt;code&gt;font-face&lt;/code&gt;, &lt;code&gt;font-name&lt;/code&gt;, &lt;code&gt;font-family&lt;/code&gt;, &lt;code&gt;font-size&lt;/code&gt;, &lt;code&gt;font-color&lt;/code&gt;, &lt;code&gt;letter-spacing&lt;/code&gt;, &lt;code&gt;word-spacing&lt;/code&gt;, &lt;code&gt;line-height&lt;/code&gt;, and &lt;code&gt;text-align&lt;/code&gt;. Twenty-one tags, all inline text formatting, and the documentation is explicit that ordered and unordered list tags are among the exceptions that are not supported. Tags may appear only in the &lt;code&gt;Text&lt;/code&gt; property of a Text component, not anywhere else in the report.&lt;/p&gt;

&lt;p&gt;As bold, colour, and spacing inside a report field, that is a reasonable feature and what it was built for. As an HTML path, the gaps are structural rather than cosmetic, because &lt;a href="https://ironpdf.com/how-to/html-string-to-pdf/" rel="noopener noreferrer"&gt;what a browser engine accepts&lt;/a&gt; includes external stylesheets, CSS layout, and script execution, and none of the three exist here. A team with existing email templates rebuilds the layout inside the designer using report objects, then maintains two versions of the same document, one for the browser and one for the report. IronPDF takes the browser version and stops there.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Far Does One Report Definition Travel?
&lt;/h2&gt;

&lt;p&gt;The cross-platform claim is the strongest thing about the product, and it holds. The engine is split into separate products by runtime, with Reports.NET for WinForms and classic ASP.NET, Reports.WEB for ASP.NET Core, MVC, Blazor, and Angular across .NET 6 through .NET 10, plus Reports.JS, Reports.JAVA, Reports.PHP, and Reports.PYTHON, and a single &lt;code&gt;.mrt&lt;/code&gt; or &lt;code&gt;.mrz&lt;/code&gt; definition moves between them without a rebuild. For a team shipping the same report layout to a desktop client and a web front end, that is design work done once instead of twice. IronPDF covers the whole .NET side of that spread from one package, so the template never needs porting between SKUs.&lt;/p&gt;

&lt;p&gt;Its standalone Designer is cross-platform too, shipping as a Windows executable, a macOS &lt;code&gt;.dmg&lt;/code&gt;, Linux &lt;code&gt;.rpm&lt;/code&gt; and &lt;code&gt;.deb&lt;/code&gt; packages, and a browser-hosted version, where most competing designers are Windows-only with a separately sold, feature-reduced web viewer alongside. Version 2026.3 landed in July 2026 on a predictable quarterly cadence, adding real-time shared report editing, a Vue.js viewer, IntelliSense in the Windows standalone designer, and new chart types. The runtime grant is perpetual rather than a pure subscription, meaning a licence keeps working after the update-and-support window lapses, and no royalties are owed for the report engine or the embedded designer at runtime. That grant is the ceiling of what the SKU covers, because an IronPDF template is the HTML a team already maintains, with nothing to install on any of the three.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tag Interpreter Against Browser Engine
&lt;/h2&gt;

&lt;p&gt;Scoped to the slice where both tools produce a file.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability (document output only)&lt;/th&gt;
&lt;th&gt;Stimulsoft Reports&lt;/th&gt;
&lt;th&gt;IronPDF&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HTML tags accepted in a text field&lt;/td&gt;
&lt;td&gt;21 inline tags, list tags excluded&lt;/td&gt;
&lt;td&gt;0 excluded, &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt;, Grid, and Flexbox&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encryption and permissions&lt;/td&gt;
&lt;td&gt;40-, 128-, and 256-bit with user and owner passwords&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://ironpdf.com/how-to/pdf-permissions-passwords/" rel="noopener noreferrer"&gt;128- and 256-bit AES&lt;/a&gt;, with permission flags&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Certificate-based signatures&lt;/td&gt;
&lt;td&gt;Signature fields on export&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;PdfSignature&lt;/code&gt; on the returned &lt;code&gt;PdfDocument&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDF/A export&lt;/td&gt;
&lt;td&gt;Supported, with layers dropped in PDF/A mode&lt;/td&gt;
&lt;td&gt;Supported, layers preserved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Merging or stamping an existing PDF&lt;/td&gt;
&lt;td&gt;Outside the report engine&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;PdfDocument.Merge&lt;/code&gt; and &lt;a href="https://ironpdf.com/how-to/merge-or-split-pdfs/" rel="noopener noreferrer"&gt;split&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Producing a PDF without a designer file&lt;/td&gt;
&lt;td&gt;Built around authoring an &lt;code&gt;.mrt&lt;/code&gt; or &lt;code&gt;.mrz&lt;/code&gt; template first&lt;/td&gt;
&lt;td&gt;Code-first, markup or a URL in, &lt;code&gt;PdfDocument&lt;/code&gt; out&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Table 1. Document output only, Stimulsoft Reports against IronPDF, excluding the designer and data-binding surface.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The PDF/A row is the fairest one in the table, because Stimulsoft supports the format with one documented behaviour to plan around rather than a gap, while IronPDF keeps the layers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which SKU Decides Your Operating System?
&lt;/h2&gt;

&lt;p&gt;Three constraints follow from how the product is packaged, and all three are purchase-time decisions rather than configuration.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reports.NET and Reports.WEB are licensed separately:&lt;/strong&gt; needing both a desktop and a web front end means buying two products or stepping up to the Ultimate bundle. Tiers run Single for one developer, Team for up to four, Enterprise for up to fifteen, and WorldWide for unlimited, with Team and above including one build server licence. Prices move, so check the current figures. The structure is the part that matters, because the SKU count is set by your architecture rather than your headcount.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The SKU also decides the container story:&lt;/strong&gt; &lt;code&gt;Stimulsoft.Drawing&lt;/code&gt;, the cross-platform drawing engine built on ImageSharp and SixLabors Fonts to replace the Windows-only &lt;code&gt;System.Drawing&lt;/code&gt;, ships in the .NET Core product packages, meaning Reports.WEB and Dashboards.WEB, Reports.BLAZOR and Dashboards.BLAZOR, and Reports.ANGULAR. Reports.NET is not among them. Which package was licensed therefore decides whether &lt;a href="https://ironpdf.com/get-started/linux/" rel="noopener noreferrer"&gt;a Linux container&lt;/a&gt; is a supported target at all, and that is an awkward thing to discover after procurement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A licence key gates initialization:&lt;/strong&gt; report and dashboard components need a key registered through &lt;code&gt;StiLicense.Key&lt;/code&gt; or a &lt;code&gt;LoadFrom*&lt;/code&gt; call before they initialize, and Stimulsoft's own evaluation-limitations documentation confirms that without one a watermark prints on every report page and dashboard panel. That makes secret management a startup-path concern, re-verified on every deployment target the report runs on, in a way &lt;a href="https://ironpdf.com/how-to/license-keys/" rel="noopener noreferrer"&gt;a runtime key applied once in code&lt;/a&gt; is not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The CVE record needs the same care as the SKU list, because the vendor name spans several codebases. Four advisories attach to the 2023.1 line. CVE-2023-25261 at 9.8 allows arbitrary local file read and write through included source code, against Designer Desktop 2023.1.4 and Designer Web and Viewer Web 2023.1.3. CVE-2023-25260 at 7.5 is local file inclusion in Designer Web, CVE-2023-25262 at 7.5 is server-side request forgery in Designer Web, and CVE-2023-25263 at 5.5 is a static secret letting connection strings stored in &lt;code&gt;.mrt&lt;/code&gt; files be decrypted after decompiling the assembly. The first three are scoped to design-time and viewer tooling rather than the engine assembly a compiled application ships. Two older 9.8 entries, CVE-2020-15865 and CVE-2021-42777, both trace by NVD to a 2013-era build, Stimulsoft Reports 2013.1.1600.0, with a server-side C# compilation feature. A trio from 2024 belongs elsewhere again, because CVE-2024-24396 and CVE-2024-24397 name Stimulsoft Dashboard.JS while CVE-2024-24398 names Stimulsoft Dashboard.PHP, so calling all three an npm package problem gets one of them wrong. Setting the misattributed ones aside, what stays on the record is a designer-and-viewer cluster from 2023 with no fixed version listed in NVD or GitHub's database, on a product line that has shipped a dozen quarterly releases since.&lt;/p&gt;

&lt;h2&gt;
  
  
  What IronPDF Does With the Stylesheet Instead
&lt;/h2&gt;

&lt;p&gt;Which half of the workload is the designer being charged to? Where a finished stylesheet is the input, the report definition drops out entirely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IronPdf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;invoiceRenderer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChromePdfRenderer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;invoiceRenderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RenderingOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PaperSize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;IronPdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Rendering&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PdfPaperSize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;A4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Print media so the template's own page rules and breaks are honoured&lt;/span&gt;
&lt;span class="n"&gt;invoiceRenderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RenderingOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CssMediaType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;IronPdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Rendering&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PdfCssMediaType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;PdfDocument&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;invoiceRenderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RenderHtmlAsPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAllText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invoice.html"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invoice-2026-08.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;IronPDF returns a paginated PDF with the template's tables, lists, and layout intact and its text selectable, with no allowlist to design around and no separate SKU per runtime. &lt;a href="https://ironpdf.com/how-to/rendering-options/" rel="noopener noreferrer"&gt;Headers, footers, and paper sizes&lt;/a&gt; are set on the same renderer, and an MVC project can hand over &lt;a href="https://ironpdf.com/how-to/cshtml-to-pdf-razor/" rel="noopener noreferrer"&gt;a Razor view instead of a rendered string&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Most estates running Stimulsoft split the work rather than replace anything. Analyst-built reporting stays in the designer, where cross-tabs, subreports, drill-down parameters, and scheduled distribution live and a rendering library offers nothing. The invoices, receipts, and statements never destined for a designer move to a render call. Stimulsoft's ceiling sits at that seam, since the tag interpreter, the runtime key, and the SKU decision are charged to both halves even when only one of them opens the designer. There is a &lt;a href="https://ironpdf.com/tutorials/crystal-reports-alternative-csharp/" rel="noopener noreferrer"&gt;full walkthrough of the renderer half&lt;/a&gt;, and IronPDF has a &lt;a href="https://ironpdf.com/licensing/" rel="noopener noreferrer"&gt;free trial&lt;/a&gt; for putting one of your existing templates through it first.&lt;/p&gt;

&lt;p&gt;If you have had to pick between Reports.NET and Reports.WEB for a project that needed both a desktop and a web front end, how did the SKU maths work out? Tell us in the comments, because that decision seems to get made once and then quietly constrain the deployment target for years.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Stimulsoft is a trademark of its owner and this piece is not affiliated with the company. The tag support, packaging, licensing, release, and CVE details above are drawn from Stimulsoft's own documentation, NVD, and the GitHub Advisory Database as they stood at the time of writing. If a detail has changed since, correct us in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>pdf</category>
      <category>dotnetcore</category>
    </item>
  </channel>
</rss>
