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: {