fix: improve psd text font recovery
This commit is contained in:
parent
aa885a3d48
commit
9b745e5591
@ -91,6 +91,7 @@ function buildInitialTemplateLayers(params: {
|
||||
detectedFontNames: layer.detectedFontNames || [],
|
||||
fonts,
|
||||
});
|
||||
const autoBoundFont = matchedFont?.confidence === "high" ? matchedFont.font : undefined;
|
||||
const isEditable = shouldAutoEditableTextLayer(layer);
|
||||
|
||||
return {
|
||||
@ -102,17 +103,17 @@ function buildInitialTemplateLayers(params: {
|
||||
placeholder: layer.defaultText || layer.layerName,
|
||||
detectedFontName: layer.detectedFontName,
|
||||
detectedFontNames: layer.detectedFontNames,
|
||||
fontId: matchedFont?.font.id,
|
||||
fontId: autoBoundFont?.id,
|
||||
fontFamily:
|
||||
matchedFont?.font.family ||
|
||||
autoBoundFont?.family ||
|
||||
layer.detectedFontNames?.[0] ||
|
||||
layer.detectedFontName,
|
||||
fontLabel:
|
||||
matchedFont?.font.name ||
|
||||
autoBoundFont?.name ||
|
||||
layer.detectedFontNames?.[0] ||
|
||||
layer.detectedFontName,
|
||||
fontFileUrl: matchedFont?.font.fileUrl,
|
||||
fontContentType: matchedFont?.font.contentType,
|
||||
fontFileUrl: autoBoundFont?.fileUrl,
|
||||
fontContentType: autoBoundFont?.contentType,
|
||||
fontSize: Math.max(
|
||||
10,
|
||||
Math.round(
|
||||
@ -130,7 +131,7 @@ function buildInitialTemplateLayers(params: {
|
||||
? Number(layer.detectedLetterSpacing)
|
||||
: 0,
|
||||
fontWeight: 500,
|
||||
textColor: "#111111",
|
||||
textColor: layer.detectedTextColor || "#111111",
|
||||
textAlign: "left",
|
||||
paddingX: 0,
|
||||
paddingY: 0,
|
||||
|
||||
@ -169,7 +169,7 @@ export async function PUT(
|
||||
],
|
||||
fonts,
|
||||
});
|
||||
if (matchedFont) {
|
||||
if (matchedFont?.confidence === "high") {
|
||||
layer.fontId = matchedFont.font.id;
|
||||
layer.fontFamily = matchedFont.font.family;
|
||||
layer.fontLabel = matchedFont.font.name;
|
||||
|
||||
@ -27,6 +27,7 @@ import {
|
||||
normalizeAgentPsdTemplateCategoryTagConfig,
|
||||
} from "@/lib/agents/psd-template-category-tag-config";
|
||||
import { readAgentPsdFonts } from "@/lib/agents/psd-font-settings";
|
||||
import { repairAgentPsdTemplateFontBindings } from "@/lib/agents/psd-template-repair";
|
||||
import { readAgentPsdTemplates } from "@/lib/agents/psd-template-settings";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
@ -92,6 +93,7 @@ export async function GET(request: NextRequest) {
|
||||
)
|
||||
.filter((template) => (requestedMode ? template.mode === requestedMode : true))
|
||||
.filter((template) => getEditableAgentPsdTemplateLayers(template).length > 0)
|
||||
.map((template) => repairAgentPsdTemplateFontBindings(template, fonts))
|
||||
.map((template) => ({
|
||||
...template,
|
||||
layers: template.layers.map((layer) => {
|
||||
|
||||
@ -32,6 +32,31 @@ export function normalizeDetectedFontNames(value: unknown) {
|
||||
);
|
||||
}
|
||||
|
||||
function getPsdFontIdentityTokens(font: Pick<AgentPsdFont, "name" | "family">) {
|
||||
return Array.from(
|
||||
new Set(
|
||||
[font.name, font.family]
|
||||
.map((item) => normalizeFontToken(String(item || "")))
|
||||
.filter(Boolean)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function doesPsdFontMatchDetectedNames(params: {
|
||||
detectedFontNames: string[];
|
||||
font: Pick<AgentPsdFont, "name" | "family">;
|
||||
}) {
|
||||
const detectedFontNames = normalizeDetectedFontNames(params.detectedFontNames);
|
||||
if (detectedFontNames.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const fontTokens = new Set(getPsdFontIdentityTokens(params.font));
|
||||
return detectedFontNames.some((detectedFontName) =>
|
||||
fontTokens.has(normalizeFontToken(detectedFontName))
|
||||
);
|
||||
}
|
||||
|
||||
export function findBestMatchingPsdFont(params: {
|
||||
detectedFontNames: string[];
|
||||
fonts: AgentPsdFont[];
|
||||
|
||||
@ -15,6 +15,11 @@ type PsdFontSetEntry = {
|
||||
Name?: string;
|
||||
};
|
||||
|
||||
type PsdTextStyleColorValue = {
|
||||
Values?: number[];
|
||||
Type?: number;
|
||||
};
|
||||
|
||||
type PsdTextStyleRun = {
|
||||
StyleSheet?: {
|
||||
StyleSheetData?: {
|
||||
@ -23,6 +28,7 @@ type PsdTextStyleRun = {
|
||||
Leading?: number;
|
||||
AutoLeading?: number | boolean;
|
||||
Tracking?: number;
|
||||
FillColor?: PsdTextStyleColorValue;
|
||||
};
|
||||
};
|
||||
};
|
||||
@ -87,6 +93,7 @@ export interface ParsedPsdTemplateLayer {
|
||||
detectedFontSize?: number;
|
||||
detectedLineHeight?: number;
|
||||
detectedLetterSpacing?: number;
|
||||
detectedTextColor?: string;
|
||||
}
|
||||
|
||||
export interface ParsedPsdTemplatePayload {
|
||||
@ -277,6 +284,30 @@ function extractTextTransformScale(additionalProperties?: Record<string, unknown
|
||||
};
|
||||
}
|
||||
|
||||
function extractHexColorFromTextStyle(value?: PsdTextStyleColorValue) {
|
||||
const channels = Array.isArray(value?.Values) ? value.Values : [];
|
||||
if (channels.length < 4) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const red = Number(channels[1]);
|
||||
const green = Number(channels[2]);
|
||||
const blue = Number(channels[3]);
|
||||
if (![red, green, blue].every((channel) => Number.isFinite(channel))) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return normalizeHexColor(
|
||||
`#${Math.round(Math.max(0, Math.min(1, red)) * 255)
|
||||
.toString(16)
|
||||
.padStart(2, "0")}${Math.round(Math.max(0, Math.min(1, green)) * 255)
|
||||
.toString(16)
|
||||
.padStart(2, "0")}${Math.round(Math.max(0, Math.min(1, blue)) * 255)
|
||||
.toString(16)
|
||||
.padStart(2, "0")}`
|
||||
);
|
||||
}
|
||||
|
||||
function extractTextStyleMetrics(
|
||||
textProperties?: PsdTextPropertiesLike,
|
||||
additionalProperties?: Record<string, unknown>
|
||||
@ -286,8 +317,12 @@ function extractTextStyleMetrics(
|
||||
const style = styleRun?.StyleSheet?.StyleSheetData;
|
||||
return Number.isFinite(Number(style?.FontSize));
|
||||
});
|
||||
const firstColorRun = styleRuns.find((styleRun) =>
|
||||
Boolean(extractHexColorFromTextStyle(styleRun?.StyleSheet?.StyleSheetData?.FillColor))
|
||||
);
|
||||
|
||||
const style = firstValidRun?.StyleSheet?.StyleSheetData;
|
||||
const colorStyle = firstColorRun?.StyleSheet?.StyleSheetData;
|
||||
const fontSizeValue = Number(style?.FontSize);
|
||||
const leadingValue = Number(style?.Leading);
|
||||
const trackingValue = Number(style?.Tracking);
|
||||
@ -312,6 +347,7 @@ function extractTextStyleMetrics(
|
||||
detectedFontSize,
|
||||
detectedLineHeight,
|
||||
detectedLetterSpacing,
|
||||
detectedTextColor: extractHexColorFromTextStyle(colorStyle?.FillColor),
|
||||
};
|
||||
}
|
||||
|
||||
@ -364,6 +400,7 @@ function collectPsdLayerEntries(
|
||||
detectedFontSize: undefined,
|
||||
detectedLineHeight: undefined,
|
||||
detectedLetterSpacing: undefined,
|
||||
detectedTextColor: undefined,
|
||||
};
|
||||
|
||||
result.push({
|
||||
@ -385,6 +422,7 @@ function collectPsdLayerEntries(
|
||||
detectedFontSize: textStyleMetrics.detectedFontSize,
|
||||
detectedLineHeight: textStyleMetrics.detectedLineHeight,
|
||||
detectedLetterSpacing: textStyleMetrics.detectedLetterSpacing,
|
||||
detectedTextColor: textStyleMetrics.detectedTextColor,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import "server-only";
|
||||
|
||||
import { findBestMatchingPsdFont } from "@/lib/agents/psd-font-matching";
|
||||
import type { AgentPsdFont } from "@/lib/agents/psd-font-config";
|
||||
import {
|
||||
doesPsdFontMatchDetectedNames,
|
||||
findBestMatchingPsdFont,
|
||||
} from "@/lib/agents/psd-font-matching";
|
||||
import { readAgentPsdFonts } from "@/lib/agents/psd-font-settings";
|
||||
import {
|
||||
AGENT_PSD_TEMPLATE_ASSET_VERSION,
|
||||
@ -111,6 +115,83 @@ function toArrayBuffer(buffer: Buffer) {
|
||||
) as ArrayBuffer;
|
||||
}
|
||||
|
||||
export function repairAgentPsdTemplateFontBindings(
|
||||
template: AgentPsdTemplate,
|
||||
fonts: AgentPsdFont[]
|
||||
) {
|
||||
if (fonts.length === 0) {
|
||||
return template;
|
||||
}
|
||||
|
||||
const fontMap = new Map(fonts.map((font) => [font.id, font]));
|
||||
let didChange = false;
|
||||
|
||||
const nextLayers = template.layers.map((layer) => {
|
||||
if (!isAgentPsdTextLayerSlot(layer) || layer.styleSource === "custom") {
|
||||
return layer;
|
||||
}
|
||||
|
||||
const detectedFontNames = [
|
||||
...((layer.detectedFontNames || []).filter(Boolean)),
|
||||
...(layer.detectedFontName ? [layer.detectedFontName] : []),
|
||||
];
|
||||
const currentFont = layer.fontId ? fontMap.get(layer.fontId) : undefined;
|
||||
const currentFontMatches = currentFont
|
||||
? doesPsdFontMatchDetectedNames({
|
||||
detectedFontNames,
|
||||
font: currentFont,
|
||||
})
|
||||
: false;
|
||||
const matchedFont = findBestMatchingPsdFont({
|
||||
detectedFontNames,
|
||||
fonts,
|
||||
});
|
||||
|
||||
if (
|
||||
matchedFont?.confidence === "high" &&
|
||||
(!currentFontMatches || !layer.fontId)
|
||||
) {
|
||||
didChange = true;
|
||||
return {
|
||||
...layer,
|
||||
fontId: matchedFont.font.id,
|
||||
fontFamily: matchedFont.font.family,
|
||||
fontLabel: matchedFont.font.name,
|
||||
fontFileUrl: matchedFont.font.fileUrl,
|
||||
fontContentType: matchedFont.font.contentType,
|
||||
};
|
||||
}
|
||||
|
||||
if (currentFont && (
|
||||
layer.fontFamily !== currentFont.family ||
|
||||
layer.fontLabel !== currentFont.name ||
|
||||
layer.fontFileUrl !== currentFont.fileUrl ||
|
||||
layer.fontContentType !== currentFont.contentType
|
||||
)) {
|
||||
didChange = true;
|
||||
return {
|
||||
...layer,
|
||||
fontFamily: currentFont.family,
|
||||
fontLabel: currentFont.name,
|
||||
fontFileUrl: currentFont.fileUrl,
|
||||
fontContentType: currentFont.contentType,
|
||||
};
|
||||
}
|
||||
|
||||
return layer;
|
||||
});
|
||||
|
||||
if (!didChange) {
|
||||
return template;
|
||||
}
|
||||
|
||||
return {
|
||||
...template,
|
||||
layers: nextLayers,
|
||||
updatedAt: new Date().toISOString(),
|
||||
} satisfies AgentPsdTemplate;
|
||||
}
|
||||
|
||||
export async function repairAgentPsdTemplateAssetsIfNeeded(
|
||||
template: AgentPsdTemplate
|
||||
) {
|
||||
@ -142,6 +223,12 @@ export async function repairAgentPsdTemplateAssetsIfNeeded(
|
||||
...layer,
|
||||
detectedFontName: parsedLayer.detectedFontName || layer.detectedFontName,
|
||||
detectedFontNames: parsedLayer.detectedFontNames || layer.detectedFontNames,
|
||||
textColor:
|
||||
layer.styleSource !== "custom" &&
|
||||
(!String(layer.textColor || "").trim() || layer.textColor === "#111111") &&
|
||||
parsedLayer.detectedTextColor
|
||||
? parsedLayer.detectedTextColor
|
||||
: layer.textColor,
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
@ -152,31 +239,14 @@ export async function repairAgentPsdTemplateAssetsIfNeeded(
|
||||
}
|
||||
}
|
||||
|
||||
nextLayers = nextLayers.map((layer) => {
|
||||
if (!isAgentPsdTextLayerSlot(layer) || layer.fontId) {
|
||||
return layer;
|
||||
}
|
||||
|
||||
const matchedFont = findBestMatchingPsdFont({
|
||||
detectedFontNames: [
|
||||
...((layer.detectedFontNames || []).filter(Boolean)),
|
||||
...(layer.detectedFontName ? [layer.detectedFontName] : []),
|
||||
],
|
||||
fonts,
|
||||
});
|
||||
if (!matchedFont) {
|
||||
return layer;
|
||||
}
|
||||
|
||||
return {
|
||||
...layer,
|
||||
fontId: matchedFont.font.id,
|
||||
fontFamily: matchedFont.font.family,
|
||||
fontLabel: matchedFont.font.name,
|
||||
fontFileUrl: matchedFont.font.fileUrl,
|
||||
fontContentType: matchedFont.font.contentType,
|
||||
};
|
||||
});
|
||||
const fontRepairedTemplate = repairAgentPsdTemplateFontBindings(
|
||||
{
|
||||
...template,
|
||||
layers: nextLayers,
|
||||
},
|
||||
fonts
|
||||
);
|
||||
nextLayers = fontRepairedTemplate.layers;
|
||||
|
||||
const hasFontRepair = nextLayers.some(
|
||||
(layer, index) => JSON.stringify(layer) !== JSON.stringify(template.layers[index])
|
||||
|
||||
@ -105,6 +105,13 @@ export async function resolveAgentPsdTemplateTextStyles(template: AgentPsdTempla
|
||||
const needsColorRepair = normalizeHexColor(nextLayer.textColor) === "#111111";
|
||||
const needsAlignRepair = nextLayer.textAlign === "left";
|
||||
|
||||
if (needsColorRepair && sourceEntry.layer.detectedTextColor) {
|
||||
nextLayer = {
|
||||
...nextLayer,
|
||||
textColor: sourceEntry.layer.detectedTextColor,
|
||||
};
|
||||
}
|
||||
|
||||
if (needsColorRepair || needsAlignRepair) {
|
||||
const analysis = await getPixelAnalysis(
|
||||
pixelAnalysisCache,
|
||||
@ -114,7 +121,10 @@ export async function resolveAgentPsdTemplateTextStyles(template: AgentPsdTempla
|
||||
sourceEntry.layer.height
|
||||
);
|
||||
|
||||
if (analysis?.dominantColor && needsColorRepair) {
|
||||
if (
|
||||
analysis?.dominantColor &&
|
||||
normalizeHexColor(nextLayer.textColor) === "#111111"
|
||||
) {
|
||||
nextLayer = {
|
||||
...nextLayer,
|
||||
textColor: analysis.dominantColor,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user