diff --git a/src/components/floatMenu/StrokePanel.tsx b/src/components/floatMenu/StrokePanel.tsx index d4f9faac..c1bba0d0 100644 --- a/src/components/floatMenu/StrokePanel.tsx +++ b/src/components/floatMenu/StrokePanel.tsx @@ -3,7 +3,7 @@ import { ColorPickerPanel } from "../molecules/ColorPickerPanel"; import { Color, LineDash, StrokeStyle } from "../../models"; import { SliderInput } from "../atoms/inputs/SliderInput"; import { ToggleInput } from "../atoms/inputs/ToggleInput"; -import { getLineCap, getLineDap, getLineDashArray, getLineJoin } from "../../utils/strokeStyle"; +import { getLineCap, getLineDap, getLineDashArrayWithCap, getLineJoin } from "../../utils/strokeStyle"; import { InlineField } from "../atoms/InlineField"; import { BlockGroupField } from "../atoms/BlockGroupField"; @@ -158,7 +158,7 @@ interface LineDashButtonProps { const LineDashButton: React.FC = ({ lineDash, highlight, onClick }) => { const dashArray = useMemo(() => { - return getLineDashArray(lineDash, 4).join(" "); + return getLineDashArrayWithCap(lineDash, "butt", 4).join(" "); }, [lineDash]); const handleClick = useCallback(() => { diff --git a/src/utils/strokeStyle.spec.ts b/src/utils/strokeStyle.spec.ts index 2d1cfc0f..a70887a3 100644 --- a/src/utils/strokeStyle.spec.ts +++ b/src/utils/strokeStyle.spec.ts @@ -2,7 +2,7 @@ import { expect, describe, test, vi } from "vitest"; import { applyStrokeStyle, createStrokeStyle, - getLineDashArray, + getLineDashArrayWithCap, isSameStrokeStyle, renderStrokeSVGAttributes, } from "./strokeStyle"; @@ -80,7 +80,17 @@ describe("renderStrokeSVGAttributes", () => { "stroke-width": 10, "stroke-linecap": "round", "stroke-linejoin": "bevel", - "stroke-dasharray": getLineDashArray("dot", 10).join(" "), + "stroke-dasharray": getLineDashArrayWithCap("dot", "round", 10).join(" "), }); }); }); + +describe("getLineDashArrayWithCap", () => { + test("should adjust dash array when line cap isn't butt", () => { + expect(getLineDashArrayWithCap("dot", "butt", 10)).toEqual([10, 10]); + expect(getLineDashArrayWithCap("dot", "round", 10)).toEqual([0.01, 20]); + expect(getLineDashArrayWithCap("dot", "square", 10)).toEqual([0.01, 20]); + expect(getLineDashArrayWithCap("short", "round", 10)).toEqual([20, 20]); + expect(getLineDashArrayWithCap("long", "round", 10)).toEqual([50, 20]); + }); +}); diff --git a/src/utils/strokeStyle.ts b/src/utils/strokeStyle.ts index f115f6c5..02522e2e 100644 --- a/src/utils/strokeStyle.ts +++ b/src/utils/strokeStyle.ts @@ -36,7 +36,7 @@ export function applyStrokeStyle(ctx: CanvasRenderingContext2D, stroke: StrokeSt ctx.strokeStyle = rednerRGBA(stroke.color); const width = getStrokeWidth(stroke); ctx.lineWidth = width; - ctx.setLineDash(getLineDashArray(stroke.dash, width)); + ctx.setLineDash(getLineDashArrayWithCap(stroke.dash, stroke.lineCap, width)); ctx.lineCap = getLineCap(stroke.lineCap); ctx.lineJoin = getLineJoin(stroke.lineJoin); } @@ -54,7 +54,7 @@ export function applyDefaultStrokeStyle(ctx: CanvasRenderingContext2D) { ctx.lineJoin = "miter"; } -export function getLineDashArray(lineDash: LineDash, width = 1): number[] { +function getLineDashArray(lineDash: LineDash, width = 1): number[] { switch (lineDash) { case "dot": return [width, width]; @@ -67,6 +67,26 @@ export function getLineDashArray(lineDash: LineDash, width = 1): number[] { } } +export function getLineDashArrayWithCap(lineDash: LineDash, lineCap: CanvasLineCap = "butt", width = 1): number[] { + switch (lineCap) { + case "butt": + return getLineDashArray(lineDash, width); + default: { + switch (lineDash) { + case "dot": + // Stroke part must have nonzero value to keep the direction + return [0.01, width * 2]; + case "short": + return [width * 2, width * 2]; + case "long": + return [width * 5, width * 2]; + default: + return []; + } + } + } +} + export function renderStrokeSVGAttributes(stroke: StrokeStyle): SVGAttributes { return stroke.disabled ? { stroke: "none" } @@ -76,6 +96,8 @@ export function renderStrokeSVGAttributes(stroke: StrokeStyle): SVGAttributes { "stroke-width": stroke.width, "stroke-linecap": getLineCap(stroke.lineCap), "stroke-linejoin": getLineJoin(stroke.lineJoin), - "stroke-dasharray": stroke.dash ? getLineDashArray(stroke.dash, stroke.width).join(" ") : undefined, + "stroke-dasharray": stroke.dash + ? getLineDashArrayWithCap(stroke.dash, stroke.lineCap, stroke.width).join(" ") + : undefined, }; }