Repository navigation
Releases
Releases · One-Learning-Community/papercut
Papercut 0.9.1
Added
DocxOptions(tocPageNumbers: false)and the Laravel config keypapercut.docx.toc_page_numbersskip 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
Added
- Section and card titles can be headings:
heading="1"to"4"on<x-doc::section>and<x-doc::card>,->heading($level)onSectionandCard. The title gets a PDF bookmark, a table of contents entry, anhtag 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 apapercut.markup.tocconfig 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 withtoc(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
Added
ColumnFlow/<x-doc::column-flow>: text set in columns, like Word's.columns(),columnWidths(),columnGap()andcolumnRule()(Bladecolumns,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
originalstyle mode adivwithcolumn-countorcolumns(a count) becomes a column flow, withcolumn-gapandcolumn-rulesetting 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-*anddivide-xto a column flow. - A gallery tile for the column flow, with
ruleandwidthsvariants. - A Versions and compatibility page in the guide: what the version promise covers, and what it doesn't.
Changed
- The
Papercut\Backendinterfaces, thePapercut\Paintdisplay list, the layout engine (Papercut\Layout), the tree walk (Papercut\Tree), the HTML writer,StyleResolver,PageThemes,FontRegistryand the text layout classes, the exporter classes andExporter(export withtoPdf(),toHtml()orexport(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 aPhpPdfBackendwith its options and passing it toPdfOptions(backend:)still is.examples/core/hello.php, which draws with the backend directly, is now labelled a contributor demo. - A
paperexample (examples/core/documents/paper.phpandexamples/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-widthormax-widththat isn't stretched across its column (align-selfor the column'salignset tostart,centerorend) resolved it against its own content, not the column. It's now a share of the column, aswidthbecame in 0.7.0.
Papercut 0.7.1
Fixed
- On PHP 8.4 and 8.5, exports could crash with
zend_mm_heap corruptedor 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
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.phprebuilds 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 andtotal()/<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 newstat.*,totals.*andsignature.*named styles. - Built-in variants:
fieldline(a form line),badgeoutline(a border in the tone's colour),cardflat(a tinted panel with no border) andsectionlarge(a bigger heading). Eachbadge.<tone>style gains a-300border colour, which shows only when a border is drawn. KeyValuePairtakes a third argument,total;KeyValueListrejects total rows, and in Blade<x-doc::kv total>is only allowed inside<x-doc::totals>.- The invoice example uses
Totalsand the report example's stat cards useStat(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 throwInvalidStructure.
Fixed
- A box or table with a percentage width that isn't stretched across its column (
align-selfor the column'salignset tostart,centerorend) 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
widthorbasiscould shrink to nothing next to a growing item, wrapping every word and sometimes throwingLayoutOverflow. Like CSS'smin-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-widthandmax-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
outlinevariant) 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
stripcode 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 throwsInvalidStructure; 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
Added
- Laravel: a
PendingOutputorPendingHtmlcan be attached to a mailable or notification with->attach(...). The document renders when it is attached, and the attachment is nameddocument.pdf,document.docxordocument.html.PendingOutput::attachment('name.pdf')sets the name (cleaned likedownload()) without rendering;PendingHtmlattaches the full page.PapercutManager,PendingDocument,PendingOutputandPendingHtmlareMacroable. Span::code($text)andText::code($text): inline code in PHP, a literal span in thecodestyle, 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,justifyandalignonView,RowandColumn;alignSelf,textAlignand the style argument ofborder;TableCell::align,Image::fit,ListNode::marker,Place::anchor, andQrCode::make(the level),errorCorrection,shape,eyesandgradient(the type). A case name also matches, so'high'isErrorCorrection::High.->align('center')now works as in Blade; an unknown string throwsInvalidValuewith a suggestion. - Other names for the anchors in the middle of each side:
top-center,bottom-center,center-leftandcenter-rightas strings and in Blade'sanchor, andAnchor::TopCenter,Anchor::BottomCenter,Anchor::CenterLeftandAnchor::CenterRightin PHP. They are the same cases asTop,Bottom,LeftandRight.<x-doc::place anchor>now also ignores case and suggests the closest name for a typo. - A
guideexample (examples/core/documents/guide.phpandexamples/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
certificateexample (examples/core/documents/certificate.phpandexamples/laravel/views/certificate.blade.php): a name, course, date and signature image placed on a blank US Letter landscape PDF template, built byexamples/core/certificate-template.php. It is PDF only, because Word and HTML exports leave page templates out. - An
articleexample (examples/core/documents/article.phpandexamples/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 theh1toh4headings (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 andbookmark($title, $level)its level. Styled by the named stylestoc,toc.title,toc.entryandtoc.level-1totoc.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, sotoc(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 throwsInvalidStructure. - 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 likedownload(), that renders the document when it is sent. Livewire actions, and so Filament actions, send files only from streamed or file responses, sodownload()does nothing there andstreamDownload()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(...))andPage::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
h1toh4style headings of that level at every theme level (Theme::withStyle(),papercut.styles, overrides). Layering: the built-in heading look, the theme'sh<n>style, the node's own style name, then its own style. - A page or document code theme (
codeThemein PHP,code_themein Laravel) is the default for code blocks that don't name a theme.
Changed
- Laravel: unknown keys in
papercut.themenow throw instead of being ignored. Onlyfont_family,mono_family,font_size,colorandline_heightare read there; the error names the key and pointscode_themetopapercut.code.themeandstylestopapercut.styles. - Breaking: a custom style named
h1,h2,h3orh4now 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 nowSpacer::fixed($length). The static factory hid thesize()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
Backendimplementations must handle the newPapercut\Paint\Anchorop. 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. InvalidStructuremoved to core asPapercut\Exception\InvalidStructure.Papercut\Laravel\Exception\InvalidStructurenow extends it, so existingcatchblocks 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,shellandzshfor 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
UnsupportedFeaturewhen 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 passonWarning. 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
heightormax-heightno longer draws over what follows without a word. It follows the overflow mode, as content too tall for a page does: Throw mode throwsLayoutOverflownaming the box, and Clip mode clips it to the box and warns. Documents whose boxes overflowed now throw in Throw mode (Laravel's default inlocalandtesting) or come out clipped. papercut/laravelandpapercut/docxrequirepapercut/core^0.6(and Laravel's dev requirement onpapercut/docxis^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
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 (defaultpapercut-light;theme, orCodeHighlighting(theme: ...)). Highlighting uses the optionalphiki/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,falseto hide it.text,plaintext,txtandplaingive aTEXTlabel with no colours;nohighlightandno-highlightgive neither. - Code block looks: the default label line,
variant="strip"(label in a header bar) andvariant="caption"(label above the box, rule on the left), withcode-block*named styles. Word export writes shaded paragraphs or tables; HTML writes<pre><code>with coloured spans. PhikiHighlighter::grammar(),theme(),alias()andinjection()register your own TextMate grammars, themes, aliases and VS Code style injection grammars; Laravel reads them frompapercut.code.{theme,variant,grammars,themes,aliases,injections}.<pre><code class="language-x">inMarkup::html()and<x-doc::markup>becomes a code block.MarkupOptions(code: false)orpapercut.markup.highlight = falsekeeps the plainpre.
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
autoandfrcolumns (and the length columns of a table fromMarkup::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, throwsLayoutOverflowin 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 withkeepWithNext(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
Changed
- Composer downloads of every package leave out tests, benchmarks and development configuration (
.gitattributesexport-ignore), sopapercut/docxinstalls at about a tenth of its 0.4.0 size andpapercut/corekeeps only its source and bundled fonts.
Papercut 0.4.0
Added
papercut:fonts:installsaves the family's licence (OFL.txt,LICENSE.txtorUFL.txt) beside the font files, from Google's fonts repository. If it can't be downloaded, the command warns and installs the fonts anyway;--forcereplaces an existing copy.- Decorator variants:
variant("large")in PHP andvariant="large"in Blade make a decorator also apply<name>.largefor 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. imagesattribute on<x-doc::markup>: anImageResolverclass for that tag, ornoneto show alt text instead of images.PapercutManager::markupImageResolver()builds each named class once per manager.FontSet::withWordName()andwordName()give a family the name Word should use for it (Cousine defaults toCourier New), andFontSet::info()reports a family'sFontClass, PANOSE classification and fixed-pitch flag from its font file. In Laravel the names come frompapercut.font_word_names.- Word export writes each family's Word name in runs and the document default, and adds
word/fontTable.xmlwith 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
monospaceresolves to the theme's newmonoFamily(defaultCousine;Theme::with(monoFamily: ...),papercut.theme.mono_familyin 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 ormonospace. white-space: pre-wrapstyle (Style::make(whiteSpace: WhiteSpace::PreWrap), attributewhite-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()andfirstFooter()in PHP,first-headerandfirst-footerslots 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 regionsFirstHeader,EvenHeader,FirstFooterandEvenFooter. - Even-page headers and footers:
Page::evenHeader()andevenFooter()in PHP,even-headerandeven-footerslots 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 andmirroron<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. rowspanon table cells:TableCell::rowspan()in PHP androwspanon<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,tdandthconvert to a table with repeating header rows,colspanandrowspan, and columns sized by their content. Inoriginalmode,widthon cells and tables is honoured. The named stylesmarkup.table,markup.tdandmarkup.thcan be restyled throughpapercut.styles. - Named styles
preandcode. InMarkup::html(),<pre>becomes apreblock that keeps spaces, tabs and line breaks (no approximation note any more), and<code>,<kbd>,<samp>and<tt>becomecodespans;<pre><code>is onepreblock. Inline markup in text tags accepts<code>. - Preformatted text in Word and HTML: HTML writes
preblocks as<pre>and inlinecodeas<code>, withwhite-space: pre-wrapandtab-size: 4; Word keeps spaces, writes tabs asw:tab(at Word's own tab stops) and line breaks asw:br, in the mono family's Word name (Courier Newfor Cousine). <hr>in markup becomes a horizontal rule (named stylemarkup.hr), in PDF, HTML and Word export.Table::fillWidth()shares leftover width amongautocolumns 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
rowspanorcolspanis clamped to the table (once per table). TableCell::colsSpanned(), an alias ofspan(), besiderowsSpanned().Spanhas a new optional last constructor argument,base: the style set outside a<code>span.Text::markup('<span font="Inter">a <code>x</code></span>')rendersxin the code font; styles apply in the order outer, code, inner.- Blade components
<x-doc::pre>(apreblock 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 inlinecodespan).
Changed
Callout::variant()andBadge::variant()reject a word equal to a built-in tone; usetone().- Breaking: custom
Decoratorimplementations must now providevariant(),variants()andpartNames(): adduse HasVariantsand list the style names the decorator applies, passing each throughpart(). - Markup conversion calls the image resolver once per distinct
src(trimmed), reusing the result, including a null, for later images with the samesrc. The resolver now receives the trimmedsrc. - HTML export ends each font stack with the generic family matching the font's class:
monospacefor Cousine and other fixed-pitch fonts,seriffor serif fonts, andsystem-ui, sans-serifotherwise (unchanged for Inter and unknown families). - Lists in Word export are real Word lists (
word/numbering.xml), except lists with markernone, 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-heightcell 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-widthormax-widththat 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-wrapelement getstab-size: 4, not onlypre. <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, notSans; 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; anonelist nested in a list is indented once. - Laravel:
papercut.theme.mono_familyerrors name the config key, and a family that is neither bundled nor inpapercut.fontsis 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 anInvalidStructureerror 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,trortable, their "can only contain" error now adds the same hint. Apreg_split()failure while reading rendered output is reported instead of ignored. - Word export no longer adds a second
w:mirrorMarginswhen the template already has one written with attributes or spacing. - The rich text guide said
blockquotewas dropped; it has converted since 0.3.0, and<mark>gets a highlight.
Papercut 0.3.0
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
Pdffacade alias is nowPapercut;Papercut::pdfOptions()replacesPdf::options(). - The Blade tag prefix is configurable through
papercut.prefix(defaultdoc). PdfOptionsis not a drop-in rename ofRenderOptions; the constructor changed shape:- the positional order is now theme, fonts, onWarning, strict, then backend, overflow, cacheDir, so use named arguments;
fontstakes aFontSet, not aFontRegistry;- fallback families moved to
FontSet::withFallbacks(); - the bundled Inter is always registered.
- Save failures now read
Directory does not exist: …orCould not write output to "…": …(previouslyCould 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()orPapercut::pdfOptions(). ->html()returns aPendingHtmlthat no longer extendsPendingOutputand has notoString(),save(),download(),inline()oroutput(). Use->html()->page()(full page) or->html()->fragment()(embeddable fragment), each aPendingOutputnameddocument.html.PendingOutputis nowfinal. Returning->html()from a route still sends the full page with noContent-Disposition.Outputand the export options expose methods, not properties:$output->bytes(),mime(),extension();$options->theme(),fonts(),onWarning(),strict(),overflow(),cacheDir().- Requires
dskripchenko/php-pdf1.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.PhpPdfBackendandDefaultBackend::make()takecacheDirinstead ofkerningCacheDir;PdfOptions(cacheDir:)and the default directory are unchanged, and oldkern-*.serfiles are left for you to delete. - In Laravel,
papercut.cache_dir = nullnow meansstorage/framework/cache/papercutinstead 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/docxpackage.$document->export(new DocxExporter())writes a.docxthat 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,langand 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, imagecoveris cropped and opacity faded withext-gd; QR codes are drawn at 300 dpi (styled ones withext-imagickand Bacon, else plain); SVG is drawn at 300 dpi withext-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 ondskripchenko/php-docx1.18. - Laravel:
->docx()on aPendingDocument(needspapercut/docx, suggested; throwsMissingExporterwithout 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()andPdfPage::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 throwPdfUnreadable. Backends opt in through a newPdfPageLoaderinterface. In Blade,<x-doc::page template="…">(ortemplate-data, withtemplate-pageandtemplate-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.xandyare lengths from the top-left corner or percentages of the page,anchor()picks the point of the content placed there, andwidth()takes a length or a percentage of the page width. In Blade,<x-doc::place x y anchor width>in the page'soverlayslot. 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 bypreserveAspectRatioand 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 adata: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 aQrEncoderinterface, or from your own grid withQrCode::fromModules(). HTML exports draw an inline SVG. - Styled QR codes:
shape(),eyes(),eyeColor()andgradient()onQrCode, andshape,eyes,eye-color,gradientandgradient-typeon<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 optionalbacon/bacon-qr-codepackage draws them, through the SVG support; styling without it throwsMissingPackagewith the command to install it.BaconQrEncoderuses 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.Encryptionsets 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–h4fill the PDF's bookmarks panel, nested by level.bookmark($title, $level)(Bladebookmark,bookmark-level) adds or retitles one on any component,bookmark(false)removes a heading's, andPdfOptions(bookmarks: false)/papercut.bookmarksturns 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 itsfontsconfig entry.PendingOutput::store($path, $disk = null, $options = [])writes a PDF or HTML export to a Laravel filesystem disk.Papercut::make($document)andPendingDocument(document(),pdf());PendingOutputtakes itsContent-Typeand default filename from theOutput.- Core:
Exporter/OutputAPI,PdfOptions,FontSet, fidelity reporting (Fidelity,FidelityReport), strict mode (UnsupportedFeature) andMissingExporter. - Config key
papercut.strict(defaultfalse). - HTML export:
Document::toHtml()in core andPapercut::view(...)->html()in Laravel (PendingHtml:(string)and{{ }}give the fragment for embedding,page()andfragment()each return aPendingOutput, and a route response is the full page). - Config key
papercut.html.fonts(nonedefault,embedorlink), andpapercut.html.image_url/papercut.html.font_url: invokable class names implementingPapercut\Laravel\Html\ImageUrlResolver/FontUrlResolver, built by the container (class names surviveconfig:cache; closures are rejected).linkneedsfont_url. Invalidhtml.*config is reported at the fir...