@openpresentation/opf 0.6.0 → 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.
@@ -1,4 +1,4 @@
1
- import { a as SchemaName } from './schemas-C1DxapX2.js';
1
+ import { a as SchemaName } from './schemas--QRVBhU1.js';
2
2
 
3
3
  /**
4
4
  * Schema for audience records in the pptx.gallery library. Each record names an audience archetype (e.g. 'executives', 'engineering-team', 'investors') and carries seniority, technical-fluency, decision-power, and attention-budget hints used by AI-driven generation. Audiences are referenced from OPF documents via audience; the engine resolves the reference against catalogs.audiences (inline) → catalogs.audiences.source → the default catalog at https://www.pptx.gallery/audiences. The audience field also accepts free-form strings; this catalog is for engine-aware audience archetypes that carry generation hints.
@@ -1,2 +1,2 @@
1
- export { C as CatalogEntry, a as CatalogIndex, b as CatalogIndexRecord, c as CatalogKind, d as CatalogRecord, i as audiences, j as catalogEntries, k as catalogIndexes, l as catalogKinds, m as catalogSchemaNames, n as catalogs, o as chartTypes, p as colorSchemes, q as fontSchemes, r as languages, s as layouts, t as narratives, u as purposes, v as socialPlatforms, w as themes, x as tones } from './catalogs-x1oFFQnd.js';
2
- import './schemas-C1DxapX2.js';
1
+ export { C as CatalogEntry, a as CatalogIndex, b as CatalogIndexRecord, c as CatalogKind, d as CatalogRecord, i as audiences, j as catalogEntries, k as catalogIndexes, l as catalogKinds, m as catalogSchemaNames, n as catalogs, o as chartTypes, p as colorSchemes, q as fontSchemes, r as languages, s as layouts, t as narratives, u as purposes, v as socialPlatforms, w as themes, x as tones } from './catalogs-BnnkUX9O.js';
2
+ import './schemas--QRVBhU1.js';
@@ -3688,10 +3688,16 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
3688
3688
  "items": {
3689
3689
  "$ref": "#/$defs/TextRun"
3690
3690
  }
3691
+ },
3692
+ {
3693
+ "$ref": "#/$defs/StyledTableCell"
3694
+ },
3695
+ {
3696
+ "type": "null"
3691
3697
  }
3692
3698
  ]
3693
3699
  },
3694
- "description": "Optional column labels rendered above table rows. Each label is a string or an array of TextRun values for inline formatting.",
3700
+ "description": "Optional column labels. Labels may be strings, rich runs or styled cell objects. Null is an empty label or a placeholder covered by a preceding column span.",
3695
3701
  "examples": [
3696
3702
  [
3697
3703
  "Region",
@@ -3862,6 +3868,17 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
3862
3868
  "description": "A cell in inline chart data."
3863
3869
  },
3864
3870
  "TableCell": {
3871
+ "oneOf": [
3872
+ {
3873
+ "$ref": "#/$defs/TableCellValue"
3874
+ },
3875
+ {
3876
+ "$ref": "#/$defs/StyledTableCell"
3877
+ }
3878
+ ],
3879
+ "description": "A scalar, rich-run array, or styled/spanning cell object. Existing scalar and rich forms remain valid."
3880
+ },
3881
+ "TableCellValue": {
3865
3882
  "oneOf": [
3866
3883
  {
3867
3884
  "type": "string"
@@ -3882,7 +3899,149 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
3882
3899
  }
3883
3900
  }
3884
3901
  ],
3885
- "description": "A cell in table content. Scalar values keep their text representation; TextRun arrays preserve inline formatting and use mixed-style text measurement."
3902
+ "description": "A scalar table value or canonical rich text runs, without cell decoration or geometry."
3903
+ },
3904
+ "StyledTableCell": {
3905
+ "type": "object",
3906
+ "additionalProperties": false,
3907
+ "required": [
3908
+ "value"
3909
+ ],
3910
+ "properties": {
3911
+ "value": {
3912
+ "$ref": "#/$defs/TableCellValue",
3913
+ "description": "Editable cell content; styling and spans do not change its scalar type or rich runs."
3914
+ },
3915
+ "style": {
3916
+ "$ref": "#/$defs/TableCellStyle"
3917
+ },
3918
+ "colSpan": {
3919
+ "type": "integer",
3920
+ "minimum": 1,
3921
+ "description": "Number of grid columns covered, starting at this cell. Covered positions must contain null. Default 1."
3922
+ },
3923
+ "rowSpan": {
3924
+ "type": "integer",
3925
+ "minimum": 1,
3926
+ "description": "Number of grid rows covered, starting at this cell. Covered positions must contain null. Header cells cannot span into body rows. Default 1."
3927
+ }
3928
+ },
3929
+ "description": "A cell with explicit visual style or merged geometry. Its position remains its array column index; use null placeholders for every covered grid position."
3930
+ },
3931
+ "TableCellStyle": {
3932
+ "type": "object",
3933
+ "additionalProperties": false,
3934
+ "properties": {
3935
+ "fill": {
3936
+ "type": "string",
3937
+ "pattern": "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$",
3938
+ "description": "Explicit RGB or RGBA color. Eight-digit colors include alpha; #00000000 is transparent."
3939
+ },
3940
+ "color": {
3941
+ "type": "string",
3942
+ "pattern": "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$",
3943
+ "description": "Default text color, overridden by individual rich run colors."
3944
+ },
3945
+ "align": {
3946
+ "type": "string",
3947
+ "enum": [
3948
+ "left",
3949
+ "center",
3950
+ "right"
3951
+ ],
3952
+ "description": "Horizontal text alignment inside the cell."
3953
+ },
3954
+ "verticalAlign": {
3955
+ "type": "string",
3956
+ "enum": [
3957
+ "top",
3958
+ "middle",
3959
+ "bottom"
3960
+ ],
3961
+ "description": "Vertical alignment inside the padded cell box."
3962
+ },
3963
+ "padding": {
3964
+ "$ref": "#/$defs/TableCellPadding"
3965
+ },
3966
+ "borders": {
3967
+ "type": "object",
3968
+ "additionalProperties": false,
3969
+ "properties": {
3970
+ "top": {
3971
+ "$ref": "#/$defs/TableCellBorder"
3972
+ },
3973
+ "right": {
3974
+ "$ref": "#/$defs/TableCellBorder"
3975
+ },
3976
+ "bottom": {
3977
+ "$ref": "#/$defs/TableCellBorder"
3978
+ },
3979
+ "left": {
3980
+ "$ref": "#/$defs/TableCellBorder"
3981
+ }
3982
+ },
3983
+ "description": "Independent cell edges. Omitted edges retain the table theme border; width 0 removes an edge."
3984
+ }
3985
+ },
3986
+ "description": "Cell appearance. Sizes use reference pixels at a 720-pixel canvas short edge and scale with the slide."
3987
+ },
3988
+ "TableCellPadding": {
3989
+ "type": "object",
3990
+ "additionalProperties": false,
3991
+ "properties": {
3992
+ "top": {
3993
+ "type": "number",
3994
+ "minimum": 0,
3995
+ "maximum": 200
3996
+ },
3997
+ "right": {
3998
+ "type": "number",
3999
+ "minimum": 0,
4000
+ "maximum": 200
4001
+ },
4002
+ "bottom": {
4003
+ "type": "number",
4004
+ "minimum": 0,
4005
+ "maximum": 200
4006
+ },
4007
+ "left": {
4008
+ "type": "number",
4009
+ "minimum": 0,
4010
+ "maximum": 200
4011
+ }
4012
+ },
4013
+ "description": "Text insets in reference pixels. Defaults: top 8, right 10, bottom 4, left 10."
4014
+ },
4015
+ "TableCellBorder": {
4016
+ "type": "object",
4017
+ "additionalProperties": false,
4018
+ "required": [
4019
+ "color",
4020
+ "width"
4021
+ ],
4022
+ "properties": {
4023
+ "color": {
4024
+ "type": "string",
4025
+ "pattern": "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$",
4026
+ "description": "Explicit RGB or RGBA color. Eight-digit colors include alpha; #00000000 is transparent."
4027
+ },
4028
+ "width": {
4029
+ "type": "number",
4030
+ "minimum": 0,
4031
+ "maximum": 32,
4032
+ "description": "Border width in reference pixels; 0 removes this edge."
4033
+ },
4034
+ "dash": {
4035
+ "type": "string",
4036
+ "enum": [
4037
+ "solid",
4038
+ "dash",
4039
+ "dot"
4040
+ ],
4041
+ "description": "Default solid."
4042
+ }
4043
+ },
4044
+ "description": "One explicit cell border."
3886
4045
  },
3887
4046
  "Catalogs": {
3888
4047
  "type": "object",
@@ -1,5 +1,5 @@
1
- import { MAX_COMPOSITION_DEPTH } from './chunk-C7GKRLI6.js';
2
- import { schemas } from './chunk-KSN6RQG2.js';
1
+ import { MAX_COMPOSITION_DEPTH, tableGrid } from './chunk-OVPWNQIJ.js';
2
+ import { schemas } from './chunk-GQSK3T7X.js';
3
3
  import { catalogSchemaNames } from './chunk-TWMRZ43O.js';
4
4
  import Ajv2020 from 'ajv/dist/2020.js';
5
5
  import addFormats from 'ajv-formats';
@@ -802,6 +802,14 @@ function validatePresentationSemantics(value) {
802
802
  slideIds.add(slide.id);
803
803
  }
804
804
  issues.push(...validateSlideRegions(slide, `/slides/${index}`));
805
+ const visitTables = (node, path) => {
806
+ if (isRecord(node.table)) for (const issue of tableGrid(node.table).issues) issues.push(semanticIssue(pathFor(path, "table") + issue.path.slice(5).replaceAll(".", "/"), issue.message));
807
+ if (Array.isArray(node.blocks)) node.blocks.forEach((block, i) => {
808
+ if (isRecord(block)) visitTables(block, `${path}/blocks/${i}`);
809
+ });
810
+ for (const key of promotedRegionKeys) if (isRecord(node[key])) visitTables(node[key], pathFor(path, key));
811
+ };
812
+ visitTables(slide, `/slides/${index}`);
805
813
  }
806
814
  });
807
815
  return issues;
@@ -1,5 +1,5 @@
1
- import { assertValidPresentation } from './chunk-23MXJL2K.js';
2
- import { resolveFontFamilies, resolveCanvasDimensions, composeSlide } from './chunk-C7GKRLI6.js';
1
+ import { assertValidPresentation } from './chunk-H7UXKXEJ.js';
2
+ import { resolveFontFamilies, resolveCanvasDimensions, composeSlide, tableRowBoundaries } from './chunk-OVPWNQIJ.js';
3
3
  import { catalogs } from './chunk-TWMRZ43O.js';
4
4
 
5
5
  // src/pagination.ts
@@ -52,7 +52,7 @@ function leafFor(path, field, value) {
52
52
  const values = Array.isArray(value) ? value : value[key];
53
53
  if (Array.isArray(values)) {
54
54
  leaf.unit = "items";
55
- leaf.boundaries = Array.from({ length: values.length + 1 }, (_, i) => i);
55
+ leaf.boundaries = field === "table" ? tableRowBoundaries(value) : Array.from({ length: values.length + 1 }, (_, i) => i);
56
56
  leaf.slice = (a, b) => Array.isArray(value) ? value.slice(a, b) : { ...value, [key]: values.slice(a, b) };
57
57
  }
58
58
  }
@@ -1,8 +1,45 @@
1
+ // src/table.ts
2
+ var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
3
+ function tableGrid(value, path = "table") {
4
+ const table = record(value), hasHeaders = Array.isArray(table.columns) && table.columns.length > 0;
5
+ const values = [...hasHeaders ? [table.columns] : [], ...Array.isArray(table.rows) ? table.rows : []];
6
+ const columnCount = values.reduce((count, row) => Math.max(count, Array.isArray(row) ? row.length : 0), 1), rowCount = values.length;
7
+ const owners = Array.from({ length: rowCount }, () => Array(columnCount));
8
+ const rows2 = Array.from({ length: rowCount }, () => []), issues = [];
9
+ const location = (r, c) => `${path}.${hasHeaders && r === 0 ? "columns" : `rows.${r - Number(hasHeaders)}`}.${c}`;
10
+ for (let r = 0; r < rowCount; r++) for (let c = 0; c < columnCount; c++) {
11
+ const input = values[r]?.[c], cellPath = location(r, c), existing = owners[r][c];
12
+ if (existing) {
13
+ if (input !== null) issues.push({ path: cellPath, message: "A position covered by a spanning cell must explicitly contain null; content cannot be hidden." });
14
+ continue;
15
+ }
16
+ const object = record(input), styled = Object.hasOwn(object, "value");
17
+ const rowSpan = styled ? object.rowSpan ?? 1 : 1, colSpan = styled ? object.colSpan ?? 1 : 1;
18
+ if (!Number.isSafeInteger(rowSpan) || !Number.isSafeInteger(colSpan) || rowSpan < 1 || colSpan < 1 || r + rowSpan > rowCount || c + colSpan > columnCount || hasHeaders && r === 0 && rowSpan > 1) {
19
+ issues.push({ path: cellPath, message: "Cell spans must be positive integers within the table grid and cannot cross from headers into body rows." });
20
+ continue;
21
+ }
22
+ const cell = { input, value: styled ? object.value : input, style: styled ? record(object.style) : {}, row: r, column: c, rowSpan, colSpan, header: hasHeaders && r === 0, path: cellPath, valuePath: styled ? cellPath + ".value" : cellPath };
23
+ rows2[r].push(cell);
24
+ for (let y = r; y < r + rowSpan; y++) for (let x = c; x < c + colSpan; x++) {
25
+ if (owners[y][x]) issues.push({ path: cellPath, message: "Cell spans cannot overlap another spanning cell." });
26
+ else owners[y][x] = cell;
27
+ }
28
+ }
29
+ return { rows: rows2, columnCount, rowCount, hasHeaders, owners, issues };
30
+ }
31
+ function tableRowBoundaries(value) {
32
+ const grid = tableGrid(value), offset = Number(grid.hasHeaders), count = grid.rowCount - offset;
33
+ const allowed = new Set(Array.from({ length: count + 1 }, (_, i) => i));
34
+ for (const row of grid.rows) for (const cell of row) if (!cell.header) for (let r = 1; r < cell.rowSpan; r++) allowed.delete(cell.row - offset + r);
35
+ return [...allowed];
36
+ }
37
+
1
38
  // src/composition.ts
2
39
  var MAX_COMPOSITION_DEPTH = 32;
3
40
  function resolveFontFamilies(input) {
4
- const scheme = record(input);
5
- const family = (value) => typeof value === "string" ? value : record(value).family;
41
+ const scheme = record2(input);
42
+ const family = (value) => typeof value === "string" ? value : record2(value).family;
6
43
  return {
7
44
  heading: family(scheme.heading) ?? scheme.major ?? scheme.minor ?? "Roboto",
8
45
  body: family(scheme.body) ?? scheme.minor ?? scheme.major ?? "Roboto",
@@ -33,7 +70,7 @@ var fields = ["text", "items", "bullets", "image", "video", "chart", "table", "c
33
70
  var headings = /* @__PURE__ */ new Set(["title", "subtitle", "tag"]);
