BiDiAlgorithm.cs 61 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722
  1. // Copyright (c) Six Labors.
  2. // Licensed under the Apache License, Version 2.0.
  3. // Ported from: https://github.com/SixLabors/Fonts/
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Runtime.CompilerServices;
  7. using System.Threading;
  8. using Avalonia.Utilities;
  9. namespace Avalonia.Media.TextFormatting.Unicode
  10. {
  11. /// <summary>
  12. /// Implementation of Unicode bidirectional algorithm (UAX #9)
  13. /// https://unicode.org/reports/tr9/
  14. /// </summary>
  15. /// <remarks>
  16. /// <para>
  17. /// The Bidi algorithm uses a number of memory arrays for resolved
  18. /// types, level information, bracket types, x9 removal maps and
  19. /// more...
  20. /// </para>
  21. /// <para>
  22. /// This implementation of the BiDi algorithm has been designed
  23. /// to reduce memory pressure on the GC by re-using the same
  24. /// work buffers, so instances of this class should be re-used
  25. /// as much as possible.
  26. /// </para>
  27. /// </remarks>
  28. internal sealed class BidiAlgorithm
  29. {
  30. /// <summary>
  31. /// The original BiDiClass classes as provided by the caller
  32. /// </summary>
  33. private ArraySlice<BidiClass> _originalClasses;
  34. /// <summary>
  35. /// Paired bracket types as provided by caller
  36. /// </summary>
  37. private ArraySlice<BidiPairedBracketType> _pairedBracketTypes;
  38. /// <summary>
  39. /// Paired bracket values as provided by caller
  40. /// </summary>
  41. private ArraySlice<int> _pairedBracketValues;
  42. /// <summary>
  43. /// Try if the incoming data is known to contain brackets
  44. /// </summary>
  45. private bool _hasBrackets;
  46. /// <summary>
  47. /// True if the incoming data is known to contain embedding runs
  48. /// </summary>
  49. private bool _hasEmbeddings;
  50. /// <summary>
  51. /// True if the incoming data is known to contain isolating runs
  52. /// </summary>
  53. private bool _hasIsolates;
  54. /// <summary>
  55. /// Two directional mapping of isolate start/end pairs
  56. /// </summary>
  57. /// <remarks>
  58. /// The forward mapping maps the start index to the end index.
  59. /// The reverse mapping maps the end index to the start index.
  60. /// </remarks>
  61. private readonly Dictionary<int, int> _isolatePairs = new Dictionary<int, int>();
  62. /// <summary>
  63. /// The working BiDi classes
  64. /// </summary>
  65. private ArraySlice<BidiClass> _workingClasses;
  66. /// <summary>
  67. /// The working classes buffer
  68. /// </summary>
  69. private ArrayBuilder<BidiClass> _workingClassesBuffer;
  70. /// <summary>
  71. /// A slice of the resolved levels
  72. /// </summary>
  73. private ArraySlice<sbyte> _resolvedLevels;
  74. /// <summary>
  75. /// The buffer underlying resolvedLevels
  76. /// </summary>
  77. private ArrayBuilder<sbyte> _resolvedLevelsBuffer;
  78. /// <summary>
  79. /// The resolve paragraph embedding level
  80. /// </summary>
  81. private sbyte _paragraphEmbeddingLevel;
  82. /// <summary>
  83. /// The status stack used during resolution of explicit
  84. /// embedding and isolating runs
  85. /// </summary>
  86. private readonly Stack<Status> _statusStack = new Stack<Status>();
  87. /// <summary>
  88. /// Mapping used to virtually remove characters for rule X9
  89. /// </summary>
  90. private ArrayBuilder<int> _x9Map;
  91. /// <summary>
  92. /// Re-usable list of level runs
  93. /// </summary>
  94. private readonly List<LevelRun> _levelRuns = new List<LevelRun>();
  95. /// <summary>
  96. /// Mapping for the current isolating sequence, built
  97. /// by joining level runs from the x9 map.
  98. /// </summary>
  99. private ArrayBuilder<int> _isolatedRunMapping;
  100. /// <summary>
  101. /// A stack of pending isolate openings used by FindIsolatePairs()
  102. /// </summary>
  103. private readonly Stack<int> _pendingIsolateOpenings = new Stack<int>();
  104. /// <summary>
  105. /// The level of the isolating run currently being processed
  106. /// </summary>
  107. private int _runLevel;
  108. /// <summary>
  109. /// The direction of the isolating run currently being processed
  110. /// </summary>
  111. private BidiClass _runDirection;
  112. /// <summary>
  113. /// The length of the isolating run currently being processed
  114. /// </summary>
  115. private int _runLength;
  116. /// <summary>
  117. /// A mapped slice of the resolved types for the isolating run currently
  118. /// being processed
  119. /// </summary>
  120. private MappedArraySlice<BidiClass> _runResolvedClasses;
  121. /// <summary>
  122. /// A mapped slice of the original types for the isolating run currently
  123. /// being processed
  124. /// </summary>
  125. private MappedArraySlice<BidiClass> _runOriginalClasses;
  126. /// <summary>
  127. /// A mapped slice of the run levels for the isolating run currently
  128. /// being processed
  129. /// </summary>
  130. private MappedArraySlice<sbyte> _runLevels;
  131. /// <summary>
  132. /// A mapped slice of the paired bracket types of the isolating
  133. /// run currently being processed
  134. /// </summary>
  135. private MappedArraySlice<BidiPairedBracketType> _runBiDiPairedBracketTypes;
  136. /// <summary>
  137. /// A mapped slice of the paired bracket values of the isolating
  138. /// run currently being processed
  139. /// </summary>
  140. private MappedArraySlice<int> _runPairedBracketValues;
  141. /// <summary>
  142. /// Maximum pairing depth for paired brackets
  143. /// </summary>
  144. private const int MaxPairedBracketDepth = 63;
  145. /// <summary>
  146. /// Reusable list of pending opening brackets used by the
  147. /// LocatePairedBrackets method
  148. /// </summary>
  149. private readonly List<int> _pendingOpeningBrackets = new List<int>();
  150. /// <summary>
  151. /// Resolved list of paired brackets
  152. /// </summary>
  153. private readonly List<BracketPair> _pairedBrackets = new List<BracketPair>();
  154. /// <summary>
  155. /// Initializes a new instance of the <see cref="BidiAlgorithm"/> class.
  156. /// </summary>
  157. internal BidiAlgorithm()
  158. {
  159. }
  160. /// <summary>
  161. /// Gets a per-thread instance that can be re-used as often
  162. /// as necessary.
  163. /// </summary>
  164. public static ThreadLocal<BidiAlgorithm> Instance { get; } = new ThreadLocal<BidiAlgorithm>(() => new BidiAlgorithm());
  165. /// <summary>
  166. /// Gets the resolved levels.
  167. /// </summary>
  168. public ArraySlice<sbyte> ResolvedLevels => _resolvedLevels;
  169. /// <summary>
  170. /// Gets the resolved paragraph embedding level
  171. /// </summary>
  172. public int ResolvedParagraphEmbeddingLevel => _paragraphEmbeddingLevel;
  173. /// <summary>
  174. /// Process data from a BiDiData instance
  175. /// </summary>
  176. /// <param name="data">The BiDi Unicode data.</param>
  177. public void Process(BidiData data)
  178. => Process(
  179. data.Classes,
  180. data.PairedBracketTypes,
  181. data.PairedBracketValues,
  182. data.ParagraphEmbeddingLevel,
  183. data.HasBrackets,
  184. data.HasEmbeddings,
  185. data.HasIsolates,
  186. null);
  187. /// <summary>
  188. /// Processes Bidi Data
  189. /// </summary>
  190. public void Process(
  191. ArraySlice<BidiClass> types,
  192. ArraySlice<BidiPairedBracketType> pairedBracketTypes,
  193. ArraySlice<int> pairedBracketValues,
  194. sbyte paragraphEmbeddingLevel,
  195. bool? hasBrackets,
  196. bool? hasEmbeddings,
  197. bool? hasIsolates,
  198. ArraySlice<sbyte>? outLevels)
  199. {
  200. // Reset state
  201. _isolatePairs.Clear();
  202. _workingClassesBuffer.Clear();
  203. _levelRuns.Clear();
  204. _resolvedLevelsBuffer.Clear();
  205. if (types.IsEmpty)
  206. {
  207. return;
  208. }
  209. // Setup original types and working types
  210. _originalClasses = types;
  211. _workingClasses = _workingClassesBuffer.Add(types);
  212. // Capture paired bracket values and types
  213. _pairedBracketTypes = pairedBracketTypes;
  214. _pairedBracketValues = pairedBracketValues;
  215. // Store things we know
  216. _hasBrackets = hasBrackets ?? _pairedBracketTypes.Length == _originalClasses.Length;
  217. _hasEmbeddings = hasEmbeddings ?? true;
  218. _hasIsolates = hasIsolates ?? true;
  219. // Find all isolate pairs
  220. FindIsolatePairs();
  221. // Resolve the paragraph embedding level
  222. if (paragraphEmbeddingLevel == 2)
  223. {
  224. _paragraphEmbeddingLevel = ResolveEmbeddingLevel(_originalClasses);
  225. }
  226. else
  227. {
  228. _paragraphEmbeddingLevel = paragraphEmbeddingLevel;
  229. }
  230. // Create resolved levels buffer
  231. if (outLevels.HasValue)
  232. {
  233. if (outLevels.Value.Length != _originalClasses.Length)
  234. {
  235. throw new ArgumentException("Out levels must be the same length as the input data");
  236. }
  237. _resolvedLevels = outLevels.Value;
  238. }
  239. else
  240. {
  241. _resolvedLevels = _resolvedLevelsBuffer.Add(_originalClasses.Length);
  242. _resolvedLevels.Fill(_paragraphEmbeddingLevel);
  243. }
  244. // Resolve explicit embedding levels (Rules X1-X8)
  245. ResolveExplicitEmbeddingLevels();
  246. // Build the rule X9 map
  247. BuildX9RemovalMap();
  248. // Process all isolated run sequences
  249. ProcessIsolatedRunSequences();
  250. // Reset whitespace levels
  251. ResetWhitespaceLevels();
  252. // Clean up
  253. AssignLevelsToCodePointsRemovedByX9();
  254. }
  255. /// <summary>
  256. /// Resolve the paragraph embedding level if not explicitly passed
  257. /// by the caller. Also used by rule X5c for FSI isolating sequences.
  258. /// </summary>
  259. /// <param name="data">The data to be evaluated</param>
  260. /// <returns>The resolved embedding level</returns>
  261. public sbyte ResolveEmbeddingLevel(ArraySlice<BidiClass> data)
  262. {
  263. // P2
  264. for (var i = 0; i < data.Length; ++i)
  265. {
  266. switch (data[i])
  267. {
  268. case BidiClass.LeftToRight:
  269. // P3
  270. return 0;
  271. case BidiClass.ArabicLetter:
  272. case BidiClass.RightToLeft:
  273. // P3
  274. return 1;
  275. case BidiClass.FirstStrongIsolate:
  276. case BidiClass.LeftToRightIsolate:
  277. case BidiClass.RightToLeftIsolate:
  278. // Skip isolate pairs
  279. // (Because we're working with a slice, we need to adjust the indices
  280. // we're using for the isolatePairs map)
  281. if (_isolatePairs.TryGetValue(data.Start + i, out i))
  282. {
  283. i -= data.Start;
  284. }
  285. else
  286. {
  287. i = data.Length;
  288. }
  289. break;
  290. }
  291. }
  292. // P3
  293. return 0;
  294. }
  295. /// <summary>
  296. /// Build a list of matching isolates for a directionality slice
  297. /// Implements BD9
  298. /// </summary>
  299. private void FindIsolatePairs()
  300. {
  301. // Redundant?
  302. if (!_hasIsolates)
  303. {
  304. return;
  305. }
  306. // Lets double check this as we go and clear the flag
  307. // if there actually aren't any isolate pairs as this might
  308. // mean we can skip some later steps
  309. _hasIsolates = false;
  310. // BD9...
  311. _pendingIsolateOpenings.Clear();
  312. for (var i = 0; i < _originalClasses.Length; i++)
  313. {
  314. var t = _originalClasses[i];
  315. switch (t)
  316. {
  317. case BidiClass.LeftToRightIsolate:
  318. case BidiClass.RightToLeftIsolate:
  319. case BidiClass.FirstStrongIsolate:
  320. {
  321. _pendingIsolateOpenings.Push(i);
  322. _hasIsolates = true;
  323. break;
  324. }
  325. case BidiClass.PopDirectionalIsolate:
  326. {
  327. if (_pendingIsolateOpenings.Count > 0)
  328. {
  329. _isolatePairs.Add(_pendingIsolateOpenings.Pop(), i);
  330. }
  331. _hasIsolates = true;
  332. break;
  333. }
  334. }
  335. }
  336. }
  337. /// <summary>
  338. /// Resolve the explicit embedding levels from the original
  339. /// data. Implements rules X1 to X8.
  340. /// </summary>
  341. private void ResolveExplicitEmbeddingLevels()
  342. {
  343. // Redundant?
  344. if (!_hasIsolates && !_hasEmbeddings)
  345. {
  346. return;
  347. }
  348. // Work variables
  349. _statusStack.Clear();
  350. var overflowIsolateCount = 0;
  351. var overflowEmbeddingCount = 0;
  352. var validIsolateCount = 0;
  353. // Constants
  354. const int maxStackDepth = 125;
  355. // Rule X1 - setup initial state
  356. _statusStack.Clear();
  357. // Neutral
  358. _statusStack.Push(new Status(_paragraphEmbeddingLevel, BidiClass.OtherNeutral, false));
  359. // Process all characters
  360. for (var i = 0; i < _originalClasses.Length; i++)
  361. {
  362. switch (_originalClasses[i])
  363. {
  364. case BidiClass.RightToLeftEmbedding:
  365. {
  366. // Rule X2
  367. var newLevel = (sbyte)((_statusStack.Peek().EmbeddingLevel + 1) | 1);
  368. if (newLevel <= maxStackDepth && overflowIsolateCount == 0 && overflowEmbeddingCount == 0)
  369. {
  370. _statusStack.Push(new Status(newLevel, BidiClass.OtherNeutral, false));
  371. _resolvedLevels[i] = newLevel;
  372. }
  373. else if (overflowIsolateCount == 0)
  374. {
  375. overflowEmbeddingCount++;
  376. }
  377. break;
  378. }
  379. case BidiClass.LeftToRightEmbedding:
  380. {
  381. // Rule X3
  382. var newLevel = (sbyte)((_statusStack.Peek().EmbeddingLevel + 2) & ~1);
  383. if (newLevel < maxStackDepth && overflowIsolateCount == 0 && overflowEmbeddingCount == 0)
  384. {
  385. _statusStack.Push(new Status(newLevel, BidiClass.OtherNeutral, false));
  386. _resolvedLevels[i] = newLevel;
  387. }
  388. else if (overflowIsolateCount == 0)
  389. {
  390. overflowEmbeddingCount++;
  391. }
  392. break;
  393. }
  394. case BidiClass.RightToLeftOverride:
  395. {
  396. // Rule X4
  397. var newLevel = (sbyte)((_statusStack.Peek().EmbeddingLevel + 1) | 1);
  398. if (newLevel <= maxStackDepth && overflowIsolateCount == 0 && overflowEmbeddingCount == 0)
  399. {
  400. _statusStack.Push(new Status(newLevel, BidiClass.RightToLeft, false));
  401. _resolvedLevels[i] = newLevel;
  402. }
  403. else if (overflowIsolateCount == 0)
  404. {
  405. overflowEmbeddingCount++;
  406. }
  407. break;
  408. }
  409. case BidiClass.LeftToRightOverride:
  410. {
  411. // Rule X5
  412. var newLevel = (sbyte)((_statusStack.Peek().EmbeddingLevel + 2) & ~1);
  413. if (newLevel <= maxStackDepth && overflowIsolateCount == 0 && overflowEmbeddingCount == 0)
  414. {
  415. _statusStack.Push(new Status(newLevel, BidiClass.LeftToRight, false));
  416. _resolvedLevels[i] = newLevel;
  417. }
  418. else if (overflowIsolateCount == 0)
  419. {
  420. overflowEmbeddingCount++;
  421. }
  422. break;
  423. }
  424. case BidiClass.RightToLeftIsolate:
  425. case BidiClass.LeftToRightIsolate:
  426. case BidiClass.FirstStrongIsolate:
  427. {
  428. // Rule X5a, X5b and X5c
  429. var resolvedIsolate = _originalClasses[i];
  430. if (resolvedIsolate == BidiClass.FirstStrongIsolate)
  431. {
  432. if (!_isolatePairs.TryGetValue(i, out var endOfIsolate))
  433. {
  434. endOfIsolate = _originalClasses.Length;
  435. }
  436. // Rule X5c
  437. if (ResolveEmbeddingLevel(_originalClasses.Slice(i + 1,endOfIsolate - (i + 1))) == 1)
  438. {
  439. resolvedIsolate = BidiClass.RightToLeftIsolate;
  440. }
  441. else
  442. {
  443. resolvedIsolate = BidiClass.LeftToRightIsolate;
  444. }
  445. }
  446. // Replace RLI's level with current embedding level
  447. var tos = _statusStack.Peek();
  448. _resolvedLevels[i] = tos.EmbeddingLevel;
  449. // Apply override
  450. if (tos.OverrideStatus != BidiClass.OtherNeutral)
  451. {
  452. _workingClasses[i] = tos.OverrideStatus;
  453. }
  454. // Work out new level
  455. sbyte newLevel;
  456. if (resolvedIsolate == BidiClass.RightToLeftIsolate)
  457. {
  458. newLevel = (sbyte)((tos.EmbeddingLevel + 1) | 1);
  459. }
  460. else
  461. {
  462. newLevel = (sbyte)((tos.EmbeddingLevel + 2) & ~1);
  463. }
  464. // Valid?
  465. if (newLevel <= maxStackDepth && overflowIsolateCount == 0 && overflowEmbeddingCount == 0)
  466. {
  467. validIsolateCount++;
  468. _statusStack.Push(new Status(newLevel, BidiClass.OtherNeutral, true));
  469. }
  470. else
  471. {
  472. overflowIsolateCount++;
  473. }
  474. break;
  475. }
  476. case BidiClass.BoundaryNeutral:
  477. {
  478. // Mentioned in rule X6 - "for all types besides ..., BN, ..."
  479. // no-op
  480. break;
  481. }
  482. default:
  483. {
  484. // Rule X6
  485. var tos = _statusStack.Peek();
  486. _resolvedLevels[i] = tos.EmbeddingLevel;
  487. if (tos.OverrideStatus != BidiClass.OtherNeutral)
  488. {
  489. _workingClasses[i] = tos.OverrideStatus;
  490. }
  491. break;
  492. }
  493. case BidiClass.PopDirectionalIsolate:
  494. {
  495. // Rule X6a
  496. if (overflowIsolateCount > 0)
  497. {
  498. overflowIsolateCount--;
  499. }
  500. else if (validIsolateCount != 0)
  501. {
  502. overflowEmbeddingCount = 0;
  503. while (!_statusStack.Peek().IsolateStatus)
  504. {
  505. _statusStack.Pop();
  506. }
  507. _statusStack.Pop();
  508. validIsolateCount--;
  509. }
  510. var tos = _statusStack.Peek();
  511. _resolvedLevels[i] = tos.EmbeddingLevel;
  512. if (tos.OverrideStatus != BidiClass.OtherNeutral)
  513. {
  514. _workingClasses[i] = tos.OverrideStatus;
  515. }
  516. break;
  517. }
  518. case BidiClass.PopDirectionalFormat:
  519. {
  520. // Rule X7
  521. if (overflowIsolateCount == 0)
  522. {
  523. if (overflowEmbeddingCount > 0)
  524. {
  525. overflowEmbeddingCount--;
  526. }
  527. else if (!_statusStack.Peek().IsolateStatus && _statusStack.Count >= 2)
  528. {
  529. _statusStack.Pop();
  530. }
  531. }
  532. break;
  533. }
  534. case BidiClass.ParagraphSeparator:
  535. {
  536. // Rule X8
  537. _resolvedLevels[i] = _paragraphEmbeddingLevel;
  538. break;
  539. }
  540. }
  541. }
  542. }
  543. /// <summary>
  544. /// Build a map to the original data positions that excludes all
  545. /// the types defined by rule X9
  546. /// </summary>
  547. private void BuildX9RemovalMap()
  548. {
  549. // Reserve room for the x9 map
  550. _x9Map.Length = _originalClasses.Length;
  551. if (_hasEmbeddings || _hasIsolates)
  552. {
  553. // Build a map the removes all x9 characters
  554. var j = 0;
  555. for (var i = 0; i < _originalClasses.Length; i++)
  556. {
  557. if (!IsRemovedByX9(_originalClasses[i]))
  558. {
  559. _x9Map[j++] = i;
  560. }
  561. }
  562. // Set the final length
  563. _x9Map.Length = j;
  564. }
  565. else
  566. {
  567. for (int i = 0, count = _originalClasses.Length; i < count; i++)
  568. {
  569. _x9Map[i] = i;
  570. }
  571. }
  572. }
  573. /// <summary>
  574. /// Find the original character index for an entry in the X9 map
  575. /// </summary>
  576. /// <param name="index">Index in the x9 removal map</param>
  577. /// <returns>Index to the original data</returns>
  578. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  579. private int MapX9(int index) => _x9Map[index];
  580. /// <summary>
  581. /// Add a new level run
  582. /// </summary>
  583. /// <remarks>
  584. /// This method resolves the sos and eos values for the run
  585. /// and adds the run to the list
  586. /// /// </remarks>
  587. /// <param name="start">The index of the start of the run (in x9 removed units)</param>
  588. /// <param name="length">The length of the run (in x9 removed units)</param>
  589. /// <param name="level">The level of the run</param>
  590. private void AddLevelRun(int start, int length, int level)
  591. {
  592. // Get original indices to first and last character in this run
  593. var firstCharIndex = MapX9(start);
  594. var lastCharIndex = MapX9(start + length - 1);
  595. // Work out sos
  596. var i = firstCharIndex - 1;
  597. while (i >= 0 && IsRemovedByX9(_originalClasses[i]))
  598. {
  599. i--;
  600. }
  601. var prevLevel = i < 0 ? _paragraphEmbeddingLevel : _resolvedLevels[i];
  602. var sos = DirectionFromLevel(Math.Max(prevLevel, level));
  603. // Work out eos
  604. var lastType = _workingClasses[lastCharIndex];
  605. int nextLevel;
  606. switch (lastType)
  607. {
  608. case BidiClass.LeftToRightIsolate:
  609. case BidiClass.RightToLeftIsolate:
  610. case BidiClass.FirstStrongIsolate:
  611. {
  612. nextLevel = _paragraphEmbeddingLevel;
  613. break;
  614. }
  615. default:
  616. {
  617. i = lastCharIndex + 1;
  618. while (i < _originalClasses.Length && IsRemovedByX9(_originalClasses[i]))
  619. {
  620. i++;
  621. }
  622. nextLevel = i >= _originalClasses.Length ? _paragraphEmbeddingLevel : _resolvedLevels[i];
  623. break;
  624. }
  625. }
  626. var eos = DirectionFromLevel(Math.Max(nextLevel, level));
  627. // Add the run
  628. _levelRuns.Add(new LevelRun(start, length, level, sos, eos));
  629. }
  630. /// <summary>
  631. /// Find all runs of the same level, populating the _levelRuns
  632. /// collection
  633. /// </summary>
  634. private void FindLevelRuns()
  635. {
  636. var currentLevel = -1;
  637. var runStart = 0;
  638. for (var i = 0; i < _x9Map.Length; ++i)
  639. {
  640. int level = _resolvedLevels[MapX9(i)];
  641. if (level == currentLevel)
  642. {
  643. continue;
  644. }
  645. if (currentLevel != -1)
  646. {
  647. AddLevelRun(runStart, i - runStart, currentLevel);
  648. }
  649. currentLevel = level;
  650. runStart = i;
  651. }
  652. // Don't forget the final level run
  653. if (currentLevel != -1)
  654. {
  655. AddLevelRun(runStart, _x9Map.Length - runStart, currentLevel);
  656. }
  657. }
  658. /// <summary>
  659. /// Given a character index, find the level run that starts at that position
  660. /// </summary>
  661. /// <param name="index">The index into the original (unmapped) data</param>
  662. /// <returns>The index of the run that starts at that index</returns>
  663. private int FindRunForIndex(int index)
  664. {
  665. for (var i = 0; i < _levelRuns.Count; i++)
  666. {
  667. // Passed index is for the original non-x9 filtered data, however
  668. // the level run ranges are for the x9 filtered data. Convert before
  669. // comparing
  670. if (MapX9(_levelRuns[i].Start) == index)
  671. {
  672. return i;
  673. }
  674. }
  675. throw new InvalidOperationException("Internal error");
  676. }
  677. /// <summary>
  678. /// Determine and the process all isolated run sequences
  679. /// </summary>
  680. private void ProcessIsolatedRunSequences()
  681. {
  682. // Find all runs with the same level
  683. FindLevelRuns();
  684. // Process them one at a time by first building
  685. // a mapping using slices from the x9 map for each
  686. // run section that needs to be joined together to
  687. // form an complete run. That full run mapping
  688. // will be placed in _isolatedRunMapping and then
  689. // processed by ProcessIsolatedRunSequence().
  690. while (_levelRuns.Count > 0)
  691. {
  692. // Clear the mapping
  693. _isolatedRunMapping.Clear();
  694. // Combine mappings from this run and all runs that continue on from it
  695. var runIndex = 0;
  696. BidiClass eos;
  697. var sos = _levelRuns[0].Sos;
  698. var level = _levelRuns[0].Level;
  699. while (true)
  700. {
  701. // Get the run
  702. var r = _levelRuns[runIndex];
  703. // The eos of the isolating run is the eos of the
  704. // last level run that comprises it.
  705. eos = r.Eos;
  706. // Remove this run as we've now processed it
  707. _levelRuns.RemoveAt(runIndex);
  708. // Add the x9 map indices for the run range to the mapping
  709. // for this isolated run
  710. _isolatedRunMapping.Add(_x9Map.AsSlice(r.Start, r.Length));
  711. // Get the last character and see if it's an isolating run with a matching
  712. // PDI and concatenate that run to this one
  713. var lastCharacterIndex = _isolatedRunMapping[_isolatedRunMapping.Length - 1];
  714. var lastType = _originalClasses[lastCharacterIndex];
  715. if ((lastType == BidiClass.LeftToRightIsolate || lastType == BidiClass.RightToLeftIsolate || lastType == BidiClass.FirstStrongIsolate) &&
  716. _isolatePairs.TryGetValue(lastCharacterIndex, out var nextRunIndex))
  717. {
  718. // Find the continuing run index
  719. runIndex = FindRunForIndex(nextRunIndex);
  720. }
  721. else
  722. {
  723. break;
  724. }
  725. }
  726. // Process this isolated run
  727. ProcessIsolatedRunSequence(sos, eos, level);
  728. }
  729. }
  730. /// <summary>
  731. /// Process a single isolated run sequence, where the character sequence
  732. /// mapping is currently held in _isolatedRunMapping.
  733. /// </summary>
  734. private void ProcessIsolatedRunSequence(BidiClass sos, BidiClass eos, int runLevel)
  735. {
  736. // Create mappings onto the underlying data
  737. _runResolvedClasses = new MappedArraySlice<BidiClass>(_workingClasses, _isolatedRunMapping.AsSlice());
  738. _runOriginalClasses = new MappedArraySlice<BidiClass>(_originalClasses, _isolatedRunMapping.AsSlice());
  739. _runLevels = new MappedArraySlice<sbyte>(_resolvedLevels, _isolatedRunMapping.AsSlice());
  740. if (_hasBrackets)
  741. {
  742. _runBiDiPairedBracketTypes = new MappedArraySlice<BidiPairedBracketType>(_pairedBracketTypes, _isolatedRunMapping.AsSlice());
  743. _runPairedBracketValues = new MappedArraySlice<int>(_pairedBracketValues, _isolatedRunMapping.AsSlice());
  744. }
  745. _runLevel = runLevel;
  746. _runDirection = DirectionFromLevel(runLevel);
  747. _runLength = _runResolvedClasses.Length;
  748. // By tracking the types of characters known to be in the current run, we can
  749. // skip some of the rules that we know won't apply. The flags will be
  750. // initialized while we're processing rule W1 below.
  751. var hasEN = false;
  752. var hasAL = false;
  753. var hasES = false;
  754. var hasCS = false;
  755. var hasAN = false;
  756. var hasET = false;
  757. // Rule W1
  758. // Also, set hasXX flags
  759. int i;
  760. var previousClass = sos;
  761. for (i = 0; i < _runLength; i++)
  762. {
  763. var resolvedClass = _runResolvedClasses[i];
  764. switch (resolvedClass)
  765. {
  766. case BidiClass.NonspacingMark:
  767. _runResolvedClasses[i] = previousClass;
  768. break;
  769. case BidiClass.LeftToRightIsolate:
  770. case BidiClass.RightToLeftIsolate:
  771. case BidiClass.FirstStrongIsolate:
  772. case BidiClass.PopDirectionalIsolate:
  773. previousClass = BidiClass.OtherNeutral;
  774. break;
  775. case BidiClass.EuropeanNumber:
  776. hasEN = true;
  777. previousClass = resolvedClass;
  778. break;
  779. case BidiClass.ArabicLetter:
  780. hasAL = true;
  781. previousClass = resolvedClass;
  782. break;
  783. case BidiClass.EuropeanSeparator:
  784. hasES = true;
  785. previousClass = resolvedClass;
  786. break;
  787. case BidiClass.CommonSeparator:
  788. hasCS = true;
  789. previousClass = resolvedClass;
  790. break;
  791. case BidiClass.ArabicNumber:
  792. hasAN = true;
  793. previousClass = resolvedClass;
  794. break;
  795. case BidiClass.EuropeanTerminator:
  796. hasET = true;
  797. previousClass = resolvedClass;
  798. break;
  799. default:
  800. previousClass = resolvedClass;
  801. break;
  802. }
  803. }
  804. // Rule W2
  805. if (hasEN)
  806. {
  807. for (i = 0; i < _runLength; i++)
  808. {
  809. if (_runResolvedClasses[i] != BidiClass.EuropeanNumber)
  810. {
  811. continue;
  812. }
  813. for (var j = i - 1; j >= 0; j--)
  814. {
  815. var resolvedClass = _runResolvedClasses[j];
  816. switch (resolvedClass)
  817. {
  818. case BidiClass.LeftToRight:
  819. case BidiClass.RightToLeft:
  820. case BidiClass.ArabicLetter:
  821. {
  822. if (resolvedClass == BidiClass.ArabicLetter)
  823. {
  824. _runResolvedClasses[i] = BidiClass.ArabicNumber;
  825. hasAN = true;
  826. }
  827. j = -1;
  828. break;
  829. }
  830. }
  831. }
  832. }
  833. }
  834. // Rule W3
  835. if (hasAL)
  836. {
  837. for (i = 0; i < _runLength; i++)
  838. {
  839. if (_runResolvedClasses[i] == BidiClass.ArabicLetter)
  840. {
  841. _runResolvedClasses[i] = BidiClass.RightToLeft;
  842. }
  843. }
  844. }
  845. // Rule W4
  846. if ((hasES || hasCS) && (hasEN || hasAN))
  847. {
  848. for (i = 1; i < _runLength - 1; ++i)
  849. {
  850. ref var resolvedClass = ref _runResolvedClasses[i];
  851. if (resolvedClass == BidiClass.EuropeanSeparator)
  852. {
  853. var previousSeparatorClass = _runResolvedClasses[i - 1];
  854. var nextSeparatorClass = _runResolvedClasses[i + 1];
  855. if (previousSeparatorClass == BidiClass.EuropeanNumber && nextSeparatorClass == BidiClass.EuropeanNumber)
  856. {
  857. // ES between EN and EN
  858. resolvedClass = BidiClass.EuropeanNumber;
  859. }
  860. }
  861. else if (resolvedClass == BidiClass.CommonSeparator)
  862. {
  863. var previousSeparatorClass = _runResolvedClasses[i - 1];
  864. var nextSeparatorClass = _runResolvedClasses[i + 1];
  865. if ((previousSeparatorClass == BidiClass.ArabicNumber && nextSeparatorClass == BidiClass.ArabicNumber) ||
  866. (previousSeparatorClass == BidiClass.EuropeanNumber && nextSeparatorClass == BidiClass.EuropeanNumber))
  867. {
  868. // CS between (AN and AN) or (EN and EN)
  869. resolvedClass = previousSeparatorClass;
  870. }
  871. }
  872. }
  873. }
  874. // Rule W5
  875. if (hasET && hasEN)
  876. {
  877. for (i = 0; i < _runLength; ++i)
  878. {
  879. if (_runResolvedClasses[i] != BidiClass.EuropeanTerminator)
  880. {
  881. continue;
  882. }
  883. // Locate end of sequence
  884. var sequenceStart = i;
  885. var sequenceEnd = i;
  886. while (sequenceEnd < _runLength && _runResolvedClasses[sequenceEnd] == BidiClass.EuropeanTerminator)
  887. {
  888. sequenceEnd++;
  889. }
  890. // Preceded by, or followed by EN?
  891. if ((sequenceStart == 0 ? sos : _runResolvedClasses[sequenceStart - 1]) == BidiClass.EuropeanNumber
  892. || (sequenceEnd == _runLength ? eos : _runResolvedClasses[sequenceEnd]) == BidiClass.EuropeanNumber)
  893. {
  894. // Change the entire range
  895. for (var j = sequenceStart; i < sequenceEnd; ++i)
  896. {
  897. _runResolvedClasses[i] = BidiClass.EuropeanNumber;
  898. }
  899. }
  900. // continue at end of sequence
  901. i = sequenceEnd;
  902. }
  903. }
  904. // Rule W6
  905. if (hasES || hasET || hasCS)
  906. {
  907. for (i = 0; i < _runLength; ++i)
  908. {
  909. ref var resolvedClass = ref _runResolvedClasses[i];
  910. switch (resolvedClass)
  911. {
  912. case BidiClass.EuropeanSeparator:
  913. case BidiClass.EuropeanTerminator:
  914. case BidiClass.CommonSeparator:
  915. {
  916. resolvedClass = BidiClass.OtherNeutral;
  917. break;
  918. }
  919. }
  920. }
  921. }
  922. // Rule W7.
  923. if (hasEN)
  924. {
  925. var previousStrongClass = sos;
  926. for (i = 0; i < _runLength; ++i)
  927. {
  928. ref var resolvedClass = ref _runResolvedClasses[i];
  929. switch (resolvedClass)
  930. {
  931. case BidiClass.EuropeanNumber:
  932. {
  933. // If prev strong type was an L change this to L too
  934. if (previousStrongClass == BidiClass.LeftToRight)
  935. {
  936. _runResolvedClasses[i] = BidiClass.LeftToRight;
  937. }
  938. break;
  939. }
  940. case BidiClass.LeftToRight:
  941. case BidiClass.RightToLeft:
  942. {
  943. // Remember previous strong type (NB: AL should already be changed to R)
  944. previousStrongClass = resolvedClass;
  945. break;
  946. }
  947. }
  948. }
  949. }
  950. // Rule N0 - process bracket pairs
  951. if (_hasBrackets)
  952. {
  953. int count;
  954. var pairedBrackets = LocatePairedBrackets();
  955. for (i = 0, count = pairedBrackets.Count; i < count; i++)
  956. {
  957. var pairedBracket = pairedBrackets[i];
  958. var strongDirection = InspectPairedBracket(pairedBracket);
  959. // Case "d" - no strong types in the brackets, ignore
  960. if (strongDirection == BidiClass.OtherNeutral)
  961. {
  962. continue;
  963. }
  964. // Case "b" - strong type found that matches the embedding direction
  965. if ((strongDirection == BidiClass.LeftToRight || strongDirection == BidiClass.RightToLeft) && strongDirection == _runDirection)
  966. {
  967. SetPairedBracketDirection(pairedBracket, strongDirection);
  968. continue;
  969. }
  970. // Case "c" - found opposite strong type found, look before to establish context
  971. strongDirection = InspectBeforePairedBracket(pairedBracket, sos);
  972. if (strongDirection == _runDirection || strongDirection == BidiClass.OtherNeutral)
  973. {
  974. strongDirection = _runDirection;
  975. }
  976. SetPairedBracketDirection(pairedBracket, strongDirection);
  977. }
  978. }
  979. // Rules N1 and N2 - resolve neutral types
  980. for (i = 0; i < _runLength; ++i)
  981. {
  982. var resolvedClass = _runResolvedClasses[i];
  983. if (IsNeutralClass(resolvedClass))
  984. {
  985. // Locate end of sequence
  986. var seqStart = i;
  987. var seqEnd = i;
  988. while (seqEnd < _runLength && IsNeutralClass(_runResolvedClasses[seqEnd]))
  989. {
  990. seqEnd++;
  991. }
  992. // Work out the preceding class
  993. BidiClass classBefore;
  994. if (seqStart == 0)
  995. {
  996. classBefore = sos;
  997. }
  998. else
  999. {
  1000. classBefore = _runResolvedClasses[seqStart - 1];
  1001. switch (classBefore)
  1002. {
  1003. case BidiClass.ArabicNumber:
  1004. case BidiClass.EuropeanNumber:
  1005. {
  1006. classBefore = BidiClass.RightToLeft;
  1007. break;
  1008. }
  1009. }
  1010. }
  1011. // Work out the following class
  1012. BidiClass classAfter;
  1013. if (seqEnd == _runLength)
  1014. {
  1015. classAfter = eos;
  1016. }
  1017. else
  1018. {
  1019. classAfter = _runResolvedClasses[seqEnd];
  1020. switch (classAfter)
  1021. {
  1022. case BidiClass.ArabicNumber:
  1023. case BidiClass.EuropeanNumber:
  1024. {
  1025. classAfter = BidiClass.RightToLeft;
  1026. break;
  1027. }
  1028. }
  1029. }
  1030. // Work out the final resolved type
  1031. BidiClass finalResolveClass;
  1032. if (classBefore == classAfter)
  1033. {
  1034. // Rule N1
  1035. finalResolveClass = classBefore;
  1036. }
  1037. else
  1038. {
  1039. // Rule N2
  1040. finalResolveClass = _runDirection;
  1041. }
  1042. // Apply changes
  1043. for (var j = seqStart; j < seqEnd; j++)
  1044. {
  1045. _runResolvedClasses[j] = finalResolveClass;
  1046. }
  1047. // continue after this run
  1048. i = seqEnd;
  1049. }
  1050. }
  1051. // Rules I1 and I2 - resolve implicit types
  1052. if ((_runLevel & 0x01) == 0)
  1053. {
  1054. // Rule I1 - even
  1055. for (i = 0; i < _runLength; i++)
  1056. {
  1057. var resolvedClass = _runResolvedClasses[i];
  1058. ref var currentRunLevel = ref _runLevels[i];
  1059. switch (resolvedClass)
  1060. {
  1061. case BidiClass.RightToLeft:
  1062. {
  1063. currentRunLevel++;
  1064. break;
  1065. }
  1066. case BidiClass.ArabicNumber:
  1067. case BidiClass.EuropeanNumber:
  1068. {
  1069. currentRunLevel += 2;
  1070. break;
  1071. }
  1072. }
  1073. }
  1074. }
  1075. else
  1076. {
  1077. // Rule I2 - odd
  1078. for (i = 0; i < _runLength; i++)
  1079. {
  1080. var resolvedClass = _runResolvedClasses[i];
  1081. ref var currentRunLevel = ref _runLevels[i];
  1082. if (resolvedClass != BidiClass.RightToLeft)
  1083. {
  1084. currentRunLevel++;
  1085. }
  1086. }
  1087. }
  1088. }
  1089. /// <summary>
  1090. /// Locate all pair brackets in the current isolating run
  1091. /// </summary>
  1092. /// <returns>A sorted list of BracketPairs</returns>
  1093. private List<BracketPair> LocatePairedBrackets()
  1094. {
  1095. // Clear work collections
  1096. _pendingOpeningBrackets.Clear();
  1097. _pairedBrackets.Clear();
  1098. // Since List.Sort is expensive on memory if called often (it internally
  1099. // allocates an ArraySorted object) and since we will rarely have many
  1100. // items in this list (most paragraphs will only have a handful of bracket
  1101. // pairs - if that), we use a simple linear lookup and insert most of the
  1102. // time. If there are more that `sortLimit` paired brackets we abort th
  1103. // linear searching/inserting and using List.Sort at the end.
  1104. const int sortLimit = 8;
  1105. // Process all characters in the run, looking for paired brackets
  1106. for (int i = 0, length = _runLength; i < length; i++)
  1107. {
  1108. // Ignore non-neutral characters
  1109. if (_runResolvedClasses[i] != BidiClass.OtherNeutral)
  1110. {
  1111. continue;
  1112. }
  1113. switch (_runBiDiPairedBracketTypes[i])
  1114. {
  1115. case BidiPairedBracketType.Open:
  1116. if (_pendingOpeningBrackets.Count == MaxPairedBracketDepth)
  1117. {
  1118. goto exit;
  1119. }
  1120. _pendingOpeningBrackets.Insert(0, i);
  1121. break;
  1122. case BidiPairedBracketType.Close:
  1123. // see if there is a match
  1124. for (var j = 0; j < _pendingOpeningBrackets.Count; j++)
  1125. {
  1126. if (_runPairedBracketValues[i] != _runPairedBracketValues[_pendingOpeningBrackets[j]])
  1127. {
  1128. continue;
  1129. }
  1130. // Add this paired bracket set
  1131. var opener = _pendingOpeningBrackets[j];
  1132. if (_pairedBrackets.Count < sortLimit)
  1133. {
  1134. var ppi = 0;
  1135. while (ppi < _pairedBrackets.Count && _pairedBrackets[ppi].OpeningIndex < opener)
  1136. {
  1137. ppi++;
  1138. }
  1139. _pairedBrackets.Insert(ppi, new BracketPair(opener, i));
  1140. }
  1141. else
  1142. {
  1143. _pairedBrackets.Add(new BracketPair(opener, i));
  1144. }
  1145. // remove up to and including matched opener
  1146. _pendingOpeningBrackets.RemoveRange(0, j + 1);
  1147. break;
  1148. }
  1149. break;
  1150. }
  1151. }
  1152. exit:
  1153. // Is a sort pending?
  1154. if (_pairedBrackets.Count > sortLimit)
  1155. {
  1156. _pairedBrackets.Sort();
  1157. }
  1158. return _pairedBrackets;
  1159. }
  1160. /// <summary>
  1161. /// Inspect a paired bracket set and determine its strong direction
  1162. /// </summary>
  1163. /// <param name="bracketPair">The paired bracket to be inspected</param>
  1164. /// <returns>The direction of the bracket set content</returns>
  1165. private BidiClass InspectPairedBracket(in BracketPair bracketPair)
  1166. {
  1167. var directionFromLevel = DirectionFromLevel(_runLevel);
  1168. var directionOpposite = BidiClass.OtherNeutral;
  1169. for (var i = bracketPair.OpeningIndex + 1; i < bracketPair.ClosingIndex; i++)
  1170. {
  1171. var dir = GetStrongClassN0(_runResolvedClasses[i]);
  1172. if (dir == BidiClass.OtherNeutral)
  1173. {
  1174. continue;
  1175. }
  1176. if (dir == directionFromLevel)
  1177. {
  1178. return dir;
  1179. }
  1180. directionOpposite = dir;
  1181. }
  1182. return directionOpposite;
  1183. }
  1184. /// <summary>
  1185. /// Look for a strong type before a paired bracket
  1186. /// </summary>
  1187. /// <param name="bracketPair">The paired bracket set to be inspected</param>
  1188. /// <param name="sos">The sos in case nothing found before the bracket</param>
  1189. /// <returns>The strong direction before the brackets</returns>
  1190. private BidiClass InspectBeforePairedBracket(in BracketPair bracketPair, BidiClass sos)
  1191. {
  1192. for (var i = bracketPair.OpeningIndex - 1; i >= 0; --i)
  1193. {
  1194. var direction = GetStrongClassN0(_runResolvedClasses[i]);
  1195. if (direction != BidiClass.OtherNeutral)
  1196. {
  1197. return direction;
  1198. }
  1199. }
  1200. return sos;
  1201. }
  1202. /// <summary>
  1203. /// Sets the direction of a bracket pair, including setting the direction of
  1204. /// NSM's inside the brackets and following.
  1205. /// </summary>
  1206. /// <param name="bracketPair">The paired brackets</param>
  1207. /// <param name="direction">The resolved direction for the bracket pair</param>
  1208. private void SetPairedBracketDirection(in BracketPair bracketPair, BidiClass direction)
  1209. {
  1210. // Set the direction of the brackets
  1211. _runResolvedClasses[bracketPair.OpeningIndex] = direction;
  1212. _runResolvedClasses[bracketPair.ClosingIndex] = direction;
  1213. // Set the directionality of NSM's inside the brackets
  1214. for (var i = bracketPair.OpeningIndex + 1; i < bracketPair.ClosingIndex; i++)
  1215. {
  1216. if (_runOriginalClasses[i] == BidiClass.NonspacingMark)
  1217. {
  1218. _runOriginalClasses[i] = direction;
  1219. }
  1220. else
  1221. {
  1222. break;
  1223. }
  1224. }
  1225. // Set the directionality of NSM's following the brackets
  1226. for (var i = bracketPair.ClosingIndex + 1; i < _runLength; i++)
  1227. {
  1228. if (_runOriginalClasses[i] == BidiClass.NonspacingMark)
  1229. {
  1230. _runResolvedClasses[i] = direction;
  1231. }
  1232. else
  1233. {
  1234. break;
  1235. }
  1236. }
  1237. }
  1238. /// <summary>
  1239. /// Resets whitespace levels. Implements rule L1
  1240. /// </summary>
  1241. private void ResetWhitespaceLevels()
  1242. {
  1243. for (var i = 0; i < _resolvedLevels.Length; i++)
  1244. {
  1245. var originalClass = _originalClasses[i];
  1246. switch (originalClass)
  1247. {
  1248. case BidiClass.ParagraphSeparator:
  1249. case BidiClass.SegmentSeparator:
  1250. {
  1251. // Rule L1, clauses one and two.
  1252. _resolvedLevels[i] = _paragraphEmbeddingLevel;
  1253. // Rule L1, clause three.
  1254. for (var j = i - 1; j >= 0; --j)
  1255. {
  1256. if (IsWhitespace(_originalClasses[j]))
  1257. {
  1258. // including format codes
  1259. _resolvedLevels[j] = _paragraphEmbeddingLevel;
  1260. }
  1261. else
  1262. {
  1263. break;
  1264. }
  1265. }
  1266. break;
  1267. }
  1268. }
  1269. }
  1270. // Rule L1, clause four.
  1271. for (var j = _resolvedLevels.Length - 1; j >= 0; j--)
  1272. {
  1273. if (IsWhitespace(_originalClasses[j]))
  1274. { // including format codes
  1275. _resolvedLevels[j] = _paragraphEmbeddingLevel;
  1276. }
  1277. else
  1278. {
  1279. break;
  1280. }
  1281. }
  1282. }
  1283. /// <summary>
  1284. /// Assign levels to any characters that would be have been
  1285. /// removed by rule X9. The idea is to keep level runs together
  1286. /// that would otherwise be broken by an interfering isolate/embedding
  1287. /// control character.
  1288. /// </summary>
  1289. private void AssignLevelsToCodePointsRemovedByX9()
  1290. {
  1291. // Redundant?
  1292. if (!_hasIsolates && !_hasEmbeddings)
  1293. {
  1294. return;
  1295. }
  1296. // No-op?
  1297. if (_workingClasses.Length == 0)
  1298. {
  1299. return;
  1300. }
  1301. // Fix up first character
  1302. if (_resolvedLevels[0] < 0)
  1303. {
  1304. _resolvedLevels[0] = _paragraphEmbeddingLevel;
  1305. }
  1306. if (IsRemovedByX9(_originalClasses[0]))
  1307. {
  1308. _workingClasses[0] = _originalClasses[0];
  1309. }
  1310. for (int i = 1, length = _workingClasses.Length; i < length; i++)
  1311. {
  1312. var originalClass = _originalClasses[i];
  1313. if (IsRemovedByX9(originalClass))
  1314. {
  1315. _workingClasses[i] = originalClass;
  1316. _resolvedLevels[i] = _resolvedLevels[i - 1];
  1317. }
  1318. }
  1319. }
  1320. /// <summary>
  1321. /// Check if a directionality type represents whitespace
  1322. /// </summary>
  1323. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  1324. private static bool IsWhitespace(BidiClass biDiClass)
  1325. {
  1326. switch (biDiClass)
  1327. {
  1328. case BidiClass.LeftToRightEmbedding:
  1329. case BidiClass.RightToLeftEmbedding:
  1330. case BidiClass.LeftToRightOverride:
  1331. case BidiClass.RightToLeftOverride:
  1332. case BidiClass.PopDirectionalFormat:
  1333. case BidiClass.LeftToRightIsolate:
  1334. case BidiClass.RightToLeftIsolate:
  1335. case BidiClass.FirstStrongIsolate:
  1336. case BidiClass.PopDirectionalIsolate:
  1337. case BidiClass.BoundaryNeutral:
  1338. case BidiClass.WhiteSpace:
  1339. return true;
  1340. default:
  1341. return false;
  1342. }
  1343. }
  1344. /// <summary>
  1345. /// Convert a level to a direction where odd is RTL and
  1346. /// even is LTR
  1347. /// </summary>
  1348. /// <param name="level">The level to convert</param>
  1349. /// <returns>A directionality</returns>
  1350. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  1351. private static BidiClass DirectionFromLevel(int level)
  1352. => ((level & 0x1) == 0) ? BidiClass.LeftToRight : BidiClass.RightToLeft;
  1353. /// <summary>
  1354. /// Helper to check if a directionality is removed by rule X9
  1355. /// </summary>
  1356. /// <param name="biDiClass">The bidi type to check</param>
  1357. /// <returns>True if rule X9 would remove this character; otherwise false</returns>
  1358. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  1359. private static bool IsRemovedByX9(BidiClass biDiClass)
  1360. {
  1361. switch (biDiClass)
  1362. {
  1363. case BidiClass.LeftToRightEmbedding:
  1364. case BidiClass.RightToLeftEmbedding:
  1365. case BidiClass.LeftToRightOverride:
  1366. case BidiClass.RightToLeftOverride:
  1367. case BidiClass.PopDirectionalFormat:
  1368. case BidiClass.BoundaryNeutral:
  1369. return true;
  1370. default:
  1371. return false;
  1372. }
  1373. }
  1374. /// <summary>
  1375. /// Check if a a directionality is neutral for rules N1 and N2
  1376. /// </summary>
  1377. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  1378. private static bool IsNeutralClass(BidiClass direction)
  1379. {
  1380. switch (direction)
  1381. {
  1382. case BidiClass.ParagraphSeparator:
  1383. case BidiClass.SegmentSeparator:
  1384. case BidiClass.WhiteSpace:
  1385. case BidiClass.OtherNeutral:
  1386. case BidiClass.RightToLeftIsolate:
  1387. case BidiClass.LeftToRightIsolate:
  1388. case BidiClass.FirstStrongIsolate:
  1389. case BidiClass.PopDirectionalIsolate:
  1390. return true;
  1391. default:
  1392. return false;
  1393. }
  1394. }
  1395. /// <summary>
  1396. /// Maps a direction to a strong class for rule N0
  1397. /// </summary>
  1398. /// <param name="direction">The direction to map</param>
  1399. /// <returns>A strong direction - R, L or ON</returns>
  1400. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  1401. private static BidiClass GetStrongClassN0(BidiClass direction)
  1402. {
  1403. switch (direction)
  1404. {
  1405. case BidiClass.EuropeanNumber:
  1406. case BidiClass.ArabicNumber:
  1407. case BidiClass.ArabicLetter:
  1408. case BidiClass.RightToLeft:
  1409. return BidiClass.RightToLeft;
  1410. case BidiClass.LeftToRight:
  1411. return BidiClass.LeftToRight;
  1412. default:
  1413. return BidiClass.OtherNeutral;
  1414. }
  1415. }
  1416. /// <summary>
  1417. /// Hold the start and end index of a pair of brackets
  1418. /// </summary>
  1419. private readonly struct BracketPair : IComparable<BracketPair>
  1420. {
  1421. /// <summary>
  1422. /// Initializes a new instance of the <see cref="BracketPair"/> struct.
  1423. /// </summary>
  1424. /// <param name="openingIndex">Index of the opening bracket</param>
  1425. /// <param name="closingIndex">Index of the closing bracket</param>
  1426. public BracketPair(int openingIndex, int closingIndex)
  1427. {
  1428. OpeningIndex = openingIndex;
  1429. ClosingIndex = closingIndex;
  1430. }
  1431. /// <summary>
  1432. /// Gets the index of the opening bracket
  1433. /// </summary>
  1434. public int OpeningIndex { get; }
  1435. /// <summary>
  1436. /// Gets the index of the closing bracket
  1437. /// </summary>
  1438. public int ClosingIndex { get; }
  1439. public int CompareTo(BracketPair other)
  1440. => OpeningIndex.CompareTo(other.OpeningIndex);
  1441. }
  1442. /// <summary>
  1443. /// Status stack entry used while resolving explicit
  1444. /// embedding levels
  1445. /// </summary>
  1446. private readonly struct Status
  1447. {
  1448. public Status(sbyte embeddingLevel, BidiClass overrideStatus, bool isolateStatus)
  1449. {
  1450. EmbeddingLevel = embeddingLevel;
  1451. OverrideStatus = overrideStatus;
  1452. IsolateStatus = isolateStatus;
  1453. }
  1454. public sbyte EmbeddingLevel { get; }
  1455. public BidiClass OverrideStatus { get; }
  1456. public bool IsolateStatus { get; }
  1457. }
  1458. /// <summary>
  1459. /// Provides information about a level run - a continuous
  1460. /// sequence of equal levels.
  1461. /// </summary>
  1462. private readonly struct LevelRun
  1463. {
  1464. public LevelRun(int start, int length, int level, BidiClass sos, BidiClass eos)
  1465. {
  1466. Start = start;
  1467. Length = length;
  1468. Level = level;
  1469. Sos = sos;
  1470. Eos = eos;
  1471. }
  1472. public int Start { get; }
  1473. public int Length { get; }
  1474. public int Level { get; }
  1475. public BidiClass Sos { get; }
  1476. public BidiClass Eos { get; }
  1477. }
  1478. }
  1479. }