Skip to content

Commit 808c7f6

Browse files
committed
New: Support for fixed-layout EPUBs
1 parent 674907c commit 808c7f6

File tree

8 files changed

+230
-76
lines changed

8 files changed

+230
-76
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
2020
- Options in OPDS to auto-download files from the catalog [`594d69f`](https://github.com/ollm/OpenComic/commit/594d69f81e3740c0be32e284dac3aed1aba69de9)
2121
- Support for tabs [`d9f7fa0`](https://github.com/ollm/OpenComic/commit/d9f7fa048b743c918c44e077e0bae08705e7b8c0)
2222
- Option to extract document images (PDF and EPUB) [`56b0f4f`](https://github.com/ollm/OpenComic/commit/56b0f4fda8c4b174469a2088c95ab786810d59b1)
23+
- New: Support for fixed-layout EPUBs
2324

2425
##### 🐛 Bug Fixes
2526

@@ -30,7 +31,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
3031
- Prevent crash when retrieving metadata with Sharp for many large images (Linux only) [`d5616d8`](https://github.com/ollm/OpenComic/commit/d5616d86bd38841e02bf70d4a2e2502063c45328)
3132
- Extract files when the file in it starts with the '-' character [`3806516`](https://github.com/ollm/OpenComic/commit/38065169d0a7583a7543154fad44351af221a23c)
3233
- Use reading sort for thumbnail/poster generation [`cca841e`](https://github.com/ollm/OpenComic/commit/cca841e3eacc88b60ca55d619a5f67beeef7f646)
33-
- Improve drag scrolling (Ignore right-click and add activation dead zone)
34+
- Improve drag scrolling (Ignore right-click and add activation dead zone) [`2df49c4`](https://github.com/ollm/OpenComic/commit/2df49c44ce8d3970c92d2b8b1e832e560c868674)
3435

3536
## [v1.6.5](https://github.com/ollm/OpenComic/releases/tag/v1.6.5) (31-10-2025)
3637

scripts/ebook.js

Lines changed: 92 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ var ebook = function(book, config = {}) {
3030

3131
for(let i = 0, len = this.book.chapters.length; i < len; i++)
3232
{
33-
promises.push(new Promise(function(resolve){
33+
promises.push(new Promise(async function(resolve){
3434

35+
const chapter = _this.book.chapters[i];
3536
let htmlString = _this.book.chapters[i].html;
3637

3738
if(typeof htmlString !== 'string')
@@ -40,12 +41,24 @@ var ebook = function(book, config = {}) {
4041
htmlString = serializer.serializeToString(htmlString);
4142
}
4243

44+
const _chapter = {
45+
fixedLayout: chapter.fixedLayout,
46+
width: chapter.width,
47+
height: chapter.height,
48+
pageSpread: chapter.pageSpread,
49+
};
50+
51+
// Only for debug splitInPages
52+
// const html = new DOMParser().parseFromString(htmlString, 'application/xhtml+xml');
53+
// const data = await _this.splitInPages(html.documentElement, chapter.basePath, chapter.path, _chapter);
54+
// resolve(data);
55+
4356
addToQueue(async function(data){
4457

4558
if(callback) await callback(i, data);
4659
resolve(data);
4760

48-
}, len, 'split-in-pages', _this.config, htmlString, _this.book.chapters[i].basePath, _this.book.chapters[i].path)
61+
}, len, 'split-in-pages', _this.config, htmlString, chapter.basePath, chapter.path, _chapter);
4962

5063
}));
5164
}
@@ -66,6 +79,8 @@ var ebook = function(book, config = {}) {
6679

6780
for(let i = 0, len = this.book.chapters.length; i < len; i++)
6881
{
82+
const chapter = _this.book.chapters[i];
83+
6984
promises.push(new Promise(function(resolve){
7085

7186
let htmlString = _this.book.chapters[i].html;
@@ -76,12 +91,27 @@ var ebook = function(book, config = {}) {
7691
htmlString = serializer.serializeToString(htmlString);
7792
}
7893

94+
const _chapter = {
95+
fixedLayout: chapter.fixedLayout,
96+
width: chapter.width,
97+
height: chapter.height,
98+
pageSpread: chapter.pageSpread,
99+
};
100+
101+
let _config = {};
102+
103+
if(chapter.fixedLayout)
104+
{
105+
_config.width = chapter.width;
106+
_config.height = chapter.height;
107+
}
108+
79109
addToQueue(async function(data){
80110

81111
if(callback) await callback(i, data);
82112
resolve(data);
83113

84-
}, len, 'render-page', _this.config, htmlString, _this.book.chapters[i].basePath)
114+
}, len, 'render-page', {..._this.config, ..._config}, htmlString, _this.book.chapters[i].basePath, false, _chapter);
85115

86116
}));
87117
}
@@ -150,18 +180,21 @@ var ebook = function(book, config = {}) {
150180

151181
}
152182

153-
this.splitInPages = async function(html, basePath, path = false) {
183+
this.splitInPages = async function(html, basePath, path = false, chapter = {}) {
154184

155185
html = html.cloneNode(true);
156186
html = await this.removeScripts(html); // This is unsafe, later the Sanitizer API would have to be applied
157187
html = await this.resolvePaths(html, basePath);
158-
html = await this.applyConfigToHtml(html);
188+
html = await this.applyConfigToHtml(html, chapter);
189+
190+
const width = chapter.fixedLayout ? chapter.width : this.config.width;
191+
const height = chapter.fixedLayout ? chapter.height : this.config.height;
159192

160193
//console.time('Load iframe');
161194

162195
let iframe = document.createElement('iframe');
163-
iframe.style.width = this.config.width+'px';
164-
iframe.style.height = this.config.height+'px';
196+
iframe.style.width = width+'px';
197+
iframe.style.height = height+'px';
165198
iframe.style.position = 'absolute';
166199
iframe.style.zIndex = '1000';
167200
iframe.style.backgroundColor = 'white';
@@ -191,7 +224,7 @@ var ebook = function(book, config = {}) {
191224
iframe.addEventListener('load', async function(event) {
192225

193226
//console.timeEnd('Load iframe');
194-
resolve(await _this.calculateAndSplit(iframe, path));
227+
resolve(await _this.calculateAndSplit(iframe, path, chapter));
195228

196229
});
197230

@@ -358,7 +391,7 @@ var ebook = function(book, config = {}) {
358391

359392
}
360393

361-
this.calculateAndSplit = async function(iframe, path) {
394+
this.calculateAndSplit = async function(iframe, path, chapter = {}) {
362395

363396
this._chaptersPage = [];
364397
this._chaptersPageFirst = true;
@@ -371,7 +404,7 @@ var ebook = function(book, config = {}) {
371404
let childs = parent.childNodes;
372405
let len = childs.length;
373406

374-
let hasToSplit = this.checkIfNodeHasToSplit(parent);
407+
let hasToSplit = chapter.fixedLayout || this.checkIfNodeHasToSplit(parent);
375408

376409
await this._calculateAndSplit(parent, childs, len, hasToSplit);
377410
this._chaptersPages.push(this._chaptersPage);
@@ -384,6 +417,7 @@ var ebook = function(book, config = {}) {
384417
path: path,
385418
ids: await this.getPageIds(this._chaptersPages[i]),
386419
html: await this.arrayBodyToHtml(iframe.contentDocument.head, this._chaptersPages[i]),
420+
chapter,
387421
};
388422
}
389423

@@ -517,17 +551,22 @@ var ebook = function(book, config = {}) {
517551

518552
}
519553

520-
this.applyConfigToHtml = function(doc) {
554+
this.applyConfigToHtml = function(doc, chapter = {}) {
521555

522556
let body = doc.body || doc.querySelector('body');
523557
let head = doc.head || doc.querySelector('head');
524558

559+
if(!body || !head)
560+
return doc;
561+
525562
body.style.overflow = 'hidden';
526563

527564
let epubType = body.getAttribute('epub:type') || '';
528565
epubType = app.extract(/^\s*([^\s]+)/, epubType, 1).toLowerCase();
529566

530-
if(epubType == 'cover')
567+
if(chapter.fixedLayout)
568+
this.applyFixedLayoutStyle(head, chapter);
569+
else if(epubType == 'cover')
531570
this.applyCoverStyle(head);
532571
else
533572
this.applyGeneralStyle(head);
@@ -536,6 +575,26 @@ var ebook = function(book, config = {}) {
536575

537576
}
538577

578+
this.applyFixedLayoutStyle = function(head, chapter) {
579+
580+
let css = `
581+
body
582+
{
583+
transform: scale(calc(100vw / ${chapter.width}px)) !important;
584+
transform-origin: top left !important;
585+
width: ${chapter.width}px;
586+
height: ${chapter.height}px;
587+
}
588+
`;
589+
590+
let style = head.querySelector('.opencomic-style') || document.createElement('style')
591+
style.className = 'opencomic-style';
592+
style.type = 'text/css';
593+
style.innerHTML = '';
594+
style.appendChild(document.createTextNode(css))
595+
head.appendChild(style);
596+
}
597+
539598
this.applyCoverStyle = function(head) {
540599

541600
let css = `
@@ -802,20 +861,19 @@ var ebook = function(book, config = {}) {
802861

803862
this.resolvePaths = async function(html, basePath) {
804863

805-
let href = html.querySelectorAll('[href]');
864+
const svg = [...html.querySelectorAll('svg *')].filter(element => element.hasAttribute('xlink:href'));
865+
const elements = [...html.querySelectorAll('[href], [src]'), ...svg];
806866

807-
for(let i = 0, len = href.length; i < len; i++)
867+
for(const item of elements)
808868
{
809-
let item = href[i];
810-
item.setAttribute('href', this.resolvePath(item.getAttribute('href'), basePath));
811-
}
812-
813-
let src = html.querySelectorAll('[src]');
869+
if(item.hasAttribute('href'))
870+
item.setAttribute('href', this.resolvePath(item.getAttribute('href'), basePath));
814871

815-
for(let i = 0, len = src.length; i < len; i++)
816-
{
817-
let item = src[i];
818-
item.setAttribute('src', this.resolvePath(item.getAttribute('src'), basePath));
872+
if(item.hasAttribute('src'))
873+
item.setAttribute('src', this.resolvePath(item.getAttribute('src'), basePath));
874+
875+
if(item.hasAttribute('xlink:href'))
876+
item.setAttribute('xlink:href', this.resolvePath(item.getAttribute('xlink:href'), basePath));
819877
}
820878

821879
return html;
@@ -828,16 +886,20 @@ var ebook = function(book, config = {}) {
828886

829887
}
830888

831-
this.pageToIframe = function(html) {
889+
this.pageToIframe = function(html, chapter = {}, configSize = false) {
832890

833-
let iframe = document.createElement('iframe');
891+
const iframe = document.createElement('iframe');
834892

835-
iframe.style.width = this.config.width+'px';
836-
iframe.style.height = this.config.height+'px';
893+
const width = chapter.fixedLayout ? chapter.width : this.config.width;
894+
const height = chapter.fixedLayout ? chapter.height : this.config.height;
895+
896+
iframe.style.width = configSize ? this.config.width+'px' : '100%';
897+
iframe.style.height = configSize ? this.config.height+'px' : '100%';
837898
iframe.style.backgroundColor = this.config.colors && this.config.colors.background ? this.config.colors.background : 'white';
838899
iframe.style.pointerEvents = 'none';
839900
iframe.sandbox = 'allow-same-origin';
840901
iframe.srcdoc = html;
902+
iframe.dataset.chapter = JSON.stringify(chapter);
841903

842904
return iframe;
843905

@@ -986,7 +1048,7 @@ var ebook = function(book, config = {}) {
9861048

9871049
let renderQueue = [], renderQueueNum = 1;
9881050

989-
async function addToQueue(callback, maxThreads, type, config, html, basePath, path = false)
1051+
async function addToQueue(callback, maxThreads, type, config, html, basePath, path = false, chapter = {})
9901052
{
9911053
renderQueue.push({
9921054
type: type,
@@ -995,6 +1057,7 @@ async function addToQueue(callback, maxThreads, type, config, html, basePath, pa
9951057
html: html,
9961058
path: path,
9971059
basePath: basePath,
1060+
chapter: chapter,
9981061
num: renderQueueNum++,
9991062
});
10001063

@@ -1035,6 +1098,7 @@ async function nextJobToRender(index = false, maxThreads = false)
10351098
html: job.html,
10361099
path: job.path,
10371100
basePath: job.basePath,
1101+
chapter: job.chapter,
10381102
};
10391103

10401104
renders[index].job = job;

0 commit comments

Comments
 (0)