1 /*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #ifdef ENABLE_TEXT_ENHANCE
16 #include <algorithm>
17 #ifdef _WIN32
18 #include <cstdlib>
19 #else
20 #include <cstddef>
21 #include <climits>
22 #endif
23 #include <iostream>
24 #include <fstream>
25 #include <unicode/utf.h>
26 #include <unicode/utf8.h>
27 #include <unordered_set>
28
29 #include "include/Hyphenator.h"
30 #include "log.h"
31 #include "trace.h"
32
33 namespace skia {
34 namespace textlayout {
35 std::once_flag Hyphenator::initFlag;
36 const std::map<std::string, std::string> HPB_FILE_NAMES = {
37 {"as", "hyph-as.hpb"}, // Assamese
38 {"be", "hyph-be.hpb"}, // Belarusian
39 {"bg", "hyph-bg.hpb"}, // Bulgarian
40 {"bn", "hyph-bn.hpb"}, // Bengali
41 {"cs", "hyph-cs.hpb"}, // Czech
42 {"cy", "hyph-cy.hpb"}, // Welsh
43 {"da", "hyph-da.hpb"}, // Danish
44 {"de-1996", "hyph-de-1996.hpb"}, // German,1996orthography
45 {"de-1901", "hyph-de-1901.hpb"}, // German,1901orthography
46 {"de-ch-1901", "hyph-de-ch-1901.hpb"}, // SwissGerman,1901orthography
47 {"el-monoton", "hyph-el-monoton.hpb"}, // ModernGreek,monotonic
48 {"el-polyton", "hyph-el-polyton.hpb"}, // ModernGreek,polytonic
49 {"en-latn", "hyph-en-gb.hpb"}, // Latin English
50 {"en-gb", "hyph-en-gb.hpb"}, // British English
51 {"en-us", "hyph-en-us.hpb"}, // American English
52 {"es", "hyph-es.hpb"}, // Spanish
53 {"et", "hyph-et.hpb"}, // Estonian
54 {"fr", "hyph-fr.hpb"}, // French
55 {"ga", "hyph-ga.hpb"}, // Irish
56 {"gl", "hyph-gl.hpb"}, // Galician
57 {"gu", "hyph-gu.hpb"}, // Gujarati
58 {"hi", "hyph-hi.hpb"}, // Hindi
59 {"hr", "hyph-hr.hpb"}, // Croatian
60 {"hu", "hyph-hu.hpb"}, // Hungarian
61 {"hy", "hyph-hy.hpb"}, // Armenian
62 {"id", "hyph-id.hpb"}, // Indonesian
63 {"is", "hyph-is.hpb"}, // Icelandic
64 {"it", "hyph-it.hpb"}, // Italian
65 {"ka", "hyph-ka.hpb"}, // Georgian
66 {"kn", "hyph-kn.hpb"}, // Kannada
67 {"la", "hyph-la.hpb"}, // Latin
68 {"lt", "hyph-lt.hpb"}, // Lithuanian
69 {"lv", "hyph-lv.hpb"}, // Latvian
70 {"mk", "hyph-mk.hpb"}, // Macedonian
71 {"ml", "hyph-ml.hpb"}, // Malayalam
72 {"mn-cyrl", "hyph-mn-cyrl.hpb"}, // Mongolian,Cyrillicscript
73 {"mr", "hyph-mr.hpb"}, // Marathi
74 {"mul-ethi", "hyph-mul-ethi.hpb"}, // Ethiopic
75 {"nl", "hyph-nl.hpb"}, // Dutch
76 {"or", "hyph-or.hpb"}, // Oriya
77 {"pa", "hyph-pa.hpb"}, // Punjabi
78 {"pl", "hyph-pl.hpb"}, // Polish
79 {"pt", "hyph-pt.hpb"}, // Portuguese
80 {"rm", "hyph-rm.hpb"}, // Romansh
81 {"ru", "hyph-ru.hpb"}, // Russian
82 {"sh-cyrl", "hyph-sh-cyrl.hpb"}, // Serbo-Croatian,Cyrillicscript
83 {"sh-latn", "hyph-sh-latn.hpb"}, // Serbo-Croatian,Latinscript
84 {"sk", "hyph-sk.hpb"}, // Slovak
85 {"sl", "hyph-sl.hpb"}, // Slovenian
86 {"sr-cyrl", "hyph-sr-cyrl.hpb"}, // Serbian,Cyrillicscript
87 {"sv", "hyph-sv.hpb"}, // Swedish
88 {"ta", "hyph-ta.hpb"}, // Tamil
89 {"te", "hyph-te.hpb"}, // Telugu
90 {"th", "hyph-th.hpb"}, // Thai
91 {"tk", "hyph-tk.hpb"}, // Turkmen
92 {"tr", "hyph-tr.hpb"}, // Turkish
93 {"uk", "hyph-uk.hpb"}, // Ukrainian
94 {"pinyin", "hyph-zh-latn-pinyin.hpb"}, // Chinese,Pinyin. language code ‘pinyin’ is not right,will be repair later
95 };
96
97 // in hyphenation, when a word ends with below chars, the char(s) is stripped during hyphenation.
98 const std::unordered_set<uint16_t> EXCLUDED_WORD_ENDING_CHARS = {
99 0x21, // !
100 0x22, // "
101 0x23, // #
102 0x24, // $
103 0x25, // %
104 0x26, // &
105 0x27, // '
106 0x28, // (
107 0x29, // )
108 0x2A, // *
109 0x2B, // +
110 0x2C, // ,
111 0x2D, // -
112 0x2e, // .
113 0x2f, // /
114 0x3A, // :
115 0x3b, // ;
116 0x3C, // <
117 0x3D, // =
118 0x3E, // >
119 0x3F // ?
120 };
121
122 struct HyphenTableInfo {
123 const HyphenatorHeader* header{nullptr};
124 const uint32_t* maindict{nullptr};
125 const ArrayOf16bits* mappings{nullptr};
126
initHyphenTableInfoskia::textlayout::HyphenTableInfo127 bool initHyphenTableInfo(const std::vector<uint8_t>& hyphenatorData) {
128 if (hyphenatorData.size() < sizeof(HyphenatorHeader)) {
129 return false;
130 }
131 header = reinterpret_cast<const HyphenatorHeader*>(hyphenatorData.data());
132 // get master table, it always is in direct mode
133 maindict = (uint32_t*)(hyphenatorData.data() + header->toc);
134 mappings = reinterpret_cast<const ArrayOf16bits*>(hyphenatorData.data() + header->mappings);
135 // this is actually beyond the real 32 bit address, but just to have an offset that
136 // is clearly out of bounds without recalculating it again
137 return !(header->minCp == header->maxCp && mappings->count == 0);
138 }
139 };
140
141 struct HyphenSubTable {
142 uint16_t* staticOffset{nullptr};
143 uint32_t nextOffset{0};
144 PathType type{PathType::PATTERN};
145
initHyphenSubTableInfoskia::textlayout::HyphenSubTable146 bool initHyphenSubTableInfo(uint16_t& code, uint16_t& offset, HyphenTableInfo& hyphenInfo) {
147 auto header = hyphenInfo.header;
148 if (offset == header->maxCount(hyphenInfo.mappings)) {
149 code = 0;
150 return false;
151 }
152
153 uint32_t baseOffset = *(hyphenInfo.maindict + offset - 1); // previous entry end
154 uint32_t initialValue = *(hyphenInfo.maindict + offset);
155 this->type = (PathType)(initialValue >> HYPHEN_SHIFT_BITS_30);
156 // direct and pairs need to have offset different from zero
157 if (initialValue == 0 && (type == PathType::DIRECT || type == PathType::PAIRS)) {
158 return false;
159 }
160 // base offset is 16 bit
161 auto address = reinterpret_cast<const uint8_t*>(header);
162 this->staticOffset = (uint16_t*)(address + HYPHEN_BASE_CODE_SHIFT * baseOffset);
163
164 // get a subtable according character
165 // once: read as 32bit, the rest of the access will be 16bit (13bit for offsets)
166 this->nextOffset = (initialValue & 0x3fffffff);
167 return true;
168 }
169 };
170
171 struct HyphenFindBreakParam {
172 const HyphenatorHeader* header{nullptr};
173 HyphenSubTable hyphenSubTable;
174 uint16_t code{0};
175 uint16_t offset{0};
176 };
177
ReadBinaryFile(const std::string & filePath,std::vector<uint8_t> & buffer)178 void ReadBinaryFile(const std::string& filePath, std::vector<uint8_t>& buffer) {
179 char tmpPath[PATH_MAX] = {0};
180 if (filePath.size() > PATH_MAX) {
181 TEXT_LOGE("File name is too long");
182 return;
183 }
184 #ifdef _WIN32
185 auto canonicalFilePath = _fullpath(tmpPath, filePath.c_str(), sizeof(tmpPath));
186 #else
187 auto canonicalFilePath = realpath(filePath.c_str(), tmpPath);
188 #endif
189 if (canonicalFilePath == nullptr) {
190 TEXT_LOGE("Invalid file %{public}s", filePath.c_str());
191 return;
192 }
193 std::ifstream file(canonicalFilePath, std::ifstream::binary);
194 if (!file.is_open()) {
195 TEXT_LOGE("Failed to open %{public}s", filePath.c_str());
196 return;
197 }
198
199 file.seekg(0, std::ios::end);
200 std::streamsize length = file.tellg();
201 file.seekg(0, std::ios::beg);
202
203 buffer.resize(length);
204 if (!file.read(reinterpret_cast<char*>(buffer.data()), length)) {
205 TEXT_LOGE("Failed to read %{public}s", filePath.c_str());
206 }
207 file.close();
208 }
209
getLanguageCode(std::string locale,int hyphenPos)210 std::string getLanguageCode(std::string locale, int hyphenPos) {
211 // to lower case
212 std::transform(locale.begin(), locale.end(), locale.begin(), ::tolower);
213
214 // find '-',substring the locale
215 size_t pos = std::string::npos;
216 int count = 0;
217 for (size_t i = 0; i < locale.size(); ++i) {
218 if (locale[i] == '-') {
219 ++count;
220 if (count == hyphenPos) {
221 pos = i;
222 break;
223 }
224 }
225 }
226
227 if (pos != std::string::npos) {
228 return locale.substr(0, pos);
229 } else {
230 return locale;
231 }
232 }
233
initTrieTree()234 void Hyphenator::initTrieTree() {
235 for (const auto& item : HPB_FILE_NAMES) {
236 fTrieTree.insert(item.first, item.second);
237 }
238 }
239
getHyphenatorData(const std::string & locale)240 const std::vector<uint8_t>& Hyphenator::getHyphenatorData(const std::string& locale) {
241 const std::vector<uint8_t>& firstResult =
242 findHyphenatorData(getLanguageCode(locale, 2)); //num 2:sub string locale to the second '-'
243 if (!firstResult.empty()) {
244 return firstResult;
245 } else {
246 return findHyphenatorData(getLanguageCode(locale, 1));
247 }
248 }
249
findHyphenatorData(const std::string & langCode)250 const std::vector<uint8_t>& Hyphenator::findHyphenatorData(const std::string& langCode) {
251 {
252 std::shared_lock<std::shared_mutex> readLock(mutex_);
253 auto search = fHyphenMap.find(langCode);
254 if (search != fHyphenMap.end()) {
255 return search->second;
256 }
257 }
258
259 return loadPatternFile(langCode);
260 }
261
loadPatternFile(const std::string & langCode)262 const std::vector<uint8_t>& Hyphenator::loadPatternFile(const std::string& langCode) {
263 std::unique_lock<std::shared_mutex> writeLock(mutex_);
264 auto search = fHyphenMap.find(langCode);
265 if (search != fHyphenMap.end()) {
266 return search->second;
267 }
268 std::string hpbFileName = fTrieTree.findPartialMatch(langCode);
269 if (!hpbFileName.empty()) {
270 std::string filename = "/system/usr/ohos_hyphen_data/" + hpbFileName;
271 std::vector<uint8_t> fileBuffer;
272 ReadBinaryFile(filename, fileBuffer);
273 if (!fileBuffer.empty()) {
274 fHyphenMap.emplace(langCode, std::move(fileBuffer));
275 return fHyphenMap[langCode];
276 }
277 }
278 return fEmptyResult;
279 }
280
formatTarget(std::vector<uint16_t> & target)281 void formatTarget(std::vector<uint16_t>& target) {
282 while (EXCLUDED_WORD_ENDING_CHARS.find(target.back()) != EXCLUDED_WORD_ENDING_CHARS.end()) {
283 target.pop_back();
284 if (target.empty()) {
285 // nothing to be hyphenated
286 return;
287 }
288 }
289 target.insert(target.cbegin(), '.');
290 target.push_back('.');
291
292 for (auto& code : target) {
293 HyphenatorHeader::toLower(code);
294 }
295 }
296
processPattern(const Pattern * p,size_t count,uint32_t index,std::vector<uint16_t> & word,std::vector<uint8_t> & res)297 void processPattern(const Pattern* p, size_t count, uint32_t index, std::vector<uint16_t>& word,
298 std::vector<uint8_t>& res) {
299 TEXT_LOGD("Index:%{public}u", index);
300 if (count > 0) {
301 count *= 0x4; // patterns are padded to 4 byte arrays
302 // when we reach pattern node (leaf), we need to increase index by one because of our
303 // own code offset
304 for (size_t currentIndex = 0; index < res.size() && currentIndex < count; index++) {
305 TEXT_LOGD("Pattern info:%{public}zu, %{public}u, 0x%{public}x", count, index, p->patterns[currentIndex]);
306 res[index] = std::max(res[index], (p->patterns[currentIndex]));
307 currentIndex++;
308 }
309 }
310 }
311
processLinear(uint16_t * data,size_t index,HyphenFindBreakParam & param,std::vector<uint16_t> & word,std::vector<uint8_t> & res)312 void processLinear(uint16_t* data, size_t index, HyphenFindBreakParam& param, std::vector<uint16_t>& word,
313 std::vector<uint8_t>& res) {
314 TEXT_LOGD("Index:%{public}zu", index);
315 const ArrayOf16bits* p = reinterpret_cast<const ArrayOf16bits*>(data);
316 uint16_t count = p->count;
317 if (count > index + 1) {
318 // the pattern is longer than the remaining word
319 return;
320 }
321 index--;
322
323 // check the rest of the string
324 for (auto j = 0; j < count; j++) {
325 if (p->codes[j] != word[index]) {
326 return;
327 } else {
328 index--;
329 }
330 }
331 uint32_t offset = 1 + count; // array size, code points, no padding for 16 bit
332 uint16_t pOffset = *(data + offset);
333 offset++; // move forward, after pattern
334 if (pOffset == 0) {
335 return;
336 }
337
338 const Pattern* matchPattern =
339 reinterpret_cast<const Pattern*>(reinterpret_cast<const uint8_t*>(param.header) + (pOffset & 0xfff));
340 index++; // matching peeks ahead
341 processPattern(matchPattern, (pOffset >> 0xc), index, word, res);
342 if (*(data + offset) != 0) { // peek if there is more to come
343 return processLinear(data + offset, index, param, word, res);
344 }
345 }
346
processDirect(uint16_t * data,HyphenFindBreakParam & param,uint32_t & nextOffset,PathType & type)347 bool processDirect(uint16_t* data, HyphenFindBreakParam& param, uint32_t& nextOffset, PathType& type) {
348 TEXT_LOGD("");
349 param.offset = param.header->codeOffset(param.code);
350 if (param.header->minCp != param.header->maxCp && param.offset > param.header->maxCp) {
351 return false;
352 }
353 uint16_t nextValue = *(data + nextOffset + param.offset);
354 nextOffset = nextValue & 0x3fff; // Use mask 0x3fff to extract the lower 14 bits of nextValue
355 type = (PathType)(nextValue >> HYPHEN_SHIFT_BITS_14);
356 return true;
357 }
358
processPairs(const ArrayOf16bits * data,HyphenFindBreakParam & param,uint16_t code,uint32_t & nextOffset,PathType & type)359 bool processPairs(const ArrayOf16bits* data, HyphenFindBreakParam& param, uint16_t code, uint32_t& nextOffset,
360 PathType& type) {
361 TEXT_LOGD("Code:0x%{public}x", code);
362 uint16_t count = data->count;
363 bool match = false;
364 for (size_t j = 0; j < count; j += HYPHEN_BASE_CODE_SHIFT) {
365 if (data->codes[j] == code) {
366 nextOffset = data->codes[j + 1] & 0x3fff;
367 type = (PathType)(data->codes[j + 1] >> HYPHEN_SHIFT_BITS_14);
368 match = true;
369 break;
370 } else if (data->codes[j] > code) {
371 break;
372 }
373 }
374 return match;
375 }
376
findBreakByType(HyphenFindBreakParam & param,const size_t & targetIndex,std::vector<uint16_t> & target,std::vector<uint8_t> & result)377 void findBreakByType(HyphenFindBreakParam& param, const size_t& targetIndex, std::vector<uint16_t>& target,
378 std::vector<uint8_t>& result) {
379 TEXT_LOGD("TopLevel:%{public}zu", targetIndex);
380 auto [staticOffset, nextOffset, type] = param.hyphenSubTable;
381 uint32_t index = 0; // used in inner loop to traverse path further (backwards)
382 while (true) {
383 TEXT_LOGD("Loop:%{public}zu %{public}u", targetIndex, index);
384 // there is always at 16bit of pattern address before next node data
385 uint16_t pOffset = *(staticOffset + nextOffset);
386 // from binary version 2 onwards, we have common nodes with 16bit offset (not bound to code points)
387 if (type == PathType::PATTERN && (param.header->version >> 0x18) > 1) {
388 pOffset =
389 *(reinterpret_cast<const uint16_t*>(param.header) + nextOffset + (param.header->version & 0xffff));
390 }
391 nextOffset++;
392 if (pOffset > 0) {
393 // if we have reached pattern, apply it to result
394 uint16_t count = (pOffset >> 0xc);
395 pOffset = 0xfff & pOffset;
396 auto p = reinterpret_cast<const Pattern*>(reinterpret_cast<const uint8_t*>(param.header) + pOffset);
397 processPattern(p, count, targetIndex - index, target, result);
398 }
399 if (type == PathType::PATTERN) {
400 // just break the loop
401 break;
402 } else if (type == PathType::DIRECT) {
403 if (index == targetIndex) {
404 break;
405 }
406 index++; // resolve new code point (on the left)
407 param.code = target[targetIndex - index];
408 if (!processDirect(staticOffset, param, nextOffset, type)) {
409 break;
410 }
411 } else if (type == PathType::LINEAR) {
412 processLinear((staticOffset + nextOffset), targetIndex - index, param, target, result);
413 // when a linear element has been processed, we always break and move to next top level index
414 break;
415 } else {
416 if (index == targetIndex) {
417 break;
418 }
419 index++;
420 auto p = reinterpret_cast<const ArrayOf16bits*>(staticOffset + nextOffset);
421 if (!processPairs(p, param, target[targetIndex - index], nextOffset, type)) {
422 break;
423 }
424 }
425 }
426 }
427
findBreaks(const std::vector<uint8_t> & hyphenatorData,std::vector<uint16_t> & target,std::vector<uint8_t> & result)428 void findBreaks(const std::vector<uint8_t>& hyphenatorData, std::vector<uint16_t>& target,
429 std::vector<uint8_t>& result) {
430 HyphenTableInfo hyphenInfo;
431 if (!hyphenInfo.initHyphenTableInfo(hyphenatorData)) {
432 return;
433 }
434
435 if (target.size() > 0) {
436 for (size_t i = target.size() - 1; i >= 1; --i) {
437 HyphenSubTable hyphenSubTable;
438 auto header = hyphenInfo.header;
439 auto code = target[i];
440 auto offset = header->codeOffset(code, hyphenInfo.mappings);
441 if (!hyphenSubTable.initHyphenSubTableInfo(code, offset, hyphenInfo)) {
442 continue;
443 }
444 HyphenFindBreakParam param{header, hyphenSubTable, code, offset};
445 findBreakByType(param, i, target, result);
446 }
447 }
448 }
449
getLanguagespecificLeadingBounds(const std::string & locale)450 size_t getLanguagespecificLeadingBounds(const std::string& locale) {
451 static const std::unordered_set<std::string> specialLocales = {"ka", "hy", "pinyin", "el-monoton", "el-polyton"};
452 size_t lead = 2; // hardcoded for the most of the language pattern files
453 if (specialLocales.count(locale)) {
454 lead = 1;
455 }
456 return lead + 1; // we pad the target with surrounding marks ('.'), thus +1
457 }
458
getLanguagespecificTrailingBounds(const std::string & locale)459 size_t getLanguagespecificTrailingBounds(const std::string& locale) {
460 static const std::unordered_set<std::string> threeCharLocales = {"en-gb", "et", "th", "pt", "ga",
461 "cs", "cy", "sk", "en-us"};
462 static const std::unordered_set<std::string> oneCharLocales = {"el-monoton", "el-polyton"};
463
464 size_t trail = 2; // hardcoded for the most of the language pattern files
465 if (threeCharLocales.count(locale)) {
466 trail = 3; // 3: At least three characters
467 } else if (oneCharLocales.count(locale)) {
468 trail = 1;
469 }
470 return trail; // we break before, so we don't add extra for end marker
471 }
472
formatResult(std::vector<uint8_t> & result,const size_t & leadingHyphmins,const size_t & trailingHyphmins,std::vector<uint8_t> & offsets)473 inline void formatResult(std::vector<uint8_t>& result, const size_t& leadingHyphmins, const size_t& trailingHyphmins,
474 std::vector<uint8_t>& offsets) {
475 if (result.size() < leadingHyphmins || result.size() <= trailingHyphmins) {
476 // Not meeting the requirements
477 return;
478 }
479 for (size_t i = 0; i < leadingHyphmins; i++) {
480 result[i] = 0;
481 }
482
483 // remove front marker
484 result.erase(result.cbegin());
485
486 // move indices to match input multi chars
487 size_t pad = 0;
488 for (size_t i = 0; i < offsets.size(); i++) {
489 while (offsets[i] != 0) {
490 result.insert(result.begin() + i + pad, result[i + pad]);
491 TEXT_LOGD("Padding %{public}zu", i + pad);
492 offsets[i]--;
493 pad++;
494 }
495 }
496 // remove end marker and uncertain results
497 result.erase(result.cbegin() + result.size() - trailingHyphmins, result.cend());
498 }
499
findBreakPositions(const SkString & locale,const SkString & text,size_t startPos,size_t endPos)500 std::vector<uint8_t> Hyphenator::findBreakPositions(const SkString& locale, const SkString& text, size_t startPos,
501 size_t endPos)
502 {
503 TEXT_TRACE_FUNC();
504 TEXT_LOGD("Find break pos:%{public}zu %{public}zu %{public}zu", text.size(), startPos, endPos);
505 const std::string dummy(locale.c_str());
506 auto hyphenatorData = getHyphenatorData(dummy);
507 std::vector<uint8_t> result;
508
509 if (startPos > text.size() || endPos > text.size() || startPos > endPos) {
510 TEXT_LOGE("Hyphen error pos %{public}zu %{public}zu %{public}zu", text.size(), startPos, endPos);
511 return result;
512 }
513 const auto leadingHyphmins = getLanguagespecificLeadingBounds(dummy);
514 const auto trailingHyphmins = getLanguagespecificTrailingBounds(dummy);
515 // resolve potential break positions
516 if (!hyphenatorData.empty() && startPos + std::max(leadingHyphmins, trailingHyphmins) <= endPos) {
517 // typically need to have at least 4 characters for hyphenator to process
518 const auto lastword = std::string(text.c_str() + startPos, text.c_str() + endPos);
519 std::vector<uint16_t> word;
520 std::vector<uint8_t> offsets;
521 int32_t i = 0;
522 const int32_t textLength = static_cast<int32_t>(endPos - startPos);
523 UChar32 c = 0;
524 int32_t prev = i;
525 while (i < textLength) {
526 U8_NEXT(reinterpret_cast<const uint8_t*>(lastword.c_str()), i, textLength, c);
527 offsets.push_back(i - prev - U16_LENGTH(c));
528 if (U16_LENGTH(c) == 1) {
529 word.push_back(c);
530 } else {
531 word.push_back(U16_LEAD(c));
532 word.push_back(U16_TRAIL(c));
533 }
534 prev = i;
535 }
536
537 formatTarget(word);
538 if (word.size() > 3) { // 3: At least four characters, like '.ab.'
539 // Bulgarian pattern file tells only the positions where
540 // breaking is not allowed, we need to initialize defaults to allow breaking
541 const uint8_t defaultValue = (dummy == "bg") ? 1 : 0; // 0: break is not allowed, 1: break level 1
542 result.resize(word.size(), defaultValue);
543 findBreaks(hyphenatorData, word, result);
544 formatResult(result, leadingHyphmins, trailingHyphmins, offsets);
545 }
546 }
547 return result;
548 }
549 } // namespace textlayout
550 } // namespace skia
551 #endif // ENABLE_TEXT_ENHANCE
552