aboutsummaryrefslogtreecommitdiffstats
path: root/text/sentence.c
blob: 4d3a7af51a9e23adeb0efd478e2712da8bb847ac (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
/* Sentence boundaries (UAX #29, Unicode 15.1): text/text.h. Extends and
 * formats fold into the character before them (SB5); the rules after a
 * sentence terminal look back through closing punctuation and spaces (kept
 * as the pass goes) and, for SB8, ahead to the next letter (found for
 * every position by one pass from the end). */
#include "text.h"
#include <stdlib.h>

enum { OTHER = VEGA_SB_OTHER, CR = VEGA_SB_CR, LF = VEGA_SB_LF, EXTEND = VEGA_SB_EXTEND, SEP = VEGA_SB_SEP,
       FORMAT = VEGA_SB_FORMAT, SP = VEGA_SB_SP, LOWER = VEGA_SB_LOWER, UPPER = VEGA_SB_UPPER, OLETTER = VEGA_SB_OLETTER,
       NUMERIC = VEGA_SB_NUMERIC, ATERM = VEGA_SB_ATERM, SCONTINUE = VEGA_SB_SCONTINUE, STERM = VEGA_SB_STERM,
       CLOSE = VEGA_SB_CLOSE };

static bool paragraph_end(unsigned c) { return c == SEP || c == CR || c == LF; }
static bool terminal(unsigned c) { return c == ATERM || c == STERM; }

bool vega_sentences(const uint32_t *codes, size_t count, uint8_t *breaks) {
    breaks[0] = count > 0; /* SB1 */
    if (!count) return true;
    uint8_t *eff = malloc(3 * count);
    if (!eff) return false;
    uint8_t *folded = eff + count, *stop = eff + 2 * count;
    for (size_t i = 0; i < count; ++i) {
        unsigned c = vega_sentence(codes[i]);
        folded[i] = (c == EXTEND || c == FORMAT) && i && !paragraph_end(eff[i - 1]);
        eff[i] = folded[i] ? eff[i - 1] : (uint8_t)c;
    }
    /* SB8's look ahead: from each position, the class of the first unit
     * that is a letter, a paragraph end or a terminal (OTHER when none is). */
    unsigned next = OTHER;
    for (size_t i = count; i--;) {
        unsigned c = eff[i];
        if (!folded[i] && (c == OLETTER || c == UPPER || c == LOWER || paragraph_end(c) || terminal(c))) next = c;
        stop[i] = (uint8_t)next;
    }
    /* After each unit: the terminal the text ends in, with only closes and
     * then spaces after it (OTHER when it does not end so); whether spaces
     * came yet; the unit before that terminal. */
    unsigned term = OTHER, before_term = OTHER, previous = OTHER;
    bool spaces = false;
    for (size_t i = 0; i < count; ++i) {
        if (folded[i]) {
            breaks[i] = 0; /* SB5 */
            continue;
        }
        unsigned B = eff[i];
        if (i) {
            unsigned A = previous;
            bool join;
            if (A == CR && B == LF) join = true;                                         /* SB3 */
            else if (paragraph_end(A)) join = false;                                     /* SB4 */
            else if (A == ATERM && B == NUMERIC) join = true;                            /* SB6 */
            else if (A == ATERM && (before_term == UPPER || before_term == LOWER) && B == UPPER) join = true; /* SB7 */
            else if (term == ATERM && stop[i] == LOWER) join = true;                     /* SB8 */
            else if (term != OTHER && (B == SCONTINUE || terminal(B))) join = true;      /* SB8a */
            else if (term != OTHER && !spaces && (B == CLOSE || B == SP || paragraph_end(B))) join = true; /* SB9 */
            else if (term != OTHER && (B == SP || paragraph_end(B))) join = true;        /* SB10 */
            else if (term != OTHER) join = false;                                        /* SB11 */
            else join = true;                                                            /* SB998 */
            breaks[i] = !join;
        }
        if (terminal(B)) {
            before_term = previous;
            term = B;
            spaces = false;
        } else if (term != OTHER && B == CLOSE && !spaces) {
            /* still closing the sentence */
        } else if (term != OTHER && B == SP) spaces = true;
        else term = OTHER;
        previous = B;
    }
    breaks[count] = 1; /* SB2 */
    free(eff);
    return true;
}