aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/044-specs/linebreak.py
blob: 3d08958474a988461d29129331a3351009a20fb1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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]