fix: improve psd template text rendering
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-04-15 21:15:43 +08:00
parent bc8c3084fe
commit 71997086e1
6 changed files with 71 additions and 25 deletions

View File

@ -70,6 +70,7 @@ import {
isAgentPsdImageLayerSlot,
isAgentPsdTextLayerSlot,
parseAgentPsdTemplates,
resolveAgentPsdTextLayerEffectiveAlign,
} from "@/lib/agents/psd-template-config";
import {
defaultAgentPsdTemplateSecondaryCategoryConfig,
@ -992,9 +993,11 @@ function getLayerFrameStyle(
function getResolvedTextPreview(
layer: AgentPsdTemplateTextLayerSlot,
value: string
value?: string
) {
return value || layer.placeholder || layer.layerName;
return typeof value === "string"
? value
: layer.placeholder || layer.layerName;
}
function getResolvedTextLayerColor(
@ -1023,10 +1026,11 @@ function getTextLayerRenderFrame(
});
const width = Math.max(layer.width, resolvedLayout.contentWidth);
const height = Math.max(layer.height, resolvedLayout.contentHeight);
const textAlign = resolveAgentPsdTextLayerEffectiveAlign(layer);
const offsetX =
layer.textAlign === "right"
textAlign === "right"
? layer.width - width
: layer.textAlign === "center"
: textAlign === "center"
? (layer.width - width) / 2
: 0;
@ -1046,16 +1050,17 @@ function renderTextLayerPreviewContent(
) {
const resolvedText = getResolvedTextPreview(layer, value);
const resolvedLayout = getTextLayerRenderFrame(layer, resolvedText);
const textAlign = resolveAgentPsdTextLayerEffectiveAlign(layer);
const textAnchor =
layer.textAlign === "center"
textAlign === "center"
? "middle"
: layer.textAlign === "right"
: textAlign === "right"
? "end"
: "start";
const textX =
layer.textAlign === "center"
textAlign === "center"
? resolvedLayout.width / 2
: layer.textAlign === "right"
: textAlign === "right"
? resolvedLayout.width - layer.paddingX
: layer.paddingX;
@ -3043,7 +3048,7 @@ export function AgentPsdTemplateEditorDialog({
}
const value =
effectiveTextAssignments[layer.id] || "";
effectiveTextAssignments[layer.id] ?? "";
const resolvedTextColor =
effectiveTextColorAssignments[layer.id] ||
layer.textColor;
@ -3076,7 +3081,7 @@ export function AgentPsdTemplateEditorDialog({
const textFrame = getTextLayerRenderFrame(
layer,
value || layer.placeholder || layer.layerName
value
);
return (
@ -3092,7 +3097,7 @@ export function AgentPsdTemplateEditorDialog({
>
{renderTextLayerPreviewContent(
layer,
value || layer.placeholder || layer.layerName,
value,
resolvedTextColor
)}
</div>
@ -3243,7 +3248,7 @@ export function AgentPsdTemplateEditorDialog({
isSelected
);
const currentValue =
effectiveTextAssignments[layer.id] || "";
effectiveTextAssignments[layer.id] ?? "";
const resolvedTextColor =
effectiveTextColorAssignments[layer.id] ||
layer.textColor;
@ -3496,7 +3501,7 @@ export function AgentPsdTemplateEditorDialog({
}
rows={4}
value={
effectiveTextAssignments[selectedTextLayer.id] || ""
effectiveTextAssignments[selectedTextLayer.id] ?? ""
}
/>
<div className="flex flex-wrap gap-x-4 gap-y-2 rounded-xl bg-white/[0.02] px-4 py-3 text-white/60 text-xs shadow-inner">

View File

@ -171,8 +171,7 @@ export function AgentWorkflowMaterialPicker({
recommendedImageIds.every((id) => selectedSet.has(id));
const isAutoSelectedResult =
Boolean(hasSystemRecommendedSelection) && !hasManualSelectionInteraction;
const canTogglePreviewMode =
totalImageCount > 0 && selectedGalleryCount > 0 && selectedGalleryCount < totalImageCount;
const canTogglePreviewMode = totalImageCount > 0;
const visibleImages = useMemo(() => {
if (!galleryImages.length) {
return [];
@ -201,8 +200,8 @@ export function AgentWorkflowMaterialPicker({
if (hasManualSelectionInteraction) {
return;
}
setPreviewDisplayMode(hasSystemRecommendedSelection ? "selected" : "all");
}, [hasManualSelectionInteraction, hasSystemRecommendedSelection, previewResetKey]);
setPreviewDisplayMode("all");
}, [hasManualSelectionInteraction, previewResetKey]);
const hasFetchSuccess = !disabled && !isLoading && !errorText && Boolean(result);

View File

@ -95,9 +95,12 @@ export async function executeAgentPsdTemplateRenderRequest(
}
if (isAgentPsdTextLayerSlot(layer)) {
const nextText = String(body.textAssignments[layer.id] || layer.defaultText || "").trim();
if (nextText) {
textAssignments[layer.id] = nextText;
if (
Object.prototype.hasOwnProperty.call(body.textAssignments, layer.id)
) {
textAssignments[layer.id] = String(
body.textAssignments[layer.id] ?? ""
).trim();
}
textColorAssignments[layer.id] = normalizeHexColor(
body.textColorAssignments[layer.id] || layer.textColor,
@ -112,7 +115,8 @@ export async function executeAgentPsdTemplateRenderRequest(
layer.isRequired &&
(isAgentPsdImageLayerSlot(layer)
? !imageAssignments[layer.id]
: !textAssignments[layer.id])
: !Object.prototype.hasOwnProperty.call(textAssignments, layer.id) &&
!String(layer.defaultText || "").trim())
);
if (missingRequiredLayer) {
throw new Error(`缺少必传素材:${missingRequiredLayer.layerName}`);

View File

@ -289,6 +289,27 @@ export function normalizeAgentPsdTextAlign(value: unknown): AgentPsdTextAlign {
return "left";
}
export function resolveAgentPsdTextLayerEffectiveAlign(
layer: Pick<
AgentPsdTemplateTextLayerSlot,
"paddingX" | "styleSource" | "textAlign" | "width"
>
): AgentPsdTextAlign {
const textAlign = normalizeAgentPsdTextAlign(layer.textAlign);
const paddingX = normalizePositiveInteger(layer.paddingX);
const width = normalizePositiveInteger(layer.width);
if (
layer.styleSource !== "custom" &&
textAlign === "center" &&
paddingX <= Math.max(2, Math.round(width * 0.02))
) {
return "left";
}
return textAlign;
}
export function normalizeAgentPsdTextStyleSource(value: unknown): AgentPsdTextStyleSource {
return normalizeText(value) === "custom" ? "custom" : "auto";
}

View File

@ -6,6 +6,7 @@ import {
hasAgentPsdTemplateStoredLayerSource,
isAgentPsdImageLayerSlot,
isAgentPsdTextLayerSlot,
resolveAgentPsdTextLayerEffectiveAlign,
type AgentPsdTemplate,
} from "@/lib/agents/psd-template-config";
import type { AgentPsdFont } from "@/lib/agents/psd-font-config";
@ -527,17 +528,30 @@ export async function renderAgentPsdTemplate(params: {
}
if (isAgentPsdTextLayerSlot(layer)) {
const text = String(textAssignments[layer.id] || layer.defaultText || "").trim();
const hasTextAssignment = Object.prototype.hasOwnProperty.call(
textAssignments,
layer.id
);
const defaultText = String(layer.defaultText || "").trim();
const text = String(
hasTextAssignment ? textAssignments[layer.id] ?? "" : defaultText
).trim();
const textColor = normalizeHexColor(
textColorAssignments[layer.id] || layer.textColor,
layer.textColor
);
const hasColorChange = normalizeHexColor(textColor, layer.textColor) !== normalizeHexColor(layer.textColor);
if (!text || (text === defaultText && !hasColorChange)) {
if (!text) {
if (!hasTextAssignment) {
await appendStoredLayer(layer);
}
continue;
}
if (text === defaultText && !hasColorChange) {
await appendStoredLayer(layer);
continue;
}
const textAlign = resolveAgentPsdTextLayerEffectiveAlign(layer);
const font = layer.fontId ? fontMap.get(layer.fontId) : undefined;
const fontBuffer = font?.fileUrl
@ -587,7 +601,7 @@ export async function renderAgentPsdTemplate(params: {
lineHeight: layer.lineHeight,
letterSpacing: layer.letterSpacing,
textColor,
textAlign: layer.textAlign,
textAlign,
paddingX: layer.paddingX,
paddingY: layer.paddingY,
maxLines: layer.maxLines,
@ -612,7 +626,7 @@ export async function renderAgentPsdTemplate(params: {
layerHeight: layer.height,
contentWidth: overlayLayout.contentWidth,
contentHeight: overlayLayout.contentHeight,
textAlign: layer.textAlign,
textAlign,
topOffset: -overlayLayout.visualOffsetY,
});

View File

@ -272,9 +272,12 @@ export function analyzeTextLayerPixels(params: {
const rightGap = Math.max(0, width - maxX - 1);
const centerTolerance = Math.max(4, width * 0.06);
const edgeTolerance = Math.max(6, width * 0.08);
const hasReliableCenterPadding =
leftGap >= edgeTolerance && rightGap >= edgeTolerance;
const contentCenterX = (minX + maxX) / 2;
const stageCenterX = (width - 1) / 2;
const inferredTextAlign: AgentPsdTextAlign =
hasReliableCenterPadding &&
Math.abs(contentCenterX - stageCenterX) <= centerTolerance &&
Math.abs(leftGap - rightGap) <= edgeTolerance
? "center"