Skip to content

Releases

Papercut 0.9.1

Papercut 0.9.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 03 Nov 22:02

Added

  • DocxOptions(tocPageNumbers: false) and the Laravel config key papercut.docx.toc_page_numbers skip the page layout that fills in a Word table of contents' page numbers. The field lists its entries without numbers until the reader updates it in Word, and the export runs nearly as fast as one without a table of contents.

Changed

  • Faster Word export with a table of contents. The page layout behind its page numbers only measures: it doesn't draw underlines, highlights or links, or re-measure kerning. On an 80-section benchmark the export takes about 435 ms instead of 560 ms and peaks at 36 MB instead of 67 MB. The page numbers are the same.
  • Faster text layout: ASCII words skip a character-by-character scan, a font remembers which text it covers, and for fonts without ligatures, such as the bundled Inter and Cousine, kerning between the words of a run is measured where they meet instead of across the whole run. PDF export of the same benchmark takes about 465 ms instead of 555 ms. The PDF output is the same.

Papercut 0.9.0

Papercut 0.9.0 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 03 Nov 21:11

Added

  • Section and card titles can be headings: heading="1" to "4" on <x-doc::section> and <x-doc::card>, ->heading($level) on Section and Card. The title gets a PDF bookmark, a table of contents entry, an h tag in HTML and a Heading style in Word, and keeps its look. A numbered section's entry and bookmark read "2 Units".
  • Text::asHeading($level) makes any text a heading at levels 1 to 4 without the h1 to h4 look. Text::headingLevel() reads the level back, from either source.
  • Markup can keep its headings out of tables of contents: <x-doc::markup toc="false">, new MarkupOptions(toc: false), and a papercut.markup.toc config default. Every heading the tag converts is left out, wherever it sits; it still looks like a heading and keeps its PDF bookmark. In Word, an updated table of contents field brings them back, as with toc(false) on a heading.

Changed

  • Faster exports that use less memory. A 100-page table renders in about 0.86 s instead of 1.72 s and peaks at 114 MB instead of 342 MB, and the 10-page benchmark document takes 55 ms instead of 95 ms. Layout reuses the styles it already resolved, each distinct style is kept once, each page's layout caches are dropped when the page is done, an export frees what it used as soon as it returns, and common values are shared rather than copied. The PDF output is the same, except for the list spacing below.
  • An export runs with PHP's cycle collector paused and collects once when it ends. On long documents the collector ran repeatedly during layout and found nothing to free. Code that runs during the export runs with it paused too. The caller's setting is restored afterwards, also when the export throws.

Fixed

  • A list inside a table cell that sits in a list item kept no spacing below it in PDF output, as if it were nested in the outer list. A table cell starts outside any list, as HTML and Word export already treated it, so the list now gets the theme's list spacing.

Papercut 0.8.0

Papercut 0.8.0 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 02 Nov 19:07

Added

  • ColumnFlow / <x-doc::column-flow>: text set in columns, like Word's. columns(), columnWidths(), columnGap() and columnRule() (Blade columns, widths, column-gap, rule) set the count (1 to 45), unequal widths, the column gap (36pt by default) and a rule. Content fills each column, then the next page, and the last page ends level. It can sit on the page or inside any box, but not in a table cell, a row, another flow or a header, footer, background or overlay. ColumnBreak / <x-doc::column-break /> sends what follows to the next column.
  • Markup: in original style mode a div with column-count or columns (a count) becomes a column flow, with column-gap and column-rule setting its gap and rule; those two are dropped with a note when there's no count, and other column CSS is dropped with a note.
  • HTML export uses CSS columns. Word export writes real section columns for a flow on the page (a flow inside a box comes out as one column, with a note).
  • The Tailwind translator on the docs site maps columns-N, gap-x-* and divide-x to a column flow.
  • A gallery tile for the column flow, with rule and widths variants.
  • A Versions and compatibility page in the guide: what the version promise covers, and what it doesn't.

Changed

  • The Papercut\Backend interfaces, the Papercut\Paint display list, the layout engine (Papercut\Layout), the tree walk (Papercut\Tree), the HTML writer, StyleResolver, PageThemes, FontRegistry and the text layout classes, the exporter classes and Exporter (export with toPdf(), toHtml() or export(new DocxExporter())), ComputedStyle, the Word export's internals and the Laravel package's component, command, preview and rendering classes are marked @internal: custom backends aren't supported. Making a PhpPdfBackend with its options and passing it to PdfOptions(backend:) still is. examples/core/hello.php, which draws with the backend directly, is now labelled a contributor demo.
  • A paper example (examples/core/documents/paper.php and examples/laravel/views/paper.blade.php): a two-page academic paper on A4 with a full-width title and abstract, a two-column body with a table and a figure that ends level, and a full-width closing note, with PDF, HTML and Word output.

