import { Injectable, Logger } from "@nestjs/common"; /** * Characters stripped from plate text during normalization. * These invisible Unicode code-points can sneak in via copy-paste * or OCR and break exact-match lookups against the plate letter map. */ const INVISIBLE_CHARS = [ "\u200C", // Zero-Width Non-Joiner "\u200D", // Zero-Width Joiner "\u200E", // Left-to-Right Mark "\u200F", // Right-to-Left Mark "\uFEFF", // BOM / Zero-Width No-Break Space ]; const INVISIBLE_RE = new RegExp(`[${INVISIBLE_CHARS.join("")}]`, "g"); /** * Normalize a Persian license-plate character (or short text) so that * visually-identical Unicode variants collapse to the canonical form * used in the plate letter mapping. * * Steps (in order): * 1. NFKC normalization — folds compatibility equivalents * 2. Strip invisible Unicode characters (ZWJ, ZWNJ, LTR/RTL marks, BOM) * 3. Convert Arabic ي (U+064A) → Persian ی (U+06CC) * 4. Trim leading/trailing whitespace */ export function normalizePlateText(text: string): string { if (!text) return text; let result = String(text); // 1. Unicode NFKC normalization result = result.normalize("NFKC"); // 2. Remove invisible characters result = result.replace(INVISIBLE_RE, ""); // 3. Arabic ي → Persian ی result = result.replace(/\u064A/g, "\u06CC"); // 4. Trim whitespace result = result.trim(); return result; } @Injectable() export class PlateNormalizerService { private readonly logger = new Logger(PlateNormalizerService.name); /** @see module-level {@link normalizePlateText} */ normalizePlateText(text: string): string { return normalizePlateText(text); } /** * Normalize a plate letter and, if it doesn't resolve in the given * mapping, log the raw Unicode code-points for debugging. * * @returns the numeric code from `mapping`, or `null` when the letter * is unknown after normalization. */ resolvePlateLetterCode( letter: string, mapping: Record, ): number | null { const normalized = this.normalizePlateText(letter); const code = mapping[normalized]; if (code !== undefined) return code; this.logger.debug( `Unknown plate letter: ${normalized} [${Array.from(normalized).map((c) => `U+${c.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0")}`).join(", ")}]`, ); return null; } }