diff --git a/crengine/include/fb2def.h b/crengine/include/fb2def.h index 72d367d89..1e2dcc338 100644 --- a/crengine/include/fb2def.h +++ b/crengine/include/fb2def.h @@ -124,6 +124,8 @@ XS_TAG1I( span ) XS_TAG1I( strong ) XS_TAG1I( sub ) XS_TAG1I( sup ) +XS_TAG1I( bdi ) +XS_TAG1I( bdo ) // EPUB3 elements (in ns_epub - otherwise set to inline like any unknown element) XS_TAG1I( switch ) // diff --git a/crengine/include/hyphman.h b/crengine/include/hyphman.h index b0fdf8c15..7f7bfbe76 100644 --- a/crengine/include/hyphman.h +++ b/crengine/include/hyphman.h @@ -21,7 +21,7 @@ class HyphMethod { public: - virtual bool hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 * flags, lUInt16 hyphCharWidth, lUInt16 maxWidth ) = 0; + virtual bool hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 * flags, lUInt16 hyphCharWidth, lUInt16 maxWidth, size_t flagSize=1 ) = 0; virtual ~HyphMethod() { } }; @@ -123,9 +123,9 @@ class HyphMan HyphMan(); ~HyphMan(); - inline static bool hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 * flags, lUInt16 hyphCharWidth, lUInt16 maxWidth ) + inline static bool hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 * flags, lUInt16 hyphCharWidth, lUInt16 maxWidth, size_t flagSize=1 ) { - return _method->hyphenate( str, len, widths, flags, hyphCharWidth, maxWidth ); + return _method->hyphenate( str, len, widths, flags, hyphCharWidth, maxWidth, flagSize ); } }; diff --git a/crengine/include/lvfnt.h b/crengine/include/lvfnt.h index 4e802ba94..4eefeb392 100644 --- a/crengine/include/lvfnt.h +++ b/crengine/include/lvfnt.h @@ -215,16 +215,42 @@ lUInt16 lvfontMeasureText( const lvfont_handle pfont, lChar16 def_char ); -#define LCHAR_IS_SPACE 1 ///< flag: this char is one of unicode space chars -#define LCHAR_ALLOW_WRAP_AFTER 2 ///< flag: line break after this char is allowed -#define LCHAR_DEPRECATED_WRAP_AFTER 4 ///< flag: line break after this char is possible but deprecated -#define LCHAR_ALLOW_HYPH_WRAP_AFTER 8 ///< flag: line break after this char is allowed with addition of hyphen -#define LCHAR_IS_LIGATURE_TAIL 16 ///< flag: this char is a tail of a ligature (ligature is carried by first char) -#define LCHAR_IS_OBJECT 32 ///< flag: this char is object or image -#define LCHAR_MANDATORY_NEWLINE 64 ///< flag: this char must start with new line -#define LCHAR_IS_COLLAPSED_SPACE 128 ///< flag: this char is a space that should not be displayed -// LCHAR_IS_EOL was not used by any code, and has been replaced by LCHAR_IS_LIGATURE_TAIL -// #define LCHAR_IS_EOL 16 ///< flag: this char is CR or LF +// These lower than 0x0100 (that fit in a lUint8) may be set by lvfntman's measureText() +// (to possibly get some informative flags back from harfbuzz) and hyphman's hyphenate(). +// (These should be changed or dropped with care, as they may be used by some other parts of CoolReader) +#define LCHAR_IS_SPACE 0x0001 ///< flag: this char is one of the unicode space chars. + // It is set only on the normal space and the normal non-breakable + // space (spaces that can have their widths expanded or shrunk). + // It is not set on the unicode fixed width spaces. +#define LCHAR_ALLOW_WRAP_AFTER 0x0002 ///< flag: line break after this char is allowed. + // It is set on all spaces, except non-breakable ones. + // It is set on soft-hyphen. + // It is not set on CJK chars. +#define LCHAR_DEPRECATED_WRAP_AFTER 0x0004 ///< flag: line break after this char is possible but deprecated + // It is set on '-' and other unicode hyphens. +#define LCHAR_ALLOW_HYPH_WRAP_AFTER 0x0008 ///< flag: line break after this char is allowed with addition of hyphen + // It is set by Hyphman when finding hyphenation points in a word. +#define LCHAR_MANDATORY_NEWLINE 0x0010 ///< flag: this char must start with new line +#define LCHAR_IS_CLUSTER_TAIL 0x0020 ///< flag: this char is a tail of a cluster (eg. ligature, + // whose glyph is carried by first char) + // It is set by harfbuzz when used. + +/// The next ones, not fitting in a lUInt8, should only be set and used by lvtextfm +#define LCHAR_IS_OBJECT 0x0100 ///< flag: this char is object (image, float) +#define LCHAR_IS_COLLAPSED_SPACE 0x0200 ///< flag: this char is a space that should not be rendered +#define LCHAR_IS_TO_IGNORE 0x0400 ///< flag: this char is to be ignored/skipped in text measurement and drawing +#define LCHAR_IS_RTL 0x0800 ///< flag: this char is part of a RTL segment + +// (Next ones are not yet used and can be removed/changed) +#define LCHAR_IS_CJK_NOT_PUNCT 0x1000 ///< flag: this char is part a CJK char but not a punctuation +#define LCHAR_IS_CJK_LEFT_PUNCT 0x2000 ///< flag: this char is part a CJK left punctuation +#define LCHAR_IS_CJK_RIGHT_PUNCT 0x4000 ///< flag: this char is part a CJK right punctuation + +#define LCHAR_IS_CJK_PUNCT 0x6000 ///< flag: (for checking) this char is a CJK punctuation (neutral if set) +#define LCHAR_IS_CJK 0x7000 ///< flag: (for checking) this char is a CJK char + +// LCHAR_IS_EOL was not used by any code, and has been replaced by LCHAR_IS_CLUSTER_TAIL +// #define LCHAR_IS_EOL 0x0010 ///< flag: this char is CR or LF /** \brief returns true if character is unicode space \param code is character diff --git a/crengine/include/lvfntman.h b/crengine/include/lvfntman.h index e1c47dd12..2abe49255 100644 --- a/crengine/include/lvfntman.h +++ b/crengine/include/lvfntman.h @@ -172,6 +172,21 @@ enum kerning_mode_t { }; +// Hint flags for measuring and drawing (some used only with full Harfbuzz) +// These 4 translate (after mask & shift) from LTEXT_WORD_* equivalents +// (see lvtextfm.h). Keep them in sync. +#define LFNT_HINT_DIRECTION_KNOWN 0x0001 /// segment direction is known +#define LFNT_HINT_DIRECTION_IS_RTL 0x0002 /// segment direction is RTL +#define LFNT_HINT_BEGINS_PARAGRAPH 0x0004 /// segment is at start of paragraph +#define LFNT_HINT_ENDS_PARAGRAPH 0x0008 /// segment is at end of paragraph + +// 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 +#define LFNT_DRAW_LINE_THROUGH 0x0400 /// striked through text +#define LFNT_DRAW_BLINK 0x0800 /// blinking text (implemented as underline) +#define LFNT_DRAW_DECORATION_MASK 0x0F00 + /** \brief base class for fonts implements single interface for font of any engine @@ -215,6 +230,7 @@ class LVFont : public LVRefCounter \param max_width is maximum width to measure line \param def_char is character to replace absent glyphs in font \param letter_spacing is number of pixels to add between letters + \param hints: hint flags (direction, begin/end of paragraph, for Harfbuzz - unrelated to font hinting) \return number of characters before max_width reached */ virtual lUInt16 measureText( @@ -224,28 +240,30 @@ class LVFont : public LVRefCounter int max_width, lChar16 def_char, int letter_spacing=0, - bool allow_hyphenation=true + bool allow_hyphenation=true, + lUInt32 hints=0 ) = 0; + /** \brief measure text \param text is text string pointer \param len is number of characters to measure \return width of specified string */ - virtual lUInt32 getTextWidth( - const lChar16 * text, int len - ) = 0; - -// /** \brief get glyph image in 1 byte per pixel format -// \param code is unicode character -// \param buf is buffer [width*height] to place glyph data -// \return true if glyph was found -// */ -// virtual bool getGlyphImage(lUInt16 code, lUInt8 * buf, lChar16 def_char=0) = 0; + virtual lUInt32 getTextWidth( const lChar16 * text, int len ) = 0; + + // /** \brief get glyph image in 1 byte per pixel format + // \param code is unicode character + // \param buf is buffer [width*height] to place glyph data + // \return true if glyph was found + // */ + // virtual bool getGlyphImage(lUInt16 code, lUInt8 * buf, lChar16 def_char=0) = 0; + /** \brief get glyph item \param code is unicode character \return glyph pointer if glyph was found, NULL otherwise */ virtual LVFontGlyphCacheItem * getGlyph(lUInt32 ch, lChar16 def_char=0) = 0; + /// returns font baseline offset virtual int getBaseline() = 0; /// returns font height including normal interline space @@ -268,8 +286,8 @@ class LVFont : public LVRefCounter virtual lString8 getTypeFace() const = 0; /// returns font family id virtual css_font_family_t getFontFamily() const = 0; - /// draws text string - virtual void DrawTextString( LVDrawBuf * buf, int x, int y, + /// draws text string (returns x advance) + virtual int DrawTextString( LVDrawBuf * buf, int x, int y, const lChar16 * text, int len, lChar16 def_char, lUInt32 * palette = NULL, bool addHyphen = false, lUInt32 flags=0, int letter_spacing=0, int width=-1, @@ -374,6 +392,8 @@ class LVFontManager virtual lString8 GetFallbackFontFace() { 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(); } /// registers font by name virtual bool RegisterFont( lString8 name ) = 0; /// registers font by name and face @@ -445,8 +465,8 @@ class LVBaseFont : public LVFont virtual lString8 getTypeFace() const { return _typeface; } /// returns font family id virtual css_font_family_t getFontFamily() const { return _family; } - /// draws text string - virtual void DrawTextString( LVDrawBuf * buf, int x, int y, + /// draws text string (returns x advance) + virtual int DrawTextString( LVDrawBuf * buf, int x, int y, const lChar16 * text, int len, lChar16 def_char, lUInt32 * palette, bool addHyphen, lUInt32 flags=0, int letter_spacing=0, int width=-1, @@ -469,7 +489,8 @@ class LBitmapFont : public LVBaseFont int max_width, lChar16 def_char, int letter_spacing=0, - bool allow_hyphenation=true + bool allow_hyphenation=true, + lUInt32 hints=0 ); /** \brief measure text \param text is text string pointer @@ -630,7 +651,8 @@ class LVWin32DrawFont : public LVBaseWin32Font int max_width, lChar16 def_char, int letter_spacing=0, - bool allow_hyphenation=true + bool allow_hyphenation=true, + lUInt32 hints=0 ); /** \brief measure text \param text is text string pointer @@ -644,8 +666,8 @@ class LVWin32DrawFont : public LVBaseWin32Font /// returns char width virtual int getCharWidth( lChar16 ch, lChar16 def_char=0 ); - /// draws text string - virtual void DrawTextString( LVDrawBuf * buf, int x, int y, + /// draws text string (returns x advance) + virtual int DrawTextString( LVDrawBuf * buf, int x, int y, const lChar16 * text, int len, lChar16 def_char, lUInt32 * palette, bool addHyphen, lUInt32 flags=0, int letter_spacing=0, int width=-1, @@ -807,7 +829,8 @@ class LVWin32Font : public LVBaseWin32Font int max_width, lChar16 def_char, int letter_spacing=0, - bool allow_hyphenation=true + bool allow_hyphenation=true, + lUInt32 hints=0 ); /** \brief measure text \param text is text string pointer diff --git a/crengine/include/lvpagesplitter.h b/crengine/include/lvpagesplitter.h index 6d22d85b4..ce685e65a 100644 --- a/crengine/include/lvpagesplitter.h +++ b/crengine/include/lvpagesplitter.h @@ -289,11 +289,20 @@ class LVRendLineInfo { { clear(); } - void addLink( LVFootNote * note ) + int getLinksCount() + { + if ( links==NULL ) + return 0; + return links->length(); + } + void addLink( LVFootNote * note, int pos=-1 ) { if ( links==NULL ) links = new LVFootNoteList(); - links->add( note ); + if ( pos >= 0 ) // insert at pos + links->insert( pos, note ); + else // append + links->add( note ); flags |= RN_SPLIT_FOOT_LINK; } }; @@ -371,8 +380,12 @@ class LVRendPageContext } bool updateRenderProgress( int numFinalBlocksRendered ); - /// append footnote link to last added line - void addLink( lString16 id ); + /// Get the number of links in the current line links list, or + // in link_ids when no page_list + int getCurrentLinksCount(); + + /// append or insert footnote link to last added line + void addLink( lString16 id, int pos=-1 ); /// get gathered links when no page_list // (returns a reference to avoid lString16Collection destructor from diff --git a/crengine/include/lvstring.h b/crengine/include/lvstring.h index 92303dd7b..b0a655408 100644 --- a/crengine/include/lvstring.h +++ b/crengine/include/lvstring.h @@ -756,6 +756,7 @@ class lString16Collection for (int i=0; i>LTEXT_WORD_DIRECTION_PARA_TO_LFNT_SHIFT) //#define LTEXT_BACKGROUND_MARK_FLAGS 0xFFFF0000l // formatted_line_t flags -#define LTEXT_LINE_SPLIT_AVOID_BEFORE 1 -#define LTEXT_LINE_SPLIT_AVOID_AFTER 2 +#define LTEXT_LINE_SPLIT_AVOID_BEFORE 0x01 +#define LTEXT_LINE_SPLIT_AVOID_AFTER 0x02 +#define LTEXT_LINE_IS_BIDI 0x04 +#define LTEXT_LINE_PARA_IS_RTL 0x08 /** \brief Text formatter formatted line */ diff --git a/crengine/include/lvtinydom.h b/crengine/include/lvtinydom.h index 6d1ae1b95..6f6ebd1cd 100755 --- a/crengine/include/lvtinydom.h +++ b/crengine/include/lvtinydom.h @@ -2456,6 +2456,10 @@ class ldomDocumentFragmentWriter : public LVXMLParserCallback lString8 headStyleText; int headStyleState; + lString16 htmlDir; + lString16 htmlLang; + bool insideHtmlTag; + public: /// return content of html/head/style element @@ -2486,6 +2490,9 @@ class ldomDocumentFragmentWriter : public LVXMLParserCallback insideTag = false; headStyleText.clear(); headStyleState = 0; + insideHtmlTag = false; + htmlDir.clear(); + htmlLang.clear(); } /// called on parsing end virtual void OnStop() @@ -2525,7 +2532,8 @@ class ldomDocumentFragmentWriter : public LVXMLParserCallback /// constructor ldomDocumentFragmentWriter( LVXMLParserCallback * parentWriter, lString16 baseTagName, lString16 baseTagReplacementName, lString16 fragmentFilePath ) : parent(parentWriter), baseTag(baseTagName), baseTagReplacement(baseTagReplacementName), - insideTag(false), styleDetectionState(0), pathSubstitutions(100), baseElement(NULL), lastBaseElement(NULL), headStyleState(0) + insideTag(false), styleDetectionState(0), pathSubstitutions(100), baseElement(NULL), lastBaseElement(NULL), + headStyleState(0), insideHtmlTag(false) { setCodeBase( fragmentFilePath ); } diff --git a/crengine/src/hyphman.cpp b/crengine/src/hyphman.cpp index 6ca5f1adb..10f896731 100755 --- a/crengine/src/hyphman.cpp +++ b/crengine/src/hyphman.cpp @@ -71,7 +71,7 @@ class TexHyph : public HyphMethod public: int largest_overflowed_word; bool match( const lChar16 * str, char * mask ); - virtual bool hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 * flags, lUInt16 hyphCharWidth, lUInt16 maxWidth ); + virtual bool hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 * flags, lUInt16 hyphCharWidth, lUInt16 maxWidth, size_t flagSize ); void addPattern( TexPattern * pattern ); TexHyph(); virtual ~TexHyph(); @@ -83,21 +83,21 @@ class TexHyph : public HyphMethod class AlgoHyph : public HyphMethod { public: - virtual bool hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 * flags, lUInt16 hyphCharWidth, lUInt16 maxWidth ); + virtual bool hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 * flags, lUInt16 hyphCharWidth, lUInt16 maxWidth, size_t flagSize ); virtual ~AlgoHyph(); }; class SoftHyphensHyph : public HyphMethod { public: - virtual bool hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 * flags, lUInt16 hyphCharWidth, lUInt16 maxWidth ); + virtual bool hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 * flags, lUInt16 hyphCharWidth, lUInt16 maxWidth, size_t flagSize ); virtual ~SoftHyphensHyph(); }; class NoHyph : public HyphMethod { public: - virtual bool hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 * flags, lUInt16 hyphCharWidth, lUInt16 maxWidth ) + virtual bool hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 * flags, lUInt16 hyphCharWidth, lUInt16 maxWidth, size_t flagSize ) { CR_UNUSED6(str, len, widths, flags, hyphCharWidth, maxWidth); return false; @@ -361,23 +361,29 @@ HyphMan::~HyphMan() // and AlgoHyph::hyphenate(): if soft hyphens are found in the // provided word, trust and use them; don't do the regular patterns // and algorithm matching. -static bool softhyphens_hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 * flags, lUInt16 hyphCharWidth, lUInt16 maxWidth ) +static bool softhyphens_hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 * flags, lUInt16 hyphCharWidth, lUInt16 maxWidth, size_t flagSize ) { bool soft_hyphens_found = false; for ( int i = 0; i maxWidth ) break; if ( str[i] == UNICODE_SOFT_HYPHEN_CODE ) { - flags[i] |= LCHAR_ALLOW_HYPH_WRAP_AFTER; + if ( flagSize == 2 ) { + lUInt16* flags16 = (lUInt16*) flags; + flags16[i] |= LCHAR_ALLOW_HYPH_WRAP_AFTER; + } + else { + flags[i] |= LCHAR_ALLOW_HYPH_WRAP_AFTER; + } soft_hyphens_found = true; } } return soft_hyphens_found; } -bool SoftHyphensHyph::hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 * flags, lUInt16 hyphCharWidth, lUInt16 maxWidth ) +bool SoftHyphensHyph::hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 * flags, lUInt16 hyphCharWidth, lUInt16 maxWidth, size_t flagSize ) { - return softhyphens_hyphenate(str, len, widths, flags, hyphCharWidth, maxWidth); + return softhyphens_hyphenate(str, len, widths, flags, hyphCharWidth, maxWidth, flagSize); } SoftHyphensHyph::~SoftHyphensHyph() @@ -803,10 +809,10 @@ bool TexHyph::match( const lChar16 * str, char * mask ) // return true; //} -bool TexHyph::hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 * flags, lUInt16 hyphCharWidth, lUInt16 maxWidth ) +bool TexHyph::hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 * flags, lUInt16 hyphCharWidth, lUInt16 maxWidth, size_t flagSize ) { if ( HyphMan::_TrustSoftHyphens ) { - if ( softhyphens_hyphenate(str, len, widths, flags, hyphCharWidth, maxWidth) ) + if ( softhyphens_hyphenate(str, len, widths, flags, hyphCharWidth, maxWidth, flagSize) ) return true; } if ( len<=3 ) @@ -894,7 +900,13 @@ bool TexHyph::hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 // p+2 because: +1 because word has a space prepended, and +1 because // mask[] holds the flag for char n on slot n+1 if ( (mask[p+2-soft_hyphens_skipped]&1) && nw <= maxWidth ) { - flags[p] |= LCHAR_ALLOW_HYPH_WRAP_AFTER; + if ( flagSize == 2 ) { + lUInt16* flags16 = (lUInt16*) flags; + flags16[p] |= LCHAR_ALLOW_HYPH_WRAP_AFTER; + } + else { + flags[p] |= LCHAR_ALLOW_HYPH_WRAP_AFTER; + } // printf(" allowed after %c\n", str[p]); res = true; } @@ -902,10 +914,10 @@ bool TexHyph::hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 return res; } -bool AlgoHyph::hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 * flags, lUInt16 hyphCharWidth, lUInt16 maxWidth ) +bool AlgoHyph::hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 * flags, lUInt16 hyphCharWidth, lUInt16 maxWidth, size_t flagSize ) { if ( HyphMan::_TrustSoftHyphens ) { - if ( softhyphens_hyphenate(str, len, widths, flags, hyphCharWidth, maxWidth) ) + if ( softhyphens_hyphenate(str, len, widths, flags, hyphCharWidth, maxWidth, flagSize) ) return true; } lUInt16 chprops[WORD_LENGTH]; @@ -966,8 +978,15 @@ bool AlgoHyph::hyphenate( const lChar16 * str, int len, lUInt16 * widths, lUInt8 disabled = true; break; } - if (!disabled) - flags[i] |= LCHAR_ALLOW_HYPH_WRAP_AFTER; + if (!disabled) { + if ( flagSize == 2 ) { + lUInt16* flags16 = (lUInt16*) flags; + flags16[i] |= LCHAR_ALLOW_HYPH_WRAP_AFTER; + } + else { + flags[i] |= LCHAR_ALLOW_HYPH_WRAP_AFTER; + } + } //widths[i] = nw; // don't add hyph width } } diff --git a/crengine/src/lvfntman.cpp b/crengine/src/lvfntman.cpp index 95cac6e46..2065c7ff6 100644 --- a/crengine/src/lvfntman.cpp +++ b/crengine/src/lvfntman.cpp @@ -24,6 +24,10 @@ #include "../include/lvstyles.h" #include "../include/lvthread.h" +// Uncomment for debugging text measurement or drawing +// #define DEBUG_MEASURE_TEXT +// #define DEBUG_DRAW_TEXT + // define to filter out all fonts except .ttf //#define LOAD_TTF_FONTS_ONLY // DEBUG ONLY @@ -42,6 +46,14 @@ #include #include FT_FREETYPE_H +#include FT_OUTLINE_H // for FT_Outline_Embolden() +#include FT_SYNTHESIS_H // for FT_GlyphSlot_Embolden() + +// Use Freetype embolden API instead of LVFontBoldTransform to +// make fake bold (for fonts that do not provide a bold face). +// This gives a chance to get them working with Harfbuzz, even if +// they won't look as nice as if they came with a real bold font. +#define USE_FT_EMBOLDEN #if USE_HARFBUZZ==1 #include @@ -77,25 +89,25 @@ static int gammaIndex = GAMMA_NO_CORRECTION_INDEX; /// returns first found face from passed list, or return face for font found by family only lString8 LVFontManager::findFontFace(lString8 commaSeparatedFaceList, css_font_family_t fallbackByFamily) { - // faces we want - lString8Collection list; - splitPropertyValueList(commaSeparatedFaceList.c_str(), list); - // faces we have - lString16Collection faces; - getFaceList(faces); - // find first matched - for (int i = 0; i < list.length(); i++) { - lString8 wantFace = list[i]; - for (int j = 0; j < faces.length(); j++) { - lString16 haveFace = faces[j]; - if (wantFace == haveFace) - return wantFace; - } - } - // not matched - get by family name + // faces we want + lString8Collection list; + splitPropertyValueList(commaSeparatedFaceList.c_str(), list); + // faces we have + lString16Collection faces; + getFaceList(faces); + // find first matched + for (int i = 0; i < list.length(); i++) { + lString8 wantFace = list[i]; + for (int j = 0; j < faces.length(); j++) { + lString16 haveFace = faces[j]; + if (wantFace == haveFace) + return wantFace; + } + } + // not matched - get by family name LVFontRef fnt = GetFont(10, 400, false, fallbackByFamily, lString8("Arial")); if (fnt.isNull()) - return lString8::empty_str; // not found + return lString8::empty_str; // not found // get face from found font return fnt->getTypeFace(); } @@ -134,8 +146,8 @@ double LVFontManager::GetGamma() { /// sets current gamma level void LVFontManager::SetGamma( double gamma ) { -// gammaLevel = cr_ft_gamma_levels[GAMMA_LEVELS/2]; -// gammaIndex = GAMMA_LEVELS/2; + // gammaLevel = cr_ft_gamma_levels[GAMMA_LEVELS/2]; + // gammaIndex = GAMMA_LEVELS/2; int oldGammaIndex = gammaIndex; for ( int i=0; i -1 +// - italic=2 (if font has no real italic, and it is synthetized +// thanks to Freetype from the regular font glyphs) +// - _weight=601 (if no real bold font, and synthetized from +// the regular font glyphs) +// It can be used as a key by caches to retrieve a registered font +// or an instantiated one, and as a query to find in the cache an +// exact or an approximate font. /** \brief Font properties definition @@ -345,32 +391,33 @@ class LVFontDef LVByteArrayRef _buf; int _bias; public: - LVFontDef(const lString8 & name, int size, int weight, int italic, css_font_family_t family, const lString8 & typeface, int index=-1, int documentId=-1, LVByteArrayRef buf = LVByteArrayRef()) - : _size(size) - , _weight(weight) - , _italic(italic) - , _family(family) - , _typeface(typeface) - , _name(name) - , _index(index) - , _documentId(documentId) - , _buf(buf) - , _bias(0) - { - } + LVFontDef(const lString8 & name, int size, int weight, int italic, css_font_family_t family, + const lString8 & typeface, int index=-1, int documentId=-1, LVByteArrayRef buf = LVByteArrayRef()) + : _size(size) + , _weight(weight) + , _italic(italic) + , _family(family) + , _typeface(typeface) + , _name(name) + , _index(index) + , _documentId(documentId) + , _buf(buf) + , _bias(0) + { + } LVFontDef(const LVFontDef & def) - : _size(def._size) - , _weight(def._weight) - , _italic(def._italic) - , _family(def._family) - , _typeface(def._typeface) - , _name(def._name) - , _index(def._index) - , _documentId(def._documentId) - , _buf(def._buf) - , _bias(def._bias) - { - } + : _size(def._size) + , _weight(def._weight) + , _italic(def._italic) + , _family(def._family) + , _typeface(def._typeface) + , _name(def._name) + , _index(def._index) + , _documentId(def._documentId) + , _buf(def._buf) + , _bias(def._bias) + { + } /// returns true if definitions are equal bool operator == ( const LVFontDef & def ) const @@ -441,8 +488,8 @@ class LVFontCacheItem LVFontRef & getFont() { return _fnt; } void setFont(LVFontRef & fnt) { _fnt = fnt; } LVFontCacheItem( const LVFontDef & def ) - : _def( def ) - { } + : _def( def ) + { } }; /// font cache @@ -463,8 +510,10 @@ class LVFontCache LVFontCacheItem * findFallback( lString8 face, int size ); LVFontCacheItem * findDuplicate( const LVFontDef * def ); LVFontCacheItem * findDocumentFontDuplicate(int documentId, lString8 name); + /// get hash of installed fonts and fallback font - virtual lUInt32 GetFontListHash(int documentId) { + virtual lUInt32 GetFontListHash(int documentId) + { lUInt32 hash = 0; for ( int i=0; i<_registered_list.length(); i++ ) { int doc = _registered_list[i]->getDef()->getDocumentId(); @@ -581,8 +630,8 @@ class LVFontGlyphSignedMetricCache : public LVFontGlyphUnsignedMetricCache } }; - class LVFreeTypeFace; + static LVFontGlyphCacheItem * newItem( LVFontLocalGlyphCache * local_cache, lChar16 ch, FT_GlyphSlot slot ) // , bool drawMonochrome { FONT_LOCAL_GLYPH_CACHE_GUARD @@ -608,8 +657,9 @@ static LVFontGlyphCacheItem * newItem( LVFontLocalGlyphCache * local_cache, lCha } ptr += bitmap->pitch;//rowsize; } - } else { -#if 0 + } + else { + #if 0 if ( bitmap->pixel_mode==FT_PIXEL_MODE_MONO ) { memset( item->bmp, 0, w*h ); lUInt8 * srcrow = bitmap->buffer; @@ -624,12 +674,12 @@ static LVFontGlyphCacheItem * newItem( LVFontLocalGlyphCache * local_cache, lCha srcrow += bitmap->pitch; dstrow += w; } - } else { -#endif - memcpy( item->bmp, bitmap->buffer, w*h ); - // correct gamma - if ( gammaIndex!=GAMMA_NO_CORRECTION_INDEX ) - cr_correct_gamma_buf(item->bmp, w*h, gammaIndex); + } // else: + #endif + memcpy( item->bmp, bitmap->buffer, w*h ); + // correct gamma + if ( gammaIndex!=GAMMA_NO_CORRECTION_INDEX ) + cr_correct_gamma_buf(item->bmp, w*h, gammaIndex); } item->origin_x = (lInt16)slot->bitmap_left; item->origin_y = (lInt16)slot->bitmap_top; @@ -640,44 +690,59 @@ static LVFontGlyphCacheItem * newItem( LVFontLocalGlyphCache * local_cache, lCha #if USE_HARFBUZZ==1 static LVFontGlyphIndexCacheItem * newItem(lUInt32 index, FT_GlyphSlot slot ) { - FONT_LOCAL_GLYPH_CACHE_GUARD - FT_Bitmap* bitmap = &slot->bitmap; - int w = bitmap->width; - int h = bitmap->rows; - LVFontGlyphIndexCacheItem* item = LVFontGlyphIndexCacheItem::newItem(index, w, h ); - if (!item) - return 0; - if ( bitmap->pixel_mode==FT_PIXEL_MODE_MONO ) { //drawMonochrome - lUInt8 mask = 0x80; - const lUInt8 * ptr = (const lUInt8 *)bitmap->buffer; - lUInt8 * dst = item->bmp; - //int rowsize = ((w + 15) / 16) * 2; - for ( int y=0; y>= 1; - if ( !mask && x!=w-1) { - mask = 0x80; - row++; - } - } - ptr += bitmap->pitch;//rowsize; - } - } else { - memcpy( item->bmp, bitmap->buffer, w*h ); - // correct gamma - if ( gammaIndex!=GAMMA_NO_CORRECTION_INDEX ) - cr_correct_gamma_buf(item->bmp, w*h, gammaIndex); - } - item->origin_x = (lInt16)slot->bitmap_left; - item->origin_y = (lInt16)slot->bitmap_top; - item->advance = (lUInt16)(myabs(slot->metrics.horiAdvance) >> 6); - return item; + FONT_LOCAL_GLYPH_CACHE_GUARD + FT_Bitmap* bitmap = &slot->bitmap; + int w = bitmap->width; + int h = bitmap->rows; + LVFontGlyphIndexCacheItem* item = LVFontGlyphIndexCacheItem::newItem(index, w, h ); + if (!item) + return 0; + if ( bitmap->pixel_mode==FT_PIXEL_MODE_MONO ) { //drawMonochrome + lUInt8 mask = 0x80; + const lUInt8 * ptr = (const lUInt8 *)bitmap->buffer; + lUInt8 * dst = item->bmp; + //int rowsize = ((w + 15) / 16) * 2; + for ( int y=0; y>= 1; + if ( !mask && x!=w-1) { + mask = 0x80; + row++; + } + } + ptr += bitmap->pitch;//rowsize; + } + } + else { + memcpy( item->bmp, bitmap->buffer, w*h ); + // correct gamma + if ( gammaIndex!=GAMMA_NO_CORRECTION_INDEX ) + cr_correct_gamma_buf(item->bmp, w*h, gammaIndex); + } + item->origin_x = (lInt16)slot->bitmap_left; + item->origin_y = (lInt16)slot->bitmap_top; + item->advance = (lUInt16)(myabs(slot->metrics.horiAdvance) >> 6); + return item; } #endif +// Each LVFontGlyphCacheItem is put in 2 caches: +// - the LVFontLocalGlyphCache LVFreeTypeFace->_glyph_cache of the +// font it comes from +// - the LVFontGlobalGlyphCache LVFreeTypeFontManager->_globalCache of +// the global and unique FontManager. +// The first one is used for quick iteration to find the glyph in a +// known font/size instance. +// The global one is used to limit the number of cached glyphs, globally +// across all fonts. +// When adding the glyph to the local cache, the local cache adds it +// to the global cache. When that happens, the global cache checks +// its max_size, and remove any LRU item, by deleting it from itself, +// and asking the relevant local cache to remove it too. + void LVFontLocalGlyphCache::clear() { FONT_LOCAL_GLYPH_CACHE_GUARD @@ -816,11 +881,13 @@ lString8 familyName( FT_Face face ) // The 2 slots with "LCHAR_IS_SPACE | LCHAR_ALLOW_WRAP_AFTER" on the 2nd line previously // were: "LCHAR_IS_SPACE | LCHAR_IS_EOL | LCHAR_ALLOW_WRAP_AFTER". -// LCHAR_IS_EOL was not used by any code, and has been replaced by LCHAR_IS_LIGATURE_TAIL -// (as flags are usually lUInt8, and the 8 bits were used, one needed to be dropped). +// LCHAR_IS_EOL was not used by any code, and has been replaced by LCHAR_IS_CLUSTER_TAIL +// (as flags were lUInt8, and the 8 bits were used, one needed to be dropped - they +// have since been upgraded to be lUInt16) static lUInt16 char_flags[] = { 0, 0, 0, 0, 0, 0, 0, 0, // 0 00 - 0, 0, LCHAR_IS_SPACE | LCHAR_ALLOW_WRAP_AFTER, 0, 0, LCHAR_IS_SPACE | LCHAR_ALLOW_WRAP_AFTER, 0, 0, // 8 08 + 0, 0, LCHAR_IS_SPACE | LCHAR_ALLOW_WRAP_AFTER, 0, // 8 08 + 0, LCHAR_IS_SPACE | LCHAR_ALLOW_WRAP_AFTER, 0, 0, // 12 0C 0, 0, 0, 0, 0, 0, 0, 0, // 16 10 0, 0, 0, 0, 0, 0, 0, 0, // 24 18 LCHAR_IS_SPACE | LCHAR_ALLOW_WRAP_AFTER, 0, 0, 0, 0, 0, 0, 0, // 32 20 @@ -840,12 +907,13 @@ static lUInt16 char_flags[] = { (ch>=UNICODE_EN_QUAD && ch<=UNICODE_ZERO_WIDTH_SPACE ? LCHAR_ALLOW_WRAP_AFTER: \ 0))))))) +// For use with Harfbuzz light struct LVCharTriplet { lChar16 prevChar; lChar16 Char; lChar16 nextChar; - bool operator==(const struct LVCharTriplet& other) { + bool operator == (const struct LVCharTriplet& other) { return prevChar == other.prevChar && Char == other.Char && nextChar == other.nextChar; } }; @@ -872,7 +940,7 @@ inline lUInt32 getHash( const struct LVCharTriplet& triplet ) class LVFreeTypeFace : public LVFont { protected: - LVMutex & _mutex; + LVMutex & _mutex; lString8 _fileName; lString8 _faceName; css_font_family_t _fontFamily; @@ -884,26 +952,32 @@ class LVFreeTypeFace : public LVFont int _height; // full line height in pixels int _hyphen_width; int _baseline; - int _weight; - int _italic; + int _weight; // 400: normal, 700: bold, 601: fake/synthetized bold, 100..900 thin..black + int _italic; // 0: regular, 1: italic, 2: fake/synthetized italic LVFontGlyphUnsignedMetricCache _wcache; // glyph width cache LVFontGlyphSignedMetricCache _lsbcache; // glyph left side bearing cache LVFontGlyphSignedMetricCache _rsbcache; // glyph right side bearing cache - LVFontLocalGlyphCache _glyph_cache; - bool _drawMonochrome; + LVFontLocalGlyphCache _glyph_cache; + bool _drawMonochrome; hinting_mode_t _hintingMode; kerning_mode_t _kerningMode; - bool _fallbackFontIsSet; - LVFontRef _fallbackFont; + bool _fallbackFontIsSet; + LVFontRef _fallbackFont; + bool _embolden; // fake/synthetized bold + FT_Pos _embolden_half_strength; // for emboldening with Harfbuzz #if USE_HARFBUZZ==1 hb_font_t* _hb_font; + // // For use with KERNING_MODE_HARFBUZZ: + #define HARFBUZZ_FULL_FEATURES_NB 2 hb_buffer_t* _hb_buffer; - hb_feature_t _hb_features[2]; + hb_feature_t _hb_features[HARFBUZZ_FULL_FEATURES_NB]; LVHashTable _glyph_cache2; + // // For use with KERNING_MODE_HARFBUZZ_LIGHT: + #define HARFBUZZ_LIGHT_FEATURES_NB 22 hb_buffer_t* _hb_light_buffer; - hb_feature_t _hb_light_features[22]; + hb_feature_t _hb_light_features[HARFBUZZ_LIGHT_FEATURES_NB]; LVHashTable _width_cache2; #endif public: @@ -920,8 +994,9 @@ class LVFreeTypeFace : public LVFont LVFont * getFallbackFont() { if ( _fallbackFontIsSet ) return _fallbackFont.get(); - if ( fontMan->GetFallbackFontFace()!=_faceName ) // to avoid circular link, disable fallback for fallback font - _fallbackFont = fontMan->GetFallbackFont(_size); + // To avoid circular link, disable fallback for fallback font: + if ( fontMan->GetFallbackFontFace()!=_faceName ) + _fallbackFont = fontMan->GetFallbackFont(_size, _weight, _italic); _fallbackFontIsSet = true; return _fallbackFont.get(); } @@ -932,44 +1007,52 @@ class LVFreeTypeFace : public LVFont virtual int getItalic() const { return _italic; } /// sets face name virtual void setFaceName( lString8 face ) { _faceName = face; } + virtual lString8 getFaceName() { return _faceName; } LVMutex & getMutex() { return _mutex; } FT_Library getLibrary() { return _library; } LVFreeTypeFace( LVMutex &mutex, FT_Library library, LVFontGlobalGlyphCache * globalCache ) - : _mutex(mutex), _fontFamily(css_ff_sans_serif), _library(library), _face(NULL), _size(0), _hyphen_width(0), _baseline(0) - , _weight(400), _italic(0) - , _glyph_cache(globalCache), _drawMonochrome(false), _kerningMode(KERNING_MODE_DISABLED), _hintingMode(HINTING_MODE_AUTOHINT), _fallbackFontIsSet(false) -#if USE_HARFBUZZ==1 - , _glyph_cache2(256) - , _width_cache2(1024) -#endif + : _mutex(mutex), _fontFamily(css_ff_sans_serif), _library(library), _face(NULL) + , _size(0), _hyphen_width(0), _baseline(0) + , _weight(400), _italic(0), _embolden(false) + , _glyph_cache(globalCache), _drawMonochrome(false) + , _kerningMode(KERNING_MODE_DISABLED), _hintingMode(HINTING_MODE_AUTOHINT) + , _fallbackFontIsSet(false) + #if USE_HARFBUZZ==1 + , _glyph_cache2(256) + , _width_cache2(1024) + #endif { _matrix.xx = 0x10000; _matrix.yy = 0x10000; _matrix.xy = 0; _matrix.yx = 0; _hintingMode = fontMan->GetHintingMode(); -#if USE_HARFBUZZ==1 + + #if USE_HARFBUZZ==1 _hb_font = 0; _hb_buffer = hb_buffer_create(); _hb_light_buffer = hb_buffer_create(); + // HarfBuzz features for full text shaping + // Update HARFBUZZ_FULL_FEATURES_NB when adding/removing hb_feature_from_string("+kern", -1, &_hb_features[0]); // font kerning hb_feature_from_string("+liga", -1, &_hb_features[1]); // ligatures // HarfBuzz features for lighweight characters width calculating with caching + // Update HARFBUZZ_LIGHT_FEATURES_NB when adding/removing // We need to disable all the features, enabled by default in Harfbuzz, that // may split a char into more glyphs, or merge chars into one glyph. // (see harfbuzz/src/hb-ot-shape.cc hb_ot_shape_collect_features() ) - + // // We can enable these ones: hb_feature_from_string("+kern", -1, &_hb_light_features[0]); // Kerning: Fine horizontal positioning of one glyph to the next, based on the shapes of the glyphs hb_feature_from_string("+mark", -1, &_hb_light_features[1]); // Mark Positioning: Fine positioning of a mark glyph to a base character hb_feature_from_string("+mkmk", -1, &_hb_light_features[2]); // Mark-to-mark Positioning: Fine positioning of a mark glyph to another mark character hb_feature_from_string("+curs", -1, &_hb_light_features[3]); // Cursive Positioning: Precise positioning of a letter's connection to an adjacent one hb_feature_from_string("+locl", -1, &_hb_light_features[4]); // Substitutes character with the preferred form based on script language - + // // We should disable these ones: hb_feature_from_string("-liga", -1, &_hb_light_features[5]); // Standard Ligatures: replaces (by default) sequence of characters with a single ligature glyph hb_feature_from_string("-rlig", -1, &_hb_light_features[6]); // Ligatures required for correct text display (any script, but in cursive) - Arabic, semitic @@ -993,17 +1076,17 @@ class LVFreeTypeFace : public LVFont // Especially needed with Fedra Serif and "The", "Thuringe": -calt // These tweaks seem fragile (adding here +smcp to experiment with small caps would break FreeSerif again). // So, when tuning these, please check it still behave well with FreeSerif. -#endif + #endif } virtual ~LVFreeTypeFace() { -#if USE_HARFBUZZ==1 + #if USE_HARFBUZZ==1 if (_hb_buffer) hb_buffer_destroy(_hb_buffer); if (_hb_light_buffer) hb_buffer_destroy(_hb_light_buffer); -#endif + #endif Clear(); } @@ -1012,7 +1095,7 @@ class LVFreeTypeFace : public LVFont _wcache.clear(); _lsbcache.clear(); _rsbcache.clear(); -#if USE_HARFBUZZ==1 + #if USE_HARFBUZZ==1 LVHashTable::pair* pair; LVHashTable::iterator it = _glyph_cache2.forwardIterator(); while ((pair = it.next())) { @@ -1022,11 +1105,10 @@ class LVFreeTypeFace : public LVFont } _glyph_cache2.clear(); _width_cache2.clear(); -#endif + #endif } - virtual int getHyphenWidth() - { + virtual int getHyphenWidth() { FONT_GUARD if ( !_hyphen_width ) { _hyphen_width = getCharWidth( UNICODE_SOFT_HYPHEN_CODE ); @@ -1034,18 +1116,16 @@ class LVFreeTypeFace : public LVFont return _hyphen_width; } - virtual kerning_mode_t getKerningMode() const { return _kerningMode; } - virtual void setKerningMode( kerning_mode_t kerningMode ) { _kerningMode = kerningMode; _hash = 0; // Force lvstyles.cpp calcHash(font_ref_t) to recompute the hash -#if USE_HARFBUZZ==1 + #if USE_HARFBUZZ==1 // in cache may be found some ligatures, so clear it clearCache(); -#endif + #endif } + virtual kerning_mode_t getKerningMode() const { return _kerningMode; } - /// sets current hinting mode virtual void setHintingMode(hinting_mode_t mode) { if (_hintingMode == mode) return; @@ -1053,12 +1133,9 @@ class LVFreeTypeFace : public LVFont _hash = 0; // Force lvstyles.cpp calcHash(font_ref_t) to recompute the hash clearCache(); } - /// returns current hinting mode virtual hinting_mode_t getHintingMode() const { return _hintingMode; } - /// get bitmap mode (true=bitmap, false=antialiased) - virtual bool getBitmapMode() { return _drawMonochrome; } - /// set bitmap mode (true=bitmap, false=antialiased) + /// get/set bitmap mode (true=bitmap, false=antialiased) virtual void setBitmapMode( bool drawBitmap ) { if ( _drawMonochrome == drawBitmap ) @@ -1066,9 +1143,58 @@ class LVFreeTypeFace : public LVFont _drawMonochrome = drawBitmap; clearCache(); } + virtual bool getBitmapMode() { return _drawMonochrome; } - bool loadFromBuffer(LVByteArrayRef buf, int index, int size, css_font_family_t fontFamily, bool monochrome, bool italicize ) - { + // Synthetized bold on a font that does not come with a bold variant. + void setEmbolden() { + _embolden = true; + // A real bold font has weight 700, vs 400 for the regular. + // LVFontBoldTransform did +200, so we get 600 (demibold). + // Let's do the same (even if I don't see why not +300). + _weight = (_weight + 200 > 900) ? 900 : _weight + 200; + // And add +1 so we can know it's a fake/synthetized font, so we + // can avoid getting it (and get the original regular font instead) + // when synthetizing an other variant of that font. + _weight += 1; + // When not using Harfbuzz, we will simply call FT_GlyphSlot_Embolden() + // to get the glyphinfo and glyph with synthetized bold and increased + // metrics, and everything should work naturally: + // "Embolden a glyph by a 'reasonable' value (which is highly a matter + // of taste) [...] For emboldened outlines the height, width, and + // advance metrics are increased by the strength of the emboldening". + // + // When using Harfbuzz, which uses itself the font metrics, that we + // can't tweak at all from outside, we'll get positionning based on + // the not-bolded font. We can't increase them as that would totally + // mess HB work. + // We can only do as MuPDF does (source/fitz/font.c): keep the HB + // positions, offset and advances, embolden the glyph by some value + // of 'strength', and shift left/bottom by 1/2 'strength', so the + // boldened glyph is centered on its original: the glyph being a + // bit larger, it will blend over its neighbour glyphs, but it + // looks quite allright. + // Caveat: words in fake bold will be bolder, but not larger than + // the same word in the regular font (unlike with a real bold font + // were they would be bolder and larger). + // We need to compute the strength as done in FT_GlyphSlot_Embolden(): + // xstr = FT_MulFix( face->units_per_EM, face->size->metrics.y_scale ) / 24; + // ystr = xstr; + // FT_Outline_EmboldenXY( &slot->outline, xstr, ystr ); + // and will do as MuPDF does (with some private value of 'strength'): + // FT_Outline_Embolden(&face->glyph->outline, strength); + // FT_Outline_Translate(&face->glyph->outline, -strength/2, -strength/2); + // (with strength: 0=no change; 64=1px embolden; 128=2px embolden and 1px x/y translation) + // int strength = (_face->units_per_EM * _face->size->metrics.y_scale) / 24; + FT_Pos embolden_strength = FT_MulFix(_face->units_per_EM, _face->size->metrics.y_scale) / 24; + // Make it slightly less bold than Freetype's bold, as we get less spacing + // around glyphs with HarfBuzz, by getting the unbolded advances. + embolden_strength = embolden_strength * 3/4; // (*1/2 is fine but a tad too light) + _embolden_half_strength = embolden_strength / 2; + } + + // Used when an embedded font (registered by RegisterDocumentFont()) is intantiated + bool loadFromBuffer(LVByteArrayRef buf, int index, int size, css_font_family_t fontFamily, + bool monochrome, bool italicize ) { FONT_GUARD _hintingMode = fontMan->GetHintingMode(); _drawMonochrome = monochrome; @@ -1082,9 +1208,11 @@ class LVFreeTypeFace : public LVFont lString8 kernFile = _fileName.substr(0, _fileName.length()-4); if ( LVFileExists(Utf8ToUnicode(kernFile) + ".afm" ) ) { kernFile += ".afm"; - } else if ( LVFileExists(Utf8ToUnicode(kernFile) + ".pfm" ) ) { + } + else if ( LVFileExists(Utf8ToUnicode(kernFile) + ".pfm" ) ) { kernFile += ".pfm"; - } else { + } + else { kernFile.clear(); } if ( !kernFile.empty() ) @@ -1097,19 +1225,21 @@ class LVFreeTypeFace : public LVFont //if ( !FT_IS_SCALABLE( _face ) ) { // Clear(); // return false; - // } + //} error = FT_Set_Pixel_Sizes( _face, /* handle to face object */ 0, /* pixel_width */ - size ); /* pixel_height */ -#if USE_HARFBUZZ==1 + size ); /* pixel_height */ + + #if USE_HARFBUZZ==1 if (FT_Err_Ok == error) { if (_hb_font) hb_font_destroy(_hb_font); _hb_font = hb_ft_font_create(_face, NULL); if (!_hb_font) { error = FT_Err_Invalid_Argument; - } else { + } + else { // NOTE: Commented out for now, it's prohibitively expensive. c.f., #230 /* // Use the same load flags as we do when using FT directly, to avoid mismatching advances & raster @@ -1117,28 +1247,33 @@ class LVFreeTypeFace : public LVFont flags |= (!_drawMonochrome ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO); if (_hintingMode == HINTING_MODE_BYTECODE_INTERPRETOR) { flags |= FT_LOAD_NO_AUTOHINT; - } else if (_hintingMode == HINTING_MODE_AUTOHINT) { + } + else if (_hintingMode == HINTING_MODE_AUTOHINT) { flags |= FT_LOAD_FORCE_AUTOHINT; - } else if (_hintingMode == HINTING_MODE_DISABLED) { + } + else if (_hintingMode == HINTING_MODE_DISABLED) { flags |= FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING; } hb_ft_font_set_load_flags(_hb_font, flags); */ } } -#endif + #endif + if (error) { Clear(); return false; } -#if 0 + + #if 0 int nheight = _face->size->metrics.height; int targetheight = size << 6; error = FT_Set_Pixel_Sizes( _face, /* handle to face object */ 0, /* pixel_width */ - (size * targetheight + nheight/2)/ nheight ); /* pixel_height */ -#endif + (size * targetheight + nheight/2)/ nheight ); /* pixel_height */ + #endif + _height = _face->size->metrics.height >> 6; _size = size; //(_face->size->metrics.height >> 6); _baseline = _height + (_face->size->metrics.descender >> 6); @@ -1152,14 +1287,23 @@ class LVFreeTypeFace : public LVFont } if ( error ) { - // error return false; } + + // If no unicode charmap, select any symbol charmap. + // This is needed with Harfbuzz shaping (with Freetype, we switch charmap + // when needed). It might not be needed with a Harfbuzz newer than 2.6.1 + // that will include https://github.com/harfbuzz/harfbuzz/pull/1948. + if (FT_Select_Charmap(_face, FT_ENCODING_UNICODE)) // non-zero means failure + // If no unicode charmap found, try symbol charmap + FT_Select_Charmap(_face, FT_ENCODING_MS_SYMBOL); + return true; } - bool loadFromFile( const char * fname, int index, int size, css_font_family_t fontFamily, bool monochrome, bool italicize ) - { + // Load font from file path + bool loadFromFile( const char * fname, int index, int size, css_font_family_t fontFamily, + bool monochrome, bool italicize ) { FONT_GUARD _hintingMode = fontMan->GetHintingMode(); _drawMonochrome = monochrome; @@ -1174,16 +1318,18 @@ class LVFreeTypeFace : public LVFont if (error) return false; if ( _fileName.endsWith(".pfb") || _fileName.endsWith(".pfa") ) { - lString8 kernFile = _fileName.substr(0, _fileName.length()-4); + lString8 kernFile = _fileName.substr(0, _fileName.length()-4); if ( LVFileExists(Utf8ToUnicode(kernFile) + ".afm") ) { - kernFile += ".afm"; - } else if ( LVFileExists(Utf8ToUnicode(kernFile) + ".pfm" ) ) { - kernFile += ".pfm"; - } else { - kernFile.clear(); - } - if ( !kernFile.empty() ) - error = FT_Attach_File( _face, kernFile.c_str() ); + kernFile += ".afm"; + } + else if ( LVFileExists(Utf8ToUnicode(kernFile) + ".pfm" ) ) { + kernFile += ".pfm"; + } + else { + kernFile.clear(); + } + if ( !kernFile.empty() ) + error = FT_Attach_File( _face, kernFile.c_str() ); } //FT_Face_SetUnpatentedHinting( _face, 1 ); _slot = _face->glyph; @@ -1192,19 +1338,21 @@ class LVFreeTypeFace : public LVFont //if ( !FT_IS_SCALABLE( _face ) ) { // Clear(); // return false; - // } + //} error = FT_Set_Pixel_Sizes( _face, /* handle to face object */ 0, /* pixel_width */ size ); /* pixel_height */ -#if USE_HARFBUZZ==1 + + #if USE_HARFBUZZ==1 if (FT_Err_Ok == error) { if (_hb_font) hb_font_destroy(_hb_font); _hb_font = hb_ft_font_create(_face, NULL); if (!_hb_font) { error = FT_Err_Invalid_Argument; - } else { + } + else { // NOTE: Commented out for now, it's prohibitively expensive. c.f., #230 /* // Use the same load flags as we do when using FT directly, to avoid mismatching advances & raster @@ -1212,28 +1360,33 @@ class LVFreeTypeFace : public LVFont flags |= (!_drawMonochrome ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO); if (_hintingMode == HINTING_MODE_BYTECODE_INTERPRETOR) { flags |= FT_LOAD_NO_AUTOHINT; - } else if (_hintingMode == HINTING_MODE_AUTOHINT) { + } + else if (_hintingMode == HINTING_MODE_AUTOHINT) { flags |= FT_LOAD_FORCE_AUTOHINT; - } else if (_hintingMode == HINTING_MODE_DISABLED) { + } + else if (_hintingMode == HINTING_MODE_DISABLED) { flags |= FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING; } hb_ft_font_set_load_flags(_hb_font, flags); */ } } -#endif + #endif + if (error) { Clear(); return false; } -#if 0 + + #if 0 int nheight = _face->size->metrics.height; int targetheight = size << 6; error = FT_Set_Pixel_Sizes( _face, /* handle to face object */ 0, /* pixel_width */ (size * targetheight + nheight/2)/ nheight ); /* pixel_height */ -#endif + #endif + _height = _face->size->metrics.height >> 6; _size = size; //(_face->size->metrics.height >> 6); _baseline = _height + (_face->size->metrics.descender >> 6); @@ -1250,33 +1403,49 @@ class LVFreeTypeFace : public LVFont // error return false; } + + // If no unicode charmap, select any symbol charmap. + // This is needed with Harfbuzz shaping (with Freetype, we switch charmap + // when needed). It might not be needed with a Harfbuzz newer than 2.6.1 + // that will include https://github.com/harfbuzz/harfbuzz/pull/1948. + if (FT_Select_Charmap(_face, FT_ENCODING_UNICODE)) // non-zero means failure + // If no unicode charmap found, try symbol charmap + FT_Select_Charmap(_face, FT_ENCODING_MS_SYMBOL); + return true; } #if USE_HARFBUZZ==1 - lChar16 filterChar(lChar16 code) { - lChar16 res; - if (code == '\t') - code = ' '; + // Used by Harfbuzz full + lChar16 filterChar(lChar16 code, lChar16 def_char=0) { + if (code == '\t') // (FreeSerif doesn't have \t, get a space + code = ' '; // rather than a '?') + FT_UInt ch_glyph_index = FT_Get_Char_Index(_face, code); - if ( ch_glyph_index==0 && code >= 0xF000 && code <= 0xF0FF) { + if (ch_glyph_index != 0) { // found + return code; + } + + if ( code >= 0xF000 && code <= 0xF0FF) { // If no glyph found and code is among the private unicode // area classically used by symbol fonts (range U+F020-U+F0FF), // try to switch to FT_ENCODING_MS_SYMBOL - if (FT_Select_Charmap(_face, FT_ENCODING_MS_SYMBOL)) { + if (!FT_Select_Charmap(_face, FT_ENCODING_MS_SYMBOL)) { ch_glyph_index = FT_Get_Char_Index( _face, code ); // restore unicode charmap if there is one FT_Select_Charmap(_face, FT_ENCODING_UNICODE); + if (ch_glyph_index != 0) { // glyph found: code is valid + return code; + } } } - if (0 != ch_glyph_index) - res = code; - else { - res = getReplacementChar(code); - if (0 == res) - res = code; - } - return res; + lChar16 res = getReplacementChar(code); + if (res != 0) + return res; + if (def_char != 0) + return def_char; + // If nothing found, let code be + return code; } bool hbCalcCharWidth(struct LVCharPosInfo* posInfo, const struct LVCharTriplet& triplet, lChar16 def_char) { @@ -1285,47 +1454,58 @@ class LVFreeTypeFace : public LVFont unsigned int segLen = 0; int cluster; hb_buffer_clear_contents(_hb_light_buffer); - if (0 != triplet.prevChar) { + if ( triplet.prevChar != 0 ) { hb_buffer_add(_hb_light_buffer, (hb_codepoint_t)triplet.prevChar, segLen); segLen++; } hb_buffer_add(_hb_light_buffer, (hb_codepoint_t)triplet.Char, segLen); cluster = segLen; segLen++; - if (0 != triplet.nextChar) { + if ( triplet.nextChar != 0 ) { hb_buffer_add(_hb_light_buffer, (hb_codepoint_t)triplet.nextChar, segLen); segLen++; } hb_buffer_set_content_type(_hb_light_buffer, HB_BUFFER_CONTENT_TYPE_UNICODE); hb_buffer_guess_segment_properties(_hb_light_buffer); - hb_shape(_hb_font, _hb_light_buffer, _hb_light_features, 22); + hb_shape(_hb_font, _hb_light_buffer, _hb_light_features, HARFBUZZ_LIGHT_FEATURES_NB); unsigned int glyph_count = hb_buffer_get_length(_hb_light_buffer); if (segLen == glyph_count) { hb_glyph_info_t *glyph_info = hb_buffer_get_glyph_infos(_hb_light_buffer, &glyph_count); hb_glyph_position_t *glyph_pos = hb_buffer_get_glyph_positions(_hb_light_buffer, &glyph_count); - if (0 != glyph_info[cluster].codepoint) { // glyph found for this char in this font - posInfo->offset = glyph_pos[cluster].x_offset >> 6; - posInfo->width = glyph_pos[cluster].x_advance >> 6; - } else { - // hb_shape() failed or glyph omitted in this font, use fallback font - glyph_info_t glyph; - LVFont *fallback = getFallbackFont(); - if (fallback) { - if (fallback->getGlyphInfo(triplet.Char, &glyph, def_char)) { - posInfo->offset = 0; - posInfo->width = glyph.width; - } + // Ignore HB measurements when there is a single glyph not found, + // as it may be found in a fallback font + int codepoint_notfound_nb = 0; + for (int i=0; ioffset = glyph_pos[cluster].x_offset >> 6; + posInfo->width = glyph_pos[cluster].x_advance >> 6; + return true; } } - } else { -#ifdef _DEBUG - CRLog::debug("hbCalcCharWidthWithKerning(): hb_buffer_get_length() return %d, must be %d, return value (-1)", glyph_count, segLen); -#endif - return false; } - return true; + // Otherwise, use plain Freetype getGlyphInfo() which will check + // again with this font, or the fallback one + glyph_info_t glyph; + if ( getGlyphInfo(triplet.Char, &glyph, def_char) ) { + posInfo->offset = 0; + posInfo->width = glyph.width; + return true; + } + return false; } -#endif +#endif // USE_HARFBUZZ==1 FT_UInt getCharIndex( lChar16 code, lChar16 def_char ) { if ( code=='\t' ) @@ -1335,7 +1515,7 @@ class LVFreeTypeFace : public LVFont // If no glyph found and code is among the private unicode // area classically used by symbol fonts (range U+F020-U+F0FF), // try to switch to FT_ENCODING_MS_SYMBOL - if (FT_Select_Charmap(_face, FT_ENCODING_MS_SYMBOL)) { + if (!FT_Select_Charmap(_face, FT_ENCODING_MS_SYMBOL)) { ch_glyph_index = FT_Get_Char_Index( _face, code ); // restore unicode charmap if there is one FT_Select_Charmap(_face, FT_ENCODING_UNICODE); @@ -1355,8 +1535,7 @@ 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 ) { //FONT_GUARD int glyph_index = getCharIndex( code, 0 ); if ( glyph_index==0 ) { @@ -1366,27 +1545,36 @@ class LVFreeTypeFace : public LVFont glyph_index = getCharIndex( code, def_char ); if ( glyph_index==0 ) return false; - } else { + } + else { // Fallback return fallback->getGlyphInfo(code, glyph, def_char); } } + int flags = FT_LOAD_DEFAULT; flags |= (!_drawMonochrome ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO); if (_hintingMode == HINTING_MODE_BYTECODE_INTERPRETOR) { flags |= FT_LOAD_NO_AUTOHINT; - } else if (_hintingMode == HINTING_MODE_AUTOHINT) { + } + else if (_hintingMode == HINTING_MODE_AUTOHINT) { flags |= FT_LOAD_FORCE_AUTOHINT; - } else if (_hintingMode == HINTING_MODE_DISABLED) { + } + else if (_hintingMode == HINTING_MODE_DISABLED) { flags |= FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING; } - updateTransform(); + updateTransform(); // no-op int error = FT_Load_Glyph( _face, /* handle to face object */ glyph_index, /* glyph index */ flags ); /* load flags, see below */ if ( error ) return false; + + if (_embolden) { // Embolden so we get the real embolden metrics + // See setEmbolden() for details + FT_GlyphSlot_Embolden(_slot); + } glyph->blackBoxX = (lUInt16)(_slot->metrics.width >> 6); glyph->blackBoxY = (lUInt16)(_slot->metrics.height >> 6); glyph->originX = (lInt16)(_slot->metrics.horiBearingX >> 6); @@ -1417,8 +1605,8 @@ class LVFreeTypeFace : public LVFont return true; } -/* - // USE GET_CHAR_FLAGS instead +#if 0 + // USE GET_CHAR_FLAGS instead inline int calcCharFlags( lChar16 ch ) { switch ( ch ) { @@ -1435,10 +1623,16 @@ class LVFreeTypeFace : public LVFont return 0; } } - */ +#endif + /** \brief measure text \param text is text string pointer \param len is number of characters to measure + \param max_width is maximum width to measure line + \param def_char is character to replace absent glyphs in font + \param letter_spacing is number of pixels to add between letters + \param allow_hyphenation whether to check for hyphenation if max_width reached + \param hints: hint flags (direction, begin/end of paragraph, for Harfbuzz - unrelated to font hinting) \return number of characters before max_width reached */ virtual lUInt16 measureText( @@ -1449,17 +1643,17 @@ class LVFreeTypeFace : public LVFont int max_width, lChar16 def_char, int letter_spacing = 0, - bool allow_hyphenation = true + bool allow_hyphenation = true, + lUInt32 hints=0 ) { FONT_GUARD if ( len <= 0 || _face==NULL ) return 0; - if ( letter_spacing < 0 ) - { + if ( letter_spacing < 0 ) { letter_spacing = 0; - } else if ( letter_spacing > MAX_LETTER_SPACING ) - { + } + else if ( letter_spacing > MAX_LETTER_SPACING ) { letter_spacing = MAX_LETTER_SPACING; } @@ -1467,133 +1661,334 @@ class LVFreeTypeFace : public LVFont lUInt16 prev_width = 0; int lastFitChar = 0; - updateTransform(); + updateTransform(); // no-op // measure character widths -#if USE_HARFBUZZ==1 + #if USE_HARFBUZZ==1 if (_kerningMode == KERNING_MODE_HARFBUZZ) { + /** from harfbuzz/src/hb-buffer.h + * hb_glyph_info_t: + * @codepoint: either a Unicode code point (before shaping) or a glyph index + * (after shaping). + * @cluster: the index of the character in the original text that corresponds + * to this #hb_glyph_info_t, or whatever the client passes to + * hb_buffer_add(). More than one #hb_glyph_info_t can have the same + * @cluster value, if they resulted from the same character (e.g. one + * to many glyph substitution), and when more than one character gets + * merged in the same glyph (e.g. many to one glyph substitution) the + * #hb_glyph_info_t will have the smallest cluster value of them. + * By default some characters are merged into the same cluster + * (e.g. combining marks have the same cluster as their bases) + * even if they are separate glyphs, hb_buffer_set_cluster_level() + * allow selecting more fine-grained cluster handling. + */ unsigned int glyph_count; hb_glyph_info_t* glyph_info = 0; hb_glyph_position_t* glyph_pos = 0; hb_buffer_clear_contents(_hb_buffer); - hb_buffer_set_replacement_codepoint(_hb_buffer, def_char); - // fill HarfBuzz buffer with filtering - for (i = 0; i < len; i++) - hb_buffer_add(_hb_buffer, (hb_codepoint_t)filterChar(text[i]), i); + + // hb_buffer_set_replacement_codepoint(_hb_buffer, def_char); + // /\ This would just set the codepoint to use when parsing + // invalid utf8/16/32. As we provide codepoints, Harfbuzz + // won't use it. This does NOT set the codepoint/glyph that + // would be used when a glyph does not exist in that for that + // codepoint. There is currently no way to specify that, and + // it's always the .notdef/tofu glyph that is measured/drawn. + + // Fill HarfBuzz buffer + // No need to call filterChar() on the input: HarfBuzz seems to do + // the right thing with symbol fonts, and we'd better not replace + // bullets & al unicode chars with generic equivalents, as they + // may be found in the fallback font. + // So, we don't, unless the current font has no fallback font, + // in which case we need to get a replacement, in the worst case + // def_char (?), because the glyph for 0/.notdef (tofu) has so + // many different looks among fonts that it would mess the text. + // We'll then get the '?' glyph of the fallback font only. + // Note: not sure if Harfbuzz is able to be fine by using other + // glyphs when the main codepoint does not exist by itself in + // the font... in which case we'll mess things up. + // 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 + for (i = 0; i < len; i++) { + hb_buffer_add(_hb_buffer, (hb_codepoint_t)(text[i]), i); + } + } + else { // No fallback font, check codepoint presence or get replacement char + for (i = 0; i < len; i++) { + hb_buffer_add(_hb_buffer, (hb_codepoint_t)filterChar(text[i], def_char), i); + } + } + // Note: hb_buffer_add_codepoints(_hb_buffer, (hb_codepoint_t*)text, len, 0, len) + // would do the same kind of loop we did above, so no speedup gain using it; and we + // get to be sure of the cluster initial value we set to each of our added chars. hb_buffer_set_content_type(_hb_buffer, HB_BUFFER_CONTENT_TYPE_UNICODE); + + // If we are provided with direction and hints, let harfbuzz know + if ( hints ) { + if ( hints & LFNT_HINT_DIRECTION_KNOWN ) { + if ( hints & LFNT_HINT_DIRECTION_IS_RTL ) + hb_buffer_set_direction(_hb_buffer, HB_DIRECTION_RTL); + else + hb_buffer_set_direction(_hb_buffer, HB_DIRECTION_LTR); + } + int hb_flags = HB_BUFFER_FLAG_DEFAULT; // (hb_buffer_flags_t won't let us do |= ) + if ( hints & LFNT_HINT_BEGINS_PARAGRAPH ) + hb_flags |= HB_BUFFER_FLAG_BOT; + if ( hints & LFNT_HINT_ENDS_PARAGRAPH ) + hb_flags |= HB_BUFFER_FLAG_EOT; + hb_buffer_set_flags(_hb_buffer, (hb_buffer_flags_t)hb_flags); + } + // Let HB guess what's not been set (script, direction, language) hb_buffer_guess_segment_properties(_hb_buffer); - // shape - hb_shape(_hb_font, _hb_buffer, _hb_features, 2); + + // Some additional care might need to be taken, see: + // https://www.w3.org/TR/css-text-3/#letter-spacing-property + if ( letter_spacing > 0 ) { + // Don't apply letter-spacing if the script is cursive + hb_script_t script = hb_buffer_get_script(_hb_buffer); + if ( isScriptCursive(script) ) + letter_spacing = 0; + } + // todo: if letter_spacing, ligatures should be disabled (-liga, -clig) + // todo: letter-spacing must not be applied at the beginning or at the end of a line + // todo: it should be applied half-before/half-after each grapheme + // cf in *some* minikin repositories: libs/minikin/Layout.cpp + + // Shape + hb_shape(_hb_font, _hb_buffer, _hb_features, HARFBUZZ_FULL_FEATURES_NB); + + // Harfbuzz has guessed and set a direction even if we did not provide one. + bool is_rtl = false; + if ( hb_buffer_get_direction(_hb_buffer) == HB_DIRECTION_RTL ) { + is_rtl = true; + // "For buffers in the right-to-left (RTL) or bottom-to-top (BTT) text + // flow direction, the directionality of the buffer itself is reversed + // for final output as a matter of design. Therefore, HarfBuzz inverts + // the monotonic property: client programs are guaranteed that + // monotonically increasing initial cluster values will be returned as + // monotonically decreasing final cluster values." + // hb_buffer_reverse_clusters() puts the advance on the last char of a + // cluster, unlike hb_buffer_reverse() which puts it on the first, which + // looks more natural (like it happens when LTR). + // But hb_buffer_reverse_clusters() is required to have the clusters + // ordered as our text indices, so we can map them back to our text. + hb_buffer_reverse_clusters(_hb_buffer); + } + glyph_count = hb_buffer_get_length(_hb_buffer); glyph_info = hb_buffer_get_glyph_infos(_hb_buffer, 0); glyph_pos = hb_buffer_get_glyph_positions(_hb_buffer, 0); -#ifdef _DEBUG - if ((int)glyph_count != len) { - CRLog::debug( - "measureText(): glyph_count not equal source text length (ligature detected?), glyph_count=%d, len=%d", - glyph_count, len); - } -#endif - uint32_t j; - uint32_t cluster; - uint32_t prev_cluster = 0; - int skipped_chars = 0; // to add to 'i' at end of loop, as 'i' is used later and should be accurate - for (i = 0; i < (int)glyph_count; i++) { - /** from harfbuzz/src/hb-buffer.h - * hb_glyph_info_t: - * @codepoint: either a Unicode code point (before shaping) or a glyph index - * (after shaping). - * @cluster: the index of the character in the original text that corresponds - * to this #hb_glyph_info_t, or whatever the client passes to - * hb_buffer_add(). More than one #hb_glyph_info_t can have the same - * @cluster value, if they resulted from the same character (e.g. one - * to many glyph substitution), and when more than one character gets - * merged in the same glyph (e.g. many to one glyph substitution) the - * #hb_glyph_info_t will have the smallest cluster value of them. - * By default some characters are merged into the same cluster - * (e.g. combining marks have the same cluster as their bases) - * even if they are separate glyphs, hb_buffer_set_cluster_level() - * allow selecting more fine-grained cluster handling. - */ - // Note: we use 'cluster' as an index similar to 'i' in 'text' - // but the docs warn about this, as it may be wrong with some "level" - // see https://harfbuzz.github.io/clusters.html for details - // But it seems to work in our case. - // Note: we deal with the "when more than one character gets merged in the - // same glyph" case. Not sure we do things correctly for the "More than one - // glyph can have the same cluster value, if they resulted from the same - // character (one to many glyph substitution)"... - - cluster = glyph_info[i].cluster; - lChar16 ch = text[cluster]; - // It seems each soft-hyphen is in its own cluster, of length 1 and width 0, - // so HarfBuzz must already deal correctly with soft-hyphens. - // No need to do what we do in the Freetype alternative code below. - bool isHyphen = (ch == UNICODE_SOFT_HYPHEN_CODE); - flags[cluster] = GET_CHAR_FLAGS(ch); //calcCharFlags( ch ); - hb_codepoint_t ch_glyph_index = glyph_info[i].codepoint; - if (0 != ch_glyph_index) // glyph found for this char in this font - widths[cluster] = prev_width + (glyph_pos[i].x_advance >> 6) + letter_spacing; - else { - // hb_shape() failed or glyph skipped in this font, use fallback font - int w = _wcache.get(ch); - if ( w == CACHED_UNSIGNED_METRIC_NOT_SET ) { - glyph_info_t glyph; - LVFont *fallback = getFallbackFont(); - if (fallback) { - if (fallback->getGlyphInfo(ch, &glyph, def_char)) { - w = glyph.width; - _wcache.put(ch, w); - } else // ignore (skip) this char - widths[cluster] = prev_width; - } else // ignore (skip) this char - widths[cluster] = prev_width; + + #ifdef DEBUG_MEASURE_TEXT + printf("MTHB >>> measureText %x len %d is_rtl=%d [%s]\n", text, len, is_rtl, _faceName.c_str()); + for (i = 0; i < (int)glyph_count; i++) { + char glyphname[32]; + hb_font_get_glyph_name(_hb_font, glyph_info[i].codepoint, glyphname, sizeof(glyphname)); + printf("MTHB g%d c%d(=t:%x) [%x %s]\tadvance=(%d,%d)", i, glyph_info[i].cluster, + text[glyph_info[i].cluster], glyph_info[i].codepoint, glyphname, + glyph_pos[i].x_advance>>6, glyph_pos[i].y_advance>>6); + if (glyph_pos[i].x_offset || glyph_pos[i].y_offset) + printf("\toffset=(%d,%d)", glyph_pos[i].x_offset>>6, glyph_pos[i].y_offset>>6); + printf("\n"); + } + printf("MTHB ---\n"); + #endif + + // We need to set widths and flags on our original text. + // hb_shape has modified buffer to contain glyphs, and text + // and buffer may desync (because of clusters, ligatures...) + // in both directions in a same run. + // Also, a cluster must not be cut, so we want to set the same + // width to all our original text chars that are part of the + // same cluster (so 2nd+ chars in a cluster, will get a 0-width, + // and, when splitting lines, will fit in a word with the first + // char). + // So run along our original text (chars, t), and try to follow + // harfbuzz buffer (glyphs, hg), putting the advance of all + // the glyphs that belong to the same cluster (hcl) on the + // first char that started that cluster (and 0-width on the + // followup chars). + // It looks like Harfbuzz makes a cluster of combined glyphs + // even when the font does not have any or all of the required + // glyphs: + // When meeting a not-found glyph (codepoint=0, name=".notdef"), + // we record the original starting t of that cluster, and + // keep processing (possibly other chars with .notdef glyphs, + // giving them the width of the 'tofu' char), until we meet a char + // with a found glyph. We then hold on on this one, while we go + // measureText() the previous segment of text (that got .notdef + // glyphs) with the fallback font, and update the wrongs width + // and flags. + + int prev_width = 0; + int cur_width = 0; + int cur_cluster = 0; + int hg = 0; // index in glyph_info/glyph_pos + int hcl = 0; // cluster glyph at hg + bool is_cluster_tail = false; + int t_notdef_start = -1; + int t_notdef_end = -1; + for (int t = 0; t < len; t++) { + #ifdef DEBUG_MEASURE_TEXT + printf("MTHB t%d (=%x) ", t, text[t]); + #endif + // Grab all glyphs that do not belong to a cluster greater that our char position + while ( hg < glyph_count ) { + hcl = glyph_info[hg].cluster; + if (hcl <= t) { + int advance = 0; + if ( glyph_info[hg].codepoint != 0 ) { // Codepoint found in this font + #ifdef DEBUG_MEASURE_TEXT + printf("(found cp=%x) ", glyph_info[hg].codepoint); + #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 ) { + // 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; + if ( t_notdef_start > 0 ) + fb_hints &= ~LFNT_HINT_BEGINS_PARAGRAPH; + if ( t_notdef_end < len ) + fb_hints &= ~LFNT_HINT_ENDS_PARAGRAPH; + fallback->measureText( text + t_notdef_start, t_notdef_end - t_notdef_start, + widths + t_notdef_start, flags + t_notdef_start, + max_width, def_char, letter_spacing, allow_hyphenation, + fb_hints ); + // Fix previous bad measurements + int last_good_width = t_notdef_start > 0 ? widths[t_notdef_start-1] : 0; + for (int tn = t_notdef_start; tn < t_notdef_end; tn++) { + widths[tn] += last_good_width; + } + // And fix our current width + cur_width = widths[t_notdef_end-1]; + prev_width = cur_width; + #ifdef DEBUG_MEASURE_TEXT + printf("MTHB ### measured past failures > W= %d\n[...]", cur_width); + #endif + } + else { + // No fallback font: stay with what's been measured: the notdef/tofu char + #ifdef DEBUG_MEASURE_TEXT + printf("[...]\nMTHB no fallback font to measure past failures, keeping def_char\nMTHB [...]"); + #endif + } + t_notdef_start = -1; + // And go on with the found glyph now that we fixed what was before + } + // Glyph found in this font + advance = glyph_pos[hg].x_advance >> 6; + } + else { + #ifdef DEBUG_MEASURE_TEXT + printf("(glyph not found) "); + #endif + // Keep the advance of .notdef/tofu in case there is no fallback font to correct them + advance = glyph_pos[hg].x_advance >> 6; + if ( t_notdef_start < 0 ) { + t_notdef_start = t; + } + } + #ifdef DEBUG_MEASURE_TEXT + printf("c%d+%d ", hcl, advance); + #endif + cur_width += advance; + cur_cluster = hcl; + hg++; + continue; // keep grabbing glyphs } - widths[cluster] = prev_width + w + letter_spacing; + break; + } + // Done grabbing clustered glyphs: they contributed to cur_width. + // All 't' lower than the next cluster will have that same cur_width. + if (cur_cluster < t) { + // Our char is part of a cluster that started on a previous char + flags[t] = LCHAR_IS_CLUSTER_TAIL; + // todo: see at using HB_GLYPH_FLAG_UNSAFE_TO_BREAK to + // set this flag instead/additionally } - for (j = prev_cluster + 1; j < cluster; j++) { - // fill flags and widths for chars skipped (because they are part of a - // ligature and are accounted in the previous cluster - we didn't know - // how many until we processed the next cluster) - flags[j] = GET_CHAR_FLAGS(text[j]) | LCHAR_IS_LIGATURE_TAIL; - widths[j] = prev_width; // so 2nd char of a ligature has width=0 - skipped_chars++; - // This will ensure we get (with word "afloat"): - // glyph 14 cluster 14 flags=0: glyph "a" - // widths[14] = 150, flags[14]=0 char "a" - // glyph 15 cluster 15 flags=0: glyph "fl" (ligature) - // widths[15] = 163, flags[15]=0 char "f" - // glyph 16 cluster 17 flags=0: glyph "o" - // widths[17] = 174, flags[17]=0 char "o" - // >widths[16] = 163, flags[16]=0 char "l" (done here) - // glyph 17 cluster 18 flags=0: glyph "a" - // widths[18] = 185, flags[18]=0 char "a" - // glyph 18 cluster 19 flags=0: glyph "t" - // widths[19] = 192, flags[19]=0 char "t" + else { + // We're either a single char cluster, or the start + // of a multi chars cluster. + cur_width += letter_spacing; // only between clusters/graphemes + flags[t] = GET_CHAR_FLAGS(text[t]); + // It seems each soft-hyphen is in its own cluster, of length 1 and width 0, + // so HarfBuzz must already deal correctly with soft-hyphens. } - prev_cluster = cluster; - if (!isHyphen) // avoid soft hyphens inside text string - prev_width = widths[cluster]; - if (prev_width > max_width) { - if (lastFitChar < cluster + 7) + widths[t] = cur_width; + #ifdef DEBUG_MEASURE_TEXT + printf("=> %d (flags=%d) => W=%d\n", cur_width - prev_width, flags[t], cur_width); + #endif + prev_width = cur_width; + + // (Not sure about how that max_width limit could play and if it could mess things) + if (cur_width > max_width) { + if (lastFitChar < hcl + 7) break; - } else { - lastFitChar = cluster + 1; } - } - // For case when ligature is the last glyph in measured text - if (prev_cluster < (uint32_t)(len - 1) && prev_width < (lUInt16)max_width) { - for (j = prev_cluster + 1; j < (uint32_t)len; j++) { - flags[j] = GET_CHAR_FLAGS(text[j]) | LCHAR_IS_LIGATURE_TAIL; - widths[j] = prev_width; - skipped_chars++; + else { + lastFitChar = t+1; + } + } // process next char t + + // 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 ) { + #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; + if ( t_notdef_start > 0 ) + fb_hints &= ~LFNT_HINT_BEGINS_PARAGRAPH; + int chars_measured = fallback->measureText( text + t_notdef_start, // start + t_notdef_end - t_notdef_start, // len + widths + t_notdef_start, flags + t_notdef_start, + max_width, def_char, letter_spacing, allow_hyphenation, + fb_hints ); + lastFitChar = t_notdef_start + chars_measured; + int last_good_width = t_notdef_start > 0 ? widths[t_notdef_start-1] : 0; + for (int tn = t_notdef_start; tn < t_notdef_end; tn++) { + widths[tn] += last_good_width; + } + // And add all that to our current width + cur_width = widths[t_notdef_end-1]; + #ifdef DEBUG_MEASURE_TEXT + printf("MTHB ### measured past failures at EOT > W= %d\n[...]", cur_width); + #endif + } + else { + #ifdef DEBUG_MEASURE_TEXT + printf("[...]\nMTHB no fallback font to measure past failures at EOT, keeping def_char\nMTHB [...]"); + #endif } } - // i is used below to "fill props for rest of chars", so make it accurate - i += skipped_chars; + // i is used below to "fill props for rest of chars", so make it accurate + i = len; // actually make it do nothing + + #ifdef DEBUG_MEASURE_TEXT + printf("MTHB <<< W=%d [%s]\n", cur_width, _faceName.c_str()); + printf("MTHB dwidths[]: "); + for (int t = 0; t < len; t++) + printf("%d:%d ", t, widths[t] - (t>0?widths[t-1]:0)); + printf("\n"); + #endif } // _kerningMode == KERNING_MODE_HARFBUZZ + else if (_kerningMode == KERNING_MODE_HARFBUZZ_LIGHT) { unsigned int glyph_count; hb_glyph_info_t* glyph_info = 0; @@ -1634,21 +2029,22 @@ class LVFreeTypeFace : public LVFont if ( prev_width > max_width ) { if ( lastFitChar < i + 7) break; - } else { + } + else { lastFitChar = i + 1; } } - } // _kerningMode == KERNING_MODE_HARFBUZZ_LIGHT + else { // _kerningMode == KERNING_MODE_DISABLED or KERNING_MODE_FREETYPE: // fallback to the non harfbuzz code -#endif // USE_HARFBUZZ + #endif // USE_HARFBUZZ FT_UInt previous = 0; int error; -#if (ALLOW_KERNING==1) + #if (ALLOW_KERNING==1) int use_kerning = _kerningMode != KERNING_MODE_DISABLED && FT_HAS_KERNING( _face ); -#endif + #endif for ( i=0; i0 ) { if ( ch_glyph_index==(FT_UInt)-1 ) ch_glyph_index = getCharIndex( ch, def_char ); @@ -1677,7 +2073,7 @@ class LVFreeTypeFace : public LVFont kerning = delta.x; } } -#endif + #endif flags[i] = GET_CHAR_FLAGS(ch); //calcCharFlags( ch ); @@ -1688,20 +2084,21 @@ class LVFreeTypeFace : public LVFont if ( getGlyphInfo( ch, &glyph, def_char ) ) { w = glyph.width; _wcache.put(ch, w); - } else { + } + else { widths[i] = prev_width; lastFitChar = i + 1; continue; /* ignore errors */ } -// if ( ch_glyph_index==(FT_UInt)-1 ) -// ch_glyph_index = getCharIndex( ch, 0 ); -// error = FT_Load_Glyph( _face, /* handle to face object */ -// ch_glyph_index, /* glyph index */ -// FT_LOAD_DEFAULT ); /* load flags, see below */ -// if ( error ) { -// widths[i] = prev_width; -// continue; /* ignore errors */ -// } + // if ( ch_glyph_index==(FT_UInt)-1 ) + // ch_glyph_index = getCharIndex( ch, 0 ); + // error = FT_Load_Glyph( _face, /* handle to face object */ + // ch_glyph_index, /* glyph index */ + // FT_LOAD_DEFAULT ); /* load flags, see below */ + // if ( error ) { + // widths[i] = prev_width; + // continue; /* ignore errors */ + // } } if ( use_kerning ) { if ( ch_glyph_index==(FT_UInt)-1 ) @@ -1714,22 +2111,21 @@ class LVFreeTypeFace : public LVFont if ( prev_width > max_width ) { if ( lastFitChar < i + 7) break; - } else { + } + else { lastFitChar = i + 1; } } -#if USE_HARFBUZZ==1 - } // else fallback to the non harfbuzz code -#endif + + #if USE_HARFBUZZ==1 + } // else fallback to the non harfbuzz code + #endif // fill props for rest of chars for ( int ii=i; iiMAX_LINE_CHARS ) @@ -1774,12 +2167,12 @@ class LVFreeTypeFace : public LVFont return 0; } - void updateTransform() { -// static void * transformOwner = NULL; -// if ( transformOwner!=this ) { -// FT_Set_Transform(_face, &_matrix, NULL); -// transformOwner = this; -// } + void updateTransform() { // called, but no-op + // static void * transformOwner = NULL; + // if ( transformOwner!=this ) { + // FT_Set_Transform(_face, &_matrix, NULL); + // transformOwner = this; + // } } /** \brief get glyph item @@ -1796,31 +2189,49 @@ class LVFreeTypeFace : public LVFont ch_glyph_index = getCharIndex( ch, def_char ); if ( ch_glyph_index==0 ) return NULL; - } else { + } + else { // Fallback + // 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); } } LVFontGlyphCacheItem * item = _glyph_cache.get( ch ); if ( !item ) { - - int rend_flags = FT_LOAD_RENDER | ( !_drawMonochrome ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO ); //|FT_LOAD_MONOCHROME|FT_LOAD_FORCE_AUTOHINT + int rend_flags = FT_LOAD_RENDER | ( !_drawMonochrome ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO ); + //|FT_LOAD_MONOCHROME|FT_LOAD_FORCE_AUTOHINT if (_hintingMode == HINTING_MODE_BYTECODE_INTERPRETOR) { rend_flags |= FT_LOAD_NO_AUTOHINT; - } else if (_hintingMode == HINTING_MODE_AUTOHINT) { + } + else if (_hintingMode == HINTING_MODE_AUTOHINT) { rend_flags |= FT_LOAD_FORCE_AUTOHINT; - } else if (_hintingMode == HINTING_MODE_DISABLED) { + } + else if (_hintingMode == HINTING_MODE_DISABLED) { + rend_flags |= FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING; + } + if (_embolden) { // Don't render yet + rend_flags &= ~FT_LOAD_RENDER; + // Also disable any hinting, as it would be wrong after embolden rend_flags |= FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING; } - /* load glyph image into the slot (erase previous one) */ - updateTransform(); - int error = FT_Load_Glyph( _face, /* handle to face object */ - ch_glyph_index, /* glyph index */ + /* load glyph image into the slot (erase previous one) */ + updateTransform(); // no-op + int error = FT_Load_Glyph( _face, /* handle to face object */ + ch_glyph_index, /* glyph index */ rend_flags ); /* load flags, see below */ if ( error ) { return NULL; /* ignore errors */ } + + if (_embolden) { // Embolden and render + // See setEmbolden() for details + FT_GlyphSlot_Embolden(_slot); + FT_Render_Glyph(_slot, _drawMonochrome?FT_RENDER_MODE_MONO:FT_RENDER_MODE_NORMAL); + } + item = newItem( &_glyph_cache, ch, _slot ); //, _drawMonochrome _glyph_cache.put( item ); } @@ -1831,35 +2242,48 @@ class LVFreeTypeFace : public LVFont LVFontGlyphIndexCacheItem * getGlyphByIndex(lUInt32 index) { //FONT_GUARD LVFontGlyphIndexCacheItem * item = 0; - //std::map::const_iterator it; - //it = _glyph_cache2.find(index); - //if (it == _glyph_cache2.end()) { if (!_glyph_cache2.get(index, item)) { // glyph not found in cache, rendering... - int rend_flags = FT_LOAD_RENDER | ( !_drawMonochrome ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO ); //|FT_LOAD_MONOCHROME|FT_LOAD_FORCE_AUTOHINT + int rend_flags = FT_LOAD_RENDER | ( !_drawMonochrome ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO ); + //|FT_LOAD_MONOCHROME|FT_LOAD_FORCE_AUTOHINT if (_hintingMode == HINTING_MODE_BYTECODE_INTERPRETOR) { rend_flags |= FT_LOAD_NO_AUTOHINT; - } else if (_hintingMode == HINTING_MODE_AUTOHINT) { + } + else if (_hintingMode == HINTING_MODE_AUTOHINT) { rend_flags |= FT_LOAD_FORCE_AUTOHINT; - } else if (_hintingMode == HINTING_MODE_DISABLED) { + } + else if (_hintingMode == HINTING_MODE_DISABLED) { rend_flags |= FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING; } - /* load glyph image into the slot (erase previous one) */ - updateTransform(); - int error = FT_Load_Glyph( _face, /* handle to face object */ - index, /* glyph index */ + if (_embolden) { // Don't render yet + rend_flags &= ~FT_LOAD_RENDER; + // Also disable any hinting, as it would be wrong after embolden + rend_flags |= FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING; + } + + /* load glyph image into the slot (erase previous one) */ + updateTransform(); // no-op + int error = FT_Load_Glyph( _face, /* handle to face object */ + index, /* glyph index */ rend_flags ); /* load flags, see below */ if ( error ) { return NULL; /* ignore errors */ } + + if (_embolden) { // Embolden and render + // See setEmbolden() for details + if ( _slot->format == FT_GLYPH_FORMAT_OUTLINE ) { + FT_Outline_Embolden(&_slot->outline, 2*_embolden_half_strength); + FT_Outline_Translate(&_slot->outline, -_embolden_half_strength, -_embolden_half_strength); + } + FT_Render_Glyph(_slot, _drawMonochrome?FT_RENDER_MODE_MONO:FT_RENDER_MODE_NORMAL); + } + item = newItem(index, _slot); if (item) - //_glyph_cache2.insert(std::pair(index, item)); _glyph_cache2.set(index, item); } - //else - // item = it->second; return item; } #endif @@ -1903,7 +2327,8 @@ class LVFreeTypeFace : public LVFont glyph_info_t glyph; if ( getGlyphInfo( ch, &glyph, def_char ) ) { w = glyph.width; - } else { + } + else { w = 0; } _wcache.put(ch, w); @@ -1921,7 +2346,8 @@ class LVFreeTypeFace : public LVFont glyph_info_t glyph; if ( getGlyphInfo( ch, &glyph, '?' ) ) { b = glyph.originX; - } else { + } + else { b = 0; } _lsbcache.put(ch, b); @@ -1941,7 +2367,8 @@ class LVFreeTypeFace : public LVFont glyph_info_t glyph; if ( getGlyphInfo( ch, &glyph, '?' ) ) { b = glyph.rsb; - } else { + } + else { b = 0; } _rsbcache.put(ch, b); @@ -1970,20 +2397,20 @@ class LVFreeTypeFace : public LVFont } virtual bool kerningEnabled() { -#if (ALLOW_KERNING==1) - #if USE_HARFBUZZ==1 - return _kerningMode == KERNING_MODE_HARFBUZZ - || (_kerningMode == KERNING_MODE_FREETYPE && FT_HAS_KERNING( _face )); - #else - return _kerningMode != KERNING_MODE_DISABLED && FT_HAS_KERNING( _face ); - #endif -#else - return false; -#endif + #if (ALLOW_KERNING==1) + #if USE_HARFBUZZ==1 + return _kerningMode == KERNING_MODE_HARFBUZZ + || (_kerningMode == KERNING_MODE_FREETYPE && FT_HAS_KERNING( _face )); + #else + return _kerningMode != KERNING_MODE_DISABLED && FT_HAS_KERNING( _face ); + #endif + #else + return false; + #endif } - /// draws text string - virtual void DrawTextString( LVDrawBuf * buf, int x, int y, + /// draws text string (returns x advance) + virtual int DrawTextString( LVDrawBuf * buf, int x, int y, const lChar16 * text, int len, lChar16 def_char, lUInt32 * palette, bool addHyphen, lUInt32 flags, int letter_spacing, int width, @@ -1991,19 +2418,18 @@ class LVFreeTypeFace : public LVFont { FONT_GUARD if ( len <= 0 || _face==NULL ) - return; - if ( letter_spacing < 0 ) - { + return 0; + if ( letter_spacing < 0 ) { letter_spacing = 0; - } else if ( letter_spacing > MAX_LETTER_SPACING ) - { + } + else if ( letter_spacing > MAX_LETTER_SPACING ) { letter_spacing = MAX_LETTER_SPACING; } lvRect clip; buf->GetClipRect( &clip ); - updateTransform(); + updateTransform(); // no-op if ( y + _height < clip.top || y >= clip.bottom ) - return; + return 0; unsigned int i; //lUInt16 prev_width = 0; @@ -2011,76 +2437,257 @@ class LVFreeTypeFace : public LVFont // measure character widths bool isHyphen = false; int x0 = x; -#if USE_HARFBUZZ==1 + + #if USE_HARFBUZZ==1 if (_kerningMode == KERNING_MODE_HARFBUZZ) { + // See measureText() for more comments on how to work with Harfbuzz, + // as we do and must work the same way here. + unsigned int glyph_count; hb_glyph_info_t *glyph_info = 0; hb_glyph_position_t *glyph_pos = 0; - unsigned int glyph_count; - int w; - unsigned int len_new = 0; hb_buffer_clear_contents(_hb_buffer); - hb_buffer_set_replacement_codepoint(_hb_buffer, 0); - // fill HarfBuzz buffer with filtering - for (i = 0; i < (unsigned int)len; i++) { - ch = text[i]; - // don't draw any soft hyphens inside text string - bool isHyphen = (ch == UNICODE_SOFT_HYPHEN_CODE); - if (!isHyphen) { // skip any soft-hyphen - // Also replace any chars to similar if glyph not found - hb_buffer_add(_hb_buffer, (hb_codepoint_t)filterChar(ch), i); - len_new++; + // Fill HarfBuzz buffer + if ( getFallbackFont() ) { // 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); + } + } + else { // No fallback font, check codepoint presence or get replacement char + for (i = 0; i < len; i++) { + hb_buffer_add(_hb_buffer, (hb_codepoint_t)filterChar(text[i], def_char), i); } } hb_buffer_set_content_type(_hb_buffer, HB_BUFFER_CONTENT_TYPE_UNICODE); + + // If we are provided with direction and hints, let harfbuzz know + if ( flags ) { + if ( flags & LFNT_HINT_DIRECTION_KNOWN ) { + // Trust direction decided by fribidi: if we made a word containing just '(', + // harfbuzz wouldn't be able to determine its direction and would render + // it LTR - while it could be in some RTL text and needs to be mirrored. + if ( flags & LFNT_HINT_DIRECTION_IS_RTL ) + hb_buffer_set_direction(_hb_buffer, HB_DIRECTION_RTL); + else + hb_buffer_set_direction(_hb_buffer, HB_DIRECTION_LTR); + } + int hb_flags = HB_BUFFER_FLAG_DEFAULT; // (hb_buffer_flags_t won't let us do |= ) + if ( flags & LFNT_HINT_BEGINS_PARAGRAPH ) + hb_flags |= HB_BUFFER_FLAG_BOT; + if ( flags & LFNT_HINT_ENDS_PARAGRAPH ) + hb_flags |= HB_BUFFER_FLAG_EOT; + hb_buffer_set_flags(_hb_buffer, (hb_buffer_flags_t)hb_flags); + } + // Let HB guess what's not been set (script, direction, language) hb_buffer_guess_segment_properties(_hb_buffer); - // shape - hb_shape(_hb_font, _hb_buffer, _hb_features, 2); + + // See measureText() for details + if ( letter_spacing > 0 ) { + // Don't apply letter-spacing if the script is cursive + hb_script_t script = hb_buffer_get_script(_hb_buffer); + if ( isScriptCursive(script) ) + letter_spacing = 0; + } + + // Shape + hb_shape(_hb_font, _hb_buffer, _hb_features, HARFBUZZ_FULL_FEATURES_NB); + + // If direction is RTL, hb_shape() has reversed the order of the glyphs, so + // they are in visual order and ready to be iterated and drawn. So, + // we do not revert them, unlike in measureText(). + bool is_rtl = hb_buffer_get_direction(_hb_buffer) == HB_DIRECTION_RTL; + glyph_count = hb_buffer_get_length(_hb_buffer); glyph_info = hb_buffer_get_glyph_infos(_hb_buffer, 0); glyph_pos = hb_buffer_get_glyph_positions(_hb_buffer, 0); -#ifdef _DEBUG - if (glyph_count != len_new) { - CRLog::debug( - "DrawTextString(): glyph_count not equal source text length, glyph_count=%d, len=%d", - glyph_count, len_new); - } -#endif - for (i = 0; i < glyph_count; i++) { - if (0 == glyph_info[i].codepoint) { - // If HarfBuzz can't find glyph in current font - // using fallback font that used in getGlyph() - ch = text[glyph_info[i].cluster]; - LVFontGlyphCacheItem *item = getGlyph(ch, def_char); - if (item) { - w = item->advance; - buf->Draw(x + item->origin_x, - y + _baseline - item->origin_y, - item->bmp, - item->bmp_width, - item->bmp_height, - palette); - x += w + letter_spacing; + + #ifdef DEBUG_DRAW_TEXT + printf("DTHB >>> drawTextString %x len %d is_rtl=%d [%s]\n", text, len, is_rtl, _faceName.c_str()); + for (i = 0; i < (int)glyph_count; i++) { + char glyphname[32]; + hb_font_get_glyph_name(_hb_font, glyph_info[i].codepoint, glyphname, sizeof(glyphname)); + printf("DTHB g%d c%d(=t:%x) [%x %s]\tadvance=(%d,%d)", i, glyph_info[i].cluster, + text[glyph_info[i].cluster], glyph_info[i].codepoint, glyphname, + glyph_pos[i].x_advance>>6, glyph_pos[i].y_advance>>6); + if (glyph_pos[i].x_offset || glyph_pos[i].y_offset) + printf("\toffset=(%d,%d)", glyph_pos[i].x_offset>>6, glyph_pos[i].y_offset>>6); + printf("\n"); + } + printf("DTHB ---\n"); + #endif + + // We want to do just like in measureText(): drawing found glyphs with + // this font, and .notdef glyphs with the fallback font, as a single segment, + // once a defined glyph is found, before drawing that defined glyph. + // The code is different from in measureText(), as the glyphs might be + // 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 + // (cluster numbers are boundaries in text indices, but it's quite tricky + // to get right). + int fb_t_start = 0; + int fb_t_end = len; + int hg = 0; // index in glyph_info/glyph_pos + while (hg < glyph_count) { // hg is the start of a new cluster at this point + bool draw_with_fallback = false; + int hcl = glyph_info[hg].cluster; + fb_t_start = hcl; // if fb drawing needed from this glyph: t[hcl:..] + // /\ Logical if !is_rtl, but also needed if is_rtl and immediately + // followed by a found glyph (so, a single glyph to draw with the + // fallback font): = hclbad + #ifdef DEBUG_DRAW_TEXT + printf("DTHB g%d c%d: ", hg, hcl); + #endif + int hg2 = hg; + while ( hg2 < glyph_count ) { + int hcl2 = glyph_info[hg2].cluster; + if ( hcl2 != hcl ) { // New cluster starts at hg2: we can draw hg > hg2-1 + #ifdef DEBUG_DRAW_TEXT + printf("all found, "); + #endif + if (is_rtl) + fb_t_end = hcl; // if fb drawing needed from next glyph: t[..:hcl] + break; + } + if ( glyph_info[hg2].codepoint != 0 || !has_fallback_font ) { + // Glyph found in this font, or not but we have no + // fallback font: we will draw the .notdef/tofu chars. + hg2++; + continue; } + #ifdef DEBUG_DRAW_TEXT + printf("g%d c%d notdef, ", hg2, hcl2); + #endif + // Glyph notdef but we have a fallback font + // Go look ahead for a complete cluster, or segment of notdef, + // so we can draw it all with the fallback using harfbuzz + draw_with_fallback = true; + // We will update hg2 and hcl2 to be the last glyph of + // a cluster/segment with notdef + int hclbad = hcl2; + int hclgood = -1; + int hg3 = hg2+1; + while ( hg3 < glyph_count ) { + int hcl3 = glyph_info[hg3].cluster; + if ( hclgood >=0 && hcl3 != hclgood ) { + // Found a complete cluster + // We can draw hg > hg2-1 with fallback font + #ifdef DEBUG_DRAW_TEXT + printf("c%d complete, need redraw up to g%d", hclgood, hg2); + #endif + if (!is_rtl) + fb_t_end = hclgood; // fb drawing t[..:hclgood] + hg2 += 1; // set hg2 to the first ok glyph + break; + } + if ( glyph_info[hg3].codepoint == 0 || hcl3 == hclbad) { + #ifdef DEBUG_DRAW_TEXT + printf("g%d c%d -, ", hg3, hcl3); + #endif + // notdef, or def but part of uncomplete previous cluster + hcl2 = hcl3; + hg2 = hg3; // move hg2 to this bad glyph + hclgood = -1; // un'good found good cluster + hclbad = hcl3; + if (is_rtl) + fb_t_start = hclbad; // fb drawing t[hclbad::..] + hg3++; + continue; + } + // Codepoint found, and we're not part of an uncomplete cluster + #ifdef DEBUG_DRAW_TEXT + printf("g%d c%d +, ", hg3, hcl3); + #endif + hclgood = hcl3; + hg3++; + } + if ( hg3 == glyph_count ) { // no good cluster met till end of text + hg2 = glyph_count; // get out of hg2 loop + if (is_rtl) + fb_t_start = 0; + else + fb_t_end = len; + } + break; + } + // Draw glyphs from hg to hg2 excluded + if (draw_with_fallback) { + #ifdef DEBUG_DRAW_TEXT + printf("[...]\nDTHB ### drawing past notdef with fallback font %d>%d ", hg, hg2); + printf(" => %d > %d\n", fb_t_start, fb_t_end); + #endif + // Adjust DrawTextString() params for fallback drawing + lUInt32 fb_flags = flags; + 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 + // if the char at start or end is a space that could be drawn + // with the main font). + if (fb_t_start > 0) + fb_flags &= ~LFNT_HINT_BEGINS_PARAGRAPH; + if (fb_t_end < len) + fb_flags &= ~LFNT_HINT_ENDS_PARAGRAPH; + // Adjust fallback y so baselines of both fonts match + int fb_y = y + _baseline - fallback->getBaseline(); + bool fb_addHyphen = false; // will be added by main font + const lChar16 * fb_text = text + fb_t_start; + int fb_len = fb_t_end - fb_t_start; + // (width and text_decoration_back_gap are only used for + // text decoration, that we dropped: no update needed) + int fb_advance = fallback->DrawTextString( buf, x, fb_y, + fb_text, fb_len, + def_char, palette, fb_addHyphen, fb_flags, letter_spacing, + width, text_decoration_back_gap ); + x += fb_advance; + #ifdef DEBUG_DRAW_TEXT + printf("DTHB ### drawn past notdef > X+= %d\n[...]", fb_advance); + #endif } else { - LVFontGlyphIndexCacheItem *item = getGlyphByIndex(glyph_info[i].codepoint); - if (item) { - w = glyph_pos[i].x_advance >> 6; - buf->Draw(x + item->origin_x + (glyph_pos[i].x_offset >> 6), - y + _baseline - item->origin_y + (glyph_pos[i].y_offset >> 6), - item->bmp, - item->bmp_width, - item->bmp_height, - palette); - x += w + letter_spacing; - // Wondered if item->origin_x and glyph_pos[i].x_offset must really - // be added (harfbuzz' x_offset correcting Freetype's origin_x), - // or are the same thing (harfbuzz' x_offset=0 replacing and - // cancelling FreeType's origin_x) ? - // Comparing screenshots seems to indicate they must be added. + #ifdef DEBUG_DRAW_TEXT + printf("regular g%d>%d cp%x: ", hg, hg2); + #endif + // Draw glyphs of this same cluster + for (i = hg; i < hg2; i++) { + LVFontGlyphIndexCacheItem *item = getGlyphByIndex(glyph_info[i].codepoint); + if (item) { + int w = glyph_pos[i].x_advance >> 6; + #ifdef DEBUG_DRAW_TEXT + printf("%x(x=%d+%d,w=%d) ", glyph_info[i].codepoint, x, + item->origin_x + (glyph_pos[i].x_offset >> 6), w); + #endif + buf->Draw(x + item->origin_x + (glyph_pos[i].x_offset >> 6), + y + _baseline - item->origin_y + (glyph_pos[i].y_offset >> 6), + item->bmp, + item->bmp_width, + item->bmp_height, + palette); + x += w; + } + #ifdef DEBUG_DRAW_TEXT + else + printf("SKIPPED %x", glyph_info[i].codepoint); + #endif } + // Whole cluster drawn: add letter spacing + x += letter_spacing; } + hg = hg2; + #ifdef DEBUG_DRAW_TEXT + printf("\n"); + #endif } + + // Wondered if item->origin_x and glyph_pos[hg].x_offset must really + // be added (harfbuzz' x_offset correcting Freetype's origin_x), + // or are the same thing (harfbuzz' x_offset=0 replacing and + // cancelling FreeType's origin_x) ? + // Comparing screenshots seems to indicate they must be added. + if (addHyphen) { ch = UNICODE_SOFT_HYPHEN_CODE; LVFontGlyphCacheItem *item = getGlyph(ch, def_char); @@ -2092,7 +2699,7 @@ class LVFreeTypeFace : public LVFont item->bmp_width, item->bmp_height, palette); - x += w + letter_spacing; + x += w; // + letter_spacing; (let's not add any letter-spacing after hyphen) } } @@ -2106,20 +2713,22 @@ class LVFreeTypeFace : public LVFont struct LVCharTriplet triplet; struct LVCharPosInfo posInfo; triplet.Char = 0; + bool is_rtl = (flags & LFNT_HINT_DIRECTION_KNOWN) && (flags & LFNT_HINT_DIRECTION_IS_RTL); for ( i=0; i<=(unsigned int)len; i++) { if ( i==len && !addHyphen ) break; if ( i w=8 o_x=0 + // drawing 308 adv=0 kerning=0 => w=0 o_x=-7 (origin_x going back) + // Drawing some hebrew char with 2 diacritics before: + // drawing 5bc adv=0 kerning=0 => w=0 o_x=4 (diacritics don't advance) + // drawing 5b6 adv=0 kerning=0 => w=0 o_x=3 + // drawing 5e1 adv=11 kerning=0 => w=11 o_x=0 (main char will advance) + ch = is_rtl ? text[len-1-i] : text[i]; if ( ch=='\t' ) ch = ' '; // don't draw any soft hyphens inside text string isHyphen = (ch==UNICODE_SOFT_HYPHEN_CODE); - } else { + } + else { ch = UNICODE_SOFT_HYPHEN_CODE; isHyphen = false; // an hyphen, but not one to not draw } FT_UInt ch_glyph_index = getCharIndex( ch, def_char ); int kerning = 0; -#if (ALLOW_KERNING==1) + #if (ALLOW_KERNING==1) if ( use_kerning && previous>0 && ch_glyph_index>0 ) { FT_Vector delta; error = FT_Get_Kerning( _face, /* handle to face object */ @@ -2183,7 +2806,7 @@ class LVFreeTypeFace : public LVFont if ( !error ) kerning = delta.x; } -#endif + #endif LVFontGlyphCacheItem * item = getGlyph(ch, def_char); if ( !item ) continue; @@ -2200,10 +2823,13 @@ class LVFreeTypeFace : public LVFont previous = ch_glyph_index; } } -#if USE_HARFBUZZ==1 - } // else fallback to the non harfbuzz code -#endif - if ( flags & LTEXT_TD_MASK ) { + + #if USE_HARFBUZZ==1 + } // else fallback to the non harfbuzz code + #endif + + int advance = x - x0; + if ( flags & LFNT_DRAW_DECORATION_MASK ) { // text decoration: underline, etc. // Don't overflow the provided width (which may be lower than our // pen x if last glyph was a space not accounted in word width) @@ -2214,20 +2840,21 @@ class LVFreeTypeFace : public LVFont x0 -= text_decoration_back_gap; int h = _size > 30 ? 2 : 1; lUInt32 cl = buf->GetTextColor(); - if ( (flags & LTEXT_TD_UNDERLINE) || (flags & LTEXT_TD_BLINK) ) { + if ( (flags & LFNT_DRAW_UNDERLINE) || (flags & LFNT_DRAW_BLINK) ) { int liney = y + _baseline + h; buf->FillRect( x0, liney, x, liney+h, cl ); } - if ( flags & LTEXT_TD_OVERLINE ) { + if ( flags & LFNT_DRAW_OVERLINE ) { int liney = y + h; buf->FillRect( x0, liney, x, liney+h, cl ); } - if ( flags & LTEXT_TD_LINE_THROUGH ) { -// int liney = y + _baseline - _size/4 - h/2; + if ( flags & LFNT_DRAW_LINE_THROUGH ) { + // int liney = y + _baseline - _size/4 - h/2; int liney = y + _baseline - _size*2/7; buf->FillRect( x0, liney, x, liney+h, cl ); } } + return advance; } /// returns true if font is empty @@ -2245,12 +2872,12 @@ class LVFreeTypeFace : public LVFont { LVLock lock(_mutex); clearCache(); -#if USE_HARFBUZZ==1 + #if USE_HARFBUZZ==1 if (_hb_font) { hb_font_destroy(_hb_font); _hb_font = 0; } -#endif + #endif if ( _face ) { FT_Done_Face(_face); _face = NULL; @@ -2337,7 +2964,8 @@ class LVFontBoldTransform : public LVFont int max_width, lChar16 def_char, int letter_spacing=0, - bool allow_hyphenation=true + bool allow_hyphenation=true, + lUInt32 hints=0 ) { CR_UNUSED(allow_hyphenation); @@ -2347,7 +2975,9 @@ class LVFontBoldTransform : public LVFont flags, max_width, def_char, - letter_spacing + letter_spacing, + allow_hyphenation, + hints ); int w = 0; for ( int i=0; iMAX_LINE_CHARS ) @@ -2543,26 +3170,25 @@ class LVFontBoldTransform : public LVFont return _baseFont->getFontFamily(); } - /// draws text string - virtual void DrawTextString( LVDrawBuf * buf, int x, int y, + /// draws text string (returns x advance) + virtual int DrawTextString( LVDrawBuf * buf, int x, int y, const lChar16 * text, int len, lChar16 def_char, lUInt32 * palette, bool addHyphen, lUInt32 flags, int letter_spacing, int width, int text_decoration_back_gap ) { if ( len <= 0 ) - return; - if ( letter_spacing < 0 ) - { + return 0; + if ( letter_spacing < 0 ) { letter_spacing = 0; - } else if ( letter_spacing > MAX_LETTER_SPACING ) - { + } + else if ( letter_spacing > MAX_LETTER_SPACING ) { letter_spacing = MAX_LETTER_SPACING; } lvRect clip; buf->GetClipRect( &clip ); if ( y + _height < clip.top || y >= clip.bottom ) - return; + return 0; //int error; @@ -2573,13 +3199,15 @@ class LVFontBoldTransform : public LVFont // measure character widths bool isHyphen = false; int x0 = x; + bool is_rtl = (flags & LFNT_HINT_DIRECTION_KNOWN) && (flags & LFNT_HINT_DIRECTION_IS_RTL); for ( i=0; i<=len; i++) { if ( i==len && !addHyphen ) break; if ( i 30 ? 2 : 1; lUInt32 cl = buf->GetTextColor(); - if ( (flags & LTEXT_TD_UNDERLINE) || (flags & LTEXT_TD_BLINK) ) { + if ( (flags & LFNT_DRAW_UNDERLINE) || (flags & LFNT_DRAW_BLINK) ) { int liney = y + _baseline + h; buf->FillRect( x0, liney, x, liney+h, cl ); } - if ( flags & LTEXT_TD_OVERLINE ) { + if ( flags & LFNT_DRAW_OVERLINE ) { int liney = y + h; buf->FillRect( x0, liney, x, liney+h, cl ); } - if ( flags & LTEXT_TD_LINE_THROUGH ) { + if ( flags & LFNT_DRAW_LINE_THROUGH ) { int liney = y + _height/2 - h/2; buf->FillRect( x0, liney, x, liney+h, cl ); } } + return advance; } /// get bitmap mode (true=monochrome bitmap, false=antialiased) @@ -2760,6 +3390,25 @@ class LVFreeTypeFontManager : public LVFontManager return GetFont(size, 400, false, css_ff_sans_serif, _fallbackFontFace, -1); } + /// returns fallback font for specified size, weight and italic + virtual LVFontRef GetFallbackFont(int size, int weight=400, bool italic=false) { + FONT_MAN_GUARD + if ( _fallbackFontFace.empty() ) + return LVFontRef(); + // reduce number of possible distinct sizes for fallback font + if ( size>40 ) + size &= 0xFFF8; + else if ( size>28 ) + size &= 0xFFFC; + else if ( size>16 ) + size &= 0xFFFE; + // 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, -1); + } + bool isBitmapModeForSize( int size ) { bool bitmap = false; @@ -2823,19 +3472,20 @@ class LVFreeTypeFontManager : public LVFontManager fonts->get(i)->getFont()->setKerningMode( mode ); } } + /// clear glyph cache virtual void clearGlyphCache() { FONT_MAN_GUARD _globalCache.clear(); -#if USE_HARFBUZZ==1 + #if USE_HARFBUZZ==1 // needs to clear each font _glyph_cache2 (for Gamma change, which // does not call any individual font method) LVPtrVector< LVFontCacheItem > * fonts = _cache.getInstances(); for ( int i=0; ilength(); i++ ) { fonts->get(i)->getFont()->clearCache(); } -#endif + #endif } virtual int GetFontCount() @@ -2897,7 +3547,8 @@ class LVFreeTypeFontManager : public LVFontManager lString8 fn( (const char *)s ); lString16 fn16( fn.c_str() ); fn16.lowercase(); - if (!fn16.endsWith(".ttf") && !fn16.endsWith(".odf") && !fn16.endsWith(".otf") && !fn16.endsWith(".pfb") && !fn16.endsWith(".pfa") ) { + if (!fn16.endsWith(".ttf") && !fn16.endsWith(".odf") && !fn16.endsWith(".otf") && + !fn16.endsWith(".pfb") && !fn16.endsWith(".pfa") ) { continue; } int weight = FC_WEIGHT_MEDIUM; @@ -2938,12 +3589,12 @@ class LVFreeTypeFontManager : public LVFontManager //case FC_WEIGHT_HEAVY: FC_WEIGHT_BLACK weight = 900; break; -#ifdef FC_WEIGHT_EXTRABLACK + #ifdef FC_WEIGHT_EXTRABLACK case FC_WEIGHT_EXTRABLACK: // 215 //case FC_WEIGHT_ULTRABLACK: FC_WEIGHT_EXTRABLACK weight = 900; break; -#endif + #endif default: weight = 400; break; @@ -2979,15 +3630,15 @@ class LVFreeTypeFontManager : public LVFontManager //CRLog::debug("no FC_SPACING for %s", s); //continue; } -// int cr_weight; -// switch(weight) { -// case FC_WEIGHT_LIGHT: cr_weight = 200; break; -// case FC_WEIGHT_MEDIUM: cr_weight = 300; break; -// case FC_WEIGHT_DEMIBOLD: cr_weight = 500; break; -// case FC_WEIGHT_BOLD: cr_weight = 700; break; -// case FC_WEIGHT_BLACK: cr_weight = 800; break; -// default: cr_weight=300; break; -// } + // int cr_weight; + // switch(weight) { + // case FC_WEIGHT_LIGHT: cr_weight = 200; break; + // case FC_WEIGHT_MEDIUM: cr_weight = 300; break; + // case FC_WEIGHT_DEMIBOLD: cr_weight = 500; break; + // case FC_WEIGHT_BOLD: cr_weight = 700; break; + // case FC_WEIGHT_BLACK: cr_weight = 800; break; + // default: cr_weight=300; break; + // } css_font_family_t fontFamily = css_ff_sans_serif; lString16 face16((const char *)family); face16.lowercase(); @@ -3024,7 +3675,8 @@ class LVFreeTypeFontManager : public LVFontManager index ); - CRLog::debug("FONTCONFIG: Font family:%s style:%s weight:%d slant:%d spacing:%d file:%s", family, style, weight, slant, spacing, s); + CRLog::debug("FONTCONFIG: Font family:%s style:%s weight:%d slant:%d spacing:%d file:%s", + family, style, weight, slant, spacing, s); if ( _cache.findDuplicate( &def ) ) { CRLog::debug("is duplicate, skipping"); continue; @@ -3039,8 +3691,6 @@ class LVFreeTypeFontManager : public LVFontManager } facesFound++; - - } FcFontSetDestroy(fontset); @@ -3057,13 +3707,14 @@ class LVFreeTypeFontManager : public LVFontManager if ( SetFallbackFontFace(lString8(fallback_faces[i])) ) { CRLog::info("Fallback font %s is found", fallback_faces[i]); break; - } else { + } + else { CRLog::trace("Fallback font %s is not found", fallback_faces[i]); } return facesFound > 0; } - #else + #else // !USE_FONTCONFIG return false; #endif } @@ -3075,11 +3726,11 @@ class LVFreeTypeFontManager : public LVFontManager _cache.clear(); if ( _library ) FT_Done_FreeType( _library ); - #if (DEBUG_FONT_MAN==1) - if ( _log ) { - fclose(_log); - } - #endif + #if (DEBUG_FONT_MAN==1) + if ( _log ) { + fclose(_log); + } + #endif } LVFreeTypeFontManager() @@ -3089,15 +3740,19 @@ class LVFreeTypeFontManager : public LVFontManager int error = FT_Init_FreeType( &_library ); if ( error ) { // error - CRLog::error("Error while initializing freetype library"); + CRLog::error("Error while initializing freetype library"); } - #if (DEBUG_FONT_MAN==1) - _log = fopen(DEBUG_FONT_MAN_LOG_FILE, "at"); - if ( _log ) { - fprintf(_log, "=========================== LOGGING STARTED ===================\n"); - } - #endif - _requiredChars = L"azAZ09";//\x0410\x042F\x0430\x044F"; + #if (DEBUG_FONT_MAN==1) + _log = fopen(DEBUG_FONT_MAN_LOG_FILE, "at"); + if ( _log ) { + fprintf(_log, "=========================== LOGGING STARTED ===================\n"); + } + #endif + // _requiredChars = L"azAZ09";//\x0410\x042F\x0430\x044F"; + // Some fonts come without any of these (ie. NotoSansMyanmar.ttf), there's + // no reason to prevent them from being used. + // So, check only for the presence of the space char, hoping it's there in any font. + _requiredChars = L" "; } virtual void gc() // garbage collector @@ -3129,11 +3784,11 @@ class LVFreeTypeFontManager : public LVFontManager _cache.getFontFileNameList(list); } - bool SetAlias(lString8 alias,lString8 facename,int id,bool bold,bool italic) -{ - FONT_MAN_GUARD - lString8 fontname=lString8("\0"); - LVFontDef def( + bool SetAlias(lString8 alias,lString8 facename,int id,bool bold,bool italic) + { + FONT_MAN_GUARD + lString8 fontname=lString8("\0"); + LVFontDef def( fontname, -1, bold?700:400, @@ -3142,9 +3797,9 @@ class LVFreeTypeFontManager : public LVFontManager facename, -1, id - ); + ); LVFontCacheItem * item = _cache.find( &def); - LVFontDef def1( + LVFontDef def1( fontname, -1, bold?700:400, @@ -3153,89 +3808,89 @@ class LVFreeTypeFontManager : public LVFontManager alias, -1, id - ); + ); - int index = 0; + int index = 0; - FT_Face face = NULL; + FT_Face face = NULL; - // for all faces in file - for ( ;; index++ ) { - int error = FT_New_Face( _library, item->getDef()->getName().c_str(), index, &face ); /* create face object */ - if ( error ) { - if (index == 0) { - CRLog::error("FT_New_Face returned error %d", error); - } - break; + // for all faces in file + for ( ;; index++ ) { + int error = FT_New_Face( _library, item->getDef()->getName().c_str(), index, &face ); /* create face object */ + if ( error ) { + if (index == 0) { + CRLog::error("FT_New_Face returned error %d", error); } - int num_faces = face->num_faces; + break; + } + int num_faces = face->num_faces; - css_font_family_t fontFamily = css_ff_sans_serif; - if ( face->face_flags & FT_FACE_FLAG_FIXED_WIDTH ) - fontFamily = css_ff_monospace; - lString8 familyName(!facename.empty() ? facename : ::familyName(face)); - // We don't need this here and in other places below: all fonts (except - // monospaces) will be marked as sans-serif, and elements with - // style {font-family:serif;} will use the default font too. - // (we don't ship any Times, and someone having unluckily such - // a font among his would see it used for {font-family:serif;} - // elements instead of his default font) - /* - if ( familyName=="Times" || familyName=="Times New Roman" ) - fontFamily = css_ff_serif; - */ + css_font_family_t fontFamily = css_ff_sans_serif; + if ( face->face_flags & FT_FACE_FLAG_FIXED_WIDTH ) + fontFamily = css_ff_monospace; + lString8 familyName(!facename.empty() ? facename : ::familyName(face)); + // We don't need this here and in other places below: all fonts (except + // monospaces) will be marked as sans-serif, and elements with + // style {font-family:serif;} will use the default font too. + // (we don't ship any Times, and someone having unluckily such + // a font among his would see it used for {font-family:serif;} + // elements instead of his default font) + /* + if ( familyName=="Times" || familyName=="Times New Roman" ) + fontFamily = css_ff_serif; + */ - bool boldFlag = !facename.empty() ? bold : (face->style_flags & FT_STYLE_FLAG_BOLD) != 0; - bool italicFlag = !facename.empty() ? italic : (face->style_flags & FT_STYLE_FLAG_ITALIC) != 0; - - LVFontDef def2( - item->getDef()->getName(), - -1, // height==-1 for scalable fonts - boldFlag ? 700 : 400, - italicFlag, - fontFamily, - alias, - index, - id - ); + bool boldFlag = !facename.empty() ? bold : (face->style_flags & FT_STYLE_FLAG_BOLD) != 0; + bool italicFlag = !facename.empty() ? italic : (face->style_flags & FT_STYLE_FLAG_ITALIC) != 0; - if ( face ) { - FT_Done_Face( face ); - face = NULL; - } + LVFontDef def2( + item->getDef()->getName(), + -1, // height==-1 for scalable fonts + boldFlag ? 700 : 400, + italicFlag, + fontFamily, + alias, + index, + id + ); - if ( _cache.findDuplicate( &def2 ) ) { - CRLog::trace("font definition is duplicate"); - return false; - } - _cache.update( &def2, LVFontRef(NULL) ); - if (!def.getItalic()) { - LVFontDef newDef( def2 ); - newDef.setItalic(2); // can italicize - if ( !_cache.findDuplicate( &newDef ) ) - _cache.update( &newDef, LVFontRef(NULL) ); - } - if ( index>=num_faces-1 ) - break; + if ( face ) { + FT_Done_Face( face ); + face = NULL; + } + + if ( _cache.findDuplicate( &def2 ) ) { + CRLog::trace("font definition is duplicate"); + return false; + } + _cache.update( &def2, LVFontRef(NULL) ); + if (!def.getItalic()) { + LVFontDef newDef( def2 ); + newDef.setItalic(2); // can italicize + if ( !_cache.findDuplicate( &newDef ) ) + _cache.update( &newDef, LVFontRef(NULL) ); } + if ( index>=num_faces-1 ) + break; + } item = _cache.find( &def1); - if (item->getDef()->getTypeFace()==alias ){ + if (item->getDef()->getTypeFace()==alias ) { return true; } - else - { + else { return false; } -} + } + virtual LVFontRef GetFont(int size, int weight, bool italic, css_font_family_t family, lString8 typeface, int documentId, bool useBias=false) { FONT_MAN_GUARD - #if (DEBUG_FONT_MAN==1) - if ( _log ) { - fprintf(_log, "GetFont(size=%d, weight=%d, italic=%d, family=%d, typeface='%s')\n", - size, weight, italic?1:0, (int)family, typeface.c_str() ); - } - #endif + #if (DEBUG_FONT_MAN==1) + if ( _log ) { + fprintf(_log, "GetFont(size=%d, weight=%d, italic=%d, family=%d, typeface='%s')\n", + size, weight, italic?1:0, (int)family, typeface.c_str() ); + } + #endif lString8 fontname; LVFontDef def( fontname, @@ -3247,74 +3902,85 @@ class LVFreeTypeFontManager : public LVFontManager -1, documentId ); - #if (DEBUG_FONT_MAN==1) - if ( _log ) - fprintf( _log, "GetFont: %s %d %s %s\n", - typeface.c_str(), - size, - weight>400?"bold":"", - italic?"italic":"" ); - #endif + #if (DEBUG_FONT_MAN==1) + if ( _log ) + fprintf( _log, "GetFont: %s %d %s %s\n", + typeface.c_str(), + size, + weight>400?"bold":"", + italic?"italic":"" ); + #endif + LVFontCacheItem * item = _cache.find( &def, useBias ); - #if (DEBUG_FONT_MAN==1) - if ( item && _log ) { //_log && - fprintf(_log, " found item: (file=%s[%d], size=%d, weight=%d, italic=%d, family=%d, typeface=%s, weightDelta=%d) FontRef=%d\n", - item->getDef()->getName().c_str(), item->getDef()->getIndex(), item->getDef()->getSize(), item->getDef()->getWeight(), item->getDef()->getItalic()?1:0, - (int)item->getDef()->getFamily(), item->getDef()->getTypeFace().c_str(), - weight - item->getDef()->getWeight(), item->getFont().isNull()?0:item->getFont()->getHeight() - ); - } - #endif + #if (DEBUG_FONT_MAN==1) + if ( item && _log ) { //_log && + fprintf(_log, " found item: (file=%s[%d], size=%d, weight=%d, italic=%d, family=%d, typeface=%s, weightDelta=%d) FontRef=%d\n", + item->getDef()->getName().c_str(), item->getDef()->getIndex(), item->getDef()->getSize(), + item->getDef()->getWeight(), item->getDef()->getItalic()?1:0, + (int)item->getDef()->getFamily(), item->getDef()->getTypeFace().c_str(), + weight - item->getDef()->getWeight(), item->getFont().isNull()?0:item->getFont()->getHeight() + ); + } + #endif + bool italicize = false; LVFontDef newDef(*item->getDef()); // printf(" got %s\n", newDef.getTypeFace().c_str()); - if (!item->getFont().isNull()) - { + if (!item->getFont().isNull()) { int deltaWeight = weight - item->getDef()->getWeight(); if ( deltaWeight >= 200 ) { - // embolden - CRLog::debug("font: apply Embolding to increase weight from %d to %d", newDef.getWeight(), newDef.getWeight() + 200 ); - newDef.setWeight( newDef.getWeight() + 200 ); - LVFontRef ref = LVFontRef( new LVFontBoldTransform( item->getFont(), &_globalCache ) ); - _cache.update( &newDef, ref ); - return ref; - } else { + // This instantiated cached font has a too low weight + #ifndef USE_FT_EMBOLDEN + // embolden using LVFontBoldTransform + CRLog::debug("font: apply Embolding to increase weight from %d to %d", + newDef.getWeight(), newDef.getWeight() + 200 ); + newDef.setWeight( newDef.getWeight() + 200 ); + LVFontRef ref = LVFontRef( new LVFontBoldTransform( item->getFont(), &_globalCache ) ); + _cache.update( &newDef, ref ); + return ref; + #endif + // when USE_FT_EMBOLDEN, ignore this low-weight cached font instance + // and go loading from the font file again to apply embolden. + } + else { //fprintf(_log, " : fount existing\n"); return item->getFont(); } } + lString8 fname = item->getDef()->getName(); - #if (DEBUG_FONT_MAN==1) - if ( _log ) { - int index = item->getDef()->getIndex(); - fprintf(_log, " no instance: adding new one for filename=%s, index = %d\n", fname.c_str(), index ); - } - #endif + #if (DEBUG_FONT_MAN==1) + if ( _log ) { + int index = item->getDef()->getIndex(); + fprintf(_log, " no instance: adding new one for filename=%s, index = %d\n", fname.c_str(), index ); + } + #endif LVFreeTypeFace * font = new LVFreeTypeFace(_lock, _library, &_globalCache); lString8 pathname = makeFontFileName( fname ); + //def.setName( fname ); //def.setIndex( index ); - //if ( fname.empty() || pathname.empty() ) { // pathname = lString8("arial.ttf"); //} if ( !item->getDef()->isRealItalic() && italic ) { //CRLog::debug("font: fake italic"); - newDef.setItalic(true); + newDef.setItalic(2); italicize = true; } - //printf("going to load font file %s\n", fname.c_str()); - bool loaded = false; // Use the family of the font we found in the cache (it may be different // from the requested family). // Assigning the requested familly to this new font could be wrong, and // may cause a style or font mismatch when loading from cache, forcing a // full re-rendering). family = item->getDef()->getFamily(); + + //printf("going to load font file %s\n", fname.c_str()); + bool loaded = false; if (item->getDef()->getBuf().isNull()) loaded = font->loadFromFile( pathname.c_str(), item->getDef()->getIndex(), size, family, isBitmapModeForSize(size), italicize ); else @@ -3328,24 +3994,35 @@ class LVFreeTypeFontManager : public LVFontManager newDef.setSize( size ); //item->setFont( ref ); //_cache.update( def, ref ); - _cache.update( &newDef, ref ); int deltaWeight = weight - newDef.getWeight(); if ( 1 && deltaWeight >= 200 ) { // embolden - CRLog::debug("font: apply Embolding to increase weight from %d to %d", newDef.getWeight(), newDef.getWeight() + 200 ); - newDef.setWeight( newDef.getWeight() + 200 ); - ref = LVFontRef( new LVFontBoldTransform( ref, &_globalCache ) ); - _cache.update( &newDef, ref ); + #ifndef USE_FT_EMBOLDEN + CRLog::debug("font: apply Embolding to increase weight from %d to %d", + newDef.getWeight(), newDef.getWeight() + 200 ); + // Create a wrapper with LVFontBoldTransform which will bolden the glyphs + newDef.setWeight( newDef.getWeight() + 200 ); + ref = LVFontRef( new LVFontBoldTransform( ref, &_globalCache ) ); + #else + // Will make some of this font's methods do embolden the glyphs and widths + font->setEmbolden(); + newDef.setWeight( font->getWeight() ); + #endif + /* + printf("CRE: %s:%d [%s %d%s]: fake bold%s\n", fname.c_str(), item->getDef()->getIndex(), + font->getFaceName().c_str(), font->getSize(), + italic?" i":"", italicize?", fake italic":""); // font->getWeight()); + */ } -// int rsz = ref->getSize(); -// if ( rsz!=size ) { -// size++; -// } + _cache.update( &newDef, ref ); + // int rsz = ref->getSize(); + // if ( rsz!=size ) { + // size++; + // } //delete def; return ref; } - else - { + else { //printf(" not found!\n"); } //delete def; @@ -3421,10 +4098,9 @@ class LVFreeTypeFontManager : public LVFontManager lvsize_t bytesRead = 0; if (stream->Read(buf->get(), size, &bytesRead) != LVERR_OK || bytesRead != size) return false; - bool res = false; + bool res = false; int index = 0; - FT_Face face = NULL; // for all faces in file @@ -3436,23 +4112,22 @@ class LVFreeTypeFontManager : public LVFontManager } break; } -// bool scal = FT_IS_SCALABLE( face ); -// bool charset = checkCharSet( face ); -// //bool monospaced = isMonoSpaced( face ); -// if ( !scal || !charset ) { -// //#if (DEBUG_FONT_MAN==1) -// // if ( _log ) { -// CRLog::debug(" won't register font %s: %s", -// name.c_str(), !charset?"no mandatory characters in charset" : "font is not scalable" -// ); -// // } -// //#endif -// if ( face ) { -// FT_Done_Face( face ); -// face = NULL; -// } -// break; -// } + // bool scal = FT_IS_SCALABLE( face ); + // bool charset = checkCharSet( face ); + // //bool monospaced = isMonoSpaced( face ); + // if ( !scal || !charset ) { + // //#if (DEBUG_FONT_MAN==1) + // // if ( _log ) { + // CRLog::debug(" won't register font %s: %s", + // name.c_str(), !charset?"no mandatory characters in charset" : "font is not scalable"); + // // } + // //#endif + // if ( face ) { + // FT_Done_Face( face ); + // face = NULL; + // } + // break; + // } int num_faces = face->num_faces; css_font_family_t fontFamily = css_ff_sans_serif; @@ -3478,17 +4153,17 @@ class LVFreeTypeFontManager : public LVFontManager documentId, buf ); - #if (DEBUG_FONT_MAN==1) - if ( _log ) { - fprintf(_log, "registering font: (file=%s[%d], size=%d, weight=%d, italic=%d, family=%d, typeface=%s)\n", - def.getName().c_str(), def.getIndex(), def.getSize(), def.getWeight(), def.getItalic()?1:0, (int)def.getFamily(), def.getTypeFace().c_str() - ); - } - #endif - if ( face ) { - FT_Done_Face( face ); - face = NULL; - } + #if (DEBUG_FONT_MAN==1) + if ( _log ) { + fprintf(_log, "registering font: (file=%s[%d], size=%d, weight=%d, italic=%d, family=%d, typeface=%s)\n", + def.getName().c_str(), def.getIndex(), def.getSize(), def.getWeight(), + def.getItalic()?1:0, (int)def.getFamily(), def.getTypeFace().c_str() ); + } + #endif + if ( face ) { + FT_Done_Face( face ); + face = NULL; + } if ( _cache.findDuplicate( &def ) ) { CRLog::trace("font definition is duplicate"); @@ -3506,25 +4181,23 @@ class LVFreeTypeFontManager : public LVFontManager if ( index>=num_faces-1 ) break; } - return res; } + /// unregisters all document fonts virtual void UnregisterDocumentFonts(int documentId) { _cache.removeDocumentFonts(documentId); } virtual bool RegisterExternalFont( lString16 name, lString8 family_name, bool bold, bool italic) { - if (name.startsWithNoCase(lString16("res://"))) - name = name.substr(6); - else if (name.startsWithNoCase(lString16("file://"))) - name = name.substr(7); - lString8 fname = UnicodeToUtf8(name); + if (name.startsWithNoCase(lString16("res://"))) + name = name.substr(6); + else if (name.startsWithNoCase(lString16("file://"))) + name = name.substr(7); + lString8 fname = UnicodeToUtf8(name); bool res = false; - int index = 0; - FT_Face face = NULL; // for all faces in file @@ -3547,13 +4220,9 @@ class LVFreeTypeFontManager : public LVFontManager } //bool monospaced = isMonoSpaced( face ); if ( !scal || !charset ) { - //#if (DEBUG_FONT_MAN==1) - // if ( _log ) { CRLog::debug(" won't register font %s: %s", name.c_str(), !charset?"no mandatory characters in charset" : "font is not scalable" ); - // } - //#endif if ( face ) { FT_Done_Face( face ); face = NULL; @@ -3580,13 +4249,14 @@ class LVFreeTypeFontManager : public LVFontManager family_name, index ); - #if (DEBUG_FONT_MAN==1) - if ( _log ) { - fprintf(_log, "registering font: (file=%s[%d], size=%d, weight=%d, italic=%d, family=%d, typeface=%s)\n", - def.getName().c_str(), def.getIndex(), def.getSize(), def.getWeight(), def.getItalic()?1:0, (int)def.getFamily(), def.getTypeFace().c_str() - ); - } - #endif + #if (DEBUG_FONT_MAN==1) + if ( _log ) { + fprintf(_log, "registering font: (file=%s[%d], size=%d, weight=%d, italic=%d, family=%d, typeface=%s)\n", + def.getName().c_str(), def.getIndex(), def.getSize(), def.getWeight(), + def.getItalic()?1:0, (int)def.getFamily(), def.getTypeFace().c_str() + ); + } + #endif if ( _cache.findDuplicate( &def ) ) { CRLog::trace("font definition is duplicate"); return false; @@ -3604,35 +4274,35 @@ class LVFreeTypeFontManager : public LVFontManager FT_Done_Face( face ); face = NULL; } - if ( index>=num_faces-1 ) break; } - return res; - } + } + // RegisterFont (and the 2 similar functions above) adds to the cache + // definitions for all the fonts in their font file. + // Font instances will be created as need from the LVFontDef name or buf. + // (The similar functions do most of the same work, and some code + // could be factorized between them.) virtual bool RegisterFont( lString8 name ) { FONT_MAN_GUARD -#ifdef LOAD_TTF_FONTS_ONLY + #ifdef LOAD_TTF_FONTS_ONLY if ( name.pos( cs8(".ttf") ) < 0 && name.pos( cs8(".TTF") ) < 0 ) return false; // load ttf fonts only -#endif + #endif //CRLog::trace("RegisterFont(%s)", name.c_str()); lString8 fname = makeFontFileName( name ); //CRLog::trace("font file name : %s", fname.c_str()); - #if (DEBUG_FONT_MAN==1) - if ( _log ) { - fprintf(_log, "RegisterFont( %s ) path=%s\n", - name.c_str(), fname.c_str() - ); - } - #endif - bool res = false; + #if (DEBUG_FONT_MAN==1) + if ( _log ) { + fprintf(_log, "RegisterFont( %s ) path=%s\n", name.c_str(), fname.c_str()); + } + #endif + bool res = false; int index = 0; - FT_Face face = NULL; // for all faces in file @@ -3656,13 +4326,8 @@ class LVFreeTypeFontManager : public LVFontManager //bool monospaced = isMonoSpaced( face ); if ( !scal || !charset ) { - //#if (DEBUG_FONT_MAN==1) - // if ( _log ) { CRLog::debug(" won't register font %s: %s", - name.c_str(), !charset?"no mandatory characters in charset" : "font is not scalable" - ); - // } - //#endif + name.c_str(), !charset?"no mandatory characters in charset" : "font is not scalable"); if ( face ) { FT_Done_Face( face ); face = NULL; @@ -3689,25 +4354,32 @@ class LVFreeTypeFontManager : public LVFontManager familyName, index ); - #if (DEBUG_FONT_MAN==1) - if ( _log ) { - fprintf(_log, "registering font: (file=%s[%d], size=%d, weight=%d, italic=%d, family=%d, typeface=%s)\n", - def.getName().c_str(), def.getIndex(), def.getSize(), def.getWeight(), def.getItalic()?1:0, (int)def.getFamily(), def.getTypeFace().c_str() - ); - } - #endif + #if (DEBUG_FONT_MAN==1) + if ( _log ) { + fprintf(_log, "registering font: (file=%s[%d], size=%d, weight=%d, italic=%d, family=%d, typeface=%s)\n", + def.getName().c_str(), def.getIndex(), def.getSize(), def.getWeight(), + def.getItalic()?1:0, (int)def.getFamily(), def.getTypeFace().c_str() + ); + } + #endif - if ( face ) { - FT_Done_Face( face ); - face = NULL; - } + if ( face ) { + FT_Done_Face( face ); + face = NULL; + } - if ( _cache.findDuplicate( &def ) ) { + if ( _cache.findDuplicate( &def ) ) { CRLog::trace("font definition is duplicate"); return false; } _cache.update( &def, LVFontRef(NULL) ); if ( scal && !def.getItalic() ) { + // If this font is not italic, create another definition + // with italic=2 (=fake italic) as we can italicize it. + // A real italic font (italic=1) will be found first + // when italic is requested. + // (Strange that italic and embolden are managed differently... + // maybe it makes the 2x2 combinations easier to manage?) LVFontDef newDef( def ); newDef.setItalic(2); // can italicize if ( !_cache.findDuplicate( &newDef ) ) @@ -3718,7 +4390,6 @@ class LVFreeTypeFontManager : public LVFontManager if ( index>=num_faces-1 ) break; } - return res; } @@ -3729,7 +4400,8 @@ class LVFreeTypeFontManager : public LVFontManager return (_library != NULL); } }; -#endif +#endif // (USE_FREETYPE==1) + #if (USE_BITMAP_FONTS==1) class LVBitmapFontManager : public LVFontManager @@ -3782,7 +4454,7 @@ class LVBitmapFontManager : public LVFontManager // weight>400?"bold":"", // italic?"italic":"" ); LVFontCacheItem * item = _cache.find( def ); - delete def; + delete def; if (!item->getFont().isNull()) { //fprintf(_log, " : fount existing\n"); @@ -3851,7 +4523,7 @@ class LVBitmapFontManager : public LVFontManager return true; } }; -#endif +#endif // (USE_BITMAP_FONTS==1) #if !defined(__SYMBIAN32__) && defined(_WIN32) && USE_FREETYPE!=1 @@ -3919,11 +4591,11 @@ class LVWin32FontManager : public LVFontManager return item->getFont(); } -#if COLOR_BACKBUFFER==0 + #if COLOR_BACKBUFFER==0 LVWin32Font * font = new LVWin32Font; -#else + #else LVWin32DrawFont * font = new LVWin32DrawFont; -#endif + #endif LVFontDef * fdef = item->getDef(); LVFontDef def2( fdef->getName(), size, weight, italic, @@ -4042,7 +4714,7 @@ int CALLBACK LVWin32FontEnumFontFamExProc( } return 1; } -#endif +#endif // !defined(__SYMBIAN32__) && defined(_WIN32) && USE_FREETYPE!=1 #if (USE_BITMAP_FONTS==1) @@ -4061,12 +4733,13 @@ LVFontRef LoadFontFromFile( const char * fname ) return ref; } -#endif +#endif // (USE_BITMAP_FONTS==1) +// Init unique font manager: either win32, freetype, or bitmap bool InitFontManager( lString8 path ) { if ( fontMan ) { - return true; + return true; //delete fontMan; } #if (USE_WIN32_FONTS==1) @@ -4090,17 +4763,26 @@ bool ShutdownFontManager() return false; } +// Font definitions matching/scoring int LVFontDef::CalcDuplicateMatch( const LVFontDef & def ) const { if (def._documentId != -1 && _documentId != def._documentId) return false; - bool size_match = (_size==-1 || def._size==-1) ? true - : (def._size == _size); - bool weight_match = (_weight==-1 || def._weight==-1) ? true - : (def._weight == _weight); + + bool size_match = (_size==-1 || def._size==-1) ? + true + : (def._size == _size); + + bool weight_match = (_weight==-1 || def._weight==-1) ? + true + : (def._weight == _weight); + bool italic_match = (_italic == def._italic || _italic==-1 || def._italic==-1); + bool family_match = (_family==css_ff_inherit || def._family==css_ff_inherit || def._family == _family); + bool typeface_match = (_typeface == def._typeface); + return size_match && weight_match && italic_match && family_match && typeface_match; } @@ -4108,29 +4790,91 @@ int LVFontDef::CalcMatch( const LVFontDef & def, bool useBias ) const { if (_documentId != -1 && _documentId != def._documentId) return 0; - int size_match = (_size==-1 || def._size==-1) ? 256 - : (def._size>_size ? _size*256/def._size : def._size*256/_size ); + + // size + int size_match = (_size==-1 || def._size==-1) ? + 256 + : (def._size>_size ? + _size*256/def._size + : def._size*256/_size ); + + // weight int weight_diff = def._weight - _weight; - if ( weight_diff<0 ) + if ( weight_diff < 0 ) weight_diff = -weight_diff; if ( weight_diff > 800 ) weight_diff = 800; - int weight_match = (_weight==-1 || def._weight==-1) ? 256 - : ( 256 - weight_diff * 256 / 800 ); - int italic_match = (_italic == def._italic || _italic==-1 || def._italic==-1) ? 256 : 0; + int weight_match = (_weight==-1 || def._weight==-1) ? + 256 + : ( 256 - weight_diff * 256 / 800 ); + + // italic + int italic_match = (_italic == def._italic || _italic==-1 || def._italic==-1) ? + 256 + : 0; + // lower the score if any is fake italic if ( (_italic==2 || def._italic==2) && _italic>0 && def._italic>0 ) italic_match = 128; - int family_match = (_family==css_ff_inherit || def._family==css_ff_inherit || def._family == _family) - ? 256 - : ( (_family==css_ff_monospace)==(def._family==css_ff_monospace) ? 64 : 0 ); + + + // family + int family_match = (_family==css_ff_inherit || def._family==css_ff_inherit || def._family == _family) ? + 256 + : ( (_family==css_ff_monospace)==(def._family==css_ff_monospace) ? 64 : 0 ); + // lower score if one is monospace and the other is not + + // typeface int typeface_match = (_typeface == def._typeface) ? 256 : 0; + + // bias int bias = useBias ? _bias : 0; + + // Special handling for synthetized fonts: + // The way this function is called: + // 'this' (or '', properties not prefixed) is either an instance of a + // registered font, or a registered font definition, + // 'def' is the requested definition. + // 'def' can never be italic=2 (fake italic) or weight=601 (fake bold), but + // either 0 or 1, or a 400,700,... any multiple of 100 + // 'this' registered can be only 400 when the font has no bold sibling, + // or 700 when 'this' is the bold sibling + // 'this' instantiated can be 400 (for the regular original) + // or 700 when 'this' is the bold sibling instantiated + // or 601 when it has been synthetised from the regular. + // We want to avoid an instantiated fake bold (resp. fake bold italic) to + // have a higher score than the registered original when a fake bold italic + // (resp. fake bold) is requested, so the italic/non italic requested can + // be re-synthetized. Otherwise, we'll get some italic when not wanting + // italic (or vice versa), depending on which has been instantiated first... + // + if ( _weight & 1) { // 'this' is an instantiated fake bold font + if ( def._italic > 0 ) { // italic requested + if ( _italic == 0 ) { // 'this' is fake bold but non-italic + weight_match = 0; // => drop score + italic_match = 0; + // The regular (italic or fake italic) is available + // and will get a better score than 'this' + } + // otherwise, 'this' is a fake bold italic, and it can match + } + else { // non-italic requested + if ( _italic > 0 ) { // 'this' is fake bold of (real or fake) italic + weight_match = 0; // => drop score + italic_match = 0; + // The regular is available and will get a better score + // than 'this' + } + } + } + + // final score int score = bias + (size_match * 100) + (weight_match * 5) + (italic_match * 5) + (family_match * 100) + (typeface_match * 1000); + // printf("### %s (%d) vs %s (%d): size=%d weight=%d italic=%d family=%d typeface=%d bias=%d => %d\n", // _typeface.c_str(), _family, def._typeface.c_str(), def._family, // size_match, weight_match, italic_match, family_match, typeface_match, _bias, score); @@ -4143,9 +4887,19 @@ int LVFontDef::CalcFallbackMatch( lString8 face, int size ) const //CRLog::trace("'%s'' != '%s'", face.c_str(), _typeface.c_str()); return 0; } - int size_match = (_size==-1 || size==-1 || _size==size) ? 256 : 0; - int weight_match = (_weight==-1) ? 256 : ( 256 - _weight * 256 / 800 ); - int italic_match = _italic == 0 ? 256 : 0; + + int size_match = (_size==-1 || size==-1 || _size==size) ? + 256 + : 0; + + int weight_match = (_weight==-1) ? + 256 + : ( 256 - _weight * 256 / 800 ); + + int italic_match = _italic == 0 ? + 256 + : 0; + return + (size_match * 100) + (weight_match * 5) @@ -4153,68 +4907,60 @@ int LVFontDef::CalcFallbackMatch( lString8 face, int size ) const } - - - - - - -void LVBaseFont::DrawTextString( LVDrawBuf * buf, int x, int y, +/// draws text string (returns x advance) +int LVBaseFont::DrawTextString( LVDrawBuf * buf, int x, int y, const lChar16 * text, int len, lChar16 def_char, lUInt32 * palette, bool addHyphen, lUInt32 , int , int, int ) { //static lUInt8 glyph_buf[16384]; //LVFont::glyph_info_t info; int baseline = getBaseline(); - while (len>=(addHyphen?0:1)) - { - if (len<=1 || *text != UNICODE_SOFT_HYPHEN_CODE) - { - lChar16 ch = ((len==0)?UNICODE_SOFT_HYPHEN_CODE:*text); - - LVFontGlyphCacheItem * item = getGlyph(ch, def_char); - int w = 0; - if ( item ) { - // avoid soft hyphens inside text string - w = item->advance; - if ( item->bmp_width && item->bmp_height ) { - buf->Draw( x + item->origin_x, - y + baseline - item->origin_y, - item->bmp, - item->bmp_width, - item->bmp_height, - palette); - } - } - x += w; // + letter_spacing; - -// if ( !getGlyphInfo( ch, &info, def_char ) ) -// { -// ch = def_char; -// if ( !getGlyphInfo( ch, &info, def_char ) ) -// ch = 0; -// } -// if (ch && getGlyphImage( ch, glyph_buf, def_char )) -// { -// if (info.blackBoxX && info.blackBoxY) -// { -// buf->Draw( x + info.originX, -// y + baseline - info.originY, -// glyph_buf, -// info.blackBoxX, -// info.blackBoxY, -// palette); -// } -// x += info.width; -// } - } - else if (*text != UNICODE_SOFT_HYPHEN_CODE) - { - //len = len; - } - len--; - text++; + int x0 = x; + while (len>=(addHyphen?0:1)) { + if (len<=1 || *text != UNICODE_SOFT_HYPHEN_CODE) { + lChar16 ch = ((len==0)?UNICODE_SOFT_HYPHEN_CODE:*text); + + LVFontGlyphCacheItem * item = getGlyph(ch, def_char); + int w = 0; + if ( item ) { + // avoid soft hyphens inside text string + w = item->advance; + if ( item->bmp_width && item->bmp_height ) { + buf->Draw( x + item->origin_x, + y + baseline - item->origin_y, + item->bmp, + item->bmp_width, + item->bmp_height, + palette); + } + } + x += w; // + letter_spacing; + + // if ( !getGlyphInfo( ch, &info, def_char ) ) { + // ch = def_char; + // if ( !getGlyphInfo( ch, &info, def_char ) ) + // ch = 0; + // } + // if (ch && getGlyphImage( ch, glyph_buf, def_char )) { + // if (info.blackBoxX && info.blackBoxY) + // { + // buf->Draw( x + info.originX, + // y + baseline - info.originY, + // glyph_buf, + // info.blackBoxX, + // info.blackBoxY, + // palette); + // } + // x += info.width; + // } + } + else if (*text != UNICODE_SOFT_HYPHEN_CODE) { + //len = len; + } + len--; + text++; } + return x - x0; } #if (USE_BITMAP_FONTS==1) @@ -4238,7 +4984,8 @@ lUInt16 LBitmapFont::measureText( int max_width, lChar16 def_char, int letter_spacing, - bool allow_hyphenation + bool allow_hyphenation, + lUInt32 hints ) { return lvfontMeasureText( m_font, text, len, widths, flags, max_width, def_char ); @@ -4246,7 +4993,6 @@ lUInt16 LBitmapFont::measureText( lUInt32 LBitmapFont::getTextWidth( const lChar16 * text, int len ) { - // static lUInt16 widths[MAX_LINE_CHARS+1]; static lUInt8 flags[MAX_LINE_CHARS+1]; if ( len>MAX_LINE_CHARS ) @@ -4299,12 +5045,12 @@ int LBitmapFont::LoadFromFile( const char * fname ) _family = (css_font_family_t) hdr->fontFamily; return 1; } -#endif +#endif // (USE_BITMAP_FONTS==1) +// Font cache management LVFontCacheItem * LVFontCache::findDuplicate( const LVFontDef * def ) { - for (int i=0; i<_registered_list.length(); i++) - { + for (int i=0; i<_registered_list.length(); i++) { if ( _registered_list[i]->_def.CalcDuplicateMatch( *def ) ) return _registered_list[i]; } @@ -4327,20 +5073,16 @@ LVFontCacheItem * LVFontCache::findFallback( lString8 face, int size ) int best_instance_index = -1; int best_instance_match = -1; int i; - for (i=0; i<_instance_list.length(); i++) - { + for (i=0; i<_instance_list.length(); i++) { int match = _instance_list[i]->_def.CalcFallbackMatch( face, size ); - if (match > best_instance_match) - { + if (match > best_instance_match) { best_instance_match = match; best_instance_index = i; } } - for (i=0; i<_registered_list.length(); i++) - { + for (i=0; i<_registered_list.length(); i++) { int match = _registered_list[i]->_def.CalcFallbackMatch( face, size ); - if (match > best_match) - { + if (match > best_match) { best_match = match; best_index = i; } @@ -4362,26 +5104,21 @@ LVFontCacheItem * LVFontCache::find( const LVFontDef * fntdef, bool useBias ) LVFontDef def(*fntdef); lString8Collection list; splitPropertyValueList( fntdef->getTypeFace().c_str(), list ); - for (int nindex=0; nindex==0 || nindex_def.CalcMatch( def, useBias ); - if (match > best_instance_match) - { + if (match > best_instance_match) { best_instance_match = match; best_instance_index = i; } } - for (i=0; i<_registered_list.length(); i++) - { + for (i=0; i<_registered_list.length(); i++) { int match = _registered_list[i]->_def.CalcMatch( def, useBias ); - if (match > best_match) - { + if (match > best_match) { best_match = match; best_index = i; } @@ -4398,13 +5135,11 @@ bool LVFontCache::setAsPreferredFontWithBias( lString8 face, int bias, bool clea { bool found = false; int i; - for (i=0; i<_instance_list.length(); i++) - { + for (i=0; i<_instance_list.length(); i++) { if (_instance_list[i]->_def.setBiasIfNameMatch( face, bias, clearOthersBias )) found = true; } - for (i=0; i<_registered_list.length(); i++) - { + for (i=0; i<_registered_list.length(); i++) { if (_registered_list[i]->_def.setBiasIfNameMatch( face, bias, clearOthersBias )) found = true; } @@ -4424,16 +5159,12 @@ void LVFontCache::update( const LVFontDef * def, LVFontRef ref ) { int i; if ( !ref.isNull() ) { - for (i=0; i<_instance_list.length(); i++) - { - if ( _instance_list[i]->_def == *def ) - { - if (ref.isNull()) - { + for (i=0; i<_instance_list.length(); i++) { + if ( _instance_list[i]->_def == *def ) { + if (ref.isNull()) { _instance_list.erase(i, 1); } - else - { + else { _instance_list[i]->_fnt = ref; } return; @@ -4443,11 +5174,10 @@ void LVFontCache::update( const LVFontDef * def, LVFontRef ref ) //LVFontCacheItem * item; //item = new LVFontCacheItem(*def); addInstance( def, ref ); - } else { - for (i=0; i<_registered_list.length(); i++) - { - if ( _registered_list[i]->_def == *def ) - { + } + else { + for (i=0; i<_registered_list.length(); i++) { + if ( _registered_list[i]->_def == *def ) { return; } } @@ -4476,15 +5206,17 @@ void LVFontCache::gc() { int droppedCount = 0; int usedCount = 0; - for (int i=_instance_list.length()-1; i>=0; i--) - { - if ( _instance_list[i]->_fnt.getRefCount()<=1 ) - { - if ( CRLog::isTraceEnabled() ) - CRLog::trace("dropping font instance %s[%d] by gc()", _instance_list[i]->getDef()->getTypeFace().c_str(), _instance_list[i]->getDef()->getSize() ); + for (int i=_instance_list.length()-1; i>=0; i--) { + if ( _instance_list[i]->_fnt.getRefCount()<=1 ) { + if ( CRLog::isTraceEnabled() ) { + CRLog::trace("dropping font instance %s[%d] by gc()", + _instance_list[i]->getDef()->getTypeFace().c_str(), + _instance_list[i]->getDef()->getSize() ); + } _instance_list.erase(i,1); droppedCount++; - } else { + } + else { usedCount++; } } @@ -4663,7 +5395,8 @@ lUInt16 LVWin32DrawFont::measureText( int max_width, lChar16 def_char, int letter_spacing, - bool allow_hyphenation + bool allow_hyphenation, + lUInt32 hints ) { if (_hfont==NULL) @@ -4761,15 +5494,15 @@ lUInt16 LVWin32DrawFont::measureText( return nchars; } -/// draws text string -void LVWin32DrawFont::DrawTextString( LVDrawBuf * buf, int x, int y, +/// draws text string (returns x advance) +int LVWin32DrawFont::DrawTextString( LVDrawBuf * buf, int x, int y, const lChar16 * text, int len, lChar16 def_char, lUInt32 * palette, bool addHyphen, lUInt32 flags, int letter_spacing, int width, int text_decoration_back_gap ) { if (_hfont==NULL) - return; + return 0; lString16 str(text, len); // substitute soft hyphens with zero width spaces @@ -4786,14 +5519,14 @@ void LVWin32DrawFont::DrawTextString( LVDrawBuf * buf, int x, int y, lvRect clip; buf->GetClipRect(&clip); if (y > clip.bottom || y+_height < clip.top) - return; + return 0; if (buf->GetBitsPerPixel()<16) { // draw using backbuffer SIZE sz; if ( !GetTextExtentPoint32W(_drawbuf.GetDC(), str.c_str(), str.length(), &sz) ) - return; + return 0; LVColorDrawBuf colorbuf( sz.cx, sz.cy ); colorbuf.Clear(0xFFFFFF); HDC dc = colorbuf.GetDC(); @@ -4824,6 +5557,7 @@ void LVWin32DrawFont::DrawTextString( LVDrawBuf * buf, int x, int y, str.c_str(), str.length(), NULL ); SelectObject( dc, oldfont ); } + return 0; // advance not implemented } /** \brief get glyph image in 1 byte per pixel format @@ -5023,7 +5757,8 @@ lUInt16 LVWin32Font::measureText( int max_width, lChar16 def_char, int letter_spacing, - bool allow_hyphenation + bool allow_hyphenation, + lUInt32 hints ) { if (_hfont==NULL) @@ -5128,20 +5863,20 @@ bool LVWin32Font::Create(int size, int weight, bool italic, css_font_family_t fa return true; } -#endif +#endif // !defined(__SYMBIAN32__) && defined(_WIN32) && USE_FREETYPE!=1 /// to compare two fonts bool operator == (const LVFont & r1, const LVFont & r2) { if ( &r1 == &r2 ) return true; - return r1.getSize()==r2.getSize() - && r1.getWeight()==r2.getWeight() - && r1.getItalic()==r2.getItalic() - && r1.getFontFamily()==r2.getFontFamily() - && r1.getTypeFace()==r2.getTypeFace() - && r1.getKerningMode()==r2.getKerningMode() - && r1.getHintingMode()==r2.getHintingMode() + return r1.getSize() == r2.getSize() + && r1.getWeight() == r2.getWeight() + && r1.getItalic() == r2.getItalic() + && r1.getFontFamily() == r2.getFontFamily() + && r1.getTypeFace() == r2.getTypeFace() + && r1.getKerningMode() == r2.getKerningMode() + && r1.getHintingMode() == r2.getHintingMode() ; } diff --git a/crengine/src/lvpagesplitter.cpp b/crengine/src/lvpagesplitter.cpp index ceafeb8d9..097b19c07 100644 --- a/crengine/src/lvpagesplitter.cpp +++ b/crengine/src/lvpagesplitter.cpp @@ -71,17 +71,33 @@ bool LVRendPageContext::updateRenderProgress( int numFinalBlocksRendered ) return false; } -/// append footnote link to last added line -void LVRendPageContext::addLink( lString16 id ) +/// Get the number of links in the current line links list, or +// in link_ids when no page_list +int LVRendPageContext::getCurrentLinksCount() { if ( !page_list ) { - link_ids.add( id ); // gather links even if no page_list + return link_ids.length(); + } + if ( lines.empty() ) + return 0; + return lines.last()->getLinksCount(); +} + +/// append or insert footnote link to last added line +void LVRendPageContext::addLink( lString16 id, int pos ) +{ + if ( !page_list ) { + // gather links even if no page_list + if ( pos >= 0 ) // insert at pos + link_ids.insert( pos, id ); + else // append + link_ids.add( id ); return; } if ( lines.empty() ) return; LVFootNote * note = getOrCreateFootNote( id ); - lines.last()->addLink(note); + lines.last()->addLink(note, pos); } /// mark start of foot note diff --git a/crengine/src/lvrend.cpp b/crengine/src/lvrend.cpp index 81ea81669..92aadad3a 100755 --- a/crengine/src/lvrend.cpp +++ b/crengine/src/lvrend.cpp @@ -2029,7 +2029,6 @@ lString16 renderListItemMarker( ldomNode * enode, int & marker_width, LFormatted // (todo: replace 'fmt' with 'int basewidth' to avoid confusion) void renderFinalBlock( ldomNode * enode, LFormattedText * txform, RenderRectAccessor * fmt, int & baseflags, int ident, int line_h, int valign_dy, bool * is_link_start ) { - int txform_src_count = txform->GetSrcCount(); // to track if we added lines to txform if ( enode->isElement() ) { lvdom_element_render_method rm = enode->getRendMethod(); if ( rm == erm_invisible ) @@ -2484,16 +2483,125 @@ void renderFinalBlock( ldomNode * enode, LFormattedText * txform, RenderRectAcce #ifdef DEBUG_DUMP_ENABLED logfile << "+BLOCK [" << cnt << "]"; #endif - // usual elements + // Usual elements bool thisIsRunIn = enode->getStyle()->display==css_d_run_in; if ( thisIsRunIn ) flags |= LTEXT_RUNIN_FLAG; + + // Some elements add some generated content + lUInt16 nodeElementId = enode->getNodeId(); + // Don't handle dir= for the erm_final (

