@openpresentation/cli 0.1.1 → 0.3.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 CHANGED
@@ -4,23 +4,18 @@ A local CLI for agents and people working with `.opf.json` presentations. Create
4
4
 
5
5
  The CLI bundles its OPF schema, catalogs, and validator. It needs no separate core package, API key, or network connection at runtime. `opf --version` reports the CLI and bundled core versions. It does not render slides; successful validation is not visual verification.
6
6
 
7
- ## Install the current preview
7
+ ## Install
8
8
 
9
- This version is an installable npm tarball, **not yet published to the npm registry**. From the OPF checkout, build and verify it:
9
+ The CLI is published on npm. Install the current release:
10
10
 
11
11
  ```sh
12
- pnpm install
13
- pnpm test:cli:packed
14
- ```
15
-
16
- That creates `artifacts/cli/openpresentation-cli-0.1.0.tgz`. Install it globally using its absolute path:
17
-
18
- ```sh
19
- npm install -g /absolute/path/to/opf/artifacts/cli/openpresentation-cli-0.1.0.tgz
12
+ npm install -g @openpresentation/cli
20
13
  opf --version
21
14
  ```
22
15
 
23
- Or install the tarball as a development dependency and use `npx --no-install opf`. For source development, run `pnpm --filter @openpresentation/cli build` and `node packages/cli/dist/index.js --help` from the checkout. Publishing is a separate release step; the package metadata and `opf` binary are ready for it.
16
+ Or install it as a development dependency and use `npx --no-install opf`. CLI 0.2.0 bundles OPF 0.5.0 and supports rich table cells and headers during validation and pagination.
17
+
18
+ To verify the standalone package from source, run `pnpm install` and `pnpm test:cli:packed`. This creates `artifacts/cli/openpresentation-cli-0.2.0.tgz`, which can be installed using its absolute path. For source development, run `pnpm --filter @openpresentation/cli build` and `node packages/cli/dist/index.js --help`.
24
19
 
25
20
  ## Create and validate
26
21
 
package/dist/index.js CHANGED
@@ -8163,7 +8163,7 @@ function createDataContent(input, options) {
8163
8163
  return { chart: { type: options.chartType ?? "column", data: { columns: names, rows: rows2 } } };
8164
8164
  }
8165
8165
 
8166
- // ../javascript/dist/chunk-HJNJHB5H.js
8166
+ // ../javascript/dist/chunk-C7GKRLI6.js
8167
8167
  var MAX_COMPOSITION_DEPTH = 32;
8168
8168
  function resolveFontFamilies(input) {
8169
8169
  const scheme = record(input);
@@ -8325,6 +8325,16 @@ function richTextLayouter(input, box, requestedSize, options) {
8325
8325
  y += height;
8326
8326
  return line;
8327
8327
  });
8328
+ if (options.uniformLineHeight) {
8329
+ const height = Math.max(...richLines.map((line) => line.height));
8330
+ y = 0;
8331
+ for (const line of richLines) {
8332
+ line.baseline += y - line.y;
8333
+ line.y = y;
8334
+ line.height = height;
8335
+ y += height;
8336
+ }
8337
+ }
8328
8338
  return { lines: ranges.map((range) => whole.slice(range.start, range.end)), fontSize, lineHeight: Math.max(fontSize * 1.22, ...richLines.map((line) => line.height)), richLines, height: y, overflow: y > box.height + 0.01 || richLines.some((line) => line.width > box.width + 0.01) };
8329
8339
  };
8330
8340
  return layout2;
@@ -8399,13 +8409,48 @@ ${flatten(entry.description)}` : ""}`;
8399
8409
  if (field === "quote") return flatten(record(value).text ?? value);
8400
8410
  return void 0;
8401
8411
  }
