@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 +21 -0
- package/README.md +157 -0
- package/assets/quarto.png +0 -0
- package/dist/css.d.ts +6 -0
- package/dist/css.js +109 -0
- package/dist/css.js.map +1 -0
- package/dist/epub.d.ts +8 -0
- package/dist/epub.js +232 -0
- package/dist/epub.js.map +1 -0
- package/dist/html.d.ts +54 -0
- package/dist/html.js +430 -0
- package/dist/html.js.map +1 -0
- package/dist/images.d.ts +24 -0
- package/dist/images.js +51 -0
- package/dist/images.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/kepub.d.ts +10 -0
- package/dist/kepub.js +32 -0
- package/dist/kepub.js.map +1 -0
- package/dist/opf.d.ts +65 -0
- package/dist/opf.js +172 -0
- package/dist/opf.js.map +1 -0
- package/dist/types.d.ts +133 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/util.d.ts +25 -0
- package/dist/util.js +157 -0
- package/dist/util.js.map +1 -0
- package/dist/zip.d.ts +9 -0
- package/dist/zip.js +22 -0
- package/dist/zip.js.map +1 -0
- package/package.json +61 -0
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
|
+

|
|
2
|
+
|
|
3
|
+
# Quarto
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@voidberg/quarto)
|
|
6
|
+
[](https://jsr.io/@voidberg/quarto)
|
|
7
|
+
[](https://github.com/voidberg/quarto/actions/workflows/ci.yml)
|
|
8
|
+
[](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
|
package/dist/css.js.map
ADDED
|
@@ -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
|
package/dist/epub.js.map
ADDED
|
@@ -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;
|