deckjsx 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as Text, c as isAuthorNode, i as Slide, l as isContentNode, n as Image, o as View, r as Shape, s as createElement, t as Fragment, u as isSlideNode } from "./jsx-lqMAdW2X.mjs";
1
+ import { a as isContentNode, c as toLegacyJsxNode, d as Slide, f as Text, i as isAuthorNode, l as Image, m as isAuthorTreeNode, n as createElement, o as isSlideNode, p as View, s as isLegacyAuthorNode, t as Fragment, u as Shape } from "./jsx-Crlbye9V.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { mkdir, writeFile } from "node:fs/promises";
4
4
  import { dirname } from "node:path";
@@ -3863,24 +3863,15 @@ function compileGroupNode(node, parentFrame, idGenerator, placement, clipRect, c
3863
3863
  }, childClipRect, context)
3864
3864
  };
3865
3865
  }
3866
- function compileTextNode(node, parentFrame, idGenerator, placement, clipRect, context) {
3867
- const { props } = node;
3868
- const textLengthContext = getTextLengthContext(props, context);
3869
- const resolved = frameFromProps(props, parentFrame, placement, textLengthContext);
3870
- const strokes = resolveNodeStrokes(props, textLengthContext);
3871
- const shadow = parseShadowShorthand(props.textShadow ?? props.boxShadow);
3866
+ function textStyleFromProps(props, textLengthContext) {
3872
3867
  const list = resolveListStyle(props, textLengthContext);
3873
3868
  const lineHeight = resolveLineHeight(props.lineHeight, textLengthContext);
3874
3869
  const underlineStyle = resolveUnderlineStyle(props.textDecorationStyle);
3875
3870
  const underlineColor = normalizeColor(props.textDecorationColor);
3876
3871
  const textDirection = resolveTextDirection(props.writingMode);
3877
3872
  const tabStops = resolveTabStops(props.tabStops, textLengthContext);
3878
- const hyperlink = props.href ? {
3879
- url: props.href,
3880
- ...props.tooltip ? { tooltip: props.tooltip } : {}
3881
- } : void 0;
3882
3873
  const fontSizePt = props.fontSize === void 0 ? void 0 : parsePointValue(props.fontSize, 0, textLengthContext);
3883
- const style = {
3874
+ return {
3884
3875
  fontFamily: props.fontFamily,
3885
3876
  fontSizePt,
3886
3877
  fontWeight: props.fontWeight,
@@ -3908,6 +3899,48 @@ function compileTextNode(node, parentFrame, idGenerator, placement, clipRect, co
3908
3899
  ...props.superscript ? { superscript: true } : {},
3909
3900
  ...props.subscript ? { subscript: true } : {}
3910
3901
  };
3902
+ }
3903
+ function isEmptyRunStyle(style) {
3904
+ return Object.values(style).every((value) => value === void 0);
3905
+ }
3906
+ function flattenUnknownChildren(children) {
3907
+ return children.flatMap((child) => Array.isArray(child) ? flattenUnknownChildren(child) : [child]);
3908
+ }
3909
+ function extractRichTextRuns(children, textTransform, textLengthContext) {
3910
+ const runs = [];
3911
+ for (const child of flattenUnknownChildren(children)) {
3912
+ if (child === null || child === void 0 || child === false || child === true) continue;
3913
+ if (typeof child === "string" || typeof child === "number") {
3914
+ runs.push({ text: extractText([child], textTransform) });
3915
+ continue;
3916
+ }
3917
+ if (typeof child === "object" && child !== null && "kind" in child) {
3918
+ const authorNode = child;
3919
+ if (authorNode.kind !== "text") throw new Error("Text nodes can only contain primitive text or inline text runs.");
3920
+ const props = normalizeTextProps(authorNode.props);
3921
+ const childLengthContext = getTextLengthContext(props, textLengthContext);
3922
+ const style = textStyleFromProps(props, childLengthContext);
3923
+ const text = extractRichTextRuns(authorNode.children, props.textTransform ?? textTransform, childLengthContext).map((run) => run.text).join("");
3924
+ runs.push({
3925
+ text,
3926
+ ...!isEmptyRunStyle(style) ? { style } : {}
3927
+ });
3928
+ }
3929
+ }
3930
+ return runs;
3931
+ }
3932
+ function compileTextNode(node, parentFrame, idGenerator, placement, clipRect, context) {
3933
+ const { props } = node;
3934
+ const textLengthContext = getTextLengthContext(props, context);
3935
+ const resolved = frameFromProps(props, parentFrame, placement, textLengthContext);
3936
+ const strokes = resolveNodeStrokes(props, textLengthContext);
3937
+ const shadow = parseShadowShorthand(props.textShadow ?? props.boxShadow);
3938
+ const hyperlink = props.href ? {
3939
+ url: props.href,
3940
+ ...props.tooltip ? { tooltip: props.tooltip } : {}
3941
+ } : void 0;
3942
+ const style = textStyleFromProps(props, textLengthContext);
3943
+ const runs = extractRichTextRuns(node.source.children, props.textTransform, textLengthContext);
3911
3944
  const visibleFrame = intersectClipRect({
3912
3945
  xEmu: resolved.xEmu,
3913
3946
  yEmu: resolved.yEmu,
@@ -3930,7 +3963,10 @@ function compileTextNode(node, parentFrame, idGenerator, placement, clipRect, co
3930
3963
  ...props.visibility !== void 0 ? { visibility: props.visibility } : {},
3931
3964
  flipH: resolved.flipH,
3932
3965
  flipV: resolved.flipV,
3933
- content: { text: extractText(node.source.children, props.textTransform) },
3966
+ content: {
3967
+ text: runs.map((run) => run.text).join(""),
3968
+ ...runs.length > 1 || runs.some((run) => run.style) ? { runs } : {}
3969
+ },
3934
3970
  style,
3935
3971
  fill: backgroundFill.fill,
3936
3972
  ...backgroundFill.backgroundLayers ? { backgroundLayers: backgroundFill.backgroundLayers } : {},
@@ -4102,13 +4138,775 @@ function renderPresentation(options, slides) {
4102
4138
  version: "0.1",
4103
4139
  meta: options.meta,
4104
4140
  size: slideSize,
4105
- slides: slides.map((factory, slideIndex) => compileSlide(factory({
4106
- slideIndex,
4107
- totalSlides: slides.length
4108
- }), {
4109
- slideIndex,
4110
- totalSlides: slides.length
4111
- }, slideFrame, idGenerator, lengthContext))
4141
+ slides: slides.map((factory, slideIndex) => {
4142
+ return compileSlide(toLegacyJsxNode(factory({ composition: {
4143
+ slideIndex,
4144
+ totalSlides: slides.length,
4145
+ deckSlideIndex: slideIndex,
4146
+ deckTotalSlides: slides.length
4147
+ } })), { slideIndex }, slideFrame, idGenerator, lengthContext);
4148
+ })
4149
+ };
4150
+ }
4151
+ //#endregion
4152
+ //#region src/diagnostics/format.ts
4153
+ function formatSpan(path) {
4154
+ return ` at ${path}`;
4155
+ }
4156
+ function formatDiagnostic(diagnostic) {
4157
+ const lines = [`${diagnostic.severity}[${diagnostic.code}]: ${diagnostic.title}`];
4158
+ if (diagnostic.message) lines.push(` ${diagnostic.message}`);
4159
+ for (const label of diagnostic.labels) {
4160
+ lines.push(formatSpan(label.path));
4161
+ lines.push(` = ${label.message}`);
4162
+ }
4163
+ for (const note of diagnostic.notes ?? []) lines.push(`note: ${note}`);
4164
+ for (const help of diagnostic.help ?? []) lines.push(`help: ${help}`);
4165
+ return lines.join("\n");
4166
+ }
4167
+ function formatDiagnostics(diagnostics) {
4168
+ return diagnostics.items.map((item) => formatDiagnostic(item)).join("\n\n");
4169
+ }
4170
+ //#endregion
4171
+ //#region src/diagnostics/errors.ts
4172
+ var DeckDiagnosticError = class extends Error {
4173
+ diagnostics;
4174
+ constructor(message, diagnostics) {
4175
+ super(message);
4176
+ this.name = "DeckDiagnosticError";
4177
+ this.diagnostics = diagnostics;
4178
+ }
4179
+ };
4180
+ var SemanticGraphDiagnosticError = class extends DeckDiagnosticError {
4181
+ constructor(diagnostics) {
4182
+ super(formatDiagnostics(diagnostics), diagnostics);
4183
+ this.name = "SemanticGraphDiagnosticError";
4184
+ }
4185
+ };
4186
+ var CompositionDiagnosticError = class extends DeckDiagnosticError {
4187
+ constructor(diagnostics) {
4188
+ super(formatDiagnostics(diagnostics), diagnostics);
4189
+ this.name = "CompositionDiagnosticError";
4190
+ }
4191
+ };
4192
+ //#endregion
4193
+ //#region src/diagnostics/index.ts
4194
+ function createDiagnostics(items = []) {
4195
+ return {
4196
+ items,
4197
+ hasErrors: items.some((item) => item.severity === "error"),
4198
+ hasWarnings: items.some((item) => item.severity === "warning")
4199
+ };
4200
+ }
4201
+ function diagnostic(input) {
4202
+ return input;
4203
+ }
4204
+ //#endregion
4205
+ //#region src/composition/types.ts
4206
+ const COMPOSITION_SOURCE = Symbol("deckjsx.compositionSource");
4207
+ function sourceIdentity(value) {
4208
+ return value;
4209
+ }
4210
+ //#endregion
4211
+ //#region src/composition/resolve.ts
4212
+ const MAX_COMPOSITION_DEPTH = 64;
4213
+ const ROOT_SOURCE = { kind: "root" };
4214
+ function addDiagnostic$1(context, item) {
4215
+ context.diagnostics.push(item);
4216
+ }
4217
+ function compositionDiagnostic(input) {
4218
+ return diagnostic({
4219
+ severity: "error",
4220
+ code: input.code,
4221
+ title: input.title,
4222
+ message: input.message,
4223
+ labels: [{
4224
+ path: input.path,
4225
+ message: input.message
4226
+ }],
4227
+ ...input.help ? { help: input.help } : {}
4228
+ });
4229
+ }
4230
+ function sourcePathFor(parentPath, sourceKey) {
4231
+ return parentPath === "root" ? sourceKey : `${parentPath}/${sourceKey}`;
4232
+ }
4233
+ function sourceOriginFor(parent, sourceKey) {
4234
+ return {
4235
+ kind: "mounted",
4236
+ sourceKey,
4237
+ sourceIdentity: sourceIdentity(`${parent.kind === "root" ? "" : `${parent.sourceIdentity}/`}${sourceKey}`)
4238
+ };
4239
+ }
4240
+ function sourceMaterialFor(source) {
4241
+ return source.kind === "root" ? ["source", "root"] : ["source", source.sourceIdentity];
4242
+ }
4243
+ function validateSourceKey(sourceKey) {
4244
+ if (sourceKey.trim().length === 0) return "Source Key must not be empty.";
4245
+ if (sourceKey === "." || sourceKey === "..") return "Source Key must not be dot or dot-dot.";
4246
+ if (sourceKey.includes("/")) return "Source Key must not contain /.";
4247
+ }
4248
+ function describeInvalidRoot(value) {
4249
+ if (isLegacyAuthorNode(value)) return "Slide factory returned a legacy author node.";
4250
+ if (isAuthorTreeNode(value)) return "Slide factory returned an author tree node that is not a <Slide /> root.";
4251
+ if (value === null) return "Slide factory returned null.";
4252
+ return `Slide factory returned ${typeof value}.`;
4253
+ }
4254
+ function isSlideRoot(value) {
4255
+ return value.kind === "element" && value.source.kind === "component" && value.source.component === "Slide";
4256
+ }
4257
+ function isPromiseLike(value) {
4258
+ return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
4259
+ }
4260
+ function mapSlotOrigins(value, origin, targets, seen) {
4261
+ if (isAuthorTreeNode(value)) {
4262
+ targets.set(value, origin);
4263
+ return;
4264
+ }
4265
+ if (!Array.isArray(value)) return;
4266
+ if (seen.has(value)) return;
4267
+ seen.add(value);
4268
+ value.forEach((item) => mapSlotOrigins(item, origin, targets, seen));
4269
+ }
4270
+ function collectSourceSlots(context, parent) {
4271
+ const origins = /* @__PURE__ */ new WeakMap();
4272
+ if (!context.present || typeof context.value !== "object" || context.value === null) return origins;
4273
+ Object.entries(context.value).forEach(([field, value]) => {
4274
+ mapSlotOrigins(value, {
4275
+ source: parent.slotOwnerSource,
4276
+ field,
4277
+ identityMaterial: [...parent.slotOwnerMaterial, `slot:${field}`]
4278
+ }, origins, /* @__PURE__ */ new WeakSet());
4279
+ });
4280
+ return origins;
4281
+ }
4282
+ function childContextFor(entry, context, path) {
4283
+ if (entry.invalidExtraContext) {
4284
+ addDiagnostic$1(context, compositionDiagnostic({
4285
+ code: "E_COMPOSITION_INVALID_MOUNT",
4286
+ title: "mount received invalid source context",
4287
+ path,
4288
+ message: "A Bound Source cannot receive additional Source Context."
4289
+ }));
4290
+ return;
4291
+ }
4292
+ if (entry.contextProvider === void 0) return { present: false };
4293
+ if (typeof entry.contextProvider !== "function") return {
4294
+ present: true,
4295
+ value: entry.contextProvider
4296
+ };
4297
+ try {
4298
+ const value = context.context.present ? entry.contextProvider(context.context.value) : entry.contextProvider();
4299
+ if (isPromiseLike(value)) {
4300
+ addDiagnostic$1(context, compositionDiagnostic({
4301
+ code: "E_COMPOSITION_CONTEXT_MAPPER_ASYNC",
4302
+ title: "source context mapper returned a Promise",
4303
+ path,
4304
+ message: "Source Context Mappers must be synchronous in v0.3."
4305
+ }));
4306
+ return;
4307
+ }
4308
+ return {
4309
+ present: true,
4310
+ value
4311
+ };
4312
+ } catch (error) {
4313
+ addDiagnostic$1(context, compositionDiagnostic({
4314
+ code: "E_COMPOSITION_CONTEXT_MAPPER_FAILED",
4315
+ title: "source context mapper failed",
4316
+ path,
4317
+ message: error instanceof Error ? error.message : "Source Context Mapper threw."
4318
+ }));
4319
+ return;
4320
+ }
4321
+ }
4322
+ function resolveSource(source, context) {
4323
+ const sourceState = source[COMPOSITION_SOURCE]();
4324
+ const effectiveContext = context.context.present ? context.context : sourceState.boundContext;
4325
+ if (context.depth > MAX_COMPOSITION_DEPTH) {
4326
+ addDiagnostic$1(context, compositionDiagnostic({
4327
+ code: "E_COMPOSITION_DEPTH_EXCEEDED",
4328
+ title: "composition depth exceeded",
4329
+ path: context.sourcePath,
4330
+ message: `Composition depth exceeded ${MAX_COMPOSITION_DEPTH}.`
4331
+ }));
4332
+ return;
4333
+ }
4334
+ if (context.stack.indexOf(sourceState.cycleId) !== -1) {
4335
+ addDiagnostic$1(context, compositionDiagnostic({
4336
+ code: "E_COMPOSITION_CYCLE",
4337
+ title: "composition cycle detected",
4338
+ path: context.sourcePath,
4339
+ message: "A Deck cannot mount itself through its descendant sources."
4340
+ }));
4341
+ return;
4342
+ }
4343
+ const entries = [];
4344
+ let slideCount = 0;
4345
+ const sourceKeys = /* @__PURE__ */ new Set();
4346
+ const slotOrigins = collectSourceSlots(effectiveContext, context);
4347
+ const nextContextBase = {
4348
+ diagnostics: context.diagnostics,
4349
+ stack: [...context.stack, sourceState.cycleId],
4350
+ depth: context.depth + 1
4351
+ };
4352
+ sourceState.entries.forEach((entry, index) => {
4353
+ if (entry.kind === "slide") {
4354
+ entries.push({
4355
+ kind: "slide",
4356
+ factory: entry.factory,
4357
+ path: `${context.sourcePath} > slideFactory[${index}]`
4358
+ });
4359
+ slideCount += 1;
4360
+ return;
4361
+ }
4362
+ const mountPath = `${context.sourcePath} > mount[${entry.sourceKey}]`;
4363
+ const invalidKey = validateSourceKey(entry.sourceKey);
4364
+ if (invalidKey) {
4365
+ addDiagnostic$1(context, compositionDiagnostic({
4366
+ code: "E_COMPOSITION_INVALID_SOURCE_KEY",
4367
+ title: "invalid source key",
4368
+ path: mountPath,
4369
+ message: invalidKey
4370
+ }));
4371
+ return;
4372
+ }
4373
+ if (sourceKeys.has(entry.sourceKey)) {
4374
+ addDiagnostic$1(context, compositionDiagnostic({
4375
+ code: "E_COMPOSITION_DUPLICATE_SOURCE_KEY",
4376
+ title: "duplicate source key",
4377
+ path: mountPath,
4378
+ message: `Source Key "${entry.sourceKey}" is already used in this parent source.`
4379
+ }));
4380
+ return;
4381
+ }
4382
+ sourceKeys.add(entry.sourceKey);
4383
+ const childContext = childContextFor(entry, context, mountPath);
4384
+ if (!childContext) return;
4385
+ const childSource = sourceOriginFor(context.source, entry.sourceKey);
4386
+ const childPlan = resolveSource(entry.source, {
4387
+ ...nextContextBase,
4388
+ source: childSource,
4389
+ sourceIdentityMaterial: sourceMaterialFor(childSource),
4390
+ sourcePath: sourcePathFor(context.sourcePath, entry.sourceKey),
4391
+ context: childContext,
4392
+ slotOwnerSource: context.source,
4393
+ slotOwnerMaterial: context.sourceIdentityMaterial
4394
+ });
4395
+ if (!childPlan) return;
4396
+ entries.push({
4397
+ kind: "source",
4398
+ source: childPlan
4399
+ });
4400
+ slideCount += childPlan.slideCount;
4401
+ });
4402
+ return {
4403
+ source: context.source,
4404
+ sourceIdentityMaterial: context.sourceIdentityMaterial,
4405
+ context: effectiveContext,
4406
+ entries,
4407
+ slideCount,
4408
+ slotOrigins
4409
+ };
4410
+ }
4411
+ function flattenPlan(plan, deckTotalSlides, deckSlideIndex, roots, diagnostics) {
4412
+ let sourceSlideIndex = 0;
4413
+ let nextDeckSlideIndex = deckSlideIndex;
4414
+ for (const entry of plan.entries) {
4415
+ if (entry.kind === "source") {
4416
+ nextDeckSlideIndex = flattenPlan(entry.source, deckTotalSlides, nextDeckSlideIndex, roots, diagnostics);
4417
+ sourceSlideIndex += entry.source.slideCount;
4418
+ continue;
4419
+ }
4420
+ const composition = {
4421
+ ...plan.source.kind === "mounted" ? { sourceKey: plan.source.sourceKey } : {},
4422
+ slideIndex: sourceSlideIndex,
4423
+ totalSlides: plan.slideCount,
4424
+ deckSlideIndex: nextDeckSlideIndex,
4425
+ deckTotalSlides
4426
+ };
4427
+ const input = plan.context.present ? {
4428
+ context: plan.context.value,
4429
+ composition
4430
+ } : { composition };
4431
+ const root = entry.factory(input);
4432
+ if (!isAuthorTreeNode(root) || !isSlideRoot(root)) diagnostics.push(compositionDiagnostic({
4433
+ code: "E_COMPOSITION_INVALID_ROOT",
4434
+ title: "slide factory must return a <Slide /> root",
4435
+ path: entry.path,
4436
+ message: describeInvalidRoot(root),
4437
+ help: ["Return <Slide>...</Slide> from the slide factory passed to deck.add()."]
4438
+ }));
4439
+ else roots.push({
4440
+ root,
4441
+ source: plan.source,
4442
+ sourceIdentityMaterial: plan.sourceIdentityMaterial,
4443
+ path: entry.path,
4444
+ composition,
4445
+ slotOrigins: plan.slotOrigins
4446
+ });
4447
+ sourceSlideIndex += 1;
4448
+ nextDeckSlideIndex += 1;
4449
+ }
4450
+ return nextDeckSlideIndex;
4451
+ }
4452
+ function resolveComposition(source) {
4453
+ const diagnostics = [];
4454
+ const rootPlan = resolveSource(source, {
4455
+ diagnostics,
4456
+ stack: [],
4457
+ depth: 0,
4458
+ source: ROOT_SOURCE,
4459
+ sourceIdentityMaterial: sourceMaterialFor(ROOT_SOURCE),
4460
+ sourcePath: "root",
4461
+ context: { present: false },
4462
+ slotOwnerSource: ROOT_SOURCE,
4463
+ slotOwnerMaterial: sourceMaterialFor(ROOT_SOURCE)
4464
+ });
4465
+ if (!rootPlan) return { diagnostics: createDiagnostics(diagnostics) };
4466
+ const roots = [];
4467
+ flattenPlan(rootPlan, rootPlan.slideCount, 0, roots, diagnostics);
4468
+ const resolvedDiagnostics = createDiagnostics(diagnostics);
4469
+ return {
4470
+ ...resolvedDiagnostics.hasErrors ? {} : { roots },
4471
+ diagnostics: resolvedDiagnostics
4472
+ };
4473
+ }
4474
+ //#endregion
4475
+ //#region src/graph/identity.ts
4476
+ function slug(value) {
4477
+ return value.replace(/[^a-zA-Z0-9:_-]+/g, "_");
4478
+ }
4479
+ function graphNodeId(material) {
4480
+ return slug(material.join("/"));
4481
+ }
4482
+ function styleEntityId(material) {
4483
+ return slug(`style/${material.join("/")}`);
4484
+ }
4485
+ function assetEntityId(material) {
4486
+ return slug(`asset/${material.join("/")}`);
4487
+ }
4488
+ //#endregion
4489
+ //#region src/graph/roles.ts
4490
+ function semanticKindForTag(tag) {
4491
+ if (tag === "img") return "image";
4492
+ if (tag === "p" || tag.startsWith("h") || tag === "span") return tag === "span" ? "textRun" : "text";
4493
+ return "container";
4494
+ }
4495
+ function semanticKindForComponent(component) {
4496
+ switch (component) {
4497
+ case "Slide": return "slide";
4498
+ case "View": return "container";
4499
+ case "Text": return "text";
4500
+ case "Image": return "image";
4501
+ case "Shape": return "shape";
4502
+ }
4503
+ }
4504
+ function semanticRoleForTag(tag) {
4505
+ switch (tag) {
4506
+ case "article":
4507
+ case "aside":
4508
+ case "footer":
4509
+ case "header":
4510
+ case "main":
4511
+ case "nav":
4512
+ case "section": return {
4513
+ kind: "sectioning",
4514
+ tag
4515
+ };
4516
+ case "div": return { kind: "genericContainer" };
4517
+ case "figure": return { kind: "figure" };
4518
+ case "p": return { kind: "paragraph" };
4519
+ case "h1":
4520
+ case "h2":
4521
+ case "h3":
4522
+ case "h4":
4523
+ case "h5":
4524
+ case "h6": return {
4525
+ kind: "heading",
4526
+ level: Number(tag.slice(1))
4527
+ };
4528
+ case "img": return { kind: "image" };
4529
+ case "span": return;
4530
+ }
4531
+ }
4532
+ function semanticRoleForComponent(component) {
4533
+ switch (component) {
4534
+ case "Slide": return { kind: "slide" };
4535
+ case "Image": return { kind: "image" };
4536
+ case "Shape": return { kind: "shape" };
4537
+ case "Text":
4538
+ case "View": return;
4539
+ }
4540
+ }
4541
+ //#endregion
4542
+ //#region src/graph/build.ts
4543
+ function keySegment(key, index) {
4544
+ return key === void 0 ? `index:${index}` : `key:${String(key)}`;
4545
+ }
4546
+ function sourceName(node) {
4547
+ return node.source.kind === "tag" ? node.source.tag : node.source.component;
4548
+ }
4549
+ function nodeSemanticKind(node) {
4550
+ return node.source.kind === "tag" ? semanticKindForTag(node.source.tag) : semanticKindForComponent(node.source.component);
4551
+ }
4552
+ function nodeRole(node) {
4553
+ return node.source.kind === "tag" ? semanticRoleForTag(node.source.tag) : semanticRoleForComponent(node.source.component);
4554
+ }
4555
+ function sourceFor(context) {
4556
+ return context.activeSlot?.source ?? context.source;
4557
+ }
4558
+ function contextForNode(node, context) {
4559
+ const slot = context.slotOrigins.get(node);
4560
+ if (!slot) return context;
4561
+ return {
4562
+ ...context,
4563
+ activeSlot: slot,
4564
+ parentMaterial: [...context.parentMaterial, ...slot.identityMaterial],
4565
+ path: `${context.path} > slot[${slot.field}]`
4566
+ };
4567
+ }
4568
+ function originFor(node, path, context) {
4569
+ return {
4570
+ kind: "authored",
4571
+ path,
4572
+ source: sourceFor(context),
4573
+ ...node.sourceSpan ? { sourceSpan: node.sourceSpan } : {}
4574
+ };
4575
+ }
4576
+ function textOriginFor(node, path, context) {
4577
+ return {
4578
+ kind: "authored",
4579
+ path,
4580
+ source: sourceFor(context),
4581
+ ...node.sourceSpan ? { sourceSpan: node.sourceSpan } : {}
4582
+ };
4583
+ }
4584
+ function propsWithoutStyle(props) {
4585
+ const { style: _style, children: _children, ...direct } = props;
4586
+ return Object.keys(direct).length === 0 ? void 0 : direct;
4587
+ }
4588
+ function styleRefFor(state, idMaterial, target, props) {
4589
+ const style = props.style;
4590
+ const direct = propsWithoutStyle(props);
4591
+ if (style === void 0 && direct === void 0) return;
4592
+ const id = styleEntityId(idMaterial);
4593
+ state.styles.set(id, {
4594
+ id,
4595
+ target,
4596
+ authored: {
4597
+ ...style !== void 0 ? { style } : {},
4598
+ ...direct !== void 0 ? { direct } : {}
4599
+ }
4600
+ });
4601
+ return id;
4602
+ }
4603
+ function addDiagnostic(state, item) {
4604
+ state.diagnostics.push(item);
4605
+ }
4606
+ function invalidStructure(path, title, message, help) {
4607
+ return diagnostic({
4608
+ severity: "error",
4609
+ code: "E_SEMANTIC_STRUCTURE",
4610
+ title,
4611
+ labels: [{
4612
+ path,
4613
+ message
4614
+ }],
4615
+ ...message ? { message } : {},
4616
+ ...help ? { help } : {}
4617
+ });
4618
+ }
4619
+ function assetForImage(state, idMaterial, props, path) {
4620
+ if (typeof props.src !== "string" && typeof props.data !== "string") {
4621
+ addDiagnostic(state, invalidStructure(path, "image source is missing", "Image nodes require either src or data.", ["Add a src path or data URL to the image."]));
4622
+ return;
4623
+ }
4624
+ const id = assetEntityId(idMaterial);
4625
+ const entity = {
4626
+ id,
4627
+ kind: "image",
4628
+ source: typeof props.src === "string" ? {
4629
+ kind: "path",
4630
+ path: props.src
4631
+ } : {
4632
+ kind: "data",
4633
+ data: props.data
4634
+ },
4635
+ metadata: typeof props.data === "string" && props.data.startsWith("data:") ? { mediaType: props.data.slice(5, props.data.indexOf(";")) || void 0 } : {},
4636
+ resolution: "unresolved"
4637
+ };
4638
+ state.assets.set(id, entity);
4639
+ return id;
4640
+ }
4641
+ function semanticBase(state, node, id, kind, path, material, context) {
4642
+ const styleRef = styleRefFor(state, material, kind, node.props);
4643
+ return {
4644
+ id,
4645
+ kind,
4646
+ origin: originFor(node, path, context),
4647
+ ...node.source.kind === "tag" ? { authoredTag: node.source.tag } : {},
4648
+ ...node.source.kind === "component" ? { authoredComponent: node.source.component } : {},
4649
+ ...node.key !== void 0 ? { key: node.key } : {},
4650
+ ...nodeRole(node) ? { role: nodeRole(node) } : {},
4651
+ ...styleRef ? { styleRef } : {}
4652
+ };
4653
+ }
4654
+ function buildTextRunFromLeaf(state, leaf, context, index) {
4655
+ const text = typeof leaf.value === "string" ? leaf.value : String(leaf.value);
4656
+ if (text.trim().length === 0) return;
4657
+ const segment = `text:${index}`;
4658
+ const id = graphNodeId([...context.parentMaterial, segment]);
4659
+ const path = `${context.path} > text[${index}]`;
4660
+ state.nodes.set(id, {
4661
+ id,
4662
+ kind: "textRun",
4663
+ origin: textOriginFor(leaf, path, context),
4664
+ text
4665
+ });
4666
+ return {
4667
+ id,
4668
+ kind: "textRun"
4669
+ };
4670
+ }
4671
+ function buildImplicitTextNode(state, leaf, context, index) {
4672
+ const run = buildTextRunFromLeaf(state, leaf, {
4673
+ ...context,
4674
+ parentMaterial: [...context.parentMaterial, `implicit-text:${index}`],
4675
+ path: `${context.path} > implicitText[${index}]`
4676
+ }, 0);
4677
+ if (!run) return;
4678
+ const id = graphNodeId([...context.parentMaterial, `implicit-text:${index}`]);
4679
+ state.nodes.set(id, {
4680
+ id,
4681
+ kind: "text",
4682
+ origin: {
4683
+ kind: "implicit",
4684
+ path: `${context.path} > implicitText[${index}]`,
4685
+ source: sourceFor(context),
4686
+ ...leaf.sourceSpan ? { sourceSpan: leaf.sourceSpan } : {},
4687
+ reason: "primitive-text-in-container"
4688
+ },
4689
+ implicit: true,
4690
+ inlineChildren: [run.id]
4691
+ });
4692
+ return {
4693
+ id,
4694
+ kind: "text"
4695
+ };
4696
+ }
4697
+ function buildChildren(state, children, context) {
4698
+ const ids = [];
4699
+ children.forEach((child, index) => {
4700
+ if (child.kind === "fragment") {
4701
+ const childContext = contextForNode(child, context);
4702
+ const segment = `fragment:${keySegment(child.key, index)}`;
4703
+ ids.push(...buildChildren(state, child.children, {
4704
+ ...childContext,
4705
+ parentMaterial: [...childContext.parentMaterial, segment],
4706
+ path: `${childContext.path} > fragment[${keySegment(child.key, index)}]`
4707
+ }));
4708
+ return;
4709
+ }
4710
+ const built = buildNode(state, child, context, index);
4711
+ if (built) ids.push(built.id);
4712
+ });
4713
+ return ids;
4714
+ }
4715
+ function buildTextLikeNode(state, node, id, path, material, context) {
4716
+ const inlineChildren = [];
4717
+ node.children.forEach((child, index) => {
4718
+ if (child.kind === "text") {
4719
+ const run = buildTextRunFromLeaf(state, child, {
4720
+ ...context,
4721
+ parentId: id,
4722
+ parentMaterial: material,
4723
+ path,
4724
+ inline: true
4725
+ }, index);
4726
+ if (run) inlineChildren.push(run.id);
4727
+ return;
4728
+ }
4729
+ if (child.kind === "fragment") {
4730
+ const childContext = contextForNode(child, {
4731
+ ...context,
4732
+ parentId: id,
4733
+ parentMaterial: material,
4734
+ path,
4735
+ inline: true
4736
+ });
4737
+ const segment = `fragment:${keySegment(child.key, index)}`;
4738
+ inlineChildren.push(...buildChildren(state, child.children, {
4739
+ ...childContext,
4740
+ parentMaterial: [...childContext.parentMaterial, segment],
4741
+ path: `${childContext.path} > fragment[${keySegment(child.key, index)}]`
4742
+ }));
4743
+ return;
4744
+ }
4745
+ if (child.source.kind === "tag" && child.source.tag === "span") {
4746
+ const built = buildNode(state, child, {
4747
+ ...context,
4748
+ parentId: id,
4749
+ parentMaterial: material,
4750
+ path,
4751
+ inline: true
4752
+ }, index);
4753
+ if (built) inlineChildren.push(built.id);
4754
+ return;
4755
+ }
4756
+ addDiagnostic(state, invalidStructure(`${path} > ${sourceName(child)}[${index}]`, "block content cannot appear inside text", "Text-like elements accept primitive text and inline spans only."));
4757
+ });
4758
+ state.nodes.set(id, {
4759
+ ...semanticBase(state, node, id, "text", path, material, context),
4760
+ kind: "text",
4761
+ inlineChildren
4762
+ });
4763
+ return {
4764
+ id,
4765
+ kind: "text"
4766
+ };
4767
+ }
4768
+ function collectInlineText(state, children, path) {
4769
+ let text = "";
4770
+ children.forEach((child, index) => {
4771
+ if (child.kind === "text") {
4772
+ text += typeof child.value === "string" ? child.value : String(child.value);
4773
+ return;
4774
+ }
4775
+ if (child.kind === "fragment") {
4776
+ text += collectInlineText(state, child.children, `${path} > fragment[${keySegment(child.key, index)}]`);
4777
+ return;
4778
+ }
4779
+ if (child.source.kind === "tag" && child.source.tag === "span") {
4780
+ text += collectInlineText(state, child.children, `${path} > span[${keySegment(child.key, index)}]`);
4781
+ return;
4782
+ }
4783
+ addDiagnostic(state, invalidStructure(`${path} > ${sourceName(child)}[${index}]`, "block content cannot appear inside span", "span accepts primitive text or nested inline spans only."));
4784
+ });
4785
+ return text;
4786
+ }
4787
+ function buildNode(state, node, context, index) {
4788
+ const nodeContext = contextForNode(node, context);
4789
+ if (node.kind === "fragment") return;
4790
+ if (node.kind === "text") return nodeContext.inline ? buildTextRunFromLeaf(state, node, nodeContext, index) : buildImplicitTextNode(state, node, nodeContext, index);
4791
+ const kind = nodeSemanticKind(node);
4792
+ const segment = `${sourceName(node)}:${keySegment(node.key, index)}`;
4793
+ const material = [...nodeContext.parentMaterial, segment];
4794
+ const id = graphNodeId(material);
4795
+ const path = `${nodeContext.path} > ${sourceName(node)}[${keySegment(node.key, index)}]`;
4796
+ if (kind === "textRun") {
4797
+ if (!context.inline) {
4798
+ addDiagnostic(state, invalidStructure(path, "span cannot appear here", "span must be inside a text-like element.", ["Wrap the span in <p>...</p> or move it inside an existing text element."]));
4799
+ return;
4800
+ }
4801
+ const text = collectInlineText(state, node.children, path);
4802
+ state.nodes.set(id, {
4803
+ ...semanticBase(state, node, id, "textRun", path, material, nodeContext),
4804
+ kind: "textRun",
4805
+ text
4806
+ });
4807
+ return {
4808
+ id,
4809
+ kind: "textRun"
4810
+ };
4811
+ }
4812
+ if (kind === "text") return buildTextLikeNode(state, node, id, path, material, nodeContext);
4813
+ if (kind === "image") {
4814
+ if (node.children.length > 0) addDiagnostic(state, invalidStructure(path, "image cannot have children", "Image nodes are leaf nodes."));
4815
+ const assetRef = assetForImage(state, material, node.props, path);
4816
+ state.nodes.set(id, {
4817
+ ...semanticBase(state, node, id, "image", path, material, nodeContext),
4818
+ kind: "image",
4819
+ ...assetRef ? { assetRef } : {}
4820
+ });
4821
+ return {
4822
+ id,
4823
+ kind: "image"
4824
+ };
4825
+ }
4826
+ const childIds = buildChildren(state, node.children, {
4827
+ parentId: id,
4828
+ parentMaterial: material,
4829
+ path,
4830
+ inline: false,
4831
+ source: sourceFor(nodeContext),
4832
+ slotOrigins: nodeContext.slotOrigins,
4833
+ activeSlot: nodeContext.activeSlot
4834
+ });
4835
+ state.nodes.set(id, {
4836
+ ...semanticBase(state, node, id, kind, path, material, nodeContext),
4837
+ kind,
4838
+ children: childIds
4839
+ });
4840
+ return {
4841
+ id,
4842
+ kind
4843
+ };
4844
+ }
4845
+ function rootSource() {
4846
+ return { kind: "root" };
4847
+ }
4848
+ function asComposedRoot(root, index) {
4849
+ if (root.kind !== "element") throw new Error("Semantic graph roots must be element nodes.");
4850
+ return {
4851
+ root,
4852
+ source: rootSource(),
4853
+ sourceIdentityMaterial: ["source", "root"],
4854
+ path: `document > slideFactory[${index}]`,
4855
+ composition: {
4856
+ slideIndex: index,
4857
+ totalSlides: 0,
4858
+ deckSlideIndex: index,
4859
+ deckTotalSlides: 0
4860
+ },
4861
+ slotOrigins: /* @__PURE__ */ new WeakMap()
4862
+ };
4863
+ }
4864
+ function buildSemanticAuthorGraph(roots) {
4865
+ const documentId = graphNodeId(["document", "root"]);
4866
+ const state = {
4867
+ nodes: /* @__PURE__ */ new Map(),
4868
+ styles: /* @__PURE__ */ new Map(),
4869
+ assets: /* @__PURE__ */ new Map(),
4870
+ diagnostics: []
4871
+ };
4872
+ const slideIds = [];
4873
+ roots.forEach((root, index) => {
4874
+ const composed = "root" in root ? root : asComposedRoot(root, index);
4875
+ const built = buildNode(state, composed.root, {
4876
+ parentId: documentId,
4877
+ parentMaterial: [
4878
+ "document",
4879
+ "root",
4880
+ ...composed.sourceIdentityMaterial
4881
+ ],
4882
+ path: composed.path,
4883
+ inline: false,
4884
+ source: composed.source,
4885
+ slotOrigins: composed.slotOrigins
4886
+ }, composed.composition.slideIndex);
4887
+ if (built) slideIds.push(built.id);
4888
+ });
4889
+ const documentNode = {
4890
+ id: documentId,
4891
+ kind: "document",
4892
+ origin: {
4893
+ kind: "implicit",
4894
+ path: "document",
4895
+ source: rootSource()
4896
+ },
4897
+ role: { kind: "document" },
4898
+ children: slideIds
4899
+ };
4900
+ state.nodes.set(documentId, documentNode);
4901
+ const diagnostics = createDiagnostics(state.diagnostics);
4902
+ return {
4903
+ graph: {
4904
+ documentId,
4905
+ nodes: state.nodes,
4906
+ styles: state.styles,
4907
+ assets: state.assets
4908
+ },
4909
+ diagnostics
4112
4910
  };
4113
4911
  }
4114
4912
  //#endregion
@@ -14974,6 +15772,24 @@ function toPptxTabStops(tabStops) {
14974
15772
  ...tabStop.alignment ? { alignment: tabStop.alignment } : {}
14975
15773
  }));
14976
15774
  }
15775
+ function toPptxTextRunOptions(style) {
15776
+ if (!style) return;
15777
+ const options = {
15778
+ fontFace: style.fontFamily,
15779
+ fontSize: style.fontSizePt,
15780
+ color: style.color,
15781
+ bold: style.fontWeight === "bold" || typeof style.fontWeight === "number" && style.fontWeight >= 600,
15782
+ italic: style.italic,
15783
+ underline: toPptxUnderline(style),
15784
+ strike: style.strike,
15785
+ charSpacing: style.charSpacing,
15786
+ superscript: style.superscript,
15787
+ subscript: style.subscript,
15788
+ breakLine: false
15789
+ };
15790
+ if (Object.values(options).every((value) => value === void 0 || value === false)) return;
15791
+ return options;
15792
+ }
14977
15793
  function emitOutlineShape(slide, shapeName, frame, outline, radiusEmu, effectiveOpacity, rotation, flipH, flipV) {
14978
15794
  if (!outline) return;
14979
15795
  const insetEmu = pointsToEmu(outline.widthPt) / 2;
@@ -15048,7 +15864,14 @@ function emitText(slide, node, inheritedOpacity) {
15048
15864
  emitOutlineShape(slide, node.radiusEmu && node.radiusEmu > 0 ? "roundRect" : "rect", node.frame, node.outline, node.radiusEmu, effectiveOpacity, node.rotation, node.flipH, node.flipV);
15049
15865
  emitEdgeStrokes(slide, node.frame, node.edgeStrokes, effectiveOpacity, node.rotation, node.flipH, node.flipV);
15050
15866
  emitBackgroundLayers(slide, node.frame, node.backgroundLayers, node.radiusEmu && node.radiusEmu > 0 ? "roundRect" : "rect", node.radiusEmu, effectiveOpacity, node.rotation, node.flipH, node.flipV);
15051
- slide.addText(node.content.text, {
15867
+ const textContent = node.content.runs ? node.content.runs.map((run) => {
15868
+ const options = toPptxTextRunOptions(run.style);
15869
+ return {
15870
+ text: run.text,
15871
+ ...options ? { options } : {}
15872
+ };
15873
+ }) : node.content.text;
15874
+ slide.addText(textContent, {
15052
15875
  x: emuToInches(node.frame.xEmu),
15053
15876
  y: emuToInches(node.frame.yEmu),
15054
15877
  w: emuToInches(node.frame.widthEmu),
@@ -15212,22 +16035,105 @@ async function outputPresentation(presentation, config) {
15212
16035
  }
15213
16036
  //#endregion
15214
16037
  //#region src/deck.ts
16038
+ function hasMountedSources(entries) {
16039
+ return entries.some((entry) => entry.kind === "mount");
16040
+ }
16041
+ function directSlideFactories(entries) {
16042
+ return entries.flatMap((entry) => entry.kind === "slide" ? [entry.factory] : []);
16043
+ }
16044
+ function mountedSourceError() {
16045
+ return /* @__PURE__ */ new Error("Mounted sources are supported by compile() only until the output pipeline supports graph composition.");
16046
+ }
16047
+ function compileSource(source, config = {}) {
16048
+ const composition = resolveComposition(source);
16049
+ if (composition.diagnostics.hasErrors) {
16050
+ if (config.mode === "inspect") return { diagnostics: composition.diagnostics };
16051
+ throw new CompositionDiagnosticError(composition.diagnostics);
16052
+ }
16053
+ const result = buildSemanticAuthorGraph(composition.roots ?? []);
16054
+ if (config.mode === "inspect") return result;
16055
+ if (result.diagnostics.hasErrors) throw new SemanticGraphDiagnosticError(result.diagnostics);
16056
+ if (!result.graph) throw new SemanticGraphDiagnosticError(result.diagnostics);
16057
+ return result.graph;
16058
+ }
16059
+ var BoundSource = class {
16060
+ #source;
16061
+ #sourceContext;
16062
+ constructor(source, sourceContext) {
16063
+ this.#source = source;
16064
+ this.#sourceContext = sourceContext;
16065
+ }
16066
+ [COMPOSITION_SOURCE]() {
16067
+ const source = this.#source[COMPOSITION_SOURCE]();
16068
+ return {
16069
+ entries: source.entries,
16070
+ cycleId: source.cycleId,
16071
+ boundContext: {
16072
+ present: true,
16073
+ value: this.#sourceContext
16074
+ }
16075
+ };
16076
+ }
16077
+ compile(config = {}) {
16078
+ return compileSource(this, config);
16079
+ }
16080
+ render() {
16081
+ const source = this.#source[COMPOSITION_SOURCE]();
16082
+ if (hasMountedSources(source.entries)) throw mountedSourceError();
16083
+ return renderPresentation(this.#source.options, directSlideFactories(source.entries).map((factory) => (input) => factory({
16084
+ ...input,
16085
+ context: this.#sourceContext
16086
+ })));
16087
+ }
16088
+ async output(config) {
16089
+ await outputPresentation(this.render(), config);
16090
+ }
16091
+ };
15215
16092
  var Deck = class {
15216
16093
  #options;
15217
- #slides = [];
16094
+ #entries = [];
16095
+ withSource;
15218
16096
  constructor(options) {
15219
16097
  this.#options = options;
16098
+ this.withSource = ((sourceContext) => new BoundSource(this, sourceContext));
16099
+ }
16100
+ get options() {
16101
+ return this.#options;
16102
+ }
16103
+ [COMPOSITION_SOURCE]() {
16104
+ return {
16105
+ entries: this.#entries,
16106
+ cycleId: this,
16107
+ boundContext: { present: false }
16108
+ };
15220
16109
  }
15221
16110
  add(slide) {
15222
- this.#slides.push(slide);
16111
+ this.#entries.push({
16112
+ kind: "slide",
16113
+ factory: slide
16114
+ });
16115
+ return this;
16116
+ }
16117
+ mount(sourceKey, child, ...context) {
16118
+ this.#entries.push({
16119
+ kind: "mount",
16120
+ sourceKey,
16121
+ source: child,
16122
+ ...context.length > 0 ? { contextProvider: context[0] } : {},
16123
+ ...child instanceof BoundSource && context.length > 0 ? { invalidExtraContext: true } : {}
16124
+ });
15223
16125
  return this;
15224
16126
  }
15225
16127
  render() {
15226
- return renderPresentation(this.#options, this.#slides);
16128
+ if (hasMountedSources(this.#entries)) throw mountedSourceError();
16129
+ return renderPresentation(this.#options, directSlideFactories(this.#entries));
16130
+ }
16131
+ compile(config = {}) {
16132
+ return compileSource(this, config);
15227
16133
  }
15228
16134
  async output(config) {
15229
16135
  await outputPresentation(this.render(), config);
15230
16136
  }
15231
16137
  };
15232
16138
  //#endregion
15233
- export { Deck, EMU_PER_INCH, Fragment, Image, POINTS_PER_INCH, Shape, Slide, Text, View, createElement, isAuthorNode, isContentNode, isSlideNode, pptxgenjsBackend };
16139
+ export { CompositionDiagnosticError, Deck, DeckDiagnosticError, EMU_PER_INCH, Fragment, Image, POINTS_PER_INCH, SemanticGraphDiagnosticError, Shape, Slide, Text, View, createElement, formatDiagnostic, formatDiagnostics, isAuthorNode, isContentNode, isSlideNode, pptxgenjsBackend };