[2.9.0] - 2026-10-29
Added
- support for
transform,style,opacity, andpreserveAspectRatio="none"on SVG<image>tags - cf. issue #537 - thanks to @agustin18 - support for colored and uncolored tiling patterns with
FPDF.create_pattern()andFPDF.use_pattern(), including SVG<pattern>elements and pattern fills and strokes on referenced<use>shapes - cf. issue #790 - thanks to @andersonhc - support for unordered lists in
multi_cell(markdown=True), using*,-or+as bullet markers - cf. issue #654 - thanks to @chaosreload - documentation on generating rMQR Codes - thanks to @Lucas-C
- Python 3.15 is now officially supported - thanks to @andersonhc
- Unicode font detection and enhanced error handling: when text cannot be encoded with the current font, the
FPDFUnicodeEncodingExceptionmessage now identifies the Unicode script (Cyrillic, Arabic, Chinese, etc.) and suggests a suitable font available on the system, via the new internalUnicodeFontManager- cf. PR #1563 - thanks to @otreci4sgelt0nas
Fixed
Nonevalues in PDF dictionaries are serialized asnullinstead of an invalid Python literal - cf. PR #1980 - thanks to @agustin18- Homogeneous arrays of PDF string and name objects use their PDF serialization, including encryption when applicable - cf. PR #2004 - thanks to @xiehuanyi and @andersonhc
- PDF dates pad years below 1000 to four digits - cf. PR #2005 - thanks to @xiehuanyi
- Corrected the
text_columnsAPI documentation link in the English tutorial and 10 translations - cf. PR #2039 - thanks to @ryanlcruz FPDF.set_display_mode()rejects infinite numeric zoom values instead of writing invalid PDF numbers.- Page-count aliases honor center and right alignment with core fonts when text shaping is enabled.
- 16-bit grayscale images, like 16-bit grayscale PNG files, are scaled down to 8 bits instead of being converted to RGBA, which made every value above 255 white, and keep the transparency of their tRNS color key - thanks to @wleeaf
- Docstrings of
PaintedPath.line_relative(),horizontal_line_relative(),vertical_line_relative()andActionname their arguments as the signatures do (dx,dy,next_action) - thanks to @mmporong - Tables inside
FPDF.unbreakable()are replayed with their rows and cells intact when the block is moved to a new page - cf. issue #1230 - thanks to @alphaZytx FPDF.table()raises a clearValueErrornaming the column whentext_alignis a tuple with fewer values than the table has columns, like it does forcol_widths, instead of anIndexError- thanks to @RavSinghChandanFPDF.table(): a cell with acolspanbelow 1 spans one column, like arowspanbelow 1 already spans one row, instead of raisingZeroDivisionErrororFPDFExceptionor breaking the column layout - thanks to @RavSinghChandan- Synchronize
font_styleandemphasissetters with font selection and state tracking - cf. issue #1223 - thanks to @agustin18 FPDF.table()raisesValueError: ... is not a valid TableBordersLayoutfor an unknownborders_layoutname, like it does forcell_fill_modeandv_align, instead of a bareAttributeError- thanks to @RavSinghChandanFPDF.set_font()andFPDF.add_font()treat a repeated style letter as the same style:style="BB"is bold, instead of raisingFPDFException: Undefined font: helveticaBB- thanks to @RavSinghChandan- Document outlines count all visible descendants and link siblings by parent, retaining bookmarks when the hierarchy starts below the top level. - thanks to @xiehuanyi
- Annotation colors and border widths are serialized as PDF numbers without exponent notation. - thanks to @xiehuanyi
- Checksums for compressed embedded files are calculated from the original file bytes, as required by the PDF specification. - thanks to @xiehuanyi
- Repeated annotation flags no longer change unrelated flag bits; annotation flags are combined with bitwise OR. - thanks to @xiehuanyi
FPDF.ink_annotation()raises a clearValueErrorfor an emptycoordssequence, instead ofValueError: min() iterable argument is empty- cf. issue #2072 - thanks to @agustin18- SVG images with a zero width or height disable rendering instead of using viewport dimensions. - thanks to @xiehuanyi
FPDF.write_html()uses the first available font of a<font face>list, like browsers, as inface="Arial, Helvetica, sans-serif", with the genericserif,sans-serifandmonospacefamilies rendered by the core fonts, instead of raisingFPDFException: Undefined font- thanks to @RavSinghChandan- SVG gradient
stop-opacityaccepts percentages, clamps out-of-range values to 0-1, and combines with the color's alpha channel instead of replacing it. - thanks to @xiehuanyi - SVG basic-shape coordinates and corner radii now accept absolute length units. - thanks to @xiehuanyi
- SVG transforms accept numbers written in scientific notation, such as
scale(1e-3), instead of silently ignoring the transform. - thanks to @xiehuanyi - SVG gradient stops honor inline
styledeclarations ofstop-opacityover the corresponding presentation attribute. - thanks to @xiehuanyi - SVG length and angle attributes accept scientific notation, such as
width="1e2pt", instead of raisingValueError. - thanks to @xiehuanyi - Floating-point numbers in PDF arrays, dictionaries and destinations are written without exponent notation, preserving their values and allowing very small numeric zoom factors to be read correctly. - thanks to @xiehuanyi
- SVG byte strings and
BytesIOinputs accept a UTF-8 BOM and XML whitespace after<svgor<?xml, instead of being sent to the raster image loader. - thanks to @xiehuanyi - SVG import: a
<polygon>or<polyline>whosepointshas an error (an invalid coordinate or an odd number of them) is drawn up to the last complete point before it, and one with emptypointsdraws nothing, as the SVG specification says, instead of raisingIndexError- thanks to @RavSinghChandan FPDF.write_html()ignores an unsupportedalignvalue on<td>,<th>and<tr>, such asmiddle, like it already does for<p>, instead of raisingValueError- thanks to @RavSinghChandanFPDF.regular_polygon()raisesValueError: 'numSides' parameter must be equal or greater than 1fornumSidesbelow 1, instead of silently writing a path with no points (a bareh S, which closes a path that was never started) - thanks to @RavSinghChandan- Bulleted text-column paragraphs now honor their top margins, indented paragraphs retain the column's right boundary, and HTML list items honor their
TextStyle.t_margin- based on PR #1218 - thanks to @andersonhc and @gmischler FPDF.text_columns()raises a clearValueErrorforncolsbelow 1, likeinsert_toc_placeholder()does forpages, instead of aZeroDivisionErroror an unrelatedNot enough horizontal spaceerror when rendering - thanks to @RavSinghChandanFPDF()andFPDF.add_page()raiseFPDFPageFormatException: Width and height must be positive numbersfor a pageformatwhose width or height is zero or negative, such as(0, 0), instead of writing a page with an empty or inverted/MediaBox- thanks to @RavSinghChandanFPDF.write_html()now supports CSS length units (px,pt) and surrounding whitespace in dimension attributes for<table>,<td>,<th>,<hr>, and<img>elements without raisingValueError- thanks to @agustin18FPDF.write_html()reads a CSSfont-sizeon<font>with its unit, like browsers, as instyle="font-size: 14pt"or"12px", instead of silently ignoring it; a number without a unit is still in points - thanks to @RavSinghChandanFPDF.write_html()supports<ul type="square">, the third standard HTML bullet type, instead of raisingNotImplementedError: Unicode fonts use ▪ and core fonts fall back to the bullet - thanks to @RavSinghChandanFPDF.write_html()accepts the<ul type>keywords in any case, like browsers:<ul type="CIRCLE">and<ul type="Square">no longer raiseNotImplementedError- thanks to @RavSinghChandanFPDF.write_html()supports<ul type="none">, rendering the list items without a marker like browsers do, instead of raisingNotImplementedError- thanks to @RavSinghChandanFPDF.write_html()keeps the default bullet for an empty or unknown<ul type>, such astype=""or a misspelledtype="disk", like browsers and like<ol>already does, instead of raisingNotImplementedError- thanks to @RavSinghChandan- SVG import: color keywords are case-insensitive (
fill="RED"), andfill/strokeaccepttransparent, painting nothing likenone, instead of raisingValueError- thanks to @RavSinghChandan FPDF.write_html()accepts cssrgb(R, G, B)colors, and colors with surrounding spaces, incolorandbgcolorattributes instead of raisingValueError- thanks to @RavSinghChandanFPDF.write_html()accepts a legacy HTML color of six hex digits without the leading#, like browsers do, as inbgcolor="eeeeee"or<font color="ff0000">, instead of raisingValueError- thanks to @RavSinghChandanFPDF.write_html()ignorescolor="transparent"andbgcolor="transparent"like browsers, keeping the inherited text or background color, instead of raisingValueError- thanks to @RavSinghChandanFPDF.write_html()ignores an invalid CSScoloron a heading, like browsers, as in<h1 style="color: nope">, keeping thecolorattribute or the heading's default color, instead of raisingValueError- thanks to @RavSinghChandanFPDF.write_html()no longer raisesIndexErroron an<ol type="a">or<ol type="A">list with more than 26 items: like browsers, letters continue withaa,ab... and astartbelow 1 shows as a number - thanks to @RavSinghChandanFPDF.write_html()numbers an<ol>with an unknowntype, such astype="disc", as 1, 2, 3, like browsers, instead of raisingNotImplementedError- thanks to @RavSinghChandanFPDF.write_html()supports<li value="N">in ordered lists: like browsers, the item is numbered N and the following ones continue from it - thanks to @RavSinghChandanFPDF.write_html()no longer raisesKeyError: 'href'on an<a>element without anhrefattribute, such as a named anchor (<a name="intro">); its text is rendered as plain text - thanks to @RavSinghChandanFPDF.set_display_mode()raisesFPDFException: Incorrect zoom display modefor a negative or non-numericzoom, such as-50orNone, like it does for an unknown zoom name, instead of writing an invalid/OpenActionzoom or failing later inoutput()- thanks to @RavSinghChandanFPDF.write_html()no longer warns about a missing end tag for the HTML void elements<meta>,<link>,<col>,<wbr>,<input>,<base>,<area>,<embed>,<source>and<track>, which never have one, as in the<head>of an HTML email - thanks to @RavSinghChandanFPDF.write_html()treats an invalidcolspanorrowspanon<td>/<th>, or one below 1, as 1 like browsers do, instead of raisingValueErrororZeroDivisionError- thanks to @RavSinghChandanFPDF.write_html()stops arowspanthat goes past the last row of a<table>at the last row, like browsers, instead of raisingFPDFException: Rowspan extends beyond end of table- thanks to @RavSinghChandanFPDF.write_html()renders a<table>whosewidthis wider than the page, such aswidth="150%", at the page width with a warning, and ignores a negativewidthlike browsers, instead of raisingValueError- thanks to @RavSinghChandanFPDF.write_html()no longer raisesValueError: Invalid .col_widths specifiedon a table whose first row gives awidthto only some of its cells, such as<td width="50%">a</td><td>b</td>: like browsers, the other cells share the remaining space, after the gaps ofcellspacing; a cell with acolspangives each column it spans an equal part of its width - thanks to @RavSinghChandan- warning detection during
FPDFRecorder.replay()now correctly identifies context managers (contextlib.AbstractContextManager,@contextmanager, custom context managers) withinunbreakable()sections - thanks to @agustin18 - SVG import:
opacity,fill-opacityandstroke-opacityaccept percentages (50%), and an out-of-rangeopacityis clamped to 0-1 like the other two instead of raisingValueError- thanks to @RavSinghChandan - css
rgb()colors accept percentages, as inrgb(100%, 0%, 50%)(clamped to 0%-100%), in SVG import andFPDF.write_html()instead of raisingValueError- thanks to @RavSinghChandan - hex color strings, such as in
set_fill_color("#..."), must contain only hexadecimal digits:"#1 2345"and"#+12345"were silently read as#012345, and"#xyz"raised an unclearinvalid literal for int()error; all now raiseValueError: ... could not be interpreted as a RGB(A) hex string- thanks to @RavSinghChandan FPDF.ink_annotation()computes the horizontal position of the annotation rectangle (/Rect) from the x coordinates of the path instead of the y coordinates, so the rectangle encloses the drawn path; it could previously lie outside the page - thanks to @RavSinghChandanFPDF.set_auto_page_break()andFPDF.set_margin()raiseValueError: Bottom margin must be zero or a positive numberfor a negative margin, instead of placing the page break below the bottom edge of the page, so that the last lines before each break were written off the page and lost - thanks to @RavSinghChandanFPDF.embed_file()accepts empty byte content and rejects simultaneous path and byte sources even when the bytes are empty. - thanks to @xiehuanyiFPDF.set_line_width()raisesValueError: Line width must be zero or a positive numberfor a negative width, likeset_dash_pattern()does for a negative dash length, instead of writing a negative line width that the PDF specification does not allow - thanks to @RavSinghChandanFPDF.star()raisesValueError: 'corners' parameter must be equal or greater than 1forcornersbelow 1, instead of a bareZeroDivisionErrorforcorners=0and silently drawing nothing for a negative value - thanks to @RavSinghChandanset_text_color(),set_fill_color()andset_draw_color()report a component outside 0-255 as given:set_text_color(300)now raisesValueError: color component 300 not in range [0, 255]instead of1.1764705882352942 not in range [0.0, 1.0]- thanks to @RavSinghChandan- An internal link with an index that
FPDF.add_link()did not return now raises a clearValueError, like a link without a page number, instead of a bareAssertionError(or aKeyErrorwithpython -O) - thanks to @RavSinghChandan FPDF.write_html()no longer writes the content of<style>and<script>elements into the document as text, like browsers, so an HTML page or email with a<style>block no longer shows its CSS - thanks to @RavSinghChandanFPDF.add_page()andFPDF.set_page_label()raiseValueError: 'label_start' parameter must be equal or greater than 1for alabel_startbelow 1, instead of writing an invalid/Stvalue that the PDF specification requires to be 1 or greater - thanks to @RavSinghChandanFPDF.set_stretching()raisesValueError: Stretching must be a positive numberfor a stretching of zero or below, instead of laying out text with no width: justified text was all put on a single line, and other text raised an unrelatedNot enough horizontal space to render a single charactererror - thanks to @RavSinghChandanFPDF.write_html()raisesNotImplementedError: Unsupported nested HTML tags inside <td> element: <table>for a<table>nested in a table cell, like for the other unsupported nested tags, instead of a bareAssertionErrorwith no message - thanks to @RavSinghChandan
Changed
- Updated the Portuguese tutorial, including tutorials 4 and 5 and the new PDF/A tutorial 7 - cf. PR #2038 - thanks to @ryanlcruz
- Modernized type annotations for Python 3.11+, using union syntax, standard collection types, and
typing.Self- thanks to @andersonhc - Removed redundant trailing zeros from numbers in PDF dictionaries and arrays outside content streams, preserving existing numeric precision - thanks to @andersonhc
Removed
- Removed the
defusedxmldependency: SVG parsing now relies on the standard-library ElementTree parser and Expat amplification limits; bounded internal entities are accepted - thanks to @agustin18, cf. issue #1793 - support for Python 3.10, that reached end-of-life - thanks to @andersonhc