diff --git a/Cargo.lock b/Cargo.lock
index a375ae0..873ff3d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2581,7 +2581,7 @@ dependencies = [
[[package]]
name = "rustpad"
-version = "0.2.69"
+version = "0.2.72"
dependencies = [
"argon2",
"aws-config",
diff --git a/Cargo.toml b/Cargo.toml
index cb42325..afe2a3a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "rustpad"
-version = "0.2.69"
+version = "0.2.72"
edition = "2024"
rust-version = "1.94"
description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL"
diff --git a/README.md b/README.md
index c7a06a8..55375e6 100644
--- a/README.md
+++ b/README.md
@@ -51,6 +51,13 @@ The selected interface language is stored in the browser and can be changed from
- Alert blocks: `success`, `info`, `warning`, and `danger`.
- Table of contents generated with `[TOC]`, using headings after the marker and nesting them by level.
- Optional line numbers in fenced code blocks.
+- PDF export with selectable A5/A4/A3, Letter, 16:9, and 4:3 page formats; optional color/background preservation keeps syntax highlighting, alerts, quotes, highlights, and table styling; `---` creates a hard PDF page break.
+
+## PDF export and page breaks
+
+Use the **PDF** button in the editor header to choose the page format and margins, then generate a browser-native PDF print view. The export supports A5/A4/A3, Letter, 16:9, and 4:3 layouts. Enable **Preserve colors and backgrounds** to keep syntax highlighting, alerts, quotes, highlighted text, table headers, inline code, links, and other styled Markdown elements. The exporter re-applies Highlight.js to the print copy and uses a print-safe light color palette so code colors do not depend on the editor theme. The end of the exported document includes one subtle localized RustPad footer.
+
+A Markdown line containing only `---` is treated as a page break during PDF export. Insert it from **More → Page break (PDF)** or with `Ctrl/Cmd+Enter`. In the editor preview it remains visible as a dashed horizontal divider.
## Fenced code blocks and language aliases
diff --git a/lang/en.json b/lang/en.json
index 1cadf90..ffc219b 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -366,6 +366,32 @@
"editor.enablePage": "Enable Page",
"editor.enablePageTitle": "Enable or disable the published page",
"editor.extendedMarkdown": "Extended Markdown",
+ "editor.pdfPopupBlocked": "The PDF window was blocked by the browser.",
+ "editor.pdfExportFailed": "Could not prepare the PDF.",
+ "editor.a4Portrait": "A4 — Portrait",
+ "editor.a4Landscape": "A4 — Landscape",
+ "editor.a3Portrait": "A3 — Portrait",
+ "editor.a3Landscape": "A3 — Landscape",
+ "editor.a5Portrait": "A5 — Portrait",
+ "editor.letterPortrait": "Letter — Portrait",
+ "editor.letterLandscape": "Letter — Landscape",
+ "editor.exportPdf": "Export PDF",
+ "editor.generatePdf": "Generate PDF",
+ "editor.includeDocumentTitle": "Include document title",
+ "editor.margins": "Margins",
+ "editor.narrowMargin": "Narrow (8 mm)",
+ "editor.normalMargin": "Normal (12 mm)",
+ "editor.pageBreak": "Page break (PDF)",
+ "editor.pageBreakShortcut": "Page break (PDF) · Ctrl/Cmd+Enter",
+ "editor.pageFormat": "Page format",
+ "editor.pdfPageBreakHelp": "Use --- or Ctrl/Cmd+Enter to start a new PDF page.",
+ "editor.preservePdfColors": "Preserve colors and backgrounds",
+ "editor.pdfColorHelp": "Keeps syntax highlighting, alerts, quotes, highlights, table headers, and other colors in the PDF.",
+ "editor.pdfPrintHelp": "The browser print dialog will open. Choose Save as PDF.",
+ "editor.pdfGeneratedFooter": "Generated by RustPad",
+ "editor.presentation16x9": "16:9 — Presentation",
+ "editor.presentation4x3": "4:3 — Presentation",
+ "editor.wideMargin": "Wide (20 mm)",
"editor.failedMermaid": "Failed to load Mermaid.",
"editor.fileCount": "{count} files",
"editor.font": "Font",
diff --git a/lang/pl.json b/lang/pl.json
index 8ba3252..f3a2731 100644
--- a/lang/pl.json
+++ b/lang/pl.json
@@ -366,6 +366,32 @@
"editor.enablePage": "Włącz stronę",
"editor.enablePageTitle": "Włącz lub wyłącz opublikowaną stronę",
"editor.extendedMarkdown": "Rozszerzony Markdown",
+ "editor.pdfPopupBlocked": "Przeglądarka zablokowała okno eksportu PDF.",
+ "editor.pdfExportFailed": "Nie udało się przygotować PDF.",
+ "editor.a4Portrait": "A4 — pionowo",
+ "editor.a4Landscape": "A4 — poziomo",
+ "editor.a3Portrait": "A3 — pionowo",
+ "editor.a3Landscape": "A3 — poziomo",
+ "editor.a5Portrait": "A5 — pionowo",
+ "editor.letterPortrait": "Letter — pionowo",
+ "editor.letterLandscape": "Letter — poziomo",
+ "editor.exportPdf": "Eksport PDF",
+ "editor.generatePdf": "Generuj PDF",
+ "editor.includeDocumentTitle": "Dołącz tytuł dokumentu",
+ "editor.margins": "Marginesy",
+ "editor.narrowMargin": "Wąskie (8 mm)",
+ "editor.normalMargin": "Normalne (12 mm)",
+ "editor.pageBreak": "Podział strony (PDF)",
+ "editor.pageBreakShortcut": "Podział strony (PDF) · Ctrl/Cmd+Enter",
+ "editor.pageFormat": "Format strony",
+ "editor.pdfPageBreakHelp": "Użyj --- lub Ctrl/Cmd+Enter, aby rozpocząć nową stronę PDF.",
+ "editor.preservePdfColors": "Zachowaj kolory i tła",
+ "editor.pdfColorHelp": "Zachowuje w PDF kolorowanie składni, alerty, cytaty, wyróżnienia, nagłówki tabel i inne kolory.",
+ "editor.pdfPrintHelp": "Otworzy się okno drukowania przeglądarki. Wybierz Zapisz jako PDF.",
+ "editor.pdfGeneratedFooter": "Wygenerowano przez RustPad",
+ "editor.presentation16x9": "16:9 — prezentacja",
+ "editor.presentation4x3": "4:3 — prezentacja",
+ "editor.wideMargin": "Szerokie (20 mm)",
"editor.failedMermaid": "Nie udało się załadować Mermaid.",
"editor.fileCount": "{count} plików",
"editor.font": "Czcionka",
diff --git a/src/assets.rs b/src/assets.rs
index a351676..cbf72c2 100644
--- a/src/assets.rs
+++ b/src/assets.rs
@@ -34,6 +34,7 @@ const MODULES: &[&str] = &[
"note-files",
"preview-edit",
"preferences",
+ "pdf-export",
"render-queue",
"session",
"socket",
diff --git a/static/css/styles.css b/static/css/styles.css
index 3870e5c..152b1d7 100644
--- a/static/css/styles.css
+++ b/static/css/styles.css
@@ -1092,6 +1092,7 @@ textarea::selection {
padding: 0;
border: 0;
background: transparent;
+ line-height: inherit;
}
.markdown-body blockquote {
@@ -1110,6 +1111,10 @@ textarea::selection {
border-top: 1px solid var(--border);
}
+.markdown-body hr.page-break-marker {
+ border-top-style: dashed;
+}
+
.editor-footer {
display: flex;
align-items: center;
@@ -3326,7 +3331,7 @@ dialog::backdrop {
.preview-source-line::before {
content: attr(data-source-line);
position: absolute;
- top: 0;
+ top: var(--preview-line-top, 0px);
left: var(--preview-line-left, -50px);
width: 32px;
color: var(--gutter-text);
@@ -5358,14 +5363,16 @@ dialog::backdrop {
}
.markdown-body pre.code-with-lines code {
+ display: block;
counter-reset: none;
}
.markdown-body pre.code-with-lines .code-line {
display: block;
- min-height: 1.35em;
+ min-height: 1em;
padding-left: 4.6em;
position: relative;
+ line-height: inherit;
white-space: pre;
}
@@ -7993,7 +8000,7 @@ dialog::backdrop {
.public-content .public-permalink {
position: absolute;
- top: 0;
+ top: var(--preview-line-top, 0px);
left: var(--preview-line-left, -50px);
z-index: 3;
display: none;
@@ -8471,4 +8478,82 @@ dialog::backdrop {
.public-header__actions .header-preferences {
margin-left: auto;
}
-}
\ No newline at end of file
+}
+
+/* PDF export */
+#pdf-export-dialog {
+ width: min(520px, calc(100% - 28px));
+ max-width: 520px;
+}
+
+.pdf-export-panel {
+ position: relative;
+ gap: 16px;
+}
+
+.pdf-export-panel h2,
+.pdf-export-panel .dialog-copy,
+.pdf-export-panel .pdf-export-help {
+ margin: 0;
+}
+
+.pdf-export-fields {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(150px, .62fr);
+ gap: 12px;
+}
+
+.pdf-export-fields label {
+ display: grid;
+ gap: 7px;
+ min-width: 0;
+ color: var(--text-secondary);
+ font-size: .82rem;
+ font-weight: 650;
+}
+
+.pdf-export-fields .app-select-input,
+.pdf-export-fields .app-select {
+ width: 100%;
+}
+
+.pdf-export-options {
+ display: grid;
+ gap: 9px;
+}
+
+.pdf-export-title-toggle,
+.pdf-export-color-toggle {
+ width: fit-content;
+}
+
+.pdf-export-color-help,
+.pdf-export-help {
+ color: var(--muted);
+ font-size: .78rem;
+ line-height: 1.45;
+}
+
+.pdf-export-color-help {
+ margin: -6px 0 0 24px;
+}
+
+#pdf-export-dialog .dialog-actions .primary-button {
+ width: auto;
+ min-width: 132px;
+ margin: 0;
+}
+
+@media (max-width: 560px) {
+ .pdf-export-fields {
+ grid-template-columns: 1fr;
+ }
+
+ #pdf-export-dialog .dialog-actions {
+ align-items: stretch;
+ }
+
+ #pdf-export-dialog .dialog-actions > button {
+ flex: 1 1 0;
+ }
+}
diff --git a/static/editor.html b/static/editor.html
index a66d9c1..a30b859 100644
--- a/static/editor.html
+++ b/static/editor.html
@@ -42,7 +42,7 @@
+ data-format="superscript">Superscript