/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 | | } |