@openpresentation/opf-editor 0.10.5 → 0.11.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/README.md +333 -8
- package/dist/annotations.d.ts +71 -0
- package/dist/annotations.js +281 -0
- package/dist/assets.d.ts +67 -0
- package/dist/assets.js +176 -0
- package/dist/background-options.d.ts +48 -0
- package/dist/background-options.js +134 -0
- package/dist/block-convert.d.ts +64 -0
- package/dist/block-convert.js +142 -0
- package/dist/canvas.d.ts +16 -0
- package/dist/canvas.js +82 -21
- package/dist/chart-data.d.ts +32 -0
- package/dist/chart-data.js +101 -0
- package/dist/chart-options-panel.d.ts +16 -0
- package/dist/chart-options-panel.js +127 -0
- package/dist/chart-options.d.ts +49 -0
- package/dist/chart-options.js +157 -0
- package/dist/content-actions.d.ts +91 -0
- package/dist/content-actions.js +207 -0
- package/dist/content-controls.js +326 -0
- package/dist/data-grid.d.ts +37 -0
- package/dist/data-grid.js +1035 -0
- package/dist/design-controls.d.ts +43 -0
- package/dist/design-controls.js +1077 -0
- package/dist/design-options.d.ts +108 -0
- package/dist/design-options.js +412 -0
- package/dist/edit-helpers.js +52 -0
- package/dist/export.d.ts +77 -0
- package/dist/export.js +216 -0
- package/dist/find-panel.d.ts +44 -0
- package/dist/find-panel.js +431 -0
- package/dist/find-replace.d.ts +100 -0
- package/dist/find-replace.js +374 -0
- package/dist/grid-model.d.ts +135 -0
- package/dist/grid-model.js +836 -0
- package/dist/grid-text.d.ts +33 -0
- package/dist/grid-text.js +251 -0
- package/dist/image-crop.d.ts +59 -0
- package/dist/image-crop.js +336 -0
- package/dist/image-cropper.d.ts +29 -0
- package/dist/image-cropper.js +519 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.js +104 -171
- package/dist/numbering-panel.d.ts +21 -0
- package/dist/numbering-panel.js +200 -0
- package/dist/numbering.d.ts +62 -0
- package/dist/numbering.js +223 -0
- package/dist/outline-view.d.ts +17 -0
- package/dist/outline-view.js +278 -0
- package/dist/outline.d.ts +56 -0
- package/dist/outline.js +271 -0
- package/dist/persistence-ui.d.ts +24 -0
- package/dist/persistence-ui.js +81 -0
- package/dist/persistence.d.ts +105 -0
- package/dist/persistence.js +429 -0
- package/dist/review-panel.d.ts +44 -0
- package/dist/review-panel.js +359 -0
- package/dist/review.d.ts +75 -0
- package/dist/review.js +170 -0
- package/dist/slide-manager.d.ts +44 -0
- package/dist/slide-manager.js +695 -0
- package/dist/slides.d.ts +96 -0
- package/dist/slides.js +433 -0
- package/dist/switches.d.ts +26 -0
- package/dist/switches.js +127 -43
- package/dist/table-options.d.ts +80 -0
- package/dist/table-options.js +419 -0
- package/dist/table-structure.d.ts +30 -0
- package/dist/table-structure.js +92 -0
- package/dist/template-panel.d.ts +31 -0
- package/dist/template-panel.js +377 -0
- package/dist/templates.d.ts +126 -0
- package/dist/templates.js +331 -0
- package/dist/zip.d.ts +4 -0
- package/dist/zip.js +71 -0
- package/package.json +150 -10
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export declare class GridTextError extends Error {
|
|
2
|
+
readonly code: "unclosed-quote" | "invalid-delimiter" | "invalid-text";
|
|
3
|
+
readonly details: Record<string, unknown>;
|
|
4
|
+
}
|
|
5
|
+
export type GridDelimiter = "\t" | ";" | ",";
|
|
6
|
+
/** The delimiter of pasted text: a tab when there is one, else the semicolon or comma that splits every line the same way, else a tab. */
|
|
7
|
+
export declare function detectDelimiter(text: string): GridDelimiter;
|
|
8
|
+
/** Read TSV or CSV text (RFC 4180 quoting) into rows of text. Rows keep their own lengths. Throws `GridTextError`. */
|
|
9
|
+
export declare function parseDelimited(text: string, options?: { delimiter?: GridDelimiter }): { rows: string[][]; delimiter: GridDelimiter };
|
|
10
|
+
/** Pad ragged rows with empty text to the longest row. */
|
|
11
|
+
export declare function rectangular(rows: string[][]): { rows: string[][]; width: number };
|
|
12
|
+
/** Write rows as TSV (default) or another delimiter, quoting fields that hold the delimiter, a quote or a line break. */
|
|
13
|
+
export declare function toDelimited(rows: ReadonlyArray<ReadonlyArray<unknown>>, options?: { delimiter?: string; newline?: string }): string;
|
|
14
|
+
|
|
15
|
+
export interface NumberFormat {
|
|
16
|
+
readonly decimal: "." | ",";
|
|
17
|
+
/** "1,234.56" or "1.234,56" */
|
|
18
|
+
readonly label: string;
|
|
19
|
+
}
|
|
20
|
+
export declare const NUMBER_FORMATS: Readonly<Record<"." | ",", NumberFormat>>;
|
|
21
|
+
/** "." or "," as given, or "auto" for the locale's decimal separator (a point when the locale is unknown). */
|
|
22
|
+
export declare function resolveNumberFormat(format?: "auto" | "." | ",", locale?: string): NumberFormat;
|
|
23
|
+
export type GridNumberResult = { empty: true; value?: undefined; error?: undefined } | { value: number; empty?: undefined; error?: undefined } | { error: string; empty?: undefined; value?: undefined };
|
|
24
|
+
/**
|
|
25
|
+
* Read one number in a number format. Blank text is `{ empty: true }`, never 0. Accepts a sign, accounting parentheses, one currency symbol,
|
|
26
|
+
* grouping in threes, the format's decimal separator and an exponent; refuses percent signs, the other format's separators and letters
|
|
27
|
+
* with a reason in `error`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function parseGridNumber(input: unknown, options?: { decimal?: "." | "," }): GridNumberResult;
|
|
30
|
+
/** A number as the shortest text that reads back the same, with the format's decimal separator and no grouping. */
|
|
31
|
+
export declare function formatGridNumber(value: number, options?: { decimal?: "." | "," }): string;
|
|
32
|
+
/** Whether `text` is how JavaScript writes the number it reads as, so storing it as a number changes nothing visible. */
|
|
33
|
+
export declare function isCanonicalNumber(text: string): boolean;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// Spreadsheet text for the data grid (RR-24): reading pasted TSV/CSV, writing TSV, and reading and
|
|
2
|
+
// writing numbers in a stated number format. Everything here is pure and deterministic; nothing reads
|
|
3
|
+
// the clipboard, the page or the network. The rules are documented in docs/data-grid.md.
|
|
4
|
+
//
|
|
5
|
+
// Numbers are never guessed. A grid cell is read in one number format, "1,234.56" (decimal point) or
|
|
6
|
+
// "1.234,56" (decimal comma), chosen by the caller or taken from the locale. Text that is not a number
|
|
7
|
+
// in that format is reported with a reason, never turned into 0 or into a gap.
|
|
8
|
+
|
|
9
|
+
export class GridTextError extends Error {
|
|
10
|
+
constructor(code, message, details = {}) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "GridTextError";
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.details = details;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// --- delimited text ------------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
const CANDIDATES = ["\t", ";", ","];
|
|
21
|
+
|
|
22
|
+
// Split `text` into records the way RFC 4180 does, with `delimiter`: fields in double quotes may hold
|
|
23
|
+
// the delimiter, line breaks and doubled quotes. A quote only opens a quoted field at the start of a
|
|
24
|
+
// field; inside an unquoted field it is plain text, as spreadsheets write it.
|
|
25
|
+
function split(text, delimiter) {
|
|
26
|
+
const rows = [];
|
|
27
|
+
let row = [];
|
|
28
|
+
let field = "";
|
|
29
|
+
let quoted = false;
|
|
30
|
+
let closed = false;
|
|
31
|
+
let started = false;
|
|
32
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
33
|
+
const char = text[index];
|
|
34
|
+
if (quoted) {
|
|
35
|
+
if (char === '"') {
|
|
36
|
+
if (text[index + 1] === '"') {
|
|
37
|
+
field += '"';
|
|
38
|
+
index += 1;
|
|
39
|
+
} else {
|
|
40
|
+
quoted = false;
|
|
41
|
+
closed = true;
|
|
42
|
+
}
|
|
43
|
+
} else field += char;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (char === delimiter) {
|
|
47
|
+
row.push(field);
|
|
48
|
+
field = "";
|
|
49
|
+
closed = false;
|
|
50
|
+
started = false;
|
|
51
|
+
} else if (char === "\n" || char === "\r") {
|
|
52
|
+
row.push(field);
|
|
53
|
+
rows.push(row);
|
|
54
|
+
row = [];
|
|
55
|
+
field = "";
|
|
56
|
+
closed = false;
|
|
57
|
+
started = false;
|
|
58
|
+
if (char === "\r" && text[index + 1] === "\n") index += 1;
|
|
59
|
+
} else if (char === '"' && !started && !closed) {
|
|
60
|
+
quoted = true;
|
|
61
|
+
started = true;
|
|
62
|
+
} else if (closed) {
|
|
63
|
+
// Text after a closing quote ("a"b): keep it, as Excel and Sheets do.
|
|
64
|
+
field += char;
|
|
65
|
+
} else {
|
|
66
|
+
started = true;
|
|
67
|
+
field += char;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (quoted) throw new GridTextError("unclosed-quote", "A quoted field is never closed. Check the quotes in the pasted text.", { delimiter });
|
|
71
|
+
if (field !== "" || row.length || closed || started) {
|
|
72
|
+
row.push(field);
|
|
73
|
+
rows.push(row);
|
|
74
|
+
}
|
|
75
|
+
return rows;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function stripQuoted(line) {
|
|
79
|
+
let out = "";
|
|
80
|
+
let quoted = false;
|
|
81
|
+
for (const char of line) {
|
|
82
|
+
if (char === '"') quoted = !quoted;
|
|
83
|
+
else if (!quoted) out += char;
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The delimiter of pasted text: a tab when the text has one (spreadsheets copy tab-separated), else
|
|
90
|
+
* the semicolon or comma that splits every non-empty line into the same number of fields (semicolon
|
|
91
|
+
* first, because comma decimals are common where semicolon CSV is), else a tab (one column).
|
|
92
|
+
*/
|
|
93
|
+
export function detectDelimiter(text) {
|
|
94
|
+
const source = String(text ?? "").replace(/^\u{FEFF}/u, "");
|
|
95
|
+
const lines = source.split(/\r\n|\n|\r/).map(stripQuoted).filter((line) => line.length);
|
|
96
|
+
if (lines.some((line) => line.includes("\t"))) return "\t";
|
|
97
|
+
for (const candidate of [";", ","]) {
|
|
98
|
+
const counts = lines.map((line) => line.split(candidate).length - 1);
|
|
99
|
+
if (counts.length && counts[0] > 0 && counts.every((count) => count === counts[0])) return candidate;
|
|
100
|
+
}
|
|
101
|
+
// Ragged but delimited: take the candidate that appears most.
|
|
102
|
+
let best = "\t";
|
|
103
|
+
let most = 0;
|
|
104
|
+
for (const candidate of [";", ","]) {
|
|
105
|
+
const total = lines.reduce((sum, line) => sum + line.split(candidate).length - 1, 0);
|
|
106
|
+
if (total > most) {
|
|
107
|
+
most = total;
|
|
108
|
+
best = candidate;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return best;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Read pasted TSV or CSV text into rows of strings. The delimiter is detected unless `options.delimiter`
|
|
116
|
+
* is given (one of tab, semicolon, comma). A byte-order mark is dropped and the final line break
|
|
117
|
+
* is not a row. Rows keep their own lengths; use {@link rectangular} to pad them. Never reads numbers:
|
|
118
|
+
* every field stays text. Throws `GridTextError` (`unclosed-quote`, `invalid-delimiter`).
|
|
119
|
+
*/
|
|
120
|
+
export function parseDelimited(text, options = {}) {
|
|
121
|
+
if (typeof text !== "string") throw new GridTextError("invalid-text", "Expected text.");
|
|
122
|
+
const source = text.replace(/^\u{FEFF}/u, "");
|
|
123
|
+
const delimiter = options.delimiter ?? detectDelimiter(source);
|
|
124
|
+
if (!CANDIDATES.includes(delimiter)) throw new GridTextError("invalid-delimiter", "The delimiter is a tab, a semicolon or a comma.", { delimiter });
|
|
125
|
+
if (source === "") return { rows: [], delimiter };
|
|
126
|
+
return { rows: split(source, delimiter), delimiter };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Pad ragged rows with empty text to the longest row. Returns `{ rows, width }`. */
|
|
130
|
+
export function rectangular(rows) {
|
|
131
|
+
const width = rows.reduce((most, row) => Math.max(most, row.length), 0);
|
|
132
|
+
return { rows: rows.map((row) => (row.length < width ? [...row, ...Array(width - row.length).fill("")] : row)), width };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Write rows of text as TSV (default) or with another delimiter. A field is quoted when it holds the
|
|
137
|
+
* delimiter, a double quote or a line break, so a spreadsheet pastes it back cell for cell.
|
|
138
|
+
*/
|
|
139
|
+
export function toDelimited(rows, options = {}) {
|
|
140
|
+
const delimiter = options.delimiter ?? "\t";
|
|
141
|
+
const quote = (value) => {
|
|
142
|
+
const text = value === null || value === undefined ? "" : String(value);
|
|
143
|
+
return /["\r\n]/.test(text) || text.includes(delimiter) ? `"${text.replaceAll('"', '""')}"` : text;
|
|
144
|
+
};
|
|
145
|
+
return rows.map((row) => row.map(quote).join(delimiter)).join(options.newline ?? "\n");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// --- numbers -------------------------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
/** The number formats a grid reads and writes. */
|
|
151
|
+
export const NUMBER_FORMATS = Object.freeze({
|
|
152
|
+
".": Object.freeze({ decimal: ".", label: "1,234.56" }),
|
|
153
|
+
",": Object.freeze({ decimal: ",", label: "1.234,56" }),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The format for `format` ("." or ",", or "auto" for the locale's own decimal separator). `locale` is a
|
|
158
|
+
* BCP 47 tag; without one, or for a locale this runtime does not know, the decimal point is used.
|
|
159
|
+
*/
|
|
160
|
+
export function resolveNumberFormat(format = "auto", locale) {
|
|
161
|
+
if (format === "." || format === ",") return NUMBER_FORMATS[format];
|
|
162
|
+
let decimal = ".";
|
|
163
|
+
try {
|
|
164
|
+
const part = new Intl.NumberFormat(locale || undefined).formatToParts(1.1).find((entry) => entry.type === "decimal")?.value;
|
|
165
|
+
// A decimal comma, or the Arabic decimal separator, reads as a comma; everything else as a point.
|
|
166
|
+
if (part === "," || part === "\u{66B}") decimal = ",";
|
|
167
|
+
} catch {
|
|
168
|
+
decimal = ".";
|
|
169
|
+
}
|
|
170
|
+
return NUMBER_FORMATS[decimal];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const CURRENCY = "[$€£¥₹₩₽₺₪฿₫₴₦₱₡]";
|
|
174
|
+
const SPACE_LIKE = /[ \xa0\u{2007}\u{2009}\u{202F}]/gu;
|
|
175
|
+
const alternate = (decimal) => (decimal === "." ? "," : ".");
|
|
176
|
+
const formatLabel = (decimal) => NUMBER_FORMATS[decimal].label;
|
|
177
|
+
|
|
178
|
+
function readBody(body, decimal) {
|
|
179
|
+
const group = decimal === "." ? "," : ".";
|
|
180
|
+
const esc = (char) => (char === "." ? "\\." : char);
|
|
181
|
+
const match = new RegExp(`^(\\d+|\\d{1,3}(?:[${esc(group)} '\\u2019]\\d{3})+)?(?:${esc(decimal)}(\\d+))?(?:[eE]([+-]?\\d+))?$`).exec(body);
|
|
182
|
+
if (!match || (match[1] === undefined && match[2] === undefined)) return undefined;
|
|
183
|
+
const integer = match[1] ?? "";
|
|
184
|
+
// One grouping character throughout: 1,234,567 or 1 234 567, never 1,234 567.
|
|
185
|
+
const groups = new Set(integer.replace(/\d/g, "").split(""));
|
|
186
|
+
if (groups.size > 1) return undefined;
|
|
187
|
+
return Number(`${integer.replace(/[^\d]/g, "") || "0"}${match[2] === undefined ? "" : `.${match[2]}`}${match[3] === undefined ? "" : `e${match[3]}`}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Read one number written in a number format (`options.decimal`, "." or ","). Returns `{ empty: true }` for
|
|
192
|
+
* blank text, `{ value }` for a number and `{ error }` (a sentence for the person) for anything else.
|
|
193
|
+
*
|
|
194
|
+
* Accepted: an optional sign (or accounting parentheses, or a Unicode minus), one currency symbol at
|
|
195
|
+
* either end, grouping with the other separator, spaces, a non-breaking or thin space or an apostrophe
|
|
196
|
+
* in groups of three, the decimal separator of the format, and an exponent (1.5e3). Refused, with a
|
|
197
|
+
* reason: a percent sign (45% is 45 or 0.45, the cell cannot know), the other format's separators
|
|
198
|
+
* where the digits would change meaning (1,5 in "1,234.56"), letters, "NaN" and "Infinity", and
|
|
199
|
+
* integers beyond 2^53. Nothing is ever read as 0.
|
|
200
|
+
*/
|
|
201
|
+
export function parseGridNumber(input, options = {}) {
|
|
202
|
+
const decimal = options.decimal === "," ? "," : ".";
|
|
203
|
+
if (typeof input === "number") return Number.isFinite(input) ? { value: input } : { error: "Infinity and NaN are not chart values." };
|
|
204
|
+
if (typeof input === "boolean") return { error: `${input} is not a number.` };
|
|
205
|
+
let text = input === null || input === undefined ? "" : String(input).trim();
|
|
206
|
+
if (text === "") return { empty: true };
|
|
207
|
+
const original = text;
|
|
208
|
+
text = text.replace(/\u{2212}/gu, "-");
|
|
209
|
+
let negative = false;
|
|
210
|
+
const parenthesized = /^\((.*)\)$/.exec(text);
|
|
211
|
+
if (parenthesized) {
|
|
212
|
+
negative = true;
|
|
213
|
+
text = parenthesized[1].trim();
|
|
214
|
+
}
|
|
215
|
+
let sign = /^[+-]/.exec(text)?.[0] ?? "";
|
|
216
|
+
text = text.slice(sign.length).trim();
|
|
217
|
+
text = text.replace(new RegExp(`^${CURRENCY}\\s*`), "");
|
|
218
|
+
if (!sign) {
|
|
219
|
+
sign = /^[+-]/.exec(text)?.[0] ?? "";
|
|
220
|
+
text = text.slice(sign.length);
|
|
221
|
+
}
|
|
222
|
+
text = text.replace(new RegExp(`\\s*${CURRENCY}$`), "").trim();
|
|
223
|
+
if (text.includes("%"))
|
|
224
|
+
return { error: `"${original}" has a percent sign, which a chart value cannot carry. Enter 45 to mean 45 percent, or 0.45 for a fraction.` };
|
|
225
|
+
const value = readBody(text.replace(SPACE_LIKE, " "), decimal);
|
|
226
|
+
if (value === undefined || !Number.isFinite(value)) {
|
|
227
|
+
const other = alternate(decimal);
|
|
228
|
+
const elsewhere = readBody(text.replace(SPACE_LIKE, " "), other);
|
|
229
|
+
const hint = elsewhere !== undefined && Number.isFinite(elsewhere)
|
|
230
|
+
? ` It reads as a number in the ${formatLabel(other)} format: switch the number format, or retype it with a ${decimal === "." ? "period" : "comma"} for the decimal.`
|
|
231
|
+
: ` Use digits, an optional sign and the ${formatLabel(decimal)} format.`;
|
|
232
|
+
return { error: `"${original}" is not a number in the ${formatLabel(decimal)} format.${hint}` };
|
|
233
|
+
}
|
|
234
|
+
let result = (negative ? -1 : 1) * (sign === "-" ? -1 : 1) * value;
|
|
235
|
+
if (Number.isInteger(result) && !Number.isSafeInteger(result)) return { error: `"${original}" is too large to keep exactly.` };
|
|
236
|
+
if (Object.is(result, -0)) result = 0;
|
|
237
|
+
return { value: result };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Write a number in a number format: the shortest text that reads back the same, no grouping. */
|
|
241
|
+
export function formatGridNumber(value, options = {}) {
|
|
242
|
+
const text = String(value);
|
|
243
|
+
return options.decimal === "," ? text.replace(".", ",") : text;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Whether `text` is exactly how JavaScript writes the number it reads as (12, -3.5, 1e21), so storing it as a number changes nothing visible. */
|
|
247
|
+
export function isCanonicalNumber(text) {
|
|
248
|
+
if (typeof text !== "string" || text === "" || text !== text.trim()) return false;
|
|
249
|
+
const value = Number(text);
|
|
250
|
+
return Number.isFinite(value) && String(value) === text && !Object.is(value, -0);
|
|
251
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { EditorChange, EditorSession, JsonPatchOperation } from "./index.js";
|
|
2
|
+
|
|
3
|
+
/** The smallest crop side, in source pixels. */
|
|
4
|
+
export declare const MIN_CROP_SIDE: number;
|
|
5
|
+
export interface Rect { x: number; y: number; width: number; height: number }
|
|
6
|
+
export interface Size { width: number; height: number }
|
|
7
|
+
export interface CropAspect { id: string; label: string; ratio?: number }
|
|
8
|
+
/** Free, Original, Frame, 1:1, 4:3, 3:2, 16:9, 3:4, 2:3 and 9:16. */
|
|
9
|
+
export declare const CROP_ASPECTS: readonly CropAspect[];
|
|
10
|
+
/** The width / height ratio of an aspect id for an image and a frame, or undefined for "free". `frame` is the frame's width / height. */
|
|
11
|
+
export declare function aspectRatioFor(id: string | undefined, context?: { width?: number; height?: number; frame?: number }): number | undefined;
|
|
12
|
+
export declare function fullRect(width: number, height: number): Rect;
|
|
13
|
+
/** Keep a rectangle inside the image and at least `min` on each side. */
|
|
14
|
+
export declare function clampRect(rect: Rect, bounds: Size, min?: number): Rect;
|
|
15
|
+
export declare function moveRect(rect: Rect, dx: number, dy: number, bounds: Size): Rect;
|
|
16
|
+
/** Drag a handle (`n`, `ne`, `e`, `se`, `s`, `sw`, `w`, `nw`) to a source-pixel point; `aspect` (width / height) keeps the ratio. */
|
|
17
|
+
export declare function resizeRect(start: Rect, handle: string, point: { x: number; y: number }, bounds: Size, options?: { aspect?: number; min?: number }): Rect;
|
|
18
|
+
/** The largest rectangle with `aspect` inside `rect`, centred on it. */
|
|
19
|
+
export declare function fitAspect(rect: Rect, aspect: number | undefined): Rect;
|
|
20
|
+
/** The window a focal point asks for: the largest `aspect` window divided by `zoom`, centred on `focal` (fractions) and inside the image. */
|
|
21
|
+
export declare function focalWindow(bounds: Size, aspect: number, focal: { x: number; y: number }, zoom?: number): Rect;
|
|
22
|
+
export declare function focalPointOf(rect: Rect, bounds: Size): { x: number; y: number };
|
|
23
|
+
export declare function roundRect(rect: Rect, bounds: Size): Rect;
|
|
24
|
+
export declare function isFullRect(rect: Rect, bounds: Size): boolean;
|
|
25
|
+
|
|
26
|
+
export interface ImageDescription {
|
|
27
|
+
path: string;
|
|
28
|
+
pointer: string;
|
|
29
|
+
form: "string" | "object";
|
|
30
|
+
/** JSON pointer of the string that holds the source (the field itself, or its `src`). */
|
|
31
|
+
srcPointer: string;
|
|
32
|
+
src: string;
|
|
33
|
+
/** The `assets` id when `src` is an `asset:` reference. */
|
|
34
|
+
assetId?: string;
|
|
35
|
+
/** What the source resolves to: the asset's `src`, or `src` itself. */
|
|
36
|
+
assetSrc: string;
|
|
37
|
+
alt?: string;
|
|
38
|
+
mediaType?: string;
|
|
39
|
+
/** The asset a crop of this picture was made from, when there is one. */
|
|
40
|
+
origin?: string;
|
|
41
|
+
entry?: unknown;
|
|
42
|
+
}
|
|
43
|
+
/** What a path holds when it is a picture, or `{ error }` with the reason it is not croppable (not a picture, a missing asset, SVG). */
|
|
44
|
+
export declare function describeImage(document: unknown, path: string): ImageDescription | { error: string };
|
|
45
|
+
export declare function countAssetReferences(document: unknown, id: string): number;
|
|
46
|
+
export interface CroppedPixels { dataUri: string; mediaType: string; width: number; height: number; bytes?: number }
|
|
47
|
+
export interface PreparedCrop { patches: JsonPatchOperation[]; assetId: string; reference: string; origin?: string; image: ImageDescription }
|
|
48
|
+
/** The patches that apply a cropped picture: a new asset and the image pointing at it (pure). Throws `not-croppable`. */
|
|
49
|
+
export declare function prepareCrop(document: unknown, path: string, pixels: CroppedPixels): PreparedCrop;
|
|
50
|
+
/** The patches that put a cropped picture back to its original asset, or null. */
|
|
51
|
+
export declare function prepareRestore(document: unknown, path: string): { patches: JsonPatchOperation[]; origin: string; image: ImageDescription } | null;
|
|
52
|
+
/** Restore the original as one undoable change; null when there is no original. */
|
|
53
|
+
export declare function restoreOriginal(editor: EditorSession, path: string, meta?: Record<string, unknown>): (EditorChange & { origin: string }) | null;
|
|
54
|
+
export interface LoadedImage { image: HTMLImageElement; width: number; height: number }
|
|
55
|
+
export declare function loadImagePixels(src: string, options?: { document?: Document; signal?: AbortSignal }): Promise<LoadedImage>;
|
|
56
|
+
/** Cut a rectangle out of a loaded picture at its own resolution (JPEG stays JPEG, everything else PNG). Needs a browser canvas. */
|
|
57
|
+
export declare function cropImagePixels(loaded: LoadedImage, rect: Rect, options?: { mediaType?: string; maxBytes?: number; document?: Document }): Promise<CroppedPixels>;
|
|
58
|
+
/** Crop a picture of the document and apply it as one undoable change. */
|
|
59
|
+
export declare function applyCrop(editor: EditorSession, path: string, rect: Rect, options?: { loaded?: LoadedImage; maxBytes?: number; meta?: Record<string, unknown> }): Promise<EditorChange & { assetId: string; reference: string; width: number; height: number }>;
|