@epubook/core 0.0.11 → 0.0.12-beta.2
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/README.md +6 -26
- package/dist/index.d.mts +235 -0
- package/dist/index.mjs +478 -556
- package/package.json +12 -17
- package/dist/chunks/index.cjs +0 -190
- package/dist/chunks/index.mjs +0 -171
- package/dist/index.cjs +0 -607
- package/dist/index.d.ts +0 -274
package/dist/index.mjs
CHANGED
|
@@ -1,24 +1,266 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
import { createDefu } from "defu";
|
|
2
|
+
import * as path from "pathe";
|
|
3
|
+
import { extname } from "pathe";
|
|
4
|
+
import { strToU8 } from "fflate";
|
|
5
|
+
import { XHTMLBuilder } from "@epubook/xml";
|
|
6
|
+
//#region src/utils/defu.ts
|
|
7
|
+
const defu = createDefu((obj, key, value) => {
|
|
8
|
+
if (obj[key] instanceof Date && value instanceof Date) {
|
|
9
|
+
obj[key] = value;
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/epub/spine.ts
|
|
15
|
+
var Spine = class {
|
|
16
|
+
_itemrefs = [];
|
|
17
|
+
constructor(rendition) {
|
|
18
|
+
this.rendition = rendition;
|
|
19
|
+
}
|
|
20
|
+
get itemrefs() {
|
|
21
|
+
return this._itemrefs;
|
|
22
|
+
}
|
|
23
|
+
shift() {
|
|
24
|
+
return this._itemrefs.shift();
|
|
25
|
+
}
|
|
26
|
+
unshift(ref) {
|
|
27
|
+
this._itemrefs.unshift(ref);
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
pop() {
|
|
31
|
+
return this._itemrefs.pop();
|
|
32
|
+
}
|
|
33
|
+
push(...itemrefs) {
|
|
34
|
+
this._itemrefs.push(...itemrefs);
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
clear() {
|
|
38
|
+
this._itemrefs.splice(0, this._itemrefs.length);
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
[Symbol.iterator]() {
|
|
42
|
+
return this._itemrefs[Symbol.iterator]();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/epub/manifest.ts
|
|
47
|
+
var Manifest = class Manifest {
|
|
48
|
+
_resources = [];
|
|
49
|
+
constructor(rendition) {
|
|
50
|
+
this.rendition = rendition;
|
|
51
|
+
}
|
|
52
|
+
get resources() {
|
|
53
|
+
return this._resources;
|
|
54
|
+
}
|
|
55
|
+
get items() {
|
|
56
|
+
return this._resources.map((r) => r.item());
|
|
57
|
+
}
|
|
58
|
+
get itemrefs() {
|
|
59
|
+
return this._resources.map((r) => r.itemref());
|
|
60
|
+
}
|
|
61
|
+
add(...resources) {
|
|
62
|
+
for (const r of resources) {
|
|
63
|
+
if (this._resources.find((item) => item.id === r.id)) continue;
|
|
64
|
+
this._resources.push(r);
|
|
65
|
+
}
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
[Symbol.iterator]() {
|
|
69
|
+
return this._resources[Symbol.iterator]();
|
|
70
|
+
}
|
|
71
|
+
map(fn) {
|
|
72
|
+
return this._resources.filter((v, i) => fn(v, i, this));
|
|
73
|
+
}
|
|
74
|
+
filter(predicate) {
|
|
75
|
+
return new Manifest(this.rendition).add(...this._resources.filter((v, i) => predicate(v, i, this)));
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var Item = class {
|
|
79
|
+
_href;
|
|
80
|
+
_id;
|
|
81
|
+
optional = {};
|
|
82
|
+
constructor(href, id, info) {
|
|
83
|
+
this._href = href;
|
|
84
|
+
this._id = id;
|
|
85
|
+
info && this.update(info);
|
|
86
|
+
}
|
|
87
|
+
update(info) {
|
|
88
|
+
for (const [key, value] of Object.entries(info)) if (!!value) this.optional[key] = value;
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
href() {
|
|
92
|
+
return this._href;
|
|
93
|
+
}
|
|
94
|
+
id() {
|
|
95
|
+
return this._id;
|
|
96
|
+
}
|
|
97
|
+
fallback() {
|
|
98
|
+
return this.optional.fallback;
|
|
99
|
+
}
|
|
100
|
+
mediaOverlay() {
|
|
101
|
+
return this.optional.mediaOverlay;
|
|
102
|
+
}
|
|
103
|
+
mediaType() {
|
|
104
|
+
return this.optional.mediaType;
|
|
105
|
+
}
|
|
106
|
+
properties() {
|
|
107
|
+
return this.optional.properties;
|
|
108
|
+
}
|
|
109
|
+
ref() {
|
|
110
|
+
return new ItemRef(this._id);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
var ItemRef = class {
|
|
114
|
+
_idref;
|
|
115
|
+
optional = {};
|
|
116
|
+
constructor(idref) {
|
|
117
|
+
this._idref = idref;
|
|
118
|
+
}
|
|
119
|
+
update(info) {
|
|
120
|
+
for (const [key, value] of Object.entries(info)) if (!!value) this.optional[key] = value;
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
idref() {
|
|
124
|
+
return this._idref;
|
|
125
|
+
}
|
|
126
|
+
id() {
|
|
127
|
+
return this.optional.id;
|
|
128
|
+
}
|
|
129
|
+
linear() {
|
|
130
|
+
return this.optional.linear;
|
|
131
|
+
}
|
|
132
|
+
properties() {
|
|
133
|
+
return this.optional.properties;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/epub/rendition.ts
|
|
138
|
+
var Rendition = class {
|
|
139
|
+
_fullPath;
|
|
140
|
+
_specVersion = "3.0";
|
|
141
|
+
_uniqueIdentifier = "uuid";
|
|
142
|
+
_identifier = crypto.randomUUID();
|
|
143
|
+
_metadata = {
|
|
144
|
+
title: "",
|
|
145
|
+
language: "zh-CN",
|
|
146
|
+
contributor: [],
|
|
147
|
+
coverage: "",
|
|
148
|
+
creator: {
|
|
149
|
+
name: "unknown",
|
|
150
|
+
uid: "creator"
|
|
151
|
+
},
|
|
152
|
+
date: /* @__PURE__ */ new Date(),
|
|
153
|
+
description: "",
|
|
154
|
+
format: "",
|
|
155
|
+
publisher: "",
|
|
156
|
+
relation: "",
|
|
157
|
+
rights: "",
|
|
158
|
+
source: "",
|
|
159
|
+
subject: "",
|
|
160
|
+
type: "",
|
|
161
|
+
lastModified: /* @__PURE__ */ new Date()
|
|
162
|
+
};
|
|
163
|
+
_cover;
|
|
164
|
+
_manifest;
|
|
165
|
+
_spine;
|
|
166
|
+
_navigation;
|
|
167
|
+
constructor(fullPath) {
|
|
168
|
+
this._fullPath = fullPath;
|
|
169
|
+
this._manifest = new Manifest(this);
|
|
170
|
+
this._spine = new Spine(this);
|
|
171
|
+
}
|
|
172
|
+
get path() {
|
|
173
|
+
return this._fullPath;
|
|
174
|
+
}
|
|
175
|
+
get version() {
|
|
176
|
+
return this._specVersion;
|
|
177
|
+
}
|
|
178
|
+
get title() {
|
|
179
|
+
return this._metadata.title;
|
|
180
|
+
}
|
|
181
|
+
get language() {
|
|
182
|
+
return this._metadata.language;
|
|
183
|
+
}
|
|
184
|
+
get creator() {
|
|
185
|
+
return this._metadata.creator;
|
|
186
|
+
}
|
|
187
|
+
get metadata() {
|
|
188
|
+
return this._metadata;
|
|
189
|
+
}
|
|
190
|
+
get cover() {
|
|
191
|
+
return this._cover;
|
|
192
|
+
}
|
|
193
|
+
updateMetadata(newMetadata) {
|
|
194
|
+
this._metadata = defu(newMetadata, this._metadata);
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
get uniqueIdentifier() {
|
|
198
|
+
return this._uniqueIdentifier;
|
|
199
|
+
}
|
|
200
|
+
get identifier() {
|
|
201
|
+
return this._identifier;
|
|
202
|
+
}
|
|
203
|
+
setIdentifier(identifier, uniqueIdentifier = "uuid") {
|
|
204
|
+
this._identifier = identifier;
|
|
205
|
+
this._uniqueIdentifier = uniqueIdentifier;
|
|
206
|
+
return this;
|
|
207
|
+
}
|
|
208
|
+
get manifest() {
|
|
209
|
+
return this._manifest;
|
|
210
|
+
}
|
|
211
|
+
get spine() {
|
|
212
|
+
return this._spine;
|
|
213
|
+
}
|
|
214
|
+
get navigation() {
|
|
215
|
+
return this._navigation;
|
|
216
|
+
}
|
|
217
|
+
setNavigation(navigation) {
|
|
218
|
+
this._navigation = navigation;
|
|
219
|
+
this._manifest.add(navigation);
|
|
220
|
+
return this;
|
|
221
|
+
}
|
|
222
|
+
setCover(cover) {
|
|
223
|
+
this._cover = cover;
|
|
224
|
+
this._manifest.add(cover);
|
|
225
|
+
return this;
|
|
226
|
+
}
|
|
227
|
+
addResource(resource) {
|
|
228
|
+
this._manifest.add(resource);
|
|
229
|
+
return this;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region src/epub/publication.ts
|
|
234
|
+
var EpubPublication = class EpubPublication {
|
|
235
|
+
/**
|
|
236
|
+
* See: https://www.w3.org/TR/epub-33/#sec-package-doc
|
|
237
|
+
*
|
|
238
|
+
* Now, it only supports single opf (OEBPS/content.opf)
|
|
239
|
+
*
|
|
240
|
+
* @returns list of package documents
|
|
241
|
+
*/
|
|
242
|
+
_rootfiles = [];
|
|
243
|
+
constructor() {}
|
|
244
|
+
static empty() {
|
|
245
|
+
return new EpubPublication();
|
|
246
|
+
}
|
|
247
|
+
static create(fullPath = "OEBPS/content.opf", rootMeta = {}) {
|
|
248
|
+
const container = new EpubPublication();
|
|
249
|
+
const rendition = new Rendition(fullPath);
|
|
250
|
+
rendition.updateMetadata(rootMeta);
|
|
251
|
+
container._rootfiles.push(rendition);
|
|
252
|
+
return container;
|
|
253
|
+
}
|
|
254
|
+
get rootfile() {
|
|
255
|
+
if (this._rootfiles.length === 0) throw new Error("no rendition is provided");
|
|
256
|
+
return this._rootfiles[0];
|
|
257
|
+
}
|
|
258
|
+
get rootfiles() {
|
|
259
|
+
return this._rootfiles;
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/constant.ts
|
|
22
264
|
const MIMETYPE = "application/epub+zip";
|
|
23
265
|
const ImageGif = "image/gif";
|
|
24
266
|
const ImageJpeg = "image/jpeg";
|
|
@@ -28,539 +270,219 @@ const ImageWebp = "image/webp";
|
|
|
28
270
|
const TextCSS = "text/css";
|
|
29
271
|
const TextXHTML = "application/xhtml+xml";
|
|
30
272
|
function getImageMediaType(file) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
case ".svg":
|
|
41
|
-
return ImageSvg;
|
|
42
|
-
case ".webp":
|
|
43
|
-
return ImageWebp;
|
|
44
|
-
default:
|
|
45
|
-
return void 0;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
class ManifestItem {
|
|
50
|
-
constructor(href, id) {
|
|
51
|
-
this.optional = {};
|
|
52
|
-
this._href = href;
|
|
53
|
-
this._id = id;
|
|
54
|
-
}
|
|
55
|
-
update(info) {
|
|
56
|
-
for (const [key, value] of Object.entries(info)) {
|
|
57
|
-
if (!!value) {
|
|
58
|
-
this.optional[key] = value;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return this;
|
|
62
|
-
}
|
|
63
|
-
href() {
|
|
64
|
-
return this._href;
|
|
65
|
-
}
|
|
66
|
-
id() {
|
|
67
|
-
return this._id;
|
|
68
|
-
}
|
|
69
|
-
fallback() {
|
|
70
|
-
return this.optional.fallback;
|
|
71
|
-
}
|
|
72
|
-
mediaOverlay() {
|
|
73
|
-
return this.optional.mediaOverlay;
|
|
74
|
-
}
|
|
75
|
-
mediaType() {
|
|
76
|
-
return this.optional.mediaType;
|
|
77
|
-
}
|
|
78
|
-
properties() {
|
|
79
|
-
return this.optional.properties;
|
|
80
|
-
}
|
|
81
|
-
ref() {
|
|
82
|
-
return new ManifestItemRef(this._id);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
class ManifestItemRef {
|
|
86
|
-
constructor(idref) {
|
|
87
|
-
this.optional = {};
|
|
88
|
-
this._idref = idref;
|
|
89
|
-
}
|
|
90
|
-
update(info) {
|
|
91
|
-
for (const [key, value] of Object.entries(info)) {
|
|
92
|
-
if (!!value) {
|
|
93
|
-
this.optional[key] = value;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return this;
|
|
97
|
-
}
|
|
98
|
-
idref() {
|
|
99
|
-
return this._idref;
|
|
100
|
-
}
|
|
101
|
-
id() {
|
|
102
|
-
return this.optional.id;
|
|
103
|
-
}
|
|
104
|
-
linear() {
|
|
105
|
-
return this.optional.linear;
|
|
106
|
-
}
|
|
107
|
-
properties() {
|
|
108
|
-
return this.optional.properties;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
class Item {
|
|
113
|
-
constructor(file, mediaType) {
|
|
114
|
-
this.file = file;
|
|
115
|
-
this.mediaType = mediaType;
|
|
116
|
-
}
|
|
117
|
-
filename() {
|
|
118
|
-
return this.file;
|
|
119
|
-
}
|
|
120
|
-
relative(from) {
|
|
121
|
-
return path$1.relative(path$1.dirname(from), this.file);
|
|
122
|
-
}
|
|
123
|
-
update(info) {
|
|
124
|
-
if (info.properties) {
|
|
125
|
-
this._properties = info.properties;
|
|
126
|
-
}
|
|
127
|
-
return this;
|
|
128
|
-
}
|
|
129
|
-
id() {
|
|
130
|
-
return this.file.replace(/\/|\\/g, "_").replace(/\.[\w]+$/, "");
|
|
131
|
-
}
|
|
132
|
-
manifest() {
|
|
133
|
-
return new ManifestItem(this.file, this.id()).update({
|
|
134
|
-
mediaType: this.mediaType,
|
|
135
|
-
properties: this._properties
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
itemref() {
|
|
139
|
-
return this.manifest().ref();
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
class Style extends Item {
|
|
143
|
-
constructor(file, content) {
|
|
144
|
-
super(file, TextCSS);
|
|
145
|
-
this.content = content;
|
|
146
|
-
}
|
|
147
|
-
static async read(src, dst) {
|
|
148
|
-
if (!src.endsWith(".css")) {
|
|
149
|
-
return void 0;
|
|
150
|
-
}
|
|
151
|
-
const content = await promises.readFile(src, "utf-8");
|
|
152
|
-
return new Style(dst, content);
|
|
153
|
-
}
|
|
154
|
-
async bundle() {
|
|
155
|
-
return strToU8(this.content);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
class Image extends Item {
|
|
159
|
-
constructor(file, type, data) {
|
|
160
|
-
super(file, type);
|
|
161
|
-
this.data = data;
|
|
162
|
-
}
|
|
163
|
-
static async read(file, src) {
|
|
164
|
-
const content = await promises.readFile(src);
|
|
165
|
-
const media = getImageMediaType(src);
|
|
166
|
-
if (media) {
|
|
167
|
-
return new Image(file, media, content);
|
|
168
|
-
} else {
|
|
169
|
-
return void 0;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
async bundle() {
|
|
173
|
-
return this.data;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
class HTML extends Item {
|
|
177
|
-
constructor(file, content) {
|
|
178
|
-
super(file, TextXHTML);
|
|
179
|
-
this.content = content;
|
|
180
|
-
}
|
|
181
|
-
static async read(src, dst) {
|
|
182
|
-
if (!src.endsWith(".xhtml")) {
|
|
183
|
-
return void 0;
|
|
184
|
-
}
|
|
185
|
-
const content = await promises.readFile(src, "utf-8");
|
|
186
|
-
return new HTML(dst, content);
|
|
187
|
-
}
|
|
188
|
-
async bundle() {
|
|
189
|
-
return strToU8(this.content);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const builder = new XMLBuilder({
|
|
194
|
-
format: true,
|
|
195
|
-
ignoreAttributes: false,
|
|
196
|
-
suppressUnpairedNode: false,
|
|
197
|
-
unpairedTags: ["link"]
|
|
198
|
-
});
|
|
199
|
-
class XHTML extends Item {
|
|
200
|
-
constructor(file, meta, content) {
|
|
201
|
-
super(file, TextXHTML);
|
|
202
|
-
this._meta = meta;
|
|
203
|
-
this._content = content;
|
|
204
|
-
}
|
|
205
|
-
meta() {
|
|
206
|
-
return this._meta;
|
|
207
|
-
}
|
|
208
|
-
title() {
|
|
209
|
-
return this._meta.title;
|
|
210
|
-
}
|
|
211
|
-
language() {
|
|
212
|
-
return this._meta.language;
|
|
213
|
-
}
|
|
214
|
-
content() {
|
|
215
|
-
return this._content;
|
|
216
|
-
}
|
|
217
|
-
async bundle() {
|
|
218
|
-
return strToU8(this._content);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
class XHTMLBuilder {
|
|
222
|
-
constructor(filename) {
|
|
223
|
-
this._meta = {
|
|
224
|
-
language: "en",
|
|
225
|
-
title: ""
|
|
226
|
-
};
|
|
227
|
-
this._head = [];
|
|
228
|
-
this._body = [];
|
|
229
|
-
this._bodyAttrs = {};
|
|
230
|
-
this._filename = filename;
|
|
231
|
-
this._meta.title = path$1.basename(filename);
|
|
232
|
-
}
|
|
233
|
-
language(value) {
|
|
234
|
-
this._meta.language = value;
|
|
235
|
-
return this;
|
|
236
|
-
}
|
|
237
|
-
title(value) {
|
|
238
|
-
this._meta.title = value;
|
|
239
|
-
return this;
|
|
240
|
-
}
|
|
241
|
-
style(...list) {
|
|
242
|
-
for (const href of list) {
|
|
243
|
-
if (typeof href === "string") {
|
|
244
|
-
this._head.push({
|
|
245
|
-
tag: "link",
|
|
246
|
-
attrs: {
|
|
247
|
-
href,
|
|
248
|
-
rel: "stylesheet",
|
|
249
|
-
type: TextCSS
|
|
250
|
-
},
|
|
251
|
-
children: [""]
|
|
252
|
-
});
|
|
253
|
-
} else {
|
|
254
|
-
this._head.push({
|
|
255
|
-
tag: "link",
|
|
256
|
-
attrs: {
|
|
257
|
-
href: href.relative(this._filename),
|
|
258
|
-
rel: "stylesheet",
|
|
259
|
-
type: TextCSS
|
|
260
|
-
},
|
|
261
|
-
children: [""]
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
return this;
|
|
266
|
-
}
|
|
267
|
-
head(...node) {
|
|
268
|
-
this._head.push(...node);
|
|
269
|
-
return this;
|
|
270
|
-
}
|
|
271
|
-
body(...node) {
|
|
272
|
-
this._body.push(...node);
|
|
273
|
-
return this;
|
|
274
|
-
}
|
|
275
|
-
bodyAttrs(attrs = {}) {
|
|
276
|
-
const a = Object.entries(attrs).map(([key, value]) => [`@_${key}`, value]);
|
|
277
|
-
this._bodyAttrs = {
|
|
278
|
-
...this._bodyAttrs,
|
|
279
|
-
...Object.fromEntries(a)
|
|
280
|
-
};
|
|
281
|
-
return this;
|
|
282
|
-
}
|
|
283
|
-
build() {
|
|
284
|
-
const replacer = {};
|
|
285
|
-
let content = builder.build({
|
|
286
|
-
html: {
|
|
287
|
-
"@_xmlns": "http://www.w3.org/1999/xhtml",
|
|
288
|
-
"@_xmlns:epub": "http://www.idpf.org/2007/ops",
|
|
289
|
-
"@_lang": this._meta.language,
|
|
290
|
-
"@_xml:lang": this._meta.language,
|
|
291
|
-
head: {
|
|
292
|
-
title: this._meta.title,
|
|
293
|
-
...list(this._head)
|
|
294
|
-
},
|
|
295
|
-
body: {
|
|
296
|
-
...this._bodyAttrs,
|
|
297
|
-
...list(this._body)
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
for (const [key, value] of Object.entries(replacer)) {
|
|
302
|
-
content = content.replace(key, value);
|
|
303
|
-
}
|
|
304
|
-
return new XHTML(this._filename, this._meta, content);
|
|
305
|
-
function build(node) {
|
|
306
|
-
const attrs = Object.fromEntries(
|
|
307
|
-
Object.entries(node.attrs ?? {}).map(([key, value]) => ["@_" + key, value])
|
|
308
|
-
);
|
|
309
|
-
if (node.attrs && "html" in node.attrs) {
|
|
310
|
-
const id = `____${randomUUID()}____`;
|
|
311
|
-
replacer[id] = node.attrs.html;
|
|
312
|
-
return { ...attrs, "@_html": void 0, "#text": id };
|
|
313
|
-
} else {
|
|
314
|
-
const obj = {
|
|
315
|
-
...attrs
|
|
316
|
-
};
|
|
317
|
-
if (Array.isArray(node.children)) {
|
|
318
|
-
const text = node.children.filter((c) => typeof c === "string");
|
|
319
|
-
const nodes = node.children.filter((c) => typeof c !== "string");
|
|
320
|
-
if (text.length > 0) {
|
|
321
|
-
obj["#text"] = text[0];
|
|
322
|
-
}
|
|
323
|
-
Object.assign(obj, list(nodes));
|
|
324
|
-
}
|
|
325
|
-
return obj;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
function list(list2) {
|
|
329
|
-
const obj = {};
|
|
330
|
-
const nodes = list2.flatMap((n) => n.tag === Fragment ? n.children ?? [] : [n]);
|
|
331
|
-
for (const c of nodes) {
|
|
332
|
-
if (typeof c === "string") {
|
|
333
|
-
if (obj["#text"]) {
|
|
334
|
-
obj["#text"] += c;
|
|
335
|
-
} else {
|
|
336
|
-
obj["#text"] = c;
|
|
337
|
-
}
|
|
338
|
-
} else if (c.tag in obj) {
|
|
339
|
-
obj[c.tag].push(build(c));
|
|
340
|
-
} else {
|
|
341
|
-
obj[c.tag] = [build(c)];
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
return obj;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
class Toc extends XHTML {
|
|
350
|
-
constructor(file, meta, content) {
|
|
351
|
-
super(file, meta, content);
|
|
352
|
-
this.update({ properties: "nav" });
|
|
353
|
-
}
|
|
354
|
-
static from(xhtml) {
|
|
355
|
-
return new Toc(xhtml.filename(), xhtml.meta(), xhtml.content());
|
|
356
|
-
}
|
|
357
|
-
static generate(file, nav, { title = "Nav", heading = 1, titleAttrs = {}, builder } = {}) {
|
|
358
|
-
if (!builder) {
|
|
359
|
-
builder = new XHTMLBuilder(file);
|
|
360
|
-
}
|
|
361
|
-
const root = {
|
|
362
|
-
tag: "nav",
|
|
363
|
-
attrs: {
|
|
364
|
-
"epub:type": "toc"
|
|
365
|
-
},
|
|
366
|
-
children: []
|
|
367
|
-
};
|
|
368
|
-
root.children.push(h("h" + heading, titleAttrs, title));
|
|
369
|
-
root.children.push(/* @__PURE__ */ h("ol", null, list(nav)));
|
|
370
|
-
return builder.body(root);
|
|
371
|
-
function list(items) {
|
|
372
|
-
return items.map(
|
|
373
|
-
(i) => "page" in i ? /* @__PURE__ */ h("li", { ...i.attrs }, /* @__PURE__ */ h("a", { href: i.page.filename() }, i.title)) : "list" in i ? /* @__PURE__ */ h("li", { ...i.attrs }, /* @__PURE__ */ h("span", null, i.title), /* @__PURE__ */ h("ol", null, list(i.list))) : false
|
|
374
|
-
);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
const defu = createDefu((obj, key, value) => {
|
|
380
|
-
if (obj[key] instanceof Date && value instanceof Date) {
|
|
381
|
-
obj[key] = value;
|
|
382
|
-
return true;
|
|
383
|
-
}
|
|
384
|
-
});
|
|
385
|
-
class PackageDocument {
|
|
386
|
-
constructor(file) {
|
|
387
|
-
this.SpecVersion = "3.0";
|
|
388
|
-
this._uniqueIdentifier = "uuid";
|
|
389
|
-
this._identifier = randomUUID();
|
|
390
|
-
this._metadata = {
|
|
391
|
-
title: "",
|
|
392
|
-
language: "zh-CN",
|
|
393
|
-
contributor: [],
|
|
394
|
-
coverage: "",
|
|
395
|
-
creator: {
|
|
396
|
-
name: "unknown",
|
|
397
|
-
uid: "creator"
|
|
398
|
-
},
|
|
399
|
-
date: /* @__PURE__ */ new Date(),
|
|
400
|
-
description: "",
|
|
401
|
-
format: "",
|
|
402
|
-
publisher: "",
|
|
403
|
-
relation: "",
|
|
404
|
-
rights: "",
|
|
405
|
-
source: "",
|
|
406
|
-
subject: "",
|
|
407
|
-
type: "",
|
|
408
|
-
lastModified: /* @__PURE__ */ new Date()
|
|
409
|
-
};
|
|
410
|
-
this._items = [];
|
|
411
|
-
this._spine = [];
|
|
412
|
-
this.file = file;
|
|
413
|
-
}
|
|
414
|
-
filename() {
|
|
415
|
-
return this.file;
|
|
416
|
-
}
|
|
417
|
-
version() {
|
|
418
|
-
return this.SpecVersion;
|
|
419
|
-
}
|
|
420
|
-
// --- metadata ---
|
|
421
|
-
update(info) {
|
|
422
|
-
this._metadata = defu(info, this._metadata);
|
|
423
|
-
return this;
|
|
424
|
-
}
|
|
425
|
-
title() {
|
|
426
|
-
return this._metadata.title;
|
|
427
|
-
}
|
|
428
|
-
language() {
|
|
429
|
-
return this._metadata.language;
|
|
430
|
-
}
|
|
431
|
-
creator() {
|
|
432
|
-
return this._metadata.creator;
|
|
433
|
-
}
|
|
434
|
-
metadata() {
|
|
435
|
-
return this._metadata;
|
|
436
|
-
}
|
|
437
|
-
// --- manifest ---
|
|
438
|
-
addItem(item) {
|
|
439
|
-
this._items.push(item);
|
|
440
|
-
return this;
|
|
441
|
-
}
|
|
442
|
-
items() {
|
|
443
|
-
const l = [...this._items];
|
|
444
|
-
if (this._toc) {
|
|
445
|
-
l.push(this._toc);
|
|
446
|
-
}
|
|
447
|
-
return l;
|
|
448
|
-
}
|
|
449
|
-
manifest() {
|
|
450
|
-
return this.items().map((i) => i.manifest());
|
|
451
|
-
}
|
|
452
|
-
// --- navigation ---
|
|
453
|
-
spine() {
|
|
454
|
-
return this._spine;
|
|
455
|
-
}
|
|
456
|
-
setSpine(items) {
|
|
457
|
-
this._spine.splice(0, this._spine.length, ...items.map((i) => i.itemref()));
|
|
458
|
-
return this;
|
|
459
|
-
}
|
|
460
|
-
toc() {
|
|
461
|
-
return this._toc;
|
|
462
|
-
}
|
|
463
|
-
setToc(nav, option = {}) {
|
|
464
|
-
const toc = Toc.generate("nav.xhtml", nav, option);
|
|
465
|
-
if (!option.builder) {
|
|
466
|
-
toc.title(option.title ?? "Nav").language(this._metadata.language);
|
|
467
|
-
}
|
|
468
|
-
this._toc = Toc.from(toc.build());
|
|
469
|
-
return this;
|
|
470
|
-
}
|
|
471
|
-
// --- identifier ---
|
|
472
|
-
uniqueIdentifier() {
|
|
473
|
-
return this._uniqueIdentifier;
|
|
474
|
-
}
|
|
475
|
-
identifier() {
|
|
476
|
-
return this._identifier;
|
|
477
|
-
}
|
|
478
|
-
setIdentifier(identifier, uniqueIdentifier = "uuid") {
|
|
479
|
-
this._identifier = identifier;
|
|
480
|
-
this._uniqueIdentifier = uniqueIdentifier;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
class Epub {
|
|
485
|
-
constructor(meta = {}) {
|
|
486
|
-
/**
|
|
487
|
-
* See: https://www.w3.org/TR/epub-33/#sec-package-doc
|
|
488
|
-
*
|
|
489
|
-
* Now, it only supports single opf (OEBPS/content.opf)
|
|
490
|
-
*
|
|
491
|
-
* @returns list of package documents
|
|
492
|
-
*/
|
|
493
|
-
this.opfs = [new PackageDocument("OEBPS/content.opf")];
|
|
494
|
-
this.opfs[0].update(meta);
|
|
495
|
-
}
|
|
496
|
-
packages() {
|
|
497
|
-
return this.opfs;
|
|
498
|
-
}
|
|
499
|
-
main() {
|
|
500
|
-
return this.opfs[0];
|
|
501
|
-
}
|
|
502
|
-
item(...items) {
|
|
503
|
-
for (const item of items) {
|
|
504
|
-
this.opfs[0].addItem(item);
|
|
505
|
-
}
|
|
506
|
-
return this;
|
|
507
|
-
}
|
|
508
|
-
toc(nav, option = {}) {
|
|
509
|
-
this.opfs[0].setToc(nav, option);
|
|
510
|
-
return this;
|
|
511
|
-
}
|
|
512
|
-
spine(...items) {
|
|
513
|
-
this.opfs[0].setSpine(items);
|
|
514
|
-
return this;
|
|
515
|
-
}
|
|
516
|
-
async bundle() {
|
|
517
|
-
const { bundle } = await import('./chunks/index.mjs');
|
|
518
|
-
return await bundle(this);
|
|
519
|
-
}
|
|
520
|
-
async writeFile(file) {
|
|
521
|
-
const buffer = await this.bundle();
|
|
522
|
-
const dir = path$1.dirname(file);
|
|
523
|
-
if (!existsSync(dir)) {
|
|
524
|
-
mkdirSync(dir, { recursive: true });
|
|
525
|
-
}
|
|
526
|
-
await promises.writeFile(file, buffer);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
class BundleError extends Error {
|
|
531
|
-
constructor(msg) {
|
|
532
|
-
super(msg);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
const UnbuildPreset = ({
|
|
537
|
-
inject = true
|
|
538
|
-
} = {}) => ({
|
|
539
|
-
rollup: {
|
|
540
|
-
esbuild: {
|
|
541
|
-
jsxFactory: inject ? "__epubook_core.h" : "h",
|
|
542
|
-
jsxFragment: inject ? "__epubook_core.Fragment" : "Fragment"
|
|
543
|
-
}
|
|
544
|
-
},
|
|
545
|
-
hooks: {
|
|
546
|
-
"rollup:options"(_options, config) {
|
|
547
|
-
const plugins = config.plugins;
|
|
548
|
-
if (inject && Array.isArray(plugins)) {
|
|
549
|
-
plugins.push(Rollup());
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
});
|
|
554
|
-
function Rollup() {
|
|
555
|
-
return {
|
|
556
|
-
name: "epubook:inject-tsx",
|
|
557
|
-
transform(code, id) {
|
|
558
|
-
if (id.endsWith(".tsx")) {
|
|
559
|
-
return `import * as __epubook_core from '@epubook/core';
|
|
560
|
-
` + code;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
};
|
|
273
|
+
switch (extname(file)) {
|
|
274
|
+
case ".gif": return ImageGif;
|
|
275
|
+
case ".jpg":
|
|
276
|
+
case ".jpeg": return ImageJpeg;
|
|
277
|
+
case ".png": return ImagePng;
|
|
278
|
+
case ".svg": return ImageSvg;
|
|
279
|
+
case ".webp": return ImageWebp;
|
|
280
|
+
default: return;
|
|
281
|
+
}
|
|
564
282
|
}
|
|
565
|
-
|
|
566
|
-
|
|
283
|
+
//#endregion
|
|
284
|
+
//#region src/resource/resource.ts
|
|
285
|
+
var Resource = class {
|
|
286
|
+
_id;
|
|
287
|
+
_fullPath;
|
|
288
|
+
_mediaType;
|
|
289
|
+
_properties;
|
|
290
|
+
constructor(id, fullPath, mediaType) {
|
|
291
|
+
this._id = id || fullPath.replace(/\/|\\/g, "_").replace(/\.[\w]+$/, "");
|
|
292
|
+
this._fullPath = fullPath;
|
|
293
|
+
this._mediaType = mediaType;
|
|
294
|
+
}
|
|
295
|
+
get id() {
|
|
296
|
+
return this._id;
|
|
297
|
+
}
|
|
298
|
+
get path() {
|
|
299
|
+
return this._fullPath;
|
|
300
|
+
}
|
|
301
|
+
relative(from) {
|
|
302
|
+
return path.relative(path.dirname(from), this._fullPath);
|
|
303
|
+
}
|
|
304
|
+
updateAttrs(info) {
|
|
305
|
+
if (info.mediaType) this._mediaType = info.mediaType;
|
|
306
|
+
if (info.properties) this._properties = info.properties;
|
|
307
|
+
return this;
|
|
308
|
+
}
|
|
309
|
+
item() {
|
|
310
|
+
return new Item(this._fullPath, this._id).update({
|
|
311
|
+
mediaType: this._mediaType,
|
|
312
|
+
properties: this._properties
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
itemref() {
|
|
316
|
+
return new ItemRef(this._id);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
var StyleSheet = class extends Resource {
|
|
320
|
+
_content;
|
|
321
|
+
constructor(fullPath, content = "", options = {}) {
|
|
322
|
+
super(options.id, fullPath, TextCSS);
|
|
323
|
+
this._content = content;
|
|
324
|
+
}
|
|
325
|
+
update(content) {
|
|
326
|
+
this._content = content;
|
|
327
|
+
return this;
|
|
328
|
+
}
|
|
329
|
+
async bundle() {
|
|
330
|
+
return strToU8(this._content);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
var Image = class extends Resource {
|
|
334
|
+
_data;
|
|
335
|
+
constructor(fullPath, type, data = void 0, options = {}) {
|
|
336
|
+
super(options.id, fullPath, type);
|
|
337
|
+
this._mediaType = type;
|
|
338
|
+
this._data = data;
|
|
339
|
+
}
|
|
340
|
+
update(type, data) {
|
|
341
|
+
this._mediaType = type;
|
|
342
|
+
this._data = data;
|
|
343
|
+
return this;
|
|
344
|
+
}
|
|
345
|
+
async bundle() {
|
|
346
|
+
if (!this._data) return new Uint8Array(0);
|
|
347
|
+
return this._data;
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
var Cover = class extends Image {
|
|
351
|
+
constructor(fullPath, type, data = void 0, options = {}) {
|
|
352
|
+
super(fullPath, type, data, options);
|
|
353
|
+
this._properties = "cover-image";
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
var XHTML = class extends Resource {
|
|
357
|
+
_meta;
|
|
358
|
+
_content;
|
|
359
|
+
constructor(fullPath, meta = {
|
|
360
|
+
title: "",
|
|
361
|
+
language: "en"
|
|
362
|
+
}, content = void 0, options = {}) {
|
|
363
|
+
super(options.id, fullPath, TextXHTML);
|
|
364
|
+
this._meta = {
|
|
365
|
+
title: "",
|
|
366
|
+
language: "en",
|
|
367
|
+
...meta
|
|
368
|
+
};
|
|
369
|
+
this._content = content;
|
|
370
|
+
}
|
|
371
|
+
get meta() {
|
|
372
|
+
return this._meta;
|
|
373
|
+
}
|
|
374
|
+
get title() {
|
|
375
|
+
return this._meta.title;
|
|
376
|
+
}
|
|
377
|
+
get language() {
|
|
378
|
+
return this._meta.language;
|
|
379
|
+
}
|
|
380
|
+
get content() {
|
|
381
|
+
return this._content;
|
|
382
|
+
}
|
|
383
|
+
update(text) {
|
|
384
|
+
this._content = text;
|
|
385
|
+
return this;
|
|
386
|
+
}
|
|
387
|
+
async bundle() {
|
|
388
|
+
return strToU8(this._content || "");
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
//#endregion
|
|
392
|
+
//#region src/resource/navigation.ts
|
|
393
|
+
var Navigation = class extends Resource {
|
|
394
|
+
_nav = [];
|
|
395
|
+
_options = {};
|
|
396
|
+
constructor(fullPath = "nav.xhtml", nav = [], options = {}) {
|
|
397
|
+
super(void 0, fullPath, TextXHTML);
|
|
398
|
+
this._properties = "nav";
|
|
399
|
+
this.update(nav, options);
|
|
400
|
+
}
|
|
401
|
+
update(nav, options = {}) {
|
|
402
|
+
this._nav = nav;
|
|
403
|
+
this._options = options;
|
|
404
|
+
return this;
|
|
405
|
+
}
|
|
406
|
+
generate() {
|
|
407
|
+
const builder = new XHTMLBuilder(this._fullPath);
|
|
408
|
+
if (this._options.title) builder.setTitle(this._options.title);
|
|
409
|
+
const nav = [];
|
|
410
|
+
if (this._options.heading?.text || this._options.title) nav.push({
|
|
411
|
+
type: "element",
|
|
412
|
+
name: `h${this._options.heading || 1}`,
|
|
413
|
+
attributes: this._options.heading?.attrs || {},
|
|
414
|
+
children: [{
|
|
415
|
+
type: "text",
|
|
416
|
+
value: this._options.heading?.text || this._options.title
|
|
417
|
+
}]
|
|
418
|
+
});
|
|
419
|
+
const ol = [];
|
|
420
|
+
const makeAnchorNode = (navItem) => {
|
|
421
|
+
if ("resource" in navItem) return {
|
|
422
|
+
type: "element",
|
|
423
|
+
name: "li",
|
|
424
|
+
attributes: navItem.attrs || {},
|
|
425
|
+
children: [{
|
|
426
|
+
type: "element",
|
|
427
|
+
name: "a",
|
|
428
|
+
attributes: { href: navItem.resource.path },
|
|
429
|
+
children: [{
|
|
430
|
+
type: "text",
|
|
431
|
+
value: navItem.title
|
|
432
|
+
}]
|
|
433
|
+
}]
|
|
434
|
+
};
|
|
435
|
+
else return {
|
|
436
|
+
type: "element",
|
|
437
|
+
name: "li",
|
|
438
|
+
attributes: navItem.attrs || {},
|
|
439
|
+
children: [{
|
|
440
|
+
type: "element",
|
|
441
|
+
name: "a",
|
|
442
|
+
attributes: { href: navItem.href },
|
|
443
|
+
children: [{
|
|
444
|
+
type: "text",
|
|
445
|
+
value: navItem.title
|
|
446
|
+
}]
|
|
447
|
+
}]
|
|
448
|
+
};
|
|
449
|
+
};
|
|
450
|
+
for (const navItem of this._nav) if ("children" in navItem) ol.push({
|
|
451
|
+
type: "element",
|
|
452
|
+
name: "li",
|
|
453
|
+
attributes: navItem.attrs || {},
|
|
454
|
+
children: [{
|
|
455
|
+
type: "element",
|
|
456
|
+
name: "span",
|
|
457
|
+
attributes: {},
|
|
458
|
+
children: [{
|
|
459
|
+
type: "text",
|
|
460
|
+
value: navItem.title
|
|
461
|
+
}]
|
|
462
|
+
}, {
|
|
463
|
+
type: "element",
|
|
464
|
+
name: "ol",
|
|
465
|
+
attributes: {},
|
|
466
|
+
children: navItem.children.map((subNavItem) => makeAnchorNode(subNavItem))
|
|
467
|
+
}]
|
|
468
|
+
});
|
|
469
|
+
else ol.push(makeAnchorNode(navItem));
|
|
470
|
+
builder.appendBody({
|
|
471
|
+
type: "element",
|
|
472
|
+
name: "nav",
|
|
473
|
+
attributes: { "epub:type": "toc" },
|
|
474
|
+
children: [...nav, {
|
|
475
|
+
type: "element",
|
|
476
|
+
name: "ol",
|
|
477
|
+
attributes: {},
|
|
478
|
+
children: ol
|
|
479
|
+
}]
|
|
480
|
+
});
|
|
481
|
+
return builder.build();
|
|
482
|
+
}
|
|
483
|
+
async bundle() {
|
|
484
|
+
return strToU8(this.generate().content);
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
//#endregion
|
|
488
|
+
export { Cover, EpubPublication, Image, ImageGif, ImageJpeg, ImagePng, ImageSvg, ImageWebp, Item, ItemRef, MIMETYPE, Manifest, Navigation, Rendition, Resource, StyleSheet, TextCSS, TextXHTML, XHTML, getImageMediaType };
|