···77//
88// BMP coarse filter: a 64-byte bitmap (1 bit per 128-codepoint block) gates
99// the binary search. Clean blocks return width 1 without touching the table.
1010+//
1111+// Lookup order in wcwidth(): ASCII/Latin-1 fast paths, then a noncharacter
1212+// guard, then codepoint_in_special() which checks (1) a TUI box-drawing fast
1313+// lane, (2) the large contiguous blocks (CJK/Hangul/SIP/tags), (3) the BMP
1414+// coarse filter, (4) the packed small-range binary search.
10151116let UNICODE_BASE = "https://www.unicode.org/Public/16.0.0/ucd";
1217···331336 }
332337}
333338339339+// TUI box-drawing fast lane: the width-1-only prefix of the box-drawing region.
340340+// U+2500..BOX_DRAWING_END (box drawing, block elements, shading, geometric
341341+// shapes) are all width 1, so wcwidth() shortcuts that whole region with one
342342+// range check. The bound is the codepoint just before the first special
343343+// codepoint at/after U+2500 (emoji U+25FD in Unicode 16.0). The assertion makes
344344+// a future revision that widens a glyph in this region fail the build rather
345345+// than silently mis-measure TUI borders.
346346+let firstSpecialAt2500 = Infinity;
347347+for (const r of allRanges) {
348348+ let hi = r.start + r.count;
349349+ if (hi < 0x2500) continue;
350350+ let c = Math.max(r.start, 0x2500);
351351+ if (c <= hi && c < firstSpecialAt2500) firstSpecialAt2500 = c;
352352+}
353353+const boxDrawingEnd = firstSpecialAt2500 - 1;
354354+if (boxDrawingEnd < 0x259f) {
355355+ throw new Error(
356356+ `box fast lane too short: ends at U+${
357357+ boxDrawingEnd.toString(16)
358358+ } (expected >= U+259F)`,
359359+ );
360360+}
361361+334362let tableBytes = 16 * 4 + // bmp_filter
335363 smallPacked.length * 4 +
336364 largeStarts.length * 4 +
···340368console.error(`special_small_ranges: ${smallPacked.length} entries`);
341369console.error(`special_large_ranges: ${largeRanges.length} entries`);
342370console.error(`BMP dirty blocks: ${dirtyBlocks} / 512`);
371371+console.error(
372372+ `Box fast lane: U+2500..U+${boxDrawingEnd.toString(16).toUpperCase()}`,
373373+);
343374console.error(`Table data: ${tableBytes} bytes`);
344375345376// Fingerprint the generated tables (not the source text or comments) so the
···396427 *
397428 * Large-range encoding (special_large_* parallel arrays):
398429 * Used for ranges whose span exceeds 1023 codepoints.
430430+ *
431431+ * TUI box-drawing fast lane (BOX_DRAWING_END):
432432+ * U+2500..BOX_DRAWING_END (box drawing, block elements, shading, geometric
433433+ * shapes) are all width 1, so wcwidth() returns 1 for them without searching.
434434+ *
435435+ * Lookup order in codepoint_in_special(): box fast lane, then the large table
436436+ * (CJK/Hangul/SIP/tags exit in ~3 comparisons), then the BMP coarse filter,
437437+ * then the packed small-range binary search.
399438 */
400439401440#include <stdint.h>
···421460${formatUint8Array(largeWidths)}
422461};
423462#define SPECIAL_LARGE_COUNT ${largeRanges.length}
463463+#define BOX_DRAWING_END 0x${boxDrawingEnd.toString(16)}
424464425465/* clang-format on */
426466427467static int codepoint_in_special(uint32_t codepoint) {
468468+ /* TUI fast lane: U+2500..BOX_DRAWING_END (box drawing, block elements,
469469+ * shading, geometric shapes) are all width 1; skip the search. */
470470+ if (codepoint >= 0x2500 && codepoint <= BOX_DRAWING_END)
471471+ return 1;
472472+ /* Big contiguous blocks (CJK, Hangul, SIP, tag chars) live in the large
473473+ * table - check it first so common ideographs exit in ~3 comparisons
474474+ * instead of missing through all the small ranges. */
475475+ int left = 0, right = SPECIAL_LARGE_COUNT - 1;
476476+ while (left <= right) {
477477+ int mid = (left + right) / 2;
478478+ if (codepoint < special_large_starts[mid])
479479+ right = mid - 1;
480480+ else if (codepoint > special_large_starts[mid] + special_large_counts[mid])
481481+ left = mid + 1;
482482+ else
483483+ return special_large_widths[mid];
484484+ }
428485 if (codepoint <= 0xffff) {
429486 uint32_t block = codepoint >> 7;
430487 if (!((bmp_filter[block >> 5] >> (block & 31u)) & 1u))
431488 return 1;
432489 }
433433- int left = 0, right = SPECIAL_SMALL_COUNT - 1;
490490+ left = 0;
491491+ right = SPECIAL_SMALL_COUNT - 1;
434492 while (left <= right) {
435493 int mid = (left + right) / 2;
436494 uint32_t entry = special_small_ranges[mid];
···441499 left = mid + 1;
442500 else
443501 return (entry & 1) ? 2 : 0;
444444- }
445445- left = 0;
446446- right = SPECIAL_LARGE_COUNT - 1;
447447- while (left <= right) {
448448- int mid = (left + right) / 2;
449449- if (codepoint < special_large_starts[mid])
450450- right = mid - 1;
451451- else if (codepoint > special_large_starts[mid] + special_large_counts[mid])
452452- left = mid + 1;
453453- else
454454- return special_large_widths[mid];
455502 }
456503 return 1;
457504}