diff --git a/crengine/include/lvxml.h b/crengine/include/lvxml.h index 5d549bfc2..9a4175d96 100644 --- a/crengine/include/lvxml.h +++ b/crengine/include/lvxml.h @@ -394,6 +394,7 @@ class LVXMLParser : public LVTextFileBase LVXMLParserCallback * m_callback; int m_state; bool m_in_cdata; + bool m_in_html_style_tag; bool m_in_html_script_tag; bool m_trimspaces; bool SkipSpaces(); diff --git a/crengine/src/lvimg.cpp b/crengine/src/lvimg.cpp index b89e79c8b..708c3bb95 100644 --- a/crengine/src/lvimg.cpp +++ b/crengine/src/lvimg.cpp @@ -2078,6 +2078,9 @@ static bool lunasvgDrawImageHelper(lunasvg::external_context_t * xcontext, const if ( doc ) { ldomNode * node = ((LVNodeImageSource *)xcontext->external_object)->GetSourceNode(); img = doc->getObjectImageSource(Utf8ToUnicode(url), node); + if (img.isNull()) { // url may be url-encoded + img = doc->getObjectImageSource(DecodeHTMLUrlString(Utf8ToUnicode(url)), node); + } } else { // We may be used by frontends without a ldomDocument to render SVG, and diff --git a/crengine/src/lvstsheet.cpp b/crengine/src/lvstsheet.cpp index ee004f521..4407e227b 100644 --- a/crengine/src/lvstsheet.cpp +++ b/crengine/src/lvstsheet.cpp @@ -1966,6 +1966,14 @@ class AtRuleLogicalConditionParser { continue; } if ( *str == ')' ) { + if ( level <= 0 ) { + // Closing ')' for a '(' we didn't account for: possibly + // buggy CSS, or we stumbled on some unsupported syntax + // using parens that we didn't parse well + malformed = true; + skip_to_next( str, 0, '{' ); + continue; + } leaveLevel(); str++; continue; @@ -2000,15 +2008,112 @@ class AtRuleLogicalConditionParser { }; // https://drafts.csswg.org/css-conditional/#at-supports +// https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@supports class AtSupportsLogicalConditionParser : public AtRuleLogicalConditionParser { public: AtSupportsLogicalConditionParser(lxmlDocBase * d) : AtRuleLogicalConditionParser(d, '{', 0) {} protected: virtual void parseCondition(const char * &str) { - // Use our regular declaration parser to see if supported - LVCssDeclaration tmp_decl; - setResult( tmp_decl.parseAndCheckIfSupported( str, doc ) ); + skip_spaces( str ); + // We may meet either some declaration: "property: value", + // or some function: "selector(a > b)". + // Actually, by specs, a declaration must be enclosed in (...), + // but a function may, as parens can also be used for grouping with + // logical operators. They are handled in AtRuleLogicalConditionParser:parse() + // as such grouping. + // So, we should try here to guess what kind it is. + const char * orig_pos = str; + char ident[16]; + if ( !parse_ident(str, ident, 16) ) { + malformed = true; + skip_to_next( str, 0, '{' ); + return; + } + skip_spaces( str ); + if ( *str != '(' ) { // It's not a function + str = orig_pos; + // Use our regular declaration parser to see if supported + LVCssDeclaration tmp_decl; + setResult( tmp_decl.parseAndCheckIfSupported( str, doc ) ); + return; + } + str++; // skip '(' - we'll have to consume the follow up ')' below + skip_spaces( str ); + lString8 name(ident); + name.lowercase(); + setResult(false); + if ( name == "selector" ) { + // Use our LVCssSelector parser, with for_functional_pseudo_class=true, + // which will have it stop on a ')' (but also on a ',' which is not + // per specs, and should not happen, so this should do) + LVCssSelector tmp_selector; + if ( tmp_selector.parse(str, doc, false, true) ) { + skip_spaces( str ); // should have stopped at a ')' + if ( *str == ')' ) { + str++; + setResult(true); + } + else { + malformed = true; + skip_to_next( str, 0, '{' ); + } + } + else { + skip_to_next( str, ')', 0 ); + } + } + else if ( name == "font-tech" ) { + // We don't support any (even if we do support some OpenType features + // with Harfbuzz, we don't support (yet) the CSS property 'font-feature', + // which is probably what would be used in a declaration following + // such a "@font-tech(features-opentype) {...}" + skip_to_next( str, ')', 0 ); + } + else if ( name == "font-format" ) { + // Not sure we support "collection" (.otc, .ttc) for embedded fonts + if ( substr_icompare( "opentype", str ) || + substr_icompare( "truetype", str ) || + substr_icompare( "woff", str ) || + substr_icompare( "woff2", str ) ) { + skip_spaces( str ); + if ( *str == ')' ) { + str++; + setResult(true); + } + else { + skip_to_next( str, ')', 0 ); + } + } + else { + malformed = true; + skip_to_next( str, 0, '{' ); + } + } + else if ( name == "at-rule" ) { + // We only really support a few + if ( substr_icompare( "@media", str ) || + substr_icompare( "@import", str ) || + substr_icompare( "@font-face", str ) || + substr_icompare( "@supports", str ) ) { + skip_spaces( str ); + if ( *str == ')' ) { + str++; + setResult(true); + } + else { + skip_to_next( str, ')', 0 ); + } + } + else { + malformed = true; + skip_to_next( str, 0, '{' ); + } + } + else { // Not a known function + malformed = true; + skip_to_next( str, 0, '{' ); + } } }; diff --git a/crengine/src/lvtextfm.cpp b/crengine/src/lvtextfm.cpp index 0e85f303e..0035a705c 100644 --- a/crengine/src/lvtextfm.cpp +++ b/crengine/src/lvtextfm.cpp @@ -1851,8 +1851,7 @@ class LVFormatter { FriBidiLevel newBidiLevel; #endif #if (USE_HARFBUZZ==1) - bool checkIfHarfbuzz = true; - bool usingHarfbuzz = false; + bool usingHarfbuzz = m_kerning_mode == KERNING_MODE_HARFBUZZ; // Unicode script change (note: hb_script_t is uint32_t) lUInt32 prevScript = HB_SCRIPT_COMMON; hb_unicode_funcs_t* _hb_unicode_funcs = hb_unicode_funcs_get_default(); @@ -1874,15 +1873,6 @@ class LVFormatter { isObject = m_flags[i] & LCHAR_IS_OBJECT; // image, float or inline box newFont = isObject ? NULL : (LVFont *)newSrc->t.font; newLetterSpacing = newSrc->letter_spacing; // 0 for objects - #if (USE_HARFBUZZ==1) - // Check if we are using Harfbuzz kerning with the first font met - if ( checkIfHarfbuzz && newFont ) { - if ( m_kerning_mode == KERNING_MODE_HARFBUZZ ) { - usingHarfbuzz = true; - } - checkIfHarfbuzz = false; - } - #endif } if (i > 0) prevCharIsObject = m_flags[i-1] & LCHAR_IS_OBJECT; // image, float or inline box diff --git a/crengine/src/lvxml.cpp b/crengine/src/lvxml.cpp index fceb738a8..6888ab2d3 100644 --- a/crengine/src/lvxml.cpp +++ b/crengine/src/lvxml.cpp @@ -2894,6 +2894,7 @@ void LVXMLParser::Reset() LVTextFileBase::Reset(); m_state = ps_bof; m_in_cdata = false; + m_in_html_style_tag = false; m_in_html_script_tag = false; } @@ -2902,6 +2903,7 @@ LVXMLParser::LVXMLParser( LVStreamRef stream, LVXMLParserCallback * callback, bo , m_callback(callback) , m_state(0) , m_in_cdata(false) + , m_in_html_style_tag(false) , m_in_html_script_tag(false) , m_trimspaces(true) , m_citags(false) @@ -3106,6 +3108,18 @@ bool LVXMLParser::Parse() // CRLog::trace("<%s>", LCSTR(tagname) ); if (!bodyStarted && tagname == "body") bodyStarted = true; + else if ( tagname.length() == 5 && + ( tagname[0] == U'S' || tagname[0] == U's') && + ( tagname[1] == U'T' || tagname[1] == U't') && + ( tagname[2] == U'Y' || tagname[2] == U'y') && + ( tagname[3] == U'L' || tagname[3] == U'l') && + ( tagname[4] == U'E' || tagname[4] == U'e') ) { + // Handle content as text, but don't stop just at any '<', + // only at '', as + if ( ptr + 1 < end ) { + if ( ptr[1] == '/' ) { + if ( ptr + 6 < end ) { + const lChar32 * buf = ptr + 2; + lString32 tag(buf, 5); + if ( tag.lowercase() == U"style" ) { + nbCharToSkipOnFlgBreak = 1; + goto end_of_node; + } + } + else if ( !hasNoMoreData ) { + break; + } + } + } + else if ( !hasNoMoreData ) { + break; + } + } + else if ( m_in_html_script_tag ) { // we're done only when we meet if ( ptr + 1 < end ) { if ( ptr[1] == '/' ) { if ( ptr + 7 < end ) {