Fix Catti reaction mouths rendering left-shifted instead of centered
Catti's dual cat-mouth (two mirrored Q-curve paths sharing a center start point) caused extractMouthPositionFromElements to return only the left half's coordinates. Every mouth generator then computed cx=(100+82)/2=91 instead of the true center x=100. The fix scans all Q-curve paths in the mouth section and computes the full horizontal extent. For Catti: startX=82, endX=118, center=100. Single-path mouths are unaffected (the extra-path loop simply doesn't execute). Froggi's dual-path mouth already had symmetric bounds so its result is unchanged.
This commit is contained in:
@@ -82,24 +82,45 @@ function detectMouthByMarker(svgText: string): MouthDetectionResult | null {
|
||||
|
||||
/**
|
||||
* Extract mouth position from mouth SVG elements.
|
||||
*
|
||||
* When the mouth section contains multiple Q-curve paths (e.g. Catti's
|
||||
* dual cat-mouth), computes the full horizontal extent across all paths
|
||||
* so the detected position represents the whole mouth, not just the
|
||||
* first half. Single-path mouths are unaffected.
|
||||
*/
|
||||
function extractMouthPositionFromElements(elements: string): MouthPosition | null {
|
||||
const pathMatch = elements.match(/d="M\s*([\d.]+)\s+([\d.]+)\s*Q\s*([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)"/);
|
||||
if (pathMatch) {
|
||||
const strokeWidthMatch = elements.match(/stroke-width="([^"]*)"/);
|
||||
const strokeWidth = strokeWidthMatch ? strokeWidthMatch[1] : '2.5';
|
||||
|
||||
return {
|
||||
startX: parseFloat(pathMatch[1]),
|
||||
startY: parseFloat(pathMatch[2]),
|
||||
controlX: parseFloat(pathMatch[3]),
|
||||
controlY: parseFloat(pathMatch[4]),
|
||||
endX: parseFloat(pathMatch[5]),
|
||||
endY: parseFloat(pathMatch[6]),
|
||||
strokeAttrs: `stroke="#1f2937" stroke-width="${strokeWidth}"`,
|
||||
};
|
||||
const qCurveRegex = /d="M\s*([\d.]+)\s+([\d.]+)\s*Q\s*([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)"/g;
|
||||
const firstMatch = qCurveRegex.exec(elements);
|
||||
if (!firstMatch) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
|
||||
const strokeWidthMatch = elements.match(/stroke-width="([^"]*)"/);
|
||||
const strokeWidth = strokeWidthMatch ? strokeWidthMatch[1] : '2.5';
|
||||
|
||||
// Start from the first path's coordinates
|
||||
let minX = Math.min(parseFloat(firstMatch[1]), parseFloat(firstMatch[5]));
|
||||
let maxX = Math.max(parseFloat(firstMatch[1]), parseFloat(firstMatch[5]));
|
||||
const startY = parseFloat(firstMatch[2]);
|
||||
const controlY = parseFloat(firstMatch[4]);
|
||||
const endY = parseFloat(firstMatch[6]);
|
||||
|
||||
// Widen bounds with any additional Q-curve paths in the section
|
||||
let extra: RegExpExecArray | null;
|
||||
while ((extra = qCurveRegex.exec(elements)) !== null) {
|
||||
minX = Math.min(minX, parseFloat(extra[1]), parseFloat(extra[5]));
|
||||
maxX = Math.max(maxX, parseFloat(extra[1]), parseFloat(extra[5]));
|
||||
}
|
||||
|
||||
return {
|
||||
startX: minX,
|
||||
startY,
|
||||
controlX: (minX + maxX) / 2,
|
||||
controlY,
|
||||
endX: maxX,
|
||||
endY,
|
||||
strokeAttrs: `stroke="#1f2937" stroke-width="${strokeWidth}"`,
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Regex-Based Detection ────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user