rapier-markdown-kit 1.2.7 → 1.2.9

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.
Files changed (67) hide show
  1. package/README.md +32 -30
  2. package/dist/agent/will.mjs +1 -6
  3. package/dist/images/gif.mjs +0 -3
  4. package/dist/images/playback.mjs +1 -10
  5. package/dist/kit/assets.mjs +0 -1
  6. package/dist/kit/conformance/measurer.mjs +0 -0
  7. package/dist/kit/conformance/run.mjs +1 -8
  8. package/dist/kit/export-fonts.mjs +0 -14
  9. package/dist/kit/index.mjs +1 -1
  10. package/dist/kit/ledger/authored-origin.mjs +0 -5
  11. package/dist/kit/ledger/authorship.mjs +0 -2
  12. package/dist/kit/ledger/carried.d.mts +20 -0
  13. package/dist/kit/ledger/carried.mjs +4 -7
  14. package/dist/kit/ledger/data.mjs +0 -8
  15. package/dist/kit/ledger/format.mjs +0 -25
  16. package/dist/kit/ledger/hash.mjs +11 -7
  17. package/dist/kit/ledger/history.mjs +10 -29
  18. package/dist/kit/ledger/inverse-source.mjs +0 -46
  19. package/dist/kit/ledger/journal-records.mjs +24 -56
  20. package/dist/kit/ledger/merge.mjs +0 -27
  21. package/dist/kit/ledger/text-merge.mjs +0 -20
  22. package/dist/kit/ledger/transport.mjs +0 -47
  23. package/dist/kit/ledger.d.mts +88 -0
  24. package/dist/kit/ledger.mjs +14 -0
  25. package/dist/kit/markdown-it.d.mts +4 -0
  26. package/dist/kit/markdown-it.mjs +36 -0
  27. package/dist/kit/pdf-glyphs.mjs +39 -45
  28. package/dist/kit/pdf-source.d.mts +5 -0
  29. package/dist/kit/pdf-source.mjs +125 -0
  30. package/dist/kit/pdf-vectors.mjs +0 -3
  31. package/dist/kit/print-capitals.mjs +0 -4
  32. package/dist/kit/render-entry.mjs +0 -1
  33. package/dist/kit/render-ink.mjs +0 -5
  34. package/dist/kit/render-markdown.mjs +15 -119
  35. package/dist/kit/render-print.mjs +4 -49
  36. package/dist/kit/render-sanitize.mjs +22 -58
  37. package/dist/kit/render-styles.mjs +0 -7
  38. package/dist/kit/render-work.mjs +0 -12
  39. package/dist/kit/render.mjs +18 -173
  40. package/dist/kit/will.d.mts +42 -0
  41. package/dist/layout/hyphenation.mjs +0 -23
  42. package/dist/layout/line-plan.mjs +0 -20
  43. package/dist/layout/model.mjs +0 -55
  44. package/dist/layout/paragraph-fit.mjs +50 -60
  45. package/dist/layout/preparation.mjs +23 -56
  46. package/dist/layout/prepared-paragraph.mjs +0 -25
  47. package/dist/layout/typography.mjs +103 -41
  48. package/dist/packages/rapier-diff/concerns.mjs +0 -16
  49. package/dist/packages/rapier-diff/diff.mjs +0 -7
  50. package/dist/packages/rapier-diff/index.mjs +0 -1
  51. package/dist/packages/rapier-diff/matching.mjs +0 -19
  52. package/dist/packages/rapier-diff/merge.mjs +0 -9
  53. package/dist/packages/rapier-diff/moves.mjs +0 -7
  54. package/dist/packages/rapier-diff/structure.mjs +0 -2
  55. package/dist/packages/rapier-diff/tokens.mjs +0 -6
  56. package/dist/packages/rapier-diff/walk.mjs +0 -12
  57. package/dist/spec/document-fonts.mjs +0 -9
  58. package/dist/spec/document-settings.mjs +6 -25
  59. package/dist/spec/frontmatter.mjs +0 -1
  60. package/dist/spec/html-blocks.mjs +3 -37
  61. package/dist/spec/md-assets.mjs +2 -61
  62. package/dist/spec/md-layout.mjs +0 -34
  63. package/dist/spec/md-marks.mjs +0 -51
  64. package/dist/spec/md-source.mjs +0 -16
  65. package/dist/spec/word-sidecar.mjs +0 -2
  66. package/package.json +21 -4
  67. package/style.css +38 -21
