@openpresentation/opf 0.10.0 → 0.10.1
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/{catalogs-HCTav5Wv.d.ts → catalogs-DoVmvDr7.d.ts} +5 -5
- package/dist/catalogs.d.ts +1 -1
- package/dist/catalogs.js +1 -1
- package/dist/{chunk-GQSK3T7X.js → chunk-D3GQIREP.js} +13 -4
- package/dist/{chunk-NPOKUFEX.js → chunk-EWJNRHXA.js} +3 -3
- package/dist/{chunk-TK3QHM3U.js → chunk-FHGJX5QK.js} +24 -12
- package/dist/{chunk-BAWLYSUN.js → chunk-PQJRDNA6.js} +109 -6
- package/dist/{chunk-OZULJBSG.js → chunk-RS5GZ5RW.js} +14 -0
- package/dist/{chunk-TWMRZ43O.js → chunk-TU7I3KSB.js} +59 -21
- package/dist/{chunk-GCB6LGJM.js → chunk-ZIL7MGZU.js} +5 -3
- package/dist/composition.d.ts +58 -6
- package/dist/composition.js +1 -1
- package/dist/docs.js +25 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -7
- package/dist/lint.d.ts +1 -1
- package/dist/lint.js +5 -5
- package/dist/pagination.d.ts +2 -0
- package/dist/pagination.js +5 -5
- package/dist/schemas.js +1 -1
- package/dist/spec/catalogs/layouts/extract/canonical.json +45 -21
- package/dist/spec/catalogs/layouts/index.json +10 -0
- package/dist/spec/catalogs/layouts/number-1x.json +1 -1
- package/dist/spec/catalogs/layouts/number-2x.json +2 -2
- package/dist/spec/catalogs/layouts/number-3x.json +3 -3
- package/dist/spec/catalogs/layouts/number-4x.json +4 -4
- package/dist/spec/catalogs/layouts/number-5x.json +5 -5
- package/dist/spec/catalogs/layouts/number-6x.json +6 -6
- package/dist/spec/catalogs/layouts/quote-1x.json +13 -0
- package/dist/spec/catalogs/layouts/timeline-1x.json +13 -0
- package/dist/spec/schemas/layout.schema.json +12 -3
- package/dist/spec/schemas/narrative.schema.json +1 -1
- package/dist/spec-files.d.ts +1 -1
- package/dist/spec-files.js +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/validator.d.ts +1 -1
- package/dist/validator.js +4 -4
- package/package.json +1 -1
|
@@ -311,9 +311,9 @@ interface Layout {
|
|
|
311
311
|
*/
|
|
312
312
|
description?: string;
|
|
313
313
|
/**
|
|
314
|
-
* Primary kind of content the layout holds. Drives pickers and AI placement decisions.
|
|
314
|
+
* Primary kind of content the layout holds. Drives pickers and AI placement decisions. Metric is the canonical numeric/KPI category; Number remains an accepted legacy label.
|
|
315
315
|
*/
|
|
316
|
-
contentType?: ("Title" | "Text" | "List" | "Image" | "Number" | "Chart");
|
|
316
|
+
contentType?: ("Title" | "Text" | "List" | "Image" | "Number" | "Metric" | "Chart" | "Table" | "Code" | "Video" | "Quote" | "Timeline");
|
|
317
317
|
/**
|
|
318
318
|
* How many parallel content blocks the layout exposes ('2x' = two-column, '3x' = three-up, etc.).
|
|
319
319
|
*/
|
|
@@ -407,9 +407,9 @@ interface Layout {
|
|
|
407
407
|
*/
|
|
408
408
|
interface Placeholder {
|
|
409
409
|
/**
|
|
410
|
-
* OPF placeholder kind. 'text' and 'list' are flexible textual content regions. The named kinds describe a specific content role used by pickers, AI generation, and engine defaulting.
|
|
410
|
+
* OPF placeholder kind. 'text' and 'list' are flexible textual content regions. 'metric' is a numeric/KPI content region filled by a metric payload, including its optional label, description, unit, delta, and trend. The named kinds describe a specific content role used by pickers, AI generation, and engine defaulting. 'picture' and legacy 'diagram' use image payloads; 'media' uses video. Quote and timeline use their corresponding payload fields.
|
|
411
411
|
*/
|
|
412
|
-
type: ("title" | "subtitle" | "tag" | "text" | "list" | "chart" | "picture" | "table" | "media" | "diagram" | "code");
|
|
412
|
+
type: ("title" | "subtitle" | "tag" | "text" | "metric" | "quote" | "timeline" | "list" | "chart" | "picture" | "table" | "media" | "diagram" | "code");
|
|
413
413
|
[k: string]: unknown;
|
|
414
414
|
}
|
|
415
415
|
/**
|
|
@@ -789,7 +789,7 @@ interface Beat {
|
|
|
789
789
|
*/
|
|
790
790
|
slideCount?: number;
|
|
791
791
|
/**
|
|
792
|
-
* Default content kind for the beat's slide.
|
|
792
|
+
* Default content kind for the beat's slide. Uses ContentPayload.type names to help engines choose a layout. The legacy shape value is retained for compatibility and requests an image representation; it is not a native ContentPayload type.
|
|
793
793
|
*/
|
|
794
794
|
slideType?: ("text" | "list" | "image" | "shape" | "chart" | "table" | "video" | "code" | "metric" | "quote" | "timeline");
|
|
795
795
|
/**
|
package/dist/catalogs.d.ts
CHANGED
|
@@ -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-
|
|
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-DoVmvDr7.js';
|
|
2
2
|
import './schemas-BYe5y8-i.js';
|
package/dist/catalogs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { audiences, catalogEntries, catalogIndexes, catalogKinds, catalogSchemaNames, catalogs, chartTypes, colorSchemes, fontSchemes, languages, layouts, narratives, purposes, socialPlatforms, themes, tones } from './chunk-
|
|
1
|
+
export { audiences, catalogEntries, catalogIndexes, catalogKinds, catalogSchemaNames, catalogs, chartTypes, colorSchemes, fontSchemes, languages, layouts, narratives, purposes, socialPlatforms, themes, tones } from './chunk-TU7I3KSB.js';
|
|
@@ -4873,9 +4873,15 @@ var layout = {
|
|
|
4873
4873
|
"List",
|
|
4874
4874
|
"Image",
|
|
4875
4875
|
"Number",
|
|
4876
|
-
"
|
|
4876
|
+
"Metric",
|
|
4877
|
+
"Chart",
|
|
4878
|
+
"Table",
|
|
4879
|
+
"Code",
|
|
4880
|
+
"Video",
|
|
4881
|
+
"Quote",
|
|
4882
|
+
"Timeline"
|
|
4877
4883
|
],
|
|
4878
|
-
"description": "Primary kind of content the layout holds. Drives pickers and AI placement decisions."
|
|
4884
|
+
"description": "Primary kind of content the layout holds. Drives pickers and AI placement decisions. Metric is the canonical numeric/KPI category; Number remains an accepted legacy label."
|
|
4879
4885
|
},
|
|
4880
4886
|
"contentMultiple": {
|
|
4881
4887
|
"type": "string",
|
|
@@ -5083,6 +5089,9 @@ var layout = {
|
|
|
5083
5089
|
"subtitle",
|
|
5084
5090
|
"tag",
|
|
5085
5091
|
"text",
|
|
5092
|
+
"metric",
|
|
5093
|
+
"quote",
|
|
5094
|
+
"timeline",
|
|
5086
5095
|
"list",
|
|
5087
5096
|
"chart",
|
|
5088
5097
|
"picture",
|
|
@@ -5091,7 +5100,7 @@ var layout = {
|
|
|
5091
5100
|
"diagram",
|
|
5092
5101
|
"code"
|
|
5093
5102
|
],
|
|
5094
|
-
"description": "OPF placeholder kind. 'text' and 'list' are flexible textual content regions. The named kinds describe a specific content role used by pickers, AI generation, and engine defaulting."
|
|
5103
|
+
"description": "OPF placeholder kind. 'text' and 'list' are flexible textual content regions. 'metric' is a numeric/KPI content region filled by a metric payload, including its optional label, description, unit, delta, and trend. The named kinds describe a specific content role used by pickers, AI generation, and engine defaulting. 'picture' and legacy 'diagram' use image payloads; 'media' uses video. Quote and timeline use their corresponding payload fields."
|
|
5095
5104
|
}
|
|
5096
5105
|
}
|
|
5097
5106
|
},
|
|
@@ -5871,7 +5880,7 @@ var narrative = {
|
|
|
5871
5880
|
"quote",
|
|
5872
5881
|
"timeline"
|
|
5873
5882
|
],
|
|
5874
|
-
"description": "Default content kind for the beat's slide.
|
|
5883
|
+
"description": "Default content kind for the beat's slide. Uses ContentPayload.type names to help engines choose a layout. The legacy shape value is retained for compatibility and requests an image representation; it is not a native ContentPayload type."
|
|
5875
5884
|
},
|
|
5876
5885
|
"layoutHint": {
|
|
5877
5886
|
"type": "string",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { validatePresentation, validationDefinition, validateCatalogRecord } from './chunk-
|
|
2
|
-
import { schemas, schemaEntries } from './chunk-
|
|
3
|
-
import { catalogKinds, catalogEntries, catalogSchemaNames } from './chunk-
|
|
1
|
+
import { validatePresentation, validationDefinition, validateCatalogRecord } from './chunk-ZIL7MGZU.js';
|
|
2
|
+
import { schemas, schemaEntries } from './chunk-D3GQIREP.js';
|
|
3
|
+
import { catalogKinds, catalogEntries, catalogSchemaNames } from './chunk-TU7I3KSB.js';
|
|
4
4
|
import { parseTree, printParseErrorCode, getNodePath } from 'jsonc-parser';
|
|
5
5
|
|
|
6
6
|
var own = (value, key) => Object.hasOwn(value, key);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { assertValidPresentation } from './chunk-
|
|
2
|
-
import { resolveFontFamilies, resolveCanvasDimensions, composeSlide, tableRowBoundaries } from './chunk-
|
|
3
|
-
import { catalogs } from './chunk-
|
|
1
|
+
import { assertValidPresentation } from './chunk-ZIL7MGZU.js';
|
|
2
|
+
import { resolveFontFamilies, resolveCanvasDimensions, composeSlide, tableRowBoundaries } from './chunk-PQJRDNA6.js';
|
|
3
|
+
import { catalogs } from './chunk-TU7I3KSB.js';
|
|
4
4
|
|
|
5
5
|
// src/pagination.ts
|
|
6
6
|
var OPFPaginationError = class extends Error {
|
|
@@ -83,14 +83,23 @@ function paginateSlide(input, options = {}) {
|
|
|
83
83
|
};
|
|
84
84
|
source = withReadability(source);
|
|
85
85
|
let evaluations = 0;
|
|
86
|
-
const geometry = (slide) => {
|
|
86
|
+
const geometry = (slide, pageIndex = 0) => {
|
|
87
87
|
if (++evaluations > 2e4) throw new OPFPaginationError("Pagination exceeded its layout evaluation limit. Split the input into smaller sections.");
|
|
88
|
-
return composeSlide(withReadability(slide, true), options);
|
|
88
|
+
return composeSlide(withReadability(slide, true), { ...options, slideNumber: (options.slideNumber ?? sourceIndex + 1) + pageIndex });
|
|
89
89
|
};
|
|
90
90
|
const initial = geometry(source);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
const repeatedMappings = (pageIndex) => {
|
|
92
|
+
if (!initial.furniture) return [];
|
|
93
|
+
const paths = new Set(initial.items.filter((item) => headingFields.has(item.field)).map((item) => item.path));
|
|
94
|
+
for (const part of initial.furniture?.parts ?? []) {
|
|
95
|
+
paths.add(part.path);
|
|
96
|
+
if (part.sourcePath) paths.add(part.sourcePath);
|
|
97
|
+
}
|
|
98
|
+
return [...paths].map((sourcePath) => ({ sourcePath, outputPath: sourcePath.startsWith(`${sourceBase}.`) ? `slides.${sourceIndex + pageIndex}${sourcePath.slice(sourceBase.length)}` : sourcePath }));
|
|
99
|
+
};
|
|
100
|
+
if (!initial.diagnostics.length) return { slides: [source], pages: [{ slideIndex: sourceIndex, mappings: initial.items.map((item) => ({ sourcePath: item.path, outputPath: item.path })), ...initial.furniture ? { repeatedMappings: repeatedMappings(0) } : {} }] };
|
|
101
|
+
const headerIssues = initial.diagnostics.filter((issue) => headingFields.has(issue.path.slice(sourceBase.length + 1)) || initial.furniture?.diagnostics.includes(issue));
|
|
102
|
+
if (headerIssues.length) throw new OPFPaginationError("Repeated headings or header/footer content cannot fit or resolve. Change the repeated content or slide design before pagination.", headerIssues);
|
|
94
103
|
const leaves = initial.items.filter((item) => !headingFields.has(item.field)).map((item) => leafFor(item.path, item.field, item.value));
|
|
95
104
|
const leafPaths = new Set(leaves.map((leaf) => leaf.path));
|
|
96
105
|
const slides = [], pages = [];
|
|
@@ -135,11 +144,13 @@ function paginateSlide(input, options = {}) {
|
|
|
135
144
|
if (pageIndex > 0) delete slide.notes;
|
|
136
145
|
return { slide, mappings };
|
|
137
146
|
};
|
|
138
|
-
const diagnosticsFor = (portions) => geometry(project(portions, slides.length).slide).diagnostics;
|
|
147
|
+
const diagnosticsFor = (portions) => geometry(project(portions, slides.length).slide, slides.length).diagnostics;
|
|
139
148
|
const finish = () => {
|
|
140
149
|
if (!selected.size) return;
|
|
141
150
|
if (slides.length >= maxSlides) throw new OPFPaginationError(`Pagination needs more than ${maxSlides} slides. No partial result was returned.`);
|
|
142
151
|
const { slide, mappings } = project(selected, slides.length);
|
|
152
|
+
const issues = geometry(slide, slides.length).diagnostics;
|
|
153
|
+
if (issues.length) throw new OPFPaginationError("A continuation page does not fit at its final page number. No partial result was returned.", issues);
|
|
143
154
|
if (slides.length && typeof source.id === "string") {
|
|
144
155
|
let suffix = slides.length + 1, id = `${source.id}--${suffix}`;
|
|
145
156
|
while (reservedIds.has(id)) id = `${source.id}--${++suffix}`;
|
|
@@ -148,7 +159,7 @@ function paginateSlide(input, options = {}) {
|
|
|
148
159
|
}
|
|
149
160
|
assertValidPresentation({ slides: [slide] });
|
|
150
161
|
slides.push(slide);
|
|
151
|
-
pages.push({ slideIndex: sourceIndex + slides.length - 1, mappings });
|
|
162
|
+
pages.push({ slideIndex: sourceIndex + slides.length - 1, mappings, ...initial.furniture ? { repeatedMappings: repeatedMappings(slides.length - 1) } : {} });
|
|
152
163
|
selected = /* @__PURE__ */ new Map();
|
|
153
164
|
};
|
|
154
165
|
for (const leaf of leaves) {
|
|
@@ -218,10 +229,11 @@ function paginatePresentation(input, options = {}) {
|
|
|
218
229
|
const fontReference = design.fontScheme ?? theme.fontScheme ?? "roboto";
|
|
219
230
|
const fontScheme = typeof fontReference === "string" ? resolve("fontSchemes", fontReference) : { ...resolve("fontSchemes", fontReference.id), ...fontReference };
|
|
220
231
|
const fonts = resolveFontFamilies(fontScheme);
|
|
221
|
-
const result = paginateSlide(slide, { ...resolveCanvasDimensions(design.dimensions ?? theme.dimensions), layout, fonts, contentAlignment: design.contentAlignment, titleAlignment: design.titleAlignment, contentBox: design.contentBox, textMeasurement: options.textMeasurement, textRasterPadding: options.textRasterPadding, ...overrides, slideIndex: index, maxSlides: maxSlides - output.length, minFontSize: options.minFontSize, reservedIds });
|
|
232
|
+
const result = paginateSlide(slide, { ...resolveCanvasDimensions(design.dimensions ?? theme.dimensions), layout, fonts, contentAlignment: design.contentAlignment, titleAlignment: design.titleAlignment, contentBox: design.contentBox, textMeasurement: options.textMeasurement, textRasterPadding: options.textRasterPadding, ...overrides, presentation, slideIndex: index, slideNumber: output.length + 1, maxSlides: maxSlides - output.length, minFontSize: options.minFontSize, reservedIds });
|
|
222
233
|
const outputStart = output.length;
|
|
223
234
|
result.pages.forEach((page, pageIndex) => {
|
|
224
|
-
|
|
235
|
+
const remap = (mapping) => ({ ...mapping, outputPath: mapping.outputPath.replace(/^slides\.\d+/, `slides.${outputStart + pageIndex}`) });
|
|
236
|
+
pages.push({ sourceSlideIndex: index, slideIndex: outputStart + pageIndex, mappings: page.mappings.map(remap), ...page.repeatedMappings ? { repeatedMappings: page.repeatedMappings.map(remap) } : {} });
|
|
225
237
|
});
|
|
226
238
|
output.push(...result.slides);
|
|
227
239
|
reservedIds.push(...result.slides.map((slide2) => slide2.id).filter(Boolean));
|
|
@@ -146,8 +146,105 @@ var headings = /* @__PURE__ */ new Set(["title", "subtitle", "tag"]);
|
|
|
146
146
|
var rows = ["top", "middle", "bottom"];
|
|
147
147
|
var columns = ["left", "center", "right"];
|
|
148
148
|
var record2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
149
|
-
var kind = (field) => field === "items"
|
|
149
|
+
var kind = (field) => field === "items" ? "list" : field === "bullets" ? "text" : field;
|
|
150
150
|
var round = (value) => Math.round(value * 1e6) / 1e6 || value;
|
|
151
|
+
function layoutFurniture(input, options = {}) {
|
|
152
|
+
const slide = record2(input), width = options.width ?? 1280, height = options.height ?? 720, scale = Math.min(width, height) / 720;
|
|
153
|
+
const settings = { ...record2(record2(options.layout).composition), ...record2(slide.composition) };
|
|
154
|
+
assertComposition(settings);
|
|
155
|
+
const minimum = (settings.minFontSize ?? 16) * scale, size = Math.max(13 * scale, minimum), padding = (options.textRasterPadding ?? 2) * scale;
|
|
156
|
+
if (![width, height, scale, minimum, padding].every(Number.isFinite) || width <= 0 || height <= 0 || minimum <= 0 || padding < 0) throw new RangeError("Furniture requires finite positive dimensions and nonnegative raster padding.");
|
|
157
|
+
const number = options.slideNumber ?? (options.slideIndex ?? 0) + 1;
|
|
158
|
+
if (!Number.isSafeInteger(number) || number < 1) throw new RangeError("Displayed slide number must be a positive safe integer.");
|
|
159
|
+
const outlines = options.textMeasurement?.outlineBounds !== void 0, parts = [], diagnostics = [];
|
|
160
|
+
const sourceRoot = `slides.${options.slideIndex ?? 0}`, organizations = Array.isArray(options.presentation?.organization) ? options.presentation.organization : [options.presentation?.organization];
|
|
161
|
+
const primaryIndex = organizations.findIndex((item) => record2(item).role === "primary"), organizationIndex = primaryIndex >= 0 ? primaryIndex : organizations.findIndex(Boolean);
|
|
162
|
+
const organization = record2(organizations[organizationIndex]), organizationPath = Array.isArray(options.presentation?.organization) ? `organization.${organizationIndex}.name` : "organization.name";
|
|
163
|
+
const fontFamily = options.fonts?.body ?? "sans-serif";
|
|
164
|
+
let headerBottom = 0, footerTop = height, configured = false;
|
|
165
|
+
const error = (path, message, code = "text-overflow") => diagnostics.push({ code, path, message });
|
|
166
|
+
for (const kind2 of ["header", "footer"]) {
|
|
167
|
+
const local = record2(slide.design)[kind2] !== void 0, value = local ? record2(slide.design)[kind2] : options.presentation?.design?.[kind2];
|
|
168
|
+
if (value === void 0 || value === false) continue;
|
|
169
|
+
const root = local ? `${sourceRoot}.design.${kind2}` : `design.${kind2}`;
|
|
170
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("Header/footer content must be an object or false.");
|
|
171
|
+
configured = true;
|
|
172
|
+
const zones = [];
|
|
173
|
+
for (const [index, zone] of ["left", "center", "right"].entries()) {
|
|
174
|
+
const content = record2(record2(value)[zone]), path = `${root}.${zone}`, x = width * (0.07 + index * 0.3), zoneWidth = width * 0.26, zoneParts = [];
|
|
175
|
+
let y = 0;
|
|
176
|
+
const add = (field, text, generated = false, sourcePath) => {
|
|
177
|
+
if (text === void 0) return;
|
|
178
|
+
if (typeof text !== "string") throw new TypeError(`Furniture field ${path}.${field} requires string content.`);
|
|
179
|
+
const partPath = `${path}.${field}`, style = resolveTextStyle({ fontFamily, fontWeight: 400, italic: false, path: partPath }, options.textMeasurement);
|
|
180
|
+
const fit = fitText(text, { width: Math.max(Number.MIN_VALUE, zoneWidth - (outlines ? 2 * padding : 0)), height: Number.MAX_VALUE }, size, size, textWidthMeasurer(style, options.textMeasurement));
|
|
181
|
+
const ink = fit.sourceLines.map((line, index2) => {
|
|
182
|
+
let outline = null;
|
|
183
|
+
if (outlines) {
|
|
184
|
+
for (const segment of line.segments) if (segment.kind === "text") {
|
|
185
|
+
const bounds = measureTextOutline(text.slice(segment.start, segment.end), size, style, options.textMeasurement);
|
|
186
|
+
if (bounds) {
|
|
187
|
+
const next = { ...bounds, x: bounds.x + segment.x };
|
|
188
|
+
if (!outline) outline = next;
|
|
189
|
+
else {
|
|
190
|
+
const right = Math.max(outline.x + outline.width, next.x + next.width), bottom = Math.max(outline.y + outline.height, next.y + next.height);
|
|
191
|
+
outline.x = Math.min(outline.x, next.x);
|
|
192
|
+
outline.y = Math.min(outline.y, next.y);
|
|
193
|
+
outline.width = right - outline.x;
|
|
194
|
+
outline.height = bottom - outline.y;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return { width: line.width, y: index2 * fit.lineHeight, baseline: size + index2 * fit.lineHeight, height: fit.lineHeight, outline };
|
|
200
|
+
});
|
|
201
|
+
const natural = outlines ? placeTextLines(ink, { x, y, width: zoneWidth, height: Number.MAX_VALUE }, zone, padding) : void 0;
|
|
202
|
+
const partHeight = natural?.height ?? fit.sourceLines.length * fit.lineHeight;
|
|
203
|
+
if (!Number.isFinite(partHeight)) throw new RangeError("Furniture exceeds finite layout coordinates.");
|
|
204
|
+
const box = { x, y, width: zoneWidth, height: Math.max(scale, partHeight) }, placement = outlines ? placeTextLines(ink, box, zone, padding) : void 0;
|
|
205
|
+
const accepted = { ...fit, ...placement ? { placement } : {}, overflow: fit.overflow || !!placement?.overflow };
|
|
206
|
+
zoneParts.push({ type: "text", kind: kind2, zone, field, path: partPath, sourcePath: sourcePath ?? (!generated ? partPath : void 0), generated, text, style, requestedFontSize: 13 * scale, minFontSize: minimum, box, alignment: zone, fit: accepted });
|
|
207
|
+
if (accepted.overflow) error(partPath, "Repeated text exceeds its zone at the selected readability floor; change the furniture or slide design.");
|
|
208
|
+
y += box.height;
|
|
209
|
+
};
|
|
210
|
+
if (content.image !== void 0) {
|
|
211
|
+
const box = { x, y, width: zoneWidth, height: Math.max(32 * scale, Math.min(height * 0.05, 72 * scale)) };
|
|
212
|
+
zoneParts.push({ type: "image", kind: kind2, zone, field: "image", path: `${path}.image`, sourcePath: `${path}.image`, generated: false, image: content.image, box, alignment: zone });
|
|
213
|
+
y += box.height;
|
|
214
|
+
}
|
|
215
|
+
add("text", content.text);
|
|
216
|
+
if (content.organization === true) {
|
|
217
|
+
if (typeof organization.name === "string") add("organization", organization.name, true, organizationPath);
|
|
218
|
+
else error(`${path}.organization`, "Generated organization name needs a named organization in the presentation.", "unresolved-content");
|
|
219
|
+
}
|
|
220
|
+
if (content.section === true) {
|
|
221
|
+
if (typeof slide.section === "string") add("section", slide.section, true, `${sourceRoot}.section`);
|
|
222
|
+
else error(`${path}.section`, "Generated section needs a literal slide section.", "unresolved-content");
|
|
223
|
+
}
|
|
224
|
+
if (content.slideNumber === true) add("slideNumber", String(number), true);
|
|
225
|
+
if (content.date === true) error(`${path}.date`, "Use a literal date string for deterministic header/footer content.", "unresolved-content");
|
|
226
|
+
else if (typeof content.date === "string") add("date", content.date);
|
|
227
|
+
zones.push(zoneParts);
|
|
228
|
+
}
|
|
229
|
+
const tallest = Math.max(0, ...zones.map((zone) => zone.reduce((sum, part) => sum + part.box.height, 0)));
|
|
230
|
+
if (!tallest) continue;
|
|
231
|
+
const top = kind2 === "header" ? height * 0.025 : Math.max(height * 0.025, height - height * 0.04 - tallest);
|
|
232
|
+
if (kind2 === "header") headerBottom = top + tallest;
|
|
233
|
+
else footerTop = top;
|
|
234
|
+
for (const zone of zones) for (const part of zone) {
|
|
235
|
+
part.box.y += top;
|
|
236
|
+
if (part.type === "text" && part.fit.placement) for (const line of part.fit.placement.lines) {
|
|
237
|
+
line.y += top;
|
|
238
|
+
line.baseline += top;
|
|
239
|
+
if (line.outline) line.outline.y += top;
|
|
240
|
+
}
|
|
241
|
+
if (part.box.y + part.box.height > height - height * 0.04 + 0.01) error(part.path, "Repeated header/footer content extends beyond the usable slide height. Change its content or design before pagination.");
|
|
242
|
+
parts.push(part);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (headerBottom > footerTop + 0.01) error(parts.find((part) => part.kind === "footer")?.path ?? sourceRoot, "Header and footer content overlap; repeated content cannot be repaired by splitting body content.");
|
|
246
|
+
return { algorithm: "furniture-flow-v2", configured, textMeasurement: options.textMeasurement ? "provided" : "estimated", textOutlines: outlines ? "provided" : "unavailable", parts, headerBottom, footerTop, diagnostics, overflow: diagnostics.length > 0 };
|
|
247
|
+
}
|
|
151
248
|
var textSegments = new Intl.Segmenter("und", { granularity: "grapheme" });
|
|
152
249
|
function measureText(text, fontSize) {
|
|
153
250
|
let units = 0;
|
|
@@ -1078,7 +1175,11 @@ function composeSlide(input, options = {}) {
|
|
|
1078
1175
|
const fitContent = (field, value, text, box, size, minimum, path2) => field === "text" ? fitPlacedText(field, value, text, box, size, minimum, path2) : field === "items" || field === "bullets" ? fitList(value, box, size, minimum, { style: styleFor(field, path2), textMeasurement: options.textMeasurement }) : fitText(text, box, size, minimum, widthFor(field, path2));
|
|
1079
1176
|
const path = `slides.${options.slideIndex ?? 0}`;
|
|
1080
1177
|
const items = [], diagnostics = [];
|
|
1081
|
-
|
|
1178
|
+
const measuredFurniture = layoutFurniture(slide, options);
|
|
1179
|
+
const furniture = measuredFurniture.configured || measuredFurniture.diagnostics.length ? measuredFurniture : void 0;
|
|
1180
|
+
if (furniture) diagnostics.push(...furniture.diagnostics);
|
|
1181
|
+
const bodyBottom = Math.min(height - padding, furniture && furniture.footerTop < height ? furniture.footerTop - gap * 0.5 : height - padding);
|
|
1182
|
+
let y = Math.max(padding, furniture?.headerBottom ? furniture.headerBottom + gap * 0.5 : padding);
|
|
1082
1183
|
for (const field of ["tag", "title", "subtitle"]) {
|
|
1083
1184
|
if (!slide[field]) continue;
|
|
1084
1185
|
const requested = (field === "title" ? 54 : field === "tag" ? 16 : 25) * scale;
|
|
@@ -1086,11 +1187,12 @@ function composeSlide(input, options = {}) {
|
|
|
1086
1187
|
const box = { x: padding, y, width: width - padding * 2, height: maxHeight };
|
|
1087
1188
|
const text = fitPlacedText(field, slide[field], String(slide[field]), box, requested, minSize, `${path}.${field}`);
|
|
1088
1189
|
box.height = Math.min(maxHeight, Math.max(text.lines.length * text.lineHeight, text.placement?.height ?? 0));
|
|
1190
|
+
if (furniture && box.y + box.height > bodyBottom + 0.01) diagnostics.push({ code: "text-overflow", path: `${path}.${field}`, message: "Repeated furniture leaves too little room for this heading. Change the header/footer or slide design." });
|
|
1089
1191
|
items.push({ path: `${path}.${field}`, field, type: "text", value: slide[field], payload: { text: slide[field] }, box, text, textStyle: styleFor(field, `${path}.${field}`), composition });
|
|
1090
1192
|
y += box.height + gap * 0.5;
|
|
1091
1193
|
}
|
|
1092
1194
|
if (items.length) y += gap * 0.5;
|
|
1093
|
-
const contentBox = { x: padding, y, width: width - padding * 2, height: Math.max(scale,
|
|
1195
|
+
const contentBox = { x: padding, y, width: width - padding * 2, height: Math.max(scale, bodyBottom - y) };
|
|
1094
1196
|
const collect = (host, basePath, depth = 0, ancestors = []) => {
|
|
1095
1197
|
if (Array.isArray(host.blocks)) {
|
|
1096
1198
|
if (depth >= MAX_COMPOSITION_DEPTH || ancestors.includes(host)) throw new RangeError(`Content groups must be acyclic and nest at most ${MAX_COMPOSITION_DEPTH} levels.`);
|
|
@@ -1285,6 +1387,7 @@ function composeSlide(input, options = {}) {
|
|
|
1285
1387
|
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]);
|
|
1286
1388
|
const strictPaths = new Set(items.filter((item) => item.composition.overflow === "error").map((item) => item.path));
|
|
1287
1389
|
const failures = diagnostics.filter((diagnostic) => {
|
|
1390
|
+
if (composition.overflow === "error") return true;
|
|
1288
1391
|
let path2 = diagnostic.path;
|
|
1289
1392
|
while (path2) {
|
|
1290
1393
|
if (strictPaths.has(path2)) return true;
|
|
@@ -1295,7 +1398,7 @@ function composeSlide(input, options = {}) {
|
|
|
1295
1398
|
return false;
|
|
1296
1399
|
});
|
|
1297
1400
|
const explanation = decisions ? {
|
|
1298
|
-
algorithm: "grid-score-
|
|
1401
|
+
algorithm: "grid-score-v9",
|
|
1299
1402
|
textMeasurement: options.textMeasurement ? "provided" : "estimated",
|
|
1300
1403
|
textOutlines: options.textMeasurement?.outlineBounds ? "provided" : "unavailable",
|
|
1301
1404
|
textRasterPadding: rasterPadding,
|
|
@@ -1303,7 +1406,7 @@ function composeSlide(input, options = {}) {
|
|
|
1303
1406
|
unmeasuredPayloads: items.filter((item) => !headings.has(item.field) && !item.quoteLayout && !item.codeLayout && !item.metricLayout && !item.timelineLayout && !["text", "items", "bullets", "table"].includes(item.field)).map((item) => item.path)
|
|
1304
1407
|
} : void 0;
|
|
1305
1408
|
if (failures.length) throw new OPFCompositionError(failures, explanation);
|
|
1306
|
-
return { width, height, contentBox, items, groups, flows, diagnostics, composition, ...explanation ? { explanation } : {} };
|
|
1409
|
+
return { width, height, contentBox, items, groups, flows, diagnostics, composition, ...furniture ? { furniture } : {}, ...explanation ? { explanation } : {} };
|
|
1307
1410
|
}
|
|
1308
1411
|
function resolveCanvasDimensions(input) {
|
|
1309
1412
|
const presets = {
|
|
@@ -1323,4 +1426,4 @@ function resolveCanvasDimensions(input) {
|
|
|
1323
1426
|
return { width, height };
|
|
1324
1427
|
}
|
|
1325
1428
|
|
|
1326
|
-
export { MAX_COMPOSITION_DEPTH, OPFCompositionError, chartColorForFill, colorContrast, composeSlide, fitList, fitRichText, fitText, layoutCode, layoutMetric, layoutQuote, layoutTable, layoutTimeline, measureText, measureTextOutline, placeTextLines, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, tableGrid, tableRowBoundaries, textColorForFill, textWidthMeasurer, wrapText };
|
|
1429
|
+
export { MAX_COMPOSITION_DEPTH, OPFCompositionError, chartColorForFill, colorContrast, composeSlide, fitList, fitRichText, fitText, layoutCode, layoutFurniture, layoutMetric, layoutQuote, layoutTable, layoutTimeline, measureText, measureTextOutline, placeTextLines, resolveCanvasDimensions, resolveFontFamilies, resolveTextStyle, tableGrid, tableRowBoundaries, textColorForFill, textWidthMeasurer, wrapText };
|
|
@@ -1872,6 +1872,12 @@ var specFileEntries = [
|
|
|
1872
1872
|
"kind": "catalogRecord",
|
|
1873
1873
|
"mediaType": "application/json"
|
|
1874
1874
|
},
|
|
1875
|
+
{
|
|
1876
|
+
"path": "catalogs/layouts/quote-1x.json",
|
|
1877
|
+
"packagePath": "@openpresentation/opf/spec/catalogs/layouts/quote-1x.json",
|
|
1878
|
+
"kind": "catalogRecord",
|
|
1879
|
+
"mediaType": "application/json"
|
|
1880
|
+
},
|
|
1875
1881
|
{
|
|
1876
1882
|
"path": "catalogs/layouts/table-1x.json",
|
|
1877
1883
|
"packagePath": "@openpresentation/opf/spec/catalogs/layouts/table-1x.json",
|
|
@@ -1896,6 +1902,12 @@ var specFileEntries = [
|
|
|
1896
1902
|
"kind": "catalogRecord",
|
|
1897
1903
|
"mediaType": "application/json"
|
|
1898
1904
|
},
|
|
1905
|
+
{
|
|
1906
|
+
"path": "catalogs/layouts/timeline-1x.json",
|
|
1907
|
+
"packagePath": "@openpresentation/opf/spec/catalogs/layouts/timeline-1x.json",
|
|
1908
|
+
"kind": "catalogRecord",
|
|
1909
|
+
"mediaType": "application/json"
|
|
1910
|
+
},
|
|
1899
1911
|
{
|
|
1900
1912
|
"path": "catalogs/layouts/title-subtitle.json",
|
|
1901
1913
|
"packagePath": "@openpresentation/opf/spec/catalogs/layouts/title-subtitle.json",
|
|
@@ -3188,10 +3200,12 @@ var specFilePaths = [
|
|
|
3188
3200
|
"catalogs/layouts/number-4x.json",
|
|
3189
3201
|
"catalogs/layouts/number-5x.json",
|
|
3190
3202
|
"catalogs/layouts/number-6x.json",
|
|
3203
|
+
"catalogs/layouts/quote-1x.json",
|
|
3191
3204
|
"catalogs/layouts/table-1x.json",
|
|
3192
3205
|
"catalogs/layouts/text-1x.json",
|
|
3193
3206
|
"catalogs/layouts/text-2x.json",
|
|
3194
3207
|
"catalogs/layouts/text-3x.json",
|
|
3208
|
+
"catalogs/layouts/timeline-1x.json",
|
|
3195
3209
|
"catalogs/layouts/title-subtitle.json",
|
|
3196
3210
|
"catalogs/layouts/title.json",
|
|
3197
3211
|
"catalogs/narratives/board-meeting.json",
|
|
@@ -1177,7 +1177,7 @@ var layouts = [
|
|
|
1177
1177
|
"type": "title"
|
|
1178
1178
|
},
|
|
1179
1179
|
{
|
|
1180
|
-
"type": "
|
|
1180
|
+
"type": "metric"
|
|
1181
1181
|
}
|
|
1182
1182
|
]
|
|
1183
1183
|
},
|
|
@@ -1190,10 +1190,10 @@ var layouts = [
|
|
|
1190
1190
|
"type": "title"
|
|
1191
1191
|
},
|
|
1192
1192
|
{
|
|
1193
|
-
"type": "
|
|
1193
|
+
"type": "metric"
|
|
1194
1194
|
},
|
|
1195
1195
|
{
|
|
1196
|
-
"type": "
|
|
1196
|
+
"type": "metric"
|
|
1197
1197
|
}
|
|
1198
1198
|
],
|
|
1199
1199
|
"composition": {
|
|
@@ -1209,13 +1209,13 @@ var layouts = [
|
|
|
1209
1209
|
"type": "title"
|
|
1210
1210
|
},
|
|
1211
1211
|
{
|
|
1212
|
-
"type": "
|
|
1212
|
+
"type": "metric"
|
|
1213
1213
|
},
|
|
1214
1214
|
{
|
|
1215
|
-
"type": "
|
|
1215
|
+
"type": "metric"
|
|
1216
1216
|
},
|
|
1217
1217
|
{
|
|
1218
|
-
"type": "
|
|
1218
|
+
"type": "metric"
|
|
1219
1219
|
}
|
|
1220
1220
|
],
|
|
1221
1221
|
"composition": {
|
|
@@ -1231,16 +1231,16 @@ var layouts = [
|
|
|
1231
1231
|
"type": "title"
|
|
1232
1232
|
},
|
|
1233
1233
|
{
|
|
1234
|
-
"type": "
|
|
1234
|
+
"type": "metric"
|
|
1235
1235
|
},
|
|
1236
1236
|
{
|
|
1237
|
-
"type": "
|
|
1237
|
+
"type": "metric"
|
|
1238
1238
|
},
|
|
1239
1239
|
{
|
|
1240
|
-
"type": "
|
|
1240
|
+
"type": "metric"
|
|
1241
1241
|
},
|
|
1242
1242
|
{
|
|
1243
|
-
"type": "
|
|
1243
|
+
"type": "metric"
|
|
1244
1244
|
}
|
|
1245
1245
|
],
|
|
1246
1246
|
"composition": {
|
|
@@ -1257,19 +1257,19 @@ var layouts = [
|
|
|
1257
1257
|
"type": "title"
|
|
1258
1258
|
},
|
|
1259
1259
|
{
|
|
1260
|
-
"type": "
|
|
1260
|
+
"type": "metric"
|
|
1261
1261
|
},
|
|
1262
1262
|
{
|
|
1263
|
-
"type": "
|
|
1263
|
+
"type": "metric"
|
|
1264
1264
|
},
|
|
1265
1265
|
{
|
|
1266
|
-
"type": "
|
|
1266
|
+
"type": "metric"
|
|
1267
1267
|
},
|
|
1268
1268
|
{
|
|
1269
|
-
"type": "
|
|
1269
|
+
"type": "metric"
|
|
1270
1270
|
},
|
|
1271
1271
|
{
|
|
1272
|
-
"type": "
|
|
1272
|
+
"type": "metric"
|
|
1273
1273
|
}
|
|
1274
1274
|
],
|
|
1275
1275
|
"composition": {
|
|
@@ -1286,22 +1286,22 @@ var layouts = [
|
|
|
1286
1286
|
"type": "title"
|
|
1287
1287
|
},
|
|
1288
1288
|
{
|
|
1289
|
-
"type": "
|
|
1289
|
+
"type": "metric"
|
|
1290
1290
|
},
|
|
1291
1291
|
{
|
|
1292
|
-
"type": "
|
|
1292
|
+
"type": "metric"
|
|
1293
1293
|
},
|
|
1294
1294
|
{
|
|
1295
|
-
"type": "
|
|
1295
|
+
"type": "metric"
|
|
1296
1296
|
},
|
|
1297
1297
|
{
|
|
1298
|
-
"type": "
|
|
1298
|
+
"type": "metric"
|
|
1299
1299
|
},
|
|
1300
1300
|
{
|
|
1301
|
-
"type": "
|
|
1301
|
+
"type": "metric"
|
|
1302
1302
|
},
|
|
1303
1303
|
{
|
|
1304
|
-
"type": "
|
|
1304
|
+
"type": "metric"
|
|
1305
1305
|
}
|
|
1306
1306
|
],
|
|
1307
1307
|
"composition": {
|
|
@@ -1309,6 +1309,19 @@ var layouts = [
|
|
|
1309
1309
|
"columns": 3
|
|
1310
1310
|
}
|
|
1311
1311
|
},
|
|
1312
|
+
{
|
|
1313
|
+
"$schema": "https://openpresentation.org/schema/opf-layout/v1",
|
|
1314
|
+
"id": "quote-1x",
|
|
1315
|
+
"name": "Quote 1x",
|
|
1316
|
+
"placeholders": [
|
|
1317
|
+
{
|
|
1318
|
+
"type": "title"
|
|
1319
|
+
},
|
|
1320
|
+
{
|
|
1321
|
+
"type": "quote"
|
|
1322
|
+
}
|
|
1323
|
+
]
|
|
1324
|
+
},
|
|
1312
1325
|
{
|
|
1313
1326
|
"$schema": "https://openpresentation.org/schema/opf-layout/v1",
|
|
1314
1327
|
"id": "table-1x",
|
|
@@ -1382,6 +1395,19 @@ var layouts = [
|
|
|
1382
1395
|
"mode": "row"
|
|
1383
1396
|
}
|
|
1384
1397
|
},
|
|
1398
|
+
{
|
|
1399
|
+
"$schema": "https://openpresentation.org/schema/opf-layout/v1",
|
|
1400
|
+
"id": "timeline-1x",
|
|
1401
|
+
"name": "Timeline 1x",
|
|
1402
|
+
"placeholders": [
|
|
1403
|
+
{
|
|
1404
|
+
"type": "title"
|
|
1405
|
+
},
|
|
1406
|
+
{
|
|
1407
|
+
"type": "timeline"
|
|
1408
|
+
}
|
|
1409
|
+
]
|
|
1410
|
+
},
|
|
1385
1411
|
{
|
|
1386
1412
|
"$schema": "https://openpresentation.org/schema/opf-layout/v1",
|
|
1387
1413
|
"id": "title",
|
|
@@ -15562,6 +15588,11 @@ var catalogIndexes = {
|
|
|
15562
15588
|
"name": "Number 6x",
|
|
15563
15589
|
"file": "number-6x.json"
|
|
15564
15590
|
},
|
|
15591
|
+
{
|
|
15592
|
+
"id": "quote-1x",
|
|
15593
|
+
"name": "Quote 1x",
|
|
15594
|
+
"file": "quote-1x.json"
|
|
15595
|
+
},
|
|
15565
15596
|
{
|
|
15566
15597
|
"id": "table-1x",
|
|
15567
15598
|
"name": "Table 1x",
|
|
@@ -15582,6 +15613,11 @@ var catalogIndexes = {
|
|
|
15582
15613
|
"name": "Text 3x",
|
|
15583
15614
|
"file": "text-3x.json"
|
|
15584
15615
|
},
|
|
15616
|
+
{
|
|
15617
|
+
"id": "timeline-1x",
|
|
15618
|
+
"name": "Timeline 1x",
|
|
15619
|
+
"file": "timeline-1x.json"
|
|
15620
|
+
},
|
|
15585
15621
|
{
|
|
15586
15622
|
"id": "title",
|
|
15587
15623
|
"name": "Title",
|
|
@@ -18231,10 +18267,12 @@ var catalogEntries = [
|
|
|
18231
18267
|
"number-4x.json",
|
|
18232
18268
|
"number-5x.json",
|
|
18233
18269
|
"number-6x.json",
|
|
18270
|
+
"quote-1x.json",
|
|
18234
18271
|
"table-1x.json",
|
|
18235
18272
|
"text-1x.json",
|
|
18236
18273
|
"text-2x.json",
|
|
18237
18274
|
"text-3x.json",
|
|
18275
|
+
"timeline-1x.json",
|
|
18238
18276
|
"title.json",
|
|
18239
18277
|
"title-subtitle.json"
|
|
18240
18278
|
], records: layouts, index: catalogIndexes.layouts },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { MAX_COMPOSITION_DEPTH, tableGrid } from './chunk-
|
|
2
|
-
import { schemas, schemaEntries } from './chunk-
|
|
3
|
-
import { catalogSchemaNames } from './chunk-
|
|
1
|
+
import { MAX_COMPOSITION_DEPTH, tableGrid } from './chunk-PQJRDNA6.js';
|
|
2
|
+
import { schemas, schemaEntries } from './chunk-D3GQIREP.js';
|
|
3
|
+
import { catalogSchemaNames } from './chunk-TU7I3KSB.js';
|
|
4
4
|
import Ajv2020 from 'ajv/dist/2020.js';
|
|
5
5
|
import addFormats from 'ajv-formats';
|
|
6
6
|
|
|
@@ -67,10 +67,12 @@ var catalogIds = {
|
|
|
67
67
|
"number-4x",
|
|
68
68
|
"number-5x",
|
|
69
69
|
"number-6x",
|
|
70
|
+
"quote-1x",
|
|
70
71
|
"table-1x",
|
|
71
72
|
"text-1x",
|
|
72
73
|
"text-2x",
|
|
73
74
|
"text-3x",
|
|
75
|
+
"timeline-1x",
|
|
74
76
|
"title",
|
|
75
77
|
"title-subtitle"
|
|
76
78
|
],
|