/**
* Card composer: Thai QR Payment header + PromptPay sub-mark + QR
* matrix + centre-overlay icon. Layout + sizing follows the official
* BOT/TBA Thai QR Payment brand guideline:
*
* - Card aspect 7.4 cm × 10.5 cm (0.7048) — portrait-ish
* - Navy `#00427A` ("TQR Maximum Blue", brand book §4)
* - Header band ~75 % of card width, ~3.5 : 1 aspect
* - PromptPay sub-mark *left-aligned* under the header (narrow strip)
* - QR ~85 % of card width, centred
* - Centre-overlay icon ~10 % of QR (ECC-H recovers the obscured modules)
*
* ┌──────────────────────────┐
* │ ▓ THAI QR PAYMENT ▓ │ navy band, tight content crop
* │ ⎡Pmt⎤ │ left-aligned narrow sub-mark
* ├──────────────────────────┤
* │ ▓▓▓▓▓▓▓▓▓▓▓▓▓ │
* │ ▓▓▓▓▓ ┌┐ ▓▓▓▓▓ │
* │ ▓▓▓▓▓ └┘ ▓▓▓▓▓ │ small icon overlay (~10%)
* │ ▓▓▓▓▓▓▓▓▓▓▓▓▓ │
* ├──────────────────────────┤
* │ optional merchant name │
* │ optional amount label │
* └──────────────────────────┘
*
* Theme rules:
* - `theme: 'color'` (default) → Thai_QR_Payment_Logo-01 + PromptPay2 (navy)
* - `theme: 'silhouette'` → silhouette variant + PromptPay1 (black/white)
*
* Default PromptPay variant is `PromptPay2` (color theme) because the
* brand book pairs the navy header with the navy PromptPay sub-mark.
* Callers can override with `promptpayLogo` to pick `PromptPay1` (the
* monochrome rounded-border variant) regardless of theme.
*/
import { COLOR_LOGOS, SILHOUETTE_LOGOS } from '@thai-qr-payment/assets';
import type { QRMatrix } from '@thai-qr-payment/qr';
import { escapeXmlAttribute, matrixToPath } from './matrix-svg.js';
export type CardTheme = 'color' | 'silhouette';
export interface CardOptions {
/** Brand artwork flavor. `color` keeps full fidelity, `silhouette` is monochrome. */
theme?: CardTheme;
/**
* Render the caption block (merchant name + amount) below the QR.
* Defaults to `false` — opt in explicitly, then supply the text via
* `merchantName` / `amountLabel`.
*/
showCaption?: boolean;
/** Optional amount label rendered below the QR. Requires `showCaption`. */
amountLabel?: string;
/** Optional merchant name rendered below the QR (above the amount). Requires `showCaption`. */
merchantName?: string;
/** Background colour of the entire card. */
background?: string;
/** Foreground / accent colour for text + silhouette artwork. */
accent?: string;
/** QR module fill colour. Defaults to `#000000` for max scanner contrast. */
qrColor?: string;
/** Override the Thai QR Payment header logo by registry name. */
headerLogo?: keyof typeof COLOR_LOGOS;
/** Override the PromptPay sub-mark by registry name. */
promptpayLogo?: keyof typeof COLOR_LOGOS;
/**
* Overlay the Thai QR Payment icon at the centre of the QR matrix.
* Defaults to `true` for `theme: 'color'`, `false` for `'silhouette'`.
* Always pair with `encodeQR({ errorCorrectionLevel: 'Q' | 'H' })`
* so the obscured modules are recoverable.
*/
centerOverlay?: boolean;
}
// Canvas 600 × 800 (3 : 4). Mirrors kittinan's 1000×1200 5:6 plus 80 px
// of footer room for optional merchant + amount labels.
const CANVAS = { width: 600, height: 800 } as const;
// Header band — full canvas width, ~6:1 aspect (BOT/TBA reference).
// Acts as a solid navy strip; the logo sits centred inside with
// generous padding rather than filling the band edge-to-edge.
const HEADER_BAND = { x: 0, y: 0, width: 600, height: 110 } as const;
// Logo placement inside the header band — 40 % of canvas width,
// centred horizontally with vertical padding.
const HEADER_LOGO = { x: 195, y: 15, width: 210, height: 80 } as const;
// PromptPay sub-mark — centred under the header. PromptPay2 native
// aspect 384 : 130 ≈ 2.95 : 1; we scale to 260 × 88 for a chunky
// readable mark matching the brand reference.
const PROMPTPAY_BAND = { x: 170, y: 140, width: 260, height: 88 } as const;
// QR frame — 500 × 500 (83 % of canvas width, larger than v6 to
// match the brand reference's QR-dominant layout).
const QR_FRAME = { x: 50, y: 250, width: 500, height: 500 } as const;
const QR_INSET = 14;
const QR_BAND = {
x: QR_FRAME.x + QR_INSET,
y: QR_FRAME.y + QR_INSET,
width: QR_FRAME.width - QR_INSET * 2,
height: QR_FRAME.height - QR_INSET * 2,
} as const;
// Tight content bbox of `Thai_QR_Payment_Logo-01.svg`. The raw asset
// is 913 × 376 with ~90 px of empty navy padding top/bottom and ~80 px
// each side. Cropping to the actual icon+wordmark bbox yields the
// brand-guide aspect (~3.55 : 1) and removes the floating-in-empty-navy
// look the previous full-viewBox render produced.
const HEADER_VIEWBOX_CROP = '88 75 750 210';
// Icon-only viewBox for the centre overlay. The icon glyph lives at
// roughly x = 88..318 of the original 0..913 viewBox; we keep the full
// vertical extent so the navy bg paints the centre overlay square.
const ICON_WIDTH = 325;
const ICON_HEIGHT = 376;
const ICON_VIEWBOX = `0 0 ${ICON_WIDTH} ${ICON_HEIGHT}`;
/** White breathing room between the centre logo and the QR modules. */
const OVERLAY_PAD = 4;
/** Strip the outer `` wrapper so the contents can be reused inside a ``. */
function unwrapSvg(svg: string): { viewBox: string; inner: string } {
const viewBoxMatch = svg.match(/viewBox="([^"]+)"/);
let viewBox = viewBoxMatch?.[1];
if (viewBox == null) {
// Asset has no explicit viewBox — derive from width/height. Many
// hand-authored / Illustrator-exported SVGs omit the viewBox and
// rely on width/height alone; without this fallback the symbol
// defaults to "0 0 100 100" and the artwork renders 0.5-px tall.
const widthMatch = svg.match(/