From 03ea11833a53520da1b3ba2f2443445c67cb4f59 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sun, 6 Sep 2026 08:13:05 +0000 Subject: [PATCH 1/6] Derive the stringWidth tables from the UCD and share one cluster width across stringWidth, sliceAnsi and console.table The width table generator now derives every field from the Unicode 17 Character Database instead of a hand-kept zero-width list and grapheme classes carried over from a Unicode 15.1 table. Format characters (Cf) and unassigned default-ignorable codepoints are zero-width, unassigned codepoints and Indic letters are narrow, Emoji_Presentation codepoints (the regional indicators) are wide, and the grapheme classes include the Unicode 16/17 InCB consonants and Extended_Pictographic changes. GraphemeState moves to stringWidth.h and is shared with sliceAnsi.cpp. A keycap needs a [0-9#*] base, a lone regional indicator is a wide emoji, an emoji modifier extends any base without widening it, the copyright and registered signs are emoji bases, and controls end a cluster on both sides. The UTF-8 width path (console.table column sizing, the markdown renderer) now clusters graphemes the same way as Bun.stringWidth. --- scripts/generate-stringwidth-tables.mjs | 260 +++--- src/jsc/bindings/sliceAnsi.cpp | 87 +- src/jsc/bindings/stringWidth.cpp | 540 ++++------- src/jsc/bindings/stringWidth.h | 154 ++- src/jsc/bindings/stringWidthTables.h | 1135 ++++++++++------------- test/js/bun/util/sliceAnsi.test.ts | 15 +- test/js/bun/util/stringWidth.test.ts | 183 +++- test/js/bun/util/wrapAnsi.test.ts | 236 ++--- 8 files changed, 1262 insertions(+), 1348 deletions(-) diff --git a/scripts/generate-stringwidth-tables.mjs b/scripts/generate-stringwidth-tables.mjs index f97059702cd1..503cfe11068f 100644 --- a/scripts/generate-stringwidth-tables.mjs +++ b/scripts/generate-stringwidth-tables.mjs @@ -4,20 +4,21 @@ // Each codepoint maps to one packed byte: // bits 0-4 grapheme break class (GraphemeBreakClass ordinal, see stringWidth.cpp) // bits 5-6 width class: 0 = zero-width, 1 = narrow, 2 = wide, 3 = East Asian Ambiguous -// bit 7 Emoji property (with the isEmojiPresentation() early-outs baked in) +// bit 7 Emoji property, minus the keycap bases [0-9#*] // -// The width and emoji bits are derived from the Unicode Character Database -// at UNICODE_VERSION (EastAsianWidth.txt, DerivedGeneralCategory.txt, -// emoji-data.txt) plus the zero-width rules in isZeroWidth(). The grapheme -// break class bits are carried over from the previous header (originally -// uucode-derived), so this script needs an existing stringWidthTables.h to -// bootstrap from. +// Every field is derived from the Unicode Character Database at +// UNICODE_VERSION: +// EastAsianWidth.txt width class +// extracted/DerivedGeneralCategory.txt zero-width categories +// DerivedCoreProperties.txt Default_Ignorable_Code_Point, InCB +// auxiliary/GraphemeBreakProperty.txt Grapheme_Cluster_Break +// emoji/emoji-data.txt Emoji, Emoji_Presentation, +// Emoji_Modifier(_Base), Extended_Pictographic // // Usage: bun scripts/generate-stringwidth-tables.mjs [--ucd ] // Rewrites src/jsc/bindings/stringWidthTables.h in place. Downloads // the UCD files from unicode.org unless --ucd points at a directory -// holding EastAsianWidth.txt, DerivedGeneralCategory.txt and -// emoji-data.txt for UNICODE_VERSION. +// holding the five files above (by base name) for UNICODE_VERSION. import { readFileSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; @@ -27,7 +28,6 @@ const UNICODE_VERSION = "17.0.0"; const UCD_BASE = `https://www.unicode.org/Public/${UNICODE_VERSION}/ucd`; const headerPath = join(dirname(fileURLToPath(import.meta.url)), "../src/jsc/bindings/stringWidthTables.h"); -const header = readFileSync(headerPath, "utf8"); // --------------------------------------------------------------------------- // Load Unicode data @@ -49,13 +49,18 @@ async function loadUCD(remotePath) { const eastAsianWidthText = await loadUCD("EastAsianWidth.txt"); const generalCategoryText = await loadUCD("extracted/DerivedGeneralCategory.txt"); +const corePropertiesText = await loadUCD("DerivedCoreProperties.txt"); +const graphemeBreakText = await loadUCD("auxiliary/GraphemeBreakProperty.txt"); const emojiDataText = await loadUCD("emoji/emoji-data.txt"); -// Parse `LO(..HI) ; VALUE # comment` lines whose VALUE is accepted by `wanted`. +// Parse `LO(..HI) ; VALUE [; VALUE2] # comment` lines. `wanted` receives the +// property value ("Mn", "W", "Extended_Pictographic") or, for two-field lines +// such as `InCB; Linker`, "InCB=Linker". function parseUCDRanges(text, wanted) { const ranges = []; - for (const m of text.matchAll(/^([0-9A-F]{4,6})(?:\.\.([0-9A-F]{4,6}))?\s*;\s*(\w+)\s*#/gm)) { - if (!wanted(m[3])) continue; + for (const m of text.matchAll(/^([0-9A-F]{4,6})(?:\.\.([0-9A-F]{4,6}))?\s*;\s*(\w+)\s*(?:;\s*(\w+)\s*)?#/gm)) { + const value = m[4] === undefined ? m[3] : `${m[3]}=${m[4]}`; + if (!wanted(value)) continue; ranges.push([parseInt(m[1], 16), m[2] === undefined ? parseInt(m[1], 16) : parseInt(m[2], 16)]); } return mergeRanges(ranges); @@ -72,38 +77,6 @@ function mergeRanges(ranges) { return merged; } -// East Asian Width `W` (wide) + `F` (fullwidth), and `A` (ambiguous). -const wideRanges = parseUCDRanges(eastAsianWidthText, type => type === "W" || type === "F"); -const ambiguousRanges = parseUCDRanges(eastAsianWidthText, type => type === "A"); -// General_Category Mn (nonspacing mark) + Me (enclosing mark). -const nonspacingMarkRanges = parseUCDRanges(generalCategoryText, gc => gc === "Mn" || gc === "Me"); -// The `Emoji` binary property (not Emoji_Presentation / Emoji_Modifier / ...). -const emojiRanges = parseUCDRanges(emojiDataText, property => property === "Emoji"); - -// --------------------------------------------------------------------------- -// Carry the grapheme break classes over from the existing table -// --------------------------------------------------------------------------- - -function parseArray(name) { - const m = header.match(new RegExp(`${name}\\[[0-9]*\\] = \\{([\\s\\S]*?)\\n\\};`)); - if (!m) throw new Error(`could not find ${name} in stringWidthTables.h (needed to bootstrap the grapheme classes)`); - return m[1] - .split(/[,\s]+/) - .filter(tok => /^\d+$/.test(tok)) - .map(Number); -} - -const stage1 = parseArray("kGraphemeBreakStage1"); -const stage2 = parseArray("kGraphemeBreakStage2"); -const stage3 = parseArray("kGraphemeBreakStage3"); - -// Existing stage3 holds fused bytes; the grapheme class is always the low 5 bits. -const classOf = cp => { - const high = cp >> 8; - const low = cp & 0xff; - return stage3[stage2[stage1[high] + low]] & 0x1f; -}; - const inRanges = (cp, ranges) => { let lo = 0; let hi = ranges.length; @@ -115,88 +88,152 @@ const inRanges = (cp, ranges) => { return lo > 0 && cp <= ranges[lo - 1][1]; }; +const wideRanges = parseUCDRanges(eastAsianWidthText, type => type === "W" || type === "F"); +const ambiguousRanges = parseUCDRanges(eastAsianWidthText, type => type === "A"); + +const categoryRanges = name => parseUCDRanges(generalCategoryText, gc => gc === name); +const controlRanges = categoryRanges("Cc"); +const formatRanges = categoryRanges("Cf"); +const surrogateRanges = categoryRanges("Cs"); +const unassignedRanges = categoryRanges("Cn"); +const nonspacingMarkRanges = parseUCDRanges(generalCategoryText, gc => gc === "Mn" || gc === "Me"); +const spacingMarkRanges = categoryRanges("Mc"); + +const defaultIgnorableRanges = parseUCDRanges(corePropertiesText, p => p === "Default_Ignorable_Code_Point"); +const incbLinkerRanges = parseUCDRanges(corePropertiesText, p => p === "InCB=Linker"); +const incbConsonantRanges = parseUCDRanges(corePropertiesText, p => p === "InCB=Consonant"); + +const graphemeRanges = name => parseUCDRanges(graphemeBreakText, p => p === name); +const gcb = { + Control: graphemeRanges("Control"), + Extend: graphemeRanges("Extend"), + Prepend: graphemeRanges("Prepend"), + RegionalIndicator: graphemeRanges("Regional_Indicator"), + SpacingMark: graphemeRanges("SpacingMark"), + L: graphemeRanges("L"), + V: graphemeRanges("V"), + T: graphemeRanges("T"), + LV: graphemeRanges("LV"), + LVT: graphemeRanges("LVT"), + ZWJ: graphemeRanges("ZWJ"), +}; + +const emojiProperty = name => parseUCDRanges(emojiDataText, p => p === name); +const emojiRanges = emojiProperty("Emoji"); +const emojiPresentationRanges = emojiProperty("Emoji_Presentation"); +const emojiModifierRanges = emojiProperty("Emoji_Modifier"); +const emojiModifierBaseRanges = emojiProperty("Emoji_Modifier_Base"); +const extendedPictographicRanges = emojiProperty("Extended_Pictographic"); + // Guard against a UCD format change silently emptying a table. -if (!inRanges(0x4e00, wideRanges) || !inRanges(0x300, nonspacingMarkRanges) || !inRanges(0x1f600, emojiRanges)) +if ( + !inRanges(0x4e00, wideRanges) || + !inRanges(0x300, nonspacingMarkRanges) || + !inRanges(0x1f600, emojiRanges) || + !inRanges(0x94d, incbLinkerRanges) || + !inRanges(0x1f1e6, gcb.RegionalIndicator) || + !inRanges(0xe0080, defaultIgnorableRanges) +) throw new Error("UCD parse sanity check failed"); +// --------------------------------------------------------------------------- +// Grapheme break class (GraphemeBreakClass ordinals in stringWidth.cpp) +// --------------------------------------------------------------------------- + +const GraphemeBreakClass = { + Other: 0, + Prepend: 1, + RegionalIndicator: 2, + SpacingMark: 3, + L: 4, + V: 5, + T: 6, + Lv: 7, + Lvt: 8, + Zwj: 9, + Zwnj: 10, + ExtendedPictographic: 11, + EmojiModifierBase: 12, + EmojiModifier: 13, + IndicConjunctBreakExtend: 14, + IndicConjunctBreakLinker: 15, + IndicConjunctBreakConsonant: 16, + Control: 17, +}; + +// Grapheme_Cluster_Break, refined with the emoji and Indic Conjunct Break +// properties the break rules GB9c and GB11 need. CR and LF are Control here: +// the width code never needs CR LF kept as one cluster. +function graphemeBreakClass(cp) { + if (inRanges(cp, gcb.Control) || cp === 0x0d || cp === 0x0a) return GraphemeBreakClass.Control; + if (inRanges(cp, incbLinkerRanges)) return GraphemeBreakClass.IndicConjunctBreakLinker; + if (inRanges(cp, emojiModifierRanges)) return GraphemeBreakClass.EmojiModifier; + if (cp === 0x200c) return GraphemeBreakClass.Zwnj; + if (inRanges(cp, gcb.Extend)) return GraphemeBreakClass.IndicConjunctBreakExtend; + if (inRanges(cp, gcb.ZWJ)) return GraphemeBreakClass.Zwj; + if (inRanges(cp, gcb.Prepend)) return GraphemeBreakClass.Prepend; + if (inRanges(cp, gcb.RegionalIndicator)) return GraphemeBreakClass.RegionalIndicator; + if (inRanges(cp, gcb.SpacingMark)) return GraphemeBreakClass.SpacingMark; + if (inRanges(cp, gcb.L)) return GraphemeBreakClass.L; + if (inRanges(cp, gcb.V)) return GraphemeBreakClass.V; + if (inRanges(cp, gcb.T)) return GraphemeBreakClass.T; + if (inRanges(cp, gcb.LV)) return GraphemeBreakClass.Lv; + if (inRanges(cp, gcb.LVT)) return GraphemeBreakClass.Lvt; + if (inRanges(cp, emojiModifierBaseRanges)) return GraphemeBreakClass.EmojiModifierBase; + if (inRanges(cp, extendedPictographicRanges)) return GraphemeBreakClass.ExtendedPictographic; + if (inRanges(cp, incbConsonantRanges)) return GraphemeBreakClass.IndicConjunctBreakConsonant; + return GraphemeBreakClass.Other; +} + // --------------------------------------------------------------------------- // Width classification // --------------------------------------------------------------------------- -// Zero-width: controls, every nonspacing/enclosing mark (Mn/Me), conjoining -// Hangul jungseong/jongseong, and the invisible format characters glibc -// wcwidth() and string-width treat as zero-width. Deliberate deviations: -// prepended concatenation marks (U+0600..) are zero-width, and the Indic -// block heuristic below also zero-widths spacing vowel signs (Mc) so a -// consonant+vowel-sign syllable stays one column. +// Zero-width: controls (Cc), format characters (Cf: soft hyphen, bidi +// controls, ZWSP/ZWJ/ZWNJ, tags, BOM, ...), nonspacing and enclosing marks +// (Mn/Me), surrogates, unassigned default-ignorable codepoints (reserved for +// future format characters), and the conjoining Hangul jungseong/jongseong +// (Grapheme_Cluster_Break V/T in the Hangul Jamo blocks; the Kirat Rai vowel +// signs are V too but are visible letters): an L+V(+T) cluster is 2 columns +// wide, all carried by the leading consonant. Deliberate deviation: the +// spacing vowel signs (Mc) of the Indic blocks U+0900-U+0DFF are zero-width +// too, so a consonant+vowel-sign syllable stays one column. function isZeroWidth(cp) { - if (cp <= 0x1f) return true; - if (cp >= 0x7f && cp <= 0x9f) return true; - if (cp === 0xad) return true; + if (inRanges(cp, controlRanges)) return true; + if (inRanges(cp, formatRanges)) return true; if (inRanges(cp, nonspacingMarkRanges)) return true; - // Hangul jungseong (V) and jongseong (T): an L+V(+T) conjoining cluster is - // 2 columns wide, all carried by the leading consonant. - if (cp >= 0x1160 && cp <= 0x11ff) return true; - if (cp >= 0xd7b0 && cp <= 0xd7ff) return true; - if (cp >= 0x200b && cp <= 0x200f) return true; - // The bidi embedding/override controls (LRE..RLO) and the rest of the - // U+2060-U+206F block (word joiner, invisible operators, the LRI..PDI bidi - // isolates, deprecated format characters): invisible format characters, - // exactly like U+200B-U+200F above. - if (cp >= 0x202a && cp <= 0x202e) return true; - if (cp >= 0x2060 && cp <= 0x206f) return true; - // U+061C ARABIC LETTER MARK, the remaining bidi control character. - if (cp === 0x61c) return true; - // The remaining default-ignorable format characters (Cf): the shorthand and - // musical notation controls. glibc wcwidth() and string-width return 0. - if (cp >= 0x1bca0 && cp <= 0x1bca3) return true; - if (cp >= 0x1d173 && cp <= 0x1d17a) return true; - // Mongolian free variation selectors and vowel separator. - if (cp >= 0x180b && cp <= 0x180f) return true; - // Blocks reserved wholly for combining marks: their unassigned tails stay - // zero-width too. - if (cp >= 0x1ab0 && cp <= 0x1aff) return true; - if (cp >= 0x20d0 && cp <= 0x20ff) return true; - if (cp === 0xfeff) return true; - if (cp >= 0xd800 && cp <= 0xdfff) return true; - if ((cp >= 0x600 && cp <= 0x605) || cp === 0x6dd || cp === 0x70f || cp === 0x8e2) return true; - // Indic vowel-sign heuristic (covers the spacing Mc vowel signs, not just - // the Mn ones): a consonant plus vowel sign renders as one column. - if (cp >= 0x900 && cp <= 0xd4f) { - const offset = cp & 0x7f; - if (offset <= 0x02) return true; - if (offset >= 0x3a && offset <= 0x4d && offset !== 0x3d) return true; - if (offset >= 0x51 && offset <= 0x57) return true; - if (offset >= 0x62 && offset <= 0x63) return true; - } - // Tag characters. - if (cp >= 0xe0000 && cp <= 0xe007f) return true; + if (inRanges(cp, surrogateRanges)) return true; + if (inRanges(cp, unassignedRanges) && inRanges(cp, defaultIgnorableRanges)) return true; + if (cp <= 0xd7ff && (inRanges(cp, gcb.V) || inRanges(cp, gcb.T))) return true; + if (cp >= 0x900 && cp <= 0xdff && inRanges(cp, spacingMarkRanges)) return true; return false; } -// 0 = zero-width, 1 = narrow, 2 = wide, 3 = ambiguous +// 0 = zero-width, 1 = narrow, 2 = wide, 3 = ambiguous. Emoji_Presentation +// codepoints are wide even when their East Asian Width is not W/F (the +// regional indicators). function widthClass(cp) { if (isZeroWidth(cp)) return 0; - if (inRanges(cp, wideRanges)) return 2; + if (inRanges(cp, wideRanges) || inRanges(cp, emojiPresentationRanges)) return 2; if (inRanges(cp, ambiguousRanges)) return 3; return 1; } -// Emoji property with the isEmojiPresentation() early-outs baked in. +// The Emoji property, minus the keycap bases: a digit, '#' or '*' is only +// emoji as part of a keycap sequence, which the width code detects through +// U+20E3 itself. function isEmoji(cp) { - if (cp < 0x203c) return false; - if (cp >= 0x2c00 && cp < 0x1f000) return false; - if (cp === 0xfe0e || cp === 0xfe0f || cp === 0x200d) return false; + if ((cp >= 0x30 && cp <= 0x39) || cp === 0x23 || cp === 0x2a) return false; return inRanges(cp, emojiRanges); } -const packed = cp => classOf(cp) | (widthClass(cp) << 5) | (isEmoji(cp) ? 0x80 : 0); +const packed = cp => graphemeBreakClass(cp) | (widthClass(cp) << 5) | (isEmoji(cp) ? 0x80 : 0); // --------------------------------------------------------------------------- -// Rebuild the 3-stage table over the packed values +// Build the 3-stage table over the packed values // --------------------------------------------------------------------------- -const highCount = stage1.length; // 8192 blocks of 256 codepoints +const highCount = 0x1100; // blocks of 256 codepoints up to U+10FFFF const blockKeyToOffset = new Map(); const newStage1 = new Array(highCount); const newStage2 = []; @@ -228,12 +265,10 @@ for (let high = 0; high < highCount; high++) { if (newStage2.length - 256 > 0xffff) throw new Error("stage1 offsets no longer fit in uint16_t"); -// Verify the rebuilt table roundtrips for every codepoint and preserves the -// original grapheme classes exactly. +// Verify the built table roundtrips for every codepoint. for (let cp = 0; cp <= 0x10ffff; cp++) { const got = newStage3[newStage2[newStage1[cp >> 8] + (cp & 0xff)]]; if (got !== packed(cp)) throw new Error(`roundtrip mismatch at U+${cp.toString(16)}`); - if ((got & 0x1f) !== classOf(cp)) throw new Error(`class mismatch at U+${cp.toString(16)}`); } // --------------------------------------------------------------------------- @@ -256,16 +291,15 @@ const output = `// clang-format off // 3-stage lookup of the packed per-codepoint classification used by // stringWidth.cpp: stage1[cp >> 8] + (cp & 0xFF) indexes stage2, which // indexes stage3. Each stage3 byte packs: -// bits 0-4 GraphemeBreakClass ordinal (grapheme break property + Indic -// Conjunct Break, uucode-derived) +// bits 0-4 GraphemeBreakClass ordinal: Grapheme_Cluster_Break refined +// with Indic_Conjunct_Break, Emoji_Modifier(_Base) and +// Extended_Pictographic // bits 5-6 width class: 0 zero-width, 1 narrow, 2 wide (East Asian Width -// W/F), 3 East Asian Ambiguous -// bit 7 the Unicode Emoji property, with the isEmojiPresentation() -// early-outs (< U+203C, [U+2C00, U+1F000), VS15/VS16/ZWJ) baked in -// Zero-width: C0/C1 controls, soft hyphen, every Mn/Me mark, conjoining Hangul -// jungseong/jongseong (U+1160-U+11FF, U+D7B0-U+D7FF), invisible format -// characters (bidi controls, ZWSP/ZWJ/ZWNJ, U+2060-206F, tags, BOM), -// surrogates and the Indic vowel-sign heuristic; see isZeroWidth() in the +// W/F or Emoji_Presentation), 3 East Asian Ambiguous +// bit 7 the Unicode Emoji property, minus the keycap bases [0-9#*] +// Zero-width: Cc, Cf, Mn, Me, surrogates, unassigned default-ignorable +// codepoints, conjoining Hangul jungseong/jongseong, and the spacing vowel +// signs (Mc) of the Indic blocks U+0900-U+0DFF; see isZeroWidth() in the // generator. #pragma once diff --git a/src/jsc/bindings/sliceAnsi.cpp b/src/jsc/bindings/sliceAnsi.cpp index fd4db0bdb043..6288473a748a 100644 --- a/src/jsc/bindings/sliceAnsi.cpp +++ b/src/jsc/bindings/sliceAnsi.cpp @@ -1,6 +1,7 @@ #include "root.h" #include "sliceAnsi.h" #include "ANSIHelpers.h" +#include "stringWidth.h" #include #include @@ -9,98 +10,26 @@ // Native exports (implemented in stringWidth.cpp) for visible width and grapheme break extern "C" uint8_t Bun__codepointWidth(uint32_t cp, bool ambiguous_as_wide); extern "C" bool Bun__graphemeBreak(uint32_t cp1, uint32_t cp2, uint8_t* state); -extern "C" bool Bun__isEmojiPresentation(uint32_t cp); extern "C" size_t Bun__visibleWidthExcludeANSI_latin1(const uint8_t* ptr, size_t len, bool ambiguous_as_wide); extern "C" size_t Bun__visibleWidthExcludeANSI_utf16(const uint16_t* ptr, size_t len, bool ambiguous_as_wide); namespace Bun { using namespace WTF; -// Shared SIMD/SGR helpers live in ANSIHelpers.h. We keep a local -// GraphemeWidthState mirror of stringWidth.cpp's GraphemeState because these are -// called per-codepoint in the hot loop — extern-call overhead would hurt more -// than the ~80 lines of duplication. Drift is caught by tests that assert -// Bun.stringWidth(s) == width of Bun.sliceAnsi(s, 0, N) for edge cases. - -// ============================================================================ -// Grapheme-aware Visible Width (mirrors stringWidth.cpp GraphemeState; see above) -// ============================================================================ - -struct GraphemeWidthState { - uint32_t firstCp = 0; - uint32_t lastCp = 0; - uint16_t nonEmojiWidth = 0; - uint8_t baseWidth = 0; - uint8_t count = 0; - bool emojiBase = false; - bool keycap = false; - bool regionalIndicator = false; - bool skinTone = false; - bool zwj = false; - bool vs15 = false; - bool vs16 = false; +// Shared SIMD/SGR helpers live in ANSIHelpers.h. Cluster widths come from +// StringWidth::GraphemeState (stringWidth.h), the same accumulator +// Bun.stringWidth uses, so a slice is always measured the way the whole +// string is. +struct GraphemeWidthState : StringWidth::GraphemeState { void reset(uint32_t cp, bool ambiguousIsWide) { - firstCp = cp; - lastCp = cp; - count = 1; - keycap = (cp == 0x20E3); - regionalIndicator = (cp >= 0x1F1E6 && cp <= 0x1F1FF); - skinTone = (cp >= 0x1F3FB && cp <= 0x1F3FF); - zwj = (cp == 0x200D); - vs15 = false; - vs16 = false; - - uint8_t w = Bun__codepointWidth(cp, ambiguousIsWide); - baseWidth = w; - nonEmojiWidth = w; - emojiBase = Bun__isEmojiPresentation(cp); + StringWidth::GraphemeState::reset(cp, StringWidth::fusedClassify(cp), ambiguousIsWide); } void add(uint32_t cp, bool ambiguousIsWide) { - lastCp = cp; - if (count < 255) - count++; - keycap = keycap || (cp == 0x20E3); - regionalIndicator = regionalIndicator || (cp >= 0x1F1E6 && cp <= 0x1F1FF); - skinTone = skinTone || (cp >= 0x1F3FB && cp <= 0x1F3FF); - zwj = zwj || (cp == 0x200D); - vs15 = vs15 || (cp == 0xFE0E); - vs16 = vs16 || (cp == 0xFE0F); - - uint8_t w = Bun__codepointWidth(cp, ambiguousIsWide); - if (w > 0) { - uint16_t newWidth = nonEmojiWidth + w; - nonEmojiWidth = newWidth < 1023 ? newWidth : 1023; - } - } - - uint8_t width() const - { - if (count == 0) - return 0; - if (regionalIndicator && count >= 2) - return 2; - if (keycap) - return 2; - if (regionalIndicator) - return 1; // Single (unpaired) regional indicator is width 1 - if (emojiBase && (skinTone || zwj)) - return 2; - // VS16 widens only a base with the Emoji property to emoji - // presentation; the fused emoji bit early-outs below U+203C, where - // (c) and (R) are the only non-keycap Emoji codepoints. Zero-width - // and narrow non-emoji bases keep their own width under VS15/VS16. - if (vs15 || vs16) { - if (baseWidth == 2 || (vs16 && (emojiBase || firstCp == 0xA9 || firstCp == 0xAE))) - return 2; - return baseWidth; - } - // Match stringWidth.cpp GraphemeState::width() exactly: return accumulated width - // (may be 0 for zero-width-only clusters like U+200B ZWSP). - return static_cast(nonEmojiWidth); + StringWidth::GraphemeState::add(cp, StringWidth::fusedClassify(cp), ambiguousIsWide); } }; diff --git a/src/jsc/bindings/stringWidth.cpp b/src/jsc/bindings/stringWidth.cpp index 65cda77f301e..618ce420f5e1 100644 --- a/src/jsc/bindings/stringWidth.cpp +++ b/src/jsc/bindings/stringWidth.cpp @@ -31,8 +31,8 @@ #include #include -#include #include +#include #include #include @@ -48,24 +48,10 @@ namespace Bun { namespace StringWidth { // ============================================================================ -// Codepoint classification (grapheme break class + width + emoji, one lookup) +// Codepoint classification // ============================================================================ -// Each codepoint maps to one packed byte via the 3-stage table in -// stringWidthTables.h (regenerate with scripts/generate-stringwidth-tables.mjs): -// bits 0-4 GraphemeBreakClass ordinal -// bits 5-6 width class: 0 zero-width, 1 narrow, 2 wide, 3 East Asian Ambiguous -// bit 7 Emoji property (with the isEmojiPresentation() early-outs baked in) -// The generator derives all three fields from the Unicode Character -// Database version pinned in the script (EastAsianWidth.txt, -// DerivedGeneralCategory.txt, emoji-data.txt). -static constexpr uint8_t kFusedClassMask = 0x1F; -static constexpr uint8_t kFusedWidthShift = 5; -static constexpr uint8_t kFusedWidthMask = 0x3; -static constexpr uint8_t kFusedWidthAmbiguous = 3; -static constexpr uint8_t kFusedEmojiBit = 0x80; - -static constexpr uint8_t fusedClassify(char32_t cp) +static constexpr uint8_t classifyFromTable(char32_t cp) { const size_t high = cp >> 8; const size_t low = cp & 0xFF; @@ -73,80 +59,57 @@ static constexpr uint8_t fusedClassify(char32_t cp) return StringWidthTables::kGraphemeBreakStage3[StringWidthTables::kGraphemeBreakStage2[stage2Index]]; } -// Terminal column width from a packed classification byte. -static constexpr uint8_t widthFromFused(uint8_t packed, bool ambiguousAsWide) -{ - const uint8_t width = (packed >> kFusedWidthShift) & kFusedWidthMask; - if (width == kFusedWidthAmbiguous) - return ambiguousAsWide ? 2 : 1; - return width; -} - // Spot-check the generated table against known codepoints. -static_assert(widthFromFused(fusedClassify(U'A'), false) == 1); -static_assert(widthFromFused(fusedClassify(0x1B), false) == 0); // ESC: control, zero width -static_assert(widthFromFused(fusedClassify(0xAD), false) == 0); // soft hyphen -static_assert(widthFromFused(fusedClassify(0x202E), false) == 0); // RLO: bidi control, zero width -static_assert(widthFromFused(fusedClassify(0x2069), false) == 0); // PDI: bidi isolate, zero width -static_assert(widthFromFused(fusedClassify(0x61C), false) == 0); // arabic letter mark, zero width -static_assert(widthFromFused(fusedClassify(0x1BCA0), false) == 0); // shorthand format control, zero width -static_assert(widthFromFused(fusedClassify(0x1D173), false) == 0); // musical format control, zero width -static_assert(widthFromFused(fusedClassify(0x4E2D), false) == 2); // CJK ideograph: wide -static_assert(widthFromFused(fusedClassify(0xFF21), false) == 2); // fullwidth A: wide -static_assert(widthFromFused(fusedClassify(0xA7), false) == 1); // section sign: ambiguous, narrow by default -static_assert(widthFromFused(fusedClassify(0xA7), true) == 2); // section sign: ambiguous as wide -static_assert((fusedClassify(0x1F600) & kFusedEmojiBit) != 0); // emoji -static_assert((fusedClassify(U'#') & kFusedEmojiBit) == 0); // '#': below the U+203C early-out -static_assert((fusedClassify(0xFE0F) & kFusedEmojiBit) == 0); // VS16 handled separately -static_assert(widthFromFused(fusedClassify(0x0591), false) == 0); // hebrew accent (Mn): zero width -static_assert(widthFromFused(fusedClassify(0x1161), false) == 0); // hangul jungseong: zero width -static_assert(widthFromFused(fusedClassify(0x1112), false) == 2); // hangul choseong: wide -static_assert(widthFromFused(fusedClassify(0x4DC0), false) == 2); // yijing hexagram: wide since Unicode 16 -static_assert((fusedClassify(0x1FA89) & kFusedEmojiBit) != 0); // Unicode 16 emoji - -uint8_t visibleCodepointWidth(char32_t cp, bool ambiguousAsWide) +static_assert(widthFromFused(classifyFromTable(U'A'), false) == 1); +static_assert(widthFromFused(classifyFromTable(0x1B), false) == 0); // ESC: control, zero width +static_assert(widthFromFused(classifyFromTable(0xAD), false) == 0); // soft hyphen +static_assert(widthFromFused(classifyFromTable(0x202E), false) == 0); // RLO: bidi control, zero width +static_assert(widthFromFused(classifyFromTable(0x2069), false) == 0); // PDI: bidi isolate, zero width +static_assert(widthFromFused(classifyFromTable(0x61C), false) == 0); // arabic letter mark, zero width +static_assert(widthFromFused(classifyFromTable(0x1BCA0), false) == 0); // shorthand format control, zero width +static_assert(widthFromFused(classifyFromTable(0x1D173), false) == 0); // musical format control, zero width +static_assert(widthFromFused(classifyFromTable(0xFFF9), false) == 0); // interlinear annotation anchor (Cf) +static_assert(widthFromFused(classifyFromTable(0xFFF0), false) == 0); // unassigned default-ignorable +static_assert(widthFromFused(classifyFromTable(0x4E2D), false) == 2); // CJK ideograph: wide +static_assert(widthFromFused(classifyFromTable(0xFF21), false) == 2); // fullwidth A: wide +static_assert(widthFromFused(classifyFromTable(0x1F1E6), false) == 2); // regional indicator: Emoji_Presentation +static_assert(widthFromFused(classifyFromTable(0xA7), false) == 1); // section sign: ambiguous, narrow by default +static_assert(widthFromFused(classifyFromTable(0xA7), true) == 2); // section sign: ambiguous as wide +static_assert((classifyFromTable(0x1F600) & kFusedEmojiBit) != 0); // emoji +static_assert((classifyFromTable(0xA9) & kFusedEmojiBit) != 0); // copyright sign: Emoji (text default) +static_assert((classifyFromTable(U'#') & kFusedEmojiBit) == 0); // keycap base +static_assert((classifyFromTable(0xFE0F) & kFusedEmojiBit) == 0); // VS16 handled separately +static_assert(widthFromFused(classifyFromTable(0x0591), false) == 0); // hebrew accent (Mn): zero width +static_assert(widthFromFused(classifyFromTable(0x093F), false) == 0); // devanagari vowel sign i (Mc): zero width +static_assert(widthFromFused(classifyFromTable(0x0980), false) == 1); // bengali anji (Lo): narrow +static_assert(widthFromFused(classifyFromTable(0x1161), false) == 0); // hangul jungseong: zero width +static_assert(widthFromFused(classifyFromTable(0x1112), false) == 2); // hangul choseong: wide +static_assert(widthFromFused(classifyFromTable(0x4DC0), false) == 2); // yijing hexagram: wide since Unicode 16 +static_assert((classifyFromTable(0x1FA89) & kFusedEmojiBit) != 0); // Unicode 16 emoji +static_assert(graphemeBreakClassFromFused(classifyFromTable(0x1F3FB)) == GraphemeBreakClass::EmojiModifier); +static_assert(graphemeBreakClassFromFused(classifyFromTable(0x0A)) == GraphemeBreakClass::Control); +static_assert(graphemeBreakClassFromFused(classifyFromTable(0x1000)) == GraphemeBreakClass::IndicConjunctBreakConsonant); // Unicode 16 InCB +static_assert(graphemeBreakClassFromFused(classifyFromTable(0x2605)) == GraphemeBreakClass::Other); // not Extended_Pictographic since Unicode 16 + +uint8_t fusedClassify(char32_t cp) { ASSERT(cp <= 0x10FFFF); - return widthFromFused(fusedClassify(cp), ambiguousAsWide); + return classifyFromTable(cp); } -bool isEmojiPresentation(char32_t cp) +uint8_t visibleCodepointWidth(char32_t cp, bool ambiguousAsWide) { ASSERT(cp <= 0x10FFFF); - return fusedClassify(cp) & kFusedEmojiBit; + return widthFromFused(classifyFromTable(cp), ambiguousAsWide); } // ============================================================================ // Grapheme break (UAX #29 incl. GB9c Indic Conjunct Break, uucode algorithm) // ============================================================================ -// Grapheme break property for codepoints, excluding control/CR/LF which the -// width loops handle before consulting the break algorithm. Ordinal values -// must match the stage3 data in stringWidthTables.h. -enum class GraphemeBreakClass : uint8_t { - Other, - Prepend, - RegionalIndicator, - SpacingMark, - L, - V, - T, - Lv, - Lvt, - Zwj, - Zwnj, - ExtendedPictographic, - EmojiModifierBase, - EmojiModifier, - IndicConjunctBreakExtend, - IndicConjunctBreakLinker, - IndicConjunctBreakConsonant, -}; -static constexpr size_t kGraphemeBreakClassCount = 17; -static_assert(kGraphemeBreakClassCount <= kFusedClassMask + 1); static_assert(static_cast(GraphemeBreakClass::RegionalIndicator) == 2); static_assert(static_cast(GraphemeBreakClass::Zwj) == 9); -static_assert(static_cast(GraphemeBreakClass::IndicConjunctBreakConsonant) == kGraphemeBreakClassCount - 1); +static_assert(static_cast(GraphemeBreakClass::Control) == kGraphemeBreakClassCount - 1); // State carried between sequential graphemeBreak() calls. Numeric values are // part of the `Bun__graphemeBreak` C ABI (opaque uint8_t, zero-initialized). @@ -161,15 +124,10 @@ static constexpr size_t kGraphemeBreakStateCount = 5; // The state is packed into bits 0-2 of graphemeBreakKey(). static_assert(kGraphemeBreakStateCount <= (1 << 3)); -static constexpr GraphemeBreakClass graphemeBreakClassFromFused(uint8_t packed) -{ - return static_cast(packed & kFusedClassMask); -} - static GraphemeBreakClass graphemeBreakClass(char32_t cp) { ASSERT(cp <= 0x10FFFF); - return graphemeBreakClassFromFused(fusedClassify(cp)); + return graphemeBreakClassFromFused(classifyFromTable(cp)); } static constexpr bool isIndicConjunctBreakExtend(GraphemeBreakClass gb) @@ -177,11 +135,15 @@ static constexpr bool isIndicConjunctBreakExtend(GraphemeBreakClass gb) return gb == GraphemeBreakClass::IndicConjunctBreakExtend || gb == GraphemeBreakClass::Zwj; } +// Grapheme_Cluster_Break=Extend: the emoji modifiers are Extend too (GB9 +// attaches them to any base; GB11 lets them sit between an Extended +// Pictographic and its ZWJ). static constexpr bool isExtend(GraphemeBreakClass gb) { return gb == GraphemeBreakClass::Zwnj || gb == GraphemeBreakClass::IndicConjunctBreakExtend - || gb == GraphemeBreakClass::IndicConjunctBreakLinker; + || gb == GraphemeBreakClass::IndicConjunctBreakLinker + || gb == GraphemeBreakClass::EmojiModifier; } static constexpr bool isExtendedPictographic(GraphemeBreakClass gb) @@ -190,13 +152,20 @@ static constexpr bool isExtendedPictographic(GraphemeBreakClass gb) } // Core grapheme break algorithm (ported from uucode's -// computeGraphemeBreakNoControl). Only evaluated at compile time to build the -// precomputed decision table below. -static constexpr bool computeGraphemeBreakNoControl(GraphemeBreakClass gb1, GraphemeBreakClass gb2, GraphemeBreakState& state) +// computeGraphemeBreakNoControl, plus GB4/GB5 for the Control class). Only +// evaluated at compile time to build the precomputed decision table below. +static constexpr bool computeGraphemeBreak(GraphemeBreakClass gb1, GraphemeBreakClass gb2, GraphemeBreakState& state) { using G = GraphemeBreakClass; using S = GraphemeBreakState; + // GB4, GB5: a control, CR or LF breaks on both sides. (CR x LF is not + // needed here: a CR LF pair is zero-width either way.) + if (gb1 == G::Control || gb2 == G::Control) { + state = S::Default; + return true; + } + // Set state back to default when gb1 or gb2 is not expected in sequence. switch (state) { case S::RegionalIndicator: @@ -205,9 +174,7 @@ static constexpr bool computeGraphemeBreakNoControl(GraphemeBreakClass gb1, Grap break; case S::ExtendedPictographic: { const auto expected = [](G gb) { - return gb == G::IndicConjunctBreakExtend || gb == G::IndicConjunctBreakLinker - || gb == G::Zwnj || gb == G::Zwj || gb == G::ExtendedPictographic - || gb == G::EmojiModifierBase || gb == G::EmojiModifier; + return isExtend(gb) || gb == G::Zwj || isExtendedPictographic(gb); }; if (!expected(gb1)) state = S::Default; @@ -309,16 +276,10 @@ static constexpr bool computeGraphemeBreakNoControl(GraphemeBreakClass gb1, Grap return false; } - // emoji_modifier_sequence: emoji_modifier_base emoji_modifier - if (gb1 == G::EmojiModifierBase && gb2 == G::EmojiModifier) { - state = S::ExtendedPictographic; - return false; - } - // else, not an Emoji ZWJ sequence } else if (state == S::ExtendedPictographic) { // continue or end sequence - if ((isExtend(gb1) || gb1 == G::EmojiModifier) && (isExtend(gb2) || gb2 == G::Zwj)) { + if (isExtend(gb1) && (isExtend(gb2) || gb2 == G::Zwj)) { // continue extend* ZWJ sequence return false; } @@ -368,7 +329,7 @@ static constexpr auto kGraphemeBreakDecisions = []() constexpr { const auto gb1 = static_cast(i1); const auto gb2 = static_cast(i2); const size_t key = graphemeBreakKey(gb1, gb2, state); - const bool shouldBreak = computeGraphemeBreakNoControl(gb1, gb2, state); + const bool shouldBreak = computeGraphemeBreak(gb1, gb2, state); result[key] = static_cast(shouldBreak) | (static_cast(state) << 1); } } @@ -378,9 +339,7 @@ static constexpr auto kGraphemeBreakDecisions = []() constexpr { // Returns true when there is a grapheme cluster break between two consecutive // codepoints with the given break classes. Must be called sequentially, -// carrying `state` between calls. Control characters, CR and LF are not -// handled here — callers treat them before consulting the break algorithm -// (they always terminate a cluster). +// carrying `state` between calls. static bool graphemeBreakClasses(GraphemeBreakClass gb1, GraphemeBreakClass gb2, GraphemeBreakState& state) { const uint8_t value = kGraphemeBreakDecisions[graphemeBreakKey(gb1, gb2, state)]; @@ -405,100 +364,65 @@ bool graphemeBreak(char32_t cp1, char32_t cp2, uint8_t& state) // Grapheme cluster width accumulator // ============================================================================ -// Accumulates the codepoints of one grapheme cluster and decides the cluster's -// terminal width: flags (regional indicator pairs), keycap sequences, emoji -// with skin tone / ZWJ, and variation selectors all override the plain sum of -// codepoint widths. -struct GraphemeState { - char32_t firstCp = 0; - uint16_t nonEmojiWidth = 0; // accumulated width, saturates at 1023 - uint8_t baseWidth = 0; // width of the first codepoint (0, 1 or 2) - uint8_t count = 0; // number of codepoints in the cluster - bool emojiBase = false; - bool keycap = false; - bool regionalIndicator = false; - bool skinTone = false; - bool zwj = false; - bool vs15 = false; - bool vs16 = false; - - static bool isRegionalIndicator(char32_t cp) { return cp >= 0x1F1E6 && cp <= 0x1F1FF; } - static bool isSkinToneModifier(char32_t cp) { return cp >= 0x1F3FB && cp <= 0x1F3FF; } - - void reset(char32_t cp, uint8_t packed, bool ambiguousAsWide) - { - firstCp = cp; - - // Fast path for ASCII - no emoji complexity, simple width calculation - if (cp < 0x80) { - const uint8_t w = (cp >= 0x20 && cp < 0x7F) ? 1 : 0; - *this = GraphemeState {}; - firstCp = cp; - count = 1; - baseWidth = w; - nonEmojiWidth = w; - return; - } - - const uint8_t w = widthFromFused(packed, ambiguousAsWide); - count = 1; - baseWidth = w; - nonEmojiWidth = w; - emojiBase = packed & kFusedEmojiBit; - keycap = (cp == 0x20E3); - regionalIndicator = isRegionalIndicator(cp); - skinTone = isSkinToneModifier(cp); - zwj = (cp == 0x200D); - vs15 = false; - vs16 = false; - } +// Feeds decoded codepoints through the grapheme break algorithm and sums the +// width of each completed cluster (GraphemeState decides a cluster's width). +// Encoding-specific walkers own the decode and the escape-sequence handling; +// state is carried across escape sequences so a combining mark that follows +// an SGR code still joins the cluster before it. +struct ClusterWidthAccumulator { + size_t len = 0; // width of the completed clusters + GraphemeState graphemeState; // the pending cluster + // Break class of the last *visible* codepoint, used for grapheme break + // decisions (carried so each codepoint is classified only once). Escape + // sequence bytes must not participate: a CSI final byte like 'm' would + // otherwise wrongly join to a following combining mark. + GraphemeBreakClass prevClass = GraphemeBreakClass::Other; + GraphemeBreakState breakState = GraphemeBreakState::Default; + bool hasPrevVisible = false; + const bool ambiguousAsWide; - void add(char32_t cp, uint8_t packed, bool ambiguousAsWide) + explicit ClusterWidthAccumulator(bool ambiguousAsWide) + : ambiguousAsWide(ambiguousAsWide) { - if (count < UINT8_MAX) - count++; - keycap = keycap || (cp == 0x20E3); - regionalIndicator = regionalIndicator || isRegionalIndicator(cp); - skinTone = skinTone || isSkinToneModifier(cp); - zwj = zwj || (cp == 0x200D); - vs15 = vs15 || (cp == 0xFE0E); - vs16 = vs16 || (cp == 0xFE0F); - - // Zero-width codepoints contribute nothing here. - const uint32_t newWidth = static_cast(nonEmojiWidth) + widthFromFused(packed, ambiguousAsWide); - nonEmojiWidth = static_cast(std::min(newWidth, 1023)); } - size_t width() const + // Returns true when `cp` starts a new cluster (the previous one, if any, + // has been added to `len`). + bool addCodepoint(char32_t cp) { - if (count == 0) - return 0; - - // Regional indicator pair (flag emoji) → width 2 - if (regionalIndicator && count >= 2) - return 2; - // Keycap sequence → width 2 - if (keycap) - return 2; - // Single regional indicator → width 1 - if (regionalIndicator) - return 1; - // Emoji with skin tone or ZWJ → width 2 - if (emojiBase && (skinTone || zwj)) - return 2; - - // VS16 widens only a base with the Emoji property to emoji - // presentation; the fused emoji bit early-outs below U+203C, where - // (c) and (R) are the only non-keycap Emoji codepoints. Zero-width - // and narrow non-emoji bases keep their own width under VS15/VS16. - if (vs15 || vs16) { - if (baseWidth == 2 || (vs16 && (emojiBase || firstCp == 0xA9 || firstCp == 0xAE))) - return 2; - return baseWidth; + const uint8_t packed = classifyFromTable(cp); + const GraphemeBreakClass cpClass = graphemeBreakClassFromFused(packed); + bool startsCluster = true; + if (!hasPrevVisible) { + graphemeState.reset(cp, packed, ambiguousAsWide); + } else if (graphemeBreakClasses(prevClass, cpClass, breakState)) { + len += graphemeState.width(); + graphemeState.reset(cp, packed, ambiguousAsWide); + } else { + graphemeState.add(cp, packed, ambiguousAsWide); + startsCluster = false; } + hasPrevVisible = true; + prevClass = cpClass; + return startsCluster; + } - return nonEmojiWidth; + // Seed the cluster state from the last codepoint of a bulk-counted run, + // flushing whatever cluster was pending before it. The codepoint's own + // width is not added here: a combining mark, jamo or ZWJ right after the + // run still joins its cluster, so the caller counts every unit but the + // last and lets GraphemeState::width() settle the final one. + void seedFromBulkRun(char32_t cp, uint8_t packed) + { + if (graphemeState.count > 0) + len += graphemeState.width(); + graphemeState.reset(cp, packed, ambiguousAsWide); + hasPrevVisible = true; + prevClass = graphemeBreakClassFromFused(packed); + breakState = GraphemeBreakState::Default; } + + size_t finish() const { return len + graphemeState.width(); } }; // ============================================================================ @@ -545,7 +469,7 @@ size_t visibleLatin1WidthExcludeANSI(std::span input) static constexpr auto kLatin1AmbiguousAsWideWidth = []() constexpr { std::array table {}; for (size_t i = 0; i < table.size(); i++) - table[i] = widthFromFused(fusedClassify(static_cast(i)), /* ambiguousAsWide */ true); + table[i] = widthFromFused(classifyFromTable(static_cast(i)), /* ambiguousAsWide */ true); return table; }(); static_assert(kLatin1AmbiguousAsWideWidth[0xA7] == 2); // section sign: ambiguous @@ -630,40 +554,21 @@ static char32_t decodeWTF8RuneMultibyte(const std::array& p, uint8_t return cp; } -// UTF-8 width of a run with no escape sequences in it: ASCII runs are counted -// in bulk, non-ASCII codepoints are decoded and summed individually (no -// grapheme clustering — keeps the historical behavior of the console.table / -// markdown renderer callers). -static size_t visibleUTF8Width(std::span input) +// Decode the WTF-8 codepoint at `p` (p < end). Returns the codepoint and +// the number of bytes it occupies (a truncated or invalid sequence decodes +// to U+FFFD). +static std::pair decodeWTF8At(const uint8_t* p, const uint8_t* end) { - std::span bytes = input; - size_t len = 0; - - while (true) { - // Runs of non-ASCII codepoints are common (CJK text); peek before - // paying for a SIMD scan that would return 0. - const size_t i = (!bytes.empty() && bytes[0] > 0x7F) ? 0 : highway_first_non_ascii8(bytes.data(), bytes.size()); - if (i == bytes.size()) - break; - len += visibleLatin1Width(bytes.first(i)); - - const auto thisChunk = bytes.subspan(i); - const uint8_t byte = thisChunk[0]; - const uint8_t skip = wtf8SequenceLength(byte); - - std::array cpBytes { byte, 0, 0, 0 }; - const size_t available = std::min(skip, thisChunk.size()); - for (size_t k = 1; k < available; k++) - cpBytes[k] = thisChunk[k]; - - const char32_t cp = (skip > 1) ? decodeWTF8RuneMultibyte(cpBytes, skip) : 0xFFFD; - len += visibleCodepointWidth(cp, false); - - bytes = bytes.subspan(std::min(i + skip, bytes.size())); - } - - len += visibleLatin1Width(bytes); - return len; + const uint8_t byte = *p; + if (byte <= 0x7F) + return { byte, 1 }; + const uint8_t skip = wtf8SequenceLength(byte); + std::array cpBytes { byte, 0, 0, 0 }; + const size_t available = std::min(skip, static_cast(end - p)); + for (size_t k = 1; k < available; k++) + cpBytes[k] = p[k]; + const char32_t cp = (skip > 1) ? decodeWTF8RuneMultibyte(cpBytes, skip) : 0xFFFD; + return { cp, std::min(skip, static_cast(end - p)) }; } // Start of the next escape sequence in UTF-8 text: ESC, or the two-byte @@ -680,92 +585,87 @@ static const uint8_t* findEscapeIntroducerUTF8(const uint8_t* p, const uint8_t* } } -size_t visibleUTF8WidthExcludeANSI(std::span input) +// Grapheme-cluster-aware walk of UTF-8 text with ANSI escape sequences +// treated as zero-width. Every cluster start is reported to `onCluster` +// (byte offset of the cluster, width of the completed clusters before it); +// a false return stops the walk. +template +static ClusterWidthAccumulator walkUTF8ExcludeANSI(std::span input, OnCluster onCluster) { - const uint8_t* p = input.data(); - const uint8_t* const end = p + input.size(); - size_t width = 0; + ClusterWidthAccumulator accumulator { /* ambiguousAsWide */ false }; + const uint8_t* const begin = input.data(); + const uint8_t* const end = begin + input.size(); + const uint8_t* p = begin; while (p != end) { const uint8_t* const esc = findEscapeIntroducerUTF8(p, end); - width += visibleUTF8Width({ p, static_cast((esc ? esc : end) - p) }); + const uint8_t* const runEnd = esc ? esc : end; + + while (p != runEnd) { + // ASCII runs: every printable byte is its own width-1 cluster and + // the non-printable ones are width-0 controls, so all but the last + // byte are counted in bulk. The last one seeds the cluster state: + // a combining mark right after the run still joins it. + if (*p <= 0x7F) { + const size_t asciiLen = highway_first_non_ascii8(p, static_cast(runEnd - p)); + if (asciiLen > 1) { + if (accumulator.graphemeState.count > 0) + accumulator.len += accumulator.graphemeState.width(); + accumulator.graphemeState = GraphemeState {}; + for (const uint8_t* q = p; q != p + asciiLen - 1; q++) { + if (!onCluster(static_cast(q - begin), accumulator.len)) + return accumulator; + accumulator.len += visibleLatin1WidthScalar(*q); + } + p += asciiLen - 1; + } + const char32_t cp = *p; + const uint8_t packed = classifyFromTable(cp); + if (!onCluster(static_cast(p - begin), accumulator.len + accumulator.graphemeState.width())) + return accumulator; + accumulator.seedFromBulkRun(cp, packed); + p++; + continue; + } + + const auto [cp, length] = decodeWTF8At(p, runEnd); + const size_t widthBefore = accumulator.finish(); + if (accumulator.addCodepoint(cp) && !onCluster(static_cast(p - begin), widthBefore)) + return accumulator; + p += length; + } + if (!esc) break; // *esc is an introducer, so consumeANSI() always makes progress. p = ANSI::consumeANSI(esc, end); } - return width; + return accumulator; } -// Walk `len` bytes of `input` starting at `start`, accumulating visible width -// into `w`. Returns the absolute byte index at which adding the next codepoint -// would exceed `maxWidth`, or nullopt if the whole run fits. -static std::optional utf8WalkRun(std::span input, size_t start, size_t len, size_t maxWidth, size_t& w) +size_t visibleUTF8WidthExcludeANSI(std::span input) { - std::span bytes = input.subspan(start, len); - - while (true) { - const size_t i = highway_first_non_ascii8(bytes.data(), bytes.size()); - if (i == bytes.size()) - break; - - // ASCII run: each printable char is width 1. - for (size_t k = 0; k < i; k++) { - const size_t cw = visibleLatin1WidthScalar(bytes[k]); - if (w + cw > maxWidth) - return static_cast(bytes.data() - input.data()) + k; - w += cw; - } - - const auto thisChunk = bytes.subspan(i); - const uint8_t byte = thisChunk[0]; - const uint8_t skip = wtf8SequenceLength(byte); - - std::array cpBytes { byte, 0, 0, 0 }; - const size_t available = std::min(skip, thisChunk.size()); - for (size_t k = 1; k < available; k++) - cpBytes[k] = thisChunk[k]; - - const char32_t cp = (skip > 1) ? decodeWTF8RuneMultibyte(cpBytes, skip) : 0xFFFD; - const size_t cw = visibleCodepointWidth(cp, false); - if (w + cw > maxWidth) - return static_cast(bytes.data() - input.data()) + i; - w += cw; - - bytes = bytes.subspan(std::min(i + skip, bytes.size())); - } - - for (size_t k = 0; k < bytes.size(); k++) { - const size_t cw = visibleLatin1WidthScalar(bytes[k]); - if (w + cw > maxWidth) - return static_cast(bytes.data() - input.data()) + k; - w += cw; - } - return std::nullopt; + return walkUTF8ExcludeANSI(input, [](size_t, size_t) { return true; }).finish(); } size_t utf8IndexAtWidthExcludeANSI(std::span input, size_t maxWidth) { - const uint8_t* const begin = input.data(); - const uint8_t* const end = begin + input.size(); - const uint8_t* p = begin; - size_t w = 0; - - while (p != end) { - const uint8_t* const esc = findEscapeIntroducerUTF8(p, end); - // Walk the visible run before the introducer. - const size_t runStart = static_cast(p - begin); - const size_t runLen = static_cast((esc ? esc : end) - p); - if (const auto stop = utf8WalkRun(input, runStart, runLen, maxWidth, w)) - return *stop; - if (!esc) - break; - - // Escape sequences count as zero-width and are always included in the - // prefix; an unterminated one consumes the rest of the input. - p = ANSI::consumeANSI(esc, end); - } - return input.size(); + // The prefix ends at the first cluster whose predecessors already fill + // `maxWidth`, or, when every cluster starts within budget, at the end of + // the input unless the final cluster overflows it. + size_t stopAt = input.size(); + size_t lastClusterStart = 0; + const auto accumulator = walkUTF8ExcludeANSI(input, [&](size_t offset, size_t widthBefore) { + if (widthBefore > maxWidth) { + stopAt = lastClusterStart; + return false; + } + lastClusterStart = offset; + return true; + }); + if (stopAt == input.size() && accumulator.finish() > maxWidth) + return lastClusterStart; + return stopAt; } // ============================================================================ @@ -810,54 +710,11 @@ static UTF16Decoded decodeUTF16Codepoint(std::span input) } // Grapheme-cluster-aware width accumulator for runs of UTF-16 text with no -// escape sequences in them. State is carried between runs so a combining mark -// that follows an escape sequence still joins the cluster before it. -struct UTF16WidthAccumulator { - size_t len = 0; - GraphemeState graphemeState; - // Break class of the last *visible* codepoint, used for grapheme break - // decisions (carried so each codepoint is classified only once). Escape - // sequence bytes must not participate: a CSI final byte like 'm' would - // otherwise wrongly join to a following combining mark. - GraphemeBreakClass prevClass = GraphemeBreakClass::Other; - GraphemeBreakState breakState = GraphemeBreakState::Default; - bool hasPrevVisible = false; - const bool ambiguousAsWide; - +// escape sequences in them. +struct UTF16WidthAccumulator : ClusterWidthAccumulator { explicit UTF16WidthAccumulator(bool ambiguousAsWide) - : ambiguousAsWide(ambiguousAsWide) - { - } - - void addCodepoint(char32_t cp) - { - const uint8_t packed = fusedClassify(cp); - const GraphemeBreakClass cpClass = graphemeBreakClassFromFused(packed); - if (!hasPrevVisible) { - graphemeState.reset(cp, packed, ambiguousAsWide); - } else if (graphemeBreakClasses(prevClass, cpClass, breakState)) { - len += graphemeState.width(); - graphemeState.reset(cp, packed, ambiguousAsWide); - } else { - graphemeState.add(cp, packed, ambiguousAsWide); - } - hasPrevVisible = true; - prevClass = cpClass; - } - - // Seed the cluster state from the last codepoint of a bulk-counted run, - // flushing whatever cluster was pending before it. The codepoint's own - // width is not added here: a combining mark, jamo or ZWJ right after the - // run still joins its cluster, so the caller counts every unit but the - // last and lets GraphemeState::width() settle the final one. - void seedFromBulkRun(char32_t cp, uint8_t packed) + : ClusterWidthAccumulator(ambiguousAsWide) { - if (graphemeState.count > 0) - len += graphemeState.width(); - graphemeState.reset(cp, packed, ambiguousAsWide); - hasPrevVisible = true; - prevClass = graphemeBreakClassFromFused(packed); - breakState = GraphemeBreakState::Default; } // Consumes text up to the end of `input`, or — when `stopAtEscape` — up to @@ -886,7 +743,7 @@ struct UTF16WidthAccumulator { reinterpret_cast(input.data()), input.size(), &bulkWidth); if (consumed > 0) { const char32_t lastCp = input[consumed - 1]; - const uint8_t lastPacked = fusedClassify(lastCp); + const uint8_t lastPacked = classifyFromTable(lastCp); seedFromBulkRun(lastCp, lastPacked); len += bulkWidth - widthFromFused(lastPacked, ambiguousAsWide); input = input.subspan(consumed); @@ -915,7 +772,7 @@ struct UTF16WidthAccumulator { } if (idx > 0) { const char32_t lastCp = input[idx - 1]; - const uint8_t lastPacked = fusedClassify(lastCp); + const uint8_t lastPacked = classifyFromTable(lastCp); seedFromBulkRun(lastCp, lastPacked); if (idx > 1) len += countPrintableAscii16(input.first(idx - 1)); @@ -1009,13 +866,6 @@ extern "C" bool Bun__graphemeBreak(uint32_t cp1, uint32_t cp2, uint8_t* state) return StringWidth::graphemeBreak(cp1, cp2, *state); } -extern "C" bool Bun__isEmojiPresentation(uint32_t cp) -{ - if (cp > 0x10FFFF) - return false; - return StringWidth::isEmojiPresentation(cp); -} - // ============================================================================ // JavaScript binding: Bun.stringWidth(input, options) // ============================================================================ diff --git a/src/jsc/bindings/stringWidth.h b/src/jsc/bindings/stringWidth.h index cfffd6e1148c..3d1f9b37836c 100644 --- a/src/jsc/bindings/stringWidth.h +++ b/src/jsc/bindings/stringWidth.h @@ -1,6 +1,7 @@ #pragma once #include "root.h" +#include #include namespace Bun { @@ -10,6 +11,63 @@ JSC_DECLARE_HOST_FUNCTION(jsFunctionBunStringWidth); namespace StringWidth { +// ============================================================================ +// Codepoint classification (grapheme break class + width + emoji, one lookup) +// ============================================================================ + +// Each codepoint maps to one packed byte (stringWidthTables.h, regenerate +// with scripts/generate-stringwidth-tables.mjs): +// bits 0-4 GraphemeBreakClass ordinal +// bits 5-6 width class: 0 zero-width, 1 narrow, 2 wide, 3 East Asian Ambiguous +// bit 7 the Emoji property, minus the keycap bases [0-9#*] +static constexpr uint8_t kFusedClassMask = 0x1F; +static constexpr uint8_t kFusedWidthShift = 5; +static constexpr uint8_t kFusedWidthMask = 0x3; +static constexpr uint8_t kFusedWidthAmbiguous = 3; +static constexpr uint8_t kFusedEmojiBit = 0x80; + +// Grapheme_Cluster_Break refined with the emoji and Indic Conjunct Break +// properties. Ordinal values must match the generator. +enum class GraphemeBreakClass : uint8_t { + Other, + Prepend, + RegionalIndicator, + SpacingMark, + L, + V, + T, + Lv, + Lvt, + Zwj, + Zwnj, + ExtendedPictographic, + EmojiModifierBase, + EmojiModifier, + IndicConjunctBreakExtend, + IndicConjunctBreakLinker, + IndicConjunctBreakConsonant, + Control, // Grapheme_Cluster_Break Control, CR and LF +}; +static constexpr size_t kGraphemeBreakClassCount = 18; +static_assert(kGraphemeBreakClassCount <= kFusedClassMask + 1); + +// Packed classification byte of a codepoint (cp <= 0x10FFFF). +uint8_t fusedClassify(char32_t cp); + +static constexpr GraphemeBreakClass graphemeBreakClassFromFused(uint8_t packed) +{ + return static_cast(packed & kFusedClassMask); +} + +// Terminal column width from a packed classification byte. +static constexpr uint8_t widthFromFused(uint8_t packed, bool ambiguousAsWide) +{ + const uint8_t width = (packed >> kFusedWidthShift) & kFusedWidthMask; + if (width == kFusedWidthAmbiguous) + return ambiguousAsWide ? 2 : 1; + return width; +} + // Terminal column width of a single codepoint (0, 1 or 2). uint8_t visibleCodepointWidth(char32_t cp, bool ambiguousAsWide); @@ -17,9 +75,91 @@ uint8_t visibleCodepointWidth(char32_t cp, bool ambiguousAsWide); // zero-initialized and carried between sequential calls. bool graphemeBreak(char32_t cp1, char32_t cp2, uint8_t& state); -// True when the codepoint renders as emoji presentation (UCHAR_EMOJI, with -// the same early-outs the width code uses). -bool isEmojiPresentation(char32_t cp); +// ============================================================================ +// Grapheme cluster width +// ============================================================================ + +// Accumulates the codepoints of one grapheme cluster and decides the +// cluster's terminal width. The base codepoint sets the width; extending +// codepoints add theirs (zero for marks and format characters, one for the +// halfwidth katakana voiced sound marks, ...) except the emoji modifiers, +// which never widen a cluster. The emoji sequences override the sum: a +// regional indicator pair (flag), a keycap, an emoji with a skin tone or a +// ZWJ, and the VS15/VS16 presentation selectors. +struct GraphemeState { + uint16_t summedWidth = 0; // saturates at 1023 + uint8_t baseWidth = 0; // width of the first codepoint (0, 1 or 2) + uint8_t count = 0; // number of codepoints in the cluster + uint8_t regionalIndicators = 0; + bool emojiBase = false; // first codepoint has the Emoji property + bool keycapBase = false; // first codepoint is [0-9#*] + bool keycap = false; // U+20E3 COMBINING ENCLOSING KEYCAP after the base + bool skinTone = false; + bool zwj = false; + bool vs15 = false; + bool vs16 = false; + + void reset(char32_t cp, uint8_t packed, bool ambiguousAsWide) + { + *this = GraphemeState {}; + const uint8_t w = widthFromFused(packed, ambiguousAsWide); + count = 1; + baseWidth = w; + summedWidth = w; + emojiBase = packed & kFusedEmojiBit; + keycapBase = (cp >= '0' && cp <= '9') || cp == '#' || cp == '*'; + regionalIndicators = graphemeBreakClassFromFused(packed) == GraphemeBreakClass::RegionalIndicator; + } + + void add(char32_t cp, uint8_t packed, bool ambiguousAsWide) + { + if (count < UINT8_MAX) + count++; + keycap = keycap || (cp == 0x20E3); + zwj = zwj || (cp == 0x200D); + vs15 = vs15 || (cp == 0xFE0E); + vs16 = vs16 || (cp == 0xFE0F); + + const GraphemeBreakClass cpClass = graphemeBreakClassFromFused(packed); + if (cpClass == GraphemeBreakClass::RegionalIndicator && regionalIndicators < UINT8_MAX) + regionalIndicators++; + if (cpClass == GraphemeBreakClass::EmojiModifier) { + skinTone = true; + return; + } + + const uint32_t newWidth = static_cast(summedWidth) + widthFromFused(packed, ambiguousAsWide); + summedWidth = static_cast(std::min(newWidth, 1023)); + } + + size_t width() const + { + if (count == 0) + return 0; + // Regional indicator pair (flag emoji) + if (regionalIndicators >= 2) + return 2; + // Keycap sequence: [0-9#*] (VS16)? U+20E3 + if (keycapBase && keycap) + return 2; + // Emoji modifier sequence or emoji ZWJ sequence + if (emojiBase && (skinTone || zwj)) + return 2; + // VS16 widens a base with the Emoji property to emoji presentation. + // Zero-width and narrow non-emoji bases keep their own width under + // VS15/VS16. + if (vs15 || vs16) { + if (baseWidth == 2 || (vs16 && emojiBase)) + return 2; + return baseWidth; + } + return summedWidth; + } +}; + +// ============================================================================ +// String width +// ============================================================================ // Visible width of Latin-1 text, counting ANSI escape sequences as visible. size_t visibleLatin1Width(std::span input); @@ -35,14 +175,14 @@ size_t visibleLatin1WidthExcludeANSIAmbiguousAsWide(std::span inp // treats ANSI escape sequences as zero-width. size_t visibleUTF16Width(std::span input, bool excludeAnsiColors, bool ambiguousAsWide); -// Visible width of UTF-8 text, treating ANSI escape sequences as zero-width. -// Sums codepoint widths (no grapheme clustering) — used for console.table -// column sizing and the markdown ANSI renderer. +// Visible width of UTF-8 text (grapheme-cluster aware), treating ANSI escape +// sequences as zero-width. Used for console.table column sizing and the +// markdown ANSI renderer. size_t visibleUTF8WidthExcludeANSI(std::span input); // Byte index of the longest prefix of UTF-8 `input` whose visible width is // <= `maxWidth`. ANSI escapes are zero-width and always included. Never -// splits a multi-byte codepoint. +// splits a grapheme cluster or a multi-byte codepoint. size_t utf8IndexAtWidthExcludeANSI(std::span input, size_t maxWidth); } // namespace StringWidth diff --git a/src/jsc/bindings/stringWidthTables.h b/src/jsc/bindings/stringWidthTables.h index 6e11ba686b28..87c753f6d24f 100644 --- a/src/jsc/bindings/stringWidthTables.h +++ b/src/jsc/bindings/stringWidthTables.h @@ -6,16 +6,15 @@ // 3-stage lookup of the packed per-codepoint classification used by // stringWidth.cpp: stage1[cp >> 8] + (cp & 0xFF) indexes stage2, which // indexes stage3. Each stage3 byte packs: -// bits 0-4 GraphemeBreakClass ordinal (grapheme break property + Indic -// Conjunct Break, uucode-derived) +// bits 0-4 GraphemeBreakClass ordinal: Grapheme_Cluster_Break refined +// with Indic_Conjunct_Break, Emoji_Modifier(_Base) and +// Extended_Pictographic // bits 5-6 width class: 0 zero-width, 1 narrow, 2 wide (East Asian Width -// W/F), 3 East Asian Ambiguous -// bit 7 the Unicode Emoji property, with the isEmojiPresentation() -// early-outs (< U+203C, [U+2C00, U+1F000), VS15/VS16/ZWJ) baked in -// Zero-width: C0/C1 controls, soft hyphen, every Mn/Me mark, conjoining Hangul -// jungseong/jongseong (U+1160-U+11FF, U+D7B0-U+D7FF), invisible format -// characters (bidi controls, ZWSP/ZWJ/ZWNJ, U+2060-206F, tags, BOM), -// surrogates and the Indic vowel-sign heuristic; see isZeroWidth() in the +// W/F or Emoji_Presentation), 3 East Asian Ambiguous +// bit 7 the Unicode Emoji property, minus the keycap bases [0-9#*] +// Zero-width: Cc, Cf, Mn, Me, surrogates, unassigned default-ignorable +// codepoints, conjoining Hangul jungseong/jongseong, and the spacing vowel +// signs (Mc) of the Indic blocks U+0900-U+0DFF; see isZeroWidth() in the // generator. #pragma once @@ -24,7 +23,7 @@ namespace Bun { namespace StringWidthTables { -static constexpr uint16_t kGraphemeBreakStage1[8192] = { +static constexpr uint16_t kGraphemeBreakStage1[4352] = { 0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048, 2304, 2560, 2816, 3072, 3328, 3584, 3840, 4096, 4352, 4608, 4864, 4608, 4608, 4608, 5120, 5376, 5632, 5888, 6144, 6400, 6656, 4608, 4608, 6912, 7168, 7424, 7680, 7936, 8192, 8448, 8704, 4608, 8960, 4608, 9216, 9472, 9728, 9984, 10240, 10496, 10752, 11008, 11264, 11264, 11264, 11264, 11264, 11264, 11264, 11264, 11264, 11264, 11264, 11264, 11264, 11264, 11264, 11264, 11264, 11264, 11264, 11264, 11264, @@ -174,7 +173,7 @@ static constexpr uint16_t kGraphemeBreakStage1[8192] = { 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 32000, 32256, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, + 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 32000, 32256, 32512, 32512, 32512, 32512, 32512, 32512, 32512, 32512, 32512, 32512, 32512, 32512, 32512, 32512, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, @@ -195,7 +194,7 @@ static constexpr uint16_t kGraphemeBreakStage1[8192] = { 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, - 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 32512, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, + 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 32768, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, @@ -206,170 +205,10 @@ static constexpr uint16_t kGraphemeBreakStage1[8192] = { 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, 15360, - 15360, 15360, 15360, 15360, 15360, 15360, 15360, 32512, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, - 4608, 4608, 4608, 4608, 4608, 4608, 4608, 4608, + 15360, 15360, 15360, 15360, 15360, 15360, 15360, 32768, }; -static constexpr uint8_t kGraphemeBreakStage2[32768] = { +static constexpr uint8_t kGraphemeBreakStage2[33024] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -438,82 +277,82 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 5, 5, 5, 5, 1, 5, 5, 5, 1, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 7, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 6, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 5, 10, 5, 1, 10, 10, - 10, 5, 5, 5, 5, 5, 5, 5, 5, 10, 10, 10, 10, 11, 8, 8, 1, 5, 5, 5, 5, 5, 5, 5, 9, 9, 9, 9, 9, 9, 9, 9, - 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, - 0, 5, 10, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 1, 9, 1, 1, 1, 9, 9, 9, 9, 0, 0, 5, 1, 5, 10, - 10, 5, 5, 5, 5, 0, 0, 10, 10, 0, 0, 10, 10, 11, 1, 1, 1, 0, 0, 0, 0, 0, 0, 5, 1, 1, 1, 1, 9, 9, 1, 9, - 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, - 0, 5, 5, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 5, 1, 10, 10, - 10, 5, 5, 0, 0, 0, 0, 5, 5, 0, 0, 5, 5, 5, 1, 1, 1, 5, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 0, 5, 5, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 1, 9, 9, 1, 9, 9, 9, 9, 9, 0, 0, 5, 1, 10, 10, - 10, 5, 5, 5, 5, 5, 0, 5, 5, 10, 0, 10, 10, 11, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 5, 5, 5, 5, 5, 5, - 0, 5, 10, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 1, 9, 9, 1, 9, 9, 9, 9, 9, 0, 0, 5, 1, 5, 5, - 10, 5, 5, 5, 5, 0, 0, 10, 10, 0, 0, 10, 10, 11, 1, 1, 1, 0, 0, 0, 0, 5, 5, 5, 1, 1, 1, 1, 9, 9, 1, 9, - 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 0, 0, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 5, 10, - 5, 10, 10, 0, 0, 0, 10, 10, 10, 0, 10, 10, 10, 5, 1, 1, 1, 0, 0, 0, 0, 0, 0, 5, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 5, 10, 10, 8, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 5, 1, 5, 5, - 5, 10, 10, 10, 10, 0, 5, 5, 5, 0, 5, 5, 5, 11, 1, 1, 1, 0, 0, 0, 0, 5, 5, 0, 9, 9, 9, 1, 1, 1, 1, 1, + 5, 5, 5, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 5, 7, 5, 1, 7, 7, + 7, 5, 5, 5, 5, 5, 5, 5, 5, 7, 7, 7, 7, 9, 7, 7, 1, 5, 5, 5, 5, 5, 5, 5, 8, 8, 8, 8, 8, 8, 8, 8, + 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, + 1, 5, 7, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 8, 8, 8, 8, 8, 8, 8, 1, 8, 1, 1, 1, 8, 8, 8, 8, 1, 1, 5, 1, 5, 7, + 7, 5, 5, 5, 5, 1, 1, 7, 7, 1, 1, 7, 7, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 8, 8, 1, 8, + 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, + 1, 5, 5, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 7, 7, + 7, 5, 5, 1, 1, 1, 1, 5, 5, 1, 1, 5, 5, 5, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 5, 5, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 8, 8, 8, 8, 8, 8, 8, 1, 8, 8, 1, 8, 8, 8, 8, 8, 1, 1, 5, 1, 7, 7, + 7, 5, 5, 5, 5, 5, 1, 5, 5, 7, 1, 7, 7, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 5, 5, 5, 5, 5, 5, + 1, 5, 7, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 8, 8, 8, 8, 8, 8, 8, 1, 8, 8, 1, 8, 8, 8, 8, 8, 1, 1, 5, 1, 5, 5, + 7, 5, 5, 5, 5, 1, 1, 7, 7, 1, 1, 7, 7, 9, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 1, 1, 1, 1, 8, 8, 1, 8, + 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 7, + 5, 7, 7, 1, 1, 1, 7, 7, 7, 1, 7, 7, 7, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 7, 7, 7, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 5, 1, 5, 5, + 5, 7, 7, 7, 7, 1, 5, 5, 5, 1, 5, 5, 5, 9, 1, 1, 1, 1, 1, 1, 1, 5, 5, 1, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 0, 5, 10, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 5, 1, 10, 5, - 5, 10, 5, 10, 10, 0, 5, 5, 5, 0, 5, 5, 5, 5, 1, 1, 1, 0, 0, 0, 0, 5, 5, 0, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 5, 5, 10, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 12, 5, 5, 1, 5, 10, - 10, 5, 5, 5, 5, 0, 10, 10, 10, 0, 10, 10, 10, 11, 7, 1, 1, 1, 1, 1, 1, 1, 1, 13, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 5, 7, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 7, 5, + 5, 7, 5, 7, 7, 1, 5, 5, 5, 1, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 7, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 5, 5, 1, 5, 7, + 7, 5, 5, 5, 5, 1, 7, 7, 7, 1, 7, 7, 7, 9, 10, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 5, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 5, 7, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 13, 8, 8, 5, 5, 5, 1, 5, 1, 8, 8, 8, 8, 8, 8, 8, 13, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 5, 7, 7, 5, 5, 5, 1, 5, 1, 7, 7, 7, 7, 7, 7, 7, 5, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 8, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 11, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 11, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 5, 1, 5, 1, 1, 1, 1, 8, 8, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 5, 1, 5, 1, 1, 1, 1, 11, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 8, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 11, 5, 5, 5, 5, 5, 1, 5, 5, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 8, 5, 5, 5, 5, 5, 5, 1, 5, 5, 8, 8, 5, 5, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 5, 5, 1, 1, 1, 1, 5, 5, - 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 5, 1, 8, 5, 5, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 5, 5, 5, 5, 11, 5, 5, 5, 5, 5, 5, 1, 9, 5, 11, 11, 5, 5, 8, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 11, 11, 5, 5, 8, 8, 8, 8, 5, 5, + 5, 8, 1, 1, 1, 8, 8, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 5, 5, 5, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 5, 1, 11, 5, 5, 1, 1, 1, 1, 1, 1, 5, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, - 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -530,13 +369,13 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 15, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 15, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 8, 5, 5, 5, 5, 5, 5, 5, 8, 8, - 8, 8, 8, 8, 8, 8, 5, 8, 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 5, 5, 11, 5, 5, 5, 5, 5, 5, 5, 11, 11, + 11, 11, 11, 11, 11, 11, 5, 11, 11, 5, 5, 5, 5, 5, 5, 5, 5, 5, 9, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 0, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -547,37 +386,37 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 5, 5, 5, 8, 8, 8, 8, 5, 5, 8, 8, 8, 1, 1, 1, 1, 8, 8, 5, 8, 8, 8, 8, 8, 8, 5, 5, 5, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 5, 11, 11, 11, 11, 5, 5, 11, 11, 11, 1, 1, 1, 1, 11, 11, 5, 11, 11, 11, 11, 11, 11, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 8, 8, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 5, 8, 5, 5, 5, 5, 5, 5, 5, 1, - 5, 1, 5, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 8, 8, 8, 8, 8, 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 5, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 11, 11, 5, 1, 1, 1, 1, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 11, 5, 11, 5, 5, 5, 5, 5, 5, 5, 1, + 9, 1, 5, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 11, 11, 11, 11, 11, 11, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 5, 5, 5, 5, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 13, 5, 5, 5, 5, 5, 13, 5, 13, 8, 8, - 8, 8, 5, 13, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 5, 5, 11, 1, 1, 1, 1, 1, 1, 8, 8, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 5, 15, 5, 5, 5, 5, 5, 15, 5, 15, 11, 11, + 11, 11, 5, 15, 16, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 5, 5, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 8, 5, 5, 5, 5, 8, 8, 5, 5, 13, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 11, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 11, 5, 5, 5, 5, 11, 11, 5, 5, 15, 9, 5, 5, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 5, 8, 5, 5, 8, 8, 8, 5, 8, 5, 5, 5, 13, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 5, 11, 5, 5, 11, 11, 11, 5, 11, 5, 5, 5, 15, 15, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 5, 5, 5, 5, 5, 5, 5, 5, 8, 8, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 11, 11, 11, 11, 11, 11, 11, 11, 5, 5, 5, 5, 5, 5, 5, 5, 11, 11, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 8, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 5, 1, 1, 8, 5, 5, 1, 1, 1, 1, 1, 1, + 5, 11, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 5, 1, 1, 11, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -587,19 +426,19 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 17, 18, 0, 0, 2, 1, 1, 2, 2, 2, 2, 1, 2, 2, 1, 1, 2, 2, 1, 1, - 2, 2, 2, 1, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0, 0, 1, 2, 1, 2, 2, 1, 2, 1, 1, 1, 1, 1, 2, 19, 1, 2, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 1, 2, 2, 2, 2, 19, 19, 0, 0, 0, 0, 0, 1, 2, 1, 2, 2, 1, 2, 1, 1, 1, 1, 1, 2, 3, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 2, 20, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 1, 1, 1, 1, 1, 1, + 1, 2, 4, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 20, 20, 20, 20, 20, 20, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 2, 2, 1, 1, 2, 1, 1, 1, 2, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 2, 2, 2, @@ -610,57 +449,57 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 21, 21, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 19, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 20, 20, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 3, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 21, 21, 21, 21, 19, 19, 19, 21, 19, 19, 21, 1, 1, 1, 1, 19, 19, 19, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 20, 20, 20, 3, 3, 3, 20, 3, 3, 20, 1, 1, 1, 1, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 20, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 19, 19, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 20, 2, 1, 1, 1, 1, 2, 2, 1, 1, - 20, 2, 1, 1, 1, 1, 2, 2, 2, 1, 1, 2, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 19, 21, 21, 1, - 19, 19, 19, 19, 19, 4, 2, 3, 3, 4, 3, 3, 3, 3, 20, 4, 3, 19, 3, 1, 21, 21, 3, 3, 19, 3, 3, 3, 4, 23, 4, 3, - 19, 3, 19, 19, 3, 3, 19, 3, 3, 3, 19, 3, 3, 3, 19, 19, 24, 24, 24, 24, 24, 24, 24, 24, 19, 19, 19, 3, 3, 3, 3, 3, - 20, 3, 20, 3, 3, 3, 3, 3, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 19, - 20, 4, 3, 20, 4, 20, 19, 4, 20, 4, 4, 3, 4, 4, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 19, 3, 3, 19, 21, - 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 22, 22, 22, 22, 22, 22, 3, 3, 19, 21, 19, 19, 19, 19, 3, 19, 3, 19, 19, 3, 4, 4, - 19, 21, 3, 3, 3, 3, 3, 19, 3, 3, 21, 21, 3, 3, 3, 3, 19, 19, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 21, 21, 4, - 3, 3, 3, 3, 21, 21, 4, 4, 20, 4, 4, 4, 4, 4, 21, 20, 4, 20, 4, 20, 21, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 3, 4, 3, 3, 3, 3, 4, 20, 21, 4, 4, 4, 4, 4, 20, 20, 21, 21, 20, 21, 4, 20, 20, 25, 21, 4, 4, 21, 4, 4, - 3, 3, 19, 3, 3, 21, 1, 1, 19, 19, 26, 26, 23, 23, 3, 19, 3, 3, 19, 1, 19, 1, 19, 1, 1, 1, 1, 1, 1, 19, 1, 1, - 1, 19, 1, 1, 1, 1, 1, 1, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 19, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, - 1, 1, 1, 1, 19, 1, 1, 19, 1, 1, 1, 1, 21, 1, 21, 1, 1, 1, 1, 21, 21, 21, 1, 21, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 19, 19, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 21, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 21, + 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 4, 2, 1, 1, 1, 1, 2, 2, 1, 1, + 4, 2, 1, 1, 1, 1, 2, 2, 2, 1, 1, 2, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 20, 20, 1, + 3, 3, 3, 3, 3, 2, 2, 1, 1, 2, 1, 1, 1, 1, 4, 2, 1, 3, 1, 1, 20, 20, 1, 1, 3, 1, 1, 1, 2, 22, 2, 1, + 3, 1, 3, 3, 1, 1, 3, 1, 1, 1, 3, 1, 1, 1, 3, 3, 21, 21, 21, 21, 21, 21, 21, 21, 3, 3, 3, 1, 1, 1, 1, 1, + 4, 1, 4, 1, 1, 1, 1, 1, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, + 4, 2, 1, 4, 2, 4, 3, 2, 4, 2, 2, 1, 2, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 3, 20, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 21, 21, 21, 21, 21, 21, 1, 1, 3, 20, 3, 3, 3, 3, 1, 3, 1, 3, 3, 1, 2, 2, + 3, 20, 1, 1, 1, 1, 1, 3, 1, 1, 20, 20, 1, 1, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 20, 2, + 1, 1, 1, 1, 20, 20, 2, 2, 4, 2, 2, 2, 2, 2, 20, 4, 2, 4, 2, 4, 20, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 1, 2, 1, 1, 1, 1, 2, 4, 20, 2, 2, 2, 2, 2, 4, 4, 20, 20, 4, 20, 2, 4, 4, 23, 20, 2, 2, 20, 2, 2, + 1, 1, 3, 1, 1, 20, 1, 1, 3, 3, 24, 24, 22, 22, 1, 3, 1, 1, 3, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 3, 1, 1, + 1, 3, 1, 1, 1, 1, 1, 1, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, + 1, 1, 1, 1, 3, 1, 1, 3, 1, 1, 1, 1, 20, 1, 20, 1, 1, 1, 1, 20, 20, 20, 1, 20, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 20, 20, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 19, 19, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 21, 21, 1, 1, 1, + 1, 1, 1, 1, 1, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 21, 1, 1, 1, 1, 21, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 1, 1, 1, 1, 20, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -686,57 +525,57 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 1, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 5, 5, 5, 5, 27, 27, 24, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 24, 22, 1, - 1, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 1, 1, 5, 5, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 1, 1, 1, 1, 1, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 1, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 1, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 1, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 2, 2, 2, 2, 2, 2, 2, 2, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 24, 22, 24, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 1, 1, 1, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 5, 5, 5, 5, 25, 25, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 20, 21, 1, + 1, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 1, 5, 5, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 1, 1, 1, 1, 1, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 2, 2, 2, 2, 2, 2, 2, 2, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 20, 21, 20, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 1, 1, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -747,109 +586,109 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 5, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 8, 8, 5, 5, 8, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 11, 11, 5, 5, 11, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 11, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 8, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 1, 1, 1, - 5, 5, 5, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 8, 8, 5, 5, 5, 5, 8, 8, 5, 5, 8, 8, - 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 11, 15, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 1, 1, 1, + 5, 5, 5, 11, 1, 1, 1, 1, 1, 8, 8, 8, 1, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 5, 11, 11, 5, 5, 5, 5, 11, 11, 5, 5, 11, 11, + 16, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 8, 8, 8, 8, 8, 5, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 8, 8, 5, 5, 8, 8, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 5, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 11, 11, 5, 5, 11, 11, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 5, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 8, 8, 8, 1, 1, 1, 1, 1, 1, 8, 1, 5, 1, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 5, 5, 5, 1, 1, 5, 5, 1, 1, 1, 1, 1, 5, 5, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 5, 5, 8, 8, 1, 1, 1, 1, 1, 8, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 8, 8, 5, 8, 8, 5, 8, 8, 1, 8, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 11, 5, 5, 11, 11, 1, 1, 1, 1, 1, 11, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, + 1, 1, 1, 11, 11, 5, 11, 11, 5, 11, 11, 1, 11, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 1, 1, 1, 1, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 1, 1, 1, 1, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, @@ -866,22 +705,22 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 1, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 1, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 1, 1, 1, 1, 1, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 1, 21, 21, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, - 1, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13, 13, + 1, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 15, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 22, 22, 22, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, + 21, 21, 21, 21, 21, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -906,8 +745,8 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 5, 5, 5, 1, 5, 5, 1, 1, 1, 1, 1, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 1, 1, 1, 1, 5, + 8, 5, 5, 5, 1, 5, 5, 1, 1, 1, 1, 1, 5, 5, 5, 5, 8, 8, 8, 8, 1, 8, 8, 8, 1, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 5, 5, 5, 1, 1, 1, 1, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -929,7 +768,7 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 5, 5, 5, 5, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -938,64 +777,64 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 8, 5, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 11, 5, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, - 5, 5, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 5, 5, 5, 5, 8, 8, 5, 5, 1, 1, 7, 1, 1, - 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 11, 11, 5, 5, 5, 5, 11, 11, 5, 5, 1, 1, 6, 1, 1, + 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 8, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 5, 5, 5, 5, 5, 11, 5, 5, 5, 5, 5, 5, 9, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 8, 11, 11, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 5, 5, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 8, - 13, 1, 7, 7, 1, 1, 1, 1, 1, 5, 5, 5, 5, 1, 8, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 11, 11, 5, 5, 5, 5, 5, 5, 5, 5, 5, 11, + 15, 1, 10, 10, 1, 1, 1, 1, 1, 5, 5, 5, 5, 1, 11, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 5, 5, 5, 8, 8, 5, 13, 5, 5, 1, 1, 1, 1, 1, 1, 5, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 11, 11, 5, 5, 5, 11, 11, 5, 15, 5, 5, 1, 1, 1, 1, 1, 1, 5, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, - 8, 8, 8, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 5, 5, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 1, 13, 8, - 5, 8, 8, 8, 8, 1, 1, 8, 8, 1, 1, 8, 8, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 8, 8, 1, 1, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13, 8, 8, 5, 5, 5, 5, 5, - 5, 1, 13, 1, 1, 13, 1, 13, 13, 13, 8, 1, 8, 8, 5, 13, 5, 7, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 11, 11, 11, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 11, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 1, 15, 11, + 5, 11, 11, 11, 11, 1, 1, 11, 11, 1, 1, 11, 11, 15, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 11, 11, 1, 1, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 8, 1, 1, 8, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 15, 11, 11, 5, 5, 5, 5, 5, + 5, 1, 15, 1, 1, 15, 1, 15, 15, 15, 11, 1, 11, 11, 5, 15, 9, 10, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 5, 5, 5, 5, 5, 5, 5, 5, - 8, 8, 5, 5, 5, 8, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 11, 11, 5, 5, 5, 5, 5, 5, 5, 5, + 11, 11, 5, 5, 5, 11, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13, 8, 8, 5, 5, 5, 5, 5, 5, 8, 5, 8, 8, 13, 8, 5, - 5, 8, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 11, 11, 5, 5, 5, 5, 5, 5, 11, 5, 11, 11, 15, 11, 5, + 5, 11, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13, 8, 8, 5, 5, 5, 5, 1, 1, 8, 8, 8, 8, 5, 5, 8, 5, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 11, 11, 5, 5, 5, 5, 1, 1, 11, 11, 11, 11, 5, 5, 11, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 5, 5, 5, 5, 5, 5, 5, 5, 8, 8, 5, 8, 5, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 11, 11, 5, 5, 5, 5, 5, 5, 5, 5, 11, 11, 5, 11, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 8, 5, 8, 8, 5, 5, 5, 5, 5, 5, 13, 5, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 11, 5, 11, 11, 5, 5, 5, 5, 5, 5, 15, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 8, 5, - 1, 1, 5, 5, 5, 5, 8, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 11, 5, + 1, 1, 5, 5, 5, 5, 11, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -1003,51 +842,50 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 8, 5, 5, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 11, 11, 5, 5, 5, 5, 5, 5, 5, 5, 5, 11, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 8, 8, 8, 8, 8, 8, 8, 1, 1, 8, 1, 1, 8, 8, 8, 8, 8, 8, 8, 8, 1, 8, 8, 1, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 15, 11, 11, 11, 11, 11, 1, 11, 11, 1, 1, 5, 5, 15, 9, 10, + 11, 10, 11, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13, 8, 8, 8, 8, 8, 1, 8, 8, 1, 1, 5, 5, 13, 5, 7, - 8, 7, 8, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 5, 5, 5, 5, 1, 1, 5, 5, 8, 8, 8, 8, - 5, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 8, 7, 5, 5, 5, 5, 1, - 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 8, 8, 5, 5, 5, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 7, 7, 7, 7, 7, 7, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 8, 5, 5, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 11, 11, 5, 5, 5, 5, 1, 1, 5, 5, 11, 11, 11, 11, + 5, 1, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 5, 5, 5, 5, 5, 5, 11, 1, 5, 5, 5, 5, 1, + 1, 1, 1, 1, 1, 1, 1, 9, 1, 1, 1, 1, 1, 1, 1, 1, 8, 5, 5, 5, 5, 5, 5, 11, 11, 5, 5, 5, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 11, 5, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 11, 5, 5, 5, 11, 5, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 5, 5, 5, 5, 5, 5, 5, 1, 5, 5, 5, 5, 5, 5, 8, 5, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 5, 5, 5, 5, 5, 5, 5, 1, 5, 5, 5, 5, 5, 5, 11, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 1, 8, 5, 5, 5, 5, 5, 5, 5, 8, 5, 5, 8, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 5, 5, 5, 5, 5, 5, 1, 11, 5, 5, 5, 5, 5, 5, 5, 11, 5, 5, 11, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 1, 1, 1, 5, 1, 5, 5, 1, 5, - 5, 5, 5, 5, 5, 5, 7, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 1, 5, 5, 1, 8, 8, 5, 8, 5, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 5, 5, 5, 5, 10, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 11, 11, 11, 11, 1, 5, 5, 1, 11, 11, 5, 11, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -1057,17 +895,18 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 5, 5, 7, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 5, 5, 5, 5, 5, 1, 1, 1, 8, 8, - 5, 13, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 11, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 10, 11, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 11, 11, 5, 5, 5, 5, 5, 1, 1, 1, 11, 11, + 5, 15, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -1075,7 +914,7 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 8, 8, 8, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 11, 11, 11, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -1101,35 +940,35 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 30, 1, 1, 1, 30, 30, 30, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 29, 1, 1, 1, 29, 29, 29, 29, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 22, 22, 22, 22, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 22, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 1, + 21, 21, 21, 21, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 25, 25, 21, 21, 21, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -1137,23 +976,23 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22, 22, 22, 22, 1, 22, 22, 22, 22, 22, 22, 22, 1, 22, 22, 1, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22, 22, 22, 1, 1, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 21, 21, 21, 21, 1, 21, 21, 21, 21, 21, 21, 21, 1, 21, 21, 1, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 21, 21, 21, 1, 1, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 21, 21, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -1173,7 +1012,7 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 13, 13, 5, 5, 5, 1, 1, 1, 13, 13, 13, 13, 13, 13, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 5, 5, 5, + 1, 1, 1, 1, 1, 15, 15, 5, 5, 5, 1, 1, 1, 15, 15, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -1186,10 +1025,10 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -1241,7 +1080,7 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 5, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 5, 5, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -1258,126 +1097,126 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 3, 3, 3, 3, 21, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 21, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 30, 30, 30, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 30, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 30, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 3, 3, 3, 3, 20, 20, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 20, 20, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 21, 2, 2, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, - 22, 21, 21, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 21, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 21, 22, 22, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 3, 3, 3, 3, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 3, 3, 3, 3, 3, 3, 3, 21, 21, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 24, 24, 24, 24, 24, 24, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 19, 3, 3, 19, 19, 19, 19, 19, 19, 19, 19, 19, 21, 21, 21, 21, 21, 21, 21, 21, 21, 19, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 19, 21, 21, - 21, 21, 21, 21, 21, 26, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 3, 3, 19, 19, 3, 19, 19, 19, 3, 3, 19, 19, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 26, 26, 26, 21, 21, 26, 21, 21, 26, 23, 23, 19, 19, 21, 21, 21, 21, 21, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 3, 3, 19, 21, 19, 3, 19, 21, 21, 21, 32, 32, 32, 32, 32, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 19, - 21, 19, 26, 26, 21, 21, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 21, 21, 21, 21, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 21, 21, 21, 26, 21, 21, 21, - 21, 26, 26, 26, 21, 26, 26, 26, 21, 21, 21, 21, 21, 21, 21, 26, 21, 26, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 26, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 19, 3, 21, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 20, 2, 2, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, + 21, 20, 20, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 20, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 20, 21, 21, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 30, 30, 30, 30, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 30, 30, 30, 30, 30, 30, 30, 20, 20, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 21, 21, 21, 21, 21, 21, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 3, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 20, 20, 20, 20, 20, 20, 20, 20, 20, 3, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 3, 20, 20, + 20, 20, 20, 20, 20, 24, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 1, 1, 3, 3, 1, 3, 3, 3, 1, 1, 3, 3, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 24, 24, 24, 20, 20, 24, 20, 20, 24, 22, 22, 3, 3, 20, 20, 20, 20, 20, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 1, 1, 3, 20, 3, 1, 3, 20, 20, 20, 32, 32, 32, 32, 32, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 3, + 20, 3, 24, 24, 20, 20, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 20, 20, 20, 24, 20, 20, 20, + 20, 24, 24, 24, 20, 24, 24, 24, 20, 20, 20, 20, 20, 20, 20, 24, 20, 24, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 24, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 3, 1, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 20, 20, 20, 20, 1, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 1, 1, 1, 1, 1, 1, 1, 3, 3, 1, 1, 3, 22, 22, 3, 3, 3, 3, 24, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 3, 3, 3, 3, 1, 1, 22, 1, 1, 1, 1, 24, 24, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 20, 3, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, + 1, 1, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 1, + 1, 3, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 3, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 24, 24, 24, 20, 20, 20, 24, 24, 24, 24, 24, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 24, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 24, 24, 24, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 24, 20, 20, 20, 20, 20, 1, 1, 1, 1, 1, 3, 24, 3, 3, 3, 20, 20, 20, 1, 1, 20, 20, 20, 20, 30, 30, 30, 20, 20, 20, 20, + 3, 3, 3, 3, 3, 3, 1, 1, 1, 3, 1, 20, 20, 30, 30, 30, 3, 1, 1, 3, 20, 20, 20, 20, 20, 20, 20, 20, 20, 30, 30, 30, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 30, 30, 30, 30, 30, 30, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 30, 30, 30, 30, 20, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 30, 30, 30, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 30, 30, 30, 30, 30, 30, 30, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 30, 30, 30, 30, 30, 30, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 30, 30, 30, 30, 30, 30, 30, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 30, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 30, 30, 30, 30, + 1, 1, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 24, 20, 20, 24, 20, 20, 20, 20, 20, 20, 20, 20, 24, 24, 24, 24, 24, 24, 24, 24, + 20, 20, 20, 20, 20, 20, 24, 20, 20, 20, 20, 20, 20, 20, 20, 20, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 20, 1, 24, 24, 24, 20, + 20, 20, 20, 20, 20, 20, 1, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 24, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 24, 24, 20, 24, 24, 20, 24, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 24, 24, 24, 20, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 30, 30, 30, 30, 30, 30, 30, 30, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 30, 30, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 30, 30, 30, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 30, 30, 30, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 24, 24, 24, 20, 30, 20, 30, 30, 30, 30, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 30, 30, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 30, 30, 30, 30, 20, 24, 24, 24, 24, 24, 24, 24, 24, 24, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 1, 1, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 1, - 1, 1, 1, 1, 1, 1, 3, 3, 3, 19, 19, 21, 21, 21, 21, 3, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 21, 21, 21, 21, 21, 21, 3, 3, 3, 3, 3, 3, 3, 19, 19, 3, 3, 19, 23, 23, 19, 19, 19, 19, 26, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 19, 3, 3, 19, 19, 19, 19, 3, 3, 23, 3, 3, 3, 3, 26, 26, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 21, 19, 3, 3, 19, 3, 3, 3, 3, 3, 3, 3, 3, 19, 19, 3, 3, 3, 3, 3, 3, 3, 3, 3, 19, 3, 3, 3, - 3, 3, 19, 19, 19, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 19, 19, 19, 3, 3, 3, 3, 3, 3, 3, 3, 19, 19, 19, 3, - 3, 19, 3, 19, 3, 3, 3, 3, 19, 3, 3, 3, 3, 3, 3, 19, 3, 3, 3, 19, 3, 3, 3, 3, 3, 3, 19, 21, 21, 21, 21, 21, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 21, 21, 21, 26, 26, 26, 21, 21, 21, 26, 26, 26, 26, 26, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 21, 26, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 26, 26, 26, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 26, 21, 21, 21, 21, 21, 3, 3, 3, 3, 3, 19, 26, 19, 19, 19, 21, 21, 21, 3, 3, 21, 21, 21, 21, 3, 3, 3, 21, 21, 21, 21, - 19, 19, 19, 19, 19, 19, 3, 3, 3, 19, 3, 21, 21, 3, 3, 3, 19, 3, 3, 19, 21, 21, 21, 21, 21, 21, 21, 21, 21, 3, 3, 3, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 3, 3, 3, 3, 21, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 26, 21, 21, 26, 21, 21, 21, 21, 21, 21, 21, 21, 26, 26, 26, 26, 26, 26, 26, 26, - 21, 21, 21, 21, 21, 21, 26, 21, 21, 21, 21, 21, 21, 21, 21, 21, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 21, 1, 26, 26, 26, 21, - 21, 21, 21, 21, 21, 21, 1, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 26, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 26, 26, 21, 26, 26, 21, 26, 21, 21, 21, 21, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 26, 26, 26, 21, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 21, 21, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 3, 3, 3, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 3, 3, 3, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 21, 21, 21, 26, 26, 26, 21, 3, 21, 3, 3, 3, 3, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 3, 3, 21, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 3, 3, 3, 3, 21, 26, 26, 26, 26, 26, 26, 26, 26, 26, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, @@ -1385,7 +1224,15 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, @@ -1397,8 +1244,8 @@ static constexpr uint8_t kGraphemeBreakStage2[32768] = { }; static constexpr uint8_t kGraphemeBreakStage3[33] = { - 0, 32, 96, 43, 107, 14, 1, 33, 35, 48, 3, 15, 16, 46, 68, 5, 6, 10, 9, 171, 235, 203, 64, 172, - 75, 236, 204, 78, 71, 72, 37, 162, 205, + 17, 32, 96, 171, 235, 14, 1, 3, 48, 15, 33, 35, 68, 5, 6, 46, 47, 10, 9, 49, 203, 64, 172, 236, + 204, 78, 71, 72, 0, 37, 43, 194, 205, }; } // namespace StringWidthTables diff --git a/test/js/bun/util/sliceAnsi.test.ts b/test/js/bun/util/sliceAnsi.test.ts index 28451848551d..70144b80d696 100644 --- a/test/js/bun/util/sliceAnsi.test.ts +++ b/test/js/bun/util/sliceAnsi.test.ts @@ -357,9 +357,10 @@ describe("Bun.sliceAnsi", () => { test("counts emoji-style graphemes as fullwidth", () => { expect(Bun.sliceAnsi("A☺️B", 1, 3)).toBe("☺️"); expect(Bun.sliceAnsi("A1️⃣B", 1, 3)).toBe("1️⃣"); - // Single (unpaired) regional indicator is width 1 — matches Bun.stringWidth - expect(Bun.stringWidth("\u{1F1E6}")).toBe(1); - expect(Bun.sliceAnsi("A\u{1F1E6}B", 1, 2)).toBe("\u{1F1E6}"); + // Single (unpaired) regional indicator is width 2 — matches Bun.stringWidth + expect(Bun.stringWidth("\u{1F1E6}")).toBe(2); + expect(Bun.sliceAnsi("A\u{1F1E6}B", 1, 3)).toBe("\u{1F1E6}"); + expect(Bun.sliceAnsi("A\u{1F1E6}B", 3, 4)).toBe("B"); }); test("does not treat text-presentation pictographs as fullwidth", () => { @@ -1267,10 +1268,12 @@ describe("Bun.sliceAnsi", () => { expect(Bun.sliceAnsi("abcX\u0600Y", 0, 4, { ellipsis: E })).toBe("abc" + E); expect(Bun.sliceAnsi("abcX\u0600YZ", 0, 4, { ellipsis: E })).toBe("abc" + E); expect(Bun.sliceAnsi("ЖЗИК\u0600\u0661", 0, 4, { ellipsis: E })).toBe("ЖЗИ" + E); - expect(Bun.sliceAnsi("ЖЗИК\u200b\u20E3", 0, 4, { ellipsis: E })).toBe("ЖЗИ" + E); - expect(Bun.sliceAnsi("ЖЗИК\n\u20E3", 0, 4, { ellipsis: E })).toBe("ЖЗИ" + E); - expect(Bun.sliceAnsi("ЖЗИК\u200b\u0903", 0, 4, { ellipsis: E })).toBe("ЖЗИ" + E); + expect(Bun.sliceAnsi("ЖЗИК\u200b\u102B", 0, 4, { ellipsis: E })).toBe("ЖЗИ" + E); expect(Bun.sliceAnsi("abcX\u0600", 0, 4, { ellipsis: E })).toBe("abcX"); + // A lone keycap mark or Indic vowel sign after a control is zero-width. + expect(Bun.sliceAnsi("ЖЗИК\u200b\u20E3", 0, 4, { ellipsis: E })).toBe("ЖЗИК"); + expect(Bun.sliceAnsi("ЖЗИК\n\u20E3", 0, 4, { ellipsis: E })).toBe("ЖЗИК"); + expect(Bun.sliceAnsi("ЖЗИК\u200b\u0903", 0, 4, { ellipsis: E })).toBe("ЖЗИК"); // start > 0 (start ellipsis budgeted) with a zero-width tail: spec // zone kept, no end ellipsis. expect(Bun.sliceAnsi("ЖЗИКЛ\n", 1, 5, { ellipsis: E })).toBe(E + "ИКЛ"); diff --git a/test/js/bun/util/stringWidth.test.ts b/test/js/bun/util/stringWidth.test.ts index bde985ac6c31..f3f9f7760925 100644 --- a/test/js/bun/util/stringWidth.test.ts +++ b/test/js/bun/util/stringWidth.test.ts @@ -262,7 +262,7 @@ describe("stringWidth extended", () => { test("combining diacritical marks extended", () => { expect(Bun.stringWidth("\u1AB0")).toBe(0); - expect(Bun.stringWidth("\u1AFF")).toBe(0); + expect(Bun.stringWidth("\u1ACE")).toBe(0); }); test("combining diacritical marks supplement", () => { @@ -272,7 +272,7 @@ describe("stringWidth extended", () => { test("combining diacritical marks for symbols", () => { expect(Bun.stringWidth("\u20D0")).toBe(0); - expect(Bun.stringWidth("\u20FF")).toBe(0); + expect(Bun.stringWidth("\u20F0")).toBe(0); }); test("combining half marks", () => { @@ -579,7 +579,7 @@ describe("stringWidth extended", () => { expect(Bun.stringWidth("🇺🇸")).toBe(2); // US flag expect(Bun.stringWidth("🇬🇧")).toBe(2); // UK flag expect(Bun.stringWidth("🇯🇵")).toBe(2); // Japan flag - expect(Bun.stringWidth("🇦")).toBe(1); // Single regional indicator + expect(Bun.stringWidth("🇦")).toBe(2); // Single regional indicator: Emoji_Presentation }); test("skin tone modifiers", () => { @@ -1888,3 +1888,180 @@ describe("width tables: combining marks, jamo, U16/17 emoji, VS after zero-width expect(Bun.stringWidth(wrapped, { ambiguousIsNarrow: false, countAnsiEscapeCodes: true })).toBe(11); }); }); + +describe("width table derived from Unicode properties", () => { + const widths = (cps: number[]) => cps.map(cp => Bun.stringWidth(String.fromCodePoint(cp))); + + test("every format character (Cf) is zero-width", () => { + const format = [ + 0x0890, + 0x0891, // Arabic pound/piastre mark above (prepend) + 0xfff9, + 0xfffa, + 0xfffb, // interlinear annotation anchor/separator/terminator + 0x110bd, + 0x110cd, // Kaithi number sign (prepend) + 0x13430, + 0x13437, + 0x1343f, // Egyptian hieroglyph format controls + 0x00ad, + 0x061c, + 0x180e, + 0x200b, + 0x2060, + 0x2066, + 0xfeff, + 0xe0001, + 0xe007f, + ]; + expect(widths(format)).toEqual(format.map(() => 0)); + expect(Bun.stringWidth("a\uFFF9b\uFFFAc\uFFFBd")).toBe(4); + }); + + test("unassigned default-ignorable codepoints are zero-width", () => { + // Reserved for future format characters (Default_Ignorable_Code_Point, + // General_Category Cn): a renderer shows nothing for them. + const reserved = [0xfff0, 0xfff8, 0xe0080, 0xe00ff, 0xe01f0, 0xe0fff]; + expect(widths(reserved)).toEqual(reserved.map(() => 0)); + }); + + test("other unassigned codepoints are narrow, even inside combining-mark blocks", () => { + const unassigned = [0x1aff, 0x20ff, 0x09ba, 0x09c5, 0x0a00, 0x0b80, 0x0d45]; + expect(widths(unassigned)).toEqual(unassigned.map(() => 1)); + }); + + test("Indic letters are narrow, Indic spacing vowel signs are zero-width", () => { + // U+0980 BENGALI ANJI, U+0C80 KANNADA SIGN SPACING CANDRABINDU and + // U+0D3A MALAYALAM LETTER TTTA are visible letters (Lo). + expect(widths([0x0980, 0x0c80, 0x0d3a])).toEqual([1, 1, 1]); + // The Mc vowel signs stay zero-width: consonant + vowel sign is one column. + expect(widths([0x093e, 0x093f, 0x09be, 0x0bbe, 0x0cc0, 0x0d3e])).toEqual([0, 0, 0, 0, 0, 0]); + expect(Bun.stringWidth("\u0915\u093E")).toBe(1); + // Kirat Rai vowel signs have Grapheme_Cluster_Break=V but are letters. + expect(widths([0x16d63, 0x16d67])).toEqual([1, 1]); + }); + + test("Emoji_Presentation codepoints are wide, including a lone regional indicator", () => { + expect(widths([0x1f1e6, 0x1f1ff, 0x1f3fb, 0x231a, 0x2b50, 0x1f600])).toEqual([2, 2, 2, 2, 2, 2]); + }); +}); + +describe("grapheme cluster width rules", () => { + test("regional indicators: pairs are flags, a lone one is still a wide emoji", () => { + expect(Bun.stringWidth("\u{1F1E6}")).toBe(2); + expect(Bun.stringWidth("\u{1F1E6}\u0301")).toBe(2); // RI + combining mark + expect(Bun.stringWidth("\u{1F1E6}\uFE0F")).toBe(2); // RI + VS16 + expect(Bun.stringWidth("\u{1F1E6}\u{1F1E7}")).toBe(2); // flag + expect(Bun.stringWidth("\u{1F1E6}\u{1F1E7}\u{1F1E8}")).toBe(4); // flag + lone RI + expect(Bun.stringWidth("A\u{1F1E6}")).toBe(3); + expect(Bun.stringWidth("\u{1F1E6}\u{1F1E7}\u{1F1E8}\u{1F1E9}")).toBe(4); // two flags + }); + + test("keycap: only [0-9#*] + U+20E3 is a keycap emoji", () => { + expect(Bun.stringWidth("\u20E3")).toBe(0); // lone enclosing keycap: a mark + expect(Bun.stringWidth("1\u20E3")).toBe(2); + expect(Bun.stringWidth("1\uFE0F\u20E3")).toBe(2); + expect(Bun.stringWidth("#\uFE0F\u20E3")).toBe(2); + expect(Bun.stringWidth("*\u20E3")).toBe(2); + expect(Bun.stringWidth("a\u20E3")).toBe(1); // not a keycap base + expect(Bun.stringWidth("\u0915\u20E3")).toBe(1); + expect(Bun.stringWidth("\u00A9\u20E3")).toBe(1); + expect(Bun.stringWidth("\u0301\u20E3")).toBe(0); + expect(Bun.stringWidth("\u20E3\u20E3")).toBe(0); + }); + + test("emoji modifiers extend any base and never widen it", () => { + expect(Bun.stringWidth("\u{1F3FB}")).toBe(2); // lone skin tone swatch + expect(Bun.stringWidth("\u{1F44B}\u{1F3FB}")).toBe(2); // modifier base + skin tone + expect(Bun.stringWidth("\u{1F600}\u{1F3FB}")).toBe(2); // not a modifier base, still one cluster + expect(Bun.stringWidth("\u{1F600}\u{1F3FB}\u200D\u{1F600}")).toBe(2); // GB11 with a modifier before the ZWJ + expect(Bun.stringWidth("\u{1F3FB}\u{1F3FB}")).toBe(2); + expect(Bun.stringWidth("A\u{1F3FB}")).toBe(1); + expect(Bun.stringWidth("\u4E2D\u{1F3FB}")).toBe(2); + expect(Bun.stringWidth("\u270C\u{1F3FD}")).toBe(2); // text-default emoji + skin tone + expect(Bun.stringWidth("\u0301\u{1F3FB}")).toBe(0); + }); + + test("copyright and registered signs are emoji bases", () => { + expect(Bun.stringWidth("\u00A9\uFE0F")).toBe(2); + expect(Bun.stringWidth("\u00A9\u200D\u{1F600}")).toBe(2); // ZWJ sequence + expect(Bun.stringWidth("\u00AE\u{1F3FB}")).toBe(2); // modifier sequence + expect(Bun.stringWidth("\u00A9")).toBe(1); + }); + + test("controls, CR and LF end a cluster on both sides", () => { + expect(Bun.stringWidth("\n\u{1F3FB}")).toBe(2); + expect(Bun.stringWidth("\r\n\u0301")).toBe(0); + expect(Bun.stringWidth("a\x01\u0301b")).toBe(2); + expect(Bun.stringWidth("\x1b\u{1F3FB}", { countAnsiEscapeCodes: true })).toBe(2); + expect(Bun.stringWidth("\u{1F600}\x00\u200D\u{1F600}")).toBe(4); + }); + + test("Extended_Pictographic and Indic_Conjunct_Break follow Unicode 17", () => { + // U+2605 BLACK STAR lost Extended_Pictographic in Unicode 16: no GB11. + expect(Bun.stringWidth("\u2605\u200D\u{1F600}")).toBe(3); + expect(Bun.stringWidth("\u2606\u200D\u{1F600}")).toBe(3); + // Myanmar consonant + virama + consonant is one conjunct cluster (InCB + // since Unicode 16); a combining mark after it still joins. + expect(Bun.stringWidth("\u1000\u1039\u1001\u{1F3FB}")).toBe(2); + expect(Bun.stringWidth("\u1000\u1039\u1001")).toBe(2); + }); + + test("sliceAnsi measures clusters exactly like stringWidth", () => { + const cases = [ + "\u{1F1E6}x", + "\u{1F1E6}\u{1F1E7}\u{1F1E8}", + "1\u20E3a\u20E3\u20E3", + "A\u{1F3FB}\u{1F600}\u{1F3FB}", + "\u00A9\u200D\u{1F600}z", + "\u2605\u200D\u{1F600}", + "\n\u{1F3FB}", + "\u{1F600}\u{1F3FB}\u200D\u{1F600}!", + ]; + const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" }); + for (const s of cases) { + const width = Bun.stringWidth(s); + // sliceAnsi(s, 0, k) keeps every cluster that starts before column k. + const clusters = [...segmenter.segment(s)].map(({ segment }) => Bun.stringWidth(segment)); + expect(clusters.reduce((a, b) => a + b, 0)).toBe(width); + const expected: number[] = []; + for (let k = 0; k <= width; k++) { + let start = 0; + let kept = 0; + for (const w of clusters) { + if (start >= k) break; + kept += w; + start += w; + } + expected.push(kept); + } + const seen: number[] = []; + for (let k = 0; k <= width; k++) seen.push(Bun.stringWidth(Bun.sliceAnsi(s, 0, k))); + expect({ s, seen, neg: Bun.stringWidth(Bun.sliceAnsi(s, -width)) }).toEqual({ s, seen: expected, neg: width }); + } + }); + + test("the UTF-8 path (console.table cells) clusters like stringWidth", () => { + // The column width of a one-cell table is the cell's visible width plus + // one space of padding on each side. + const cells = [ + "\u{1F468}\u200D\u{1F469}\u200D\u{1F467}", // family ZWJ sequence + "\u{1F1FA}\u{1F1F8}", // flag + "\u{1F1E6}", + "1\uFE0F\u20E3", + "a\u20E3", + "\u{1F44B}\u{1F3FD}", + "A\u{1F3FB}", + "\u0915\u093F", + "\u1112\u1161\u11AB", + "e\u0301\u0301", + "\x1b[31m\u{1F468}\u200D\u{1F469}\x1b[0m", + "\uFFF9x\uFFFB", + ]; + for (const s of cells) { + const top = Bun.inspect.table([{ s }], { colors: false }).split("\n")[0]; + const column = top.slice(top.lastIndexOf("\u252C") + 1, top.lastIndexOf("\u2510")); + expect({ s, column: column.length }).toEqual({ s, column: Bun.stringWidth(s) + 2 }); + } + }); +}); diff --git a/test/js/bun/util/wrapAnsi.test.ts b/test/js/bun/util/wrapAnsi.test.ts index d7cfe662a4aa..6d27e8e9a9f4 100644 --- a/test/js/bun/util/wrapAnsi.test.ts +++ b/test/js/bun/util/wrapAnsi.test.ts @@ -424,22 +424,22 @@ describe("Bun.wrapAnsi", () => { // row's width less than the sum of its parts, so it must be recomputed. const cases: [input: string, columns: number, hard: boolean, wordWrap: boolean, trim: boolean, expected: string][] = [ - ["aa \u20E3bb cc", 7, false, false, false, "aa \u20E3bb \ncc"], - ["aa \u20E3bb cc", 7, false, false, true, "aa \u20E3bb\ncc"], + ["aa \u20E3bb cc", 7, false, false, false, "aa \u20E3bb c\nc"], + ["aa \u20E3bb cc", 7, false, false, true, "aa \u20E3bb c\nc"], ["aa \u20E3bb cc", 7, false, true, false, "aa \u20E3bb \ncc"], ["aa \u20E3bb cc", 7, false, true, true, "aa \u20E3bb\ncc"], - ["aa \u20E3bb cc", 7, true, false, false, "aa \u20E3bb \ncc"], - ["aa \u20E3bb cc", 7, true, false, true, "aa \u20E3bb\ncc"], + ["aa \u20E3bb cc", 7, true, false, false, "aa \u20E3bb c\nc"], + ["aa \u20E3bb cc", 7, true, false, true, "aa \u20E3bb c\nc"], ["aa \u20E3bb cc", 7, true, true, false, "aa \u20E3bb \ncc"], ["aa \u20E3bb cc", 7, true, true, true, "aa \u20E3bb\ncc"], - ["aa \u20E3bb cc", 8, false, false, false, "aa \u20E3bb c\nc"], - ["aa \u20E3bb cc", 8, false, false, true, "aa \u20E3bb c\nc"], - ["aa \u20E3bb cc", 8, false, true, false, "aa \u20E3bb \ncc"], - ["aa \u20E3bb cc", 8, false, true, true, "aa \u20E3bb\ncc"], - ["aa \u20E3bb cc", 8, true, false, false, "aa \u20E3bb c\nc"], - ["aa \u20E3bb cc", 8, true, false, true, "aa \u20E3bb c\nc"], - ["aa \u20E3bb cc", 8, true, true, false, "aa \u20E3bb \ncc"], - ["aa \u20E3bb cc", 8, true, true, true, "aa \u20E3bb\ncc"], + ["aa \u20E3bb cc", 8, false, false, false, "aa \u20E3bb cc"], + ["aa \u20E3bb cc", 8, false, false, true, "aa \u20E3bb cc"], + ["aa \u20E3bb cc", 8, false, true, false, "aa \u20E3bb cc"], + ["aa \u20E3bb cc", 8, false, true, true, "aa \u20E3bb cc"], + ["aa \u20E3bb cc", 8, true, false, false, "aa \u20E3bb cc"], + ["aa \u20E3bb cc", 8, true, false, true, "aa \u20E3bb cc"], + ["aa \u20E3bb cc", 8, true, true, false, "aa \u20E3bb cc"], + ["aa \u20E3bb cc", 8, true, true, true, "aa \u20E3bb cc"], ["aa \u20E3bb cc", 9, false, false, false, "aa \u20E3bb cc"], ["aa \u20E3bb cc", 9, false, false, true, "aa \u20E3bb cc"], ["aa \u20E3bb cc", 9, false, true, false, "aa \u20E3bb cc"], @@ -454,7 +454,7 @@ describe("Bun.wrapAnsi", () => { false, false, false, - "\u001B[31maa\u001B[39m \u20E3bb \n\u001B[31mcc\u001B[39m", + "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mc\u001B[39m\n\u001B[31mc\u001B[39m", ], [ "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", @@ -462,7 +462,7 @@ describe("Bun.wrapAnsi", () => { false, false, true, - "\u001B[31maa\u001B[39m \u20E3bb\n\u001B[31mcc\u001B[39m", + "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mc\u001B[39m\n\u001B[31mc\u001B[39m", ], [ "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", @@ -486,7 +486,7 @@ describe("Bun.wrapAnsi", () => { true, false, false, - "\u001B[31maa\u001B[39m \u20E3bb \n\u001B[31mcc\u001B[39m", + "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mc\u001B[39m\n\u001B[31mc\u001B[39m", ], [ "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", @@ -494,7 +494,7 @@ describe("Bun.wrapAnsi", () => { true, false, true, - "\u001B[31maa\u001B[39m \u20E3bb\n\u001B[31mcc\u001B[39m", + "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mc\u001B[39m\n\u001B[31mc\u001B[39m", ], [ "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", @@ -518,7 +518,7 @@ describe("Bun.wrapAnsi", () => { false, false, false, - "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mc\u001B[39m\n\u001B[31mc\u001B[39m", + "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", ], [ "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", @@ -526,7 +526,7 @@ describe("Bun.wrapAnsi", () => { false, false, true, - "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mc\u001B[39m\n\u001B[31mc\u001B[39m", + "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", ], [ "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", @@ -534,7 +534,7 @@ describe("Bun.wrapAnsi", () => { false, true, false, - "\u001B[31maa\u001B[39m \u20E3bb \n\u001B[31mcc\u001B[39m", + "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", ], [ "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", @@ -542,7 +542,7 @@ describe("Bun.wrapAnsi", () => { false, true, true, - "\u001B[31maa\u001B[39m \u20E3bb\n\u001B[31mcc\u001B[39m", + "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", ], [ "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", @@ -550,7 +550,7 @@ describe("Bun.wrapAnsi", () => { true, false, false, - "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mc\u001B[39m\n\u001B[31mc\u001B[39m", + "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", ], [ "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", @@ -558,7 +558,7 @@ describe("Bun.wrapAnsi", () => { true, false, true, - "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mc\u001B[39m\n\u001B[31mc\u001B[39m", + "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", ], [ "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", @@ -566,7 +566,7 @@ describe("Bun.wrapAnsi", () => { true, true, false, - "\u001B[31maa\u001B[39m \u20E3bb \n\u001B[31mcc\u001B[39m", + "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", ], [ "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", @@ -574,7 +574,7 @@ describe("Bun.wrapAnsi", () => { true, true, true, - "\u001B[31maa\u001B[39m \u20E3bb\n\u001B[31mcc\u001B[39m", + "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", ], [ "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", @@ -640,184 +640,118 @@ describe("Bun.wrapAnsi", () => { true, "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", ], - ["aa\tz \uFE0Fbb cc", 8, false, false, false, "aa\tz \uFE0Fbb c\nc"], - ["aa\tz \uFE0Fbb cc", 8, false, false, true, "aa\tz \uFE0Fbb c\nc"], - ["aa\tz \uFE0Fbb cc", 8, false, true, false, "aa\tz \uFE0Fbb \ncc"], - ["aa\tz \uFE0Fbb cc", 8, false, true, true, "aa\tz \uFE0Fbb\ncc"], - ["aa\tz \uFE0Fbb cc", 8, true, false, false, "aa\tz \uFE0Fbb c\nc"], - ["aa\tz \uFE0Fbb cc", 8, true, false, true, "aa\tz \uFE0Fbb c\nc"], - ["aa\tz \uFE0Fbb cc", 8, true, true, false, "aa\tz \uFE0Fbb \ncc"], - ["aa\tz \uFE0Fbb cc", 8, true, true, true, "aa\tz \uFE0Fbb\ncc"], - ["aa \u0301bb cc", 8, false, false, false, "aa \u0301bb cc"], - ["aa \u0301bb cc", 8, false, false, true, "aa \u0301bb cc"], - ["aa \u0301bb cc", 8, false, true, false, "aa \u0301bb cc"], - ["aa \u0301bb cc", 8, false, true, true, "aa \u0301bb cc"], - ["aa \u0301bb cc", 8, true, false, false, "aa \u0301bb cc"], - ["aa \u0301bb cc", 8, true, false, true, "aa \u0301bb cc"], - ["aa \u0301bb cc", 8, true, true, false, "aa \u0301bb cc"], - ["aa \u0301bb cc", 8, true, true, true, "aa \u0301bb cc"], - // U+0600 (Prepend) is a width-0 first word; with trim the next word is - // appended with no separator space and fuses with it across the rows. - ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 7, false, false, false, "\u0600 \u{1F44D}\u{1F3FF}ab c\nd"], - ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 7, false, false, true, "\u0600\u{1F44D}\u{1F3FF}ab\ncd"], - ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 7, false, true, false, "\u0600 \u{1F44D}\u{1F3FF}ab \ncd"], - ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 7, false, true, true, "\u0600\u{1F44D}\u{1F3FF}ab\ncd"], - ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 7, true, false, false, "\u0600 \u{1F44D}\u{1F3FF}ab c\nd"], - ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 7, true, false, true, "\u0600\u{1F44D}\u{1F3FF}ab\ncd"], - ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 7, true, true, false, "\u0600 \u{1F44D}\u{1F3FF}ab \ncd"], - ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 7, true, true, true, "\u0600\u{1F44D}\u{1F3FF}ab\ncd"], - ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 8, false, false, false, "\u0600 \u{1F44D}\u{1F3FF}ab cd"], - ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 8, false, false, true, "\u0600\u{1F44D}\u{1F3FF}ab c\nd"], - ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 8, false, true, false, "\u0600 \u{1F44D}\u{1F3FF}ab cd"], - ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 8, false, true, true, "\u0600\u{1F44D}\u{1F3FF}ab\ncd"], - ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 8, true, false, false, "\u0600 \u{1F44D}\u{1F3FF}ab cd"], - ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 8, true, false, true, "\u0600\u{1F44D}\u{1F3FF}ab c\nd"], - ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 8, true, true, false, "\u0600 \u{1F44D}\u{1F3FF}ab cd"], - ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 8, true, true, true, "\u0600\u{1F44D}\u{1F3FF}ab\ncd"], - // Same no-space seam with the Prepend hidden behind a trailing escape: - // the cluster still fuses across the escape sequence. - [ - "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab cd", - 7, - false, - false, - false, - "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab c\nd", - ], - [ - "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab cd", - 7, - false, - false, - true, - "\u001B[31m\u0600\u001B[39m\u{1F44D}\u{1F3FF}ab\ncd", - ], - [ - "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab cd", - 7, - false, - true, - false, - "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab \ncd", - ], - [ - "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab cd", - 7, - false, - true, - true, - "\u001B[31m\u0600\u001B[39m\u{1F44D}\u{1F3FF}ab\ncd", - ], - [ - "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab cd", - 7, - true, - false, - false, - "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab c\nd", - ], - [ - "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab cd", - 7, - true, - false, - true, - "\u001B[31m\u0600\u001B[39m\u{1F44D}\u{1F3FF}ab\ncd", - ], - [ - "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab cd", - 7, - true, - true, - false, - "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab \ncd", - ], - [ - "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab cd", - 7, - true, - true, - true, - "\u001B[31m\u0600\u001B[39m\u{1F44D}\u{1F3FF}ab\ncd", - ], - // ANSI-prefixed words: an SGR sequence (ESC is ASCII) at the start of a word - // must not hide the cluster-fusing codepoint that actually lands on the seam. - // Escape-wrapped emoji+modifier word after an escape-wrapped width-0 Prepend row. - [ - "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab cd", + ["aa\tz ️bb cc", 8, false, false, false, "aa\tz ️bb c\nc"], + ["aa\tz ️bb cc", 8, false, false, true, "aa\tz ️bb c\nc"], + ["aa\tz ️bb cc", 8, false, true, false, "aa\tz ️bb \ncc"], + ["aa\tz ️bb cc", 8, false, true, true, "aa\tz ️bb\ncc"], + ["aa\tz ️bb cc", 8, true, false, false, "aa\tz ️bb c\nc"], + ["aa\tz ️bb cc", 8, true, false, true, "aa\tz ️bb c\nc"], + ["aa\tz ️bb cc", 8, true, true, false, "aa\tz ️bb \ncc"], + ["aa\tz ️bb cc", 8, true, true, true, "aa\tz ️bb\ncc"], + ["aa ́bb cc", 8, false, false, false, "aa ́bb cc"], + ["aa ́bb cc", 8, false, false, true, "aa ́bb cc"], + ["aa ́bb cc", 8, false, true, false, "aa ́bb cc"], + ["aa ́bb cc", 8, false, true, true, "aa ́bb cc"], + ["aa ́bb cc", 8, true, false, false, "aa ́bb cc"], + ["aa ́bb cc", 8, true, false, true, "aa ́bb cc"], + ["aa ́bb cc", 8, true, true, false, "aa ́bb cc"], + ["aa ́bb cc", 8, true, true, true, "aa ́bb cc"], + ["\u0600 👍🏿ab cd", 7, false, false, false, "\u0600 👍🏿ab c\nd"], + ["\u0600 👍🏿ab cd", 7, false, false, true, "\u0600👍🏿ab cd"], + ["\u0600 👍🏿ab cd", 7, false, true, false, "\u0600 👍🏿ab \ncd"], + ["\u0600 👍🏿ab cd", 7, false, true, true, "\u0600👍🏿ab cd"], + ["\u0600 👍🏿ab cd", 7, true, false, false, "\u0600 👍🏿ab c\nd"], + ["\u0600 👍🏿ab cd", 7, true, false, true, "\u0600👍🏿ab cd"], + ["\u0600 👍🏿ab cd", 7, true, true, false, "\u0600 👍🏿ab \ncd"], + ["\u0600 👍🏿ab cd", 7, true, true, true, "\u0600👍🏿ab cd"], + ["\u0600 👍🏿ab cd", 8, false, false, false, "\u0600 👍🏿ab cd"], + ["\u0600 👍🏿ab cd", 8, false, false, true, "\u0600👍🏿ab cd"], + ["\u0600 👍🏿ab cd", 8, false, true, false, "\u0600 👍🏿ab cd"], + ["\u0600 👍🏿ab cd", 8, false, true, true, "\u0600👍🏿ab cd"], + ["\u0600 👍🏿ab cd", 8, true, false, false, "\u0600 👍🏿ab cd"], + ["\u0600 👍🏿ab cd", 8, true, false, true, "\u0600👍🏿ab cd"], + ["\u0600 👍🏿ab cd", 8, true, true, false, "\u0600 👍🏿ab cd"], + ["\u0600 👍🏿ab cd", 8, true, true, true, "\u0600👍🏿ab cd"], + ["\u001B[31m\u0600\u001B[39m 👍🏿ab cd", 7, false, false, false, "\u001B[31m\u0600\u001B[39m 👍🏿ab c\nd"], + ["\u001B[31m\u0600\u001B[39m 👍🏿ab cd", 7, false, false, true, "\u001B[31m\u0600\u001B[39m👍🏿ab cd"], + ["\u001B[31m\u0600\u001B[39m 👍🏿ab cd", 7, false, true, false, "\u001B[31m\u0600\u001B[39m 👍🏿ab \ncd"], + ["\u001B[31m\u0600\u001B[39m 👍🏿ab cd", 7, false, true, true, "\u001B[31m\u0600\u001B[39m👍🏿ab cd"], + ["\u001B[31m\u0600\u001B[39m 👍🏿ab cd", 7, true, false, false, "\u001B[31m\u0600\u001B[39m 👍🏿ab c\nd"], + ["\u001B[31m\u0600\u001B[39m 👍🏿ab cd", 7, true, false, true, "\u001B[31m\u0600\u001B[39m👍🏿ab cd"], + ["\u001B[31m\u0600\u001B[39m 👍🏿ab cd", 7, true, true, false, "\u001B[31m\u0600\u001B[39m 👍🏿ab \ncd"], + ["\u001B[31m\u0600\u001B[39m 👍🏿ab cd", 7, true, true, true, "\u001B[31m\u0600\u001B[39m👍🏿ab cd"], + [ + "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab cd", 7, false, false, false, - "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab c\nd", + "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab c\nd", ], [ - "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab cd", + "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab cd", 7, false, false, true, - "\u001B[31m\u0600\u001B[39m\u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab\ncd", + "\u001B[31m\u0600\u001B[39m\u001B[31m👍🏿\u001B[39mab cd", ], [ - "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab cd", + "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab cd", 7, false, true, false, - "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab \ncd", + "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab \ncd", ], [ - "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab cd", + "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab cd", 7, false, true, true, - "\u001B[31m\u0600\u001B[39m\u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab\ncd", + "\u001B[31m\u0600\u001B[39m\u001B[31m👍🏿\u001B[39mab cd", ], [ - "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab cd", + "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab cd", 7, true, false, false, - "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab c\nd", + "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab c\nd", ], [ - "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab cd", + "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab cd", 7, true, false, true, - "\u001B[31m\u0600\u001B[39m\u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab\ncd", + "\u001B[31m\u0600\u001B[39m\u001B[31m👍🏿\u001B[39mab cd", ], [ - "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab cd", + "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab cd", 7, true, true, false, - "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab \ncd", + "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab \ncd", ], [ - "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab cd", + "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab cd", 7, true, true, true, - "\u001B[31m\u0600\u001B[39m\u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab\ncd", + "\u001B[31m\u0600\u001B[39m\u001B[31m👍🏿\u001B[39mab cd", ], - // Escape-prefixed keycap word: SPACE + U+20E3 still fuses across the escape. - ["aa \u001B[31m\u20E3bb\u001B[39m cc", 7, false, false, false, "aa \u001B[31m\u20E3bb\u001B[39m \ncc"], - ["aa \u001B[31m\u20E3bb\u001B[39m cc", 7, false, false, true, "aa \u001B[31m\u20E3bb\u001B[39m\ncc"], + ["aa \u001B[31m\u20E3bb\u001B[39m cc", 7, false, false, false, "aa \u001B[31m\u20E3bb\u001B[39m c\nc"], + ["aa \u001B[31m\u20E3bb\u001B[39m cc", 7, false, false, true, "aa \u001B[31m\u20E3bb\u001B[39m c\nc"], ["aa \u001B[31m\u20E3bb\u001B[39m cc", 7, false, true, false, "aa \u001B[31m\u20E3bb\u001B[39m \ncc"], ["aa \u001B[31m\u20E3bb\u001B[39m cc", 7, false, true, true, "aa \u001B[31m\u20E3bb\u001B[39m\ncc"], - ["aa \u001B[31m\u20E3bb\u001B[39m cc", 7, true, false, false, "aa \u001B[31m\u20E3bb\u001B[39m \ncc"], - ["aa \u001B[31m\u20E3bb\u001B[39m cc", 7, true, false, true, "aa \u001B[31m\u20E3bb\u001B[39m\ncc"], + ["aa \u001B[31m\u20E3bb\u001B[39m cc", 7, true, false, false, "aa \u001B[31m\u20E3bb\u001B[39m c\nc"], + ["aa \u001B[31m\u20E3bb\u001B[39m cc", 7, true, false, true, "aa \u001B[31m\u20E3bb\u001B[39m c\nc"], ["aa \u001B[31m\u20E3bb\u001B[39m cc", 7, true, true, false, "aa \u001B[31m\u20E3bb\u001B[39m \ncc"], ["aa \u001B[31m\u20E3bb\u001B[39m cc", 7, true, true, true, "aa \u001B[31m\u20E3bb\u001B[39m\ncc"], - // At 9 columns the fused row fits exactly (real width 9); the stale additive - // width (10) would wrongly wrap it. ["aa \u001B[31m\u20E3bb\u001B[39m cc", 9, false, false, false, "aa \u001B[31m\u20E3bb\u001B[39m cc"], ["aa \u001B[31m\u20E3bb\u001B[39m cc", 9, false, false, true, "aa \u001B[31m\u20E3bb\u001B[39m cc"], ["aa \u001B[31m\u20E3bb\u001B[39m cc", 9, false, true, false, "aa \u001B[31m\u20E3bb\u001B[39m cc"], From e03f2432bc15add7f390ed2c67792d1755a3eb7b Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sun, 6 Sep 2026 09:00:15 +0000 Subject: [PATCH 2/6] Hard-wrap by grapheme cluster and keep a Prepend's successor in its cluster on the bulk paths wrapWord in wrapAnsi.cpp advanced one codepoint at a time with the per-codepoint width, so a flag (two wide regional indicators) was split across rows. It now gathers one cluster with the shared GraphemeState. The UTF-16 bulk kernels and the UTF-8 ASCII run seeded a new cluster with their first unit even when a pending Prepend joins it (GB9b). That unit now goes through the scalar path, so stringWidth, sliceAnsi and console.table agree on U+0600 followed by a keycap. The width-only UTF-8 walk counts ASCII runs with the SIMD kernel again. The wrapAnsi cluster-fusing table keeps its comments and escapes; only the expected column of the 44 changed rows differs from main. --- src/jsc/bindings/stringWidth.cpp | 63 ++++++--- src/jsc/bindings/wrapAnsi.cpp | 64 ++++++--- test/js/bun/util/stringWidth.test.ts | 18 +++ test/js/bun/util/wrapAnsi.test.ts | 203 +++++++++++++++++++-------- 4 files changed, 249 insertions(+), 99 deletions(-) diff --git a/src/jsc/bindings/stringWidth.cpp b/src/jsc/bindings/stringWidth.cpp index 618ce420f5e1..7ca535053f6f 100644 --- a/src/jsc/bindings/stringWidth.cpp +++ b/src/jsc/bindings/stringWidth.cpp @@ -407,6 +407,17 @@ struct ClusterWidthAccumulator { return startsCluster; } + // True when `cp` would join the pending cluster instead of starting a new + // one (a Prepend before it, GB9b). The bulk paths check their first unit + // with this before they count it as a cluster of its own. + bool joinsPendingCluster(char32_t cp) const + { + if (!hasPrevVisible) + return false; + GraphemeBreakState state = breakState; + return !graphemeBreakClasses(prevClass, graphemeBreakClassFromFused(classifyFromTable(cp)), state); + } + // Seed the cluster state from the last codepoint of a bulk-counted run, // flushing whatever cluster was pending before it. The codepoint's own // width is not added here: a combining mark, jamo or ZWJ right after the @@ -586,10 +597,11 @@ static const uint8_t* findEscapeIntroducerUTF8(const uint8_t* p, const uint8_t* } // Grapheme-cluster-aware walk of UTF-8 text with ANSI escape sequences -// treated as zero-width. Every cluster start is reported to `onCluster` -// (byte offset of the cluster, width of the completed clusters before it); -// a false return stops the walk. -template +// treated as zero-width. With `kReportClusters`, every cluster start is +// reported to `onCluster` (byte offset of the cluster, width of the completed +// clusters before it) and a false return stops the walk; without it, ASCII +// runs are counted with the SIMD kernel. +template static ClusterWidthAccumulator walkUTF8ExcludeANSI(std::span input, OnCluster onCluster) { ClusterWidthAccumulator accumulator { /* ambiguousAsWide */ false }; @@ -606,32 +618,42 @@ static ClusterWidthAccumulator walkUTF8ExcludeANSI(std::span inpu // the non-printable ones are width-0 controls, so all but the last // byte are counted in bulk. The last one seeds the cluster state: // a combining mark right after the run still joins it. - if (*p <= 0x7F) { + if (*p <= 0x7F && !accumulator.joinsPendingCluster(*p)) { const size_t asciiLen = highway_first_non_ascii8(p, static_cast(runEnd - p)); if (asciiLen > 1) { if (accumulator.graphemeState.count > 0) accumulator.len += accumulator.graphemeState.width(); accumulator.graphemeState = GraphemeState {}; - for (const uint8_t* q = p; q != p + asciiLen - 1; q++) { - if (!onCluster(static_cast(q - begin), accumulator.len)) - return accumulator; - accumulator.len += visibleLatin1WidthScalar(*q); + if constexpr (kReportClusters) { + for (const uint8_t* q = p; q != p + asciiLen - 1; q++) { + if (!onCluster(static_cast(q - begin), accumulator.len)) + return accumulator; + accumulator.len += visibleLatin1WidthScalar(*q); + } + } else { + accumulator.len += visibleLatin1Width({ p, asciiLen - 1 }); } p += asciiLen - 1; } const char32_t cp = *p; const uint8_t packed = classifyFromTable(cp); - if (!onCluster(static_cast(p - begin), accumulator.len + accumulator.graphemeState.width())) - return accumulator; + if constexpr (kReportClusters) { + if (!onCluster(static_cast(p - begin), accumulator.len + accumulator.graphemeState.width())) + return accumulator; + } accumulator.seedFromBulkRun(cp, packed); p++; continue; } const auto [cp, length] = decodeWTF8At(p, runEnd); - const size_t widthBefore = accumulator.finish(); - if (accumulator.addCodepoint(cp) && !onCluster(static_cast(p - begin), widthBefore)) - return accumulator; + if constexpr (kReportClusters) { + const size_t widthBefore = accumulator.finish(); + if (accumulator.addCodepoint(cp) && !onCluster(static_cast(p - begin), widthBefore)) + return accumulator; + } else { + accumulator.addCodepoint(cp); + } p += length; } @@ -645,7 +667,7 @@ static ClusterWidthAccumulator walkUTF8ExcludeANSI(std::span inpu size_t visibleUTF8WidthExcludeANSI(std::span input) { - return walkUTF8ExcludeANSI(input, [](size_t, size_t) { return true; }).finish(); + return walkUTF8ExcludeANSI(input, [](size_t, size_t) { return true; }).finish(); } size_t utf8IndexAtWidthExcludeANSI(std::span input, size_t maxWidth) @@ -655,7 +677,7 @@ size_t utf8IndexAtWidthExcludeANSI(std::span input, size_t maxWid // the input unless the final cluster overflows it. size_t stopAt = input.size(); size_t lastClusterStart = 0; - const auto accumulator = walkUTF8ExcludeANSI(input, [&](size_t offset, size_t widthBefore) { + const auto accumulator = walkUTF8ExcludeANSI(input, [&](size_t offset, size_t widthBefore) { if (widthBefore > maxWidth) { stopAt = lastClusterStart; return false; @@ -737,6 +759,15 @@ struct UTF16WidthAccumulator : ClusterWidthAccumulator { // Ambiguous); the first-unit check skips the call when the next // codepoint (surrogate pair, control) clearly needs the scalar // path anyway. + // A unit that joins the pending cluster (after a Prepend) goes + // through the scalar path: both bulk kernels below would count it + // as a cluster of its own. + if (!input.empty() && !U16_IS_SURROGATE(input[0]) && joinsPendingCluster(input[0])) { + addCodepoint(input[0]); + input = input.subspan(1); + continue; + } + if (!ambiguousAsWide && !input.empty() && input[0] >= 0x20 && !U16_IS_SURROGATE(input[0])) { size_t bulkWidth = 0; const size_t consumed = highway_visible_utf16_width( diff --git a/src/jsc/bindings/wrapAnsi.cpp b/src/jsc/bindings/wrapAnsi.cpp index 79387d5cb3cd..3dbe093a9f6b 100644 --- a/src/jsc/bindings/wrapAnsi.cpp +++ b/src/jsc/bindings/wrapAnsi.cpp @@ -1,6 +1,7 @@ #include "root.h" #include "wrapAnsi.h" #include "ANSIHelpers.h" +#include "stringWidth.h" #include #include @@ -11,25 +12,18 @@ // Native exports (implemented in stringWidth.cpp) for visible width calculation extern "C" size_t Bun__visibleWidthExcludeANSI_utf16(const uint16_t* ptr, size_t len, bool ambiguous_as_wide); extern "C" size_t Bun__visibleWidthExcludeANSI_latin1(const uint8_t* ptr, size_t len, bool ambiguous_as_wide); -extern "C" uint8_t Bun__codepointWidth(uint32_t cp, bool ambiguous_as_wide); extern "C" bool Bun__graphemeBreak(uint32_t cp1, uint32_t cp2, uint8_t* state); namespace Bun { using namespace WTF; -// UTF-16 decoding and codepoint width are in ANSIHelpers.h (shared with -// sliceAnsi.cpp). The local wrapper here just delegates to keep existing -// call sites unchanged. +// UTF-16 decoding is in ANSIHelpers.h (shared with sliceAnsi.cpp). The local +// wrapper here just delegates to keep existing call sites unchanged. static inline char32_t decodeUTF16(const UChar* ptr, size_t available, size_t& outLen) { return ANSI::decodeUTF16(ptr, available, outLen); } -static inline uint8_t getVisibleWidth(char32_t cp, bool ambiguousIsWide) -{ - return Bun__codepointWidth(cp, ambiguousIsWide); -} - // Options for wrapping struct WrapAnsiOptions { bool hard = false; @@ -212,10 +206,13 @@ class Row { // Word Wrapping Core Logic // ============================================================================ +// Hard-wraps one word that is wider than `columns`, one grapheme cluster at a +// time: a flag, a keycap or an emoji ZWJ sequence is never split across rows. template static void wrapWord(Vector>& rows, const Char* wordStart, const Char* wordEnd, size_t columns, const WrapAnsiOptions& options) { size_t vis = rows.last().width(options.ambiguousIsNarrow); + const bool ambiguousIsWide = !options.ambiguousIsNarrow; const Char* it = wordStart; while (it < wordEnd) { @@ -227,26 +224,47 @@ static void wrapWord(Vector>& rows, const Char* wordStart, const Char* continue; } - size_t charLen = 1; - char32_t cp; - if constexpr (sizeof(Char) == 1) { - // Latin1: each byte is one character, direct 1:1 mapping to U+0000-U+00FF - cp = static_cast(*it); - } else { - cp = decodeUTF16(it, wordEnd - it, charLen); + // Gather one grapheme cluster. An escape sequence between two + // codepoints of the cluster travels with it; escapes after the + // cluster's last codepoint are left to the loop above. + StringWidth::GraphemeState cluster; + uint8_t breakState = 0; + char32_t prevCp = 0; + const Char* clusterEnd = it; + for (const Char* p = it; p < wordEnd;) { + if (ANSI::isEscapeCharacter(*p)) { + p = ANSI::consumeANSI(p, wordEnd); + continue; + } + size_t charLen = 1; + char32_t cp; + if constexpr (sizeof(Char) == 1) { + cp = static_cast(*p); + } else { + cp = decodeUTF16(p, wordEnd - p, charLen); + } + if (clusterEnd == it) + cluster.reset(cp, StringWidth::fusedClassify(cp), ambiguousIsWide); + else if (Bun__graphemeBreak(prevCp, cp, &breakState)) + break; + else + cluster.add(cp, StringWidth::fusedClassify(cp), ambiguousIsWide); + prevCp = cp; + p += charLen; + clusterEnd = p; } - uint8_t charWidth = getVisibleWidth(cp, !options.ambiguousIsNarrow); + const size_t clusterWidth = cluster.width(); - if (vis + charWidth > columns) { - // Character doesn't fit on current line, start a new line + if (vis + clusterWidth > columns) { + // Cluster doesn't fit on current line, start a new line rows.append(Row()); vis = 0; } - rows.last().append(it, it + charLen); - vis += charWidth; - it += charLen; + rows.last().append(it, clusterEnd); + vis += clusterWidth; + it = clusterEnd; - if (vis == columns && it < wordEnd) { + if (vis >= columns && it < wordEnd) { rows.append(Row()); vis = 0; } diff --git a/test/js/bun/util/stringWidth.test.ts b/test/js/bun/util/stringWidth.test.ts index f3f9f7760925..af1cb503932a 100644 --- a/test/js/bun/util/stringWidth.test.ts +++ b/test/js/bun/util/stringWidth.test.ts @@ -1989,6 +1989,24 @@ describe("grapheme cluster width rules", () => { expect(Bun.stringWidth("\u00A9")).toBe(1); }); + test("a Prepend joins the codepoint after it on every path", () => { + // GB9b: U+0600 ARABIC NUMBER SIGN x anything. The digit that follows is + // not a cluster start, so it is not a keycap base, and the ASCII / bulk + // fast paths must not count it as a cluster of its own. + const cases = ["\u0600" + "1\u20E3", "\u06001", "\u0600a", "a\u0600" + "1\u20E3", "\u0600\u0661", "\u0600\u4E2D"]; + for (const s of cases) { + const width = Bun.stringWidth(s); + const top = Bun.inspect.table([{ s }], { colors: false }).split("\n")[0]; + const column = top.slice(top.lastIndexOf("\u252C") + 1, top.lastIndexOf("\u2510")); + expect({ s, width, slice: Bun.stringWidth(Bun.sliceAnsi(s, 0, width)), table: column.length }).toEqual({ + s, + width: s.startsWith("a") ? 2 : s.endsWith("\u4E2D") ? 2 : 1, + slice: width, + table: width + 2, + }); + } + }); + test("controls, CR and LF end a cluster on both sides", () => { expect(Bun.stringWidth("\n\u{1F3FB}")).toBe(2); expect(Bun.stringWidth("\r\n\u0301")).toBe(0); diff --git a/test/js/bun/util/wrapAnsi.test.ts b/test/js/bun/util/wrapAnsi.test.ts index 6d27e8e9a9f4..468ed55f7b48 100644 --- a/test/js/bun/util/wrapAnsi.test.ts +++ b/test/js/bun/util/wrapAnsi.test.ts @@ -100,11 +100,28 @@ describe("Bun.wrapAnsi", () => { expect(Bun.wrapAnsi(input, columns, { hard: true })).toBe(expected); }); - test("family emoji hard-wrapped at columns=1 keeps every ZWJ", () => { + test("family emoji hard-wrapped at columns=1 stays one cluster", () => { + // A hard wrap advances by grapheme cluster: the ZWJ sequence is one + // 2-column cluster that does not fit in 1 column, so it moves to its + // own row whole instead of being cut between codepoints. const fam = "\u{1F469}\u200D\u{1F469}\u200D\u{1F467}\u200D\u{1F466}"; - expect(Bun.wrapAnsi(fam, 1, { hard: true })).toBe( - "\n\u{1F469}\n\u200D\n\u{1F469}\n\u200D\n\u{1F467}\n\u200D\n\u{1F466}", + expect(Bun.wrapAnsi(fam, 1, { hard: true })).toBe("\n" + fam); + }); + + test("hard wrap never splits a flag, keycap or modifier sequence", () => { + expect(Bun.wrapAnsi("\u{1F1FA}\u{1F1F8}abc", 2, { hard: true })).toBe("\u{1F1FA}\u{1F1F8}\nab\nc"); + expect(Bun.wrapAnsi("ab\u{1F1FA}\u{1F1F8}cd", 3, { hard: true })).toBe("ab\n\u{1F1FA}\u{1F1F8}c\nd"); + expect(Bun.wrapAnsi("x1\uFE0F\u20E3y", 2, { hard: true })).toBe("x\n1\uFE0F\u20E3\ny"); + expect(Bun.wrapAnsi("\u{1F44B}\u{1F3FD}\u{1F44B}\u{1F3FD}", 2, { hard: true })).toBe( + "\u{1F44B}\u{1F3FD}\n\u{1F44B}\u{1F3FD}", ); + // An SGR sequence inside the cluster travels with it (the style is + // reopened on the next row as for any wrapped word). + expect(Bun.wrapAnsi("a\x1b[31m\u0301\x1b[39mb", 1, { hard: true })).toBe( + "a\x1b[31m\u0301\x1b[39m\n\x1b[31m\x1b[39mb", + ); + // Each lone regional indicator is a 2-column emoji. + expect(Bun.wrapAnsi("\u{1F1E6}\u{1F1E7}\u{1F1E8}", 2, { hard: true })).toBe("\u{1F1E6}\u{1F1E7}\n\u{1F1E8}"); }); }); @@ -640,110 +657,174 @@ describe("Bun.wrapAnsi", () => { true, "\u001B[31maa\u001B[39m \u20E3bb \u001B[31mcc\u001B[39m", ], - ["aa\tz ️bb cc", 8, false, false, false, "aa\tz ️bb c\nc"], - ["aa\tz ️bb cc", 8, false, false, true, "aa\tz ️bb c\nc"], - ["aa\tz ️bb cc", 8, false, true, false, "aa\tz ️bb \ncc"], - ["aa\tz ️bb cc", 8, false, true, true, "aa\tz ️bb\ncc"], - ["aa\tz ️bb cc", 8, true, false, false, "aa\tz ️bb c\nc"], - ["aa\tz ️bb cc", 8, true, false, true, "aa\tz ️bb c\nc"], - ["aa\tz ️bb cc", 8, true, true, false, "aa\tz ️bb \ncc"], - ["aa\tz ️bb cc", 8, true, true, true, "aa\tz ️bb\ncc"], - ["aa ́bb cc", 8, false, false, false, "aa ́bb cc"], - ["aa ́bb cc", 8, false, false, true, "aa ́bb cc"], - ["aa ́bb cc", 8, false, true, false, "aa ́bb cc"], - ["aa ́bb cc", 8, false, true, true, "aa ́bb cc"], - ["aa ́bb cc", 8, true, false, false, "aa ́bb cc"], - ["aa ́bb cc", 8, true, false, true, "aa ́bb cc"], - ["aa ́bb cc", 8, true, true, false, "aa ́bb cc"], - ["aa ́bb cc", 8, true, true, true, "aa ́bb cc"], - ["\u0600 👍🏿ab cd", 7, false, false, false, "\u0600 👍🏿ab c\nd"], - ["\u0600 👍🏿ab cd", 7, false, false, true, "\u0600👍🏿ab cd"], - ["\u0600 👍🏿ab cd", 7, false, true, false, "\u0600 👍🏿ab \ncd"], - ["\u0600 👍🏿ab cd", 7, false, true, true, "\u0600👍🏿ab cd"], - ["\u0600 👍🏿ab cd", 7, true, false, false, "\u0600 👍🏿ab c\nd"], - ["\u0600 👍🏿ab cd", 7, true, false, true, "\u0600👍🏿ab cd"], - ["\u0600 👍🏿ab cd", 7, true, true, false, "\u0600 👍🏿ab \ncd"], - ["\u0600 👍🏿ab cd", 7, true, true, true, "\u0600👍🏿ab cd"], - ["\u0600 👍🏿ab cd", 8, false, false, false, "\u0600 👍🏿ab cd"], - ["\u0600 👍🏿ab cd", 8, false, false, true, "\u0600👍🏿ab cd"], - ["\u0600 👍🏿ab cd", 8, false, true, false, "\u0600 👍🏿ab cd"], - ["\u0600 👍🏿ab cd", 8, false, true, true, "\u0600👍🏿ab cd"], - ["\u0600 👍🏿ab cd", 8, true, false, false, "\u0600 👍🏿ab cd"], - ["\u0600 👍🏿ab cd", 8, true, false, true, "\u0600👍🏿ab cd"], - ["\u0600 👍🏿ab cd", 8, true, true, false, "\u0600 👍🏿ab cd"], - ["\u0600 👍🏿ab cd", 8, true, true, true, "\u0600👍🏿ab cd"], - ["\u001B[31m\u0600\u001B[39m 👍🏿ab cd", 7, false, false, false, "\u001B[31m\u0600\u001B[39m 👍🏿ab c\nd"], - ["\u001B[31m\u0600\u001B[39m 👍🏿ab cd", 7, false, false, true, "\u001B[31m\u0600\u001B[39m👍🏿ab cd"], - ["\u001B[31m\u0600\u001B[39m 👍🏿ab cd", 7, false, true, false, "\u001B[31m\u0600\u001B[39m 👍🏿ab \ncd"], - ["\u001B[31m\u0600\u001B[39m 👍🏿ab cd", 7, false, true, true, "\u001B[31m\u0600\u001B[39m👍🏿ab cd"], - ["\u001B[31m\u0600\u001B[39m 👍🏿ab cd", 7, true, false, false, "\u001B[31m\u0600\u001B[39m 👍🏿ab c\nd"], - ["\u001B[31m\u0600\u001B[39m 👍🏿ab cd", 7, true, false, true, "\u001B[31m\u0600\u001B[39m👍🏿ab cd"], - ["\u001B[31m\u0600\u001B[39m 👍🏿ab cd", 7, true, true, false, "\u001B[31m\u0600\u001B[39m 👍🏿ab \ncd"], - ["\u001B[31m\u0600\u001B[39m 👍🏿ab cd", 7, true, true, true, "\u001B[31m\u0600\u001B[39m👍🏿ab cd"], - [ - "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab cd", + ["aa\tz \uFE0Fbb cc", 8, false, false, false, "aa\tz \uFE0Fbb c\nc"], + ["aa\tz \uFE0Fbb cc", 8, false, false, true, "aa\tz \uFE0Fbb c\nc"], + ["aa\tz \uFE0Fbb cc", 8, false, true, false, "aa\tz \uFE0Fbb \ncc"], + ["aa\tz \uFE0Fbb cc", 8, false, true, true, "aa\tz \uFE0Fbb\ncc"], + ["aa\tz \uFE0Fbb cc", 8, true, false, false, "aa\tz \uFE0Fbb c\nc"], + ["aa\tz \uFE0Fbb cc", 8, true, false, true, "aa\tz \uFE0Fbb c\nc"], + ["aa\tz \uFE0Fbb cc", 8, true, true, false, "aa\tz \uFE0Fbb \ncc"], + ["aa\tz \uFE0Fbb cc", 8, true, true, true, "aa\tz \uFE0Fbb\ncc"], + ["aa \u0301bb cc", 8, false, false, false, "aa \u0301bb cc"], + ["aa \u0301bb cc", 8, false, false, true, "aa \u0301bb cc"], + ["aa \u0301bb cc", 8, false, true, false, "aa \u0301bb cc"], + ["aa \u0301bb cc", 8, false, true, true, "aa \u0301bb cc"], + ["aa \u0301bb cc", 8, true, false, false, "aa \u0301bb cc"], + ["aa \u0301bb cc", 8, true, false, true, "aa \u0301bb cc"], + ["aa \u0301bb cc", 8, true, true, false, "aa \u0301bb cc"], + ["aa \u0301bb cc", 8, true, true, true, "aa \u0301bb cc"], + // U+0600 (Prepend) is a width-0 first word; with trim the next word is + // appended with no separator space and fuses with it across the rows. + ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 7, false, false, false, "\u0600 \u{1F44D}\u{1F3FF}ab c\nd"], + ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 7, false, false, true, "\u0600\u{1F44D}\u{1F3FF}ab cd"], + ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 7, false, true, false, "\u0600 \u{1F44D}\u{1F3FF}ab \ncd"], + ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 7, false, true, true, "\u0600\u{1F44D}\u{1F3FF}ab cd"], + ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 7, true, false, false, "\u0600 \u{1F44D}\u{1F3FF}ab c\nd"], + ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 7, true, false, true, "\u0600\u{1F44D}\u{1F3FF}ab cd"], + ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 7, true, true, false, "\u0600 \u{1F44D}\u{1F3FF}ab \ncd"], + ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 7, true, true, true, "\u0600\u{1F44D}\u{1F3FF}ab cd"], + ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 8, false, false, false, "\u0600 \u{1F44D}\u{1F3FF}ab cd"], + ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 8, false, false, true, "\u0600\u{1F44D}\u{1F3FF}ab cd"], + ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 8, false, true, false, "\u0600 \u{1F44D}\u{1F3FF}ab cd"], + ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 8, false, true, true, "\u0600\u{1F44D}\u{1F3FF}ab cd"], + ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 8, true, false, false, "\u0600 \u{1F44D}\u{1F3FF}ab cd"], + ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 8, true, false, true, "\u0600\u{1F44D}\u{1F3FF}ab cd"], + ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 8, true, true, false, "\u0600 \u{1F44D}\u{1F3FF}ab cd"], + ["\u0600 \u{1F44D}\u{1F3FF}ab cd", 8, true, true, true, "\u0600\u{1F44D}\u{1F3FF}ab cd"], + // Same no-space seam with the Prepend hidden behind a trailing escape: + // the cluster still fuses across the escape sequence. + [ + "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab cd", + 7, + false, + false, + false, + "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab c\nd", + ], + [ + "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab cd", + 7, + false, + false, + true, + "\u001B[31m\u0600\u001B[39m\u{1F44D}\u{1F3FF}ab cd", + ], + [ + "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab cd", + 7, + false, + true, + false, + "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab \ncd", + ], + [ + "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab cd", + 7, + false, + true, + true, + "\u001B[31m\u0600\u001B[39m\u{1F44D}\u{1F3FF}ab cd", + ], + [ + "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab cd", + 7, + true, + false, + false, + "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab c\nd", + ], + [ + "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab cd", + 7, + true, + false, + true, + "\u001B[31m\u0600\u001B[39m\u{1F44D}\u{1F3FF}ab cd", + ], + [ + "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab cd", + 7, + true, + true, + false, + "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab \ncd", + ], + [ + "\u001B[31m\u0600\u001B[39m \u{1F44D}\u{1F3FF}ab cd", + 7, + true, + true, + true, + "\u001B[31m\u0600\u001B[39m\u{1F44D}\u{1F3FF}ab cd", + ], + // ANSI-prefixed words: an SGR sequence (ESC is ASCII) at the start of a word + // must not hide the cluster-fusing codepoint that actually lands on the seam. + // Escape-wrapped emoji+modifier word after an escape-wrapped width-0 Prepend row. + [ + "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab cd", 7, false, false, false, - "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab c\nd", + "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab c\nd", ], [ - "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab cd", + "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab cd", 7, false, false, true, - "\u001B[31m\u0600\u001B[39m\u001B[31m👍🏿\u001B[39mab cd", + "\u001B[31m\u0600\u001B[39m\u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab cd", ], [ - "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab cd", + "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab cd", 7, false, true, false, - "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab \ncd", + "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab \ncd", ], [ - "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab cd", + "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab cd", 7, false, true, true, - "\u001B[31m\u0600\u001B[39m\u001B[31m👍🏿\u001B[39mab cd", + "\u001B[31m\u0600\u001B[39m\u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab cd", ], [ - "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab cd", + "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab cd", 7, true, false, false, - "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab c\nd", + "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab c\nd", ], [ - "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab cd", + "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab cd", 7, true, false, true, - "\u001B[31m\u0600\u001B[39m\u001B[31m👍🏿\u001B[39mab cd", + "\u001B[31m\u0600\u001B[39m\u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab cd", ], [ - "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab cd", + "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab cd", 7, true, true, false, - "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab \ncd", + "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab \ncd", ], [ - "\u001B[31m\u0600\u001B[39m \u001B[31m👍🏿\u001B[39mab cd", + "\u001B[31m\u0600\u001B[39m \u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab cd", 7, true, true, true, - "\u001B[31m\u0600\u001B[39m\u001B[31m👍🏿\u001B[39mab cd", + "\u001B[31m\u0600\u001B[39m\u001B[31m\u{1F44D}\u{1F3FF}\u001B[39mab cd", ], + // Escape-prefixed keycap word: SPACE + U+20E3 still fuses across the escape. ["aa \u001B[31m\u20E3bb\u001B[39m cc", 7, false, false, false, "aa \u001B[31m\u20E3bb\u001B[39m c\nc"], ["aa \u001B[31m\u20E3bb\u001B[39m cc", 7, false, false, true, "aa \u001B[31m\u20E3bb\u001B[39m c\nc"], ["aa \u001B[31m\u20E3bb\u001B[39m cc", 7, false, true, false, "aa \u001B[31m\u20E3bb\u001B[39m \ncc"], @@ -752,6 +833,8 @@ describe("Bun.wrapAnsi", () => { ["aa \u001B[31m\u20E3bb\u001B[39m cc", 7, true, false, true, "aa \u001B[31m\u20E3bb\u001B[39m c\nc"], ["aa \u001B[31m\u20E3bb\u001B[39m cc", 7, true, true, false, "aa \u001B[31m\u20E3bb\u001B[39m \ncc"], ["aa \u001B[31m\u20E3bb\u001B[39m cc", 7, true, true, true, "aa \u001B[31m\u20E3bb\u001B[39m\ncc"], + // At 9 columns the fused row fits exactly (real width 9); the stale additive + // width (10) would wrongly wrap it. ["aa \u001B[31m\u20E3bb\u001B[39m cc", 9, false, false, false, "aa \u001B[31m\u20E3bb\u001B[39m cc"], ["aa \u001B[31m\u20E3bb\u001B[39m cc", 9, false, false, true, "aa \u001B[31m\u20E3bb\u001B[39m cc"], ["aa \u001B[31m\u20E3bb\u001B[39m cc", 9, false, true, false, "aa \u001B[31m\u20E3bb\u001B[39m cc"], From 02d63eb8edfa102aa826e7754c17cea64d0a6d27 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sun, 6 Sep 2026 09:02:35 +0000 Subject: [PATCH 3/6] Shorten the comments in the width code --- src/jsc/bindings/sliceAnsi.cpp | 7 ++-- src/jsc/bindings/stringWidth.cpp | 61 +++++++++----------------------- src/jsc/bindings/stringWidth.h | 44 ++++++----------------- src/jsc/bindings/wrapAnsi.cpp | 10 ++---- 4 files changed, 32 insertions(+), 90 deletions(-) diff --git a/src/jsc/bindings/sliceAnsi.cpp b/src/jsc/bindings/sliceAnsi.cpp index 6288473a748a..b542056ee4a8 100644 --- a/src/jsc/bindings/sliceAnsi.cpp +++ b/src/jsc/bindings/sliceAnsi.cpp @@ -16,11 +16,8 @@ extern "C" size_t Bun__visibleWidthExcludeANSI_utf16(const uint16_t* ptr, size_t namespace Bun { using namespace WTF; -// Shared SIMD/SGR helpers live in ANSIHelpers.h. Cluster widths come from -// StringWidth::GraphemeState (stringWidth.h), the same accumulator -// Bun.stringWidth uses, so a slice is always measured the way the whole -// string is. - +// Shared SIMD/SGR helpers live in ANSIHelpers.h; cluster widths come from +// the GraphemeState that Bun.stringWidth uses. struct GraphemeWidthState : StringWidth::GraphemeState { void reset(uint32_t cp, bool ambiguousIsWide) { diff --git a/src/jsc/bindings/stringWidth.cpp b/src/jsc/bindings/stringWidth.cpp index 7ca535053f6f..ad0852f051a6 100644 --- a/src/jsc/bindings/stringWidth.cpp +++ b/src/jsc/bindings/stringWidth.cpp @@ -135,9 +135,7 @@ static constexpr bool isIndicConjunctBreakExtend(GraphemeBreakClass gb) return gb == GraphemeBreakClass::IndicConjunctBreakExtend || gb == GraphemeBreakClass::Zwj; } -// Grapheme_Cluster_Break=Extend: the emoji modifiers are Extend too (GB9 -// attaches them to any base; GB11 lets them sit between an Extended -// Pictographic and its ZWJ). +// Grapheme_Cluster_Break=Extend, which includes the emoji modifiers. static constexpr bool isExtend(GraphemeBreakClass gb) { return gb == GraphemeBreakClass::Zwnj @@ -151,16 +149,14 @@ static constexpr bool isExtendedPictographic(GraphemeBreakClass gb) return gb == GraphemeBreakClass::ExtendedPictographic || gb == GraphemeBreakClass::EmojiModifierBase; } -// Core grapheme break algorithm (ported from uucode's -// computeGraphemeBreakNoControl, plus GB4/GB5 for the Control class). Only +// Core grapheme break algorithm (ported from uucode, plus GB4/GB5). Only // evaluated at compile time to build the precomputed decision table below. static constexpr bool computeGraphemeBreak(GraphemeBreakClass gb1, GraphemeBreakClass gb2, GraphemeBreakState& state) { using G = GraphemeBreakClass; using S = GraphemeBreakState; - // GB4, GB5: a control, CR or LF breaks on both sides. (CR x LF is not - // needed here: a CR LF pair is zero-width either way.) + // GB4, GB5: a control breaks on both sides (CR x LF is zero-width anyway). if (gb1 == G::Control || gb2 == G::Control) { state = S::Default; return true; @@ -364,18 +360,12 @@ bool graphemeBreak(char32_t cp1, char32_t cp2, uint8_t& state) // Grapheme cluster width accumulator // ============================================================================ -// Feeds decoded codepoints through the grapheme break algorithm and sums the -// width of each completed cluster (GraphemeState decides a cluster's width). -// Encoding-specific walkers own the decode and the escape-sequence handling; -// state is carried across escape sequences so a combining mark that follows -// an SGR code still joins the cluster before it. +// Sums the width of each completed grapheme cluster. The encoding-specific +// walkers decode and skip escape sequences; state carries across an escape. struct ClusterWidthAccumulator { size_t len = 0; // width of the completed clusters GraphemeState graphemeState; // the pending cluster - // Break class of the last *visible* codepoint, used for grapheme break - // decisions (carried so each codepoint is classified only once). Escape - // sequence bytes must not participate: a CSI final byte like 'm' would - // otherwise wrongly join to a following combining mark. + // Break class of the last visible codepoint (escape bytes never take part). GraphemeBreakClass prevClass = GraphemeBreakClass::Other; GraphemeBreakState breakState = GraphemeBreakState::Default; bool hasPrevVisible = false; @@ -386,8 +376,7 @@ struct ClusterWidthAccumulator { { } - // Returns true when `cp` starts a new cluster (the previous one, if any, - // has been added to `len`). + // Returns true when `cp` starts a new cluster. bool addCodepoint(char32_t cp) { const uint8_t packed = classifyFromTable(cp); @@ -407,9 +396,7 @@ struct ClusterWidthAccumulator { return startsCluster; } - // True when `cp` would join the pending cluster instead of starting a new - // one (a Prepend before it, GB9b). The bulk paths check their first unit - // with this before they count it as a cluster of its own. + // True when `cp` joins the pending cluster (GB9b, a Prepend before it). bool joinsPendingCluster(char32_t cp) const { if (!hasPrevVisible) @@ -418,11 +405,8 @@ struct ClusterWidthAccumulator { return !graphemeBreakClasses(prevClass, graphemeBreakClassFromFused(classifyFromTable(cp)), state); } - // Seed the cluster state from the last codepoint of a bulk-counted run, - // flushing whatever cluster was pending before it. The codepoint's own - // width is not added here: a combining mark, jamo or ZWJ right after the - // run still joins its cluster, so the caller counts every unit but the - // last and lets GraphemeState::width() settle the final one. + // Seed the cluster state from the last codepoint of a bulk-counted run. + // The caller counts every unit but that one; its cluster settles here. void seedFromBulkRun(char32_t cp, uint8_t packed) { if (graphemeState.count > 0) @@ -565,9 +549,7 @@ static char32_t decodeWTF8RuneMultibyte(const std::array& p, uint8_t return cp; } -// Decode the WTF-8 codepoint at `p` (p < end). Returns the codepoint and -// the number of bytes it occupies (a truncated or invalid sequence decodes -// to U+FFFD). +// Decodes the WTF-8 codepoint at `p` (p < end): codepoint and byte length. static std::pair decodeWTF8At(const uint8_t* p, const uint8_t* end) { const uint8_t byte = *p; @@ -596,11 +578,9 @@ static const uint8_t* findEscapeIntroducerUTF8(const uint8_t* p, const uint8_t* } } -// Grapheme-cluster-aware walk of UTF-8 text with ANSI escape sequences -// treated as zero-width. With `kReportClusters`, every cluster start is -// reported to `onCluster` (byte offset of the cluster, width of the completed -// clusters before it) and a false return stops the walk; without it, ASCII -// runs are counted with the SIMD kernel. +// Cluster-aware walk of UTF-8 text, escape sequences zero-width. With +// `kReportClusters`, `onCluster(byteOffset, widthBefore)` sees every cluster +// start and may return false to stop. template static ClusterWidthAccumulator walkUTF8ExcludeANSI(std::span input, OnCluster onCluster) { @@ -614,10 +594,8 @@ static ClusterWidthAccumulator walkUTF8ExcludeANSI(std::span inpu const uint8_t* const runEnd = esc ? esc : end; while (p != runEnd) { - // ASCII runs: every printable byte is its own width-1 cluster and - // the non-printable ones are width-0 controls, so all but the last - // byte are counted in bulk. The last one seeds the cluster state: - // a combining mark right after the run still joins it. + // ASCII bytes never join each other: count all but the last in + // bulk, and let the last one seed the cluster state. if (*p <= 0x7F && !accumulator.joinsPendingCluster(*p)) { const size_t asciiLen = highway_first_non_ascii8(p, static_cast(runEnd - p)); if (asciiLen > 1) { @@ -672,9 +650,6 @@ size_t visibleUTF8WidthExcludeANSI(std::span input) size_t utf8IndexAtWidthExcludeANSI(std::span input, size_t maxWidth) { - // The prefix ends at the first cluster whose predecessors already fill - // `maxWidth`, or, when every cluster starts within budget, at the end of - // the input unless the final cluster overflows it. size_t stopAt = input.size(); size_t lastClusterStart = 0; const auto accumulator = walkUTF8ExcludeANSI(input, [&](size_t offset, size_t widthBefore) { @@ -759,9 +734,7 @@ struct UTF16WidthAccumulator : ClusterWidthAccumulator { // Ambiguous); the first-unit check skips the call when the next // codepoint (surrogate pair, control) clearly needs the scalar // path anyway. - // A unit that joins the pending cluster (after a Prepend) goes - // through the scalar path: both bulk kernels below would count it - // as a cluster of its own. + // The bulk kernels count a unit after a Prepend as its own cluster. if (!input.empty() && !U16_IS_SURROGATE(input[0]) && joinsPendingCluster(input[0])) { addCodepoint(input[0]); input = input.subspan(1); diff --git a/src/jsc/bindings/stringWidth.h b/src/jsc/bindings/stringWidth.h index 3d1f9b37836c..b6ca3d646c8b 100644 --- a/src/jsc/bindings/stringWidth.h +++ b/src/jsc/bindings/stringWidth.h @@ -11,23 +11,16 @@ JSC_DECLARE_HOST_FUNCTION(jsFunctionBunStringWidth); namespace StringWidth { -// ============================================================================ -// Codepoint classification (grapheme break class + width + emoji, one lookup) -// ============================================================================ - -// Each codepoint maps to one packed byte (stringWidthTables.h, regenerate -// with scripts/generate-stringwidth-tables.mjs): -// bits 0-4 GraphemeBreakClass ordinal -// bits 5-6 width class: 0 zero-width, 1 narrow, 2 wide, 3 East Asian Ambiguous -// bit 7 the Emoji property, minus the keycap bases [0-9#*] +// Packed classification byte per codepoint (stringWidthTables.h, generated by +// scripts/generate-stringwidth-tables.mjs): bits 0-4 GraphemeBreakClass, +// bits 5-6 width class (0 zero, 1 narrow, 2 wide, 3 ambiguous), bit 7 Emoji. static constexpr uint8_t kFusedClassMask = 0x1F; static constexpr uint8_t kFusedWidthShift = 5; static constexpr uint8_t kFusedWidthMask = 0x3; static constexpr uint8_t kFusedWidthAmbiguous = 3; static constexpr uint8_t kFusedEmojiBit = 0x80; -// Grapheme_Cluster_Break refined with the emoji and Indic Conjunct Break -// properties. Ordinal values must match the generator. +// Ordinal values must match the generator. enum class GraphemeBreakClass : uint8_t { Other, Prepend, @@ -75,17 +68,8 @@ uint8_t visibleCodepointWidth(char32_t cp, bool ambiguousAsWide); // zero-initialized and carried between sequential calls. bool graphemeBreak(char32_t cp1, char32_t cp2, uint8_t& state); -// ============================================================================ -// Grapheme cluster width -// ============================================================================ - -// Accumulates the codepoints of one grapheme cluster and decides the -// cluster's terminal width. The base codepoint sets the width; extending -// codepoints add theirs (zero for marks and format characters, one for the -// halfwidth katakana voiced sound marks, ...) except the emoji modifiers, -// which never widen a cluster. The emoji sequences override the sum: a -// regional indicator pair (flag), a keycap, an emoji with a skin tone or a -// ZWJ, and the VS15/VS16 presentation selectors. +// Terminal width of one grapheme cluster: the sum of its codepoint widths +// (emoji modifiers add nothing), overridden by the emoji sequence rules. struct GraphemeState { uint16_t summedWidth = 0; // saturates at 1023 uint8_t baseWidth = 0; // width of the first codepoint (0, 1 or 2) @@ -145,9 +129,7 @@ struct GraphemeState { // Emoji modifier sequence or emoji ZWJ sequence if (emojiBase && (skinTone || zwj)) return 2; - // VS16 widens a base with the Emoji property to emoji presentation. - // Zero-width and narrow non-emoji bases keep their own width under - // VS15/VS16. + // VS16 widens an Emoji base; other bases keep their own width. if (vs15 || vs16) { if (baseWidth == 2 || (vs16 && emojiBase)) return 2; @@ -157,10 +139,6 @@ struct GraphemeState { } }; -// ============================================================================ -// String width -// ============================================================================ - // Visible width of Latin-1 text, counting ANSI escape sequences as visible. size_t visibleLatin1Width(std::span input); @@ -176,13 +154,11 @@ size_t visibleLatin1WidthExcludeANSIAmbiguousAsWide(std::span inp size_t visibleUTF16Width(std::span input, bool excludeAnsiColors, bool ambiguousAsWide); // Visible width of UTF-8 text (grapheme-cluster aware), treating ANSI escape -// sequences as zero-width. Used for console.table column sizing and the -// markdown ANSI renderer. +// sequences as zero-width. size_t visibleUTF8WidthExcludeANSI(std::span input); -// Byte index of the longest prefix of UTF-8 `input` whose visible width is -// <= `maxWidth`. ANSI escapes are zero-width and always included. Never -// splits a grapheme cluster or a multi-byte codepoint. +// Byte index of the longest prefix of whole clusters of UTF-8 `input` whose +// visible width is <= `maxWidth`. ANSI escapes are zero-width and included. size_t utf8IndexAtWidthExcludeANSI(std::span input, size_t maxWidth); } // namespace StringWidth diff --git a/src/jsc/bindings/wrapAnsi.cpp b/src/jsc/bindings/wrapAnsi.cpp index 3dbe093a9f6b..51ff4a05ae1a 100644 --- a/src/jsc/bindings/wrapAnsi.cpp +++ b/src/jsc/bindings/wrapAnsi.cpp @@ -17,8 +17,7 @@ extern "C" bool Bun__graphemeBreak(uint32_t cp1, uint32_t cp2, uint8_t* state); namespace Bun { using namespace WTF; -// UTF-16 decoding is in ANSIHelpers.h (shared with sliceAnsi.cpp). The local -// wrapper here just delegates to keep existing call sites unchanged. +// UTF-16 decoding is in ANSIHelpers.h (shared with sliceAnsi.cpp). static inline char32_t decodeUTF16(const UChar* ptr, size_t available, size_t& outLen) { return ANSI::decodeUTF16(ptr, available, outLen); @@ -206,8 +205,7 @@ class Row { // Word Wrapping Core Logic // ============================================================================ -// Hard-wraps one word that is wider than `columns`, one grapheme cluster at a -// time: a flag, a keycap or an emoji ZWJ sequence is never split across rows. +// Hard-wraps one word wider than `columns`, one grapheme cluster at a time. template static void wrapWord(Vector>& rows, const Char* wordStart, const Char* wordEnd, size_t columns, const WrapAnsiOptions& options) { @@ -224,9 +222,7 @@ static void wrapWord(Vector>& rows, const Char* wordStart, const Char* continue; } - // Gather one grapheme cluster. An escape sequence between two - // codepoints of the cluster travels with it; escapes after the - // cluster's last codepoint are left to the loop above. + // Gather one cluster; escapes inside it travel with it. StringWidth::GraphemeState cluster; uint8_t breakState = 0; char32_t prevCp = 0; From 853c840e5c6f5fd18d0137095198644b250b8225 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sun, 6 Sep 2026 09:37:21 +0000 Subject: [PATCH 4/6] Keep the exact-fill row break in wrapWord A cluster wider than the row already starts a new row through the check before it is placed. The check after it must only fire on an exact fill, or two over-wide clusters in a row get a blank row between them. --- src/jsc/bindings/wrapAnsi.cpp | 2 +- test/js/bun/util/wrapAnsi.test.ts | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/jsc/bindings/wrapAnsi.cpp b/src/jsc/bindings/wrapAnsi.cpp index 51ff4a05ae1a..69972326407d 100644 --- a/src/jsc/bindings/wrapAnsi.cpp +++ b/src/jsc/bindings/wrapAnsi.cpp @@ -260,7 +260,7 @@ static void wrapWord(Vector>& rows, const Char* wordStart, const Char* vis += clusterWidth; it = clusterEnd; - if (vis >= columns && it < wordEnd) { + if (vis == columns && it < wordEnd) { rows.append(Row()); vis = 0; } diff --git a/test/js/bun/util/wrapAnsi.test.ts b/test/js/bun/util/wrapAnsi.test.ts index 468ed55f7b48..b62733850c6c 100644 --- a/test/js/bun/util/wrapAnsi.test.ts +++ b/test/js/bun/util/wrapAnsi.test.ts @@ -122,6 +122,13 @@ describe("Bun.wrapAnsi", () => { ); // Each lone regional indicator is a 2-column emoji. expect(Bun.wrapAnsi("\u{1F1E6}\u{1F1E7}\u{1F1E8}", 2, { hard: true })).toBe("\u{1F1E6}\u{1F1E7}\n\u{1F1E8}"); + // Consecutive clusters wider than the row get one row each, no blank + // row between them (same shape as two wide CJK characters). + expect(Bun.wrapAnsi("\u{1F600}\u{1F600}", 1, { hard: true })).toBe("\n\u{1F600}\n\u{1F600}"); + expect(Bun.wrapAnsi("\u4E2D\u4E2D", 1, { hard: true })).toBe("\n\u4E2D\n\u4E2D"); + expect(Bun.wrapAnsi("\u{1F1FA}\u{1F1F8}\u{1F1FA}\u{1F1F8}", 1, { hard: true })).toBe( + "\n\u{1F1FA}\u{1F1F8}\n\u{1F1FA}\u{1F1F8}", + ); }); }); From 0b11145833e694005e85e6cbe69aefd06bcd45bb Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sun, 6 Sep 2026 11:00:40 +0000 Subject: [PATCH 5/6] Keep a lone regional indicator at width 1 A single regional indicator stays narrow, as string-width reports it and as the existing tests pinned it. Only the pair (a flag) is wide, and a combining mark after a lone one no longer changes that. --- scripts/generate-stringwidth-tables.mjs | 13 +++++-------- src/jsc/bindings/stringWidth.cpp | 2 +- src/jsc/bindings/stringWidth.h | 2 +- src/jsc/bindings/stringWidthTables.h | 4 ++-- test/js/bun/util/sliceAnsi.test.ts | 7 +++---- test/js/bun/util/stringWidth.test.ts | 20 +++++++++++--------- test/js/bun/util/wrapAnsi.test.ts | 3 ++- 7 files changed, 25 insertions(+), 26 deletions(-) diff --git a/scripts/generate-stringwidth-tables.mjs b/scripts/generate-stringwidth-tables.mjs index 503cfe11068f..1f87ad48d8f0 100644 --- a/scripts/generate-stringwidth-tables.mjs +++ b/scripts/generate-stringwidth-tables.mjs @@ -12,8 +12,8 @@ // extracted/DerivedGeneralCategory.txt zero-width categories // DerivedCoreProperties.txt Default_Ignorable_Code_Point, InCB // auxiliary/GraphemeBreakProperty.txt Grapheme_Cluster_Break -// emoji/emoji-data.txt Emoji, Emoji_Presentation, -// Emoji_Modifier(_Base), Extended_Pictographic +// emoji/emoji-data.txt Emoji, Emoji_Modifier(_Base), +// Extended_Pictographic // // Usage: bun scripts/generate-stringwidth-tables.mjs [--ucd ] // Rewrites src/jsc/bindings/stringWidthTables.h in place. Downloads @@ -120,7 +120,6 @@ const gcb = { const emojiProperty = name => parseUCDRanges(emojiDataText, p => p === name); const emojiRanges = emojiProperty("Emoji"); -const emojiPresentationRanges = emojiProperty("Emoji_Presentation"); const emojiModifierRanges = emojiProperty("Emoji_Modifier"); const emojiModifierBaseRanges = emojiProperty("Emoji_Modifier_Base"); const extendedPictographicRanges = emojiProperty("Extended_Pictographic"); @@ -209,12 +208,10 @@ function isZeroWidth(cp) { return false; } -// 0 = zero-width, 1 = narrow, 2 = wide, 3 = ambiguous. Emoji_Presentation -// codepoints are wide even when their East Asian Width is not W/F (the -// regional indicators). +// 0 = zero-width, 1 = narrow, 2 = wide, 3 = ambiguous function widthClass(cp) { if (isZeroWidth(cp)) return 0; - if (inRanges(cp, wideRanges) || inRanges(cp, emojiPresentationRanges)) return 2; + if (inRanges(cp, wideRanges)) return 2; if (inRanges(cp, ambiguousRanges)) return 3; return 1; } @@ -295,7 +292,7 @@ const output = `// clang-format off // with Indic_Conjunct_Break, Emoji_Modifier(_Base) and // Extended_Pictographic // bits 5-6 width class: 0 zero-width, 1 narrow, 2 wide (East Asian Width -// W/F or Emoji_Presentation), 3 East Asian Ambiguous +// W/F), 3 East Asian Ambiguous // bit 7 the Unicode Emoji property, minus the keycap bases [0-9#*] // Zero-width: Cc, Cf, Mn, Me, surrogates, unassigned default-ignorable // codepoints, conjoining Hangul jungseong/jongseong, and the spacing vowel diff --git a/src/jsc/bindings/stringWidth.cpp b/src/jsc/bindings/stringWidth.cpp index ad0852f051a6..f2b4ab120ac8 100644 --- a/src/jsc/bindings/stringWidth.cpp +++ b/src/jsc/bindings/stringWidth.cpp @@ -72,7 +72,7 @@ static_assert(widthFromFused(classifyFromTable(0xFFF9), false) == 0); // interli static_assert(widthFromFused(classifyFromTable(0xFFF0), false) == 0); // unassigned default-ignorable static_assert(widthFromFused(classifyFromTable(0x4E2D), false) == 2); // CJK ideograph: wide static_assert(widthFromFused(classifyFromTable(0xFF21), false) == 2); // fullwidth A: wide -static_assert(widthFromFused(classifyFromTable(0x1F1E6), false) == 2); // regional indicator: Emoji_Presentation +static_assert(widthFromFused(classifyFromTable(0x1F1E6), false) == 1); // regional indicator: narrow on its own static_assert(widthFromFused(classifyFromTable(0xA7), false) == 1); // section sign: ambiguous, narrow by default static_assert(widthFromFused(classifyFromTable(0xA7), true) == 2); // section sign: ambiguous as wide static_assert((classifyFromTable(0x1F600) & kFusedEmojiBit) != 0); // emoji diff --git a/src/jsc/bindings/stringWidth.h b/src/jsc/bindings/stringWidth.h index b6ca3d646c8b..6fdb286db83b 100644 --- a/src/jsc/bindings/stringWidth.h +++ b/src/jsc/bindings/stringWidth.h @@ -120,7 +120,7 @@ struct GraphemeState { { if (count == 0) return 0; - // Regional indicator pair (flag emoji) + // Regional indicator pair (flag emoji); a lone one stays narrow if (regionalIndicators >= 2) return 2; // Keycap sequence: [0-9#*] (VS16)? U+20E3 diff --git a/src/jsc/bindings/stringWidthTables.h b/src/jsc/bindings/stringWidthTables.h index 87c753f6d24f..22f07a8e285f 100644 --- a/src/jsc/bindings/stringWidthTables.h +++ b/src/jsc/bindings/stringWidthTables.h @@ -10,7 +10,7 @@ // with Indic_Conjunct_Break, Emoji_Modifier(_Base) and // Extended_Pictographic // bits 5-6 width class: 0 zero-width, 1 narrow, 2 wide (East Asian Width -// W/F or Emoji_Presentation), 3 East Asian Ambiguous +// W/F), 3 East Asian Ambiguous // bit 7 the Unicode Emoji property, minus the keycap bases [0-9#*] // Zero-width: Cc, Cf, Mn, Me, surrogates, unassigned default-ignorable // codepoints, conjoining Hangul jungseong/jongseong, and the spacing vowel @@ -1245,7 +1245,7 @@ static constexpr uint8_t kGraphemeBreakStage2[33024] = { static constexpr uint8_t kGraphemeBreakStage3[33] = { 17, 32, 96, 171, 235, 14, 1, 3, 48, 15, 33, 35, 68, 5, 6, 46, 47, 10, 9, 49, 203, 64, 172, 236, - 204, 78, 71, 72, 0, 37, 43, 194, 205, + 204, 78, 71, 72, 0, 37, 43, 162, 205, }; } // namespace StringWidthTables diff --git a/test/js/bun/util/sliceAnsi.test.ts b/test/js/bun/util/sliceAnsi.test.ts index 70144b80d696..f6b8028607ac 100644 --- a/test/js/bun/util/sliceAnsi.test.ts +++ b/test/js/bun/util/sliceAnsi.test.ts @@ -357,10 +357,9 @@ describe("Bun.sliceAnsi", () => { test("counts emoji-style graphemes as fullwidth", () => { expect(Bun.sliceAnsi("A☺️B", 1, 3)).toBe("☺️"); expect(Bun.sliceAnsi("A1️⃣B", 1, 3)).toBe("1️⃣"); - // Single (unpaired) regional indicator is width 2 — matches Bun.stringWidth - expect(Bun.stringWidth("\u{1F1E6}")).toBe(2); - expect(Bun.sliceAnsi("A\u{1F1E6}B", 1, 3)).toBe("\u{1F1E6}"); - expect(Bun.sliceAnsi("A\u{1F1E6}B", 3, 4)).toBe("B"); + // Single (unpaired) regional indicator is width 1 — matches Bun.stringWidth + expect(Bun.stringWidth("\u{1F1E6}")).toBe(1); + expect(Bun.sliceAnsi("A\u{1F1E6}B", 1, 2)).toBe("\u{1F1E6}"); }); test("does not treat text-presentation pictographs as fullwidth", () => { diff --git a/test/js/bun/util/stringWidth.test.ts b/test/js/bun/util/stringWidth.test.ts index af1cb503932a..07437e75af0f 100644 --- a/test/js/bun/util/stringWidth.test.ts +++ b/test/js/bun/util/stringWidth.test.ts @@ -579,7 +579,7 @@ describe("stringWidth extended", () => { expect(Bun.stringWidth("🇺🇸")).toBe(2); // US flag expect(Bun.stringWidth("🇬🇧")).toBe(2); // UK flag expect(Bun.stringWidth("🇯🇵")).toBe(2); // Japan flag - expect(Bun.stringWidth("🇦")).toBe(2); // Single regional indicator: Emoji_Presentation + expect(Bun.stringWidth("🇦")).toBe(1); // Single regional indicator (string-width: 1) }); test("skin tone modifiers", () => { @@ -1941,19 +1941,21 @@ describe("width table derived from Unicode properties", () => { expect(widths([0x16d63, 0x16d67])).toEqual([1, 1]); }); - test("Emoji_Presentation codepoints are wide, including a lone regional indicator", () => { - expect(widths([0x1f1e6, 0x1f1ff, 0x1f3fb, 0x231a, 0x2b50, 0x1f600])).toEqual([2, 2, 2, 2, 2, 2]); + test("emoji presentation symbols are wide, a lone regional indicator is narrow", () => { + expect(widths([0x1f3fb, 0x231a, 0x2b50, 0x1f600])).toEqual([2, 2, 2, 2]); + expect(widths([0x1f1e6, 0x1f1ff])).toEqual([1, 1]); }); }); describe("grapheme cluster width rules", () => { - test("regional indicators: pairs are flags, a lone one is still a wide emoji", () => { - expect(Bun.stringWidth("\u{1F1E6}")).toBe(2); - expect(Bun.stringWidth("\u{1F1E6}\u0301")).toBe(2); // RI + combining mark - expect(Bun.stringWidth("\u{1F1E6}\uFE0F")).toBe(2); // RI + VS16 + test("regional indicators: pairs are flags, a lone one is narrow with or without marks", () => { + expect(Bun.stringWidth("\u{1F1E6}")).toBe(1); + expect(Bun.stringWidth("\u{1F1E6}\u0301")).toBe(1); // RI + combining mark + expect(Bun.stringWidth("\u{1F1E6}\u{1F3FB}")).toBe(2); // RI + emoji modifier: modifier sequence + expect(Bun.stringWidth("\u{1F1E6}\uFE0F")).toBe(2); // RI + VS16: emoji presentation expect(Bun.stringWidth("\u{1F1E6}\u{1F1E7}")).toBe(2); // flag - expect(Bun.stringWidth("\u{1F1E6}\u{1F1E7}\u{1F1E8}")).toBe(4); // flag + lone RI - expect(Bun.stringWidth("A\u{1F1E6}")).toBe(3); + expect(Bun.stringWidth("\u{1F1E6}\u{1F1E7}\u{1F1E8}")).toBe(3); // flag + lone RI + expect(Bun.stringWidth("A\u{1F1E6}")).toBe(2); expect(Bun.stringWidth("\u{1F1E6}\u{1F1E7}\u{1F1E8}\u{1F1E9}")).toBe(4); // two flags }); diff --git a/test/js/bun/util/wrapAnsi.test.ts b/test/js/bun/util/wrapAnsi.test.ts index b62733850c6c..afda56617cc0 100644 --- a/test/js/bun/util/wrapAnsi.test.ts +++ b/test/js/bun/util/wrapAnsi.test.ts @@ -120,8 +120,9 @@ describe("Bun.wrapAnsi", () => { expect(Bun.wrapAnsi("a\x1b[31m\u0301\x1b[39mb", 1, { hard: true })).toBe( "a\x1b[31m\u0301\x1b[39m\n\x1b[31m\x1b[39mb", ); - // Each lone regional indicator is a 2-column emoji. + // A lone regional indicator after a flag is its own narrow cluster. expect(Bun.wrapAnsi("\u{1F1E6}\u{1F1E7}\u{1F1E8}", 2, { hard: true })).toBe("\u{1F1E6}\u{1F1E7}\n\u{1F1E8}"); + expect(Bun.wrapAnsi("\u{1F1E6}\u{1F1E7}\u{1F1E8}x", 3, { hard: true })).toBe("\u{1F1E6}\u{1F1E7}\u{1F1E8}\nx"); // Consecutive clusters wider than the row get one row each, no blank // row between them (same shape as two wide CJK characters). expect(Bun.wrapAnsi("\u{1F600}\u{1F600}", 1, { hard: true })).toBe("\n\u{1F600}\n\u{1F600}"); From 2a1ea344f627404dace42a1ff92d1ad4257caded Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sun, 6 Sep 2026 16:55:13 +0000 Subject: [PATCH 6/6] Spell out the cluster widths in the sliceAnsi agreement test The expected clusters came from Intl.Segmenter, whose Unicode version depends on the platform ICU: on macOS x64 it still treats U+2605 as Extended_Pictographic and joins it to a ZWJ sequence, while the width table follows Unicode 17. --- test/js/bun/util/stringWidth.test.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/test/js/bun/util/stringWidth.test.ts b/test/js/bun/util/stringWidth.test.ts index 07437e75af0f..acf39b6eeb9c 100644 --- a/test/js/bun/util/stringWidth.test.ts +++ b/test/js/bun/util/stringWidth.test.ts @@ -2028,22 +2028,22 @@ describe("grapheme cluster width rules", () => { }); test("sliceAnsi measures clusters exactly like stringWidth", () => { - const cases = [ - "\u{1F1E6}x", - "\u{1F1E6}\u{1F1E7}\u{1F1E8}", - "1\u20E3a\u20E3\u20E3", - "A\u{1F3FB}\u{1F600}\u{1F3FB}", - "\u00A9\u200D\u{1F600}z", - "\u2605\u200D\u{1F600}", - "\n\u{1F3FB}", - "\u{1F600}\u{1F3FB}\u200D\u{1F600}!", + // Each case lists the width of its grapheme clusters (Unicode 17: U+2605 + // is no longer Extended_Pictographic, so it does not join a ZWJ sequence). + const cases: [string, number[]][] = [ + ["\u{1F1E6}x", [1, 1]], + ["\u{1F1E6}\u{1F1E7}\u{1F1E8}", [2, 1]], + ["1\u20E3a\u20E3\u20E3", [2, 1]], + ["A\u{1F3FB}\u{1F600}\u{1F3FB}", [1, 2]], + ["\u00A9\u200D\u{1F600}z", [2, 1]], + ["\u2605\u200D\u{1F600}", [1, 2]], + ["\n\u{1F3FB}", [0, 2]], + ["\u{1F600}\u{1F3FB}\u200D\u{1F600}!", [2, 1]], ]; - const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" }); - for (const s of cases) { + for (const [s, clusters] of cases) { const width = Bun.stringWidth(s); // sliceAnsi(s, 0, k) keeps every cluster that starts before column k. - const clusters = [...segmenter.segment(s)].map(({ segment }) => Bun.stringWidth(segment)); - expect(clusters.reduce((a, b) => a + b, 0)).toBe(width); + expect({ s, width }).toEqual({ s, width: clusters.reduce((a, b) => a + b, 0) }); const expected: number[] = []; for (let k = 0; k <= width; k++) { let start = 0;