(function () { function escapeHtml(value) { return String(value === undefined || value === null ? '' : value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function valueOrDefault(value, fallback) { return value === undefined || value === null || value === '' ? fallback : value; } function normalizeLockRatio(value) { var raw = String(value || '').trim(); if (!raw) { return ''; } if (!/^\d+\s*:\s*\d+$/.test(raw)) { return ''; } return raw.replace(/\s+/g, ''); } function parseLockRatio(value) { var normalized = normalizeLockRatio(value); if (!normalized) { return null; } var parts = normalized.split(':'); var width = Number(parts[0]); var height = Number(parts[1]); if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) { return null; } return { label: normalized, ratio: width / height }; } function getDefaultRegionSize(regionType, lockRatio) { var ratio = parseLockRatio(lockRatio); var locked = regionType === 'image' || regionType === 'webpage' || regionType === 'html' || regionType === 'rtmp' || regionType === 'rss' || regionType === 'api'; if (ratio) { if (ratio.ratio >= 1) { var baseWidth = locked ? 420 : 300; return { width: Math.max(12, Math.round(baseWidth)), height: Math.max(12, Math.round(baseWidth / ratio.ratio)) }; } var baseHeight = locked ? 300 : 240; return { width: Math.max(12, Math.round(baseHeight * ratio.ratio)), height: Math.max(12, Math.round(baseHeight)) }; } return { width: locked ? 420 : 300, height: locked ? 240 : 120 }; } function clamp(value, min, max) { return Math.max(min, Math.min(max, value)); } function getRegionName(card) { return String(card.querySelector('[name="region_name[]"]').value || '').trim(); } function syncRegionIdentity(card, value) { var next = String(value || '').trim(); card.querySelector('[name="region_name[]"]').value = next; card.querySelector('[name="region_key[]"]').value = next; card.querySelector('[name="region_label[]"]').value = next; } function readCard(card) { var name = getRegionName(card); return { region_key: name, label: name, region_type: card.querySelector('[name="region_type[]"]').value, font_family: card.querySelector('[name="font_family[]"]').value, lock_ratio: normalizeLockRatio(card.querySelector('[name="region_lock_ratio[]"]').value), x: Number(card.querySelector('[name="region_x[]"]').value || 0), y: Number(card.querySelector('[name="region_y[]"]').value || 0), width: Number(card.querySelector('[name="region_width[]"]').value || 0), height: Number(card.querySelector('[name="region_height[]"]').value || 0), z_index: Number(card.querySelector('[name="region_z[]"]').value || 0) }; } function writeCard(card, values) { if (values.region_name !== undefined) { syncRegionIdentity(card, values.region_name); } else if (values.region_key !== undefined) { syncRegionIdentity(card, values.region_key); } else if (values.label !== undefined) { syncRegionIdentity(card, values.label); } if (values.region_type !== undefined) { card.querySelector('[name="region_type[]"]').value = values.region_type; } if (values.font_family !== undefined) { card.querySelector('[name="font_family[]"]').value = values.font_family; } if (values.lock_ratio !== undefined) { card.querySelector('[name="region_lock_ratio[]"]').value = normalizeLockRatio(values.lock_ratio); } if (values.x !== undefined) { card.querySelector('[name="region_x[]"]').value = Math.round(values.x); } if (values.y !== undefined) { card.querySelector('[name="region_y[]"]').value = Math.round(values.y); } if (values.width !== undefined) { card.querySelector('[name="region_width[]"]').value = Math.round(values.width); } if (values.height !== undefined) { card.querySelector('[name="region_height[]"]').value = Math.round(values.height); } if (values.z_index !== undefined) { card.querySelector('[name="region_z[]"]').value = Math.round(values.z_index); } } function clampRegion(region, size, minSize) { var bounds = size || { width: 1920, height: 1080 }; var minimum = minSize || 12; var x = clamp(region.x, 0, bounds.width - minimum); var y = clamp(region.y, 0, bounds.height - minimum); var width = Math.max(minimum, region.width); var height = Math.max(minimum, region.height); if (x + width > bounds.width) { width = bounds.width - x; } if (y + height > bounds.height) { height = bounds.height - y; } return { x: Math.round(x), y: Math.round(y), width: Math.round(Math.max(minimum, width)), height: Math.round(Math.max(minimum, height)) }; } function getOverlayRect(overlay) { return overlay.getBoundingClientRect(); } function toCanvasPoint(event, overlay, size) { var rect = getOverlayRect(overlay); var canvasSize = size || { width: 1920, height: 1080 }; var x = clamp(event.clientX - rect.left, 0, rect.width); var y = clamp(event.clientY - rect.top, 0, rect.height); return { x: Math.round((x / Math.max(rect.width, 1)) * canvasSize.width), y: Math.round((y / Math.max(rect.height, 1)) * canvasSize.height) }; } function canvasRectToPixels(region, overlay, size) { var rect = getOverlayRect(overlay); var canvasSize = size || { width: 1920, height: 1080 }; return { left: (region.x / canvasSize.width) * rect.width, top: (region.y / canvasSize.height) * rect.height, width: (region.width / canvasSize.width) * rect.width, height: (region.height / canvasSize.height) * rect.height }; } window.templateDesignerUtils = { escapeHtml: escapeHtml, valueOrDefault: valueOrDefault, clamp: clamp, getRegionName: getRegionName, syncRegionIdentity: syncRegionIdentity, readCard: readCard, writeCard: writeCard, clampRegion: clampRegion, getOverlayRect: getOverlayRect, toCanvasPoint: toCanvasPoint, canvasRectToPixels: canvasRectToPixels, normalizeLockRatio: normalizeLockRatio, parseLockRatio: parseLockRatio, getDefaultRegionSize: getDefaultRegionSize }; }());