8402
- function tableOverflows(value, box, scale, settings, options) {
8403
- const table = record(value), rows2 = Array.isArray(table.rows) ? table.rows : [];
8404
- const allRows = Array.isArray(table.columns) && table.columns.length ? [table.columns, ...rows2] : rows2;
8405
- const columns2 = Math.max(1, ...allRows.map((row) => row.length));
8406
- const width = box.width / columns2 - 20 * scale;
8407
- const height = Math.min(54 * scale, box.height / Math.max(1, allRows.length)) - 12 * scale;
8408
- return width <= 0 || height <= 0 || allRows.some((row, index2) => row.some((value2) => fitText(flatten(value2), { width, height }, 15 * scale, (settings.minFontSize ?? 16) * scale, textWidthMeasurer(resolveTextStyle({ fontFamily: options.fonts?.body ?? "sans-serif", fontWeight: index2 === 0 && table.columns?.length ? 700 : 400 }, options.textMeasurement), options.textMeasurement)).overflow));
8412
+ function layoutTable(value, box, options = {}) {
8413
+ const scale = options.scale ?? 1, requested = 15 * scale, minimum = (options.minFontSize ?? 16) * scale;
8414
+ 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.");
8415
+ const table = record(value), headers = Array.isArray(table.columns) && table.columns.length > 0;
8416
+ const values = [...headers ? [table.columns] : [], ...Array.isArray(table.rows) ? table.rows : []];
8417
+ const columnCount = Math.max(1, ...values.map((row) => row.length)), cellWidth = box.width / columnCount;
8418
+ const width = Math.max(scale, cellWidth - 20 * scale), padding = 12 * scale;
8419
+ const fitCell = (cell2, height, min) => {
8420
+ const textBox = { width, height: Math.max(scale, height) };
8421
+ return Array.isArray(cell2.value) ? fitRichText(cell2.value, textBox, requested, min, { style: cell2.style, textMeasurement: options.textMeasurement, uniformLineHeight: true }) : fitText(flatten(cell2.value), textBox, requested, min, textWidthMeasurer(resolveTextStyle(cell2.style, options.textMeasurement), options.textMeasurement));
8422
+ };
8423
+ const textHeight = (fit) => "height" in fit ? fit.height : fit.lines.length * fit.lineHeight;
8424
+ const measured = values.map((row, index2) => {
8425
+ const header = headers && index2 === 0;
8426
+ const cells = Array.from({ length: columnCount }, (_, column) => {
8427
+ const path2 = `${options.path ?? "table"}.${header ? "columns" : `rows.${index2 - Number(headers)}`}.${column}`;
8428
+ return { value: row[column], path: path2, header, style: { fontFamily: options.fontFamily ?? "sans-serif", fontWeight: header ? 700 : 400, italic: false, path: path2 } };
8429
+ });
8430
+ const natural2 = Math.max(...cells.map((cell2) => textHeight(fitCell(cell2, scale, requested)) + padding));
8431
+ const preferred2 = Math.max(54 * scale, natural2);
8432
+ const needed2 = Math.min(preferred2, Math.max(...cells.map((cell2) => textHeight(fitCell(cell2, scale, minimum)) + padding)));
8433
+ return { cells, preferred: preferred2, natural: natural2, needed: needed2 };
8434
+ });
8435
+ 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);
8436
+ let y = box.y, overflow = cellWidth <= 20 * scale;
8437
+ const rows2 = measured.map((row) => {
8438
+ 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;
8439
+ const rowBox = { x: box.x, y, width: box.width, height };
8440
+ const cells = row.cells.map((cell2, column) => {
8441
+ const cellBox = { x: box.x + column * cellWidth, y, width: cellWidth, height };
8442
+ const textBox = { x: cellBox.x + 10 * scale, y: y + 8 * scale, width, height: Math.max(scale, height - padding) };
8443
+ const fit = fitCell(cell2, textBox.height, minimum);
8444
+ overflow ||= height <= padding || fit.overflow;
8445
+ return { value: cell2.value, path: cell2.path, header: cell2.header, rich: Array.isArray(cell2.value), box: cellBox, textBox, textStyle: resolveTextStyle(cell2.style, options.textMeasurement), fit };
8446
+ });
8447
+ y += height;
8448
+ return { box: rowBox, cells };
8449
+ });
8450
+ return { rows: rows2, columnCount, height: y - box.y, overflow };
8451
+ }
8452
+ function tableOverflows(value, box, scale, settings, options, path2) {
8453
+ return box.width <= 0 || box.height <= 0 || layoutTable(value, box, { scale, minFontSize: settings.minFontSize, fontFamily: options.fonts?.body, textMeasurement: options.textMeasurement, path: path2 }).overflow;
8409
8454
  }
8410
8455
  function flatten(value) {
8411
8456
  if (value == null) return "";
@@ -8534,7 +8579,7 @@ function composeSlide(input, options = {}) {
8534
8579
  const fit = fitContent(node.field, node.value, text, box, 25 * scale, (settings.minFontSize ?? 16) * scale, node.path);
8535
8580
  score += (25 * scale - fit.fontSize) / scale + (fit.overflow ? 1e3 : 0);
8536
8581
  }
8537
- if (node.field === "table" && tableOverflows(node.value, box, scale, settings, options)) score += 1e3;
8582
+ if (node.field === "table" && tableOverflows(node.value, box, scale, settings, options, node.path)) score += 1e3;
8538
8583
  if (box.width < 100 * scale || box.height < 60 * scale) score += 100;
8539
8584
  return score;
8540
8585
  };
