import * as errors from "@superbuilders/errors" import * as logger from "@superbuilders/slog" import { LABEL_AVG_CHAR_WIDTH_PX } from "@/utils/constants" import type { Path2D } from "@/utils/path-builder" import { estimateWrappedTextDimensions } from "@/utils/text" // NEW: 2D extents and finalized SVG types export type Extents2D = { minX: number maxX: number minY: number maxY: number } export type FinalizedSvg = { svgBody: string vbMinX: number vbMinY: number width: number height: number } // NEW: Options for constructing a Canvas implementation export interface CanvasOptions { chartArea: { left: number; top: number; width: number; height: number } fontPxDefault: number lineHeightDefault: number } // --- REMOVED: Legacy type for backward compatibility --- // export type Extents = { minX: number; maxX: number } // NEW: Full Canvas interface as the single source of truth for rendering. // Text drawing must account for rotation and baseline when updating extents. // All methods append markup and synchronously update shared Extents2D. export interface Canvas { // Geometry (clipped rendering region) clipId: string // Scoped clipping method - use this instead of manual SVG string construction drawInClippedRegion(renderFn: (clippedCanvas: Canvas) => void): void // Text primitives drawText(opts: { x: number y: number text: string anchor?: "start" | "middle" | "end" dominantBaseline?: "hanging" | "middle" | "baseline" fontPx?: number fontWeight?: | "100" | "200" | "300" | "400" | "500" | "600" | "700" | "800" | "900" paintOrder?: "stroke fill" stroke?: string strokeWidth?: number | string fill?: string opacity?: number strokeOpacity?: number fillOpacity?: number transform?: string rotate?: { angle: number; cx: number; cy: number } }): void drawWrappedText(opts: { x: number y: number text: string className?: string maxWidthPx: number anchor?: "start" | "middle" | "end" fontPx?: number lineHeight?: number fontWeight?: | "100" | "200" | "300" | "400" | "500" | "600" | "700" | "800" | "900" paintOrder?: "stroke fill" stroke?: string strokeWidth?: number | string fill?: string opacity?: number strokeOpacity?: number fillOpacity?: number transform?: string rotate?: { angle: number; cx: number; cy: number } dominantBaseline?: | "auto" | "alphabetic" | "hanging" | "ideographic" | "mathematical" | "middle" }): void // Basic shapes drawLine( x1: number, y1: number, x2: number, y2: number, style: { stroke: string strokeWidth: number dash?: string strokeLinecap?: "butt" | "round" | "square" strokeLinejoin?: "miter" | "round" | "bevel" strokeMiterlimit?: number markerStart?: string markerMid?: string markerEnd?: string opacity?: number strokeOpacity?: number transform?: string } ): void drawCircle( cx: number, cy: number, r: number, style: { fill?: string stroke?: string strokeWidth?: number dash?: string fillPatternId?: string opacity?: number fillOpacity?: number strokeOpacity?: number transform?: string } ): void drawRect( x: number, y: number, w: number, h: number, style: { fill?: string stroke?: string strokeWidth?: number dash?: string fillPatternId?: string opacity?: number fillOpacity?: number strokeOpacity?: number transform?: string } ): void drawEllipse( cx: number, cy: number, rx: number, ry: number, style: { fill?: string stroke?: string strokeWidth?: number dash?: string fillPatternId?: string opacity?: number fillOpacity?: number strokeOpacity?: number transform?: string } ): void // Complex paths (unclipped) drawPath( path: Path2D, style: { fill?: string stroke?: string strokeWidth?: number dash?: string strokeLinecap?: "butt" | "round" | "square" strokeLinejoin?: "miter" | "round" | "bevel" strokeMiterlimit?: number fillRule?: "nonzero" | "evenodd" markerStart?: string markerMid?: string markerEnd?: string opacity?: number fillOpacity?: number strokeOpacity?: number transform?: string } ): void // Polygon/polyline convenience (arrays for robust extent tracking) drawPolygon( points: Array<{ x: number; y: number }>, style: { fill?: string stroke?: string strokeWidth?: number dash?: string fillRule?: "nonzero" | "evenodd" strokeLinejoin?: "miter" | "round" | "bevel" strokeMiterlimit?: number opacity?: number fillOpacity?: number strokeOpacity?: number transform?: string } ): void drawPolyline( points: Array<{ x: number; y: number }>, style: { stroke: string strokeWidth: number dash?: string strokeLinecap?: "butt" | "round" | "square" strokeLinejoin?: "miter" | "round" | "bevel" strokeMiterlimit?: number opacity?: number strokeOpacity?: number transform?: string } ): void // Complex content drawForeignObject(opts: { x: number y: number width: number height: number content: string transform?: string }): void // Images drawImage(opts: { x: number y: number width: number height: number href: string preserveAspectRatio?: string opacity?: number transform?: string }): void // Legends/blocks drawLegendBlock(opts: { startX: number startY: number rows: Array<{ sample: { stroke: string strokeWidth: number dash?: string marker?: "circle" | "square" } label: string }> rowGapPx: number labelFontPx?: number sampleLengthPx?: number }): void // Advanced SVG structure utilities addDef(def: string): void // append raw content to addStyle(cssRules: string): void // append a