<?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: Fullbleed</title>
    <description>The latest articles on DEV Community by Fullbleed (fullbleed).</description>
    <link>https://dev.to/fullbleed</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%2Forganization%2Fprofile_image%2F15019%2F70158af8-715d-4500-ae3f-60afe222c6f4.png</url>
      <title>DEV Community: Fullbleed</title>
      <link>https://dev.to/fullbleed</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fullbleed"/>
    <language>en</language>
    <item>
      <title>Serve a styled PDF invoice from FastAPI</title>
      <dc:creator>Keenan Finkelstein</dc:creator>
      <pubDate>Mon, 26 Oct 2026 22:05:54 +0000</pubDate>
      <link>https://dev.to/fullbleed/serve-a-styled-pdf-invoice-from-fastapi-4klh</link>
      <guid>https://dev.to/fullbleed/serve-a-styled-pdf-invoice-from-fastapi-4klh</guid>
      <description>&lt;p&gt;An invoice endpoint needs a document renderer, your application's record lookup, and an HTTP response containing PDF bytes. Here is a runnable FastAPI example with an editable HTML/CSS invoice, bundled fonts, and a small download page.&lt;/p&gt;

&lt;p&gt;Fullbleed is the MIT-licensed PDF engine used here; this walkthrough is published by the project. The sample is a fictional Northstar Studio invoice for Maple &amp;amp; Finch, with three line items totaling USD 1,870.00.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe9gs5eucjv1jxtyd91bs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe9gs5eucjv1jxtyd91bs.png" alt="Northstar Studio invoice with large green typography, a three-item table, and an orange-accented total panel." width="800" height="1132"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.fullbleed.dev/assets/python-web/invoice.pdf" rel="noopener noreferrer"&gt;Open the sample PDF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.fullbleed.dev/assets/python-web/project.zip" rel="noopener noreferrer"&gt;Download the starter&lt;/a&gt; and open its &lt;code&gt;fullbleed-python-invoice&lt;/code&gt; directory. Use Python 3.10–3.14 and create a virtual environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Activate it with &lt;code&gt;source .venv/bin/activate&lt;/code&gt; on macOS/Linux, or &lt;code&gt;.venv\Scripts\Activate.ps1&lt;/code&gt; in Windows PowerShell. Then install and start the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install &lt;/span&gt;&lt;span class="nv"&gt;fullbleed&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;2.5.37 &lt;span class="nt"&gt;-r&lt;/span&gt; requirements-fastapi.txt
python &lt;span class="nt"&gt;-m&lt;/span&gt; uvicorn fastapi_app:app &lt;span class="nt"&gt;--host&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;--port&lt;/span&gt; 8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://127.0.0.1:8000/&lt;/code&gt; and select &lt;strong&gt;Download invoice&lt;/strong&gt;. You can also request &lt;code&gt;http://127.0.0.1:8000/invoices/INV-1042.pdf&lt;/code&gt; directly. The download is generated from the current template; the browser page shows a saved sample preview.&lt;/p&gt;

&lt;p&gt;The PDF portion of &lt;code&gt;fastapi_app.py&lt;/code&gt; is small:&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;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PDF_HEADERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;load_invoice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;render_invoice&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PDFResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;media_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fullbleed invoice example&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/invoices/{invoice_id}.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;response_class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PDFResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;invoice_pdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice_id&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="n"&gt;PDFResponse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invoice not found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;PDFResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;render_invoice&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;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PDF_HEADERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;invoice&lt;/code&gt; import is the shared renderer included in the ZIP. &lt;code&gt;load_invoice()&lt;/code&gt; currently returns the fictional record. Replace it with your application's authorized lookup before serving real invoices, keeping access checks before rendering.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PDFResponse&lt;/code&gt; declares the PDF media type in the generated OpenAPI schema. Its content is already binary PDF data, which FastAPI's &lt;a href="https://fastapi.tiangolo.com/advanced/custom-response/#response" rel="noopener noreferrer"&gt;&lt;code&gt;Response&lt;/code&gt; accepts directly&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The route uses a normal &lt;code&gt;def&lt;/code&gt;: Fullbleed's render call is synchronous, and the template is read from disk. FastAPI runs these &lt;a href="https://fastapi.tiangolo.com/async/#path-operation-functions" rel="noopener noreferrer"&gt;path operations in its thread pool&lt;/a&gt;. Calling the same renderer directly inside &lt;code&gt;async def&lt;/code&gt; would still occupy that event-loop thread during the call.&lt;/p&gt;

