@uniweb/press 0.4.15 → 0.4.17
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/package.json +1 -1
- package/src/adapters/math-css.js +400 -64
- package/src/docx/Link.jsx +18 -18
- package/src/docx/LinkPart.jsx +10 -9
- package/src/docx/classifyHref.js +61 -0
package/package.json
CHANGED
package/src/adapters/math-css.js
CHANGED
|
@@ -1,89 +1,425 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview The CSS a Temml-produced MathML document needs to lay out.
|
|
3
3
|
*
|
|
4
|
-
* Math arrives as MathML, which browsers render natively — so it is tempting
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Math arrives as MathML, which browsers render natively — so it is tempting to
|
|
5
|
+
* conclude, as this pipeline's own comments once did, that it carries no CSS
|
|
6
|
+
* dependency at all. It carries a large one, and `<menclose>` is the proof:
|
|
7
|
+
* that element is NOT part of MathML-Core, it is Temml's own polyfill, and the
|
|
8
|
+
* rules that draw `\cancel`, `\overline` and `\sout` over it exist only in
|
|
9
|
+
* Temml's stylesheet. Without them those constructs render as the bare term — a
|
|
10
|
+
* formula that means something different from what the author wrote.
|
|
7
11
|
*
|
|
8
|
-
*
|
|
9
|
-
* `tml-left` / `tml-right` carry the `&` column alignment of an `aligned`
|
|
10
|
-
* environment, and `tml-jot` carries its extra row spacing. With nothing
|
|
11
|
-
* defining them, a derivation's `=` signs do not line up and its rows sit
|
|
12
|
-
* flush against each other. The declarations below are Temml's own, from
|
|
13
|
-
* `Temml-Local.css`.
|
|
12
|
+
* ── Why this is a vendored string and not an import ──
|
|
14
13
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* derivation and leaves the matrix broken; the `math mtd` rule below is the
|
|
22
|
-
* part Temml does not have.
|
|
14
|
+
* The browser lane (`@uniweb/kit/math-tokens.css`) simply does
|
|
15
|
+
* `@import "temml/dist/Temml-Local.css"` and lets the bundler resolve it. press
|
|
16
|
+
* cannot: its adapters EMIT CSS as a string into a document, run in browser
|
|
17
|
+
* hosts as well as Node ones (see the host-supplied asset loaders in
|
|
18
|
+
* `assets/fetch.js`), and press has — by constitution — no `@uniweb/*`
|
|
19
|
+
* dependency at all. So the declarations are genuinely duplicated here.
|
|
23
20
|
*
|
|
24
|
-
*
|
|
21
|
+
* That duplicate is pinned mechanically rather than by comment:
|
|
22
|
+
* `framework/_contracts/math-css-parity.test.js` recomposes this value from the
|
|
23
|
+
* real `temml` package plus kit's stylesheet and fails when they disagree.
|
|
24
|
+
* Regenerate rather than hand-edit `TEMML_BASE`.
|
|
25
25
|
*
|
|
26
|
-
*
|
|
27
|
-
* supplied asset loaders in `assets/fetch.js`), so press cannot read a file at
|
|
28
|
-
* runtime, and it does not depend on `@uniweb/kit`. The same declarations
|
|
29
|
-
* therefore live in `kit/src/prose-tokens.css` for the browser lane.
|
|
26
|
+
* ── The two deliberate departures from Temml's sheet ──
|
|
30
27
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
28
|
+
* 1. **`@font-face` is stripped.** It points at `Temml.woff2`, which a bundler
|
|
29
|
+
* emits as an asset for the browser lane but which press would have to
|
|
30
|
+
* package into the EPUB itself. Fixing it is an asset question for the EPUB
|
|
31
|
+
* adapter, not a CSS one.
|
|
32
|
+
*
|
|
33
|
+
* Consequence, accepted for now, and it is wider than it first looks: two
|
|
34
|
+
* rules want that face — `math .mathscr` and, more commonly, `mo.tml-prime`,
|
|
35
|
+
* so every `f'` and `f''` in a compiled document loses its prime alignment
|
|
36
|
+
* and falls back to a system glyph. (It is NOT what draws `\mathcal`; that
|
|
37
|
+
* is `*.mathcal { font-feature-settings: 'ss01' }` and survives stripping.)
|
|
38
|
+
*
|
|
39
|
+
* 2. **`.tml-eqn::before` is rescoped to `.tml-eqn:empty::before`.** Temml
|
|
40
|
+
* leaves the tag span empty and asks a CSS counter to draw the number. That
|
|
41
|
+
* works in a browser and not in a document: Paged.js rewrites counters for
|
|
42
|
+
* its own pagination and strips `counter-increment`, so every equation
|
|
43
|
+
* rendered as "(0)" (measured 2026-07-31). `numberEquations` below therefore
|
|
44
|
+
* writes the number in as TEXT — and a span carrying a number is no longer
|
|
45
|
+
* `:empty`, so the counter rule and the text can never both fire.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Temml's own stylesheet, vendored. Generated from `temml/dist/Temml-Local.css`
|
|
50
|
+
* with the two departures described above. Do not hand-edit.
|
|
35
51
|
*/
|
|
52
|
+
const TEMML_BASE = `math {
|
|
53
|
+
font-family: "Cambria Math", 'STIXTwoMath-Regular', 'NotoSansMath-Regular', math;
|
|
54
|
+
font-style: normal;
|
|
55
|
+
font-weight: normal;
|
|
56
|
+
line-height: normal;
|
|
57
|
+
font-size-adjust: none;
|
|
58
|
+
text-indent: 0;
|
|
59
|
+
text-transform: none;
|
|
60
|
+
letter-spacing: normal;
|
|
61
|
+
word-wrap: normal;
|
|
62
|
+
direction: ltr;
|
|
63
|
+
/* Prevent Firefox from omitting the dot on i or j. */
|
|
64
|
+
font-feature-settings: "dtls" off;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
math * {
|
|
68
|
+
border-color: currentColor;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* display: block is necessary in Firefox and Safari.
|
|
72
|
+
* Not in Chromium, which recognizes display: "block math" written inline. */
|
|
73
|
+
math.tml-display {
|
|
74
|
+
display: block;
|
|
75
|
+
width: 100%;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
*.mathcal {
|
|
79
|
+
/* NotoSans */
|
|
80
|
+
font-feature-settings: 'ss01';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
math .mathscr {
|
|
84
|
+
font-family: "Temml";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
mo.tml-prime {
|
|
88
|
+
font-family: Temml;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Cramped superscripts in WebKit */
|
|
92
|
+
mfrac > :nth-child(2),
|
|
93
|
+
msqrt,
|
|
94
|
+
mover > :first-child {
|
|
95
|
+
math-shift: compact
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.menclose {
|
|
99
|
+
display: inline-block;
|
|
100
|
+
position: relative;
|
|
101
|
+
padding: 0.5ex 0ex;
|
|
102
|
+
}
|
|
103
|
+
.tml-cancelto {
|
|
104
|
+
display: inline-block;
|
|
105
|
+
position: absolute;
|
|
106
|
+
top: 0;
|
|
107
|
+
left: 0;
|
|
108
|
+
padding: 0.5ex 0ex;
|
|
109
|
+
background-color: currentColor;
|
|
110
|
+
/* Use the SVG as an alpha mask (painted by background-color) */
|
|
111
|
+
-webkit-mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><defs><marker id='a' markerHeight='5' markerUnits='strokeWidth' markerWidth='7' orient='auto' refX='7' refY='2.5'><path fill='black' d='m0 0 7 2.5L0 5z'/></marker></defs><line x2='100%25' y1='100%25' stroke='black' stroke-width='.06em' marker-end='url(%23a)' vector-effect='non-scaling-stroke'/></svg>");
|
|
112
|
+
mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><defs><marker id='a' markerHeight='5' markerUnits='strokeWidth' markerWidth='7' orient='auto' refX='7' refY='2.5'><path fill='black' d='m0 0 7 2.5L0 5z'/></marker></defs><line x2='100%25' y1='100%25' stroke='black' stroke-width='.06em' marker-end='url(%23a)' vector-effect='non-scaling-stroke'/></svg>");
|
|
113
|
+
-webkit-mask-repeat: no-repeat;
|
|
114
|
+
mask-repeat: no-repeat;
|
|
115
|
+
-webkit-mask-size: 100% 100%;
|
|
116
|
+
mask-size: 100% 100%;
|
|
117
|
+
-webkit-mask-position: 0 0;
|
|
118
|
+
mask-position: 0 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@supports (-moz-appearance: none) {
|
|
122
|
+
/* \vec w/o italic correction for Firefox */
|
|
123
|
+
.tml-vec {
|
|
124
|
+
transform: scale(0.75)
|
|
125
|
+
}
|
|
126
|
+
/* Fix \cancelto in Firefox */
|
|
127
|
+
.ff-narrow {
|
|
128
|
+
width: 0em;
|
|
129
|
+
}
|
|
130
|
+
.ff-nudge-left {
|
|
131
|
+
margin-left: -0.2em;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
36
134
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
.tml-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
135
|
+
@supports (not (-moz-appearance: none)) {
|
|
136
|
+
/* Chromium and WebKit */
|
|
137
|
+
/* prime vertical alignment */
|
|
138
|
+
mo.tml-prime {
|
|
139
|
+
font-family: Temml;
|
|
140
|
+
}
|
|
141
|
+
/* Italic correction on superscripts */
|
|
142
|
+
.tml-sml-pad {
|
|
143
|
+
padding-left: 0.05em;
|
|
144
|
+
}
|
|
145
|
+
.tml-med-pad {
|
|
146
|
+
padding-left: 0.10em;
|
|
147
|
+
}
|
|
148
|
+
.tml-lrg-pad {
|
|
149
|
+
padding-left: 0.15em;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@supports (-webkit-backdrop-filter: blur(1px)) {
|
|
154
|
+
/* WebKit vertical & italic correction on accents */
|
|
155
|
+
.wbk-acc {
|
|
156
|
+
/* lower by x-height distance */
|
|
157
|
+
transform: translate(0em, 0.431em);
|
|
158
|
+
}
|
|
159
|
+
.wbk-sml {
|
|
160
|
+
transform: translate(0.07em, 0);
|
|
161
|
+
}
|
|
162
|
+
.wbk-sml-acc {
|
|
163
|
+
transform: translate(0.07em, 0.431em);
|
|
164
|
+
}
|
|
165
|
+
.wbk-sml-vec {
|
|
166
|
+
transform: scale(0.75) translate(0.07em, 0);
|
|
167
|
+
}
|
|
168
|
+
.wbk-med {
|
|
169
|
+
transform: translate(0.14em, 0);
|
|
170
|
+
}
|
|
171
|
+
.wbk-med-acc {
|
|
172
|
+
transform: translate(0.14em, 0.431em);
|
|
173
|
+
}
|
|
174
|
+
.wbk-med-vec {
|
|
175
|
+
transform: scale(0.75) translate(0.14em, 0);
|
|
176
|
+
}
|
|
177
|
+
.wbk-lrg {
|
|
178
|
+
transform: translate(0.21em, 0);
|
|
179
|
+
}
|
|
180
|
+
.wbk-lrg-acc {
|
|
181
|
+
transform: translate(0.21em, 0.431em);
|
|
182
|
+
}
|
|
183
|
+
.wbk-lrg-vec {
|
|
184
|
+
transform: scale(0.75) translate(0.21em, 0);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* \cancel & \phase use background images. Get them to print. */
|
|
189
|
+
menclose {
|
|
190
|
+
-webkit-print-color-adjust: exact; /* Chrome & Edge */
|
|
191
|
+
print-color-adjust: exact;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* Array cell justification in Firefox & WebKit */
|
|
195
|
+
.tml-right {
|
|
196
|
+
text-align: right;
|
|
197
|
+
}
|
|
198
|
+
.tml-left {
|
|
199
|
+
text-align: left;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* For CD labels that grow to the left in Firefox and WebKit */
|
|
203
|
+
.tml-shift-left { margin-left:-200% }
|
|
204
|
+
|
|
205
|
+
/* Styles for Chromium only */
|
|
206
|
+
@supports (not (-webkit-backdrop-filter: blur(1px))) and (not (-moz-appearance: none)) {
|
|
207
|
+
/* Italic correction on accents */
|
|
208
|
+
.chr-sml {
|
|
209
|
+
transform: translate(0.07em, 0)
|
|
210
|
+
}
|
|
211
|
+
.chr-sml-vec {
|
|
212
|
+
transform: scale(0.75) translate(0.07em, 0)
|
|
213
|
+
}
|
|
214
|
+
.chr-med {
|
|
215
|
+
transform: translate(0.14em, 0)
|
|
216
|
+
}
|
|
217
|
+
.chr-med-vec {
|
|
218
|
+
transform: scale(0.75) translate(0.14em, 0)
|
|
219
|
+
}
|
|
220
|
+
.chr-lrg {
|
|
221
|
+
transform: translate(0.21em, 0)
|
|
222
|
+
}
|
|
223
|
+
.chr-lrg-vec {
|
|
224
|
+
transform: scale(0.75) translate(0.21em, 0)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* For CD labels that grow to the left */
|
|
228
|
+
.tml-shift-left { margin-left:-100% }
|
|
229
|
+
|
|
230
|
+
/* MathML Core & Chromium do not support the MathML 3.0 element <menclose> attributes. */
|
|
231
|
+
/* So use styles. */
|
|
232
|
+
menclose {
|
|
233
|
+
position: relative;
|
|
234
|
+
padding: 0.5ex 0ex;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.tml-overline {
|
|
238
|
+
padding: 0.1em 0 0 0;
|
|
239
|
+
border-top: 0.065em solid;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.tml-underline {
|
|
243
|
+
padding: 0 0 0.1em 0;
|
|
244
|
+
border-bottom: 0.065em solid;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.tml-cancel {
|
|
248
|
+
display: inline-block;
|
|
249
|
+
position: absolute;
|
|
250
|
+
left: 0.5px;
|
|
251
|
+
bottom: 0;
|
|
252
|
+
width: 100%;
|
|
253
|
+
height: 100%;
|
|
254
|
+
background-color: currentColor;
|
|
255
|
+
}
|
|
256
|
+
.upstrike {
|
|
257
|
+
clip-path: polygon(0.05em 100%, 0em calc(100% - 0.05em), calc(100% - 0.05em) 0em, 100% 0.05em);
|
|
258
|
+
}
|
|
259
|
+
.downstrike {
|
|
260
|
+
clip-path: polygon(0em 0.05em, 0.05em 0em, 100% calc(100% - 0.05em), calc(100% - 0.05em) 100%);
|
|
261
|
+
}
|
|
262
|
+
.sout {
|
|
263
|
+
clip-path: polygon(0em calc(55% + 0.0333em), 0em calc(55% - 0.0333em), 100% calc(55% - 0.0333em), 100% calc(55% + 0.0333em));
|
|
264
|
+
}
|
|
265
|
+
.tml-xcancel {
|
|
266
|
+
clip-path: polygon(0.05em 0em, 0em 0.05em, calc(50% - 0.05em) 50%, 0em calc(100% - 0.05em), 0.05em 100%, 50% calc(50% + 0.05em), calc(100% - 0.05em) 100%, 100% calc(100% - 0.05em), calc(50% + 0.05em) 50%, 100% 0.05em, calc(100% - 0.05em) 0%, 50% calc(50% - 0.05em));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.longdiv-top {
|
|
270
|
+
border-top: 0.067em solid;
|
|
271
|
+
padding: 0.1em 0.2em 0.2em 0.433em;
|
|
272
|
+
}
|
|
273
|
+
.longdiv-arc {
|
|
274
|
+
position: absolute;
|
|
275
|
+
top: 0;
|
|
276
|
+
bottom: 0.1em;
|
|
277
|
+
left: -0.4em;
|
|
278
|
+
width: 0.7em;
|
|
279
|
+
border: 0.067em solid;
|
|
280
|
+
transform: translateY(-0.067em);
|
|
281
|
+
border-radius: 70%;
|
|
282
|
+
clip-path: inset(0 0 0 0.4em);
|
|
283
|
+
box-sizing: border-box;}
|
|
284
|
+
.menclose {display: inline-block;
|
|
285
|
+
text-align: left;
|
|
286
|
+
position: relative;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.phasor-bottom {
|
|
290
|
+
border-bottom: 0.067em solid;
|
|
291
|
+
padding: 0.2em 0.2em 0.1em 0.6em;
|
|
292
|
+
}
|
|
293
|
+
.phasor-angle {
|
|
294
|
+
display: inline-block;
|
|
295
|
+
position: absolute;
|
|
296
|
+
left: 0.5px;
|
|
297
|
+
bottom: -0.04em;
|
|
298
|
+
height: 100%;
|
|
299
|
+
aspect-ratio: 0.5;
|
|
300
|
+
background-color: currentColor;
|
|
301
|
+
clip-path: polygon(0.05em 100%, 0em calc(100% - 0.05em), calc(100% - 0.05em) 0em, 100% 0.05em);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.tml-fbox {
|
|
305
|
+
padding: 3pt;
|
|
306
|
+
border: 1px solid;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.circle-pad {
|
|
310
|
+
padding: 0.267em;
|
|
311
|
+
}
|
|
312
|
+
.textcircle {
|
|
313
|
+
position: absolute;
|
|
314
|
+
top: 0;
|
|
315
|
+
bottom: 0;
|
|
316
|
+
right: 0;
|
|
317
|
+
left: 0;
|
|
318
|
+
border: 0.067em solid;
|
|
319
|
+
border-radius: 50%;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.actuarial {
|
|
323
|
+
padding: 0.03889em 0.03889em 0 0.03889em;
|
|
324
|
+
border-width: 0.08em 0.08em 0em 0em;
|
|
325
|
+
border-style: solid;
|
|
326
|
+
margin-right: 0.03889em;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/* Stretch \widetilde */
|
|
330
|
+
.tml-crooked-2 {
|
|
331
|
+
transform: scale(2.0, 1.1)
|
|
332
|
+
}
|
|
333
|
+
.tml-crooked-3 {
|
|
334
|
+
transform: scale(3.0, 1.3)
|
|
335
|
+
}
|
|
336
|
+
.tml-crooked-4 {
|
|
337
|
+
transform: scale(4.0, 1.4)
|
|
338
|
+
}
|
|
339
|
+
/* set array cell justification */
|
|
340
|
+
.tml-right {
|
|
341
|
+
text-align: -webkit-right;
|
|
342
|
+
}
|
|
343
|
+
.tml-left {
|
|
344
|
+
text-align: -webkit-left;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.special-fraction {
|
|
349
|
+
font-family: 'STIX TWO', 'Times New Roman', Times, Tinos, serif;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/* flex-wrap for line-breaking in Chromium */
|
|
353
|
+
math {
|
|
354
|
+
display: inline-flex;
|
|
355
|
+
flex-wrap: wrap;
|
|
356
|
+
align-items: baseline;
|
|
357
|
+
}
|
|
358
|
+
math > mrow {
|
|
359
|
+
padding: 0.5ex 0ex;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/* Default mtd top padding is 0.5ex per MathML-Core and user-agent CSS */
|
|
363
|
+
/* We adjust for jot and small */
|
|
364
|
+
mtable.tml-jot mtd {
|
|
365
|
+
padding-top: 0.7ex;
|
|
366
|
+
padding-bottom: 0.7ex;
|
|
367
|
+
}
|
|
368
|
+
mtable.tml-small mtd {
|
|
369
|
+
padding-top: 0.35ex;
|
|
370
|
+
padding-bottom: 0.35ex;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/* Firefox */
|
|
374
|
+
@-moz-document url-prefix() {
|
|
375
|
+
/* Avoid flex-wrap */
|
|
376
|
+
math { display: inline; }
|
|
377
|
+
math > mrow { padding: 0 }
|
|
378
|
+
/* Adjust Firefox spacing between array rows */
|
|
379
|
+
mtd, mtable.tml-small mtd { padding-top: 0; padding-bottom: 0; }
|
|
380
|
+
mtable.tml-jot mtd { padding-top: 0.2ex; padding-bottom: 0.ex; }
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/* AMS environment auto-numbering via CSS counter. */
|
|
60
384
|
.tml-eqn:empty::before {
|
|
61
385
|
counter-increment: tmlEqnNo;
|
|
62
386
|
content: "(" counter(tmlEqnNo) ")";
|
|
63
387
|
}
|
|
388
|
+
|
|
64
389
|
body {
|
|
65
390
|
counter-reset: tmlEqnNo;
|
|
391
|
+
}`
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* What Temml does not have. Kept byte-identical to the rules in
|
|
395
|
+
* `@uniweb/kit/math-tokens.css`, which the parity test enforces.
|
|
396
|
+
*
|
|
397
|
+
* 1. `mtd` padding: Temml's sheet only ADJUSTS row spacing for jot and small,
|
|
398
|
+
* on the stated assumption that "default mtd top padding is 0.5ex per
|
|
399
|
+
* MathML-Core and user-agent CSS". Measured in Chrome (2026-07), a pristine
|
|
400
|
+
* `mtd` gets 0px, and a `pmatrix` or `cases` carries no `tml-*` class at
|
|
401
|
+
* all — so its rows touch. Guarded to exclude Firefox because Temml
|
|
402
|
+
* deliberately ZEROES mtd padding there, and an unguarded `math mtd`
|
|
403
|
+
* outranks it on specificity (0,0,2 vs 0,0,1) regardless of the at-rule.
|
|
404
|
+
*
|
|
405
|
+
* 2. Display math needs CSS block layout, not MathML layout, for an equation
|
|
406
|
+
* TAG to reach the right margin. Temml already declares
|
|
407
|
+
* `math.tml-display { display: block; width: 100% }` and cannot win: it emits
|
|
408
|
+
* `style="display:block math"` inline on every display formula, and an
|
|
409
|
+
* inline style beats any stylesheet rule. This restores Temml's own intent
|
|
410
|
+
* with the `!important` its rule lacks, scoped with `:has()` so unnumbered
|
|
411
|
+
* formulas keep MathML layout and stay centred.
|
|
412
|
+
*/
|
|
413
|
+
const UNIWEB_CORRECTIONS = `@supports (not (-moz-appearance: none)) {
|
|
414
|
+
math mtd { padding-top: 0.5ex; padding-bottom: 0.5ex; }
|
|
66
415
|
}
|
|
67
|
-
/* Display math needs CSS block layout, not MathML layout, for an equation TAG
|
|
68
|
-
to reach the right margin: the tag rides in an mtable whose width:100%
|
|
69
|
-
Chromium ignores under display: block math, collapsing the spacer cells so
|
|
70
|
-
"(1)" sits glued to the equation instead of at the margin.
|
|
71
|
-
|
|
72
|
-
!important is not decoration. Temml emits style=display:block math on
|
|
73
|
-
every display formula unconditionally — no option turns it off — so an inline
|
|
74
|
-
style beats any rule we write, including Temml's own math.tml-display { display: block }, which is exactly what this restores. Fixing it here
|
|
75
|
-
rather than in the generator also repairs math that was already built.
|
|
76
|
-
|
|
77
|
-
Scoped with :has() to formulas that actually carry a tag. Switching every
|
|
78
|
-
display formula to CSS block layout would left-align the lot -- MathML layout
|
|
79
|
-
is what centres them -- so an unnumbered derivation or matrix keeps
|
|
80
|
-
display: block math and stays centred. Where :has() is unsupported the tag
|
|
81
|
-
simply does not reach the margin; nothing else changes. */
|
|
82
416
|
math.tml-display:has(.tml-eqn) {
|
|
83
417
|
display: block !important;
|
|
84
418
|
width: 100%;
|
|
85
419
|
}`
|
|
86
420
|
|
|
421
|
+
export const MATH_CSS = `${TEMML_BASE}\n${UNIWEB_CORRECTIONS}`
|
|
422
|
+
|
|
87
423
|
/**
|
|
88
424
|
* Write equation numbers into Temml's tag spans as text.
|
|
89
425
|
*
|
package/src/docx/Link.jsx
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Link component for document output.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Emits an in-document anchor for a `#fragment` and a destination for
|
|
5
|
+
* everything else. See `classifyHref` for why that is the whole rule.
|
|
5
6
|
*
|
|
6
7
|
* @param {Object} props
|
|
7
8
|
* @param {Object} props.data - Link data: { label, href }
|
|
8
9
|
*/
|
|
10
|
+
import { classifyHref } from './classifyHref.js'
|
|
9
11
|
|
|
10
12
|
export default function Link({ data, ...props }) {
|
|
11
13
|
if (!data) return null
|
|
@@ -14,18 +16,11 @@ export default function Link({ data, ...props }) {
|
|
|
14
16
|
|
|
15
17
|
if (!href) return null
|
|
16
18
|
|
|
17
|
-
const
|
|
19
|
+
const { anchor } = classifyHref(href)
|
|
18
20
|
|
|
19
|
-
if (
|
|
21
|
+
if (anchor !== null) {
|
|
20
22
|
return (
|
|
21
|
-
<a
|
|
22
|
-
data-type="externalHyperlink"
|
|
23
|
-
data-link={href}
|
|
24
|
-
href={href}
|
|
25
|
-
target="_blank"
|
|
26
|
-
rel="noopener noreferrer"
|
|
27
|
-
{...props}
|
|
28
|
-
>
|
|
23
|
+
<a data-type="internalHyperlink" data-anchor={anchor} href={href} {...props}>
|
|
29
24
|
<span data-type="text" data-style="Hyperlink">
|
|
30
25
|
{label || href}
|
|
31
26
|
</span>
|
|
@@ -33,15 +28,20 @@ export default function Link({ data, ...props }) {
|
|
|
33
28
|
)
|
|
34
29
|
}
|
|
35
30
|
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
const anchor = href.startsWith('#') ? href.slice(1) : href
|
|
31
|
+
// `target`/`rel` only affect the React preview, never the compiled
|
|
32
|
+
// document, so they stay on the narrower http test they have always
|
|
33
|
+
// used — opening a mailto: or a site path in a new tab is not what
|
|
34
|
+
// this component previously did and is not what this change is about.
|
|
35
|
+
const offSite = href.startsWith('http')
|
|
42
36
|
|
|
43
37
|
return (
|
|
44
|
-
<a
|
|
38
|
+
<a
|
|
39
|
+
data-type="externalHyperlink"
|
|
40
|
+
data-link={href}
|
|
41
|
+
href={href}
|
|
42
|
+
{...(offSite ? { target: '_blank', rel: 'noopener noreferrer' } : {})}
|
|
43
|
+
{...props}
|
|
44
|
+
>
|
|
45
45
|
<span data-type="text" data-style="Hyperlink">
|
|
46
46
|
{label || href}
|
|
47
47
|
</span>
|
package/src/docx/LinkPart.jsx
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* in-document anchor the same way (see below).
|
|
14
14
|
*/
|
|
15
15
|
import TextRun from './TextRun.jsx'
|
|
16
|
+
import { classifyHref } from './classifyHref.js'
|
|
16
17
|
|
|
17
18
|
/** `parts` is absent only for a hand-built part; fall back to the label. */
|
|
18
19
|
export function labelRuns(part) {
|
|
@@ -22,15 +23,15 @@ export function labelRuns(part) {
|
|
|
22
23
|
export default function LinkPart({ part }) {
|
|
23
24
|
const href = part.href || ''
|
|
24
25
|
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
// Same rule as the <Link> builder, from the same module rather than a
|
|
27
|
+
// second copy of the condition — the two used to classify the same href
|
|
28
|
+
// differently, which is what let `mailto:` compile to a dead anchor on
|
|
29
|
+
// one path and a working link on the other.
|
|
30
|
+
const { anchor } = classifyHref(href)
|
|
31
|
+
const linkAttrs =
|
|
32
|
+
anchor !== null
|
|
33
|
+
? { 'data-type': 'internalHyperlink', 'data-anchor': anchor }
|
|
34
|
+
: { 'data-type': 'externalHyperlink', 'data-link': href }
|
|
34
35
|
|
|
35
36
|
return (
|
|
36
37
|
<a {...linkAttrs} href={href}>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decide what SHAPE a link takes in the compiled document. INTERNAL helper —
|
|
3
|
+
* not in the barrel.
|
|
4
|
+
*
|
|
5
|
+
* The whole rule: **a `#fragment` is an in-document anchor; everything else is
|
|
6
|
+
* a destination, emitted verbatim.**
|
|
7
|
+
*
|
|
8
|
+
* ## Why that is the whole rule
|
|
9
|
+
*
|
|
10
|
+
* A fragment means "within this document" in every hypertext format, so this
|
|
11
|
+
* is the one classification Press can make from the href alone without knowing
|
|
12
|
+
* anything about the caller. Everything else — `https://…`, `mailto:…`,
|
|
13
|
+
* `/about`, `../sibling`, `page:installation` — is a destination Press has no
|
|
14
|
+
* standing to interpret.
|
|
15
|
+
*
|
|
16
|
+
* What this replaced was a guess: `href.startsWith('http')` treated everything
|
|
17
|
+
* else as an in-document anchor, so `mailto:a@b.com` and `/about` compiled to
|
|
18
|
+
* `<w:hyperlink w:anchor="mailto:a@b.com">` — a reference to a bookmark that
|
|
19
|
+
* cannot exist. That does not merely fail to navigate: **an anchor name is not
|
|
20
|
+
* a URL, so the destination is destroyed** and nothing downstream can recover
|
|
21
|
+
* it. Emitting it as a destination keeps it in the file, where a wrong link is
|
|
22
|
+
* at least visible and diagnosable.
|
|
23
|
+
*
|
|
24
|
+
* ## Why a bare name is NOT accepted as an anchor
|
|
25
|
+
*
|
|
26
|
+
* `section-3` and `page.html` are indistinguishable. Any rule that reads a bare
|
|
27
|
+
* token as a bookmark name necessarily turns some page links into dead anchors
|
|
28
|
+
* — which is the bug above. `#` is the disambiguator; that is what fragments
|
|
29
|
+
* are for. An author writes `#section-3`.
|
|
30
|
+
*
|
|
31
|
+
* ## Resolution is NOT Press's job
|
|
32
|
+
*
|
|
33
|
+
* Press does not know what `page:installation` means and must not learn. That
|
|
34
|
+
* is the same boundary the LaTeX inset helpers state for cite keys and xref
|
|
35
|
+
* ids — *"Resolution stays foundation-side … the foundation passes
|
|
36
|
+
* already-resolved keys in; Press's job is the emission shape"* — and the same
|
|
37
|
+
* one `DocumentProvider` states for `basePath` (*"Press itself has no awareness
|
|
38
|
+
* of the host runtime"*). Press declares no `@uniweb/*` dependency, and
|
|
39
|
+
* resolving a framework reference here would mean duplicating a resolver that
|
|
40
|
+
* lives in the runtime, which is how two implementations drift apart.
|
|
41
|
+
*
|
|
42
|
+
* So an unresolved `page:` reference compiles to a link pointing at the literal
|
|
43
|
+
* string. That is intended: it is the caller's unresolved input, preserved
|
|
44
|
+
* rather than swallowed. Callers compiling Uniweb content resolve authored
|
|
45
|
+
* hrefs before handing strings to the builders.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} href
|
|
48
|
+
* @returns {{ anchor: string | null }} `anchor` is the bare bookmark name for
|
|
49
|
+
* an in-document reference, or null when the href is a destination.
|
|
50
|
+
*/
|
|
51
|
+
export function classifyHref(href) {
|
|
52
|
+
if (!href || typeof href !== 'string') return { anchor: null }
|
|
53
|
+
|
|
54
|
+
// The bookmark name carries no '#'. A paragraph's `data-bookmark="x"`
|
|
55
|
+
// becomes `<w:bookmarkStart w:name="x"/>`, and Word matches
|
|
56
|
+
// `<w:hyperlink w:anchor="x">` against that name, so the fragment marker
|
|
57
|
+
// has to come off or the two never meet.
|
|
58
|
+
if (href.startsWith('#')) return { anchor: href.slice(1) }
|
|
59
|
+
|
|
60
|
+
return { anchor: null }
|
|
61
|
+
}
|