docrev 0.8.1 → 0.8.5
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/.claude/settings.local.json +9 -0
- package/PLAN-tables-and-postprocess.md +850 -0
- package/README.md +33 -0
- package/bin/rev.js +12 -131
- package/bin/rev.ts +145 -0
- package/dist/bin/rev.d.ts +9 -0
- package/dist/bin/rev.d.ts.map +1 -0
- package/dist/bin/rev.js +118 -0
- package/dist/bin/rev.js.map +1 -0
- package/dist/lib/annotations.d.ts +91 -0
- package/dist/lib/annotations.d.ts.map +1 -0
- package/dist/lib/annotations.js +554 -0
- package/dist/lib/annotations.js.map +1 -0
- package/dist/lib/build.d.ts +171 -0
- package/dist/lib/build.d.ts.map +1 -0
- package/dist/lib/build.js +755 -0
- package/dist/lib/build.js.map +1 -0
- package/dist/lib/citations.d.ts +34 -0
- package/dist/lib/citations.d.ts.map +1 -0
- package/dist/lib/citations.js +140 -0
- package/dist/lib/citations.js.map +1 -0
- package/dist/lib/commands/build.d.ts +13 -0
- package/dist/lib/commands/build.d.ts.map +1 -0
- package/dist/lib/commands/build.js +678 -0
- package/dist/lib/commands/build.js.map +1 -0
- package/dist/lib/commands/citations.d.ts +11 -0
- package/dist/lib/commands/citations.d.ts.map +1 -0
- package/dist/lib/commands/citations.js +428 -0
- package/dist/lib/commands/citations.js.map +1 -0
- package/dist/lib/commands/comments.d.ts +11 -0
- package/dist/lib/commands/comments.d.ts.map +1 -0
- package/dist/lib/commands/comments.js +883 -0
- package/dist/lib/commands/comments.js.map +1 -0
- package/dist/lib/commands/context.d.ts +35 -0
- package/dist/lib/commands/context.d.ts.map +1 -0
- package/dist/lib/commands/context.js +59 -0
- package/dist/lib/commands/context.js.map +1 -0
- package/dist/lib/commands/core.d.ts +11 -0
- package/dist/lib/commands/core.d.ts.map +1 -0
- package/dist/lib/commands/core.js +246 -0
- package/dist/lib/commands/core.js.map +1 -0
- package/dist/lib/commands/doi.d.ts +11 -0
- package/dist/lib/commands/doi.d.ts.map +1 -0
- package/dist/lib/commands/doi.js +373 -0
- package/dist/lib/commands/doi.js.map +1 -0
- package/dist/lib/commands/history.d.ts +11 -0
- package/dist/lib/commands/history.d.ts.map +1 -0
- package/dist/lib/commands/history.js +245 -0
- package/dist/lib/commands/history.js.map +1 -0
- package/dist/lib/commands/index.d.ts +28 -0
- package/dist/lib/commands/index.d.ts.map +1 -0
- package/dist/lib/commands/index.js +35 -0
- package/dist/lib/commands/index.js.map +1 -0
- package/dist/lib/commands/init.d.ts +11 -0
- package/dist/lib/commands/init.d.ts.map +1 -0
- package/dist/lib/commands/init.js +209 -0
- package/dist/lib/commands/init.js.map +1 -0
- package/dist/lib/commands/response.d.ts +11 -0
- package/dist/lib/commands/response.d.ts.map +1 -0
- package/dist/lib/commands/response.js +317 -0
- package/dist/lib/commands/response.js.map +1 -0
- package/dist/lib/commands/sections.d.ts +11 -0
- package/dist/lib/commands/sections.d.ts.map +1 -0
- package/dist/lib/commands/sections.js +1071 -0
- package/dist/lib/commands/sections.js.map +1 -0
- package/dist/lib/commands/utilities.d.ts +19 -0
- package/dist/lib/commands/utilities.d.ts.map +1 -0
- package/dist/lib/commands/utilities.js +2009 -0
- package/dist/lib/commands/utilities.js.map +1 -0
- package/dist/lib/comment-realign.d.ts +50 -0
- package/dist/lib/comment-realign.d.ts.map +1 -0
- package/dist/lib/comment-realign.js +372 -0
- package/dist/lib/comment-realign.js.map +1 -0
- package/dist/lib/config.d.ts +41 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +76 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/crossref.d.ts +108 -0
- package/dist/lib/crossref.d.ts.map +1 -0
- package/dist/lib/crossref.js +597 -0
- package/dist/lib/crossref.js.map +1 -0
- package/dist/lib/dependencies.d.ts +30 -0
- package/dist/lib/dependencies.d.ts.map +1 -0
- package/dist/lib/dependencies.js +95 -0
- package/dist/lib/dependencies.js.map +1 -0
- package/dist/lib/doi-cache.d.ts +29 -0
- package/dist/lib/doi-cache.d.ts.map +1 -0
- package/dist/lib/doi-cache.js +104 -0
- package/dist/lib/doi-cache.js.map +1 -0
- package/dist/lib/doi.d.ts +65 -0
- package/dist/lib/doi.d.ts.map +1 -0
- package/dist/lib/doi.js +710 -0
- package/dist/lib/doi.js.map +1 -0
- package/dist/lib/equations.d.ts +61 -0
- package/dist/lib/equations.d.ts.map +1 -0
- package/dist/lib/equations.js +445 -0
- package/dist/lib/equations.js.map +1 -0
- package/dist/lib/errors.d.ts +60 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +303 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/format.d.ts +104 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/format.js +416 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/lib/git.d.ts +88 -0
- package/dist/lib/git.d.ts.map +1 -0
- package/dist/lib/git.js +304 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/grammar.d.ts +62 -0
- package/dist/lib/grammar.d.ts.map +1 -0
- package/dist/lib/grammar.js +244 -0
- package/dist/lib/grammar.js.map +1 -0
- package/dist/lib/image-registry.d.ts +68 -0
- package/dist/lib/image-registry.d.ts.map +1 -0
- package/dist/lib/image-registry.js +112 -0
- package/dist/lib/image-registry.js.map +1 -0
- package/dist/lib/import.d.ts +184 -0
- package/dist/lib/import.d.ts.map +1 -0
- package/dist/lib/import.js +1581 -0
- package/dist/lib/import.js.map +1 -0
- package/dist/lib/journals.d.ts +55 -0
- package/dist/lib/journals.d.ts.map +1 -0
- package/dist/lib/journals.js +417 -0
- package/dist/lib/journals.js.map +1 -0
- package/dist/lib/merge.d.ts +138 -0
- package/dist/lib/merge.d.ts.map +1 -0
- package/dist/lib/merge.js +603 -0
- package/dist/lib/merge.js.map +1 -0
- package/dist/lib/orcid.d.ts +36 -0
- package/dist/lib/orcid.d.ts.map +1 -0
- package/dist/lib/orcid.js +117 -0
- package/dist/lib/orcid.js.map +1 -0
- package/dist/lib/pdf-comments.d.ts +95 -0
- package/dist/lib/pdf-comments.d.ts.map +1 -0
- package/dist/lib/pdf-comments.js +192 -0
- package/dist/lib/pdf-comments.js.map +1 -0
- package/dist/lib/pdf-import.d.ts +118 -0
- package/dist/lib/pdf-import.d.ts.map +1 -0
- package/dist/lib/pdf-import.js +397 -0
- package/dist/lib/pdf-import.js.map +1 -0
- package/dist/lib/plugins.d.ts +76 -0
- package/dist/lib/plugins.d.ts.map +1 -0
- package/dist/lib/plugins.js +235 -0
- package/dist/lib/plugins.js.map +1 -0
- package/dist/lib/postprocess.d.ts +42 -0
- package/dist/lib/postprocess.d.ts.map +1 -0
- package/dist/lib/postprocess.js +138 -0
- package/dist/lib/postprocess.js.map +1 -0
- package/dist/lib/pptx-template.d.ts +59 -0
- package/dist/lib/pptx-template.d.ts.map +1 -0
- package/dist/lib/pptx-template.js +613 -0
- package/dist/lib/pptx-template.js.map +1 -0
- package/dist/lib/pptx-themes.d.ts +80 -0
- package/dist/lib/pptx-themes.d.ts.map +1 -0
- package/dist/lib/pptx-themes.js +818 -0
- package/dist/lib/pptx-themes.js.map +1 -0
- package/dist/lib/protect-restore.d.ts +137 -0
- package/dist/lib/protect-restore.d.ts.map +1 -0
- package/dist/lib/protect-restore.js +394 -0
- package/dist/lib/protect-restore.js.map +1 -0
- package/dist/lib/rate-limiter.d.ts +27 -0
- package/dist/lib/rate-limiter.d.ts.map +1 -0
- package/dist/lib/rate-limiter.js +79 -0
- package/dist/lib/rate-limiter.js.map +1 -0
- package/dist/lib/response.d.ts +41 -0
- package/dist/lib/response.d.ts.map +1 -0
- package/dist/lib/response.js +150 -0
- package/dist/lib/response.js.map +1 -0
- package/dist/lib/review.d.ts +35 -0
- package/dist/lib/review.d.ts.map +1 -0
- package/dist/lib/review.js +263 -0
- package/dist/lib/review.js.map +1 -0
- package/dist/lib/schema.d.ts +66 -0
- package/dist/lib/schema.d.ts.map +1 -0
- package/dist/lib/schema.js +339 -0
- package/dist/lib/schema.js.map +1 -0
- package/dist/lib/scientific-words.d.ts +6 -0
- package/dist/lib/scientific-words.d.ts.map +1 -0
- package/dist/lib/scientific-words.js +66 -0
- package/dist/lib/scientific-words.js.map +1 -0
- package/dist/lib/sections.d.ts +40 -0
- package/dist/lib/sections.d.ts.map +1 -0
- package/dist/lib/sections.js +288 -0
- package/dist/lib/sections.js.map +1 -0
- package/dist/lib/slides.d.ts +86 -0
- package/dist/lib/slides.d.ts.map +1 -0
- package/dist/lib/slides.js +676 -0
- package/dist/lib/slides.js.map +1 -0
- package/dist/lib/spelling.d.ts +76 -0
- package/dist/lib/spelling.d.ts.map +1 -0
- package/dist/lib/spelling.js +272 -0
- package/dist/lib/spelling.js.map +1 -0
- package/dist/lib/templates.d.ts +30 -0
- package/dist/lib/templates.d.ts.map +1 -0
- package/dist/lib/templates.js +504 -0
- package/dist/lib/templates.js.map +1 -0
- package/dist/lib/themes.d.ts +85 -0
- package/dist/lib/themes.d.ts.map +1 -0
- package/dist/lib/themes.js +652 -0
- package/dist/lib/themes.js.map +1 -0
- package/dist/lib/trackchanges.d.ts +51 -0
- package/dist/lib/trackchanges.d.ts.map +1 -0
- package/dist/lib/trackchanges.js +202 -0
- package/dist/lib/trackchanges.js.map +1 -0
- package/dist/lib/tui.d.ts +76 -0
- package/dist/lib/tui.d.ts.map +1 -0
- package/dist/lib/tui.js +377 -0
- package/dist/lib/tui.js.map +1 -0
- package/dist/lib/types.d.ts +447 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +6 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/undo.d.ts +57 -0
- package/dist/lib/undo.d.ts.map +1 -0
- package/dist/lib/undo.js +185 -0
- package/dist/lib/undo.js.map +1 -0
- package/dist/lib/utils.d.ts +16 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +40 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/lib/variables.d.ts +42 -0
- package/dist/lib/variables.d.ts.map +1 -0
- package/dist/lib/variables.js +141 -0
- package/dist/lib/variables.js.map +1 -0
- package/dist/lib/word.d.ts +80 -0
- package/dist/lib/word.d.ts.map +1 -0
- package/dist/lib/word.js +360 -0
- package/dist/lib/word.js.map +1 -0
- package/dist/lib/wordcomments.d.ts +51 -0
- package/dist/lib/wordcomments.d.ts.map +1 -0
- package/dist/lib/wordcomments.js +587 -0
- package/dist/lib/wordcomments.js.map +1 -0
- package/eslint.config.js +27 -0
- package/lib/annotations.ts +622 -0
- package/lib/apply-buildup-colors.py +88 -0
- package/lib/build.ts +1013 -0
- package/lib/{citations.js → citations.ts} +38 -27
- package/lib/commands/{build.js → build.ts} +80 -27
- package/lib/commands/{citations.js → citations.ts} +36 -18
- package/lib/commands/{comments.js → comments.ts} +187 -54
- package/lib/commands/{context.js → context.ts} +18 -8
- package/lib/commands/{core.js → core.ts} +34 -20
- package/lib/commands/{doi.js → doi.ts} +32 -16
- package/lib/commands/{history.js → history.ts} +25 -12
- package/lib/commands/{index.js → index.ts} +9 -5
- package/lib/commands/{init.js → init.ts} +20 -8
- package/lib/commands/{response.js → response.ts} +47 -20
- package/lib/commands/{sections.js → sections.ts} +273 -68
- package/lib/commands/{utilities.js → utilities.ts} +338 -158
- package/lib/{comment-realign.js → comment-realign.ts} +117 -45
- package/lib/config.ts +84 -0
- package/lib/{crossref.js → crossref.ts} +213 -138
- package/lib/dependencies.ts +106 -0
- package/lib/doi-cache.ts +115 -0
- package/lib/{doi.js → doi.ts} +115 -281
- package/lib/{equations.js → equations.ts} +60 -64
- package/lib/{errors.js → errors.ts} +56 -48
- package/lib/{format.js → format.ts} +137 -63
- package/lib/{git.js → git.ts} +66 -63
- package/lib/{grammar.js → grammar.ts} +45 -32
- package/lib/image-registry.ts +180 -0
- package/lib/import.ts +2060 -0
- package/lib/journals.ts +505 -0
- package/lib/{merge.js → merge.ts} +185 -135
- package/lib/{orcid.js → orcid.ts} +17 -22
- package/lib/{pdf-comments.js → pdf-comments.ts} +76 -18
- package/lib/{pdf-import.js → pdf-import.ts} +148 -70
- package/lib/{plugins.js → plugins.ts} +82 -39
- package/lib/postprocess.ts +188 -0
- package/lib/pptx-color-filter.lua +37 -0
- package/lib/pptx-template.ts +625 -0
- package/lib/pptx-themes/academic.pptx +0 -0
- package/lib/pptx-themes/corporate.pptx +0 -0
- package/lib/pptx-themes/dark.pptx +0 -0
- package/lib/pptx-themes/default.pptx +0 -0
- package/lib/pptx-themes/minimal.pptx +0 -0
- package/lib/pptx-themes/plant.pptx +0 -0
- package/lib/pptx-themes.ts +896 -0
- package/lib/protect-restore.ts +516 -0
- package/lib/rate-limiter.ts +94 -0
- package/lib/{response.js → response.ts} +36 -21
- package/lib/{review.js → review.ts} +53 -43
- package/lib/{schema.js → schema.ts} +70 -25
- package/lib/{sections.js → sections.ts} +71 -76
- package/lib/slides.ts +793 -0
- package/lib/{spelling.js → spelling.ts} +43 -59
- package/lib/{templates.js → templates.ts} +20 -17
- package/lib/themes.ts +742 -0
- package/lib/{trackchanges.js → trackchanges.ts} +52 -23
- package/lib/types.ts +509 -0
- package/lib/{undo.js → undo.ts} +75 -52
- package/lib/utils.ts +41 -0
- package/lib/{variables.js → variables.ts} +60 -54
- package/lib/word.ts +428 -0
- package/lib/{wordcomments.js → wordcomments.ts} +94 -40
- package/package.json +15 -5
- package/skill/REFERENCE.md +67 -0
- package/tsconfig.json +26 -0
- package/lib/annotations.js +0 -414
- package/lib/build.js +0 -639
- package/lib/config.js +0 -79
- package/lib/import.js +0 -1145
- package/lib/journals.js +0 -629
- package/lib/word.js +0 -225
- /package/lib/{scientific-words.js → scientific-words.ts} +0 -0
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PPTX post-processing
|
|
3
|
+
*
|
|
4
|
+
* Injects logos into each slide of a generated PPTX to match ref.pptx styling.
|
|
5
|
+
* Uses ref.pptx as-is for --reference-doc, then post-processes to add logos.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';
|
|
8
|
+
import { join, dirname } from 'node:path';
|
|
9
|
+
import { execSync } from 'node:child_process';
|
|
10
|
+
/**
|
|
11
|
+
* Extract PPTX to directory
|
|
12
|
+
*/
|
|
13
|
+
async function extractPptx(pptxPath, destDir) {
|
|
14
|
+
if (process.platform === 'win32') {
|
|
15
|
+
const zipPath = pptxPath.replace(/\.pptx$/i, '.zip');
|
|
16
|
+
const content = readFileSync(pptxPath);
|
|
17
|
+
writeFileSync(zipPath, content);
|
|
18
|
+
try {
|
|
19
|
+
execSync(`powershell -Command "Expand-Archive -LiteralPath '${zipPath}' -DestinationPath '${destDir}' -Force"`, { stdio: 'pipe' });
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
try {
|
|
23
|
+
unlinkSync(zipPath);
|
|
24
|
+
}
|
|
25
|
+
catch { /* ignore */ }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
execSync(`unzip -q "${pptxPath}" -d "${destDir}"`, { stdio: 'pipe' });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create PPTX from directory
|
|
34
|
+
* Uses same compression settings as the original file
|
|
35
|
+
*/
|
|
36
|
+
async function createPptx(srcDir, pptxPath) {
|
|
37
|
+
const scriptPath = join(dirname(pptxPath), '.zip-create.py');
|
|
38
|
+
const script = `import zipfile, os, sys
|
|
39
|
+
|
|
40
|
+
src, dst = sys.argv[1], sys.argv[2]
|
|
41
|
+
|
|
42
|
+
# Collect all files
|
|
43
|
+
files_to_add = []
|
|
44
|
+
for root, dirs, files in os.walk(src):
|
|
45
|
+
for f in files:
|
|
46
|
+
fp = os.path.join(root, f)
|
|
47
|
+
arcname = os.path.relpath(fp, src).replace(os.sep, '/')
|
|
48
|
+
files_to_add.append((fp, arcname))
|
|
49
|
+
|
|
50
|
+
# Write ZIP with DEFLATED compression
|
|
51
|
+
with zipfile.ZipFile(dst, 'w', zipfile.ZIP_DEFLATED, compresslevel=6) as zf:
|
|
52
|
+
for fp, arcname in files_to_add:
|
|
53
|
+
zf.write(fp, arcname)
|
|
54
|
+
`;
|
|
55
|
+
writeFileSync(scriptPath, script);
|
|
56
|
+
try {
|
|
57
|
+
const pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
|
|
58
|
+
execSync(`${pythonCmd} "${scriptPath}" "${srcDir}" "${pptxPath}"`, { stdio: 'pipe' });
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
try {
|
|
62
|
+
unlinkSync(scriptPath);
|
|
63
|
+
}
|
|
64
|
+
catch { /* ignore */ }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Recursively remove directory
|
|
69
|
+
*/
|
|
70
|
+
function rmSync(path, options) {
|
|
71
|
+
const fs = require('node:fs');
|
|
72
|
+
if (fs.rmSync) {
|
|
73
|
+
fs.rmSync(path, options);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const items = fs.readdirSync(path);
|
|
77
|
+
for (const item of items) {
|
|
78
|
+
const itemPath = join(path, item);
|
|
79
|
+
if (fs.statSync(itemPath).isDirectory()) {
|
|
80
|
+
rmSync(itemPath, options);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
fs.unlinkSync(itemPath);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
fs.rmdirSync(path);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Inject slide numbers into each slide of a PPTX
|
|
91
|
+
* Only adds slide numbers to slides that have a footer (i.e., slides with the green banner).
|
|
92
|
+
* Title slides, section slides, cover slides don't have the banner so they don't get numbers.
|
|
93
|
+
* Uses in-place ZIP modification to preserve file structure.
|
|
94
|
+
*/
|
|
95
|
+
export async function injectSlideNumbers(pptxPath) {
|
|
96
|
+
if (!existsSync(pptxPath))
|
|
97
|
+
return;
|
|
98
|
+
const scriptPath = join(dirname(pptxPath), '.inject-slidenum.py');
|
|
99
|
+
const script = `import zipfile, sys, re, os
|
|
100
|
+
|
|
101
|
+
pptx_path = sys.argv[1]
|
|
102
|
+
temp_path = pptx_path + '.tmp'
|
|
103
|
+
|
|
104
|
+
# Slide number XML template with manual number (white text, 16pt)
|
|
105
|
+
def get_slidenum_xml(max_id, num):
|
|
106
|
+
return f'<p:sp><p:nvSpPr><p:cNvPr id="{max_id}" name="Slide Number Placeholder {max_id}"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr><p:ph type="sldNum" sz="quarter" idx="12"/></p:nvPr></p:nvSpPr><p:spPr><a:xfrm><a:off x="8610600" y="6581838"/><a:ext cx="2743200" cy="319024"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr><p:txBody><a:bodyPr/><a:lstStyle><a:lvl1pPr><a:defRPr sz="1600"><a:solidFill><a:srgbClr val="FFFFFF"/></a:solidFill></a:defRPr></a:lvl1pPr></a:lstStyle><a:p><a:r><a:rPr lang="en-GB" sz="1600" dirty="0"><a:solidFill><a:srgbClr val="FFFFFF"/></a:solidFill></a:rPr><a:t>{num}</a:t></a:r></a:p></p:txBody></p:sp>'
|
|
107
|
+
|
|
108
|
+
def is_content_slide(text):
|
|
109
|
+
"""Check if slide is a content slide (has footer AND body placeholder)"""
|
|
110
|
+
has_footer = 'type="ftr"' in text
|
|
111
|
+
has_body = 'idx="1"' in text or 'type="body"' in text
|
|
112
|
+
return has_footer and has_body
|
|
113
|
+
|
|
114
|
+
# First pass: identify content slides and assign sequential numbers
|
|
115
|
+
with zipfile.ZipFile(pptx_path, 'r') as zin:
|
|
116
|
+
slide_numbers = {} # filename -> sequential number
|
|
117
|
+
content_num = 1
|
|
118
|
+
|
|
119
|
+
# Get all slide files sorted by number
|
|
120
|
+
slide_files = sorted([f for f in zin.namelist()
|
|
121
|
+
if f.startswith('ppt/slides/slide') and f.endswith('.xml')],
|
|
122
|
+
key=lambda x: int(re.search(r'slide(\\d+)', x).group(1)))
|
|
123
|
+
|
|
124
|
+
for fname in slide_files:
|
|
125
|
+
text = zin.read(fname).decode('utf-8')
|
|
126
|
+
if is_content_slide(text) and 'type="sldNum"' not in text:
|
|
127
|
+
slide_numbers[fname] = content_num
|
|
128
|
+
content_num += 1
|
|
129
|
+
|
|
130
|
+
# Second pass: inject numbers
|
|
131
|
+
with zipfile.ZipFile(pptx_path, 'r') as zin:
|
|
132
|
+
with zipfile.ZipFile(temp_path, 'w') as zout:
|
|
133
|
+
for item in zin.infolist():
|
|
134
|
+
content = zin.read(item.filename)
|
|
135
|
+
|
|
136
|
+
if item.filename in slide_numbers:
|
|
137
|
+
text = content.decode('utf-8')
|
|
138
|
+
# Find max id
|
|
139
|
+
ids = [int(m) for m in re.findall(r'id="(\\d+)"', text)]
|
|
140
|
+
max_id = max(ids) + 1 if ids else 100
|
|
141
|
+
|
|
142
|
+
# Insert slide number with sequential count
|
|
143
|
+
slidenum_xml = get_slidenum_xml(max_id, slide_numbers[item.filename])
|
|
144
|
+
text = text.replace('</p:spTree>', slidenum_xml + '</p:spTree>')
|
|
145
|
+
content = text.encode('utf-8')
|
|
146
|
+
|
|
147
|
+
zout.writestr(item, content)
|
|
148
|
+
|
|
149
|
+
os.replace(temp_path, pptx_path)
|
|
150
|
+
`;
|
|
151
|
+
writeFileSync(scriptPath, script);
|
|
152
|
+
try {
|
|
153
|
+
const pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
|
|
154
|
+
execSync(`${pythonCmd} "${scriptPath}" "${pptxPath}"`, { stdio: 'pipe' });
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
try {
|
|
158
|
+
unlinkSync(scriptPath);
|
|
159
|
+
}
|
|
160
|
+
catch { /* ignore */ }
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Inject logos into cover slide of a PPTX (matching ref.pptx style)
|
|
165
|
+
* Uses in-place ZIP modification to preserve file structure.
|
|
166
|
+
*/
|
|
167
|
+
export async function injectLogosIntoSlides(pptxPath, mediaDir) {
|
|
168
|
+
if (!mediaDir || !existsSync(mediaDir) || !existsSync(pptxPath))
|
|
169
|
+
return;
|
|
170
|
+
// Check for logo files
|
|
171
|
+
const logoLeft = join(mediaDir, 'logo-left.png');
|
|
172
|
+
const logoRight = join(mediaDir, 'logo-right.png');
|
|
173
|
+
const hasLeft = existsSync(logoLeft);
|
|
174
|
+
const hasRight = existsSync(logoRight);
|
|
175
|
+
if (!hasLeft && !hasRight)
|
|
176
|
+
return;
|
|
177
|
+
// Read logo files as base64
|
|
178
|
+
const logoLeftData = hasLeft ? readFileSync(logoLeft).toString('base64') : '';
|
|
179
|
+
const logoRightData = hasRight ? readFileSync(logoRight).toString('base64') : '';
|
|
180
|
+
const scriptPath = join(dirname(pptxPath), '.inject-logos.py');
|
|
181
|
+
const script = `import zipfile, sys, re, os, base64
|
|
182
|
+
|
|
183
|
+
pptx_path = sys.argv[1]
|
|
184
|
+
has_left = ${hasLeft ? 'True' : 'False'}
|
|
185
|
+
has_right = ${hasRight ? 'True' : 'False'}
|
|
186
|
+
logo_left_b64 = """${logoLeftData}"""
|
|
187
|
+
logo_right_b64 = """${logoRightData}"""
|
|
188
|
+
|
|
189
|
+
temp_path = pptx_path + '.tmp'
|
|
190
|
+
|
|
191
|
+
# Find next available image number
|
|
192
|
+
def get_next_image_num(zf):
|
|
193
|
+
max_num = 0
|
|
194
|
+
for name in zf.namelist():
|
|
195
|
+
m = re.match(r'ppt/media/image(\\d+)\\.', name)
|
|
196
|
+
if m:
|
|
197
|
+
max_num = max(max_num, int(m.group(1)))
|
|
198
|
+
return max_num + 1
|
|
199
|
+
|
|
200
|
+
with zipfile.ZipFile(pptx_path, 'r') as zin:
|
|
201
|
+
next_img = get_next_image_num(zin)
|
|
202
|
+
right_img_name = f'ppt/media/image{next_img}.png' if has_right else None
|
|
203
|
+
left_img_name = f'ppt/media/image{next_img + 1}.png' if has_left else None
|
|
204
|
+
|
|
205
|
+
with zipfile.ZipFile(temp_path, 'w') as zout:
|
|
206
|
+
for item in zin.infolist():
|
|
207
|
+
content = zin.read(item.filename)
|
|
208
|
+
|
|
209
|
+
# Update [Content_Types].xml to include png if needed
|
|
210
|
+
if item.filename == '[Content_Types].xml':
|
|
211
|
+
text = content.decode('utf-8')
|
|
212
|
+
if 'Extension="png"' not in text:
|
|
213
|
+
text = text.replace('</Types>', '<Default Extension="png" ContentType="image/png"/></Types>')
|
|
214
|
+
content = text.encode('utf-8')
|
|
215
|
+
|
|
216
|
+
# Update slide1.xml.rels to add image relationships
|
|
217
|
+
if item.filename == 'ppt/slides/_rels/slide1.xml.rels':
|
|
218
|
+
text = content.decode('utf-8')
|
|
219
|
+
# Find max rId
|
|
220
|
+
rids = [int(m) for m in re.findall(r'Id="rId(\\d+)"', text)]
|
|
221
|
+
max_rid = max(rids) if rids else 0
|
|
222
|
+
|
|
223
|
+
new_rels = []
|
|
224
|
+
right_rid = None
|
|
225
|
+
left_rid = None
|
|
226
|
+
|
|
227
|
+
if has_right:
|
|
228
|
+
max_rid += 1
|
|
229
|
+
right_rid = f'rId{max_rid}'
|
|
230
|
+
new_rels.append(f'<Relationship Id="{right_rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/image{next_img}.png"/>')
|
|
231
|
+
|
|
232
|
+
if has_left:
|
|
233
|
+
max_rid += 1
|
|
234
|
+
left_rid = f'rId{max_rid}'
|
|
235
|
+
new_rels.append(f'<Relationship Id="{left_rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/image{next_img + 1}.png"/>')
|
|
236
|
+
|
|
237
|
+
if new_rels:
|
|
238
|
+
text = text.replace('</Relationships>', ''.join(new_rels) + '</Relationships>')
|
|
239
|
+
content = text.encode('utf-8')
|
|
240
|
+
|
|
241
|
+
# Store rIds for slide1 modification
|
|
242
|
+
zout.right_rid = right_rid
|
|
243
|
+
zout.left_rid = left_rid
|
|
244
|
+
|
|
245
|
+
# Update slide1.xml to add picture elements
|
|
246
|
+
if item.filename == 'ppt/slides/slide1.xml':
|
|
247
|
+
text = content.decode('utf-8')
|
|
248
|
+
# Find max id
|
|
249
|
+
ids = [int(m) for m in re.findall(r'id="(\\d+)"', text)]
|
|
250
|
+
max_id = max(ids) if ids else 0
|
|
251
|
+
|
|
252
|
+
pics = []
|
|
253
|
+
right_rid = getattr(zout, 'right_rid', None)
|
|
254
|
+
left_rid = getattr(zout, 'left_rid', None)
|
|
255
|
+
|
|
256
|
+
if has_right and right_rid:
|
|
257
|
+
max_id += 1
|
|
258
|
+
pics.append(f'<p:pic><p:nvPicPr><p:cNvPr id="{max_id}" name="Picture {max_id}"/><p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr><p:nvPr/></p:nvPicPr><p:blipFill><a:blip r:embed="{right_rid}"/><a:stretch><a:fillRect/></a:stretch></p:blipFill><p:spPr><a:xfrm><a:off x="9492000" y="5742001"/><a:ext cx="2700000" cy="1115999"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr></p:pic>')
|
|
259
|
+
|
|
260
|
+
if has_left and left_rid:
|
|
261
|
+
max_id += 1
|
|
262
|
+
pics.append(f'<p:pic><p:nvPicPr><p:cNvPr id="{max_id}" name="Picture {max_id}"/><p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr><p:nvPr/></p:nvPicPr><p:blipFill><a:blip r:embed="{left_rid}"/><a:srcRect t="22495" b="27262"/><a:stretch/></p:blipFill><p:spPr><a:xfrm><a:off x="0" y="5904608"/><a:ext cx="3794408" cy="954349"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr></p:pic>')
|
|
263
|
+
|
|
264
|
+
if pics:
|
|
265
|
+
text = text.replace('</p:spTree>', ''.join(pics) + '</p:spTree>')
|
|
266
|
+
content = text.encode('utf-8')
|
|
267
|
+
|
|
268
|
+
zout.writestr(item, content)
|
|
269
|
+
|
|
270
|
+
# Add logo image files
|
|
271
|
+
if has_right:
|
|
272
|
+
zout.writestr(right_img_name, base64.b64decode(logo_right_b64))
|
|
273
|
+
if has_left:
|
|
274
|
+
zout.writestr(left_img_name, base64.b64decode(logo_left_b64))
|
|
275
|
+
|
|
276
|
+
os.replace(temp_path, pptx_path)
|
|
277
|
+
`;
|
|
278
|
+
writeFileSync(scriptPath, script);
|
|
279
|
+
try {
|
|
280
|
+
const pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
|
|
281
|
+
execSync(`${pythonCmd} "${scriptPath}" "${pptxPath}"`, { stdio: 'pipe' });
|
|
282
|
+
}
|
|
283
|
+
finally {
|
|
284
|
+
try {
|
|
285
|
+
unlinkSync(scriptPath);
|
|
286
|
+
}
|
|
287
|
+
catch { /* ignore */ }
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// Legacy exports for compatibility
|
|
291
|
+
export async function generatePptxTemplate(options) {
|
|
292
|
+
// No longer modifying template - just return the base template path
|
|
293
|
+
const { baseTemplate, outputPath } = options;
|
|
294
|
+
if (baseTemplate && existsSync(baseTemplate)) {
|
|
295
|
+
// Copy base template to output
|
|
296
|
+
writeFileSync(outputPath, readFileSync(baseTemplate));
|
|
297
|
+
return outputPath;
|
|
298
|
+
}
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
export function templateNeedsRegeneration(templatePath, mediaDir, baseTemplate) {
|
|
302
|
+
return false; // No template regeneration needed - we use ref.pptx as-is
|
|
303
|
+
}
|
|
304
|
+
export async function injectMediaIntoPptx(pptxPath, mediaDir) {
|
|
305
|
+
// Redirect to the new function
|
|
306
|
+
return injectLogosIntoSlides(pptxPath, mediaDir);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Apply theme fonts to all text in a PPTX
|
|
310
|
+
* Pandoc generates slides with hardcoded fonts; this replaces them with theme font references.
|
|
311
|
+
* Uses in-place ZIP modification to preserve file structure.
|
|
312
|
+
*/
|
|
313
|
+
export async function applyThemeFonts(pptxPath, theme) {
|
|
314
|
+
if (!existsSync(pptxPath) || !theme || !theme.fonts)
|
|
315
|
+
return;
|
|
316
|
+
const { major, minor } = theme.fonts;
|
|
317
|
+
if (!major && !minor)
|
|
318
|
+
return;
|
|
319
|
+
const scriptPath = join(dirname(pptxPath), '.apply-fonts.py');
|
|
320
|
+
const script = `import zipfile, sys, re, os
|
|
321
|
+
|
|
322
|
+
pptx_path = sys.argv[1]
|
|
323
|
+
temp_path = pptx_path + '.tmp'
|
|
324
|
+
|
|
325
|
+
# Fonts to replace with theme fonts
|
|
326
|
+
default_fonts = ['Calibri', 'Arial', 'Helvetica', 'Times New Roman', 'Cambria']
|
|
327
|
+
|
|
328
|
+
with zipfile.ZipFile(pptx_path, 'r') as zin:
|
|
329
|
+
with zipfile.ZipFile(temp_path, 'w') as zout:
|
|
330
|
+
for item in zin.infolist():
|
|
331
|
+
content = zin.read(item.filename)
|
|
332
|
+
|
|
333
|
+
# Process slide XML files
|
|
334
|
+
if item.filename.startswith('ppt/slides/slide') and item.filename.endswith('.xml'):
|
|
335
|
+
text = content.decode('utf-8')
|
|
336
|
+
|
|
337
|
+
# Replace common pandoc fonts with theme minor font reference
|
|
338
|
+
for font in default_fonts:
|
|
339
|
+
text = re.sub(rf'(<a:latin\\s+typeface="){font}(")', r'\\1+mn-lt\\2', text)
|
|
340
|
+
|
|
341
|
+
content = text.encode('utf-8')
|
|
342
|
+
|
|
343
|
+
zout.writestr(item, content)
|
|
344
|
+
|
|
345
|
+
os.replace(temp_path, pptx_path)
|
|
346
|
+
`;
|
|
347
|
+
writeFileSync(scriptPath, script);
|
|
348
|
+
try {
|
|
349
|
+
const pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
|
|
350
|
+
execSync(`${pythonCmd} "${scriptPath}" "${pptxPath}"`, { stdio: 'pipe' });
|
|
351
|
+
}
|
|
352
|
+
finally {
|
|
353
|
+
try {
|
|
354
|
+
unlinkSync(scriptPath);
|
|
355
|
+
}
|
|
356
|
+
catch { /* ignore */ }
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Apply vertical centering to slides that have the .center class
|
|
361
|
+
* Uses in-place ZIP modification to preserve file structure.
|
|
362
|
+
*/
|
|
363
|
+
export async function applyCentering(pptxPath, centeredSlideIndices) {
|
|
364
|
+
if (!existsSync(pptxPath) || !centeredSlideIndices || centeredSlideIndices.length === 0)
|
|
365
|
+
return;
|
|
366
|
+
const scriptPath = join(dirname(pptxPath), '.apply-centering.py');
|
|
367
|
+
const indicesJson = JSON.stringify(centeredSlideIndices);
|
|
368
|
+
const script = `import zipfile, sys, re, os, json
|
|
369
|
+
|
|
370
|
+
pptx_path = sys.argv[1]
|
|
371
|
+
centered_indices = json.loads('${indicesJson}')
|
|
372
|
+
temp_path = pptx_path + '.tmp'
|
|
373
|
+
|
|
374
|
+
# Build set of slide filenames to center
|
|
375
|
+
centered_files = {f'ppt/slides/slide{i}.xml' for i in centered_indices}
|
|
376
|
+
|
|
377
|
+
with zipfile.ZipFile(pptx_path, 'r') as zin:
|
|
378
|
+
with zipfile.ZipFile(temp_path, 'w') as zout:
|
|
379
|
+
for item in zin.infolist():
|
|
380
|
+
content = zin.read(item.filename)
|
|
381
|
+
|
|
382
|
+
# Process centered slides
|
|
383
|
+
if item.filename in centered_files:
|
|
384
|
+
text = content.decode('utf-8')
|
|
385
|
+
|
|
386
|
+
# Process each shape (<p:sp>) separately to skip footer and slide number
|
|
387
|
+
def process_shape(shape_match):
|
|
388
|
+
shape = shape_match.group(0)
|
|
389
|
+
# Skip footer and slide number placeholders
|
|
390
|
+
if 'type="sldNum"' in shape or 'type="ftr"' in shape:
|
|
391
|
+
return shape
|
|
392
|
+
|
|
393
|
+
# Add algn="ctr" to existing <a:pPr> elements
|
|
394
|
+
# Handle both <a:pPr ...> and <a:pPr ... /> (self-closing)
|
|
395
|
+
def add_center_align(match):
|
|
396
|
+
before, attrs, closing = match.groups()
|
|
397
|
+
attrs = attrs.rstrip()
|
|
398
|
+
is_self_closing = '/' in closing
|
|
399
|
+
|
|
400
|
+
if attrs.endswith('/'):
|
|
401
|
+
attrs = attrs[:-1].rstrip()
|
|
402
|
+
is_self_closing = True
|
|
403
|
+
|
|
404
|
+
if 'algn=' not in attrs:
|
|
405
|
+
attrs += ' algn="ctr"'
|
|
406
|
+
else:
|
|
407
|
+
attrs = re.sub(r'algn="[^"]*"', 'algn="ctr"', attrs)
|
|
408
|
+
|
|
409
|
+
return before + attrs + (' />' if is_self_closing else '>')
|
|
410
|
+
|
|
411
|
+
shape = re.sub(r'(<a:pPr)((?:[^/>]|/(?!>))*)(\\s*/?>)', add_center_align, shape)
|
|
412
|
+
|
|
413
|
+
# Add <a:pPr algn="ctr"/> to paragraphs without pPr
|
|
414
|
+
shape = re.sub(r'(<a:p>)(<a:r>)', r'\\1<a:pPr algn="ctr"/>\\2', shape)
|
|
415
|
+
|
|
416
|
+
return shape
|
|
417
|
+
|
|
418
|
+
# Process all shapes
|
|
419
|
+
text = re.sub(r'<p:sp>.*?</p:sp>', process_shape, text, flags=re.DOTALL)
|
|
420
|
+
|
|
421
|
+
content = text.encode('utf-8')
|
|
422
|
+
|
|
423
|
+
zout.writestr(item, content)
|
|
424
|
+
|
|
425
|
+
os.replace(temp_path, pptx_path)
|
|
426
|
+
`;
|
|
427
|
+
writeFileSync(scriptPath, script);
|
|
428
|
+
try {
|
|
429
|
+
const pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
|
|
430
|
+
execSync(`${pythonCmd} "${scriptPath}" "${pptxPath}"`, { stdio: 'pipe' });
|
|
431
|
+
}
|
|
432
|
+
finally {
|
|
433
|
+
try {
|
|
434
|
+
unlinkSync(scriptPath);
|
|
435
|
+
}
|
|
436
|
+
catch { /* ignore */ }
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Apply buildup greying to slides with buildup content
|
|
441
|
+
* Greys out all bullet items except the last one, which gets the accent color.
|
|
442
|
+
* Only affects actual bullet items (not intro text with buNone).
|
|
443
|
+
* Uses in-place ZIP modification to preserve file structure.
|
|
444
|
+
*/
|
|
445
|
+
export async function applyBuildupColors(pptxPath, config = {}) {
|
|
446
|
+
if (!existsSync(pptxPath))
|
|
447
|
+
return;
|
|
448
|
+
// Check if buildup colors are disabled
|
|
449
|
+
if (config.enabled === false)
|
|
450
|
+
return;
|
|
451
|
+
// Get colors from config with defaults
|
|
452
|
+
const defaultColor = (config.default || '608C32').replace(/^#/, '');
|
|
453
|
+
const titleColor = (config.title || defaultColor).replace(/^#/, '');
|
|
454
|
+
const greyColor = (config.grey || '888888').replace(/^#/, '');
|
|
455
|
+
const accentColor = (config.accent || defaultColor).replace(/^#/, '');
|
|
456
|
+
const scriptPath = join(dirname(pptxPath), '.apply-buildup.py');
|
|
457
|
+
const script = `import zipfile
|
|
458
|
+
import sys
|
|
459
|
+
import re
|
|
460
|
+
import os
|
|
461
|
+
|
|
462
|
+
pptx_path = sys.argv[1]
|
|
463
|
+
temp_path = pptx_path + '.tmp'
|
|
464
|
+
|
|
465
|
+
DEFAULT = '${defaultColor}'
|
|
466
|
+
TITLE = '${titleColor}'
|
|
467
|
+
GREY = '${greyColor}'
|
|
468
|
+
ACCENT = '${accentColor}'
|
|
469
|
+
|
|
470
|
+
def get_bullet_paragraphs(body):
|
|
471
|
+
"""Return indices of paragraphs that are actual bullet items (have lvl="0" but NOT buNone)"""
|
|
472
|
+
paras = list(re.finditer(r'<a:p>.*?</a:p>', body, re.DOTALL))
|
|
473
|
+
bullet_indices = []
|
|
474
|
+
|
|
475
|
+
for i, p in enumerate(paras):
|
|
476
|
+
para_text = p.group(0)
|
|
477
|
+
if 'lvl="0"' in para_text and '<a:buNone' not in para_text:
|
|
478
|
+
bullet_indices.append(i)
|
|
479
|
+
|
|
480
|
+
return bullet_indices, paras
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def apply_color_to_para(para, color):
|
|
484
|
+
"""Apply a color to all text runs in a paragraph"""
|
|
485
|
+
new_para = re.sub(
|
|
486
|
+
r'<a:rPr\\s*/>',
|
|
487
|
+
f'<a:rPr><a:solidFill><a:srgbClr val="{color}"/></a:solidFill></a:rPr>',
|
|
488
|
+
para
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
def fix_rpr_with_attrs(m):
|
|
492
|
+
attrs = m.group(1).strip()
|
|
493
|
+
return f'<a:rPr {attrs}><a:solidFill><a:srgbClr val="{color}"/></a:solidFill></a:rPr>'
|
|
494
|
+
|
|
495
|
+
new_para = re.sub(
|
|
496
|
+
r'<a:rPr\\s+([^>]+?)\\s*/>',
|
|
497
|
+
fix_rpr_with_attrs,
|
|
498
|
+
new_para
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
return new_para
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def is_buildup_slide(xml):
|
|
505
|
+
"""Check if slide has buildup marker (animEffect with filter=wipe)"""
|
|
506
|
+
# Buildup slides have animation effects from pandoc's incremental lists
|
|
507
|
+
return 'animEffect' in xml or '<a:bldLst>' in xml
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def color_content_placeholder(xml):
|
|
511
|
+
"""Apply colors to all text in content placeholder.
|
|
512
|
+
|
|
513
|
+
For buildup slides: grey previous bullet items, accent on last bullet item.
|
|
514
|
+
For all text (bullets and non-bullets): apply default color unless overridden by buildup.
|
|
515
|
+
"""
|
|
516
|
+
|
|
517
|
+
pattern = r'(<p:sp>.*?<p:ph idx="1"[^/]*/?>.*?<p:txBody>)(.*?)(</p:txBody>.*?</p:sp>)'
|
|
518
|
+
match = re.search(pattern, xml, re.DOTALL)
|
|
519
|
+
|
|
520
|
+
if not match:
|
|
521
|
+
return xml
|
|
522
|
+
|
|
523
|
+
before_body = match.group(1)
|
|
524
|
+
body = match.group(2)
|
|
525
|
+
after_body = match.group(3)
|
|
526
|
+
|
|
527
|
+
bullet_indices, paras = get_bullet_paragraphs(body)
|
|
528
|
+
is_buildup = is_buildup_slide(xml)
|
|
529
|
+
|
|
530
|
+
new_body = body
|
|
531
|
+
offset = 0
|
|
532
|
+
|
|
533
|
+
for i, para_match in enumerate(paras):
|
|
534
|
+
start = para_match.start() + offset
|
|
535
|
+
end = para_match.end() + offset
|
|
536
|
+
para = para_match.group(0)
|
|
537
|
+
|
|
538
|
+
# Determine color for this paragraph
|
|
539
|
+
if i in bullet_indices and is_buildup:
|
|
540
|
+
# Buildup bullet: grey all but last, accent on last
|
|
541
|
+
if i == bullet_indices[-1]:
|
|
542
|
+
color = ACCENT
|
|
543
|
+
else:
|
|
544
|
+
color = GREY
|
|
545
|
+
else:
|
|
546
|
+
# Non-bullet text OR non-buildup slide: use default color
|
|
547
|
+
color = DEFAULT
|
|
548
|
+
|
|
549
|
+
new_para = apply_color_to_para(para, color)
|
|
550
|
+
new_body = new_body[:start] + new_para + new_body[end:]
|
|
551
|
+
offset += len(new_para) - len(para)
|
|
552
|
+
|
|
553
|
+
return xml[:match.start()] + before_body + new_body + after_body + xml[match.end():]
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def color_title_placeholder(xml):
|
|
557
|
+
"""Apply title color to title placeholder (type='title' or type='ctrTitle')."""
|
|
558
|
+
|
|
559
|
+
# Match title placeholders: type="title" or type="ctrTitle"
|
|
560
|
+
pattern = r'(<p:sp>.*?<p:ph[^>]*type="(?:title|ctrTitle)"[^/]*/?>.*?<p:txBody>)(.*?)(</p:txBody>.*?</p:sp>)'
|
|
561
|
+
match = re.search(pattern, xml, re.DOTALL)
|
|
562
|
+
|
|
563
|
+
if not match:
|
|
564
|
+
return xml
|
|
565
|
+
|
|
566
|
+
before_body = match.group(1)
|
|
567
|
+
body = match.group(2)
|
|
568
|
+
after_body = match.group(3)
|
|
569
|
+
|
|
570
|
+
paras = list(re.finditer(r'<a:p>.*?</a:p>', body, re.DOTALL))
|
|
571
|
+
|
|
572
|
+
new_body = body
|
|
573
|
+
offset = 0
|
|
574
|
+
|
|
575
|
+
for para_match in paras:
|
|
576
|
+
start = para_match.start() + offset
|
|
577
|
+
end = para_match.end() + offset
|
|
578
|
+
para = para_match.group(0)
|
|
579
|
+
|
|
580
|
+
new_para = apply_color_to_para(para, TITLE)
|
|
581
|
+
new_body = new_body[:start] + new_para + new_body[end:]
|
|
582
|
+
offset += len(new_para) - len(para)
|
|
583
|
+
|
|
584
|
+
return xml[:match.start()] + before_body + new_body + after_body + xml[match.end():]
|
|
585
|
+
|
|
586
|
+
with zipfile.ZipFile(pptx_path, 'r') as zin:
|
|
587
|
+
with zipfile.ZipFile(temp_path, 'w') as zout:
|
|
588
|
+
for item in zin.infolist():
|
|
589
|
+
content = zin.read(item.filename)
|
|
590
|
+
|
|
591
|
+
if item.filename.startswith('ppt/slides/slide') and item.filename.endswith('.xml'):
|
|
592
|
+
text = content.decode('utf-8')
|
|
593
|
+
text = color_content_placeholder(text)
|
|
594
|
+
text = color_title_placeholder(text)
|
|
595
|
+
content = text.encode('utf-8')
|
|
596
|
+
|
|
597
|
+
zout.writestr(item, content)
|
|
598
|
+
|
|
599
|
+
os.replace(temp_path, pptx_path)
|
|
600
|
+
`;
|
|
601
|
+
writeFileSync(scriptPath, script);
|
|
602
|
+
try {
|
|
603
|
+
const pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
|
|
604
|
+
execSync(`${pythonCmd} "${scriptPath}" "${pptxPath}"`, { stdio: 'pipe' });
|
|
605
|
+
}
|
|
606
|
+
finally {
|
|
607
|
+
try {
|
|
608
|
+
unlinkSync(scriptPath);
|
|
609
|
+
}
|
|
610
|
+
catch { /* ignore */ }
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
//# sourceMappingURL=pptx-template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pptx-template.js","sourceRoot":"","sources":["../../lib/pptx-template.ts"],"names":[],"mappings":"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"}
|