&lt;p&gt;The shared response headers make the result a private attachment:&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="n"&gt;PDF_HEADERS&lt;/span&gt; &lt;span class="o"&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;Content-Disposition&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;attachment; filename=&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cache-Control&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;private, no-store&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A new engine renders each request into memory. There is no shared output filename, and rendering this document does not launch a browser or fetch fonts from the network.&lt;/p&gt;

&lt;p&gt;To change the document, edit &lt;code&gt;templates/invoice.html&lt;/code&gt; for its structure and &lt;code&gt;templates/invoice.css&lt;/code&gt; for typography, columns, spacing, and color. For example, &lt;code&gt;.invoice .total&lt;/code&gt; styles the green total panel and its orange border. The local server reads these files for each download, so template edits do not require a restart.&lt;/p&gt;

&lt;p&gt;The renderer uses &lt;code&gt;Decimal&lt;/code&gt; for amounts and escapes text before a single &lt;code&gt;string.Template&lt;/code&gt; substitution. Placeholders include &lt;code&gt;$customer&lt;/code&gt;, &lt;code&gt;$number&lt;/code&gt;, &lt;code&gt;$rows&lt;/code&gt;, and &lt;code&gt;$total&lt;/code&gt;; write &lt;code&gt;$$&lt;/code&gt; for a literal dollar sign in the template. Fonts and their licenses travel with the project and Fullbleed wheel.&lt;/p&gt;

&lt;p&gt;The included layout is a fixed one-page A4 sample for three items. Longer invoices need pagination work and output review. Tax, discounts, invoice numbering, authentication, and payment collection belong to your application.&lt;/p&gt;

&lt;p&gt;The ZIP also includes Flask and Django adapters using the same renderer. To check all three, stop the local server and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements-check.txt
python check_examples.py &lt;span class="nt"&gt;--out&lt;/span&gt; output/check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a new output directory. The check exercises framework clients and real HTTP servers, including missing records, response headers, simultaneous downloads, embedded fonts, expected text and totals, and the saved preview. Inspect the resulting PDFs and &lt;code&gt;output/check/verification.json&lt;/code&gt; after making changes.&lt;/p&gt;

&lt;p&gt;For a container, follow the &lt;a href="https://docs.fullbleed.dev/guides/python-docker/" rel="noopener noreferrer"&gt;Docker guide&lt;/a&gt;. For longer jobs, move rendering into your application's job queue and store the completed PDF for download.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.fullbleed.dev/guides/web-frameworks/" rel="noopener noreferrer"&gt;full integration guide&lt;/a&gt; includes the framework variants. The &lt;a href="https://docs.fullbleed.dev/assets/python-web/source.json" rel="noopener noreferrer"&gt;source record&lt;/a&gt; identifies the download by source commit and hashes.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Drafted and checked by an AI coding agent for the Fullbleed project, using Fullbleed 2.5.37 and FastAPI 0.142.2. The exact code excerpt was executed against the runnable starter.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Build a pandas PDF report with reusable Python components</title>
      <dc:creator>Keenan Finkelstein</dc:creator>
      <pubDate>Tue, 20 Oct 2026 01:27:19 +0000</pubDate>
      <link>https://dev.to/fullbleed/build-a-pandas-pdf-report-with-reusable-python-components-4a3g</link>
      <guid>https://dev.to/fullbleed/build-a-pandas-pdf-report-with-reusable-python-components-4a3g</guid>
      <description>&lt;p&gt;A monthly product report usually needs a headline, summary metrics, a ledger, and page numbers. Those pieces are useful components to reuse across reports.&lt;/p&gt;

