From cc88ee5f82811a2b0a6fa0dbb600c85054f488ba Mon Sep 17 00:00:00 2001 From: XLor Date: Fri, 3 Mar 2023 04:42:09 +0800 Subject: [PATCH] feat: generate cover --- .github/workflows/ci.yml | 1 + packages/core/src/bundle/bundle.ts | 2 +- packages/core/src/epub/item.ts | 4 +++ packages/core/src/theme.ts | 19 ++++++++-- packages/epubook/src/epubook.ts | 56 +++++++++++++++++++++++------ packages/epubook/src/theme.ts | 17 ++++++++- packages/epubook/test/index.test.ts | 3 +- 7 files changed, 85 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff08e0b..1c2ca96 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,3 +41,4 @@ jobs: pnpm test:ci unzip epubcheck-5.0.0.zip java -jar ./epubcheck-5.0.0/epubcheck.jar .output/test-core.epub + java -jar ./epubcheck-5.0.0/epubcheck.jar .output/test-cover.epub diff --git a/packages/core/src/bundle/bundle.ts b/packages/core/src/bundle/bundle.ts index f7989a0..1223646 100644 --- a/packages/core/src/bundle/bundle.ts +++ b/packages/core/src/bundle/bundle.ts @@ -156,7 +156,7 @@ export function makePackageDocument(opf: PackageDocument): string { { '@_refines': '#' + opf.creator().uid, '@_property': 'file-as', - '#text': opf.creator()?.fileAs ?? '' + '#text': opf.creator()?.fileAs ?? opf.creator().name } ] }; diff --git a/packages/core/src/epub/item.ts b/packages/core/src/epub/item.ts index 6c9f9da..96c453a 100644 --- a/packages/core/src/epub/item.ts +++ b/packages/core/src/epub/item.ts @@ -123,6 +123,10 @@ export abstract class Item { return this.file; } + public relative(from: string) { + return path.relative(path.dirname(from), this.file); + } + public update(info: Partial<{ properties: string }>) { if (info.properties) { this._properties = info.properties; diff --git a/packages/core/src/theme.ts b/packages/core/src/theme.ts index ab2b9f1..5e50291 100644 --- a/packages/core/src/theme.ts +++ b/packages/core/src/theme.ts @@ -1,9 +1,22 @@ -export interface Theme { - name: string; +import { Image } from './epub'; +import { XHTMLBuilder } from './render'; + +type Prettify = { + [K in keyof T]: T[K]; +} & {}; - pages: string[]; +export type PageTemplate = (file: string, props: T) => XHTMLBuilder; + +export interface Theme

>> { + name: string; styles: string[]; images: string[]; + + pages: Prettify< + { + cover(file: string, props: { image: Image }): XHTMLBuilder; + } & P + >; } diff --git a/packages/epubook/src/epubook.ts b/packages/epubook/src/epubook.ts index fe08be7..47f78b4 100644 --- a/packages/epubook/src/epubook.ts +++ b/packages/epubook/src/epubook.ts @@ -1,7 +1,9 @@ import { type Author, type Theme, + type PageTemplate, type ImageMediaType, + Html, Epub, Image, ImageExtension @@ -14,6 +16,8 @@ import { loadTheme } from './theme'; export interface EpubookOption { title: string; + description: string; + language: string; author: Author[]; @@ -21,23 +25,26 @@ export interface EpubookOption { theme: string; } +const TextDir = 'text'; const ImageDir = 'images'; -export class Epubook { +export class Epubook

= {}> { private container: Epub; private option: EpubookOption; - private theme!: Theme; + private theme!: Theme

; + + private content: Html[] = []; - private counter = { - image: 0, - page: 0 + private counter: Record = { + image: 1 }; private constructor(option: Partial) { this.option = { - title: '', + title: 'unknown', + description: 'unknown', language: 'zh-CN', author: [{ name: 'unknown' }], theme: '@epubook/theme-default', @@ -47,13 +54,15 @@ export class Epubook { this.container = new Epub(this.option); } - public static async create(option: Partial) { + public static async create

= {}>( + option: Partial + ): Promise> { const epubook = new Epubook(option); - return await epubook.loadTheme(); + return (await epubook.loadTheme()) as Epubook

; } private async loadTheme() { - await loadTheme(this.option.theme); + this.theme = (await loadTheme(this.option.theme)) as Theme

; return this; } @@ -89,6 +98,7 @@ export class Epubook { typeof img === 'string' ? await this.loadImage(`${ImageDir}/image-${this.counter.image}.${ext}`, img) : await this.loadImage(`${ImageDir}/image-${this.counter.image}.${ext}`, ext!); + this.counter.image++; return image; } @@ -104,14 +114,31 @@ export class Epubook { : await this.loadImage(`${ImageDir}/cover.${ext}`, ext!); if (image) { image.update({ properties: 'cover-image' }); + const page = this.page('cover', { image }, { file: `${TextDir}/cover.xhtml` }); + this.content.unshift(page); return image; } else { return undefined; } } - public page() { - return this; + public page['pages']>( + template: T, + props: Parameters['pages'][T]>[1], + option: { file?: string } = {} + ) { + const render: Theme

['pages'][T] = this.theme.pages[template]; + const file = option.file ?? `${TextDir}/${template}-${this.counter[template] ?? 1}.xhtml`; + const builder = render(file, props); + const content = builder.build(); + if (!this.counter[template]) { + this.counter[template] = 2; + } else { + this.counter[template]++; + } + const xhtml = new Html(file, content); + this.container.addItem(xhtml); + return xhtml; } public toc() { @@ -122,11 +149,18 @@ export class Epubook { return this; } + private async preBundle() { + this.container.toc(this.content.map((c) => ({ text: c.id(), item: c }))); + this.container.spine(...this.content); + } + public async bundle() { + this.preBundle(); return this.container.bundle(); } public async writeFile(file: string) { + this.preBundle(); return this.container.writeFile(file); } } diff --git a/packages/epubook/src/theme.ts b/packages/epubook/src/theme.ts index 1c0c00c..5b75823 100644 --- a/packages/epubook/src/theme.ts +++ b/packages/epubook/src/theme.ts @@ -1 +1,16 @@ -export async function loadTheme(pkg: string) {} +import { Theme, XHTMLBuilder } from '@epubook/core'; + +export async function loadTheme(pkg: string): Promise> { + return { + name: 'theme-default', + images: [], + styles: [], + pages: { + cover(file, { image }) { + return new XHTMLBuilder() + .title('cover') + .body({ tag: 'img', attrs: { src: image.relative(file) } }); + } + } + }; +} diff --git a/packages/epubook/test/index.test.ts b/packages/epubook/test/index.test.ts index e724f02..626f12d 100644 --- a/packages/epubook/test/index.test.ts +++ b/packages/epubook/test/index.test.ts @@ -5,7 +5,8 @@ import { Epubook } from '../src'; describe('epubook', () => { it('should write epub with cover', async () => { const book = await Epubook.create({ - title: 'cover' + title: 'cover', + description: 'This is generated for testing cover image' }); await book.cover('../../assets/cover.jpg');