pptx-custom 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,11 @@
1
1
  # pptx-custom
2
2
 
3
- Utilities to customize PPTX JSON templates in two stages:
3
+ [中文文档](./README.zh-CN.md)
4
4
 
5
- - `applyCustomContent`: replace default slide content from backend JSON text.
6
- - `applyCustomTheme`: apply user theme settings (palette/font/background).
7
- - `parseCustomContent`: parse custom content to slide data
5
+ Utilities for customizing `json2pptx` JSON decks in two stages:
6
+
7
+ - Content stage: map backend slide content into a template deck.
8
+ - Theme stage: replace theme colors/font/background and apply scoped media.
8
9
 
9
10
  ## Install
10
11
 
@@ -12,31 +13,85 @@ Utilities to customize PPTX JSON templates in two stages:
12
13
  npm i pptx-custom
13
14
  ```
14
15
 
15
- ## Usage
16
+ ## Exports
17
+
18
+ - `applyCustomContent(template, input)`
19
+ - `parseCustomContent(raw)`
20
+ - `applyCustomContentToTemplate(template, slides)`
21
+ - `applyCustomTheme(deck, themeInput)`
22
+
23
+ Types are also exported, including:
24
+ `CustomSlide`, `Deck`, `PptxCustomContentInput`, `PptxCustomThemeInput`,
25
+ `PptxCustomOptions`, `TemplateJson`, `TemplateJsonSlide`, `TemplateJsonElement`,
26
+ `TemplateJsonTheme`.
27
+
28
+ ## Quick Start
16
29
 
17
30
  ```ts
18
31
  import {
19
32
  applyCustomContent,
20
33
  applyCustomTheme,
21
34
  parseCustomContent,
35
+ applyCustomContentToTemplate
22
36
  } from 'pptx-custom'
23
37
 
24
- const deckWithContent = applyCustomContent(templateDeck, backendText)
38
+ const withContent = applyCustomContent(templateDeck, backendText)
25
39
 