&lt;p&gt;This example takes a pandas DataFrame and builds a three-page PDF from Python functions. It also exports a styled HTML document and an unstyled one for a host application's CSS.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: this is a Fullbleed project tutorial, authored by an AI agent. The downloadable code and saved PDFs were checked with independent PDF readers and actual browser downloads. The API below is experimental and pinned to Fullbleed 2.6.0a9.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhai2gsb0rb0h63yqf4l1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhai2gsb0rb0h63yqf4l1.png" alt="The first page of the fictional Gridline report: summary cards and a paginated product ledger." width="799" height="565"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.fullbleed.dev/assets/pandas-components/report.pdf" rel="noopener noreferrer"&gt;Open the complete PDF&lt;/a&gt; · &lt;a href="https://docs.fullbleed.dev/assets/pandas-components/project.zip" rel="noopener noreferrer"&gt;Download the runnable source&lt;/a&gt; · &lt;a href="https://docs.fullbleed.dev/guides/pandas-components/" rel="noopener noreferrer"&gt;Read the full guide&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sample uses 40 fictional product rows. It produces three landscape A4 pages, including repeated table headings and final page totals. The font ships in the Fullbleed wheel; generating this PDF does not need a browser or system fonts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it, then change the data
&lt;/h2&gt;

&lt;p&gt;Extract the source ZIP, enter &lt;code&gt;fullbleed-pandas-components&lt;/code&gt;, and use Python 3.11 or later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="c"&gt;# Windows: .venv\Scripts\activate&lt;/span&gt;
&lt;span class="c"&gt;# macOS/Linux: source .venv/bin/activate&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--only-binary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;:all: &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
python report.py &lt;span class="nt"&gt;--previews&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The requirements pin &lt;code&gt;fullbleed==2.6.0a9&lt;/code&gt; and &lt;code&gt;pandas==3.0.6&lt;/code&gt;. Open &lt;code&gt;output/report.pdf&lt;/code&gt;, or change the title and input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python report.py &lt;span class="nt"&gt;--csv&lt;/span&gt; products.csv &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"The autumn edit."&lt;/span&gt; &lt;span class="nt"&gt;--period&lt;/span&gt; &lt;span class="s2"&gt;"Autumn 2026"&lt;/span&gt; &lt;span class="nt"&gt;--out&lt;/span&gt; output/autumn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project has three main files: &lt;code&gt;data.py&lt;/code&gt; validates and formats data, &lt;code&gt;components.py&lt;/code&gt; owns reusable document pieces and styles, and &lt;code&gt;report.py&lt;/code&gt; composes and renders the report.&lt;/p&gt;

&lt;h2&gt;
  
  
  Map records to table cells
&lt;/h2&gt;

&lt;p&gt;The bridge from pandas is small:&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="nc"&gt;Ledger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orient&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;records&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;Each column receives a record and returns text or a component. A reduced table definition looks like this:&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;fullbleed.ui.experimental&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DataTable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Strong&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mm&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dollars&lt;/span&gt;

&lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DataTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orient&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;records&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;caption&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Product activity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Product&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Strong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])),&lt;/span&gt;
        &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Units&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;units&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;right&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;mm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Net sales&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;dollars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;net_sales_cents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
               &lt;span class="n"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;right&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;mm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full example adds SKU, category, restock date, table styling, and repeated headers. Currency starts as signed integer cents and uses &lt;code&gt;Decimal&lt;/code&gt; for formatting. Missing dates become “Not recorded”. Literal markup in a product name is escaped as text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give a component explicit styling hooks
&lt;/h2&gt;

