// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// Paragraph metadata for BiDi processing.
///
/// Indices are UTF-16 code-unit offsets in the source text.
pub struct ParagraphInfo {
range_start : Int
range_end : Int
level : Int
}
///|
pub fn ParagraphInfo::range_start(self : ParagraphInfo) -> Int {
self.range_start
}
///|
pub fn ParagraphInfo::range_end(self : ParagraphInfo) -> Int {
self.range_end
}
///|
pub fn ParagraphInfo::level(self : ParagraphInfo) -> Int {
self.level
}
///|
pub fn ParagraphInfo::is_rtl(self : ParagraphInfo) -> Bool {
self.level % 2 != 0
}
///|
/// BiDi analysis result aligned with `unicode-bidi` / `cosmic-text` flow.
///
/// `original_classes` and `levels` are indexed by UTF-16 code-unit offset.
pub struct BidiInfo {
text : String
original_classes : Array[@moon_swash.BidiClass]
levels : Array[Int]
paragraphs : Array[ParagraphInfo]
}
///|
priv struct BidiParagraphFlags {
is_pure_ltr : Bool
has_isolate_controls : Bool
}
///|
priv struct BidiLevelRun {
start : Int
end : Int
}
///|
priv struct BidiIsolatingRunSequence {
runs : Array[BidiLevelRun]
sos : @moon_swash.BidiClass
eos : @moon_swash.BidiClass
}
///|
priv struct BidiBracketPair {
start : Int
end : Int
start_run : Int
end_run : Int
}
///|
priv struct BidiTextMeta {
char_len_at : Array[Int]
char_at_start : Array[Char]
}
///|
priv enum BidiOverrideStatus {
Neutral
RTL
LTR
Isolate
}
///|
priv struct BidiExplicitStatus {
level : Int
status : BidiOverrideStatus
}
///|
const BIDI_MAX_EXPLICIT_LEVEL : Int = 125
///|
const BIDI_MAX_IMPLICIT_LEVEL : Int = 126
///|
fn bidi_utf16_len(ch : Char) -> Int {
if ch.to_int() <= 0xFFFF {
1
} else {
2
}
}
///|
fn bidi_clamp_para_level(level : Int) -> Int {
if level < 0 {
0
} else if level > BIDI_MAX_EXPLICIT_LEVEL {
BIDI_MAX_EXPLICIT_LEVEL
} else {
level
}
}
///|
fn bidi_level_is_rtl(level : Int) -> Bool {
level % 2 != 0
}
///|
fn bidi_level_new_explicit_next_ltr(level : Int) -> Int? {
let mut next = level + 1
if next % 2 != 0 {
next = next + 1
}
if next <= BIDI_MAX_EXPLICIT_LEVEL {
Some(next)
} else {
None
}
}
///|
fn bidi_level_new_explicit_next_rtl(level : Int) -> Int? {
let mut next = level + 1
if next % 2 == 0 {
next = next + 1
}
if next <= BIDI_MAX_EXPLICIT_LEVEL {
Some(next)
} else {
None
}
}
///|
fn bidi_level_raise(level : Int, amount : Int, max_value : Int) -> Int? {
let next = level + amount
if next <= max_value {
Some(next)
} else {
None
}
}
///|
fn bidi_level_to_class(level : Int) -> @moon_swash.BidiClass {
if bidi_level_is_rtl(level) {
R
} else {
L
}
}
///|
fn bidi_max_int(a : Int, b : Int) -> Int {
if a >= b {
a
} else {
b
}
}
///|
fn bidi_is_rtl_embedding_control(class : @moon_swash.BidiClass) -> Bool {
match class {
RLE | RLO | RLI => true
_ => false
}
}
///|
fn bidi_is_removed_by_x9(class : @moon_swash.BidiClass) -> Bool {
match class {
RLE | LRE | RLO | LRO | PDF | BN => true
_ => false
}
}
///|
fn bidi_is_ni_class(class : @moon_swash.BidiClass) -> Bool {
match class {
B | S | WS | ON | FSI | LRI | RLI | PDI => true
_ => false
}
}
///|
fn bidi_is_isolate_initiator(class : @moon_swash.BidiClass) -> Bool {
match class {
RLI | LRI | FSI => true
_ => false
}
}
///|
fn bidi_push_repeated_class(
classes : Array[@moon_swash.BidiClass],
class : @moon_swash.BidiClass,
n : Int,
) -> Unit {
let mut i = 0
while i < n {
classes.push(class)
i = i + 1
}
}
///|
fn bidi_set_repeated_class(
classes : Array[@moon_swash.BidiClass],
start : Int,
n : Int,
class : @moon_swash.BidiClass,
) -> Unit {
let mut i = 0
while i < n {
let p = start + i
if p >= 0 && p < classes.length() {
classes.set(p, class)
}
i = i + 1
}
}
///|
fn bidi_build_text_meta(text : String) -> BidiTextMeta {
let len = text.length()
let char_len_at : Array[Int] = Array::makei(len, _ => 0)
let char_at_start : Array[Char] = Array::makei(len, _ => '\u{0000}')
for p in text.iter2() {
let idx = p.0
let ch = p.1
char_len_at.set(idx, bidi_utf16_len(ch))
char_at_start.set(idx, ch)
}
BidiTextMeta::{ char_len_at, char_at_start }
}
///|
fn bidi_char_len_at(meta : BidiTextMeta, index : Int) -> Int {
if index < 0 || index >= meta.char_len_at.length() {
0
} else {
meta.char_len_at[index]
}
}
///|
fn bidi_find_prev_non_removed_index(
classes : Array[@moon_swash.BidiClass],
from : Int,
min_index : Int,
) -> Int? {
let mut i = from - 1
while i >= min_index {
if !bidi_is_removed_by_x9(classes[i]) {
return Some(i)
}
i = i - 1
}
None
}
///|
fn bidi_find_next_non_removed_index(
classes : Array[@moon_swash.BidiClass],
from : Int,
max_index : Int,
) -> Int? {
let mut i = from
while i < max_index {
if !bidi_is_removed_by_x9(classes[i]) {
return Some(i)
}
i = i + 1
}
None
}
///|
fn bidi_compute_initial_info(
text : String,
default_para_level : Int?,
) -> (
Array[@moon_swash.BidiClass],
Array[ParagraphInfo],
Array[BidiParagraphFlags],
) {
let len = text.length()
let original_classes : Array[@moon_swash.BidiClass] = []
let paragraphs : Array[ParagraphInfo] = []
let paragraph_flags : Array[BidiParagraphFlags] = []
let isolate_stack : Array[Int] = []
let mut para_start = 0
let mut para_level = match default_para_level {
Some(v) => Some(bidi_clamp_para_level(v))
None => None
}
let mut is_pure_ltr = true
let mut has_isolate_controls = false
for p in text.iter2() {
let i = p.0
let ch = p.1
let class = @moon_swash.CharInfo::from_char(ch).properties().bidi_class()
let char_len = bidi_utf16_len(ch)
bidi_push_repeated_class(original_classes, class, char_len)
match class {
B => {
let para_end = i + char_len
paragraphs.push(ParagraphInfo::{
range_start: para_start,
range_end: para_end,
level: para_level.unwrap_or(0),
})
paragraph_flags.push(BidiParagraphFlags::{
is_pure_ltr,
has_isolate_controls,
})
para_start = para_end
para_level = match default_para_level {
Some(v) => Some(bidi_clamp_para_level(v))
None => None
}
is_pure_ltr = true
has_isolate_controls = false
isolate_stack.clear()
}
L | R | AL => {
if !(class is L) {
is_pure_ltr = false
}
if isolate_stack.length() > 0 {
let isolate_start = isolate_stack[isolate_stack.length() - 1]
if original_classes[isolate_start] is FSI {
bidi_set_repeated_class(
original_classes,
isolate_start,
1,
if class is L {
LRI
} else {
RLI
},
)
}
} else if para_level is None {
para_level = Some(if class is L { 0 } else { 1 })
}
}
AN | LRE | RLE | LRO | RLO => is_pure_ltr = false
RLI | LRI | FSI => {
is_pure_ltr = false
has_isolate_controls = true
isolate_stack.push(i)
}
PDI =>
if isolate_stack.length() > 0 {
isolate_stack.truncate(isolate_stack.length() - 1)
}
_ => ()
}
}
if para_start < len {
paragraphs.push(ParagraphInfo::{
range_start: para_start,
range_end: len,
level: para_level.unwrap_or(0),
})
paragraph_flags.push(BidiParagraphFlags::{
is_pure_ltr,
has_isolate_controls,
})
}
(original_classes, paragraphs, paragraph_flags)
}
///|
fn bidi_explicit_compute_for_para(
text : String,
meta : BidiTextMeta,
para : ParagraphInfo,
original_classes : Array[@moon_swash.BidiClass],
levels : Array[Int],
processing_classes : Array[@moon_swash.BidiClass],
runs : Array[BidiLevelRun],
) -> Unit {
let stack : Array[BidiExplicitStatus] = [
BidiExplicitStatus::{
level: para.level,
status: BidiOverrideStatus::Neutral,
},
]
let mut overflow_isolate_count = 0
let mut overflow_embedding_count = 0
let mut valid_isolate_count = 0
let mut current_run_level = para.level
let mut current_run_start = para.range_start
let mut has_any_char = false
for p in text.iter2() {
let i = p.0
if i < para.range_start {
continue
}
if i >= para.range_end {
break
}
let char_len = bidi_char_len_at(meta, i)
if char_len <= 0 {
continue
}
let class = original_classes[i]
let last = stack[stack.length() - 1]
match class {
RLE | LRE | RLO | LRO | RLI | LRI | FSI => {
levels.set(i, last.level)
let is_isolate = bidi_is_isolate_initiator(class)
if is_isolate {
match last.status {
RTL => processing_classes.set(i, R)
LTR => processing_classes.set(i, L)
_ => ()
}
}
let new_level_opt = if bidi_is_rtl_embedding_control(class) {
bidi_level_new_explicit_next_rtl(last.level)
} else {
bidi_level_new_explicit_next_ltr(last.level)
}
if new_level_opt is Some(new_level) &&
overflow_isolate_count == 0 &&
overflow_embedding_count == 0 {
stack.push(BidiExplicitStatus::{
level: new_level,
status: match class {
RLO => RTL
LRO => LTR
RLI | LRI | FSI => Isolate
_ => Neutral
},
})
if is_isolate {
valid_isolate_count = valid_isolate_count + 1
} else {
levels.set(i, new_level)
}
} else if is_isolate {
overflow_isolate_count = overflow_isolate_count + 1
} else if overflow_isolate_count == 0 {
overflow_embedding_count = overflow_embedding_count + 1
}
if !is_isolate {
processing_classes.set(i, BN)
}
}
PDI => {
if overflow_isolate_count > 0 {
overflow_isolate_count = overflow_isolate_count - 1
} else if valid_isolate_count > 0 {
overflow_embedding_count = 0
let mut done = false
while stack.length() > 0 && !done {
let top = stack[stack.length() - 1]
stack.truncate(stack.length() - 1)
if top.status is Isolate {
done = true
}
}
valid_isolate_count = valid_isolate_count - 1
}
let now = stack[stack.length() - 1]
levels.set(i, now.level)
match now.status {
RTL => processing_classes.set(i, R)
LTR => processing_classes.set(i, L)
_ => ()
}
}
PDF => {
if overflow_isolate_count > 0 {
()
} else if overflow_embedding_count > 0 {
overflow_embedding_count = overflow_embedding_count - 1
} else if stack.length() >= 2 {
let now = stack[stack.length() - 1]
if !(now.status is Isolate) {
stack.truncate(stack.length() - 1)
}
}
let now = stack[stack.length() - 1]
levels.set(i, now.level)
processing_classes.set(i, BN)
}
B => ()
_ => {
levels.set(i, last.level)
if !(class is BN) {
match last.status {
RTL => processing_classes.set(i, R)
LTR => processing_classes.set(i, L)
_ => ()
}
}
}
}
let mut j = 1
while j < char_len {
let p = i + j
if p < para.range_end {
levels.set(p, levels[i])
processing_classes.set(p, processing_classes[i])
}
j = j + 1
}
if !has_any_char {
current_run_level = levels[i]
has_any_char = true
} else if !bidi_is_removed_by_x9(class) && levels[i] != current_run_level {
runs.push(BidiLevelRun::{ start: current_run_start, end: i })
current_run_level = levels[i]
current_run_start = i
}
}
if has_any_char && para.range_end > current_run_start {
runs.push(BidiLevelRun::{ start: current_run_start, end: para.range_end })
}
}
///|
fn bidi_run_first_non_removed_level(
run : BidiLevelRun,
classes : Array[@moon_swash.BidiClass],
levels : Array[Int],
) -> Int {
let mut i = run.start
while i < run.end {
if !bidi_is_removed_by_x9(classes[i]) {
return levels[i]
}
i = i + 1
}
levels[run.start]
}
///|
fn bidi_run_last_non_removed_level(
run : BidiLevelRun,
classes : Array[@moon_swash.BidiClass],
levels : Array[Int],
) -> Int {
let mut i = run.end - 1
while i >= run.start {
if !bidi_is_removed_by_x9(classes[i]) {
return levels[i]
}
i = i - 1
}
levels[run.end - 1]
}
///|
fn bidi_sequence_find_forward_from(
sequence : BidiIsolatingRunSequence,
pos : Int,
level_run_index : Int,
pred : (Int) -> Bool,
) -> Int? {
let mut ri = level_run_index
while ri < sequence.runs.length() {
let run = sequence.runs[ri]
let mut i = if ri == level_run_index { pos } else { run.start }
if i < run.start {
i = run.start
}
while i < run.end {
if pred(i) {
return Some(i)
}
i = i + 1
}
ri = ri + 1
}
None
}
///|
fn bidi_sequence_find_backward_from(
sequence : BidiIsolatingRunSequence,
pos : Int,
level_run_index : Int,
pred : (Int) -> Bool,
) -> Int? {
let mut ri = level_run_index
while ri >= 0 {
let run = sequence.runs[ri]
let mut i = if ri == level_run_index { pos - 1 } else { run.end - 1 }
if i >= run.end {
i = run.end - 1
}
while i >= run.start {
if pred(i) {
return Some(i)
}
i = i - 1
}
if ri == 0 {
break
}
ri = ri - 1
}
None
}
///|
fn bidi_sequence_set_contiguous_bn_backward(
sequence : BidiIsolatingRunSequence,
pos : Int,
level_run_index : Int,
processing_classes : Array[@moon_swash.BidiClass],
class_to_set : @moon_swash.BidiClass,
) -> Unit {
let mut ri = level_run_index
while ri >= 0 {
let run = sequence.runs[ri]
let mut i = if ri == level_run_index { pos - 1 } else { run.end - 1 }
if i >= run.end {
i = run.end - 1
}
while i >= run.start {
if !(processing_classes[i] is BN) {
return
}
processing_classes.set(i, class_to_set)
i = i - 1
}
if ri == 0 {
return
}
ri = ri - 1
}
}
///|
fn bidi_sequence_set_contiguous_bn_forward(
sequence : BidiIsolatingRunSequence,
pos : Int,
level_run_index : Int,
processing_classes : Array[@moon_swash.BidiClass],
class_to_set : @moon_swash.BidiClass,
) -> Unit {
let mut ri = level_run_index
while ri < sequence.runs.length() {
let run = sequence.runs[ri]
let mut i = if ri == level_run_index { pos } else { run.start }
if i < run.start {
i = run.start
}
while i < run.end {
if !(processing_classes[i] is BN) {
return
}
processing_classes.set(i, class_to_set)
i = i + 1
}
ri = ri + 1
}
}
///|
fn bidi_sequence_set_nsm_or_bn_forward(
sequence : BidiIsolatingRunSequence,
pos : Int,
level_run_index : Int,
original_classes : Array[@moon_swash.BidiClass],
processing_classes : Array[@moon_swash.BidiClass],
class_to_set : @moon_swash.BidiClass,
) -> Unit {
let mut ri = level_run_index
while ri < sequence.runs.length() {
let run = sequence.runs[ri]
let mut i = if ri == level_run_index { pos } else { run.start }
if i < run.start {
i = run.start
}
while i < run.end {
if original_classes[i] is NSM || processing_classes[i] is BN {
processing_classes.set(i, class_to_set)
} else {
return
}
i = i + 1
}
ri = ri + 1
}
}
///|
fn bidi_prepare_isolating_run_sequences_for_para(
para : ParagraphInfo,
original_classes : Array[@moon_swash.BidiClass],
levels : Array[Int],
runs : Array[BidiLevelRun],
has_isolate_controls : Bool,
out_sequences : Array[BidiIsolatingRunSequence],
) -> Unit {
if runs.length() == 0 {
return
}
if !has_isolate_controls {
let non_removed : Array[Int] = []
let mut i = para.range_start
while i < para.range_end {
if !bidi_is_removed_by_x9(original_classes[i]) {
non_removed.push(i)
}
i = i + 1
}
let mut pred_cursor = 0
let mut succ_cursor = 0
for run in runs {
let seq_level = bidi_run_first_non_removed_level(
run, original_classes, levels,
)
let end_level = bidi_run_last_non_removed_level(
run, original_classes, levels,
)
while pred_cursor < non_removed.length() &&
non_removed[pred_cursor] < run.start {
pred_cursor = pred_cursor + 1
}
let pred_level = if pred_cursor == 0 {
para.level
} else {
levels[non_removed[pred_cursor - 1]]
}
while succ_cursor < non_removed.length() &&
non_removed[succ_cursor] < run.end {
succ_cursor = succ_cursor + 1
}
let succ_level = if succ_cursor < non_removed.length() {
levels[non_removed[succ_cursor]]
} else {
para.level
}
out_sequences.push(BidiIsolatingRunSequence::{
runs: [run],
sos: bidi_level_to_class(bidi_max_int(seq_level, pred_level)),
eos: bidi_level_to_class(bidi_max_int(end_level, succ_level)),
})
}
return
}
let sequence_stack : Array[Array[BidiLevelRun]] = [[]]
let sequences : Array[Array[BidiLevelRun]] = []
for run in runs {
let start_class = original_classes[run.start]
let mut end_class = start_class
let mut i = run.end - 1
while i >= run.start {
let class = original_classes[i]
if !bidi_is_removed_by_x9(class) {
end_class = class
break
}
i = i - 1
}
let mut seq : Array[BidiLevelRun] = []
if start_class is PDI && sequence_stack.length() > 1 {
let last_i = sequence_stack.length() - 1
seq = sequence_stack[last_i]
sequence_stack.truncate(last_i)
}
seq.push(run)
if bidi_is_isolate_initiator(end_class) {
sequence_stack.push(seq)
} else {
sequences.push(seq)
}
}
let mut si = sequence_stack.length()
while si > 0 {
si = si - 1
let seq = sequence_stack[si]
if seq.length() != 0 {
sequences.push(seq)
}
}
for seq in sequences {
let start_of_seq = seq[0].start
let runs_len = seq.length()
let end_of_seq = seq[runs_len - 1].end
let result = BidiIsolatingRunSequence::{ runs: seq, sos: L, eos: L }
let seq_level = match
bidi_sequence_find_forward_from(result, start_of_seq, 0, fn(i) {
!bidi_is_removed_by_x9(original_classes[i])
}) {
Some(idx) => levels[idx]
None => levels[start_of_seq]
}
let end_level = match
bidi_sequence_find_backward_from(result, end_of_seq, runs_len - 1, fn(i) {
!bidi_is_removed_by_x9(original_classes[i])
}) {
Some(idx) => levels[idx]
None => levels[end_of_seq - 1]
}
let pred_level = match
bidi_find_prev_non_removed_index(
original_classes,
start_of_seq,
para.range_start,
) {
Some(idx) => levels[idx]
None => para.level
}
let last_non_removed_class = match
bidi_find_prev_non_removed_index(
original_classes,
end_of_seq,
para.range_start,
) {
Some(idx) => original_classes[idx]
None => BN
}
let succ_level = if bidi_is_isolate_initiator(last_non_removed_class) {
para.level
} else {
match
bidi_find_next_non_removed_index(
original_classes,
end_of_seq,
para.range_end,
) {
Some(idx) => levels[idx]
None => para.level
}
}
out_sequences.push(BidiIsolatingRunSequence::{
runs: result.runs,
sos: bidi_level_to_class(bidi_max_int(seq_level, pred_level)),
eos: bidi_level_to_class(bidi_max_int(end_level, succ_level)),
})
}
}
///|
fn bidi_resolve_weak_for_sequence(
meta : BidiTextMeta,
sequence : BidiIsolatingRunSequence,
processing_classes : Array[@moon_swash.BidiClass],
) -> Unit {
let mut prev_class_before_w4 = sequence.sos
let mut prev_class_before_w5 = sequence.sos
let mut prev_class_before_w1 = sequence.sos
let mut last_strong_is_al = false
let et_run_indices : Array[Int] = []
let bn_run_indices : Array[Int] = []
for run_index in 0.. ON
_ => prev_class_before_w1
},
)
w2_processing_class = processing_classes[i]
}
prev_class_before_w1 = processing_classes[i]
match processing_classes[i] {
EN => if last_strong_is_al { processing_classes.set(i, AN) }
AL => processing_classes.set(i, R)
_ => ()
}
match w2_processing_class {
L | R => last_strong_is_al = false
AL => last_strong_is_al = true
_ => ()
}
let class_before_w456 = processing_classes[i]
match processing_classes[i] {
EN => {
for j in et_run_indices {
processing_classes.set(j, EN)
}
et_run_indices.clear()
}
ES | CS => {
let char_len = bidi_char_len_at(meta, i)
if char_len > 0 {
let mut next_class = match
bidi_sequence_find_forward_from(
sequence,
i + char_len,
run_index,
fn(j) { !bidi_is_removed_by_x9(processing_classes[j]) },
) {
Some(idx) => processing_classes[idx]
None => sequence.eos
}
if next_class is EN && last_strong_is_al {
next_class = AN
}
let new_class : @moon_swash.BidiClass = if prev_class_before_w4
is EN &&
(processing_classes[i] is ES || processing_classes[i] is CS) &&
next_class is EN {
EN
} else if prev_class_before_w4 is AN &&
processing_classes[i] is CS &&
next_class is AN {
AN
} else {
ON
}
processing_classes.set(i, new_class)
if new_class is ON {
bidi_sequence_set_contiguous_bn_backward(
sequence,
i,
run_index,
processing_classes,
ON,
)
bidi_sequence_set_contiguous_bn_forward(
sequence,
i + char_len,
run_index,
processing_classes,
ON,
)
}
} else if i > 0 {
processing_classes.set(i, processing_classes[i - 1])
}
}
ET =>
if prev_class_before_w5 is EN {
processing_classes.set(i, EN)
} else {
for j in bn_run_indices {
et_run_indices.push(j)
}
et_run_indices.push(i)
}
_ => ()
}
bn_run_indices.clear()
prev_class_before_w5 = processing_classes[i]
if !(prev_class_before_w5 is ET) {
for j in et_run_indices {
processing_classes.set(j, ON)
}
et_run_indices.clear()
}
prev_class_before_w4 = class_before_w456
i = i + 1
}
}
for j in et_run_indices {
processing_classes.set(j, ON)
}
et_run_indices.clear()
let mut last_strong_is_l = sequence.sos is L
for run in sequence.runs {
let mut i = run.start
while i < run.end {
match processing_classes[i] {
EN => if last_strong_is_l { processing_classes.set(i, L) }
L => last_strong_is_l = true
R | AL => last_strong_is_l = false
_ => ()
}
i = i + 1
}
}
}
///|
fn bidi_sort_bracket_pairs_by_start(pairs : Array[BidiBracketPair]) -> Unit {
let mut i = 1
while i < pairs.length() {
let key = pairs[i]
let mut j = i - 1
while pairs[j].start > key.start {
pairs.set(j + 1, pairs[j])
if j == 0 {
break
}
j = j - 1
}
if pairs[j].start > key.start {
pairs.set(j, key)
} else {
pairs.set(j + 1, key)
}
i = i + 1
}
}
///|
fn bidi_identify_bracket_pairs(
meta : BidiTextMeta,
sequence : BidiIsolatingRunSequence,
classes : Array[@moon_swash.BidiClass],
out_pairs : Array[BidiBracketPair],
) -> Unit {
let stack : Array[(Char, Int, Int)] = []
for run_index in 0.. {
if stack.length() >= 63 {
break
}
stack.push((ch, i, run_index))
}
Close(opening) =>
if stack.length() == 0 {
()
} else {
let mut si = stack.length()
let mut matched = false
while si > 0 && !matched {
si = si - 1
let item = stack[si]
if item.0 == opening {
out_pairs.push(BidiBracketPair::{
start: item.1,
end: i,
start_run: item.2,
end_run: run_index,
})
stack.truncate(si)
matched = true
}
}
}
None => ()
}
i = i + 1
}
}
bidi_sort_bracket_pairs_by_start(out_pairs)
}
///|
fn bidi_resolve_neutral_run_class(
prev_class : @moon_swash.BidiClass,
next_class : @moon_swash.BidiClass,
embedding_class : @moon_swash.BidiClass,
) -> @moon_swash.BidiClass {
match (prev_class, next_class) {
(L, L) => L
(R, R)
| (R, AN)
| (R, EN)
| (AN, R)
| (AN, AN)
| (AN, EN)
| (EN, R)
| (EN, AN)
| (EN, EN) => R
_ => embedding_class
}
}
///|
fn bidi_resolve_neutral_for_sequence(
meta : BidiTextMeta,
sequence : BidiIsolatingRunSequence,
levels : Array[Int],
original_classes : Array[@moon_swash.BidiClass],
processing_classes : Array[@moon_swash.BidiClass],
) -> Unit {
let embedding_class = bidi_level_to_class(levels[sequence.runs[0].start])
let opposite_embedding_class : @moon_swash.BidiClass = if embedding_class is L {
R
} else {
L
}
let bracket_pairs : Array[BidiBracketPair] = []
bidi_identify_bracket_pairs(meta, sequence, processing_classes, bracket_pairs)
for pair in bracket_pairs {
let mut found_embedding = false
let mut found_opposite = false
let mut class_to_set : @moon_swash.BidiClass? = None
let start_char_len = {
let l = bidi_char_len_at(meta, pair.start)
if l > 0 {
l
} else {
1
}
}
let end_char_len = {
let l = bidi_char_len_at(meta, pair.end)
if l > 0 {
l
} else {
1
}
}
let enclosed_opt = bidi_sequence_find_forward_from(
sequence,
pair.start + start_char_len,
pair.start_run,
fn(idx) {
if idx >= pair.end {
false
} else {
let class = processing_classes[idx]
if (embedding_class is L && class is L) ||
(embedding_class is R && class is R) {
found_embedding = true
} else if (opposite_embedding_class is L && class is L) ||
(opposite_embedding_class is R && class is R) {
found_opposite = true
} else if class is EN || class is AN {
if embedding_class is L {
found_opposite = true
} else {
found_embedding = true
}
}
found_embedding
}
},
)
match enclosed_opt {
Some(_) => ()
None => ()
}
if found_embedding {
class_to_set = Some(embedding_class)
} else if found_opposite {
let mut previous_strong = match
bidi_sequence_find_backward_from(sequence, pair.start, pair.start_run, fn(
idx,
) {
let class = processing_classes[idx]
class is L || class is R || class is EN || class is AN
}) {
Some(idx) => processing_classes[idx]
None => sequence.sos
}
if previous_strong is EN || previous_strong is AN {
previous_strong = R
}
class_to_set = Some(previous_strong)
}
if class_to_set is Some(cset) {
let mut i = pair.start
while i < pair.start + start_char_len {
processing_classes.set(i, cset)
i = i + 1
}
let mut j = pair.end
while j < pair.end + end_char_len {
processing_classes.set(j, cset)
j = j + 1
}
bidi_sequence_set_contiguous_bn_backward(
sequence,
pair.start,
pair.start_run,
processing_classes,
cset,
)
bidi_sequence_set_nsm_or_bn_forward(
sequence,
pair.start + start_char_len,
pair.start_run,
original_classes,
processing_classes,
cset,
)
bidi_sequence_set_nsm_or_bn_forward(
sequence,
pair.end + end_char_len,
pair.end_run,
original_classes,
processing_classes,
cset,
)
}
}
let mut prev_class = sequence.sos
let ni_run : Array[Int] = []
let runs = sequence.runs
if runs.length() == 0 {
return
}
let mut run_i = 0
let mut i = runs[0].start
while run_i < runs.length() {
let run = runs[run_i]
if i >= run.end {
run_i = run_i + 1
if run_i < runs.length() {
i = runs[run_i].start
}
continue
}
let class_i = processing_classes[i]
if bidi_is_ni_class(class_i) || class_i is BN {
ni_run.push(i)
let mut next_class = sequence.eos
let mut found_next_strong = false
let mut scan_run_i = run_i
let mut scan_i = i + 1
while scan_run_i < runs.length() {
let scan_run = runs[scan_run_i]
if scan_i < scan_run.start {
scan_i = scan_run.start
}
while scan_i < scan_run.end {
let cls = processing_classes[scan_i]
if bidi_is_ni_class(cls) || cls is BN {
ni_run.push(scan_i)
scan_i = scan_i + 1
} else {
next_class = cls
found_next_strong = true
break
}
}
if found_next_strong {
break
}
scan_run_i = scan_run_i + 1
if scan_run_i < runs.length() {
scan_i = runs[scan_run_i].start
}
}
let new_class = bidi_resolve_neutral_run_class(
prev_class, next_class, embedding_class,
)
for j in ni_run {
processing_classes.set(j, new_class)
}
ni_run.clear()
prev_class = new_class
if found_next_strong {
run_i = scan_run_i
i = scan_i
} else {
break
}
} else {
prev_class = class_i
i = i + 1
}
}
}
///|
fn bidi_resolve_levels_for_para(
para : ParagraphInfo,
processing_classes : Array[@moon_swash.BidiClass],
levels : Array[Int],
) -> Int {
let mut max_level = para.level
let mut i = para.range_start
while i < para.range_end {
let mut level = levels[i]
let is_rtl = bidi_level_is_rtl(level)
match (is_rtl, processing_classes[i]) {
(false, AN) | (false, EN) =>
match bidi_level_raise(level, 2, BIDI_MAX_IMPLICIT_LEVEL) {
Some(v) => level = v
None => ()
}
(false, R) | (true, L) | (true, EN) | (true, AN) =>
match bidi_level_raise(level, 1, BIDI_MAX_IMPLICIT_LEVEL) {
Some(v) => level = v
None => ()
}
_ => ()
}
levels.set(i, level)
max_level = bidi_max_int(max_level, level)
i = i + 1
}
max_level
}
///|
fn bidi_assign_levels_to_removed_chars_for_para(
para : ParagraphInfo,
classes : Array[@moon_swash.BidiClass],
levels : Array[Int],
) -> Unit {
let mut i = para.range_start
while i < para.range_end {
if bidi_is_removed_by_x9(classes[i]) {
levels.set(
i,
if i > para.range_start {
levels[i - 1]
} else {
para.level
},
)
}
i = i + 1
}
}
///|
fn bidi_compute_info_for_para(
text : String,
meta : BidiTextMeta,
para : ParagraphInfo,
is_pure_ltr : Bool,
has_isolate_controls : Bool,
original_classes : Array[@moon_swash.BidiClass],
processing_classes : Array[@moon_swash.BidiClass],
levels : Array[Int],
) -> Unit {
let mut i = para.range_start
while i < para.range_end {
levels.set(i, para.level)
i = i + 1
}
if para.level == 0 && is_pure_ltr {
return
}
let runs : Array[BidiLevelRun] = []
bidi_explicit_compute_for_para(
text, meta, para, original_classes, levels, processing_classes, runs,
)
let sequences : Array[BidiIsolatingRunSequence] = []
bidi_prepare_isolating_run_sequences_for_para(
para, original_classes, levels, runs, has_isolate_controls, sequences,
)
for sequence in sequences {
bidi_resolve_weak_for_sequence(meta, sequence, processing_classes)
bidi_resolve_neutral_for_sequence(
meta, sequence, levels, original_classes, processing_classes,
)
}
bidi_resolve_levels_for_para(para, processing_classes, levels) |> ignore
bidi_assign_levels_to_removed_chars_for_para(para, original_classes, levels)
}
///|
/// Reference-aligned L1 whitespace reset step.
fn bidi_adjust_levels_l1_for_para(
text : String,
meta : BidiTextMeta,
classes : Array[@moon_swash.BidiClass],
para : ParagraphInfo,
levels : Array[Int],
) -> Unit {
if para.range_start >= para.range_end {
return
}
let mut reset_from : Int? = Some(para.range_start)
let mut reset_to : Int? = None
let mut prev_level = para.level
for p in text.iter2() {
let i = p.0
if i < para.range_start {
continue
}
if i >= para.range_end {
break
}
let char_len = bidi_char_len_at(meta, i)
if char_len <= 0 {
continue
}
let class = classes[i]
match class {
B | S => {
reset_to = Some(i + char_len)
if reset_from is None {
reset_from = Some(i)
}
}
WS | FSI | LRI | RLI | PDI =>
if reset_from is None {
reset_from = Some(i)
}
RLE | LRE | RLO | LRO | PDF | BN => {
if reset_from is None {
reset_from = Some(i)
}
let mut j = i
while j < i + char_len && j < levels.length() {
levels.set(j, prev_level)
j = j + 1
}
}
_ => reset_from = None
}
match (reset_from, reset_to) {
(Some(from), Some(to)) => {
let mut j = from
while j < to && j < levels.length() {
levels.set(j, para.level)
j = j + 1
}
reset_from = None
reset_to = None
}
_ => ()
}
prev_level = levels[i]
}
if reset_from is Some(from) {
let mut j = from
while j < para.range_end && j < levels.length() {
levels.set(j, para.level)
j = j + 1
}
}
}
///|
pub fn BidiInfo::new(text : String) -> BidiInfo {
BidiInfo::new_with_para_level(text, None)
}
///|
pub fn BidiInfo::new_with_para_level(
text : String,
default_para_level : Int?,
) -> BidiInfo {
let normalized_default = match default_para_level {
Some(v) => Some(bidi_clamp_para_level(v))
None => None
}
let (original_classes, paragraphs, paragraph_flags) = bidi_compute_initial_info(
text, normalized_default,
)
let meta = bidi_build_text_meta(text)
let levels : Array[Int] = Array::makei(text.length(), _ => 0)
let processing_classes : Array[@moon_swash.BidiClass] = Array::makei(
original_classes.length(),
i => original_classes[i],
)
for i in 0.. String {
self.text
}
///|
pub fn BidiInfo::paragraphs(self : BidiInfo) -> Array[ParagraphInfo] {
self.paragraphs
}
///|
pub fn BidiInfo::levels(self : BidiInfo) -> Array[Int] {
self.levels
}
///|
pub fn BidiInfo::rtl(self : BidiInfo) -> Bool {
if self.paragraphs.length() == 0 {
false
} else {
self.paragraphs[0].is_rtl()
}
}
///|
pub fn BidiInfo::adjusted_levels(self : BidiInfo) -> Array[Int] {
let adjusted : Array[Int] = Array::makei(self.levels.length(), i => {
self.levels[i]
})
let meta = bidi_build_text_meta(self.text)
for para in self.paragraphs {
bidi_adjust_levels_l1_for_para(
self.text,
meta,
self.original_classes,
para,
adjusted,
)
}
adjusted
}