Fixed

  • A box with a percentage min-width or max-width that isn't stretched across its column (align-self or the column's align set to start, center or end) resolved it against its own content, not the column. It's now a share of the column, as width became in 0.7.0.

Papercut 0.7.1

Papercut 0.7.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 02 Nov 14:17

Fixed

  • On PHP 8.4 and 8.5, exports could crash with zend_mm_heap corrupted or a segfault. A PHP garbage-collector bug (php/php-src#20001) corrupts memory when an object is a key in several WeakMaps that hold objects, arrays or strings; Papercut's caches no longer use that pattern.

Papercut 0.7.0

Papercut 0.7.0 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 01 Nov 23:01

Added

  • A Gallery page in the docs: a render of every built-in component, with its layout variants and a link to its docs. php site/gallery/render.php rebuilds the images.
  • Three decorators. Stat / <x-doc::stat>: a label, a large value and an optional note on a tinted panel. Totals / <x-doc::totals>: label and amount rows with the amounts right-aligned and total() / <x-doc::kv total> rows emphasised, 45% wide at the right by default, and it doesn't shrink beside a growing paragraph. Signature / <x-doc::signature>: a line to sign on, holding an image, a typed name or nothing, with an optional name, role and date under it. Styled by the new stat.*, totals.* and signature.* named styles.
  • Built-in variants: field line (a form line), badge outline (a border in the tone's colour), card flat (a tinted panel with no border) and section large (a bigger heading). Each badge.<tone> style gains a -300 border colour, which shows only when a border is drawn.
  • KeyValuePair takes a third argument, total; KeyValueList rejects total rows, and in Blade <x-doc::kv total> is only allowed inside <x-doc::totals>.
  • The invoice example uses Totals and the report example's stat cards use Stat (outputs regenerated).
  • Laravel: <x-doc::markup> takes plain HTML in its slot, converted like :html: <x-doc::markup><h3>Results</h3><table>…</table></x-doc::markup>. Blade loops, echoes and components that render HTML work inside; Papercut tags inside throw InvalidStructure.

Fixed

  • A box or table with a percentage width that isn't stretched across its column (align-self or the column's align set to start, center or end) was sized as a share of its own content, not of the column. It is now a share of the column, as in CSS.
  • In a row, an item with a width or basis could shrink to nothing next to a growing item, wrapping every word and sometimes throwing LayoutOverflow. Like CSS's min-width: auto, an item now shrinks no further than the narrowest its content can be (or its own width, if that is smaller).
  • Row sizing follows CSS more closely: growing or shrinking is decided from the sizes after min-width and max-width, the space to share is measured from the base sizes, and an item already held by its minimum or maximum keeps it. Layouts without such limits are unchanged.
  • Word export: a table aligned to the centre or end of its column keeps that alignment.
  • Word export: a badge with a border (such as the outline variant) stays in its line as bordered text, as a filled badge does with shading, instead of turning the whole row into a table.
  • Word export: a box with a background whose children are stacked boxes (a card with a tinted panel, a strip code block) shades every row that has no fill of its own; the body or code area used to come out white.
  • Word export: a box packed to its end or centre (justify) places its content at the bottom or middle of its cell, so a signature rests on its line.

Changed

  • Laravel: <x-doc::markup> with both :html (or :text) and slot content now throws InvalidStructure; the slot used to be dropped. The error for a markup tag with no source now reads "requires one of html, text or HTML in its slot", and for one with two "takes only one of html, text or HTML in its slot".

Papercut 0.6.0

Papercut 0.6.0 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 01 Nov 09:09

Added

  • Laravel: a PendingOutput or PendingHtml can be attached to a mailable or notification with ->attach(...). The document renders when it is attached, and the attachment is named document.pdf, document.docx or document.html. PendingOutput::attachment('name.pdf') sets the name (cleaned like download()) without rendering; PendingHtml attaches the full page. PapercutManager, PendingDocument, PendingOutput and PendingHtml are Macroable.
  • Span::code($text) and Text::code($text): inline code in PHP, a literal span in the code style, the same as <x-doc::code> and <code> in markup.
  • PHP setters that take an option also take its string value, matched without regard to case or surrounding spaces: direction, justify and align on View, Row and Column; alignSelf, textAlign and the style argument of border; TableCell::align, Image::fit, ListNode::marker, Place::anchor, and QrCode::make (the level), errorCorrection, shape, eyes and gradient (the type). A case name also matches, so 'high' is ErrorCorrection::High. ->align('center') now works as in Blade; an unknown string throws InvalidValue with a suggestion.
  • Other names for the anchors in the middle of each side: top-center, bottom-center, center-left and center-right as strings and in Blade's anchor, and Anchor::TopCenter, Anchor::BottomCenter, Anchor::CenterLeft and Anchor::CenterRight in PHP. They are the same cases as Top, Bottom, Left and Right. <x-doc::place anchor> now also ignores case and suggests the closest name for a typo.
  • A guide example (examples/core/documents/guide.php and examples/laravel/views/guide.blade.php): a three-page API integration guide on US Letter, portrait then landscape then portrait, with syntax-highlighted code blocks, badges in a table, a QR code and a go-live checklist.
  • A certificate example (examples/core/documents/certificate.php and examples/laravel/views/certificate.blade.php): a name, course, date and signature image placed on a blank US Letter landscape PDF template, built by examples/core/certificate-template.php. It is PDF only, because Word and HTML exports leave page templates out.
  • An article example (examples/core/documents/article.php and examples/laravel/views/article.blade.php): a two-page knowledge base article on A4, made from the HTML a rich text editor saves, with PDF, HTML and Word output.
  • Tables of contents: Toc::make()->levels(1, 3)->title('Contents')->leaders(false) in PHP and <x-doc::toc levels="1-3" title="Contents" :leaders="false" /> in Blade. Entries are the h1 to h4 headings (default levels 1 to 2), each with its physical page number and dot leaders, and a link to the heading in the PDF. ->toc(false) / toc="false" leaves a heading out; bookmark('Short') sets the entry's text and bookmark($title, $level) its level. Styled by the named styles toc, toc.title, toc.entry and toc.level-1 to toc.level-4. Word export writes a real TOC field filled in with the PDF's page numbers (Update Field in Word renumbers it; an updated field reads Heading 1 to 4, so toc(false) and bookmark levels aren't honoured then), and runs the PDF layout for documents that have one (if that layout can't run, the field is written without page numbers and the export still succeeds). HTML export writes a <nav class="papercut-toc"> of links without page numbers, reported as approximated. A table of contents in a header, footer, background or overlay throws InvalidStructure.
  • The report example has a table of contents (PDF, Word and HTML outputs regenerated).
  • Laravel: PendingOutput::streamDownload('name.pdf') returns the download as a streamed response, named like download(), that renders the document when it is sent. Livewire actions, and so Filament actions, send files only from streamed or file responses, so download() does nothing there and streamDownload() works.
  • A Laravel recipes page in the docs: storing on S3 or any disk, making documents in a queued job, emailing them, downloading from Livewire or Filament, and drawing charts as SVG from a Blade component.
  • Theme overrides per document and per page. In PHP, Document::theme(new ThemeOverride(...)) and Page::theme(...) set font family, size, weight, colour, line height, cell padding, minimum lines, mono family, code theme and named styles for one document or page; Theme::withOverride() does the merge. In Blade, <x-doc::document :theme="[...]"> and <x-doc::page :theme="[...]">; PendingDocument::theme([...]) from a controller. Order: config, the view's document, the controller, then the page. Named styles in an override merge onto the existing style. PDF lays out each page with its theme, HTML writes the page's differences on its section, and Word writes the document theme as Word's defaults and page values on that page's paragraphs.
  • Named styles h1 to h4 style headings of that level at every theme level (Theme::withStyle(), papercut.styles, overrides). Layering: the built-in heading look, the theme's h<n> style, the node's own style name, then its own style.
  • A page or document code theme (codeTheme in PHP, code_theme in Laravel) is the default for code blocks that don't name a theme.

Changed

  • Laravel: unknown keys in papercut.theme now throw instead of being ignored. Only font_family, mono_family, font_size, color and line_height are read there; the error names the key and points code_theme to papercut.code.theme and styles to papercut.styles.
  • Breaking: a custom style named h1, h2, h3 or h4 now applies to headings of that level. Without one, headings look as before.
  • Word export (papercut/docx) is no longer labelled experimental. It is versioned with core and Laravel, and follows the same compatibility rules. Word, LibreOffice and Google Docs still lay out the file themselves, so page breaks can differ from the PDF.
  • Breaking: Spacer::size($length) is now Spacer::fixed($length). The static factory hid the size() modifier every other node has, so $spacer->size(pt(9)) quietly made a new spacer and dropped its grow and background. On a spacer, ->size() now sets the gap and keeps the rest of its style. Blade's <x-doc::spacer size="..."> is unchanged.
  • Custom Backend implementations must handle the new Papercut\Paint\Anchor op. It draws nothing and marks a named destination for a table of contents entry; it only appears in documents that have a table of contents.
  • InvalidStructure moved to core as Papercut\Exception\InvalidStructure. Papercut\Laravel\Exception\InvalidStructure now extends it, so existing catch blocks keep working.
  • The guide example uses Text::code() for inline code and one string option (->align('center')).
  • The docs say that columns() accepts any length unit, and list the code block language aliases (bash, sh, shell and zsh for shell, js, ts, py, yml).
  • The README, the Laravel package description and the social preview image name all three formats (PDF, Word, and HTML); core's description names PDF and HTML. The guide's limitations say "fillable form fields (AcroForms)" instead of "form filling", since filling in an existing PDF works, and the HTML export page says backgrounds and overlays are approximate.
  • The invoice and transcript examples use US Letter and inches instead of A4 and millimetres, so the examples show both (the report and form responses stay on A4). The transcript now runs to three pages.

Fixed

  • Invisible formatting characters (soft hyphens, zero-width joiners and spaces, direction marks, word joiners, byte order marks), common in text pasted from Word or a CMS, are no longer drawn as empty boxes in PDF. The line and paragraph separators (U+2028, U+2029) count as newlines.
  • Strict mode throws UnsupportedFeature when a printed page of a PDF has characters no font has, in text or SVG text, naming the page and the characters. They print as empty boxes, and before only a warning said so, which plain PHP drops unless you pass onWarning. The fonts guide has a new section on fallback fonts for names in other scripts, and says that a short right-to-left run, such as an Arabic or Hebrew name, already lays out correctly.
  • Content taller than its box's height or max-height no longer draws over what follows without a word. It follows the overflow mode, as content too tall for a page does: Throw mode throws LayoutOverflow naming the box, and Clip mode clips it to the box and warns. Documents whose boxes overflowed now throw in Throw mode (Laravel's default in local and testing) or come out clipped.
  • papercut/laravel and papercut/docx require papercut/core ^0.6 (and Laravel's dev requirement on papercut/docx is ^0.6); they still asked for ^0.5, which no 0.6 core satisfies.
  • A release candidate tag (v1.0.0-rc.1, any version with a -) is published as a GitHub pre-release.
  • A table row too tall for one page, split just below the header, no longer paints its first line past the bottom of the page when that line doesn't fit below the header. This includes rows with spanning cells and tables inside table cells. A repeated header is left out on that page instead; on the first page the header stands alone and the rows start on the next page, with a warning.

Papercut 0.5.0

Papercut 0.5.0 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 30 Oct 01:03

Added

  • Syntax-highlighted code blocks: CodeBlock::make($code)->language('php') in PHP and <x-doc::pre language="php"> in Blade, coloured by a TextMate theme (default papercut-light; theme, or CodeHighlighting(theme: ...)). Highlighting uses the optional phiki/phiki; without it, or for an unknown language, the block is plain text, and an unknown theme is highlighted with the default one; each is reported as dropped highlighting (strict mode throws).
  • Code block labels: the language's name in capitals by default, label="..." for your own text, false to hide it. text, plaintext, txt and plain give a TEXT label with no colours; nohighlight and no-highlight give neither.
  • Code block looks: the default label line, variant="strip" (label in a header bar) and variant="caption" (label above the box, rule on the left), with code-block* named styles. Word export writes shaded paragraphs or tables; HTML writes <pre><code> with coloured spans.
  • PhikiHighlighter::grammar(), theme(), alias() and injection() register your own TextMate grammars, themes, aliases and VS Code style injection grammars; Laravel reads them from papercut.code.{theme,variant,grammars,themes,aliases,injections}.
  • <pre><code class="language-x"> in Markup::html() and <x-doc::markup> becomes a code block. MarkupOptions(code: false) or papercut.markup.highlight = false keeps the plain pre.

Changed

  • In Word, two horizontal rules in a row are now two rules, as in the PDF, instead of one.

Fixed

  • A table whose columns don't fit its box even at their narrowest no longer runs into the margin or off the page. The widest auto and fr columns (and the length columns of a table from Markup::html()) are narrowed to a shared width and long words in them break across lines. No column gets narrower than its content can go (cell padding plus its widest letter; a fixed-width box or a nested table keeps the width it needs, while images, SVGs and imported PDF pages scale down). This is reported as approximated, with how much more room the table needed. A table that still can't fit, such as one with hand-set length columns wider than its box, throws LayoutOverflow in throw mode instead of only warning; clip mode draws it too wide and warns, as before. A header or footer that is laid out only to look ahead, and never drawn, doesn't throw or report. Word export uses the same column widths and reports each table at its own path.
  • Body text containing papercut-shd- no longer makes Word export throw.
  • In Word, a kept-together table (or a kept-together box that Word draws as a table) now stays on one page. Before, each row stayed whole but the table could still break between rows.
  • Headings now keep with the block after them, as the docs say. A heading is no longer left alone at the bottom of a page when a keep-together block, table or code block after it moves to the next page. In Word the heading paragraph gets keep-with-next, and in HTML break-after: avoid. Turn it off on one heading with keepWithNext(false). When a heading and the block after it can't fit on one page together, the heading still ends up alone.

Papercut 0.4.1

Papercut 0.4.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 29 Oct 17:36

Changed

  • Composer downloads of every package leave out tests, benchmarks and development configuration (.gitattributes export-ignore), so papercut/docx installs at about a tenth of its 0.4.0 size and papercut/core keeps only its source and bundled fonts.

Papercut 0.4.0

Papercut 0.4.0 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 29 Oct 17:32

Added

  • papercut:fonts:install saves the family's licence (OFL.txt, LICENSE.txt or UFL.txt) beside the font files, from Google's fonts repository. If it can't be downloaded, the command warns and installs the fonts anyway; --force replaces an existing copy.
  • Decorator variants: variant("large") in PHP and variant="large" in Blade make a decorator also apply <name>.large for every part it styles, when the theme defines it. Decorator::partNames() lists a decorator's parts. In Blade a variant that styles no part throws; in PHP it does nothing.
  • images attribute on <x-doc::markup>: an ImageResolver class for that tag, or none to show alt text instead of images. PapercutManager::markupImageResolver() builds each named class once per manager.
  • FontSet::withWordName() and wordName() give a family the name Word should use for it (Cousine defaults to Courier New), and FontSet::info() reports a family's FontClass, PANOSE classification and fixed-pitch flag from its font file. In Laravel the names come from papercut.font_word_names.
  • Word export writes each family's Word name in runs and the document default, and adds word/fontTable.xml with PANOSE, family class and pitch so Word picks a matching stand-in for a missing font.
  • Cousine (regular, bold, italic, bold italic; SIL OFL) is bundled next to Inter. The font family keyword monospace resolves to the theme's new monoFamily (default Cousine; Theme::with(monoFamily: ...), papercut.theme.mono_family in Laravel). A family registered under the name "monospace" is never selected, since the keyword always means the theme's mono family; the mono family cannot itself be empty or monospace.
  • white-space: pre-wrap style (Style::make(whiteSpace: WhiteSpace::PreWrap), attribute white-space, inherited): text layout keeps spaces, treats newlines as line breaks, moves tabs to the next multiple of four space widths and still wraps lines longer than the box. Justified text is not stretched on these lines.
  • First-page headers and footers: Page::firstHeader() and firstFooter() in PHP, first-header and first-footer slots in Blade. An empty one means nothing on the first page. Word export sets "Different First Page"; HTML shows the first-page header at the top. The tree walker reports them as the regions FirstHeader, EvenHeader, FirstFooter and EvenFooter.
  • Even-page headers and footers: Page::evenHeader() and evenFooter() in PHP, even-header and even-footer slots in Blade. Word export sets "Different Odd & Even Pages"; HTML export leaves them out and reports it as approximated.
  • Mirrored margins: Page::mirrorMargins() in PHP and mirror on <x-doc::page> in Blade swap the left and right margins on even pages. Word export sets "Mirror margins"; HTML applies it in print, from the first page run.
  • rowspan on table cells: TableCell::rowspan() in PHP and rowspan on <x-doc::td> and <x-doc::th> in Blade. Spanning cells lay out in PDF, HTML and Word export, and a group of spanned rows stays together across page breaks unless it is taller than a page.
  • Tables in Markup::html(): table, caption, thead, tbody, tfoot, tr, td and th convert to a table with repeating header rows, colspan and rowspan, and columns sized by their content. In original mode, width on cells and tables is honoured. The named styles markup.table, markup.td and markup.th can be restyled through papercut.styles.
  • Named styles pre and code. In Markup::html(), <pre> becomes a pre block that keeps spaces, tabs and line breaks (no approximation note any more), and <code>, <kbd>, <samp> and <tt> become code spans; <pre><code> is one pre block. Inline markup in text tags accepts <code>.
  • Preformatted text in Word and HTML: HTML writes pre blocks as <pre> and inline code as <code>, with white-space: pre-wrap and tab-size: 4; Word keeps spaces, writes tabs as w:tab (at Word's own tab stops) and line breaks as w:br, in the mono family's Word name (Courier New for Cousine).
  • <hr> in markup becomes a horizontal rule (named style markup.hr), in PDF, HTML and Word export.
  • Table::fillWidth() shares leftover width among auto columns in proportion to their content.
  • Word export: a row of only inline content (text, badges, links) exports as one paragraph, with badges as shaded runs, when its items fit on one line at their natural widths. A row with a long wrapping value stays a table.
  • A warning when a rowspan or colspan is clamped to the table (once per table).
  • TableCell::colsSpanned(), an alias of span(), beside rowsSpanned().
  • Span has a new optional last constructor argument, base: the style set outside a <code> span. Text::markup('<span font="Inter">a <code>x</code></span>') renders x in the code font; styles apply in the order outer, code, inner.
  • Blade components <x-doc::pre> (a pre block with literal content: tags are not parsed, entities are decoded, the first and last blank lines are dropped and the common indentation removed) and <x-doc::code> (an inline code span).

Changed

  • Callout::variant() and Badge::variant() reject a word equal to a built-in tone; use tone().
  • Breaking: custom Decorator implementations must now provide variant(), variants() and partNames(): add use HasVariants and list the style names the decorator applies, passing each through part().
  • Markup conversion calls the image resolver once per distinct src (trimmed), reusing the result, including a null, for later images with the same src. The resolver now receives the trimmed src.
  • HTML export ends each font stack with the generic family matching the font's class: monospace for Cousine and other fixed-pitch fonts, serif for serif fonts, and system-ui, sans-serif otherwise (unchanged for Inter and unknown families).
  • Lists in Word export are real Word lists (word/numbering.xml), except lists with marker none, which stay indented paragraphs. Enter continues them, and renumbering and the bullet and number buttons work. Nested lists sit one level deeper, extra blocks in an item stay indented under it, and an item whose number breaks the sequence starts a new Word list from that item on. Numbers Word can't show (below 1, or roman numerals above 3999) keep typed markers and are reported as approximated.
  • Lists have space after them in PDF, Word and HTML: the theme's paragraph spacing, unless the last item already ends with it or the list has a margin set, which then decides its spacing. Nested lists add none, and neither does a list that ends a table cell, or a box with padding, a border or a background. Documents with lists lay out differently.

Fixed

  • Pagination: a block that doesn't fit the page it lands on moves past any page whose body is shorter than the next one, instead of failing, and a block that fits no page is no longer left behind an empty page. A min-height cell in a split row or a rowspan group stops at the page slice instead of painting past the body.
  • Layout warnings from a layout that is repeated (for example a table split across pages of different sizes) are reported once.
  • A table cell with min-width or max-width that differs from its column was reported as a stray cell outside a table; the column still decides its width.
  • HTML export shows a first-page footer when the page has no normal footer, and reports it as approximated when a normal footer is shown in its place. Every white-space: pre-wrap element gets tab-size: 4, not only pre.
  • <code><pre> no longer applies the inline code style inside the pre block. A transparent background no longer counts as a visible edge for list spacing, and a component that ends a list item is no longer rendered twice by list spacing.
  • Fonts: a PANOSE family kind of 0 or 1 (any, no fit; many fonts ship all zeros) is class Other, not Sans; the HTML fallback stack is still sans.
  • Word export: a line break inside a link stays in one hyperlink; a signature line (a bordered box with no content) keeps its label's slack and its border as a cell border, and padding alone no longer counts as a drawn box; a rowspan to the right of a short row keeps its merged cell; rowspan continuation cells keep the spanner's borders and shading; lists inside a row's children, and bordered or shaded lists, restart at level 0 instead of counting outer lists; a list item that starts with a rule gets its number on a line above it; mirrored margins are written in any existing settings.xml; a none list nested in a list is indented once.
  • Laravel: papercut.theme.mono_family errors name the config key, and a family that is neither bundled nor in papercut.fonts is rejected when options are built.
  • Rendered <x-doc> output reused from an earlier render (cached HTML, a static, once(), a nested render) now fails with an InvalidStructure error that says to cache data instead, when the marker came from this process. Text in marker form that this process never issued, such as user data, still renders as text. When such a marker reaches <x-doc::choices>, key-value, ol, ul, thead, tr or table, their "can only contain" error now adds the same hint. A preg_split() failure while reading rendered output is reported instead of ignored.
  • Word export no longer adds a second w:mirrorMargins when the template already has one written with attributes or spacing.
  • The rich text guide said blockquote was dropped; it has converted since 0.3.0, and <mark> gets a highlight.

Papercut 0.3.0

Papercut 0.3.0 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 28 Oct 21:35

The first release on Packagist. 0.1 and 0.2 were never published, so there are no deprecation shims; the table
below maps v0.1 names for anyone who used the repository directly.

Changed (breaking)

Migration from v0.1:

v0.1 0.2
Pdf::view(...) Papercut::view(...)->pdf()
<x-pdf::…> <x-doc::…>
PendingPdf PendingOutput
PdfManager PapercutManager
Document::render() / toString() toPdf()->bytes()
Document::save($p) / stream() toPdf()->save($p) / toPdf()->stream()
RenderOptions PdfOptions
Image::handle() removed; use Image::file() / Image::bytes()
  • The Pdf facade alias is now Papercut; Papercut::pdfOptions() replaces Pdf::options().
  • The Blade tag prefix is configurable through papercut.prefix (default doc).
  • PdfOptions is not a drop-in rename of RenderOptions; the constructor changed shape:
    • the positional order is now theme, fonts, onWarning, strict, then backend, overflow, cacheDir, so use named arguments;
    • fonts takes a FontSet, not a FontRegistry;
    • fallback families moved to FontSet::withFallbacks();
    • the bundled Inter is always registered.
  • Save failures now read Directory does not exist: … or Could not write output to "…": … (previously Could not write PDF to …).
  • In Laravel, all config errors (theme, styles, overflow, strict, fonts, including a missing or unreadable font file) now surface when the output is rendered, not at view(), make() or Papercut::pdfOptions().
  • ->html() returns a PendingHtml that no longer extends PendingOutput and has no toString(), save(), download(), inline() or output(). Use ->html()->page() (full page) or ->html()->fragment() (embeddable fragment), each a PendingOutput named document.html. PendingOutput is now final. Returning ->html() from a route still sends the full page with no Content-Disposition.
  • Output and the export options expose methods, not properties: $output->bytes(), mime(), extension(); $options->theme(), fonts(), onWarning(), strict(), overflow(), cacheDir().
  • Requires dskripchenko/php-pdf 1.26, which parses fonts and their kerning in about a millisecond. The kerning cache (KerningCache) is replaced by a font cache (FontStateCache) that keeps each parsed font, kerning included, in a file of a few hundred KB rather than 5-6 MB. PhpPdfBackend and DefaultBackend::make() take cacheDir instead of kerningCacheDir; PdfOptions(cacheDir:) and the default directory are unchanged, and old kern-*.ser files are left for you to delete.
  • In Laravel, papercut.cache_dir = null now means storage/framework/cache/papercut instead of the system temp directory, which PHP-FPM and the CLI can see differently.
  • Embedded font subsets are named with a tag (/BaseFont /ABCDEF+Inter-Regular) and carry a /CIDSet, as the PDF and PDF/A specs require (php-pdf 1.26), so PDF bytes differ from earlier versions; the drawing is unchanged.

Added

  • Word export (experimental): the new papercut/docx package. $document->export(new DocxExporter()) writes a .docx that Word, LibreOffice and Google Docs open and paginate themselves: columns flow as paragraphs, rows become borderless tables at the PDF's widths, and boxes with padding, a border or a background become one-cell tables. Lists, tables (repeating headers, rows that don't split, colspan), links, headings, images, page numbers, headers and footers, metadata, lang and pagination rules carry over. Each run of pages with the same size, margins, header, footer and layers becomes a Word section, so later pages keep their own setup and headers. Pictures Word can't take as they are are drawn at the PDF's size: WebP and AVIF are converted, image cover is cropped and opacity faded with ext-gd; QR codes are drawn at 300 dpi (styled ones with ext-imagick and Bacon, else plain); SVG is drawn at 300 dpi with ext-imagick, with nothing outside the file loaded. Opacity is drawn by blending each faded colour onto what lies beneath it, and text backgrounds are shaded in their own colour. An image, QR code or SVG in a background (before anything else in it) or alone in an overlay place becomes a floating picture at the PDF's position, repeated on every page through the header. Corner radius on boxes, other layer content, a border or background on a layer picture, and PDF pages and templates are reported as dropped, and the rest of what Word can't show exactly as approximated. The same input gives the same bytes. Built on dskripchenko/php-docx 1.18.
  • Laravel: ->docx() on a PendingDocument (needs papercut/docx, suggested; throws MissingExporter without it), and preview routes accept ?format=docx.
  • PDF templates: Page::template(PdfPage::file($path)) makes a page the size of a page of an existing PDF and draws that page behind everything else on it, for certificates and letterheads. PdfPage::file() and PdfPage::bytes() take a page number and a password, and also draw a PDF page anywhere in the content, sized like an image. The page is drawn as it is, with its text and vector artwork intact, stored once however many pages use it, and encrypted with the document. A missing file, bytes that aren't a PDF, a wrong password or a missing page throw PdfUnreadable. Backends opt in through a new PdfPageLoader interface. In Blade, <x-doc::page template="…"> (or template-data, with template-page and template-password) and <x-doc::pdf-page src|data>. HTML exports leave PDF pages out and report them as dropped.
  • Placed content: Page::overlay(Place::at($x, $y, ...)) puts content at a point on the page, over everything else, on every page. x and y are lengths from the top-left corner or percentages of the page, anchor() picks the point of the content placed there, and width() takes a length or a percentage of the page width. In Blade, <x-doc::place x y anchor width> in the page's overlay slot. HTML exports position places with CSS.
  • SVG: Svg::file($path), Svg::fromString($markup) and <x-doc::svg src|data> (or markup inside the tag) draw an SVG as vector paths through php-pdf's SVG renderer, sized like an image from its width, height or view box, placed by preserveAspectRatio and clipped to its box. fill-rule="evenodd" keeps the holes in rings, letters and stars. SVG <text> is set in the document's embedded fonts, never a base-14 one. Untrusted SVG is checked first: no external files, DTDs or entity declarations, scripts and event handlers removed, and sources over 10 MB or over a million units of drawing work (about a second and a half) refused. Elements not drawn yet, like <clipPath> and <filter>, are named in a warning. HTML exports show an <img> with a data: URI and the SVG's <title> as alt text. Uses the even-odd fill and the SVG group and gradient fixes in php-pdf 1.26.
  • QR codes: QrCode::make($value) and <x-doc::qr-code value="…"> draw a QR code as vector squares in the text color, square, 25mm by default, with error-correction level and quiet zone. Encoded with php-pdf's encoder (up to 213 bytes at Medium) behind a QrEncoder interface, or from your own grid with QrCode::fromModules(). HTML exports draw an inline SVG.
  • Styled QR codes: shape(), eyes(), eyeColor() and gradient() on QrCode, and shape, eyes, eye-color, gradient and gradient-type on <x-doc::qr-code>, give a code dots or rounded modules, dot or pointy eyes, a separate eye color and a linear or radial gradient. Dots and rounded modules raise the error correction to Quartile, unless it is High, so they scan reliably. Papercut encodes the modules and the optional bacon/bacon-qr-code package draws them, through the SVG support; styling without it throws MissingPackage with the command to install it. BaconQrEncoder uses Bacon's encoder for text up to 2,953 bytes. HTML exports show a styled code as an SVG image.
  • Encryption: PdfOptions(encryption: Encryption::password('secret')) and Laravel's ->encrypt() write AES-256 PDFs that open only with the password. Encryption sets print, copy, modify, annotate and assemble permissions; a missing owner password is generated at random so the permissions hold, and screen-reader access is always allowed.
  • Bookmarks: headings h1–h4 fill the PDF's bookmarks panel, nested by level. bookmark($title, $level) (Blade bookmark, bookmark-level) adds or retitles one on any component, bookmark(false) removes a heading's, and PdfOptions(bookmarks: false) / papercut.bookmarks turns them off. Headers, footers and backgrounds never add bookmarks.
  • php artisan papercut:fonts:install <family> downloads a family from Google Fonts into the app for use in PDFs, and prints its fonts config entry.
  • PendingOutput::store($path, $disk = null, $options = []) writes a PDF or HTML export to a Laravel filesystem disk.
  • Papercut::make($document) and PendingDocument (document(), pdf()); PendingOutput takes its Content-Type and default filename from the Output.
  • Core: Exporter/Output API, PdfOptions, FontSet, fidelity reporting (Fidelity, FidelityReport), strict mode (UnsupportedFeature) and MissingExporter.
  • Config key papercut.strict (default false).
  • HTML export: Document::toHtml() in core and Papercut::view(...)->html() in Laravel (PendingHtml: (string) and {{ }} give the fragment for embedding, page() and fragment() each return a PendingOutput, and a route response is the full page).
  • Config key papercut.html.fonts (none default, embed or link), and papercut.html.image_url / papercut.html.font_url: invokable class names implementing Papercut\Laravel\Html\ImageUrlResolver / FontUrlResolver, built by the container (class names survive config:cache; closures are rejected). link needs font_url. Invalid html.* config is reported at the fir...
Read more