refactor: preview contour correctly

This commit is contained in:
2026-03-15 14:55:50 +08:00
parent ee2fa057f6
commit 2b1dbd41e1
3 changed files with 77 additions and 34 deletions
+5 -1
View File
@@ -19,7 +19,11 @@ export function CardPreview(props: CardPreviewProps) {
const selectionStyle = createMemo(() => const selectionStyle = createMemo(() =>
getSelectionBoxStyle(store.state.selectStart, store.state.selectEnd, store.state.dimensions) getSelectionBoxStyle(store.state.selectStart, store.state.selectEnd, store.state.dimensions)
); );
const shapeClipPath = createMemo(() => getShapeClipPath(store.state.shape)); const shapeClipPath = createMemo(() => {
const dims = store.state.dimensions;
if (!dims) return 'none';
return getShapeClipPath(store.state.shape, dims.cardWidth, dims.cardHeight, store.state.cornerRadius);
});
const selection = useCardSelection(store); const selection = useCardSelection(store);
+1 -1
View File
@@ -152,7 +152,7 @@ export function PrintPreview(props: PrintPreviewProps) {
const cardWidth = store.state.dimensions?.cardWidth || 56; const cardWidth = store.state.dimensions?.cardWidth || 56;
const cardHeight = store.state.dimensions?.cardHeight || 88; const cardHeight = store.state.dimensions?.cardHeight || 88;
const clipPathId = `clip-${page.pageIndex}-${card.data.id || card.x}-${card.y}`; const clipPathId = `clip-${page.pageIndex}-${card.data.id || card.x}-${card.y}`;
const shapeClipPath = getShapeSvgClipPath(clipPathId, cardWidth, cardHeight, store.state.shape); const shapeClipPath = getShapeSvgClipPath(clipPathId, cardWidth, cardHeight, store.state.shape, store.state.cornerRadius);
return ( return (
<g class="card-group"> <g class="card-group">
+64 -25
View File
@@ -1,56 +1,95 @@
import type { CardShape } from '../types'; import type { CardShape } from '../types';
import { getCardShapePoints, contourToSvgPath } from '../../../plotcutter/contour';
/**
* 将轮廓点转换为 CSS polygon() 格式
* @param points 轮廓点数组(相对于左上角,单位:mm)
* @returns CSS polygon() 字符串,使用百分比坐标
*/
function pointsToCssPolygon(points: [number, number][], width: number, height: number): string {
if (points.length === 0) return 'none';
const coords = points.map(([x, y]) => {
const percentX = (x / width) * 100;
const percentY = (y / height) * 100;
return `${percentX.toFixed(4)}% ${percentY.toFixed(4)}%`;
});
return `polygon(${coords.join(', ')})`;
}
/** /**
* 获取 CSS clip-path 值用于形状裁剪 * 获取 CSS clip-path 值用于形状裁剪
*
* @param shape 卡片形状
* @param width 卡片宽度(用于计算多边形坐标)
* @param height 卡片高度(用于计算多边形坐标)
* @param cornerRadius 圆角半径(可选,>0 时使用多边形近似)
* @param segmentsPerCorner 每个圆角的分段数(可选)
*/ */
export function getShapeClipPath(shape: CardShape): string { export function getShapeClipPath(
shape: CardShape,
width: number,
height: number,
cornerRadius: number = 0,
segmentsPerCorner: number = 4
): string {
// 无圆角的基本形状使用简化的 CSS clip-path
if (cornerRadius <= 0) {
switch (shape) { switch (shape) {
case 'circle': case 'circle':
return 'circle(50% at 50% 50%)'; return 'circle(50% at 50% 50%)';
case 'triangle':
return 'polygon(50% 0%, 0% 100%, 100% 100%)';
case 'hexagon':
return 'polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%)';
case 'rectangle': case 'rectangle':
default:
return 'none'; return 'none';
} }
} }
// 其他情况使用多边形近似(包括圆角形状、三角形、六边形)
const points = getCardShapePoints(shape, width, height, cornerRadius, segmentsPerCorner);
return pointsToCssPolygon(points, width, height);
}
/** /**
* 获取 SVG clipPath 定义 * 获取 SVG clipPath 定义
* @param id clipPath 的唯一 ID * @param id clipPath 的唯一 ID
* @param width 卡片宽度 * @param width 卡片宽度
* @param height 卡片高度 * @param height 卡片高度
* @param shape 卡片形状 * @param shape 卡片形状
* @param cornerRadius 圆角半径(可选)
* @param segmentsPerCorner 每个圆角的分段数(可选)
*/ */
export function getShapeSvgClipPath( export function getShapeSvgClipPath(
id: string, id: string,
width: number, width: number,
height: number, height: number,
shape: CardShape shape: CardShape,
cornerRadius: number = 0,
segmentsPerCorner: number = 4
): string { ): string {
const halfW = width / 2; const points = getCardShapePoints(shape, width, height, cornerRadius, segmentsPerCorner);
const halfH = height / 2; const pathData = contourToSvgPath(points, true);
switch (shape) {
case 'circle':
return ` return `
<clipPath id="${id}"> <clipPath id="${id}">
<circle cx="${halfW}" cy="${halfH}" r="${halfW}" /> <path d="${pathData}" />
</clipPath>`; </clipPath>`;
case 'triangle':
return `
<clipPath id="${id}">
<polygon points="${halfW},0 0,${height} ${width},${height}" />
</clipPath>`;
case 'hexagon':
return `
<clipPath id="${id}">
<polygon points="${halfW},0 ${width},${height * 0.25} ${width},${height * 0.75} ${halfW},${height} 0,${height * 0.75} 0,${height * 0.25}" />
</clipPath>`;
case 'rectangle':
default:
return '';
} }
/**
* 获取卡片形状的 SVG path 数据
* @param width 卡片宽度
* @param height 卡片高度
* @param shape 卡片形状
* @param cornerRadius 圆角半径(可选)
* @param segmentsPerCorner 每个圆角的分段数(可选)
*/
export function getCardShapePath(
width: number,
height: number,
shape: CardShape,
cornerRadius: number = 0,
segmentsPerCorner: number = 4
): string {
const points = getCardShapePoints(shape, width, height, cornerRadius, segmentsPerCorner);
return contourToSvgPath(points, true);
} }