@jsamuel1/pptxgenjs 4.1.2 → 4.1.4
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/pptxgen.bundle.js +2 -95
- package/dist/pptxgen.bundle.js.map +1 -1
- package/dist/pptxgen.cjs.js +295 -31
- package/dist/pptxgen.es.js +295 -31
- package/dist/pptxgen.min.js +2 -95
- package/dist/pptxgen.min.js.map +1 -1
- package/dist/utils.cjs.js +148 -0
- package/dist/utils.es.js +146 -0
- package/dist/utils.js +148 -0
- package/package.json +25 -26
- package/types/index.d.ts +41 -0
- package/types/utils.d.ts +40 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/* PptxGenJS 4.1.4 @ 2026-06-08T05:10:40.789Z */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* PptxGenJS — Theme Extraction utility (docs/feature-theme-extraction.md)
|
|
6
|
+
*
|
|
7
|
+
* Parses CSS `:root { --var: value; }` custom properties and maps known variable-name
|
|
8
|
+
* patterns to a theme palette (background/accent/text/font + an extended colour set).
|
|
9
|
+
* Pure, dependency-free, regex-based parsing — no DOM and no browser required, so it runs
|
|
10
|
+
* in Node.js. This is an OPTIONAL utility (imported from `@jsamuel1/pptxgenjs/utils`), not
|
|
11
|
+
* part of the main `PptxGenJS` class, keeping the core library focused on OOXML generation.
|
|
12
|
+
*/
|
|
13
|
+
/** Built-in dark preset (matches docs/feature-theme-extraction.md). */
|
|
14
|
+
const DARK_PRESET = {
|
|
15
|
+
bg: '121218',
|
|
16
|
+
bgSecondary: '1A1A24',
|
|
17
|
+
accent: '7C3AED',
|
|
18
|
+
accentSoft: 'A78BFA',
|
|
19
|
+
text: 'E4E4ED',
|
|
20
|
+
textSecondary: '8A8A9A',
|
|
21
|
+
font: 'Inter',
|
|
22
|
+
sky: '38BDF8',
|
|
23
|
+
green: '10B981',
|
|
24
|
+
orange: 'FF9900',
|
|
25
|
+
red: 'EF4444',
|
|
26
|
+
};
|
|
27
|
+
/** Built-in light preset. */
|
|
28
|
+
const LIGHT_PRESET = {
|
|
29
|
+
bg: 'FFFFFF',
|
|
30
|
+
bgSecondary: 'F4F4F7',
|
|
31
|
+
accent: '7C3AED',
|
|
32
|
+
accentSoft: 'A78BFA',
|
|
33
|
+
text: '121218',
|
|
34
|
+
textSecondary: '5A5A6A',
|
|
35
|
+
font: 'Inter',
|
|
36
|
+
sky: '0EA5E9',
|
|
37
|
+
green: '059669',
|
|
38
|
+
orange: 'EA580C',
|
|
39
|
+
red: 'DC2626',
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Exact CSS-variable-name → theme-slot map. Names are matched exactly (NOT by substring) so
|
|
43
|
+
* `--bg` and `--bg-card` resolve to different slots. Mirrors the table in the feature spec.
|
|
44
|
+
*/
|
|
45
|
+
const VAR_TO_SLOT = {
|
|
46
|
+
// bg
|
|
47
|
+
bg: 'bg', 'color-bg': 'bg', background: 'bg', 'bg-deep': 'bg',
|
|
48
|
+
// bgSecondary
|
|
49
|
+
'bg-card': 'bgSecondary', card: 'bgSecondary', 'color-bg-secondary': 'bgSecondary', 'bg-surface': 'bgSecondary',
|
|
50
|
+
// accent
|
|
51
|
+
purple: 'accent', accent: 'accent', 'color-primary': 'accent', primary: 'accent',
|
|
52
|
+
// accentSoft
|
|
53
|
+
'purple-soft': 'accentSoft', 'accent-soft': 'accentSoft', 'color-primary-light': 'accentSoft',
|
|
54
|
+
// text
|
|
55
|
+
white: 'text', text: 'text', 'color-text': 'text', foreground: 'text',
|
|
56
|
+
// textSecondary
|
|
57
|
+
gray: 'textSecondary', muted: 'textSecondary', 'color-text-secondary': 'textSecondary',
|
|
58
|
+
// sky
|
|
59
|
+
sky: 'sky', blue: 'sky', info: 'sky',
|
|
60
|
+
// green
|
|
61
|
+
green: 'green', success: 'green',
|
|
62
|
+
// orange
|
|
63
|
+
orange: 'orange', warning: 'orange',
|
|
64
|
+
// red
|
|
65
|
+
red: 'red', error: 'red', danger: 'red',
|
|
66
|
+
// font
|
|
67
|
+
font: 'font', 'font-family': 'font',
|
|
68
|
+
};
|
|
69
|
+
/** Slots whose value is a colour (vs. a font family) — used to decide value normalisation. */
|
|
70
|
+
const COLOR_SLOTS = new Set(['bg', 'bgSecondary', 'accent', 'accentSoft', 'text', 'textSecondary', 'sky', 'green', 'orange', 'red']);
|
|
71
|
+
/** Normalise a colour value to a 6-digit hex (no `#`). 3-digit hex is expanded; non-hex returned as-is. */
|
|
72
|
+
function normalizeColor(raw) {
|
|
73
|
+
let v = raw.trim().replace(/^#/, '');
|
|
74
|
+
// Expand 3-digit shorthand (#abc -> AABBCC)
|
|
75
|
+
if (/^[0-9a-fA-F]{3}$/.test(v))
|
|
76
|
+
v = v.split('').map(c => c + c).join('');
|
|
77
|
+
// Uppercase 6/8-digit hex for consistency with the rest of the library
|
|
78
|
+
if (/^[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/.test(v))
|
|
79
|
+
return v.toUpperCase();
|
|
80
|
+
// rgb()/hsl()/named colours are returned trimmed but unconverted (documented limitation)
|
|
81
|
+
return v;
|
|
82
|
+
}
|
|
83
|
+
/** Normalise a font-family value: strip surrounding quotes and take the first family. */
|
|
84
|
+
function normalizeFont(raw) {
|
|
85
|
+
const first = raw.split(',')[0].trim();
|
|
86
|
+
return first.replace(/^['"]/, '').replace(/['"]$/, '').trim();
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Extract `--name: value;` custom-property declarations from CSS text.
|
|
90
|
+
* Prefers declarations inside `:root { … }` blocks; if none are found, falls back to scanning
|
|
91
|
+
* the entire string (covers inline/style-block custom props without a `:root` selector).
|
|
92
|
+
* @returns map of bare variable name (no leading `--`) -> value
|
|
93
|
+
*/
|
|
94
|
+
function parseCssVars(css) {
|
|
95
|
+
const out = {};
|
|
96
|
+
const declRegex = /--([\w-]+)\s*:\s*([^;]+);/g;
|
|
97
|
+
const collect = (text) => {
|
|
98
|
+
let m;
|
|
99
|
+
while ((m = declRegex.exec(text)) !== null) {
|
|
100
|
+
out[m[1].trim().toLowerCase()] = m[2].trim();
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
// 1) :root blocks (there can be more than one)
|
|
104
|
+
const rootRegex = /:root\s*\{([^}]*)\}/g;
|
|
105
|
+
let rootMatch;
|
|
106
|
+
let foundRoot = false;
|
|
107
|
+
while ((rootMatch = rootRegex.exec(css)) !== null) {
|
|
108
|
+
foundRoot = true;
|
|
109
|
+
declRegex.lastIndex = 0;
|
|
110
|
+
collect(rootMatch[1]);
|
|
111
|
+
}
|
|
112
|
+
// 2) Fallback: no :root vars — scan the whole CSS for custom-prop declarations
|
|
113
|
+
if (!foundRoot) {
|
|
114
|
+
declRegex.lastIndex = 0;
|
|
115
|
+
collect(css);
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Parse CSS `:root` custom properties into a theme palette, falling back to a preset for any
|
|
121
|
+
* slot not present in the CSS.
|
|
122
|
+
* @param {string} css - CSS text (or any text containing `--name: value;` declarations)
|
|
123
|
+
* @param {ExtractThemeOptions} [options] - presets + which preset to fall back to
|
|
124
|
+
* @returns {ThemePalette} the resolved palette (always complete — preset fills the gaps)
|
|
125
|
+
* @example
|
|
126
|
+
* const theme = extractThemeFromCSS(':root{ --bg:#121218; --purple:#7C3AED; }')
|
|
127
|
+
* // => { bg: '121218', accent: '7C3AED', ... }
|
|
128
|
+
*/
|
|
129
|
+
function extractThemeFromCSS(css, options = {}) {
|
|
130
|
+
const presets = Object.assign({ dark: DARK_PRESET, light: LIGHT_PRESET }, (options.presets || {}));
|
|
131
|
+
const presetName = options.defaultPreset || 'dark';
|
|
132
|
+
const base = presets[presetName] || DARK_PRESET;
|
|
133
|
+
// Start from a complete palette (dark) then layer the chosen preset so the result is always whole
|
|
134
|
+
const theme = Object.assign(Object.assign({}, DARK_PRESET), base);
|
|
135
|
+
if (typeof css === 'string' && css.length > 0) {
|
|
136
|
+
const vars = parseCssVars(css);
|
|
137
|
+
Object.keys(vars).forEach(name => {
|
|
138
|
+
const slot = VAR_TO_SLOT[name];
|
|
139
|
+
if (!slot)
|
|
140
|
+
return;
|
|
141
|
+
const value = vars[name];
|
|
142
|
+
theme[slot] = slot === 'font' || !COLOR_SLOTS.has(slot) ? normalizeFont(value) : normalizeColor(value);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return theme;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
exports.extractThemeFromCSS = extractThemeFromCSS;
|
package/dist/utils.es.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/* PptxGenJS 4.1.4 @ 2026-06-08T05:10:40.789Z */
|
|
2
|
+
/**
|
|
3
|
+
* PptxGenJS — Theme Extraction utility (docs/feature-theme-extraction.md)
|
|
4
|
+
*
|
|
5
|
+
* Parses CSS `:root { --var: value; }` custom properties and maps known variable-name
|
|
6
|
+
* patterns to a theme palette (background/accent/text/font + an extended colour set).
|
|
7
|
+
* Pure, dependency-free, regex-based parsing — no DOM and no browser required, so it runs
|
|
8
|
+
* in Node.js. This is an OPTIONAL utility (imported from `@jsamuel1/pptxgenjs/utils`), not
|
|
9
|
+
* part of the main `PptxGenJS` class, keeping the core library focused on OOXML generation.
|
|
10
|
+
*/
|
|
11
|
+
/** Built-in dark preset (matches docs/feature-theme-extraction.md). */
|
|
12
|
+
const DARK_PRESET = {
|
|
13
|
+
bg: '121218',
|
|
14
|
+
bgSecondary: '1A1A24',
|
|
15
|
+
accent: '7C3AED',
|
|
16
|
+
accentSoft: 'A78BFA',
|
|
17
|
+
text: 'E4E4ED',
|
|
18
|
+
textSecondary: '8A8A9A',
|
|
19
|
+
font: 'Inter',
|
|
20
|
+
sky: '38BDF8',
|
|
21
|
+
green: '10B981',
|
|
22
|
+
orange: 'FF9900',
|
|
23
|
+
red: 'EF4444',
|
|
24
|
+
};
|
|
25
|
+
/** Built-in light preset. */
|
|
26
|
+
const LIGHT_PRESET = {
|
|
27
|
+
bg: 'FFFFFF',
|
|
28
|
+
bgSecondary: 'F4F4F7',
|
|
29
|
+
accent: '7C3AED',
|
|
30
|
+
accentSoft: 'A78BFA',
|
|
31
|
+
text: '121218',
|
|
32
|
+
textSecondary: '5A5A6A',
|
|
33
|
+
font: 'Inter',
|
|
34
|
+
sky: '0EA5E9',
|
|
35
|
+
green: '059669',
|
|
36
|
+
orange: 'EA580C',
|
|
37
|
+
red: 'DC2626',
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Exact CSS-variable-name → theme-slot map. Names are matched exactly (NOT by substring) so
|
|
41
|
+
* `--bg` and `--bg-card` resolve to different slots. Mirrors the table in the feature spec.
|
|
42
|
+
*/
|
|
43
|
+
const VAR_TO_SLOT = {
|
|
44
|
+
// bg
|
|
45
|
+
bg: 'bg', 'color-bg': 'bg', background: 'bg', 'bg-deep': 'bg',
|
|
46
|
+
// bgSecondary
|
|
47
|
+
'bg-card': 'bgSecondary', card: 'bgSecondary', 'color-bg-secondary': 'bgSecondary', 'bg-surface': 'bgSecondary',
|
|
48
|
+
// accent
|
|
49
|
+
purple: 'accent', accent: 'accent', 'color-primary': 'accent', primary: 'accent',
|
|
50
|
+
// accentSoft
|
|
51
|
+
'purple-soft': 'accentSoft', 'accent-soft': 'accentSoft', 'color-primary-light': 'accentSoft',
|
|
52
|
+
// text
|
|
53
|
+
white: 'text', text: 'text', 'color-text': 'text', foreground: 'text',
|
|
54
|
+
// textSecondary
|
|
55
|
+
gray: 'textSecondary', muted: 'textSecondary', 'color-text-secondary': 'textSecondary',
|
|
56
|
+
// sky
|
|
57
|
+
sky: 'sky', blue: 'sky', info: 'sky',
|
|
58
|
+
// green
|
|
59
|
+
green: 'green', success: 'green',
|
|
60
|
+
// orange
|
|
61
|
+
orange: 'orange', warning: 'orange',
|
|
62
|
+
// red
|
|
63
|
+
red: 'red', error: 'red', danger: 'red',
|
|
64
|
+
// font
|
|
65
|
+
font: 'font', 'font-family': 'font',
|
|
66
|
+
};
|
|
67
|
+
/** Slots whose value is a colour (vs. a font family) — used to decide value normalisation. */
|
|
68
|
+
const COLOR_SLOTS = new Set(['bg', 'bgSecondary', 'accent', 'accentSoft', 'text', 'textSecondary', 'sky', 'green', 'orange', 'red']);
|
|
69
|
+
/** Normalise a colour value to a 6-digit hex (no `#`). 3-digit hex is expanded; non-hex returned as-is. */
|
|
70
|
+
function normalizeColor(raw) {
|
|
71
|
+
let v = raw.trim().replace(/^#/, '');
|
|
72
|
+
// Expand 3-digit shorthand (#abc -> AABBCC)
|
|
73
|
+
if (/^[0-9a-fA-F]{3}$/.test(v))
|
|
74
|
+
v = v.split('').map(c => c + c).join('');
|
|
75
|
+
// Uppercase 6/8-digit hex for consistency with the rest of the library
|
|
76
|
+
if (/^[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/.test(v))
|
|
77
|
+
return v.toUpperCase();
|
|
78
|
+
// rgb()/hsl()/named colours are returned trimmed but unconverted (documented limitation)
|
|
79
|
+
return v;
|
|
80
|
+
}
|
|
81
|
+
/** Normalise a font-family value: strip surrounding quotes and take the first family. */
|
|
82
|
+
function normalizeFont(raw) {
|
|
83
|
+
const first = raw.split(',')[0].trim();
|
|
84
|
+
return first.replace(/^['"]/, '').replace(/['"]$/, '').trim();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Extract `--name: value;` custom-property declarations from CSS text.
|
|
88
|
+
* Prefers declarations inside `:root { … }` blocks; if none are found, falls back to scanning
|
|
89
|
+
* the entire string (covers inline/style-block custom props without a `:root` selector).
|
|
90
|
+
* @returns map of bare variable name (no leading `--`) -> value
|
|
91
|
+
*/
|
|
92
|
+
function parseCssVars(css) {
|
|
93
|
+
const out = {};
|
|
94
|
+
const declRegex = /--([\w-]+)\s*:\s*([^;]+);/g;
|
|
95
|
+
const collect = (text) => {
|
|
96
|
+
let m;
|
|
97
|
+
while ((m = declRegex.exec(text)) !== null) {
|
|
98
|
+
out[m[1].trim().toLowerCase()] = m[2].trim();
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
// 1) :root blocks (there can be more than one)
|
|
102
|
+
const rootRegex = /:root\s*\{([^}]*)\}/g;
|
|
103
|
+
let rootMatch;
|
|
104
|
+
let foundRoot = false;
|
|
105
|
+
while ((rootMatch = rootRegex.exec(css)) !== null) {
|
|
106
|
+
foundRoot = true;
|
|
107
|
+
declRegex.lastIndex = 0;
|
|
108
|
+
collect(rootMatch[1]);
|
|
109
|
+
}
|
|
110
|
+
// 2) Fallback: no :root vars — scan the whole CSS for custom-prop declarations
|
|
111
|
+
if (!foundRoot) {
|
|
112
|
+
declRegex.lastIndex = 0;
|
|
113
|
+
collect(css);
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Parse CSS `:root` custom properties into a theme palette, falling back to a preset for any
|
|
119
|
+
* slot not present in the CSS.
|
|
120
|
+
* @param {string} css - CSS text (or any text containing `--name: value;` declarations)
|
|
121
|
+
* @param {ExtractThemeOptions} [options] - presets + which preset to fall back to
|
|
122
|
+
* @returns {ThemePalette} the resolved palette (always complete — preset fills the gaps)
|
|
123
|
+
* @example
|
|
124
|
+
* const theme = extractThemeFromCSS(':root{ --bg:#121218; --purple:#7C3AED; }')
|
|
125
|
+
* // => { bg: '121218', accent: '7C3AED', ... }
|
|
126
|
+
*/
|
|
127
|
+
function extractThemeFromCSS(css, options = {}) {
|
|
128
|
+
const presets = Object.assign({ dark: DARK_PRESET, light: LIGHT_PRESET }, (options.presets || {}));
|
|
129
|
+
const presetName = options.defaultPreset || 'dark';
|
|
130
|
+
const base = presets[presetName] || DARK_PRESET;
|
|
131
|
+
// Start from a complete palette (dark) then layer the chosen preset so the result is always whole
|
|
132
|
+
const theme = Object.assign(Object.assign({}, DARK_PRESET), base);
|
|
133
|
+
if (typeof css === 'string' && css.length > 0) {
|
|
134
|
+
const vars = parseCssVars(css);
|
|
135
|
+
Object.keys(vars).forEach(name => {
|
|
136
|
+
const slot = VAR_TO_SLOT[name];
|
|
137
|
+
if (!slot)
|
|
138
|
+
return;
|
|
139
|
+
const value = vars[name];
|
|
140
|
+
theme[slot] = slot === 'font' || !COLOR_SLOTS.has(slot) ? normalizeFont(value) : normalizeColor(value);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return theme;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export { extractThemeFromCSS };
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/* PptxGenJS 4.1.4 @ 2026-06-08T05:10:40.789Z */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* PptxGenJS — Theme Extraction utility (docs/feature-theme-extraction.md)
|
|
6
|
+
*
|
|
7
|
+
* Parses CSS `:root { --var: value; }` custom properties and maps known variable-name
|
|
8
|
+
* patterns to a theme palette (background/accent/text/font + an extended colour set).
|
|
9
|
+
* Pure, dependency-free, regex-based parsing — no DOM and no browser required, so it runs
|
|
10
|
+
* in Node.js. This is an OPTIONAL utility (imported from `@jsamuel1/pptxgenjs/utils`), not
|
|
11
|
+
* part of the main `PptxGenJS` class, keeping the core library focused on OOXML generation.
|
|
12
|
+
*/
|
|
13
|
+
/** Built-in dark preset (matches docs/feature-theme-extraction.md). */
|
|
14
|
+
const DARK_PRESET = {
|
|
15
|
+
bg: '121218',
|
|
16
|
+
bgSecondary: '1A1A24',
|
|
17
|
+
accent: '7C3AED',
|
|
18
|
+
accentSoft: 'A78BFA',
|
|
19
|
+
text: 'E4E4ED',
|
|
20
|
+
textSecondary: '8A8A9A',
|
|
21
|
+
font: 'Inter',
|
|
22
|
+
sky: '38BDF8',
|
|
23
|
+
green: '10B981',
|
|
24
|
+
orange: 'FF9900',
|
|
25
|
+
red: 'EF4444',
|
|
26
|
+
};
|
|
27
|
+
/** Built-in light preset. */
|
|
28
|
+
const LIGHT_PRESET = {
|
|
29
|
+
bg: 'FFFFFF',
|
|
30
|
+
bgSecondary: 'F4F4F7',
|
|
31
|
+
accent: '7C3AED',
|
|
32
|
+
accentSoft: 'A78BFA',
|
|
33
|
+
text: '121218',
|
|
34
|
+
textSecondary: '5A5A6A',
|
|
35
|
+
font: 'Inter',
|
|
36
|
+
sky: '0EA5E9',
|
|
37
|
+
green: '059669',
|
|
38
|
+
orange: 'EA580C',
|
|
39
|
+
red: 'DC2626',
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Exact CSS-variable-name → theme-slot map. Names are matched exactly (NOT by substring) so
|
|
43
|
+
* `--bg` and `--bg-card` resolve to different slots. Mirrors the table in the feature spec.
|
|
44
|
+
*/
|
|
45
|
+
const VAR_TO_SLOT = {
|
|
46
|
+
// bg
|
|
47
|
+
bg: 'bg', 'color-bg': 'bg', background: 'bg', 'bg-deep': 'bg',
|
|
48
|
+
// bgSecondary
|
|
49
|
+
'bg-card': 'bgSecondary', card: 'bgSecondary', 'color-bg-secondary': 'bgSecondary', 'bg-surface': 'bgSecondary',
|
|
50
|
+
// accent
|
|
51
|
+
purple: 'accent', accent: 'accent', 'color-primary': 'accent', primary: 'accent',
|
|
52
|
+
// accentSoft
|
|
53
|
+
'purple-soft': 'accentSoft', 'accent-soft': 'accentSoft', 'color-primary-light': 'accentSoft',
|
|
54
|
+
// text
|
|
55
|
+
white: 'text', text: 'text', 'color-text': 'text', foreground: 'text',
|
|
56
|
+
// textSecondary
|
|
57
|
+
gray: 'textSecondary', muted: 'textSecondary', 'color-text-secondary': 'textSecondary',
|
|
58
|
+
// sky
|
|
59
|
+
sky: 'sky', blue: 'sky', info: 'sky',
|
|
60
|
+
// green
|
|
61
|
+
green: 'green', success: 'green',
|
|
62
|
+
// orange
|
|
63
|
+
orange: 'orange', warning: 'orange',
|
|
64
|
+
// red
|
|
65
|
+
red: 'red', error: 'red', danger: 'red',
|
|
66
|
+
// font
|
|
67
|
+
font: 'font', 'font-family': 'font',
|
|
68
|
+
};
|
|
69
|
+
/** Slots whose value is a colour (vs. a font family) — used to decide value normalisation. */
|
|
70
|
+
const COLOR_SLOTS = new Set(['bg', 'bgSecondary', 'accent', 'accentSoft', 'text', 'textSecondary', 'sky', 'green', 'orange', 'red']);
|
|
71
|
+
/** Normalise a colour value to a 6-digit hex (no `#`). 3-digit hex is expanded; non-hex returned as-is. */
|
|
72
|
+
function normalizeColor(raw) {
|
|
73
|
+
let v = raw.trim().replace(/^#/, '');
|
|
74
|
+
// Expand 3-digit shorthand (#abc -> AABBCC)
|
|
75
|
+
if (/^[0-9a-fA-F]{3}$/.test(v))
|
|
76
|
+
v = v.split('').map(c => c + c).join('');
|
|
77
|
+
// Uppercase 6/8-digit hex for consistency with the rest of the library
|
|
78
|
+
if (/^[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/.test(v))
|
|
79
|
+
return v.toUpperCase();
|
|
80
|
+
// rgb()/hsl()/named colours are returned trimmed but unconverted (documented limitation)
|
|
81
|
+
return v;
|
|
82
|
+
}
|
|
83
|
+
/** Normalise a font-family value: strip surrounding quotes and take the first family. */
|
|
84
|
+
function normalizeFont(raw) {
|
|
85
|
+
const first = raw.split(',')[0].trim();
|
|
86
|
+
return first.replace(/^['"]/, '').replace(/['"]$/, '').trim();
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Extract `--name: value;` custom-property declarations from CSS text.
|
|
90
|
+
* Prefers declarations inside `:root { … }` blocks; if none are found, falls back to scanning
|
|
91
|
+
* the entire string (covers inline/style-block custom props without a `:root` selector).
|
|
92
|
+
* @returns map of bare variable name (no leading `--`) -> value
|
|
93
|
+
*/
|
|
94
|
+
function parseCssVars(css) {
|
|
95
|
+
const out = {};
|
|
96
|
+
const declRegex = /--([\w-]+)\s*:\s*([^;]+);/g;
|
|
97
|
+
const collect = (text) => {
|
|
98
|
+
let m;
|
|
99
|
+
while ((m = declRegex.exec(text)) !== null) {
|
|
100
|
+
out[m[1].trim().toLowerCase()] = m[2].trim();
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
// 1) :root blocks (there can be more than one)
|
|
104
|
+
const rootRegex = /:root\s*\{([^}]*)\}/g;
|
|
105
|
+
let rootMatch;
|
|
106
|
+
let foundRoot = false;
|
|
107
|
+
while ((rootMatch = rootRegex.exec(css)) !== null) {
|
|
108
|
+
foundRoot = true;
|
|
109
|
+
declRegex.lastIndex = 0;
|
|
110
|
+
collect(rootMatch[1]);
|
|
111
|
+
}
|
|
112
|
+
// 2) Fallback: no :root vars — scan the whole CSS for custom-prop declarations
|
|
113
|
+
if (!foundRoot) {
|
|
114
|
+
declRegex.lastIndex = 0;
|
|
115
|
+
collect(css);
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Parse CSS `:root` custom properties into a theme palette, falling back to a preset for any
|
|
121
|
+
* slot not present in the CSS.
|
|
122
|
+
* @param {string} css - CSS text (or any text containing `--name: value;` declarations)
|
|
123
|
+
* @param {ExtractThemeOptions} [options] - presets + which preset to fall back to
|
|
124
|
+
* @returns {ThemePalette} the resolved palette (always complete — preset fills the gaps)
|
|
125
|
+
* @example
|
|
126
|
+
* const theme = extractThemeFromCSS(':root{ --bg:#121218; --purple:#7C3AED; }')
|
|
127
|
+
* // => { bg: '121218', accent: '7C3AED', ... }
|
|
128
|
+
*/
|
|
129
|
+
function extractThemeFromCSS(css, options = {}) {
|
|
130
|
+
const presets = Object.assign({ dark: DARK_PRESET, light: LIGHT_PRESET }, (options.presets || {}));
|
|
131
|
+
const presetName = options.defaultPreset || 'dark';
|
|
132
|
+
const base = presets[presetName] || DARK_PRESET;
|
|
133
|
+
// Start from a complete palette (dark) then layer the chosen preset so the result is always whole
|
|
134
|
+
const theme = Object.assign(Object.assign({}, DARK_PRESET), base);
|
|
135
|
+
if (typeof css === 'string' && css.length > 0) {
|
|
136
|
+
const vars = parseCssVars(css);
|
|
137
|
+
Object.keys(vars).forEach(name => {
|
|
138
|
+
const slot = VAR_TO_SLOT[name];
|
|
139
|
+
if (!slot)
|
|
140
|
+
return;
|
|
141
|
+
const value = vars[name];
|
|
142
|
+
theme[slot] = slot === 'font' || !COLOR_SLOTS.has(slot) ? normalizeFont(value) : normalizeColor(value);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return theme;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
exports.extractThemeFromCSS = extractThemeFromCSS;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsamuel1/pptxgenjs",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.4",
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "Joshua Samuel",
|
|
6
6
|
"url": "https://github.com/jsamuel1/"
|
|
@@ -15,22 +15,31 @@
|
|
|
15
15
|
"homepage": "https://github.com/jsamuel1/PptxGenJS#readme",
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"exports": {
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./types/index.d.ts",
|
|
20
|
+
"import": "./dist/pptxgen.es.js",
|
|
21
|
+
"require": "./dist/pptxgen.cjs.js"
|
|
22
|
+
},
|
|
23
|
+
"./utils": {
|
|
24
|
+
"types": "./types/utils.d.ts",
|
|
25
|
+
"import": "./dist/utils.es.js",
|
|
26
|
+
"require": "./dist/utils.cjs.js"
|
|
27
|
+
}
|
|
21
28
|
},
|
|
22
29
|
"main": "dist/pptxgen.cjs.js",
|
|
23
30
|
"module": "dist/pptxgen.es.js",
|
|
31
|
+
"sideEffects": false,
|
|
24
32
|
"files": [
|
|
25
33
|
"dist",
|
|
26
34
|
"types"
|
|
27
35
|
],
|
|
28
36
|
"types": "types",
|
|
29
37
|
"scripts": {
|
|
30
|
-
"build": "rollup -c
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
38
|
+
"build": "rollup -c",
|
|
39
|
+
"lint": "eslint src",
|
|
40
|
+
"lint:fix": "eslint src --fix",
|
|
41
|
+
"ship": "SHIP=1 rollup -c && node tools/build/copy-demo-artifacts.mjs",
|
|
42
|
+
"prepublishOnly": "SHIP=1 rollup -c",
|
|
34
43
|
"watch": "rollup -cw",
|
|
35
44
|
"pretest": "npm run build",
|
|
36
45
|
"test": "node test/run.js && node test/run-schema.js",
|
|
@@ -41,41 +50,31 @@
|
|
|
41
50
|
"browser": {
|
|
42
51
|
"express": false,
|
|
43
52
|
"fs": false,
|
|
44
|
-
"https": false,
|
|
45
|
-
"image-size": false,
|
|
46
53
|
"node:fs": false,
|
|
47
54
|
"node:https": false,
|
|
48
55
|
"os": false,
|
|
49
56
|
"path": false
|
|
50
57
|
},
|
|
51
58
|
"dependencies": {
|
|
52
|
-
"@types/node": "^22.19.20",
|
|
53
|
-
"https": "^1.0.0",
|
|
54
|
-
"image-size": "^2.0.2",
|
|
55
59
|
"jszip": "^3.10.1"
|
|
56
60
|
},
|
|
57
61
|
"devDependencies": {
|
|
58
|
-
"@eslint/js": "^
|
|
62
|
+
"@eslint/js": "^10.0.1",
|
|
59
63
|
"@rollup/plugin-commonjs": "^29.0.3",
|
|
60
64
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
65
|
+
"@rollup/plugin-terser": "^1.0.0",
|
|
61
66
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
67
|
+
"@types/node": "^25.9.2",
|
|
62
68
|
"@typescript-eslint/eslint-plugin": "^8.60.1",
|
|
63
69
|
"@typescript-eslint/parser": "^8.60.1",
|
|
64
|
-
"eslint": "^
|
|
70
|
+
"eslint": "^10.4.1",
|
|
65
71
|
"express": "^5.2.1",
|
|
66
|
-
"
|
|
67
|
-
"gulp-concat": "^2.6.1",
|
|
68
|
-
"gulp-delete-lines": "0.0.7",
|
|
69
|
-
"gulp-ignore": "^3.0.0",
|
|
70
|
-
"gulp-insert": "^0.5.0",
|
|
71
|
-
"gulp-sourcemaps": "^3.0.0",
|
|
72
|
-
"gulp-uglify": "^3.0.2",
|
|
72
|
+
"playwright": "^1.60.0",
|
|
73
73
|
"rollup": "^4.61.1",
|
|
74
74
|
"rollup-plugin-typescript2": "^0.37.0",
|
|
75
|
-
"tslib": "^2.8.
|
|
76
|
-
"typescript": "^
|
|
77
|
-
"typescript-eslint": "^8.60.1"
|
|
78
|
-
"playwright": "^1.60.0"
|
|
75
|
+
"tslib": "^2.8.1",
|
|
76
|
+
"typescript": "^6.0.3",
|
|
77
|
+
"typescript-eslint": "^8.60.1"
|
|
79
78
|
},
|
|
80
79
|
"repository": {
|
|
81
80
|
"type": "git",
|
package/types/index.d.ts
CHANGED
|
@@ -124,6 +124,14 @@ declare class PptxGenJS {
|
|
|
124
124
|
* @deprecated use `addSlide(IAddSlideOptions)`
|
|
125
125
|
*/
|
|
126
126
|
addSlide(masterName?: string): PptxGenJS.Slide
|
|
127
|
+
/**
|
|
128
|
+
* Compute evenly-spaced grid cell positions within a bounding area.
|
|
129
|
+
* - Pure layout helper: returns one `{ x, y, w, h }` (inches) per item; emits no slide content.
|
|
130
|
+
* @param {LayoutGridProps} props grid options
|
|
131
|
+
* @returns {LayoutGridResult} array of `{ x, y, w, h }` cells (inches), one per item
|
|
132
|
+
* @example const grid = pptx.layoutGrid({ items: 6, columns: 3, area: { x: 0.5, y: 2, w: 12, h: 4 }, gap: 0.2 })
|
|
133
|
+
*/
|
|
134
|
+
layoutGrid(props: PptxGenJS.LayoutGridProps): PptxGenJS.LayoutGridResult
|
|
127
135
|
/**
|
|
128
136
|
* Create a custom Slide Layout in any size
|
|
129
137
|
* @param {PresLayout} layout an object with user-defined w/h
|
|
@@ -845,6 +853,39 @@ declare namespace PptxGenJS {
|
|
|
845
853
|
* @example '75%' // coordinate as percentage of slide size
|
|
846
854
|
*/
|
|
847
855
|
export type Coord = number | `${number}%`
|
|
856
|
+
export interface LayoutGridArea {
|
|
857
|
+
x: number
|
|
858
|
+
y: number
|
|
859
|
+
w: number
|
|
860
|
+
h: number
|
|
861
|
+
}
|
|
862
|
+
export interface LayoutGridProps {
|
|
863
|
+
/** Number of items (cells) to position */
|
|
864
|
+
items: number
|
|
865
|
+
/** Items per row (rows are auto-calculated) */
|
|
866
|
+
columns: number
|
|
867
|
+
/** Bounding box (inches) to lay the grid out within */
|
|
868
|
+
area: LayoutGridArea
|
|
869
|
+
/** Gap between cells (inches) - used for both axes unless overridden @default 0.2 */
|
|
870
|
+
gap?: number
|
|
871
|
+
/** Horizontal gap override (inches) */
|
|
872
|
+
gapX?: number
|
|
873
|
+
/** Vertical gap override (inches) */
|
|
874
|
+
gapY?: number
|
|
875
|
+
/** Inner padding per cell (inches) - insets each returned cell box @default 0 */
|
|
876
|
+
padding?: number
|
|
877
|
+
/** Horizontal alignment of a partial last row @default 'start' */
|
|
878
|
+
align?: 'start' | 'center' | 'stretch'
|
|
879
|
+
/** Vertical alignment (reserved) @default 'start' */
|
|
880
|
+
valign?: 'start' | 'center' | 'stretch'
|
|
881
|
+
}
|
|
882
|
+
export interface LayoutGridCell {
|
|
883
|
+
x: number
|
|
884
|
+
y: number
|
|
885
|
+
w: number
|
|
886
|
+
h: number
|
|
887
|
+
}
|
|
888
|
+
export type LayoutGridResult = LayoutGridCell[]
|
|
848
889
|
export interface PositionProps {
|
|
849
890
|
/**
|
|
850
891
|
* Horizontal position
|
package/types/utils.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Type definitions for @jsamuel1/pptxgenjs/utils
|
|
2
|
+
// Optional, format-agnostic helpers (not part of the main PptxGenJS class).
|
|
3
|
+
|
|
4
|
+
/** A resolved theme palette. All colours are 6-digit hex strings (no leading `#`). */
|
|
5
|
+
export interface ThemePalette {
|
|
6
|
+
/** Background colour. */
|
|
7
|
+
bg: string
|
|
8
|
+
/** Card/surface (secondary background) colour. */
|
|
9
|
+
bgSecondary: string
|
|
10
|
+
/** Primary accent colour. */
|
|
11
|
+
accent: string
|
|
12
|
+
/** Lighter accent colour. */
|
|
13
|
+
accentSoft: string
|
|
14
|
+
/** Primary text colour. */
|
|
15
|
+
text: string
|
|
16
|
+
/** Muted/secondary text colour. */
|
|
17
|
+
textSecondary: string
|
|
18
|
+
/** Font family. */
|
|
19
|
+
font: string
|
|
20
|
+
/** Extended palette — informational/utility colours. */
|
|
21
|
+
sky: string
|
|
22
|
+
green: string
|
|
23
|
+
orange: string
|
|
24
|
+
red: string
|
|
25
|
+
[key: string]: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Options for `extractThemeFromCSS`. */
|
|
29
|
+
export interface ExtractThemeOptions {
|
|
30
|
+
/** Named fallback presets; merged over the built-ins (`dark`, `light`). */
|
|
31
|
+
presets?: Record<string, Partial<ThemePalette>>
|
|
32
|
+
/** Which preset to use as the base/fallback. @default 'dark' */
|
|
33
|
+
defaultPreset?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Parse CSS `:root` custom properties into a theme palette, falling back to a preset for any
|
|
38
|
+
* slot not present in the CSS.
|
|
39
|
+
*/
|
|
40
|
+
export function extractThemeFromCSS(css: string, options?: ExtractThemeOptions): ThemePalette
|