fix: preserve psd text and layer exports
This commit is contained in:
parent
fb6b2170bd
commit
bc432ffb44
@ -147,8 +147,14 @@ function buildInitialTemplateLayers(params: {
|
||||
detectedFontName: layer.detectedFontName,
|
||||
detectedFontNames: layer.detectedFontNames,
|
||||
fontId: matchedFont?.font.id,
|
||||
fontFamily: matchedFont?.font.family,
|
||||
fontLabel: matchedFont?.font.name,
|
||||
fontFamily:
|
||||
matchedFont?.font.family ||
|
||||
layer.detectedFontNames?.[0] ||
|
||||
layer.detectedFontName,
|
||||
fontLabel:
|
||||
matchedFont?.font.name ||
|
||||
layer.detectedFontNames?.[0] ||
|
||||
layer.detectedFontName,
|
||||
fontFileUrl: matchedFont?.font.fileUrl,
|
||||
fontContentType: matchedFont?.font.contentType,
|
||||
fontSize: Math.max(
|
||||
|
||||
@ -133,18 +133,58 @@ function buildPreviewFontFaces(template: AgentPsdTemplate | null) {
|
||||
if (!isAgentPsdTextLayerSlot(layer) || !layer.fontFamily || !layer.fontFileUrl) {
|
||||
continue;
|
||||
}
|
||||
if (rules.has(layer.fontFamily)) {
|
||||
continue;
|
||||
}
|
||||
rules.set(
|
||||
layer.fontFamily,
|
||||
`@font-face { font-family: "${layer.fontFamily}"; src: url("${layer.fontFileUrl}") format("${layer.fontContentType?.includes("woff2") ? "woff2" : layer.fontContentType?.includes("woff") ? "woff" : layer.fontContentType?.includes("otf") ? "opentype" : "truetype"}"); font-display: swap; }`
|
||||
const familyAliases = Array.from(
|
||||
new Set(
|
||||
[
|
||||
layer.fontFamily,
|
||||
...(layer.detectedFontNames || []),
|
||||
layer.detectedFontName,
|
||||
]
|
||||
.map((family) => String(family || "").trim())
|
||||
.filter(Boolean)
|
||||
)
|
||||
);
|
||||
|
||||
for (const familyAlias of familyAliases) {
|
||||
if (rules.has(familyAlias)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rules.set(
|
||||
familyAlias,
|
||||
`@font-face { font-family: "${familyAlias}"; src: url("${layer.fontFileUrl}") format("${layer.fontContentType?.includes("woff2") ? "woff2" : layer.fontContentType?.includes("woff") ? "woff" : layer.fontContentType?.includes("otf") ? "opentype" : "truetype"}"); font-display: swap; }`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(rules.values()).join("\n");
|
||||
}
|
||||
|
||||
function buildTextLayerFontFamily(layer: AgentPsdTemplateTextLayerSlot) {
|
||||
const families = Array.from(
|
||||
new Set(
|
||||
[
|
||||
layer.fontFamily,
|
||||
...(layer.detectedFontNames || []),
|
||||
layer.detectedFontName,
|
||||
"PingFang SC",
|
||||
"Microsoft YaHei",
|
||||
"Noto Sans SC",
|
||||
"Source Han Sans SC",
|
||||
"sans-serif",
|
||||
]
|
||||
.map((family) => String(family || "").trim())
|
||||
.filter(Boolean)
|
||||
)
|
||||
);
|
||||
|
||||
return families
|
||||
.map((family) =>
|
||||
family === "sans-serif" ? family : `"${family.replace(/"/g, '\\"')}"`
|
||||
)
|
||||
.join(", ");
|
||||
}
|
||||
|
||||
function getPreferredSelectedLayerId(template: AgentPsdTemplate | null) {
|
||||
if (!template || template.layers.length === 0) {
|
||||
return "";
|
||||
@ -205,12 +245,13 @@ function getTextLayerContentStyle(
|
||||
paddingX: layer.paddingX,
|
||||
paddingY: layer.paddingY,
|
||||
maxLines: layer.maxLines,
|
||||
autoFit: false,
|
||||
});
|
||||
|
||||
return {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
fontFamily: layer.fontFamily || "sans-serif",
|
||||
fontFamily: buildTextLayerFontFamily(layer),
|
||||
fontSize: resolvedLayout.fontSize,
|
||||
lineHeight: layer.lineHeight,
|
||||
letterSpacing: `${layer.letterSpacing}px`,
|
||||
|
||||
@ -601,6 +601,17 @@ export async function preparePsdTemplateRenderAssets(params: {
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
currentLayer.paddingY <= 0 &&
|
||||
Number.isFinite(Number(analysis.suggestedPaddingY)) &&
|
||||
Number(analysis.suggestedPaddingY) > 0
|
||||
) {
|
||||
nextLayer = {
|
||||
...nextLayer,
|
||||
paddingY: Math.max(0, Number(analysis.suggestedPaddingY)),
|
||||
};
|
||||
}
|
||||
|
||||
if (nextLayer !== currentLayer) {
|
||||
nextLayers[layerIndex] = nextLayer;
|
||||
}
|
||||
|
||||
@ -233,6 +233,7 @@ async function createTextOverlayBuffer(params: {
|
||||
paddingX,
|
||||
paddingY,
|
||||
maxLines,
|
||||
autoFit: false,
|
||||
});
|
||||
const adjustedFontSize = resolvedLayout.fontSize;
|
||||
const lineHeightPx = resolvedLayout.lineHeightPx;
|
||||
@ -411,35 +412,9 @@ export async function renderAgentPsdTemplate(params: {
|
||||
await appendOriginalLayer(layer.layerId);
|
||||
}
|
||||
|
||||
const hasDedicatedRenderBase =
|
||||
Boolean(String(template.renderBasePath || "").trim()) ||
|
||||
(Boolean(String(template.renderBaseUrl || "").trim()) &&
|
||||
String(template.renderBaseUrl || "").trim() !== String(template.previewUrl || "").trim());
|
||||
let hasBaseOverlay = false;
|
||||
|
||||
if (hasDedicatedRenderBase && template.renderBaseUrl) {
|
||||
try {
|
||||
const baseBuffer = await readCachedBuffer(template.renderBaseUrl);
|
||||
hasBaseOverlay = await appendBufferOverlay({
|
||||
input: baseBuffer,
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: canvasWidth,
|
||||
height: canvasHeight,
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn("[PSD Template] failed to reuse render base during export:", {
|
||||
templateId: template.id,
|
||||
error,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const layer of [...template.layers].reverse()) {
|
||||
if (!layer.isEditable) {
|
||||
if (!hasBaseOverlay) {
|
||||
await appendOriginalLayer(layer.layerId);
|
||||
}
|
||||
await appendStoredLayer(layer);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -542,14 +517,22 @@ export async function renderAgentPsdTemplate(params: {
|
||||
text,
|
||||
fontBuffer,
|
||||
font,
|
||||
fontFamily: layer.fontFamily || font?.family || "sans-serif",
|
||||
fontFamily:
|
||||
layer.fontFamily ||
|
||||
layer.detectedFontNames?.[0] ||
|
||||
layer.detectedFontName ||
|
||||
font?.family ||
|
||||
"sans-serif",
|
||||
renderFontFamilies: runtimeFont
|
||||
? [
|
||||
runtimeFont.fontPostscriptName,
|
||||
runtimeFont.fontFullName,
|
||||
runtimeFont.fontFamily,
|
||||
].filter((value): value is string => Boolean(String(value || "").trim()))
|
||||
: undefined,
|
||||
: [
|
||||
...(layer.detectedFontNames || []),
|
||||
...(layer.detectedFontName ? [layer.detectedFontName] : []),
|
||||
].filter((value): value is string => Boolean(String(value || "").trim())),
|
||||
renderFontFamily: runtimeFont?.fontFamily,
|
||||
useExactFontFace: Boolean(runtimeFont?.fontPostscriptName || runtimeFont?.fontFullName),
|
||||
fontSize: layer.fontSize,
|
||||
|
||||
@ -142,6 +142,17 @@ export async function resolveAgentPsdTemplateTextStyles(template: AgentPsdTempla
|
||||
paddingX: Math.max(0, Number(analysis?.suggestedPaddingX)),
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
Number(nextLayer.paddingY || 0) <= 0 &&
|
||||
Number.isFinite(Number(analysis?.suggestedPaddingY)) &&
|
||||
Number(analysis?.suggestedPaddingY) > 0
|
||||
) {
|
||||
nextLayer = {
|
||||
...nextLayer,
|
||||
paddingY: Math.max(0, Number(analysis?.suggestedPaddingY)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (nextLayer !== layer) {
|
||||
|
||||
@ -91,6 +91,7 @@ export function resolveTextLayout(params: {
|
||||
paddingX: number;
|
||||
paddingY: number;
|
||||
maxLines: number;
|
||||
autoFit?: boolean;
|
||||
}) {
|
||||
const {
|
||||
text,
|
||||
@ -102,6 +103,7 @@ export function resolveTextLayout(params: {
|
||||
paddingX,
|
||||
paddingY,
|
||||
maxLines,
|
||||
autoFit = true,
|
||||
} = params;
|
||||
|
||||
const normalizedText = String(text || "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
||||
@ -114,6 +116,23 @@ export function resolveTextLayout(params: {
|
||||
let bestLines: string[] = [];
|
||||
let bestLineHeightPx = Math.max(fittedFontSize, fittedFontSize * resolvedLineHeight);
|
||||
|
||||
if (!autoFit) {
|
||||
bestLines = wrapTextByWidth({
|
||||
text: normalizedText,
|
||||
maxWidth: availableWidth,
|
||||
fontSize: fittedFontSize,
|
||||
letterSpacing,
|
||||
maxLines: resolvedMaxLines,
|
||||
}).slice(0, resolvedMaxLines);
|
||||
bestLineHeightPx = Math.max(fittedFontSize, fittedFontSize * resolvedLineHeight);
|
||||
|
||||
return {
|
||||
fontSize: Number(fittedFontSize.toFixed(2)),
|
||||
lineHeightPx: Number(bestLineHeightPx.toFixed(2)),
|
||||
lines: bestLines,
|
||||
};
|
||||
}
|
||||
|
||||
for (let index = 0; index < 24; index += 1) {
|
||||
const lineHeightPx = Math.max(fittedFontSize, fittedFontSize * resolvedLineHeight);
|
||||
const heightLimitedMaxLines = Math.max(
|
||||
@ -216,11 +235,15 @@ export function analyzeTextLayerPixels(params: {
|
||||
: inferredTextAlign === "center"
|
||||
? Math.max(0, Math.round(Math.min(leftGap, rightGap)))
|
||||
: Math.max(0, Math.round(leftGap));
|
||||
const topGap = minY;
|
||||
const bottomGap = Math.max(0, height - maxY - 1);
|
||||
const suggestedPaddingY = Math.max(0, Math.round(Math.min(topGap, bottomGap)));
|
||||
|
||||
return {
|
||||
dominantColor,
|
||||
inferredTextAlign,
|
||||
suggestedPaddingX,
|
||||
suggestedPaddingY,
|
||||
visibleBounds: {
|
||||
minX,
|
||||
minY,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user