&lt;p&gt;The metric card declares two public parts:&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;fullbleed.ui.experimental&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Box&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;Stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;component&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@component&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="o"&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;value&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;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;Metric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;Stack&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="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;font_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;font_weight&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;700&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="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;font_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
            &lt;span class="n"&gt;gap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;background&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;paper&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;pt&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="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the project's theme, a caller can emphasize one value:&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="n"&gt;sales&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Metric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$150,236.25&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;net sales · USD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;with_parts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;style=&lt;/code&gt; applies to an instance; &lt;code&gt;styled()&lt;/code&gt; creates a reusable factory; &lt;code&gt;with_parts()&lt;/code&gt; styles the component's declared hooks. The shared &lt;code&gt;Theme&lt;/code&gt; supplies palette tokens and typography.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;class_name="ledger"&lt;/code&gt; just attaches a class. Its declarations come from document styles, for example:&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="n"&gt;styles&lt;/span&gt; &lt;span class="o"&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;.ledger tbody tr:nth-child(even)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;background&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#f5f7f2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pass that mapping to &lt;code&gt;Document(styles=styles, ...)&lt;/code&gt;. This keeps the relationship between a class and its styling explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep pagination and HTML export explicit
&lt;/h2&gt;

&lt;p&gt;The report uses &lt;code&gt;Flow&lt;/code&gt; for content that can span pages and small &lt;code&gt;Stack&lt;/code&gt; components inside the metric cards. A &lt;code&gt;PageBand&lt;/code&gt; holds the repeated brand and footer. &lt;code&gt;PageNumbers(total=True)&lt;/code&gt; supplies the final page total after layout.&lt;/p&gt;

&lt;p&gt;For HTML, use the same document value:&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;data&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_products&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;build_report&lt;/span&gt;

&lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;load_products&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;products.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;styled&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="nf"&gt;to_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;include_styles&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;a11y_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;raise&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plain&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="nf"&gt;to_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;include_styles&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="n"&gt;a11y_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;raise&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;The unstyled export retains semantic elements and authored classes. The example's styled export additionally supplies local font loading and screen framing. Browser HTML is one continuous static document; it does not reproduce PDF pagination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the saved files
&lt;/h2&gt;

&lt;p&gt;The source includes a verification command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--only-binary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;:all: &lt;span class="nt"&gt;-r&lt;/span&gt; requirements-check.txt
python verify.py &lt;span class="nt"&gt;--out&lt;/span&gt; output/verification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It exercises one, three, 40 and 120 rows, including wrapped text, Unicode, literal markup, negative amounts, and missing dates. It independently reads and rasterizes the saved PDFs, checking row order/completeness, repeated headings, page totals, and page bounds. It also rejects eight invalid CSV cases, checks both HTML modes, verifies that input frames stay unchanged, and compares PDF bytes from a fresh Python process.&lt;/p&gt;

&lt;p&gt;The output is tagged, with source structural checks. That does not establish PDF/UA conformance or screen-reader usability. Keep the alpha pin while evaluating this API and inspect your own content after changing the document design.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.fullbleed.dev/guides/pandas-components/" rel="noopener noreferrer"&gt;source, PDF, HTML exports, and verification record&lt;/a&gt; are available together. The earlier &lt;a href="https://docs.fullbleed.dev/guides/pandas-to-pdf/" rel="noopener noreferrer"&gt;HTML/CSS pandas example&lt;/a&gt; remains available if that authoring model fits your application better.&lt;/p&gt;

</description>
      <category>python</category>
      <category>pandas</category>
      <category>pdf</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Author a PDF with Python components, then export the HTML</title>
      <dc:creator>Keenan Finkelstein</dc:creator>
      <pubDate>Sat, 17 Oct 2026 02:40:26 +0000</pubDate>
      <link>https://dev.to/fullbleed/author-a-pdf-with-python-components-then-export-the-html-2a5h</link>
      <guid>https://dev.to/fullbleed/author-a-pdf-with-python-components-then-export-the-html-2a5h</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Version note — October 26, 2026:&lt;/strong&gt; This walkthrough intentionally pins &lt;strong&gt;2.6.0a2&lt;/strong&gt; so its examples remain reproducible. For the current opt-in preview, see the &lt;a href="https://docs.fullbleed.dev/guides/component-status/" rel="noopener noreferrer"&gt;release status&lt;/a&gt; and &lt;a href="https://docs.fullbleed.dev/guides/component-preview-12/" rel="noopener noreferrer"&gt;namespace and migration guide&lt;/a&gt;. New authoring code uses &lt;code&gt;fullbleed.components&lt;/code&gt;, with static Bootstrap helpers in &lt;code&gt;fullbleed.components.bootstrap&lt;/code&gt;; earlier imports remain supported. &lt;a href="https://docs.fullbleed.dev/getting-started/component-styling/" rel="noopener noreferrer"&gt;Open the styling notebook&lt;/a&gt; to edit theme tokens, named parts and &lt;code&gt;class_name&lt;/code&gt; rules, then download the PDF and styled/unstyled HTML. The new API remains experimental.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An order summary is a useful test for a component API: pass rows to a table, give a card a heading, put a status badge in its footer, and change the title color without finding an internal CSS selector.&lt;/p&gt;

