tesseract  4.00.00dev
validate_grapheme.cpp
Go to the documentation of this file.
1 #include "validate_grapheme.h"
2 #include "tprintf.h"
3 #include "unicode/uchar.h" // From libicu
4 
5 namespace tesseract {
6 
8  int num_codes = codes_.size();
9  char32 prev_prev_ch = ' ';
10  char32 prev_ch = ' ';
12  int num_codes_in_grapheme = 0;
13  while (codes_used_ < num_codes) {
14  CharClass cc = codes_[codes_used_].first;
15  char32 ch = codes_[codes_used_].second;
16  const bool is_combiner =
18  // Reject easily detected badly formed sequences.
19  if (prev_cc == CharClass::kWhitespace && is_combiner) {
20  if (report_errors_) tprintf("Word started with a combiner:0x%x\n", ch);
21  return false;
22  }
23  if (prev_cc == CharClass::kVirama && cc == CharClass::kVirama) {
24  if (report_errors_)
25  tprintf("Two grapheme links in a row:0x%x 0x%x\n", prev_ch, ch);
26  return false;
27  }
28  if (prev_cc != CharClass::kWhitespace && cc != CharClass::kWhitespace &&
29  IsBadlyFormed(prev_ch, ch)) {
30  return false;
31  }
32  bool prev_is_fwd_combiner =
33  prev_ch == kZeroWidthJoiner || prev_cc == CharClass::kVirama ||
34  (prev_ch == kZeroWidthNonJoiner &&
35  (cc == CharClass::kVirama || prev_prev_ch == kZeroWidthJoiner));
36  if (num_codes_in_grapheme > 0 && !is_combiner && !prev_is_fwd_combiner)
37  break;
39  ++num_codes_in_grapheme;
40  prev_prev_ch = prev_ch;
41  prev_ch = ch;
42  prev_cc = cc;
43  }
44  if (num_codes_in_grapheme > 0) MultiCodePart(num_codes_in_grapheme);
45  return true;
46 }
47 
49  if (IsVedicAccent(ch)) return CharClass::kVedicMark;
50  // The ZeroWidth[Non]Joiner characters are mapped to kCombiner as they
51  // always combine with the previous character.
52  if (u_hasBinaryProperty(ch, UCHAR_GRAPHEME_LINK)) return CharClass::kVirama;
53  if (u_isUWhiteSpace(ch)) return CharClass::kWhitespace;
54  int char_type = u_charType(ch);
55  if (char_type == U_NON_SPACING_MARK || char_type == U_ENCLOSING_MARK ||
56  char_type == U_COMBINING_SPACING_MARK || ch == kZeroWidthNonJoiner ||
57  ch == kZeroWidthJoiner)
58  return CharClass::kCombiner;
59  return CharClass::kOther;
60 }
61 
62 // Helper returns true if the sequence prev_ch,ch is invalid.
63 bool ValidateGrapheme::IsBadlyFormed(char32 prev_ch, char32 ch) {
64  // Reject badly formed Indic vowels.
65  if (IsBadlyFormedIndicVowel(prev_ch, ch)) {
66  if (report_errors_)
67  tprintf("Badly formed Indic vowel sequence:0x%x 0x%x\n", prev_ch, ch);
68  return true;
69  }
70  if (IsBadlyFormedThai(prev_ch, ch)) {
71  if (report_errors_) tprintf("Badly formed Thai:0x%x 0x%x\n", prev_ch, ch);
72  return true;
73  }
74  return false;
75 }
76 
77 // Helper returns true if the sequence prev_ch,ch is an invalid Indic vowel.
78 // Some vowels in Indic scripts may be analytically decomposed into atomic pairs
79 // of components that are themselves valid unicode symbols. (See Table 12-1 in
80 // http://www.unicode.org/versions/Unicode9.0.0/ch12.pdf
81 // for examples in Devanagari). The Unicode standard discourages specifying
82 // vowels this way, but they are sometimes encountered in text, probably because
83 // some editors still permit it. Renderers however dislike such pairs, and so
84 // this function may be used to detect their occurrence for removal.
85 // TODO(rays) This function only covers a subset of Indic languages and doesn't
86 // include all rules. Add rules as appropriate to support other languages or
87 // find a way to generalize these existing rules that makes use of the
88 // regularity of the mapping from ISCII to Unicode.
89 /* static */
90 bool ValidateGrapheme::IsBadlyFormedIndicVowel(char32 prev_ch, char32 ch) {
91  return ((prev_ch == 0x905 && (ch == 0x946 || ch == 0x93E)) ||
92  (prev_ch == 0x909 && ch == 0x941) ||
93  (prev_ch == 0x90F && (ch >= 0x945 && ch <= 0x947)) ||
94  (prev_ch == 0x905 && (ch >= 0x949 && ch <= 0x94C)) ||
95  (prev_ch == 0x906 && (ch >= 0x949 && ch <= 0x94C)) ||
96  // Illegal combinations of two dependent Devanagari vowels.
97  (prev_ch == 0x93E && (ch >= 0x945 && ch <= 0x948)) ||
98  // Dependent Devanagari vowels following a virama.
99  (prev_ch == 0x94D && (ch >= 0x93E && ch <= 0x94C)) ||
100  // Bengali vowels (Table 9-5, pg 313)
101  (prev_ch == 0x985 && ch == 0x9BE) ||
102  // Telugu vowels (Table 9-19, pg 331)
103  (prev_ch == 0xC12 && (ch == 0xC55 || ch == 0xC4C)) ||
104  // Kannada vowels (Table 9-20, pg 332)
105  (prev_ch == 0xC92 && ch == 0xCCC));
106 }
107 
108 // Helper returns true if ch is a Thai consonant.
109 static bool IsThaiConsonant(char32 ch) { return 0xe01 <= ch && ch <= 0xe2e; }
110 
111 // Helper returns true is ch is a before-consonant vowel.
112 static bool IsThaiBeforeConsonantVowel(char32 ch) {
113  return 0xe40 <= ch && ch <= 0xe44;
114 }
115 
116 // Helper returns true if ch is a Thai tone mark.
117 static bool IsThaiToneMark(char32 ch) { return 0xe48 <= ch && ch <= 0xe4b; }
118 
119 // Helper returns true if ch is a Thai vowel that may be followed by a tone
120 // mark.
121 static bool IsThaiTonableVowel(char32 ch) {
122  return (0xe34 <= ch && ch <= 0xe39) || ch == 0xe31;
123 }
124 
125 // Helper returns true if the sequence prev_ch,ch is invalid Thai.
126 // These rules come from a native Thai speaker, and are not covered by the
127 // Thai section in the unicode book:
128 // http://www.unicode.org/versions/Unicode9.0.0/ch16.pdf
129 // Comments below added by Ray interpreting the code ranges.
130 /* static */
131 bool ValidateGrapheme::IsBadlyFormedThai(char32 prev_ch, char32 ch) {
132  // Tone marks must follow consonants or specific vowels.
133  if (IsThaiToneMark(ch) &&
134  !(IsThaiConsonant(prev_ch) || IsThaiTonableVowel(prev_ch))) {
135  return true;
136  }
137  // Tonable vowels must follow consonants.
138  if ((IsThaiTonableVowel(ch) || ch == 0xe47) && !IsThaiConsonant(prev_ch)) {
139  return true;
140  }
141  // Thanthakhat must follow consonant or specific vowels.
142  if (ch == 0xe4c &&
143  !(IsThaiConsonant(prev_ch) || prev_ch == 0xe38 || prev_ch == 0xe34)) {
144  return true;
145  }
146  // Nikkhahit must follow a consonant ?or certain markers?.
147  // TODO(rays) confirm this, but there were so many in the ground truth of the
148  // validation set that it seems reasonable to assume it is valid.
149  if (ch == 0xe4d &&
150  !(IsThaiConsonant(prev_ch) || prev_ch == 0xe48 || prev_ch == 0xe49)) {
151  return true;
152  }
153  // The vowels e30, e32, e33 can be used more liberally.
154  if ((ch == 0xe30 || ch == 0xe32 || ch == 0xe33) &&
155  !(IsThaiConsonant(prev_ch) || IsThaiToneMark(prev_ch)) &&
156  !(prev_ch == 0xe32 && ch == 0xe30) &&
157  !(prev_ch == 0xe4d && ch == 0xe32)) {
158  return true;
159  }
160  // Some vowels come before consonants, and therefore cannot follow things
161  // that cannot end a syllable.
162  if (IsThaiBeforeConsonantVowel(ch) &&
163  (IsThaiBeforeConsonantVowel(prev_ch) || prev_ch == 0xe31 ||
164  prev_ch == 0xe37)) {
165  return true;
166  }
167  // Don't allow the standalone vowel U+0e24 to be followed by other vowels.
168  if ((0xe30 <= ch && ch <= 0xe4D) && prev_ch == 0xe24) {
169  return true;
170  }
171  return false;
172 }
173 
174 } // namespace tesseract
CharClass UnicodeToCharClass(char32 ch) const override
#define tprintf(...)
Definition: tprintf.h:31
signed int char32
Definition: unichar.h:52
std::vector< IndicPair > codes_
Definition: validator.h:228
static bool IsVedicAccent(char32 unicode)
Definition: validator.cpp:179
void MultiCodePart(int length)
Definition: validator.h:181
static const char32 kZeroWidthJoiner
Definition: validator.h:96
bool ConsumeGraphemeIfValid() override
static const char32 kZeroWidthNonJoiner
Definition: validator.h:95