CatPtain commited on
Commit
1d4c41b
·
verified ·
1 Parent(s): 900310e

Upload screenshotService.js

Browse files
backend/src/services/screenshotService.js CHANGED
@@ -3,10 +3,15 @@ import puppeteer from 'puppeteer';
3
  class ScreenshotService {
4
  async generateScreenshot(htmlContent, options = {}) {
5
  let browser = null;
 
6
 
7
  try {
8
  console.log('Starting Puppeteer browser...');
9
 
 
 
 
 
10
  // 更适合容器环境的Puppeteer配置
11
  browser = await puppeteer.launch({
12
  headless: 'new',
@@ -45,32 +50,17 @@ class ScreenshotService {
45
  const page = await browser.newPage();
46
  console.log('New page created');
47
 
48
- // PPT标准尺寸 (4:3 比例)
49
- const pptWidth = 1000;
50
- const pptHeight = 750;
51
-
52
- // 设置页面视窗大小,添加足够的边距以避免裁切问题
53
  await page.setViewport({
54
- width: pptWidth + 100, // 添加边距
55
- height: pptHeight + 100, // 添加边距
56
  deviceScaleFactor: 2 // 高分辨率
57
  });
58
 
59
- console.log('Viewport set');
60
-
61
- // 修改HTML内容,确保PPT容器居中且尺寸精确
62
- const modifiedHtmlContent = htmlContent.replace(
63
- '<div class="slide-container"',
64
- `<div class="slide-container" style="width: ${pptWidth}px; height: ${pptHeight}px; position: absolute; left: 50px; top: 50px; margin: 0; transform: none; box-shadow: 0 2px 10px rgba(0,0,0,0.1);"`
65
- ).replace(
66
- '.viewport-container {',
67
- `.viewport-container {
68
- position: relative;
69
- width: ${pptWidth + 100}px;
70
- height: ${pptHeight + 100}px;
71
- background-color: transparent;
72
- display: block;`
73
- );
74
 
75
  // 设置HTML内容
76
  await page.setContent(modifiedHtmlContent, {
@@ -81,19 +71,68 @@ class ScreenshotService {
81
  console.log('HTML content set');
82
 
83
  // 等待渲染完成
84
- await page.waitForTimeout(1000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
- console.log('Taking screenshot...');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
- // 精确截取PPT区域
 
 
89
  const screenshot = await page.screenshot({
90
  type: 'jpeg',
91
- quality: 90, // 提高质量
 
92
  clip: {
93
- x: 50,
94
- y: 50,
95
- width: pptWidth,
96
- height: pptHeight
97
  },
98
  timeout: 10000
99
  });
@@ -105,7 +144,7 @@ class ScreenshotService {
105
  console.error('Screenshot generation error:', error);
106
 
107
  // 返回一个错误图片而不是抛出��常
108
- return this.generateErrorImage(error.message);
109
  } finally {
110
  if (browser) {
111
  try {
@@ -118,19 +157,192 @@ class ScreenshotService {
118
  }
119
  }
120
 
121
- // 生成错误提示图片 - 也使用精确的PPT尺寸
122
- generateErrorImage(errorMessage = '截图生成失败') {
123
- const pptWidth = 1000;
124
- const pptHeight = 750;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
  const canvas = `
127
- <svg width="${pptWidth}" height="${pptHeight}" xmlns="http://www.w3.org/2000/svg">
128
- <rect width="${pptWidth}" height="${pptHeight}" fill="#f8f9fa"/>
129
- <rect x="50" y="50" width="${pptWidth - 100}" height="${pptHeight - 100}" fill="white" stroke="#dee2e6" stroke-width="2"/>
130
- <text x="${pptWidth / 2}" y="${pptHeight / 2 - 20}" text-anchor="middle" font-family="Arial" font-size="24" fill="#6c757d">
131
  截图生成失败
132
  </text>
133
- <text x="${pptWidth / 2}" y="${pptHeight / 2 + 20}" text-anchor="middle" font-family="Arial" font-size="16" fill="#868e96">
134
  ${errorMessage}
135
  </text>
136
  </svg>
 
3
  class ScreenshotService {
4
  async generateScreenshot(htmlContent, options = {}) {
5
  let browser = null;
6
+ let pptDimensions = { width: 1000, height: 750 }; // 默认值
7
 
8
  try {
9
  console.log('Starting Puppeteer browser...');
10
 
11
+ // 提取PPT的实际尺寸和长宽比
12
+ pptDimensions = this.extractPptDimensions(htmlContent);
13
+ console.log('Extracted PPT dimensions:', pptDimensions);
14
+
15
  // 更适合容器环境的Puppeteer配置
16
  browser = await puppeteer.launch({
17
  headless: 'new',
 
50
  const page = await browser.newPage();
51
  console.log('New page created');
52
 
53
+ // 设置页面视窗大小为PPT的精确尺寸
 
 
 
 
54
  await page.setViewport({
55
+ width: pptDimensions.width,
56
+ height: pptDimensions.height,
57
  deviceScaleFactor: 2 // 高分辨率
58
  });
59
 
60
+ console.log(`Viewport set to exact PPT size: ${pptDimensions.width}x${pptDimensions.height}`);
61
+
62
+ // 修改HTML内容,确保PPT容器精确匹配视窗尺寸,消除白边
63
+ const modifiedHtmlContent = this.modifyHtmlForScreenshot(htmlContent, pptDimensions);
 
 
 
 
 
 
 
 
 
 
 
64
 
65
  // 设置HTML内容
66
  await page.setContent(modifiedHtmlContent, {
 
71
  console.log('HTML content set');
72
 
73
  // 等待渲染完成
74
+ await page.waitForTimeout(1500);
75
+
76
+ // 验证页面中的PPT容器尺寸是否精确匹配
77
+ const actualDimensions = await page.evaluate(() => {
78
+ const container = document.getElementById('slideContainer');
79
+ const body = document.body;
80
+ const html = document.documentElement;
81
+
82
+ if (container) {
83
+ const rect = container.getBoundingClientRect();
84
+ return {
85
+ container: {
86
+ width: rect.width,
87
+ height: rect.height,
88
+ left: rect.left,
89
+ top: rect.top
90
+ },
91
+ body: {
92
+ width: body.offsetWidth,
93
+ height: body.offsetHeight
94
+ },
95
+ html: {
96
+ width: html.offsetWidth,
97
+ height: html.offsetHeight
98
+ },
99
+ viewport: {
100
+ width: window.innerWidth,
101
+ height: window.innerHeight
102
+ }
103
+ };
104
+ }
105
+ return null;
106
+ });
107
 
108
+ console.log('Page dimensions verification:', actualDimensions);
109
+
110
+ // 确保页面尺寸完全匹配PPT尺寸
111
+ if (actualDimensions && (
112
+ Math.abs(actualDimensions.viewport.width - pptDimensions.width) > 1 ||
113
+ Math.abs(actualDimensions.viewport.height - pptDimensions.height) > 1
114
+ )) {
115
+ console.warn('Page dimensions do not match PPT dimensions exactly, adjusting...');
116
+ await page.setViewport({
117
+ width: pptDimensions.width,
118
+ height: pptDimensions.height,
119
+ deviceScaleFactor: 2
120
+ });
121
+ await page.waitForTimeout(500);
122
+ }
123
 
124
+ console.log('Taking precise screenshot...');
125
+
126
+ // 截取精确的PPT内容区域,不包含任何白边
127
  const screenshot = await page.screenshot({
128
  type: 'jpeg',
129
+ quality: 95,
130
+ // 使用clip精确截取PPT内容区域
131
  clip: {
132
+ x: 0,
133
+ y: 0,
134
+ width: pptDimensions.width,
135
+ height: pptDimensions.height
136
  },
137
  timeout: 10000
138
  });
 
144
  console.error('Screenshot generation error:', error);
145
 
146
  // 返回一个错误图片而不是抛出��常
147
+ return this.generateErrorImage(error.message, pptDimensions);
148
  } finally {
149
  if (browser) {
150
  try {
 
157
  }
158
  }
159
 
160
+ // 从HTML内容中提取PPT尺寸信息和长宽比
161
+ extractPptDimensions(htmlContent) {
162
+ // 方法1:从JavaScript变量中提取尺寸 - 这是最准确的方法
163
+ const dimensionsMatch = htmlContent.match(/window\.PPT_DIMENSIONS\s*=\s*{\s*width:\s*(\d+),\s*height:\s*(\d+)\s*}/);
164
+ if (dimensionsMatch) {
165
+ const width = parseInt(dimensionsMatch[1]);
166
+ const height = parseInt(dimensionsMatch[2]);
167
+ console.log('Extracted dimensions from JavaScript:', { width, height });
168
+ return { width, height };
169
+ }
170
+
171
+ // 方法2:从CSS样式中提取尺寸
172
+ const cssMatch = htmlContent.match(/\.slide-container\s*{\s*[^}]*width:\s*(\d+)px;[^}]*height:\s*(\d+)px;/s);
173
+ if (cssMatch) {
174
+ const width = parseInt(cssMatch[1]);
175
+ const height = parseInt(cssMatch[2]);
176
+ console.log('Extracted dimensions from CSS:', { width, height });
177
+ return { width, height };
178
+ }
179
+
180
+ // 方法3:从内联样式中提取尺寸
181
+ const inlineMatch = htmlContent.match(/width:\s*(\d+)px[^;]*;\s*height:\s*(\d+)px/);
182
+ if (inlineMatch) {
183
+ const width = parseInt(inlineMatch[1]);
184
+ const height = parseInt(inlineMatch[2]);
185
+ console.log('Extracted dimensions from inline styles:', { width, height });
186
+ return { width, height };
187
+ }
188
+
189
+ // 默认尺寸 - 使用标准PPT 16:9 比例
190
+ console.warn('Could not extract PPT dimensions from HTML, using default 16:9 ratio');
191
+ return { width: 1280, height: 720 }; // 16:9 标准比例
192
+ }
193
+
194
+ // 修改HTML内容以适应截图需求,确保零白边
195
+ modifyHtmlForScreenshot(htmlContent, pptDimensions) {
196
+ const { width, height } = pptDimensions;
197
+
198
+ // 创建完全匹配PPT尺寸的HTML,消除所有可能的白边
199
+ let modifiedHtml = htmlContent;
200
+
201
+ // 设置严格的CSS样式,确保页面尺寸完全匹配PPT尺寸
202
+ modifiedHtml = modifiedHtml.replace(
203
+ /<style>[\s\S]*?<\/style>/,
204
+ `<style>
205
+ * {
206
+ margin: 0 !important;
207
+ padding: 0 !important;
208
+ box-sizing: border-box !important;
209
+ border: none !important;
210
+ }
211
+
212
+ html {
213
+ width: ${width}px !important;
214
+ height: ${height}px !important;
215
+ overflow: hidden !important;
216
+ background: transparent !important;
217
+ min-width: ${width}px !important;
218
+ min-height: ${height}px !important;
219
+ max-width: ${width}px !important;
220
+ max-height: ${height}px !important;
221
+ }
222
+
223
+ body {
224
+ width: ${width}px !important;
225
+ height: ${height}px !important;
226
+ overflow: hidden !important;
227
+ font-family: 'Microsoft YaHei', Arial, sans-serif !important;
228
+ background: transparent !important;
229
+ position: absolute !important;
230
+ top: 0 !important;
231
+ left: 0 !important;
232
+ margin: 0 !important;
233
+ padding: 0 !important;
234
+ min-width: ${width}px !important;
235
+ min-height: ${height}px !important;
236
+ max-width: ${width}px !important;
237
+ max-height: ${height}px !important;
238
+ }
239
+
240
+ .slide-container {
241
+ width: ${width}px !important;
242
+ height: ${height}px !important;
243
+ position: absolute !important;
244
+ top: 0 !important;
245
+ left: 0 !important;
246
+ margin: 0 !important;
247
+ padding: 0 !important;
248
+ overflow: hidden !important;
249
+ background-color: var(--slide-bg-color, #ffffff) !important;
250
+ transform: none !important;
251
+ transform-origin: top left !important;
252
+ min-width: ${width}px !important;
253
+ min-height: ${height}px !important;
254
+ max-width: ${width}px !important;
255
+ max-height: ${height}px !important;
256
+ }
257
+
258
+ /* 确保所有元素都相对于slide-container定位 */
259
+ .slide-container > * {
260
+ position: absolute !important;
261
+ }
262
+
263
+ /* 确保背景图片也严格匹配尺寸 */
264
+ .slide-container::before {
265
+ width: ${width}px !important;
266
+ height: ${height}px !important;
267
+ top: 0 !important;
268
+ left: 0 !important;
269
+ margin: 0 !important;
270
+ padding: 0 !important;
271
+ }
272
+ </style>`
273
+ );
274
+
275
+ // 移除所有可能影响尺寸的JavaScript,确保固定尺寸
276
+ modifiedHtml = modifiedHtml.replace(
277
+ /<script>[\s\S]*?<\/script>/g,
278
+ `<script>
279
+ // 截图模式 - 固定尺寸,消除白边
280
+ console.log('Screenshot mode - exact dimensions: ${width}x${height}');
281
+
282
+ // 确保页面加载完成后设置精确的尺寸
283
+ window.addEventListener('load', function() {
284
+ // 设置容器精确尺寸
285
+ const container = document.getElementById('slideContainer');
286
+ if (container) {
287
+ container.style.cssText = \`
288
+ width: ${width}px !important;
289
+ height: ${height}px !important;
290
+ position: absolute !important;
291
+ top: 0 !important;
292
+ left: 0 !important;
293
+ margin: 0 !important;
294
+ padding: 0 !important;
295
+ transform: none !important;
296
+ overflow: hidden !important;
297
+ \`;
298
+ }
299
+
300
+ // 设置body和html精确尺寸
301
+ document.body.style.cssText = \`
302
+ width: ${width}px !important;
303
+ height: ${height}px !important;
304
+ margin: 0 !important;
305
+ padding: 0 !important;
306
+ overflow: hidden !important;
307
+ position: absolute !important;
308
+ top: 0 !important;
309
+ left: 0 !important;
310
+ \`;
311
+
312
+ document.documentElement.style.cssText = \`
313
+ width: ${width}px !important;
314
+ height: ${height}px !important;
315
+ margin: 0 !important;
316
+ padding: 0 !important;
317
+ overflow: hidden !important;
318
+ \`;
319
+
320
+ console.log('Exact dimensions applied for screenshot');
321
+ });
322
+
323
+ // 禁用所有可能的缩放和调整
324
+ window.addEventListener('resize', function(e) {
325
+ e.preventDefault();
326
+ e.stopPropagation();
327
+ });
328
+ </script>`
329
+ );
330
+
331
+ return modifiedHtml;
332
+ }
333
+
334
+ // 生成错误提示图片 - 使用动态尺寸
335
+ generateErrorImage(errorMessage = '截图生成失败', dimensions = { width: 1280, height: 720 }) {
336
+ const { width, height } = dimensions;
337
 
338
  const canvas = `
339
+ <svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
340
+ <rect width="${width}" height="${height}" fill="#f8f9fa"/>
341
+ <rect x="50" y="50" width="${width - 100}" height="${height - 100}" fill="white" stroke="#dee2e6" stroke-width="2"/>
342
+ <text x="${width / 2}" y="${height / 2 - 20}" text-anchor="middle" font-family="Arial" font-size="24" fill="#6c757d">
343
  截图生成失败
344
  </text>
345
+ <text x="${width / 2}" y="${height / 2 + 20}" text-anchor="middle" font-family="Arial" font-size="16" fill="#868e96">
346
  ${errorMessage}
347
  </text>
348
  </svg>