init commit
This commit is contained in:
221
src/elementBox.ts
Normal file
221
src/elementBox.ts
Normal file
@@ -0,0 +1,221 @@
|
||||
import { TemplateElement, ElementSide } from './types';
|
||||
import { resolveElementBackgroundFill } from './utils';
|
||||
|
||||
export interface CornerRadiiPx {
|
||||
tl: number;
|
||||
tr: number;
|
||||
br: number;
|
||||
bl: number;
|
||||
}
|
||||
|
||||
export interface BorderSides {
|
||||
top: boolean;
|
||||
right: boolean;
|
||||
bottom: boolean;
|
||||
left: boolean;
|
||||
}
|
||||
|
||||
export function resolveCornerRadiiPx(
|
||||
elem: TemplateElement,
|
||||
scale: number,
|
||||
w: number,
|
||||
h: number
|
||||
): CornerRadiiPx {
|
||||
const maxR = Math.min(w, h) / 2;
|
||||
const baseMm = elem.borderRadius ?? 0;
|
||||
|
||||
const radiusFor = (specificMm?: number): number => {
|
||||
const mm = specificMm !== undefined && specificMm !== null ? specificMm : baseMm;
|
||||
if (mm <= 0) return 0;
|
||||
return Math.min(maxR, mm * scale);
|
||||
};
|
||||
|
||||
return {
|
||||
tl: radiusFor(elem.borderRadiusTL),
|
||||
tr: radiusFor(elem.borderRadiusTR),
|
||||
br: radiusFor(elem.borderRadiusBR),
|
||||
bl: radiusFor(elem.borderRadiusBL),
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveBorderSides(elem: TemplateElement): BorderSides {
|
||||
const width = elem.borderWidth ?? 0;
|
||||
if (width <= 0) {
|
||||
return { top: false, right: false, bottom: false, left: false };
|
||||
}
|
||||
const sides = elem.borderSides ?? {};
|
||||
return {
|
||||
top: sides.top !== false,
|
||||
right: sides.right !== false,
|
||||
bottom: sides.bottom !== false,
|
||||
left: sides.left !== false,
|
||||
};
|
||||
}
|
||||
|
||||
/** 构建顺时针闭合圆角矩形路径 */
|
||||
export function traceRoundedRect(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number,
|
||||
r: CornerRadiiPx
|
||||
) {
|
||||
const { tl, tr, br, bl } = r;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + tl, y);
|
||||
ctx.lineTo(x + w - tr, y);
|
||||
if (tr > 0) ctx.arcTo(x + w, y, x + w, y + tr, tr);
|
||||
else ctx.lineTo(x + w, y);
|
||||
ctx.lineTo(x + w, y + h - br);
|
||||
if (br > 0) ctx.arcTo(x + w, y + h, x + w - br, y + h, br);
|
||||
else ctx.lineTo(x + w, y + h);
|
||||
ctx.lineTo(x + bl, y + h);
|
||||
if (bl > 0) ctx.arcTo(x, y + h, x, y + h - bl, bl);
|
||||
else ctx.lineTo(x, y + h);
|
||||
ctx.lineTo(x, y + tl);
|
||||
if (tl > 0) ctx.arcTo(x, y, x + tl, y, tl);
|
||||
else ctx.lineTo(x, y);
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
export function drawElementFill(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number,
|
||||
elem: TemplateElement,
|
||||
radii: CornerRadiiPx
|
||||
) {
|
||||
const fill = resolveElementBackgroundFill(elem.backgroundColor, elem.backgroundOpacity);
|
||||
if (!fill) return;
|
||||
traceRoundedRect(ctx, x, y, w, h, radii);
|
||||
ctx.fillStyle = fill;
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
export function drawElementBorders(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number,
|
||||
radii: CornerRadiiPx,
|
||||
sides: BorderSides,
|
||||
elem: TemplateElement,
|
||||
scale: number
|
||||
) {
|
||||
const widthMm = elem.borderWidth ?? 0;
|
||||
if (widthMm <= 0) return;
|
||||
|
||||
const color = elem.borderColor || '#111827';
|
||||
const lw = Math.max(1, widthMm * scale);
|
||||
const { tl, tr, br, bl } = radii;
|
||||
const inset = lw / 2;
|
||||
|
||||
ctx.strokeStyle = color;
|
||||
ctx.lineWidth = lw;
|
||||
ctx.lineCap = 'butt';
|
||||
ctx.lineJoin = 'round';
|
||||
|
||||
if (sides.top) {
|
||||
ctx.beginPath();
|
||||
if (sides.left && tl > 0) {
|
||||
ctx.arc(x + tl, y + tl, Math.max(0, tl - inset), Math.PI, 1.5 * Math.PI);
|
||||
} else {
|
||||
ctx.moveTo(x, y + inset);
|
||||
}
|
||||
if (sides.right && tr > 0) {
|
||||
ctx.lineTo(x + w - tr, y + inset);
|
||||
ctx.arc(x + w - tr, y + tr, Math.max(0, tr - inset), 1.5 * Math.PI, 2 * Math.PI);
|
||||
} else {
|
||||
ctx.lineTo(x + w, y + inset);
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
if (sides.right) {
|
||||
ctx.beginPath();
|
||||
const topStart = sides.top && tr > 0;
|
||||
if (topStart) {
|
||||
ctx.moveTo(x + w - inset, y + tr);
|
||||
} else {
|
||||
ctx.moveTo(x + w - inset, y);
|
||||
}
|
||||
if (sides.bottom && br > 0) {
|
||||
ctx.lineTo(x + w - inset, y + h - br);
|
||||
ctx.arc(x + w - br, y + h - br, Math.max(0, br - inset), 0, 0.5 * Math.PI);
|
||||
} else {
|
||||
ctx.lineTo(x + w - inset, y + h);
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
if (sides.bottom) {
|
||||
ctx.beginPath();
|
||||
if (sides.right && br > 0) {
|
||||
ctx.moveTo(x + w - inset, y + h - br);
|
||||
ctx.arc(x + w - br, y + h - br, Math.max(0, br - inset), 0, 0.5 * Math.PI);
|
||||
} else {
|
||||
ctx.moveTo(x + w - inset, y + h - inset);
|
||||
}
|
||||
if (sides.left && bl > 0) {
|
||||
ctx.lineTo(x + bl, y + h - inset);
|
||||
ctx.arc(x + bl, y + h - bl, Math.max(0, bl - inset), 0.5 * Math.PI, Math.PI);
|
||||
} else {
|
||||
ctx.lineTo(x, y + h - inset);
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
if (sides.left) {
|
||||
ctx.beginPath();
|
||||
const bottomStart = sides.bottom && bl > 0;
|
||||
if (bottomStart) {
|
||||
ctx.moveTo(x + inset, y + h - bl);
|
||||
} else {
|
||||
ctx.moveTo(x + inset, y + h);
|
||||
}
|
||||
if (sides.top && tl > 0) {
|
||||
ctx.lineTo(x + inset, y + tl);
|
||||
ctx.arc(x + tl, y + tl, Math.max(0, tl - inset), Math.PI, 1.5 * Math.PI);
|
||||
} else {
|
||||
ctx.lineTo(x + inset, y);
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
export function drawElementChrome(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number,
|
||||
elem: TemplateElement,
|
||||
scale: number
|
||||
) {
|
||||
const radii = resolveCornerRadiiPx(elem, scale, w, h);
|
||||
const sides = resolveBorderSides(elem);
|
||||
drawElementFill(ctx, x, y, w, h, elem, radii);
|
||||
drawElementBorders(ctx, x, y, w, h, radii, sides, elem, scale);
|
||||
return { radii, sides };
|
||||
}
|
||||
|
||||
export function toggleSide(
|
||||
current: Partial<Record<ElementSide, boolean>> | undefined,
|
||||
side: ElementSide
|
||||
): Partial<Record<ElementSide, boolean>> {
|
||||
const next = { ...current };
|
||||
const enabled = current?.[side] !== false;
|
||||
next[side] = !enabled;
|
||||
return next;
|
||||
}
|
||||
|
||||
export function isSideEnabled(
|
||||
sides: Partial<Record<ElementSide, boolean>> | undefined,
|
||||
side: ElementSide
|
||||
): boolean {
|
||||
return sides?.[side] !== false;
|
||||
}
|
||||
Reference in New Issue
Block a user