forked from akai/readest
fix(epub): support SVG cover for ebooks from standardebooks.org (#2645)
This commit is contained in:
@@ -72,6 +72,7 @@ import { BOOK_FILE_NOT_FOUND_ERROR } from './errors';
|
||||
import { CustomTextureInfo } from '@/styles/textures';
|
||||
import { CustomFont, CustomFontInfo } from '@/styles/fonts';
|
||||
import { parseFontInfo } from '@/utils/font';
|
||||
import { svg2png } from '@/utils/svg';
|
||||
|
||||
export abstract class BaseAppService implements AppService {
|
||||
osPlatform: OsPlatform = getOSPlatform();
|
||||
@@ -418,7 +419,13 @@ export abstract class BaseAppService implements AppService {
|
||||
}
|
||||
}
|
||||
if (saveCover && (!(await this.fs.exists(getCoverFilename(book), 'Books')) || overwrite)) {
|
||||
const cover = await loadedBook.getCover();
|
||||
let cover = await loadedBook.getCover();
|
||||
if (cover?.type === 'image/svg+xml') {
|
||||
try {
|
||||
console.log('Converting SVG cover to PNG...');
|
||||
cover = await svg2png(cover);
|
||||
} catch {}
|
||||
}
|
||||
if (cover) {
|
||||
await this.fs.writeFile(getCoverFilename(book), 'Books', await cover.arrayBuffer());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
function parseSvgLength(value: string) {
|
||||
const n = parseFloat(value);
|
||||
if (!isNaN(n)) return n;
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function getSvgSize(
|
||||
svgBlob: Blob,
|
||||
defaultWidth: number = 700,
|
||||
defaultHeight: number = 1050,
|
||||
): Promise<{ width: number; height: number }> {
|
||||
const text = await svgBlob.text();
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(text, 'image/svg+xml');
|
||||
const svg = doc.documentElement;
|
||||
|
||||
const widthAttr = svg.getAttribute('width');
|
||||
const heightAttr = svg.getAttribute('height');
|
||||
|
||||
if (widthAttr && heightAttr) {
|
||||
return {
|
||||
width: parseSvgLength(widthAttr) || defaultWidth,
|
||||
height: parseSvgLength(heightAttr) || defaultHeight,
|
||||
};
|
||||
}
|
||||
|
||||
const viewBox = svg.getAttribute('viewBox');
|
||||
if (viewBox) {
|
||||
const parts = viewBox.split(/\s+/).map(Number);
|
||||
if (parts.length === 4 && !parts.some(isNaN)) {
|
||||
const [, , vbWidth, vbHeight] = parts;
|
||||
return { width: vbWidth || defaultWidth, height: vbHeight || defaultHeight };
|
||||
}
|
||||
}
|
||||
|
||||
return { width: defaultWidth, height: defaultHeight };
|
||||
}
|
||||
|
||||
export async function svg2png(svgBlob: Blob, quality: number = 0.9): Promise<Blob> {
|
||||
const svgText = await svgBlob.text();
|
||||
|
||||
const svgUrl = URL.createObjectURL(new Blob([svgText], { type: 'image/svg+xml' }));
|
||||
|
||||
const img = new Image();
|
||||
img.decoding = 'sync';
|
||||
img.crossOrigin = 'anonymous';
|
||||
img.src = svgUrl;
|
||||
|
||||
await img.decode().catch(
|
||||
() =>
|
||||
new Promise((resolve, reject) => {
|
||||
img.onload = resolve;
|
||||
img.onerror = reject;
|
||||
}),
|
||||
);
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const { width, height } = await getSvgSize(svgBlob);
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
ctx.drawImage(img, 0, 0);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
canvas.toBlob((blob) => resolve(blob!), 'image/png', quality);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user