feat(canvas): render remapped stroke segments in previews and exports
Shape previews, boolean previews, and SVG export now run stored stroke segments through strokeSegmentsForEditedPath so the drawn boundary styles always match the current path geometry, falling back to the stored segments when no edge indexes are available. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import type { CanvasNode } from "@/domain/design";
|
||||
import { normalizedPathDataToSvg } from "@/ui/pages/CanvasWorkspace/canvas/editablePath";
|
||||
import { normalizedPathDataToSvg, strokeSegmentsForEditedPath } from "@/ui/pages/CanvasWorkspace/canvas/editablePath";
|
||||
import { booleanShapePathResult } from "@/ui/pages/CanvasWorkspace/canvas/booleanShapePath";
|
||||
import { isBooleanShapeGroupNode, parseBooleanShapeOptions, parseShapeOptions, shapeEndpointToWorldPoint, type ShapeStrokeSegment } from "@/ui/pages/CanvasWorkspace/canvas/shapeNode";
|
||||
import { normalizeStrokeStyle } from "@/ui/pages/CanvasWorkspace/canvas/frameStyle";
|
||||
@@ -27,11 +27,12 @@ export function ShapeNodePreview({ node }: { node: CanvasNode }) {
|
||||
|
||||
if (options.kind === "path") {
|
||||
const path = normalizedPathDataToSvg(options.pathData, width, height);
|
||||
const hasSegmentStrokes = Boolean(options.strokeSegments?.length);
|
||||
const strokeSegments = options.pathData ? strokeSegmentsForEditedPath(options.pathData, options.strokeSegments) || options.strokeSegments || [] : [];
|
||||
const hasSegmentStrokes = strokeSegments.length > 0;
|
||||
return (
|
||||
<svg className="shape-node-preview" viewBox={`0 0 ${width} ${height}`} preserveAspectRatio="none">
|
||||
<path d={path} fill={fill} fillOpacity={opacity} fillRule="evenodd" stroke={hasSegmentStrokes ? "none" : stroke} strokeWidth={strokeWidth} strokeDasharray={dashArray} strokeLinecap="round" strokeLinejoin="round" vectorEffect="non-scaling-stroke" />
|
||||
{hasSegmentStrokes && <ShapeStrokeSegments segments={options.strokeSegments || []} width={width} height={height} />}
|
||||
{hasSegmentStrokes && <ShapeStrokeSegments segments={strokeSegments} width={width} height={height} />}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -90,7 +91,8 @@ function BooleanShapePreview({ node }: { node: CanvasNode }) {
|
||||
const height = Math.max(node.height, 1);
|
||||
const computed = options.pathData?.length && options.strokeSegments?.length ? null : booleanShapePathResult(options.operands, options.operation, options.width, options.height);
|
||||
const pathData = options.pathData?.length ? options.pathData : computed?.pathData;
|
||||
const strokeSegments = options.strokeSegments?.length ? options.strokeSegments : computed?.strokeSegments || [];
|
||||
const storedStrokeSegments = options.strokeSegments?.length ? options.strokeSegments : computed?.strokeSegments || [];
|
||||
const strokeSegments = pathData ? strokeSegmentsForEditedPath(pathData, storedStrokeSegments) || storedStrokeSegments : [];
|
||||
const path = normalizedPathDataToSvg(pathData, width, height);
|
||||
const fill = node.fillColor || "transparent";
|
||||
const stroke = node.strokeColor || "#111827";
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Justification, Layer as PsdLayer, LayerTextData, Psd, RGB } from "
|
||||
import { imageAdjustmentImageStyle, isDefaultImageAdjustments, parseImageAdjustments } from "@/ui/lib/imageAdjustments";
|
||||
import { booleanShapePathResult } from "@/ui/pages/CanvasWorkspace/canvas/booleanShapePath";
|
||||
import { isBooleanShapeGroupNode, isShapeNode, parseBooleanShapeOptions, parseShapeOptions, shapeEndpointToWorldPoint, type ShapeStrokeSegment } from "@/ui/pages/CanvasWorkspace/canvas/shapeNode";
|
||||
import { normalizedPathDataToSvg } from "@/ui/pages/CanvasWorkspace/canvas/editablePath";
|
||||
import { normalizedPathDataToSvg, strokeSegmentsForEditedPath } from "@/ui/pages/CanvasWorkspace/canvas/editablePath";
|
||||
import { defaultFrameFillColor, defaultFrameStrokeColor, defaultFrameStrokeStyle, defaultFrameStrokeWidth, normalizeHexColor, normalizeStrokeStyle } from "./frameStyle";
|
||||
|
||||
export type ImageExportFormat = "png" | "jpg" | "svg" | "psd";
|
||||
@@ -503,7 +503,8 @@ function booleanShapeSvgFragment(node: CanvasNode, x: number, y: number, width:
|
||||
const options = parseBooleanShapeOptions(node.content);
|
||||
const computed = options.pathData?.length && options.strokeSegments?.length ? null : booleanShapePathResult(options.operands, options.operation, options.width, options.height);
|
||||
const pathData = options.pathData?.length ? options.pathData : computed?.pathData;
|
||||
const strokeSegments = options.strokeSegments?.length ? options.strokeSegments : computed?.strokeSegments || [];
|
||||
const storedStrokeSegments = options.strokeSegments?.length ? options.strokeSegments : computed?.strokeSegments || [];
|
||||
const strokeSegments = pathData ? strokeSegmentsForEditedPath(pathData, storedStrokeSegments) || storedStrokeSegments : [];
|
||||
const path = normalizedPathDataToSvg(pathData, width, height, x, y);
|
||||
if (!path) return "";
|
||||
const strokeWidth = Math.max(0, node.strokeWidth ?? defaultFrameStrokeWidth);
|
||||
@@ -526,7 +527,7 @@ function shapeSvgFragment(node: CanvasNode, x: number, y: number, width: number,
|
||||
const inset = strokeWidth / 2;
|
||||
|
||||
if (options.kind === "path") {
|
||||
const strokeSegments = options.strokeSegments || [];
|
||||
const strokeSegments = options.pathData ? strokeSegmentsForEditedPath(options.pathData, options.strokeSegments) || options.strokeSegments || [] : [];
|
||||
return `<path d="${normalizedPathDataToSvg(options.pathData, width, height, x, y)}" ${fill} ${strokeSegments.length ? `stroke="none"` : stroke} fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"${transform}/>${shapeStrokeSvgFragments(strokeSegments, x, y, width, height, transform)}`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user