diff options
Diffstat (limited to 'research/experiments/044-specs/linebreak.py')
| -rw-r--r-- | research/experiments/044-specs/linebreak.py | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/research/experiments/044-specs/linebreak.py b/research/experiments/044-specs/linebreak.py new file mode 100644 index 0000000..3d08958 --- /dev/null +++ b/research/experiments/044-specs/linebreak.py @@ -0,0 +1,110 @@ +# Line breaking (UAX #14, Unicode 16.0) in plain Python: the reference 140 counts aup's lines against, the rules in the +# standard's order as Vega's text/linebreak.c has them. A character's properties come from a table of ranges (the +# check builds it from the UCD): its class after LB1, whether it is an initial or a final quotation mark (Pi, Pf), East +# Asian (width F, W or H), and pictographic and unassigned (Extended_Pictographic and Cn). +import bisect + +(BK, CR, LF, NL, SP, ZW, ZWJ, CM, WJ, GL, BA, BB, B2, HY, CB, CL, CP, EX, IN, NS, OP, QU, IS, NU, PO, PR, SY, AK, AL, AP, + AS, EB, EM, H2, H3, HL, ID, JL, JV, JT, RI, VF, VI) = range(43) +NONE, ALLOWED, MANDATORY = 0, 1, 2 +ENDS = (BK, CR, LF, NL) + + +def breaks(codes, starts, rows): + """Each boundary of codes, the ends included: NONE, ALLOWED or MANDATORY.""" + n = len(codes) + props = [rows[bisect.bisect_right(starts, c) - 1] for c in codes] # (class, pi, pf, east, pictographic) + cls = [p[0] for p in props] + eff, joined, start, nonspace, number, ri = [0] * n, [False] * n, [0] * n, [None] * n, [False] * n, [0] * n + for i in range(n): + mark = cls[i] in (CM, ZWJ) + previous = eff[i - 1] if i else SP + joined[i] = mark and i > 0 and previous not in ENDS + (SP, ZW) # LB9, LB10 + eff[i] = previous if joined[i] else AL if mark else cls[i] + start[i] = start[i - 1] if joined[i] else i + nonspace[i] = start[i] if eff[i] != SP else nonspace[i - 1] if i else None + e = eff[i] + number[i] = number[i - 1] if joined[i] else e == NU or (e in (SY, IS) and i > 0 and number[i - 1]) + ri[i] = ri[i - 1] if joined[i] else (ri[i - 1] if i else 0) + 1 if e == RI else 0 + + def next_unit(i): + j = i + 1 + while j < n and joined[j]: + j += 1 + return j + + def aksara(u): + return eff[u] in (AK, AS) or codes[u] == 0x25CC + + def decide(i): + a, b, A, B = cls[i - 1], cls[i], eff[i - 1], eff[i] + unit, before = start[i - 1], nonspace[i - 1] + base = codes[unit] + pi, pf, east, pictographic = lambda k: props[k][1], lambda k: props[k][2], lambda k: props[k][3], lambda k: props[k][4] + if a == BK: return MANDATORY # LB4 + if a == CR and b == LF: return NONE # LB5 + if a in (CR, LF, NL): return MANDATORY + if b in ENDS: return NONE # LB6 + if b in (SP, ZW): return NONE # LB7 + if before is not None and eff[before] == ZW: return ALLOWED # LB8 + if a == ZWJ: return NONE # LB8a + if joined[i]: return NONE # LB9 + if WJ in (A, B): return NONE # LB11 + if A == GL: return NONE # LB12 + if B == GL and A not in (SP, BA, HY): return NONE # LB12a + if B in (CL, CP, EX, SY): return NONE # LB13 + if before is not None and eff[before] == OP: return NONE # LB14 + if before is not None and eff[before] == QU and pi(before): # LB15a + if not before or eff[before - 1] in ENDS + (OP, QU, GL, SP, ZW): return NONE + if B == QU and pf(i): # LB15b + j = next_unit(i) + if j == n or eff[j] in (SP, GL, WJ, CL, QU, CP, EX, IS, SY, BK, CR, LF, NL, ZW): return NONE + if A == SP and B == IS: # LB15c + j = next_unit(i) + if j < n and eff[j] == NU: return ALLOWED + if B == IS: return NONE # LB15d + if B == NS and before is not None and eff[before] in (CL, CP): return NONE # LB16 + if B == B2 and before is not None and eff[before] == B2: return NONE # LB17 + if A == SP: return ALLOWED # LB18 + if B == QU and not pi(i): return NONE # LB19 + if A == QU and not pf(unit): return NONE + if B == QU: # LB19a + j = next_unit(i) + if not east(unit) or j == n or not east(j): return NONE + if A == QU and (not east(i) or not unit or not east(start[unit - 1])): return NONE + if CB in (A, B): return ALLOWED # LB20 + if (A == HY or base == 0x2010) and B == AL: # LB20a + if (eff[unit - 1] if unit else SP) in ENDS + (SP, ZW, CB, GL): return NONE + if B in (BA, HY, NS) or A == BB: return NONE # LB21 + if (A == HY or (A == BA and not east(unit))) and unit and eff[unit - 1] == HL and B != HL: return NONE # LB21a + if A == SY and B == HL: return NONE # LB21b + if B == IN: return NONE # LB22 + if (A in (AL, HL) and B == NU) or (A == NU and B in (AL, HL)): return NONE # LB23 + if (A == PR and B in (ID, EB, EM)) or (A in (ID, EB, EM) and B == PO): return NONE # LB23a + if (A in (PR, PO) and B in (AL, HL)) or (A in (AL, HL) and B in (PR, PO)): return NONE # LB24 + if B in (PO, PR) and (number[i - 1] or (A in (CL, CP) and unit and number[unit - 1])): return NONE # LB25 + if A in (PO, PR): + if B == NU: return NONE + if B == OP: + j = next_unit(i) + if j < n and eff[j] == IS: j = next_unit(j) + if j < n and eff[j] == NU: return NONE + if (A in (HY, IS) or number[i - 1]) and B == NU: return NONE + if A == JL and B in (JL, JV, H2, H3): return NONE # LB26 + if (A in (JV, H2) and B in (JV, JT)) or (A in (JT, H3) and B == JT): return NONE + if (A in (JL, JV, JT, H2, H3) and B == PO) or (A == PR and B in (JL, JV, JT, H2, H3)): return NONE # LB27 + if A in (AL, HL) and B in (AL, HL): return NONE # LB28 + if A == AP and aksara(i): return NONE # LB28a + if aksara(unit) and B in (VF, VI): return NONE + if A == VI and unit and aksara(start[unit - 1]) and (B == AK or codes[i] == 0x25CC): return NONE + if aksara(unit) and aksara(i): + j = next_unit(i) + if j < n and eff[j] == VF: return NONE + if A == IS and B in (AL, HL): return NONE # LB29 + if A in (AL, HL, NU) and B == OP and not east(i): return NONE # LB30 + if A == CP and not east(unit) and B in (AL, HL, NU): return NONE + if A == RI and B == RI and ri[i - 1] % 2: return NONE # LB30a + if B == EM and (A == EB or pictographic(unit)): return NONE # LB30b + return ALLOWED # LB31 + + return [NONE] + [decide(i) for i in range(1, n)] + [MANDATORY] if n else [NONE] |
