web-doc 0.6.2 → 0.7.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.
Files changed (188) hide show
  1. package/THIRD_PARTY_NOTICES.md +26 -1
  2. package/dist/adapters/docx-images.d.ts +13 -4
  3. package/dist/adapters/docx-images.js +3 -0
  4. package/dist/adapters/docx-paragraphs.d.ts +65 -0
  5. package/dist/adapters/docx-paragraphs.js +165 -0
  6. package/dist/adapters/docx-prepass.d.ts +17 -0
  7. package/dist/adapters/docx-prepass.js +230 -0
  8. package/dist/adapters/office.d.ts +31 -5
  9. package/dist/adapters/office.js +60 -29
  10. package/dist/adapters/pdf.d.ts +9 -0
  11. package/dist/adapters/pdf.js +10 -0
  12. package/dist/assets/pdfium/pdfium.wasm +0 -0
  13. package/dist/contracts.d.ts +47 -2
  14. package/dist/edit/assets.d.ts +23 -0
  15. package/dist/edit/assets.js +75 -0
  16. package/dist/edit/docx/elements.d.ts +6 -0
  17. package/dist/edit/docx/elements.js +90 -0
  18. package/dist/edit/docx/engine.d.ts +51 -0
  19. package/dist/edit/docx/engine.js +473 -0
  20. package/dist/edit/docx/handlers.d.ts +3 -0
  21. package/dist/edit/docx/handlers.js +15 -0
  22. package/dist/edit/docx/ids.d.ts +45 -0
  23. package/dist/edit/docx/ids.js +101 -0
  24. package/dist/edit/docx/model.d.ts +92 -0
  25. package/dist/edit/docx/model.js +339 -0
  26. package/dist/edit/docx/operations.d.ts +37 -0
  27. package/dist/edit/docx/operations.js +3 -0
  28. package/dist/edit/docx/provider.d.ts +13 -0
  29. package/dist/edit/docx/provider.js +34 -0
  30. package/dist/edit/docx/schemas.d.ts +6 -0
  31. package/dist/edit/docx/schemas.js +192 -0
  32. package/dist/edit/docx/session.d.ts +37 -0
  33. package/dist/edit/docx/session.js +426 -0
  34. package/dist/edit/docx/structure-ops.d.ts +6 -0
  35. package/dist/edit/docx/structure-ops.js +494 -0
  36. package/dist/edit/docx/style.d.ts +45 -0
  37. package/dist/edit/docx/style.js +375 -0
  38. package/dist/edit/docx/table-ops.d.ts +4 -0
  39. package/dist/edit/docx/table-ops.js +219 -0
  40. package/dist/edit/docx/text-ops.d.ts +18 -0
  41. package/dist/edit/docx/text-ops.js +458 -0
  42. package/dist/edit/docx/text.d.ts +34 -0
  43. package/dist/edit/docx/text.js +263 -0
  44. package/dist/edit/docx/types.d.ts +191 -0
  45. package/dist/edit/docx/types.js +1 -0
  46. package/dist/edit/docx/write.d.ts +50 -0
  47. package/dist/edit/docx/write.js +352 -0
  48. package/dist/edit/engine.d.ts +117 -0
  49. package/dist/edit/engine.js +1 -0
  50. package/dist/edit/history.d.ts +53 -0
  51. package/dist/edit/history.js +101 -0
  52. package/dist/edit/ooxml/names.d.ts +20 -0
  53. package/dist/edit/ooxml/names.js +61 -0
  54. package/dist/edit/ooxml/opc.d.ts +50 -0
  55. package/dist/edit/ooxml/opc.js +150 -0
  56. package/dist/edit/ooxml/package.d.ts +82 -0
  57. package/dist/edit/ooxml/package.js +233 -0
  58. package/dist/edit/ooxml/patch.d.ts +51 -0
  59. package/dist/edit/ooxml/patch.js +250 -0
  60. package/dist/edit/ooxml/transaction.d.ts +39 -0
  61. package/dist/edit/ooxml/transaction.js +316 -0
  62. package/dist/edit/ooxml/worker.d.ts +8 -0
  63. package/dist/edit/ooxml/worker.js +12 -0
  64. package/dist/edit/ooxml/writer.d.ts +21 -0
  65. package/dist/edit/ooxml/writer.js +187 -0
  66. package/dist/edit/ooxml/xml.d.ts +74 -0
  67. package/dist/edit/ooxml/xml.js +451 -0
  68. package/dist/edit/ooxml/zip.d.ts +54 -0
  69. package/dist/edit/ooxml/zip.js +280 -0
  70. package/dist/edit/operations.d.ts +19 -0
  71. package/dist/edit/operations.js +137 -0
  72. package/dist/edit/pdf/engine/compact.d.ts +6 -0
  73. package/dist/edit/pdf/engine/compact.js +442 -0
  74. package/dist/edit/pdf/engine/document.d.ts +95 -0
  75. package/dist/edit/pdf/engine/document.js +868 -0
  76. package/dist/edit/pdf/engine/elements.d.ts +45 -0
  77. package/dist/edit/pdf/engine/elements.js +313 -0
  78. package/dist/edit/pdf/engine/existing-text.d.ts +4 -0
  79. package/dist/edit/pdf/engine/existing-text.js +424 -0
  80. package/dist/edit/pdf/engine/fonts.d.ts +78 -0
  81. package/dist/edit/pdf/engine/fonts.js +466 -0
  82. package/dist/edit/pdf/engine/geometry.d.ts +36 -0
  83. package/dist/edit/pdf/engine/geometry.js +97 -0
  84. package/dist/edit/pdf/engine/handler.d.ts +21 -0
  85. package/dist/edit/pdf/engine/handler.js +108 -0
  86. package/dist/edit/pdf/engine/images.d.ts +33 -0
  87. package/dist/edit/pdf/engine/images.js +188 -0
  88. package/dist/edit/pdf/engine/layout.d.ts +23 -0
  89. package/dist/edit/pdf/engine/layout.js +177 -0
  90. package/dist/edit/pdf/engine/operations.d.ts +67 -0
  91. package/dist/edit/pdf/engine/operations.js +3 -0
  92. package/dist/edit/pdf/engine/pages.d.ts +6 -0
  93. package/dist/edit/pdf/engine/pages.js +98 -0
  94. package/dist/edit/pdf/engine/pdfium.d.ts +195 -0
  95. package/dist/edit/pdf/engine/pdfium.js +249 -0
  96. package/dist/edit/pdf/engine/shapes.d.ts +4 -0
  97. package/dist/edit/pdf/engine/shapes.js +183 -0
  98. package/dist/edit/pdf/engine/tables.d.ts +44 -0
  99. package/dist/edit/pdf/engine/tables.js +315 -0
  100. package/dist/edit/pdf/engine/text-box.d.ts +71 -0
  101. package/dist/edit/pdf/engine/text-box.js +317 -0
  102. package/dist/edit/pdf/engine/text-layout.d.ts +28 -0
  103. package/dist/edit/pdf/engine/text-layout.js +67 -0
  104. package/dist/edit/pdf/engine/transform.d.ts +5 -0
  105. package/dist/edit/pdf/engine/transform.js +137 -0
  106. package/dist/edit/pdf/provider.d.ts +35 -0
  107. package/dist/edit/pdf/provider.js +92 -0
  108. package/dist/edit/pdf/range-map.d.ts +14 -0
  109. package/dist/edit/pdf/range-map.js +107 -0
  110. package/dist/edit/pdf/schemas.d.ts +11 -0
  111. package/dist/edit/pdf/schemas.js +291 -0
  112. package/dist/edit/pdf/selection.d.ts +5 -0
  113. package/dist/edit/pdf/selection.js +152 -0
  114. package/dist/edit/pdf/session.d.ts +53 -0
  115. package/dist/edit/pdf/session.js +273 -0
  116. package/dist/edit/pdf/types.d.ts +336 -0
  117. package/dist/edit/pdf/types.js +1 -0
  118. package/dist/edit/pptx/elements.d.ts +74 -0
  119. package/dist/edit/pptx/elements.js +301 -0
  120. package/dist/edit/pptx/engine.d.ts +37 -0
  121. package/dist/edit/pptx/engine.js +466 -0
  122. package/dist/edit/pptx/geometry.d.ts +72 -0
  123. package/dist/edit/pptx/geometry.js +193 -0
  124. package/dist/edit/pptx/handler.d.ts +7 -0
  125. package/dist/edit/pptx/handler.js +91 -0
  126. package/dist/edit/pptx/handlers.d.ts +3 -0
  127. package/dist/edit/pptx/handlers.js +21 -0
  128. package/dist/edit/pptx/image-table-ops.d.ts +5 -0
  129. package/dist/edit/pptx/image-table-ops.js +233 -0
  130. package/dist/edit/pptx/model.d.ts +69 -0
  131. package/dist/edit/pptx/model.js +170 -0
  132. package/dist/edit/pptx/operations.d.ts +50 -0
  133. package/dist/edit/pptx/operations.js +11 -0
  134. package/dist/edit/pptx/provider.d.ts +17 -0
  135. package/dist/edit/pptx/provider.js +40 -0
  136. package/dist/edit/pptx/schemas.d.ts +6 -0
  137. package/dist/edit/pptx/schemas.js +220 -0
  138. package/dist/edit/pptx/session.d.ts +44 -0
  139. package/dist/edit/pptx/session.js +114 -0
  140. package/dist/edit/pptx/shape-ops.d.ts +10 -0
  141. package/dist/edit/pptx/shape-ops.js +486 -0
  142. package/dist/edit/pptx/slide-ops.d.ts +6 -0
  143. package/dist/edit/pptx/slide-ops.js +353 -0
  144. package/dist/edit/pptx/style.d.ts +31 -0
  145. package/dist/edit/pptx/style.js +181 -0
  146. package/dist/edit/pptx/text-ops.d.ts +13 -0
  147. package/dist/edit/pptx/text-ops.js +267 -0
  148. package/dist/edit/pptx/text-write.d.ts +67 -0
  149. package/dist/edit/pptx/text-write.js +293 -0
  150. package/dist/edit/pptx/text.d.ts +41 -0
  151. package/dist/edit/pptx/text.js +99 -0
  152. package/dist/edit/pptx/types.d.ts +232 -0
  153. package/dist/edit/pptx/types.js +1 -0
  154. package/dist/edit/schema.d.ts +11 -0
  155. package/dist/edit/schema.js +275 -0
  156. package/dist/edit/session.d.ts +66 -0
  157. package/dist/edit/session.js +701 -0
  158. package/dist/edit/sessions.d.ts +8 -0
  159. package/dist/edit/sessions.js +1 -0
  160. package/dist/edit/types.d.ts +276 -0
  161. package/dist/edit/types.js +1 -0
  162. package/dist/edit/worker-engine.d.ts +31 -0
  163. package/dist/edit/worker-engine.js +91 -0
  164. package/dist/fonts/THIRD_PARTY_NOTICES.md +3 -0
  165. package/dist/fonts/manifest.json +6 -1
  166. package/dist/fonts/noto-sans-latin-cyrillic.ttf +0 -0
  167. package/dist/fonts.d.ts +2 -0
  168. package/dist/fonts.js +4 -0
  169. package/dist/headless.d.ts +4 -0
  170. package/dist/headless.js +4 -0
  171. package/dist/index.d.ts +5 -0
  172. package/dist/index.js +5 -0
  173. package/dist/limits.js +3 -0
  174. package/dist/ooxml-edit-worker.d.ts +1 -0
  175. package/dist/ooxml-edit-worker.js +8 -0
  176. package/dist/pdf-edit-worker.d.ts +1 -0
  177. package/dist/pdf-edit-worker.js +35 -0
  178. package/dist/spreadsheet-viewport.d.ts +5 -0
  179. package/dist/spreadsheet-viewport.js +11 -0
  180. package/dist/ui.js +7 -0
  181. package/dist/viewer.d.ts +6 -0
  182. package/dist/viewer.js +293 -19
  183. package/dist/viewport.d.ts +13 -0
  184. package/dist/viewport.js +126 -1
  185. package/dist/worker-protocol.d.ts +39 -2
  186. package/dist/workers/ooxml-edit-worker.js +8671 -0
  187. package/dist/workers/pdf-edit-worker.js +10844 -0
  188. package/package.json +3 -3