@@ -8589,7 +8634,7 @@ function composeSlide(input, options = {}) {
8589
8634
  arrange(pending, contentBox, rootSettings, composition.mode ? 0 : placeholders.length);
8590
8635
  for (const item of items) {
8591
8636
  for (const key of ["x", "y", "width", "height"]) item.box[key] = round(item.box[key]);
8592
- if (item.field === "table" && tableOverflows(item.value, item.box, scale, item.composition, options)) diagnostics.push({ code: "text-overflow", path: item.path, message: "Table cells do not fit; use fewer rows, fewer columns, or split the table across slides." });
8637
+ if (item.field === "table" && tableOverflows(item.value, item.box, scale, item.composition, options, item.path)) diagnostics.push({ code: "text-overflow", path: item.path, message: "Table cells do not fit; use fewer rows, fewer columns, or split the table across slides." });
8593
8638
  if (item.text?.overflow) diagnostics.push({ code: "text-overflow", path: item.path, message: "Text exceeds its cell at the minimum font size; shorten it, increase its space, or split the slide." });
8594
8639
  }
8595
8640
  for (const group of groups) for (const box of [group.box, group.contentBox]) for (const key of ["x", "y", "width", "height"]) box[key] = round(box[key]);
@@ -8616,7 +8661,7 @@ function resolveCanvasDimensions(input) {
8616
8661
  return { width, height };
8617
8662
  }
8618
8663
 
8619
- // ../javascript/dist/chunk-GCXOU5MZ.js
8664
+ // ../javascript/dist/chunk-KSN6RQG2.js
8620
8665
  var presentation = {
8621
8666
  "$schema": "https://json-schema.org/draft/2020-12/schema",
8622
8667
  "$id": "https://openpresentation.org/schema/opf/v1",
@@ -12297,9 +12342,19 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
12297
12342
  "columns": {
12298
12343
  "type": "array",
12299
12344
  "items": {
12300
- "type": "string"
12345
+ "oneOf": [
12346
+ {
12347
+ "type": "string"
12348
+ },
12349
+ {
12350
+ "type": "array",
12351
+ "items": {
12352
+ "$ref": "#/$defs/TextRun"
12353
+ }
12354
+ }
12355
+ ]
12301
12356
  },
12302
- "description": "Optional column labels rendered above table rows.",
12357
+ "description": "Optional column labels rendered above table rows. Each label is a string or an array of TextRun values for inline formatting.",
12303
12358
  "examples": [
12304
12359
  [
12305
12360
  "Region",
@@ -12482,9 +12537,15 @@ Resolution order: inline catalogs.fontSchemes.records[] -> catalogs.fontSchemes.
12482
12537
  },
12483
12538
  {
12484
12539
  "type": "null"
12540
+ },
12541
+ {
12542
+ "type": "array",
12543
+ "items": {
12544
+ "$ref": "#/$defs/TextRun"
12545
+ }
12485
12546
  }
12486
12547
  ],
12487
- "description": "A cell in table content."
12548
+ "description": "A cell in table content. Scalar values keep their text representation; TextRun arrays preserve inline formatting and use mixed-style text measurement."
12488
12549
  },
12489
12550
  "Catalogs": {
12490
12551
  "type": "object",
@@ -33678,7 +33739,7 @@ var catalogEntries = [
33678
33739
  ], records: fontSchemes, index: catalogIndexes.fontSchemes }
33679
33740
  ];
33680
33741
 
33681
- // ../javascript/dist/chunk-EMTMQRX4.js
33742
+ // ../javascript/dist/chunk-23MXJL2K.js
33682
33743
  var import__ = __toESM(require__(), 1);
33683
33744
  var import_ajv_formats = __toESM(require_dist(), 1);
33684
33745
  var catalogIds = {
@@ -34672,7 +34733,7 @@ function assertValidPresentation(value) {
34672
34733
  assertValid(value, "presentation");
34673
34734
  }
34674
34735
 
34675
- // ../javascript/dist/chunk-75N5I6M4.js
34736
+ // ../javascript/dist/chunk-C6U7LLBT.js
34676
34737
  var OPFPaginationError = class extends Error {
34677
34738
  constructor(message, diagnostics = []) {
34678
34739
  super(message);
@@ -35112,7 +35173,7 @@ async function main(argv) {
35112
35173
  return;
35113
35174
  }
35114
35175
  if (argv.length === 1 && argv[0] === "--version") {
35115
- print({ cli: "0.1.1", opf: "0.4.1" });
35176
+ print({ cli: "0.3.0", opf: "0.6.0" });
35116
35177
  return;
35117
35178
  }
35118
35179
  const [command, ...args] = argv;