@voidberg/quarto 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021-2026 Alexandru Badiu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,157 @@
1
+ ![Quarto](assets/quarto.png)
2
+
3
+ # Quarto
4
+
5
+ [![npm](https://img.shields.io/npm/v/@voidberg/quarto.svg)](https://www.npmjs.com/package/@voidberg/quarto)
6
+ [![JSR](https://jsr.io/badges/@voidberg/quarto)](https://jsr.io/@voidberg/quarto)
7
+ [![CI](https://github.com/voidberg/quarto/actions/workflows/ci.yml/badge.svg)](https://github.com/voidberg/quarto/actions/workflows/ci.yml)
8
+ [![MIT License](https://img.shields.io/npm/l/@voidberg/quarto.svg)](https://opensource.org/licenses/MIT)
9
+
10
+ > a book size of about 9½ × 12 inches (24 × 30 centimetres), determined by folding printed sheets twice to form four leaves or eight pages.
11
+
12
+ Generate **EPUB3** and Kobo **kepub** files from HTML — entirely in memory, with no native dependencies and no external binaries.
13
+
14
+ ```ts
15
+ import { writeFile } from "node:fs/promises";
16
+ import { generateEpub, toKepub } from "@voidberg/quarto";
17
+
18
+ const epub = await generateEpub({
19
+ title: "On the Shortness of Life",
20
+ author: "Seneca",
21
+ chapters: [{ title: "I", html: "<p>It is not that we have a short time to live…</p>" }],
22
+ });
23
+
24
+ await writeFile("seneca.epub", epub); // or Deno.writeFile, Bun.write…
25
+ await writeFile("seneca.kepub.epub", toKepub(epub));
26
+ ```
27
+
28
+ ## Why another EPUB library?
29
+
30
+ Quarto came out of specific needs in my own projects (like [instakobo](https://github.com/voidberg/instakobo)):
31
+
32
+ - skip the table of contents, which for articles and newsletters is just noise
33
+ - generate kepubs without relying on kepubify
34
+ - work in the browser
35
+
36
+ ## Features
37
+
38
+ - Valid EPUB3 (verified against [EPUBCheck](https://www.w3.org/publishing/epubcheck/) in CI)
39
+ - Optional table of contents (`includeToc`)
40
+ - Native kepub conversion (`toKepub`) — Kobo reading-location spans, no binary needed
41
+ - In-memory: returns a `Uint8Array`, never touches the filesystem
42
+ - Runtime-agnostic: Node, Deno, Bun, and the browser (Web APIs + [`fflate`](https://github.com/101arrowz/fflate))
43
+ - Re-serializes messy HTML into well-formed XHTML for you
44
+ - Downloads and embeds remote images so the book is self-contained
45
+ - Modern **ESM-only** (Node ≥ 18; `require()`-able from CommonJS on Node ≥ 20.19 / 22)
46
+
47
+ ## Used by
48
+
49
+ - [instakobo](https://github.com/voidberg/instakobo) — Read and annotate (and sync back) your Instapaper articles on your Kobo device
50
+ - reSafari — Send webpages to your reMarkable tablet from Safari
51
+
52
+ ## Install
53
+
54
+ ```sh
55
+ npm install @voidberg/quarto # npm / pnpm / yarn
56
+ deno add jsr:@voidberg/quarto # Deno (JSR)
57
+ ```
58
+
59
+ ## API
60
+
61
+ ### `generateEpub(input): Promise<Uint8Array>`
62
+
63
+ | Option | Type | Default | Notes |
64
+ |---------------------|------------------------|-----------------------|-------|
65
+ | `title` | `string` | — | Required. |
66
+ | `chapters` | `Chapter[]` | — | Required, at least one. |
67
+ | `author` | `string \| string[]` | — | One or many creators. |
68
+ | `includeToc` | `boolean` | `true` | `false` ⇒ no visible TOC page. |
69
+ | `tocTitle` | `string` | `"Table of Contents"` | Heading on the TOC page. |
70
+ | `cover` | `string \| Uint8Array` | — | URL or raw bytes; generates a cover page. |
71
+ | `coverFromLeadImage`| `boolean` | `false` | Promote a chapter's leading image to the cover (see below). |
72
+ | `coverBackground` | `string` | reader default | CSS colour filling the cover's letterbox bands. |
73
+ | `language` | `string` | `"en"` | BCP-47 tag. |
74
+ | `css` | `string \| false` | bundled stylesheet | `false` ships no CSS. |
75
+ | `downloadImages` | `boolean` | `true` | Embed remote `<img>` sources. |
76
+ | `transformImage` | `ImageTransform` | — | Rewrite each image before embedding (see below). |
77
+ | `transformCover` | `CoverTransform` | — | Compose/replace the cover before embedding (see below). |
78
+ | `publisher` | `string` | — | |
79
+ | `description` | `string` | — | |
80
+ | `date` | `string` (ISO-8601) | — | Pass for reproducible builds. |
81
+ | `id` | `string` | derived (stable UUID) | Unique book identifier. |
82
+ | `fetch` | `typeof fetch` | global `fetch` | Override for proxies/testing. |
83
+
84
+ A **`Chapter`** is `{ title, html, excludeFromToc?, author?, insertTitle? }`. `html` is an HTML fragment — it does not need to be well-formed; Quarto parses and re-serializes it as valid XHTML. Set `insertTitle: false` to suppress the auto-generated `<h1>` heading and render only your markup.
85
+
86
+ ### `toKepub(epub: Uint8Array): Uint8Array`
87
+
88
+ Converts an EPUB (such as the output of `generateEpub`) into a Kobo kepub: every content document is rewritten with `koboSpan` reading-location markers and Kobo's `book-columns` / `book-inner` wrappers. Write the result with a `.kepub.epub` extension. No `kepubify` binary required.
89
+
90
+ ### `DEFAULT_CSS: string`
91
+
92
+ The bundled stylesheet, exported so you can extend rather than replace it.
93
+
94
+ ### `imageSize(bytes, mime): { width, height } | undefined`
95
+
96
+ Reads an image's pixel dimensions straight from its header — no decoding, no native deps. Supports PNG, GIF and JPEG; returns `undefined` for anything else or malformed data. Handy inside a `transformCover` to make layout decisions.
97
+
98
+ ## Images & covers
99
+
100
+ By default every `<img>` source (and the `cover`) is downloaded and embedded so the book is self-contained. Two hooks let you customize what gets stored:
101
+
102
+ - **`transformImage(image)`** runs on each fetched image *before* the core-media-type check, so it can transcode an unsupported format (e.g. WebP/AVIF → PNG for older e-readers), resize, or return `null` to drop the image.
103
+ - **`transformCover(cover, meta)`** runs after the cover source is downloaded and passed through `transformImage`. It's called **even when there's no cover source**, so you can compose a designed cover from `meta.title` / `meta.author` alone. Return `null` for no cover.
104
+
105
+ Both receive/return `RawImage` (`{ data: Uint8Array; mime: string }`) and may be async.
106
+
107
+ ```ts
108
+ import { generateEpub, imageSize, type CoverTransform } from "@voidberg/quarto";
109
+
110
+ const brandCover: CoverTransform = (cover, meta) => {
111
+ if (cover && imageSize(cover.data, cover.mime)) return cover; // usable as-is
112
+ return renderCover(meta.title, meta.author); // your designer → RawImage
113
+ };
114
+
115
+ await generateEpub({
116
+ title: "Field Notes",
117
+ coverFromLeadImage: true, // no cover? promote the article's leading image
118
+ coverBackground: "#f4f1ea", // blend the letterbox bands into the artwork
119
+ transformCover: brandCover,
120
+ chapters: [{ title: "Field Notes", html }],
121
+ });
122
+ ```
123
+
124
+ `coverFromLeadImage` only promotes an image that appears **before any text** in the first chapter (and removes it from the body so it isn't shown twice); images that follow text are left in place.
125
+
126
+ ## Example: no table of contents
127
+
128
+ ```ts
129
+ const epub = await generateEpub({
130
+ title: "A Single Essay",
131
+ includeToc: false,
132
+ chapters: [{ title: "Essay", html: essayHtml, excludeFromToc: true }],
133
+ });
134
+ ```
135
+
136
+ ## Development
137
+
138
+ ```sh
139
+ npm install
140
+ npm test # vitest
141
+ npm run build # tsc → dist (ESM + d.ts)
142
+ npm run typecheck
143
+ npm run validate # EPUBCheck (needs Java + EPUBCHECK_JAR)
144
+ ```
145
+
146
+ EPUBCheck runs in CI against generated fixtures to guarantee spec compliance.
147
+
148
+ ## Thanks to
149
+
150
+ - [epub-gen](https://github.com/cyrilis/epub-gen) and [epub-gen-memory](https://github.com/cpiber/epub-gen-memory) for inspiration.
151
+ - [kepubify](https://github.com/pgaskin/kepubify) for documenting the kepub transform.
152
+ - [epub-css-starter-kit](https://github.com/mattharrison/epub-css-starter-kit) for the styles.
153
+ - [Freepik – Flaticon](https://www.flaticon.com/free-icons/book) for the logo.
154
+
155
+ ## License
156
+
157
+ [MIT](./LICENSE) © Alexandru Badiu
Binary file
package/dist/css.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Default EPUB stylesheet. Adapted from the public-domain
3
+ * epub-css-starter-kit (https://github.com/mattharrison/epub-css-starter-kit) —
4
+ * conservative defaults that render well across e-readers, Kobo included.
5
+ */
6
+ export declare const DEFAULT_CSS: string;
package/dist/css.js ADDED
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Default EPUB stylesheet. Adapted from the public-domain
3
+ * epub-css-starter-kit (https://github.com/mattharrison/epub-css-starter-kit) —
4
+ * conservative defaults that render well across e-readers, Kobo included.
5
+ */
6
+ export const DEFAULT_CSS = `@charset "UTF-8";
7
+
8
+ html, body {
9
+ margin: 0;
10
+ padding: 0;
11
+ }
12
+
13
+ body {
14
+ font-family: serif;
15
+ line-height: 1.5;
16
+ text-align: justify;
17
+ padding: 0 1em;
18
+ widows: 2;
19
+ orphans: 2;
20
+ }
21
+
22
+ h1, h2, h3, h4, h5, h6 {
23
+ font-family: sans-serif;
24
+ line-height: 1.2;
25
+ text-align: left;
26
+ page-break-after: avoid;
27
+ break-after: avoid;
28
+ -webkit-hyphens: none;
29
+ hyphens: none;
30
+ }
31
+
32
+ h1 {
33
+ font-size: 1.6em;
34
+ margin: 1em 0 0.6em;
35
+ }
36
+
37
+ h2 { font-size: 1.4em; }
38
+ h3 { font-size: 1.2em; }
39
+
40
+ p {
41
+ margin: 0;
42
+ text-indent: 1.2em;
43
+ }
44
+
45
+ p:first-of-type,
46
+ h1 + p,
47
+ h2 + p,
48
+ h3 + p,
49
+ blockquote p:first-child {
50
+ text-indent: 0;
51
+ }
52
+
53
+ a { color: inherit; text-decoration: underline; }
54
+
55
+ img {
56
+ max-width: 100%;
57
+ height: auto;
58
+ }
59
+
60
+ figure { margin: 1em 0; text-align: center; }
61
+ figcaption { font-size: 0.85em; font-style: italic; text-align: center; }
62
+
63
+ blockquote {
64
+ margin: 1em 1.5em;
65
+ font-style: italic;
66
+ }
67
+
68
+ pre, code, kbd, samp {
69
+ font-family: monospace;
70
+ white-space: pre-wrap;
71
+ }
72
+
73
+ pre {
74
+ font-size: 0.85em;
75
+ margin: 1em 0;
76
+ overflow-wrap: break-word;
77
+ }
78
+
79
+ hr {
80
+ border: none;
81
+ border-top: 1px solid currentColor;
82
+ margin: 1.5em auto;
83
+ width: 25%;
84
+ opacity: 0.4;
85
+ }
86
+
87
+ ul, ol { margin: 1em 0; padding-left: 1.5em; }
88
+
89
+ table { border-collapse: collapse; margin: 1em 0; }
90
+ th, td { border: 1px solid currentColor; padding: 0.3em 0.6em; }
91
+
92
+ .quarto-title { text-align: left; }
93
+ .quarto-author {
94
+ font-family: sans-serif;
95
+ font-size: 0.9em;
96
+ font-style: italic;
97
+ margin: 0 0 1.5em;
98
+ }
99
+
100
+ .quarto-cover {
101
+ margin: 0;
102
+ padding: 0;
103
+ text-align: center;
104
+ height: 100%;
105
+ page-break-after: always;
106
+ }
107
+ .quarto-cover img { max-width: 100%; max-height: 100%; }
108
+ `;
109
+ //# sourceMappingURL=css.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"css.js","sourceRoot":"","sources":["../src/css.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsGlC,CAAC"}
package/dist/epub.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import type { EpubInput } from "./types.js";
2
+ /**
3
+ * Generate a complete, valid EPUB3 publication in memory from HTML chapters.
4
+ *
5
+ * The returned bytes are a ready-to-write `.epub`. Pass the result to
6
+ * {@link toKepub} to produce a Kobo-optimised `.kepub.epub`.
7
+ */
8
+ export declare function generateEpub(input: EpubInput): Promise<Uint8Array>;
package/dist/epub.js ADDED
@@ -0,0 +1,232 @@
1
+ import { DEFAULT_CSS } from "./css.js";
2
+ import { HtmlFragment } from "./html.js";
3
+ import { embedImages, fetchImage } from "./images.js";
4
+ import { buildNav, buildNcx, buildOpf, buildSvgCover, buildXhtml, CONTAINER_XML, } from "./opf.js";
5
+ import { deterministicUuid, encodeUtf8, escapeXml, extFromMime, imageSize, isCoreImageType, } from "./util.js";
6
+ import { zipEpub } from "./zip.js";
7
+ /**
8
+ * Generate a complete, valid EPUB3 publication in memory from HTML chapters.
9
+ *
10
+ * The returned bytes are a ready-to-write `.epub`. Pass the result to
11
+ * {@link toKepub} to produce a Kobo-optimised `.kepub.epub`.
12
+ */
13
+ export async function generateEpub(input) {
14
+ if (!input.chapters?.length) {
15
+ throw new Error("generateEpub: at least one chapter is required.");
16
+ }
17
+ const opts = resolveOptions(input);
18
+ const authors = normalizeAuthors(input.author);
19
+ const id = input.id ?? deterministicUuid(`${input.title} ${authors.join(",")}`);
20
+ const ctx = { files: {}, manifest: [], spine: [] };
21
+ ctx.files["META-INF/container.xml"] = encodeUtf8(CONTAINER_XML);
22
+ addStylesheet(ctx, opts.css);
23
+ const fragments = input.chapters.map((ch) => HtmlFragment.parse(ch.html));
24
+ // No explicit cover? Optionally promote the first chapter's lead image.
25
+ const coverSource = input.cover ?? (opts.coverFromLeadImage ? fragments[0].extractLeadImage() : undefined);
26
+ const cover = await addCover(ctx, coverSource, input.title, authors, opts);
27
+ await embedChapterImages(ctx, fragments, opts);
28
+ const navEntries = addChapters(ctx, input.chapters, fragments, opts);
29
+ const ncxId = addNavigation(ctx, input.chapters, navEntries, opts, cover.hasCoverPage, id, input.title);
30
+ // EPUB2 guide (cover + reading start) for readers that rely on it.
31
+ const guide = [];
32
+ if (cover.hasCoverPage)
33
+ guide.push({ type: "cover", href: "text/cover.xhtml", title: "Cover" });
34
+ guide.push({ type: "text", href: "text/chapter-1.xhtml", title: "Start" });
35
+ ctx.files["EPUB/content.opf"] = encodeUtf8(buildOpf({
36
+ id,
37
+ title: input.title,
38
+ authors,
39
+ language: opts.language,
40
+ publisher: input.publisher,
41
+ description: input.description,
42
+ date: input.date,
43
+ coverImageId: cover.coverImageId,
44
+ }, ctx.manifest, ctx.spine, { ncxId, guide }));
45
+ return zipEpub(ctx.files);
46
+ }
47
+ function resolveOptions(input) {
48
+ const css = input.css === false ? undefined : (input.css ?? DEFAULT_CSS);
49
+ return {
50
+ language: input.language ?? "en",
51
+ includeToc: input.includeToc ?? true,
52
+ downloadImages: input.downloadImages ?? true,
53
+ tocTitle: input.tocTitle ?? "Table of Contents",
54
+ fetcher: input.fetch ?? globalThis.fetch,
55
+ coverFromLeadImage: input.coverFromLeadImage ?? false,
56
+ coverBackground: input.coverBackground,
57
+ transformImage: input.transformImage,
58
+ transformCover: input.transformCover,
59
+ css,
60
+ cssHref: css ? "../style.css" : undefined,
61
+ };
62
+ }
63
+ /** Write the stylesheet (when enabled) and register it in the manifest. */
64
+ function addStylesheet(ctx, css) {
65
+ if (!css)
66
+ return;
67
+ ctx.files["EPUB/style.css"] = encodeUtf8(css);
68
+ ctx.manifest.push({ id: "css", href: "style.css", mediaType: "text/css" });
69
+ }
70
+ /**
71
+ * Resolve and embed the cover. The source image (if any) is downloaded and run
72
+ * through {@link ResolvedOptions.transformImage}; then {@link ResolvedOptions.transformCover}
73
+ * gets the final say (even with no source image, so it can compose one from
74
+ * metadata). The result is embedded as the cover image + a dedicated cover page
75
+ * (SVG-wrapped for reliable scaling) at the front of the spine. Any failure is
76
+ * swallowed — a missing cover must never abort the build.
77
+ */
78
+ async function addCover(ctx, coverSource, title, authors, opts) {
79
+ // 1. Download + transcode the source image, if there is one.
80
+ let img = null;
81
+ if (coverSource) {
82
+ try {
83
+ const fetched = await fetchImage(coverSource, opts.fetcher);
84
+ const transformed = opts.transformImage ? await opts.transformImage(fetched) : fetched;
85
+ // Only keep a cover whose type EPUB can carry without a fallback.
86
+ if (transformed && isCoreImageType(transformed.mime))
87
+ img = transformed;
88
+ }
89
+ catch {
90
+ // Leave img null; transformCover may still compose one.
91
+ }
92
+ }
93
+ // 2. Let the caller shape the final cover (compose with title, normalise, …).
94
+ if (opts.transformCover) {
95
+ try {
96
+ img = await opts.transformCover(img, { title, author: authors[0] });
97
+ }
98
+ catch {
99
+ // Fall back to the un-composed image (or none) rather than aborting.
100
+ }
101
+ }
102
+ if (!img)
103
+ return { hasCoverPage: false };
104
+ // 3. Embed the cover image + cover page.
105
+ const coverPath = `images/cover.${extFromMime(img.mime)}`;
106
+ ctx.files[`EPUB/${coverPath}`] = img.data;
107
+ ctx.manifest.push({
108
+ id: "cover-image",
109
+ href: coverPath,
110
+ mediaType: img.mime,
111
+ properties: "cover-image",
112
+ });
113
+ // Prefer an SVG-wrapped cover (fills + centers reliably on e-readers); fall
114
+ // back to a plain <img> only when we can't read the image's dimensions.
115
+ const coverHref = `../${coverPath}`;
116
+ const dims = imageSize(img.data, img.mime);
117
+ if (dims) {
118
+ ctx.files["EPUB/text/cover.xhtml"] = encodeUtf8(buildSvgCover({
119
+ title,
120
+ language: opts.language,
121
+ imageHref: coverHref,
122
+ width: dims.width,
123
+ height: dims.height,
124
+ background: opts.coverBackground,
125
+ }));
126
+ ctx.manifest.push({
127
+ id: "cover-page",
128
+ href: "text/cover.xhtml",
129
+ mediaType: "application/xhtml+xml",
130
+ properties: "svg",
131
+ });
132
+ }
133
+ else {
134
+ const coverBody = ` <div class="quarto-cover" epub:type="cover">\n <img src="${coverHref}" alt="${escapeXml(title)}"/>\n </div>`;
135
+ ctx.files["EPUB/text/cover.xhtml"] = encodeUtf8(buildXhtml({ title, language: opts.language, cssHref: opts.cssHref, bodyHtml: coverBody }));
136
+ ctx.manifest.push({
137
+ id: "cover-page",
138
+ href: "text/cover.xhtml",
139
+ mediaType: "application/xhtml+xml",
140
+ });
141
+ }
142
+ ctx.spine.push("cover-page");
143
+ return { coverImageId: "cover-image", hasCoverPage: true };
144
+ }
145
+ /**
146
+ * Resolve the images referenced by the chapter fragments. When downloading,
147
+ * fetch and embed them (rewriting each `<img>` to its in-package path); when not,
148
+ * drop external references so only self-contained `data:` images survive.
149
+ */
150
+ async function embedChapterImages(ctx, fragments, opts) {
151
+ if (!opts.downloadImages) {
152
+ for (const f of fragments)
153
+ f.stripExternalImages();
154
+ return;
155
+ }
156
+ const allUrls = fragments.flatMap((f) => f.imageSources());
157
+ const { images, rewrites } = await embedImages(allUrls, opts.fetcher, opts.transformImage);
158
+ // Chapter files live in EPUB/text/, images in EPUB/images/.
159
+ const chapterRewrites = new Map();
160
+ for (const [url, path] of rewrites)
161
+ chapterRewrites.set(url, `../${path}`);
162
+ for (const f of fragments)
163
+ f.resolveImages(chapterRewrites, true);
164
+ for (const img of images) {
165
+ ctx.files[`EPUB/${img.path}`] = img.data;
166
+ ctx.manifest.push({ id: img.id, href: img.path, mediaType: img.mime });
167
+ }
168
+ }
169
+ /** Write one XHTML document per chapter; return the TOC entries to surface. */
170
+ function addChapters(ctx, chapters, fragments, opts) {
171
+ const navEntries = [];
172
+ chapters.forEach((chapter, i) => {
173
+ const fileId = `chapter-${i + 1}`;
174
+ const href = `text/${fileId}.xhtml`;
175
+ const heading = chapter.insertTitle === false ? "" : titleBlock(chapter.title, chapter.author);
176
+ const bodyHtml = `${heading}${fragments[i].serialize()}`;
177
+ ctx.files[`EPUB/${href}`] = encodeUtf8(buildXhtml({
178
+ title: chapter.title,
179
+ language: opts.language,
180
+ cssHref: opts.cssHref,
181
+ bodyHtml: ` ${bodyHtml}`,
182
+ }));
183
+ ctx.manifest.push({ id: fileId, href, mediaType: "application/xhtml+xml" });
184
+ ctx.spine.push(fileId);
185
+ if (!chapter.excludeFromToc)
186
+ navEntries.push({ href, title: chapter.title });
187
+ });
188
+ return navEntries;
189
+ }
190
+ /**
191
+ * Emit the navigation document (always — EPUB3 requires one) plus, for a visible
192
+ * TOC, the spine entry and NCX. Returns the NCX id, or `undefined` when hidden.
193
+ */
194
+ function addNavigation(ctx, chapters, navEntries, opts, hasCoverPage, id, title) {
195
+ // A visible TOC requires includeToc AND at least one non-excluded chapter.
196
+ const showToc = opts.includeToc && navEntries.length > 0;
197
+ // The nav doc must reference content even when hidden, so fall back to all chapters.
198
+ const navList = navEntries.length
199
+ ? navEntries
200
+ : chapters.map((ch, i) => ({ href: `text/chapter-${i + 1}.xhtml`, title: ch.title }));
201
+ // Landmarks let readers jump to the cover / reading start.
202
+ const landmarks = [];
203
+ if (hasCoverPage)
204
+ landmarks.push({ type: "cover", href: "text/cover.xhtml", title: "Cover" });
205
+ landmarks.push({ type: "bodymatter", href: "text/chapter-1.xhtml", title: "Start" });
206
+ if (showToc)
207
+ landmarks.push({ type: "toc", href: "nav.xhtml", title: opts.tocTitle });
208
+ ctx.files["EPUB/nav.xhtml"] = encodeUtf8(buildNav(opts.tocTitle, opts.language, navList, !showToc, landmarks));
209
+ ctx.manifest.push({
210
+ id: "nav",
211
+ href: "nav.xhtml",
212
+ mediaType: "application/xhtml+xml",
213
+ properties: "nav",
214
+ });
215
+ if (!showToc)
216
+ return undefined;
217
+ // Reading order: cover (if any) → TOC page → chapters.
218
+ ctx.spine.splice(hasCoverPage ? 1 : 0, 0, "nav");
219
+ ctx.files["EPUB/toc.ncx"] = encodeUtf8(buildNcx(id, title, navList));
220
+ ctx.manifest.push({ id: "ncx", href: "toc.ncx", mediaType: "application/x-dtbncx+xml" });
221
+ return "ncx";
222
+ }
223
+ function normalizeAuthors(author) {
224
+ if (!author)
225
+ return [];
226
+ return (Array.isArray(author) ? author : [author]).filter((a) => a.trim().length > 0);
227
+ }
228
+ function titleBlock(title, author) {
229
+ const authorLine = author ? `\n <p class="quarto-author">${escapeXml(author)}</p>` : "";
230
+ return `<h1 class="quarto-title">${escapeXml(title)}</h1>${authorLine}\n `;
231
+ }
232
+ //# sourceMappingURL=epub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"epub.js","sourceRoot":"","sources":["../src/epub.ts"],"names":[],"mappings":"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"}
package/dist/html.d.ts ADDED
@@ -0,0 +1,54 @@
1
+ /** A parsed HTML fragment ready to inspect, mutate and serialize. */
2
+ export declare class HtmlFragment {
3
+ private readonly nodes;
4
+ private constructor();
5
+ static parse(html: string): HtmlFragment;
6
+ /** Collect the `src` of every `<img>` (in document order). */
7
+ imageSources(): string[];
8
+ /**
9
+ * Walk every `<img>` (depth-first, splice-safe) and apply a decision based on
10
+ * its `src`:
11
+ * - a string → rewrite `src` to it (and ensure `alt`);
12
+ * - `null` → remove the element;
13
+ * - `undefined` → leave it (and ensure `alt`).
14
+ */
15
+ private transformImages;
16
+ /**
17
+ * Resolve every `<img>` against an embedded-image map (original src → in-package
18
+ * path). Matched images are rewritten and given alt text. When
19
+ * `stripUnresolved` is set, images that were not embedded (failed downloads,
20
+ * unsupported sources) are removed entirely so the EPUB has no dangling
21
+ * foreign resources — EPUBCheck rejects those.
22
+ */
23
+ resolveImages(map: Map<string, string>, stripUnresolved: boolean): void;
24
+ /**
25
+ * Remove every `<img>` that references something outside the container — i.e.
26
+ * anything that isn't an inline `data:` URI (remote URLs and relative paths
27
+ * alike). Used when image downloading is disabled: only self-contained `data:`
28
+ * images can survive, so this keeps the EPUB valid instead of leaving dangling
29
+ * references EPUBCheck rejects.
30
+ */
31
+ stripExternalImages(): void;
32
+ /**
33
+ * If the fragment's first meaningful content is an image (i.e. no text
34
+ * precedes it), remove that image and return its `src` — for promoting an
35
+ * article's lead image to the cover. Returns `undefined` when text comes first,
36
+ * so we never pull an image out of the middle of the prose.
37
+ */
38
+ extractLeadImage(): string | undefined;
39
+ serialize(): string;
40
+ }
41
+ /**
42
+ * Transform an XHTML content document into its Kobo (kepub) form, mirroring
43
+ * kepubify:
44
+ *
45
+ * - every text run inside a block element is wrapped in
46
+ * `<span class="koboSpan" id="kobo.{segment}.{fragment}">` so the firmware can
47
+ * track reading position and anchor highlights precisely;
48
+ * - the body content is wrapped in `<div id="book-columns"><div id="book-inner">`
49
+ * which Kobo relies on for pagination and justification.
50
+ *
51
+ * Returns a complete XHTML document string. If the input has no parseable
52
+ * `<body>`, it is returned unchanged.
53
+ */
54
+ export declare function kepubifyXhtml(documentHtml: string): string;