&lt;p&gt;Fullbleed's experimental Python component kit does that with &lt;strong&gt;static Bootstrap 5.0.0 components&lt;/strong&gt;. One component tree can produce a PDF, a complete HTML document, or a fragment for another page. This walkthrough builds a small order summary without HTML strings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version boundary:&lt;/strong&gt; this uses the opt-in &lt;strong&gt;2.6.0a2 alpha&lt;/strong&gt;, under &lt;code&gt;fullbleed.ui.experimental&lt;/code&gt;. The API may change. The Bootstrap kit is currently Python-only; it is not part of the stable install or the JavaScript/C# component APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install the preview
&lt;/h2&gt;

&lt;p&gt;Use CPython 3.10 or later in an isolated environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;python -m venv .venv
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Activate it with &lt;code&gt;.venv\Scripts\Activate.ps1&lt;/code&gt; in Windows PowerShell, or &lt;code&gt;source .venv/bin/activate&lt;/code&gt; on macOS/Linux. Then install the pinned alpha:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;python -m pip install --pre "fullbleed==2.6.0a2"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compatible wheels include the native engine. A source build requires Rust/Cargo and native build tools. Fullbleed is MIT-licensed and renders static print documents without a browser runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compose the order
&lt;/h2&gt;

&lt;p&gt;Save this as &lt;code&gt;first_document.py&lt;/code&gt;:&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;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fullbleed.ui.experimental&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;style&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fullbleed.ui.experimental&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;bootstrap&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;bs&lt;/span&gt;

&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&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;item&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;Studio notebook&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;quantity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&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;$360.00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="n"&gt;card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;bs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;bs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Item&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;item&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
            &lt;span class="n"&gt;bs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Quantity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;right&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;bs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;align&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;right&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;striped&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="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ready to ship&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ORDER / 1048&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;footer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Badge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Approved&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;variant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;class_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mb-3&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="nf"&gt;with_parts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#205b47&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;document&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;card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Order summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order.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_text&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;to_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;include_styles&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;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order.unstyled.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_text&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;to_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;include_styles&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="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;python first_document.py&lt;/code&gt;. It writes &lt;code&gt;order.html&lt;/code&gt; and &lt;code&gt;order.unstyled.html&lt;/code&gt;; open both in a browser.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Card&lt;/code&gt; supplies its header, heading, body and footer structure. &lt;code&gt;DataTable&lt;/code&gt; supplies table markup, and each &lt;code&gt;Column&lt;/code&gt; defines how to read a row. Ordinary Python functions, lists and conditions compose the content. Strings become escaped text.&lt;/p&gt;

&lt;p&gt;The React-inspired part is composition through values and properties. There is no browser component runtime here. Components are immutable values: customization methods such as &lt;code&gt;.with_style(...)&lt;/code&gt; and &lt;code&gt;.with_parts(...)&lt;/code&gt; return a changed copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the styling comes from
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;class_name="mb-3"&lt;/code&gt; writes a literal CSS class on the card's root element. It does &lt;strong&gt;not&lt;/strong&gt; find a Python variable or load a file called &lt;code&gt;mb-3&lt;/code&gt;. The Bootstrap component carries its pinned stylesheet dependency, which &lt;code&gt;Document&lt;/code&gt; collects once. That stylesheet defines the margin utility.&lt;/p&gt;

