@stll/folio-core 0.50.0 → 0.52.0
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/ai-edits/word-diff.js +134 -1
- package/dist/compare/content-alignment.js +220 -5
- package/dist/compare/content.d.ts +1 -0
- package/dist/compare/content.js +51 -20
- package/dist/compare/inline-provenance.js +18 -22
- package/dist/compare/section-boundary-properties.js +13 -0
- package/dist/docx/selectiveSave.d.ts +1 -1
- package/dist/docx/selectiveSave.js +16 -6
- package/dist/docx/structuralXmlPatch.d.ts +14 -0
- package/dist/docx/structuralXmlPatch.js +209 -0
- package/dist/generated/text_shaper.js +26 -0
- package/dist/generated/text_shaper_bg.wasm +0 -0
- package/dist/managers/editorShortcuts.d.ts +16 -3
- package/dist/managers/editorShortcuts.js +6 -4
- package/dist/prosemirror/extensions/StarterKit.d.ts +3 -0
- package/dist/prosemirror/extensions/StarterKit.js +2 -1
- package/dist/prosemirror/extensions/core/HistoryExtension.d.ts +9 -1
- package/dist/prosemirror/extensions/core/HistoryExtension.js +4 -3
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +12 -5
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +91 -33
- package/dist/prosemirror/runStyleFormatting.d.ts +7 -1
- package/dist/prosemirror/runStyleFormatting.js +13 -7
- package/dist/shaping/shaper.d.ts +47 -4
- package/dist/shaping/shaper.js +46 -15
- package/dist/text-shaping.d.ts +4 -0
- package/dist/text-shaping.js +4 -0
- package/package.json +6 -1
|
@@ -18,9 +18,20 @@ const isTrackedSectionEndpointRemovalMeta = (value) => typeof value === "object"
|
|
|
18
18
|
function countDocumentStructure(doc) {
|
|
19
19
|
let paragraphs = 0;
|
|
20
20
|
let sectionEndpoints = 0;
|
|
21
|
+
const blockRecords = [];
|
|
21
22
|
const endpointRecords = [];
|
|
22
23
|
const visit = (parent, parentPath) => {
|
|
23
24
|
parent.forEach((node, _offset, index) => {
|
|
25
|
+
const path = parentPath.length === 0 ? `${index}` : `${parentPath}.${index}`;
|
|
26
|
+
blockRecords.push(node.type.name === "paragraph" ? [
|
|
27
|
+
path,
|
|
28
|
+
node.type.name,
|
|
29
|
+
node.attrs["paraId"] ?? null
|
|
30
|
+
] : [
|
|
31
|
+
path,
|
|
32
|
+
node.type.name,
|
|
33
|
+
canonicalJson(node.attrs)
|
|
34
|
+
]);
|
|
24
35
|
if (node.type.name === "paragraph") {
|
|
25
36
|
paragraphs++;
|
|
26
37
|
const sectionProperties = sectionPropertiesOf(node);
|
|
@@ -34,34 +45,36 @@ function countDocumentStructure(doc) {
|
|
|
34
45
|
}
|
|
35
46
|
return;
|
|
36
47
|
}
|
|
37
|
-
if (node.childCount > 0)
|
|
38
|
-
const path = parentPath.length === 0 ? `${index}` : `${parentPath}.${index}`;
|
|
39
|
-
visit(node, path);
|
|
40
|
-
}
|
|
48
|
+
if (node.childCount > 0) visit(node, path);
|
|
41
49
|
});
|
|
42
50
|
};
|
|
43
51
|
visit(doc, "");
|
|
44
52
|
return {
|
|
53
|
+
blockStructureFingerprint: JSON.stringify(blockRecords),
|
|
45
54
|
paragraphs,
|
|
46
55
|
sectionEndpoints,
|
|
47
56
|
sectionEndpointFingerprint: canonicalJson(endpointRecords)
|
|
48
57
|
};
|
|
49
58
|
}
|
|
59
|
+
const isUsableParaId = (value) => typeof value === "string" && value.length > 0 && value !== "00000000";
|
|
50
60
|
/**
|
|
51
61
|
* Collect paraIds of all paragraphs that overlap with the given range
|
|
52
62
|
*/
|
|
53
63
|
function collectAffectedParaIds(doc, from, to) {
|
|
54
64
|
const ids = /* @__PURE__ */ new Set();
|
|
65
|
+
const positions = /* @__PURE__ */ new Set();
|
|
55
66
|
let hasUntracked = false;
|
|
56
|
-
doc.nodesBetween(from, to, (node) => {
|
|
67
|
+
doc.nodesBetween(from, to, (node, pos) => {
|
|
57
68
|
if (node.type.name === "paragraph") {
|
|
69
|
+
positions.add(pos);
|
|
58
70
|
const paraId = node.attrs["paraId"];
|
|
59
|
-
if (paraId) ids.add(paraId);
|
|
71
|
+
if (isUsableParaId(paraId)) ids.add(paraId);
|
|
60
72
|
else hasUntracked = true;
|
|
61
73
|
}
|
|
62
74
|
});
|
|
63
75
|
return {
|
|
64
76
|
ids,
|
|
77
|
+
positions,
|
|
65
78
|
hasUntracked
|
|
66
79
|
};
|
|
67
80
|
}
|
|
@@ -75,28 +88,40 @@ function collectAffectedParaIdsFromMarkLikeStep(doc, from, to) {
|
|
|
75
88
|
const hi = Math.max(from, to);
|
|
76
89
|
const primary = collectAffectedParaIds(doc, lo, hi > lo ? hi : lo + 1);
|
|
77
90
|
if (primary.ids.size > 0 || primary.hasUntracked) return primary;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
ids: /* @__PURE__ */ new Set(),
|
|
90
|
-
hasUntracked: true
|
|
91
|
-
};
|
|
92
|
-
}
|
|
91
|
+
const $p = doc.resolve(lo);
|
|
92
|
+
for (let depth = $p.depth; depth > 0; depth--) {
|
|
93
|
+
const node = $p.node(depth);
|
|
94
|
+
if (node.type.name === "paragraph") {
|
|
95
|
+
const paraId = node.attrs["paraId"];
|
|
96
|
+
return {
|
|
97
|
+
ids: new Set(isUsableParaId(paraId) ? [paraId] : []),
|
|
98
|
+
positions: /* @__PURE__ */ new Set([$p.before(depth)]),
|
|
99
|
+
hasUntracked: !isUsableParaId(paraId)
|
|
100
|
+
};
|
|
93
101
|
}
|
|
94
|
-
}
|
|
102
|
+
}
|
|
95
103
|
return {
|
|
96
104
|
ids: /* @__PURE__ */ new Set(),
|
|
105
|
+
positions: /* @__PURE__ */ new Set(),
|
|
97
106
|
hasUntracked: false
|
|
98
107
|
};
|
|
99
108
|
}
|
|
109
|
+
/** Map an edited paragraph's interior so markup replacement retains its ownership. */
|
|
110
|
+
const mapParagraphPosition = (tr, position) => {
|
|
111
|
+
const mapped = tr.mapping.mapResult(position + 1, 1);
|
|
112
|
+
if (mapped.deletedAcross) return null;
|
|
113
|
+
const $position = tr.doc.resolve(mapped.pos);
|
|
114
|
+
for (let depth = $position.depth; depth > 0; depth--) if ($position.node(depth).type.name === "paragraph") return $position.before(depth);
|
|
115
|
+
return null;
|
|
116
|
+
};
|
|
117
|
+
const mapAffectedParagraphPositions = (tr, positions) => {
|
|
118
|
+
const mappedPositions = /* @__PURE__ */ new Set();
|
|
119
|
+
for (const position of positions) {
|
|
120
|
+
const mapped = mapParagraphPosition(tr, position);
|
|
121
|
+
if (mapped !== null) mappedPositions.add(mapped);
|
|
122
|
+
}
|
|
123
|
+
return mappedPositions;
|
|
124
|
+
};
|
|
100
125
|
function mapStepPosition(remap, pos, assoc) {
|
|
101
126
|
return remap.map(pos, assoc);
|
|
102
127
|
}
|
|
@@ -107,6 +132,9 @@ function createParagraphChangeTrackerPlugin() {
|
|
|
107
132
|
init(_config, state) {
|
|
108
133
|
const counts = countDocumentStructure(state.doc);
|
|
109
134
|
return {
|
|
135
|
+
affectedParagraphPositions: /* @__PURE__ */ new Set(),
|
|
136
|
+
hasUntrackedSourceChanges: false,
|
|
137
|
+
blockStructureFingerprint: counts.blockStructureFingerprint,
|
|
110
138
|
changedParaIds: /* @__PURE__ */ new Set(),
|
|
111
139
|
structuralChange: false,
|
|
112
140
|
hasUntrackedChanges: false,
|
|
@@ -123,6 +151,9 @@ function createParagraphChangeTrackerPlugin() {
|
|
|
123
151
|
if (meta === CLEAR_META) {
|
|
124
152
|
const counts = countDocumentStructure(tr.doc);
|
|
125
153
|
return {
|
|
154
|
+
affectedParagraphPositions: /* @__PURE__ */ new Set(),
|
|
155
|
+
hasUntrackedSourceChanges: false,
|
|
156
|
+
blockStructureFingerprint: counts.blockStructureFingerprint,
|
|
126
157
|
changedParaIds: /* @__PURE__ */ new Set(),
|
|
127
158
|
structuralChange: false,
|
|
128
159
|
hasUntrackedChanges: false,
|
|
@@ -133,13 +164,22 @@ function createParagraphChangeTrackerPlugin() {
|
|
|
133
164
|
};
|
|
134
165
|
}
|
|
135
166
|
if (meta === IGNORE_META) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
167
|
+
if (!tr.docChanged) return prevState;
|
|
168
|
+
const counts = countDocumentStructure(tr.doc);
|
|
169
|
+
const affectedParagraphPositions = mapAffectedParagraphPositions(tr, prevState.affectedParagraphPositions);
|
|
170
|
+
const changedParaIds = /* @__PURE__ */ new Set();
|
|
171
|
+
let unresolvedChanges = prevState.hasUntrackedSourceChanges;
|
|
172
|
+
for (const position of affectedParagraphPositions) {
|
|
173
|
+
const paraId = tr.doc.nodeAt(position)?.attrs["paraId"];
|
|
174
|
+
if (isUsableParaId(paraId)) changedParaIds.add(paraId);
|
|
175
|
+
else unresolvedChanges = true;
|
|
176
|
+
}
|
|
141
177
|
return {
|
|
142
178
|
...prevState,
|
|
179
|
+
affectedParagraphPositions,
|
|
180
|
+
changedParaIds,
|
|
181
|
+
hasUntrackedChanges: unresolvedChanges,
|
|
182
|
+
blockStructureFingerprint: counts.blockStructureFingerprint,
|
|
143
183
|
paragraphCount: counts.paragraphs,
|
|
144
184
|
sectionEndpointCount: counts.sectionEndpoints,
|
|
145
185
|
sectionEndpointRemoval: counts.sectionEndpointFingerprint === prevState.sectionEndpointFingerprint ? prevState.sectionEndpointRemoval : null,
|
|
@@ -162,6 +202,9 @@ function createParagraphChangeTrackerPlugin() {
|
|
|
162
202
|
};
|
|
163
203
|
} else sectionEndpointRemoval = null;
|
|
164
204
|
const newState = {
|
|
205
|
+
affectedParagraphPositions: mapAffectedParagraphPositions(tr, prevState.affectedParagraphPositions),
|
|
206
|
+
hasUntrackedSourceChanges: prevState.hasUntrackedSourceChanges,
|
|
207
|
+
blockStructureFingerprint: counts.blockStructureFingerprint,
|
|
165
208
|
changedParaIds: new Set(prevState.changedParaIds),
|
|
166
209
|
structuralChange: prevState.structuralChange || meta === STRUCTURAL_META,
|
|
167
210
|
hasUntrackedChanges: prevState.hasUntrackedChanges,
|
|
@@ -176,12 +219,13 @@ function createParagraphChangeTrackerPlugin() {
|
|
|
176
219
|
const from = mapStepPosition(remap, range.from, 1);
|
|
177
220
|
const to = mapStepPosition(remap, range.to, -1);
|
|
178
221
|
if (to <= from) continue;
|
|
179
|
-
const { ids, hasUntracked } = collectAffectedParaIdsFromMarkLikeStep(tr.doc, from, to);
|
|
222
|
+
const { ids, positions, hasUntracked } = collectAffectedParaIdsFromMarkLikeStep(tr.doc, from, to);
|
|
223
|
+
for (const position of positions) newState.affectedParagraphPositions.add(position);
|
|
180
224
|
for (const id of ids) newState.changedParaIds.add(id);
|
|
181
225
|
if (hasUntracked) newState.hasUntrackedChanges = true;
|
|
182
226
|
}
|
|
183
227
|
}
|
|
184
|
-
if (prevState.
|
|
228
|
+
if (prevState.blockStructureFingerprint !== counts.blockStructureFingerprint) newState.structuralChange = true;
|
|
185
229
|
for (let stepIndex = 0; stepIndex < tr.steps.length; stepIndex++) {
|
|
186
230
|
const step = tr.steps[stepIndex];
|
|
187
231
|
const remap = tr.mapping.slice(stepIndex + 1);
|
|
@@ -189,7 +233,8 @@ function createParagraphChangeTrackerPlugin() {
|
|
|
189
233
|
const from = mapStepPosition(remap, step.from, 1);
|
|
190
234
|
const to = mapStepPosition(remap, step.to, -1);
|
|
191
235
|
if (to <= from) continue;
|
|
192
|
-
const { ids, hasUntracked } = collectAffectedParaIdsFromMarkLikeStep(tr.doc, from, to);
|
|
236
|
+
const { ids, positions, hasUntracked } = collectAffectedParaIdsFromMarkLikeStep(tr.doc, from, to);
|
|
237
|
+
for (const position of positions) newState.affectedParagraphPositions.add(position);
|
|
193
238
|
for (const id of ids) newState.changedParaIds.add(id);
|
|
194
239
|
if (hasUntracked) newState.hasUntrackedChanges = true;
|
|
195
240
|
continue;
|
|
@@ -199,7 +244,8 @@ function createParagraphChangeTrackerPlugin() {
|
|
|
199
244
|
const node = tr.doc.nodeAt(pos);
|
|
200
245
|
if (!node) continue;
|
|
201
246
|
const end = pos + node.nodeSize;
|
|
202
|
-
const { ids, hasUntracked } = collectAffectedParaIds(tr.doc, pos, end);
|
|
247
|
+
const { ids, positions, hasUntracked } = collectAffectedParaIds(tr.doc, pos, end);
|
|
248
|
+
for (const position of positions) newState.affectedParagraphPositions.add(position);
|
|
203
249
|
for (const id of ids) newState.changedParaIds.add(id);
|
|
204
250
|
if (hasUntracked) newState.hasUntrackedChanges = true;
|
|
205
251
|
continue;
|
|
@@ -208,11 +254,23 @@ function createParagraphChangeTrackerPlugin() {
|
|
|
208
254
|
const from = mapStepPosition(remap, newStart, 1);
|
|
209
255
|
const to = mapStepPosition(remap, newEnd, -1);
|
|
210
256
|
if (to < from) return;
|
|
211
|
-
const { ids, hasUntracked } = collectAffectedParaIds(tr.doc, from, to);
|
|
257
|
+
const { ids, positions, hasUntracked } = collectAffectedParaIds(tr.doc, from, to);
|
|
258
|
+
for (const position of positions) newState.affectedParagraphPositions.add(position);
|
|
212
259
|
for (const id of ids) newState.changedParaIds.add(id);
|
|
213
260
|
if (hasUntracked) newState.hasUntrackedChanges = true;
|
|
214
261
|
});
|
|
215
262
|
}
|
|
263
|
+
if (newState.hasUntrackedChanges || prevState.blockStructureFingerprint !== counts.blockStructureFingerprint) tr.before.descendants((node, position) => {
|
|
264
|
+
if (node.type.name !== "paragraph") return true;
|
|
265
|
+
if (!isUsableParaId(node.attrs["paraId"])) {
|
|
266
|
+
const mapped = mapParagraphPosition(tr, position);
|
|
267
|
+
if (mapped === null || newState.affectedParagraphPositions.has(mapped)) {
|
|
268
|
+
newState.hasUntrackedSourceChanges = true;
|
|
269
|
+
newState.hasUntrackedChanges = true;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return false;
|
|
273
|
+
});
|
|
216
274
|
return newState;
|
|
217
275
|
}
|
|
218
276
|
}
|
|
@@ -231,7 +289,7 @@ function getChangedParagraphIds(state) {
|
|
|
231
289
|
return getChangeTrackerState(state)?.changedParaIds ?? /* @__PURE__ */ new Set();
|
|
232
290
|
}
|
|
233
291
|
/**
|
|
234
|
-
* Check if
|
|
292
|
+
* Check if paragraph membership, order, or block structure changed
|
|
235
293
|
*/
|
|
236
294
|
function hasStructuralChanges(state) {
|
|
237
295
|
return getChangeTrackerState(state)?.structuralChange ?? false;
|
|
@@ -52,6 +52,12 @@ type ParagraphRunStyleContextAtOptions = {
|
|
|
52
52
|
styleResolver?: RunStyleResolver | null;
|
|
53
53
|
};
|
|
54
54
|
declare const paragraphRunStyleContextAt: ({ doc, pos, styleResolver }: ParagraphRunStyleContextAtOptions) => ParagraphRunStyleContext;
|
|
55
|
+
/**
|
|
56
|
+
* {@link paragraphRunStyleContextAt} for a caller that already knows the
|
|
57
|
+
* nearest paragraph, or that there is none. A document walk knows it for free,
|
|
58
|
+
* where `doc.resolve` re-descends from the root once per run.
|
|
59
|
+
*/
|
|
60
|
+
declare const nearestParagraphRunStyleContext: (paragraph: Node | null, styleResolver?: RunStyleResolver | null) => ParagraphRunStyleContext;
|
|
55
61
|
type ResolveEffectiveRunStyleFormattingOptions = {
|
|
56
62
|
marks: readonly Mark[];
|
|
57
63
|
paragraphFormatting: document_d_exports.TextFormatting | undefined;
|
|
@@ -60,4 +66,4 @@ type ResolveEffectiveRunStyleFormattingOptions = {
|
|
|
60
66
|
/** Resolve the non-direct run cascade from shared document styles and the current paragraph. */
|
|
61
67
|
declare const resolveEffectiveRunStyleFormatting: ({ marks, paragraphFormatting, styleResolver }: ResolveEffectiveRunStyleFormattingOptions) => document_d_exports.TextFormatting | undefined;
|
|
62
68
|
//#endregion
|
|
63
|
-
export { ParagraphRunStyleContext, RunStyleResolver, getParagraphMarkSuppressionOverrides, hasDirectRunFormatting, paragraphFormattingForRun, paragraphRunStyleContext, paragraphRunStyleContextAt, resolveEffectiveRunStyleFormatting, resolveParagraphBodyRunFormatting, stripParagraphMarkFormattingForBodyRuns, stripParagraphMarkOnlyFormatting, suppressParagraphMarkFormatting };
|
|
69
|
+
export { ParagraphRunStyleContext, RunStyleResolver, getParagraphMarkSuppressionOverrides, hasDirectRunFormatting, nearestParagraphRunStyleContext, paragraphFormattingForRun, paragraphRunStyleContext, paragraphRunStyleContextAt, resolveEffectiveRunStyleFormatting, resolveParagraphBodyRunFormatting, stripParagraphMarkFormattingForBodyRuns, stripParagraphMarkOnlyFormatting, suppressParagraphMarkFormatting };
|
|
@@ -153,12 +153,18 @@ const paragraphRunStyleContextAt = ({ doc, pos, styleResolver }) => {
|
|
|
153
153
|
const ancestor = resolved.node(depth);
|
|
154
154
|
if (ancestor.type.name === "paragraph") return paragraphRunStyleContext(ancestor, styleResolver);
|
|
155
155
|
}
|
|
156
|
-
return
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
156
|
+
return nearestParagraphRunStyleContext(null, styleResolver);
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* {@link paragraphRunStyleContextAt} for a caller that already knows the
|
|
160
|
+
* nearest paragraph, or that there is none. A document walk knows it for free,
|
|
161
|
+
* where `doc.resolve` re-descends from the root once per run.
|
|
162
|
+
*/
|
|
163
|
+
const nearestParagraphRunStyleContext = (paragraph, styleResolver) => paragraph ? paragraphRunStyleContext(paragraph, styleResolver) : {
|
|
164
|
+
baseParagraphFormatting: void 0,
|
|
165
|
+
paragraphFormatting: void 0,
|
|
166
|
+
paragraphMarkFormatting: void 0,
|
|
167
|
+
paragraphMarkPrecedesStyle: false
|
|
162
168
|
};
|
|
163
169
|
/** Resolve the non-direct run cascade from shared document styles and the current paragraph. */
|
|
164
170
|
const resolveEffectiveRunStyleFormatting = ({ marks, paragraphFormatting, styleResolver }) => {
|
|
@@ -176,4 +182,4 @@ const resolveEffectiveRunStyleFormatting = ({ marks, paragraphFormatting, styleR
|
|
|
176
182
|
});
|
|
177
183
|
};
|
|
178
184
|
//#endregion
|
|
179
|
-
export { getParagraphMarkSuppressionOverrides, hasDirectRunFormatting, paragraphFormattingForRun, paragraphRunStyleContext, paragraphRunStyleContextAt, resolveEffectiveRunStyleFormatting, resolveParagraphBodyRunFormatting, stripParagraphMarkFormattingForBodyRuns, stripParagraphMarkOnlyFormatting, suppressParagraphMarkFormatting };
|
|
185
|
+
export { getParagraphMarkSuppressionOverrides, hasDirectRunFormatting, nearestParagraphRunStyleContext, paragraphFormattingForRun, paragraphRunStyleContext, paragraphRunStyleContextAt, resolveEffectiveRunStyleFormatting, resolveParagraphBodyRunFormatting, stripParagraphMarkFormattingForBodyRuns, stripParagraphMarkOnlyFormatting, suppressParagraphMarkFormatting };
|
package/dist/shaping/shaper.d.ts
CHANGED
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
* the Unicode data that engine needs, and a document in Latin, Cyrillic or
|
|
15
15
|
* Greek needs none of it: `getShaper` is called only once a run in a shaping
|
|
16
16
|
* script is about to be measured or painted, and never before.
|
|
17
|
+
*
|
|
18
|
+
* The same artifact resolves the bidirectional algorithm, because splitting a
|
|
19
|
+
* line into runs that each read one way is what shaping needs first.
|
|
17
20
|
*/
|
|
18
21
|
/** Which way a run advances. Resolved by the producer, never guessed here. */
|
|
19
22
|
declare const SHAPING_DIRECTION: {
|
|
@@ -57,22 +60,62 @@ type ShapedRun = {
|
|
|
57
60
|
/** Design units per em, so a caller scales without re-reading the face. */
|
|
58
61
|
readonly unitsPerEm: number;
|
|
59
62
|
};
|
|
63
|
+
/** The paragraph direction of a line whose bidirectional levels are resolved. */
|
|
64
|
+
declare const BIDI_DIRECTION: {
|
|
65
|
+
readonly leftToRight: "ltr";
|
|
66
|
+
readonly rightToLeft: "rtl";
|
|
67
|
+
/** Rules P2 and P3: the first strong character outside an isolate decides. */
|
|
68
|
+
readonly auto: "auto";
|
|
69
|
+
};
|
|
70
|
+
type BidiDirection = (typeof BIDI_DIRECTION)[keyof typeof BIDI_DIRECTION];
|
|
71
|
+
type ResolveBidiRequest = {
|
|
72
|
+
/** One paragraph laid out as one line; a paragraph separator does not split it. */
|
|
73
|
+
readonly text: string;
|
|
74
|
+
readonly direction: BidiDirection;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* One line after the Unicode Bidirectional Algorithm (UAX #9), rules P2
|
|
78
|
+
* through L2. Indices count code points, not UTF-16 units.
|
|
79
|
+
*/
|
|
80
|
+
type BidiLine = {
|
|
81
|
+
/** 0 when the paragraph reads left to right, 1 when right to left. */
|
|
82
|
+
readonly paragraphLevel: number;
|
|
83
|
+
/**
|
|
84
|
+
* Embedding level per code point, after rule L1; odd levels read right to
|
|
85
|
+
* left. Characters rule X9 removes (explicit formatting characters such as
|
|
86
|
+
* isolates) carry a level but draw nothing.
|
|
87
|
+
*/
|
|
88
|
+
readonly levels: readonly number[];
|
|
89
|
+
/** Code-point indices in visual order, left to right (rule L2). */
|
|
90
|
+
readonly visualOrder: readonly number[];
|
|
91
|
+
};
|
|
60
92
|
type Shaper = {
|
|
61
93
|
readonly shapeRun: (request: ShapeRunRequest) => ShapedRun;
|
|
94
|
+
readonly resolveBidi: (request: ResolveBidiRequest) => BidiLine;
|
|
62
95
|
};
|
|
63
96
|
declare const ShaperError_base: import("better-result").TaggedErrorClass<"ShaperError">;
|
|
64
97
|
declare class ShaperError extends ShaperError_base<{
|
|
65
98
|
message: string;
|
|
66
99
|
cause?: unknown;
|
|
67
100
|
}> {}
|
|
101
|
+
/**
|
|
102
|
+
* Where the artifact comes from when the caller already holds it: a runtime
|
|
103
|
+
* that cannot fetch its own package files (a single-file executable, an
|
|
104
|
+
* embedded asset) hands over the WebAssembly bytes instead.
|
|
105
|
+
*/
|
|
106
|
+
type ShaperSource = {
|
|
107
|
+
readonly wasm: Uint8Array | ArrayBuffer;
|
|
108
|
+
};
|
|
68
109
|
/**
|
|
69
110
|
* Loads the shaper, once, and hands back the single implementation both the
|
|
70
111
|
* measurer and the PDF backend use.
|
|
71
112
|
*
|
|
72
|
-
* Calling this is what fetches the artifact, so a caller asks
|
|
73
|
-
* establishing that some run actually needs shaping.
|
|
113
|
+
* Calling this without a source is what fetches the artifact, so a caller asks
|
|
114
|
+
* for it only after establishing that some run actually needs shaping. With a
|
|
115
|
+
* source, the bytes given are instantiated and nothing is fetched. Either way
|
|
116
|
+
* the first successful load is the one every later call shares.
|
|
74
117
|
*/
|
|
75
|
-
declare const getShaper: () => Promise<Shaper>;
|
|
118
|
+
declare const getShaper: (source?: ShaperSource) => Promise<Shaper>;
|
|
76
119
|
/**
|
|
77
120
|
* Whether the artifact has been loaded in this process.
|
|
78
121
|
*
|
|
@@ -89,4 +132,4 @@ declare const shaperLoaded: () => boolean;
|
|
|
89
132
|
*/
|
|
90
133
|
declare const resolvedShaper: () => Shaper | null;
|
|
91
134
|
//#endregion
|
|
92
|
-
export { SHAPING_DIRECTION, ShapeRunRequest, ShapedGlyph, ShapedRun, Shaper, ShaperError, ShapingDirection, getShaper, resolvedShaper, shaperLoaded };
|
|
135
|
+
export { BIDI_DIRECTION, BidiDirection, BidiLine, ResolveBidiRequest, SHAPING_DIRECTION, ShapeRunRequest, ShapedGlyph, ShapedRun, Shaper, ShaperError, ShaperSource, ShapingDirection, getShaper, resolvedShaper, shaperLoaded };
|
package/dist/shaping/shaper.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import __wbg_init, { shapeRun } from "../generated/text_shaper.js";
|
|
1
|
+
import __wbg_init, { resolveBidi, shapeRun } from "../generated/text_shaper.js";
|
|
2
2
|
import { TaggedError } from "better-result";
|
|
3
3
|
//#region src/shaping/shaper.ts
|
|
4
4
|
/**
|
|
@@ -16,12 +16,22 @@ import { TaggedError } from "better-result";
|
|
|
16
16
|
* the Unicode data that engine needs, and a document in Latin, Cyrillic or
|
|
17
17
|
* Greek needs none of it: `getShaper` is called only once a run in a shaping
|
|
18
18
|
* script is about to be measured or painted, and never before.
|
|
19
|
+
*
|
|
20
|
+
* The same artifact resolves the bidirectional algorithm, because splitting a
|
|
21
|
+
* line into runs that each read one way is what shaping needs first.
|
|
19
22
|
*/
|
|
20
23
|
/** Which way a run advances. Resolved by the producer, never guessed here. */
|
|
21
24
|
const SHAPING_DIRECTION = {
|
|
22
25
|
leftToRight: "ltr",
|
|
23
26
|
rightToLeft: "rtl"
|
|
24
27
|
};
|
|
28
|
+
/** The paragraph direction of a line whose bidirectional levels are resolved. */
|
|
29
|
+
const BIDI_DIRECTION = {
|
|
30
|
+
leftToRight: "ltr",
|
|
31
|
+
rightToLeft: "rtl",
|
|
32
|
+
/** Rules P2 and P3: the first strong character outside an isolate decides. */
|
|
33
|
+
auto: "auto"
|
|
34
|
+
};
|
|
25
35
|
var ShaperError = class extends TaggedError("ShaperError") {};
|
|
26
36
|
/**
|
|
27
37
|
* The flat buffer the WebAssembly entry returns:
|
|
@@ -51,26 +61,47 @@ const decode = (buffer) => {
|
|
|
51
61
|
unitsPerEm
|
|
52
62
|
};
|
|
53
63
|
};
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
/** `[paragraphLevel, count, level * count, visualIndex * count]`. */
|
|
65
|
+
const decodeBidi = (buffer) => {
|
|
66
|
+
const count = buffer[1] ?? 0;
|
|
67
|
+
const levelsAt = HEADER_FIELDS;
|
|
68
|
+
const orderAt = HEADER_FIELDS + count;
|
|
69
|
+
return {
|
|
70
|
+
paragraphLevel: buffer[0] ?? 0,
|
|
71
|
+
levels: Array.from(buffer.subarray(levelsAt, orderAt)),
|
|
72
|
+
visualOrder: Array.from(buffer.subarray(orderAt, orderAt + count))
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
const RIGHT_TO_LEFT_BY_BIDI_DIRECTION = {
|
|
76
|
+
ltr: false,
|
|
77
|
+
rtl: true,
|
|
78
|
+
auto: void 0
|
|
79
|
+
};
|
|
80
|
+
const shaper = {
|
|
81
|
+
resolveBidi: ({ text, direction }) => decodeBidi(resolveBidi(text, RIGHT_TO_LEFT_BY_BIDI_DIRECTION[direction])),
|
|
82
|
+
shapeRun: ({ font, faceIndex = 0, text, direction, script = "", language = "", featuresOn = [], featuresOff = [] }) => {
|
|
83
|
+
try {
|
|
84
|
+
return decode(shapeRun(font, faceIndex, text, direction === SHAPING_DIRECTION.rightToLeft, script, language, [...featuresOn], [...featuresOff]));
|
|
85
|
+
} catch (cause) {
|
|
86
|
+
throw new ShaperError({
|
|
87
|
+
message: "Could not shape a run of text",
|
|
88
|
+
cause
|
|
89
|
+
});
|
|
90
|
+
}
|
|
62
91
|
}
|
|
63
|
-
}
|
|
92
|
+
};
|
|
64
93
|
let loading;
|
|
65
94
|
/**
|
|
66
95
|
* Loads the shaper, once, and hands back the single implementation both the
|
|
67
96
|
* measurer and the PDF backend use.
|
|
68
97
|
*
|
|
69
|
-
* Calling this is what fetches the artifact, so a caller asks
|
|
70
|
-
* establishing that some run actually needs shaping.
|
|
98
|
+
* Calling this without a source is what fetches the artifact, so a caller asks
|
|
99
|
+
* for it only after establishing that some run actually needs shaping. With a
|
|
100
|
+
* source, the bytes given are instantiated and nothing is fetched. Either way
|
|
101
|
+
* the first successful load is the one every later call shares.
|
|
71
102
|
*/
|
|
72
|
-
const getShaper = () => {
|
|
73
|
-
loading ??= __wbg_init().then(() => {
|
|
103
|
+
const getShaper = (source) => {
|
|
104
|
+
loading ??= __wbg_init(source === void 0 ? void 0 : { module_or_path: source.wasm }).then(() => {
|
|
74
105
|
resolved = shaper;
|
|
75
106
|
return shaper;
|
|
76
107
|
}).catch((cause) => {
|
|
@@ -99,4 +130,4 @@ let resolved = null;
|
|
|
99
130
|
*/
|
|
100
131
|
const resolvedShaper = () => resolved;
|
|
101
132
|
//#endregion
|
|
102
|
-
export { SHAPING_DIRECTION, ShaperError, getShaper, resolvedShaper, shaperLoaded };
|
|
133
|
+
export { BIDI_DIRECTION, SHAPING_DIRECTION, ShaperError, getShaper, resolvedShaper, shaperLoaded };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { BIDI_DIRECTION, BidiLine, ResolveBidiRequest, SHAPING_DIRECTION, ShapeRunRequest, ShapedRun, Shaper, ShaperSource, getShaper } from "./shaping/shaper.js";
|
|
2
|
+
import { SfntFont, parseSfnt } from "./fonts/sfnt/parse.js";
|
|
3
|
+
import { TrueTypeSubset, subsetTrueType } from "./fonts/sfnt/subset.js";
|
|
4
|
+
export { BIDI_DIRECTION, type BidiLine, type ResolveBidiRequest, SHAPING_DIRECTION, type SfntFont, type ShapeRunRequest, type ShapedRun, type Shaper, type ShaperSource, type TrueTypeSubset, getShaper, parseSfnt, subsetTrueType };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { parseSfnt } from "./fonts/sfnt/parse.js";
|
|
2
|
+
import { subsetTrueType } from "./fonts/sfnt/subset.js";
|
|
3
|
+
import { BIDI_DIRECTION, SHAPING_DIRECTION, getShaper } from "./shaping/shaper.js";
|
|
4
|
+
export { BIDI_DIRECTION, SHAPING_DIRECTION, getShaper, parseSfnt, subsetTrueType };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/folio-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.52.0",
|
|
4
4
|
"description": "Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"document-model",
|
|
@@ -106,6 +106,11 @@
|
|
|
106
106
|
"types": "./dist/docx/documentClone.d.ts",
|
|
107
107
|
"import": "./dist/docx/documentClone.js"
|
|
108
108
|
},
|
|
109
|
+
"./text-shaping": {
|
|
110
|
+
"types": "./dist/text-shaping.d.ts",
|
|
111
|
+
"import": "./dist/text-shaping.js"
|
|
112
|
+
},
|
|
113
|
+
"./text-shaping/wasm": "./dist/generated/text_shaper_bg.wasm",
|
|
109
114
|
"./docx/paragraphPropertySource": null,
|
|
110
115
|
"./internal/*": null,
|
|
111
116
|
"./*": {
|