postext-epub 0.49.5 → 0.49.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/reflowable/css/apparatus.d.ts +17 -0
- package/dist/reflowable/css/apparatus.d.ts.map +1 -0
- package/dist/reflowable/css/apparatus.js +127 -0
- package/dist/reflowable/css/apparatus.js.map +1 -0
- package/dist/reflowable/css/boxes.d.ts +20 -0
- package/dist/reflowable/css/boxes.d.ts.map +1 -0
- package/dist/reflowable/css/boxes.js +134 -0
- package/dist/reflowable/css/boxes.js.map +1 -0
- package/dist/reflowable/css/figures.d.ts +16 -0
- package/dist/reflowable/css/figures.d.ts.map +1 -0
- package/dist/reflowable/css/figures.js +174 -0
- package/dist/reflowable/css/figures.js.map +1 -0
- package/dist/reflowable/css/helpers.d.ts +32 -0
- package/dist/reflowable/css/helpers.d.ts.map +1 -0
- package/dist/reflowable/css/helpers.js +77 -0
- package/dist/reflowable/css/helpers.js.map +1 -0
- package/dist/reflowable/css/text.d.ts +19 -0
- package/dist/reflowable/css/text.d.ts.map +1 -0
- package/dist/reflowable/css/text.js +151 -0
- package/dist/reflowable/css/text.js.map +1 -0
- package/dist/reflowable/css/types.d.ts +46 -0
- package/dist/reflowable/css/types.d.ts.map +1 -0
- package/dist/reflowable/css/types.js +4 -0
- package/dist/reflowable/css/types.js.map +1 -0
- package/dist/reflowable/css/verse.d.ts +18 -0
- package/dist/reflowable/css/verse.d.ts.map +1 -0
- package/dist/reflowable/css/verse.js +126 -0
- package/dist/reflowable/css/verse.js.map +1 -0
- package/dist/reflowable/css.d.ts +4 -52
- package/dist/reflowable/css.d.ts.map +1 -1
- package/dist/reflowable/css.js +24 -736
- package/dist/reflowable/css.js.map +1 -1
- package/package.json +3 -3
package/dist/reflowable/css.js
CHANGED
|
@@ -3,80 +3,15 @@
|
|
|
3
3
|
// override. Sizes are in ems of the body text (the reader's chosen size),
|
|
4
4
|
// line heights are ratios, colours come from the resolved palette, and no
|
|
5
5
|
// rule sets a width the screen would have to honour.
|
|
6
|
-
import { dimensionToPx,
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* with burasagari (`cjk.hangingPunctuation`, on by default in Japan),
|
|
16
|
-
* 、。,. hanging past the line end; and, in any book that asks for it,
|
|
17
|
-
* lines broken only between phrases (`cjk.wordBreak: 'keep-all'`, #463).
|
|
18
|
-
* None for other books. */
|
|
19
|
-
function japaneseDecls(cjk) {
|
|
20
|
-
const out = [];
|
|
21
|
-
if (cjk.wordBreak === 'keep-all')
|
|
22
|
-
out.push('word-break: keep-all');
|
|
23
|
-
const lineBreak = LINE_BREAK[cjk.lineBreak];
|
|
24
|
-
if (lineBreak)
|
|
25
|
-
out.push(`line-break: ${lineBreak}`, `-epub-line-break: ${lineBreak}`, `-webkit-line-break: ${lineBreak}`);
|
|
26
|
-
if (cjk.region === 'japan' && cjk.hangingPunctuation !== 'none') {
|
|
27
|
-
out.push(`hanging-punctuation: ${cjk.hangingPunctuation === 'force' ? 'force-end' : 'allow-end'}`);
|
|
28
|
-
}
|
|
29
|
-
return out;
|
|
30
|
-
}
|
|
31
|
-
/** The emphasis-mark rule of a class `dotsClasses` gives: a shape and fill
|
|
32
|
-
* (`pt-dots-filled-sesame`), or a side (`pt-dots-over`: over horizontal
|
|
33
|
-
* text and right of vertical text; `pt-dots-under`: under and left). */
|
|
34
|
-
function dotsRule(cls) {
|
|
35
|
-
const side = /^pt-dots-(over|under)$/.exec(cls);
|
|
36
|
-
if (side) {
|
|
37
|
-
const pos = side[1] === 'over' ? 'over right' : 'under left';
|
|
38
|
-
return rule(`.pt-dots.${cls}`, [`text-emphasis-position: ${pos}`, `-webkit-text-emphasis-position: ${pos}`]);
|
|
39
|
-
}
|
|
40
|
-
const shape = /^pt-dots-(filled|open)-(dot|circle|sesame)$/.exec(cls);
|
|
41
|
-
if (!shape)
|
|
42
|
-
return '';
|
|
43
|
-
const style = `${shape[1]} ${shape[2]}`;
|
|
44
|
-
return rule(`.pt-dots.${cls}`, [`text-emphasis-style: ${style}`, `-epub-text-emphasis-style: ${style}`, `-webkit-text-emphasis-style: ${style}`]);
|
|
45
|
-
}
|
|
46
|
-
const SANS = /\b(sans|grotesk|grotesque|gothic|helvetica|arial|inter|roboto|lato|montserrat|open sans|source sans|fira sans|heiti|hei\b|黑)/i;
|
|
47
|
-
const MONO = /\b(mono|code|courier|consol)/i;
|
|
48
|
-
/** A family as a CSS value: the family quoted, then a generic fallback. A
|
|
49
|
-
* feature family (#1237, `Lora@onum`) is its base family's. */
|
|
50
|
-
export function familyStack(family) {
|
|
51
|
-
const name = familyName(family);
|
|
52
|
-
const generic = MONO.test(name) ? 'monospace' : SANS.test(name) ? 'sans-serif' : 'serif';
|
|
53
|
-
return `${cssString(name)}, ${generic}`;
|
|
54
|
-
}
|
|
55
|
-
/** The family names the stylesheet asks for (to warn about missing
|
|
56
|
-
* faces). */
|
|
57
|
-
export function familyName(family) {
|
|
58
|
-
return featureFamilyParts(primaryFontFamily(family).trim().replace(/^['"]|['"]$/g, '')).family;
|
|
59
|
-
}
|
|
60
|
-
/** Whether a configuration sets any style's figures (#1237): a family of
|
|
61
|
-
* it is a feature family. */
|
|
62
|
-
function setsFigures(config) {
|
|
63
|
-
return /"[^"]*@(?:onum|lnum|pnum|tnum)(?:\+(?:onum|lnum|pnum|tnum))*"/.test(JSON.stringify(config));
|
|
64
|
-
}
|
|
65
|
-
const ALIGN = { left: 'start', right: 'end', center: 'center', justify: 'justify' };
|
|
66
|
-
/** Beside every `letter-spacing` (#703): letter-spacing never changes the
|
|
67
|
-
* glyphs. A reading system turns the common ligatures off where the
|
|
68
|
-
* letter-spacing is not zero; asked for as features they stay, as in the
|
|
69
|
-
* print layout, the fixed-layout pages and the PDF. */
|
|
70
|
-
const TRACKED_LIGATURES = "font-feature-settings: 'liga' 1, 'clig' 1";
|
|
71
|
-
/** A rule from its declarations (the falsy ones left out); none when it
|
|
72
|
-
* declares nothing. */
|
|
73
|
-
function rule(selector, decls) {
|
|
74
|
-
const body = decls.filter((d) => typeof d === 'string' && d.length > 0);
|
|
75
|
-
return body.length > 0 ? { selector, decls: body } : '';
|
|
76
|
-
}
|
|
77
|
-
function ruleText(r) {
|
|
78
|
-
return `${r.selector} {\n${r.decls.map((d) => ` ${d};`).join('\n')}\n}\n`;
|
|
79
|
-
}
|
|
6
|
+
import { dimensionToPx, fontVariantNumeric, featureFamilyParts, primaryFontFamily } from 'postext';
|
|
7
|
+
import { round } from './inline.js';
|
|
8
|
+
import { setsFigures, familyName, familyStack, ruleText } from './css/helpers.js';
|
|
9
|
+
import { createTextRules } from './css/text.js';
|
|
10
|
+
import { createFigureRules } from './css/figures.js';
|
|
11
|
+
import { createBoxRules } from './css/boxes.js';
|
|
12
|
+
import { createApparatusRules } from './css/apparatus.js';
|
|
13
|
+
import { createVerseRules } from './css/verse.js';
|
|
14
|
+
export { familyStack, familyName } from './css/helpers.js';
|
|
80
15
|
export function bookStylesheet(config, fontFaces, options) {
|
|
81
16
|
const dpi = config.page.dpi;
|
|
82
17
|
const body = config.bodyText;
|
|
@@ -118,668 +53,21 @@ export function bookStylesheet(config, fontFaces, options) {
|
|
|
118
53
|
out.push('@charset "UTF-8";\n');
|
|
119
54
|
if (fontFaces)
|
|
120
55
|
out.push(fontFaces);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
body
|
|
135
|
-
|
|
136
|
-
// the lines a paragraph keeps at the foot of a page (the engine's
|
|
137
|
-
// `avoidWidows`), `widows` the lines at the head of the next (its
|
|
138
|
-
// `avoidOrphans`). A rule that is off writes 1: the initial value of
|
|
139
|
-
// both properties is 2, so leaving one out would keep the rule.
|
|
140
|
-
`orphans: ${body.avoidWidows ? Math.max(1, body.widowMinLines) : 1}`,
|
|
141
|
-
`widows: ${body.avoidOrphans ? Math.max(1, body.orphanMinLines) : 1}`,
|
|
142
|
-
'margin: 0',
|
|
143
|
-
'padding: 0 1em',
|
|
144
|
-
...japaneseDecls(config.cjk),
|
|
145
|
-
]));
|
|
146
|
-
const indent = px(body.firstLineIndent);
|
|
147
|
-
const ratio = round(dimensionToPx(body.lineHeight, dpi, bodyPx) / bodyPx);
|
|
148
|
-
const spacing = spacingEm(body.paragraphSpacing, body.lineHeight);
|
|
149
|
-
out.push(rule('p', [
|
|
150
|
-
'margin: 0',
|
|
151
|
-
spacing === 0 && indent > 0 && `text-indent: ${round(indent / bodyPx)}em`,
|
|
152
|
-
spacing > 0 && `margin-block-end: ${spacing}em`,
|
|
153
|
-
]));
|
|
154
|
-
if (spacing === 0 && indent > 0) {
|
|
155
|
-
// The first paragraph after a heading, a list, a figure… is not
|
|
156
|
-
// indented (unless the book indents after headings).
|
|
157
|
-
const after = ['ul', 'ol', 'blockquote', 'figure', 'div', 'aside', 'nav', 'section > p:first-child'];
|
|
158
|
-
if (!body.indentAfterHeading)
|
|
159
|
-
after.unshift('h1', 'h2', 'h3', 'h4', 'h5', 'h6');
|
|
160
|
-
out.push(rule(after.map((s) => (s.includes(' ') ? s : `${s} + p`)).join(', '), ['text-indent: 0']));
|
|
161
|
-
}
|
|
162
|
-
out.push(rule('strong, b', [`font-weight: ${body.boldFontWeight}`, color(body.boldColor)]));
|
|
163
|
-
out.push(rule('em, i', [color(body.italicColor)]));
|
|
164
|
-
out.push(rule('a', ['color: inherit', 'text-decoration: underline']));
|
|
165
|
-
out.push(rule('a.pt-ref', [
|
|
166
|
-
color(body.referenceColor),
|
|
167
|
-
'text-decoration: none',
|
|
168
|
-
body.referenceBold && `font-weight: ${body.boldFontWeight}`,
|
|
169
|
-
body.referenceItalic && 'font-style: italic',
|
|
170
|
-
]));
|
|
171
|
-
out.push(rule('a.pt-pageref', ['text-decoration: none']));
|
|
172
|
-
out.push(rule('sup, sub', ['line-height: 0', 'font-size: 0.7em']));
|
|
173
|
-
// --- headings ------------------------------------------------------------
|
|
174
|
-
const headings = config.headings;
|
|
175
|
-
/** The body's line pitch, px: a 行取り heading (`lineSpan`, #424) takes
|
|
176
|
-
* whole numbers of it. */
|
|
177
|
-
const bodyLinePx = body.lineHeight.unit === 'em' || body.lineHeight.unit === 'rem'
|
|
178
|
-
? body.lineHeight.value * bodyPx
|
|
179
|
-
: px(body.lineHeight);
|
|
180
|
-
for (const level of headings.levels) {
|
|
181
|
-
if (level.level < 1 || level.level > 6)
|
|
182
|
-
continue;
|
|
183
|
-
const hPx = px(level.fontSize);
|
|
184
|
-
// 行取り: the heading's line centred in N body lines, its margins the
|
|
185
|
-
// rest of them (a reflowable book has no grid to keep).
|
|
186
|
-
const lineSpanMargin = level.lineSpan !== undefined
|
|
187
|
-
? Math.max(0, (level.lineSpan * bodyLinePx - (level.lineHeight.unit === 'em' || level.lineHeight.unit === 'rem' ? level.lineHeight.value * hPx : px(level.lineHeight, hPx))) / 2)
|
|
188
|
-
: undefined;
|
|
189
|
-
out.push(rule(`h${level.level}`, [
|
|
190
|
-
fam(level.fontFamily || headings.fontFamily),
|
|
191
|
-
`font-size: ${round(hPx / bodyPx)}em`,
|
|
192
|
-
lh(level.lineHeight, hPx),
|
|
193
|
-
`font-weight: ${level.fontWeight}`,
|
|
194
|
-
`font-style: ${level.italic ? 'italic' : 'normal'}`,
|
|
195
|
-
color(level.color),
|
|
196
|
-
`text-align: ${ALIGN[headings.textAlign] ?? 'start'}`,
|
|
197
|
-
level.letterSpacing.value !== 0 && `letter-spacing: ${em(level.letterSpacing, hPx)}`,
|
|
198
|
-
level.letterSpacing.value !== 0 && TRACKED_LIGATURES,
|
|
199
|
-
level.textTransform === 'uppercase' && 'text-transform: uppercase',
|
|
200
|
-
lineSpanMargin !== undefined
|
|
201
|
-
? `margin: ${round(lineSpanMargin / hPx)}em 0`
|
|
202
|
-
: `margin: ${round(px(level.marginTop, hPx) / hPx)}em 0 ${round(px(level.marginBottom, hPx) / hPx)}em`,
|
|
203
|
-
// 字下げ: the indent counts body characters.
|
|
204
|
-
level.indent && px(level.indent) > 0 && `margin-inline-start: ${round(px(level.indent) / hPx)}em`,
|
|
205
|
-
'hyphens: manual',
|
|
206
|
-
'-epub-hyphens: manual',
|
|
207
|
-
'page-break-after: avoid',
|
|
208
|
-
'break-after: avoid',
|
|
209
|
-
]));
|
|
210
|
-
}
|
|
211
|
-
for (const style of config.headingStyles) {
|
|
212
|
-
const o = style.overrides;
|
|
213
|
-
const sPx = o.fontSize ? px(o.fontSize) : undefined;
|
|
214
|
-
out.push(rule(`.${idOf('hs-', style.id)}`, [
|
|
215
|
-
o.fontFamily && fam(o.fontFamily),
|
|
216
|
-
sPx !== undefined && `font-size: ${round(sPx / bodyPx)}em`,
|
|
217
|
-
o.fontWeight !== undefined && `font-weight: ${o.fontWeight}`,
|
|
218
|
-
o.italic !== undefined && `font-style: ${o.italic ? 'italic' : 'normal'}`,
|
|
219
|
-
o.color && color(o.color),
|
|
220
|
-
o.textTransform && `text-transform: ${o.textTransform === 'uppercase' ? 'uppercase' : 'none'}`,
|
|
221
|
-
]));
|
|
222
|
-
}
|
|
223
|
-
out.push(rule('.pt-part-title', ['text-align: center', 'margin-block-start: 3em']));
|
|
224
|
-
out.push(rule('.pt-part-number', ['display: block', 'font-size: 0.6em', 'margin-block-end: 0.5em']));
|
|
225
|
-
out.push(rule('.pt-hidden', [
|
|
226
|
-
'position: absolute',
|
|
227
|
-
'width: 1px',
|
|
228
|
-
'height: 1px',
|
|
229
|
-
'overflow: hidden',
|
|
230
|
-
'clip: rect(0 0 0 0)',
|
|
231
|
-
'white-space: nowrap',
|
|
232
|
-
]));
|
|
233
|
-
// --- quotations, lists ---------------------------------------------------
|
|
234
|
-
const bq = body.blockquote;
|
|
235
|
-
out.push(rule('blockquote', [
|
|
236
|
-
`margin: ${ratio / 2}em 0`,
|
|
237
|
-
`margin-inline-start: ${em(bq.indent)}`,
|
|
238
|
-
color(bq.color),
|
|
239
|
-
bq.italic && 'font-style: italic',
|
|
240
|
-
]));
|
|
241
|
-
if (bq.italic)
|
|
242
|
-
out.push(rule('blockquote em', ['font-style: normal']));
|
|
243
|
-
const ul = config.unorderedLists;
|
|
244
|
-
const ol = config.orderedLists;
|
|
245
|
-
out.push(rule('ul, ol', [
|
|
246
|
-
'list-style: none',
|
|
247
|
-
`margin: ${em(ol.marginTop)} 0 ${em(ol.marginBottom)}`,
|
|
248
|
-
'padding-inline-start: 1.6em',
|
|
249
|
-
'text-indent: 0',
|
|
250
|
-
]));
|
|
251
|
-
out.push(rule('li', [ol.itemSpacing.value > 0 && `margin-block-start: ${em(ol.itemSpacing)}`]));
|
|
252
|
-
out.push(rule('li li', ['margin-block-start: 0']));
|
|
253
|
-
out.push(rule('li > .pt-lbl:first-child', [
|
|
254
|
-
'display: inline-block',
|
|
255
|
-
'min-width: 1.6em',
|
|
256
|
-
'margin-inline-start: -1.6em',
|
|
257
|
-
'text-indent: 0',
|
|
258
|
-
]));
|
|
259
|
-
out.push(rule('ul > li > .pt-lbl:first-child', [color(ul.color), ul.fontWeight !== body.fontWeight && `font-weight: ${ul.fontWeight}`]));
|
|
260
|
-
out.push(rule('ol > li > .pt-lbl:first-child', [color(ol.color), ol.fontWeight !== body.fontWeight && `font-weight: ${ol.fontWeight}`]));
|
|
261
|
-
out.push(rule('.pt-done', ['text-decoration: line-through']));
|
|
262
|
-
// --- figures, tables, captions ------------------------------------------
|
|
263
|
-
const cap = config.captionStyle;
|
|
264
|
-
const capPx = px(cap.fontSize);
|
|
265
|
-
out.push(rule('figure', ['margin: 1em 0', 'text-align: center', 'page-break-inside: avoid', 'break-inside: avoid']));
|
|
266
|
-
out.push(rule('figure img', ['max-width: 100%', 'height: auto']));
|
|
267
|
-
// The labels drawn over a picture (#955): a box as wide as the image,
|
|
268
|
-
// the SVG laid over all of it.
|
|
269
|
-
if (options.labelLayers) {
|
|
270
|
-
out.push(rule('.pt-labelled', ['display: inline-block', 'position: relative', 'max-width: 100%', 'line-height: 0']));
|
|
271
|
-
out.push(rule('.pt-label-layer', ['position: absolute', 'left: 0', 'top: 0', 'width: 100%', 'height: 100%', 'pointer-events: none']));
|
|
272
|
-
}
|
|
273
|
-
out.push(rule('figure video', ['width: 100%', 'height: auto', 'background-color: #000']));
|
|
274
|
-
// Comic pages (#565): each panel's picture as wide as the text at most,
|
|
275
|
-
// its lettering under it as plain lines.
|
|
276
|
-
out.push(rule('.pt-comic', ['margin: 1em 0']));
|
|
277
|
-
out.push(rule('.pt-comic-panel', ['margin: 1em 0 0.5em']));
|
|
278
|
-
out.push(rule('.pt-comic-panel svg', ['display: block', 'max-width: 100%', 'height: auto', 'margin: 0 auto']));
|
|
279
|
-
out.push(rule('.pt-comic p', ['text-indent: 0', 'margin: 0.25em 0']));
|
|
280
|
-
out.push(rule('a.pt-video-link', ['display: block']));
|
|
281
|
-
out.push(rule('figcaption, caption', [
|
|
282
|
-
fam(cap.fontFamily),
|
|
283
|
-
`font-size: ${round(capPx / bodyPx)}em`,
|
|
284
|
-
color(cap.color),
|
|
285
|
-
`text-align: ${ALIGN[cap.align] ?? 'start'}`,
|
|
286
|
-
cap.descriptionItalic && 'font-style: italic',
|
|
287
|
-
cap.backgroundEnabled && color(cap.background, 'background-color'),
|
|
288
|
-
cap.backgroundEnabled && `padding: ${round(px(cap.padding, capPx) / capPx)}em`,
|
|
289
|
-
`margin: ${round(px(cap.gap, capPx) / capPx)}em 0`,
|
|
290
|
-
'hyphens: manual',
|
|
291
|
-
]));
|
|
292
|
-
out.push(rule('caption.pt-caption-below', ['caption-side: bottom']));
|
|
293
|
-
out.push(rule('.pt-label', [
|
|
294
|
-
color(cap.labelColor),
|
|
295
|
-
cap.labelBold ? 'font-weight: bold' : undefined,
|
|
296
|
-
`font-style: ${cap.labelItalic ? 'italic' : 'normal'}`,
|
|
297
|
-
]));
|
|
298
|
-
const note = cap.note;
|
|
299
|
-
out.push(rule('.pt-note', [
|
|
300
|
-
`font-size: ${em(note.fontSize)}`,
|
|
301
|
-
color(note.color),
|
|
302
|
-
`font-style: ${note.italic ? 'italic' : 'normal'}`,
|
|
303
|
-
`text-align: ${ALIGN[note.align] ?? 'start'}`,
|
|
304
|
-
'text-indent: 0',
|
|
305
|
-
]));
|
|
306
|
-
out.push(rule('figcaption .pt-note', ['display: block', `margin-block-start: ${em(note.gap, px(cap.fontSize))}`]));
|
|
307
|
-
out.push(rule('.pt-missing', ['border: 1px dashed currentColor', 'padding: 1em', 'font-style: italic']));
|
|
308
|
-
const ts = config.tableStyle;
|
|
309
|
-
/** A rule `widthPx` (at the page dpi) wide, in CSS px. */
|
|
310
|
-
const line = (widthPx, c) => `${round(Math.max(0.5, widthPx * (96 / dpi)))}px solid ${c.hex}`;
|
|
311
|
-
/** What a table style decides, by element: on `table` for the document's
|
|
312
|
-
* style; on `table.pt-table-<id>` for a named one (#625), which then
|
|
313
|
-
* also undoes what the document's style (`reset`) sets and it does not. */
|
|
314
|
-
const tableStyleDecls = (st, reset) => {
|
|
315
|
-
const cellPx = px(st.bodyFontSize);
|
|
316
|
-
const booktabs = st.borders && st.rules === 'booktabs';
|
|
317
|
-
const border = st.borders && st.rules !== 'none' && !booktabs && px(st.borderWidth) > 0
|
|
318
|
-
? line(px(st.borderWidth), st.borderColor)
|
|
319
|
-
: undefined;
|
|
320
|
-
const tableBorder = border !== undefined && (st.rules === 'outer' || st.rules === 'grid');
|
|
321
|
-
// Booktabs: a heavy rule above and under the table, a light one under
|
|
322
|
-
// the header and, under a head spanning columns above the last header
|
|
323
|
-
// row, a span rule, trimmed at both ends by a background line (a CSS
|
|
324
|
-
// border cannot be).
|
|
325
|
-
const width = (d) => {
|
|
326
|
-
const v = px(d, cellPx);
|
|
327
|
-
return booktabs && v > 0 ? Math.max(0.25, v) : 0;
|
|
328
|
-
};
|
|
329
|
-
const heavy = width(st.heavyRuleWidth);
|
|
330
|
-
const light = width(st.lightRuleWidth);
|
|
331
|
-
const span = st.spanRules !== 'none' ? width(st.spanRuleWidth) : 0;
|
|
332
|
-
const wasBooktabs = reset?.borders === true && reset.rules === 'booktabs';
|
|
333
|
-
const extra = [];
|
|
334
|
-
if (light > 0)
|
|
335
|
-
extra.push(['thead', [`border-bottom: ${line(light, st.borderColor)}`]]);
|
|
336
|
-
else if (wasBooktabs)
|
|
337
|
-
extra.push(['thead', ['border-bottom: none']]);
|
|
338
|
-
const spanSel = 'thead > tr:not(:last-child) > th[colspan]';
|
|
339
|
-
if (span > 0 && st.spanRules === 'trimmed') {
|
|
340
|
-
extra.push([spanSel, [
|
|
341
|
-
`background-image: linear-gradient(${st.borderColor.hex}, ${st.borderColor.hex})`,
|
|
342
|
-
'background-repeat: no-repeat',
|
|
343
|
-
'background-position: center bottom',
|
|
344
|
-
`background-size: calc(100% - ${round(2 * (px(st.spanRuleTrim, cellPx) / cellPx))}em) ${round(Math.max(0.5, span * (96 / dpi)))}px`,
|
|
345
|
-
]]);
|
|
346
|
-
}
|
|
347
|
-
else {
|
|
348
|
-
if (span > 0)
|
|
349
|
-
extra.push([spanSel, [`border-bottom: ${line(span, st.borderColor)}`]]);
|
|
350
|
-
if (wasBooktabs && reset.spanRules === 'trimmed')
|
|
351
|
-
extra.push([spanSel, ['background-image: none']]);
|
|
352
|
-
}
|
|
353
|
-
if (st.groupRules && light > 0) {
|
|
354
|
-
extra.push(['tbody > tr.pt-group:not(:first-child) > *', [`border-top: ${line(light, st.borderColor)}`]]);
|
|
355
|
-
}
|
|
356
|
-
return {
|
|
357
|
-
table: [
|
|
358
|
-
reset && fam(st.bodyFontFamily),
|
|
359
|
-
reset && `font-size: ${round(cellPx / bodyPx)}em`,
|
|
360
|
-
reset && color(st.bodyColor),
|
|
361
|
-
tableBorder ? `border: ${border}` : reset && 'border: none',
|
|
362
|
-
heavy > 0 && `border-top: ${line(heavy, st.borderColor)}`,
|
|
363
|
-
heavy > 0 && `border-bottom: ${line(heavy, st.borderColor)}`,
|
|
364
|
-
],
|
|
365
|
-
cells: [
|
|
366
|
-
reset && `padding: ${round(px(st.cellPadding, cellPx) / cellPx)}em`,
|
|
367
|
-
border && st.rules === 'grid' ? `border: ${border}` : undefined,
|
|
368
|
-
border && st.rules === 'horizontal' ? `border-block: ${border}` : undefined,
|
|
369
|
-
reset && !(border && (st.rules === 'grid' || st.rules === 'horizontal')) && 'border: none',
|
|
370
|
-
],
|
|
371
|
-
td: [st.bodyBackgroundEnabled ? color(st.bodyBackground, 'background-color') : reset && 'background-color: transparent'],
|
|
372
|
-
alt: st.bodyAlternateBackgroundEnabled ? [color(st.bodyAlternateBackground, 'background-color')] : undefined,
|
|
373
|
-
th: [
|
|
374
|
-
fam(st.headerFontFamily),
|
|
375
|
-
`font-size: ${round(px(st.headerFontSize) / cellPx)}em`,
|
|
376
|
-
color(st.headerColor),
|
|
377
|
-
`font-weight: ${st.headerBold ? 'bold' : 'normal'}`,
|
|
378
|
-
st.headerItalic ? 'font-style: italic' : reset && 'font-style: normal',
|
|
379
|
-
st.headerTextTransform === 'uppercase' ? 'text-transform: uppercase' : reset && 'text-transform: none',
|
|
380
|
-
st.headerLetterSpacing.value !== 0 ? `letter-spacing: ${round(px(st.headerLetterSpacing, cellPx) / cellPx)}em` : reset && 'letter-spacing: normal',
|
|
381
|
-
st.headerLetterSpacing.value !== 0 && TRACKED_LIGATURES,
|
|
382
|
-
st.headerBackgroundEnabled ? color(st.headerBackground, 'background-color') : reset && 'background-color: transparent',
|
|
383
|
-
],
|
|
384
|
-
extra,
|
|
385
|
-
};
|
|
386
|
-
};
|
|
387
|
-
const bodyCellPx = px(ts.bodyFontSize);
|
|
388
|
-
const base = tableStyleDecls(ts, undefined);
|
|
389
|
-
out.push(rule('.pt-table', ['margin: 1em 0', 'overflow-x: auto']));
|
|
390
|
-
out.push(rule('table', [
|
|
391
|
-
'border-collapse: collapse',
|
|
392
|
-
'margin: 0 auto',
|
|
393
|
-
'max-width: 100%',
|
|
394
|
-
fam(ts.bodyFontFamily),
|
|
395
|
-
`font-size: ${round(bodyCellPx / bodyPx)}em`,
|
|
396
|
-
color(ts.bodyColor),
|
|
397
|
-
'text-indent: 0',
|
|
398
|
-
'hyphens: manual',
|
|
399
|
-
...base.table,
|
|
400
|
-
]));
|
|
401
|
-
out.push(rule('th, td', [
|
|
402
|
-
`padding: ${round(px(ts.cellPadding, bodyCellPx) / bodyCellPx)}em`,
|
|
403
|
-
'vertical-align: top',
|
|
404
|
-
'text-align: start',
|
|
405
|
-
...base.cells,
|
|
406
|
-
]));
|
|
407
|
-
out.push(rule('td', base.td));
|
|
408
|
-
if (base.alt)
|
|
409
|
-
out.push(rule('td.pt-alt', base.alt));
|
|
410
|
-
out.push(rule('th', base.th));
|
|
411
|
-
for (const [sel, decls] of base.extra)
|
|
412
|
-
out.push(rule(sel, decls));
|
|
413
|
-
for (const st of config.tableStyles ?? []) {
|
|
414
|
-
const table = `table.${idOf('pt-table-', st.id)}`;
|
|
415
|
-
const sub = (sel) => sel.split(', ').map((x) => `${table} ${x}`).join(', ');
|
|
416
|
-
const d = tableStyleDecls(st, ts);
|
|
417
|
-
out.push(rule(table, d.table));
|
|
418
|
-
out.push(rule(sub('th, td'), d.cells));
|
|
419
|
-
out.push(rule(sub('td'), d.td));
|
|
420
|
-
if (d.alt)
|
|
421
|
-
out.push(rule(sub('td.pt-alt'), d.alt));
|
|
422
|
-
out.push(rule(sub('th'), d.th));
|
|
423
|
-
for (const [sel, decls] of d.extra)
|
|
424
|
-
out.push(rule(sub(sel), decls));
|
|
425
|
-
}
|
|
426
|
-
out.push(rule('td img, th img', ['max-width: 100%', 'height: auto', 'display: block', 'margin: 0 auto']));
|
|
427
|
-
// --- callouts ------------------------------------------------------------
|
|
428
|
-
out.push(rule('aside.pt-callout', ['margin: 1em 0', 'padding: 0.75em 1em', 'page-break-inside: avoid']));
|
|
429
|
-
out.push(rule('.pt-callout-title', ['font-weight: bold', 'text-indent: 0', 'margin-block-end: 0.4em']));
|
|
430
|
-
// A composed page's frames (#862): blocks of its section, in Markdown
|
|
431
|
-
// order; a frame set in a callout style is drawn as its box.
|
|
432
|
-
if (options.composed) {
|
|
433
|
-
out.push(rule('section.pt-composed', ['margin-block: 1em']));
|
|
434
|
-
out.push(rule('div.pt-frame', ['margin: 0 0 1em']));
|
|
435
|
-
// A frame set in a writing mode of its own (#960): its lines run along
|
|
436
|
-
// the length written on the box (`height` for a vertical frame).
|
|
437
|
-
out.push(rule('div.pt-frame-vertical', ['writing-mode: vertical-rl', '-epub-writing-mode: vertical-rl', '-webkit-writing-mode: vertical-rl']));
|
|
438
|
-
out.push(rule('div.pt-frame-horizontal', ['writing-mode: horizontal-tb', '-epub-writing-mode: horizontal-tb', '-webkit-writing-mode: horizontal-tb']));
|
|
439
|
-
out.push(rule('div.pt-frame.pt-callout', ['padding: 0.75em 1em', 'page-break-inside: avoid']));
|
|
440
|
-
}
|
|
441
|
-
const frameStyles = new Set(options.composed?.frameStyles ?? []);
|
|
442
|
-
// The boxes an exercise is set in (#1244): its own frameless one, or the
|
|
443
|
-
// box style its exercise style names. An exercise is a `div` group.
|
|
444
|
-
const exerciseFrames = new Set((config.exerciseSettings?.styles ?? []).flatMap((st) => (st.box ? [st.box] : [])));
|
|
445
|
-
for (const s of config.calloutStyles) {
|
|
446
|
-
const cls = idOf('pt-callout-', s.id);
|
|
447
|
-
// The box's selector, and the frames' set in its style (#862).
|
|
448
|
-
const sels = frameStyles.has(s.id) ? [`aside.${cls}`, `div.pt-frame.${cls}`] : [`aside.${cls}`];
|
|
449
|
-
if (exerciseFrames.has(s.id) || s.id.startsWith('__postext-ex'))
|
|
450
|
-
sels.push(`div.pt-exercise.${cls}`);
|
|
451
|
-
const sel = sels.join(', ');
|
|
452
|
-
const under = (rest) => sels.map((x) => `${x} ${rest}`).join(', ');
|
|
453
|
-
const bPx = px(s.body.fontSize);
|
|
454
|
-
const pad = s.padding;
|
|
455
|
-
const stripe = s.stripe.enabled && px(s.stripe.width) > 0 ? `${round(px(s.stripe.width) * (96 / dpi))}px solid ${s.stripe.color.hex}` : undefined;
|
|
456
|
-
const side = s.stripe.side === 'top' ? 'border-block-start' : s.stripe.side === 'right' ? 'border-inline-end' : 'border-inline-start';
|
|
457
|
-
// A box's sides are the body flow's (#371): in a right-to-left book,
|
|
458
|
-
// whose flow is mirrored, its left padding is on the page's right.
|
|
459
|
-
const [padRight, padLeft] = rtl ? [pad.left, pad.right] : [pad.right, pad.left];
|
|
460
|
-
// Corners of their own and a border per side (#1059); a joined tab
|
|
461
|
-
// reflows as a plain tab.
|
|
462
|
-
const radius = isCornerRadii(s.borderRadius)
|
|
463
|
-
? (() => {
|
|
464
|
-
const r = s.borderRadius;
|
|
465
|
-
const one = (d) => (d ? em(d) : '0');
|
|
466
|
-
// The corners of the body flow's sides, as the padding's.
|
|
467
|
-
const [tl, tr, br, bl] = rtl ? [r.topRight, r.topLeft, r.bottomLeft, r.bottomRight] : [r.topLeft, r.topRight, r.bottomRight, r.bottomLeft];
|
|
468
|
-
return [tl, tr, br, bl].some((d) => d && px(d) > 0)
|
|
469
|
-
? `border-radius: ${one(tl)} ${one(tr)} ${one(br)} ${one(bl)}`
|
|
470
|
-
: undefined;
|
|
471
|
-
})()
|
|
472
|
-
: px(uniformRadius(s.borderRadius)) > 0 && `border-radius: ${em(uniformRadius(s.borderRadius))}`;
|
|
473
|
-
const sideBorders = s.border.sides
|
|
474
|
-
? ['top', 'right', 'bottom', 'left'].map((k) => {
|
|
475
|
-
const b = s.border.sides[rtl && k === 'left' ? 'right' : rtl && k === 'right' ? 'left' : k];
|
|
476
|
-
return b.enabled && px(b.width) > 0 ? `border-${k}: ${round(px(b.width) * (96 / dpi))}px solid ${b.color.hex}` : `border-${k}: none`;
|
|
477
|
-
})
|
|
478
|
-
: [];
|
|
479
|
-
out.push(rule(sel, [
|
|
480
|
-
s.backgroundEnabled && color(s.background, 'background-color'),
|
|
481
|
-
!s.border.sides && s.border.enabled && px(s.border.width) > 0 && `border: ${round(px(s.border.width) * (96 / dpi))}px solid ${s.border.color.hex}`,
|
|
482
|
-
...sideBorders,
|
|
483
|
-
stripe && `${side}: ${stripe}`,
|
|
484
|
-
radius,
|
|
485
|
-
`padding: ${em(pad.top)} ${em(padRight)} ${em(pad.bottom)} ${em(padLeft)}`,
|
|
486
|
-
`margin: ${em(s.marginTop)} 0 ${em(s.marginBottom)}`,
|
|
487
|
-
fam(s.body.fontFamily),
|
|
488
|
-
`font-size: ${round(bPx / bodyPx)}em`,
|
|
489
|
-
color(s.body.color),
|
|
490
|
-
s.body.italic && 'font-style: italic',
|
|
491
|
-
s.body.smallCaps && 'font-variant: small-caps',
|
|
492
|
-
`text-align: ${ALIGN[s.body.textAlign] ?? 'start'}`,
|
|
493
|
-
]));
|
|
494
|
-
out.push(rule(under('p'), [
|
|
495
|
-
`text-indent: ${round(px(s.body.firstLineIndent, bPx) / bPx)}em`,
|
|
496
|
-
spacingEm(s.body.paragraphSpacing, s.body.lineHeight, bPx) > 0 && `margin-block-end: ${spacingEm(s.body.paragraphSpacing, s.body.lineHeight, bPx)}em`,
|
|
497
|
-
lh(s.body.lineHeight, bPx),
|
|
498
|
-
]));
|
|
499
|
-
const t = s.titleStyle;
|
|
500
|
-
const tPx = px(t.fontSize);
|
|
501
|
-
out.push(rule(under('> .pt-callout-title'), [
|
|
502
|
-
fam(t.fontFamily),
|
|
503
|
-
`font-size: ${round(tPx / bPx)}em`,
|
|
504
|
-
`font-weight: ${t.fontWeight}`,
|
|
505
|
-
`font-style: ${t.italic ? 'italic' : 'normal'}`,
|
|
506
|
-
color(t.color),
|
|
507
|
-
t.textTransform === 'uppercase' && 'text-transform: uppercase',
|
|
508
|
-
t.letterSpacing.value !== 0 && `letter-spacing: ${round(px(t.letterSpacing, tPx) / tPx)}em`,
|
|
509
|
-
t.letterSpacing.value !== 0 && TRACKED_LIGATURES,
|
|
510
|
-
`margin-block-end: ${round(px(t.gap, tPx) / tPx)}em`,
|
|
511
|
-
'text-indent: 0',
|
|
512
|
-
]));
|
|
513
|
-
out.push(rule(under('li > .pt-lbl:first-child'), [color(s.lists.color)]));
|
|
514
|
-
}
|
|
515
|
-
// --- paragraph styles ----------------------------------------------------
|
|
516
|
-
for (const s of config.paragraphStyles) {
|
|
517
|
-
const sPx = px(s.fontSize);
|
|
518
|
-
const cls = idOf('ps-', s.id);
|
|
519
|
-
out.push(rule(`p.${cls}, div.pt-verse.${cls}, div.pt-stanza.${cls}`, [
|
|
520
|
-
fam(s.fontFamily),
|
|
521
|
-
`font-size: ${round(sPx / bodyPx)}em`,
|
|
522
|
-
lh(s.lineHeight, sPx),
|
|
523
|
-
color(s.color),
|
|
524
|
-
`text-align: ${ALIGN[s.textAlign] ?? 'start'}`,
|
|
525
|
-
s.fontWeight !== body.fontWeight && `font-weight: ${s.fontWeight}`,
|
|
526
|
-
s.italic && 'font-style: italic',
|
|
527
|
-
s.smallCaps && 'font-variant: small-caps',
|
|
528
|
-
!s.hyphenation && 'hyphens: manual',
|
|
529
|
-
s.textTransform === 'uppercase' && 'text-transform: uppercase',
|
|
530
|
-
// The style's own tracking (#907), in its own ems.
|
|
531
|
-
s.letterSpacing && s.letterSpacing.value !== 0 && `letter-spacing: ${round(px(s.letterSpacing, sPx) / sPx)}em`,
|
|
532
|
-
s.letterSpacing && s.letterSpacing.value !== 0 && TRACKED_LIGATURES,
|
|
533
|
-
// The style's own line breaking between CJK characters (#463).
|
|
534
|
-
s.wordBreak && `word-break: ${s.wordBreak}`,
|
|
535
|
-
px(s.indent, sPx) > 0 && `margin-inline-start: ${round(px(s.indent, sPx) / sPx)}em`,
|
|
536
|
-
// 地からN字上げ (#424).
|
|
537
|
-
s.endIndent && px(s.endIndent, sPx) > 0 && `margin-inline-end: ${round(px(s.endIndent, sPx) / sPx)}em`,
|
|
538
|
-
`text-indent: ${round(px(s.firstLineIndent, sPx) / sPx)}em`,
|
|
539
|
-
px(s.marginTop, sPx) > 0 && `margin-block-start: ${round(px(s.marginTop, sPx) / sPx)}em`,
|
|
540
|
-
px(s.marginBottom, sPx) > 0 && `margin-block-end: ${round(px(s.marginBottom, sPx) / sPx)}em`,
|
|
541
|
-
]));
|
|
542
|
-
// A hanging indent: the paragraph's turnovers padded in, its first line
|
|
543
|
-
// taken back to its own indent (the style's, when it sets one, #620).
|
|
544
|
-
// A poem's lines hang on their own (`.pt-verse-line`).
|
|
545
|
-
const hang = px(s.hangingIndent, sPx);
|
|
546
|
-
if (hang > 0) {
|
|
547
|
-
const first = s.ownFirstLineIndent ? px(s.firstLineIndent, sPx) : 0;
|
|
548
|
-
out.push(rule(`p.${cls}`, [`padding-inline-start: ${round(hang / sPx)}em`, `text-indent: ${round((first - hang) / sPx)}em`]));
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
// --- notes, contents, bibliography, index -------------------------------
|
|
552
|
-
const fn = config.footnotes;
|
|
553
|
-
const fnPx = px(fn.fontSize);
|
|
554
|
-
out.push(rule('.pt-footnotes', ['margin-block-start: 2em', 'border-block-start: 1px solid currentColor', 'padding-block-start: 0.5em']));
|
|
555
|
-
out.push(rule('aside.pt-footnote', [
|
|
556
|
-
`font-size: ${round(fnPx / bodyPx)}em`,
|
|
557
|
-
lh(fn.lineHeight, fnPx),
|
|
558
|
-
color(fn.color),
|
|
559
|
-
fn.textAlign && `text-align: ${ALIGN[fn.textAlign] ?? 'start'}`,
|
|
560
|
-
`margin-block-end: ${round(px(fn.spaceBetween, fnPx) / fnPx)}em`,
|
|
561
|
-
]));
|
|
562
|
-
out.push(rule('aside.pt-footnote p', ['text-indent: 0']));
|
|
563
|
-
out.push(rule('a.pt-noteref', ['text-decoration: none', color(body.referenceColor)]));
|
|
564
|
-
out.push(rule('a[role="doc-backlink"]', ['text-decoration: none']));
|
|
565
|
-
out.push(rule('nav.pt-toc ol', ['list-style: none', 'padding-inline-start: 0']));
|
|
566
|
-
out.push(rule('nav.pt-toc li', ['margin-block-start: 0.3em']));
|
|
567
|
-
out.push(rule('nav.pt-toc a', ['text-decoration: none', 'color: inherit']));
|
|
568
|
-
out.push(rule('nav.pt-toc li > .pt-lbl:first-child', ['margin-inline-start: 0', 'min-width: 2em']));
|
|
569
|
-
out.push(rule('.pt-toc-part', ['font-weight: bold', 'margin-block-start: 0.8em']));
|
|
570
|
-
out.push(rule('.pt-toc-subtitle', ['font-style: italic', 'font-size: 0.9em']));
|
|
571
|
-
out.push(rule('p.pt-bib', ['padding-inline-start: 2em', 'text-indent: -2em', 'margin-block-end: 0.3em']));
|
|
572
|
-
out.push(rule('p.pt-index-group', ['font-weight: bold', 'text-indent: 0', 'margin-block-start: 1em', 'page-break-after: avoid', 'break-after: avoid']));
|
|
573
|
-
// Index entries hang their turnover lines, and a sub-entry sits one
|
|
574
|
-
// indent in per level, both in the index's own em.
|
|
575
|
-
const ix = config.index;
|
|
576
|
-
const ixPx = px(ix.fontSize);
|
|
577
|
-
const turnover = round(px(ix.turnoverIndent, ixPx) / ixPx);
|
|
578
|
-
const step = round(px(ix.indent, ixPx) / ixPx);
|
|
579
|
-
out.push(rule('p.pt-index-entry', [
|
|
580
|
-
ixPx !== bodyPx && `font-size: ${round(ixPx / bodyPx)}em`,
|
|
581
|
-
`padding-inline-start: ${turnover}em`,
|
|
582
|
-
`text-indent: -${turnover}em`,
|
|
583
|
-
'text-align: start',
|
|
584
|
-
'hyphens: manual',
|
|
585
|
-
]));
|
|
586
|
-
for (const level of [1, 2, 3]) {
|
|
587
|
-
out.push(rule(`p.pt-index-l${level}`, [`margin-inline-start: ${round(step * level)}em`]));
|
|
588
|
-
}
|
|
589
|
-
// --- maths, chips, swatches, Chinese marks ------------------------------
|
|
590
|
-
out.push(rule('.pt-math-display', ['text-align: center', 'margin: 0.5em 0', 'text-indent: 0', 'overflow-x: auto']));
|
|
591
|
-
// Music scores (#1172): systems as wide as the column, a little air
|
|
592
|
-
// between two.
|
|
593
|
-
out.push(rule('.pt-score', ['margin: 0.75em 0', 'text-indent: 0']));
|
|
594
|
-
out.push(rule('.pt-score-system', ['display: block', 'width: 100%', 'height: auto', 'margin: 0 0 0.4em']));
|
|
595
|
-
out.push(rule('.pt-math svg', ['display: inline']));
|
|
596
|
-
out.push(rule('.pt-swatch', ['display: inline-block', 'width: 0.8em', 'height: 0.8em', 'border: 1px solid currentColor', 'vertical-align: baseline']));
|
|
597
|
-
out.push(rule('.pt-chip', ['display: inline-block', 'padding: 0 0.3em', 'border-radius: 0.2em', 'text-indent: 0', 'line-height: 1.2']));
|
|
598
|
-
for (const c of config.chipStyles) {
|
|
599
|
-
const cPx = c.fontSize ? px(c.fontSize) : bodyPx;
|
|
600
|
-
out.push(rule(`.${idOf('pt-chip-', c.id)}`, [
|
|
601
|
-
c.backgroundEnabled && color(c.background, 'background-color'),
|
|
602
|
-
// A blank to write on (#1227) is underlined, not boxed.
|
|
603
|
-
px(c.borderWidth) > 0 && `${c.underline ? 'border-bottom' : 'border'}: ${round(px(c.borderWidth) * (96 / dpi))}px ${c.underline ?? 'solid'} ${c.borderColor.hex}`,
|
|
604
|
-
px(c.borderRadius) > 0 && `border-radius: ${round(px(c.borderRadius, cPx) / cPx)}em`,
|
|
605
|
-
`padding: ${round(px(c.paddingY, cPx) / cPx)}em ${round(px(c.paddingX, cPx) / cPx)}em`,
|
|
606
|
-
c.fontFamily && fam(c.fontFamily),
|
|
607
|
-
c.fontSize && `font-size: ${round(cPx / bodyPx)}em`,
|
|
608
|
-
c.color && color(c.color),
|
|
609
|
-
c.bold && 'font-weight: bold',
|
|
610
|
-
c.italic && 'font-style: italic',
|
|
611
|
-
]));
|
|
612
|
-
}
|
|
613
|
-
// Exercises (#1231): a blank holds its input, a drawn box its picture
|
|
614
|
-
// (and, the script running, the controls laid over it); the answer key
|
|
615
|
-
// for a reader with no script follows the exercise.
|
|
616
|
-
out.push(rule('.pt-ex-slot', ['position: relative']));
|
|
617
|
-
out.push(rule('.pt-ex-blank', ['height: 1.2em', 'vertical-align: -0.25em']));
|
|
618
|
-
out.push(rule('.pt-ex-blank > input', ['box-sizing: border-box', 'width: 100%', 'height: 100%', 'margin: 0', 'padding: 0', 'border: 0', 'background: transparent', 'color: inherit', 'font: inherit', 'text-align: center']));
|
|
619
|
-
out.push(rule('.pt-ex-drawn', ['position: relative', 'margin: 0.4em 0', 'text-indent: 0', 'max-width: 100%']));
|
|
620
|
-
out.push(rule('.pt-ex-drawn > svg', ['display: block', 'overflow: visible', 'width: 100%', 'height: auto']));
|
|
621
|
-
out.push(rule('.pt-ex-static', ['margin: 0.6em 0 0', 'font-size: 0.9em', 'text-indent: 0']));
|
|
622
|
-
out.push(rule('.pt-ex-static > summary', ['cursor: pointer']));
|
|
623
|
-
out.push(rule('.pt-sc', ['font-variant: small-caps']));
|
|
624
|
-
out.push(rule('.pt-dots', ['font-style: normal', 'text-emphasis: filled dot', '-epub-text-emphasis-style: filled dot', '-webkit-text-emphasis-style: filled dot', 'text-emphasis-position: under right', '-webkit-text-emphasis-position: under right']));
|
|
625
|
-
// Other shapes and sides (the Japanese sesame over the text, #428),
|
|
626
|
-
// sorted so the stylesheet is the same whatever order they were read in.
|
|
627
|
-
for (const cls of [...(options.dots ?? [])].sort())
|
|
628
|
-
out.push(dotsRule(cls));
|
|
629
|
-
out.push(rule('.pt-proper', ['text-decoration: underline']));
|
|
630
|
-
out.push(rule('.pt-book', ['text-decoration: underline wavy']));
|
|
631
|
-
// Side lines (傍線): under horizontal text and left of vertical text, or
|
|
632
|
-
// over and right of it; one unbroken line, descenders and all.
|
|
633
|
-
const underSide = ['text-underline-position: under left', '-epub-text-underline-position: under left', '-webkit-text-underline-position: under left'];
|
|
634
|
-
out.push(rule('.pt-side', ['text-decoration-line: underline', 'text-decoration-skip-ink: none', ...underSide]));
|
|
635
|
-
out.push(rule('.pt-side.pt-side-over', ['text-decoration-line: overline']));
|
|
636
|
-
for (const style of ['double', 'wavy', 'dotted'])
|
|
637
|
-
out.push(rule(`.pt-side.pt-side-${style}`, [`text-decoration-style: ${style}`]));
|
|
638
|
-
out.push(rule('.pt-tcy', ['text-combine-upright: all', '-epub-text-combine: horizontal', '-webkit-text-combine: horizontal']));
|
|
639
|
-
out.push(rule('.pt-upright', ['text-orientation: upright', '-epub-text-orientation: upright', '-webkit-text-orientation: upright']));
|
|
640
|
-
out.push(rule('.pt-sideways', ['text-orientation: sideways', '-epub-text-orientation: sideways', '-webkit-text-orientation: sideways']));
|
|
641
|
-
out.push(rule('.pt-warichu', ['font-size: 0.6em']));
|
|
642
|
-
out.push(rule('ruby.pt-ruby-under', ['ruby-position: under', '-epub-ruby-position: under', '-webkit-ruby-position: after']));
|
|
643
|
-
out.push(rule('ruby.pt-ruby-right', ['ruby-position: inter-character']));
|
|
644
|
-
// Kanbun marks (訓点): half size, the 送り仮名 raised (right of vertical
|
|
645
|
-
// text) and the 返り点 lowered (left of it), after their character.
|
|
646
|
-
out.push(rule('.pt-okuri, .pt-kaeri', ['font-size: 0.5em', 'line-height: 0']));
|
|
647
|
-
out.push(rule('.pt-okuri', ['vertical-align: super']));
|
|
648
|
-
out.push(rule('.pt-kaeri', ['vertical-align: sub']));
|
|
649
|
-
out.push(rule('.pt-marker', ['height: 0']));
|
|
650
|
-
// Japanese note markers (JLReq §4.2.3), only in a book that sets them.
|
|
651
|
-
// In the line gap: a box of no advance, its text running back from it
|
|
652
|
-
// (right to left, inline-start being the end), its baseline raised past
|
|
653
|
-
// the text's em box (0.88 em over its baseline) without growing the
|
|
654
|
-
// line. Right of a vertical line: reduced, its box against
|
|
655
|
-
// the line's right side.
|
|
656
|
-
const marker = config.footnotes.markerPosition;
|
|
657
|
-
if (marker === 'side' || marker === 'right') {
|
|
658
|
-
const size = config.footnotes.markerSize;
|
|
659
|
-
const em = size.unit === 'em' || size.unit === 'rem' ? size.value : px(size) / bodyPx;
|
|
660
|
-
if (marker === 'side') {
|
|
661
|
-
out.push(rule('.pt-note-side', ['display: inline-block', 'inline-size: 0', 'direction: rtl', 'white-space: nowrap', 'line-height: 0', `font-size: ${round(em)}em`, `vertical-align: ${round(0.88 / em + 0.12)}em`]));
|
|
662
|
-
out.push(rule('.pt-note-side > span', ['direction: ltr', 'unicode-bidi: isolate']));
|
|
663
|
-
}
|
|
664
|
-
else {
|
|
665
|
-
out.push(rule('.pt-note-right', [`font-size: ${round(em)}em`, 'vertical-align: text-top', 'line-height: 0']));
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
// --- classical verse -----------------------------------------------------
|
|
669
|
-
// A bayt is a row of two equal hemistich columns (and the ornament's,
|
|
670
|
-
// empty without one): the ṣadr from the start side, the ʿajuz flush
|
|
671
|
-
// with the end side, so the rhymes stand in one column as in print. On
|
|
672
|
-
// a narrow screen the two halves stagger, the ʿajuz on its own line
|
|
673
|
-
// flush with the end, as the print does when they do not fit.
|
|
674
|
-
// A stanza set line by line (#620): its lines of verse, a blank line
|
|
675
|
-
// between stanzas, never hyphenated; each line's hang is on its own
|
|
676
|
-
// element.
|
|
677
|
-
if (options.stanzas) {
|
|
678
|
-
out.push(rule('.pt-stanza', ['margin: 0 0 1em', 'text-indent: 0', 'text-align: start', 'hyphens: manual']));
|
|
679
|
-
out.push(rule('.pt-verse-line', ['display: block']));
|
|
680
|
-
}
|
|
681
|
-
// Line numbers (#621): a poem's every Nth line carries its number in a
|
|
682
|
-
// margin on the start side of the stanza, out of the reading order.
|
|
683
|
-
if (options.stanzas && options.lineNumbers) {
|
|
684
|
-
out.push(rule('.pt-stanza', ['padding-left: 3em']));
|
|
685
|
-
out.push(rule('.pt-verse-line', ['position: relative']));
|
|
686
|
-
out.push(rule('.pt-line-number', [
|
|
687
|
-
'position: absolute', 'right: 100%', 'margin-right: 0.8em', 'text-indent: 0', 'font-size: 0.8em',
|
|
688
|
-
'white-space: nowrap', '-webkit-user-select: none', 'user-select: none',
|
|
689
|
-
]));
|
|
690
|
-
out.push(rule('[dir="rtl"] .pt-stanza, .pt-stanza[dir="rtl"]', ['padding-left: 0', 'padding-right: 3em']));
|
|
691
|
-
out.push(rule('[dir="rtl"] .pt-line-number, .pt-stanza[dir="rtl"] .pt-line-number', ['right: auto', 'left: 100%', 'margin-right: 0', 'margin-left: 0.8em']));
|
|
692
|
-
}
|
|
693
|
-
// Tab stops (#622): a line holding tabs is a row of its parts; a filler
|
|
694
|
-
// (with its leader as a border on the baseline) pushes the part after it
|
|
695
|
-
// to the row's end, a start stop's part keeps its printed width.
|
|
696
|
-
if (options.tabs) {
|
|
697
|
-
out.push(rule('.pt-tab-row', ['display: flex', 'align-items: baseline', 'text-indent: 0']));
|
|
698
|
-
out.push(rule('.pt-tab-part', ['flex: 0 1 auto']));
|
|
699
|
-
out.push(rule('.pt-tab-rest', ['flex: 1 1 0']));
|
|
700
|
-
out.push(rule('.pt-tab-fill', ['flex: 1 1 auto', 'min-width: 1em', 'margin: 0 0.5em']));
|
|
701
|
-
out.push(rule('.pt-tab-gap', ['flex: 0 0 0.5em']));
|
|
702
|
-
out.push(rule('.pt-leader-dots', ['border-bottom: 0.12em dotted currentColor']));
|
|
703
|
-
out.push(rule('.pt-leader-rule', ['border-bottom: 0.06em solid currentColor']));
|
|
704
|
-
}
|
|
705
|
-
// Code listings (#624): the box of `codeStyle` around `<pre><code>`, the
|
|
706
|
-
// lines as written in the code face, turned over by the reading system
|
|
707
|
-
// when the screen is narrow (`pre-wrap`); numbers and wrap left out of a
|
|
708
|
-
// selection; the highlighted lines tinted.
|
|
709
|
-
if (options.code) {
|
|
710
|
-
const cs = resolvedCodeStyle(config);
|
|
711
|
-
const codePx = dimensionToPx(cs.fontSize, dpi, bodyPx);
|
|
712
|
-
const bodyLead = body.lineHeight.unit === 'em' || body.lineHeight.unit === 'rem' ? body.lineHeight.value * bodyPx : px(body.lineHeight);
|
|
713
|
-
const codeLead = cs.lineHeight ? lh(cs.lineHeight, codePx) : `line-height: ${round(bodyLead / codePx)}`;
|
|
714
|
-
const len = (d) => em(d, codePx);
|
|
715
|
-
out.push(rule('.pt-code-box', [
|
|
716
|
-
'display: block',
|
|
717
|
-
`margin: ${em(cs.marginTop, codePx)} 0 ${em(cs.marginBottom, codePx)}`,
|
|
718
|
-
`font-size: ${round(codePx / bodyPx)}em`,
|
|
719
|
-
cs.backgroundEnabled && color(cs.background, 'background-color'),
|
|
720
|
-
cs.border.enabled && `border: ${round(px(cs.border.width) / codePx)}em solid ${cs.border.color.hex}`,
|
|
721
|
-
px(cs.borderRadius, codePx) > 0 && `border-radius: ${len(cs.borderRadius)}`,
|
|
722
|
-
`padding: ${len(cs.padding.top)} ${len(cs.padding.right)} ${len(cs.padding.bottom)} ${len(cs.padding.left)}`,
|
|
723
|
-
'text-indent: 0',
|
|
724
|
-
]));
|
|
725
|
-
out.push(rule('.pt-code-title', [fam(cs.titleStyle?.fontFamily ?? cs.fontFamily), 'font-weight: bold', 'margin: 0 0 0.4em', 'text-indent: 0']));
|
|
726
|
-
out.push(rule('pre.pt-code', [
|
|
727
|
-
fam(cs.fontFamily), 'font-size: 1em', codeLead, 'margin: 0', color(cs.color),
|
|
728
|
-
'white-space: pre-wrap', 'overflow-wrap: anywhere', `tab-size: ${cs.tabSize}`, `-moz-tab-size: ${cs.tabSize}`,
|
|
729
|
-
'text-align: left', 'hyphens: none',
|
|
730
|
-
]));
|
|
731
|
-
out.push(rule('pre.pt-code code', ['font: inherit', 'background: none']));
|
|
732
|
-
out.push(rule('.pt-code-num', [color(cs.lineNumberColor), 'margin-right: 1em', '-webkit-user-select: none', 'user-select: none']));
|
|
733
|
-
out.push(rule('mark.pt-code-hl', [color(cs.highlightBackground, 'background-color'), 'color: inherit']));
|
|
734
|
-
}
|
|
735
|
-
// Drop caps (#623): CSS initial letters, `lines` tall, sunk `sink`
|
|
736
|
-
// lines; a reading system without `initial-letter` floats the letter,
|
|
737
|
-
// about as tall, on the start side.
|
|
738
|
-
if (options.dropCaps && options.dropCaps.length > 0) {
|
|
739
|
-
out.push(rule('.pt-has-dropcap', ['text-indent: 0']));
|
|
740
|
-
out.push(rule('.pt-dropcap', ['float: left', 'line-height: 0.85', 'margin: 0.05em 0.08em 0 0', 'text-indent: 0']));
|
|
741
|
-
out.push(rule('[dir="rtl"] .pt-dropcap, p[dir="rtl"] .pt-dropcap', ['float: right', 'margin: 0.05em 0 0 0.08em']));
|
|
742
|
-
for (const shape of options.dropCaps) {
|
|
743
|
-
const [lines, sink] = shape.split('-').map(Number);
|
|
744
|
-
out.push(rule(`.pt-dropcap-${shape}`, [`font-size: ${round(lines * 1.25)}em`]));
|
|
745
|
-
out.push(`@supports (initial-letter: 1) or (-webkit-initial-letter: 1) {\n .pt-dropcap-${shape} { float: none; margin: 0 0.08em 0 0; font-size: 1em; line-height: inherit; -webkit-initial-letter: ${lines} ${sink}; initial-letter: ${lines} ${sink}; }\n}\n`);
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
// Text wrap (#627): a figure or a box floated to its side; what cannot
|
|
749
|
-
// run beside it in print (headings, other figures, tables, boxes,
|
|
750
|
-
// formulas, poems) clears it.
|
|
751
|
-
if (options.wraps) {
|
|
752
|
-
out.push(rule('.pt-wrap', ['margin-top: 0', 'margin-bottom: 1em']));
|
|
753
|
-
out.push(rule('.pt-wrap-left', ['float: left', 'clear: left']));
|
|
754
|
-
out.push(rule('.pt-wrap-right', ['float: right', 'clear: right']));
|
|
755
|
-
out.push(rule('.pt-wrap img', ['max-width: 100%', 'height: auto']));
|
|
756
|
-
out.push(rule('h1, h2, h3, h4, h5, h6, figure:not(.pt-wrap), table, aside:not(.pt-wrap), .pt-math-display, .pt-stanza, .pt-verse', ['clear: both']));
|
|
757
|
-
}
|
|
758
|
-
if (options.verse) {
|
|
759
|
-
// The poem as wide as its widest bayt, centred: its hemistichs share
|
|
760
|
-
// one width, as in print, instead of drifting to the screen's edges.
|
|
761
|
-
out.push(rule('.pt-verse', ['width: fit-content', 'max-width: 100%', 'margin: 0.5em auto', 'text-indent: 0']));
|
|
762
|
-
out.push(rule('.pt-bayt', [
|
|
763
|
-
'display: grid',
|
|
764
|
-
'grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr)',
|
|
765
|
-
'column-gap: 1em',
|
|
766
|
-
'margin: 0',
|
|
767
|
-
'text-indent: 0',
|
|
768
|
-
'text-align: start',
|
|
769
|
-
'hyphens: manual',
|
|
770
|
-
'page-break-inside: avoid',
|
|
771
|
-
'break-inside: avoid',
|
|
772
|
-
]));
|
|
773
|
-
out.push(rule('.pt-sadr', ['grid-column: 1', 'text-align: start']));
|
|
774
|
-
out.push(rule('.pt-verse-ornament', ['grid-column: 2', 'text-align: center']));
|
|
775
|
-
out.push(rule('.pt-ajuz', ['grid-column: 3', 'text-align: end']));
|
|
776
|
-
out.push(rule('.pt-bayt-single .pt-sadr', ['grid-column: 1 / -1', 'text-align: center']));
|
|
777
|
-
out.push('@media (max-width: 30em) {\n' +
|
|
778
|
-
' .pt-bayt { display: block; }\n' +
|
|
779
|
-
' .pt-sadr, .pt-ajuz { display: block; }\n' +
|
|
780
|
-
' .pt-verse-ornament { display: none; }\n' +
|
|
781
|
-
'}\n');
|
|
782
|
-
}
|
|
56
|
+
createTextRules({
|
|
57
|
+
config, options, dpi, body, bodyPx, fam, em, lh, color, px, spacingEm, out
|
|
58
|
+
});
|
|
59
|
+
createFigureRules({
|
|
60
|
+
config, options, dpi, bodyPx, fam, em, color, px, out
|
|
61
|
+
});
|
|
62
|
+
createBoxRules({
|
|
63
|
+
config, options, dpi, body, bodyPx, fam, em, lh, color, px, spacingEm, rtl, out
|
|
64
|
+
});
|
|
65
|
+
createApparatusRules({
|
|
66
|
+
config, options, dpi, body, bodyPx, fam, lh, color, px, out
|
|
67
|
+
});
|
|
68
|
+
createVerseRules({
|
|
69
|
+
config, options, dpi, body, bodyPx, fam, em, lh, color, px, out
|
|
70
|
+
});
|
|
783
71
|
return {
|
|
784
72
|
css: out.filter(Boolean).map((r) => (typeof r === 'string' ? r : ruleText(r))).join('\n'),
|
|
785
73
|
families,
|