From 2071f7ff99c536ad09d24b00baf5f2aa818dee22 Mon Sep 17 00:00:00 2001 From: poire-z Date: Fri, 24 Apr 2020 14:23:55 +0200 Subject: [PATCH 1/4] Simplify libunibreak includes --- crengine/include/textlang.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crengine/include/textlang.h b/crengine/include/textlang.h index 2a8b970f7..8644ded69 100644 --- a/crengine/include/textlang.h +++ b/crengine/include/textlang.h @@ -7,13 +7,10 @@ #endif #if USE_LIBUNIBREAK==1 -#include - // linebreakdef.h is not wrapped by this, unlike linebreak.h - // (not wrapping results in "undefined symbol" with the original - // function name kinda obfuscated) #ifdef __cplusplus extern "C" { #endif +#include #include #ifdef __cplusplus } From 01d68bbdd7c1e51f2698205d352e972565456638 Mon Sep 17 00:00:00 2001 From: poire-z Date: Fri, 24 Apr 2020 14:23:57 +0200 Subject: [PATCH 2/4] Text: fix read/write outside array bounds with the recently added libunibreak changes. Witnessed with an embedded float (so, malloc'ed dynamic buffers), being a which made text start with U+2068 (FIRST STRONG ISOLATE, which happens to be breakable after a leading space), leading to "|= LCHAR_ALLOW_WRAP_AFTER" modifying byte at -1, causing a double free (while "&= ~LCHAR_ALLOW_WRAP_AFTER", done when no break allowed, did not cause any harm.) Also switch to using a Zero-Width Joiner instead of a space as the initial libunibreak char. --- crengine/src/lvtextfm.cpp | 93 ++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/crengine/src/lvtextfm.cpp b/crengine/src/lvtextfm.cpp index 45e364250..0aa0a35b8 100755 --- a/crengine/src/lvtextfm.cpp +++ b/crengine/src/lvtextfm.cpp @@ -924,13 +924,13 @@ class LVFormatter { { #if (USE_LIBUNIBREAK==1) struct LineBreakContext lbCtx; - // Let's init it before the first char, by adding a leading space which - // will be treated as WJ (Word Joiner, non-breakable) and should not - // change the behaviour with the real first char coming up. We then - // can just use lb_process_next_char() with the real text. + // Let's init it before the first char, by adding a leading Zero-Width Joiner + // (Word Joiner, non-breakable) which should not change the behaviour with + // the real first char coming up. We then can just use lb_process_next_char() + // with the real text. // The lang lb_props will be plugged in from the TextLangCfg of the - // coming up text node. - lb_init_break_context(&lbCtx, 0x0020, NULL); + // coming up text node. We provide NULL in the meantime. + lb_init_break_context(&lbCtx, 0x200D, NULL); // ZERO WIDTH JOINER #endif m_has_bidi = false; // will be set if fribidi detects it is bidirectionnal text @@ -1171,45 +1171,48 @@ class LVFormatter { ch = src->lang_cfg->getLBCharSubFunc()(m_text, pos, len-1 - k); } int brk = lb_process_next_char(&lbCtx, (utf32_t)ch); - // printf("between <%c%c>: brk %d\n", m_text[pos-1], m_text[pos], brk); - if (brk != LINEBREAK_ALLOWBREAK) { - m_flags[pos-1] &= ~LCHAR_ALLOW_WRAP_AFTER; - } - else { - m_flags[pos-1] |= LCHAR_ALLOW_WRAP_AFTER; - // brk is set on the last space in a sequence of multiple spaces. - // between : brk 2 - // between : brk 2 - // between : brk 2 - // between <. >: brk 2 - // between < >: brk 2 - // between < >: brk 2 - // between < T>: brk 1 - // between : brk 2 - // between : brk 2 - // between : brk 2 - // between : brk 2 - // between < >: brk 2 - // between < h>: brk 1 - // between : brk 2 - // between : brk 2 - // between : brk 2 - // between : brk 2 - // between < a>: brk 1 - // between : brk 2 - // Given the algorithm described in addLine(), we want the break - // after the first space, so the following collapsed spaces can - // be at start of next line where they will be ignored. - // (Not certain this is really needed, but let's do it, as the - // code expecting that has been quite well tested and fixed over - // the months, so let's avoid adding uncertainty.) - if ( m_flags[pos-1] & LCHAR_IS_COLLAPSED_SPACE ) { - // We have spaces before, and if we are allowed to break, - // the break is allowed on all preceeding spaces. - int j = pos-2; - while ( j >= 0 && ( (m_flags[j] & LCHAR_IS_COLLAPSED_SPACE) || m_text[j] == ' ' ) ) { - m_flags[j] |= LCHAR_ALLOW_WRAP_AFTER; - j--; + if ( pos > 0 ) { + // printf("between <%c%c>: brk %d\n", m_text[pos-1], m_text[pos], brk); + // printf("between <%x.%x>: brk %d\n", m_text[pos-1], m_text[pos], brk); + if (brk != LINEBREAK_ALLOWBREAK) { + m_flags[pos-1] &= ~LCHAR_ALLOW_WRAP_AFTER; + } + else { + m_flags[pos-1] |= LCHAR_ALLOW_WRAP_AFTER; + // brk is set on the last space in a sequence of multiple spaces. + // between : brk 2 + // between : brk 2 + // between : brk 2 + // between <. >: brk 2 + // between < >: brk 2 + // between < >: brk 2 + // between < T>: brk 1 + // between : brk 2 + // between : brk 2 + // between : brk 2 + // between : brk 2 + // between < >: brk 2 + // between < h>: brk 1 + // between : brk 2 + // between : brk 2 + // between : brk 2 + // between : brk 2 + // between < a>: brk 1 + // between : brk 2 + // Given the algorithm described in addLine(), we want the break + // after the first space, so the following collapsed spaces can + // be at start of next line where they will be ignored. + // (Not certain this is really needed, but let's do it, as the + // code expecting that has been quite well tested and fixed over + // the months, so let's avoid adding uncertainty.) + if ( m_flags[pos-1] & LCHAR_IS_COLLAPSED_SPACE ) { + // We have spaces before, and if we are allowed to break, + // the break is allowed on all preceeding spaces. + int j = pos-2; + while ( j >= 0 && ( (m_flags[j] & LCHAR_IS_COLLAPSED_SPACE) || m_text[j] == ' ' ) ) { + m_flags[j] |= LCHAR_ALLOW_WRAP_AFTER; + j--; + } } } } From 6c7e2b8449a0aae60ba3d422bd22c9654d902b4a Mon Sep 17 00:00:00 2001 From: poire-z Date: Fri, 24 Apr 2020 14:23:59 +0200 Subject: [PATCH 3/4] lvtextfm: dont adjust space after initial quotation mark/dash (rework) Rework f41517d7 so it works even when there are leading collapsed spaces, and probably too on BiDi/RTL text. --- crengine/src/lvtextfm.cpp | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/crengine/src/lvtextfm.cpp b/crengine/src/lvtextfm.cpp index 0aa0a35b8..1d9104035 100755 --- a/crengine/src/lvtextfm.cpp +++ b/crengine/src/lvtextfm.cpp @@ -1500,6 +1500,8 @@ class LVFormatter { lUInt32 prevScript = HB_SCRIPT_COMMON; hb_unicode_funcs_t* _hb_unicode_funcs = hb_unicode_funcs_get_default(); #endif + int first_word_len = 0; // set to -1 when done with it (only used to check + // for single char first word, see below) for ( i=0; i<=m_length; i++ ) { LVFont * newFont = NULL; lInt16 newLetterSpacing = 0; @@ -1672,10 +1674,32 @@ class LVFormatter { // We can just account for the space reduction (or increase) in cumulative_width_removed cumulative_width_removed += char_width - scaled_width; widths[k] -= cumulative_width_removed; + if ( first_word_len >= 0 ) { // This is the space (or nbsp) after first word + if ( first_word_len == 1 ) { // Previous word is a single char + if ( isLeftPunctuation(m_text[k-1]) ) { + // This space follows one of the common opening quotation marks or + // dashes used to introduce a quotation or a part of a dialog: + // https://en.wikipedia.org/wiki/Quotation_mark + // Don't allow this space to change width, so text justification + // doesn't move away next word, so that other similar paragraphs + // get their real first words vertically aligned. + flags[k] &= ~LCHAR_IS_SPACE; + // Note: we do this check here, with the text still in logical + // order, so we get that working with RTL text too (where, in + // visual order, we'll have lost track of which word is the + // first word). + } + } + first_word_len = -1; // We don't need to deal with this anymore + } } else { // remove, from the measured cumulative width, what we previously removed widths[k] -= cumulative_width_removed; + if ( first_word_len >= 0 ) { + // Not a collapsed space and not a space: this will be part of first word + first_word_len++; + } } m_widths[start + k] = lastWidth + widths[k]; #if (USE_LIBUNIBREAK==1) @@ -2702,16 +2726,11 @@ class LVFormatter { // increased if needed (for text justification), so actually // making that space larger or smaller. bool can_adjust_width = true; - if ( wstart == 0 && word->t.len == 2 && isLeftPunctuation(m_text[0]) ) { - // Single char (with space) at start of line is one of the - // common opening quotation marks or dashes used to introduce - // a quotation or a part of a dialog: - // https://en.wikipedia.org/wiki/Quotation_mark - // Don't allow the following space to change width, so other - // similar lines get their real first word similarly aligned. - can_adjust_width = false; - } - else if ( word->t.len>=2 && i>=2 && m_text[i-1]==UNICODE_NO_BREAK_SPACE + // Note: checking if the first word of first line is one of the + // common opening quotation marks or dashes is done in measureText(), + // to have it work also with BiDi/RTL text (checking that here + // would be too late, as reordering has been done). + if ( word->t.len>=2 && i>=2 && m_text[i-1]==UNICODE_NO_BREAK_SPACE && m_text[i-2]==UNICODE_NO_BREAK_SPACE ) { // condition for double nbsp after run-in footnote title can_adjust_width = false; From d9800596903f6e6a2511ff0fb64d02dfd9d6a3fd Mon Sep 17 00:00:00 2001 From: poire-z Date: Fri, 24 Apr 2020 14:24:01 +0200 Subject: [PATCH 4/4] Fonts: allow providing and using multiple fallback fonts As our use of the fallback font was a recursive calls, we can provide chained fallback fonts. As one of the fallback font could be the main font, we need to use first getFallbackFont() (to get the first fallback font, even on a font among these fallback fonts), and getNextFallbackFont() to get the chained fallback font. Caveat: we might try rendering/drawing with the main font twice if it is among the fallback fonts (but avoiding that wouldn't make this change as simple). --- crengine/include/lvdocviewprops.h | 1 + crengine/include/lvfntman.h | 31 +++--- crengine/src/lvdocview.cpp | 12 +-- crengine/src/lvfntman.cpp | 161 +++++++++++++++++++++--------- crengine/src/lvtinydom.cpp | 2 +- 5 files changed, 139 insertions(+), 68 deletions(-) diff --git a/crengine/include/lvdocviewprops.h b/crengine/include/lvdocviewprops.h index 6c91d0f54..776a49207 100644 --- a/crengine/include/lvdocviewprops.h +++ b/crengine/include/lvdocviewprops.h @@ -18,6 +18,7 @@ #define PROP_LOG_AUTOFLUSH "crengine.log.autoflush" #define PROP_FONT_SIZE "crengine.font.size" #define PROP_FALLBACK_FONT_FACE "crengine.font.fallback.face" + // multiple fallback font faces allowed, separated by '|' (name kept singular for compatibility) #define PROP_STATUS_FONT_COLOR "crengine.page.header.font.color" #define PROP_STATUS_FONT_FACE "crengine.page.header.font.face" #define PROP_STATUS_FONT_SIZE "crengine.page.header.font.size" diff --git a/crengine/include/lvfntman.h b/crengine/include/lvfntman.h index 25348de4c..b259aefe0 100644 --- a/crengine/include/lvfntman.h +++ b/crengine/include/lvfntman.h @@ -209,6 +209,8 @@ enum kerning_mode_t { #define LFNT_HINT_BEGINS_PARAGRAPH 0x0004 /// segment is at start of paragraph #define LFNT_HINT_ENDS_PARAGRAPH 0x0008 /// segment is at end of paragraph +#define LFNT_HINT_IS_FALLBACK_FONT 0x0010 /// set on recursive Harfbuzz rendering/drawing with a fallback font + // These 4 translate from LTEXT_TD_* equivalents (see lvtextfm.h). Keep them in sync. #define LFNT_DRAW_UNDERLINE 0x0100 /// underlined text #define LFNT_DRAW_OVERLINE 0x0200 /// overlined text @@ -324,7 +326,7 @@ class LVFont : public LVRefCounter \param glyph is pointer to glyph_info_t struct to place retrieved info \return true if glyh was found */ - virtual bool getGlyphInfo( lUInt32 code, glyph_info_t * glyph, lChar16 def_char=0 ) = 0; + virtual bool getGlyphInfo( lUInt32 code, glyph_info_t * glyph, lChar16 def_char=0, bool is_fallback=false ) = 0; /** \brief measure text \param text is text string pointer @@ -365,7 +367,7 @@ class LVFont : public LVRefCounter \param code is unicode character \return glyph pointer if glyph was found, NULL otherwise */ - virtual LVFontGlyphCacheItem * getGlyph(lUInt32 ch, lChar16 def_char=0) = 0; + virtual LVFontGlyphCacheItem * getGlyph(lUInt32 ch, lChar16 def_char=0, bool is_fallback=false) = 0; /// returns font baseline offset virtual int getBaseline() = 0; @@ -436,6 +438,11 @@ class LVFont : public LVRefCounter virtual void setFallbackFont( LVProtectedFastRef font ) { CR_UNUSED(font); } /// get fallback font for this font LVFont * getFallbackFont() { return NULL; } + + /// set next fallback font for this font (for when used as a fallback font) + virtual void setNextFallbackFont( LVProtectedFastRef font ) { CR_UNUSED(font); } + /// get next fallback font for this font (when already used as a fallback font) + LVFont * getNextFallbackFont() { return NULL; } }; typedef LVProtectedFastRef LVFontRef; @@ -496,14 +503,14 @@ class LVFontManager /// returns most similar font virtual LVFontRef GetFont(int size, int weight, bool italic, css_font_family_t family, lString8 typeface, int features=0, int documentId = -1, bool useBias=false) = 0; - /// set fallback font face (returns true if specified font is found) - virtual bool SetFallbackFontFace( lString8 face ) { CR_UNUSED(face); return false; } - /// get fallback font face (returns empty string if no fallback font is set) - virtual lString8 GetFallbackFontFace() { return lString8::empty_str; } + /// set fallback font faces (separated by '|') + virtual bool SetFallbackFontFaces( lString8 facesString ) { CR_UNUSED(facesString); return false; } + /// get fallback font faces string (returns empty string if no fallback font is set) + virtual lString8 GetFallbackFontFaces() { return lString8::empty_str; } /// returns fallback font for specified size virtual LVFontRef GetFallbackFont(int /*size*/) { return LVFontRef(); } /// returns fallback font for specified size, weight and italic - virtual LVFontRef GetFallbackFont(int size, int weight=400, bool italic=false ) { return LVFontRef(); } + virtual LVFontRef GetFallbackFont(int size, int weight=400, bool italic=false, lString8 forFaceName=lString8::empty_str ) { return LVFontRef(); } /// registers font by name virtual bool RegisterFont( lString8 name ) = 0; /// registers font by name and face @@ -592,7 +599,7 @@ class LBitmapFont : public LVBaseFont lvfont_handle m_font; public: LBitmapFont() : m_font(NULL) { } - virtual bool getGlyphInfo( lUInt32 code, LVFont::glyph_info_t * glyph, lChar16 def_char=0 ); + virtual bool getGlyphInfo( lUInt32 code, LVFont::glyph_info_t * glyph, lChar16 def_char=0, bool is_fallback=false ); virtual lUInt16 measureText( const lChar16 * text, int len, lUInt16 * widths, @@ -612,7 +619,7 @@ class LBitmapFont : public LVBaseFont virtual lUInt32 getTextWidth( const lChar16 * text, int len, TextLangCfg * lang_cfg=NULL ); - virtual LVFontGlyphCacheItem * getGlyph(lUInt32 ch, lChar16 def_char=0); + virtual LVFontGlyphCacheItem * getGlyph(lUInt32 ch, lChar16 def_char=0, bool is_fallback=false); /// returns font baseline offset virtual int getBaseline(); /// returns font height @@ -727,7 +734,7 @@ class LVBaseWin32Font : public LVBaseFont return css_ff_inherit; } - virtual LVFontGlyphCacheItem * getGlyph(lUInt32 ch, lChar16 def_char=0) { + virtual LVFontGlyphCacheItem * getGlyph(lUInt32 ch, lChar16 def_char=0, bool is_fallback=false) { return NULL; } @@ -750,7 +757,7 @@ class LVWin32DrawFont : public LVBaseWin32Font \param glyph is pointer to glyph_info_t struct to place retrieved info \return true if glyh was found */ - virtual bool getGlyphInfo( lUInt32 code, glyph_info_t * glyph, lChar16 def_char=0 ); + virtual bool getGlyphInfo( lUInt32 code, glyph_info_t * glyph, lChar16 def_char=0, bool is_fallback=false ); /** \brief measure text \param glyph is pointer to glyph_info_t struct to place retrieved info @@ -930,7 +937,7 @@ class LVWin32Font : public LVBaseWin32Font \param glyph is pointer to glyph_info_t struct to place retrieved info \return true if glyh was found */ - virtual bool getGlyphInfo( lUInt32 code, glyph_info_t * glyph, lChar16 def_char=0 ); + virtual bool getGlyphInfo( lUInt32 code, glyph_info_t * glyph, lChar16 def_char=0, bool is_fallback=false ); /** \brief measure text \param glyph is pointer to glyph_info_t struct to place retrieved info diff --git a/crengine/src/lvdocview.cpp b/crengine/src/lvdocview.cpp index fb2f528b3..3a0111906 100755 --- a/crengine/src/lvdocview.cpp +++ b/crengine/src/lvdocview.cpp @@ -6307,12 +6307,12 @@ CRPropRef LVDocView::propsApply(CRPropRef props) { } else if (name == PROP_FONT_FACE) { setDefaultFontFace(UnicodeToUtf8(value)); } else if (name == PROP_FALLBACK_FONT_FACE) { - lString8 oldFace = fontMan->GetFallbackFontFace(); - if ( UnicodeToUtf8(value)!=oldFace ) - fontMan->SetFallbackFontFace(UnicodeToUtf8(value)); - value = Utf8ToUnicode(fontMan->GetFallbackFontFace()); - if ( UnicodeToUtf8(value) != oldFace ) { - REQUEST_RENDER("propsApply fallback font face") + lString8 oldFaces = fontMan->GetFallbackFontFaces(); + if ( UnicodeToUtf8(value)!=oldFaces ) + fontMan->SetFallbackFontFaces(UnicodeToUtf8(value)); + value = Utf8ToUnicode(fontMan->GetFallbackFontFaces()); + if ( UnicodeToUtf8(value) != oldFaces ) { + REQUEST_RENDER("propsApply fallback font faces") } } else if (name == PROP_STATUS_FONT_FACE) { setStatusFontFace(UnicodeToUtf8(value)); diff --git a/crengine/src/lvfntman.cpp b/crengine/src/lvfntman.cpp index 061cc5675..1f4c07c1f 100644 --- a/crengine/src/lvfntman.cpp +++ b/crengine/src/lvfntman.cpp @@ -565,10 +565,13 @@ class LVFontCache LVPtrVector< LVFontCacheItem > * fonts = getInstances(); for ( int i=0; ilength(); i++ ) { fonts->get(i)->getFont()->setFallbackFont(LVFontRef()); + fonts->get(i)->getFont()->setNextFallbackFont(LVFontRef()); } for ( int i=0; i<_registered_list.length(); i++ ) { - if (!_registered_list[i]->getFont().isNull()) + if (!_registered_list[i]->getFont().isNull()) { _registered_list[i]->getFont()->setFallbackFont(LVFontRef()); + _registered_list[i]->getFont()->setNextFallbackFont(LVFontRef()); + } } } LVFontCache( ) @@ -1035,6 +1038,8 @@ class LVFreeTypeFace : public LVFont kerning_mode_t _kerningMode; bool _fallbackFontIsSet; LVFontRef _fallbackFont; + bool _nextFallbackFontIsSet; + LVFontRef _nextFallbackFont; bool _embolden; // fake/synthetized bold FT_Pos _embolden_half_strength; // for emboldening with Harfbuzz int _features; // requested OpenType features bitmap @@ -1058,17 +1063,32 @@ class LVFreeTypeFace : public LVFont clearCache(); } - /// get fallback font for this font + /// get fallback font for this font (when it is used as the main font) LVFont * getFallbackFont() { if ( _fallbackFontIsSet ) return _fallbackFont.get(); - // To avoid circular link, disable fallback for fallback font: - if ( fontMan->GetFallbackFontFace()!=_faceName ) - _fallbackFont = fontMan->GetFallbackFont(_size, _weight, _italic); + _fallbackFont = fontMan->GetFallbackFont(_size, _weight, _italic); _fallbackFontIsSet = true; return _fallbackFont.get(); } + /// set next fallback font for this font (for when used as a fallback font) + virtual void setNextFallbackFont( LVFontRef font ) { + _nextFallbackFont = font; + _nextFallbackFontIsSet = !font.isNull(); + clearCache(); + } + + /// get next fallback font for this font (when it is already used as a fallback font) + LVFont * getNextFallbackFont() { + if ( _nextFallbackFontIsSet ) + return _nextFallbackFont.get(); + _nextFallbackFont = fontMan->GetFallbackFont(_size, _weight, _italic, _faceName); + _nextFallbackFontIsSet = true; + return _nextFallbackFont.get(); + } + + /// returns font weight virtual int getWeight() const { return _weight; } /// returns italic flag @@ -1086,7 +1106,7 @@ class LVFreeTypeFace : public LVFont , _weight(400), _italic(0), _embolden(false), _features(0) , _glyph_cache(globalCache), _drawMonochrome(false) , _kerningMode(KERNING_MODE_DISABLED), _hintingMode(HINTING_MODE_AUTOHINT) - , _fallbackFontIsSet(false) + , _fallbackFontIsSet(false), _nextFallbackFontIsSet(false) #if USE_HARFBUZZ==1 , _hb_features(NULL) , _glyph_cache2(globalCache) @@ -1685,11 +1705,11 @@ class LVFreeTypeFace : public LVFont \param glyph is pointer to glyph_info_t struct to place retrieved info \return true if glyh was found */ - virtual bool getGlyphInfo( lUInt32 code, glyph_info_t * glyph, lChar16 def_char=0 ) { + virtual bool getGlyphInfo( lUInt32 code, glyph_info_t * glyph, lChar16 def_char=0, bool is_fallback=false ) { //FONT_GUARD int glyph_index = getCharIndex( code, 0 ); if ( glyph_index==0 ) { - LVFont * fallback = getFallbackFont(); + LVFont * fallback = is_fallback ? getNextFallbackFont() : getFallbackFont(); if ( !fallback ) { // No fallback glyph_index = getCharIndex( code, def_char ); @@ -1698,7 +1718,7 @@ class LVFreeTypeFace : public LVFont } else { // Fallback - return fallback->getGlyphInfo(code, glyph, def_char); + return fallback->getGlyphInfo(code, glyph, def_char, true); } } @@ -1868,7 +1888,10 @@ class LVFreeTypeFace : public LVFont // todo: (if needed) might need a pre-pass in the fallback case: // full shaping without filterChar(), and if any .notdef // codepoint, re-shape with filterChar()... - if ( getFallbackFont() ) { // It has a fallback font, add chars as-is + bool is_fallback_font = hints & LFNT_HINT_IS_FALLBACK_FONT; + LVFont * fallback = is_fallback_font ? getNextFallbackFont() : getFallbackFont(); + bool has_fallback_font = (bool) fallback; + if ( has_fallback_font ) { // It has a fallback font, add chars as-is for (i = 0; i < len; i++) { hb_buffer_add(_hb_buffer, (hb_codepoint_t)(text[i]), i); } @@ -2008,16 +2031,15 @@ class LVFreeTypeFace : public LVFont #endif if ( t_notdef_start >= 0 ) { // But we have a segment of previous ".notdef" t_notdef_end = t; - LVFont *fallback = getFallbackFont(); // The code ensures the main fallback font has no fallback font - if ( fallback ) { + if ( has_fallback_font ) { // Let the fallback font replace the wrong values in widths and flags #ifdef DEBUG_MEASURE_TEXT printf("[...]\nMTHB ### measuring past failures with fallback font %d>%d\n", t_notdef_start, t_notdef_end); #endif // Drop BOT/EOT flags if this segment is not at start/end - lUInt32 fb_hints = hints; + lUInt32 fb_hints = hints | LFNT_HINT_IS_FALLBACK_FONT; if ( t_notdef_start > 0 ) fb_hints &= ~LFNT_HINT_BEGINS_PARAGRAPH; if ( t_notdef_end < len ) @@ -2105,14 +2127,13 @@ class LVFreeTypeFace : public LVFont // Process .notdef glyphs at end of text (same logic as above) if ( t_notdef_start >= 0 ) { t_notdef_end = len; - LVFont *fallback = getFallbackFont(); - if ( fallback ) { + if ( has_fallback_font ) { #ifdef DEBUG_MEASURE_TEXT printf("[...]\nMTHB ### measuring past failures at EOT with fallback font %d>%d\n", t_notdef_start, t_notdef_end); #endif // Drop BOT flag if this segment is not at start (it is at end) - lUInt32 fb_hints = hints; + lUInt32 fb_hints = hints | LFNT_HINT_IS_FALLBACK_FONT; if ( t_notdef_start > 0 ) fb_hints &= ~LFNT_HINT_BEGINS_PARAGRAPH; int chars_measured = fallback->measureText( text + t_notdef_start, // start @@ -2343,11 +2364,11 @@ class LVFreeTypeFace : public LVFont \param code is unicode character \return glyph pointer if glyph was found, NULL otherwise */ - virtual LVFontGlyphCacheItem * getGlyph(lUInt32 ch, lChar16 def_char=0) { + virtual LVFontGlyphCacheItem * getGlyph(lUInt32 ch, lChar16 def_char=0, bool is_fallback=false) { //FONT_GUARD FT_UInt ch_glyph_index = getCharIndex( ch, 0 ); if ( ch_glyph_index==0 ) { - LVFont * fallback = getFallbackFont(); + LVFont * fallback = is_fallback ? getNextFallbackFont() : getFallbackFont(); if ( !fallback ) { // No fallback ch_glyph_index = getCharIndex( ch, def_char ); @@ -2359,7 +2380,7 @@ class LVFreeTypeFace : public LVFont // todo: find a way to adjust origin_y by this font and // fallback font baseline difference, without modifying // the item in the cache of the fallback font - return fallback->getGlyph(ch, def_char); + return fallback->getGlyph(ch, def_char, true); } } LVFontGlyphCacheItem * item = _glyph_cache.getByChar( ch ); @@ -2623,7 +2644,10 @@ class LVFreeTypeFace : public LVFont hb_glyph_position_t *glyph_pos = 0; hb_buffer_clear_contents(_hb_buffer); // Fill HarfBuzz buffer - if ( getFallbackFont() ) { // It has a fallback font, add chars as-is + bool is_fallback_font = flags & LFNT_HINT_IS_FALLBACK_FONT; + LVFont * fallback = is_fallback_font ? getNextFallbackFont() : getFallbackFont(); + bool has_fallback_font = (bool) fallback; + if ( has_fallback_font ) { // It has a fallback font, add chars as-is for (i = 0; i < len; i++) { hb_buffer_add(_hb_buffer, (hb_codepoint_t)(text[i]), i); } @@ -2702,8 +2726,6 @@ class LVFreeTypeFace : public LVFont // inverted for RTL drawing, and we can't uninvert them. We also loop // thru glyphs here rather than chars. int w; - LVFont *fallback = getFallbackFont(); - bool has_fallback_font = (bool) fallback; // Cluster numbers may increase or decrease (if RTL) while we walk the glyphs. // We'll update fallback drawing text indices as we walk glyphs and cluster @@ -2807,7 +2829,7 @@ class LVFreeTypeFace : public LVFont printf(" => %d > %d\n", fb_t_start, fb_t_end); #endif // Adjust DrawTextString() params for fallback drawing - lUInt32 fb_flags = flags; + lUInt32 fb_flags = flags | LFNT_HINT_IS_FALLBACK_FONT; fb_flags &= ~LFNT_DRAW_DECORATION_MASK; // main font will do text decoration // We must keep direction, but we should drop BOT/EOT flags // if this segment is not at start/end (this might be bogus @@ -3127,9 +3149,9 @@ class LVFontBoldTransform : public LVFont \param glyph is pointer to glyph_info_t struct to place retrieved info \return true if glyh was found */ - virtual bool getGlyphInfo( lUInt32 code, glyph_info_t * glyph, lChar16 def_char=0 ) + virtual bool getGlyphInfo( lUInt32 code, glyph_info_t * glyph, lChar16 def_char=0, bool is_fallback=false ) { - bool res = _baseFont->getGlyphInfo( code, glyph, def_char ); + bool res = _baseFont->getGlyphInfo( code, glyph, def_char, is_fallback ); if ( !res ) return res; glyph->blackBoxX += glyph->blackBoxX>0 ? _hShift : 0; @@ -3208,13 +3230,13 @@ class LVFontBoldTransform : public LVFont \param code is unicode character \return glyph pointer if glyph was found, NULL otherwise */ - virtual LVFontGlyphCacheItem * getGlyph(lUInt32 ch, lChar16 def_char=0) { + virtual LVFontGlyphCacheItem * getGlyph(lUInt32 ch, lChar16 def_char=0, bool is_fallback=false) { LVFontGlyphCacheItem * item = _glyph_cache.getByChar( ch ); if ( item ) return item; - LVFontGlyphCacheItem * olditem = _baseFont->getGlyph( ch, def_char ); + LVFontGlyphCacheItem * olditem = _baseFont->getGlyph( ch, def_char, is_fallback ); if ( !olditem ) return NULL; @@ -3518,7 +3540,8 @@ class LVFreeTypeFontManager : public LVFontManager { private: lString8 _path; - lString8 _fallbackFontFace; + lString8 _fallbackFontFacesString; // comma separated list of fallback fonts + lString8Collection _fallbackFontFaces; // splitted from previous LVFontCache _cache; FT_Library _library; LVFontGlobalGlyphCache _globalCache; @@ -3532,29 +3555,55 @@ class LVFreeTypeFontManager : public LVFontManager /// get hash of installed fonts and fallback font virtual lUInt32 GetFontListHash(int documentId) { FONT_MAN_GUARD - return _cache.GetFontListHash(documentId) * 75 + _fallbackFontFace.getHash(); + return _cache.GetFontListHash(documentId) * 75 + _fallbackFontFacesString.getHash(); } - /// set fallback font - virtual bool SetFallbackFontFace( lString8 face ) { + /// set fallback fonts + virtual bool SetFallbackFontFaces( lString8 facesString ) { FONT_MAN_GUARD - if ( face!=_fallbackFontFace ) { - CRLog::trace("Looking for fallback font %s", face.c_str()); - LVFontCacheItem * item = _cache.findFallback( face, -1 ); - if ( !item ) { - face.clear(); - // Don't reset previous fallback if this one is not found/valid + if ( facesString != _fallbackFontFacesString ) { + // Multiple fallback font names can be provided, separated by '|' + lString8Collection faces = lString8Collection(facesString, lString8("|")); + bool has_valid_face = false; + for (int i = 0; i < faces.length(); i++) { + lString8 face = faces[i]; + CRLog::trace("Looking for fallback font %s", face.c_str()); + LVFontCacheItem * item = _cache.findFallback( face, -1 ); + if ( !item ) { // not found + continue; + } + if ( !has_valid_face ) { + has_valid_face = true; + // One valid font: clear previous set of fallback fonts + _fallbackFontFaces.clear(); + } + // Check if duplicate (to avoid fallback font loops) + bool is_duplicate = false; + for ( int i=0; i < _fallbackFontFaces.length(); i++ ) { + if ( face == _fallbackFontFaces[i] ) { + is_duplicate = true; + break; + } + } + if ( is_duplicate ) { + continue; + } + _fallbackFontFaces.add(face); + } + if ( !has_valid_face ) { + // Don't clear previous fallbacks and cache if this one has + // not a single found and valid font return false; } + _fallbackFontFacesString = facesString; _cache.clearFallbackFonts(); - _fallbackFontFace = face; // Somehow, with Fedra Serif (only!), changing the fallback font does // not prevent glyphs from previous fallback font to be re-used... // So let's clear glyphs caches too. gc(); clearGlyphCache(); } - return !_fallbackFontFace.empty(); + return !_fallbackFontFacesString.empty(); } /// set as preferred font with the given bias to add in CalcMatch algorithm @@ -3565,12 +3614,12 @@ class LVFreeTypeFontManager : public LVFontManager /// get fallback font face (returns empty string if no fallback font is set) - virtual lString8 GetFallbackFontFace() { return _fallbackFontFace; } + virtual lString8 GetFallbackFontFaces() { return _fallbackFontFacesString; } /// returns fallback font for specified size virtual LVFontRef GetFallbackFont(int size) { FONT_MAN_GUARD - if ( _fallbackFontFace.empty() ) + if ( _fallbackFontFaces.length() == 0 ) return LVFontRef(); // reduce number of possible distinct sizes for fallback font if ( size>40 ) @@ -3579,16 +3628,16 @@ class LVFreeTypeFontManager : public LVFontManager size &= 0xFFFC; else if ( size>16 ) size &= 0xFFFE; - LVFontCacheItem * item = _cache.findFallback( _fallbackFontFace, size ); + LVFontCacheItem * item = _cache.findFallback( _fallbackFontFaces[0], size ); if ( !item->getFont().isNull() ) return item->getFont(); - return GetFont(size, 400, false, css_ff_sans_serif, _fallbackFontFace, 0, -1); + return GetFont(size, 400, false, css_ff_sans_serif, _fallbackFontFaces[0], 0, -1); } /// returns fallback font for specified size, weight and italic - virtual LVFontRef GetFallbackFont(int size, int weight=400, bool italic=false) { + virtual LVFontRef GetFallbackFont(int size, int weight=400, bool italic=false, lString8 forFaceName=lString8::empty_str) { FONT_MAN_GUARD - if ( _fallbackFontFace.empty() ) + if ( _fallbackFontFaces.length() == 0 ) return LVFontRef(); // reduce number of possible distinct sizes for fallback font if ( size>40 ) @@ -3597,11 +3646,25 @@ class LVFreeTypeFontManager : public LVFontManager size &= 0xFFFC; else if ( size>16 ) size &= 0xFFFE; + // If forFaceName not provided, returns first font among _fallbackFontFaces. + // If forFaceName provided, returns the one just after it, if forFaceName is + // among _fallbackFontFaces. If it is not, return the first one. + int idx = 0; + if ( !forFaceName.empty() ) { + for ( int i=0; i < _fallbackFontFaces.length(); i++ ) { + if ( forFaceName == _fallbackFontFaces[i] ) { + idx = i + 1; + if ( idx >= _fallbackFontFaces.length() ) // forFaceName was last fallback font + return LVFontRef(); + break; + } + } + } // We don't use/extend findFallback(), which was made to work // assuming the fallback font is a standalone regular font // without any bold/italic sibling. // GetFont() works just as fine when we need specified weigh and italic. - return GetFont(size, weight, italic, css_ff_sans_serif, _fallbackFontFace, 0, -1); + return GetFont(size, weight, italic, css_ff_sans_serif, _fallbackFontFaces[idx], 0, -1); } bool isBitmapModeForSize( int size ) @@ -5198,7 +5261,7 @@ int LVBaseFont::DrawTextString( LVDrawBuf * buf, int x, int y, } #if (USE_BITMAP_FONTS==1) -bool LBitmapFont::getGlyphInfo( lUInt32 code, LVFont::glyph_info_t * glyph, lChar16 def_char ) +bool LBitmapFont::getGlyphInfo( lUInt32 code, LVFont::glyph_info_t * glyph, lChar16 def_char, bool is_fallback=false ) { const lvfont_glyph_t * ptr = lvfontGetGlyph( m_font, code ); if (!ptr) @@ -5561,7 +5624,7 @@ bool LVBaseWin32Font::Create(int size, int weight, bool italic, css_font_family_ \param glyph is pointer to glyph_info_t struct to place retrieved info \return true if glyh was found */ -bool LVWin32DrawFont::getGlyphInfo( lUInt16 code, glyph_info_t * glyph, lChar16 def_char ) +bool LVWin32DrawFont::getGlyphInfo( lUInt16 code, glyph_info_t * glyph, lChar16 def_char, bool is_fallback=false ) { return false; } @@ -5956,7 +6019,7 @@ glyph_t * LVWin32Font::GetGlyphRec( lChar16 ch ) \param glyph is pointer to glyph_info_t struct to place retrieved info \return true if glyh was found */ -bool LVWin32Font::getGlyphInfo( lUInt16 code, glyph_info_t * glyph, lChar16 def_char ) +bool LVWin32Font::getGlyphInfo( lUInt16 code, glyph_info_t * glyph, lChar16 def_char, bool is_fallback=false ) { if (_hfont==NULL) return false; diff --git a/crengine/src/lvtinydom.cpp b/crengine/src/lvtinydom.cpp index c1db25355..f3c6ba404 100644 --- a/crengine/src/lvtinydom.cpp +++ b/crengine/src/lvtinydom.cpp @@ -86,7 +86,7 @@ int gDOMVersionRequested = DOM_VERSION_CURRENT; // increment to force complete reload/reparsing of old file #define CACHE_FILE_FORMAT_VERSION "3.05.39k" /// increment following value to force re-formatting of old book after load -#define FORMATTING_VERSION_ID 0x0022 +#define FORMATTING_VERSION_ID 0x0023 #ifndef DOC_DATA_COMPRESSION_LEVEL /// data compression level (0=no compression, 1=fast compressions, 3=normal compression)