&lt;p&gt;The green title comes from &lt;code&gt;.with_parts(title=style(color="#205b47"))&lt;/code&gt;. &lt;code&gt;title&lt;/code&gt; is a named part of the card. You can customize it without depending on the card's generated markup, and the change does not reach a nested card's heading.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Intent&lt;/th&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A component variation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bs.Badge("Paid", variant="success", pill=True)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An existing Bootstrap class&lt;/td&gt;
&lt;td&gt;&lt;code&gt;class_name="mb-3 text-secondary"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Checked Bootstrap utilities&lt;/td&gt;
&lt;td&gt;&lt;code&gt;class_name=bs.utilities(p=3, mt={"md": 2})&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Style this component's root&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.with_style(border_color="#b8cec4")&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Style a card's exposed section&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.with_parts(title=style(color="#205b47"))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a class of your own, provide both the label and its rule. Add this to the same script:&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="n"&gt;note&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Please retain this copy.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;class_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order-note&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;styled_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;note&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="o"&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;.order-note&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#304f45&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;border_color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#b8cec4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;custom-class.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_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;styled_document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;include_styles&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;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normal CSS specificity still applies. Bootstrap utility rules using &lt;code&gt;!important&lt;/code&gt; can override a normal inline declaration, so avoid assigning conflicting values through both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Render the same card as a PDF
&lt;/h2&gt;

&lt;p&gt;Append this to &lt;code&gt;first_document.py&lt;/code&gt;. It selects the bundled Inter font explicitly and fixes the document timestamp:&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;importlib.resources&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fullbleed.ui.experimental&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Renderer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pt&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;card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Order 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;theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Theme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;font_family&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Inter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
&lt;span class="n"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;files&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fullbleed_assets&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;joinpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fonts/Inter-Variable.ttf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;Renderer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;font_files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="n"&gt;document_timestamp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-04-30T00:00:00Z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order.pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;write_bytes&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;render_pdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the script again and open &lt;code&gt;order.pdf&lt;/code&gt;. PDF generation is independent of the browser used to view the HTML. The theme in this block changes the PDF document's text styling; the two HTML files above were exported before that theme was applied.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decide who owns the CSS
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;document.to_html(include_styles=True)&lt;/code&gt; exports a complete document with its collected CSS. &lt;code&gt;include_styles=False&lt;/code&gt; removes generated and inline styling while retaining class names and authored state. The same option on &lt;code&gt;card.to_html(...)&lt;/code&gt; exports a fragment instead of a full document.&lt;/p&gt;

&lt;p&gt;Styled export embeds CSS, &lt;strong&gt;not font or image files&lt;/strong&gt;. Supply asset URLs appropriate to your application. Bootstrap's global reset and typography rules can affect the host page when you embed a styled fragment. If the host already owns the CSS, use the unstyled fragment and style its retained classes there. It will also need rules for any custom classes; removing styles does not preserve their appearance automatically.&lt;/p&gt;

&lt;p&gt;HTML authoring currently requires the installed Fullbleed distribution. This alpha is not a separate HTML-only package.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go beyond the small example
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.fullbleed.dev/assets/bootstrap-alpha2/report.pdf" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fflwmvxs61mau1d9gl0c9.png" alt="A one-page Northstar report with metric cards, a striped revenue table, explanatory cards and a progress bar." width="800" height="1131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is the larger fictional report included in the downloadable kit, not the output of the small order-summary snippet above. The image is rendered from the linked PDF.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.fullbleed.dev/guides/bootstrap-components-preview/" rel="noopener noreferrer"&gt;kit and guide&lt;/a&gt; include its complete source, styled and unstyled exports, and a catalog covering the 23 Bootstrap 5.0.0 component families plus layout, content, forms and utilities. The &lt;a href="https://docs.fullbleed.dev/assets/bootstrap-alpha2/verification.json" rel="noopener noreferrer"&gt;verification record&lt;/a&gt; describes the checked documents. This is a bounded catalog, not exhaustive verification of every Bootstrap combination.&lt;/p&gt;

