From 03d7a3453f762ea35d5bc969134fad963d06b90d Mon Sep 17 00:00:00 2001 From: Omar Aglan Date: Mon, 14 Sep 2026 12:30:49 +0300 Subject: [PATCH] feat(text): Add Unicode paragraph direction detection --- .../Source/WWVegas/WW3D2/CMakeLists.txt | 2 + .../WWVegas/WW3D2/supplementarybidi.inl | 265 ++++++++++++++++++ .../Source/WWVegas/WW3D2/unicode-license.txt | 39 +++ .../Source/WWVegas/WW3D2/unicodebidi.h | 84 ++++++ scripts/generate_supplementary_bidi.py | 54 ++++ 5 files changed, 444 insertions(+) create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/supplementarybidi.inl create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/unicode-license.txt create mode 100644 Core/Libraries/Source/WWVegas/WW3D2/unicodebidi.h create mode 100644 scripts/generate_supplementary_bidi.py diff --git a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt index 68f59638745..ed8c7be4f63 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/WW3D2/CMakeLists.txt @@ -163,6 +163,8 @@ set(WW3D2_SRC #render2d.h render2dsentence.cpp render2dsentence.h + unicodebidi.h + supplementarybidi.inl renderobjectrecycler.cpp renderobjectrecycler.h rendobj.cpp diff --git a/Core/Libraries/Source/WWVegas/WW3D2/supplementarybidi.inl b/Core/Libraries/Source/WWVegas/WW3D2/supplementarybidi.inl new file mode 100644 index 00000000000..317207d8226 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/supplementarybidi.inl @@ -0,0 +1,265 @@ +// Generated by scripts/generate_supplementary_bidi.py; do not edit. +// Unicode 17.0.0 DerivedBidiClass.txt, including its @missing defaults. +// Copyright 2025 Unicode, Inc. See unicode-license.txt. +// Omitted ranges have direction LeftToRight. Neutral means neither L, R nor AL. + { 0x10101, 0x10101, Neutral }, + { 0x10140, 0x1018C, Neutral }, + { 0x10190, 0x1019C, Neutral }, + { 0x101A0, 0x101A0, Neutral }, + { 0x101FD, 0x101FD, Neutral }, + { 0x102E0, 0x102FB, Neutral }, + { 0x10376, 0x1037A, Neutral }, + { 0x10800, 0x1091E, RightToLeft }, + { 0x1091F, 0x1091F, Neutral }, + { 0x10920, 0x10A00, RightToLeft }, + { 0x10A01, 0x10A03, Neutral }, + { 0x10A04, 0x10A04, RightToLeft }, + { 0x10A05, 0x10A06, Neutral }, + { 0x10A07, 0x10A0B, RightToLeft }, + { 0x10A0C, 0x10A0F, Neutral }, + { 0x10A10, 0x10A37, RightToLeft }, + { 0x10A38, 0x10A3A, Neutral }, + { 0x10A3B, 0x10A3E, RightToLeft }, + { 0x10A3F, 0x10A3F, Neutral }, + { 0x10A40, 0x10AE4, RightToLeft }, + { 0x10AE5, 0x10AE6, Neutral }, + { 0x10AE7, 0x10B38, RightToLeft }, + { 0x10B39, 0x10B3F, Neutral }, + { 0x10B40, 0x10D23, RightToLeft }, + { 0x10D24, 0x10D27, Neutral }, + { 0x10D28, 0x10D2F, RightToLeft }, + { 0x10D30, 0x10D39, Neutral }, + { 0x10D3A, 0x10D3F, RightToLeft }, + { 0x10D40, 0x10D49, Neutral }, + { 0x10D4A, 0x10D68, RightToLeft }, + { 0x10D69, 0x10D6E, Neutral }, + { 0x10D6F, 0x10E5F, RightToLeft }, + { 0x10E60, 0x10E7E, Neutral }, + { 0x10E7F, 0x10EAA, RightToLeft }, + { 0x10EAB, 0x10EAC, Neutral }, + { 0x10EAD, 0x10ECF, RightToLeft }, + { 0x10ED0, 0x10ED8, Neutral }, + { 0x10ED9, 0x10EF9, RightToLeft }, + { 0x10EFA, 0x10EFF, Neutral }, + { 0x10F00, 0x10F45, RightToLeft }, + { 0x10F46, 0x10F50, Neutral }, + { 0x10F51, 0x10F81, RightToLeft }, + { 0x10F82, 0x10F85, Neutral }, + { 0x10F86, 0x10FFF, RightToLeft }, + { 0x11001, 0x11001, Neutral }, + { 0x11038, 0x11046, Neutral }, + { 0x11052, 0x11065, Neutral }, + { 0x11070, 0x11070, Neutral }, + { 0x11073, 0x11074, Neutral }, + { 0x1107F, 0x11081, Neutral }, + { 0x110B3, 0x110B6, Neutral }, + { 0x110B9, 0x110BA, Neutral }, + { 0x110C2, 0x110C2, Neutral }, + { 0x11100, 0x11102, Neutral }, + { 0x11127, 0x1112B, Neutral }, + { 0x1112D, 0x11134, Neutral }, + { 0x11173, 0x11173, Neutral }, + { 0x11180, 0x11181, Neutral }, + { 0x111B6, 0x111BE, Neutral }, + { 0x111C9, 0x111CC, Neutral }, + { 0x111CF, 0x111CF, Neutral }, + { 0x1122F, 0x11231, Neutral }, + { 0x11234, 0x11234, Neutral }, + { 0x11236, 0x11237, Neutral }, + { 0x1123E, 0x1123E, Neutral }, + { 0x11241, 0x11241, Neutral }, + { 0x112DF, 0x112DF, Neutral }, + { 0x112E3, 0x112EA, Neutral }, + { 0x11300, 0x11301, Neutral }, + { 0x1133B, 0x1133C, Neutral }, + { 0x11340, 0x11340, Neutral }, + { 0x11366, 0x1136C, Neutral }, + { 0x11370, 0x11374, Neutral }, + { 0x113BB, 0x113C0, Neutral }, + { 0x113CE, 0x113CE, Neutral }, + { 0x113D0, 0x113D0, Neutral }, + { 0x113D2, 0x113D2, Neutral }, + { 0x113E1, 0x113E2, Neutral }, + { 0x11438, 0x1143F, Neutral }, + { 0x11442, 0x11444, Neutral }, + { 0x11446, 0x11446, Neutral }, + { 0x1145E, 0x1145E, Neutral }, + { 0x114B3, 0x114B8, Neutral }, + { 0x114BA, 0x114BA, Neutral }, + { 0x114BF, 0x114C0, Neutral }, + { 0x114C2, 0x114C3, Neutral }, + { 0x115B2, 0x115B5, Neutral }, + { 0x115BC, 0x115BD, Neutral }, + { 0x115BF, 0x115C0, Neutral }, + { 0x115DC, 0x115DD, Neutral }, + { 0x11633, 0x1163A, Neutral }, + { 0x1163D, 0x1163D, Neutral }, + { 0x1163F, 0x11640, Neutral }, + { 0x11660, 0x1166C, Neutral }, + { 0x116AB, 0x116AB, Neutral }, + { 0x116AD, 0x116AD, Neutral }, + { 0x116B0, 0x116B5, Neutral }, + { 0x116B7, 0x116B7, Neutral }, + { 0x1171D, 0x1171D, Neutral }, + { 0x1171F, 0x1171F, Neutral }, + { 0x11722, 0x11725, Neutral }, + { 0x11727, 0x1172B, Neutral }, + { 0x1182F, 0x11837, Neutral }, + { 0x11839, 0x1183A, Neutral }, + { 0x1193B, 0x1193C, Neutral }, + { 0x1193E, 0x1193E, Neutral }, + { 0x11943, 0x11943, Neutral }, + { 0x119D4, 0x119D7, Neutral }, + { 0x119DA, 0x119DB, Neutral }, + { 0x119E0, 0x119E0, Neutral }, + { 0x11A01, 0x11A06, Neutral }, + { 0x11A09, 0x11A0A, Neutral }, + { 0x11A33, 0x11A38, Neutral }, + { 0x11A3B, 0x11A3E, Neutral }, + { 0x11A47, 0x11A47, Neutral }, + { 0x11A51, 0x11A56, Neutral }, + { 0x11A59, 0x11A5B, Neutral }, + { 0x11A8A, 0x11A96, Neutral }, + { 0x11A98, 0x11A99, Neutral }, + { 0x11B60, 0x11B60, Neutral }, + { 0x11B62, 0x11B64, Neutral }, + { 0x11B66, 0x11B66, Neutral }, + { 0x11C30, 0x11C36, Neutral }, + { 0x11C38, 0x11C3D, Neutral }, + { 0x11C92, 0x11CA7, Neutral }, + { 0x11CAA, 0x11CB0, Neutral }, + { 0x11CB2, 0x11CB3, Neutral }, + { 0x11CB5, 0x11CB6, Neutral }, + { 0x11D31, 0x11D36, Neutral }, + { 0x11D3A, 0x11D3A, Neutral }, + { 0x11D3C, 0x11D3D, Neutral }, + { 0x11D3F, 0x11D45, Neutral }, + { 0x11D47, 0x11D47, Neutral }, + { 0x11D90, 0x11D91, Neutral }, + { 0x11D95, 0x11D95, Neutral }, + { 0x11D97, 0x11D97, Neutral }, + { 0x11EF3, 0x11EF4, Neutral }, + { 0x11F00, 0x11F01, Neutral }, + { 0x11F36, 0x11F3A, Neutral }, + { 0x11F40, 0x11F40, Neutral }, + { 0x11F42, 0x11F42, Neutral }, + { 0x11F5A, 0x11F5A, Neutral }, + { 0x11FD5, 0x11FF1, Neutral }, + { 0x13440, 0x13440, Neutral }, + { 0x13447, 0x13455, Neutral }, + { 0x1611E, 0x16129, Neutral }, + { 0x1612D, 0x1612F, Neutral }, + { 0x16AF0, 0x16AF4, Neutral }, + { 0x16B30, 0x16B36, Neutral }, + { 0x16F4F, 0x16F4F, Neutral }, + { 0x16F8F, 0x16F92, Neutral }, + { 0x16FE2, 0x16FE2, Neutral }, + { 0x16FE4, 0x16FE4, Neutral }, + { 0x1BC9D, 0x1BC9E, Neutral }, + { 0x1BCA0, 0x1BCA3, Neutral }, + { 0x1CC00, 0x1CCD5, Neutral }, + { 0x1CCF0, 0x1CCFC, Neutral }, + { 0x1CD00, 0x1CEB3, Neutral }, + { 0x1CEBA, 0x1CED0, Neutral }, + { 0x1CEE0, 0x1CEF0, Neutral }, + { 0x1CF00, 0x1CF2D, Neutral }, + { 0x1CF30, 0x1CF46, Neutral }, + { 0x1D167, 0x1D169, Neutral }, + { 0x1D173, 0x1D182, Neutral }, + { 0x1D185, 0x1D18B, Neutral }, + { 0x1D1AA, 0x1D1AD, Neutral }, + { 0x1D1E9, 0x1D1EA, Neutral }, + { 0x1D200, 0x1D245, Neutral }, + { 0x1D300, 0x1D356, Neutral }, + { 0x1D6C1, 0x1D6C1, Neutral }, + { 0x1D6DB, 0x1D6DB, Neutral }, + { 0x1D6FB, 0x1D6FB, Neutral }, + { 0x1D715, 0x1D715, Neutral }, + { 0x1D735, 0x1D735, Neutral }, + { 0x1D74F, 0x1D74F, Neutral }, + { 0x1D76F, 0x1D76F, Neutral }, + { 0x1D789, 0x1D789, Neutral }, + { 0x1D7A9, 0x1D7A9, Neutral }, + { 0x1D7C3, 0x1D7C3, Neutral }, + { 0x1D7CE, 0x1D7FF, Neutral }, + { 0x1DA00, 0x1DA36, Neutral }, + { 0x1DA3B, 0x1DA6C, Neutral }, + { 0x1DA75, 0x1DA75, Neutral }, + { 0x1DA84, 0x1DA84, Neutral }, + { 0x1DA9B, 0x1DA9F, Neutral }, + { 0x1DAA1, 0x1DAAF, Neutral }, + { 0x1E000, 0x1E006, Neutral }, + { 0x1E008, 0x1E018, Neutral }, + { 0x1E01B, 0x1E021, Neutral }, + { 0x1E023, 0x1E024, Neutral }, + { 0x1E026, 0x1E02A, Neutral }, + { 0x1E08F, 0x1E08F, Neutral }, + { 0x1E130, 0x1E136, Neutral }, + { 0x1E2AE, 0x1E2AE, Neutral }, + { 0x1E2EC, 0x1E2EF, Neutral }, + { 0x1E2FF, 0x1E2FF, Neutral }, + { 0x1E4EC, 0x1E4EF, Neutral }, + { 0x1E5EE, 0x1E5EF, Neutral }, + { 0x1E6E3, 0x1E6E3, Neutral }, + { 0x1E6E6, 0x1E6E6, Neutral }, + { 0x1E6EE, 0x1E6EF, Neutral }, + { 0x1E6F5, 0x1E6F5, Neutral }, + { 0x1E800, 0x1E8CF, RightToLeft }, + { 0x1E8D0, 0x1E8D6, Neutral }, + { 0x1E8D7, 0x1E943, RightToLeft }, + { 0x1E944, 0x1E94A, Neutral }, + { 0x1E94B, 0x1EEEF, RightToLeft }, + { 0x1EEF0, 0x1EEF1, Neutral }, + { 0x1EEF2, 0x1EFFF, RightToLeft }, + { 0x1F000, 0x1F02B, Neutral }, + { 0x1F030, 0x1F093, Neutral }, + { 0x1F0A0, 0x1F0AE, Neutral }, + { 0x1F0B1, 0x1F0BF, Neutral }, + { 0x1F0C1, 0x1F0CF, Neutral }, + { 0x1F0D1, 0x1F0F5, Neutral }, + { 0x1F100, 0x1F10F, Neutral }, + { 0x1F12F, 0x1F12F, Neutral }, + { 0x1F16A, 0x1F16F, Neutral }, + { 0x1F1AD, 0x1F1AD, Neutral }, + { 0x1F260, 0x1F265, Neutral }, + { 0x1F300, 0x1F6D8, Neutral }, + { 0x1F6DC, 0x1F6EC, Neutral }, + { 0x1F6F0, 0x1F6FC, Neutral }, + { 0x1F700, 0x1F7D9, Neutral }, + { 0x1F7E0, 0x1F7EB, Neutral }, + { 0x1F7F0, 0x1F7F0, Neutral }, + { 0x1F800, 0x1F80B, Neutral }, + { 0x1F810, 0x1F847, Neutral }, + { 0x1F850, 0x1F859, Neutral }, + { 0x1F860, 0x1F887, Neutral }, + { 0x1F890, 0x1F8AD, Neutral }, + { 0x1F8B0, 0x1F8BB, Neutral }, + { 0x1F8C0, 0x1F8C1, Neutral }, + { 0x1F8D0, 0x1F8D8, Neutral }, + { 0x1F900, 0x1FA57, Neutral }, + { 0x1FA60, 0x1FA6D, Neutral }, + { 0x1FA70, 0x1FA7C, Neutral }, + { 0x1FA80, 0x1FA8A, Neutral }, + { 0x1FA8E, 0x1FAC6, Neutral }, + { 0x1FAC8, 0x1FAC8, Neutral }, + { 0x1FACD, 0x1FADC, Neutral }, + { 0x1FADF, 0x1FAEA, Neutral }, + { 0x1FAEF, 0x1FAF8, Neutral }, + { 0x1FB00, 0x1FB92, Neutral }, + { 0x1FB94, 0x1FBFA, Neutral }, + { 0x1FFFE, 0x1FFFF, Neutral }, + { 0x2FFFE, 0x2FFFF, Neutral }, + { 0x3FFFE, 0x3FFFF, Neutral }, + { 0x4FFFE, 0x4FFFF, Neutral }, + { 0x5FFFE, 0x5FFFF, Neutral }, + { 0x6FFFE, 0x6FFFF, Neutral }, + { 0x7FFFE, 0x7FFFF, Neutral }, + { 0x8FFFE, 0x8FFFF, Neutral }, + { 0x9FFFE, 0x9FFFF, Neutral }, + { 0xAFFFE, 0xAFFFF, Neutral }, + { 0xBFFFE, 0xBFFFF, Neutral }, + { 0xCFFFE, 0xCFFFF, Neutral }, + { 0xDFFFE, 0xE0FFF, Neutral }, + { 0xEFFFE, 0xEFFFF, Neutral }, + { 0xFFFFE, 0xFFFFF, Neutral }, + { 0x10FFFE, 0x10FFFF, Neutral }, diff --git a/Core/Libraries/Source/WWVegas/WW3D2/unicode-license.txt b/Core/Libraries/Source/WWVegas/WW3D2/unicode-license.txt new file mode 100644 index 00000000000..56da5891280 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/unicode-license.txt @@ -0,0 +1,39 @@ +UNICODE LICENSE V3 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 1991-2026 Unicode, Inc. + +NOTICE TO USER: Carefully read the following legal agreement. BY +DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR +SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT +DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of data files and any associated documentation (the "Data Files") or +software and any associated documentation (the "Software") to deal in the +Data Files or Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, and/or sell +copies of the Data Files or Software, and to permit persons to whom the +Data Files or Software are furnished to do so, provided that either (a) +this copyright and permission notice appear with all copies of the Data +Files or Software, or (b) this copyright and permission notice appear in +associated Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF +THIRD PARTY RIGHTS. + +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE +BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA +FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall +not be used in advertising or otherwise to promote the sale, use or other +dealings in these Data Files or Software without prior written +authorization of the copyright holder. diff --git a/Core/Libraries/Source/WWVegas/WW3D2/unicodebidi.h b/Core/Libraries/Source/WWVegas/WW3D2/unicodebidi.h new file mode 100644 index 00000000000..b9d1dfcd306 --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WW3D2/unicodebidi.h @@ -0,0 +1,84 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2026 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +#include "WWLib/win.h" + +namespace UnicodeBidi +{ + +enum Direction { Neutral, LeftToRight, RightToLeft }; + +inline Direction Get_Supplementary_Direction(unsigned codepoint) +{ + struct Range { unsigned First, Last; Direction Value; }; + static const Range ranges[] = { +#include "supplementarybidi.inl" + }; + unsigned first = 0, last = sizeof(ranges) / sizeof(ranges[0]); + while (first < last) { + const unsigned middle = first + (last - first) / 2; + if (codepoint < ranges[middle].First) { + last = middle; + } else if (codepoint > ranges[middle].Last) { + first = middle + 1; + } else { + return ranges[middle].Value; + } + } + return LeftToRight; +} + +// TheSuperHackers @bugfix Omar Aglan 13/09/2026 Determine paragraph direction from complete UTF-16 +// characters. GetStringTypeW does not classify supplementary characters reliably. +inline WORD Get_Paragraph_Level(const WCHAR *text, int length) +{ + int isolate_depth = 0; + for (int index = 0; index < length; ++index) { + const WCHAR ch = text[index]; + if (ch >= 0x2066 && ch <= 0x2068) { + ++isolate_depth; + continue; + } + if (ch == 0x2069) { + if (isolate_depth > 0) --isolate_depth; + continue; + } + // UAX #9 P2 excludes the contents of directional isolates. + if (isolate_depth > 0) continue; + + Direction direction = Neutral; + if (ch >= 0xD800 && ch <= 0xDBFF) { + if (index + 1 < length && text[index + 1] >= 0xDC00 && text[index + 1] <= 0xDFFF) { + const unsigned codepoint = 0x10000 + ((ch - 0xD800) << 10) + (text[++index] - 0xDC00); + direction = Get_Supplementary_Direction(codepoint); + } + } else if (ch < 0xDC00 || ch > 0xDFFF) { + WORD type = C2_NOTAPPLICABLE; + if (::GetStringTypeW(CT_CTYPE2, &ch, 1, &type)) { + if (type == C2_LEFTTORIGHT) direction = LeftToRight; + if (type == C2_RIGHTTOLEFT) direction = RightToLeft; + } + } + if (direction != Neutral) return direction == RightToLeft ? 1 : 0; + } + return 0; +} + +} diff --git a/scripts/generate_supplementary_bidi.py b/scripts/generate_supplementary_bidi.py new file mode 100644 index 00000000000..e5e779c64b6 --- /dev/null +++ b/scripts/generate_supplementary_bidi.py @@ -0,0 +1,54 @@ +"""Generate the supplementary first-strong-direction table from Unicode 17.0.0. + +Input: https://www.unicode.org/Public/17.0.0/ucd/extracted/DerivedBidiClass.txt +Usage: python scripts/generate_supplementary_bidi.py DerivedBidiClass.txt [--check] +""" + +import argparse +import hashlib +from pathlib import Path +import re + + +def generate(source): + if not source.startswith("# DerivedBidiClass-17.0.0.txt"): + raise ValueError("Expected Unicode 17.0.0 DerivedBidiClass.txt") + if hashlib.sha256(source.encode("utf-8")).hexdigest() != "4867b4b7f0731ed1bfcd34cc6251211ff1542541fce0734b6fbda139ee80b3a4": + raise ValueError("Unexpected Unicode data checksum") + classes = ["L"] * 0x110000 + aliases = {"Left_To_Right": "L", "Right_To_Left": "R", "Arabic_Letter": "AL"} + missing = re.findall(r"@missing: ([0-9A-F]+)\.\.([0-9A-F]+); (\w+)", source) + explicit = re.findall(r"^([0-9A-F]+)(?:\.\.([0-9A-F]+))?\s*;\s*(\w+)", source, re.M) + for lo, hi, value in missing + explicit: + lo, hi = int(lo, 16), int(hi or lo, 16) + classes[lo:hi + 1] = [aliases.get(value, value)] * (hi - lo + 1) + directions = ["LeftToRight" if c == "L" else "RightToLeft" if c in ("R", "AL") + else "Neutral" for c in classes] + lines = [ + "// Generated by scripts/generate_supplementary_bidi.py; do not edit.", + "// Unicode 17.0.0 DerivedBidiClass.txt, including its @missing defaults.", + "// Copyright 2025 Unicode, Inc. See unicode-license.txt.", + "// Omitted ranges have direction LeftToRight. Neutral means neither L, R nor AL.", + ] + start = 0x10000 + for end in range(start + 1, len(directions) + 1): + if end == len(directions) or directions[end] != directions[start]: + if directions[start] != "LeftToRight": + lines.append("\t{ 0x%05X, 0x%05X, %s }," % (start, end - 1, directions[start])) + start = end + return "\n".join(lines) + "\n" + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("source", type=Path) + parser.add_argument("--check", action="store_true") + args = parser.parse_args() + output = Path(__file__).resolve().parents[1] / "Core/Libraries/Source/WWVegas/WW3D2/supplementarybidi.inl" + generated = generate(args.source.read_text(encoding="utf-8")) + if args.check: + if output.read_text(encoding="utf-8") != generated: + raise SystemExit("Supplementary bidi table is out of date") + print("Unicode 17.0.0 supplementary bidi table matches") + else: + output.write_text(generated, encoding="utf-8")