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.
- package/README.md +32 -30
- package/dist/agent/will.mjs +1 -6
- package/dist/images/gif.mjs +0 -3
- package/dist/images/playback.mjs +1 -10
- package/dist/kit/assets.mjs +0 -1
- package/dist/kit/conformance/measurer.mjs +0 -0
- package/dist/kit/conformance/run.mjs +1 -8
- package/dist/kit/export-fonts.mjs +0 -14
- package/dist/kit/index.mjs +1 -1
- package/dist/kit/ledger/authored-origin.mjs +0 -5
- package/dist/kit/ledger/authorship.mjs +0 -2
- package/dist/kit/ledger/carried.d.mts +20 -0
- package/dist/kit/ledger/carried.mjs +4 -7
- package/dist/kit/ledger/data.mjs +0 -8
- package/dist/kit/ledger/format.mjs +0 -25
- package/dist/kit/ledger/hash.mjs +11 -7
- package/dist/kit/ledger/history.mjs +10 -29
- package/dist/kit/ledger/inverse-source.mjs +0 -46
- package/dist/kit/ledger/journal-records.mjs +24 -56
- package/dist/kit/ledger/merge.mjs +0 -27
- package/dist/kit/ledger/text-merge.mjs +0 -20
- package/dist/kit/ledger/transport.mjs +0 -47
- package/dist/kit/ledger.d.mts +88 -0
- package/dist/kit/ledger.mjs +14 -0
- package/dist/kit/markdown-it.d.mts +4 -0
- package/dist/kit/markdown-it.mjs +36 -0
- package/dist/kit/pdf-glyphs.mjs +39 -45
- package/dist/kit/pdf-source.d.mts +5 -0
- package/dist/kit/pdf-source.mjs +125 -0
- package/dist/kit/pdf-vectors.mjs +0 -3
- package/dist/kit/print-capitals.mjs +0 -4
- package/dist/kit/render-entry.mjs +0 -1
- package/dist/kit/render-ink.mjs +0 -5
- package/dist/kit/render-markdown.mjs +15 -119
- package/dist/kit/render-print.mjs +4 -49
- package/dist/kit/render-sanitize.mjs +22 -58
- package/dist/kit/render-styles.mjs +0 -7
- package/dist/kit/render-work.mjs +0 -12
- package/dist/kit/render.mjs +18 -173
- package/dist/kit/will.d.mts +42 -0
- package/dist/layout/hyphenation.mjs +0 -23
- package/dist/layout/line-plan.mjs +0 -20
- package/dist/layout/model.mjs +0 -55
- package/dist/layout/paragraph-fit.mjs +50 -60
- package/dist/layout/preparation.mjs +23 -56
- package/dist/layout/prepared-paragraph.mjs +0 -25
- package/dist/layout/typography.mjs +103 -41
- package/dist/packages/rapier-diff/concerns.mjs +0 -16
- package/dist/packages/rapier-diff/diff.mjs +0 -7
- package/dist/packages/rapier-diff/index.mjs +0 -1
- package/dist/packages/rapier-diff/matching.mjs +0 -19
- package/dist/packages/rapier-diff/merge.mjs +0 -9
- package/dist/packages/rapier-diff/moves.mjs +0 -7
- package/dist/packages/rapier-diff/structure.mjs +0 -2
- package/dist/packages/rapier-diff/tokens.mjs +0 -6
- package/dist/packages/rapier-diff/walk.mjs +0 -12
- package/dist/spec/document-fonts.mjs +0 -9
- package/dist/spec/document-settings.mjs +6 -25
- package/dist/spec/frontmatter.mjs +0 -1
- package/dist/spec/html-blocks.mjs +3 -37
- package/dist/spec/md-assets.mjs +2 -61
- package/dist/spec/md-layout.mjs +0 -34
- package/dist/spec/md-marks.mjs +0 -51
- package/dist/spec/md-source.mjs +0 -16
- package/dist/spec/word-sidecar.mjs +0 -2
- package/package.json +21 -4
- package/style.css +38 -21
package/dist/kit/pdf-glyphs.mjs
CHANGED
|
@@ -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();
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
if (
|
|
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)
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
if (
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
+
}
|
package/dist/kit/pdf-vectors.mjs
CHANGED
|
@@ -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';
|
package/dist/kit/render-ink.mjs
CHANGED
|
@@ -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) {
|