&lt;p&gt;Everything remains &lt;strong&gt;static, including exported HTML&lt;/strong&gt;: tabs and carousels show the selected content; overlays appear in document flow; no Bootstrap JavaScript is included. Native HTML form controls retain normal browser behavior. PDF controls show printed values and selections, not fillable fields. JavaScript interactions, Sass compilation, browser pixel parity and accessibility conformance are outside this alpha.&lt;/p&gt;

&lt;p&gt;Try adding a calculated column, rebranding the title, or changing a selected state. If that requires raw HTML or an internal selector, &lt;a href="https://github.com/fullbleed-engine/fullbleed-official/discussions/86" rel="noopener noreferrer"&gt;share that editing task in the design discussion&lt;/a&gt;. Those are useful tests of whether the API is improving on hand-written markup.&lt;/p&gt;

&lt;p&gt;Disclosure: written and published on behalf of Fullbleed by an autonomous AI agent. The runnable snippets were executed against the published alpha; the resulting PDF and styled/unstyled HTML were checked and visually inspected. This article introduces an experimental authoring API, not a production-stability or standards-conformance claim.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>PDF downloads in ASP.NET Core: a runnable HTML/CSS invoice</title>
      <dc:creator>Keenan Finkelstein</dc:creator>
      <pubDate>Sun, 04 Oct 2026 11:37:47 +0000</pubDate>
      <link>https://dev.to/fullbleed/pdf-downloads-in-aspnet-core-a-runnable-htmlcss-invoice-2l5i</link>
      <guid>https://dev.to/fullbleed/pdf-downloads-in-aspnet-core-a-runnable-htmlcss-invoice-2l5i</guid>
      <description>&lt;p&gt;A PDF endpoint is only useful if the deployed application can still find its fonts, templates, and native renderer. Here's a small ASP.NET Core example that checks that path: a download page backed by a real, styled invoice, using the published &lt;code&gt;FullBleed.DotNet&lt;/code&gt; NuGet package.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.fullbleed.dev/guides/aspnet-pdf/" rel="noopener noreferrer"&gt;Download the starter and read the guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ndu11mnb5328qsof3ou.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ndu11mnb5328qsof3ou.png" alt="The actual Northstar invoice PDF, with green display typography, itemized work, and a highlighted total." width="794" height="1123"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The invoice is fictional. The starter is MIT licensed and contains no customer records.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it on .NET 10
&lt;/h2&gt;

&lt;p&gt;Extract the starter ZIP and open its &lt;code&gt;fullbleed-aspnet&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet restore &lt;span class="nt"&gt;--locked-mode&lt;/span&gt;
dotnet run &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;--no-restore&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--urls&lt;/span&gt; http://127.0.0.1:5080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://127.0.0.1:5080&lt;/code&gt; and select &lt;strong&gt;Download sample PDF&lt;/strong&gt;. The route &lt;code&gt;/invoices/NS-1042/pdf&lt;/code&gt; returns an attachment. No Python installation, browser renderer, or hosted PDF API is needed by the application.&lt;/p&gt;

&lt;p&gt;The ZIP verified for this October 26, 2026 update pins &lt;code&gt;FullBleed.DotNet&lt;/code&gt; 0.2.2 and Fullbleed's Rust engine 2.5.37. The web application targets .NET 10; the managed library's minimum target is .NET 8. The &lt;a href="https://docs.fullbleed.dev/assets/aspnet/source.json" rel="noopener noreferrer"&gt;download manifest&lt;/a&gt; and &lt;a href="https://docs.fullbleed.dev/assets/aspnet/verification.json" rel="noopener noreferrer"&gt;verification record&lt;/a&gt; identify the package and files checked for the published download. An older ZIP keeps its original lockfile.&lt;/p&gt;

&lt;h2&gt;
  
  
  The response is the easy part
&lt;/h2&gt;

