Der Lesestand wird gleich beim Öffnen erfragt (parallel zur Datei), ein PDF zeichnet zuerst die Seite, an der es weitergeht, statt Seite 1; Titel, Inhaltsverzeichnis und Seitenzahlen kommen nebenher und halten das Öffnen nicht mehr auf. Eine Zeichnung, die schon läuft, wird abgewartet statt neu begonnen. „Leserfarben“ (neben Originalfarben und Invertieren): die Seite in den Farben des Lesers (Dunkel, Sepia) per SVG-Filter, Bilder darüber wie gedruckt (ihre Lage aus den Zeichenbefehlen); ein Bild, das die halbe Seite füllt, ist ein Scan und wird mit umgefärbt. pdf.js' eigene pageColors färben die Bilder mit um. Ein anderes Design ändert nur die Farben des Filters. Seitenzahlen des Buchs (PageLabels: „iv“, „A-3“) im Kopf mit der Seite der Datei daneben, eingeben lässt sich beides; Lesezeichen und der Zurück-Knopf nennen sie. Das Test-PDF hat dafür Seitenzahlen und ein farbiges Bild bekommen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
414 lines
20 KiB
JavaScript
414 lines
20 KiB
JavaScript
'use strict';
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Reader Settings
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// Typography defaults. Phones keep the long-standing ones; a wide screen
|
||
// (desktop browser, diora desktop) starts bigger and wider.
|
||
const _READER_TYPE_DEFAULTS_NARROW = { fontSize: 16, lineHeight: 1.8, maxWidth: 65, fontWeight: null };
|
||
const _READER_TYPE_DEFAULTS_WIDE = { fontSize: 20, lineHeight: 1.6, maxWidth: 90, fontWeight: 375 };
|
||
const _READER_WIDTH_MAX = 140; // ch, the width slider's upper end ("Full" stays separate)
|
||
|
||
function _readerTypeDefaults() {
|
||
return window.matchMedia('(min-width: 900px)').matches
|
||
? _READER_TYPE_DEFAULTS_WIDE : _READER_TYPE_DEFAULTS_NARROW;
|
||
}
|
||
|
||
// Where the settings shown are kept: the book's id, or 'article' (all articles, 65-article.js).
|
||
let _readerSettingsKey = null;
|
||
|
||
function loadReaderSettings(bookId) {
|
||
_readerSettingsKey = bookId;
|
||
// Reset to defaults, then apply per-book overrides
|
||
Object.assign(readerSettings, { theme: 'app', fontFamily: 'serif', noBold: false,
|
||
pdfZoom: 100, pdfInverted: false, pdfPageColors: false, pdfPaginated: false, pdfSpread: false },
|
||
_readerTypeDefaults());
|
||
try {
|
||
const saved = JSON.parse(localStorage.getItem(`diora_reader_settings_${bookId}`) || '{}');
|
||
// Settings saved before v2 hold every value, changed or not. A value equal
|
||
// to the old default was never chosen, so it gives way to today's default.
|
||
if (!saved.v) {
|
||
for (const [key, old] of Object.entries(_READER_TYPE_DEFAULTS_NARROW)) {
|
||
if (saved[key] === old) delete saved[key];
|
||
}
|
||
}
|
||
// Before v3 the reader's theme was always stored, "dark" by default: that now means "Wie die App".
|
||
if ((saved.v || 0) < 3 && saved.theme === 'dark') delete saved.theme;
|
||
delete saved.v;
|
||
Object.assign(readerSettings, saved);
|
||
if (saved.pdfPaginated === undefined) {
|
||
readerSettings.pdfPaginated = window.innerWidth < 768;
|
||
}
|
||
} catch (e) {}
|
||
}
|
||
|
||
function saveReaderSettings() {
|
||
const key = currentBookId || _readerSettingsKey;
|
||
if (key) {
|
||
localStorage.setItem(`diora_reader_settings_${key}`, JSON.stringify({ ...readerSettings, v: 3 }));
|
||
}
|
||
}
|
||
|
||
function applyReaderSettings(isPdf) {
|
||
const overlay = $('reader-overlay');
|
||
const contentEl = $('reader-content');
|
||
if (!overlay || !contentEl) return;
|
||
|
||
if (!isPdf) {
|
||
contentEl.style.fontSize = readerSettings.fontSize + 'px';
|
||
contentEl.style.lineHeight = readerSettings.lineHeight;
|
||
contentEl.style.setProperty('--reader-max-width', readerSettings.maxWidth + 'ch');
|
||
const fontMap = {
|
||
serif: "'Source Serif 4', Georgia, 'Times New Roman', serif",
|
||
sans: 'system-ui, -apple-system, sans-serif',
|
||
humanist: 'Verdana, Geneva, sans-serif',
|
||
mono: "'Courier New', monospace",
|
||
};
|
||
contentEl.style.fontFamily = fontMap[readerSettings.fontFamily] || fontMap.serif;
|
||
contentEl.classList.toggle('reader-no-bold', !!readerSettings.noBold);
|
||
// Weight: null keeps the stylesheet's. --reader-weight lets old WebViews
|
||
// without variable-font support (which drop weights like 425) pick a
|
||
// static cut instead.
|
||
contentEl.style.fontWeight = '';
|
||
if (readerSettings.fontWeight) {
|
||
contentEl.style.fontWeight = String(readerSettings.fontWeight);
|
||
contentEl.style.setProperty('--reader-weight', String(readerSettings.fontWeight));
|
||
} else {
|
||
contentEl.style.removeProperty('--reader-weight');
|
||
}
|
||
if (_currentPositionAnchor && currentBookId) {
|
||
_suppressScrollJumpDetect();
|
||
requestAnimationFrame(() => restoreFromAnchor($('reader-content'), _currentPositionAnchor));
|
||
}
|
||
}
|
||
|
||
// Theme
|
||
overlay.classList.remove('reader-theme-sepia', 'reader-theme-bright');
|
||
const readerTheme = readerSettings.theme === 'app'
|
||
? (document.documentElement.getAttribute('data-theme') === 'light' ? 'bright' : 'dark')
|
||
: readerSettings.theme;
|
||
if (readerTheme === 'sepia') overlay.classList.add('reader-theme-sepia');
|
||
else if (readerTheme === 'bright') overlay.classList.add('reader-theme-bright');
|
||
|
||
// PDF invert
|
||
if (isPdf && readerSettings.pdfInverted) overlay.classList.add('pdf-inverted');
|
||
else overlay.classList.remove('pdf-inverted');
|
||
// Leserfarben: another theme changes the filter's colours; switching it on or off draws
|
||
// the pages anew (with or without their pictures laid over them).
|
||
if (isPdf && currentPdfDoc) {
|
||
_pdfColorFilter();
|
||
if (_pdfView && _pdfView.colorsKey !== _pdfColorsKey()) reRenderPdf();
|
||
}
|
||
|
||
// Annotations (marker mode + margin panel) are EPUB-only for now — PDFs have no text layer yet
|
||
const marginBtn = $('reader-margin-btn');
|
||
if (marginBtn) marginBtn.style.display = isPdf ? 'none' : '';
|
||
if (isPdf && readerAnnotationsMarginOpen) toggleAnnotationsMargin();
|
||
document.querySelectorAll('.marker-mode-btn').forEach(b => { b.style.display = isPdf ? 'none' : ''; });
|
||
if (isPdf && markerModeActive) toggleMarkerMode();
|
||
// Line length or font changed: the column's place next to an open margin too.
|
||
if (readerAnnotationsMarginOpen) _centerReaderColumn();
|
||
}
|
||
|
||
// Font weight slider: the variable Source Serif 4 takes any weight, the
|
||
// system fonts only their fixed cuts (multiples of 100).
|
||
function _readerWeightStep() {
|
||
return readerSettings.fontFamily === 'serif' ? 25 : 100;
|
||
}
|
||
|
||
function _readerWeight() {
|
||
const step = _readerWeightStep();
|
||
const w = readerSettings.fontWeight
|
||
|| parseInt(getComputedStyle($('reader-content')).fontWeight, 10) || 400;
|
||
return Math.round(w / step) * step;
|
||
}
|
||
|
||
// "Scrollen | Seiten" and, in pages, whether the notes stand at the page's foot
|
||
// and whether two pages lie side by side.
|
||
// The apps' e-reader mode always reads in pages: no choice there.
|
||
function _readerModeButtons() {
|
||
if (!window.DioraPages || DioraPages.isForced()) return '';
|
||
const pages = DioraPages.isOn();
|
||
return `
|
||
<button class="btn btn-sm ${pages ? '' : 'active'}" data-rs-mode="scroll">Scrollen</button>
|
||
<button class="btn btn-sm ${pages ? 'active' : ''}" data-rs-mode="pages">Seiten</button>
|
||
<button class="btn btn-sm ${DioraPages.pageNotes() ? 'active' : ''}" id="rs-page-notes" style="${pages ? '' : 'display:none'}"
|
||
title="Die Fußnoten stehen unten auf der Seite, auf der auf sie verwiesen wird">Fußnoten unten</button>
|
||
<button class="btn btn-sm ${DioraPages.spread() ? 'active' : ''}" id="rs-page-spread" style="${pages ? '' : 'display:none'}"
|
||
title="Zwei Seiten nebeneinander wie ein aufgeschlagenes Buch — ab einer Breite von 900 Pixeln">Doppelseite</button>`;
|
||
}
|
||
|
||
function toggleSettingsPanel() {
|
||
const overlay = $('reader-overlay');
|
||
const contentEl = $('reader-content');
|
||
if (!overlay || !contentEl) return;
|
||
|
||
const existing = document.getElementById('reader-settings-panel');
|
||
if (existing) {
|
||
existing.remove();
|
||
readerSettingsPanelOpen = false;
|
||
return;
|
||
}
|
||
|
||
readerSettingsPanelOpen = true;
|
||
const isPdf = !!currentPdfDoc;
|
||
|
||
const panel = document.createElement('div');
|
||
panel.id = 'reader-settings-panel';
|
||
panel.className = 'reader-settings-panel';
|
||
|
||
if (!isPdf) {
|
||
panel.innerHTML = `
|
||
<label>Schrift <button class="btn btn-sm" id="rs-font-minus">−</button> <input type="range" id="rs-font" min="12" max="24" step="1" value="${readerSettings.fontSize}"> <button class="btn btn-sm" id="rs-font-plus">+</button> <span id="rs-font-val">${readerSettings.fontSize}px</span></label>
|
||
<label>Zeilen <button class="btn btn-sm" id="rs-line-minus">−</button> <input type="range" id="rs-line" min="12" max="30" step="1" value="${Math.round(readerSettings.lineHeight * 10)}"> <button class="btn btn-sm" id="rs-line-plus">+</button> <span id="rs-line-val">${readerSettings.lineHeight}</span></label>
|
||
<label>Breite <button class="btn btn-sm" id="rs-width-minus">−</button> <input type="range" id="rs-width" min="40" max="${_READER_WIDTH_MAX}" step="5" value="${Math.min(readerSettings.maxWidth, _READER_WIDTH_MAX)}"> <button class="btn btn-sm" id="rs-width-plus">+</button> <span id="rs-width-val">${readerSettings.maxWidth}ch</span></label>
|
||
<button class="btn btn-sm" id="rs-width-full">Volle Breite</button>
|
||
<label>Stärke <button class="btn btn-sm" id="rs-weight-minus">−</button> <input type="range" id="rs-weight" min="200" max="800" step="${_readerWeightStep()}" value="${_readerWeight()}"> <button class="btn btn-sm" id="rs-weight-plus">+</button> <span id="rs-weight-val">${_readerWeight()}</span></label>
|
||
<button class="btn btn-sm ${readerSettings.fontFamily === 'serif' ? 'active' : ''}" data-rs-font="serif">Serif</button>
|
||
<button class="btn btn-sm ${readerSettings.fontFamily === 'sans' ? 'active' : ''}" data-rs-font="sans">Sans</button>
|
||
<button class="btn btn-sm ${readerSettings.fontFamily === 'humanist' ? 'active' : ''}" data-rs-font="humanist">Verdana</button>
|
||
<button class="btn btn-sm ${readerSettings.fontFamily === 'mono' ? 'active' : ''}" data-rs-font="mono">Mono</button>
|
||
<button class="btn btn-sm ${readerSettings.noBold ? 'active' : ''}" id="rs-no-bold">Kein Fett</button>
|
||
<button class="btn btn-sm ${readerSettings.theme === 'app' ? 'active' : ''}" data-rs-theme="app">Wie die App</button>
|
||
<button class="btn btn-sm ${readerSettings.theme === 'dark' ? 'active' : ''}" data-rs-theme="dark">Dunkel</button>
|
||
<button class="btn btn-sm ${readerSettings.theme === 'sepia' ? 'active' : ''}" data-rs-theme="sepia">Sepia</button>
|
||
<button class="btn btn-sm ${readerSettings.theme === 'bright' ? 'active' : ''}" data-rs-theme="bright">Hell</button>
|
||
${_readerModeButtons()}
|
||
`;
|
||
} else {
|
||
panel.innerHTML = `
|
||
<label>Zoom <button class="btn btn-sm" id="rs-zoom-minus">−</button> <input type="range" id="rs-zoom" min="50" max="200" step="10" value="${readerSettings.pdfZoom}"> <button class="btn btn-sm" id="rs-zoom-plus">+</button> <span id="rs-zoom-val">${readerSettings.pdfZoom}%</span></label>
|
||
<button class="btn btn-sm ${readerSettings.pdfPaginated ? '' : 'active'}" data-rs-pdfmode="scroll">Scrollen</button>
|
||
<button class="btn btn-sm ${readerSettings.pdfPaginated ? 'active' : ''}" data-rs-pdfmode="pages">Seiten</button>
|
||
<button class="btn btn-sm ${!readerSettings.pdfInverted && !readerSettings.pdfPageColors ? 'active' : ''}" data-rs-pdfcolors="original"
|
||
title="Die Seiten wie gedruckt">Originalfarben</button>
|
||
<button class="btn btn-sm ${!readerSettings.pdfInverted && readerSettings.pdfPageColors ? 'active' : ''}" data-rs-pdfcolors="reader"
|
||
title="Text und Hintergrund in den Farben des Lesers (Dunkel, Sepia), Bilder bleiben, wie sie sind">Leserfarben</button>
|
||
<button class="btn btn-sm ${readerSettings.pdfInverted ? 'active' : ''}" data-rs-pdfcolors="invert"
|
||
title="Die ganze Seite umgekehrt, auch Bilder (Farben bleiben ungefähr)">Invertieren</button>
|
||
<button class="btn btn-sm ${readerSettings.pdfSpread ? 'active' : ''}" id="rs-spread"${readerSettings.pdfPaginated ? ' style="display:none"' : ''}>Doppelseite</button>
|
||
`;
|
||
}
|
||
|
||
overlay.insertBefore(panel, $('reader-body-row'));
|
||
|
||
if (!isPdf) {
|
||
const fontRange = panel.querySelector('#rs-font');
|
||
const fontVal = panel.querySelector('#rs-font-val');
|
||
fontRange.addEventListener('input', () => {
|
||
readerSettings.fontSize = parseInt(fontRange.value, 10);
|
||
fontVal.textContent = readerSettings.fontSize + 'px';
|
||
applyReaderSettings(false);
|
||
saveReaderSettings();
|
||
});
|
||
panel.querySelector('#rs-font-minus').addEventListener('click', () => {
|
||
fontRange.value = Math.max(+fontRange.min, parseInt(fontRange.value, 10) - 1);
|
||
fontRange.dispatchEvent(new Event('input'));
|
||
});
|
||
panel.querySelector('#rs-font-plus').addEventListener('click', () => {
|
||
fontRange.value = Math.min(+fontRange.max, parseInt(fontRange.value, 10) + 1);
|
||
fontRange.dispatchEvent(new Event('input'));
|
||
});
|
||
|
||
const lineRange = panel.querySelector('#rs-line');
|
||
const lineVal = panel.querySelector('#rs-line-val');
|
||
lineRange.addEventListener('input', () => {
|
||
readerSettings.lineHeight = (parseInt(lineRange.value, 10) / 10).toFixed(1);
|
||
lineVal.textContent = readerSettings.lineHeight;
|
||
applyReaderSettings(false);
|
||
saveReaderSettings();
|
||
});
|
||
panel.querySelector('#rs-line-minus').addEventListener('click', () => {
|
||
lineRange.value = Math.max(+lineRange.min, parseInt(lineRange.value, 10) - 1);
|
||
lineRange.dispatchEvent(new Event('input'));
|
||
});
|
||
panel.querySelector('#rs-line-plus').addEventListener('click', () => {
|
||
lineRange.value = Math.min(+lineRange.max, parseInt(lineRange.value, 10) + 1);
|
||
lineRange.dispatchEvent(new Event('input'));
|
||
});
|
||
|
||
const widthRange = panel.querySelector('#rs-width');
|
||
const widthVal = panel.querySelector('#rs-width-val');
|
||
widthRange.addEventListener('input', () => {
|
||
readerSettings.maxWidth = parseInt(widthRange.value, 10);
|
||
widthVal.textContent = readerSettings.maxWidth + 'ch';
|
||
applyReaderSettings(false);
|
||
saveReaderSettings();
|
||
});
|
||
panel.querySelector('#rs-width-minus').addEventListener('click', () => {
|
||
widthRange.value = Math.max(+widthRange.min, parseInt(widthRange.value, 10) - 5);
|
||
widthRange.dispatchEvent(new Event('input'));
|
||
});
|
||
panel.querySelector('#rs-width-plus').addEventListener('click', () => {
|
||
widthRange.value = Math.min(+widthRange.max, parseInt(widthRange.value, 10) + 5);
|
||
widthRange.dispatchEvent(new Event('input'));
|
||
});
|
||
|
||
panel.querySelector('#rs-width-full').addEventListener('click', () => {
|
||
readerSettings.maxWidth = 999;
|
||
widthRange.value = 90;
|
||
widthVal.textContent = 'full';
|
||
applyReaderSettings(false);
|
||
saveReaderSettings();
|
||
});
|
||
|
||
const weightRange = panel.querySelector('#rs-weight');
|
||
const weightVal = panel.querySelector('#rs-weight-val');
|
||
weightRange.addEventListener('input', () => {
|
||
readerSettings.fontWeight = parseInt(weightRange.value, 10);
|
||
weightVal.textContent = readerSettings.fontWeight;
|
||
applyReaderSettings(false);
|
||
saveReaderSettings();
|
||
});
|
||
panel.querySelector('#rs-weight-minus').addEventListener('click', () => {
|
||
weightRange.value = Math.max(+weightRange.min, parseInt(weightRange.value, 10) - +weightRange.step);
|
||
weightRange.dispatchEvent(new Event('input'));
|
||
});
|
||
panel.querySelector('#rs-weight-plus').addEventListener('click', () => {
|
||
weightRange.value = Math.min(+weightRange.max, parseInt(weightRange.value, 10) + +weightRange.step);
|
||
weightRange.dispatchEvent(new Event('input'));
|
||
});
|
||
|
||
panel.querySelectorAll('[data-rs-font]').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
readerSettings.fontFamily = btn.dataset.rsFont;
|
||
panel.querySelectorAll('[data-rs-font]').forEach(b => b.classList.toggle('active', b === btn));
|
||
// The system fonts only have cuts at multiples of 100.
|
||
weightRange.step = _readerWeightStep();
|
||
if (readerSettings.fontWeight) {
|
||
readerSettings.fontWeight = Math.round(readerSettings.fontWeight / weightRange.step) * weightRange.step;
|
||
}
|
||
applyReaderSettings(false);
|
||
saveReaderSettings();
|
||
weightRange.value = _readerWeight();
|
||
weightVal.textContent = _readerWeight();
|
||
});
|
||
});
|
||
|
||
panel.querySelector('#rs-no-bold').addEventListener('click', function () {
|
||
readerSettings.noBold = !readerSettings.noBold;
|
||
this.classList.toggle('active', readerSettings.noBold);
|
||
applyReaderSettings(false);
|
||
saveReaderSettings();
|
||
});
|
||
|
||
panel.querySelectorAll('[data-rs-theme]').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
readerSettings.theme = btn.dataset.rsTheme;
|
||
panel.querySelectorAll('[data-rs-theme]').forEach(b => b.classList.toggle('active', b === btn));
|
||
applyReaderSettings(false);
|
||
saveReaderSettings();
|
||
});
|
||
});
|
||
|
||
// Scrolling or pages (63-pages.js): per device, for every book.
|
||
const syncMode = () => {
|
||
const pages = DioraPages.isOn();
|
||
panel.querySelectorAll('[data-rs-mode]').forEach(b => b.classList.toggle('active', (b.dataset.rsMode === 'pages') === pages));
|
||
const notes = panel.querySelector('#rs-page-notes');
|
||
if (notes) {
|
||
notes.style.display = pages ? '' : 'none';
|
||
notes.classList.toggle('active', DioraPages.pageNotes());
|
||
}
|
||
const spread = panel.querySelector('#rs-page-spread');
|
||
if (spread) {
|
||
spread.style.display = pages ? '' : 'none';
|
||
spread.classList.toggle('active', DioraPages.spread());
|
||
}
|
||
};
|
||
panel.querySelectorAll('[data-rs-mode]').forEach(btn => {
|
||
btn.addEventListener('click', () => { DioraPages.setOn(btn.dataset.rsMode === 'pages'); syncMode(); });
|
||
});
|
||
panel.querySelector('#rs-page-notes')?.addEventListener('click', () => { DioraPages.togglePageNotes(); syncMode(); });
|
||
panel.querySelector('#rs-page-spread')?.addEventListener('click', () => { DioraPages.setSpread(!DioraPages.spread()); syncMode(); });
|
||
} else {
|
||
const zoomRange = panel.querySelector('#rs-zoom');
|
||
|
||
// While the slider moves only the number follows; the pages are drawn anew once it rests.
|
||
let zoomTimer = 0;
|
||
zoomRange.addEventListener('input', () => {
|
||
const z = parseInt(zoomRange.value, 10);
|
||
const val = panel.querySelector('#rs-zoom-val');
|
||
if (val) val.textContent = z + '%';
|
||
clearTimeout(zoomTimer);
|
||
zoomTimer = setTimeout(() => applyPdfZoom(z), 250);
|
||
});
|
||
panel.querySelector('#rs-zoom-minus').addEventListener('click', () =>
|
||
applyPdfZoom(Math.max(50, readerSettings.pdfZoom - 10)));
|
||
panel.querySelector('#rs-zoom-plus').addEventListener('click', () =>
|
||
applyPdfZoom(Math.min(200, readerSettings.pdfZoom + 10)));
|
||
|
||
// The pages' colours: as printed, in the reader's colours, or inverted.
|
||
panel.querySelectorAll('[data-rs-pdfcolors]').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
const which = btn.dataset.rsPdfcolors;
|
||
readerSettings.pdfInverted = which === 'invert';
|
||
readerSettings.pdfPageColors = which === 'reader';
|
||
panel.querySelectorAll('[data-rs-pdfcolors]').forEach(b => b.classList.toggle('active', b === btn));
|
||
applyReaderSettings(true);
|
||
saveReaderSettings();
|
||
});
|
||
});
|
||
|
||
// Scrolling or page by page (phones and e-readers start with pages).
|
||
panel.querySelectorAll('[data-rs-pdfmode]').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
const pages = btn.dataset.rsPdfmode === 'pages';
|
||
if (pages === !!readerSettings.pdfPaginated || !currentPdfDoc) return;
|
||
const contentEl = $('reader-content');
|
||
const page = pages ? _pdfTopPage(contentEl) : pdfCurrentPage;
|
||
readerSettings.pdfPaginated = pages;
|
||
saveReaderSettings();
|
||
panel.querySelectorAll('[data-rs-pdfmode]').forEach(b => b.classList.toggle('active', b === btn));
|
||
const spread = panel.querySelector('#rs-spread');
|
||
if (spread) spread.style.display = pages ? 'none' : '';
|
||
if (pages) {
|
||
pdfCurrentPage = page;
|
||
// Spreads are a scrolling thing: one page at a time here.
|
||
if (readerSettings.pdfSpread) reRenderPdf(page).then(() => enterPdfPaginatedMode());
|
||
else enterPdfPaginatedMode();
|
||
} else {
|
||
exitPdfPaginatedMode(page);
|
||
}
|
||
});
|
||
});
|
||
|
||
panel.querySelector('#rs-spread').addEventListener('click', function () {
|
||
readerSettings.pdfSpread = !readerSettings.pdfSpread;
|
||
this.classList.toggle('active', readerSettings.pdfSpread);
|
||
saveReaderSettings();
|
||
reRenderPdf();
|
||
});
|
||
|
||
}
|
||
}
|
||
|
||
async function applyPdfZoom(newZoom) {
|
||
readerSettings.pdfZoom = newZoom;
|
||
const zoomRange = document.getElementById('rs-zoom');
|
||
const zoomVal = document.getElementById('rs-zoom-val');
|
||
if (zoomRange) zoomRange.value = newZoom;
|
||
if (zoomVal) zoomVal.textContent = newZoom + '%';
|
||
saveReaderSettings();
|
||
if (readerSettings.pdfPaginated) {
|
||
pdfSmartZoomPage(pdfCurrentPage);
|
||
} else {
|
||
await reRenderPdf();
|
||
}
|
||
}
|
||
|
||
// The page at the top of the view (scrolling) — 58-pdf.js keeps the pages in order.
|
||
function _currentScrollPage(contentEl) {
|
||
return _pdfTopPage(contentEl);
|
||
}
|
||
|
||
/** Lay the open PDF out again (zoom, spreads, another width), at [pivot] or the page shown now. */
|
||
async function reRenderPdf(pivot) {
|
||
if (!currentPdfDoc) return;
|
||
const contentEl = $('reader-content');
|
||
if (!contentEl) return;
|
||
if (!pivot) pivot = readerSettings.pdfPaginated ? pdfCurrentPage : _pdfTopPage(contentEl);
|
||
await renderPdf(null, contentEl, undefined, pivot);
|
||
if (readerSettings.pdfPaginated) enterPdfPaginatedMode();
|
||
}
|