26
- const deckWithTheme = applyCustomTheme(deckWithContent, {
40
+ const withTheme = applyCustomTheme(withContent, {
27
41
  themeColors: ['#111111', '#333333', '#555555', '#777777', '#999999', '#BBBBBB'],
28
42
  fontColor: '#222222',
29
- backgroundColor: '#FFFFFF'
43
+ backgroundColor: '#FFFFFF',
44
+ backgroundImage: {
45
+ src: 'https://example.com/background.png',
46
+ scope: {
47
+ cover: false,
48
+ contents: true,
49
+ transition: true,
50
+ content: true,
51
+ end: false
52
+ }
53
+ },
54
+ logoImage: {
55
+ src: 'https://example.com/logo.png',
56
+ position: 'right',
57
+ scope: {
58
+ cover: true,
59
+ contents: true,
60
+ transition: true,
61
+ content: true,
62
+ end: true
63
+ }
64
+ }
30
65
  })
31
66
 
32
67
  const slides = parseCustomContent(backendText)
33
- const deckFromSlides = applyCustomContentToTemplate(templateDeck, slides)
34
-
68
+ const withContentDirect = applyCustomContentToTemplate(templateDeck, slides)
35
69
  ```
36
70
 
37
- ## Custom Content Format
71
+ ## Custom Content Input
72
+
73
+ `parseCustomContent` and `applyCustomContent` support:
74
+
75
+ 1. NDJSON (one slide per line)
76
+ 2. JSON array of slides
77
+ 3. JSON object with a `slides` array
78
+ 4. JSON object containing a single slide (with `type`)
79
+
80
+ Supported slide types:
38
81
 
39
- `applyCustomContent` and `parseCustomContent` accept NDJSON-style lines or JSON arrays/objects of slides:
82
+ - `cover`
83
+ - `contents`
84
+ - `transition`
85
+ - `content`
86
+ - `end`
87
+
88
+ Legacy aliases accepted in input:
89
+
90
+ - `agenda` -> `contents`
91
+ - `section` -> `transition`
92
+ - `ending` -> `end`
93
+
94
+ Example NDJSON:
40
95
 
41
96
  ```json
42
97
  {"type":"cover","data":{"title":"Title","text":"Subtitle"}}
@@ -46,8 +101,31 @@ const deckFromSlides = applyCustomContentToTemplate(templateDeck, slides)
46
101
  {"type":"end"}
47
102
  ```
48
103
 
49
- ## Exported APIs
104
+ ## Theme Input
50
105
 
51
- - `applyCustomContent(template, input)`
52
- - `parseCustomContent(raw)`
53
- - `applyCustomTheme(deck, themeUpdate)`
106
+ `applyCustomTheme` accepts `PptxCustomThemeInput`:
107
+
108
+ - `themeColors: string[]` (uses first 6)
109
+ - `fontColor: string`
110
+ - `backgroundColor?: string`
111
+ - `backgroundImage?: { src, scope, width?, height? }`
112
+ - `logoImage?: { src, scope, position, width?, height? }`
113
+ - `clearBackgroundImage?: boolean`
114
+ - `clearLogoImage?: boolean`
115
+
116
+ `scope` keys:
117
+ `cover | contents | transition | content | end`
118
+
119
+ ## Behavior Notes
120
+
121
+ - Both `applyCustomContent` and `applyCustomTheme` run through `json2pptx-schema`
122
+ parsing/normalization before returning.
123
+ - `applyCustomContent` selects template slides by `type`, and for `contents/content`
124
+ prefers layouts with the closest capacity to the requested item count.
125
+ - `applyCustomContent` normalizes logo elements (`imageType: "logo"`) to stay within
126
+ top margins and removes logo clipping.
127
+ - `applyCustomTheme` inserts scoped background images as slide elements with
128
+ `imageType: "background"` and logo images with `imageType: "logo"`.
129
+ - When both `backgroundImage` and `backgroundColor` are provided, background color is
130
+ applied as a 50% alpha overlay color on targeted slides.
131
+ - `clearBackgroundImage` also clears logo images in current behavior.
@@ -0,0 +1,131 @@
1
+ # pptx-custom
2
+
3
+ [English](./README.md)
4
+
5
+ 用于对 `json2pptx` JSON deck 进行两阶段定制:
6
+
7
+ - 内容阶段:将后端内容映射到模板 deck。
8
+ - 主题阶段:替换主题色/字体/背景,并按范围应用媒体资源。
9
+
10
+ ## 安装
11
+
12
+ ```bash
13
+ npm i pptx-custom
14
+ ```
15
+
16
+ ## 导出 API
17
+
18
+ - `applyCustomContent(template, input)`
19
+ - `parseCustomContent(raw)`
20
+ - `applyCustomContentToTemplate(template, slides)`
21
+ - `applyCustomTheme(deck, themeInput)`
22
+
23
+ 同时导出类型,包括:
24
+ `CustomSlide`、`Deck`、`PptxCustomContentInput`、`PptxCustomThemeInput`、
25
+ `PptxCustomOptions`、`TemplateJson`、`TemplateJsonSlide`、`TemplateJsonElement`、
26
+ `TemplateJsonTheme`。
27
+
28
+ ## 快速开始
29
+
30
+ ```ts
31
+ import {
32
+ applyCustomContent,
33
+ applyCustomTheme,
34
+ parseCustomContent,
35
+ applyCustomContentToTemplate
36
+ } from 'pptx-custom'
37
+
38
+ const withContent = applyCustomContent(templateDeck, backendText)
39
+
40
+ const withTheme = applyCustomTheme(withContent, {
41
+ themeColors: ['#111111', '#333333', '#555555', '#777777', '#999999', '#BBBBBB'],
42
+ fontColor: '#222222',
43
+ backgroundColor: '#FFFFFF',
44
+ backgroundImage: {
45
+ src: 'https://example.com/background.png',
46
+ scope: {
47
+ cover: false,
48
+ contents: true,
49
+ transition: true,
50
+ content: true,
51
+ end: false
52
+ }
53
+ },
54
+ logoImage: {
55
+ src: 'https://example.com/logo.png',
56
+ position: 'right',
57
+ scope: {
58
+ cover: true,
59
+ contents: true,
60
+ transition: true,
61
+ content: true,
62
+ end: true
63
+ }
64
+ }
65
+ })
66
+
67
+ const slides = parseCustomContent(backendText)
68
+ const withContentDirect = applyCustomContentToTemplate(templateDeck, slides)
69
+ ```
70
+
71
+ ## 自定义内容输入格式
72
+
73
+ `parseCustomContent` 和 `applyCustomContent` 支持:
74
+
75
+ 1. NDJSON(每行一个 slide)
76
+ 2. JSON slide 数组
77
+ 3. 带 `slides` 字段的 JSON 对象
78
+ 4. 单个 slide JSON 对象(包含 `type`)
79
+
80
+ 支持的 slide 类型:
81
+
82
+ - `cover`
83
+ - `contents`
84
+ - `transition`
85
+ - `content`
86
+ - `end`
87
+
88
+ 兼容的历史别名:
89
+
90
+ - `agenda` -> `contents`
91
+ - `section` -> `transition`
92
+ - `ending` -> `end`
93
+
94
+ NDJSON 示例:
95
+
96
+ ```json
97
+ {"type":"cover","data":{"title":"Title","text":"Subtitle"}}
98
+ {"type":"contents","data":{"items":["Part A","Part B"]}}
99
+ {"type":"transition","data":{"title":"Part A","text":"Section intro"}}
100
+ {"type":"content","data":{"title":"Topic","items":[{"title":"Point","text":"Detail"}]}}
101
+ {"type":"end"}
102
+ ```
103
+
104
+ ## 主题输入
105
+
106
+ `applyCustomTheme` 接收 `PptxCustomThemeInput`:
107
+
108
+ - `themeColors: string[]`(最多使用前 6 个)
109
+ - `fontColor: string`
110
+ - `backgroundColor?: string`
111
+ - `backgroundImage?: { src, scope, width?, height? }`
112
+ - `logoImage?: { src, scope, position, width?, height? }`
113
+ - `clearBackgroundImage?: boolean`
114
+ - `clearLogoImage?: boolean`
115
+
116
+ `scope` 可选键:
117
+ `cover | contents | transition | content | end`
118
+
119
+ ## 行为说明
120
+
121
+ - `applyCustomContent` 与 `applyCustomTheme` 都会在返回前经过
122
+ `json2pptx-schema` 的解析与规范化。
123
+ - `applyCustomContent` 按 `type` 选模板页;对 `contents/content` 会优先选择
124
+ 与输入条目数容量最接近的布局。
125
+ - `applyCustomContent` 会规范化 logo 元素(`imageType: "logo"`):
126
+ 保持在顶部边距内,并移除 logo 裁剪配置。
127
+ - `applyCustomTheme` 会将背景图以元素形式写入(`imageType: "background"`),
128
+ 并将 logo 以 `imageType: "logo"` 写入。
129
+ - 当同时提供 `backgroundImage` 与 `backgroundColor` 时,会在目标页应用
130
+ 50% 透明度的背景色叠加效果。
131
+ - 当前行为下,`clearBackgroundImage` 也会同时清除 logo 图片。
package/dist/index.js CHANGED
@@ -453,6 +453,187 @@ function findMappingForColor(value, mappings) {
453
453
  );
454
454
  }
455
455
 
456
+ // src/custom-theme/media.ts
457
+ var FALLBACK_SLIDE_WIDTH2 = 1e3;
458
+ var FALLBACK_SLIDE_HEIGHT2 = 562.5;
459
+ var LOGO_MARGIN_X2 = 24;
460
+ var LOGO_MARGIN_Y2 = 18;
461
+ var LOGO_MAX_WIDTH_RATIO2 = 0.34;
462
+ var LOGO_MAX_HEIGHT_RATIO2 = 0.16;
463
+ var DEFAULT_LOGO_ASPECT_RATIO = 3.2;
464
+ function mapSlideTypeToScopeKey(type) {
465
+ const normalized = type?.trim().toLowerCase();
466
+ if (!normalized) return null;
467
+ if (normalized === "cover") return "cover";
468
+ if (normalized === "contents" || normalized === "agenda") return "contents";
469
+ if (normalized === "transition" || normalized === "section") return "transition";
470
+ if (normalized === "content") return "content";
471
+ if (normalized === "end" || normalized === "ending") return "end";
472
+ return null;
473
+ }
474
+ function isSlideInScope(slide, scope) {
475
+ const key = mapSlideTypeToScopeKey(slide.type);
476
+ if (!key) return false;
477
+ return Boolean(scope[key]);
478
+ }
479
+ function createElementId(prefix, slideIndex) {
480
+ const random = Math.random().toString(36).slice(2, 10);
481
+ return `${prefix}-${slideIndex}-${random}`;
482
+ }
483
+ function toHalfOpacityColor(value) {
484
+ const rgb = parseColorToRgb(value);
485
+ if (!rgb) return value;
486
+ return `rgba(${rgb.r},${rgb.g},${rgb.b},0.5)`;
487
+ }
488
+ function buildBackgroundElement(input) {
489
+ const { src, slideWidth, slideHeight, slideIndex } = input;
490
+ return {
491
+ type: "image",
492
+ id: createElementId("background", slideIndex),
493
+ src,
494
+ width: slideWidth,
495
+ height: slideHeight,
496
+ left: 0,
497
+ top: 0,
498
+ fixedRatio: true,
499
+ rotate: 0,
500
+ imageType: "background",
501
+ filters: {
502
+ opacity: "100%"
503
+ }
504
+ };
505
+ }
506
+ function resolveLogoAspectRatio(width, height) {
507
+ if (typeof width === "number" && typeof height === "number" && width > 0 && height > 0) {
508
+ return width / height;
509
+ }
510
+ return DEFAULT_LOGO_ASPECT_RATIO;
511
+ }
512
+ function resolveLogoSize(input) {
513
+ const {
514
+ slideWidth,
515
+ slideHeight,
516
+ logoWidth,
517
+ logoHeight
518
+ } = input;
519
+ const aspectRatio = resolveLogoAspectRatio(logoWidth, logoHeight);
520
+ const maxWidth = slideWidth * LOGO_MAX_WIDTH_RATIO2;
521
+ const maxHeight = slideHeight * LOGO_MAX_HEIGHT_RATIO2;
522
+ let width = maxWidth;
523
+ let height = width / aspectRatio;
524
+ if (height > maxHeight) {
525
+ height = maxHeight;
526
+ width = height * aspectRatio;
527
+ }
528
+ return { width, height };
529
+ }
530
+ function buildLogoElement(input) {
531
+ const { src, slideWidth, slideIndex, position } = input;
532
+ const { width, height } = resolveLogoSize(input);
533
+ const left = position === "left" ? LOGO_MARGIN_X2 : Math.max(LOGO_MARGIN_X2, slideWidth - width - LOGO_MARGIN_X2);
534
+ return {
535
+ type: "image",
536
+ id: createElementId("logo", slideIndex),
537
+ src,
538
+ width,
539
+ height,
540
+ left,
541
+ top: LOGO_MARGIN_Y2,
542
+ fixedRatio: true,
543
+ rotate: 0,
544
+ imageType: "logo"
545
+ };
546
+ }
547
+ function replaceScopedMedia(deck, input) {
548
+ if (!deck.slides?.length) return deck;
549
+ const slideWidth = deck.width ?? FALLBACK_SLIDE_WIDTH2;
550
+ const slideHeight = deck.height ?? FALLBACK_SLIDE_HEIGHT2;
551
+ const backgroundInput = input.backgroundImage;
552
+ const logoInput = input.logoImage;
553
+ const shouldClearBackground = Boolean(input.clearBackgroundImage);
554
+ const shouldClearLogo = Boolean(input.clearLogoImage || shouldClearBackground);
555
+ if (!backgroundInput?.src && !logoInput?.src && !shouldClearBackground && !shouldClearLogo) {
556
+ return deck;
557
+ }
558
+ const slides = deck.slides.map((slide, slideIndex) => {
559
+ let nextElements = slide.elements ? [...slide.elements] : [];
560
+ let nextBackground = slide.background;
561
+ let changed = false;
562
+ if (shouldClearBackground) {
563
+ const before = nextElements.length;
564
+ nextElements = nextElements.filter(
565
+ (element) => !(element.type === "image" && element.imageType === "background")
566
+ );
567
+ if (nextElements.length !== before) {
568
+ changed = true;
569
+ if (input.backgroundColor) {
570
+ nextBackground = {
571
+ ...nextBackground ?? {},
572
+ type: "solid",
573
+ color: input.backgroundColor
574
+ };
575
+ }
576
+ }
577
+ }
578
+ if (shouldClearLogo) {
579
+ const before = nextElements.length;
580
+ nextElements = nextElements.filter(
581
+ (element) => !(element.type === "image" && element.imageType === "logo")
582
+ );
583
+ if (nextElements.length !== before) {
584
+ changed = true;
585
+ }
586
+ }
587
+ if (backgroundInput?.src && isSlideInScope(slide, backgroundInput.scope)) {
588
+ const withoutBackground = nextElements.filter(
589
+ (element) => !(element.type === "image" && element.imageType === "background")
590
+ );
591
+ nextElements = [
592
+ buildBackgroundElement({
593
+ src: backgroundInput.src,
594
+ slideWidth,
595
+ slideHeight,
596
+ slideIndex
597
+ }),
598
+ ...withoutBackground
599
+ ];
600
+ changed = true;
601
+ if (input.backgroundColor) {
602
+ nextBackground = {
603
+ ...nextBackground ?? {},
604
+ type: "solid",
605
+ color: toHalfOpacityColor(input.backgroundColor)
606
+ };
607
+ }
608
+ }
609
+ if (logoInput?.src && isSlideInScope(slide, logoInput.scope)) {
610
+ const withoutLogo = nextElements.filter(
611
+ (element) => !(element.type === "image" && element.imageType === "logo")
612
+ );
613
+ nextElements = [
614
+ ...withoutLogo,
615
+ buildLogoElement({
616
+ src: logoInput.src,
617
+ slideWidth,
618
+ slideHeight,
619
+ slideIndex,
620
+ position: logoInput.position,
621
+ logoWidth: logoInput.width,
622
+ logoHeight: logoInput.height
623
+ })
624
+ ];
625
+ changed = true;
626
+ }
627
+ if (!changed && nextBackground === slide.background) return slide;
628
+ return {
629
+ ...slide,
630
+ ...changed ? { elements: nextElements } : {},
631
+ ...nextBackground ? { background: nextBackground } : {}
632
+ };
633
+ });
634
+ return { ...deck, slides };
635
+ }
636
+
456
637
  // src/custom-theme/mappings.ts
457
638
  function buildColorMappings(previous, next) {
458
639
  const mappings = [];
@@ -631,173 +812,6 @@ function applyTheme2Json(deck, update) {
631
812
  }
632
813
 
633
814
  // src/custom-theme/index.ts
634
- var FALLBACK_SLIDE_WIDTH2 = 1e3;
635
- var FALLBACK_SLIDE_HEIGHT2 = 562.5;
636
- var LOGO_MARGIN_X2 = 24;
637
- var LOGO_MARGIN_Y2 = 18;
638
- var LOGO_MAX_WIDTH_RATIO2 = 0.34;
639
- var LOGO_MAX_HEIGHT_RATIO2 = 0.16;
640
- var DEFAULT_LOGO_ASPECT_RATIO = 3.2;
641
- function mapSlideTypeToScopeKey(type) {
642
- const normalized = type?.trim().toLowerCase();
643
- if (!normalized) return null;
644
- if (normalized === "cover") return "cover";
645
- if (normalized === "contents" || normalized === "agenda") return "contents";
646
- if (normalized === "transition" || normalized === "section") return "transition";
647
- if (normalized === "content") return "content";
648
- if (normalized === "end" || normalized === "ending") return "end";
649
- return null;
650
- }
651
- function isSlideInScope(slide, scope) {
652
- const key = mapSlideTypeToScopeKey(slide.type);
653
- if (!key) return false;
654
- return Boolean(scope[key]);
655
- }
656
- function createElementId(prefix, slideIndex) {
657
- const random = Math.random().toString(36).slice(2, 10);
658
- return `${prefix}-${slideIndex}-${random}`;
659
- }
660
- function toHalfOpacityColor(value) {
661
- const rgb = parseColorToRgb(value);
662
- if (!rgb) return value;
663
- return `rgba(${rgb.r},${rgb.g},${rgb.b},0.5)`;
664
- }
665
- function buildBackgroundElement(src, slideWidth, slideHeight, slideIndex) {
666
- return {
667
- type: "image",
668
- id: createElementId("background", slideIndex),
669
- src,
670
- width: slideWidth,
671
- height: slideHeight,
672
- left: 0,
673
- top: 0,
674
- fixedRatio: true,
675
- rotate: 0,
676
- imageType: "background",
677
- filters: {
678
- opacity: "100%"
679
- }
680
- };
681
- }
682
- function buildLogoElement(src, slideWidth, slideHeight, position, slideIndex, logoWidth, logoHeight) {
683
- const aspectRatio = resolveLogoAspectRatio(logoWidth, logoHeight);
684
- const maxWidth = slideWidth * LOGO_MAX_WIDTH_RATIO2;
685
- const maxHeight = slideHeight * LOGO_MAX_HEIGHT_RATIO2;
686
- let width = maxWidth;
687
- let height = width / aspectRatio;
688
- if (height > maxHeight) {
689
- height = maxHeight;
690
- width = height * aspectRatio;
691
- }
692
- const left = position === "left" ? LOGO_MARGIN_X2 : Math.max(LOGO_MARGIN_X2, slideWidth - width - LOGO_MARGIN_X2);
693
- return {
694
- type: "image",
695
- id: createElementId("logo", slideIndex),
696
- src,
697
- width,
698
- height,
699
- left,
700
- top: LOGO_MARGIN_Y2,
701
- fixedRatio: true,
702
- rotate: 0,
703
- imageType: "logo"
704
- };
705
- }
706
- function resolveLogoAspectRatio(width, height) {
707
- if (typeof width === "number" && typeof height === "number" && width > 0 && height > 0) {
708
- return width / height;
709
- }
710
- return DEFAULT_LOGO_ASPECT_RATIO;
711
- }
712
- function replaceScopedMedia(deck, input) {
713
- if (!deck.slides?.length) return deck;
714
- const slideWidth = deck.width ?? FALLBACK_SLIDE_WIDTH2;
715
- const slideHeight = deck.height ?? FALLBACK_SLIDE_HEIGHT2;
716
- const backgroundInput = input.backgroundImage;
717
- const logoInput = input.logoImage;
718
- const shouldClearBackground = Boolean(input.clearBackgroundImage);
719
- const shouldClearLogo = Boolean(input.clearLogoImage || shouldClearBackground);
720
- if (!backgroundInput?.src && !logoInput?.src && !shouldClearBackground && !shouldClearLogo) {
721
- return deck;
722
- }
723
- const slides = deck.slides.map((slide, slideIndex) => {
724
- let nextElements = slide.elements ? [...slide.elements] : [];
725
- let nextBackground = slide.background;
726
- let changed = false;
727
- if (shouldClearBackground) {
728
- const before = nextElements.length;
729
- nextElements = nextElements.filter(
730
- (element) => !(element.type === "image" && element.imageType === "background")
731
- );
732
- if (nextElements.length !== before) {
733
- changed = true;
734
- if (input.backgroundColor) {
735
- nextBackground = {
736
- ...nextBackground ?? {},
737
- type: "solid",
738
- color: input.backgroundColor
739
- };
740
- }
741
- }
742
- }
743
- if (shouldClearLogo) {
744
- const before = nextElements.length;
745
- nextElements = nextElements.filter(
746
- (element) => !(element.type === "image" && element.imageType === "logo")
747
- );
748
- if (nextElements.length !== before) {
749
- changed = true;
750
- }
751
- }
752
- if (backgroundInput?.src && isSlideInScope(slide, backgroundInput.scope)) {
753
- const withoutBackground = nextElements.filter(
754
- (element) => !(element.type === "image" && element.imageType === "background")
755
- );
756
- nextElements = [
757
- buildBackgroundElement(
758
- backgroundInput.src,
759
- slideWidth,
760
- slideHeight,
761
- slideIndex
762
- ),
763
- ...withoutBackground
764
- ];
765
- changed = true;
766
- if (input.backgroundColor) {
767
- nextBackground = {
768
- ...nextBackground ?? {},
769
- type: "solid",
770
- color: toHalfOpacityColor(input.backgroundColor)
771
- };
772
- }
773
- }
774
- if (logoInput?.src && isSlideInScope(slide, logoInput.scope)) {
775
- const withoutLogo = nextElements.filter(
776
- (element) => !(element.type === "image" && element.imageType === "logo")
777
- );
778
- nextElements = [
779
- ...withoutLogo,
780
- buildLogoElement(
781
- logoInput.src,
782
- slideWidth,
783
- slideHeight,
784
- logoInput.position,
785
- slideIndex,
786
- logoInput.width,
787
- logoInput.height
788
- )
789
- ];
790
- changed = true;
791
- }
792
- if (!changed && nextBackground === slide.background) return slide;
793
- return {
794
- ...slide,
795
- ...changed ? { elements: nextElements } : {},
796
- ...nextBackground ? { background: nextBackground } : {}
797
- };
798
- });
799
- return { ...deck, slides };
800
- }
801
815
  function applyCustomTheme(deck, input) {
802
816
  const normalizedDeck = (0, import_json2pptx_schema2.parseDocument)(deck);
803
817
  const withColors = applyTheme2Json(normalizedDeck, input);