diff --git a/app/api/admin/psd-templates/[id]/parse/route.ts b/app/api/admin/psd-templates/[id]/parse/route.ts index e24b328a4..a7252e18e 100644 --- a/app/api/admin/psd-templates/[id]/parse/route.ts +++ b/app/api/admin/psd-templates/[id]/parse/route.ts @@ -147,9 +147,22 @@ function buildInitialTemplateLayers(params: { fontId: matchedFont?.font.id, fontFamily: matchedFont?.font.family, fontLabel: matchedFont?.font.name, - fontSize: Math.max(16, Math.min(layer.height, Math.round(layer.height * 0.78))), - lineHeight: 1.2, - letterSpacing: 0, + fontSize: Math.max( + 10, + Math.round( + Number( + layer.detectedFontSize || + Math.max(16, Math.min(layer.height, Math.round(layer.height * 0.78))) + ) + ) + ), + lineHeight: + Number.isFinite(Number(layer.detectedLineHeight)) && Number(layer.detectedLineHeight) > 0 + ? Number(layer.detectedLineHeight) + : 1.2, + letterSpacing: Number.isFinite(Number(layer.detectedLetterSpacing)) + ? Number(layer.detectedLetterSpacing) + : 0, fontWeight: 500, textColor: "#111111", textAlign: "left", diff --git a/lib/agents/psd-template-parser.ts b/lib/agents/psd-template-parser.ts index c8494761e..00e0876c7 100644 --- a/lib/agents/psd-template-parser.ts +++ b/lib/agents/psd-template-parser.ts @@ -73,6 +73,9 @@ export interface ParsedPsdTemplateLayer { defaultText?: string; detectedFontName?: string; detectedFontNames?: string[]; + detectedFontSize?: number; + detectedLineHeight?: number; + detectedLetterSpacing?: number; } export interface ParsedPsdTemplatePayload { @@ -203,6 +206,38 @@ function extractDetectedFontNames(textProperties?: PsdTextPropertiesLike) { return normalizeDetectedFontNames(candidates); } +function extractTextStyleMetrics(textProperties?: PsdTextPropertiesLike) { + const styleRuns = textProperties?.EngineDict?.StyleRun?.RunArray || []; + const firstValidRun = styleRuns.find((styleRun) => { + const style = styleRun?.StyleSheet?.StyleSheetData; + return Number.isFinite(Number(style?.FontSize)); + }); + + const style = firstValidRun?.StyleSheet?.StyleSheetData; + const fontSizeValue = Number(style?.FontSize); + const leadingValue = Number(style?.Leading); + const trackingValue = Number(style?.Tracking); + + const detectedFontSize = + Number.isFinite(fontSizeValue) && fontSizeValue > 0 ? fontSizeValue : undefined; + const detectedLineHeight = + detectedFontSize && Number.isFinite(leadingValue) && leadingValue > 0 + ? Number((leadingValue / detectedFontSize).toFixed(3)) + : style?.AutoLeading + ? 1.2 + : undefined; + const detectedLetterSpacing = + detectedFontSize && Number.isFinite(trackingValue) + ? Number(((trackingValue / 1000) * detectedFontSize).toFixed(3)) + : undefined; + + return { + detectedFontSize, + detectedLineHeight, + detectedLetterSpacing, + }; +} + function collectPsdLayerEntries( nodes: PsdLayerLike[], ancestors: string[] = [], @@ -246,6 +281,13 @@ function collectPsdLayerEntries( const detectedFontNames = defaultText ? extractDetectedFontNames(node.textProperties) : []; + const textStyleMetrics = defaultText + ? extractTextStyleMetrics(node.textProperties) + : { + detectedFontSize: undefined, + detectedLineHeight: undefined, + detectedLetterSpacing: undefined, + }; result.push({ node, @@ -263,6 +305,9 @@ function collectPsdLayerEntries( defaultText: defaultText || undefined, detectedFontName: detectedFontNames[0] || undefined, detectedFontNames: detectedFontNames.length > 0 ? detectedFontNames : undefined, + detectedFontSize: textStyleMetrics.detectedFontSize, + detectedLineHeight: textStyleMetrics.detectedLineHeight, + detectedLetterSpacing: textStyleMetrics.detectedLetterSpacing, }, }); } diff --git a/lib/agents/psd-template-renderer.ts b/lib/agents/psd-template-renderer.ts index 674e99588..1860f6ca9 100644 --- a/lib/agents/psd-template-renderer.ts +++ b/lib/agents/psd-template-renderer.ts @@ -231,17 +231,27 @@ async function createTextOverlayBuffer(params: { maxLines, } = params; + const normalizedText = String(text || "").replace(/\r\n/g, "\n").replace(/\r/g, "\n"); + const explicitLineCount = Math.max(1, normalizedText.split("\n").length); + const availableHeight = Math.max(8, height - paddingY * 2); + const adjustedFontSize = + explicitLineCount > 1 && fontSize * Math.max(1, lineHeight) * explicitLineCount > availableHeight * 1.18 + ? Math.max( + 10, + Math.floor((availableHeight / (explicitLineCount * Math.max(1, lineHeight))) * 100) / 100 + ) + : fontSize; const availableWidth = Math.max(8, width - paddingX * 2); - const lineHeightPx = Math.max(fontSize, fontSize * lineHeight); + const lineHeightPx = Math.max(adjustedFontSize, adjustedFontSize * lineHeight); const heightLimitedMaxLines = Math.max( 1, - Math.floor((height - paddingY * 2 + fontSize * 0.2) / lineHeightPx) + Math.floor((height - paddingY * 2 + adjustedFontSize * 0.2) / lineHeightPx) ); const resolvedMaxLines = Math.max(1, Math.min(maxLines, heightLimitedMaxLines)); const lines = wrapTextByWidth({ - text, + text: normalizedText, maxWidth: availableWidth, - fontSize, + fontSize: adjustedFontSize, letterSpacing, maxLines: resolvedMaxLines, }); @@ -266,7 +276,8 @@ async function createTextOverlayBuffer(params: { const tspans = lines .map((line, index) => { const y = paddingY + fontSize + index * lineHeightPx; - return `${escapeXml(line)}`; + const baselineY = paddingY + adjustedFontSize + index * lineHeightPx; + return `${escapeXml(line)}`; }) .join(""); @@ -275,7 +286,7 @@ async function createTextOverlayBuffer(params: {