// Pure thumbnail preview payload helpers shared by routes and screenshot capture. const { escapeHtml, renderEditorJsContent, sanitizeFontFamily, sanitizeFontSize, sanitizeTextColor } = require('#src/player/render-helpers'); function normalizeBaseUrl(baseUrl) { return String(baseUrl || '').trim().replace(/\/$/, ''); } function resolveAssetUrl(baseUrl, value) { const raw = String(value || '').trim(); if (!raw) { return ''; } if (/^(?:https?:)?\/\//i.test(raw) || raw.startsWith('data:')) { return raw; } const normalizedBaseUrl = normalizeBaseUrl(baseUrl); if (!normalizedBaseUrl) { return raw; } if (raw.startsWith('/')) { return normalizedBaseUrl + raw; } return normalizedBaseUrl + '/' + raw.replace(/^\/+/, ''); } function getThumbnailCanvasSize(slide) { const template = slide && slide.template ? slide.template : null; return { width: Math.max(1, Number(template && template.canvas_size_width ? template.canvas_size_width : 1920)), height: Math.max(1, Number(template && template.canvas_size_height ? template.canvas_size_height : 1080)) }; } function getRegionContent(slide, region) { const content = slide && slide.content && slide.content[region.region_key] ? slide.content[region.region_key] : {}; return content && typeof content === 'object' ? content : { value: content }; } function buildThumbnailRegionStyle(region, canvasWidth, canvasHeight) { const left = Number.isFinite(Number(region && region.x)) && canvasWidth > 0 ? (Number(region.x) / canvasWidth) * 100 : 0; const top = Number.isFinite(Number(region && region.y)) && canvasHeight > 0 ? (Number(region.y) / canvasHeight) * 100 : 0; const width = Number.isFinite(Number(region && region.width)) && canvasWidth > 0 ? (Number(region.width) / canvasWidth) * 100 : 0; const height = Number.isFinite(Number(region && region.height)) && canvasHeight > 0 ? (Number(region.height) / canvasHeight) * 100 : 0; return 'left:' + left + '%;top:' + top + '%;width:' + width + '%;height:' + height + '%;z-index:' + Number(region && region.z_index || 0) + ';'; } function hasVisibleContent(html) { return Boolean(String(html || '').replace(/<[^>]+>/g, '').trim()); } function buildTextRegionMarkup(region, regionContent) { const fontFamily = sanitizeFontFamily(regionContent.font_family || region.font_family); const fontSize = sanitizeFontSize(regionContent.font_size || region.font_size); const fontColor = sanitizeTextColor(regionContent.font_color || region.font_color); const renderedBody = renderEditorJsContent(regionContent.value || ''); if (!hasVisibleContent(renderedBody)) { return ''; } return '
' + renderedBody + '
'; } function buildRegionInnerHtml(region, regionContent, baseUrl) { const regionType = String(regionContent.type || region.region_type || 'text').trim().toLowerCase(); const rawValue = regionContent && regionContent.value !== undefined ? regionContent.value : ''; if (regionType === 'image') { const src = resolveAssetUrl(baseUrl, rawValue); return src ? '
' + escapeHtml(region.label || region.region_key || 'image') + '
' : ''; } if (regionType === 'video') { const src = resolveAssetUrl(baseUrl, rawValue); return src ? '
' : ''; } if (regionType === 'webpage') { const src = resolveAssetUrl(baseUrl, rawValue); return src ? '
' : ''; } if (regionType === 'qr-code') { const src = String(regionContent.qr_preview || '').trim(); const borderRadius = Math.max(0, Math.round(Number(regionContent.qr_border_radius || 0))); const radiusStyle = borderRadius > 0 ? ' style="border-radius:' + borderRadius + 'px;overflow:hidden;"' : ''; return src ? '
' + escapeHtml(region.label || region.region_key || 'qr code') + '
' : ''; } if (regionType === 'html') { const html = String(rawValue || '').trim(); return html ? '
' : ''; } if (regionType === 'rtmp') { const label = String(rawValue || '').trim() || 'RTMP source'; return '
' + escapeHtml(label) + '
'; } return buildTextRegionMarkup(region, regionContent); } function buildThumbnailPreviewMarkup(slide, baseUrl) { const template = slide && slide.template ? slide.template : null; if (!template) { return ''; } const canvasSize = getThumbnailCanvasSize(slide); const normalizedBaseUrl = normalizeBaseUrl(baseUrl); return (Array.isArray(template.regions) ? template.regions : []).map(function (region) { const regionContent = getRegionContent(slide, region); const previewRegion = Object.assign({}, region, { baseStyle: buildThumbnailRegionStyle(region, canvasSize.width, canvasSize.height), pixelWidth: Math.max(1, Math.round(Number(region && region.width || 0) || 1)), pixelHeight: Math.max(1, Math.round(Number(region && region.height || 0) || 1)) }); return buildRegionInnerHtml(previewRegion, regionContent, normalizedBaseUrl); }).join(''); } function buildThumbnailPreviewPayload(slide, options) { const template = slide && slide.template ? slide.template : null; const canvasSize = getThumbnailCanvasSize(slide); return { thumbnailPreview: true, canvasWidth: canvasSize.width, canvasHeight: canvasSize.height, backgroundColor: template && template.background_color ? String(template.background_color) : '#111111', backgroundImagePath: template && template.background_image_path ? String(template.background_image_path) : '', fontStylesheetHref: String(options && options.fontStylesheetHref || '').trim(), html: buildThumbnailPreviewMarkup(slide, options && options.baseUrl) }; } module.exports = { buildThumbnailPreviewPayload: buildThumbnailPreviewPayload, buildThumbnailPreviewMarkup: buildThumbnailPreviewMarkup };