@@ -1,6 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // The licensed static faces retain the shipped variable font's exact glyphs and metrics.
3
- // Browser ranges own placement; unsupported shaping stays in the original page paint.
4
3
 
5
4
  const familyName = value => value.trim().replace(/^['"]|['"]$/g, '');
6
5
 
@@ -80,7 +79,6 @@ function shape(value, font, preserve, ligatures) {
80
79
  if (code === 0x00ad) {
81
80
  at += character.length;
82
81
  const own = font.glyph(code), hyphen = font.glyph(0x2d), glyph = own || hyphen;
83
- // A discretionary hyphen with no glyph stays in the page paint. Do not skip the lookup.
84
82
  if (!glyph || glyph >= font.glyphCount) return null;
85
83
  result.push({start, end: at, text: own ? character : '-', glyph, soft: true});
86
84
  continue;
@@ -101,7 +99,7 @@ function shape(value, font, preserve, ligatures) {
101
99
  }
102
100
 
103
101
  export async function collectPdfGlyphs(main, {width, check, faces = []}) {
104
- const fontCache = new Map(); // Parsed byte copies belong to this PDF operation.
102
+ const fontCache = new Map();
105
103
  const document = main.ownerDocument, window = document.defaultView, origin = main.getBoundingClientRect();
106
104
  const nodes = new Map(), hidden = [], canvas = document.createElement('canvas'); canvas.width = canvas.height = 1;
107
105
  const context = canvas.getContext('2d', {willReadFrequently: true});
@@ -130,11 +128,9 @@ export async function collectPdfGlyphs(main, {width, check, faces = []}) {
130
128
  let visited = 0;
131
129
  for (const element of main.querySelectorAll('*')) {
132
130
  if (++visited % 128 === 0) {await new Promise(resolve => setTimeout(resolve, 0)); check();}
133
- // Leaving the existing element in place preserves selectors, counters, line breaking and inline marks.
134
- // Keep preformatted blocks, their inline struts and syntax paint together.
135
- if (element.namespaceURI !== 'http://www.w3.org/1999/xhtml' || element.children.length || element.closest('svg,pre,.rapier-will-carrier')) continue;
136
- const textNodes = Array.from(element.childNodes).filter(node => node.nodeType === 3);
137
- if (textNodes.length !== 1 || !textNodes[0].data.trim() || !safe(element)) continue;
131
+ if (element.namespaceURI !== 'http://www.w3.org/1999/xhtml' || element.closest('svg,pre,.rapier-will-carrier')) continue;
132
+ const textNodes = Array.from(element.childNodes).filter(node => node.nodeType === 3 && node.data.trim());
133
+ if (!textNodes.length || !safe(element)) continue;
138
134
  const language = (element.closest('[lang]')?.getAttribute('lang') || document.documentElement.lang || '').toLowerCase();
139
135
  if (language && !/^en(?:-|$)/.test(language)) continue;
140
136
  const style = styleOf(element), family = familyName(style.fontFamily.split(',')[0]), weight = Number(style.fontWeight), size = Number.parseFloat(style.fontSize);
@@ -148,49 +144,47 @@ export async function collectPdfGlyphs(main, {width, check, faces = []}) {
148
144
  if (generated || altered || !Array.from(document.fonts).some(face => familyName(face.family) === family && face.status === 'loaded')) continue;
149
145
  const asset = faces.find(face => face.family === family && face.weight === weight);
150
146
  if (!asset) continue;
151
- const font = readFont(asset, fontCache), preserve = /pre|break-spaces/.test(style.whiteSpace), node = textNodes[0];
152
- const shaped = shape(node.data, font, preserve, style.fontVariantLigatures === 'normal' && !(Number.parseFloat(style.letterSpacing) || 0));
153
- if (!shaped) continue;
147
+ const font = readFont(asset, fontCache), preserve = /pre|break-spaces/.test(style.whiteSpace);
154
148
  context.font = [style.fontStyle, style.fontWeight, style.fontSize, style.fontFamily].join(' ');
155
149
  const metrics = context.measureText('Hg'), ascent = metrics.fontBoundingBoxAscent;
156
150
  if (!Number.isFinite(ascent)) continue;
157
151
  const rgba = color(style.webkitTextFillColor || style.color), range = document.createRange();
158
- let valid = true;
159
- const placed = [];
160
- for (const item of shaped) {
161
- range.setStart(node, item.start); range.setEnd(node, item.end);
162
- const ink = Array.from(range.getClientRects()).filter(rect => rect.height > 0);
163
- // A break after a soft hyphen can leave the next letter with a phantom box on the
164
- // hyphen's line. Keep the line that actually holds the ink.
165
- const lines = [];
166
- for (const rect of ink) {
167
- const line = lines.find(row => Math.abs(row[0].top - rect.top) <= .25);
168
- if (line) line.push(rect); else lines.push([rect]);
152
+ for (const node of textNodes) {
153
+ const shaped = shape(node.data, font, preserve, style.fontVariantLigatures === 'normal' && !(Number.parseFloat(style.letterSpacing) || 0));
154
+ if (!shaped) continue;
155
+ let valid = true;
156
+ const placed = [];
157
+ for (const item of shaped) {
158
+ range.setStart(node, item.start); range.setEnd(node, item.end);
159
+ const ink = Array.from(range.getClientRects()).filter(rect => rect.height > 0);
160
+ const lines = [];
161
+ for (const rect of ink) {
162
+ const line = lines.find(row => Math.abs(row[0].top - rect.top) <= .25);
163
+ if (line) line.push(rect); else lines.push([rect]);
164
+ }
165
+ lines.sort((a, b) => b.reduce((sum, rect) => sum + rect.width, 0) - a.reduce((sum, rect) => sum + rect.width, 0));
166
+ const previous = shaped[shaped.indexOf(item) - 1];
167
+ if (lines.length > 1 && !item.soft && !previous?.soft) {valid = false; break;}
168
+ const rects = lines[0] || [];
169
+ const first = (item.soft ? rects.find(rect => rect.width > 0) : null) || rects[0];
170
+ if (item.soft && !(first?.width > 0)) continue;
171
+ if (!first) {valid = false; break;}
172
+ const page = Math.max(0, Math.floor((first.left - origin.left + .01) / width));
173
+ Object.assign(item, {font, size, rgba, x: first.left - origin.left - page * width, baseline: first.top - origin.top + ascent});
174
+ placed.push(item);
169
175
  }
170
- lines.sort((a, b) => b.reduce((sum, rect) => sum + rect.width, 0) - a.reduce((sum, rect) => sum + rect.width, 0));
171
- const previous = shaped[shaped.indexOf(item) - 1];
172
- if (lines.length > 1 && !item.soft && !previous?.soft) {valid = false; break;}
173
- const rects = lines[0] || [];
174
- const first = (item.soft ? rects.find(rect => rect.width > 0) : null) || rects[0];
175
- // An unbroken soft hyphen has no ink. Leave it out of the vector run.
176
- if (item.soft && !(first?.width > 0)) continue;
177
- if (!first) {valid = false; break;}
178
- const page = Math.max(0, Math.floor((first.left - origin.left + .01) / width));
179
- Object.assign(item, {font, size, rgba, x: first.left - origin.left - page * width, baseline: first.top - origin.top + ascent});
180
- placed.push(item);
181
- }
182
- if (!valid || !placed.length) continue;
183
- nodes.set(node, placed);
184
- if (element.localName === 'li') {
185
- // A list item's marker inherits its fill and is not vectorized: hide only the words.
186
- const wrap = document.createElement('span');
187
- node.replaceWith(wrap); wrap.append(node);
188
- wrap.style.setProperty('-webkit-text-fill-color', 'transparent', 'important');
189
- hidden.push({wrap});
190
- continue;
176
+ if (!valid || !placed.length) continue;
177
+ nodes.set(node, placed);
178
+ if (element.localName === 'li' || element.children.length || textNodes.length > 1) {
179
+ const wrap = document.createElement('span');
180
+ node.replaceWith(wrap); wrap.append(node);
181
+ wrap.style.setProperty('-webkit-text-fill-color', 'transparent', 'important');
182
+ hidden.push({wrap});
183
+ continue;
184
+ }
185
+ hidden.push({node: element, value: element.style.getPropertyValue('-webkit-text-fill-color'), priority: element.style.getPropertyPriority('-webkit-text-fill-color')});
186
+ element.style.setProperty('-webkit-text-fill-color', 'transparent', 'important');
191
187
  }
192
- hidden.push({node: element, value: element.style.getPropertyValue('-webkit-text-fill-color'), priority: element.style.getPropertyPriority('-webkit-text-fill-color')});
193
- element.style.setProperty('-webkit-text-fill-color', 'transparent', 'important');
194
188
  }
195
189
  check(); return {nodes, restore};
196
190
  } catch (error) {restore(); throw error;}
@@ -0,0 +1,5 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Attach the exact source to a PDF Rapier wrote, without rewriting its pages (rapier-markdown-kit/pdf).
3
+ export function readPdfPageCount(input: Uint8Array | ArrayBuffer): number;
4
+ /** The PDF with `source` attached as an associated file; throws `pdf_source_invalid` for a PDF it cannot extend. */
5
+ export function attachPdfSource(input: Uint8Array | ArrayBuffer, options: {source: string; filename: string; maxBytes?: number; check?: () => void}): Promise<Uint8Array>;
@@ -0,0 +1,125 @@
1
+ // SPDX-License-Identifier: MIT
2
+ function pdfStructure(input) {
3
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
4
+ const fail = () => { throw Object.assign(new Error('The PDF source could not be attached.'), {code: 'pdf_source_invalid'}); };
5
+ let text = '';
6
+ for (let at = 0; at < bytes.length; at += 32768) text += String.fromCharCode(...bytes.subarray(at, at + 32768));
7
+ if (!text.startsWith('%PDF-')) fail();
8
+ const end = /startxref\s+(\d+)\s+%%EOF\s*$/.exec(text), xref = Number(end?.[1]);
9
+ if (!end || !Number.isSafeInteger(xref) || text.slice(xref, xref + 4) !== 'xref') fail();
10
+ const offsets = new Map();
11
+ let at = xref + 4;
12
+ const space = () => { while (at < text.length && /[\s\0]/.test(text[at])) at++; };
13
+ for (;;) {
14
+ space(); if (text.startsWith('trailer', at)) { at += 7; break; }
15
+ const section = /^(\d+)\s+(\d+)\s+/.exec(text.slice(at)); if (!section) fail();
16
+ at += section[0].length;
17
+ const from = Number(section[1]), count = Number(section[2]);
18
+ if (!Number.isSafeInteger(from + count) || count > bytes.length / 18) fail();
19
+ for (let i = 0; i < count; i++) {
20
+ const entry = /^(\d{10})\s+(\d{5})\s+([nf])\s+/.exec(text.slice(at)); if (!entry) fail();
21
+ at += entry[0].length;
22
+ if (entry[3] === 'n') offsets.set(from + i, {at: Number(entry[1]), generation: Number(entry[2])});
23
+ }
24
+ }
25
+ const skip = pos => { for (;;) { while (pos < text.length && /[\s\0]/.test(text[pos])) pos++; if (text[pos] !== '%') return pos; while (pos < text.length && !/[\r\n]/.test(text[pos])) pos++; } };
26
+ const tokenEnd = pos => { while (pos < text.length && !/[\s\0()[\]<>/%]/.test(text[pos])) pos++; return pos; };
27
+ const valueEnd = (pos, depth = 0) => {
28
+ if (depth > 32) fail(); pos = skip(pos);
29
+ if (text.startsWith('<<', pos)) {
30
+ pos += 2;
31
+ for (;;) { pos = skip(pos); if (text.startsWith('>>', pos)) return pos + 2; if (text[pos] !== '/') fail(); pos = valueEnd(tokenEnd(pos + 1), depth + 1); }
32
+ }
33
+ if (text[pos] === '[') { pos++; for (;;) { pos = skip(pos); if (text[pos] === ']') return pos + 1; pos = valueEnd(pos, depth + 1); } }
34
+ if (text[pos] === '(') {
35
+ let nesting = 1; pos++;
36
+ while (pos < text.length && nesting) { const c = text[pos++]; if (c === '\\') pos++; else if (c === '(') nesting++; else if (c === ')') nesting--; }
37
+ if (nesting) fail(); return pos;
38
+ }
39
+ if (text[pos] === '<') { const close = text.indexOf('>', pos + 1); if (close < 0) fail(); return close + 1; }
40
+ if (text[pos] === '/') return tokenEnd(pos + 1);
41
+ const first = tokenEnd(pos); if (first === pos) fail();
42
+ const reference = /^\d+\s+\d+\s+R\b/.exec(text.slice(pos)); return reference ? pos + reference[0].length : first;
43
+ };
44
+ const dictionary = pos => {
45
+ pos = skip(pos); if (!text.startsWith('<<', pos)) fail();
46
+ const start = pos, fields = new Map(); pos += 2;
47
+ for (;;) {
48
+ pos = skip(pos); if (text.startsWith('>>', pos)) return {start, end: pos + 2, fields};
49
+ if (text[pos] !== '/') fail();
50
+ const keyStart = pos, keyEnd = tokenEnd(pos + 1), name = text.slice(pos + 1, keyEnd);
51
+ if (fields.has(name)) fail();
52
+ const from = skip(keyEnd), end = valueEnd(from);
53
+ fields.set(name, {start: keyStart, from, end, value: text.slice(from, end)}); pos = end;
54
+ }
55
+ };
56
+ const trailer = dictionary(at), rootRef = /^(\d+)\s+(\d+)\s+R$/.exec(trailer.fields.get('Root')?.value || '');
57
+ const size = Number(trailer.fields.get('Size')?.value), rootId = Number(rootRef?.[1]), generation = Number(rootRef?.[2]);
58
+ let highest = 0; for (const id of offsets.keys()) highest = Math.max(highest, id);
59
+ if (!rootRef || !Number.isSafeInteger(size) || size <= highest || trailer.fields.has('Encrypt') || trailer.fields.has('Prev')) fail();
60
+ const readDictionary = (id, gen) => {
61
+ const place = offsets.get(id); if (!place || place.generation !== gen) fail();
62
+ const header = new RegExp('^' + id + '\\s+' + gen + '\\s+obj\\b').exec(text.slice(place.at)); if (!header) fail();
63
+ return dictionary(place.at + header[0].length);
64
+ };
65
+ const root = readDictionary(rootId, generation);
66
+ return {bytes, text, offsets, root, rootId, generation, trailer, size, xref, readDictionary, dictionary, fail};
67
+ }
68
+
69
+ export function readPdfPageCount(input) {
70
+ const {root, readDictionary, fail} = pdfStructure(input);
71
+ const reference = /^(\d+)\s+(\d+)\s+R$/.exec(root.fields.get('Pages')?.value || '');
72
+ if (!reference) fail();
73
+ const pages = readDictionary(Number(reference[1]), Number(reference[2])), count = Number(pages.fields.get('Count')?.value);
74
+ if (pages.fields.get('Type')?.value !== '/Pages' || !Number.isSafeInteger(count) || count < 1) fail();
75
+ return count;
76
+ }
77
+
78
+ export async function attachPdfSource(input, {source, filename, maxBytes = Infinity, check = () => {}}) {
79
+ const {bytes, text, offsets, root, rootId, generation, trailer, size, readDictionary, dictionary, fail} = pdfStructure(input);
80
+ if (typeof source !== 'string' || typeof filename !== 'string' || !filename) fail();
81
+ const ascii = value => Uint8Array.from(value, character => character.charCodeAt(0));
82
+ const encode = new TextEncoder(), hex = value => Array.from({length: value.length}, (_, i) => value.charCodeAt(i).toString(16).padStart(4, '0')).join('');
83
+ if (root.fields.get('Type')?.value !== '/Catalog' || root.fields.has('AF')) fail();
84
+ let names = '<<', remove = root.fields.get('Names');
85
+ if (remove) {
86
+ const ref = /^(\d+)\s+(\d+)\s+R$/.exec(remove.value);
87
+ const held = ref ? readDictionary(Number(ref[1]), Number(ref[2])) : dictionary(remove.from);
88
+ if (held.fields.has('EmbeddedFiles')) fail();
89
+ names = text.slice(held.start, held.end - 2);
90
+ }
91
+ const original = remove ? text.slice(root.start, remove.start) + text.slice(remove.end, root.end - 2) : text.slice(root.start, root.end - 2);
92
+ const payload = encode.encode(source); check();
93
+ const compressed = new Uint8Array(await new Response(new Blob([payload]).stream().pipeThrough(new CompressionStream('deflate'))).arrayBuffer()); check();
94
+ const fileId = size, streamId = size + 1, infoId = size + 2, totalSize = size + 3;
95
+ let infoBody = '<< /Producer (Rapier) /Creator (Rapier) >>';
96
+ const existingInfo = trailer.fields.get('Info');
97
+ if (existingInfo) {
98
+ const ref = /^(\d+)\s+(\d+)\s+R$/.exec(existingInfo.value);
99
+ const held = ref ? readDictionary(Number(ref[1]), Number(ref[2])) : dictionary(existingInfo.from);
100
+ const named = ['Producer', 'Creator'].map(key => held.fields.get(key)).filter(Boolean).sort((a, b) => a.start - b.start);
101
+ let kept = '', at = held.start;
102
+ for (const field of named) { kept += text.slice(at, field.start); at = field.end; }
103
+ infoBody = kept + text.slice(at, held.end - 2) + ' /Producer (Rapier) /Creator (Rapier) >>';
104
+ }
105
+ const escaped = '(' + filename.replace(/[^\x20-\x7e]/g, '_').replace(/[\\()]/g, '\\$&') + ')';
106
+ const parts = [bytes, ascii('\n')], merged = new Map(offsets);
107
+ let length = bytes.length + 1;
108
+ const append = (id, gen, chunks) => { merged.set(id, {at: length, generation: gen}); const body = [ascii(id + ' ' + gen + ' obj\n'), ...chunks, ascii('\nendobj\n')]; for (const chunk of body) {parts.push(chunk); length += chunk.length;} };
109
+ append(rootId, generation, [ascii(original + ' /Names ' + names + ' /EmbeddedFiles << /Names [<feff' + hex(filename) + '> ' + fileId + ' 0 R] >> >> /AF [' + fileId + ' 0 R] >>')]);
110
+ append(fileId, 0, [ascii('<< /Type /Filespec /F ' + escaped + ' /UF <feff' + hex(filename) + '> /AFRelationship /Source /EF << /F ' + streamId + ' 0 R /UF ' + streamId + ' 0 R >> >>')]);
111
+ append(streamId, 0, [ascii('<< /Type /EmbeddedFile /Subtype /text#2Fmarkdown /Params << /Size ' + payload.length + ' >> /Filter /FlateDecode /Length ' + compressed.length + ' >>\nstream\n'), compressed, ascii('\nendstream')]);
112
+ append(infoId, 0, [ascii(infoBody)]);
113
+ let table = 'xref\n0 ' + totalSize + '\n0000000000 65535 f \n';
114
+ for (let id = 1; id < totalSize; id++) {
115
+ const place = merged.get(id);
116
+ table += place ? String(place.at).padStart(10, '0') + ' ' + String(place.generation).padStart(5, '0') + ' n \n' : '0000000000 00000 f \n';
117
+ }
118
+ const identity = trailer.fields.get('ID');
119
+ table += 'trailer\n<< /Size ' + totalSize + ' /Root ' + rootId + ' ' + generation + ' R /Info ' + infoId + ' 0 R' + (identity ? ' /ID ' + identity.value : '') + ' >>\nstartxref\n' + length + '\n%%EOF\n';
120
+ parts.push(ascii(table)); length += table.length;
121
+ if (length > maxBytes) throw Object.assign(new Error('The exported file exceeds the file limit.'), {code: 'export_too_large'});
122
+ const result = new Uint8Array(length); let at = 0;
123
+ for (const chunk of parts) { result.set(chunk, at); at += chunk.length; }
124
+ check(); return result;
125
+ }
@@ -1,5 +1,4 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // SVG keeps its geometry in PDF; only paint operations that need browser compositing are rasterized.
3
2
  const SVG = 'http://www.w3.org/2000/svg';
4
3
  const number = value => Number(Number(value).toFixed(6));
5
4
  const nums = values => values.map(number).join(' ');
@@ -132,7 +131,6 @@ export async function collectPdfVectors(main, {width, height, count, capture, si
132
131
  root = document.importNode(parsed.documentElement, true);
133
132
  root.style.setProperty('width', originalBox.width + 'px', 'important'); root.style.setProperty('height', originalBox.height + 'px', 'important');
134
133
  mount = document.createElement('div'); mount.style.cssText = `position:fixed;left:${originalBox.left}px;top:${originalBox.top}px;width:${originalBox.width}px;height:${originalBox.height}px;pointer-events:none`;
135
- // A shadow root keeps the picture's own <style> on the picture, as an <img> does.
136
134
  mount.attachShadow({mode: 'open'}).append(root); document.body.append(mount); temporary.push(mount); await document.fonts.ready;
137
135
  textRoots.push(root);
138
136
  }
@@ -177,7 +175,6 @@ export async function collectPdfVectors(main, {width, height, count, capture, si
177
175
  let path, fill, stroke;
178
176
  try {
179
177
  path = geometry(node); fill = color(style.fill);
180
- // SVG zero-width strokes are absent; PDF's zero width instead paints a hairline.
181
178
  stroke = Number.parseFloat(style.strokeWidth) === 0 ? null : color(style.stroke);
182
179
  if (style.vectorEffect !== 'none' || style.markerStart !== 'none' || style.markerMid !== 'none' || style.markerEnd !== 'none' || style.paintOrder !== 'normal') unsupported();
183
180
  } catch (_) { sequence.push({raster: node}); return; }
@@ -9,8 +9,6 @@ export function followingPrintProse(paragraph) {
9
9
  return next;
10
10
  }
11
11
 
12
- // The print compositor owns pagination. Only a normal-flow picture initial moves into
13
- // its adjacent prose, so a native float can remain with the lines it introduces.
14
12
  export function markPrintCapitals(root) {
15
13
  for (const image of root.querySelectorAll('img[data-rapier-image-layout]')) {
16
14
  const layout = parseLayoutAttribute(image.getAttribute('data-rapier-image-layout'));
@@ -23,7 +21,6 @@ export function markPrintCapitals(root) {
23
21
  }
24
22
  }
25
23
 
26
- // This factory is also embedded in the offline page, with the same line metrics owner.
27
24
  export function createPrintCapitals(lineMetrics, linesHeight, followingProse) {
28
25
  const selector = 'img[data-rapier-print-lines]', states = new WeakMap();
29
26
  const restoreStyle = (node, value) => value == null ? node.removeAttribute('style') : node.setAttribute('style', value);
@@ -55,7 +52,6 @@ export function createPrintCapitals(lineMetrics, linesHeight, followingProse) {
55
52
  const availableWidth = owner.getBoundingClientRect().width -
56
53
  ['paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'].reduce((sum, name) => sum + (parseFloat(style[name]) || 0), 0);
57
54
  if (!(availableWidth > 0)) continue;
58
- // As imageBox does, cap the width first and retain the picture's natural ratio.
59
55
  const width = Math.min(availableWidth, height * naturalWidth / naturalHeight);
60
56
  const occurrence = state?.occurrence || (image.parentElement.tagName === 'A' ? image.parentElement : image);
61
57
  if (!state) {
@@ -1,5 +1,4 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // The public renderer door; each factory binds explicit host ports once per document realm.
3
2
  export {createRenderer} from './render.mjs';
4
3
  export {createMarkdownRenderer} from './render-markdown.mjs';
5
4
  export {createRenderStyles} from './render-styles.mjs';
@@ -1,5 +1,4 @@
1
1
  // SPDX-License-Identifier: MIT
2
- // Published typography is measured once; an annotation keeps its words and paths in one inert, scalable group.
3
2
  import {check, pause} from './render-work.mjs';
4
3
  import {apply as applyTypography} from '../layout/typography.mjs';
5
4
  import {documentLanguage} from '../spec/document-settings.mjs';
@@ -75,7 +74,6 @@ export async function freezePublishedInk(root, {document: doc, css, fontCss = ''
75
74
  group.style.cssText = 'display:flow-root;position:relative;width:100%;margin:0;padding:0;border:0;overflow:visible';
76
75
  main.insertBefore(group, captured.nodes[first]);
77
76
  for (let index = first; index <= last; index++) group.appendChild(captured.nodes[index]);
78
- // A fixed group cannot grow after disclosure. Show each marked passage in full.
79
77
  for (const span of group.querySelectorAll(MARK)) {
80
78
  for (let parent = span.parentElement; parent && parent !== group; parent = parent.parentElement)
81
79
  if (parent.localName === 'details') parent.open = true;
@@ -88,7 +86,6 @@ export async function freezePublishedInk(root, {document: doc, css, fontCss = ''
88
86
  try { await image.decode(); }
89
87
  catch (_) { throw failure('an embedded picture could not be decoded.'); }
90
88
  })));
91
- // The captured CSS supplies the faces; validate them before measuring typography or ink.
92
89
  main.getBoundingClientRect();
93
90
  await wait(waitForFonts(main, {settings, css, fontCss, install: false}));
94
91
  if (paper.fonts?.ready) await wait(paper.fonts.ready);
@@ -114,8 +111,6 @@ export async function freezePublishedInk(root, {document: doc, css, fontCss = ''
114
111
  results.push(svg);
115
112
  await pause(work);
116
113
  }
117
- // Keep the measured typography outside annotated groups too. The caller's detached
118
- // projection changes only after every resource and group has succeeded.
119
114
  for (const [index, group] of prepared.entries()) group.replaceWith(results[index]);
120
115
  const projected = doc.createDocumentFragment();
121
116
  for (const node of main.childNodes) {