|
| 1 | +// compat.sheenbidi — the Unicode bidirectional algorithm, on text that needs it. |
| 2 | +// |
| 3 | +// WHAT THIS ASSERTS AND WHY |
| 4 | +// |
| 5 | +// The Unicode tables are most of the package: Source/Data/*Lookup.c encode the |
| 6 | +// Unicode Character Database, and a build that compiled none of them would not |
| 7 | +// link — but one that compiled stale ones would, and would quietly get new |
| 8 | +// scripts wrong. So every check asks the library a question whose answer is in |
| 9 | +// a table or in the algorithm over it: |
| 10 | +// |
| 11 | +// the version SBVersionGetString, the linked library's own string |
| 12 | +// bidi types BidiTypeLookup.c — L, R, AL, EN, AN |
| 13 | +// Unicode 17.0.0 a Sidetic letter (new in 17.0) is R and Sidetic |
| 14 | +// general category GeneralCategoryLookup.c |
| 15 | +// mirroring, brackets PairingLookup.c — "(" mirrors to ")" |
| 16 | +// scripts ScriptLookup.c, the script locator over mixed text |
| 17 | +// paragraphs the base level from the first strong character |
| 18 | +// lines and runs UAX #9 itself: runs in visual order, with levels |
| 19 | +// the mirror locator which characters of a line are drawn mirrored |
| 20 | +// UTF-8 and UTF-16 the same answer whatever the code units |
| 21 | +// |
| 22 | +// A pass means the tarball's committed tables were compiled, through the unity |
| 23 | +// source — the claim that lets this be a descriptor with nothing generated. |
| 24 | + |
| 25 | +#include <SheenBidi/SheenBidi.h> |
| 26 | + |
| 27 | +#include <cstdint> |
| 28 | +#include <cstdio> |
| 29 | +#include <cstring> |
| 30 | +#include <vector> |
| 31 | + |
| 32 | +namespace { |
| 33 | + |
| 34 | +int failures = 0; |
| 35 | + |
| 36 | +void check(bool ok, const char *what) |
| 37 | +{ |
| 38 | + std::printf("%-62s %s\n", what, ok ? "ok" : "FAILED"); |
| 39 | + if (!ok) { |
| 40 | + ++failures; |
| 41 | + } |
| 42 | +} |
| 43 | + |
| 44 | +SBCodepointSequence utf32(const std::vector<SBUInt32> &text) |
| 45 | +{ |
| 46 | + return SBCodepointSequence{SBStringEncodingUTF32, text.data(), text.size()}; |
| 47 | +} |
| 48 | + |
| 49 | +} // namespace |
| 50 | + |
| 51 | +int main() |
| 52 | +{ |
| 53 | + // ── 1. the version ──────────────────────────────────────────────────── |
| 54 | + std::printf(" SheenBidi %s\n", SBVersionGetString()); |
| 55 | + check(std::strcmp(SBVersionGetString(), "3.0.0") == 0, |
| 56 | + "SBVersionGetString is 3.0.0, as the descriptor is"); |
| 57 | + check(std::strcmp(SBVersionGetString(), SHEENBIDI_VERSION_STRING) == 0, |
| 58 | + "...and agrees with the header it was compiled against"); |
| 59 | + |
| 60 | + // ── 2. bidi types — BidiTypeLookup.c ────────────────────────────────── |
| 61 | + check(SBCodepointGetBidiType('a') == SBBidiTypeL, "'a' is L"); |
| 62 | + check(SBCodepointGetBidiType(0x05D0) == SBBidiTypeR, "U+05D0 HEBREW ALEF is R"); |
| 63 | + check(SBCodepointGetBidiType(0x0627) == SBBidiTypeAL, "U+0627 ARABIC ALEF is AL"); |
| 64 | + check(SBCodepointGetBidiType('1') == SBBidiTypeEN, "'1' is EN"); |
| 65 | + check(SBCodepointGetBidiType(0x0661) == SBBidiTypeAN, "U+0661 ARABIC-INDIC ONE is AN"); |
| 66 | + check(SBBidiTypeIsStrong(SBCodepointGetBidiType(0x05D0)), "SBBidiTypeIsStrong(R)"); |
| 67 | + |
| 68 | + // ── 3. Unicode 17.0.0 — the tables are this release's ───────────────── |
| 69 | + // U+10940 SIDETIC LETTER N01 was added in Unicode 17.0: right-to-left, |
| 70 | + // script Sidetic. Tables older than 17.0 answer L (unassigned) and Zzzz. |
| 71 | + check(SBCodepointGetBidiType(0x10940) == SBBidiTypeR, |
| 72 | + "U+10940 SIDETIC LETTER N01 (Unicode 17.0) is R"); |
| 73 | + check(SBCodepointGetScript(0x10940) == SBScriptSIDT, "...and its script is Sidetic"); |
| 74 | + |
| 75 | + // ── 4. general category, mirroring, scripts ─────────────────────────── |
| 76 | + check(SBCodepointGetGeneralCategory('A') == SBGeneralCategoryLU, "'A' is Lu"); |
| 77 | + check(SBCodepointGetGeneralCategory(0x0627) == SBGeneralCategoryLO, "U+0627 is Lo"); |
| 78 | + check(SBCodepointGetMirror('(') == ')', "'(' mirrors to ')'"); |
| 79 | + check(SBCodepointGetMirror('a') == 0, "'a' has no mirror (0, not itself)"); |
| 80 | + check(SBCodepointGetScript(0x0627) == SBScriptARAB, "U+0627 is Arabic script"); |
| 81 | + check(SBScriptGetUnicodeTag(SBScriptHEBR) == 0x48656272u /* 'Hebr' */, |
| 82 | + "the ISO 15924 tag of Hebrew is 'Hebr'"); |
| 83 | + |
| 84 | + // ── 5. paragraphs: the base level from the first strong character ───── |
| 85 | + { |
| 86 | + const std::vector<SBUInt32> hebrew_first = {0x05D0, ' ', 'A'}; |
| 87 | + const std::vector<SBUInt32> latin_first = {'A', ' ', 0x05D0}; |
| 88 | + for (int i = 0; i < 2; ++i) { |
| 89 | + const auto seq = utf32(i == 0 ? hebrew_first : latin_first); |
| 90 | + SBAlgorithmRef algorithm = SBAlgorithmCreate(&seq); |
| 91 | + SBParagraphRef paragraph = |
| 92 | + SBAlgorithmCreateParagraph(algorithm, 0, seq.stringLength, SBLevelDefaultLTR); |
| 93 | + const SBLevel base = SBParagraphGetBaseLevel(paragraph); |
| 94 | + check(i == 0 ? base == 1 : base == 0, |
| 95 | + i == 0 ? "a Hebrew-initial paragraph has base level 1" |
| 96 | + : "...and a Latin-initial one base level 0"); |
| 97 | + SBParagraphRelease(paragraph); |
| 98 | + SBAlgorithmRelease(algorithm); |
| 99 | + } |
| 100 | + } |
| 101 | + |
| 102 | + // ── 6. lines and runs: UAX #9 itself ────────────────────────────────── |
| 103 | + // "abc אבג" in a right-to-left paragraph. The Latin run is at level 2, the |
| 104 | + // space and the Hebrew at level 1. A line's runs come in VISUAL order, left |
| 105 | + // to right: the space and Hebrew (offset 3, length 4, level 1) first, then |
| 106 | + // "abc" (offset 0, length 3, level 2). |
| 107 | + { |
| 108 | + const std::vector<SBUInt32> text = {'a', 'b', 'c', ' ', 0x05D0, 0x05D1, 0x05D2}; |
| 109 | + const auto seq = utf32(text); |
| 110 | + SBAlgorithmRef algorithm = SBAlgorithmCreate(&seq); |
| 111 | + SBParagraphRef paragraph = SBAlgorithmCreateParagraph(algorithm, 0, text.size(), 1); |
| 112 | + check(SBParagraphGetLength(paragraph) == text.size(), "the paragraph covers the text"); |
| 113 | + |
| 114 | + const SBLevel *levels = SBParagraphGetLevelsPtr(paragraph); |
| 115 | + check(levels[0] == 2 && levels[2] == 2 && levels[3] == 1 && levels[4] == 1, |
| 116 | + "levels: Latin 2, space 1, Hebrew 1"); |
| 117 | + |
| 118 | + SBLineRef line = SBParagraphCreateLine(paragraph, 0, text.size()); |
| 119 | + const SBUInteger count = SBLineGetRunCount(line); |
| 120 | + const SBRun *runs = SBLineGetRunsPtr(line); |
| 121 | + std::printf(" runs:"); |
| 122 | + for (SBUInteger r = 0; r < count; ++r) { |
| 123 | + std::printf(" {%zu,%zu,L%u}", static_cast<size_t>(runs[r].offset), |
| 124 | + static_cast<size_t>(runs[r].length), static_cast<unsigned>(runs[r].level)); |
| 125 | + } |
| 126 | + std::printf("\n"); |
| 127 | + check(count == 2, "the line has two runs"); |
| 128 | + check(count == 2 && runs[0].offset == 3 && runs[0].length == 4 && runs[0].level == 1, |
| 129 | + "the first visual run is the space and the Hebrew, at level 1"); |
| 130 | + check(count == 2 && runs[1].offset == 0 && runs[1].length == 3 && runs[1].level == 2, |
| 131 | + "...and the second is \"abc\", at level 2"); |
| 132 | + |
| 133 | + SBLineRelease(line); |
| 134 | + SBParagraphRelease(paragraph); |
| 135 | + SBAlgorithmRelease(algorithm); |
| 136 | + } |
| 137 | + |
| 138 | + // ── 7. the mirror locator — what a line draws mirrored ──────────────── |
| 139 | + // "אב(ג)" right to left: both parentheses resolve to level 1, so both are |
| 140 | + // drawn as their mirrors. |
| 141 | + { |
| 142 | + const std::vector<SBUInt32> text = {0x05D0, 0x05D1, '(', 0x05D2, ')'}; |
| 143 | + const auto seq = utf32(text); |
| 144 | + SBAlgorithmRef algorithm = SBAlgorithmCreate(&seq); |
| 145 | + SBParagraphRef paragraph = |
| 146 | + SBAlgorithmCreateParagraph(algorithm, 0, text.size(), SBLevelDefaultLTR); |
| 147 | + SBLineRef line = SBParagraphCreateLine(paragraph, 0, text.size()); |
| 148 | + SBMirrorLocatorRef locator = SBMirrorLocatorCreate(); |
| 149 | + SBMirrorLocatorLoadLine(locator, line, text.data()); |
| 150 | + std::vector<SBMirrorAgent> agents; |
| 151 | + while (SBMirrorLocatorMoveNext(locator)) { |
| 152 | + agents.push_back(*SBMirrorLocatorGetAgent(locator)); |
| 153 | + } |
| 154 | + check(agents.size() == 2, "two characters are drawn mirrored"); |
| 155 | + check(agents.size() == 2 && agents[0].index == 2 && agents[0].mirror == ')' && |
| 156 | + agents[1].index == 4 && agents[1].mirror == '(', |
| 157 | + "...'(' at 2 as ')' and ')' at 4 as '('"); |
| 158 | + SBMirrorLocatorRelease(locator); |
| 159 | + SBLineRelease(line); |
| 160 | + SBParagraphRelease(paragraph); |
| 161 | + SBAlgorithmRelease(algorithm); |
| 162 | + } |
| 163 | + |
| 164 | + // ── 8. scripts over mixed text, from UTF-8 ──────────────────────────── |
| 165 | + // "abc عربي": Latin, then (the space joins the run before it) Arabic. |
| 166 | + { |
| 167 | + const char *utf8 = "abc \xD8\xB9\xD8\xB1\xD8\xA8\xD9\x8A"; |
| 168 | + const SBCodepointSequence seq{SBStringEncodingUTF8, utf8, std::strlen(utf8)}; |
| 169 | + SBScriptLocatorRef locator = SBScriptLocatorCreate(); |
| 170 | + SBScriptLocatorLoadCodepoints(locator, &seq); |
| 171 | + std::vector<SBScriptAgent> runs; |
| 172 | + while (SBScriptLocatorMoveNext(locator)) { |
| 173 | + runs.push_back(*SBScriptLocatorGetAgent(locator)); |
| 174 | + } |
| 175 | + check(runs.size() == 2 && runs[0].script == SBScriptLATN && runs[0].offset == 0 && |
| 176 | + runs[1].script == SBScriptARAB && runs[1].offset + runs[1].length == seq.stringLength, |
| 177 | + "script runs over UTF-8: Latin, then Arabic to the end"); |
| 178 | + SBScriptLocatorRelease(locator); |
| 179 | + } |
| 180 | + |
| 181 | + // ── 9. the same paragraph from UTF-16 code units ────────────────────── |
| 182 | + { |
| 183 | + const SBUInt16 utf16[] = {'a', ' ', 0x05D0, 0x05D1}; |
| 184 | + const SBCodepointSequence seq{SBStringEncodingUTF16, utf16, 4}; |
| 185 | + SBAlgorithmRef algorithm = SBAlgorithmCreate(&seq); |
| 186 | + SBParagraphRef paragraph = SBAlgorithmCreateParagraph(algorithm, 0, 4, SBLevelDefaultRTL); |
| 187 | + const SBLevel *levels = SBParagraphGetLevelsPtr(paragraph); |
| 188 | + check(SBParagraphGetBaseLevel(paragraph) == 0 && levels[0] == 0 && levels[2] == 1, |
| 189 | + "UTF-16: base 0 (first strong is 'a'), Hebrew at level 1"); |
| 190 | + SBParagraphRelease(paragraph); |
| 191 | + SBAlgorithmRelease(algorithm); |
| 192 | + } |
| 193 | + |
| 194 | + std::printf("\n%d check(s) failed\n", failures); |
| 195 | + return failures == 0 ? 0 : 1; |
| 196 | +} |
0 commit comments