diff --git a/.claude/skills/pptx/scripts/html2pptx.js b/.claude/skills/pptx/scripts/html2pptx.js index 437bf7c..cf31ab2 100644 --- a/.claude/skills/pptx/scripts/html2pptx.js +++ b/.claude/skills/pptx/scripts/html2pptx.js @@ -120,9 +120,21 @@ function validateTextBoxPosition(slideData, bodyDimensions) { // Helper: Add background to slide async function addBackground(slideData, targetSlide, tmpDir) { if (slideData.background.type === 'image' && slideData.background.path) { - let imagePath = slideData.background.path.startsWith('file://') - ? slideData.background.path.replace('file://', '') - : slideData.background.path; + let imagePath = slideData.background.path; + if (imagePath.startsWith('file://')) { + // Use URL to properly handle file:// paths on all platforms + try { + const url = new URL(imagePath); + imagePath = url.pathname; + // On Windows, pathname starts with / before drive letter, remove it + if (process.platform === 'win32' && /^\/[A-Z]:/.test(imagePath)) { + imagePath = imagePath.slice(1); + } + } catch (e) { + // Fallback to simple replace + imagePath = imagePath.replace('file://', ''); + } + } targetSlide.background = { path: imagePath }; } else if (slideData.background.type === 'color' && slideData.background.value) { targetSlide.background = { color: slideData.background.value }; @@ -133,7 +145,21 @@ async function addBackground(slideData, targetSlide, tmpDir) { function addElements(slideData, targetSlide, pres) { for (const el of slideData.elements) { if (el.type === 'image') { - let imagePath = el.src.startsWith('file://') ? el.src.replace('file://', '') : el.src; + let imagePath = el.src; + if (imagePath.startsWith('file://')) { + // Use URL to properly handle file:// paths on all platforms + try { + const url = new URL(imagePath); + imagePath = url.pathname; + // On Windows, pathname starts with / before drive letter, remove it + if (process.platform === 'win32' && /^\/[A-Z]:/.test(imagePath)) { + imagePath = imagePath.slice(1); + } + } catch (e) { + // Fallback to simple replace + imagePath = imagePath.replace('file://', ''); + } + } targetSlide.addImage({ path: imagePath, x: el.position.x, diff --git a/package.json b/package.json index edbcf58..ae74de2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "dependencies": { "playwright": "^1.56.1", - "pptxgenjs": "^4.0.1" + "pptxgenjs": "^4.0.1", + "sharp": "^0.34.5" } } diff --git a/work_chinese/PPT04122025/_build/alimentari_piccolo.html b/work_chinese/PPT04122025/_build/alimentari_piccolo.html index ecd84cb..5def3a4 100644 --- a/work_chinese/PPT04122025/_build/alimentari_piccolo.html +++ b/work_chinese/PPT04122025/_build/alimentari_piccolo.html @@ -56,11 +56,11 @@ .title-container { position: absolute; - top: 95pt; + top: 80pt; left: 50%; transform: translateX(-50%); background: #F8F8F8; - padding: 28pt 45pt; + padding: 24pt 45pt; border-radius: 15pt; box-shadow: 0 8pt 25pt rgba(0, 0, 0, 0.15); border: 3pt solid #D4AF37; @@ -363,7 +363,7 @@ .final-box { position: absolute; - bottom: 62pt; + bottom: 90pt; left: 30pt; right: 30pt; height: 46pt; diff --git a/work_chinese/PPT04122025/_build/generate.js b/work_chinese/PPT04122025/_build/generate.js new file mode 100644 index 0000000..a228c71 --- /dev/null +++ b/work_chinese/PPT04122025/_build/generate.js @@ -0,0 +1,32 @@ +const pptxgen = require('pptxgenjs'); +const html2pptx = require('../../../.claude/skills/pptx/scripts/html2pptx.js'); +const path = require('path'); + +async function generatePresentation() { + console.log('🍝 Génération Alimentari Piccolo PowerPoint...\n'); + + const pptx = new pptxgen(); + pptx.layout = 'LAYOUT_16x9'; + pptx.author = 'Alexis - Xiezuo Course'; + pptx.title = 'Alimentari Piccolo - 意大利餐吧评价'; + pptx.subject = '餐厅评价作业'; + + const htmlFile = path.join(__dirname, 'alimentari_piccolo.html'); + + try { + await html2pptx(htmlFile, pptx, { tmpDir: __dirname }); + + const outputPath = path.join(__dirname, '..', 'Alimentari_Piccolo.pptx'); + await pptx.writeFile({ fileName: outputPath }); + + console.log(`✅ Présentation créée avec succès: ${outputPath}`); + } catch (error) { + console.error('❌ Erreur lors de la génération:', error.message); + if (error.stack) { + console.error(error.stack); + } + process.exit(1); + } +} + +generatePresentation(); diff --git a/work_chinese/PPT04122025/create_alimentari.js b/work_chinese/PPT04122025/create_alimentari.js index 2ec5d28..27740ec 100644 --- a/work_chinese/PPT04122025/create_alimentari.js +++ b/work_chinese/PPT04122025/create_alimentari.js @@ -17,7 +17,7 @@ async function createPresentation() { await html2pptx( path.join(__dirname, 'slides/slide1_title.html'), pptx, - { tmpDir: '/tmp' } + { tmpDir: path.join(__dirname, '_build') } ); // Slide 2: Location @@ -25,7 +25,7 @@ async function createPresentation() { await html2pptx( path.join(__dirname, 'slides/slide2_location.html'), pptx, - { tmpDir: '/tmp' } + { tmpDir: path.join(__dirname, '_build') } ); // Slide 3: Piadina @@ -33,7 +33,7 @@ async function createPresentation() { await html2pptx( path.join(__dirname, 'slides/slide3_piadina.html'), pptx, - { tmpDir: '/tmp' } + { tmpDir: path.join(__dirname, '_build') } ); // Slide 4: Croquettes @@ -41,7 +41,7 @@ async function createPresentation() { await html2pptx( path.join(__dirname, 'slides/slide4_croquettes.html'), pptx, - { tmpDir: '/tmp' } + { tmpDir: path.join(__dirname, '_build') } ); // Slide 5: Ambiance @@ -49,7 +49,7 @@ async function createPresentation() { await html2pptx( path.join(__dirname, 'slides/slide5_ambiance.html'), pptx, - { tmpDir: '/tmp' } + { tmpDir: path.join(__dirname, '_build') } ); // Slide 6: Menu @@ -57,7 +57,7 @@ async function createPresentation() { await html2pptx( path.join(__dirname, 'slides/slide6_menu.html'), pptx, - { tmpDir: '/tmp' } + { tmpDir: path.join(__dirname, '_build') } ); // Slide 7: Credits @@ -65,11 +65,12 @@ async function createPresentation() { await html2pptx( path.join(__dirname, 'slides/slide7_credits.html'), pptx, - { tmpDir: '/tmp' } + { tmpDir: path.join(__dirname, '_build') } ); // Save - const outputFile = 'Alimentari_Piccolo_v3.pptx'; + const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5); + const outputFile = `Alimentari_Piccolo_${timestamp}.pptx`; await pptx.writeFile({ fileName: path.join(__dirname, outputFile) }); console.log('\n✅ 演示文稿创建成功!'); diff --git a/work_chinese/PPT04122025/fix_flags.js b/work_chinese/PPT04122025/fix_flags.js new file mode 100644 index 0000000..0c86795 --- /dev/null +++ b/work_chinese/PPT04122025/fix_flags.js @@ -0,0 +1,58 @@ +const fs = require('fs'); +const path = require('path'); + +const slidesDir = path.join(__dirname, 'slides'); +const files = fs.readdirSync(slidesDir).filter(f => f.endsWith('.html') && f !== 'slide1_title.html'); + +const flagCSSFix = `/* Italian flag diagonal - simple rectangles positioned diagonally */ +.flag-green { + position: absolute; + top: 15pt; + left: 15pt; + width: 45pt; + height: 12pt; + background: #2D5016; + opacity: 0.85; +} + +.flag-white { + position: absolute; + top: 27pt; + left: 27pt; + width: 45pt; + height: 12pt; + background: #F5F5DC; + opacity: 0.85; +} + +.flag-red { + position: absolute; + top: 39pt; + left: 39pt; + width: 45pt; + height: 12pt; + background: #8B1A1A; + opacity: 0.85; +}`; + +files.forEach(file => { + const filePath = path.join(slidesDir, file); + let content = fs.readFileSync(filePath, 'utf8'); + + // Replace the old flag CSS with the new one + content = content.replace( + /\/\* Italian flag diagonal \*\/[\s\S]*?\.flag-red \{[\s\S]*?\}/, + flagCSSFix + ); + + // Remove the flag-diagonal wrapper div in HTML + content = content.replace( + /