Compare commits

...
Author SHA1 Message Date
suyx 5e8a491f2a fix(POSTV1-TEMPLATE-PLACEMENT-18): 修复文字模板坐标与描边解析
Dada P0-A isolated Windows CI / validate-and-package (push) Waiting to run
2026-08-06 18:25:41 +08:00
4 changed files with 771 additions and 735 deletions
+16 -4
View File
@@ -79,11 +79,18 @@ function destinationEdges(size: number, first: number, last: number) {
return [first * ratio, last * ratio] as const;
}
export function ninePatchSlices(width: number, height: number, patch: TextTemplateNinePatch): NinePatchSlice[] {
export function ninePatchSlices(
width: number,
height: number,
patch: TextTemplateNinePatch,
bitmapSize: { height: number; width: number } = { height: patch.sourceHeight, width: patch.sourceWidth },
): NinePatchSlice[] {
const [left, right] = destinationEdges(width, patch.left, patch.right);
const [top, bottom] = destinationEdges(height, patch.top, patch.bottom);
const sourceColumns = [0, patch.left, patch.sourceWidth - patch.right, patch.sourceWidth];
const sourceRows = [0, patch.top, patch.sourceHeight - patch.bottom, patch.sourceHeight];
const sourceScaleX = bitmapSize.width / patch.sourceWidth;
const sourceScaleY = bitmapSize.height / patch.sourceHeight;
const sourceColumns = [0, patch.left * sourceScaleX, bitmapSize.width - patch.right * sourceScaleX, bitmapSize.width];
const sourceRows = [0, patch.top * sourceScaleY, bitmapSize.height - patch.bottom * sourceScaleY, bitmapSize.height];
const destinationColumns = [0, left, width - right, width];
const destinationRows = [0, top, height - bottom, height];
const slices: NinePatchSlice[] = [];
@@ -139,7 +146,12 @@ function drawTemplateImageLayer(
const left = -layer.anchorX * layer.width;
const top = -layer.anchorY * layer.height;
if (layer.ninePatch) {
for (const slice of ninePatchSlices(layer.width, layer.height, layer.ninePatch)) {
for (const slice of ninePatchSlices(
layer.width,
layer.height,
layer.ninePatch,
{ height: image.naturalHeight, width: image.naturalWidth },
)) {
context.drawImage(
image, slice.source.x, slice.source.y, slice.source.width, slice.source.height,
left + slice.destination.x, top + slice.destination.y, slice.destination.width, slice.destination.height,
File diff suppressed because it is too large Load Diff
+12 -13
View File
@@ -491,15 +491,14 @@ function prefabLayers(prefab, input) {
if (textMesh) {
const textRenderer = nodeComponents.find((item) => item.typeId === "TextRenderer")?.object;
const style = textMesh.m_fontStyleInfo ?? {};
const outline = style.outlineInfo?.outlineSize > 0 ? style.outlineInfo
: style.shadowInfos?.find((item) => item?.outlineInfo?.outlineSize > 0)?.outlineInfo;
const outline = style.outlineInfo?.outlineSize > 0 ? style.outlineInfo : undefined;
const shadow = style.shadowInfos?.find((item) => Number(item?.offset?.x ?? 0) !== 0 || Number(item?.offset?.y ?? 0) !== 0);
const fontUuid = textMesh.m_font?.uuid?.uuid;
layers.push({
align: Number(style.alignment ?? 0) === 2 ? "right" : Number(style.alignment ?? 0) === 1 ? "left" : "center",
...(transform.alpha === 1 ? {} : { alpha: transform.alpha }),
...(transform.anchorX === 0.5 ? {} : { anchor_x: transform.anchorX }),
...(transform.anchorY === 0.5 ? {} : { anchor_y: transform.anchorY }),
...(transform.anchorY === 0.5 ? {} : { anchor_y: 1 - transform.anchorY }),
fill_color: colorHex(style.color),
fill_pattern_asset_id: firstMaterialTextureAssetId(textRenderer, input),
font_file: typeof fontUuid === "string" ? input.manifestMappings.get(fontUuid) : undefined,
@@ -508,7 +507,7 @@ function prefabLayers(prefab, input) {
letter_spacing: Number(style.characterSpacing ?? 1),
line_height: Number(style.lineSpacing ?? 1),
order: order++,
rotation: transform.rotation,
rotation: -transform.rotation,
scale_x: Math.sign(scaleX) || 1,
scale_y: Math.sign(scaleY) || 1,
shadow_blur: Math.max(0, Number(shadow?.blur ?? shadow?.SDFFontBorder ?? 0)),
@@ -521,7 +520,7 @@ function prefabLayers(prefab, input) {
type: "text",
width: Math.max(1, Number(object.m_contentSize?.width ?? 1) * Math.abs(scaleX)),
x: transform.x,
y: transform.y,
y: -transform.y,
});
for (const underline of localUnderlines.filter((item) => item?.p1?.enable !== false && item?.p0 === object.m_Name)) {
const config = underline.p1?.exportParams ?? {};
@@ -540,13 +539,13 @@ function prefabLayers(prefab, input) {
asset_id: assetId,
height: targetHeight,
order: order++,
rotation: transform.rotation,
rotation: -transform.rotation,
scale_x: Math.sign(scaleX) || 1,
scale_y: Math.sign(scaleY) || 1,
type: "image",
width: targetWidth,
x: transform.x,
y: transform.y + Number(object.m_contentSize?.height ?? 48) * Math.abs(scaleY) / 2
y: -transform.y + Number(object.m_contentSize?.height ?? 48) * Math.abs(scaleY) / 2
+ Number(config.relativeDistance ?? 0) + targetHeight / 2,
});
}
@@ -559,7 +558,7 @@ function prefabLayers(prefab, input) {
layers.push({
alpha: transform.alpha * Math.max(0, Math.min(1, Number(particleComponent.m_AlphaIdensity ?? 1))),
anchor_x: transform.anchorX,
anchor_y: transform.anchorY,
anchor_y: 1 - transform.anchorY,
asset_id: assetId,
atlas_columns: Math.max(1, Number(particleComponent.m_altasUcount ?? 1)),
atlas_rows: Math.max(1, Number(particleComponent.m_altasVcount ?? 1)),
@@ -571,13 +570,13 @@ function prefabLayers(prefab, input) {
particle_width: Math.max(1, Number(particleComponent.m_particlesRenderSize?.x ?? 8)),
randomize_angle: Number(particleComponent.m_particlesRandomizeAngle ?? 0),
randomize_position: Number(particleComponent.m_particlesRandomizePosition ?? 0),
rotation: transform.rotation,
rotation: -transform.rotation,
scale_x: Math.sign(scaleX) || 1,
scale_y: Math.sign(scaleY) || 1,
type: "particles",
width: Math.max(1, Number(object.m_contentSize?.width ?? 1) * Math.abs(scaleX)),
x: transform.x,
y: transform.y,
y: -transform.y,
});
}
}
@@ -606,17 +605,17 @@ function prefabLayers(prefab, input) {
layers.push({
...(transform.alpha === 1 ? {} : { alpha: transform.alpha }),
...(transform.anchorX === 0.5 ? {} : { anchor_x: transform.anchorX }),
...(transform.anchorY === 0.5 ? {} : { anchor_y: transform.anchorY }),
...(transform.anchorY === 0.5 ? {} : { anchor_y: 1 - transform.anchorY }),
asset_id: assetId,
height: Math.max(1, Number(object.m_contentSize?.height ?? 1) * Math.abs(scaleY)),
order: order++,
rotation: transform.rotation,
rotation: -transform.rotation,
scale_x: Math.sign(scaleX) || 1,
scale_y: Math.sign(scaleY) || 1,
type: "image",
width: Math.max(1, Number(object.m_contentSize?.width ?? 1) * Math.abs(scaleX)),
x: transform.x,
y: transform.y,
y: -transform.y,
...(ninePatch ? { nine_patch: ninePatch } : {}),
});
} else {
+26 -1
View File
@@ -94,7 +94,14 @@ describe("POSTV1-ASSET-ALL-16 complete asset catalog", () => {
expect.objectContaining({ anchor_x: 0 }),
expect.objectContaining({ anchor_x: 1 }),
]));
expect(byId.get("H079")!.render_model.text_layers.slice(0, 3).every((layer) => Math.abs(layer.rotation - 10) < 0.001)).toBe(true);
expect(byId.get("H079")!.render_model.text_layers.slice(0, 3).every((layer) => Math.abs(layer.rotation + 10) < 0.001)).toBe(true);
expect(byId.get("H005")!.render_model.text_layers[1]).toMatchObject({ anchor_y: 0 });
const heading041 = byId.get("H041")!;
const heading041Text = heading041.render_model.text_layers[0]!;
const heading041Underline = heading041.render_model.image_layers.find((layer) => layer.asset_id === "TEXT-IMAGE-H041-004")!;
expect(heading041Text.stroke_width).toBe(0);
expect(heading041Underline.y).toBeGreaterThan(heading041Text.y);
expect(byId.get("FLOWER024")!.render_model.image_layers[0]).toMatchObject({ alpha: 0.4000000059604645 });
});
@@ -108,6 +115,24 @@ describe("POSTV1-ASSET-ALL-16 complete asset catalog", () => {
expect(slices[8]).toEqual({ destination: { height: 16, width: 25, x: 206, y: 47 }, source: { height: 16, width: 25, x: 134, y: 47 } });
});
it("scales nine-patch source slices to the deployed bitmap without changing layout edges", () => {
const slices = ninePatchSlices(231, 63, {
bottom: 16, left: 53, right: 25, sourceHeight: 63, sourceWidth: 159, top: 12,
}, { height: 199, width: 509 });
expect(slices[0]!.destination).toEqual({ height: 12, width: 53, x: 0, y: 0 });
expect(slices[4]!.destination).toEqual({ height: 35, width: 153, x: 53, y: 12 });
expect(slices[8]!.destination).toEqual({ height: 16, width: 25, x: 206, y: 47 });
expect(slices[4]!.source.x).toBeCloseTo(169.667, 3);
expect(slices[4]!.source.y).toBeCloseTo(37.905, 3);
expect(slices[4]!.source.width).toBeCloseTo(259.302, 3);
expect(slices[4]!.source.height).toBeCloseTo(110.556, 3);
expect(slices[8]!.source.x).toBeCloseTo(428.969, 3);
expect(slices[8]!.source.y).toBeCloseTo(148.46, 2);
expect(slices[8]!.source.width).toBeCloseTo(80.031, 3);
expect(slices[8]!.source.height).toBeCloseTo(50.54, 2);
});
it("exposes every generated definition to the editor", () => {
expect(P0A_TEXT_TEMPLATES).toHaveLength(332);
expect(P0A_TEXT_TEMPLATES.every((item) => item.available && item.fontUrl)).toBe(true);