All files / qr-code/src/render ids.ts

100% Statements 11/11
100% Branches 4/4
100% Functions 4/4
100% Lines 10/10

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63                                  48x 48x 1646x 1646x   48x           23x 46x 23x                                           25x               25x    
import type { QRGradient } from "../types";
 
/**
 * Deterministic DOM ids for the SVG fragments a QR code needs to reference —
 * gradients and the logo's clip path.
 *
 * SVG ids are **document-global**. Two codes on one page that reference
 * `url(#logo-clip)` both resolve to whichever element the browser sees first,
 * so an id must encode everything that makes the referenced fragment
 * different: identical definitions may safely share an id, differing ones must
 * never collide. Hashing the definition gives exactly that, and being a pure
 * function of the input it also survives server rendering and hydration —
 * which a counter or a random id would not.
 */
 
/** FNV-1a, rendered base-36. */
function hash(value: string): string {
  let h = 0x811c9dc5;
  for (let i = 0; i < value.length; i++) {
    h ^= value.charCodeAt(i);
    h = Math.imul(h, 0x01000193) >>> 0;
  }
  return h.toString(36);
}
 
/** A stable id for a gradient definition. */
export function gradientId(gradient: QRGradient): string {
  const canonical =
    `${gradient.type}|${gradient.rotation ?? 0}|` +
    gradient.stops.map((stop) => `${stop.offset}:${stop.color}`).join(",");
  return `usefy-qr-g-${hash(canonical)}`;
}
 
/** The identity of a logo clip path — everything that changes its geometry. */
export interface LogoClipIdentity {
  href: string;
  x: number;
  y: number;
  width: number;
  height: number;
  clipCircle: boolean;
}
 
/**
 * A stable id for a logo's clip path.
 *
 * Every geometric input is included. Deriving it from the viewBox alone (an
 * earlier version of this code) meant two codes of the same version and margin
 * shared an id while having *different* clip circles — so the second code's
 * logo was clipped by the first code's geometry and all but disappeared.
 */
export function logoClipId(identity: LogoClipIdentity): string {
  const canonical = [
    identity.href,
    identity.x,
    identity.y,
    identity.width,
    identity.height,
    identity.clipCircle ? "c" : "s",
  ].join("|");
  return `usefy-qr-c-${hash(canonical)}`;
}