Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
All notable changes are recorded here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and the project follows [Semantic Versioning](https://semver.org/).

## [Unreleased] - 0.2.0
## [0.3.0] - unreleased

v0.1 was never published to Packagist, so there are no deprecation shims.
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)

Expand Down Expand Up @@ -39,7 +40,7 @@ Migration from v0.1:

### 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. One page setup from the first page applies to the whole document. 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. Corner radius on boxes, background and overlay layers, 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.17.
- 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.
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ composer --working-dir=packages/core install
composer --working-dir=packages/laravel install
```

The Laravel package's path repository pins core to `0.1.x-dev` (`options.versions` in its `composer.json`), so it
The Laravel and DOCX packages' path repositories pin core (and DOCX) to `0.3.x-dev` (`options.versions` in its `composer.json`), so it
resolves on any branch or detached checkout. Bump that pin together with core's `branch-alias` when the minor
version changes.

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ is small, strict and ours, and a typo in an attribute is an error that names the

**[Documentation](https://one-learning-community.github.io/papercut/)** · [Guide](https://one-learning-community.github.io/papercut/guide/) · [Laravel](https://one-learning-community.github.io/papercut/laravel/) · [Reference](https://one-learning-community.github.io/papercut/reference/blade-tags.html) · [Examples](https://one-learning-community.github.io/papercut/examples.html)

> **Status: 0.2, pre-release.** Not yet published to Packagist. See [Status](#status) below.
> **Status: 0.3, early release.** Before 1.0 a minor version (0.4) may change the API; patch versions won't. See [Status](#status) below.

## A Blade document

Expand Down Expand Up @@ -117,7 +117,7 @@ The guide lives at [one-learning-community.github.io/papercut](https://one-learn

## Status

0.2, pre-release, not yet published to Packagist. No performance or compatibility benchmarks have been
0.3, the first release on Packagist. Before 1.0 a minor version may change the API; patch versions won't. No performance or compatibility benchmarks have been
published; do not assume any. Requires PHP 8.4+ and Laravel 12 or 13 (CI tests against both).

Not supported yet: HarfBuzz shaping (the backend exposes no glyph-level drawing API; its built-in shaping covers kerning
Expand Down
3 changes: 3 additions & 0 deletions docs/superpowers/RULINGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,6 @@ Design: docs/superpowers/specs/2026-10-19-html-input-design.md. Each entry: what
- Ruling (pictures): styled QR codes go through Bacon's Imagick backend rather than through the SVG and Imagick's SVG reader — no dependency on which SVG delegate Imagick was built with — cost if wrong: none
- Ruling (small gaps): span backgrounds are written as run shading (`w:shd`) in their exact colour — php-docx writes a highlight value verbatim, so TextMapper writes a marker there and DocxPatcher turns it into `w:shd`, throwing if a marker is left — cost if wrong: none; a php-docx change fails loudly
- Ruling (small gaps): opacity multiplies down the tree and each faded colour is blended onto what lies beneath it (the nearest fill, faded or not, else white), not reported — the PDF fades each thing a node draws, so text in a faded card lands on the card's faded fill — cost if wrong: a faded node over a page background layer or an image blends onto the nearest fill instead
- Ruling (php-docx 1.18, sections): a new Word section starts only where the page setup, header, footer or layer pictures change; pages that look alike stay one section joined by page breaks, as before — existing documents keep their output, and Word's section breaks are where page breaks go wrong most — cost if wrong: none visible; more sections would read the same
- Ruling (php-docx 1.18, layers): layer pictures float from the section's header, so they repeat on every page like Papercut's layers; Word draws header content behind the body, so an overlay picture sits behind body text, documented not reported — anchoring in the body would place it on one page only, since Word, not Papercut, paginates — cost if wrong: an overlay stamp over text shows beneath it in Word
- Ruling (php-docx 1.18, layers): a background converts its pictures until the first node that isn't one; everything from there is dropped and reported, since its height in Word is unknown — cost if wrong: a picture under a text block in a background is left out
Original file line number Diff line number Diff line change
Expand Up @@ -384,5 +384,5 @@ v0.1 was never published to Packagist, so there are no deprecation shims. The ch
- *Done as `Markup::html()` and `<x-doc::markup>`, see `2026-10-19-html-input-design.md`:* **Raw HTML input** (`<x-doc::html>`): convert HTML into Papercut nodes once, when the tree is built, using php-pdf's `HtmlParser` and CSS cascade as the front end plus a translator from its elements (`Paragraph`, `Run`, `Table`, `ListNode`) to `Text`/`Span`/`Table`. The result works in every format. It reverses v0.1's "no HTML parser" stance, so it needs its own spec. It would likely also bring a list node.
- **Font embedding in DOCX** (upstream).
- **An editable DOCX mode** (fewer layout tables, more native paragraphs), as a contribution.
- **Page background in DOCX** as an anchored shape behind the text.
- *Done for pictures (php-docx 1.18):* **Page background in DOCX** as an anchored shape behind the text.
- *Done in 0.2 for PDF (DOCX and HTML anchors not yet):* **Bookmarks** (moved from v0.2 to v0.3), mapping naturally to PDF outlines, DOCX heading styles and HTML anchors.
2 changes: 1 addition & 1 deletion packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ There is no browser and no CSS cascade.

**Documentation:** [one-learning-community.github.io/papercut](https://one-learning-community.github.io/papercut/guide/)

> **Status: 0.2, pre-release.** Not yet published to Packagist. Using Laravel? See
> **Status: 0.3, early release.** Before 1.0 a minor version may change the API. Using Laravel? See
> [`papercut/laravel`](../laravel) for the Blade front end of this same engine.

## Requirements
Expand Down
2 changes: 1 addition & 1 deletion packages/core/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
},
"extra": {
"branch-alias": {
"dev-main": "0.1.x-dev"
"dev-main": "0.3.x-dev"
}
}
}
14 changes: 8 additions & 6 deletions packages/docx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@ Word's paragraphs and tables; php-docx writes the file. Thank you.

**Documentation:** [Word export](https://one-learning-community.github.io/papercut/guide/word-export.html)

> **Status: experimental, pre-release. Not yet published to Packagist.** Word, LibreOffice and Google Docs lay out
> **Status: experimental.** Word, LibreOffice and Google Docs lay out
> the file themselves, so spacing and page breaks can differ slightly from the PDF, and the output may change between
> minor versions while it settles.

## Install

Once published:

```
composer require papercut/docx
```
Expand All @@ -44,14 +42,18 @@ Word, LibreOffice and Google Docs lay out the pages themselves, so page breaks c
the PDF's. Columns flow as paragraphs, rows become borderless tables at the widths the PDF uses, and a box with
padding, a border or a background becomes a one-cell table.

- One page setup for the whole document, taken from the first page; other pages' sizes, headers and footers are
reported as approximated.
- Each run of pages with the same size, margins, header, footer and layers becomes a Word section, so pages
keep their own setup and headers.
- 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 place the PDF draws it, on every page. Word draws it with the header, so an overlay sits
behind the body's text.
- Fonts are named, not embedded: the reader's machine supplies them.
- Pictures Word can't take as they are are drawn at the size the PDF gives them: WebP and AVIF images, image
`cover` and opacity, and plain QR codes need `ext-gd`; styled QR codes need `ext-imagick` and
`bacon/bacon-qr-code`; SVG needs an `ext-imagick` that reads SVG. Without them, these are left out and reported.
- Opacity is drawn by blending each faded colour onto what lies beneath it; text backgrounds keep their exact colour.
- Corner radius on boxes, background and overlay layers, PDF page templates
- Corner radius on boxes, other content in background and overlay layers, a border or background on a layer
picture, PDF page templates
and embedded PDF pages are left out and reported (strict mode throws on them).

Approximations and omissions are reported through `ExportOptions::onWarning` with the node's path, as with HTML
Expand Down
6 changes: 3 additions & 3 deletions packages/docx/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@
"options": {
"symlink": true,
"versions": {
"papercut/core": "0.1.x-dev"
"papercut/core": "0.3.x-dev"
}
}
}
],
"require": {
"php": "^8.4",
"ext-zip": "*",
"dskripchenko/php-docx": "^1.17",
"papercut/core": "^0.1"
"dskripchenko/php-docx": "^1.18",
"papercut/core": "^0.3"
},
"require-dev": {
"bacon/bacon-qr-code": "^3.1",
Expand Down
Loading
Loading