Upload public.js
Browse files- backend/src/routes/public.js +82 -16
backend/src/routes/public.js
CHANGED
|
@@ -739,29 +739,86 @@ router.get('/direct-image/:userId/:pptId/:slideIndex?', async (req, res, next) =
|
|
| 739 |
}
|
| 740 |
});
|
| 741 |
|
| 742 |
-
// 辅助函数:生成SVG
|
| 743 |
function generateSlideSVG(slide, pptData, options = {}) {
|
| 744 |
const { width = 1000, height = 562 } = options;
|
| 745 |
|
| 746 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 747 |
|
|
|
|
| 748 |
const elementsHTML = slide.elements.map(element => {
|
| 749 |
const x = element.left || 0;
|
| 750 |
const y = element.top || 0;
|
| 751 |
const w = element.width || 100;
|
| 752 |
const h = element.height || 100;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 753 |
|
| 754 |
if (element.type === 'text') {
|
| 755 |
const fontSize = element.fontSize || 16;
|
| 756 |
-
const fontFamily = element.fontName || 'Arial';
|
| 757 |
const color = element.defaultColor || element.color || '#000000';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 758 |
const content = (element.content || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
|
|
| 759 |
|
| 760 |
-
return
|
| 761 |
-
|
| 762 |
-
|
| 763 |
-
|
| 764 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 765 |
}
|
| 766 |
|
| 767 |
if (element.type === 'shape') {
|
|
@@ -774,25 +831,34 @@ function generateSlideSVG(slide, pptData, options = {}) {
|
|
| 774 |
const cy = y + h/2;
|
| 775 |
const rx = w/2;
|
| 776 |
const ry = h/2;
|
| 777 |
-
return `<ellipse cx="${cx}" cy="${cy}" rx="${rx}" ry="${ry}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeWidth}"/>`;
|
| 778 |
}
|
| 779 |
|
| 780 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 781 |
}
|
| 782 |
|
| 783 |
if (element.type === 'image' && element.src) {
|
| 784 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 785 |
}
|
| 786 |
|
| 787 |
return '';
|
| 788 |
-
}).join('');
|
| 789 |
|
| 790 |
-
return `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
|
|
|
|
| 791 |
<rect width="100%" height="100%" fill="${backgroundStyle}"/>
|
| 792 |
<g>${elementsHTML}</g>
|
| 793 |
-
<text x="${width-10}" y="${height-10}" text-anchor="end" font-family="Arial" font-size="10" fill="#999" opacity="0.7">
|
| 794 |
-
${pptData.title || 'PPTist'} - Generated at ${new Date().toISOString()}
|
| 795 |
-
</text>
|
| 796 |
</svg>`;
|
| 797 |
}
|
| 798 |
|
|
|
|
| 739 |
}
|
| 740 |
});
|
| 741 |
|
| 742 |
+
// 辅助函数:生成SVG - 改进版,支持更精确的PPT渲染
|
| 743 |
function generateSlideSVG(slide, pptData, options = {}) {
|
| 744 |
const { width = 1000, height = 562 } = options;
|
| 745 |
|
| 746 |
+
// 处理背景
|
| 747 |
+
let backgroundStyle = '#ffffff';
|
| 748 |
+
if (slide.background) {
|
| 749 |
+
if (slide.background.type === 'solid') {
|
| 750 |
+
backgroundStyle = slide.background.color || '#ffffff';
|
| 751 |
+
} else if (slide.background.type === 'gradient' && slide.background.gradient) {
|
| 752 |
+
// SVG渐变处理
|
| 753 |
+
const colors = slide.background.gradient.colors || [];
|
| 754 |
+
if (colors.length > 0) {
|
| 755 |
+
backgroundStyle = colors[0].color || '#ffffff';
|
| 756 |
+
}
|
| 757 |
+
}
|
| 758 |
+
}
|
| 759 |
+
|
| 760 |
+
// 生成渐变定义(如果需要)
|
| 761 |
+
let gradientDefs = '';
|
| 762 |
+
if (slide.background?.type === 'gradient' && slide.background.gradient?.colors) {
|
| 763 |
+
const colors = slide.background.gradient.colors;
|
| 764 |
+
const gradientId = 'bg-gradient';
|
| 765 |
+
if (slide.background.gradient.type === 'linear') {
|
| 766 |
+
gradientDefs = `
|
| 767 |
+
<defs>
|
| 768 |
+
<linearGradient id="${gradientId}" x1="0%" y1="0%" x2="100%" y2="0%">
|
| 769 |
+
${colors.map((color, index) =>
|
| 770 |
+
`<stop offset="${(index / (colors.length - 1)) * 100}%" style="stop-color:${color.color};stop-opacity:1" />`
|
| 771 |
+
).join('')}
|
| 772 |
+
</linearGradient>
|
| 773 |
+
</defs>
|
| 774 |
+
`;
|
| 775 |
+
backgroundStyle = `url(#${gradientId})`;
|
| 776 |
+
}
|
| 777 |
+
}
|
| 778 |
|
| 779 |
+
// 渲染元素
|
| 780 |
const elementsHTML = slide.elements.map(element => {
|
| 781 |
const x = element.left || 0;
|
| 782 |
const y = element.top || 0;
|
| 783 |
const w = element.width || 100;
|
| 784 |
const h = element.height || 100;
|
| 785 |
+
const rotation = element.rotate || 0;
|
| 786 |
+
|
| 787 |
+
// 变换属性
|
| 788 |
+
const transform = rotation !== 0 ? `transform="rotate(${rotation} ${x + w/2} ${y + h/2})"` : '';
|
| 789 |
|
| 790 |
if (element.type === 'text') {
|
| 791 |
const fontSize = element.fontSize || 16;
|
| 792 |
+
const fontFamily = element.fontName || 'Arial, sans-serif';
|
| 793 |
const color = element.defaultColor || element.color || '#000000';
|
| 794 |
+
const fontWeight = element.bold ? 'bold' : 'normal';
|
| 795 |
+
const fontStyle = element.italic ? 'italic' : 'normal';
|
| 796 |
+
const textDecoration = element.underline ? 'underline' : 'none';
|
| 797 |
+
const textAnchor = element.align === 'center' ? 'middle' : element.align === 'right' ? 'end' : 'start';
|
| 798 |
+
|
| 799 |
+
// 计算文本位置
|
| 800 |
+
let textX = x + 10;
|
| 801 |
+
if (element.align === 'center') textX = x + w/2;
|
| 802 |
+
else if (element.align === 'right') textX = x + w - 10;
|
| 803 |
+
|
| 804 |
+
// 处理多行文本
|
| 805 |
const content = (element.content || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
| 806 |
+
const lines = content.split('\n');
|
| 807 |
|
| 808 |
+
return `
|
| 809 |
+
<g ${transform}>
|
| 810 |
+
${lines.map((line, index) => `
|
| 811 |
+
<text x="${textX}" y="${y + fontSize + (index * fontSize * 1.2)}"
|
| 812 |
+
font-family="${fontFamily}"
|
| 813 |
+
font-size="${fontSize}"
|
| 814 |
+
fill="${color}"
|
| 815 |
+
font-weight="${fontWeight}"
|
| 816 |
+
font-style="${fontStyle}"
|
| 817 |
+
text-decoration="${textDecoration}"
|
| 818 |
+
text-anchor="${textAnchor}">${line}</text>
|
| 819 |
+
`).join('')}
|
| 820 |
+
</g>
|
| 821 |
+
`;
|
| 822 |
}
|
| 823 |
|
| 824 |
if (element.type === 'shape') {
|
|
|
|
| 831 |
const cy = y + h/2;
|
| 832 |
const rx = w/2;
|
| 833 |
const ry = h/2;
|
| 834 |
+
return `<ellipse cx="${cx}" cy="${cy}" rx="${rx}" ry="${ry}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeWidth}" ${transform}/>`;
|
| 835 |
}
|
| 836 |
|
| 837 |
+
// 默认矩形
|
| 838 |
+
const borderRadius = element.borderRadius || 0;
|
| 839 |
+
if (borderRadius > 0) {
|
| 840 |
+
return `<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="${borderRadius}" ry="${borderRadius}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeWidth}" ${transform}/>`;
|
| 841 |
+
}
|
| 842 |
+
return `<rect x="${x}" y="${y}" width="${w}" height="${h}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeWidth}" ${transform}/>`;
|
| 843 |
}
|
| 844 |
|
| 845 |
if (element.type === 'image' && element.src) {
|
| 846 |
+
// 检查是否是base64图片
|
| 847 |
+
if (element.src.startsWith('data:image/')) {
|
| 848 |
+
return `<image x="${x}" y="${y}" width="${w}" height="${h}" href="${element.src}" ${transform}/>`;
|
| 849 |
+
}
|
| 850 |
+
// 外部图片 - 在SVG中可能有跨域问题,显示占位符
|
| 851 |
+
return `<rect x="${x}" y="${y}" width="${w}" height="${h}" fill="#f0f0f0" stroke="#ccc" stroke-width="1" ${transform}/>
|
| 852 |
+
<text x="${x + w/2}" y="${y + h/2}" text-anchor="middle" font-size="12" fill="#666">图片</text>`;
|
| 853 |
}
|
| 854 |
|
| 855 |
return '';
|
| 856 |
+
}).filter(Boolean).join('');
|
| 857 |
|
| 858 |
+
return `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
| 859 |
+
${gradientDefs}
|
| 860 |
<rect width="100%" height="100%" fill="${backgroundStyle}"/>
|
| 861 |
<g>${elementsHTML}</g>
|
|
|
|
|
|
|
|
|
|
| 862 |
</svg>`;
|
| 863 |
}
|
| 864 |
|