Papercut
Guide
Laravel
Reference
Examples
Changelog
GitHub
Guide
Laravel
Reference
Examples
Changelog
GitHub
  • Start

    • Introduction
    • Installation
    • Your first document
  • Building documents

    • Layout
    • Text
    • Tables
    • Pages and pagination
    • Images, SVG, links and QR codes
    • Decorators
  • Look and output

    • Styles and themes
    • Fonts
    • Rich text
    • HTML export
    • Fonts in HTML
    • Word export
    • Passwords, signatures and merging

Rich text

Sometimes the content isn't in your template. It's HTML saved from a rich text editor, or plain text someone typed into a form. Markup converts either one into Papercut components. It handles a practical subset of HTML, and tells you about anything it couldn't convert. There's no browser involved.

Blade
<x-doc::markup :html="$slide->body" />
<x-doc::markup :text="$prompt" />
<x-doc::markup :html="$slide->body" styles="original" blank-paragraphs="keep" />
PHP
use Papercut\{Column, Document, Page};
use Papercut\Markup\{Markup, MarkupOptions, StyleMode};
use function Papercut\pt;

$slide = '<h2>Agenda</h2><p>Welcome to <b>Papercut</b>.</p><ol start="3"><li>Intro</li><li>Demo</li></ol>';

$document = Document::make(Page::a4()->body(
    Column::make(
        Markup::html($slide, new MarkupOptions(styles: StyleMode::Theme)),
        Markup::text("Line one\nLine two\n\nA new paragraph."),
    )->gap(pt(8)),
));

In Blade, bind the value with :html rather than html, otherwise Blade escapes the HTML before Papercut sees it.

Markup::text() turns each newline into a line break and each blank line into a new paragraph. Characters like < are treated as plain text.

Inside a line of text

Put markup inside a text tag and it joins that line, picking up the surrounding style:

<x-doc::text bold>× <x-doc::markup :html="$label" /></x-doc::text>

In PHP, Markup::flatten() does the same thing and merges the converted content into a single Text. Block-level content gets flattened and images are dropped, and both are reported.

What gets converted

HTMLBecomes
p, diva paragraph
h1 to h6the theme's headings (h5 and h6 use the h4 style)
b, strong, i, em, u, s, strike, del, span, brinline formatting
a hrefa link, for http, https, mailto, tel and #fragment URLs
ol, ul, lia list, including start and type on ol and value on li
imgan image, loaded by your resolver (see below)
figure, figcaptionthe content with its caption underneath
iframe, video, audioa paragraph linking to the source
other block tags, like blockquote, pre, table and hrthe tag is dropped, and its content kept as paragraphs
anything elsethe tag is dropped, and its content kept inline

script, style, template, head, noscript, object and embed are removed along with everything inside them. Classes, ids and data-* attributes are ignored. Empty paragraphs are dropped unless you ask to keep them.

Style modes

  • theme (the default): the HTML provides the structure, and your theme decides how it looks. Inline style="…" attributes are ignored.
  • original: inline styles are applied on top of the theme. Colour, font size, weight, italic and text decoration work everywhere. Background, alignment, margin, padding and sizes work on block elements. Anything else is dropped and reported.

Images

Papercut never downloads an <img src> by itself. Instead, you give it a resolver: a function that receives the src exactly as written and returns the image data, or null if the image can't be found.

use Papercut\{Document, Page};
use Papercut\Markup\{Markup, MarkupOptions};

$png = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==');
$markup = new MarkupOptions(images: fn (string $src): ?string => $src === 'logo.png' ? $png : null);

$document = Document::make(Page::a4()->body(
    Markup::html('<p><img src="logo.png" width="40"></p><p><img src="gone.png" alt="Missing logo"></p>', $markup),
));

When an image is missing, its alt text is shown in italics. In Laravel, set papercut.markup.images to a resolver class. The Laravel configuration page has a full example.

Treat src as user input

Your resolver decides which files can be read. Don't pass src straight to the filesystem or a storage disk. Check it against the paths you expect, and reject anything containing ...

Conversion warnings

Everything Markup couldn't convert exactly is recorded, and those notes are passed to the warning callback and the fidelity report of each export. There are two kinds:

  • Approximated: the content is there, but looks different from the original. For example, an iframe shown as a link, a missing image shown as its alt text, or an unknown tag removed with its content kept.
  • Dropped: the content is gone. For example, a missing image with no alt text, a link with an unsupported URL, or an unsupported CSS property.

Strict mode

If you'd rather fail than silently lose content, turn on strict mode with strict: true in the export options, or 'strict' => true in the Laravel config. An export that would drop content then throws UnsupportedFeature instead. Approximations are still allowed.

Strict mode works well in tests: a template that starts losing content fails the build instead of shipping.

Edit this page on GitHub
Last Updated: 10/21/26, 1:31 PM
Prev
Fonts
Next
HTML export