@openpresentation/opf 0.16.0 → 0.18.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/README.md +16 -2
- package/dist/browser.js +38 -0
- package/dist/{catalog-refs-_anZs-Qz.d.ts → catalog-refs-BSfGELpt.d.ts} +1 -1
- package/dist/{catalog-schemas-BZCjgAxu.d.ts → catalog-schemas-CinPh5WA.d.ts} +1 -1
- package/dist/catalog.d.ts +2 -2
- package/dist/catalog.js +3 -23712
- package/dist/{chunk-BSYQDWT4.js → chunk-64YSJZDH.js} +28 -15
- package/dist/{chunk-WEDGVC5W.js → chunk-6WW2EDUW.js} +108 -108
- package/dist/{chunk-5UEUAW57.js → chunk-CJPDTLGD.js} +1 -1
- package/dist/{chunk-FQSIIADJ.js → chunk-DBYZI75U.js} +30 -5
- package/dist/{chunk-PX5AWL7Y.js → chunk-JT3QXA22.js} +99 -29
- package/dist/{chunk-6OR326EA.js → chunk-JXVS7BJK.js} +1 -1
- package/dist/chunk-KXBZ6ZSB.js +991 -0
- package/dist/{chunk-5PDN7BN6.js → chunk-MBU42S5Y.js} +427 -167
- package/dist/{chunk-K4DQQIT4.js → chunk-NS3VYB57.js} +5 -5
- package/dist/chunk-QLFWWHRF.js +919 -0
- package/dist/{chunk-5TO6I3GB.js → chunk-RAKEVM52.js} +1 -1
- package/dist/{chunk-NAWQR7MN.js → chunk-VCGRMNY2.js} +1 -1
- package/dist/chunk-WCG3THTD.js +23712 -0
- package/dist/{chunk-UM24JFUE.js → chunk-YWXB4XAT.js} +2 -2
- package/dist/{chunk-3XDRQYWZ.js → chunk-YZXTXX2D.js} +1 -1
- package/dist/{chunk-SMLMI6IZ.js → chunk-ZCFSDSUX.js} +3 -3
- package/dist/{chunk-KINQFU6X.js → chunk-ZOHFLP3O.js} +19 -12
- package/dist/{composition-D4ADF5c-.d.ts → composition-C-OoqvAr.d.ts} +227 -167
- package/dist/composition.d.ts +2 -2
- package/dist/composition.js +1 -1
- package/dist/conversion-C5foedh6.d.ts +502 -0
- package/dist/conversion-KGDVJM75.js +21 -0
- package/dist/convert.d.ts +3 -3
- package/dist/convert.js +7 -7
- package/dist/data.d.ts +6 -6
- package/dist/data.js +2 -2
- package/dist/deck-report-wszX7uo8.d.ts +40 -0
- package/dist/deck.d.ts +21 -51
- package/dist/deck.js +10 -10
- package/dist/diff.js +5 -5
- package/dist/docs.js +33 -27
- package/dist/examples.d.ts +1 -1
- package/dist/examples.js +418 -451
- package/dist/format.js +2 -2
- package/dist/index.d.ts +179 -16
- package/dist/index.js +51 -796
- package/dist/markdown.d.ts +10 -6
- package/dist/markdown.js +7 -7
- package/dist/node-engine.d.ts +114 -0
- package/dist/node-engine.js +44 -0
- package/dist/{pagination-DzZPFheM.d.ts → pagination-BaUjDrTz.d.ts} +25 -8
- package/dist/pagination.d.ts +3 -3
- package/dist/pagination.js +4 -4
- package/dist/patch.js +4 -4
- package/dist/{presentation-COrFuj2g.d.ts → presentation-HondgJ3l.d.ts} +278 -454
- package/dist/repo-readme.js +1 -1
- package/dist/schemas.d.ts +4 -2
- package/dist/schemas.js +1 -1
- package/dist/spec/catalogs/manifest.json +1 -1
- package/dist/spec/schemas/opf.schema.json +74 -100
- package/dist/types.d.ts +2 -2
- package/dist/{validation-types-HxIgJfKx.d.ts → validation-types-PbGDrNUr.d.ts} +23 -7
- package/dist/validator.d.ts +6 -6
- package/dist/validator.js +3 -3
- package/dist/yaml.d.ts +4 -4
- package/dist/yaml.js +7 -7
- package/package.json +37 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { assertValid, resolveSlideContext } from './chunk-
|
|
2
|
-
import { composeSlide, codeHighlightSlice, tableRowBoundaries, visitContentPayloads, sliceNumberedItems } from './chunk-
|
|
1
|
+
import { assertValid, resolveSlideScriptMeasurement, resolveSlideContext } from './chunk-JT3QXA22.js';
|
|
2
|
+
import { resolveSlideVariables, usesSlideBuiltin, composeSlide, codeHighlightSlice, tableRowBoundaries, visitContentPayloads, slideTokenSpans, sliceNumberedItems } from './chunk-MBU42S5Y.js';
|
|
3
3
|
import { checkCatalogsOption } from './chunk-SPFV27O5.js';
|
|
4
4
|
|
|
5
5
|
// src/pagination.ts
|
|
@@ -39,6 +39,10 @@ var deckContext = (presentation) => {
|
|
|
39
39
|
const deck = isRecord(presentation) ? presentation : {};
|
|
40
40
|
return { ...Array.isArray(deck.references) ? { references: deck.references } : {}, ...isRecord(deck.datasets) ? { datasets: deck.datasets } : {} };
|
|
41
41
|
};
|
|
42
|
+
function keepTokensWhole(boundaries, text) {
|
|
43
|
+
const spans = slideTokenSpans(text);
|
|
44
|
+
return spans.length ? boundaries.filter((boundary) => !spans.some(([start, end]) => boundary > start && boundary < end)) : boundaries;
|
|
45
|
+
}
|
|
42
46
|
function leafFor(path, field, value) {
|
|
43
47
|
const leaf = { path, field, value };
|
|
44
48
|
let text;
|
|
@@ -62,7 +66,7 @@ function leafFor(path, field, value) {
|
|
|
62
66
|
}
|
|
63
67
|
if (text !== void 0) {
|
|
64
68
|
leaf.unit = "utf16";
|
|
65
|
-
leaf.boundaries = [0, ...Array.from(graphemes.segment(text), (segment) => segment.index + segment.segment.length)];
|
|
69
|
+
leaf.boundaries = keepTokensWhole([0, ...Array.from(graphemes.segment(text), (segment) => segment.index + segment.segment.length)], text);
|
|
66
70
|
} else if (field === "items" || field === "bullets") {
|
|
67
71
|
leaf.unit = "items";
|
|
68
72
|
leaf.boundaries = Array.from({ length: value.length + 1 }, (_, i) => i);
|
|
@@ -103,8 +107,9 @@ function paginateSlide(input, options = {}) {
|
|
|
103
107
|
};
|
|
104
108
|
source = withReadability(source);
|
|
105
109
|
let evaluations = 0;
|
|
106
|
-
const { fonts, minFontSize: _minFontSize, maxSlides: _maxSlides, reservedIds: _reservedIds, ...engineOptions } = options;
|
|
107
|
-
const
|
|
110
|
+
const { fonts, catalogs, minFontSize: _minFontSize, maxSlides: _maxSlides, reservedIds: _reservedIds, ...engineOptions } = options;
|
|
111
|
+
const textMeasurement = fonts?.textMeasurement && options.presentation !== void 0 ? resolveSlideScriptMeasurement(options.presentation, sourceIndex, fonts.textMeasurement, { ...catalogs !== void 0 ? { catalogs } : {}, ...options.fontFamilies ? { fontFamilies: options.fontFamilies } : {} }) : fonts?.textMeasurement;
|
|
112
|
+
const composeOptions = { ...engineOptions, ...textMeasurement ? { textMeasurement } : {} };
|
|
108
113
|
const readableLayout = (layout) => {
|
|
109
114
|
if (!isRecord(layout) || !Array.isArray(layout.placeholders) || !layout.placeholders.some((entry) => isRecord(entry) && entry.type === "group")) return layout;
|
|
110
115
|
const visit = (entries) => entries.map((entry) => {
|
|
@@ -115,9 +120,13 @@ function paginateSlide(input, options = {}) {
|
|
|
115
120
|
return { ...layout, placeholders: visit(layout.placeholders) };
|
|
116
121
|
};
|
|
117
122
|
if (options.layout !== void 0) composeOptions.layout = readableLayout(options.layout);
|
|
123
|
+
const presentationSlides = options.presentation?.slides;
|
|
124
|
+
const slideCount = options.slideCount ?? (Array.isArray(presentationSlides) && presentationSlides.length ? presentationSlides.length : void 0);
|
|
125
|
+
const logoContext = options.presentation !== void 0 ? { presentation: options.presentation, darkBackground: options.darkBackground === true } : {};
|
|
118
126
|
const geometry = (slide, pageIndex = 0) => {
|
|
119
127
|
if (++evaluations > 2e4) throw new OPFPaginationError("Pagination exceeded its layout evaluation limit. Split the input into smaller sections.");
|
|
120
|
-
|
|
128
|
+
const slideNumber = (options.slideNumber ?? sourceIndex + 1) + pageIndex;
|
|
129
|
+
return composeSlide(resolveSlideVariables(withReadability(slide, true), { slideNumber, ...slideCount !== void 0 ? { slideCount } : {}, ...logoContext }), { ...composeOptions, slideNumber });
|
|
121
130
|
};
|
|
122
131
|
const initial = geometry(source);
|
|
123
132
|
const fit = (diagnostics) => diagnostics.filter((issue) => issue.code === "text-overflow" || issue.code === "small-cell" || !!initial.furniture?.diagnostics.includes(issue));
|
|
@@ -133,7 +142,14 @@ function paginateSlide(input, options = {}) {
|
|
|
133
142
|
if (!fit(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) } : {} }] };
|
|
134
143
|
const headerIssues = initial.diagnostics.filter((issue) => headingFields.has(issue.path.slice(sourceBase.length + 1)) || initial.furniture?.diagnostics.includes(issue));
|
|
135
144
|
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);
|
|
136
|
-
const
|
|
145
|
+
const tokens = resolveSlideVariables(source, { slideNumber: 1, slideCount: 1, ...logoContext }) !== source;
|
|
146
|
+
const sourceValue = (path, fallback) => {
|
|
147
|
+
if (!tokens || !path.startsWith(`${sourceBase}.`)) return fallback;
|
|
148
|
+
let node = source;
|
|
149
|
+
for (const key of path.slice(sourceBase.length + 1).split(".")) node = isRecord(node) || Array.isArray(node) ? node[key] : void 0;
|
|
150
|
+
return node === void 0 ? fallback : node;
|
|
151
|
+
};
|
|
152
|
+
const leaves = initial.items.filter((item) => !headingFields.has(item.field)).map((item) => leafFor(item.path, item.field, sourceValue(item.path, item.value)));
|
|
137
153
|
const leafPaths = new Set(leaves.map((leaf) => leaf.path));
|
|
138
154
|
const slides = [], pages = [];
|
|
139
155
|
let selected = /* @__PURE__ */ new Map();
|
|
@@ -256,10 +272,6 @@ function paginateSlide(input, options = {}) {
|
|
|
256
272
|
if (!slides.length) throw new OPFPaginationError("No body content can be paginated.", initial.diagnostics);
|
|
257
273
|
return { slides, pages };
|
|
258
274
|
}
|
|
259
|
-
var usesSlideTotal = (presentation) => [presentation, ...presentation.slides].some((owner) => ["header", "footer"].some((kind) => ["left", "center", "right"].some((zone) => {
|
|
260
|
-
const content = owner?.design?.[kind]?.[zone];
|
|
261
|
-
return content?.slideNumber === true && typeof content.slideNumberFormat === "string" && content.slideNumberFormat.includes("{total}");
|
|
262
|
-
})));
|
|
263
275
|
function paginate(input, options = {}) {
|
|
264
276
|
checkCatalogsOption(options.catalogs, "paginate");
|
|
265
277
|
assertValid(input, { only: ["format"] });
|
|
@@ -280,7 +292,7 @@ function paginate(input, options = {}) {
|
|
|
280
292
|
}
|
|
281
293
|
}
|
|
282
294
|
if (output2.length >= maxSlides) throw new OPFPaginationError(`Pagination needs more than ${maxSlides} slides. No partial result was returned.`);
|
|
283
|
-
const result2 = paginateSlide(slide, { ...context.options, textRasterPadding: options.textRasterPadding, fonts: options.fonts, presentation, slideIndex: index, slideNumber: output2.length + 1, slideCount: slideCount2, date: options.date, maxSlides: maxSlides - output2.length, minFontSize: options.minFontSize, reservedIds });
|
|
295
|
+
const result2 = paginateSlide(slide, { ...context.options, textRasterPadding: options.textRasterPadding, fonts: options.fonts, ...options.catalogs ? { catalogs: options.catalogs } : {}, presentation, slideIndex: index, slideNumber: output2.length + 1, slideCount: slideCount2, date: options.date, maxSlides: maxSlides - output2.length, minFontSize: options.minFontSize, reservedIds });
|
|
284
296
|
const outputStart = output2.length;
|
|
285
297
|
result2.pages.forEach((page, pageIndex) => {
|
|
286
298
|
const remap = (mapping) => ({ ...mapping, outputPath: mapping.outputPath.replace(/^slides\.\d+/, `slides.${outputStart + pageIndex}`) });
|
|
@@ -291,16 +303,17 @@ function paginate(input, options = {}) {
|
|
|
291
303
|
});
|
|
292
304
|
return { output: output2, pages: pages2 };
|
|
293
305
|
};
|
|
306
|
+
const usesSlideCount = usesSlideBuiltin(presentation, "deck.slideCount");
|
|
294
307
|
let slideCount = presentation.slides.length, result = run(slideCount);
|
|
295
|
-
for (let attempt = 0;
|
|
296
|
-
if (attempt >= 3) throw new OPFPaginationError("
|
|
308
|
+
for (let attempt = 0; usesSlideCount && result.output.length !== slideCount; attempt++) {
|
|
309
|
+
if (attempt >= 3) throw new OPFPaginationError("The slide count did not converge. Change the text around {{deck.slideCount}} or split the input.");
|
|
297
310
|
slideCount = result.output.length;
|
|
298
311
|
result = run(slideCount);
|
|
299
312
|
}
|
|
300
313
|
const { output, pages } = result;
|
|
301
314
|
presentation.slides = output;
|
|
302
315
|
assertValid(presentation, { only: ["format"] });
|
|
303
|
-
return { presentation, pages };
|
|
316
|
+
return { presentation, pages, layout: options.fonts ? "measured" : "estimated" };
|
|
304
317
|
}
|
|
305
318
|
|
|
306
319
|
export { OPFPaginationError, paginate, paginateSlide };
|
|
@@ -52,7 +52,7 @@ var presentation = {
|
|
|
52
52
|
"minItems": 1
|
|
53
53
|
}
|
|
54
54
|
],
|
|
55
|
-
"description": "Organization associated with the presentation, usually the presenting company. Array form supports hosts, partners, clients, and sponsors. The primary organization (Organization.role 'primary', else the first item) supplies the
|
|
55
|
+
"description": "Organization associated with the presentation, usually the presenting company. Array form supports hosts, partners, clients, and sponsors. The primary organization (Organization.role 'primary', else the first item) supplies the cover and section logo, the 'socials' header/footer field and the 'organization.<field>' built-in variables; the others are available by id as 'organization.<id>.<field>' and are not drawn automatically."
|
|
56
56
|
},
|
|
57
57
|
"speaker": {
|
|
58
58
|
"oneOf": [
|
|
@@ -67,7 +67,7 @@ var presentation = {
|
|
|
67
67
|
"minItems": 1
|
|
68
68
|
}
|
|
69
69
|
],
|
|
70
|
-
"description": "Person presenting the deck. Array form supports panels and multi-speaker decks. The first speaker is the primary speaker: the built-in variables 'speaker.<field>'
|
|
70
|
+
"description": "Person presenting the deck. Array form supports panels and multi-speaker decks. The first speaker is the primary speaker: the built-in variables 'speaker.<field>' read it, 'speakers' lists every name, and 'speaker.<id>.<field>' addresses one by id. A speaker is drawn nowhere else."
|
|
71
71
|
},
|
|
72
72
|
"author": {
|
|
73
73
|
"oneOf": [
|
|
@@ -775,7 +775,7 @@ var presentation = {
|
|
|
775
775
|
},
|
|
776
776
|
"Organization": {
|
|
777
777
|
"type": "object",
|
|
778
|
-
"description": "An organization associated with the presentation, typically the presenting company, but also hosts, partners, clients, or sponsors. The primary organization (role 'primary', else the first one) supplies the
|
|
778
|
+
"description": "An organization associated with the presentation, typically the presenting company, but also hosts, partners, clients, or sponsors. The primary organization (role 'primary', else the first one) supplies the cover and section logo (unless design.logo overrides it) and the 'socials' header/footer field (its socials). Every field is also a built-in variable ('{{organization.name}}', 'organization.<id>.<field>'), and every logo a slide-scoped image ('var:organization.logo.icon', 'var:organization.<id>.logo'). Nothing else about an organization is drawn automatically. See docs/templates-and-variables.md.",
|
|
779
779
|
"required": [
|
|
780
780
|
"id",
|
|
781
781
|
"name"
|
|
@@ -793,7 +793,7 @@ var presentation = {
|
|
|
793
793
|
},
|
|
794
794
|
"name": {
|
|
795
795
|
"type": "string",
|
|
796
|
-
"description": "Display name.
|
|
796
|
+
"description": "Display name. The built-in variable 'organization.name' (primary organization) or 'organization.<id>.name' carries it into any string, such as a header or footer text of '{{organization.name}}'.",
|
|
797
797
|
"examples": [
|
|
798
798
|
"Acme Corp",
|
|
799
799
|
"DevConf 2026",
|
|
@@ -809,12 +809,29 @@ var presentation = {
|
|
|
809
809
|
]
|
|
810
810
|
},
|
|
811
811
|
"logo": {
|
|
812
|
-
"
|
|
813
|
-
|
|
812
|
+
"oneOf": [
|
|
813
|
+
{
|
|
814
|
+
"$ref": "#/$defs/Asset"
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
"$ref": "#/$defs/OrganizationLogo"
|
|
818
|
+
}
|
|
819
|
+
],
|
|
820
|
+
"description": "The organization's logo. A path, URL or Asset is one logo for every shape; an object gives up to four shapes (full, stacked, icon, wordmark), each a path or Asset, or { onLight, onDark } for the two backgrounds. A path is resolved against the OPF file location; an HTTPS URL, data URI or 'asset:<id>' reference works too. SVG (preferred for vector logos) or PNG with transparency are typical. The primary organization's full logo (role 'primary', else the first organization) is drawn on cover and section slides unless design.logo names another or is false. Every logo is also a slide-scoped built-in image, placed with a whole-field reference in any image field: 'var:organization.logo' (the full logo), 'var:organization.logo.icon' (likewise stacked, wordmark, full), and 'var:organization.<id>.logo' or 'var:organization.<id>.logo.<shape>' for another organization. A missing shape falls back to full, and full to the first defined of wordmark, stacked and icon. The onLight or onDark value is chosen for each slide's background; a missing one uses the other. See docs/templates-and-variables.md and docs/design-resolution.md.",
|
|
814
821
|
"examples": [
|
|
822
|
+
"./assets/acme-logo.svg",
|
|
823
|
+
{
|
|
824
|
+
"full": "./assets/acme-logo.svg",
|
|
825
|
+
"icon": "./assets/acme-mark.svg"
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
"full": {
|
|
829
|
+
"onLight": "./assets/acme-logo.svg",
|
|
830
|
+
"onDark": "./assets/acme-logo-white.svg"
|
|
831
|
+
},
|
|
832
|
+
"icon": "./assets/acme-mark.svg"
|
|
833
|
+
},
|
|
815
834
|
"https://cdn.acme.com/logo.svg",
|
|
816
|
-
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9V3iWggAAAAASUVORK5CYII=",
|
|
817
|
-
"./assets/logo.jpg",
|
|
818
835
|
"asset:acme-logo"
|
|
819
836
|
]
|
|
820
837
|
},
|
|
@@ -863,7 +880,7 @@ var presentation = {
|
|
|
863
880
|
"sponsor",
|
|
864
881
|
"host"
|
|
865
882
|
],
|
|
866
|
-
"description": "Role of the organization relative to the presentation. Only 'primary' has behavior: it selects the primary organization (
|
|
883
|
+
"description": "Role of the organization relative to the presentation. Only 'primary' has behavior: it selects the primary organization (cover and section logo, the 'socials' header/footer field, and the 'organization.<field>' built-ins). When no organization has role 'primary', the first one is primary. 'partner', 'client', 'sponsor' and 'host' are descriptive labels that change nothing."
|
|
867
884
|
},
|
|
868
885
|
"socials": {
|
|
869
886
|
"$ref": "#/$defs/Socials",
|
|
@@ -873,7 +890,7 @@ var presentation = {
|
|
|
873
890
|
},
|
|
874
891
|
"Speaker": {
|
|
875
892
|
"type": "object",
|
|
876
|
-
"description": "A person presenting the deck. A speaker is drawn only through built-in variables ('{{speaker.name}}' inside any string, 'var:speaker.photo' as a whole image field)
|
|
893
|
+
"description": "A person presenting the deck. A speaker is drawn only through built-in variables ('{{speaker.name}}' inside any string, a header or footer text included, and 'var:speaker.photo' as a whole image field). No layout, cover or bio slide places a speaker on its own. See docs/templates-and-variables.md.",
|
|
877
894
|
"required": [
|
|
878
895
|
"id",
|
|
879
896
|
"name"
|
|
@@ -891,7 +908,7 @@ var presentation = {
|
|
|
891
908
|
},
|
|
892
909
|
"name": {
|
|
893
910
|
"type": "string",
|
|
894
|
-
"description": "Display name. Built-in variable 'speaker.name' (first speaker) or 'speaker.<id>.name';
|
|
911
|
+
"description": "Display name. Built-in variable 'speaker.name' (first speaker) or 'speaker.<id>.name'; every name is listed by 'speakers'.",
|
|
895
912
|
"examples": [
|
|
896
913
|
"Alice Chen",
|
|
897
914
|
"Bob Lee",
|
|
@@ -900,7 +917,7 @@ var presentation = {
|
|
|
900
917
|
},
|
|
901
918
|
"title": {
|
|
902
919
|
"type": "string",
|
|
903
|
-
"description": "Role or title. Built-in variable 'speaker.title'
|
|
920
|
+
"description": "Role or title. Built-in variable 'speaker.title' (or 'speaker.<id>.title'), for example in a footer text of '{{speaker.name}}, {{speaker.title}}'.",
|
|
904
921
|
"examples": [
|
|
905
922
|
"VP of Engineering",
|
|
906
923
|
"Founder & CEO",
|
|
@@ -1086,20 +1103,19 @@ var presentation = {
|
|
|
1086
1103
|
"logo": {
|
|
1087
1104
|
"oneOf": [
|
|
1088
1105
|
{
|
|
1089
|
-
"
|
|
1106
|
+
"type": "string",
|
|
1107
|
+
"pattern": "^var:organization(\\.[A-Za-z0-9_-]+)?\\.logo(\\.(full|stacked|icon|wordmark))?$"
|
|
1090
1108
|
},
|
|
1091
1109
|
{
|
|
1092
|
-
"
|
|
1110
|
+
"type": "boolean",
|
|
1111
|
+
"const": false
|
|
1093
1112
|
}
|
|
1094
1113
|
],
|
|
1095
|
-
"description": "
|
|
1114
|
+
"description": "Which logo covers, section dividers and picture bullets draw. Logos live on the organization (Organization.logo); without this field covers and sections draw the primary organization's full logo and picture bullets its icon. A logo reference overrides that: 'var:organization.beta.logo' draws organization beta's logo, and a reference that names a shape ('var:organization.logo.wordmark') draws that shape everywhere this field applies. false draws no logo. A slide's design.logo overrides the deck's; absence inherits. An override naming an organization without a logo draws nothing (validation warns). Placement (reference engines, vetoable): on a cover or section slide (no body payload on a heading-only layout such as title, title-subtitle or section-divider, or no layout) the logo is drawn at the top-left of the free area, inside the slide padding and below any header furniture, in a box 56 reference pixels tall and at most four times as wide; the tag/title/subtitle group then centers in the remaining span. Content slides never get an automatic logo; a header or footer zone places one with image: 'var:organization.logo.icon'. composeSlide returns the cover box as geometry.logo.",
|
|
1096
1115
|
"examples": [
|
|
1097
|
-
"
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
"light": "asset:acme-logo-white",
|
|
1101
|
-
"icon": "asset:acme-icon"
|
|
1102
|
-
}
|
|
1116
|
+
"var:organization.logo.wordmark",
|
|
1117
|
+
"var:organization.beta.logo",
|
|
1118
|
+
false
|
|
1103
1119
|
]
|
|
1104
1120
|
},
|
|
1105
1121
|
"watermark": {
|
|
@@ -1144,10 +1160,10 @@ var presentation = {
|
|
|
1144
1160
|
false,
|
|
1145
1161
|
{
|
|
1146
1162
|
"left": {
|
|
1147
|
-
"
|
|
1163
|
+
"text": "{{slide.section}}"
|
|
1148
1164
|
},
|
|
1149
1165
|
"right": {
|
|
1150
|
-
"image": "
|
|
1166
|
+
"image": "var:organization.logo.icon"
|
|
1151
1167
|
}
|
|
1152
1168
|
}
|
|
1153
1169
|
]
|
|
@@ -1167,13 +1183,13 @@ var presentation = {
|
|
|
1167
1183
|
false,
|
|
1168
1184
|
{
|
|
1169
1185
|
"left": {
|
|
1170
|
-
"image": "
|
|
1186
|
+
"image": "var:organization.logo.icon"
|
|
1171
1187
|
},
|
|
1172
1188
|
"center": {
|
|
1173
1189
|
"text": "Confidential"
|
|
1174
1190
|
},
|
|
1175
1191
|
"right": {
|
|
1176
|
-
"
|
|
1192
|
+
"text": "{{slide.number}}"
|
|
1177
1193
|
}
|
|
1178
1194
|
}
|
|
1179
1195
|
]
|
|
@@ -1234,7 +1250,7 @@ var presentation = {
|
|
|
1234
1250
|
"character",
|
|
1235
1251
|
"image"
|
|
1236
1252
|
],
|
|
1237
|
-
"description": "Marker style for items and bullets lists. Effective value: the slide's design, then the deck's design, then the layout record's design.listBullet, then 'character'. 'character' (the default) draws the glyph marker. 'image' draws the
|
|
1253
|
+
"description": "Marker style for items and bullets lists. Effective value: the slide's design, then the deck's design, then the layout record's design.listBullet, then 'character'. 'character' (the default) draws the glyph marker. 'image' draws the primary organization's icon logo (or the organization and shape design.logo names; the onDark value on dark backgrounds) as a picture bullet: a square of the marker's font size whose bottom sits on the marker baseline, so list geometry does not change. Without a logo the glyph is drawn and the engine reports unresolved-content at this field."
|
|
1238
1254
|
}
|
|
1239
1255
|
}
|
|
1240
1256
|
},
|
|
@@ -1666,7 +1682,7 @@ var presentation = {
|
|
|
1666
1682
|
"additionalProperties": {
|
|
1667
1683
|
"$ref": "#/$defs/Variable"
|
|
1668
1684
|
},
|
|
1669
|
-
"description": "Named variables, keyed by stable kebab-case id. A variable is a typed, named value the deck declares once and uses in many places: a color ('var:<id>' in color fields, the original use), or content that fills a template (text, number, date, image, url, list). Content is referenced inline as '{{<id>}}' inside any string, or whole as 'var:<id>' in a field of the matching type; '\\{{' writes a literal '{{'. A hex string is shorthand for a color variable. A variable with no 'value' is unfilled: expected in a template (root 'template': true), an error in a normal deck. Engines resolve variables with resolveVariables() before composing, rendering or exporting, so a template plus its values previews and exports exactly like the equivalent hand-written deck. Read-only built-in variables come from the deck's own metadata and use dotted names that a user id can never take: 'deck.name', 'deck.description', 'deck.author', 'speaker.name|title|email|phone|bio|photo' (first speaker), 'speakers' (every speaker's name, a list), 'organization.name|legalName|tagline|domain|email|phone
|
|
1685
|
+
"description": "Named variables, keyed by stable kebab-case id. A variable is a typed, named value the deck declares once and uses in many places: a color ('var:<id>' in color fields, the original use), or content that fills a template (text, number, date, image, url, list). Content is referenced inline as '{{<id>}}' inside any string, or whole as 'var:<id>' in a field of the matching type; '\\{{' writes a literal '{{'. A hex string is shorthand for a color variable. A variable with no 'value' is unfilled: expected in a template (root 'template': true), an error in a normal deck. Engines resolve variables with resolveVariables() before composing, rendering or exporting, so a template plus its values previews and exports exactly like the equivalent hand-written deck. Read-only built-in variables come from the deck's own metadata and use dotted names that a user id can never take: 'deck.name', 'deck.description', 'deck.author', 'speaker.name|title|email|phone|bio|photo' (first speaker), 'speakers' (every speaker's name, a list), 'organization.name|legalName|tagline|domain|email|phone' (primary organization), and 'speaker.<id>.<field>' / 'organization.<id>.<field>' by id. They are written the same two ways ('{{speaker.name}}', 'var:speaker.photo'), are never declared, and an unknown built-in path is a validation error; a known one with no source value resolves to nothing with a warning. The id 'speakers' is reserved. Three built-ins are slide-scoped and written only as inline tokens: '{{slide.number}}' (the displayed slide number after pagination), '{{slide.section}}' (the slide's section) and '{{deck.slideCount}}' (the number of slides after pagination). resolveVariables() leaves them as written; they resolve per slide as it is composed (resolveSlideVariables(), and header/footer text in layoutFurniture()), so every 'slide.' name is reserved and 'var:slide.*' or 'var:deck.slideCount' is a validation error. Organization logos are slide-scoped whole-field images: 'var:organization.logo', 'var:organization.logo.<shape>' (full, stacked, icon, wordmark), 'var:organization.<id>.logo' and 'var:organization.<id>.logo.<shape>' resolve per slide to the onLight or onDark asset for its background. See docs/templates-and-variables.md.",
|
|
1670
1686
|
"examples": [
|
|
1671
1687
|
{
|
|
1672
1688
|
"risk": "#B42318",
|
|
@@ -2509,60 +2525,71 @@ var presentation = {
|
|
|
2509
2525
|
}
|
|
2510
2526
|
}
|
|
2511
2527
|
},
|
|
2512
|
-
"
|
|
2528
|
+
"OrganizationLogo": {
|
|
2513
2529
|
"type": "object",
|
|
2514
|
-
"description": "
|
|
2530
|
+
"description": "An organization's logo in up to four shapes. Each shape is a path or Asset, or { onLight, onDark } for the two backgrounds. A missing shape falls back to full, and full to the first defined of wordmark, stacked and icon; within a shape a missing onLight or onDark uses the other. Shapes are placed with slide-scoped references such as 'var:organization.logo.icon'.",
|
|
2515
2531
|
"additionalProperties": false,
|
|
2532
|
+
"minProperties": 1,
|
|
2516
2533
|
"properties": {
|
|
2517
|
-
"
|
|
2518
|
-
"$ref": "#/$defs/
|
|
2519
|
-
"description": "
|
|
2520
|
-
},
|
|
2521
|
-
"light": {
|
|
2522
|
-
"$ref": "#/$defs/Asset",
|
|
2523
|
-
"description": "Light-colored full-lockup logo intended for rendering on dark backgrounds."
|
|
2524
|
-
},
|
|
2525
|
-
"dark": {
|
|
2526
|
-
"$ref": "#/$defs/Asset",
|
|
2527
|
-
"description": "Dark-colored full-lockup logo intended for rendering on light backgrounds."
|
|
2534
|
+
"full": {
|
|
2535
|
+
"$ref": "#/$defs/LogoSource",
|
|
2536
|
+
"description": "The full logo (lockup): mark and name side by side. Drawn on cover and section slides, and placed by 'var:organization.logo'."
|
|
2528
2537
|
},
|
|
2529
2538
|
"stacked": {
|
|
2530
|
-
"$ref": "#/$defs/
|
|
2531
|
-
"description": "
|
|
2532
|
-
},
|
|
2533
|
-
"stackedLight": {
|
|
2534
|
-
"$ref": "#/$defs/Asset",
|
|
2535
|
-
"description": "Light-colored stacked logo variant intended for rendering on dark backgrounds."
|
|
2536
|
-
},
|
|
2537
|
-
"stackedDark": {
|
|
2538
|
-
"$ref": "#/$defs/Asset",
|
|
2539
|
-
"description": "Dark-colored stacked logo variant intended for rendering on light backgrounds."
|
|
2539
|
+
"$ref": "#/$defs/LogoSource",
|
|
2540
|
+
"description": "The stacked lockup: mark above the name, for portrait or square slots. Placed by 'var:organization.logo.stacked'."
|
|
2540
2541
|
},
|
|
2541
2542
|
"icon": {
|
|
2542
|
-
"$ref": "#/$defs/
|
|
2543
|
-
"description": "
|
|
2544
|
-
},
|
|
2545
|
-
"iconLight": {
|
|
2546
|
-
"$ref": "#/$defs/Asset",
|
|
2547
|
-
"description": "Light-colored icon variant intended for rendering on dark backgrounds."
|
|
2548
|
-
},
|
|
2549
|
-
"iconDark": {
|
|
2550
|
-
"$ref": "#/$defs/Asset",
|
|
2551
|
-
"description": "Dark-colored icon variant intended for rendering on light backgrounds."
|
|
2543
|
+
"$ref": "#/$defs/LogoSource",
|
|
2544
|
+
"description": "The mark or symbol without the name, for tight spaces such as header and footer zones and picture bullets. Placed by 'var:organization.logo.icon'."
|
|
2552
2545
|
},
|
|
2553
2546
|
"wordmark": {
|
|
2554
|
-
"$ref": "#/$defs/
|
|
2555
|
-
"description": "
|
|
2547
|
+
"$ref": "#/$defs/LogoSource",
|
|
2548
|
+
"description": "The name set in the brand's lettering, without the mark. Placed by 'var:organization.logo.wordmark'."
|
|
2549
|
+
}
|
|
2550
|
+
},
|
|
2551
|
+
"examples": [
|
|
2552
|
+
{
|
|
2553
|
+
"full": "./assets/acme-logo.svg",
|
|
2554
|
+
"icon": "./assets/acme-mark.svg"
|
|
2556
2555
|
},
|
|
2557
|
-
|
|
2558
|
-
"
|
|
2559
|
-
|
|
2556
|
+
{
|
|
2557
|
+
"full": {
|
|
2558
|
+
"onLight": "./assets/acme-logo.svg",
|
|
2559
|
+
"onDark": "./assets/acme-logo-white.svg"
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
]
|
|
2563
|
+
},
|
|
2564
|
+
"LogoSource": {
|
|
2565
|
+
"description": "One logo shape: a path or Asset for every background, or { onLight, onDark }. onLight is drawn on light backgrounds (usually dark artwork) and onDark on dark backgrounds (usually light artwork), by each slide's background; a missing one uses the other.",
|
|
2566
|
+
"oneOf": [
|
|
2567
|
+
{
|
|
2568
|
+
"$ref": "#/$defs/Asset"
|
|
2560
2569
|
},
|
|
2561
|
-
|
|
2562
|
-
"
|
|
2563
|
-
"
|
|
2570
|
+
{
|
|
2571
|
+
"type": "object",
|
|
2572
|
+
"additionalProperties": false,
|
|
2573
|
+
"minProperties": 1,
|
|
2574
|
+
"properties": {
|
|
2575
|
+
"onLight": {
|
|
2576
|
+
"$ref": "#/$defs/Asset",
|
|
2577
|
+
"description": "The logo for light backgrounds, usually dark artwork."
|
|
2578
|
+
},
|
|
2579
|
+
"onDark": {
|
|
2580
|
+
"$ref": "#/$defs/Asset",
|
|
2581
|
+
"description": "The logo for dark backgrounds, usually light or white artwork."
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2564
2584
|
}
|
|
2565
|
-
|
|
2585
|
+
],
|
|
2586
|
+
"examples": [
|
|
2587
|
+
"./assets/acme-mark.svg",
|
|
2588
|
+
{
|
|
2589
|
+
"onLight": "./assets/acme-logo.svg",
|
|
2590
|
+
"onDark": "./assets/acme-logo-white.svg"
|
|
2591
|
+
}
|
|
2592
|
+
]
|
|
2566
2593
|
},
|
|
2567
2594
|
"Watermark": {
|
|
2568
2595
|
"type": "object",
|
|
@@ -2625,41 +2652,26 @@ var presentation = {
|
|
|
2625
2652
|
},
|
|
2626
2653
|
"HeaderFooterItem": {
|
|
2627
2654
|
"type": "object",
|
|
2628
|
-
"description": "One header/footer zone. Every configured field renders
|
|
2655
|
+
"description": "One header/footer zone. Every configured field renders, side by side in one row in the order image, text, socials, date (mirrored in a right-to-left deck), aligned to the zone's edge (left, center or right) and vertically centered on each other; a line break inside text or socials ('\\n') stacks lines within that part. When the parts are wider than the zone their text wraps, and a row that cannot fit even so is reported as overflow. Generated values such as the slide number, the slide count, the section, the organization or the speaker are variables inside text ('{{slide.number}} / {{deck.slideCount}}', '{{organization.name}}'), so one text writes several values in the author's order. An organization logo is image: 'var:organization.logo.icon'. See docs/templates-and-variables.md.",
|
|
2656
|
+
"additionalProperties": false,
|
|
2629
2657
|
"properties": {
|
|
2630
|
-
"logo": {
|
|
2631
|
-
"type": "boolean",
|
|
2632
|
-
"description": "Whether to render the deck's icon logo in this zone: a slide's design.logo, then design.logo, then the primary organization's logo (LogoSet icon variants first, light ones on dark backgrounds). It is a generated image part in the same box as image; without a logo the engine reports unresolved-content at this field. Use image for a literal picture that is not the deck logo."
|
|
2633
|
-
},
|
|
2634
2658
|
"text": {
|
|
2635
2659
|
"type": "string",
|
|
2636
|
-
"description": "
|
|
2660
|
+
"description": "Text rendered in this zone. Variables resolve inside it like any string: deck-wide ones ('{{organization.name}}', '{{speaker.name}}, {{speaker.title}}', '{{customer}}') before composition, and the slide-scoped built-ins '{{slide.number}}', '{{slide.section}}' and '{{deck.slideCount}}' for each slide as it is laid out. Each '{{slide.number}}' is a live slide-number field: PPTX export writes it as a native field when its value fits within one accepted text line, and as static text with a diagnostic otherwise. '{{deck.slideCount}}' is fixed text, because PowerPoint has no slide-count field. Write '\\{{' for literal braces.",
|
|
2637
2661
|
"examples": [
|
|
2638
2662
|
"Confidential",
|
|
2639
|
-
"Q4 Business Review"
|
|
2663
|
+
"Q4 Business Review",
|
|
2664
|
+
"{{slide.number}} / {{deck.slideCount}}",
|
|
2665
|
+
"{{organization.name}}\n{{slide.section}}"
|
|
2640
2666
|
]
|
|
2641
2667
|
},
|
|
2642
2668
|
"image": {
|
|
2643
2669
|
"$ref": "#/$defs/Asset",
|
|
2644
|
-
"description": "
|
|
2670
|
+
"description": "Image rendered first in this zone's row, such as a logo, partner mark, certification badge, or icon: a path, URL or Asset, or an organization logo reference ('var:organization.logo.icon', 'var:organization.<id>.logo.wordmark'), which resolves for each slide's background (onLight or onDark).",
|
|
2645
2671
|
"examples": [
|
|
2646
|
-
"
|
|
2647
|
-
"./assets/footer-badge.svg"
|
|
2648
|
-
|
|
2649
|
-
},
|
|
2650
|
-
"slideNumber": {
|
|
2651
|
-
"type": "boolean",
|
|
2652
|
-
"description": "Whether to render the current slide number in this zone. PPTX export writes a native slide-number field when its value fits within one accepted text line; a value split across lines exports as static text with a diagnostic."
|
|
2653
|
-
},
|
|
2654
|
-
"slideNumberFormat": {
|
|
2655
|
-
"type": "string",
|
|
2656
|
-
"pattern": "[{]current[}]",
|
|
2657
|
-
"description": 'Template for the slide number when slideNumber is true. {current} is the displayed slide number (a native PPTX field when its value fits within one accepted text line); {total} is the number of slides in the rendered or exported deck, written as fixed text because PowerPoint has no slide-count field. Other characters are literal. Defaults to "{current}".',
|
|
2658
|
-
"examples": [
|
|
2659
|
-
"{current}",
|
|
2660
|
-
"{current} / {total}",
|
|
2661
|
-
"A-{current}",
|
|
2662
|
-
"Page {current} of {total}"
|
|
2672
|
+
"var:organization.logo.icon",
|
|
2673
|
+
"./assets/footer-badge.svg",
|
|
2674
|
+
"var:organization.beta.logo"
|
|
2663
2675
|
]
|
|
2664
2676
|
},
|
|
2665
2677
|
"date": {
|
|
@@ -2689,18 +2701,6 @@ var presentation = {
|
|
|
2689
2701
|
"MMMM d, yyyy"
|
|
2690
2702
|
]
|
|
2691
2703
|
},
|
|
2692
|
-
"organization": {
|
|
2693
|
-
"type": "boolean",
|
|
2694
|
-
"description": "Whether to render the primary organization's name from organization (role 'primary', else the first organization)."
|
|
2695
|
-
},
|
|
2696
|
-
"speaker": {
|
|
2697
|
-
"type": "boolean",
|
|
2698
|
-
"description": "Whether to render the primary (first) speaker's name and title from speaker, joined as 'Ada Lovelace, CTO' (just the name when the speaker has no title). It is generated text: without a named speaker the engine reports unresolved-content at this field. PPTX export writes it as static text and import restores the flag from the file's furniture provenance."
|
|
2699
|
-
},
|
|
2700
|
-
"section": {
|
|
2701
|
-
"type": "boolean",
|
|
2702
|
-
"description": "Whether to render the current slide section label."
|
|
2703
|
-
},
|
|
2704
2704
|
"socials": {
|
|
2705
2705
|
"type": "boolean",
|
|
2706
2706
|
"description": "Whether to render the primary organization's social profiles from organization.socials, one line per platform in key order. A handle is formatted through the platform's socialPlatforms record (companyUrlPattern, else profileUrlPattern, else baseUrl, with handlePrefix stripped) and shown as that profile URL without its 'https://' scheme; a value that is already a URL is shown without an 'https://' scheme; an unknown platform key shows the raw value. Exporters link each line to its full URL when one exists. Speaker socials are not rendered by this field."
|
|
@@ -3100,7 +3100,7 @@ var presentation = {
|
|
|
3100
3100
|
},
|
|
3101
3101
|
"section": {
|
|
3102
3102
|
"type": "string",
|
|
3103
|
-
"description": "PowerPoint-style slide section label. Consecutive slides with the same value belong to the same section in presenter view, outlines, and PowerPoint section-aware exports.",
|
|
3103
|
+
"description": "PowerPoint-style slide section label. Consecutive slides with the same value belong to the same section in presenter view, outlines, and PowerPoint section-aware exports. Also the slide-scoped built-in variable '{{slide.section}}', for example in a header text.",
|
|
3104
3104
|
"examples": [
|
|
3105
3105
|
"Intro",
|
|
3106
3106
|
"Problem",
|
|
@@ -3111,7 +3111,7 @@ var presentation = {
|
|
|
3111
3111
|
},
|
|
3112
3112
|
"hidden": {
|
|
3113
3113
|
"type": "boolean",
|
|
3114
|
-
"description": "Whether the slide is hidden from the presented sequence. The player skips it, the PPTX export writes it as a hidden slide, and per-slide image and PDF output skips it unless the caller asks to include hidden slides (opf CLI: --include-hidden; the deck-level
|
|
3114
|
+
"description": "Whether the slide is hidden from the presented sequence. The player skips it, the PPTX export writes it as a hidden slide, and per-slide image and PDF output skips it unless the caller asks to include hidden slides (opf CLI: --include-hidden; the deck-level toSvg: the opt-in skipHidden). A slide named explicitly (opf convert --slides, toSvg with a slide number) is still rendered."
|
|
3115
3115
|
},
|
|
3116
3116
|
"composition": {
|
|
3117
3117
|
"$ref": "#/$defs/Composition"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Ctx, splitLines, lineRange, readYamlMapping, NOT_CHECKED, OPFMarkdownError, writeYaml, readCsv, DECIMAL, csvField, scalarText } from './chunk-
|
|
2
|
-
import { parseQuoteLines, levelsFromIndents, parseCodeFence, splitWhen, fenceCode } from './chunk-
|
|
3
|
-
import { same, isRecord, runText, isPlainRun, FORMATTING } from './chunk-
|
|
4
|
-
import { validate } from './chunk-
|
|
1
|
+
import { Ctx, splitLines, lineRange, readYamlMapping, NOT_CHECKED, OPFMarkdownError, writeYaml, readCsv, DECIMAL, csvField, scalarText } from './chunk-CJPDTLGD.js';
|
|
2
|
+
import { parseQuoteLines, levelsFromIndents, parseCodeFence, splitWhen, fenceCode } from './chunk-VCGRMNY2.js';
|
|
3
|
+
import { same, isRecord, runText, isPlainRun, FORMATTING } from './chunk-RAKEVM52.js';
|
|
4
|
+
import { validate } from './chunk-JT3QXA22.js';
|
|
5
5
|
import { checkCatalogsOption } from './chunk-SPFV27O5.js';
|
|
6
6
|
|
|
7
7
|
// src/markdown/canon.ts
|
|
@@ -789,6 +789,30 @@ function splitSegments(lines, split) {
|
|
|
789
789
|
}
|
|
790
790
|
return segments;
|
|
791
791
|
}
|
|
792
|
+
function isOutline(lines) {
|
|
793
|
+
let fence;
|
|
794
|
+
let comment = false;
|
|
795
|
+
let notes = false;
|
|
796
|
+
let headings = 0;
|
|
797
|
+
for (const { text } of lines) {
|
|
798
|
+
if (fence !== void 0) {
|
|
799
|
+
if (fenceClose(fence).test(text)) fence = void 0;
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
if (comment) {
|
|
803
|
+
if (text.includes("-->")) comment = false;
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
if (SEPARATOR.test(text)) return false;
|
|
807
|
+
if (notes) continue;
|
|
808
|
+
if (/^#(?:[ \t]|$)/.test(text)) headings++;
|
|
809
|
+
const open = fenceStart(text);
|
|
810
|
+
if (open) fence = open.fence;
|
|
811
|
+
else if (COMMENT.test(text) && !text.includes("-->", text.indexOf("<!--") + 4)) comment = true;
|
|
812
|
+
else if (NOTES.test(text)) notes = true;
|
|
813
|
+
}
|
|
814
|
+
return headings >= 2;
|
|
815
|
+
}
|
|
792
816
|
function joinParagraph(lines) {
|
|
793
817
|
let out = "";
|
|
794
818
|
lines.forEach((raw, k) => {
|
|
@@ -1776,7 +1800,8 @@ function fromMarkdown(markdown, options = {}) {
|
|
|
1776
1800
|
body = lines.slice(matter.end);
|
|
1777
1801
|
}
|
|
1778
1802
|
const document = { ...options.defaults ?? {}, ...front };
|
|
1779
|
-
const
|
|
1803
|
+
const split = options.split ?? "auto";
|
|
1804
|
+
const segments = splitSegments(body, split === "headings" || split === "auto" && isOutline(body) ? "headings" : "rules");
|
|
1780
1805
|
const slides = [];
|
|
1781
1806
|
segments.forEach((segment, index) => {
|
|
1782
1807
|
if (emptySegment(segment)) {
|