diff --git a/android/app/CMakeLists.txt b/android/app/CMakeLists.txt index d327ab7b9f..b182310fa3 100644 --- a/android/app/CMakeLists.txt +++ b/android/app/CMakeLists.txt @@ -33,6 +33,7 @@ add_definitions(-DCR3_ANTIWORD_PATCH=1 -DENABLE_ANTIWORD=1) add_definitions(-DMAX_IMAGE_SCALE_MUL=2) add_definitions(-DUSE_NANOSVG=1) add_definitions(-DBUNDLED_FRIBIDI=1) +add_definitions(-DKO_LIBUNIBREAK_PATCH=1) # patch "add_lb_get_char_class.patch" for libunibreak from koreader #set(LOCAL_CFLAGS "-Wno-psabi -Wno-unused-variable -Wno-sign-compare -Wno-write-strings -Wno-main -Wno-unused-but-set-variable -Wno-unused-function -Wall") # Option '-Wl,--no-merge-exidx-entries' removed as incompatible with clang. diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 06d650a3e8..606dbb1dc0 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -15,7 +15,8 @@ CRFLAGS := -DLINUX=1 -D_LINUX=1 -DFOR_ANDROID=1 -DCR3_PATCH \ -DCR3_ANTIWORD_PATCH=1 -DENABLE_ANTIWORD=1 \ -DMAX_IMAGE_SCALE_MUL=2 \ -DUSE_NANOSVG=1 \ - -DBUNDLED_FRIBIDI=1 + -DBUNDLED_FRIBIDI=1 \ + -DKO_LIBUNIBREAK_PATCH=1 CR3_ROOT := $(LOCAL_PATH)/../.. diff --git a/android/jni/docview.cpp b/android/jni/docview.cpp index 84afdb74af..ecaad2145b 100644 --- a/android/jni/docview.cpp +++ b/android/jni/docview.cpp @@ -1701,7 +1701,7 @@ JNIEXPORT jobject JNICALL Java_org_coolreader_crengine_DocView_getPositionPropsI } else { useCurPos = p->_docview->getViewMode()==DVM_SCROLL; if ( !useCurPos ) { - bm = p->_docview->getBookmark(); + bm = p->_docview->getBookmark(false); if ( bm.isNull() ) { CRLog::error("getPositionPropsInternal: Cannot get current position bookmark"); } @@ -1725,7 +1725,7 @@ JNIEXPORT jobject JNICALL Java_org_coolreader_crengine_DocView_getPositionPropsI CRStringField(v,"pageText").set(p->_docview->getPageText(false, -1)); #else p->_docview->getMutex().lock(); - LVRef range = p->_docview->getPageDocumentRange(-1); + LVRef range = p->_docview->getPageDocumentRange(-1, false); p->_docview->getMutex().unlock(); lString16 text; if (!range.isNull()) diff --git a/crengine/Tools/Fb2Linux/fb2v.cpp b/crengine/Tools/Fb2Linux/fb2v.cpp index 9701a33a89..f595f3df8d 100644 --- a/crengine/Tools/Fb2Linux/fb2v.cpp +++ b/crengine/Tools/Fb2Linux/fb2v.cpp @@ -242,18 +242,6 @@ class MyXWindowApp } }; -void initHyph(const char * fname) -{ - HyphMan hyphman; - LVStreamRef stream = LVOpenFileStream( fname, LVOM_READ); - if (!stream) - { - printf("Cannot load hyphenation file %s\n", fname); - return; - } - HyphMan::activateDictionaryFromStream( stream ); -} - int main( int argc, const char * argv[] ) { CRLog::setStdoutLogger(); @@ -312,10 +300,9 @@ int main( int argc, const char * argv[] ) #endif // init hyphenation manager - char hyphfn[1024]; - sprintf(hyphfn, "%sRussian_EnUS_hyphen_(Alan).pdb", exedir ); - initHyph( hyphfn ); - + HyphMan::initDictionaries(Utf8ToUnicode(exedir)); + HyphMan::activateDictionary(L"Russian_EnUS_hyphen_(Alan).pdb"); + //LVCHECKPOINT("WinMain start"); LVDocView text_view; diff --git a/crengine/include/lvdocview.h b/crengine/include/lvdocview.h index a99d7d2ab7..c2b35bf309 100644 --- a/crengine/include/lvdocview.h +++ b/crengine/include/lvdocview.h @@ -597,7 +597,7 @@ class LVDocView : public CacheLoadingCallback void updateSelections(); void updateBookMarksRanges(); /// get page document range, -1 for current page - LVRef getPageDocumentRange( int pageIndex=-1 ); + LVRef getPageDocumentRange( int pageIndex=-1, bool precise = true ); /// get page text, -1 for current page lString16 getPageText( bool wrapWords, int pageIndex=-1 ); /// returns number of non-space characters on current page @@ -803,7 +803,7 @@ class LVDocView : public CacheLoadingCallback LVStreamRef getCoverPageImageStream(); /// returns bookmark - ldomXPointer getBookmark(); + ldomXPointer getBookmark( bool precise = true ); /// returns bookmark for specified page ldomXPointer getPageBookmark( int page ); /// sets current bookmark diff --git a/crengine/include/textlang.h b/crengine/include/textlang.h index 98b2ceb1b4..ea80a912ec 100644 --- a/crengine/include/textlang.h +++ b/crengine/include/textlang.h @@ -99,9 +99,11 @@ class TextLangMan ~TextLangMan(); }; -#define MAX_NB_LB_PROPS_ITEMS 10 // for our statically sized array (increase if needed) +#define MAX_NB_LB_PROPS_ITEMS 20 // for our statically sized array (increase if needed) -typedef lChar16 (*lb_char_sub_func_t)(const lChar16 * text, int pos, int next_usable); +#if USE_LIBUNIBREAK==1 +typedef lChar16 (*lb_char_sub_func_t)(struct LineBreakContext *lbpCtx, const lChar16 * text, int pos, int next_usable); +#endif class TextLangCfg { diff --git a/crengine/src/lvdocview.cpp b/crengine/src/lvdocview.cpp index 946dd6ab7d..948354e0e3 100644 --- a/crengine/src/lvdocview.cpp +++ b/crengine/src/lvdocview.cpp @@ -1007,15 +1007,17 @@ LVStreamRef LVDocView::getCoverPageImageStream() { // FB2 coverpage //CRLog::trace("LVDocView::getCoverPageImage()"); //m_doc->dumpStatistics(); - lUInt16 path[] = { el_FictionBook, el_description, el_title_info, - el_coverpage, 0 }; + lUInt16 path[] = { el_FictionBook, el_description, el_title_info, el_coverpage, 0 }; //lUInt16 path[] = { el_FictionBook, el_description, el_title_info, el_coverpage, el_image, 0 }; ldomNode * rootNode = m_doc->getRootNode(); ldomNode * cover_el = 0; - if (rootNode) + if (rootNode) { cover_el = rootNode->findChildElement(path); - //ldomNode * cover_img_el = m_doc->getRootNode()->findChildElement( path ); - + if (!cover_el) { // might otherwise be found inside + lUInt16 path2[] = { el_FictionBook, el_description, el_src_title_info, el_coverpage, 0 }; + cover_el = rootNode->findChildElement(path2); + } + } if (cover_el) { ldomNode * cover_img_el = cover_el->findChildElement(LXML_NS_ANY, el_image, 0); @@ -1034,15 +1036,17 @@ LVImageSourceRef LVDocView::getCoverPageImage() { // CRLog::trace("Image stream size is %d", (int)stream->GetSize() ); //CRLog::trace("LVDocView::getCoverPageImage()"); //m_doc->dumpStatistics(); - lUInt16 path[] = { el_FictionBook, el_description, el_title_info, - el_coverpage, 0 }; + lUInt16 path[] = { el_FictionBook, el_description, el_title_info, el_coverpage, 0 }; //lUInt16 path[] = { el_FictionBook, el_description, el_title_info, el_coverpage, el_image, 0 }; ldomNode * cover_el = 0; ldomNode * rootNode = m_doc->getRootNode(); - if (rootNode) + if (rootNode) { cover_el = rootNode->findChildElement(path); - //ldomNode * cover_img_el = m_doc->getRootNode()->findChildElement( path ); - + if (!cover_el) { // might otherwise be found inside + lUInt16 path2[] = { el_FictionBook, el_description, el_src_title_info, el_coverpage, 0 }; + cover_el = rootNode->findChildElement(path2); + } + } if (cover_el) { ldomNode * cover_img_el = cover_el->findChildElement(LXML_NS_ANY, el_image, 0); @@ -1125,7 +1129,9 @@ void LVDocView::drawCoverTo(LVDrawBuf * drawBuf, lvRect & rc) { if (dst_dy > rc.height() * 6 / 8) dst_dy = imgrc.height(); //CRLog::trace("drawCoverTo() - drawing image"); - LVColorDrawBuf buf2(src_dx, src_dy, 32); + // It's best to use a 16bpp LVColorDrawBuf as the intermediate buffer, + // as using 32bpp would mess colors up when drawBuf is itself 32bpp. + LVColorDrawBuf buf2(src_dx, src_dy, 16); buf2.Draw(imgsrc, 0, 0, src_dx, src_dy, true); drawBuf->DrawRescaled(&buf2, imgrc.left + (imgrc.width() - dst_dx) / 2, imgrc.top + (imgrc.height() - dst_dy) / 2, dst_dx, dst_dy, 0); @@ -2610,7 +2616,7 @@ void LVDocView::setHeaderIcons(LVRefVec icons) { } /// get page document range, -1 for current page -LVRef LVDocView::getPageDocumentRange(int pageIndex) { +LVRef LVDocView::getPageDocumentRange(int pageIndex, bool precise) { LVLock lock(getMutex()); CHECK_RENDER("getPageDocRange()") // On some pages (eg: that ends with some padding between an @@ -2618,32 +2624,51 @@ LVRef LVDocView::getPageDocumentRange(int pageIndex) { // be some area which is rendered "final" without any content, // thus holding no node. We could then get a null 'start' or // 'end' below by looking only at start_y or end_y. - // So, in all cases, loop while increasing or decrasing y + // So, in all cases, loop while increasing or decreasing y // to get more chances of finding a valid XPointer. LVRef < ldomXRange > res(NULL); int start_y; int end_y; - if (isScrollMode()) { // SCROLL mode + if (isScrollMode()) { + // SCROLL mode start_y = _pos; end_y = _pos + m_dy; int fh = GetFullHeight(); if (end_y >= fh) end_y = fh - 1; + if (!precise) { + ldomXPointer start = m_doc->createXPointer(lvPoint(0, start_y)); + ldomXPointer end = m_doc->createXPointer(lvPoint(0, end_y)); + if (start.isNull() || end.isNull()) + return res; + res = LVRef (new ldomXRange(start, end)); + } } - else { // PAGES mode + else { + // PAGES mode if (pageIndex < 0 || pageIndex >= m_pages.length()) pageIndex = getCurPage(); if (pageIndex >= 0 && pageIndex < m_pages.length()) { LVRendPageInfo * page = m_pages[pageIndex]; if (page->flags & RN_PAGE_TYPE_COVER) return res; - start_y = page->start; - end_y = page->start + page->height; + if (!precise) { + ldomXPointer start = m_doc->createXPointer(lvPoint(0, page->start)); + ldomXPointer end = m_doc->createXPointer(lvPoint(0, page->start + page->height), 1); + if (start.isNull() || end.isNull()) + return res; + res = LVRef(new ldomXRange(start, end)); + } else { + start_y = page->start; + end_y = page->start + page->height; + } } else { return res; } } + if (!res.isNull()) + return res; int height = end_y - start_y; if (height < 0) return res; @@ -4982,7 +5007,7 @@ ldomXPointer LVDocView::getCurrentPageMiddleParagraph() { } /// returns bookmark -ldomXPointer LVDocView::getBookmark() { +ldomXPointer LVDocView::getBookmark( bool precise ) { LVLock lock(getMutex()); checkPos(); ldomXPointer ptr; @@ -4996,18 +5021,24 @@ ldomXPointer LVDocView::getBookmark() { LVRendPageInfo * page = m_pages[_page]; bool found = false; ldomXPointer fallback_ptr; - for (int y = page->start; y < page->start + page->height; y++) { - ptr = m_doc->createXPointer(lvPoint(0, y), PT_DIR_SCAN_FORWARD_LOGICAL_FIRST); - lvPoint pt = ptr.toPoint(); - if (pt.y >= page->start) { - if (!fallback_ptr) - fallback_ptr = ptr; - if ( pt.y < page->start + page->height ) { - // valid, resolves back to same page - found = true; - break; + if (precise) { + for (int y = page->start; y < page->start + page->height; y++) { + ptr = m_doc->createXPointer(lvPoint(0, y), + PT_DIR_SCAN_FORWARD_LOGICAL_FIRST); + lvPoint pt = ptr.toPoint(); + if (pt.y >= page->start) { + if (!fallback_ptr) + fallback_ptr = ptr; + if (pt.y < page->start + page->height) { + // valid, resolves back to same page + found = true; + break; + } } } + } else { + ptr = m_doc->createXPointer(lvPoint(0, page->start)); + found = true; } if (!found) { // None looking forward resolved to that same page, we @@ -5030,7 +5061,6 @@ ldomXPointer LVDocView::getBookmark() { } } } else { - // ptr = m_doc->createXPointer(lvPoint(0, _pos)); // In scroll mode, the y position may not resolve to any xpointer // (because of margins, empty elements...) // When inside an image (top of page being the middle of an image), @@ -5039,10 +5069,14 @@ ldomXPointer LVDocView::getBookmark() { // scrolling a bit up. // Let's do the same in that case: get the previous text node // position - for (int y = _pos; y >= 0; y--) { - ptr = m_doc->createXPointer(lvPoint(0, y), PT_DIR_SCAN_BACKWARD_LOGICAL_FIRST); - if (!ptr.isNull()) - break; + if (precise) { + for (int y = _pos; y >= 0; y--) { + ptr = m_doc->createXPointer(lvPoint(0, y), PT_DIR_SCAN_BACKWARD_LOGICAL_FIRST); + if (!ptr.isNull()) + break; + } + } else { + ptr = m_doc->createXPointer(lvPoint(0, _pos)); } } } diff --git a/crengine/src/lvrend.cpp b/crengine/src/lvrend.cpp index 7e6352c5fc..e12d3eddf0 100644 --- a/crengine/src/lvrend.cpp +++ b/crengine/src/lvrend.cpp @@ -10095,7 +10095,7 @@ void getRenderedWidths(ldomNode * node, int &maxWidth, int &minWidth, int direct lChar16 c = *(txt + start + i); lChar16 next_c = *(txt + start + i + 1); // might be 0 at end of string if ( lang_cfg->hasLBCharSubFunc() ) { - next_c = lang_cfg->getLBCharSubFunc()(txt+start, i+1, len-1 - (i+1)); + next_c = lang_cfg->getLBCharSubFunc()(&lbCtx, txt+start, i+1, len-1 - (i+1)); } int brk = lb_process_next_char(&lbCtx, (utf32_t)next_c); // We don't really need to bother with consecutive spaces (that diff --git a/crengine/src/lvtextfm.cpp b/crengine/src/lvtextfm.cpp index 94aaed56a8..268c73e892 100644 --- a/crengine/src/lvtextfm.cpp +++ b/crengine/src/lvtextfm.cpp @@ -1409,7 +1409,7 @@ class LVFormatter { if ( src->lang_cfg->hasLBCharSubFunc() ) { // Lang specific function may want to substitute char (for // libunibreak only) to tweak line breaking around it - ch = src->lang_cfg->getLBCharSubFunc()(m_text, pos, len-1 - k); + ch = src->lang_cfg->getLBCharSubFunc()(&lbCtx, m_text, pos, len-1 - k); } int brk = lb_process_next_char(&lbCtx, (utf32_t)ch); if ( pos > 0 ) { diff --git a/crengine/src/textlang.cpp b/crengine/src/textlang.cpp index a5454eeaab..c5c11decb4 100644 --- a/crengine/src/textlang.cpp +++ b/crengine/src/textlang.cpp @@ -483,7 +483,93 @@ static quotes_spec _quotes_spec_table[] = { static quotes_spec _quotes_spec_default = { "", L"\x201c", L"\x201d", L"\x2018", L"\x2019" }; #if USE_LIBUNIBREAK==1 -lChar16 lb_char_sub_func_polish(const lChar16 * text, int pos, int next_usable) { +#if KO_LIBUNIBREAK_PATCH==1 +lChar16 lb_char_sub_func_english(struct LineBreakContext *lbpCtx, const lChar16 * text, int pos, int next_usable) { + // https://github.com/koreader/crengine/issues/364 + // Normally, line breaks are allowed at both sides of an em-dash. + // When an em-dash is at the "end of a word" (or beginning), we want to avoid separating it from its word, + // this is detected by looking for letters/numbers at both sides of the dash, if on any side a space + // is closer than any letter/number, treat it as a non-breakable dash. + // The current implementation does not allow examining the following characters beyond the current node, + // so the detection is not perfect and we replace the dash with "opening" or "closing" characters + // (or "ambiguous), to play safer (note that "}" allows a break after, while ")" doesn't). + // + // The intent is the following: + // blah—blah -> — (break before or after) + // blah “—blah ,

