Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Merge enhancements and fixes from Readest project into foliate-js upstream #43

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion epub.js
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,8 @@ class Resources {
?.getAttribute('content'))
?? this.getItemByHref(this.guide
?.find(ref => ref.type.includes('cover'))?.href)
?? this.manifest.find(item => item.href.includes('cover')
&& item.mediaType.startsWith('image'))

this.cfis = CFI.fromElements($$itemref)
}
Expand Down Expand Up @@ -859,7 +861,7 @@ class Loader {
const h = window?.innerHeight ?? 600
return replacedImports
// unprefix as most of the props are (only) supported unprefixed
.replace(/(?<=[{\s;])-epub-/gi, '')
.replace(/([{\s;])-epub-/gi, '$1')
// replace vw and vh as they cause problems with layout
.replace(/(\d*\.?\d+)vw/gi, (_, d) => parseFloat(d) * w / 100 + 'px')
.replace(/(\d*\.?\d+)vh/gi, (_, d) => parseFloat(d) * h / 100 + 'px')
Expand Down
2 changes: 2 additions & 0 deletions fixed-layout.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'construct-style-sheets-polyfill'

const parseViewport = str => str
?.split(/[,;\s]/) // NOTE: technically, only the comma is valid
?.filter(x => x)
Expand Down
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,8 @@
"dictd",
"stardict",
"opds"
]
],
"dependencies": {
"construct-style-sheets-polyfill": "^3.1.0"
}
}
48 changes: 36 additions & 12 deletions paginator.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,9 @@ class View {
// it needs to be visible for Firefox to get computed style
this.#iframe.style.display = 'block'
const { vertical, rtl } = getDirection(doc)
const background = getBackground(doc)
this.docBackground = getBackground(doc)
doc.body.style.background = 'none'
const background = this.docBackground
this.#iframe.style.display = 'none'

this.#vertical = vertical
Expand Down Expand Up @@ -610,7 +612,7 @@ export class Paginator extends HTMLElement {

this.#mediaQueryListener = () => {
if (!this.#view) return
this.#background.style.background = getBackground(this.#view.document)
this.#replaceBackground(this.#view.docBackground, this.columnCount)
}
this.#mediaQuery.addEventListener('change', this.#mediaQueryListener)
}
Expand Down Expand Up @@ -648,15 +650,31 @@ export class Paginator extends HTMLElement {
this.#container.append(this.#view.element)
return this.#view
}
#replaceBackground(background, columnCount) {
const doc = this.#view?.document
const htmlStyle = doc.defaultView.getComputedStyle(doc.documentElement)
const themeBgColor = htmlStyle.getPropertyValue('--theme-bg-color')
if (background && themeBgColor) {
const parsedBackground = background.split(/\s(?=(?:url|rgb|hsl|#[0-9a-fA-F]{3,6}))/)
parsedBackground[0] = themeBgColor
background = parsedBackground.join(' ')
}
this.#background.innerHTML = ''
this.#background.style.display = 'grid'
this.#background.style.gridTemplateColumns = `repeat(${columnCount}, 1fr)`
for (let i = 0; i < columnCount; i++) {
const column = document.createElement('div')
column.style.background = background
column.style.width = '100%'
column.style.height = '100%'
this.#background.appendChild(column)
}
}
#beforeRender({ vertical, rtl, background }) {
this.#vertical = vertical
this.#rtl = rtl
this.#top.classList.toggle('vertical', vertical)

// set background to `doc` background
// this is needed because the iframe does not fill the whole element
this.#background.style.background = background

const { width, height } = this.#container.getBoundingClientRect()
const size = vertical ? height : width

Expand Down Expand Up @@ -705,6 +723,11 @@ export class Paginator extends HTMLElement {
const columnWidth = (size / divisor) - gap
this.setAttribute('dir', rtl ? 'rtl' : 'ltr')

// set background to `doc` background
// this is needed because the iframe does not fill the whole element
this.columnCount = divisor
this.#replaceBackground(background, this.columnCount)

const marginalDivisor = vertical
? Math.min(2, Math.ceil(width / maxInlineSize))
: divisor
Expand Down Expand Up @@ -1042,11 +1065,11 @@ export class Paginator extends HTMLElement {
if (shouldGo || !this.hasAttribute('animated')) await wait(100)
this.#locked = false
}
prev(distance) {
return this.#turnPage(-1, distance)
async prev(distance) {
return await this.#turnPage(-1, distance)
}
next(distance) {
return this.#turnPage(1, distance)
async next(distance) {
return await this.#turnPage(1, distance)
}
prevSection() {
return this.goTo({ index: this.#adjacentIndex(-1) })
Expand Down Expand Up @@ -1082,8 +1105,9 @@ export class Paginator extends HTMLElement {
} else $style.textContent = styles

// NOTE: needs `requestAnimationFrame` in Chromium
requestAnimationFrame(() =>
this.#background.style.background = getBackground(this.#view.document))
requestAnimationFrame(() => {
this.#replaceBackground(this.#view.docBackground, this.columnCount)
})

// needed because the resize observer doesn't work in Firefox
this.#view?.document?.fonts?.ready?.then(() => this.#view.expand())
Expand Down
21 changes: 13 additions & 8 deletions pdf.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
const pdfjsPath = path => new URL(`vendor/pdfjs/${path}`, import.meta.url).toString()
const pdfjsPath = path => `/vendor/pdfjs/${path}`

import './vendor/pdfjs/pdf.mjs'
import '@pdfjs/pdf.mjs'
const pdfjsLib = globalThis.pdfjsLib
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsPath('pdf.worker.mjs')
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsPath('pdf.worker.min.mjs')

const fetchText = async url => await (await fetch(url)).text()

// https://github.com/mozilla/pdf.js/blob/642b9a5ae67ef642b9a8808fd9efd447e8c350e2/web/text_layer_builder.css
const textLayerBuilderCSS = await fetchText(pdfjsPath('text_layer_builder.css'))

// https://github.com/mozilla/pdf.js/blob/642b9a5ae67ef642b9a8808fd9efd447e8c350e2/web/annotation_layer_builder.css
const annotationLayerBuilderCSS = await fetchText(pdfjsPath('annotation_layer_builder.css'))
let textLayerBuilderCSS = null
let annotationLayerBuilderCSS = null

const render = async (page, doc, zoom) => {
const scale = zoom * devicePixelRatio
Expand Down Expand Up @@ -77,6 +74,14 @@ const renderPage = async (page, getImageBlob) => {
await page.render({ canvasContext, viewport }).promise
return new Promise(resolve => canvas.toBlob(resolve))
}
// https://github.com/mozilla/pdf.js/blob/642b9a5ae67ef642b9a8808fd9efd447e8c350e2/web/text_layer_builder.css
if (textLayerBuilderCSS == null) {
textLayerBuilderCSS = await fetchText(pdfjsPath('text_layer_builder.css'))
}
// https://github.com/mozilla/pdf.js/blob/642b9a5ae67ef642b9a8808fd9efd447e8c350e2/web/annotation_layer_builder.css
if (annotationLayerBuilderCSS == null) {
annotationLayerBuilderCSS = await fetchText(pdfjsPath('annotation_layer_builder.css'))
}
const src = URL.createObjectURL(new Blob([`
<!DOCTYPE html>
<html lang="en">
Expand Down
2 changes: 1 addition & 1 deletion text-walker.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const textWalker = function* (x, func) {
const walker = document.createTreeWalker(root, filter, { acceptNode })
const walk = x.commonAncestorContainer ? walkRange : walkDocument
const nodes = walk(x, walker)
const strs = nodes.map(node => node.nodeValue)
const strs = nodes.map(node => node.nodeValue ?? '')
const makeRange = (startIndex, startOffset, endIndex, endOffset) => {
const range = document.createRange()
range.setStart(nodes[startIndex], startOffset)
Expand Down
10 changes: 5 additions & 5 deletions tts.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const getSegmenter = (lang = 'en', granularity = 'word') => {
const segmenter = new Intl.Segmenter(lang, { granularity })
const granularityIsWord = granularity === 'word'
return function* (strs, makeRange) {
const str = strs.join('')
const str = strs.join('').replace(/\r\n/g, ' ').replace(/\r/g, ' ').replace(/\n/g, ' ')
let name = 0
let strIndex = -1
let sum = 0
Expand All @@ -34,10 +34,10 @@ const getSegmenter = (lang = 'en', granularity = 'word') => {
while (sum <= index) sum += strs[++strIndex].length
const startIndex = strIndex
const startOffset = index - (sum - strs[strIndex].length)
const end = index + segment.length
const end = index + segment.length - 1
if (end < str.length) while (sum <= end) sum += strs[++strIndex].length
const endIndex = strIndex
const endOffset = end - (sum - strs[strIndex].length)
const endOffset = end - (sum - strs[strIndex].length) + 1
yield [(name++).toString(),
makeRange(startIndex, startOffset, endIndex, endOffset)]
}
Expand Down Expand Up @@ -207,11 +207,11 @@ export class TTS {
#ranges
#lastMark
#serializer = new XMLSerializer()
constructor(doc, textWalker, highlight) {
constructor(doc, textWalker, highlight, granularity) {
this.doc = doc
this.highlight = highlight
this.#list = new ListIterator(getBlocks(doc), range => {
const { entries, ssml } = getFragmentWithMarks(range, textWalker)
const { entries, ssml } = getFragmentWithMarks(range, textWalker, granularity)
this.#ranges = new Map(entries)
return [ssml, range]
})
Expand Down
4 changes: 2 additions & 2 deletions view.js
Original file line number Diff line number Diff line change
Expand Up @@ -577,12 +577,12 @@ export class View extends HTMLElement {
for (const item of list) this.deleteAnnotation(item)
this.#searchResults.clear()
}
async initTTS() {
async initTTS(granularity = 'word') {
const doc = this.renderer.getContents()[0].doc
if (this.tts && this.tts.doc === doc) return
const { TTS } = await import('./tts.js')
this.tts = new TTS(doc, textWalker, range =>
this.renderer.scrollToAnchor(range, true))
this.renderer.scrollToAnchor(range, true), granularity)
}
startMediaOverlay() {
const { index } = this.renderer.getContents()[0]
Expand Down