tesseract  4.0.0-1-g2a2b
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  // Workaround for Javanese Aksara's Taling, do not label it as a combiner
55  if (ch == 0xa9ba) return CharClass::kConsonant;
56  int char_type = u_charType(ch);
57  if (char_type == U_NON_SPACING_MARK || char_type == U_ENCLOSING_MARK ||
58  char_type == U_COMBINING_SPACING_MARK || ch == kZeroWidthNonJoiner ||
59  ch == kZeroWidthJoiner)
60  return CharClass::kCombiner;
61  return CharClass::kOther;
62 }
63 
64 // Helper returns true if the sequence prev_ch,ch is invalid.
65 bool ValidateGrapheme::IsBadlyFormed(char32 prev_ch, char32 ch) {
66  // Reject badly formed Indic vowels.
67  if (IsBadlyFormedIndicVowel(prev_ch, ch)) {
68  if (report_errors_)
69  tprintf("Badly formed Indic vowel sequence:0x%x 0x%x\n", prev_ch, ch);
70  return true;
71  }
72  if (IsBadlyFormedThai(prev_ch, ch)) {
73  if (report_errors_) tprintf("Badly formed Thai:0x%x 0x%x\n", prev_ch, ch);
74  return true;
75  }
76  return false;
77 }
78 
79 // Helper returns true if the sequence prev_ch,ch is an invalid Indic vowel.
80 // Some vowels in Indic scripts may be analytically decomposed into atomic pairs
81 // of components that are themselves valid unicode symbols. (See Table 12-1 in
82 // http://www.unicode.org/versions/Unicode9.0.0/ch12.pdf
83 // for examples in Devanagari). The Unicode standard discourages specifying
84 // vowels this way, but they are sometimes encountered in text, probably because
85 // some editors still permit it. Renderers however dislike such pairs, and so
86 // this function may be used to detect their occurrence for removal.
87 // TODO(rays) This function only covers a subset of Indic languages and doesn't
88 // include all rules. Add rules as appropriate to support other languages or
89 // find a way to generalize these existing rules that makes use of the
90 // regularity of the mapping from ISCII to Unicode.
91 /* static */
92 bool ValidateGrapheme::IsBadlyFormedIndicVowel(char32 prev_ch, char32 ch) {
93  return ((prev_ch == 0x905 && (ch == 0x946 || ch == 0x93E)) ||
94  (prev_ch == 0x909 && ch == 0x941) ||
95  (prev_ch == 0x90F && (ch >= 0x945 && ch <= 0x947)) ||
96  (prev_ch == 0x905 && (ch >= 0x949 && ch <= 0x94C)) ||
97  (prev_ch == 0x906 && (ch >= 0x949 && ch <= 0x94C)) ||
98  // Illegal combinations of two dependent Devanagari vowels.
99  (prev_ch == 0x93E && (ch >= 0x945 && ch <= 0x948)) ||
100  // Dependent Devanagari vowels following a virama.
101  (prev_ch == 0x94D && (ch >= 0x93E && ch <= 0x94C)) ||
102  // Bengali vowels (Table 9-5, pg 313)
103  (prev_ch == 0x985 && ch == 0x9BE) ||
104  // Telugu vowels (Table 9-19, pg 331)
105  (prev_ch == 0xC12 && (ch == 0xC55 || ch == 0xC4C)) ||
106  // Kannada vowels (Table 9-20, pg 332)
107  (prev_ch == 0xC92 && ch == 0xCCC));
108 }
109 
110 // Helper returns true if ch is a Thai consonant.
111 static bool IsThaiConsonant(char32 ch) { return 0xe01 <= ch && ch <= 0xe2e; }
112 
113 // Helper returns true is ch is a before-consonant vowel.
114 static bool IsThaiBeforeConsonantVowel(char32 ch) {
115  return 0xe40 <= ch && ch <= 0xe44;
116 }
117 
118 // Helper returns true if ch is a Thai tone mark.
119 static bool IsThaiToneMark(char32 ch) { return 0xe48 <= ch && ch <= 0xe4b; }
120 
121 // Helper returns true if ch is a Thai vowel that may be followed by a tone
122 // mark.
123 static bool IsThaiTonableVowel(char32 ch) {
124  return (0xe34 <= ch && ch <= 0xe39) || ch == 0xe31;
125 }
126 
127 // Helper returns true if the sequence prev_ch,ch is invalid Thai.
128 // These rules come from a native Thai speaker, and are not covered by the
129 // Thai section in the unicode book:
130 // http://www.unicode.org/versions/Unicode9.0.0/ch16.pdf
131 // Comments below added by Ray interpreting the code ranges.
132 /* static */
133 bool ValidateGrapheme::IsBadlyFormedThai(char32 prev_ch, char32 ch) {
134  // Tone marks must follow consonants or specific vowels.
135  if (IsThaiToneMark(ch) &&
136  !(IsThaiConsonant(prev_ch) || IsThaiTonableVowel(prev_ch))) {
137  return true;
138  }
139  // Tonable vowels must follow consonants.
140  if ((IsThaiTonableVowel(ch) || ch == 0xe47) && !IsThaiConsonant(prev_ch)) {
141  return true;
142  }
143  // Thanthakhat must follow consonant or specific vowels.
144  if (ch == 0xe4c &&
145  !(IsThaiConsonant(prev_ch) || prev_ch == 0xe38 || prev_ch == 0xe34)) {
146  return true;
147  }
148  // Nikkhahit must follow a consonant ?or certain markers?.
149  // TODO(rays) confirm this, but there were so many in the ground truth of the
150  // validation set that it seems reasonable to assume it is valid.
151  if (ch == 0xe4d &&
152  !(IsThaiConsonant(prev_ch) || prev_ch == 0xe48 || prev_ch == 0xe49)) {
153  return true;
154  }
155  // The vowels e30, e32, e33 can be used more liberally.
156  if ((ch == 0xe30 || ch == 0xe32 || ch == 0xe33) &&
157  !(IsThaiConsonant(prev_ch) || IsThaiToneMark(prev_ch)) &&
158  !(prev_ch == 0xe32 && ch == 0xe30) &&
159  !(prev_ch == 0xe4d && ch == 0xe32)) {
160  return true;
161  }
162  // Some vowels come before consonants, and therefore cannot follow things
163  // that cannot end a syllable.
164  if (IsThaiBeforeConsonantVowel(ch) &&
165  (IsThaiBeforeConsonantVowel(prev_ch) || prev_ch == 0xe31 ||
166  prev_ch == 0xe37)) {
167  return true;
168  }
169  // Don't allow the standalone vowel U+0e24 to be followed by other vowels.
170  if ((0xe30 <= ch && ch <= 0xe4D) && prev_ch == 0xe24) {
171  return true;
172  }
173  return false;
174 }
175 
176 } // namespace tesseract
std::vector< IndicPair > codes_
Definition: validator.h:232
signed int char32
static bool IsVedicAccent(char32 unicode)
Definition: validator.cpp:191
static const char32 kZeroWidthNonJoiner
Definition: validator.h:96
signed int char32
Definition: unichar.h:52
CharClass UnicodeToCharClass(char32 ch) const override
void MultiCodePart(int length)
Definition: validator.h:182
bool ConsumeGraphemeIfValid() override
DLLSYM void tprintf(const char *format,...)
Definition: tprintf.cpp:37
static const char32 kZeroWidthJoiner
Definition: validator.h:97