(page)
| 242 | |
| 243 | // Helper: Extract slide data from HTML page |
| 244 | async function extractSlideData(page) { |
| 245 | return await page.evaluate(() => { |
| 246 | const PT_PER_PX = 0.75; |
| 247 | const PX_PER_IN = 96; |
| 248 | |
| 249 | // Fonts that are single-weight and should not have bold applied |
| 250 | // (applying bold causes PowerPoint to use faux bold which makes text wider) |
| 251 | const SINGLE_WEIGHT_FONTS = ['impact']; |
| 252 | |
| 253 | // Helper: Check if a font should skip bold formatting |
| 254 | const shouldSkipBold = (fontFamily) => { |
| 255 | if (!fontFamily) return false; |
| 256 | const normalizedFont = fontFamily.toLowerCase().replace(/['"]/g, '').split(',')[0].trim(); |
| 257 | return SINGLE_WEIGHT_FONTS.includes(normalizedFont); |
| 258 | }; |
| 259 | |
| 260 | // Unit conversion helpers |
| 261 | const pxToInch = (px) => px / PX_PER_IN; |
| 262 | const pxToPoints = (pxStr) => parseFloat(pxStr) * PT_PER_PX; |
| 263 | const rgbToHex = (rgbStr) => { |
| 264 | // Handle transparent backgrounds by defaulting to white |
| 265 | if (rgbStr === 'rgba(0, 0, 0, 0)' || rgbStr === 'transparent') return 'FFFFFF'; |
| 266 | |
| 267 | const match = rgbStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); |
| 268 | if (!match) return 'FFFFFF'; |
| 269 | return match.slice(1).map(n => parseInt(n).toString(16).padStart(2, '0')).join(''); |
| 270 | }; |
| 271 | |
| 272 | const extractAlpha = (rgbStr) => { |
| 273 | const match = rgbStr.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)/); |
| 274 | if (!match || !match[4]) return null; |
| 275 | const alpha = parseFloat(match[4]); |
| 276 | return Math.round((1 - alpha) * 100); |
| 277 | }; |
| 278 | |
| 279 | const applyTextTransform = (text, textTransform) => { |
| 280 | if (textTransform === 'uppercase') return text.toUpperCase(); |
| 281 | if (textTransform === 'lowercase') return text.toLowerCase(); |
| 282 | if (textTransform === 'capitalize') { |
| 283 | return text.replace(/\b\w/g, c => c.toUpperCase()); |
| 284 | } |
| 285 | return text; |
| 286 | }; |
| 287 | |
| 288 | // Extract rotation angle from CSS transform and writing-mode |
| 289 | const getRotation = (transform, writingMode) => { |
| 290 | let angle = 0; |
| 291 | |
| 292 | // Handle writing-mode first |
| 293 | // PowerPoint: 90° = text rotated 90° clockwise (reads top to bottom, letters upright) |
| 294 | // PowerPoint: 270° = text rotated 270° clockwise (reads bottom to top, letters upright) |
| 295 | if (writingMode === 'vertical-rl') { |
| 296 | // vertical-rl alone = text reads top to bottom = 90° in PowerPoint |
| 297 | angle = 90; |
| 298 | } else if (writingMode === 'vertical-lr') { |
| 299 | // vertical-lr alone = text reads bottom to top = 270° in PowerPoint |
| 300 | angle = 270; |
| 301 | } |
no test coverage detected