/** * Shared helpers for the chart preview renderers. * * Both `chart-renderer.ts` (classic `c:chart`) and * `chart-ex-renderer.ts` (ChartEx `cx:chart`) used to carry their own * copies of the same handful of math / colour / formatting helpers. The * duplicates had drifted over time — subtle differences in NaN guards, * hex normalisation, theme palettes, etc. — so a fix in one file * silently left the other buggy. This module is the single authoritative * home for the helpers shared between the two renderers (plus a few * internals — `escapeXml`, `escapeXmlAttr` — that had identical copies * scattered across the chart pipeline). * * Design constraints: * * - **Zero dependencies on chart-renderer / chart-ex-renderer** so * this module sits below them in the import graph and does not * create cycles. Types-only imports from `./types` are fine. * - **Behaviour-preserving** compared with the consolidated * implementations. The old files had two classes of helper: * (1) strictly identical duplicates and (2) near-duplicates where * one version was more defensive than the other. For the latter we * kept the defensive variant — graceful degradation on malformed * input is always the safer choice for a preview path. * - **Structural rect typing**. The renderers use two different * concrete rect types (`ChartSceneRect` in chart-renderer, * `SvgRect` in chart-ex-renderer) that share the same shape. * Helpers here take structural `{ x: number; y: number; width: * number; height: number }` so both renderers can call them * directly without adapters. */ import type { ChartColor, ChartFill, ChartLine } from "./types.js"; /** * RGB(A) colour triple used by the chart PDF bridge. `a` is optional and * defaults to 1 (fully opaque); surfaces that implement transparency * (e.g. `@pdf/builder` `PdfPageBuilder`) materialise `a < 1` as an * `/ExtGState` resource and emit the corresponding `gs` operator. Older * surfaces that ignore `a` render as opaque, which matches the * pre-alpha behaviour exactly. * * Lives in `chart-utils.ts` so both renderers can produce values * independently; `chart-renderer.ts` re-exports the type to keep the * public surface (`@excel/chart` index / external consumers) stable. */ export interface PdfColor { r: number; g: number; b: number; a?: number; } /** * Structural rect type for helper functions. Both `ChartSceneRect` * (chart-renderer.ts) and the ChartEx renderer's private `SvgRect` * are assignable to this. */ export interface ChartRect { x: number; y: number; width: number; height: number; } /** Default SVG / PNG / PDF canvas width when the caller omits `options.width`. */ export declare const DEFAULT_WIDTH = 640; /** Default SVG / PNG / PDF canvas height when the caller omits `options.height`. */ export declare const DEFAULT_HEIGHT = 360; /** * Default series colour rotation. Matches the Excel 2019+ Office theme * accent1..accent6 palette so a brand-new workbook with no theme XML * still renders with the colours Excel would have used. */ export declare const COLORS: string[]; /** Dark grey for axis lines / frame strokes. */ export declare const AXIS_COLOR = "#444444"; /** Light grey for gridlines. */ export declare const GRID_COLOR = "#D9D9D9"; /** * Convert a theme-palette index (`ChartColor.theme`) back to its * DrawingML `schemeClr` token. Out-of-range indices fall back to * `"dk1"` so a malformed model still produces parseable XML Excel * renders as the first theme slot rather than XML that contains an * `undefined` literal. */ export declare function themeIndexToName(index: number): string; /** * Clamp to the closed interval `[0, 1]`. Non-finite input collapses to * `0` rather than propagating `NaN` — downstream callers use the * result as an interpolation parameter where `NaN` would paint black. */ export declare function clamp01(value: number): number; /** * Map a data-space value onto the plot area's y coordinate space (SVG * y grows downward, so large `value` ends up near the top of the * plot). Guards against `max === min` — a single-point dataset or a * range that collapsed for any reason would otherwise produce `NaN`. */ export declare function valueToY(value: number, min: number, max: number, plot: { y: number; height: number; }): number; /** * Map a data-space value onto the plot area's x coordinate space. Same * `max === min` guard as {@link valueToY}. */ export declare function valueToX(value: number, min: number, max: number, plot: { x: number; width: number; }): number; /** Convert polar `(radius, angle)` to Cartesian around `(cx, cy)`. */ export declare function polar(cx: number, cy: number, radius: number, angle: number): { x: number; y: number; }; /** * Shrink a rect by `amount` on each side (uniform inset). Non-finite * inputs collapse to zero on all four components rather than * propagating `NaN` — `Math.max(0, NaN)` returns `NaN` per ECMAScript, * and a `NaN` width feeding downstream math (e.g. `valueToX`) cascades * into every rendered coordinate. */ export declare function insetRect(rect: T, amount: number): ChartRect; /** * Format a number for SVG attribute values and inline XML. Two-decimal * precision with trailing `.00` stripped (`1.00 → "1"`, `1.50 → * "1.50"` — we intentionally keep the second zero to avoid widening the * diff against Excel's own byte output on round-trips). Non-finite * input returns `"0"` — `Number.toFixed(NaN)` produces the literal * string `"NaN"`, which is invalid inside SVG attribute values and * breaks downstream parsers. */ export declare function fmt(value: number): string; /** * Format a numeric attribute value for XML emission. Returns an empty * string for `undefined` / `NaN` / `±Infinity` — non-finite values would * otherwise serialise as literal `"NaN"` / `"Infinity"` text, which no * XSD validator accepts as `xsd:double` and downstream parsers reject. * Emit an empty string so the caller's attribute-inclusion guard * (`if (attrs.length > 0)` or explicit `!== undefined` check) drops * the attribute entirely rather than stamping garbage into the wire * format. * * Uses JavaScript's default `toString()` which is round-trip-safe per * IEEE 754 (no precision drift) for both integer and fractional input. */ export declare function fmtNumAttr(value: number | undefined): string; /** * Format a numeric text-node value. Same semantics as {@link fmtNumAttr}, * but for use inside element content (e.g. `42.5`). * Non-finite values become `"0"` — a text node must be non-empty to * keep the element well-formed, and `0` is the least-surprising * fallback for a blank / error cell (matches how most OOXML readers * treat empty numeric ``). */ export declare function fmtNumText(value: number): string; /** * Format a number for user-visible axis tick labels. * * - Integer values → plain digits (`42` → `"42"`). * - `|value| < 0.01` but non-zero → scientific notation with three * significant figures (`1.00e-3`). Two-decimal mantissa keeps the * label compact while preserving enough precision that tick values * remain distinguishable; a single-digit mantissa would collapse * consecutive ticks like `1.1e-3` and `1.4e-3` to the same label. * - Otherwise → single decimal (`toFixed(1)`). * - Non-finite input → `""` (tick position still drawn without * label, keeping the chart legible while flagging the upstream * computation bug on inspection). * * This is the "axis-label" formatter — it's intentionally compact and * does not apply OOXML number formats. Callers that need the authored * format code should resolve it upstream. */ export declare function formatNumber(value: number): string; /** * Escape XML text content — `&`, `<`, `>` become `&`, `<`, * `>` respectively. XML-illegal characters are stripped up front * via the shared {@link stripXmlIllegalChars} helper — that covers * the forbidden C0 controls (`0x00-0x08 | 0x0B | 0x0C | 0x0E-0x1F`), * DEL (`0x7F`, project-policy strip despite being technically legal * XML), lone UTF-16 surrogate halves, and the `0xFFFE` / `0xFFFF` * noncharacters. * * Apostrophe and double-quote pass through unescaped — both are legal * inside text content and leaving them alone preserves Excel's byte * output (`'Sheet'!Pivot1` with literal quotes). * * We keep this separate from {@link xmlEncode} in `@xml/encode` * because that helper escapes all five reserved entities; the chart * module prefers byte-level parity with Excel's own output, which * leaves `'` / `"` alone in text context. */ export declare function escapeXml(value: string): string; export { xmlEncodeAttr as escapeXmlAttr } from "../../xml/encode.js"; /** * Normalise any supported hex input to a 6-digit uppercase value * (without the leading `#`). Accepts: * * - 3-digit shorthand `#FFF` → `FFFFFF` (each nibble duplicated). * - 4-digit shorthand `#FFFA` with alpha → `FFFFFF` (alpha dropped; * renderer encodes alpha separately). * - 6-digit `#112233`. * - 8-digit `#11223344` with alpha → `112233` (alpha dropped). * * Returns `undefined` when the input cannot be coerced — lets callers * fall back to a default colour rather than emit `#NaNNaNNaN`. */ export declare function normalizeHex6(hex: string | undefined): string | undefined; /** * White-blend `hex` by `alpha` — a cheap opacity approximation for * SVG surfaces that don't support per-element `fill-opacity`. Accepts * 3/4/6/8-digit hex; returns the input untouched for anything else * rather than emit `"#NaNNaNNaN"`. */ export declare function withAlpha(hex: string, alpha: number): string; /** * Linearly interpolate between two hex colours. `t` is clamped to * `[0, 1]`; non-finite `t` falls back to `0`. Malformed hex inputs * degrade gracefully (prefer the end colour when `t >= 0.5`; prefer * the start colour otherwise) so one bad input channel never produces * `"#NaNNaNNaN"`. */ export declare function interpolateColor(a: string, b: string, t: number): string; /** * Convert a hex colour string to a {@link PdfColor}. Preserves an * explicit alpha byte when the caller supplied 4- or 8-digit hex — * PDF surfaces that honour `PdfColor.a` (notably `PdfPageBuilder` via * `/ExtGState`) then produce real translucency. Malformed input * degrades to opaque black rather than producing `NaN` channels, which * would emit broken PDF content. */ export declare function hexToPdfColor(hex: string): PdfColor; /** * Like {@link hexToPdfColor} but attaches an explicit alpha value. * Callers use this to mirror the SVG path's `withAlpha(color, 0.35)` * pattern on the PDF bridge: the hex itself stays opaque, `a` carries * the transparency the SVG would paint by white-blending. */ export declare function hexToPdfColorWithAlpha(hex: string, alpha: number): PdfColor; /** * Resolve a {@link ChartColor} into a preview-grade hex colour. Accepts * every DrawingML colour variant Excel emits — `srgb`, `theme` (looked * up in {@link THEME_PREVIEW_PALETTE}), `sysClr` (the two Excel keeps * meaningful in the preview — `windowText` / `window`), and `prstClr` * (the ~140-entry {@link PRESET_COLOR_HEX_TABLE}). Returns `undefined` * when the colour is absent or references something this preview cannot * resolve (an unknown preset / sysClr, or a theme index outside the * 12-entry palette). Callers that need a default coalesce with `??`. * * Shared by fill, line, and text-property resolvers so a file that * sets, say, a gridline colour via `` * paints the correct accent-3 hex — previously the line/text paths * only honoured `srgbClr` and silently fell back to the renderer's * default grey. * * Colour modifiers (`tint`, `shade`, `lumMod`, `lumOff`, `alpha`, * `satMod`) are intentionally NOT applied — the preview path doesn't * model the DrawingML blend pipeline, and approximating one component * in isolation produces visibly-wrong results. Authors who need * exact theme-derived colours should rasterise with a full DrawingML * renderer. */ export declare function resolveChartColor(color: ChartColor | undefined): string | undefined; /** * Resolve a {@link ChartFill} into a preview-grade hex colour. Recognises * the four `` children Excel writes (`srgbClr`, `schemeClr`, * `sysClr`, `prstClr`) via the shared {@link resolveChartColor} resolver * and the {@link THEME_PREVIEW_PALETTE} / {@link PRESET_COLOR_HEX_TABLE} * lookup tables for the theme / preset variants. * * When the fill cannot be resolved (no fill set, empty `solidFill`, * unknown preset / system colour), returns `fallback`. Callers that * want `undefined` for misses should pass `undefined` as the fallback; * callers that want a palette rotation entry should resolve the * fallback themselves and pass it in. This unifies what used to be * two almost-identical helpers (`colorFromShapeFill` in * chart-renderer.ts and `shapeFillColor` in chart-ex-renderer.ts). */ export declare function previewShapeFillColor(fill: ChartFill | undefined, fallback: F): F | string; /** * Resolve a {@link ShapeProperties.ln} stroke colour to a preview-grade * hex string. Honours every DrawingML colour variant on the line's own * `` via {@link resolveChartColor} — previously only * `srgbClr` was read, which silently reverted every theme / preset / * sysClr line colour (gridlines, axes, trendlines, error bars) to the * renderer's default grey on load. */ export declare function previewShapeLineColor(line: ChartLine | undefined): string | undefined; /** * Resolve a {@link ShapeProperties.ln} stroke width (OOXML EMU) to a * preview pixel width. OOXML stores line widths in EMU (1 pt = 12 700 * EMU); the preview clamps to a minimum of 0.5 px so a tiny authored * width still renders as a visible stroke. */ export declare function previewShapeLineWidthPx(line: ChartLine | undefined): number | undefined; /** * DrawingML preset colour table (`ST_PresetColorVal` from the OOXML * schema). ~140 named colours — superset of HTML's X11 palette. The * renderers resolve `` through this table so * template-built workbooks with preset colour references (`darkRed`, * `navy`, `forestGreen`, …) render with the correct hex instead of * rotating through the default series palette. * * Previously this lived on `chart-renderer.ts` and was imported by * `chart-ex-renderer.ts`. Moved here so it sits below both renderers * in the import graph. */ export declare const PRESET_COLOR_HEX_TABLE: Readonly>;