import { type PdfColor } from "./chart-utils.js"; import type { ChartMarker, ChartModel, EffectList, LegendPosition } from "./types.js"; export type { PdfColor }; /** * Legacy name — kept so existing imports * (`import { PRESET_COLOR_HEX } from "./chart-renderer.js"`) continue to * resolve. Prefer importing directly from `./chart-utils` in new code. */ export declare const PRESET_COLOR_HEX: Readonly>; /** * Options for the built-in deterministic chart preview renderer. * * The renderer is intentionally lightweight and self-contained; it produces a * stable SVG/PNG/PDF preview of chart data and core styling, not an * Excel-identical layout or rasterization. * * Notable scope boundaries: * * - **Text metrics** come from `@excel/utils/text-metrics` (Calibri/Arial/ * Times per-character widths + ~230 category fallbacks); legend and title * layouts adapt to real label widths. * - **Data labels** honour every `DataLabelPosition` (see `positionDataLabel`). * Pie/doughnut `outEnd`/`bestFit` layouts emit leader lines with a simple * per-hemisphere greedy vertical nudge (`layoutPieLabels`). Bar/line labels * drop or stack when `resolveLabelCollisions` detects bbox overlap. * - **PDF bridge** is feature-matched with the SVG path: labels (with * anchor + color + fontFamily/bold/italic from `txPr`), markers * (square/diamond/triangle/x/plus/circle), error bars, trendlines * (with dash), leader lines, rotated axis titles, and real alpha on * area/bubble/radar/bar3D fills flowing through `PdfColor.a` → * `/ExtGState`. Surfaces that ignore the new optional fields receive * the legacy call shape with pre-anchored coordinates and opaque * colours, so pre-alpha consumers keep working unchanged. * - **3D charts** — `bar3D` renders as a true extruded box (top + front * + right faces) whose axonometric projection is driven by * `view3D.rotX` / `view3D.rotY` / `view3D.rAngAx`. See * {@link resolveBar3DProjection}. The other 3D variants * (`line3D`/`pie3D`/`area3D`/`surface3D`) render as their 2D * equivalents — OOXML `view3D` / `Scene3D` / `ShapeProperties3D` * metadata is preserved in the model but not consumed for those * types; the renderer returns a deterministic preview rather than a * full 3D scene. */ export interface ChartRenderOptions { width?: number; height?: number; title?: string; /** Background fill for SVG/PNG previews. Set to "transparent" for transparent PNG output. */ backgroundColor?: string; /** Output scale multiplier for PNG previews. Useful for high-DPI exports. */ scale?: number; /** PNG output DPI metadata. Stored as a pHYs chunk when provided. */ dpi?: number; /** * Optional geographic data source for ChartEx `regionMap` previews. * When present and the matched features cover all labels, the * renderer draws country (or other region) outlines from the user- * supplied TopoJSON instead of falling back to the built-in * centroid-dot preview. Purely opt-in so the default library has * zero bundled geographic assets — supply e.g. `world-atlas` * `countries-110m.json` via `topology`. * * See {@link RegionMapDataOptions} and * `src/modules/excel/chart/topojson.ts`. */ regionMap?: RegionMapDataOptions; } /** * Individual match rule for {@link RegionMapDataOptions.match}. A rule * is either the literal string `"id"` (compare against `feature.id`) * or `` `property:${propertyKey}` `` (compare against * `feature.properties[propertyKey]`). Multiple rules are combined via * an ordered fall-back array on the options object itself — see * {@link RegionMapDataOptions.match}. */ export type RegionMapMatchRule = "id" | `property:${string}`; /** * User-supplied geographic data for `regionMap` rendering. The caller * is responsible for loading / caching the TopoJSON file — the library * is strictly zero-dependency and ships no map data. */ export interface RegionMapDataOptions { /** * Parsed TopoJSON topology. Compatible with the output of * `world-atlas` bundles or any `topojson-server` emitter. */ topology: unknown; /** * Name of the geometry collection to draw — typically `"countries"` * for `world-atlas`. Throws at render time if missing from the * topology. */ objectName: string; /** * How to match each chart category label to a TopoJSON feature. * * - `"id"` — compare against `feature.id`. * - `"property:"` — compare against `feature.properties[key]`. * Common for world-atlas: `"property:name"`. * * Accepts either a single rule (back-compat) or an ordered fall-back * list (`matchers`-style). When a list is supplied the renderer tries * each rule in order per feature and keeps the first one that finds a * data value for this feature; this is the pattern Natural-Earth- * derived topologies need when the categories are localised (e.g. * try `property:name_zh` first, then `property:name_en`, then fall * back to `id`). Comparison stays case-insensitive and * whitespace-trimmed for every rule. Default: `"id"`. */ match?: RegionMapMatchRule | RegionMapMatchRule[]; /** * Projection to use. Overrides `series.layoutPr.projection`. Supports * the same set the built-in renderer implements (`mercator`, `miller`, * `albers`, `robinson`). */ projection?: "mercator" | "miller" | "albers" | "robinson"; /** * Optional stroke colour for region borders. Default `"#FFFFFF"`. */ strokeColor?: string; } export interface PdfChartRenderOptions extends ChartRenderOptions { x: number; y: number; /** Optional deterministic sink for regression tests; production drawing APIs can ignore it. */ trace?: string[]; } export interface ChartPdfDrawingSurface { drawRect(options: { x: number; y: number; width: number; height: number; fill?: PdfColor; stroke?: PdfColor; /** Stroke width when `stroke` is set. Surfaces that ignore it fall back to 1 px. */ lineWidth?: number; }): this; drawLine(options: { x1: number; y1: number; x2: number; y2: number; color?: PdfColor; /** Stroke width. Surfaces that ignore it fall back to 1 px. */ lineWidth?: number; /** * PDF dash pattern (even entries are "on" lengths, odd entries are "off" * lengths, in points). Omit for a solid stroke. Surfaces without dash * support may ignore this silently. */ dashPattern?: number[]; }): this; /** * Draw a single line of text. `anchor` and `rotation` are honoured by * surfaces that support them; those that do not are expected to * treat the request as `start` / `0°` (i.e. legacy `drawText` behaviour). * Chart callers should not assume rotation is available and should * still pre-compute anchored x coordinates when a visual fallback is * required (see {@link drawPdfText}). */ drawText(text: string, options: { x: number; y: number; fontSize?: number; color?: PdfColor; /** * Degrees, clockwise. Optional; only the PDF bridge built on * `@pdf/builder` honours it today. */ rotation?: number; /** * Horizontal alignment around `x`. Optional; chart code supplies an * explicit pre-anchored `x` as a fallback so surfaces that ignore * this still render at the correct position. */ anchor?: "start" | "middle" | "end"; bold?: boolean; italic?: boolean; fontFamily?: string; }): this; drawCircle?(options: { cx: number; cy: number; r: number; fill?: PdfColor; stroke?: PdfColor; lineWidth?: number; }): this; drawPath?(ops: ChartPdfPathOp[], options?: { fill?: PdfColor; stroke?: PdfColor; closePath?: boolean; lineWidth?: number; dashPattern?: number[]; }): this; } export type ChartPdfPathOp = { op: "move"; x: number; y: number; } | { op: "line"; x: number; y: number; } | { op: "curve"; x1: number; y1: number; x2: number; y2: number; x3: number; y3: number; } | { op: "close"; }; export interface ChartScene { width: number; height: number; title?: ChartSceneText; plot: ChartSceneRect; axes: { x?: ChartSceneLine; y?: ChartSceneLine; x2?: ChartSceneLine; y2?: ChartSceneLine; }; gridlines: ChartSceneLine[]; xLabels: ChartSceneText[]; yLabels: ChartSceneText[]; secondaryXLabels: ChartSceneText[]; secondaryYLabels: ChartSceneText[]; axisTitles: ChartSceneText[]; series: ChartSceneSeries[]; legend: ChartSceneLegend; /** * Data-table overlay drawn below the plot area when * `model.chart.plotArea.dataTable` is set. The preview writes a * compact grid using the same deterministic text-metrics pipeline as * the rest of the scene. Full OOXML `c:dTable` styling is honoured at * the XML-round-trip level; this preview covers the four display * switches (`showHorzBorder`, `showVertBorder`, `showOutline`, * `showKeys`) and the typography derived from `txPr`. When the data * table is present, the primary x-axis labels are suppressed — Excel * does the same so category names only appear once. */ dataTable?: ChartSceneDataTable; /** * SVG filter definitions referenced by individual series via * `effectFilterId`. One entry per unique `a:effectLst` observed on * the normalised series. Rendered into `` by * {@link renderChartSvg}; the PDF surface ignores them because SVG * filters don't map directly onto PDF graphics state. */ effectFilters: ChartSceneEffectFilter[]; } /** * Layout-resolved representation of a `c:dTable` element for the * preview pipeline. Geometry is already in pixel space so SVG/PDF/PNG * bridges can consume it uniformly. */ export interface ChartSceneDataTable { /** Outer rectangle containing the entire table. */ rect: ChartSceneRect; /** * Column boundaries (x coordinates) including the left and right * edges. `columns[0]` is the left edge of the series-name column, * `columns[1]` starts the first category cell, and so on. */ columns: number[]; /** * Row boundaries (y coordinates) including the top and bottom edges. * `rows[0]` is the header row (category names) if it's non-empty, * `rows[1..]` are the per-series rows. */ rows: number[]; /** Text nodes for every cell — series name plus each value. */ cells: ChartSceneText[]; /** * Legend-key swatches drawn to the left of each series name when * `showKeys` is enabled. Colors match the series `color`. */ legendSwatches: Array; /** Border strokes derived from `showHorzBorder` / `showVertBorder` / `showOutline`. */ borders: ChartSceneLine[]; } export interface ChartSceneEffectFilter { /** Stable id the series references in `filter="url(#)"`. */ id: string; /** Full `` XML produced by {@link buildEffectFilter}. */ xml: string; } export interface ChartSceneLegend { items: Array<{ label: string; color: string; }>; rect: ChartSceneRect; visible: boolean; position?: LegendPosition; orientation: "horizontal" | "vertical"; /** * Font-related overrides derived from `model.chart.legend.txPr` * (see {@link textStyleFromTxPr}). When populated these flow into * both the SVG legend emit and the PDF legend renderer so legend * labels match the typography the author requested in the chart * XML. `undefined` fields keep the renderer defaults (10 pt Arial). */ textStyle?: { fontFamily?: string; bold?: boolean; italic?: boolean; fontSize?: number; color?: string; }; } export type ChartSceneSeries = ChartSceneBarSeries | ChartSceneAreaSeries | ChartSceneLineSeries | ChartSceneBubbleSeries | ChartScenePieSeries | ChartSceneRadarSeries | ChartSceneStockSeries | ChartSceneSurfaceSeries; export interface ChartSceneAdornment { labels?: ChartSceneText[]; markers?: ChartSceneMarker[]; trendlines?: ChartSceneTrendline[]; errorBars?: ChartSceneErrorBar[]; /** * Leader lines connecting data labels to their data points. Currently * emitted only by pie/doughnut series when the effective * `DataLabelPosition` places labels outside the slice (`outEnd`, * `bestFit`). The renderer treats them as decorative strokes and does * not attempt collision avoidance beyond the greedy fan-out already * applied by {@link buildDataLabels}. */ leaderLines?: ChartSceneLine[]; /** * SVG `` id assigned by {@link buildChartScene} when this * series carries a DrawingML `a:effectLst` (shadow, glow, reflection, * soft-edge, blur, inner-shadow). `renderChartSvg` emits the matching * `` in the SVG `` block and the series' primary shape * references it via `filter="url(#)"`. * * Only present on series whose `spPr.effectList` was non-empty; other * series render without a filter attribute so the shared `` does * not bloat the output. */ effectFilterId?: string; } export interface ChartSceneBarSeries extends ChartSceneAdornment { type: "bar"; color: string; bars: ChartSceneRect[]; label?: string; horizontal?: boolean; /** * Decorative depth hint (pixels) used only by the SVG/PNG preview to * distinguish `bar3D` from `bar`. When `projection3D` is also present * the renderer uses this depth along with the projection deltas to * emit a proper extruded box (top + front + right side faces, with * shading). For plain `bar` this stays `0` and only the front rect * is drawn. */ depth?: number; /** * Axonometric projection deltas derived from OOXML `view3D.rotX` / * `view3D.rotY`. Populated for `bar3D` series so the renderer can * extrude each bar into a true 3D box: the back face sits at * `(bar.x + dx, bar.y - dy)` and back-right edge at * `(bar.x + bar.width + dx, …)`. `undefined` for plain `bar` series * where no 3D transform applies. */ projection3D?: { dx: number; dy: number; }; } export interface ChartSceneAreaSeries extends ChartSceneAdornment { type: "area"; color: string; points: ChartScenePoint[]; lowerPoints?: ChartScenePoint[]; baselineY: number; label?: string; closed?: boolean; } export interface ChartSceneLineSeries extends ChartSceneAdornment { type: "line" | "scatter"; color: string; points: ChartScenePoint[]; label?: string; smooth?: boolean; showLine?: boolean; } export interface ChartSceneBubbleSeries extends ChartSceneAdornment { type: "bubble"; color: string; bubbles: ChartSceneBubble[]; label?: string; } export interface ChartScenePieSeries extends ChartSceneAdornment { type: "pie" | "doughnut" | "ofPie"; slices: ChartScenePieSlice[]; secondarySlices?: ChartScenePieSlice[]; connectors?: ChartSceneLine[]; label?: string; } export interface ChartSceneRadarSeries extends ChartSceneAdornment { type: "radar"; color: string; points: ChartScenePoint[]; center: ChartScenePoint; radius: number; filled?: boolean; label?: string; } export interface ChartSceneStockSeries extends ChartSceneAdornment { type: "stock"; color: string; candles: ChartSceneStockCandle[]; label?: string; } export interface ChartSceneSurfaceSeries extends ChartSceneAdornment { type: "surface"; cells: ChartSceneSurfaceCell[]; wireframe?: boolean; label?: string; } export interface ChartSceneBubble extends ChartScenePoint { radius: number; } export interface ChartSceneMarker extends ChartScenePoint { color: string; size: number; symbol?: NonNullable; } export interface ChartSceneTrendline { color: string; width?: number; dash?: string; points: ChartScenePoint[]; label?: ChartSceneText; } export interface ChartSceneErrorBar { line: ChartSceneLine; cap1?: ChartSceneLine; cap2?: ChartSceneLine; } export interface ChartSceneStockCandle { x: number; highY: number; lowY: number; openY?: number; closeY?: number; width: number; up: boolean; } export interface ChartSceneSurfaceCell extends ChartSceneRect { color: string; } export interface ChartScenePieSlice { color: string; cx: number; cy: number; radius: number; innerRadius: number; startAngle: number; endAngle: number; } export interface ChartSceneLine { x1: number; y1: number; x2: number; y2: number; color: string; width?: number; } export interface ChartSceneText { x: number; y: number; text: string; fontSize: number; color: string; anchor?: "start" | "middle" | "end"; rotate?: number; /** * Font family typeface, populated from OOXML `a:latin/@typeface` when * the originating `txPr` carries a font. `undefined` keeps the * renderer defaults: `"Arial"` for SVG, `"Helvetica"` for PDF. The * SVG and PDF bridges both honour this — the PDF `FontManager` * handles unknown families by silently mapping to Helvetica. */ fontFamily?: string; /** Bold from `a:rPr/@b` / `a:defRPr/@b`. */ bold?: boolean; /** Italic from `a:rPr/@i` / `a:defRPr/@i`. */ italic?: boolean; } export interface ChartSceneRect { x: number; y: number; width: number; height: number; } export interface ChartScenePoint { x: number; y: number; } export declare function buildChartScene(model: ChartModel, options?: ChartRenderOptions): ChartScene; export declare function renderChartSvg(model: ChartModel, options?: ChartRenderOptions): string; export declare function renderChartPng(model: ChartModel, options?: ChartRenderOptions): Promise; export declare function renderSvgToPng(svg: string, options: Required> & Pick): Promise; export declare function drawChartPdf(page: ChartPdfDrawingSurface, model: ChartModel, options: PdfChartRenderOptions): ChartPdfDrawingSurface; /** * Apply a logarithmic axis transform when the given axis is configured * with `scaling.logBase`. Caller uses the transformed value / min / max * with the plain {@link valueToY} / {@link valueToX} helpers so the * rest of the renderer does not need to know about the log scale. * * Returns the input unchanged when `logBase` is absent / invalid or * when the value is non-positive (log scales can't plot ≤0 values; we * fall back to the raw value rather than `-Infinity` so the point * still shows up somewhere near the bottom). */ export declare function applyAxisTransform(value: number, logBase: number | undefined): number; /** * Translate a DrawingML {@link EffectList} into an SVG `` * element. The translation is lossy — SVG filters don't express every * Excel effect pixel-for-pixel — but they do capture the four effects * that cover the overwhelming majority of real-world usage: * * - `outerShadow` / `presetShadow` → `` + * `` + `` (Excel's outer-shadow dist/dir/blur * maps directly onto SVG's offset + blur-stdDeviation). * - `innerShadow` → `` * carving a shadow inside the shape. * - `glow` → blurred coloured duplicate * under the source. * - `blur` → a single ``. * - `softEdge` → a `` feeding * into the source alpha. * * Reflection is modelled as a semi-transparent y-flipped duplicate; * Excel's actual implementation uses a much more complicated * gradient-masked clone. The preview is recognisable as a reflection * but does not match Excel pixel-for-pixel. * * Callers are expected to embed the returned string inside an SVG * `` block and reference it via `filter="url(#)"` on the * shapes they want to affect. Returns `""` when the effect list is * empty — callers can skip emission entirely. */ export declare function buildEffectFilter(id: string, effects: EffectList | undefined): string;