canva-pptx 1.0.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.
Files changed (87) hide show
  1. package/README.md +1147 -0
  2. package/package.json +63 -0
  3. package/src/AdvancedComponents/accordionList.js +39 -0
  4. package/src/AdvancedComponents/calendarGrid.js +50 -0
  5. package/src/AdvancedComponents/certificateFrame.js +35 -0
  6. package/src/AdvancedComponents/deviceMockup.js +59 -0
  7. package/src/AdvancedComponents/featureGrid.js +70 -0
  8. package/src/AdvancedComponents/funnelDiagram.js +49 -0
  9. package/src/AdvancedComponents/ganttChart.js +35 -0
  10. package/src/AdvancedComponents/invoiceTable.js +32 -0
  11. package/src/AdvancedComponents/matrixGrid.js +43 -0
  12. package/src/AdvancedComponents/mindMap.js +43 -0
  13. package/src/AdvancedComponents/orgChart.js +76 -0
  14. package/src/AdvancedComponents/personaCard.js +44 -0
  15. package/src/AdvancedComponents/processCycle.js +47 -0
  16. package/src/AdvancedComponents/pyramidHierarchy.js +38 -0
  17. package/src/AdvancedComponents/saasFeatureBlock.js +38 -0
  18. package/src/AdvancedComponents/swotMatrix.js +55 -0
  19. package/src/AdvancedComponents/teamMemberProfile.js +75 -0
  20. package/src/AdvancedComponents/trafficLight.js +35 -0
  21. package/src/AdvancedComponents/vennDiagram.js +35 -0
  22. package/src/AdvancedComponents/winLossChart.js +43 -0
  23. package/src/components/StatMetric.js +43 -0
  24. package/src/components/alertBox.js +46 -0
  25. package/src/components/avatarGroup.js +56 -0
  26. package/src/components/badge.js +40 -0
  27. package/src/components/breadcrumbNav.js +31 -0
  28. package/src/components/browserWindow.js +86 -0
  29. package/src/components/brushStroke.js +23 -0
  30. package/src/components/callToAction.js +27 -0
  31. package/src/components/card.js +64 -0
  32. package/src/components/chart.js +19 -0
  33. package/src/components/codeBlock.js +40 -0
  34. package/src/components/codeDiff.js +51 -0
  35. package/src/components/comparisonTable.js +43 -0
  36. package/src/components/cornerAccent.js +32 -0
  37. package/src/components/dotPattern.js +30 -0
  38. package/src/components/geometricConfetti.js +34 -0
  39. package/src/components/gradientMesh.js +32 -0
  40. package/src/components/iconList.js +43 -0
  41. package/src/components/image.js +11 -0
  42. package/src/components/kanbanColumn.js +38 -0
  43. package/src/components/link.js +23 -0
  44. package/src/components/organicBlob.js +45 -0
  45. package/src/components/pricingColumn.js +53 -0
  46. package/src/components/progressBar.js +55 -0
  47. package/src/components/ratingStars.js +25 -0
  48. package/src/components/shape.js +12 -0
  49. package/src/components/slide.js +5 -0
  50. package/src/components/socialBar.js +59 -0
  51. package/src/components/squiggleLine.js +26 -0
  52. package/src/components/stepProcess.js +39 -0
  53. package/src/components/table.js +23 -0
  54. package/src/components/tagCloud.js +39 -0
  55. package/src/components/testimonialCard.js +54 -0
  56. package/src/components/text.js +14 -0
  57. package/src/components/theme.js +7 -0
  58. package/src/components/timeline.js +73 -0
  59. package/src/components/waveDecoration.js +35 -0
  60. package/src/core/PPTManager.js +17 -0
  61. package/src/index.js +225 -0
  62. package/src/layout/bento.js +47 -0
  63. package/src/layout/checkerboard.js +36 -0
  64. package/src/layout/filmStrip.js +29 -0
  65. package/src/layout/gallery.js +50 -0
  66. package/src/layout/grid.js +37 -0
  67. package/src/layout/hero.js +30 -0
  68. package/src/layout/magazine.js +39 -0
  69. package/src/layout/radial.js +34 -0
  70. package/src/layout/sidebar.js +26 -0
  71. package/src/layout/splitScreen.js +36 -0
  72. package/src/layout/zPattern.js +29 -0
  73. package/src/system/animation.js +36 -0
  74. package/src/system/contrastChecker.js +35 -0
  75. package/src/system/dataAdapter.js +36 -0
  76. package/src/system/layoutDebugger.js +40 -0
  77. package/src/system/markdownEngine.js +45 -0
  78. package/src/system/masterOverlay.js +50 -0
  79. package/src/system/sectionDivider.js +41 -0
  80. package/src/system/smartIcon.js +33 -0
  81. package/src/system/smartText.js +44 -0
  82. package/src/system/speakerNotes.js +12 -0
  83. package/src/system/syntaxHighlighter.js +71 -0
  84. package/src/system/themeGenerator.js +25 -0
  85. package/src/system/tocGenerator.js +68 -0
  86. package/src/system/watermark.js +26 -0
  87. package/src/themes/index.js +93 -0
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Returns zones for alternating text/image rows.
3
+ * Row 1: Text Left, Image Right
4
+ * Row 2: Image Left, Text Right
5
+ */
6
+ function getCheckerboardLayout(rows = 2, options = {}) {
7
+ const {
8
+ x = 0.5, y = 0.5, w = 9, h = 4.63,
9
+ gap = 0.5
10
+ } = options;
11
+
12
+ const rowHeight = (h - (rows - 1) * gap) / rows;
13
+ const colWidth = (w - gap) / 2;
14
+ const zones = [];
15
+
16
+ for (let i = 0; i < rows; i++) {
17
+ const isEven = i % 2 === 0;
18
+ const curY = y + i * (rowHeight + gap);
19
+
20
+ if (isEven) {
21
+ zones.push({
22
+ text: { x: x, y: curY, w: colWidth, h: rowHeight },
23
+ media: { x: x + colWidth + gap, y: curY, w: colWidth, h: rowHeight }
24
+ });
25
+ } else {
26
+ zones.push({
27
+ media: { x: x, y: curY, w: colWidth, h: rowHeight },
28
+ text: { x: x + colWidth + gap, y: curY, w: colWidth, h: rowHeight }
29
+ });
30
+ }
31
+ }
32
+
33
+ return zones;
34
+ }
35
+
36
+ module.exports = { getCheckerboardLayout };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Returns a horizontal strip of items, potentially overflowing the slide width
3
+ * (though in PPT, we clamp or fit them).
4
+ */
5
+ function getFilmStripLayout(count = 5, options = {}) {
6
+ const {
7
+ x = 0, y = 2, w = 10, h = 1.5,
8
+ gap = 0.2
9
+ } = options;
10
+
11
+ // Calculate item width to fit ALL in the specific width
12
+ const totalGap = (count - 1) * gap;
13
+ const itemW = (w - (x * 2) - totalGap) / count;
14
+
15
+ const positions = [];
16
+
17
+ for (let i = 0; i < count; i++) {
18
+ positions.push({
19
+ x: x + i * (itemW + gap),
20
+ y: y,
21
+ w: itemW,
22
+ h: h
23
+ });
24
+ }
25
+
26
+ return positions;
27
+ }
28
+
29
+ module.exports = { getFilmStripLayout };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Returns an array of coordinate objects for N items.
3
+ * Automatically calculates columns based on item count if not specified.
4
+ */
5
+ function getGalleryLayout(count = 6, options = {}) {
6
+ const {
7
+ x = 0.5,
8
+ y = 0.5,
9
+ w = 9,
10
+ h = 4.63,
11
+ gap = 0.2,
12
+ cols = undefined // if undefined, auto-calculate
13
+ } = options;
14
+
15
+ // Auto-determine columns if not provided
16
+ // 1-3 items = 1 row (count cols)
17
+ // 4 items = 2 cols
18
+ // 5-6 items = 3 cols
19
+ // 7+ items = 4 cols
20
+ let calculatedCols = cols;
21
+ if (!calculatedCols) {
22
+ if (count <= 3) calculatedCols = count;
23
+ else if (count === 4) calculatedCols = 2;
24
+ else if (count <= 6) calculatedCols = 3;
25
+ else calculatedCols = 4;
26
+ }
27
+
28
+ const rows = Math.ceil(count / calculatedCols);
29
+
30
+ const itemW = (w - (calculatedCols - 1) * gap) / calculatedCols;
31
+ const itemH = (h - (rows - 1) * gap) / rows;
32
+
33
+ const positions = [];
34
+
35
+ for (let i = 0; i < count; i++) {
36
+ const colIndex = i % calculatedCols;
37
+ const rowIndex = Math.floor(i / calculatedCols);
38
+
39
+ positions.push({
40
+ x: x + colIndex * (itemW + gap),
41
+ y: y + rowIndex * (itemH + gap),
42
+ w: itemW,
43
+ h: itemH
44
+ });
45
+ }
46
+
47
+ return positions;
48
+ }
49
+
50
+ module.exports = { getGalleryLayout };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Create a grid system inside a container
3
+ * Container can be full slide or a subsection
4
+ */
5
+ function createGrid(container, options = {}) {
6
+ const {
7
+ x = 0,
8
+ y = 0,
9
+ w = 10,
10
+ h = 5.63, // default slide height
11
+ cols = 2,
12
+ rows = 1,
13
+ gap = 0.2,
14
+ } = options;
15
+
16
+ const colWidth = (w - gap * (cols - 1)) / cols;
17
+ const rowHeight = (h - gap * (rows - 1)) / rows;
18
+
19
+ function getCell(col, row) {
20
+ return {
21
+ x: x + col * (colWidth + gap),
22
+ y: y + row * (rowHeight + gap),
23
+ w: colWidth,
24
+ h: rowHeight,
25
+ };
26
+ }
27
+
28
+ return {
29
+ getCell,
30
+ colWidth,
31
+ rowHeight,
32
+ cols,
33
+ rows,
34
+ };
35
+ }
36
+
37
+ module.exports = { createGrid };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Returns zones for a Title, Subtitle, and Background Image.
3
+ * @param {Object} options - { align: 'center'|'left' }
4
+ */
5
+ function getHeroLayout(options = {}) {
6
+ const {
7
+ align = "center",
8
+ w = 10,
9
+ h = 5.63,
10
+ padding = 1
11
+ } = options;
12
+
13
+ // Background is always full slide
14
+ const background = { x: 0, y: 0, w: w, h: h };
15
+
16
+ // Content zones based on alignment
17
+ let title, subtitle;
18
+
19
+ if (align === "center") {
20
+ title = { x: 0, y: h * 0.4, w: w, h: 1 };
21
+ subtitle = { x: 0, y: h * 0.4 + 1, w: w, h: 1 };
22
+ } else if (align === "left") {
23
+ title = { x: padding, y: h * 0.4, w: w - padding * 2, h: 1 };
24
+ subtitle = { x: padding, y: h * 0.4 + 1, w: w - padding * 2, h: 1 };
25
+ }
26
+
27
+ return { background, title, subtitle };
28
+ }
29
+
30
+ module.exports = { getHeroLayout };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Returns a magazine-style layout: One main focus item, multiple side items.
3
+ */
4
+ function getMagazineLayout(sideItemCount = 3, options = {}) {
5
+ const {
6
+ x = 0.5, y = 0.5, w = 9, h = 4.63,
7
+ gap = 0.2,
8
+ focusPosition = "left" // 'left' or 'right'
9
+ } = options;
10
+
11
+ // Focus takes up 60% width
12
+ const focusW = w * 0.6 - gap;
13
+ const sideW = w * 0.4;
14
+
15
+ const sideItemH = (h - (sideItemCount - 1) * gap) / sideItemCount;
16
+
17
+ const focusZone = {
18
+ x: focusPosition === "left" ? x : x + sideW + gap,
19
+ y: y,
20
+ w: focusW,
21
+ h: h
22
+ };
23
+
24
+ const sideZones = [];
25
+ const sideX = focusPosition === "left" ? x + focusW + gap : x;
26
+
27
+ for (let i = 0; i < sideItemCount; i++) {
28
+ sideZones.push({
29
+ x: sideX,
30
+ y: y + i * (sideItemH + gap),
31
+ w: sideW,
32
+ h: sideItemH
33
+ });
34
+ }
35
+
36
+ return { focus: focusZone, side: sideZones };
37
+ }
38
+
39
+ module.exports = { getMagazineLayout };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Returns coordinates for N items arranged in a circle.
3
+ * @param {number} count - Number of items
4
+ */
5
+ function getRadialLayout(count = 5, options = {}) {
6
+ const {
7
+ x = 5, y = 2.8, // Center of slide
8
+ radius = 2,
9
+ itemSize = 1
10
+ } = options;
11
+
12
+ const positions = [];
13
+ const angleStep = (2 * Math.PI) / count;
14
+
15
+ for (let i = 0; i < count; i++) {
16
+ // Start from top (-PI/2)
17
+ const angle = i * angleStep - Math.PI / 2;
18
+
19
+ const curX = x + radius * Math.cos(angle) - (itemSize / 2);
20
+ const curY = y + radius * Math.sin(angle) - (itemSize / 2);
21
+
22
+ positions.push({
23
+ x: curX,
24
+ y: curY,
25
+ w: itemSize,
26
+ h: itemSize,
27
+ angle: (angle * 180) / Math.PI // Return angle in degrees for rotation if needed
28
+ });
29
+ }
30
+
31
+ return positions;
32
+ }
33
+
34
+ module.exports = { getRadialLayout };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Returns coordinates for a sidebar layout.
3
+ * @param {Object} options - { position: 'left'|'right', width: 2.5 }
4
+ */
5
+ function getSidebarLayout(options = {}) {
6
+ const {
7
+ position = "left",
8
+ width = 2.5,
9
+ w = 10,
10
+ h = 5.63
11
+ } = options;
12
+
13
+ if (position === "left") {
14
+ return {
15
+ sidebar: { x: 0, y: 0, w: width, h: h },
16
+ main: { x: width, y: 0, w: w - width, h: h }
17
+ };
18
+ } else {
19
+ return {
20
+ main: { x: 0, y: 0, w: w - width, h: h },
21
+ sidebar: { x: w - width, y: 0, w: width, h: h }
22
+ };
23
+ }
24
+ }
25
+
26
+ module.exports = { getSidebarLayout };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Returns coordinates for a split screen layout.
3
+ * @param {Object} options - { type: 'vertical'|'horizontal', ratio: 0.5, gutter: 0.2 }
4
+ */
5
+ function getSplitScreen(options = {}) {
6
+ const {
7
+ type = "vertical", // 'vertical' (left/right) or 'horizontal' (top/bottom)
8
+ ratio = 0.5, // Split ratio (0.5 = 50/50)
9
+ gutter = 0, // Space between panels
10
+ w = 10,
11
+ h = 5.63,
12
+ x = 0,
13
+ y = 0
14
+ } = options;
15
+
16
+ if (type === "vertical") {
17
+ const width1 = (w * ratio) - (gutter / 2);
18
+ const width2 = (w * (1 - ratio)) - (gutter / 2);
19
+
20
+ return {
21
+ left: { x: x, y: y, w: width1, h: h },
22
+ right: { x: x + width1 + gutter, y: y, w: width2, h: h }
23
+ };
24
+ } else {
25
+ // Horizontal (Top/Bottom)
26
+ const height1 = (h * ratio) - (gutter / 2);
27
+ const height2 = (h * (1 - ratio)) - (gutter / 2);
28
+
29
+ return {
30
+ top: { x: x, y: y, w: w, h: height1 },
31
+ bottom: { x: x, y: y + height1 + gutter, w: w, h: height2 }
32
+ };
33
+ }
34
+ }
35
+
36
+ module.exports = { getSplitScreen };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Returns 4 coordinate zones in a Z-pattern.
3
+ * Useful for guiding the eye through 4 key points.
4
+ */
5
+ function getZPattern(options = {}) {
6
+ const {
7
+ w = 10,
8
+ h = 5.63,
9
+ margin = 0.5,
10
+ gap = 0.4
11
+ } = options;
12
+
13
+ // Workable area
14
+ const safeW = w - (margin * 2);
15
+ const safeH = h - (margin * 2);
16
+
17
+ // Dimensions for each quadrant
18
+ const itemW = (safeW - gap) / 2;
19
+ const itemH = (safeH - gap) / 2;
20
+
21
+ return {
22
+ topLeft: { x: margin, y: margin, w: itemW, h: itemH },
23
+ topRight: { x: margin + itemW + gap, y: margin, w: itemW, h: itemH },
24
+ bottomLeft: { x: margin, y: margin + itemH + gap, w: itemW, h: itemH },
25
+ bottomRight: { x: margin + itemW + gap, y: margin + itemH + gap, w: itemW, h: itemH }
26
+ };
27
+ }
28
+
29
+ module.exports = { getZPattern };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Animation helper.
3
+ * Usage: animate(slide).addText(...).withEffect('fadeIn')
4
+ */
5
+ class AnimationBuilder {
6
+ constructor(slide) {
7
+ this.slide = slide;
8
+ }
9
+
10
+ addShape(type, options) {
11
+ this.lastShape = this.slide.addShape(type, options);
12
+ return this;
13
+ }
14
+
15
+ addText(text, options) {
16
+ this.lastShape = this.slide.addText(text, options);
17
+ return this;
18
+ }
19
+
20
+ // Chainable animation
21
+ withEffect(effectType = "fadeIn", duration = 1000) {
22
+ // Note: pptxgenjs usually applies animation in the options object of addShape/addText.
23
+ // If the object was already created, modifying it is hard.
24
+ // A true sequencer usually wraps the *creation*.
25
+ // Here we assume the user calls this wrapper to generate the options.
26
+ console.log("Animation metadata attached (Concept only in this wrapper)");
27
+ // In real implementation, we'd merge { animation: { type: effectType } } into options
28
+ return this;
29
+ }
30
+ }
31
+
32
+ function animate(slide) {
33
+ return new AnimationBuilder(slide);
34
+ }
35
+
36
+ module.exports = { animate };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Checks contrast ratio between text and background.
3
+ * Logs a warning if ratio is below 4.5:1 (WCAG AA).
4
+ * * Note: This runs in the console during generation, not on the slide itself.
5
+ */
6
+ function checkContrast(slideName, textColorHex, bgColorHex) {
7
+ // Helper to get luminance
8
+ const getLum = (hex) => {
9
+ const rgb = parseInt(hex.replace("#", ""), 16);
10
+ const r = (rgb >> 16) & 0xff;
11
+ const g = (rgb >> 8) & 0xff;
12
+ const b = (rgb >> 0) & 0xff;
13
+
14
+ // WCAG formula
15
+ const a = [r, g, b].map(v => {
16
+ v /= 255;
17
+ return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
18
+ });
19
+ return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
20
+ };
21
+
22
+ const lum1 = getLum(textColorHex || "000000");
23
+ const lum2 = getLum(bgColorHex || "FFFFFF");
24
+ const brightest = Math.max(lum1, lum2);
25
+ const darkest = Math.min(lum1, lum2);
26
+ const ratio = (brightest + 0.05) / (darkest + 0.05);
27
+
28
+ if (ratio < 4.5) {
29
+ console.warn(`⚠️ ACCESSIBILITY WARN [${slideName}]: Low contrast ratio (${ratio.toFixed(2)}). Text: #${textColorHex} on Bg: #${bgColorHex}`);
30
+ } else {
31
+ // console.log(`✅ Contrast OK: ${ratio.toFixed(2)}`);
32
+ }
33
+ }
34
+
35
+ module.exports = { checkContrast };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Converts array of objects to Table rows.
3
+ * Input: [{ id: 1, name: 'A'}, { id: 2, name: 'B' }]
4
+ * Output: [['id', 'name'], [1, 'A'], [2, 'B']]
5
+ */
6
+ function jsonToTableData(jsonArray) {
7
+ if (!jsonArray || jsonArray.length === 0) return [];
8
+
9
+ const headers = Object.keys(jsonArray[0]);
10
+ const rows = jsonArray.map(obj => Object.values(obj));
11
+
12
+ return [headers, ...rows];
13
+ }
14
+
15
+ /**
16
+ * Converts CSV string to Table rows.
17
+ */
18
+ function csvToTableData(csvString) {
19
+ const rows = csvString.trim().split("\n");
20
+ return rows.map(r => r.split(",").map(c => c.trim()));
21
+ }
22
+
23
+ /**
24
+ * Converts JSON to Chart Data format for pptxgenjs.
25
+ * Input: [{ label: "Q1", value: 10 }, { label: "Q2", value: 20 }]
26
+ * Output: [{ name: "Series1", labels: ["Q1", "Q2"], values: [10, 20] }]
27
+ */
28
+ function jsonToChartData(jsonArray, labelKey, valueKey, seriesName = "Series 1") {
29
+ return [{
30
+ name: seriesName,
31
+ labels: jsonArray.map(i => i[labelKey]),
32
+ values: jsonArray.map(i => i[valueKey])
33
+ }];
34
+ }
35
+
36
+ module.exports = { jsonToTableData, csvToTableData, jsonToChartData };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Visual Debugger
3
+ * Draws a 1-inch grid and safe-zone borders to help align content.
4
+ */
5
+ function debugSlide(slide, options = {}) {
6
+ const { color = "FF0000", showGrid = true, showSafeZone = true } = options;
7
+
8
+ // 1. Safe Zone Border (Margin)
9
+ if (showSafeZone) {
10
+ slide.addShape("rect", {
11
+ x: 0.5, y: 0.5, w: 9, h: 4.63,
12
+ fill: { color: undefined },
13
+ line: { color: color, width: 1, dashType: "dash" }
14
+ });
15
+ }
16
+
17
+ // 2. Grid Lines (Every 1 inch)
18
+ if (showGrid) {
19
+ // Vertical Lines (0 to 10)
20
+ for (let i = 1; i < 10; i++) {
21
+ slide.addShape("line", {
22
+ x: i, y: 0, w: 0, h: 5.63,
23
+ line: { color: "E0E0E0", width: 1 } // Light grey
24
+ });
25
+ }
26
+ // Horizontal Lines (0 to 5.63)
27
+ for (let i = 1; i < 6; i++) {
28
+ slide.addShape("line", {
29
+ x: 0, y: i, w: 10, h: 0,
30
+ line: { color: "E0E0E0", width: 1 }
31
+ });
32
+ }
33
+ }
34
+
35
+ // 3. Center Crosshair
36
+ slide.addShape("line", { x: 5, y: 2.7, w: 0, h: 0.2, line: { color: color } }); // V
37
+ slide.addShape("line", { x: 4.9, y: 2.815, w: 0.2, h: 0, line: { color: color } }); // H
38
+ }
39
+
40
+ module.exports = { debugSlide };
@@ -0,0 +1,45 @@
1
+ const { addSlide } = require("../components/slide");
2
+ const { addText } = require("../components/text");
3
+
4
+ /**
5
+ * Parses markdown string and generates slides.
6
+ * Separator: "---" creates a new slide.
7
+ * # Title
8
+ * ## Subtitle
9
+ * - Bullet point
10
+ */
11
+ function renderMarkdown(manager, markdown) {
12
+ const pptx = manager.getPptx();
13
+ const slides = markdown.split(/\n---\n/); // Split by horizontal rule
14
+
15
+ slides.forEach(content => {
16
+ const slide = addSlide(pptx);
17
+ const lines = content.trim().split("\n");
18
+ let yPos = 1.0;
19
+
20
+ lines.forEach(line => {
21
+ line = line.trim();
22
+ if (!line) return;
23
+
24
+ if (line.startsWith("# ")) {
25
+ // H1 Title
26
+ addText(slide, line.replace("# ", ""), { x: 0.5, y: 0.5, fontSize: 32, bold: true, w: 9 });
27
+ yPos = 1.5;
28
+ } else if (line.startsWith("## ")) {
29
+ // H2 Subtitle
30
+ addText(slide, line.replace("## ", ""), { x: 0.5, y: yPos, fontSize: 20, color: "666666", w: 9 });
31
+ yPos += 0.8;
32
+ } else if (line.startsWith("- ")) {
33
+ // Bullet
34
+ addText(slide, line.replace("- ", ""), { x: 1, y: yPos, fontSize: 16, bullet: true, w: 8, h: 0.5 });
35
+ yPos += 0.6;
36
+ } else {
37
+ // Regular Paragraph
38
+ addText(slide, line, { x: 1, y: yPos, fontSize: 14, w: 8 });
39
+ yPos += 0.6;
40
+ }
41
+ });
42
+ });
43
+ }
44
+
45
+ module.exports = { renderMarkdown };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Applies a layout master function to a slide.
3
+ * Useful for logos, footers, and page numbers.
4
+ */
5
+ function applyMaster(slide, options = {}) {
6
+ const {
7
+ logo,
8
+ footerText,
9
+ showPageNumber = true,
10
+ accentColor = "007AFF"
11
+ } = options;
12
+
13
+ // 1. Logo (Top Right)
14
+ if (logo) {
15
+ slide.addImage({
16
+ path: logo,
17
+ x: 9.0, y: 0.2, w: 0.8, h: 0.8,
18
+ sizing: { type: "contain", w: 0.8, h: 0.8 }
19
+ });
20
+ }
21
+
22
+ // 2. Footer Bar (Bottom)
23
+ slide.addShape("line", {
24
+ x: 0.5, y: 5.2, w: 9, h: 0,
25
+ line: { color: "E0E0E0", width: 1 }
26
+ });
27
+
28
+ // 3. Footer Text (Bottom Left)
29
+ if (footerText) {
30
+ slide.addText(footerText, {
31
+ x: 0.5, y: 5.3, w: 6, h: 0.3,
32
+ fontSize: 10, color: "888888"
33
+ });
34
+ }
35
+
36
+ // 4. Page Number (Bottom Right)
37
+ if (showPageNumber) {
38
+ // FIX: slideNumber is a property, not a function
39
+ slide.slideNumber = {
40
+ x: 9.0,
41
+ y: 5.3,
42
+ w: 0.5,
43
+ h: 0.3,
44
+ fontSize: 10,
45
+ color: accentColor
46
+ };
47
+ }
48
+ }
49
+
50
+ module.exports = { applyMaster };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Creates a distinct section divider slide.
3
+ * Useful for breaking up long presentations.
4
+ */
5
+ function addSectionDivider(manager, title, subtitle = "") {
6
+ const pptx = manager.getPptx();
7
+ const slide = pptx.addSlide();
8
+
9
+ // Dark Background for contrast
10
+ slide.background = { fill: "111111" };
11
+
12
+ // Big Title
13
+ slide.addText(title, {
14
+ x: 1, y: 2, w: 8, h: 1,
15
+ fontSize: 44,
16
+ bold: true,
17
+ color: "FFFFFF",
18
+ align: "center"
19
+ });
20
+
21
+ // Horizontal Line
22
+ slide.addShape("line", {
23
+ x: 3, y: 3.2, w: 4, h: 0,
24
+ line: { color: "007AFF", width: 3 }
25
+ });
26
+
27
+ // Subtitle
28
+ if (subtitle) {
29
+ slide.addText(subtitle, {
30
+ x: 1, y: 3.5, w: 8, h: 1,
31
+ fontSize: 18,
32
+ color: "BBBBBB",
33
+ align: "center",
34
+ italic: true
35
+ });
36
+ }
37
+
38
+ return slide;
39
+ }
40
+
41
+ module.exports = { addSectionDivider };
@@ -0,0 +1,33 @@
1
+ // A small set of standard SVG paths (simulated)
2
+ const ICONS = {
3
+ user: "M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z",
4
+ home: "M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z",
5
+ settings: "M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.68 8.87c-.11.21-.06.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.04.24.24.41.48.41h3.84c.24 0.43-.17.47-.41l.36-2.54c.59-.24 1.13-.57 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.11-.21.06-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z",
6
+ star: "M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"
7
+ };
8
+
9
+ function addSmartIcon(slide, iconName, options = {}) {
10
+ const { x = 1, y = 1, size = 0.5, color = "000000" } = options;
11
+
12
+ // Note: pptxgenjs supports SVG paths via 'path' property in addShape (custom geometry)
13
+ // or via addImage with dataURI. Custom geometry is cleaner if supported,
14
+ // but addImage with SVG XML is more robust across versions.
15
+
16
+ const pathData = ICONS[iconName];
17
+ if (!pathData) {
18
+ console.warn(`Icon ${iconName} not found.`);
19
+ return;
20
+ }
21
+
22
+ // Construct a minimal SVG string
23
+ const svgData = `data:image/svg+xml;base64,${Buffer.from(
24
+ `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#${color}"><path d="${pathData}"/></svg>`
25
+ ).toString("base64")}`;
26
+
27
+ slide.addImage({
28
+ data: svgData,
29
+ x, y, w: size, h: size
30
+ });
31
+ }
32
+
33
+ module.exports = { addSmartIcon };