Typography was in the stylesheet foliate prepends, which the book overrides; moved to the appended one. The window keydown handler was never bound. Also: wider reading area, page on a card, arrows moved onto it, spread gutter, and the sidebar stays open on chapter clicks.
164 lines
4.8 KiB
TypeScript
164 lines
4.8 KiB
TypeScript
import type { ReaderSettings } from '$lib/schema/reader';
|
|
|
|
export interface ReaderPalette {
|
|
bg: string;
|
|
fg: string;
|
|
muted: string;
|
|
link: string;
|
|
dark: boolean;
|
|
}
|
|
|
|
/**
|
|
* Reads the app palette out of the live document.
|
|
*
|
|
* The reader route uses a +layout@ breakout, so it never runs (root)/+layout and
|
|
* cannot reach the theme context. It does not need to: hooks.server.ts inlines
|
|
* <style id="chitai-theme"> with the full palette into every page, so the tokens
|
|
* are resolved on documentElement before first paint.
|
|
*
|
|
* `dark` is passed in rather than read off the class list, because mode-watcher's
|
|
* store is the authority and the class trails it by a frame. Call this after that
|
|
* frame, though — the token values themselves do come from the class.
|
|
*/
|
|
export function readReaderPalette(dark: boolean): ReaderPalette {
|
|
const styles = getComputedStyle(document.documentElement);
|
|
const token = (name: string, fallback: string) =>
|
|
styles.getPropertyValue(name).trim() || fallback;
|
|
|
|
return {
|
|
// --card rather than --background: the book is a sheet of paper on the page.
|
|
bg: token('--card', '#ffffff'),
|
|
fg: token('--foreground', '#000000'),
|
|
muted: token('--muted-foreground', '#666666'),
|
|
link: token('--primary', '#0066cc'),
|
|
dark
|
|
};
|
|
}
|
|
|
|
/** Body-text containers. Headings are excluded so their scale survives. */
|
|
const TEXT = 'p, li, dd, dt, blockquote, td, th, div';
|
|
|
|
const HEADINGS = 'h1, h2, h3, h4, h5, h6';
|
|
|
|
/**
|
|
* Builds the two stylesheets foliate injects into each section document.
|
|
*
|
|
* The paginator creates two <style> elements per document: the first is
|
|
* head.prepend()ed, so the book's own CSS overrides it; the second is
|
|
* head.append()ed, so it wins. setStyles takes the pair as a tuple and
|
|
* re-applies it on every section load.
|
|
*
|
|
* Nearly everything goes in `after`, with !important. These are the reader's
|
|
* settings, and EPUBs routinely set their own font-family, font-size and
|
|
* line-height on body and on paragraphs — put the settings in `before` and the
|
|
* book simply overrides them, which is what made the typography controls look
|
|
* like they did nothing.
|
|
*
|
|
* `before` is left for the few defaults a book should be free to override.
|
|
*/
|
|
export function buildReaderStyles(
|
|
s: ReaderSettings,
|
|
p: ReaderPalette
|
|
): [before: string, after: string] {
|
|
const hyphens = s.hyphenate ? 'auto' : 'manual';
|
|
|
|
const before = `
|
|
@namespace epub "http://www.idpf.org/2007/ops";
|
|
|
|
${TEXT} {
|
|
-webkit-hyphenate-limit-before: 3;
|
|
-webkit-hyphenate-limit-after: 2;
|
|
-webkit-hyphenate-limit-lines: 2;
|
|
hanging-punctuation: allow-end last;
|
|
widows: 2;
|
|
orphans: 2;
|
|
}
|
|
|
|
pre {
|
|
white-space: pre-wrap;
|
|
}
|
|
`;
|
|
|
|
const after = `
|
|
/* The rem base. Headings sized in em/rem keep their scale relative to it. */
|
|
html {
|
|
font-size: ${s.fontSize}px !important;
|
|
}
|
|
|
|
body {
|
|
font-size: 1rem !important;
|
|
font-weight: ${s.fontWeight} !important;
|
|
}
|
|
|
|
/* inherit rather than a fixed size: books that set an absolute size in pt
|
|
or px on their paragraphs would otherwise ignore the size setting
|
|
entirely, while headings keep whatever relative scale they declare. */
|
|
${TEXT} {
|
|
font-size: inherit !important;
|
|
}
|
|
|
|
body, ${TEXT}, ${HEADINGS}, figcaption, caption {
|
|
font-family: ${s.fontFamily} !important;
|
|
}
|
|
|
|
body, ${TEXT} {
|
|
line-height: ${s.lineHeight} !important;
|
|
letter-spacing: ${s.letterSpacing}em !important;
|
|
}
|
|
|
|
p, li, dd, blockquote {
|
|
text-align: ${s.justify ? 'justify' : 'start'} !important;
|
|
-webkit-hyphens: ${hyphens} !important;
|
|
hyphens: ${hyphens} !important;
|
|
}
|
|
|
|
/* Justification must not silently override an explicit align attribute. */
|
|
[align="left"] { text-align: left !important; }
|
|
[align="right"] { text-align: right !important; }
|
|
[align="center"] { text-align: center !important; }
|
|
[align="justify"] { text-align: justify !important; }
|
|
|
|
/* Tells the book's own prefers-color-scheme rules which way we are going.
|
|
Without it the paginator's media query follows the OS, so a book with
|
|
dark styles can invert against the app. */
|
|
html {
|
|
color-scheme: ${p.dark ? 'dark' : 'light'};
|
|
}
|
|
|
|
/* Most EPUBs set their own body background and colour, so an
|
|
unprioritised override loses to them and the page stays white. */
|
|
html, body {
|
|
background: ${p.bg} !important;
|
|
color: ${p.fg} !important;
|
|
}
|
|
|
|
${TEXT}, ${HEADINGS}, span, dl, figcaption, caption {
|
|
color: ${p.fg} !important;
|
|
}
|
|
|
|
a:any-link {
|
|
color: ${p.link} !important;
|
|
}
|
|
|
|
hr {
|
|
border-color: ${p.muted} !important;
|
|
}
|
|
|
|
/* Keep line art and diagrams legible on a dark page without touching
|
|
photographs, which invert badly. */
|
|
${
|
|
p.dark
|
|
? `svg { color: ${p.fg}; }
|
|
img[src$=".svg"] { filter: invert(1) hue-rotate(180deg); }`
|
|
: ''
|
|
}
|
|
|
|
img, svg, video {
|
|
max-width: 100%;
|
|
height: auto;
|
|
}
|
|
`;
|
|
|
|
return [before, after];
|
|
}
|