Repository navigation
Papercut 0.3.0
Pre-release
Pre-release
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 first->html()export. - Laravel:
papercut.html.fonts = sources, withhtml.font_sources(default[GoogleFonts::class]),html.google_fonts(host,display,families) andhtml.local_fonts(mode,families). The service provider bindsGoogleFontsandLocalFontsfrom config. Checked lazily at the first->html()export, and only when used. - Core: lists.
ListNode::ordered()/unordered()withListItem,start(),marker()andListItem::withValue();ListMarker(decimal, lower/upper alpha and roman, disc, circle, square, none). Markers follow nesting depth unless set, sit in a right-aligned gutter on the first line's baseline, and items split across pages. PDF and HTML (<ol>/<ul>/<li>). - Core:
Markup::html(),Markup::text()andMarkup::flatten()withMarkupOptions(styles:StyleMode::ThemeorOriginal,imagesresolver closure,blankParagraphs). Converts an HTML fragment or plain text to nodes. Unsupported content is reported through the fidelity report atapproximatedordropped, so strict mode fails only on the dropped cases. Nesting deeper than 256 levels is flattened to text with one report. Bytes the image resolver returns that are not a recognised image format count as a missing image.imgwidth/heightattributes set a width only (the image keeps the aspect of its bytes and still shrinks to fit a page). Block-level tags without a mapping (blockquote,pre,section, tables,dl,hr, …) keep their content as separate paragraphs (prekeeps its line breaks); unknown tags are reported once per tag name. Liststart/valueattributes are clamped to the 32-bit range, and roman markers fall back to decimal outside 1–3999. - Laravel:
<x-doc::ol>,<x-doc::ul>and<x-doc::li>. - Laravel:
<x-doc::markup :html|:text>(inline inside a text tag; numbers are taken as strings) and config keyspapercut.markup.images(a class implementingPapercut\Laravel\Markup\ImageResolver),markup.stylesandmarkup.blank_paragraphs. - Laravel:
<x-doc::node :node="$node" />embeds a PHP-builtNodeorComponent, as a block or inline inside a text tag. An inline tag cannot span an embedded node. - Preview routes accept
?format=pdf|html(anddocx, see Word export); any other value is a 404. Papercut::exportOptions()(shared theme, fonts, warnings, strict) andPapercut::htmlOptions();pdfOptions()builds on the shared options.HtmlOptions(fullPage,fontMode,imageUrl,fontUrl),FontMode(None,Embed,Link) and theimageUrl/fontUrlcallbacks: images embed as data URIs unlessimageUrlreturns a safe URL; fonts embed or link perFontMode.onWarningcallbacks are arity-aware: a one-parameter callback gets the message only, a callback with a second parameter also gets theFidelityReport.- A table cell outside a table (not in a row) is laid out as a column and warns
A table cell outside a table is laid out as a column, in both PDF and HTML. - HTML font sources:
FontMode::SourceswithHtmlOptions::fontSources, an ordered stack ofFontSourceobjects or closures (GoogleFontsfor Google Fonts and Bunny Fonts,LocalFonts::embed()/link()for registered files). Full pages get<link>tags in<head>; fragments get@importrules first in<style>. Unsafe answers fall through to the next source with a warning, and a family nobody answers for is reported asfont not linked. Papercut\Tree\Visit:TreeVisitorcallbacks now receive aVisit(path, index, parent, header and region context) instead of loose arguments.- Images: an image taller than an empty page shrinks to fit it, keeping its aspect ratio, as it already did for width (previously
LayoutOverflow). An explicitheightis still respected. - Images: WebP, GIF, BMP and AVIF are accepted when the GD extension is loaded (
ext-gd, now insuggest). They are converted to PNG for PDF, which is lossless and keeps transparency; HTML export embeds the original bytes. PNG and JPEG never need GD. Without GD the error names the extension to install, and the unsupported-format message lists every accepted format. - Paper sizes:
Page::named()addsa0,a1,a2,b4,b5,tabloidandexecutive(Page::sizeNames()lists them all), and an unknown name lists the valid ones. In Laravel,<x-doc::page size>andpapercut.paperalso accept a width and height, such as"8.5in 11in". - Core: decorator components in
Papercut\Decorator:Card,Callout,Blockquote,Section,Field,ChoiceList,KeyValueListandBadge. They render from existing nodes, so PDF and HTML export both work, and their look comes from built-in named styles (card,callout.warning,choice.marker,badge.tip, …) that anyThemeresolves andwithStyle()can override. - Core:
Themeaccepts space-separated style names (apply('callout callout.warning')), merged left to right; an unknown name in the list is reported with a suggestion. - Core:
View::htmlTag()sets the element HTML export writes (div,section,aside,blockquote,header,footer,figure); PDF ignores it. - Core: highlighted text runs. Spans honour
background, and<mark>inText::markup()paintsyellow-200behind the run (one rectangle per line fragment) in PDF and HTML. - Core:
Markup::html()converts<blockquote>toBlockquote(a trailing<cite>or<footer>becomes the attribution) and<mark>to a highlight;<span style="background-color">keeps its colour inStyleMode::Original. - Laravel:
<x-doc::card>,<x-doc::callout>,<x-doc::blockquote>,<x-doc::section>,<x-doc::field>,<x-doc::choices>/<x-doc::choice>,<x-doc::key-value>/<x-doc::kv>and<x-doc::badge>, plus aresponsesexample and a "Decorators" section in the README. - Upgrade note: decorator style names (
card,card.title,callout.*,section.*,field.*,choice.*,key-value.*,badge.*,blockquote.*) are used by the decorators, and a theme or config style with the same name replaces the built-in one. If you already define a style calledcard, rename it or expect it to restyle<x-doc::card>. - Core:
Theme::withStyle()rejects a name containing whitespace, since it could never be resolved;Callout::tone()rejects internal whitespace; blank titles, numbers, labels and cites count as unset; a fully transparent span background paints nothing. - Core:
emandlhlengths in styles (padding="0.5em",min-height="4lh",Length::em(),Length::lh()):emis the element's font size andlhits line height, resolved to points when styles are computed. A relativefont-sizeis a share of the parent's. Page margins, table tracks and editor-HTML CSS stay absolute. - Decorators follow the theme's size:
Field::lines(n)reserves n lines of the field's own text inside its padding, choice markers are0.75emand centred on the first line of their label, and the field label, badge and blockquote cite use0.9eminstead of 9pt. - Core: a
responsesexample (examples/core/documents/responses.php), the PHP twin of the Blade one, built from all eight decorators. - A documentation site built with VuePress in
site/, deployed to GitHub Pages by.github/workflows/docs.yml. PhpPdfBackend(fontCacheFormat: FontStateCache::PHP)writes the font cache as PHP files that opcache keeps in shared memory, for services that render PDFs all day.
Fixed
- Laravel:
<x-doc::image :data="$bytes">now receives binary image data unchanged. Blade's escaping of bound attributes had replaced bytes that aren't valid UTF-8. Runphp artisan view:clearafter upgrading, since views compiled before the fix still escape it. - Laravel: bound and echoed attribute values reach tags as given. Blade escapes them on the way into a component's attributes, so
<x-doc::qr-code :value="$url">encoded&as&and a bound linkhreflost its query string. min-heightandmax-heightnow apply to boxes and text that size to their content in the PDF (the HTML export already had them). Amin-heightthat does not fit in the space left on a page moves the box to the next page (on an empty page it is cut to the page). A box withmax-height(and text with aheight) is kept whole and placed or moved, like a fixed-height box; only a cap taller than an empty page lets it split, so no content is cut off. Text also honours an explicitheight.- Page margins that meet or cross now throw
InvalidValue(kindpage margins) naming the page and the margins, in both PDF and HTML. Left and right margins wider than the page used to render one letter per line, and the HTML export rendered silently; top and bottom margins taller than the page blamed the header and footer. Checked at export, after the size and orientation are final. - Visual change:
Field::lines(n)now counts lines inside the padding, so a field withlinesis taller by its padding and border (lines="2"goes from 28pt to 40pt at the default size).