—blah -> { (do not break after) + // blah—” Blah , blah—”

-> } (do not break before) + // blah — blah , blah —blah -> " (break only at spaces) + switch ( text[pos] ) { + case 0x2014: // em dash + case 0x2E3A: // two-em dash + case 0x2E3B: // three-em dash + { + // The variable "replacement" will be the output char, + // we start by setting it to the actual input char. + // It will be '{' if no-break on right, + // '}' if no-break on left, + // '"' if no-break on both. + lChar16 replacement = text[pos]; + int new_pos; + enum LineBreakClass new_lbc; + // 1. Detect no-break on right (scan left of dash) + // + // already at the beginning of text + if ( pos == 0 ) { + replacement = '{'; + } + else { + // inspect preceding characters + new_pos = pos; + while ( new_pos > 0) { + new_pos--; + new_lbc = lb_get_char_class(lbpCtx, text[new_pos]); + if ( new_lbc == LBP_AL || new_lbc == LBP_NU ) { + // found word / number + break; + } + else if ( new_lbc == LBP_SP || new_pos == 0 ) { + // found space or beginning + replacement = '{'; + break; + } + } + } + // 2. Detect no-break on left (scan right of dash) + // If already no-break on right, replacement will be '"' + // + // already at the end of text + if ( next_usable == 0 ) { + replacement = ( replacement == '{' ) ? '"' : '}'; + } + else { + // inspect following characters + new_pos = pos; + while ( new_pos < pos+next_usable ) { + new_pos++; + new_lbc = lb_get_char_class(lbpCtx, text[new_pos]); + if ( new_lbc == LBP_AL || new_lbc == LBP_NU ) { + // found word / number + break; + } + else if ( new_lbc == LBP_SP || new_pos == pos+next_usable ) { + // found space or end (of the current text node, there could be letters beyond) + replacement = ( replacement == '{' ) ? '"' : '}'; + break; + } + } + } + return replacement; + } + break; + default: + break; + } + return text[pos]; +} +#endif // KO_LIBUNIBREAK_PATCH==1 + +lChar16 lb_char_sub_func_polish(struct LineBreakContext *lbpCtx, const lChar16 * text, int pos, int next_usable) { // https://github.com/koreader/koreader/issues/5645#issuecomment-559193057 // Letters aiouwzAIOUWS are prepositions that should not be left at the // end of a line. @@ -513,7 +599,7 @@ lChar16 lb_char_sub_func_polish(const lChar16 * text, int pos, int next_usable) return text[pos]; } -lChar16 lb_char_sub_func_czech_slovak(const lChar16 * text, int pos, int next_usable) { +lChar16 lb_char_sub_func_czech_slovak(struct LineBreakContext *lbpCtx, const lChar16 * text, int pos, int next_usable) { // Same for Czech and Slovak : AIiVvOoUuSsZzKk // https://tex.stackexchange.com/questions/27780/one-letter-word-at-the-end-of-line // https://github.com/michal-h21/luavlna @@ -629,6 +715,10 @@ TextLangCfg::TextLangCfg( lString16 lang_tag ) { bool has_left_double_angle_quotation_mark_closing = false; bool has_right_double_angle_quotation_mark_opening = false; // U+00BB » bool has_right_double_angle_quotation_mark_closing = false; + // Additional rule for treating em-dashes as e.g. "horizontal bar" + // This is appropriate for languages that typically have a space at a + // breakable side of the dash + bool has_em_dash_alphabetic = false; // U+2014 —, U+2E3A ⸺, U+2E3B ⸻ // Note: these macros use 'lang_tag'. if ( LANG_STARTS_WITH(("en")) ) { // English @@ -644,6 +734,7 @@ TextLangCfg::TextLangCfg( lString16 lang_tag ) { has_right_single_angle_quotation_mark_closing = true; has_left_double_angle_quotation_mark_opening = true; has_right_double_angle_quotation_mark_closing = true; + has_em_dash_alphabetic = true; } else if ( LANG_STARTS_WITH(("de")) ) { // German has_left_single_quotation_mark_closing = true; @@ -678,6 +769,7 @@ TextLangCfg::TextLangCfg( lString16 lang_tag ) { _lb_props[n++] = { 0x00AD, 0x00AD, LBP_ZWJ }; if ( has_right_double_angle_quotation_mark_opening ) _lb_props[n++] = { 0x00BB, 0x00BB, LBP_OP }; if ( has_right_double_angle_quotation_mark_closing ) _lb_props[n++] = { 0x00BB, 0x00BB, LBP_CL }; + if ( has_em_dash_alphabetic ) _lb_props[n++] = { 0x2014, 0x2014, LBP_AL }; if ( has_left_single_quotation_mark_opening ) _lb_props[n++] = { 0x2018, 0x2018, LBP_OP }; if ( has_left_single_quotation_mark_closing ) _lb_props[n++] = { 0x2018, 0x2018, LBP_CL }; if ( has_right_single_quotation_mark_opening ) _lb_props[n++] = { 0x2019, 0x2019, LBP_OP }; @@ -691,20 +783,29 @@ TextLangCfg::TextLangCfg( lString16 lang_tag ) { if ( has_left_single_angle_quotation_mark_closing ) _lb_props[n++] = { 0x2039, 0x2039, LBP_CL }; if ( has_right_single_angle_quotation_mark_opening ) _lb_props[n++] = { 0x203A, 0x203A, LBP_OP }; if ( has_right_single_angle_quotation_mark_closing ) _lb_props[n++] = { 0x203A, 0x203A, LBP_CL }; + if ( has_em_dash_alphabetic ) _lb_props[n++] = { 0x2E3A, 0x2E3B, LBP_AL }; // End of list _lb_props[n++] = { 0, 0, LBP_Undefined }; + // When adding properties, be sure combinations for all languages + // do fit in _lb_props[MAX_NB_LB_PROPS_ITEMS] (MAX_NB_LB_PROPS_ITEMS + // is defined in textlang.h, currently at 20). // Done with libunibreak per-language LineBreakProperties extensions // Other line breaking and text layout tweaks _lb_char_sub_func = NULL; +#if KO_LIBUNIBREAK_PATCH==1 + if ( LANG_STARTS_WITH(("en")) ) { // English + _lb_char_sub_func = &lb_char_sub_func_english; + } else +#endif if ( LANG_STARTS_WITH(("pl")) ) { // Polish _lb_char_sub_func = &lb_char_sub_func_polish; _duplicate_real_hyphen_on_next_line = true; } - if ( LANG_STARTS_WITH(("cs") ("sk")) ) { // Czech, Slovak + else if ( LANG_STARTS_WITH(("cs") ("sk")) ) { // Czech, Slovak _lb_char_sub_func = &lb_char_sub_func_czech_slovak; } - if ( LANG_STARTS_WITH(("pt")) ) { // Portuguese + else if ( LANG_STARTS_WITH(("pt")) ) { // Portuguese _duplicate_real_hyphen_on_next_line = true; } #endif diff --git a/crengine/src/xutils.cpp b/crengine/src/xutils.cpp index 038f2c04f6..fd7345aba1 100644 --- a/crengine/src/xutils.cpp +++ b/crengine/src/xutils.cpp @@ -81,7 +81,7 @@ void DrawBuf2Drawable(Display *display, Drawable d, GC gc, int x, int y, LVDrawB { int pixelsPerByte = (8 / buf->GetBitsPerPixel()); int bytesPerRow = (buf->GetWidth() * buf->GetBitsPerPixel() + 7) / 8; - int mask = (1<GetBitsPerPixel()) - 1; + unsigned int mask = (unsigned int) ( (1UL<GetBitsPerPixel()) - 1 ); int width = buf->GetWidth(); int dwidth = buf->GetWidth()*scale; diff --git a/thirdparty/libunibreak/add_lb_get_char_class.patch b/thirdparty/libunibreak/add_lb_get_char_class.patch new file mode 100644 index 0000000000..66bdd66d3b --- /dev/null +++ b/thirdparty/libunibreak/add_lb_get_char_class.patch @@ -0,0 +1,28 @@ +diff --git a/src/linebreak.c b/src/linebreak.c +index c2ccdda..45afba2 100644 +--- a/src/linebreak.c ++++ b/src/linebreak.c +@@ -751,6 +751,12 @@ int lb_process_next_char( + + return brk; + } ++enum LineBreakClass lb_get_char_class( ++ struct LineBreakContext *lbpCtx, ++ utf32_t ch) ++{ ++ return get_char_lb_class_lang(ch, lbpCtx->lbpLang); ++} + + /** + * Sets the line breaking information for a generic input string. +diff --git a/src/linebreakdef.h b/src/linebreakdef.h +index 5bb8838..8db80a6 100644 +--- a/src/linebreakdef.h ++++ b/src/linebreakdef.h +@@ -169,3 +169,6 @@ void set_linebreaks( + const char *lang, + char *brks, + get_next_char_t get_next_char); ++enum LineBreakClass lb_get_char_class( ++ struct LineBreakContext *lbpCtx, ++ utf32_t ch); diff --git a/thirdparty/libunibreak/src/linebreak.c b/thirdparty/libunibreak/src/linebreak.c index c2ccdda520..45afba2085 100644 --- a/thirdparty/libunibreak/src/linebreak.c +++ b/thirdparty/libunibreak/src/linebreak.c @@ -751,6 +751,12 @@ int lb_process_next_char( return brk; } +enum LineBreakClass lb_get_char_class( + struct LineBreakContext *lbpCtx, + utf32_t ch) +{ + return get_char_lb_class_lang(ch, lbpCtx->lbpLang); +} /** * Sets the line breaking information for a generic input string. diff --git a/thirdparty/libunibreak/src/linebreakdef.h b/thirdparty/libunibreak/src/linebreakdef.h index 5bb8838e40..8db80a6582 100644 --- a/thirdparty/libunibreak/src/linebreakdef.h +++ b/thirdparty/libunibreak/src/linebreakdef.h @@ -169,3 +169,6 @@ void set_linebreaks( const char *lang, char *brks, get_next_char_t get_next_char); +enum LineBreakClass lb_get_char_class( + struct LineBreakContext *lbpCtx, + utf32_t ch);