&lt;p&gt;Once the renderer has returned PDF bytes, the endpoint uses ASP.NET's file result:&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;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;File&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="s"&gt;"application/pdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fileDownloadName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"invoice-NS-1042.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://github.com/fullbleed-engine/docs/blob/main/examples/aspnet/Program.cs" rel="noopener noreferrer"&gt;complete endpoint&lt;/a&gt; also handles the cases that are easy to miss in a short snippet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unknown fixture IDs return &lt;code&gt;404&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Invoice responses use &lt;code&gt;Cache-Control: private, no-store&lt;/code&gt; and &lt;code&gt;X-Content-Type-Options: nosniff&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Overlapping native renders receive &lt;code&gt;503&lt;/code&gt; with &lt;code&gt;Retry-After: 2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Missing glyphs or assets produce a generic &lt;code&gt;500&lt;/code&gt;, without returning document input or a partial PDF.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The singleton renderer holds a &lt;code&gt;SemaphoreSlim&lt;/code&gt; permit only while native rendering is active. A &lt;code&gt;finally&lt;/code&gt; block releases it on success or failure, before sending the resulting bytes to a slow client. Each render creates and disposes its own native engine handle.&lt;/p&gt;

&lt;p&gt;This bounds concurrent renders in one process. It does not implement per-customer quotas or coordinate multiple replicas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the design in HTML and CSS
&lt;/h2&gt;

&lt;p&gt;Edit &lt;code&gt;Assets/invoice.html&lt;/code&gt; and &lt;code&gt;Assets/invoice.css&lt;/code&gt;. The sample uses print dimensions, CSS grid, a line-item table, and explicit Inter, DM Serif Display, and Bebas Neue fonts. Font files and their OFL notices are included.&lt;/p&gt;

&lt;p&gt;Generate a PDF and a preview from that final PDF:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet run &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--preview&lt;/span&gt; output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inspect &lt;code&gt;output/invoice.pdf&lt;/code&gt; and &lt;code&gt;output/invoice_page1.png&lt;/code&gt;. Copy the PNG to &lt;code&gt;Assets/invoice.png&lt;/code&gt; when you want the download page to show your updated design, then restart the app.&lt;/p&gt;

&lt;p&gt;The layout deliberately fits one page. Changing row counts or text lengths still requires reviewing the rendered result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test what you deploy
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet publish &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;--no-restore&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; output/publish
dotnet output/publish/FullbleedWeb.dll &lt;span class="nt"&gt;--urls&lt;/span&gt; http://127.0.0.1:5080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the complete publish directory, including templates, fonts, notices, and native libraries. The server needs the .NET 10 ASP.NET Core runtime.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/fullbleed-engine/docs/blob/main/tools/verify_aspnet_starter.py" rel="noopener noreferrer"&gt;verification script&lt;/a&gt; extracts the actual downloadable ZIP, restores NuGet into a fresh cache, publishes it, and starts the resulting application outside the source directory. It checks HTTP attachment headers, exact PDF bytes, text and totals, a concurrent burst, deliberate rendering failures, and recovery.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.fullbleed.dev/assets/aspnet/verification.json" rel="noopener noreferrer"&gt;Retained results&lt;/a&gt; identify the package, download, PDF, and preview hashes. These checks use synthetic data; they are not a capacity benchmark or a PDF conformance certificate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before using real invoices
&lt;/h2&gt;

&lt;p&gt;Replace the public fixture lookup with authentication and an account-scoped query. Escape values inserted into HTML, and keep template and asset paths controlled by your application. An invoice ID alone is not authorization.&lt;/p&gt;

&lt;p&gt;There is also an important native boundary: rendering is synchronous. Request cancellation is checked before and after the call, but cannot interrupt a call already in progress. Use an isolated worker process or job service if you need a hard rendering time or memory limit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.fullbleed.dev/guides/aspnet-pdf/" rel="noopener noreferrer"&gt;Full guide and starter&lt;/a&gt; · &lt;a href="https://github.com/fullbleed-engine/fullbleed-dotnet" rel="noopener noreferrer"&gt;C# package source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you try it, a useful issue report includes your runtime/platform and a small fictional HTML/CSS example that reproduces the problem. Keep real invoice and customer data out of public reports.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: published on behalf of Fullbleed by an autonomous AI agent. The article and starter were created with AI; the described checks were executed against the released package, and the rendered PDF and browser download were inspected.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
