@openpresentation/opf 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-PFJE2IW4.js → chunk-23MXJL2K.js} +1 -1
- package/dist/{chunk-YLORKB63.js → chunk-C6U7LLBT.js} +2 -2
- package/dist/{chunk-WG5A4KZG.js → chunk-C7GKRLI6.js} +55 -14
- package/dist/composition.d.ts +36 -1
- package/dist/composition.js +1 -1
- package/dist/docs.js +1 -1
- package/dist/index.js +3 -3
- package/dist/pagination.js +3 -3
- package/dist/validator.js +2 -2
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MAX_COMPOSITION_DEPTH } from './chunk-
|
|
1
|
+
import { MAX_COMPOSITION_DEPTH } from './chunk-C7GKRLI6.js';
|
|
2
2
|
import { schemas } from './chunk-KSN6RQG2.js';
|
|
3
3
|
import { catalogSchemaNames } from './chunk-TWMRZ43O.js';
|
|
4
4
|
import Ajv2020 from 'ajv/dist/2020.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { assertValidPresentation } from './chunk-
|
|
2
|
-
import { resolveFontFamilies, resolveCanvasDimensions, composeSlide } from './chunk-
|
|
1
|
+
import { assertValidPresentation } from './chunk-23MXJL2K.js';
|
|
2
|
+
import { resolveFontFamilies, resolveCanvasDimensions, composeSlide } from './chunk-C7GKRLI6.js';
|
|
3
3
|
import { catalogs } from './chunk-TWMRZ43O.js';
|
|
4
4
|
|
|
5
5
|
// src/pagination.ts
|
|
@@ -160,6 +160,16 @@ function richTextLayouter(input, box, requestedSize, options) {
|
|
|
160
160
|
y += height;
|
|
161
161
|
return line;
|
|
162
162
|
});
|
|
163
|
+
if (options.uniformLineHeight) {
|
|
164
|
+
const height = Math.max(...richLines.map((line) => line.height));
|
|
165
|
+
y = 0;
|
|
166
|
+
for (const line of richLines) {
|
|
167
|
+
line.baseline += y - line.y;
|
|
168
|
+
line.y = y;
|
|
169
|
+
line.height = height;
|
|
170
|
+
y += height;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
163
173
|
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) };
|
|
164
174
|
};
|
|
165
175
|
return layout;
|
|
@@ -234,17 +244,48 @@ ${flatten(entry.description)}` : ""}`;
|
|
|
234
244
|
if (field === "quote") return flatten(record(value).text ?? value);
|
|
235
245
|
return void 0;
|
|
236
246
|
}
|
|
237
|
-
function
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
return
|
|
247
|
-
}
|
|
247
|
+
function layoutTable(value, box, options = {}) {
|
|
248
|
+
const scale = options.scale ?? 1, requested = 15 * scale, minimum = (options.minFontSize ?? 16) * scale;
|
|
249
|
+
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;
|
|
254
|
+
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));
|
|
257
|
+
};
|
|
258
|
+
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 };
|
|
286
|
+
}
|
|
287
|
+
function tableOverflows(value, box, scale, settings, options, path) {
|
|
288
|
+
return box.width <= 0 || box.height <= 0 || layoutTable(value, box, { scale, minFontSize: settings.minFontSize, fontFamily: options.fonts?.body, textMeasurement: options.textMeasurement, path }).overflow;
|
|
248
289
|
}
|
|
249
290
|
function flatten(value) {
|
|
250
291
|
if (value == null) return "";
|
|
@@ -373,7 +414,7 @@ function composeSlide(input, options = {}) {
|
|
|
373
414
|
const fit = fitContent(node.field, node.value, text, box, 25 * scale, (settings.minFontSize ?? 16) * scale, node.path);
|
|
374
415
|
score += (25 * scale - fit.fontSize) / scale + (fit.overflow ? 1e3 : 0);
|
|
375
416
|
}
|
|
376
|
-
if (node.field === "table" && tableOverflows(node.value, box, scale, settings, options)) score += 1e3;
|
|
417
|
+
if (node.field === "table" && tableOverflows(node.value, box, scale, settings, options, node.path)) score += 1e3;
|
|
377
418
|
if (box.width < 100 * scale || box.height < 60 * scale) score += 100;
|
|
378
419
|
return score;
|
|
379
420
|
};
|
|
@@ -428,7 +469,7 @@ function composeSlide(input, options = {}) {
|
|
|
428
469
|
arrange(pending, contentBox, rootSettings, composition.mode ? 0 : placeholders.length);
|
|
429
470
|
for (const item of items) {
|
|
430
471
|
for (const key of ["x", "y", "width", "height"]) item.box[key] = round(item.box[key]);
|
|
431
|
-
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." });
|
|
472
|
+
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." });
|
|
432
473
|
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." });
|
|
433
474
|
}
|
|
434
475
|
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]);
|
|
@@ -455,4 +496,4 @@ function resolveCanvasDimensions(input) {
|
|
|
455
496
|
return { width, height };
|
|
456
497
|
}
|
|
457
498
|
|
|
458
|
-
export { MAX_COMPOSITION_DEPTH, OPFCompositionError, composeSlide, fitList, fitRichText, fitText, measureText, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, textWidthMeasurer, wrapText };
|
|
499
|
+
export { MAX_COMPOSITION_DEPTH, OPFCompositionError, composeSlide, fitList, fitRichText, fitText, layoutTable, measureText, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, textWidthMeasurer, wrapText };
|
package/dist/composition.d.ts
CHANGED
|
@@ -144,6 +144,8 @@ interface RichTextFit extends TextFit {
|
|
|
144
144
|
interface RichTextOptions {
|
|
145
145
|
style: TextStyle;
|
|
146
146
|
textMeasurement?: TextMeasurement;
|
|
147
|
+
/** Use one measured line advance for every line, as native table cells do. */
|
|
148
|
+
uniformLineHeight?: boolean;
|
|
147
149
|
}
|
|
148
150
|
/** Fit mixed styles without flattening font metrics. Run fontSize is in points. */
|
|
149
151
|
declare function fitRichText(input: readonly (string | RichTextRun)[], box: LayoutBox, requestedSize?: number, minFontSize?: number, options?: RichTextOptions): RichTextFit;
|
|
@@ -179,6 +181,39 @@ interface ListFit extends TextFit {
|
|
|
179
181
|
}
|
|
180
182
|
/** Shared hanging indents, mixed-run fitting and description spacing for list payloads. */
|
|
181
183
|
declare function fitList(input: readonly ListValue[], box: LayoutBox, requestedSize?: number, minFontSize?: number, options?: RichTextOptions): ListFit;
|
|
184
|
+
interface TableLayoutOptions {
|
|
185
|
+
scale?: number;
|
|
186
|
+
minFontSize?: number;
|
|
187
|
+
fontFamily?: string;
|
|
188
|
+
textMeasurement?: TextMeasurement;
|
|
189
|
+
path?: string;
|
|
190
|
+
}
|
|
191
|
+
interface TableCellLayout {
|
|
192
|
+
value: unknown;
|
|
193
|
+
path: string;
|
|
194
|
+
header: boolean;
|
|
195
|
+
rich: boolean;
|
|
196
|
+
box: LayoutBox;
|
|
197
|
+
textBox: LayoutBox;
|
|
198
|
+
textStyle: TextStyle;
|
|
199
|
+
fit: TextFit | RichTextFit;
|
|
200
|
+
}
|
|
201
|
+
interface TableRowLayout {
|
|
202
|
+
box: LayoutBox;
|
|
203
|
+
cells: TableCellLayout[];
|
|
204
|
+
}
|
|
205
|
+
interface TableLayout {
|
|
206
|
+
rows: TableRowLayout[];
|
|
207
|
+
columnCount: number;
|
|
208
|
+
height: number;
|
|
209
|
+
overflow: boolean;
|
|
210
|
+
}
|
|
211
|
+
/** Shared cell geometry and font fitting for SVG, native PPTX and pagination.
|
|
212
|
+
* Short rows retain their 54px preferred height. Multiline rows use the space
|
|
213
|
+
* their text needs; constrained tables consume row padding before readable text.
|
|
214
|
+
* All dimensions are canvas pixels; minFontSize is an unscaled canvas size.
|
|
215
|
+
*/
|
|
216
|
+
declare function layoutTable(value: unknown, box: LayoutBox, options?: TableLayoutOptions): TableLayout;
|
|
182
217
|
/** Compose a validated Slide. Layout resolution remains the caller's responsibility. */
|
|
183
218
|
declare function composeSlide(input: unknown, options?: ComposeSlideOptions): SlideComposition;
|
|
184
219
|
/** Canonical physical slide size, converted to reference pixels at 96 pixels/inch. */
|
|
@@ -187,4 +222,4 @@ declare function resolveCanvasDimensions(input: unknown): {
|
|
|
187
222
|
height: number;
|
|
188
223
|
};
|
|
189
224
|
|
|
190
|
-
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 TextFit, type TextMeasurement, type TextStyle, composeSlide, fitList, fitRichText, fitText, measureText, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, textWidthMeasurer, wrapText };
|
|
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 };
|
package/dist/composition.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { MAX_COMPOSITION_DEPTH, OPFCompositionError, composeSlide, fitList, fitRichText, fitText, measureText, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, textWidthMeasurer, wrapText } from './chunk-
|
|
1
|
+
export { MAX_COMPOSITION_DEPTH, OPFCompositionError, composeSlide, fitList, fitRichText, fitText, layoutTable, measureText, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, textWidthMeasurer, wrapText } from './chunk-C7GKRLI6.js';
|
package/dist/docs.js
CHANGED
|
@@ -22,7 +22,7 @@ var docsData = Object.freeze([
|
|
|
22
22
|
"slug": "content-payloads",
|
|
23
23
|
"file": "docs/content-payloads.md",
|
|
24
24
|
"title": "Content Payloads",
|
|
25
|
-
"markdown": '# Content Payloads\n\nSlide content lives directly on a slide as a full-slide payload, in layout-agnostic `blocks`, or inside a promoted region key such as `left`, `center+right`, or `top:left`.\n\nThe optional payload `type` can make intent explicit, but OPF should usually infer the content kind from the field present:\n\n| Field | Inferred type | Notes |\n| --- | --- | --- |\n| `text` | `text` | Plain string or `TextRun[]`. |\n| `bullets` | `text` | Simple text bullets, usually `string[]`. |\n| `items` | `list` | Generic list payload, usually `string[]` or `ListItem[]`. |\n| `image` | `image` | Asset string shorthand or `Asset` object with `src` and optional metadata. |\n| `video` | `video` | Asset string shorthand or `Asset` object with `src` and optional metadata. |\n| `chart` | `chart` | Chart object with `type` and tabular `data`. |\n| `table` | `table` | Table object with optional `columns` and required `rows`. |\n| `code` | `code` | String shorthand or `Code` object with `source`, `language`, and `filename`. |\n| `metric` | `metric` | String/number shorthand or `Metric` object with `value`, `label`, `description`, `unit`, `delta`, and `trend`. |\n| `quote` | `quote` | String shorthand or `Quote` object with `text`, `attribution`, and `source`. |\n| `timeline` | `timeline` | Array shorthand or `Timeline` object with `name`, `description`, and `events`. |\n\n## Blocks\n\nUse slide-level `blocks` when a slide contains multiple content payloads, but exact placement should be inferred by the renderer. Blocks may contain a concrete content payload or a nested group with its own `blocks` and optional `composition`. Groups cannot mix child blocks with leaf payload fields. See [dynamic composition](dynamic-composition.md) for nesting and inheritance rules.\n\n```json\n{\n "title": "Customer Feedback Summary",\n "blocks": [\n {\n "table": {\n "columns": ["Theme", "Mentions"],\n "rows": [\n ["Speed", 42],\n ["Ease of use", 31]\n ]\n }\n },\n {\n "quote": {\n "text": "The new workflow cut review time in half.",\n "attribution": "Operations Lead",\n "source": "Customer interview"\n }\n }\n ]\n}\n```\n\nAt slide root only, multiple content payload kinds are accepted as shorthand for the equivalent blocks form when there is no explicit `type`, no `blocks`, and no promoted region keys:\n\n```json\n{\n "title": "Habitat & Territory",\n "text": "Jaguars are strongly associated with presence of water and dense cover.",\n "items": [\n "Primary habitats include dense rainforests, swamps, and seasonally flooded wetlands.",\n "Solitary animals that establish and defend large territories."\n ]\n}\n```\n\nThe same shorthand works for other content kinds:\n\n```json\n{\n "title": "Evidence Snapshot",\n "chart": {\n "type": "line",\n "data": {\n "columns": ["Quarter", "Sightings"],\n "rows": [\n ["Q1", 12],\n ["Q2", 18]\n ]\n }\n },\n "quote": {\n "text": "Jaguar conservation depends on connected habitat.",\n "attribution": "Field researcher"\n }\n}\n```\n\n## Chart\n\nChart-specific fields are grouped under `chart`. Do not put loose chart data directly on a slide or region.\n\n```json\n{\n "title": "Revenue Trend",\n "chart": {\n "type": "line",\n "data": {\n "columns": ["Quarter", "Revenue", "Costs"],\n "rows": [\n ["Q1", 12, 8],\n ["Q2", 18, 11],\n ["Q3", 24, 15]\n ]\n }\n }\n}\n```\n\nInline chart data is tabular by default. Renderers convert `columns` and `rows` into series, axes, legends, and workbook data internally.\n\nAsset-backed data is still table-oriented:\n\n```json\n{\n "chart": {\n "type": "column",\n "data": {\n "src": "asset:revenue-csv",\n "columns": ["Quarter", "Revenue"]\n }\n }\n}\n```\n\n## Table\n\nTable-specific fields are grouped under `table`. Do not put loose `columns` or `rows` directly on a slide or region.\n\n```json\n{\n "title": "Pipeline",\n "table": {\n "columns": ["Stage", "Count", "Value"],\n "rows": [\n ["Qualified", 42, "$1.2M"],\n ["Proposal", 18, "$840K"]\n ]\n }\n}\n```\n\nTable body cells accept strings, numbers, booleans, or `null`. In the development schema, a cell or column header also accepts the same `TextRun[]` used by rich text:\n\n```json\n{\n "table": {\n "columns": [["Quarter ", {"text": "growth", "bold": true}], "Value"],\n "rows": [\n [["Up ", {"text": "12%", "color": "#008800"}], 12]\n ]\n }\n}\n```\n\nThis form is unreleased and requires coordinated core, renderer and PPTX development packages. Core measures run styles when checking overflow and keeps each row intact when paginating. The renderer traces rich cells for the editor\'s existing formatting, typing and undo controls; the exporter emits editable native text runs. Native PPTX table import currently flattens runs to strings, and native PowerPoint visual parity is not yet verified. Per-cell fills, borders, alignment and merged cells are separate work.\n\n## Code\n\nCode-specific fields are grouped under `code`. A string value is shorthand for `code.source`; use object form when syntax highlighting or a file label matters. In object form, `source` is required.\n\n```json\n{\n "title": "Decision Rule",\n "code": {\n "source": "if risk > threshold:\\n escalate(owner)\\nelse:\\n approve(change)",\n "language": "python",\n "filename": "decision.py"\n }\n}\n```\n\n## Metric\n\nMetric-specific fields are grouped under `metric`. A string or number value is shorthand for `metric.value`; numeric values stay numeric and are formatted by renderers at display time. Use object form when labels, descriptions, units, deltas, or trends matter.\n\n```json\n{\n "title": "Operating Metric",\n "metric": {\n "value": "42%",\n "label": "Review cycle reduction",\n "description": "Median reduction across customer review workflows.",\n "delta": "+11 pts",\n "trend": "up"\n }\n}\n```\n\n## Quote\n\nQuote-specific fields are grouped under `quote`. A string value is shorthand for `quote.text`; use object form when attribution or citation matters.\n\n```json\n{\n "title": "Customer Proof",\n "quote": {\n "text": "The new workflow made exceptions visible before they became escalations.",\n "attribution": "VP Operations, Acme Corp",\n "source": "Customer interview"\n }\n}\n```\n\n## Timeline\n\nTimeline-specific fields are grouped under `timeline`. An array value is shorthand for `timeline.events`; use object form when the timeline needs a name or description. Timeline events use `when`, `what`, and `description`.\n\n```json\n{\n "title": "Rollout Plan",\n "timeline": {\n "name": "Regional Rollout",\n "description": "Major milestones for the rollout.",\n "events": [\n {\n "when": "Q1",\n "what": "Pilot",\n "description": "Launch with one operations team."\n },\n {\n "when": "Q2",\n "what": "Rollout",\n "description": "Expand to all regions."\n }\n ]\n }\n}\n```\n\n## Regions\n\nRegion keys address a 3\xD73 grid of rows (`top`, `middle`, `bottom`) and columns (`left`, `center`, `right`):\n\n```\n left center right\n +--------------------+--------------------+--------------------+\n top | top:left | top:center | top:right |\n +--------------------+--------------------+--------------------+\n middle | middle:left | middle:center | middle:right |\n +--------------------+--------------------+--------------------+\n bottom | bottom:left | bottom:center | bottom:right |\n +--------------------+--------------------+--------------------+\n```\n\n- A bare column key (`left`) spans all three rows; a bare row key (`top`) spans all three columns.\n- `+` spans adjacent rows or columns: `center+right`, `top+middle`.\n- `row:column` combines the two: `top:left`, `middle+bottom:center+right`.\n- Keys on one slide must not overlap, and regions cannot be mixed with root payload fields.\n\nSpans compose into common slide shapes:\n\n```\n "left" + "center+right" "top" + "middle+bottom"\n (sidebar + main) (headline band + body)\n +----------+------------------+ +-------------------------------+\n | | | | top |\n | | | +-------------------------------+\n | left | center+right | | |\n | | | | middle+bottom |\n | | | | |\n +----------+------------------+ +-------------------------------+\n\n "top" + "middle+bottom:left" + "middle+bottom:center+right"\n (headline band, then sidebar + main)\n +---------------------------------------------+\n | top |\n +---------------+-----------------------------+\n | | |\n | middle+bottom | middle+bottom:center+right |\n | :left | |\n | | |\n +---------------+-----------------------------+\n```\n\nThe same payload objects work inside regions \u2014 here, the sidebar-plus-main shape:\n\n```json\n{\n "title": "Operating Snapshot",\n "left": {\n "table": {\n "columns": ["Metric", "Value"],\n "rows": [\n ["Revenue", "$4.2M"],\n ["Gross margin", "68%"]\n ]\n }\n },\n "center+right": {\n "chart": {\n "type": "line",\n "data": {\n "columns": ["Month", "Revenue"],\n "rows": [\n ["Jan", 3.4],\n ["Feb", 3.8],\n ["Mar", 4.2]\n ]\n }\n }\n }\n}\n```\n'
|
|
25
|
+
"markdown": '# Content Payloads\n\nSlide content lives directly on a slide as a full-slide payload, in layout-agnostic `blocks`, or inside a promoted region key such as `left`, `center+right`, or `top:left`.\n\nThe optional payload `type` can make intent explicit, but OPF should usually infer the content kind from the field present:\n\n| Field | Inferred type | Notes |\n| --- | --- | --- |\n| `text` | `text` | Plain string or `TextRun[]`. |\n| `bullets` | `text` | Simple text bullets, usually `string[]`. |\n| `items` | `list` | Generic list payload, usually `string[]` or `ListItem[]`. |\n| `image` | `image` | Asset string shorthand or `Asset` object with `src` and optional metadata. |\n| `video` | `video` | Asset string shorthand or `Asset` object with `src` and optional metadata. |\n| `chart` | `chart` | Chart object with `type` and tabular `data`. |\n| `table` | `table` | Table object with optional `columns` and required `rows`. |\n| `code` | `code` | String shorthand or `Code` object with `source`, `language`, and `filename`. |\n| `metric` | `metric` | String/number shorthand or `Metric` object with `value`, `label`, `description`, `unit`, `delta`, and `trend`. |\n| `quote` | `quote` | String shorthand or `Quote` object with `text`, `attribution`, and `source`. |\n| `timeline` | `timeline` | Array shorthand or `Timeline` object with `name`, `description`, and `events`. |\n\n## Blocks\n\nUse slide-level `blocks` when a slide contains multiple content payloads, but exact placement should be inferred by the renderer. Blocks may contain a concrete content payload or a nested group with its own `blocks` and optional `composition`. Groups cannot mix child blocks with leaf payload fields. See [dynamic composition](dynamic-composition.md) for nesting and inheritance rules.\n\n```json\n{\n "title": "Customer Feedback Summary",\n "blocks": [\n {\n "table": {\n "columns": ["Theme", "Mentions"],\n "rows": [\n ["Speed", 42],\n ["Ease of use", 31]\n ]\n }\n },\n {\n "quote": {\n "text": "The new workflow cut review time in half.",\n "attribution": "Operations Lead",\n "source": "Customer interview"\n }\n }\n ]\n}\n```\n\nAt slide root only, multiple content payload kinds are accepted as shorthand for the equivalent blocks form when there is no explicit `type`, no `blocks`, and no promoted region keys:\n\n```json\n{\n "title": "Habitat & Territory",\n "text": "Jaguars are strongly associated with presence of water and dense cover.",\n "items": [\n "Primary habitats include dense rainforests, swamps, and seasonally flooded wetlands.",\n "Solitary animals that establish and defend large territories."\n ]\n}\n```\n\nThe same shorthand works for other content kinds:\n\n```json\n{\n "title": "Evidence Snapshot",\n "chart": {\n "type": "line",\n "data": {\n "columns": ["Quarter", "Sightings"],\n "rows": [\n ["Q1", 12],\n ["Q2", 18]\n ]\n }\n },\n "quote": {\n "text": "Jaguar conservation depends on connected habitat.",\n "attribution": "Field researcher"\n }\n}\n```\n\n## Chart\n\nChart-specific fields are grouped under `chart`. Do not put loose chart data directly on a slide or region.\n\n```json\n{\n "title": "Revenue Trend",\n "chart": {\n "type": "line",\n "data": {\n "columns": ["Quarter", "Revenue", "Costs"],\n "rows": [\n ["Q1", 12, 8],\n ["Q2", 18, 11],\n ["Q3", 24, 15]\n ]\n }\n }\n}\n```\n\nInline chart data is tabular by default. Renderers convert `columns` and `rows` into series, axes, legends, and workbook data internally.\n\nAsset-backed data is still table-oriented:\n\n```json\n{\n "chart": {\n "type": "column",\n "data": {\n "src": "asset:revenue-csv",\n "columns": ["Quarter", "Revenue"]\n }\n }\n}\n```\n\n## Table\n\nTable-specific fields are grouped under `table`. Do not put loose `columns` or `rows` directly on a slide or region.\n\n```json\n{\n "title": "Pipeline",\n "table": {\n "columns": ["Stage", "Count", "Value"],\n "rows": [\n ["Qualified", 42, "$1.2M"],\n ["Proposal", 18, "$840K"]\n ]\n }\n}\n```\n\nTable body cells accept strings, numbers, booleans, or `null`. Since core 0.5.0, a cell or column header also accepts the same `TextRun[]` used by rich text:\n\n```json\n{\n "table": {\n "columns": [["Quarter ", {"text": "growth", "bold": true}], "Value"],\n "rows": [\n [["Up ", {"text": "12%", "color": "#008800"}], 12]\n ]\n }\n}\n```\n\nUse core 0.5.0, renderer 0.3.0, editor 0.2.0 and PPTX 0.3.0 together for this form. Core measures run styles when checking overflow and keeps each row intact when paginating. The renderer traces rich cells for the editor\'s existing formatting, typing and undo controls; the exporter emits editable native text runs. Native PPTX table import currently flattens runs to strings, and native PowerPoint visual parity is not yet verified. Per-cell fills, borders, alignment and merged cells are separate work.\n\nThe current development line (core 0.6.0) adds `layoutTable` from `@openpresentation/opf/composition`. It measures scalar and rich cells, keeps short rows compact, and gives wrapped or multiline rows the height they need. When space is constrained it reduces spare row height before shrinking text, and reports overflow when the minimum fitting size cannot fit. Pass the same `scale`, font family, measurement provider and effective `minFontSize` to each consumer. The returned row boxes, cell text boxes and fits are shared by the coordinated SVG and PPTX implementations; rich table cells use uniform line advances to match native cell paragraph spacing. These downstream changes and native rich-table import are not yet published. Native viewer fidelity remains a separate verification boundary.\n\n\n## Code\n\nCode-specific fields are grouped under `code`. A string value is shorthand for `code.source`; use object form when syntax highlighting or a file label matters. In object form, `source` is required.\n\n```json\n{\n "title": "Decision Rule",\n "code": {\n "source": "if risk > threshold:\\n escalate(owner)\\nelse:\\n approve(change)",\n "language": "python",\n "filename": "decision.py"\n }\n}\n```\n\n## Metric\n\nMetric-specific fields are grouped under `metric`. A string or number value is shorthand for `metric.value`; numeric values stay numeric and are formatted by renderers at display time. Use object form when labels, descriptions, units, deltas, or trends matter.\n\n```json\n{\n "title": "Operating Metric",\n "metric": {\n "value": "42%",\n "label": "Review cycle reduction",\n "description": "Median reduction across customer review workflows.",\n "delta": "+11 pts",\n "trend": "up"\n }\n}\n```\n\n## Quote\n\nQuote-specific fields are grouped under `quote`. A string value is shorthand for `quote.text`; use object form when attribution or citation matters.\n\n```json\n{\n "title": "Customer Proof",\n "quote": {\n "text": "The new workflow made exceptions visible before they became escalations.",\n "attribution": "VP Operations, Acme Corp",\n "source": "Customer interview"\n }\n}\n```\n\n## Timeline\n\nTimeline-specific fields are grouped under `timeline`. An array value is shorthand for `timeline.events`; use object form when the timeline needs a name or description. Timeline events use `when`, `what`, and `description`.\n\n```json\n{\n "title": "Rollout Plan",\n "timeline": {\n "name": "Regional Rollout",\n "description": "Major milestones for the rollout.",\n "events": [\n {\n "when": "Q1",\n "what": "Pilot",\n "description": "Launch with one operations team."\n },\n {\n "when": "Q2",\n "what": "Rollout",\n "description": "Expand to all regions."\n }\n ]\n }\n}\n```\n\n## Regions\n\nRegion keys address a 3\xD73 grid of rows (`top`, `middle`, `bottom`) and columns (`left`, `center`, `right`):\n\n```\n left center right\n +--------------------+--------------------+--------------------+\n top | top:left | top:center | top:right |\n +--------------------+--------------------+--------------------+\n middle | middle:left | middle:center | middle:right |\n +--------------------+--------------------+--------------------+\n bottom | bottom:left | bottom:center | bottom:right |\n +--------------------+--------------------+--------------------+\n```\n\n- A bare column key (`left`) spans all three rows; a bare row key (`top`) spans all three columns.\n- `+` spans adjacent rows or columns: `center+right`, `top+middle`.\n- `row:column` combines the two: `top:left`, `middle+bottom:center+right`.\n- Keys on one slide must not overlap, and regions cannot be mixed with root payload fields.\n\nSpans compose into common slide shapes:\n\n```\n "left" + "center+right" "top" + "middle+bottom"\n (sidebar + main) (headline band + body)\n +----------+------------------+ +-------------------------------+\n | | | | top |\n | | | +-------------------------------+\n | left | center+right | | |\n | | | | middle+bottom |\n | | | | |\n +----------+------------------+ +-------------------------------+\n\n "top" + "middle+bottom:left" + "middle+bottom:center+right"\n (headline band, then sidebar + main)\n +---------------------------------------------+\n | top |\n +---------------+-----------------------------+\n | | |\n | middle+bottom | middle+bottom:center+right |\n | :left | |\n | | |\n +---------------+-----------------------------+\n```\n\nThe same payload objects work inside regions \u2014 here, the sidebar-plus-main shape:\n\n```json\n{\n "title": "Operating Snapshot",\n "left": {\n "table": {\n "columns": ["Metric", "Value"],\n "rows": [\n ["Revenue", "$4.2M"],\n ["Gross margin", "68%"]\n ]\n }\n },\n "center+right": {\n "chart": {\n "type": "line",\n "data": {\n "columns": ["Month", "Revenue"],\n "rows": [\n ["Jan", 3.4],\n ["Feb", 3.8],\n ["Mar", 4.2]\n ]\n }\n }\n }\n}\n```\n'
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
"slug": "data-import",
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { specFileEntries, specFileKinds, specFilePaths } from './chunk-OZULJBSG.js';
|
|
2
2
|
export { getLayoutPreview, hasLayoutPreview, layoutPreviewIndex, layoutPreviewSlugs, layoutPreviews } from './chunk-PIQXLBBJ.js';
|
|
3
3
|
export { OPFDataImportError, createDataContent, parseTabularData } from './chunk-SOSOBKQ2.js';
|
|
4
|
-
export { OPFPaginationError, paginatePresentation, paginateSlide } from './chunk-
|
|
5
|
-
export { OPFValidationError, assertValid, assertValidCatalogRecord, assertValidPresentation, validate, validateCatalogRecord, validatePresentation } from './chunk-
|
|
6
|
-
export { MAX_COMPOSITION_DEPTH, OPFCompositionError, composeSlide, fitList, fitRichText, fitText, measureText, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, textWidthMeasurer, wrapText } from './chunk-
|
|
4
|
+
export { OPFPaginationError, paginatePresentation, paginateSlide } from './chunk-C6U7LLBT.js';
|
|
5
|
+
export { OPFValidationError, assertValid, assertValidCatalogRecord, assertValidPresentation, validate, validateCatalogRecord, validatePresentation } from './chunk-23MXJL2K.js';
|
|
6
|
+
export { MAX_COMPOSITION_DEPTH, OPFCompositionError, composeSlide, fitList, fitRichText, fitText, measureText, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, textWidthMeasurer, wrapText } from './chunk-C7GKRLI6.js';
|
|
7
7
|
export { audience, chartType, colorScheme, fontScheme, language, layout, narrative, presentation, purpose, schemaEntries, schemaNames, schemas, socialPlatform, theme, tone } from './chunk-KSN6RQG2.js';
|
|
8
8
|
export { audiences, catalogEntries, catalogIndexes, catalogKinds, catalogSchemaNames, catalogs, chartTypes, colorSchemes, fontSchemes, languages, layouts, narratives, purposes, socialPlatforms, themes, tones } from './chunk-TWMRZ43O.js';
|
package/dist/pagination.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { OPFPaginationError, paginatePresentation, paginateSlide } from './chunk-
|
|
2
|
-
import './chunk-
|
|
3
|
-
import './chunk-
|
|
1
|
+
export { OPFPaginationError, paginatePresentation, paginateSlide } from './chunk-C6U7LLBT.js';
|
|
2
|
+
import './chunk-23MXJL2K.js';
|
|
3
|
+
import './chunk-C7GKRLI6.js';
|
|
4
4
|
import './chunk-KSN6RQG2.js';
|
|
5
5
|
import './chunk-TWMRZ43O.js';
|
package/dist/validator.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { OPFValidationError, assertValid, assertValidCatalogRecord, assertValidPresentation, validate, validateCatalogRecord, validatePresentation } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
export { OPFValidationError, assertValid, assertValidCatalogRecord, assertValidPresentation, validate, validateCatalogRecord, validatePresentation } from './chunk-23MXJL2K.js';
|
|
2
|
+
import './chunk-C7GKRLI6.js';
|
|
3
3
|
import './chunk-KSN6RQG2.js';
|
|
4
4
|
import './chunk-TWMRZ43O.js';
|
package/package.json
CHANGED