Coverage Report

Created: 2026-07-30 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/kcodecs/src/probers/nsHebrewProber.cpp
Line
Count
Source
1
/*  -*- C++ -*-
2
    SPDX-FileCopyrightText: 1998 Netscape Communications Corporation <developer@mozilla.org>
3
4
    SPDX-License-Identifier: MIT
5
*/
6
7
#include "nsHebrewProber.h"
8
#include "nsSBCharSetProber.h"
9
10
#include <format>
11
12
// windows-1255 / ISO-8859-8 code points of interest
13
180k
#define FINAL_KAF ('\xea')
14
74.6k
#define NORMAL_KAF ('\xeb')
15
178k
#define FINAL_MEM ('\xed')
16
73.5k
#define NORMAL_MEM ('\xee')
17
159k
#define FINAL_NUN ('\xef')
18
64.9k
#define NORMAL_NUN ('\xf0')
19
158k
#define FINAL_PE ('\xf3')
20
64.3k
#define NORMAL_PE ('\xf4')
21
154k
#define FINAL_TSADI ('\xf5')
22
#define NORMAL_TSADI ('\xf6')
23
24
// Minimum Visual vs Logical final letter score difference.
25
// If the difference is below this, don't rely solely on the final letter score distance.
26
116
#define MIN_FINAL_CHAR_DISTANCE (5)
27
28
// Minimum Visual vs Logical model score difference.
29
// If the difference is below this, don't rely at all on the model score distance.
30
53
#define MIN_MODEL_DISTANCE (0.01)
31
32
38
#define VISUAL_HEBREW_NAME ("ISO-8859-8")
33
23
#define LOGICAL_HEBREW_NAME ("windows-1255")
34
35
namespace
36
{
37
bool isFinal(char c)
38
180k
{
39
180k
    return ((c == FINAL_KAF) || (c == FINAL_MEM) || (c == FINAL_NUN) || (c == FINAL_PE) || (c == FINAL_TSADI));
40
180k
}
41
42
bool isNonFinal(char c)
43
74.6k
{
44
74.6k
    return ((c == NORMAL_KAF) || (c == NORMAL_MEM) || (c == NORMAL_NUN) || (c == NORMAL_PE));
45
    // The normal Tsadi is not a good Non-Final letter due to words like
46
    // 'lechotet' (to chat) containing an apostrophe after the tsadi. This
47
    // apostrophe is converted to a space in FilterWithoutEnglishLetters causing
48
    // the Non-Final tsadi to appear at an end of a word even though this is not
49
    // the case in the original text.
50
    // The letters Pe and Kaf rarely display a related behavior of not being a
51
    // good Non-Final letter. Words like 'Pop', 'Winamp' and 'Mubarak' for
52
    // example legally end with a Non-Final Pe or Kaf. However, the benefit of
53
    // these letters as Non-Final letters outweighs the damage since these words
54
    // are quite rare.
55
74.6k
}
56
} // namespace <anonymous>
57
58
namespace kencodingprober
59
{
60
nsHebrewProber::nsHebrewProber()
61
1.48k
    : mLogicalProb(new nsSingleByteCharSetProber<false>(&Win1255Model))
62
1.48k
    , mVisualProb(new nsSingleByteCharSetProber<true>(&Win1255Model))
63
1.48k
{
64
1.48k
}
65
/** HandleData
66
 * Final letter analysis for logical-visual decision.
67
 * Look for evidence that the received buffer is either logical Hebrew or
68
 * visual Hebrew.
69
 * The following cases are checked:
70
 * 1) A word longer than 1 letter, ending with a final letter. This is an
71
 *    indication that the text is laid out "naturally" since the final letter
72
 *    really appears at the end. +1 for logical score.
73
 * 2) A word longer than 1 letter, ending with a Non-Final letter. In normal
74
 *    Hebrew, words ending with Kaf, Mem, Nun, Pe or Tsadi, should not end with
75
 *    the Non-Final form of that letter. Exceptions to this rule are mentioned
76
 *    above in isNonFinal(). This is an indication that the text is laid out
77
 *    backwards. +1 for visual score
78
 * 3) A word longer than 1 letter, starting with a final letter. Final letters
79
 *    should not appear at the beginning of a word. This is an indication that
80
 *    the text is laid out backwards. +1 for visual score.
81
 *
82
 * The visual score and logical score are accumulated throughout the text and
83
 * are finally checked against each other in GetCharSetName().
84
 * No checking for final letters in the middle of words is done since that case
85
 * is not an indication for either Logical or Visual text.
86
 *
87
 * The input buffer should not contain any white spaces that are not (' ')
88
 * or any low-ascii punctuation marks.
89
 */
90
nsProbingState nsHebrewProber::HandleData(const char *aBuf, unsigned int aLen)
91
1.46k
{
92
1.46k
    mLogicalProb->HandleData(aBuf, aLen);
93
1.46k
    mVisualProb->HandleData(aBuf, aLen);
94
95
    // Both model probers say it's not them. No reason to continue.
96
1.46k
    if (GetState() == eNotMe) {
97
56
        return eNotMe;
98
56
    }
99
100
1.41k
    const char *endPtr = aBuf + aLen;
101
102
2.60M
    for (const char *curPtr = aBuf; curPtr < endPtr; ++curPtr) {
103
2.60M
        const char cur = *curPtr;
104
2.60M
        if (cur == ' ') { // We stand on a space - a word just ended
105
110k
            if (mBeforePrev != ' ') { // *(curPtr-2) was not a space so prev is not a 1 letter word
106
88.9k
                if (isFinal(mPrev)) { // case (1) [-2:not space][-1:final letter][cur:space]
107
14.3k
                    ++mFinalCharLogicalScore;
108
74.6k
                } else if (isNonFinal(mPrev)) { // case (2) [-2:not space][-1:Non-Final letter][cur:space]
109
11.1k
                    ++mFinalCharVisualScore;
110
11.1k
                }
111
88.9k
            }
112
2.49M
        } else { // Not standing on a space
113
2.49M
            if ((mBeforePrev == ' ') && (isFinal(mPrev)) && (cur != ' ')) { // case (3) [-2:space][-1:final letter][cur:not space]
114
12.5k
                ++mFinalCharVisualScore;
115
12.5k
            }
116
2.49M
        }
117
2.60M
        mBeforePrev = mPrev;
118
2.60M
        mPrev = cur;
119
2.60M
    }
120
121
    // Forever detecting, till the end or until both model probers return eNotMe (handled above).
122
1.41k
    return eDetecting;
123
1.46k
}
124
125
float nsHebrewProber::GetConfidence()
126
3.38k
{
127
3.38k
    if (GetState() == eNotMe) {
128
0
        return 0.01f;
129
0
    }
130
131
3.38k
    int finalsub = mFinalCharLogicalScore - mFinalCharVisualScore;
132
3.38k
    auto logicalConfidence = mLogicalProb->GetConfidence();
133
3.38k
    auto visualConfidence = mVisualProb->GetConfidence();
134
135
3.38k
    if ((logicalConfidence - 0.1 > visualConfidence) && (finalsub >= 0)) {
136
104
        return logicalConfidence;
137
3.28k
    } else if ((visualConfidence - 0.1 > logicalConfidence) && (finalsub <= 0)) {
138
186
        return visualConfidence;
139
3.09k
    } else {
140
3.09k
        return 0.01f;
141
3.09k
    }
142
3.38k
}
143
144
// Make the decision: is it Logical or Visual?
145
const char *nsHebrewProber::GetCharSetName()
146
61
{
147
    // If the final letter score distance is dominant enough, rely on it.
148
61
    int finalsub = mFinalCharLogicalScore - mFinalCharVisualScore;
149
61
    if (finalsub >= MIN_FINAL_CHAR_DISTANCE) {
150
6
        return LOGICAL_HEBREW_NAME;
151
6
    }
152
55
    if (finalsub <= -(MIN_FINAL_CHAR_DISTANCE)) {
153
20
        return VISUAL_HEBREW_NAME;
154
20
    }
155
156
    // It's not dominant enough, try to rely on the model scores instead.
157
35
    float modelsub = mLogicalProb->GetConfidence() - mVisualProb->GetConfidence();
158
35
    if (modelsub > MIN_MODEL_DISTANCE) {
159
17
        return LOGICAL_HEBREW_NAME;
160
17
    }
161
18
    if (modelsub < -(MIN_MODEL_DISTANCE)) {
162
18
        return VISUAL_HEBREW_NAME;
163
18
    }
164
165
    // Still no good, back to final letter distance, maybe it'll save the day.
166
0
    if (finalsub < 0) {
167
0
        return VISUAL_HEBREW_NAME;
168
0
    }
169
170
    // (finalsub > 0 - Logical) or (don't know what to do) default to Logical.
171
0
    return LOGICAL_HEBREW_NAME;
172
0
}
173
174
nsProbingState nsHebrewProber::GetState(void)
175
4.85k
{
176
    // Remain active as long as any of the model probers are active.
177
4.85k
    if ((mLogicalProb->GetState() == eNotMe) && (mVisualProb->GetState() == eNotMe)) {
178
56
        return eNotMe;
179
56
    }
180
4.79k
    return eDetecting;
181
4.85k
}
182
183
std::string nsHebrewProber::StatusOutput(uint8_t indent)
184
0
{
185
0
    indent += 2;
186
0
    auto output = std::format("{:1.3f} [HEB] {} -- {}", GetConfidence(), mFinalCharLogicalScore, mFinalCharVisualScore);
187
0
    output += '\n' + std::string(indent, ' ') + "  Log: ";
188
0
    output += mLogicalProb->StatusOutput(indent);
189
0
    output += '\n' + std::string(indent, ' ') + "  Vis: ";
190
0
    output += mVisualProb->StatusOutput(indent);
191
0
    return output;
192
0
}
193
}