# Line breaking (UAX #14, Unicode 16.0): each boundary of a line of code points decided by the first rule, in the # standard's order, that matches it (as Vega's text/linebreak.c). The properties come in as data, ranges of code points # loaded once: each its class after LB1, whether an initial or a final quotation mark (Pi, Pf), East Asian (width F, W # or H), and pictographic and unassigned (Extended_Pictographic and Cn). A line's boundaries, the ends included, are 0 # (none), 1 (allowed) or 2 (mandatory). record Range { start = 0 class = 0 pi = false pf = false east = false pictographic = false } record Point { code = 0 } record Text { # each position's: its code, class, range, and what the rules look back at codes: list of number cls: list of number eff: list of number # its unit's class: a mark takes its base's (LB9), a lone mark is AL (LB10) joined: list of bool # a mark LB9 joins to the character before start: list of number # where its unit starts nonspace: list of number # where the last unit up to it that is not a space starts, or -1 numeric: list of bool # its unit ends NU (NU | SY | IS)* ri: list of number # how many regional indicators end at its unit at: list of number # its range } state ranges: list of Range # by start, from 0 state breaks: bytes # the last line's boundaries view boundaries = breaks input load = message { rows: list of Range } input line = message { points: list of Point } fn BK = 0; fn CR = 1; fn LF = 2; fn NL = 3; fn SP = 4; fn ZW = 5; fn ZWJ = 6; fn CM = 7; fn WJ = 8; fn GL = 9 fn BA = 10; fn BB = 11; fn B2 = 12; fn HY = 13; fn CB = 14; fn CL = 15; fn CP = 16; fn EX = 17; fn IN = 18; fn NS = 19 fn OP = 20; fn QU = 21; fn IS = 22; fn NU = 23; fn PO = 24; fn PR = 25; fn SY = 26; fn AK = 27; fn AL = 28; fn AP = 29 fn AS = 30; fn EB = 31; fn EM = 32; fn H2 = 33; fn H3 = 34; fn HL = 35; fn ID = 36; fn JL = 37; fn JV = 38; fn JT = 39 fn RI = 40; fn VF = 41; fn VI = 42 fn NONE = 0; fn ALLOWED = 1; fn MANDATORY = 2 fn end(c) = c == BK || c == CR || c == LF || c == NL on load { for r in load.rows { ranges.append(Range { ..r }) } } # The range holding code point c (ranges by start: the last whose start is c or before). fn range(c) { let low, high = 0, ranges.count while high - low > 1 { let middle = (low + high) // 2 if ranges[middle].start <= c { low = middle } else { high = middle } } return low } fn next_unit(t, i) { let j = i + 1 while j < t.codes.count && t.joined[j] { j = j + 1 } return j } fn aksara(t, u) = t.eff[u] == AK || t.eff[u] == AS || t.codes[u] == 0x25CC fn pi(t, k) = ranges[t.at[k]].pi fn pf(t, k) = ranges[t.at[k]].pf fn east(t, k) = ranges[t.at[k]].east fn decide(t, i) { let n = t.codes.count let a, b, A, B = t.cls[i - 1], t.cls[i], t.eff[i - 1], t.eff[i] let unit, before = t.start[i - 1], t.nonspace[i - 1] let base = t.codes[unit] if a == BK { return MANDATORY } # LB4 if a == CR && b == LF { return NONE } # LB5 if a == CR || a == LF || a == NL { return MANDATORY } if end(b) { return NONE } # LB6 if b == SP || b == ZW { return NONE } # LB7 if before >= 0 && t.eff[before] == ZW { return ALLOWED } # LB8 if a == ZWJ { return NONE } # LB8a if t.joined[i] { return NONE } # LB9 if A == WJ || B == WJ { return NONE } # LB11 if A == GL { return NONE } # LB12 if B == GL && A != SP && A != BA && A != HY { return NONE } # LB12a if B == CL || B == CP || B == EX || B == SY { return NONE } # LB13 if before >= 0 && t.eff[before] == OP { return NONE } # LB14 if before >= 0 && t.eff[before] == QU && pi(t, before) { # LB15a if before == 0 { return NONE } let q = t.eff[before - 1] if end(q) || q == OP || q == QU || q == GL || q == SP || q == ZW { return NONE } } if B == QU && pf(t, i) { # LB15b let j = next_unit(t, i) if j == n { return NONE } let e = t.eff[j] if e == SP || e == GL || e == WJ || e == CL || e == QU || e == CP || e == EX || e == IS || e == SY || end(e) || e == ZW { return NONE } } if A == SP && B == IS { # LB15c let j = next_unit(t, i) if j < n && t.eff[j] == NU { return ALLOWED } } if B == IS { return NONE } # LB15d if B == NS && before >= 0 && (t.eff[before] == CL || t.eff[before] == CP) { return NONE } # LB16 if B == B2 && before >= 0 && t.eff[before] == B2 { return NONE } # LB17 if A == SP { return ALLOWED } # LB18 if B == QU && !pi(t, i) { return NONE } # LB19 if A == QU && !pf(t, unit) { return NONE } if B == QU { # LB19a let j = next_unit(t, i) if !east(t, unit) || j == n || !east(t, j) { return NONE } } if A == QU && (!east(t, i) || unit == 0 || !east(t, t.start[unit - 1])) { return NONE } if B == CB || A == CB { return ALLOWED } # LB20 if (A == HY || base == 0x2010) && B == AL { # LB20a let w = if unit > 0 { t.eff[unit - 1] } else { SP } if end(w) || w == SP || w == ZW || w == CB || w == GL { return NONE } } if B == BA || B == HY || B == NS || A == BB { return NONE } # LB21 if (A == HY || (A == BA && !east(t, unit))) && unit > 0 && t.eff[unit - 1] == HL && B != HL { return NONE } # LB21a if A == SY && B == HL { return NONE } # LB21b if B == IN { return NONE } # LB22 if ((A == AL || A == HL) && B == NU) || (A == NU && (B == AL || B == HL)) { return NONE } # LB23 if (A == PR && (B == ID || B == EB || B == EM)) || ((A == ID || A == EB || A == EM) && B == PO) { return NONE } # LB23a if ((A == PR || A == PO) && (B == AL || B == HL)) || ((A == AL || A == HL) && (B == PR || B == PO)) { return NONE } # LB24 if (B == PO || B == PR) && (t.numeric[i - 1] || ((A == CL || A == CP) && unit > 0 && t.numeric[unit - 1])) { return NONE } # LB25 if A == PO || A == PR { if B == NU { return NONE } if B == OP { let j = next_unit(t, i) if j < n && t.eff[j] == IS { j = next_unit(t, j) } if j < n && t.eff[j] == NU { return NONE } } } if (A == HY || A == IS || t.numeric[i - 1]) && B == NU { return NONE } if A == JL && (B == JL || B == JV || B == H2 || B == H3) { return NONE } # LB26 if ((A == JV || A == H2) && (B == JV || B == JT)) || ((A == JT || A == H3) && B == JT) { return NONE } let jamo = A == JL || A == JV || A == JT || A == H2 || A == H3 # LB27 if (jamo && B == PO) || (A == PR && (B == JL || B == JV || B == JT || B == H2 || B == H3)) { return NONE } if (A == AL || A == HL) && (B == AL || B == HL) { return NONE } # LB28 if A == AP && aksara(t, i) { return NONE } # LB28a if aksara(t, unit) && (B == VF || B == VI) { return NONE } if A == VI && unit > 0 && aksara(t, t.start[unit - 1]) && (B == AK || t.codes[i] == 0x25CC) { return NONE } if aksara(t, unit) && aksara(t, i) { let j = next_unit(t, i) if j < n && t.eff[j] == VF { return NONE } } if A == IS && (B == AL || B == HL) { return NONE } # LB29 if (A == AL || A == HL || A == NU) && B == OP && !east(t, i) { return NONE } # LB30 if A == CP && !east(t, unit) && (B == AL || B == HL || B == NU) { return NONE } if A == RI && B == RI && t.ri[i - 1] % 2 == 1 { return NONE } # LB30a if B == EM && (A == EB || ranges[t.at[unit]].pictographic) { return NONE } # LB30b return ALLOWED # LB31 } on line { let codes, cls, eff, start, nonspace, ri, at: list of number let joined, numeric: list of bool for p in line.points { let i = codes.count let r = range(p.code) let c = ranges[r].class let mark = c == CM || c == ZWJ let previous = if i > 0 { eff[i - 1] } else { SP } let joins = mark && i > 0 && !end(previous) && previous != SP && previous != ZW # LB9, LB10 let e = if joins { previous } else if mark { AL } else { c } codes.append(p.code) cls.append(c) at.append(r) joined.append(joins) eff.append(e) start.append(if joins { start[i - 1] } else { i }) nonspace.append(if e != SP { start[i] } else if i > 0 { nonspace[i - 1] } else { -1 }) numeric.append(if joins { numeric[i - 1] } else { e == NU || ((e == SY || e == IS) && i > 0 && numeric[i - 1]) }) ri.append(if joins { ri[i - 1] } else if e == RI { (if i > 0 { ri[i - 1] } else { 0 }) + 1 } else { 0 }) } let t = Text { codes: codes, cls: cls, eff: eff, joined: joined, start: start, nonspace: nonspace, numeric: numeric, ri: ri, at: at } let out: list of i64 = [0] # LB2 for i in 1 .. codes.count { out.append(i64(decide(t, i))) } if codes.count > 0 { out.append(2) } # LB3 breaks = bytes(out) }