@@ -0,0 +1,14 @@
1
+ import type { EditOperation, EditReceipt, TextRange } from "../types.js";
2
+ export interface MutationRecord {
3
+ /** The revision the call produced. */
4
+ readonly revision: number;
5
+ readonly kind: "apply" | "undo" | "redo" | "reset";
6
+ /**
7
+ * The operations the call applied (apply, redo) or took back (undo, reset);
8
+ * for a reset every batch of the session, in order.
9
+ */
10
+ readonly operations: readonly EditOperation[];
11
+ readonly receipt: EditReceipt;
12
+ }
13
+ /** The range after the records, or `undefined` when its element is gone. */
14
+ export declare function mapRangeThrough(range: TextRange, records: readonly MutationRecord[]): TextRange | undefined;
@@ -0,0 +1,107 @@
1
+ /** The range after the records, or `undefined` when its element is gone. */
2
+ export function mapRangeThrough(range, records) {
3
+ const start = carry(range.start, records);
4
+ const end = carry(range.end, records);
5
+ if (start.gone || end.gone)
6
+ return undefined;
7
+ if (start.position.elementId === end.position.elementId &&
8
+ end.position.offset < start.position.offset)
9
+ return { start: start.position, end: start.position };
10
+ return { start: start.position, end: end.position };
11
+ }
12
+ function carry(position, records) {
13
+ const carried = { position, gone: false };
14
+ for (const record of records) {
15
+ const forward = record.kind === "apply" || record.kind === "redo";
16
+ const operations = forward
17
+ ? record.operations
18
+ : [...record.operations].reverse();
19
+ for (const operation of operations) {
20
+ if (forward)
21
+ applyForward(carried, operation);
22
+ else
23
+ applyBackward(carried, operation);
24
+ }
25
+ const { receipt } = record;
26
+ const id = carried.position.elementId;
27
+ if (receipt.removedIds.includes(id))
28
+ carried.gone = true;
29
+ if (receipt.createdIds.includes(id))
30
+ carried.gone = false;
31
+ const renamed = receipt.remappedIds?.[id];
32
+ if (renamed)
33
+ carried.position = { ...carried.position, elementId: renamed };
34
+ }
35
+ return carried;
36
+ }
37
+ function applyForward(carried, operation) {
38
+ const id = carried.position.elementId;
39
+ if (operation.op === "deleteElement" && targetOf(operation) === id) {
40
+ carried.gone = true;
41
+ return;
42
+ }
43
+ const replace = asReplace(operation, id);
44
+ if (!replace)
45
+ return;
46
+ const { offset } = carried.position;
47
+ const span = spanOf(replace);
48
+ // A whole-text replacement: offsets stay and the live element bounds them.
49
+ if (!span)
50
+ return;
51
+ const delta = replace.text.length - (span.end - span.start);
52
+ carried.position = {
53
+ elementId: id,
54
+ offset: offset <= span.start
55
+ ? offset
56
+ : offset >= span.end
57
+ ? offset + delta
58
+ : Math.min(offset, span.start + replace.text.length),
59
+ };
60
+ }
61
+ function applyBackward(carried, operation) {
62
+ const id = carried.position.elementId;
63
+ if (operation.op === "deleteElement" && targetOf(operation) === id) {
64
+ carried.gone = false;
65
+ return;
66
+ }
67
+ const replace = asReplace(operation, id);
68
+ if (!replace)
69
+ return;
70
+ const span = spanOf(replace);
71
+ // A whole-text replacement taken back: the old length is unknown here.
72
+ if (!span)
73
+ return;
74
+ const { offset } = carried.position;
75
+ const newEnd = span.start + replace.text.length;
76
+ const delta = span.end - span.start - replace.text.length;
77
+ carried.position = {
78
+ elementId: id,
79
+ offset: offset <= span.start
80
+ ? offset
81
+ : offset >= newEnd
82
+ ? offset + delta
83
+ : Math.min(offset, span.end),
84
+ };
85
+ }
86
+ function asReplace(operation, elementId) {
87
+ if (operation.op !== "replaceText")
88
+ return undefined;
89
+ const candidate = operation;
90
+ if (candidate.target !== elementId || typeof candidate.text !== "string")
91
+ return undefined;
92
+ return candidate;
93
+ }
94
+ function spanOf(replace) {
95
+ const { range } = replace;
96
+ if (!range ||
97
+ range.start.elementId !== replace.target ||
98
+ range.end.elementId !== replace.target)
99
+ return undefined;
100
+ return {
101
+ start: Math.min(range.start.offset, range.end.offset),
102
+ end: Math.max(range.start.offset, range.end.offset),
103
+ };
104
+ }
105
+ function targetOf(operation) {
106
+ return operation.target;
107
+ }
@@ -0,0 +1,11 @@
1
+ import type { JsonSchema, OperationSchemaSet } from "../types.js";
2
+ export declare const pdfOperationSchemas: OperationSchemaSet;
3
+ /**
4
+ * What a `WebDoc` mark found in a file must look like before its objects are
5
+ * treated as one of web-doc's own elements. Anything else stays plain objects.
6
+ */
7
+ /** The head mark of a table, on its path objects, holding every input. */
8
+ export declare const tableMarkSchema: JsonSchema;
9
+ /** The mark on a table's text objects: the id alone. */
10
+ export declare const tableMemberMarkSchema: JsonSchema;
11
+ export declare const textBoxMarkSchema: JsonSchema;
@@ -0,0 +1,291 @@
1
+ /*
2
+ * JSON Schemas of the PDF operations, shared by the worker (engine
3
+ * validation), the main thread (shape checks) and clients (`session.schemas`).
4
+ * Only keywords the in-repo validator implements may appear here.
5
+ */
6
+ const definitions = {
7
+ rect: {
8
+ type: "object",
9
+ required: ["x", "y", "width", "height"],
10
+ additionalProperties: false,
11
+ properties: {
12
+ x: { type: "number" },
13
+ y: { type: "number" },
14
+ width: { type: "number", exclusiveMinimum: 0 },
15
+ height: { type: "number", exclusiveMinimum: 0 },
16
+ },
17
+ },
18
+ color: { type: "string", pattern: "^#[0-9A-Fa-f]{6}$" },
19
+ target: { type: "string", minLength: 1, maxLength: 200 },
20
+ textPosition: {
21
+ type: "object",
22
+ required: ["elementId", "offset"],
23
+ additionalProperties: false,
24
+ properties: {
25
+ elementId: { $ref: "#/$defs/target" },
26
+ offset: { type: "integer", minimum: 0 },
27
+ },
28
+ },
29
+ textRange: {
30
+ type: "object",
31
+ required: ["start", "end"],
32
+ additionalProperties: false,
33
+ properties: {
34
+ start: { $ref: "#/$defs/textPosition" },
35
+ end: { $ref: "#/$defs/textPosition" },
36
+ },
37
+ },
38
+ point: {
39
+ type: "object",
40
+ required: ["x", "y"],
41
+ additionalProperties: false,
42
+ properties: { x: { type: "number" }, y: { type: "number" } },
43
+ },
44
+ offset: {
45
+ type: "object",
46
+ required: ["dx", "dy"],
47
+ additionalProperties: false,
48
+ properties: { dx: { type: "number" }, dy: { type: "number" } },
49
+ },
50
+ stroke: {
51
+ type: "object",
52
+ required: ["color", "width"],
53
+ additionalProperties: false,
54
+ properties: {
55
+ color: { $ref: "#/$defs/color" },
56
+ width: { type: "number", minimum: 0, maximum: 100 },
57
+ },
58
+ },
59
+ fill: {
60
+ type: "object",
61
+ required: ["color"],
62
+ additionalProperties: false,
63
+ properties: { color: { $ref: "#/$defs/color" } },
64
+ },
65
+ strokeOrNull: {
66
+ type: ["object", "null"],
67
+ required: ["color", "width"],
68
+ additionalProperties: false,
69
+ properties: {
70
+ color: { $ref: "#/$defs/color" },
71
+ width: { type: "number", minimum: 0, maximum: 100 },
72
+ },
73
+ },
74
+ fillOrNull: {
75
+ type: ["object", "null"],
76
+ required: ["color"],
77
+ additionalProperties: false,
78
+ properties: { color: { $ref: "#/$defs/color" } },
79
+ },
80
+ textBoxStyle: {
81
+ type: "object",
82
+ additionalProperties: false,
83
+ properties: {
84
+ fontFamily: { type: "string", minLength: 1, maxLength: 100 },
85
+ fontSize: { type: "number", minimum: 1, maximum: 500 },
86
+ bold: { type: "boolean" },
87
+ italic: { type: "boolean" },
88
+ color: { $ref: "#/$defs/color" },
89
+ align: { enum: ["left", "center", "right"] },
90
+ lineHeight: { type: "number", minimum: 0.5, maximum: 5 },
91
+ },
92
+ },
93
+ tableStyle: {
94
+ type: "object",
95
+ additionalProperties: false,
96
+ properties: {
97
+ fontFamily: { type: "string", minLength: 1, maxLength: 100 },
98
+ fontSize: { type: "number", minimum: 1, maximum: 500 },
99
+ color: { $ref: "#/$defs/color" },
100
+ borderColor: { $ref: "#/$defs/color" },
101
+ borderWidth: { type: "number", minimum: 0, maximum: 20 },
102
+ cellPadding: { type: "number", minimum: 0, maximum: 100 },
103
+ headerFill: { $ref: "#/$defs/color" },
104
+ },
105
+ },
106
+ tableRows: {
107
+ type: "array",
108
+ minItems: 1,
109
+ maxItems: 100,
110
+ items: {
111
+ type: "array",
112
+ minItems: 1,
113
+ maxItems: 20,
114
+ items: { type: "string", maxLength: 2000 },
115
+ },
116
+ },
117
+ };
118
+ function operation(name, properties, required) {
119
+ return {
120
+ type: "object",
121
+ required: ["op", ...required],
122
+ additionalProperties: false,
123
+ properties: { op: { const: name }, ...properties },
124
+ $defs: definitions,
125
+ };
126
+ }
127
+ export const pdfOperationSchemas = Object.freeze({
128
+ format: "pdf",
129
+ version: 1,
130
+ operations: Object.freeze({
131
+ insertTextBox: operation("insertTextBox", {
132
+ pageIndex: { type: "integer", minimum: 0 },
133
+ rect: { $ref: "#/$defs/rect" },
134
+ text: { type: "string", minLength: 1, maxLength: 20000 },
135
+ style: { $ref: "#/$defs/textBoxStyle" },
136
+ }, ["pageIndex", "rect", "text"]),
137
+ replaceText: operation("replaceText", {
138
+ target: { $ref: "#/$defs/target" },
139
+ text: { type: "string", minLength: 1, maxLength: 20000 },
140
+ range: { $ref: "#/$defs/textRange" },
141
+ }, ["target", "text"]),
142
+ setTextStyle: operation("setTextStyle", {
143
+ target: { $ref: "#/$defs/target" },
144
+ style: { $ref: "#/$defs/textBoxStyle" },
145
+ }, ["target", "style"]),
146
+ resizeElement: operation("resizeElement", {
147
+ target: { $ref: "#/$defs/target" },
148
+ rect: { $ref: "#/$defs/rect" },
149
+ }, ["target", "rect"]),
150
+ moveElement: operation("moveElement", {
151
+ target: { $ref: "#/$defs/target" },
152
+ to: { $ref: "#/$defs/point" },
153
+ by: { $ref: "#/$defs/offset" },
154
+ }, ["target"]),
155
+ deleteElement: operation("deleteElement", { target: { $ref: "#/$defs/target" } }, ["target"]),
156
+ insertPage: operation("insertPage", {
157
+ index: { type: "integer", minimum: 0 },
158
+ size: {
159
+ type: "object",
160
+ required: ["width", "height"],
161
+ additionalProperties: false,
162
+ properties: {
163
+ width: { type: "number", minimum: 3, maximum: 14400 },
164
+ height: { type: "number", minimum: 3, maximum: 14400 },
165
+ },
166
+ },
167
+ }, ["index"]),
168
+ deletePage: operation("deletePage", { pageIndex: { type: "integer", minimum: 0 } }, ["pageIndex"]),
169
+ movePage: operation("movePage", {
170
+ from: { type: "integer", minimum: 0 },
171
+ to: { type: "integer", minimum: 0 },
172
+ }, ["from", "to"]),
173
+ rotatePage: operation("rotatePage", {
174
+ pageIndex: { type: "integer", minimum: 0 },
175
+ rotation: { enum: [0, 90, 180, 270] },
176
+ }, ["pageIndex", "rotation"]),
177
+ insertShape: operation("insertShape", {
178
+ pageIndex: { type: "integer", minimum: 0 },
179
+ shape: { enum: ["rectangle", "ellipse", "line"] },
180
+ rect: { $ref: "#/$defs/rect" },
181
+ from: { $ref: "#/$defs/point" },
182
+ to: { $ref: "#/$defs/point" },
183
+ stroke: { $ref: "#/$defs/stroke" },
184
+ fill: { $ref: "#/$defs/fill" },
185
+ }, ["pageIndex", "shape"]),
186
+ setShapeStyle: operation("setShapeStyle", {
187
+ target: { $ref: "#/$defs/target" },
188
+ stroke: { $ref: "#/$defs/strokeOrNull" },
189
+ fill: { $ref: "#/$defs/fillOrNull" },
190
+ }, ["target"]),
191
+ insertImage: operation("insertImage", {
192
+ pageIndex: { type: "integer", minimum: 0 },
193
+ rect: { $ref: "#/$defs/rect" },
194
+ data: { "x-binary": true, type: "string", contentEncoding: "base64" },
195
+ mimeType: { enum: ["image/png", "image/jpeg"] },
196
+ }, ["pageIndex", "rect", "data", "mimeType"]),
197
+ insertTable: operation("insertTable", {
198
+ pageIndex: { type: "integer", minimum: 0 },
199
+ at: { $ref: "#/$defs/point" },
200
+ width: { type: "number", exclusiveMinimum: 0 },
201
+ rows: { $ref: "#/$defs/tableRows" },
202
+ columnWidths: {
203
+ type: "array",
204
+ minItems: 1,
205
+ maxItems: 20,
206
+ items: { type: "number", exclusiveMinimum: 0 },
207
+ },
208
+ style: { $ref: "#/$defs/tableStyle" },
209
+ }, ["pageIndex", "at", "width", "rows"]),
210
+ setTableCell: operation("setTableCell", {
211
+ target: { $ref: "#/$defs/target" },
212
+ row: { type: "integer", minimum: 0 },
213
+ column: { type: "integer", minimum: 0 },
214
+ text: { type: "string", maxLength: 2000 },
215
+ }, ["target", "row", "column", "text"]),
216
+ }),
217
+ });
218
+ /**
219
+ * What a `WebDoc` mark found in a file must look like before its objects are
220
+ * treated as one of web-doc's own elements. Anything else stays plain objects.
221
+ */
222
+ /** The head mark of a table, on its path objects, holding every input. */
223
+ export const tableMarkSchema = {
224
+ type: "object",
225
+ required: ["kind", "id", "at", "rows", "columnWidths", "style"],
226
+ additionalProperties: false,
227
+ properties: {
228
+ kind: { const: "table" },
229
+ id: { $ref: "#/$defs/target" },
230
+ at: { $ref: "#/$defs/point" },
231
+ rows: { $ref: "#/$defs/tableRows" },
232
+ columnWidths: {
233
+ type: "array",
234
+ minItems: 1,
235
+ maxItems: 20,
236
+ items: { type: "number", exclusiveMinimum: 0 },
237
+ },
238
+ style: {
239
+ type: "object",
240
+ required: [
241
+ "fontFamily",
242
+ "fontSize",
243
+ "color",
244
+ "borderColor",
245
+ "borderWidth",
246
+ "cellPadding",
247
+ "headerFill",
248
+ ],
249
+ additionalProperties: false,
250
+ properties: {
251
+ ...definitions.tableStyle.properties,
252
+ headerFill: { type: ["string", "null"], pattern: "^#[0-9A-Fa-f]{6}$" },
253
+ },
254
+ },
255
+ },
256
+ $defs: definitions,
257
+ };
258
+ /** The mark on a table's text objects: the id alone. */
259
+ export const tableMemberMarkSchema = {
260
+ type: "object",
261
+ required: ["kind", "id"],
262
+ additionalProperties: false,
263
+ properties: { kind: { const: "table" }, id: { $ref: "#/$defs/target" } },
264
+ $defs: definitions,
265
+ };
266
+ export const textBoxMarkSchema = {
267
+ type: "object",
268
+ required: ["kind", "id", "rect", "text", "style"],
269
+ additionalProperties: false,
270
+ properties: {
271
+ kind: { const: "textBox" },
272
+ id: { $ref: "#/$defs/target" },
273
+ rect: { $ref: "#/$defs/rect" },
274
+ text: { type: "string", minLength: 1, maxLength: 20000 },
275
+ style: {
276
+ type: "object",
277
+ required: [
278
+ "fontFamily",
279
+ "fontSize",
280
+ "bold",
281
+ "italic",
282
+ "color",
283
+ "align",
284
+ "lineHeight",
285
+ ],
286
+ additionalProperties: false,
287
+ properties: definitions.textBoxStyle.properties,
288
+ },
289
+ },
290
+ $defs: definitions,
291
+ };
@@ -0,0 +1,5 @@
1
+ import type { TextSelection } from "../../contracts.js";
2
+ import type { TextRange } from "../types.js";
3
+ import type { PageLayout } from "./types.js";
4
+ /** Ranges of the selected text, merged per element, in reading order. */
5
+ export declare function resolveSelection(selection: TextSelection, pages: readonly PageLayout[]): TextRange[];
@@ -0,0 +1,152 @@
1
+ import { normalizeWithMap } from "../../search-text.js";
2
+ /** Slack, in points, when a run's box is compared with a line's. */
3
+ const SLACK = 0.5;
4
+ /** Ranges of the selected text, merged per element, in reading order. */
5
+ export function resolveSelection(selection, pages) {
6
+ const candidates = [];
7
+ for (const page of pages)
8
+ for (const layout of page.layouts)
9
+ for (const line of layout.lines)
10
+ candidates.push({
11
+ order: candidates.length,
12
+ elementId: layout.elementId,
13
+ line,
14
+ });
15
+ const found = [];
16
+ for (const run of selection.runs) {
17
+ if (!run.text.trim())
18
+ continue;
19
+ const match = matchRun(run, candidates);
20
+ if (!match)
21
+ continue;
22
+ const range = rangeWithin(run, match.line, match.elementId);
23
+ if (range)
24
+ found.push({ order: match.order, range });
25
+ }
26
+ return merge(found);
27
+ }
28
+ function matchRun(run, candidates) {
29
+ const box = { x: run.x, y: run.y, width: run.width, height: run.height };
30
+ // 1. The line whose box the run covers by at least half.
31
+ let best;
32
+ let bestOverlap = 0;
33
+ for (const candidate of candidates) {
34
+ const overlap = overlapArea(box, candidate.line.bounds);
35
+ if (overlap >= area(candidate.line.bounds) / 2 && overlap > bestOverlap) {
36
+ best = candidate;
37
+ bestOverlap = overlap;
38
+ }
39
+ }
40
+ if (best)
41
+ return best;
42
+ // 2. The single line that contains the run's box.
43
+ const containing = candidates.filter((candidate) => contains(candidate.line.bounds, box));
44
+ if (containing.length > 0)
45
+ return containing.reduce((smallest, candidate) => area(candidate.line.bounds) < area(smallest.line.bounds)
46
+ ? candidate
47
+ : smallest);
48
+ // 3. A line whose folded text contains the run's folded text.
49
+ const needle = fold(run.text).text;
50
+ if (!needle)
51
+ return undefined;
52
+ return candidates.find((candidate) => fold(candidate.line.text).text.includes(needle));
53
+ }
54
+ /**
55
+ * The part of the line the run covers. The run's text decides when it occurs
56
+ * in the line — the viewer cuts runs proportionally, so their boxes are only
57
+ * approximate — and its box picks the occurrence nearest to the glyphs it
58
+ * covers; without a textual match the covered glyphs stand on their own.
59
+ */
60
+ function rangeWithin(run, line, elementId) {
61
+ const box = { x: run.x, y: run.y, width: run.width, height: run.height };
62
+ const covered = line.glyphs
63
+ .filter((glyph) => contains(grow(box, SLACK), centre(glyph.box)))
64
+ .map((glyph) => glyph.offset);
65
+ const anchor = covered.length > 0 ? Math.min(...covered) : line.range.start.offset;
66
+ const haystack = fold(line.text);
67
+ const needle = fold(run.text).text;
68
+ const base = line.range.start.offset;
69
+ let best;
70
+ let bestDistance = Number.POSITIVE_INFINITY;
71
+ for (let at = needle ? haystack.text.indexOf(needle) : -1; at >= 0; at = haystack.text.indexOf(needle, at + 1)) {
72
+ const start = base + haystack.starts[at];
73
+ const distance = Math.abs(start - anchor);
74
+ if (distance < bestDistance) {
75
+ bestDistance = distance;
76
+ best = {
77
+ start: { elementId, offset: start },
78
+ end: {
79
+ elementId,
80
+ offset: base + haystack.ends[at + needle.length - 1],
81
+ },
82
+ };
83
+ }
84
+ }
85
+ if (best)
86
+ return best;
87
+ if (covered.length === 0)
88
+ return undefined;
89
+ return {
90
+ start: { elementId, offset: Math.min(...covered) },
91
+ end: { elementId, offset: Math.max(...covered) + 1 },
92
+ };
93
+ }
94
+ /** NFKC-folded text without whitespace, with each folded unit's source offsets. */
95
+ function fold(text) {
96
+ const mapped = normalizeWithMap(text, false);
97
+ let folded = "";
98
+ const starts = [];
99
+ const ends = [];
100
+ for (let index = 0; index < mapped.text.length; index += 1) {
101
+ const unit = mapped.text[index];
102
+ if (/\s/u.test(unit))
103
+ continue;
104
+ folded += unit;
105
+ starts.push(mapped.starts[index]);
106
+ ends.push(mapped.ends[index]);
107
+ }
108
+ return { text: folded, starts, ends };
109
+ }
110
+ function merge(found) {
111
+ const sorted = [...found].sort((a, b) => a.order - b.order || a.range.start.offset - b.range.start.offset);
112
+ const merged = [];
113
+ for (const { range } of sorted) {
114
+ const last = merged.at(-1);
115
+ if (last &&
116
+ last.end.elementId === range.start.elementId &&
117
+ range.start.offset <= last.end.offset) {
118
+ if (range.end.offset > last.end.offset)
119
+ merged[merged.length - 1] = { start: last.start, end: range.end };
120
+ }
121
+ else
122
+ merged.push(range);
123
+ }
124
+ return merged;
125
+ }
126
+ function area(rect) {
127
+ return Math.max(0, rect.width) * Math.max(0, rect.height);
128
+ }
129
+ function overlapArea(a, b) {
130
+ const width = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);
131
+ const height = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);
132
+ return width > 0 && height > 0 ? width * height : 0;
133
+ }
134
+ function contains(outer, inner) {
135
+ const width = "width" in inner ? inner.width : 0;
136
+ const height = "height" in inner ? inner.height : 0;
137
+ return (inner.x >= outer.x - SLACK &&
138
+ inner.y >= outer.y - SLACK &&
139
+ inner.x + width <= outer.x + outer.width + SLACK &&
140
+ inner.y + height <= outer.y + outer.height + SLACK);
141
+ }
142
+ function grow(rect, by) {
143
+ return {
144
+ x: rect.x - by,
145
+ y: rect.y - by,
146
+ width: rect.width + 2 * by,
147
+ height: rect.height + 2 * by,
148
+ };
149
+ }
150
+ function centre(rect) {
151
+ return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };
152
+ }
@@ -0,0 +1,53 @@
1
+ import type { TextSelection } from "../../contracts.js";
2
+ import type { EditSessionCore } from "../engine.js";
3
+ import type { ApplyOptions, AssetOptions, EditFindOptions, EditOperation, EditReceipt, EditState, ElementQuery, HistoryOptions, OperationSchemaSet, PagePoint, PageRect, ReadItem, ReadOptions, ReadResult, SavedDocument, TextPosition, TextRange, TextTarget } from "../types.js";
4
+ import type { DeleteElementOperation, DeletePageOperation, Fields, InsertImageOperation, InsertPageOperation, InsertShapeOperation, InsertTableOperation, InsertTextBoxOperation, MoveElementOperation, MovePageOperation, PdfEditSession, PdfElement, PdfOperation, PdfSaveOptions, ReplaceTextOperation, ResizeElementOperation, RotatePageOperation, SetShapeStyleOperation, SetTableCellOperation, PageBitmap, PageLayout, RenderOptions, SetTextStyleOperation, TextLayout } from "./types.js";
5
+ /**
6
+ * The PDF session: the core session narrowed to PDF operations and elements.
7
+ * Typed methods are added here as their operations ship; each is `apply()`
8
+ * with a single operation.
9
+ */
10
+ export declare class PdfSession implements PdfEditSession {
11
+ #private;
12
+ readonly format: "pdf";
13
+ constructor(core: EditSessionCore);
14
+ get sessionId(): string;
15
+ get state(): EditState;
16
+ get schemas(): OperationSchemaSet;
17
+ apply(operations: readonly PdfOperation[], options?: ApplyOptions): Promise<EditReceipt>;
18
+ applyJson(operations: readonly EditOperation[], options?: ApplyOptions): Promise<EditReceipt>;
19
+ undo(options?: HistoryOptions): Promise<EditReceipt>;
20
+ redo(options?: HistoryOptions): Promise<EditReceipt>;
21
+ reset(options?: HistoryOptions): Promise<EditReceipt>;
22
+ save(options?: PdfSaveOptions): Promise<SavedDocument>;
23
+ markSaved(stateToken: string): void;
24
+ addAsset(data: Uint8Array, options?: AssetOptions): Promise<string>;
25
+ getElements(query?: ElementQuery, options?: ReadOptions): Promise<ReadResult<PdfElement>>;
26
+ elementsAtSync(pageIndex: number, point: PagePoint): ReadResult<PdfElement>;
27
+ get cachedPages(): readonly number[];
28
+ getElement(id: string, options?: ReadOptions): Promise<ReadItem<PdfElement>>;
29
+ elementsAt(pageIndex: number, point: PagePoint, options?: ReadOptions): Promise<ReadResult<PdfElement>>;
30
+ findText(query: string, options?: EditFindOptions): Promise<ReadResult<TextTarget>>;
31
+ getTextLayout(elementId: string, options?: ReadOptions): Promise<ReadItem<TextLayout>>;
32
+ positionAt(pageIndex: number, point: PagePoint, options?: ReadOptions): Promise<ReadItem<TextPosition>>;
33
+ rangeRects(range: TextRange, options?: ReadOptions): Promise<ReadResult<PageRect>>;
34
+ getPageLayout(pageIndex: number, options?: ReadOptions): Promise<ReadItem<PageLayout>>;
35
+ elementsForSelection(selection: TextSelection, options?: ReadOptions): Promise<ReadResult<TextRange>>;
36
+ mapRange(range: TextRange, fromRevision: number, options?: ReadOptions): Promise<ReadItem<TextRange>>;
37
+ renderPageWithout(pageIndex: number, elementIds: readonly string[], options?: RenderOptions): Promise<ReadItem<PageBitmap>>;
38
+ insertTextBox(fields: Fields<InsertTextBoxOperation>, options?: ApplyOptions): Promise<EditReceipt>;
39
+ replaceText(fields: Fields<ReplaceTextOperation>, options?: ApplyOptions): Promise<EditReceipt>;
40
+ setTextStyle(fields: Fields<SetTextStyleOperation>, options?: ApplyOptions): Promise<EditReceipt>;
41
+ resizeElement(fields: Fields<ResizeElementOperation>, options?: ApplyOptions): Promise<EditReceipt>;
42
+ moveElement(fields: Fields<MoveElementOperation>, options?: ApplyOptions): Promise<EditReceipt>;
43
+ deleteElement(fields: Fields<DeleteElementOperation>, options?: ApplyOptions): Promise<EditReceipt>;
44
+ insertPage(fields: Fields<InsertPageOperation>, options?: ApplyOptions): Promise<EditReceipt>;
45
+ deletePage(fields: Fields<DeletePageOperation>, options?: ApplyOptions): Promise<EditReceipt>;
46
+ movePage(fields: Fields<MovePageOperation>, options?: ApplyOptions): Promise<EditReceipt>;
47
+ rotatePage(fields: Fields<RotatePageOperation>, options?: ApplyOptions): Promise<EditReceipt>;
48
+ insertShape(fields: Fields<InsertShapeOperation>, options?: ApplyOptions): Promise<EditReceipt>;
49
+ setShapeStyle(fields: Fields<SetShapeStyleOperation>, options?: ApplyOptions): Promise<EditReceipt>;
50
+ insertImage(fields: Fields<InsertImageOperation>, options?: ApplyOptions): Promise<EditReceipt>;
51
+ insertTable(fields: Fields<InsertTableOperation>, options?: ApplyOptions): Promise<EditReceipt>;
52
+ setTableCell(fields: Fields<SetTableCellOperation>, options?: ApplyOptions): Promise<EditReceipt>;
53
+ }