hasAttribute( attr_dir ) && rm != erm_final; + bool addGeneratedContent = hasDirAttribute || + nodeElementId == el_bdi || + nodeElementId == el_bdo || + nodeElementId == el_q; + bool closeWithPDI = false; + bool closeWithPDF = false; + bool closeWithPDFPDI = false; + if ( addGeneratedContent ) { + // Note: we need to explicitely clear newline flag after + // any txform->AddSourceLine(). If we delay that and add another + // char before, this other char would generate a new line. + LVFont * font = enode->getFont().get(); + lUInt32 cl = style->color.type!=css_val_color ? 0xFFFFFFFF : style->color.value; + lUInt32 bgcl = style->background_color.type!=css_val_color ? 0xFFFFFFFF : style->background_color.value; + if ( nodeElementId == el_q ) { + // Add default quoting opening char + // We do not support showing a different char for multiple embedded , + // and neither the way to specify this with CSS, ie: + // q::before { content: open-quote; } + // :root { quotes: '\201c' '\201d' '\2018' '\2019'; } + // Note: this specific char seem to not be mirrored (when using HarfBuzz) when + // added to some RTL arabic text. But it appears that way with Firefox too! + // But if we use another char (0x00AB / 0x00BB), it gets mirrored correctly. + // Might be that HarfBuzz first substitute it with arabic quotes (which happen + // to look inverted), and then mirror that? + txform->AddSourceLine( L"\x201C", 1, cl, bgcl, font, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy); + flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag + } + // The following is needed for fribidi to do the right thing when the content creator + // has provided hints to explicite ambiguous cases. + // and are HTML5 tags allowing to inform or override the bidi algorithm. + // When meeting them, we add the equivalent unicode opening and closing chars so + // that fribidi (working on text only) can ensure what's specified with HTML tags. + // See http://unicode.org/reports/tr9/#Markup_And_Formatting + lString16 dir = enode->getAttributeValue( attr_dir ); + dir = dir.lowercase(); // (no need for trim(), it's done by the XMLParser) + if ( nodeElementId == el_bdo ) { + // (bidirectional override): prevents the bidirectional algorithm from + // rearranging the sequence of characters it encloses + // dir=ltr => LRO U+202D LEFT-TO-RIGHT OVERRIDE + // dir=rtl => RLO U+202E RIGHT-TO-LEFT OVERRIDE + // leaving => PDF U+202C POP DIRECTIONAL FORMATTING + // The link above suggest using these combinations: + // dir=ltr => FSI LRO + // dir=rtl => FSI RLO + // leaving => PDF PDI + // but it then doesn't have the intended effect (fribidi bug or limitation?) + if ( dir.compare("rtl") == 0 ) { + // txform->AddSourceLine( L"\x2068\x202E", 1, cl, bgcl, font, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy); + // closeWithPDFPDI = true; + txform->AddSourceLine( L"\x202E", 1, cl, bgcl, font, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy); + closeWithPDF = true; + flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag + } + else if ( dir.compare("ltr") == 0 ) { + // txform->AddSourceLine( L"\x2068\x202D", 1, cl, bgcl, font, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy); + // closeWithPDFPDI = true; + txform->AddSourceLine( L"\x202D", 1, cl, bgcl, font, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy); + closeWithPDF = true; + flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag + } + } + else if ( hasDirAttribute || nodeElementId == el_bdi ) { + // (bidirectional isolate): isolates its content from the surrounding text, + // and to be used also for any inline elements with "dir=": + // dir=ltr => LRI U+2066 LEFT-TO-RIGHT ISOLATE + // dir=rtl => RLI U+2067 RIGHT-TO-LEFT ISOLATE + // dir=auto => FSI U+2068 FIRST STRONG ISOLATE + // leaving => PDI U+2069 POP DIRECTIONAL ISOLATE + if ( dir.compare("rtl") == 0 ) { + txform->AddSourceLine( L"\x2067", 1, cl, bgcl, font, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy); + closeWithPDI = true; + flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag + } + else if ( dir.compare("ltr") == 0 ) { + txform->AddSourceLine( L"\x2066", 1, cl, bgcl, font, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy); + closeWithPDI = true; + flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag + } + else if ( nodeElementId == el_bdi || dir.compare("auto") == 0 ) { + txform->AddSourceLine( L"\x2068", 1, cl, bgcl, font, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy); + closeWithPDI = true; + flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag + } + // Pre HTML5, we would have used for any inline tag with a dir= attribute: + // dir=ltr => LRE U+202A LEFT-TO-RIGHT EMBEDDING + // dir=rtl => RLE U+202B RIGHT-TO-LEFT EMBEDDING + // leaving => PDF U+202C POP DIRECTIONAL FORMATTING + } + // Note: in lvtextfm, we have to explicitely ignore these (added by us, + // or already present in the HTML), in measurement and drawing, as + // FreeType could draw some real glyphes for these, when the font + // provide a glyph (ie: "[FSI]"). No issue when HarfBuzz is used. + // + // Note: if we wanted to support tags, we could use the same kind + // of trick. Unicode provides U+FFF9 to U+FFFA to wrap ruby content. + // HarfBuzz does not support these (because multiple font sizes would + // be involved for drawing ruby), but lvtextfm could deal with these + // itself (by ignoring them in measurement, going back the previous + // advance, increasing the line height, drawing above...) + } + // is_link_start is given to inner elements (to flag the first // text node part of a link), and will be reset to false by // the first non-space-only text node bool * is_link_start_p = is_link_start; // copy of orignal (possibly NULL) pointer bool tmp_is_link_start = true; // new bool, for new pointer if we're a - if ( enode->getNodeId()==el_a ) { + if ( nodeElementId == el_a ) { is_link_start_p = &tmp_is_link_start; // use new pointer } for (int i=0; igetChildNode( i ); renderFinalBlock( child, txform, fmt, flags, ident, line_h, valign_dy, is_link_start_p ); } + + if ( addGeneratedContent ) { + LVFont * font = enode->getFont().get(); + lUInt32 cl = style->color.type!=css_val_color ? 0xFFFFFFFF : style->color.value; + lUInt32 bgcl = style->background_color.type!=css_val_color ? 0xFFFFFFFF : style->background_color.value; + if ( nodeElementId == el_q ) { + // Add default quoting closing char + txform->AddSourceLine( L"\x201D", 1, cl, bgcl, font, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy); + flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag + } + // See comment above: these are the closing counterpart + if ( closeWithPDI ) { + txform->AddSourceLine( L"\x2069", 1, cl, bgcl, font, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy); + flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag + } + else if ( closeWithPDFPDI ) { + txform->AddSourceLine( L"\x202C\x2069", 1, cl, bgcl, font, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy); + flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag + } + else if ( closeWithPDF ) { + txform->AddSourceLine( L"\x202C", 1, cl, bgcl, font, flags|LTEXT_FLAG_OWNTEXT, line_h, valign_dy); + flags &= ~LTEXT_FLAG_NEWLINE & ~LTEXT_SRC_IS_CLEAR_BOTH; // clear newline flag + } + } + // Note: CSS "display: run-in" is no longer used with our epub.css (it is // used with older css files for "body[name="notes"] section title", either // for crengine internal footnotes displaying, or some FB2 features) @@ -2590,6 +2723,8 @@ void renderFinalBlock( ldomNode * enode, LFormattedText * txform, RenderRectAcce case css_c_both: baseflags |= LTEXT_SRC_IS_CLEAR_BOTH; break; + default: + break; } } //baseflags &= ~LTEXT_RUNIN_FLAG; @@ -3336,7 +3471,7 @@ int renderBlockElementLegacy( LVRendPageContext & context, ldomNode * enode, int // is erm_invisible) // (No need to do anything when list-style-type none.) ldomNode * tmpnode = enode; - while ( tmpnode->hasChildren() ) { + while ( tmpnode && tmpnode->hasChildren() ) { tmpnode = tmpnode->getChildNode( 0 ); if (tmpnode && tmpnode->getRendMethod() == erm_final) { // We need renderFinalBlock() to be able to reach the current @@ -3523,6 +3658,12 @@ int renderBlockElementLegacy( LVRendPageContext & context, ldomNode * enode, int } // footnote links analysis if ( !isFootNoteBody && enode->getDocument()->getDocFlag(DOC_FLAG_ENABLE_FOOTNOTES) ) { // disable footnotes for footnotes + // If paragraph is RTL, we are meeting words in the reverse of the reading order: + // so, insert each link for this line at the same position, instead of at the end. + int link_insert_pos = -1; // append + if ( line->flags & LTEXT_LINE_PARA_IS_RTL ) { + link_insert_pos = context.getCurrentLinksCount(); + } for ( int w=0; wword_count; w++ ) { // check link start flag for every word if ( line->words[w].flags & LTEXT_WORD_IS_LINK_START ) { @@ -3540,7 +3681,7 @@ int renderBlockElementLegacy( LVRendPageContext & context, ldomNode * enode, int lString16 href = parent->getAttributeValue(LXML_NS_ANY, attr_href ); if ( href.length()>0 && href.at(0)=='#' ) { href.erase(0,1); - context.addLink( href ); + context.addLink( href, link_insert_pos ); } } } @@ -3556,6 +3697,12 @@ int renderBlockElementLegacy( LVRendPageContext & context, ldomNode * enode, int for (int i=0; iGetLineInfo(i); if ( !isFootNoteBody && enode->getDocument()->getDocFlag(DOC_FLAG_ENABLE_FOOTNOTES) ) { + // If paragraph is RTL, we are meeting words in the reverse of the reading order: + // so, insert each link for this line at the same position, instead of at the end. + int link_insert_pos = -1; // append + if ( line->flags & LTEXT_LINE_PARA_IS_RTL ) { + link_insert_pos = context.getCurrentLinksCount(); + } for ( int w=0; wword_count; w++ ) { // check link start flag for every word if ( line->words[w].flags & LTEXT_WORD_IS_LINK_START ) { @@ -3569,7 +3716,7 @@ int renderBlockElementLegacy( LVRendPageContext & context, ldomNode * enode, int lString16 href = parent->getAttributeValue(LXML_NS_ANY, attr_href ); if ( href.length()>0 && href.at(0)=='#' ) { href.erase(0,1); - context.addLink( href ); + context.addLink( href, link_insert_pos ); } } } @@ -5681,7 +5828,7 @@ void renderBlockElementEnhanced( FlowState * flow, ldomNode * enode, int x, int // is erm_invisible) // (No need to do anything when list-style-type none.) ldomNode * tmpnode = enode; - while ( tmpnode->hasChildren() ) { + while ( tmpnode && tmpnode->hasChildren() ) { tmpnode = tmpnode->getChildNode( 0 ); if (tmpnode && tmpnode->getRendMethod() == erm_final) { // We need renderFinalBlock() to be able to reach the current @@ -6119,6 +6266,12 @@ void renderBlockElementEnhanced( FlowState * flow, ldomNode * enode, int x, int // a reference to it so page splitting can bring the footnotes // text on this page, and then decide about page split. if ( !isFootNoteBody && enode->getDocument()->getDocFlag(DOC_FLAG_ENABLE_FOOTNOTES) ) { // disable footnotes for footnotes + // If paragraph is RTL, we are meeting words in the reverse of the reading order: + // so, insert each link for this line at the same position, instead of at the end. + int link_insert_pos = -1; // append + if ( line->flags & LTEXT_LINE_PARA_IS_RTL ) { + link_insert_pos = flow->getPageContext()->getCurrentLinksCount(); + } for ( int w=0; wword_count; w++ ) { // check link start flag for every word if ( line->words[w].flags & LTEXT_WORD_IS_LINK_START ) { @@ -6136,7 +6289,7 @@ void renderBlockElementEnhanced( FlowState * flow, ldomNode * enode, int x, int lString16 href = parent->getAttributeValue(LXML_NS_ANY, attr_href ); if ( href.length()>0 && href.at(0)=='#' ) { href.erase(0,1); - flow->getPageContext()->addLink( href ); + flow->getPageContext()->addLink( href, link_insert_pos ); } } } @@ -8084,6 +8237,8 @@ void getRenderedWidths(ldomNode * node, int &maxWidth, int &minWidth, bool ignor // and getRightSideBearing(). These should be called with the exact same // parameters as used in lvtextfm.cpp getAdditionalCharWidth() and // getAdditionalCharWidthOnLeft(). + // todo: use fribidi and split measurement at fribidi level change, + // and beware left/right side bearing adjustments... while (true) { LVFont * font = node->getParentNode()->getFont().get(); int chars_measured = font->measureText( @@ -8094,6 +8249,7 @@ void getRenderedWidths(ldomNode * node, int &maxWidth, int &minWidth, bool ignor '?', // replacement char letter_spacing, false); // no hyphenation + // todo: provide direction and hints for (int i=0; i0 ? widths[i-1] : 0); lChar16 c = *(txt + start + i); diff --git a/crengine/src/lvstring.cpp b/crengine/src/lvstring.cpp index 315352908..9f6b1e14d 100644 --- a/crengine/src/lvstring.cpp +++ b/crengine/src/lvstring.cpp @@ -1304,6 +1304,17 @@ int lString16Collection::add( const lString16 & str ) str.addref(); return count++; } +int lString16Collection::insert( int pos, const lString16 & str ) +{ + if (pos<0 || pos>=count) + return add(str); + reserve( 1 ); + for (int i=count; i>pos; --i) + chunks[i] = chunks[i-1]; + chunks[pos] = str.pchunk; + str.addref(); + return count++; +} void lString16Collection::clear() { if (chunks) { diff --git a/crengine/src/lvtextfm.cpp b/crengine/src/lvtextfm.cpp index 6989a88da..8d115d647 100755 --- a/crengine/src/lvtextfm.cpp +++ b/crengine/src/lvtextfm.cpp @@ -19,6 +19,7 @@ #include "../include/lvfnt.h" #include "../include/lvtextfm.h" #include "../include/lvdrawbuf.h" +#include "../include/fb2def.h" #ifdef __cplusplus #include "../include/lvimg.h" @@ -26,8 +27,13 @@ #include "../include/lvrend.h" #endif +#if (USE_FRIBIDI==1) +#include +#endif + #define MIN_SPACE_CONDENSING_PERCENT 50 + // to debug formatter #if defined(_DEBUG) && 0 @@ -360,7 +366,7 @@ class LVFormatter { bool m_staticBufs; static bool m_staticBufs_inUse; lChar16 * m_text; - lUInt8 * m_flags; + lUInt16 * m_flags; src_text_fragment_t * * m_srcs; lUInt16 * m_charindex; int * m_widths; @@ -370,6 +376,17 @@ class LVFormatter { bool m_has_float_to_position; bool m_has_ongoing_float; bool m_no_clear_own_floats; + #if (USE_FRIBIDI==1) + // Bidi/RTL support + FriBidiCharType * m_bidi_ctypes; + FriBidiBracketType * m_bidi_btypes; + FriBidiLevel * m_bidi_levels; + FriBidiParType m_para_bidi_type; + #endif + // These default to false and LTR when USE_FRIBIDI==0, + // just to avoid too many "#if (USE_FRIBIDI==1)" + bool m_has_bidi; // true when Bidi (or pure RTL) detected + bool m_para_dir_is_rtl; // boolean shortcut of m_para_bidi_type #define OBJECT_CHAR_INDEX ((lUInt16)0xFFFF) #define FLOAT_CHAR_INDEX ((lUInt16)0xFFFE) @@ -389,6 +406,11 @@ class LVFormatter { m_has_float_to_position = false; m_has_ongoing_float = false; m_no_clear_own_floats = false; + #if (USE_FRIBIDI==1) + m_bidi_ctypes = NULL; + m_bidi_btypes = NULL; + m_bidi_levels = NULL; + #endif } ~LVFormatter() @@ -788,15 +810,28 @@ class LVFormatter { m_charindex = cr_realloc(m_staticBufs ? NULL : m_charindex, m_size); m_srcs = cr_realloc(m_staticBufs ? NULL : m_srcs, m_size); m_widths = cr_realloc(m_staticBufs ? NULL : m_widths, m_size); + #if (USE_FRIBIDI==1) + // Note: we could here check for RTL chars (and have a flag + // to then not do it in copyText()) so we don't need to allocate + // the following ones if we won't be using them. + m_bidi_ctypes = cr_realloc(m_staticBufs ? NULL : m_bidi_ctypes, m_size); + m_bidi_btypes = cr_realloc(m_staticBufs ? NULL : m_bidi_btypes, m_size); + m_bidi_levels = cr_realloc(m_staticBufs ? NULL : m_bidi_levels, m_size); + #endif } m_staticBufs = false; } else { // static buffer space static lChar16 m_static_text[STATIC_BUFS_SIZE]; - static lUInt8 m_static_flags[STATIC_BUFS_SIZE]; + static lUInt16 m_static_flags[STATIC_BUFS_SIZE]; static src_text_fragment_t * m_static_srcs[STATIC_BUFS_SIZE]; static lUInt16 m_static_charindex[STATIC_BUFS_SIZE]; static int m_static_widths[STATIC_BUFS_SIZE]; + #if (USE_FRIBIDI==1) + static FriBidiCharType m_static_bidi_ctypes[STATIC_BUFS_SIZE]; + static FriBidiBracketType m_static_bidi_btypes[STATIC_BUFS_SIZE]; + static FriBidiLevel m_static_bidi_levels[STATIC_BUFS_SIZE]; + #endif m_text = m_static_text; m_flags = m_static_flags; m_charindex = m_static_charindex; @@ -805,8 +840,13 @@ class LVFormatter { m_staticBufs = true; m_staticBufs_inUse = true; // printf("using static buffers\n"); + #if (USE_FRIBIDI==1) + m_bidi_ctypes = m_static_bidi_ctypes; + m_bidi_btypes = m_static_bidi_btypes; + m_bidi_levels = m_static_bidi_levels; + #endif } - memset( m_flags, 0, sizeof(lUInt8)*m_length ); // start with all flags set to zero + memset( m_flags, 0, sizeof(lUInt16)*m_length ); // start with all flags set to zero pos = 0; // We set to zero the additional slot that the code may peek at (with @@ -817,11 +857,26 @@ class LVFormatter { m_charindex[m_length] = 0; m_srcs[m_length] = NULL; m_widths[m_length] = 0; + #if (USE_FRIBIDI==1) + m_bidi_ctypes[m_length] = 0; + m_bidi_btypes[m_length] = 0; + m_bidi_levels[m_length] = 0; + #endif } /// copy text of current paragraph to buffers void copyText( int start, int end ) { + m_has_bidi = false; // will be set if fribidi detects it is bidirectionnal text + m_para_dir_is_rtl = false; + bool has_rtl = false; // if no RTL char, no need for expensive bidi processing + // todo: according to https://www.w3.org/TR/css-text-3/#bidi-linebox + // the bidi direction, if determined from the text itself (no dir= from + // outer containers) must follow up to next paragraphs (separated by
or newlines). + // Here in lvtextfm, each gets its own call to copyText(), so we might need some state. + // This link also points out that line box direction and its text content direction + // might be different... + int pos = 0; int i; bool prev_was_space = true; // start with true, to get rid of all leading spaces @@ -833,10 +888,12 @@ class LVFormatter { m_srcs[pos] = src; m_flags[pos] = LCHAR_IS_OBJECT; m_charindex[pos] = FLOAT_CHAR_INDEX; //0xFFFE; - // m_flags is a lUInt8, and there are already 8 LCHAR_IS_* bits/flags - // so we can't add our own. But using LCHAR_IS_OBJECT should not hurt, - // as we do the FLOAT tests before it is used. - // m_charindex[pos] is the one to use to detect FLOATs + // Note: m_flags was a lUInt8, and there were already 8 LCHAR_IS_* bits/flags + // so we couldn't add our own. But using LCHAR_IS_OBJECT should not hurt, + // as we do the FLOAT tests before it is used. + // m_charindex[pos] is the one to use to detect FLOATs + // m_flags has since be updated to lUint16, but no real need + // to change what we did for floats to use a new flag. pos++; // No need to update prev_was_space or last_non_space_pos } @@ -912,7 +969,9 @@ class LVFormatter { bool preformatted = (src->flags & LTEXT_FLAG_PREFORMATTED); for ( int k=0; k, and the dir= attribute). + // Try to balance the searches: + if ( c >= 0x202A ) { + if ( c <= 0x2069 ) { + if ( c <= 0x202E ) m_flags[pos] = LCHAR_IS_TO_IGNORE; // 202A>202E + else if ( c >= 0x2066 ) m_flags[pos] = LCHAR_IS_TO_IGNORE; // 2066>2069 + } + } + // We might want to add some others when we happen to meet them. + // todo: see harfbuzz hb-unicode.hh is_default_ignorable() for how + // to do this kind of check fast + + // Note: the overhead of using one of the following is quite minimal, so do if needed + /* + utf8proc_category_t uc = utf8proc_category(c); + if (uc == UTF8PROC_CATEGORY_CF) + printf("format char %x\n", c); + else if (uc == UTF8PROC_CATEGORY_CC) + printf("control char %x\n", c); + // Alternative, using HarfBuzz: + int uc = hb_unicode_general_category(hb_unicode_funcs_get_default(), c); + if (uc == HB_UNICODE_GENERAL_CATEGORY_FORMAT) + printf("format char %x\n", c); + else if (uc == HB_UNICODE_GENERAL_CATEGORY_CONTROL) + printf("control char %x\n", c); + */ + + #if (USE_FRIBIDI==1) + // Also try to detect if we have RTL chars, so that if we don't have any, + // we don't need to invoke expensive fribidi processing below (which + // may add a 50% duration increase to the text rendering phase). + // Looking at fribidi/lib/bidi-type.tab.i and its rules for tagging + // a char as RTL, only the following ranges will trigger it: + // 0590>08FF Hebrew, Arabic, Syriac, Thaana, Nko, Samaritan... + // 200F 202B 202E Right-To-Left mark, embedding, override format flags + // 2067 2068 Right-To-Left isolate, first strong isolate format flags + // FB1D>FDFF Hebrew and Arabic presentation forms + // FE70>FEFF Arabic presentation forms + // 10800>10FFF Other rare scripts possibly RTL + // 1E800>1EEBB Other rare scripts possibly RTL + // (There may be LTR chars in these ranges, but it's fine, we'll + // invoke fribidi, which will say there's no bidi.) + if ( !has_rtl ) { + // Try to balance the searches + if ( c >= 0x0590 ) { + if ( c <= 0x2068 ) { + if ( c <= 0x08FF ) has_rtl = true; + else if ( c >= 0x200F ) { + if ( c >= 0x2067 ) has_rtl = true; + else if ( c == 0x200F || c == 0x202B || c == 0x202E ) has_rtl = true; + } + } + else if ( c >= 0xFB1D ) { + if ( c <= 0xFDFF ) has_rtl = true; + else if ( c <= 0xFEFF ) { + if ( c >= 0xFE70) has_rtl = true; + } + else if ( c <= 0x1EEBB ) { + if (c >= 0x1E800) has_rtl = true; + else if ( c <= 0x10FFF && c >= 0x10800 ) has_rtl = true; + } + } + } + } + #endif + m_charindex[pos] = k; m_srcs[pos] = src; -// lChar16 ch = m_text[pos]; -// if ( ch == '-' || ch == 0x2010 || ch == '.' || ch == '+' || ch==UNICODE_NO_BREAK_SPACE ) -// m_flags[pos] |= LCHAR_DEPRECATED_WRAP_AFTER; pos++; } } @@ -958,6 +1097,95 @@ class LVFormatter { } } TR("%s", LCSTR(lString16(m_text, m_length))); + + #if (USE_FRIBIDI==1) + if ( has_rtl ) { + // HarfBuzz reordering and shaping is different if paragraph is set + // to LTR or RTL, so we must get it right. + // m_para_bidi_type = FRIBIDI_PAR_LTR; + // m_para_bidi_type = FRIBIDI_PAR_RTL; + m_para_bidi_type = FRIBIDI_PAR_WLTR; // Weak LTR (= auto with a bias toward LTR) + // More work is needed in lvrend/lvstyles/lvstsheet to propertly support text + // direction via CSS and attributes (dir="rtl") according to the specs. + // But some specs say content creators should prefer setting text direction + // via the dir= attribute, rather than via CSS, so, for now, let's ensure just that. + // From the first text node of this paragraph, look at its parents until + // we find one with a dir= attribute. (This might be a bit expensive, but well, + // proper rtl can cost a bit more...) + // Note: this should better be done by renderBlockElementEnhanced and FlowState, + // and stored in the final block RenderRectAccessor. + for ( i=start; isrctext[i]; + if ( !src->object ) // might be NULL for added text (like list-item bullets) + continue; + ldomNode * node = (ldomNode *) src->object; // text node or image + // todo: if we're going to keep the following, make it a + // ldomNode::getWrittingDirection() method, returning: + // 1=LTR -1=RTL 0=unspecified + while (node) { + if ( node->getRendMethod() == erm_inline) { + // Ignore inline wrapping nodes: we're interested only in + // the attribute set on an upper block element (erm_final + // and upper erm_block). + node = node->getParentNode(); + continue; + } + if ( !node->hasAttribute( attr_dir ) ) { + node = node->getParentNode(); + continue; + } + lString16 dir = node->getAttributeValue( attr_dir ); + dir = dir.lowercase(); // (no need for trim(), it's done by the XMLParser) + if ( dir.compare("rtl") == 0 ) { + m_para_bidi_type = FRIBIDI_PAR_RTL; // Strong RTL + } + else if ( dir.compare("ltr") == 0 ) { + m_para_bidi_type = FRIBIDI_PAR_LTR; // Strong LTR + } + // otherwise ("auto" or bad value), stay with FRIBIDI_PAR_WLTR Weak LTR + break; + } + break; + } + + // Compute bidi levels + fribidi_get_bidi_types( (const FriBidiChar*)m_text, m_length, m_bidi_ctypes); + fribidi_get_bracket_types( (const FriBidiChar*)m_text, m_length, m_bidi_ctypes, m_bidi_btypes); + int max_level = fribidi_get_par_embedding_levels_ex(m_bidi_ctypes, m_bidi_btypes, + m_length, (FriBidiParType*)&m_para_bidi_type, m_bidi_levels); + // If computed max level == 1, we are in plain and only LTR, so no need for + // more bidi work later. + if ( max_level > 1 ) { + m_has_bidi = true; + } + if ( m_para_bidi_type == FRIBIDI_PAR_RTL || m_para_bidi_type == FRIBIDI_PAR_WRTL ) + m_para_dir_is_rtl = true; + + // fribidi_shape(FRIBIDI_FLAG_SHAPE_MIRRORING, m_bidi_levels, m_length, NULL, (FriBidiChar*)m_text); + // No use mirroring at this point I think, as it's not the text that will + // be drawn. Hoping parens & al. have the same widths when mirrored. + // We'll do that in addLine() when processing words when meeting + // a rtl one, with fribidi_get_mirror_char(). + + /* For debugging: + printf("par_type %d , max_level %d\n", m_para_bidi_type, max_level); + for (int i=0; it.len > MAX_MEASURED_WORD_SIZE) return false; + lUInt32 hints = WORD_FLAGS_TO_FNT_FLAGS(word->flags); int chars_measured = srcfont->measureText( str, word->t.len, @@ -1106,7 +1335,8 @@ class LVFormatter { 0x7FFF, '?', srcline->letter_spacing, - false); + false, + hints ); width = widths[word->t.len-1]; return true; } @@ -1116,6 +1346,7 @@ class LVFormatter { { int i; LVFont * lastFont = NULL; + lInt16 lastLetterSpacing = 0; //src_text_fragment_t * lastSrc = NULL; int start = 0; int lastWidth = 0; @@ -1123,8 +1354,13 @@ class LVFormatter { static lUInt16 widths[MAX_TEXT_CHUNK_SIZE+1]; static lUInt8 flags[MAX_TEXT_CHUNK_SIZE+1]; int tabIndex = -1; + #if (USE_FRIBIDI==1) + FriBidiLevel lastBidiLevel = 0; + FriBidiLevel newBidiLevel; + #endif for ( i=0; i<=m_length; i++ ) { LVFont * newFont = NULL; + lInt16 newLetterSpacing = 0; src_text_fragment_t * newSrc = NULL; if ( tabIndex<0 && m_text[i]=='\t' ) { tabIndex = i; @@ -1136,31 +1372,70 @@ class LVFormatter { isObject = m_charindex[i] == OBJECT_CHAR_INDEX || m_charindex[i] == FLOAT_CHAR_INDEX; newFont = isObject ? NULL : (LVFont *)newSrc->t.font; + newLetterSpacing = newSrc->letter_spacing; // 0 for objects } if (i > 0) prevCharIsObject = m_charindex[i-1] == OBJECT_CHAR_INDEX || m_charindex[i-1] == FLOAT_CHAR_INDEX; if ( !lastFont ) lastFont = newFont; - if ( i>start && (newFont!=lastFont + if (i == 0) + lastLetterSpacing = newLetterSpacing; + bool bidiLevelChanged = false; + int lastDirection = 0; // unknown + #if (USE_FRIBIDI==1) + lastDirection = 1; // direction known: LTR if no bidi found + if (m_has_bidi) { + newBidiLevel = m_bidi_levels[i]; + if (i == 0) + lastBidiLevel = newBidiLevel; + else if ( newBidiLevel != lastBidiLevel ) + bidiLevelChanged = true; + if ( FRIBIDI_LEVEL_IS_RTL(lastBidiLevel) ) + lastDirection = -1; // RTL + } + #endif + // Note: some additional tweaks (like disabling letter-spacing when + // a cursive script is detected) are done in measureText() and drawTextString(). + + // Make a new segment to measure when any property changes from previous char + if ( i>start && ( newFont != lastFont + || newLetterSpacing != lastLetterSpacing + || bidiLevelChanged || isObject || prevCharIsObject - || i>=start+MAX_TEXT_CHUNK_SIZE - || (m_flags[i]&LCHAR_MANDATORY_NEWLINE)) ) { + || i >= start+MAX_TEXT_CHUNK_SIZE + || (m_flags[i] & LCHAR_IS_TO_IGNORE) + || (m_flags[i] & LCHAR_MANDATORY_NEWLINE) ) ) { // measure start..i-1 chars if ( m_charindex[i-1]!=OBJECT_CHAR_INDEX && m_charindex[i-1]!=FLOAT_CHAR_INDEX ) { // measure text + // Note: we provide text in the logical order, and measureText() + // will apply kerning in that order, which might be wrong if some + // text fragment happens to be RTL (except for Harfbuzz which will + // do the right thing). int len = i - start; + // Provide direction and start/end of paragraph hints, for Harfbuzz + lUInt32 hints = 0; + if ( start == 0 ) hints |= LFNT_HINT_BEGINS_PARAGRAPH; + if ( i == m_length ) hints |= LFNT_HINT_ENDS_PARAGRAPH; + if ( lastDirection ) { + hints |= LFNT_HINT_DIRECTION_KNOWN; + if ( lastDirection < 0 ) + hints |= LFNT_HINT_DIRECTION_IS_RTL; + } int chars_measured = lastFont->measureText( m_text + start, len, widths, flags, 0x7FFF, //pbuffer->width, - //300, //TODO '?', - m_srcs[start]->letter_spacing, - false); + lastLetterSpacing, + false, + hints + ); if ( chars_measured w=%d\n", m_widths[start + k]); } -// // debug dump -// lString16 buf; -// for ( int k=0; k=0 ) { int tabPosition = -m_srcs[0]->margin; @@ -1268,14 +1560,14 @@ class LVFormatter { #define MAX_WORD_SIZE 64 /// align line: add or reduce widths of spaces to achieve desired text alignment - void alignLine( formatted_line_t * frmline, int alignment ) { + void alignLine( formatted_line_t * frmline, int alignment, int rightIndent=0 ) { // Fetch current line x offset and max width int x_offset; int width = getAvailableWidthAtY(m_y, m_pbuffer->strut_height, x_offset); // printf("alignLine %d+%d < %d\n", frmline->x, frmline->width, width); // (frmline->x may be different from x_offset when non-zero text-indent) - int available_width = x_offset + width - (frmline->x + frmline->width); + int available_width = x_offset + width - (frmline->x + frmline->width) - rightIndent; if ( available_width < 0 ) { // line is too wide // reduce spaces to fit line @@ -1347,16 +1639,36 @@ class LVFormatter { { // Note: provided 'interval' is no more used int maxWidth = getCurrentLineWidth(); + int rightIndent = 0; + if ( m_para_dir_is_rtl ) { + rightIndent = x; + maxWidth -= x; // put x/first char indent on the right: reduce width + x = getCurrentLineX(); // use shift induced by left floats + } + else { + x += getCurrentLineX(); // add shift induced by left floats + } //int w0 = start>0 ? m_widths[start-1] : 0; int align = para->flags & LTEXT_FLAG_NEWLINE; TR("addLine(%d, %d) y=%d align=%d", start, end, m_y, align); // printf("addLine(%d, %d) y=%d align=%d maxWidth=%d\n", start, end, m_y, align, maxWidth); + // For some reason, text_align_last inheritance is not ensured in lvrend.cpp, + // may be to be able to kill justification for the last (or a single) line as + // easily as what follows below. + // Here, text_align_last = 0 when it has not explicitely been set by the style + // of the erm_final node. int text_align_last = (para->flags >> LTEXT_LAST_LINE_ALIGN_SHIFT) & LTEXT_FLAG_NEWLINE; if ( last && !first && align==LTEXT_ALIGN_WIDTH && text_align_last!=0 ) align = text_align_last; - else if ( align==LTEXT_ALIGN_WIDTH && last ) + else if ( align==LTEXT_ALIGN_WIDTH && last ) { + // text-align-last: not specified, justification is in use, and this line + // is the last (or a single line): align it to the left. align = LTEXT_ALIGN_LEFT; + // Unless fribidi detected this paragraph is RTL: align it to the right + if ( m_para_dir_is_rtl ) + align = LTEXT_ALIGN_RIGHT; + } if ( preFormattedOnly || !align ) align = LTEXT_ALIGN_LEFT; @@ -1372,6 +1684,174 @@ class LVFormatter { align = last_align; } + bool trustDirection = false; + bool lineIsBidi = false; + #if (USE_FRIBIDI==1) + trustDirection = true; + bool restore_last_width = false; + int last_width_to_restore; + if (m_has_bidi) { + // We don't want to mess too much with the follow up code, so we + // do the following, which might be expensive for full RTL documents: + // we just reorder all chars, flags, width and references to + // the original nodes, according to how fribidi decides the visual + // order of chars should be. + // We can mess with the m_* arrays (the range that spans the current + // line) as they won't be used anymore after this function. + // Except for the width of the last char (that we may modify + // while zeroing the widths of collapsed spaces) that will be + // used as the starting width of next line. We'll restore it + // when done with this line. + last_width_to_restore = m_widths[end-1]; + restore_last_width = true; + + // From fribidi documentation: + // fribidi_reorder_line() reorders the characters in a line of text + // from logical to final visual order. Note: + // - the embedding levels may change a bit + // - the bidi types and embedding levels are not reordered + // - last parameter is a map of string indices which is reordered to + // reflect where each glyph ends up + // + // For re-ordering, we need some temporary buffers. + // We use static buffers, and don't bother with dynamic buffers + // in case we would overflow the static buffers. + // (4096, if some glyphs spans 4 composing unicode codepoints, would + // make 1000 glyphs, which with a small font of width 4px, would + // allow them to be displayed on a 4000px screen. + // Increase that if not enough.) + #define MAX_LINE_SIZE 4096 + if ( end-start > MAX_LINE_SIZE ) { + // Show a warning and truncate to avoid a segfault. + printf("CRE WARNING: bidi processing line overflow (%d > %d)\n", end-start, MAX_LINE_SIZE); + end = start + MAX_LINE_SIZE; + } + static lChar16 bidi_tmp_text[MAX_LINE_SIZE]; + static lUInt16 bidi_tmp_flags[MAX_LINE_SIZE]; + static src_text_fragment_t * bidi_tmp_srcs[MAX_LINE_SIZE]; + static lUInt16 bidi_tmp_charindex[MAX_LINE_SIZE]; + static int bidi_tmp_widths[MAX_LINE_SIZE]; + // Map of string indices which is reordered to reflect where each + // glyph ends up. Note that fribidi will access it starting + // from 0 (and not from 'start'): this would need us to allocate + // it the size of the full m_text (instead of MAX_LINE_SIZE)! + // But we can trick that by providing a fake start address, + // shifted by 'start' (which is ugly and could cause a segfault + // if some other part than [start:end] would be accessed, but + // we know fribid doesn't - by contract as it shouldn't reorder + // any other part except between start:end). + static FriBidiStrIndex bidi_indices_map[MAX_LINE_SIZE]; + for (int i=start; i 1) { + lineIsBidi = true; + // bidi_tmp_* will contain things in the visual order, from which + // we will make words (exactly as if it had been LTR that way) + for (int i=start; i 0 ? m_widths[j-1] : 0); + // todo: we should probably also need to update/move the + // LCHAR_IS_CLUSTER_TAIL flag... haven't really checked + // (might be easier or harder due to the fact that we + // don't use FRIBIDI_FLAG_REORDER_NSM?) + } + + // It looks like fribidi is quite good enough at taking + // care of collapsed spaces! No real extra space seen + // when testing, except at start and end. + // Anyway, we handle collapsed spaces and their widths + // as we would expect them to be with LTR text just out + // of copyText(). + bool prev_was_space = true; // start as true to make leading spaces collapsed + int prev_non_collapsed_space = -1; + int w = start > 0 ? m_widths[start-1] : 0; + for (int i=start; iflags & LTEXT_FLAG_PREFORMATTED) ) { + prev_was_space = false; + prev_non_collapsed_space = -1; + m_flags[i] &= ~LCHAR_IS_COLLAPSED_SPACE; + } + else { + if ( m_text[i] == ' ' ) { + if (prev_was_space) { + m_flags[i] |= LCHAR_IS_COLLAPSED_SPACE; + // Put this (now collapsed, but possibly previously non-collapsed) + // space width on the preceeding now non-collapsed space + int w_orig = bidi_tmp_widths[bidx]; + bidi_tmp_widths[bidx] = 0; + if ( prev_non_collapsed_space >= 0 ) { + m_widths[prev_non_collapsed_space] += w_orig; + w += w_orig; + } + } + else { + m_flags[i] &= ~LCHAR_IS_COLLAPSED_SPACE; + prev_was_space = true; + prev_non_collapsed_space = i; + } + } + else { + prev_was_space = false; + prev_non_collapsed_space = -1; + m_flags[i] &= ~LCHAR_IS_COLLAPSED_SPACE; + } + } + w += bidi_tmp_widths[bidx]; + m_widths[i] = w; + // printf("%x:f%x,w%d ", m_text[i], m_flags[i], m_widths[i]); + } + // Also flag as collapsed the trailing spaces on the reordered line + if (prev_non_collapsed_space >= 0) { + int prev_width = prev_non_collapsed_space > 0 ? m_widths[prev_non_collapsed_space-1] :0 ; + for (int i=prev_non_collapsed_space; it.text, so FreeType and HarfBuzz will + // get the text in logical order (as HarfBuzz expects it). + // Also, when parens/brackets are involved in RTL text, only HarfBuzz + // will correctly mirror them. When not using Harfbuzz, we'll mirror + // mirrorable chars below when a word is RTL. + } + #endif + int lastnonspace = 0; if ( align==LTEXT_ALIGN_WIDTH || splitBySpaces ) { for ( int i=start; iflags |= LTEXT_LINE_IS_BIDI; + } + if ( m_para_dir_is_rtl ) { + frmline->flags |= LTEXT_LINE_PARA_IS_RTL; + // Not used yet, but might be useful (we may have a bidi line + // in a LTR paragraph). + } + // Ignore space at start of line (this rarely happens, as line // splitting discards the space on which a split is made - but it // can happen in other rare wrap cases like lastDeprecatedWrap) @@ -1451,6 +1942,11 @@ class LVFormatter { bool isSpace = false; //bool nextIsSpace = false; bool space = false; + // Bidi + bool lastIsRTL = false; + bool isRTL = false; + // Ignorables + bool isToIgnore = false; for ( int i=start; i<=end; i++ ) { // loop thru each char src_text_fragment_t * newSrc = iwstart && (newSrc!=lastSrc || space || lastWord || isCJKIdeograph(m_text[i])) ) { + if ( i>wstart && ( newSrc!=lastSrc + || space + || lastWord + || isCJKIdeograph(m_text[i]) + || isRTL != lastIsRTL + || isToIgnore + ) ) { // New HTML source node, space met just before, last word, or CJK char: // create and add new word with chars from wstart to i-1 @@ -1523,9 +2027,10 @@ class LVFormatter { // but they will be drawn as a space by Draw(). We need // to increment the start index into the src_text_fragment_t // for Draw() to start rendering the text from this position. - // Also skip floating nodes + // Also skip floating nodes and chars flagged as to be ignored. while (wstart < i) { if ( !(m_flags[wstart] & LCHAR_IS_COLLAPSED_SPACE) && + !(m_flags[wstart] & LCHAR_IS_TO_IGNORE) && !(m_srcs[wstart]->flags & LTEXT_SRC_IS_FLOAT) ) break; // printf("_"); // to see when we remove one, before the TR() below @@ -1545,6 +2050,14 @@ class LVFormatter { if (lastWord && frmline->word_count == 0) { if (!isLastPara) { wstart--; // make a single word with a single collapsed space + if (m_flags[wstart] & LCHAR_IS_TO_IGNORE) { + // In this (edgy) case, we would be rendering this char we + // want to ignore. + // This is a bit hacky, but no other solution: just + // replace that ignorable char with a space in the + // src text + *((lChar16 *) (m_srcs[wstart]->t.text + m_charindex[wstart])) = L' '; + } } else { // Last or single para with no word // A line has already been added: just make @@ -1559,6 +2072,7 @@ class LVFormatter { // no word made, get ready for next loop lastSrc = newSrc; lastIsSpace = isSpace; + lastIsRTL = isRTL; continue; } } @@ -1651,15 +2165,58 @@ class LVFormatter { word->x = frmline->width; word->flags = 0; - word->t.start = m_charindex[wstart]; - word->t.len = i - wstart; + + // For Harfbuzz, which may shape differently words at start or end of paragraph + if (first && frmline->word_count == 1) // first line of paragraph + first word of line + word->flags |= LTEXT_WORD_BEGINS_PARAGRAPH; + if (last && lastWord) // last line of paragraph + last word of line + word->flags |= LTEXT_WORD_ENDS_PARAGRAPH; + + if ( trustDirection) + word->flags |= LTEXT_WORD_DIRECTION_KNOWN; + if ( !m_has_bidi ) { + // No bidi, everything is linear + word->t.start = m_charindex[wstart]; + word->t.len = i - wstart; + } + else if ( m_flags[wstart] & LCHAR_IS_RTL ) { + // Bidi and first char RTL. + // As we split on bidi level change, the full word is RTL. + // As we split on src text fragment, we are sure all chars + // are in the same text node. + // charindex may have been reordered, and may not be sync'ed with wstart/i-1, + // but it is linearly decreasing between i-1 and wstart + word->t.start = m_charindex[i-1]; + word->t.len = m_charindex[wstart] - m_charindex[i-1] + 1; + word->flags |= LTEXT_WORD_DIRECTION_IS_RTL; // Draw glyphs in reverse order + #if (USE_FRIBIDI==1) + // If not using Harfbuzz, procede to mirror parens & al (don't + // do that if Harfbuzz is used, as it does that by itself, and + // would mirror back our mirrored chars!) + if ( font->getKerningMode() != KERNING_MODE_HARFBUZZ) { + lChar16 * str = (lChar16*)(srcline->t.text + word->t.start); + FriBidiChar mirror; + for (int i=0; i < word->t.len; i++) { + if ( fribidi_get_mirror_char( (FriBidiChar)(str[i]), &mirror) ) + str[i] = (lChar16)mirror; + } + } + #endif + } + else { + // Bidi and first char LTR. Same comments as above, except for last one: + // it is linearly increasing between wstart and i-1 + word->t.start = m_charindex[wstart]; + word->t.len = m_charindex[i-1] + 1 - m_charindex[wstart]; + } + word->width = m_widths[i>0 ? i-1 : 0] - (wstart>0 ? m_widths[wstart-1] : 0); word->min_width = word->width; TR("addLine - word(%d, %d) x=%d (%d..%d)[%d] |%s|", wstart, i, frmline->width, wstart>0 ? m_widths[wstart-1] : 0, m_widths[i-1], word->width, LCSTR(lString16(m_text+wstart, i-wstart))); // lChar16 lastch = m_text[i-1]; // if ( lastch==UNICODE_NO_BREAK_SPACE ) // CRLog::trace("last char is UNICODE_NO_BREAK_SPACE"); - if ( m_flags[wstart] & LCHAR_IS_LIGATURE_TAIL ) { + if ( m_flags[wstart] & LCHAR_IS_CLUSTER_TAIL ) { // The start of this word is part of a ligature that started // in a previous word: some hyphenation wrap happened on // this ligature, which will not be rendered as such. @@ -1673,7 +2230,7 @@ class LVFormatter { } } if ( m_flags[i-1] & LCHAR_ALLOW_HYPH_WRAP_AFTER ) { - if ( m_flags[i] & LCHAR_IS_LIGATURE_TAIL ) { + if ( m_flags[i] & LCHAR_IS_CLUSTER_TAIL ) { // The end of this word is part of a ligature that, because // of hyphenation, has been splitted onto next word. // We are the first part of the hyphenated word, and @@ -1858,12 +2415,17 @@ class LVFormatter { wstart = i; } lastIsSpace = isSpace; + lastIsRTL = isRTL; } - alignLine( frmline, align ); + alignLine( frmline, align, rightIndent ); m_y += frmline->height; m_pbuffer->height = m_y; checkOngoingFloat(); positionDelayedFloats(); + #if (USE_FRIBIDI==1) + if ( restore_last_width ) // bidi: restore last width to not mess with next line + m_widths[end-1] = last_width_to_restore; + #endif } int getMaxCondensedSpaceTruncation(int pos) { @@ -1994,6 +2556,10 @@ class LVFormatter { int minWidth = 3 * m_pbuffer->strut_height; for (;pos=0 ? (pos==0 ? indent : 0) : (pos==0 ? 0 : -indent); int w0 = pos>0 ? m_widths[pos-1] : 0; int i; @@ -2011,6 +2577,16 @@ class LVFormatter { // and given it's only about italic chars, and that we would need to remove // stuff in getRenderedWidths... letting it as it is.) + if ( m_has_bidi ) { + // If bidi, our first char may be no more the first char + // inside AddLine, so reset firtCharMargin to 0. + firstCharMargin = 0; + // todo: probably some other things to avoid if bidi or + // if m_para_dir_is_rtl, like hyphenation. + // Also possible: scan chars as they fit on this line for + // bidi level > 1: if none, this line is pure LTR + } + maxWidth = getCurrentLineWidth(); if (maxWidth <= minWidth) { // Find y with available minWidth @@ -2066,7 +2642,7 @@ class LVFormatter { } continue; } - lUInt8 flags = m_flags[i]; + lUInt16 flags = m_flags[i]; if ( m_text[i]=='\n' ) { lastMandatoryWrap = i; break; @@ -2167,6 +2743,7 @@ class LVFormatter { // If, with normal wrapping, more than 5% of line is occupied by // spaces, try to find a word (after where we stopped) to hyphenate, // if hyphenation is not forbidden by CSS. + // todo: decide if we should hyphenate if bidi is happening up to now if ( lastMandatoryWrap<0 && lastNormalWrap 5 && !(m_srcs[wordpos]->flags & LTEXT_SRC_IS_OBJECT) && (m_srcs[wordpos]->flags & LTEXT_HYPHENATE) ) { // hyphenate word @@ -2193,7 +2770,10 @@ class LVFormatter { if ( start=lastNormalWrap && len>=MIN_WORD_LEN_TO_HYPHENATE ) { if ( len > MAX_WORD_SIZE ) len = MAX_WORD_SIZE; - lUInt8 * flags = m_flags + start; + // HyphMan::hyphenate(), which is used by some other parts of the code, + // expects a lUInt8 array. We added flagSize=1|2 so it can set the correct + // flags on our upgraded (from lUInt8 to lUInt16) m_flags. + lUInt8 * flags = (lUInt8*) (m_flags + start); static lUInt16 widths[MAX_WORD_SIZE]; int wordStart_w = start>0 ? m_widths[start-1] : 0; for ( int i=0; it.font)->getHyphenWidth(); - if ( HyphMan::hyphenate(m_text+start, len, widths, flags, _hyphen_width, max_width) ) { + if ( HyphMan::hyphenate(m_text+start, len, widths, flags, _hyphen_width, max_width, 2) ) { for ( int i=0; imax_width ) { @@ -2294,6 +2874,7 @@ class LVFormatter { // that, we'll be ignoring multiple stuck OBJECTs at end of line. // So, not touching it... } + // todo: probably need be avoided if bidi/rtl: int dw = lastnonspace>=start ? getAdditionalCharWidth(lastnonspace, lastnonspace+1) : 0; // If we ended the line with some hyphenation, no need to account for italic // right side bearing overflow, as the last glyph will be an hyphen. @@ -2304,7 +2885,6 @@ class LVFormatter { m_widths[lastnonspace] += dw; } if (endp>m_length) endp=m_length; - x += getCurrentLineX(); // add shift induced by left floats addLine(pos, endp, x + firstCharMargin, para, interval, pos==0, wrapPos>=m_length-1, preFormattedOnly, needReduceSpace, isLastPara); pos = wrapPos + 1; } @@ -2371,6 +2951,14 @@ class LVFormatter { m_srcs = NULL; m_charindex = NULL; m_widths = NULL; + #if (USE_FRIBIDI==1) + free( m_bidi_ctypes ); + free( m_bidi_btypes ); + free( m_bidi_levels ); + m_bidi_ctypes = NULL; + m_bidi_btypes = NULL; + m_bidi_levels = NULL; + #endif m_staticBufs = true; // printf("freeing dynamic buffers\n"); } @@ -2580,6 +3168,12 @@ void LFormattedText::Draw( LVDrawBuf * buf, int x, int y, ldomMarkedRangeList * //buf->FillRect( x+frmline->x, y + frmline->y, x+frmline->x + frmline->width, y + frmline->y + frmline->height, bgcl ); // draw background for each word + // (if multiple consecutive words share the same bgcolor, this will + // actually fill a single rect encompassing these words) + // todo: the way background color (not inherited in lvrend.cpp) is + // handled here (only looking at the style of the inline node + // that contains the word, and not at its other inline parents), + // some words may not get their proper bgcolor lUInt32 lastWordColor = 0xFFFFFFFF; int lastWordStart = -1; int lastWordEnd = -1; @@ -2671,9 +3265,21 @@ void LFormattedText::Draw( LVDrawBuf * buf, int x, int y, ldomMarkedRangeList * else { bool flgHyphen = false; - if ( (j==frmline->word_count-1) && - (word->flags<EXT_WORD_CAN_HYPH_BREAK_LINE_AFTER)) - flgHyphen = true; + if ( word->flags<EXT_WORD_CAN_HYPH_BREAK_LINE_AFTER) { + if (j==frmline->word_count-1) + flgHyphen = true; + // Also do that even if it's not the last word in the line + // AND the line is bidi: the hyphen may be in the middle of + // the text, but it's fine for some people with bidi, see + // conversation "Bidi reordering of soft hyphen" at: + // https://unicode.org/pipermail/unicode/2014-April/thread.html#348 + // If that's not desirable, just disable hyphenation lookup + // in processParagraph() if m_has_bidi or if chars found in + // line span multilple bidi levels (so that we don't get + // a blank space for a hyphen not drawn after this word). + else if (frmline->flags & LTEXT_LINE_IS_BIDI) + flgHyphen = true; + } srcline = &m_pbuffer->srctext[word->src_text_index]; font = (LVFont *) srcline->t.font; str = srcline->t.text + word->t.start; @@ -2704,6 +3310,10 @@ void LFormattedText::Draw( LVDrawBuf * buf, int x, int y, ldomMarkedRangeList * buf->SetTextColor( cl ); if ( bgcl!=0xFFFFFFFF ) buf->SetBackgroundColor( bgcl ); + // Add drawing flags: text decoration (underline...) + lUInt32 drawFlags = srcline->flags & LTEXT_TD_MASK; + // and chars direction, and if word begins or ends paragraph (for Harfbuzz) + drawFlags |= WORD_FLAGS_TO_FNT_FLAGS(word->flags); font->DrawTextString( buf, x + frmline->x + word->x, @@ -2713,7 +3323,7 @@ void LFormattedText::Draw( LVDrawBuf * buf, int x, int y, ldomMarkedRangeList * '?', NULL, flgHyphen, - srcline->flags & 0x0F00, + drawFlags, srcline->letter_spacing, word->width, text_decoration_back_gap); @@ -2814,6 +3424,7 @@ void LFormattedText::Draw( LVDrawBuf * buf, int x, int y, ldomMarkedRangeList * DrawDocument( *buf, node, x0, y0, dx, dy, doc_x, doc_y, page_height, absmarks, bookmarks ); } } + delete absmarks; } #endif diff --git a/crengine/src/lvtinydom.cpp b/crengine/src/lvtinydom.cpp index ec1a53c1f..dace54fa3 100644 --- a/crengine/src/lvtinydom.cpp +++ b/crengine/src/lvtinydom.cpp @@ -67,7 +67,7 @@ int gDOMVersionRequested = DOM_VERSION_CURRENT; /// change in case of incompatible changes in swap/cache file format to avoid using incompatible swap file // increment to force complete reload/reparsing of old file -#define CACHE_FILE_FORMAT_VERSION "3.05.26k" +#define CACHE_FILE_FORMAT_VERSION "3.05.27k" /// increment following value to force re-formatting of old book after load #define FORMATTING_VERSION_ID 0x001B @@ -5392,7 +5392,7 @@ void ldomNode::initNodeRendMethod() // (If new floats appear after loading, we won't render well, but // a style hash mismatch will happen and the user will be // suggested to reload the book with cache cleaned.) - if ( this->getDocument()->_cacheFile == NULL ) { + if ( parent && this->getDocument()->_cacheFile == NULL ) { // Replace this element with a floatBox in its parent children collection, // and move it inside, as the single child of this floatBox. int pos = getNodeIndex(); @@ -5732,7 +5732,7 @@ ldomNode * ldomDocumentWriter::OnTagOpen( const lChar16 * nsname, const lChar16 // if we see a BODY coming and we are a DocFragment, its time to apply the // styles set to the DocFragment before switching to BODY (so the styles can // be applied to BODY) - if (id == el_body && _currNode->_element->getNodeId() == el_DocFragment) { + if (id == el_body && _currNode && _currNode->_element->getNodeId() == el_DocFragment) { _currNode->_stylesheetIsSet = _currNode->getElement()->applyNodeStylesheet(); // _stylesheetIsSet will be used to pop() the stylesheet when // leaving/destroying this DocFragment ldomElementWriter @@ -6495,9 +6495,12 @@ ldomXPointer ldomDocument::createXPointer( lvPoint pt, int direction, bool stric return ptr; } } + bool line_is_bidi = frmline->flags & LTEXT_LINE_IS_BIDI; for ( int w=0; wwords[w]; - if ( x < word->x + word->width || w==wc-1 ) { + if ( ( !line_is_bidi && x < word->x + word->width ) || + ( line_is_bidi && x >= word->x && x < word->x + word->width ) || + ( w == wc-1 ) ) { const src_text_fragment_t * src = txtform->GetSrcInfo(word->src_text_index); // CRLog::debug(" word found [%d]: x=%d..%d, start=%d, len=%d %08X", // w, word->x, word->x + word->width, word->t.start, word->t.len, src->object); @@ -6543,14 +6546,30 @@ ldomXPointer ldomDocument::createXPointer( lvPoint pt, int direction, bool stric break; } - font->measureText( str.c_str()+word->t.start, word->t.len, width, flg, word->width+50, '?', src->letter_spacing); - for ( int i=0; it.len; i++ ) { - int xx = ( i>0 ) ? (width[i-1] + width[i])/2 : width[i]/2; - if ( x < word->x + xx ) { - return ldomXPointer( node, src->t.offset + word->t.start + i ); + lUInt32 hints = WORD_FLAGS_TO_FNT_FLAGS(word->flags); + font->measureText( str.c_str()+word->t.start, word->t.len, width, flg, + word->width+50, '?', src->letter_spacing, false, hints); + + bool word_is_rtl = word->flags & LTEXT_WORD_DIRECTION_IS_RTL; + if ( word_is_rtl ) { + for ( int i=word->t.len-1; i>=0; i-- ) { + int xx = ( i>0 ) ? (width[i-1] + width[i])/2 : width[i]/2; + xx = word->width - xx; + if ( x < word->x + xx ) { + return ldomXPointer( node, src->t.offset + word->t.start + i ); + } + } + return ldomXPointer( node, src->t.offset + word->t.start ); + } + else { + for ( int i=0; it.len; i++ ) { + int xx = ( i>0 ) ? (width[i-1] + width[i])/2 : width[i]/2; + if ( x < word->x + xx ) { + return ldomXPointer( node, src->t.offset + word->t.start + i ); + } } + return ldomXPointer( node, src->t.offset + word->t.start + word->t.len ); } - return ldomXPointer( node, src->t.offset + word->t.start + word->t.len ); } } } @@ -6585,7 +6604,7 @@ bool ldomXPointer::getRect(lvRect & rect, bool extended, bool adjusted) const //CRLog::trace("ldomXPointer::getRect() - p==NULL"); } //printf("getRect( p=%08X type=%d )\n", (unsigned)p, (int)p->getNodeType() ); - if ( !p->getDocument() ) { + else if ( !p->getDocument() ) { //CRLog::trace("ldomXPointer::getRect() - p->getDocument()==NULL"); } ldomNode * mainNode = p->getDocument()->getRootNode(); @@ -6733,15 +6752,252 @@ bool ldomXPointer::getRect(lvRect & rect, bool extended, bool adjusted) const srcLen = lastLen; offset = lastOffset; } + + // Some state for non-linear bidi word search + int nearestForwardSrcIndex = -1; + int nearestForwardSrcOffset = -1; + lvRect bestBidiRect = lvRect(); + bool hasBestBidiRect = false; + for ( int l = 0; lGetLineCount(); l++ ) { const formatted_line_t * frmline = txtform->GetLineInfo(l); + bool line_is_bidi = frmline->flags & LTEXT_LINE_IS_BIDI; for ( int w=0; w<(int)frmline->word_count; w++ ) { const formatted_word_t * word = &frmline->words[w]; + bool word_is_rtl = word->flags & LTEXT_WORD_DIRECTION_IS_RTL; bool lastWord = (l == txtform->GetLineCount() - 1 && w == frmline->word_count - 1); + + if ( line_is_bidi ) { + // When line is bidi, src text nodes may be shuffled, so we can't + // just be done when meeting a forward src in logical order. + // We'd better have a dedicated searching code to not mess with + // the visual=logical order generic code below. + // todo: see if additional tweaks according to + // frmline->flags<EXT_LINE_PARA_IS_RTL may help adjusting + // char rects depending on it vs word_is_rtl. + if ( word->src_text_index>=srcIndex || lastWord ) { + // Found word from same or forward src line + if (word->src_text_index > srcIndex && + ( nearestForwardSrcIndex == -1 || + word->src_text_index < nearestForwardSrcIndex || + (word->src_text_index == nearestForwardSrcIndex && + word->t.start < nearestForwardSrcOffset ) ) ) { + // Found some word from a forward src that is nearest than previously found one: + // get its start as a possible best result. + bestBidiRect.top = rc.top + frmline->y; + bestBidiRect.bottom = bestBidiRect.top + frmline->height; + if ( word_is_rtl ) { + bestBidiRect.right = word->x + word->width + rc.left + frmline->x; + bestBidiRect.left = bestBidiRect.right - 1; + } + else { + bestBidiRect.left = word->x + rc.left + frmline->x; + if (extended) { + if (word->flags & LTEXT_WORD_IS_OBJECT && word->width > 0) + bestBidiRect.right = bestBidiRect.left + word->width; // width of image + else + bestBidiRect.right = bestBidiRect.left + 1; + } + } + hasBestBidiRect = true; + nearestForwardSrcIndex = word->src_text_index; + if (word->flags & LTEXT_WORD_IS_OBJECT) + nearestForwardSrcOffset = 0; + else + nearestForwardSrcOffset = word->t.start; + } + else if (word->src_text_index == srcIndex) { + // Found word in that exact source text node + if ( word->flags & LTEXT_WORD_IS_OBJECT ) { + // An image is the single thing in its srcIndex + rect.top = rc.top + frmline->y; + rect.bottom = rect.top + frmline->height; + rect.left = word->x + rc.left + frmline->x; + if (word->width > 0) + rect.right = rect.left + word->width; // width of image + else + rect.right = rect.left + 1; + return true; + } + // Target is in this text node. We may not find it part + // of a word, so look at all words and keep the nearest + // (forward if possible) in case we don't find an exact one + if ( word->t.start > offset ) { // later word in logical order + if (nearestForwardSrcIndex != word->src_text_index || + word->t.start <= nearestForwardSrcOffset ) { + bestBidiRect.top = rc.top + frmline->y; + bestBidiRect.bottom = bestBidiRect.top + frmline->height; + if ( word_is_rtl ) { // right edge of next logical word, as it is drawn on the left + bestBidiRect.right = word->x + word->width + rc.left + frmline->x; + bestBidiRect.left = bestBidiRect.right - 1; + } + else { // left edge of next logical word, as it is drawn on the right + bestBidiRect.left = word->x + rc.left + frmline->x; + bestBidiRect.right = bestBidiRect.left + 1; + } + hasBestBidiRect = true; + nearestForwardSrcIndex = word->src_text_index; + nearestForwardSrcOffset = word->t.start; + } + } + else if ( word->t.start+word->t.len <= offset ) { // past word in logical order + // Only if/while we haven't yet found one with the right src index and + // a forward offset + if (nearestForwardSrcIndex != word->src_text_index || + ( nearestForwardSrcOffset < word->t.start && + word->t.start+word->t.len > nearestForwardSrcOffset ) ) { + bestBidiRect.top = rc.top + frmline->y; + bestBidiRect.bottom = bestBidiRect.top + frmline->height; + if ( word_is_rtl ) { // left edge of previous logical word, as it is drawn on the right + bestBidiRect.left = word->x + rc.left + frmline->x; + bestBidiRect.right = bestBidiRect.left + 1; + } + else { // right edge of previous logical word, as it is drawn on the left + bestBidiRect.right = word->x + word->width + rc.left + frmline->x; + bestBidiRect.left = bestBidiRect.right - 1; + } + hasBestBidiRect = true; + nearestForwardSrcIndex = word->src_text_index; + nearestForwardSrcOffset = word->t.start+word->t.len; + } + } + else { // exact word found + // Measure word + LVFont *font = (LVFont *) txtform->GetSrcInfo(srcIndex)->t.font; + lUInt16 w[512]; + lUInt8 flg[512]; + lString16 str = node->getText(); + if (offset == word->t.start && str.empty()) { + rect.left = word->x + rc.left + frmline->x; + rect.top = rc.top + frmline->y; + rect.right = rect.left + 1; + rect.bottom = rect.top + frmline->height; + return true; + } + // We need to transform the node text as it had been when + // rendered (the transform may change chars widths) for the + // rect to be correct + switch ( node->getParentNode()->getStyle()->text_transform ) { + case css_tt_uppercase: + str.uppercase(); + break; + case css_tt_lowercase: + str.lowercase(); + break; + case css_tt_capitalize: + str.capitalize(); + break; + case css_tt_full_width: + // str.fullWidthChars(); // disabled for now in lvrend.cpp + break; + default: + break; + } + lUInt32 hints = WORD_FLAGS_TO_FNT_FLAGS(word->flags); + font->measureText( + str.c_str()+word->t.start, + word->t.len, + w, + flg, + word->width+50, + '?', + txtform->GetSrcInfo(srcIndex)->letter_spacing, + false, + hints); + rect.top = rc.top + frmline->y; + rect.bottom = rect.top + frmline->height; + // chx is the width of previous chars in the word + int chx = (offset > word->t.start) ? w[ offset - word->t.start - 1 ] : 0; + if ( word_is_rtl ) { + rect.right = word->x + word->width - chx + rc.left + frmline->x; + rect.left = rect.right - 1; + } + else { + rect.left = word->x + chx + rc.left + frmline->x; + rect.right = rect.left + 1; + } + if (extended) { // get width of char at offset + if (offset == word->t.start && word->t.len == 1) { + // With CJK chars, the measured width seems + // less correct than the one measured while + // making words. So use the calculated word + // width for one-char-long words instead + if ( word_is_rtl ) + rect.left = rect.right - word->width; + else + rect.right = rect.left + word->width; + } + else { + int chw = w[ offset - word->t.start ] - chx; + bool hyphen_added = false; + if ( offset == word->t.start + word->t.len - 1 + && (word->flags & LTEXT_WORD_CAN_HYPH_BREAK_LINE_AFTER) + && !gFlgFloatingPunctuationEnabled ) { + // if offset is the end of word, and this word has + // been hyphenated, includes the hyphen width + // (but not when floating punctuation is enabled, + // to keep nice looking rectangles on multi lines + // text selection) + chw += font->getHyphenWidth(); + // We then should not account for the right side + // bearing below + hyphen_added = true; + } + if ( word_is_rtl ) + rect.left = rect.right - chw; + else + rect.right = rect.left + chw; + if (adjusted) { + // Extend left or right if this glyph overflows its + // origin/advance box (can happen with an italic font, + // or with a regular font on the right of the letter 'f' + // or on the left of the letter 'J'). + // Only when negative (overflow) and not when positive + // (which are more frequent), mostly to keep some good + // looking rectangles on the sides when highlighting + // multiple lines. + rect.left += font->getLeftSideBearing(str[offset], true); + if ( !hyphen_added ) + rect.right -= font->getRightSideBearing(str[offset], true); + // Should work wheter rtl or ltr + } + } + // Ensure we always return a non-zero width, even for zero-width + // chars or collapsed spaces (to avoid isEmpty() returning true + // which could be considered as a failure) + if ( rect.right <= rect.left ) { + if ( word_is_rtl ) + rect.left = rect.right - 1; + else + rect.right = rect.left + 1; + } + } + return true; + } + } + if ( lastWord ) { + // If no exact word found, return best candidate + if (hasBestBidiRect) { + rect = bestBidiRect; + return true; + } + // Otherwise, return end of last word (?) + rect.top = rc.top + frmline->y; + rect.bottom = rect.top + frmline->height; + rect.left = word->x + rc.left + frmline->x + word->width; + rect.right = rect.left + 1; + return true; + } + } + continue; + } // end if line_is_bidi + + // ================================ + // Generic code when visual order = logical order if ( word->src_text_index>=srcIndex || lastWord ) { // found word from same src line - if ( word->flags == LTEXT_WORD_IS_OBJECT + if ( word->flags & LTEXT_WORD_IS_OBJECT || word->src_text_index > srcIndex || (!extended && offset <= word->t.start) || (extended && offset < word->t.start) @@ -6752,13 +7008,15 @@ bool ldomXPointer::getRect(lvRect & rect, bool extended, bool adjusted) const rect.left = word->x + rc.left + frmline->x; //rect.top = word->y + rc.top + frmline->y + frmline->baseline; rect.top = rc.top + frmline->y; - if (extended) - if (word->flags == LTEXT_WORD_IS_OBJECT) + if (extended) { + if (word->flags & LTEXT_WORD_IS_OBJECT && word->width > 0) rect.right = rect.left + word->width; // width of image else rect.right = rect.left + 1; // not the right word: no char width - else + } + else { rect.right = rect.left + 1; + } rect.bottom = rect.top + frmline->height; return true; } else if ( (offset < word->t.start+word->t.len) @@ -6804,6 +7062,7 @@ bool ldomXPointer::getRect(lvRect & rect, bool extended, bool adjusted) const default: break; } + lUInt32 hints = WORD_FLAGS_TO_FNT_FLAGS(word->flags); font->measureText( str.c_str()+word->t.start, word->t.len, @@ -6811,7 +7070,9 @@ bool ldomXPointer::getRect(lvRect & rect, bool extended, bool adjusted) const flg, word->width+50, '?', - txtform->GetSrcInfo(srcIndex)->letter_spacing); + txtform->GetSrcInfo(srcIndex)->letter_spacing, + false, + hints ); // chx is the width of previous chars in the word int chx = (offset > word->t.start) ? w[ offset - word->t.start - 1 ] : 0; rect.left = word->x + chx + rc.left + frmline->x; @@ -6856,6 +7117,11 @@ bool ldomXPointer::getRect(lvRect & rect, bool extended, bool adjusted) const rect.right -= font->getRightSideBearing(str[offset], true); } } + // Ensure we always return a non-zero width, even for zero-width + // chars or collapsed spaces (to avoid isEmpty() returning true + // which could be considered as a failure) + if ( rect.right <= rect.left ) + rect.right = rect.left + 1; } else rect.right = rect.left + 1; @@ -7777,6 +8043,14 @@ void ldomXRangeList::getRanges( ldomMarkedRangeList &dst ) lvPoint ptEnd = range->getEnd().toPoint(); // // LVE:DEBUG // CRLog::trace("selectRange( %d,%d : %d,%d : %s, %s )", ptStart.x, ptStart.y, ptEnd.x, ptEnd.y, LCSTR(range->getStart().toString()), LCSTR(range->getEnd().toString()) ); + if ( ptStart.y > ptEnd.y || ( ptStart.y == ptEnd.y && ptStart.x >= ptEnd.x ) ) { + // Swap ptStart and ptEnd if coordinates seems inverted (or we would + // get item->empty()), which is needed for bidi/rtl. + // Hoping this has no side effect. + lvPoint ptTmp = ptStart; + ptStart = ptEnd; + ptEnd = ptTmp; + } ldomMarkedRange * item = new ldomMarkedRange( ptStart, ptEnd, range->getFlags() ); if ( !item->empty() ) dst.add( item ); @@ -7901,6 +8175,11 @@ void ldomXRange::getSegmentRects( LVArray & rects ) // smaller font size - but using ldomXRange.getRectEx() on multiple // text nodes gives wrong rects for the last chars on a line...) + // Note: someRect.extend(someOtherRect) and !someRect.isEmpty() expect + // a rect to have both width and height non-zero. So, make sure + // in getRectEx() that we always get a rect of width at least 1px, + // otherwise some lines may not be highlighted. + // Note: the range end offset is NOT part of the range (it points to the // char after, or last char + 1 if it includes the whole text node text) ldomXPointerEx rangeEnd = getEnd(); @@ -9767,7 +10046,15 @@ void ldomDocumentFragmentWriter::OnAttribute( const lChar16 * nsname, const lCha parent->OnAttribute(nsname, attrname, attrvalue); } } else { - if ( styleDetectionState ) { + if (insideHtmlTag) { + // Grab attributes from (not included in the DOM) + // to reinject them in + if ( !lStr_cmp(attrname, "dir") ) + htmlDir = attrvalue; + else if ( !lStr_cmp(attrname, "lang") ) + htmlLang = attrvalue; + } + else if ( styleDetectionState ) { if ( !lStr_cmp(attrname, "rel") && lString16(attrvalue).lowercase() == L"stylesheet" ) styleDetectionState |= 2; else if ( !lStr_cmp(attrname, "type") ) { @@ -9803,8 +10090,13 @@ ldomNode * ldomDocumentFragmentWriter::OnTagOpen( const lChar16 * nsname, const } else { if ( !lStr_cmp(tagname, "link") ) styleDetectionState = 1; - if ( !lStr_cmp(tagname, "style") ) + else if ( !lStr_cmp(tagname, "style") ) headStyleState = 1; + else if ( !lStr_cmp(tagname, "html") ) { + insideHtmlTag = true; + htmlDir.clear(); + htmlLang.clear(); + } } // When meeting the of each of an EPUB's embedded HTML files, @@ -9839,6 +10131,11 @@ ldomNode * ldomDocumentFragmentWriter::OnTagOpen( const lChar16 * nsname, const } if ( !codeBasePrefix.empty() ) // add attribute OnAttribute(L"", L"id", codeBasePrefix.c_str() ); + if ( !htmlDir.empty() ) // add attribute tag + parent->OnAttribute(L"", L"dir", htmlDir.c_str() ); + if ( !htmlLang.empty() ) // add attribute tag + parent->OnAttribute(L"", L"lang", htmlLang.c_str() ); + parent->OnTagBody(); // inside if ( !headStyleText.empty() || stylesheetLinks.length() > 0 ) { // add stylesheet element as child of : @@ -9902,6 +10199,9 @@ void ldomDocumentFragmentWriter::OnTagBody() if ( insideTag ) { parent->OnTagBody(); } + else if ( insideHtmlTag ) { + insideHtmlTag = false; + } if ( styleDetectionState == 11 ) { // incomplete ; assuming type="text/css" if ( !stylesheetFile.empty() ) @@ -10244,7 +10544,8 @@ void ldomDocumentWriterFilter::OnTagClose( const lChar16 * /*nsname*/, const lCh lUInt16 id = _document->getElementNameIndex(tagname); // HTML title detection - if ( id==el_title && _currNode->_element->getParentNode()!= NULL && _currNode->_element->getParentNode()->getNodeId()==el_head ) { + if ( id==el_title && _currNode && _currNode->_element && _currNode->_element->getParentNode() != NULL + && _currNode->_element->getParentNode()->getNodeId() == el_head ) { lString16 s = _currNode->_element->getText(); s.trim(); if ( !s.empty() ) { @@ -13364,6 +13665,10 @@ bool ldomNode::getNodeListMarker( int & counterValue, lString16 & marker, int & css_style_ref_t cs = child->getStyle(); if ( cs.isNull() ) continue; + if ( cs->display!=css_d_list_item_block && cs->display!=css_d_list_item) { + // Alien element among list item nodes, skip it to not mess numbering + continue; + } switch ( cs->list_style_type ) { case css_lst_decimal: case css_lst_lower_roman: