1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
//! Render server-side templates to downloadable PDF documents.
//!
//! [`Pdf`](crate::pdf::Pdf) turns an HTML string — typically a [`maud::Markup`] view you
//! already render on-screen — into a PDF document served with the right
//! `Content-Type: application/pdf` and `Content-Disposition` headers (via
//! [`Download`](crate::download::Download), which already handles RFC
//! 6266-safe filenames and the `inline`/`attachment` switch — see its module
//! docs for that half of the contract).
//!
//! # Quick example
//!
//! ```rust,no_run
//! use autumn_web::pdf::Pdf;
//! use autumn_web::prelude::*;
//!
//! #[get("/invoices/{id}/pdf")]
//! async fn invoice_pdf(id: Path<i64>) -> Pdf {
//! let markup = html! {
//! h1 { "Invoice #" (*id) }
//! p { "Total: $42.00" }
//! };
//! Pdf::from_markup(markup).filename("invoice.pdf")
//! }
//! ```
//!
//! # Not a CSS layout engine
//!
//! This renders a **deliberately small HTML subset** — headings, paragraphs,
//! tables, lists, `<strong>`/`<em>` emphasis, `<br>`/`<hr>` — flowed
//! top-to-bottom in a single column with the built-in PDF base-14 fonts
//! (Helvetica). It does **not** parse CSS, does not lay out `<div>`s as boxes
//! with padding/borders/floats, and does not do pixel-perfect text metrics —
//! that is out of scope by design (see issue #1317's "Out of Scope" section).
//! Any tag this renderer doesn't specifically recognize (`<div>`, `<span>`,
//! `<a>`, widget-generated wrapper markup, ...) is treated as a transparent
//! container: its text content still renders, just without special styling —
//! so a typical scaffold view degrades gracefully instead of dropping
//! content or erroring. `<script>`, `<style>`, `<noscript>`, `<template>`,
//! `<head>`, and `<title>` are the exception — their content is never
//! rendered as visible text, matching how a browser treats them, so passing
//! a full server-rendered page (not just a purpose-built fragment) to
//! [`Pdf::from_html`](crate::pdf::Pdf::from_html) doesn't leak inline CSS/JS source into the PDF.
//!
//! Text outside the base-14 fonts' WinAnsi encoding (CJK, emoji, ...) is
//! rendered as `?` by the underlying PDF writer rather than corrupting the
//! output — a known limitation of avoiding embedded font files (see the
//! "Runtime dependencies" section below). A single table cell whose content
//! wraps to more than a full page of lines is clipped at the bottom margin
//! rather than spilling onto a second page — realistic scaffold tables
//! (invoice line items, a handful of columns) never approach this.
//!
//! # Determinism
//!
//! Rendering the same HTML input always produces the same visible content:
//! [`extract_text`](crate::pdf::extract_text) (and the [`assert_pdf_contains`](crate::test::TestResponse::assert_pdf_contains)
//! test helper built on it) returns identical text for identical input, with
//! no wall-clock or other hidden state read during rendering — any
//! timestamp that should appear in the document (e.g. an invoice's "Generated
//! at" line) is the caller's responsibility to render into the HTML using
//! the injected [`Clock`](crate::time::Clock), not something this module
//! reads on its own.
//!
//! The **raw bytes** are not guaranteed to be identical between renders: the
//! underlying PDF writer ([`printpdf`]) assigns each document a random
//! trailer `/ID`, per the PDF spec's file-identification convention, and
//! that generator isn't exposed as configurable. Nothing else in the file
//! varies with identical input, but this rules out a byte-for-byte equality
//! assertion — text-content equality is the supported determinism contract.
//!
//! # Runtime dependencies
//!
//! Rendering uses [`printpdf`]'s core PDF writer with `default-features =
//! false` — no system-installed browser or renderer, and no embedded font
//! files: the base-14 fonts (Helvetica, Times, Courier, ...) are guaranteed
//! present in every PDF-compliant viewer, so nothing needs to ship inside
//! (or be downloaded by) your binary. This keeps PDF generation compatible
//! with the single-binary deployment story (issue #1004).
use ;
use crateDownload;
/// A PDF document rendered from an HTML string, ready to return from a
/// handler.
///
/// Construct with [`from_html`](Pdf::from_html) or (with the `maud` feature)
/// [`from_markup`](Pdf::from_markup), then chain [`filename`](Pdf::filename)
/// / [`inline`](Pdf::inline) and return it — it implements [`IntoResponse`].
///
/// See the [module docs](crate::pdf) for what HTML is supported.
/// [`extract_text`] could not read `bytes` back as a PDF.
;
/// Extract the visible text of a rendered PDF as one space-joined string —
/// enough to assert on with a plain substring check.
///
/// Backed by `printpdf`'s own PDF parser (via
/// [`PdfDocument::extract_text`](printpdf::PdfDocument::extract_text)), so it
/// reads back exactly what [`Pdf`] (or any other well-formed PDF) wrote,
/// rather than re-implementing PDF text extraction. `printpdf` returns one
/// chunk per text-showing operator rather than one per visual line — [`Pdf`]
/// draws each word with its own operator (to position bold/italic runs
/// independently), so this joins every chunk, across every page, with a
/// single space rather than trying to reconstruct line/page boundaries.
/// That means a phrase that happens to wrap across two lines is still found
/// as one contiguous substring, at the cost of not distinguishing "same
/// line" from "next line" in the returned string. It also means that two
/// *differently styled* words with no whitespace between them in the source
/// HTML (e.g. `$<strong>42.00</strong>`) render visually adjacent, with no
/// gap, but still come back from this function as two separate
/// space-joined chunks (`"$ 42.00"`) — match on the two pieces separately
/// (or drop styling at the boundary) rather than the single glued string.
///
/// # Errors
///
/// Returns [`PdfParseError`] if `bytes` is not a parseable PDF.