34
71
  var rows = ["top", "middle", "bottom"];
35
72
  var columns = ["left", "center", "right"];
36
- var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
73
+ var record2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
37
74
  var kind = (field) => field === "items" || field === "bullets" ? "list" : field;
38
75
  var round = (value) => Math.round(value * 1e6) / 1e6 || value;
39
76
  var textSegments = new Intl.Segmenter("und", { granularity: "grapheme" });
@@ -236,53 +273,61 @@ function fitList(input, box, requestedSize = 25, minFontSize = 16, options = { s
236
273
  function contentText(field, value) {
237
274
  if (field === "text" || headings.has(field)) return flatten(value);
238
275
  if (field === "items" || field === "bullets") return (Array.isArray(value) ? value : [value]).map((item) => {
239
- const entry = record(item);
276
+ const entry = record2(item);
240
277
  return `\u2022 ${flatten(item)}${entry.description ? `
241
278
  ${flatten(entry.description)}` : ""}`;
242
279
  }).join("\n");
243
- if (field === "code") return flatten(record(value).source ?? value);
244
- if (field === "quote") return flatten(record(value).text ?? value);
280
+ if (field === "code") return flatten(record2(value).source ?? value);
281
+ if (field === "quote") return flatten(record2(value).text ?? value);
245
282
  return void 0;
246
283
  }
247
284
  function layoutTable(value, box, options = {}) {
248
285
  const scale = options.scale ?? 1, requested = 15 * scale, minimum = (options.minFontSize ?? 16) * scale;
249
286
  if (![box.x, box.y, box.width, box.height, scale, minimum].every(Number.isFinite) || box.width <= 0 || box.height <= 0 || scale <= 0 || minimum <= 0) throw new RangeError("Table dimensions, scale and font sizes must be finite and positive.");
250
- const table = record(value), headers = Array.isArray(table.columns) && table.columns.length > 0;
251
- const values = [...headers ? [table.columns] : [], ...Array.isArray(table.rows) ? table.rows : []];
252
- const columnCount = Math.max(1, ...values.map((row) => row.length)), cellWidth = box.width / columnCount;
253
- const width = Math.max(scale, cellWidth - 20 * scale), padding = 12 * scale;
287
+ const grid = tableGrid(value, options.path ?? "table");
288
+ if (grid.issues.length) throw new RangeError(`${grid.issues[0].path}: ${grid.issues[0].message}`);
289
+ const columnCount = grid.columnCount, cellWidth = box.width / columnCount;
290
+ const cells = grid.rows.flat().map((cell) => {
291
+ const padding = { top: 8, right: 10, bottom: 4, left: 10, ...cell.style.padding };
292
+ const width = cellWidth * cell.colSpan - (padding.left + padding.right) * scale;
293
+ const style = { fontFamily: options.fontFamily ?? "sans-serif", fontWeight: cell.header ? 700 : 400, italic: false, path: cell.valuePath };
294
+ return { ...cell, padding, width, textStyle: style };
295
+ });
254
296
  const fitCell = (cell, height, min) => {
255
- const textBox = { width, height: Math.max(scale, height) };
256
- return Array.isArray(cell.value) ? fitRichText(cell.value, textBox, requested, min, { style: cell.style, textMeasurement: options.textMeasurement, uniformLineHeight: true }) : fitText(flatten(cell.value), textBox, requested, min, textWidthMeasurer(resolveTextStyle(cell.style, options.textMeasurement), options.textMeasurement));
297
+ const textBox = { width: Math.max(scale, cell.width), height: Math.max(scale, height) };
298
+ return Array.isArray(cell.value) ? fitRichText(cell.value, textBox, requested, min, { style: cell.textStyle, textMeasurement: options.textMeasurement, uniformLineHeight: true }) : fitText(flatten(cell.value), textBox, requested, min, textWidthMeasurer(resolveTextStyle(cell.textStyle, options.textMeasurement), options.textMeasurement));
257
299
  };
258
300
  const textHeight = (fit) => "height" in fit ? fit.height : fit.lines.length * fit.lineHeight;
259
- const measured = values.map((row, index) => {
260
- const header = headers && index === 0;
261
- const cells = Array.from({ length: columnCount }, (_, column) => {
262
- const path = `${options.path ?? "table"}.${header ? "columns" : `rows.${index - Number(headers)}`}.${column}`;
263
- return { value: row[column], path, header, style: { fontFamily: options.fontFamily ?? "sans-serif", fontWeight: header ? 700 : 400, italic: false, path } };
264
- });
265
- const natural2 = Math.max(...cells.map((cell) => textHeight(fitCell(cell, scale, requested)) + padding));
266
- const preferred2 = Math.max(54 * scale, natural2);
267
- const needed2 = Math.min(preferred2, Math.max(...cells.map((cell) => textHeight(fitCell(cell, scale, minimum)) + padding)));
268
- return { cells, preferred: preferred2, natural: natural2, needed: needed2 };
269
- });
270
- const preferred = measured.reduce((sum, row) => sum + row.preferred, 0), natural = measured.reduce((sum, row) => sum + row.natural, 0), needed = measured.reduce((sum, row) => sum + row.needed, 0);
271
- let y = box.y, overflow = cellWidth <= 20 * scale;
272
- const rows2 = measured.map((row) => {
273
- const height = preferred <= box.height ? row.preferred : natural <= box.height ? row.natural + (row.preferred - row.natural) * (box.height - natural) / Math.max(Number.EPSILON, preferred - natural) : needed <= box.height ? row.needed + (row.natural - row.needed) * (box.height - needed) / Math.max(Number.EPSILON, natural - needed) : row.needed * box.height / needed;
274
- const rowBox = { x: box.x, y, width: box.width, height };
275
- const cells = row.cells.map((cell, column) => {
276
- const cellBox = { x: box.x + column * cellWidth, y, width: cellWidth, height };
277
- const textBox = { x: cellBox.x + 10 * scale, y: y + 8 * scale, width, height: Math.max(scale, height - padding) };
278
- const fit = fitCell(cell, textBox.height, minimum);
279
- overflow ||= height <= padding || fit.overflow;
280
- return { value: cell.value, path: cell.path, header: cell.header, rich: Array.isArray(cell.value), box: cellBox, textBox, textStyle: resolveTextStyle(cell.style, options.textMeasurement), fit };
281
- });
282
- y += height;
283
- return { box: rowBox, cells };
284
- });
285
- return { rows: rows2, columnCount, height: y - box.y, overflow };
301
+ const required = (cell, size) => textHeight(fitCell(cell, scale, size)) + (cell.padding.top + cell.padding.bottom) * scale;
302
+ const natural = Array(grid.rowCount).fill(0), needed = Array(grid.rowCount).fill(0);
303
+ for (const cell of cells) if (cell.rowSpan === 1) {
304
+ natural[cell.row] = Math.max(natural[cell.row], required(cell, requested));
305
+ needed[cell.row] = Math.max(needed[cell.row], required(cell, minimum));
306
+ }
307
+ for (const cell of cells) if (cell.rowSpan > 1) for (const [heights2, size] of [[natural, requested], [needed, minimum]]) {
308
+ const current = heights2.slice(cell.row, cell.row + cell.rowSpan).reduce((a, b) => a + b, 0);
309
+ const extra = Math.max(0, required(cell, size) - current) / cell.rowSpan;
310
+ for (let r = cell.row; r < cell.row + cell.rowSpan; r++) heights2[r] += extra;
311
+ }
312
+ const preferred = natural.map((height) => Math.max(54 * scale, height));
313
+ for (let r = 0; r < needed.length; r++) needed[r] = Math.min(preferred[r], needed[r]);
314
+ const sum = (values) => values.reduce((a, b) => a + b, 0);
315
+ const preferredTotal = sum(preferred), naturalTotal = sum(natural), neededTotal = sum(needed);
316
+ const heights = preferred.map((height, r) => preferredTotal <= box.height ? height : naturalTotal <= box.height ? natural[r] + (height - natural[r]) * (box.height - naturalTotal) / Math.max(Number.EPSILON, preferredTotal - naturalTotal) : neededTotal <= box.height ? needed[r] + (natural[r] - needed[r]) * (box.height - neededTotal) / Math.max(Number.EPSILON, naturalTotal - neededTotal) : needed[r] * box.height / neededTotal);
317
+ const ys = [box.y];
318
+ for (const height of heights) ys.push(ys.at(-1) + height);
319
+ let overflow = false;
320
+ const rows2 = heights.map((height, r) => ({ box: { x: box.x, y: ys[r], width: box.width, height }, cells: [] }));
321
+ for (const cell of cells) {
322
+ const cellBox = { x: box.x + cell.column * cellWidth, y: ys[cell.row], width: cell.colSpan * cellWidth, height: ys[cell.row + cell.rowSpan] - ys[cell.row] };
323
+ const available = cellBox.height - (cell.padding.top + cell.padding.bottom) * scale;
324
+ const fit = fitCell(cell, available, minimum), height = textHeight(fit);
325
+ const offset = cell.style.verticalAlign === "bottom" ? Math.max(0, available - height) : cell.style.verticalAlign === "middle" ? Math.max(0, (available - height) / 2) : 0;
326
+ const textBox = { x: cellBox.x + cell.padding.left * scale, y: cellBox.y + cell.padding.top * scale + offset, width: Math.max(scale, cell.width), height: Math.max(scale, available - offset) };
327
+ overflow ||= cell.width <= 0 || available <= 0 || fit.overflow;
328
+ rows2[cell.row].cells.push({ value: cell.value, input: cell.input, path: cell.valuePath, sourcePath: cell.path, style: cell.style, row: cell.row, column: cell.column, rowSpan: cell.rowSpan, colSpan: cell.colSpan, header: cell.header, rich: Array.isArray(cell.value), box: cellBox, textBox, textStyle: resolveTextStyle(cell.textStyle, options.textMeasurement), fit });
329
+ }
330
+ return { rows: rows2, columnCount, height: sum(heights), overflow };
286
331
  }
287
332
  function tableOverflows(value, box, scale, settings, options, path) {
288
333
  return box.width <= 0 || box.height <= 0 || layoutTable(value, box, { scale, minFontSize: settings.minFontSize, fontFamily: options.fonts?.body, textMeasurement: options.textMeasurement, path }).overflow;
@@ -291,7 +336,7 @@ function flatten(value) {
291
336
  if (value == null) return "";
292
337
  if (typeof value !== "object") return String(value);
293
338
  if (Array.isArray(value)) return value.map(flatten).join("");
294
- const item = record(value);
339
+ const item = record2(value);
295
340
  return flatten(item.text ?? item.value ?? item.source ?? JSON.stringify(value));
296
341
  }
297
342
  function regionParts(key) {
@@ -347,11 +392,11 @@ function assertComposition(value) {
347
392
  if (value.overflow !== void 0 && !["warn", "error"].includes(value.overflow)) throw new RangeError("Invalid composition.overflow.");
348
393
  }
349
394
  function composeSlide(input, options = {}) {
350
- const slide = record(input), layout = record(options.layout);
395
+ const slide = record2(input), layout = record2(options.layout);
351
396
  const width = options.width ?? 1280, height = options.height ?? 720;
352
397
  if (![width, height].every((value) => Number.isFinite(value) && value > 0)) throw new RangeError("Canvas dimensions must be finite and positive.");
353
398
  const scale = Math.min(width, height) / 720;
354
- const composition = { ...record(layout.composition), ...record(slide.composition) };
399
+ const composition = { ...record2(layout.composition), ...record2(slide.composition) };
355
400
  assertComposition(composition);
356
401
  const padding = (composition.padding ?? 0.08) * Math.min(width, height);
357
402
  const gap = (composition.gap ?? 1 / 30) * Math.min(width, height);
@@ -381,7 +426,7 @@ function composeSlide(input, options = {}) {
381
426
  const collect = (host, basePath, depth = 0, ancestors = []) => {
382
427
  if (Array.isArray(host.blocks)) {
383
428
  if (depth >= MAX_COMPOSITION_DEPTH || ancestors.includes(host)) throw new RangeError(`Content groups must be acyclic and nest at most ${MAX_COMPOSITION_DEPTH} levels.`);
384
- const settings = record(host.composition);
429
+ const settings = record2(host.composition);
385
430
  assertComposition(settings);
386
431
  return [{
387
432
  field: "blocks",
@@ -390,13 +435,13 @@ function composeSlide(input, options = {}) {
390
435
  path: basePath,
391
436
  payload: host,
392
437
  composition: settings,
393
- children: host.blocks.flatMap((block, index) => collect(record(block), `${basePath}.blocks.${index}`, depth + 1, [...ancestors, host]))
438
+ children: host.blocks.flatMap((block, index) => collect(record2(block), `${basePath}.blocks.${index}`, depth + 1, [...ancestors, host]))
394
439
  }];
395
440
  }
396
441
  return fields.filter((field) => host[field] !== void 0).map((field) => ({ field, type: host.type ?? kind(field), value: host[field], path: `${basePath}.${field}`, payload: { type: host.type ?? kind(field), [field]: host[field] } }));
397
442
  };
398
443
  const regions = Object.keys(slide).filter((key) => regionParts(key)).sort();
399
- const pending = regions.length ? regions.flatMap((key) => collect(record(slide[key]), `${path}.${key}`).map((item) => ({ ...item, region: regionParts(key) }))) : Array.isArray(slide.blocks) ? slide.blocks.flatMap((block, index) => collect(record(block), `${path}.blocks.${index}`)) : collect(slide, path);
444
+ const pending = regions.length ? regions.flatMap((key) => collect(record2(slide[key]), `${path}.${key}`).map((item) => ({ ...item, region: regionParts(key) }))) : Array.isArray(slide.blocks) ? slide.blocks.flatMap((block, index) => collect(record2(block), `${path}.blocks.${index}`)) : collect(slide, path);
400
445
  const groups = [], flows = [];
401
446
  const inheritedSettings = (parent, own = {}) => ({
402
447
  minFontSize: parent.minFontSize,
@@ -488,7 +533,7 @@ function resolveCanvasDimensions(input) {
488
533
  letter: [11, 8.5],
489
534
  a4: [11.69, 8.27]
490
535
  };
491
- const value = record(input);
536
+ const value = record2(input);
492
537
  const preset = presets[typeof input === "string" ? input : value.preset] ?? presets.widescreen;
493
538
  const width = (value.widthInches ?? preset[0]) * 96;
494
539
  const height = (value.heightInches ?? preset[1]) * 96;
@@ -496,4 +541,4 @@ function resolveCanvasDimensions(input) {
496
541
  return { width, height };
497
542
  }
498
543
 
499
- export { MAX_COMPOSITION_DEPTH, OPFCompositionError, composeSlide, fitList, fitRichText, fitText, layoutTable, measureText, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, textWidthMeasurer, wrapText };
544
+ export { MAX_COMPOSITION_DEPTH, OPFCompositionError, composeSlide, fitList, fitRichText, fitText, layoutTable, measureText, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, tableGrid, tableRowBoundaries, textWidthMeasurer, wrapText };
@@ -1,3 +1,46 @@
1
+ /** Canonical table grid. No renderer, fonts, DOM or network dependencies. */
2
+ interface TableBorder {
3
+ color: string;
4
+ width: number;
5
+ dash?: 'solid' | 'dash' | 'dot';
6
+ }
7
+ interface TableCellStyle {
8
+ fill?: string;
9
+ color?: string;
10
+ align?: 'left' | 'center' | 'right';
11
+ verticalAlign?: 'top' | 'middle' | 'bottom';
12
+ padding?: Partial<Record<'top' | 'right' | 'bottom' | 'left', number>>;
13
+ borders?: Partial<Record<'top' | 'right' | 'bottom' | 'left', TableBorder>>;
14
+ }
15
+ interface TableGridCell {
16
+ input: unknown;
17
+ value: unknown;
18
+ style: TableCellStyle;
19
+ row: number;
20
+ column: number;
21
+ rowSpan: number;
22
+ colSpan: number;
23
+ header: boolean;
24
+ path: string;
25
+ valuePath: string;
26
+ }
27
+ interface TableGridIssue {
28
+ path: string;
29
+ message: string;
30
+ }
31
+ interface TableGrid {
32
+ rows: TableGridCell[][];
33
+ columnCount: number;
34
+ rowCount: number;
35
+ hasHeaders: boolean;
36
+ /** Anchor at each covered position; covered positions are not additional cells. */
37
+ owners: (TableGridCell | undefined)[][];
38
+ issues: TableGridIssue[];
39
+ }
40
+ declare function tableGrid(value: unknown, path?: string): TableGrid;
41
+ /** Row boundaries that preserve every vertical merge, used by pagination. */
42
+ declare function tableRowBoundaries(value: unknown): number[];
43
+
1
44
  /** Portable layout geometry. No fonts, DOM, renderer, or network dependencies. */
2
45
  interface Composition {
3
46
  mode?: "auto" | "grid" | "row" | "column";
@@ -190,6 +233,13 @@ interface TableLayoutOptions {
190
233
  }
191
234
  interface TableCellLayout {
192
235
  value: unknown;
236
+ input: unknown;
237
+ sourcePath: string;
238
+ style: TableCellStyle;
239
+ row: number;
240
+ column: number;
241
+ rowSpan: number;
242
+ colSpan: number;
193
243
  path: string;
194
244
  header: boolean;
195
245
  rich: boolean;
@@ -222,4 +272,4 @@ declare function resolveCanvasDimensions(input: unknown): {
222
272
  height: number;
223
273
  };
224
274
 
225
- export { type ComposeSlideOptions, type ComposedFlow, type ComposedGroup, type ComposedItem, type Composition, type CompositionTrack, type FontFamilies, type LayoutBox, type LayoutDiagnostic, type ListEntryLayout, type ListFit, type ListText, type ListValue, MAX_COMPOSITION_DEPTH, type MeasureTextWidth, OPFCompositionError, type RichTextFit, type RichTextFragment, type RichTextLine, type RichTextOptions, type RichTextRun, type SlideComposition, type TableCellLayout, type TableLayout, type TableLayoutOptions, type TableRowLayout, type TextFit, type TextMeasurement, type TextStyle, composeSlide, fitList, fitRichText, fitText, layoutTable, measureText, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, textWidthMeasurer, wrapText };
275
+ export { type ComposeSlideOptions, type ComposedFlow, type ComposedGroup, type ComposedItem, type Composition, type CompositionTrack, type FontFamilies, type LayoutBox, type LayoutDiagnostic, type ListEntryLayout, type ListFit, type ListText, type ListValue, MAX_COMPOSITION_DEPTH, type MeasureTextWidth, OPFCompositionError, type RichTextFit, type RichTextFragment, type RichTextLine, type RichTextOptions, type RichTextRun, type SlideComposition, type TableBorder, type TableCellLayout, type TableCellStyle, type TableGrid, type TableGridCell, type TableGridIssue, type TableLayout, type TableLayoutOptions, type TableRowLayout, type TextFit, type TextMeasurement, type TextStyle, composeSlide, fitList, fitRichText, fitText, layoutTable, measureText, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, tableGrid, tableRowBoundaries, textWidthMeasurer, wrapText };
@@ -1 +1 @@
1
- export { MAX_COMPOSITION_DEPTH, OPFCompositionError, composeSlide, fitList, fitRichText, fitText, layoutTable, measureText, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, textWidthMeasurer, wrapText } from './chunk-C7GKRLI6.js';
1
+ export { MAX_COMPOSITION_DEPTH, OPFCompositionError, composeSlide, fitList, fitRichText, fitText, layoutTable, measureText, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, tableGrid, tableRowBoundaries, textWidthMeasurer, wrapText } from './chunk-OVPWNQIJ.js';