From d159a66ea658f9ee0e4eb88b903b09a9cf679b1f Mon Sep 17 00:00:00 2001 From: poire-z Date: Sun, 19 Jul 2020 18:45:22 +0200 Subject: [PATCH] CSS: switch -cr-hint from enum to bitmap This allows setting multiple hints on a same node (i.e. "-cr-hint: footnote-inpage strut-confined"). --- crengine/include/cssdef.h | 70 ++++++++++++++++++--------- crengine/include/lvstyles.h | 6 +-- crengine/src/lvrend.cpp | 23 +++++++-- crengine/src/lvstsheet.cpp | 96 +++++++++++++++++++++++-------------- crengine/src/lvstyles.cpp | 6 +-- crengine/src/lvtinydom.cpp | 39 +++++++++------ 6 files changed, 157 insertions(+), 83 deletions(-) diff --git a/crengine/include/cssdef.h b/crengine/include/cssdef.h index 77b19176a..8507e64b0 100644 --- a/crengine/include/cssdef.h +++ b/crengine/include/cssdef.h @@ -336,28 +336,54 @@ enum css_generic_value_t { css_generic_cover = -5 // (css_val_unspecified, css_generic_cover), for "background-size: cover" }; -// Non standard property for providing hints to crengine via style tweaks -// (see src/lvstsheet.cpp css_cr_hint_names[]= for explanations) -enum css_cr_hint_t { - css_cr_hint_inherit, - css_cr_hint_none, - css_cr_hint_noteref, - css_cr_hint_noteref_ignore, - css_cr_hint_footnote, - css_cr_hint_footnote_ignore, - css_cr_hint_footnote_inpage, - css_cr_hint_toc_level1, - css_cr_hint_toc_level2, - css_cr_hint_toc_level3, - css_cr_hint_toc_level4, - css_cr_hint_toc_level5, - css_cr_hint_toc_level6, - css_cr_hint_toc_ignore, - css_cr_hint_strut_confined, - css_cr_hint_text_selection_inline, - css_cr_hint_text_selection_block, - css_cr_hint_text_selection_skip -}; +// -cr-hint is a non standard property for providing hints to crengine via style tweaks +// Handled as a bitmap, with a flag for each hint, as we might set multiple on a same node (max 31 bits) +#define CSS_CR_HINT_NONE 0x00000000 // default value + +// Reset any hint previously set and don't inherit any from parent +#define CSS_CR_HINT_NONE_NO_INHERIT 0x00000001 // -cr-hint: none + +// Text and images should not overflow/modify their paragraph strut baseline and height +// (it could have been a non-standard named value for line-height:, but we want to be +// able to not override existing line-height: values) +#define CSS_CR_HINT_STRUT_CONFINED 0x00000002 // -cr-hint: strut-confined (inheritable) + +// A node with these should be considered as TOC item of level N when building alternate TOC +#define CSS_CR_HINT_TOC_LEVEL1 0x00000100 // -cr-hint: toc-level1 +#define CSS_CR_HINT_TOC_LEVEL2 0x00000200 // -cr-hint: toc-level2 +#define CSS_CR_HINT_TOC_LEVEL3 0x00000400 // -cr-hint: toc-level3 +#define CSS_CR_HINT_TOC_LEVEL4 0x00000800 // -cr-hint: toc-level4 +#define CSS_CR_HINT_TOC_LEVEL5 0x00001000 // -cr-hint: toc-level5 +#define CSS_CR_HINT_TOC_LEVEL6 0x00002000 // -cr-hint: toc-level6 +#define CSS_CR_HINT_TOC_LEVELS_MASK 0x00003F00 +// Ignore H1...H6 that have this when building alternate TOC +#define CSS_CR_HINT_TOC_IGNORE 0x00004000 // -cr-hint: toc-ignore + +// Tweak text selection behaviour when traversing a node with these hints +#define CSS_CR_HINT_TEXT_SELECTION_INLINE 0x00010000 // -cr-hint: text-selection-inline don't add a '\n' before inner text + // (even if the node happens to be block) +#define CSS_CR_HINT_TEXT_SELECTION_BLOCK 0x00020000 // -cr-hint: text-selection-block add a '\n' before inner text (even + // if the node happens to be inline) +#define CSS_CR_HINT_TEXT_SELECTION_SKIP 0x00040000 // -cr-hint: text-selection-skip don't include inner text in selection + +// To be set on a block element: it is a footnote (must be a full footnote block container), +// and to be displayed at the bottom of all pages that contain a link to it. +#define CSS_CR_HINT_FOOTNOTE_INPAGE 0x00080000 // -cr-hint: footnote-inpage + +// For footnote popup detection by koreader-base/cre.cpp +#define CSS_CR_HINT_NOTEREF 0x01000000 // -cr-hint: noteref link is to a footnote +#define CSS_CR_HINT_NOTEREF_IGNORE 0x02000000 // -cr-hint: noteref-ignore link is not to a footnote (even if + // everything else indicates it is) +#define CSS_CR_HINT_FOOTNOTE 0x04000000 // -cr-hint: footnote block is a footnote (must be a full + // footnote block container) +#define CSS_CR_HINT_FOOTNOTE_IGNORE 0x08000000 // -cr-hint: footnote-ignore block is not a footnote (even if + // everything else indicates it is) + +// A few of them are inheritable, most are not. +#define CSS_CR_HINT_INHERITABLE_MASK 0x00000002 + +// Macro for easier checking +#define STYLE_HAS_CR_HINT(s, h) ( (bool)(s->cr_hint.value & CSS_CR_HINT_##h) ) /// css length value typedef struct css_length_tag { diff --git a/crengine/include/lvstyles.h b/crengine/include/lvstyles.h index ae8798cbc..16f71ff64 100644 --- a/crengine/include/lvstyles.h +++ b/crengine/include/lvstyles.h @@ -153,7 +153,7 @@ struct css_style_rec_tag { css_clear_t clear; css_direction_t direction; lString16 content; - css_cr_hint_t cr_hint; + css_length_t cr_hint; // The following should only be used when applying stylesheets while in lvend.cpp setNodeStyle(), // and cleaned up there, before the style is cached and shared. They are not serialized. lInt8 flags; // bitmap of STYLE_REC_FLAG_* @@ -202,7 +202,7 @@ struct css_style_rec_tag { , float_(css_f_none) , clear(css_c_none) , direction(css_dir_inherit) - , cr_hint(css_cr_hint_none) + , cr_hint(css_val_inherited, 0) , flags(0) , pseudo_elem_before_style(NULL) , pseudo_elem_after_style(NULL) @@ -240,7 +240,7 @@ struct css_style_rec_tag { if (is_important == 0x3) importance |= bit; // update importance flag (!important comes from higher_importance CSS) } } - // Similar to previous one, but logical-OR'ing values, for bitmaps (currently, only style->font_features) + // Similar to previous one, but logical-OR'ing values, for bitmaps (currently, only style->font_features and style->cr_hint) inline void ApplyAsBitmapOr( css_length_t value, css_length_t *field, css_style_rec_important_bit bit, lUInt8 is_important ) { if ( !(important & bit) || (is_important == 0x3) diff --git a/crengine/src/lvrend.cpp b/crengine/src/lvrend.cpp index 026ae59bf..2cb46a7eb 100644 --- a/crengine/src/lvrend.cpp +++ b/crengine/src/lvrend.cpp @@ -2380,7 +2380,7 @@ lString16 renderListItemMarker( ldomNode * enode, int & marker_width, LFormatted // Scale it according to gInterlineScaleFactor if (style->line_height.type != css_val_screen_px && gInterlineScaleFactor != INTERLINE_SCALE_FACTOR_NO_SCALE) line_h = (line_h * gInterlineScaleFactor) >> INTERLINE_SCALE_FACTOR_SHIFT; - if ( style->cr_hint == css_cr_hint_strut_confined ) + if ( STYLE_HAS_CR_HINT(style, STRUT_CONFINED) ) flags |= LTEXT_STRUT_CONFINED; } marker += "\t"; @@ -2657,7 +2657,7 @@ void renderFinalBlock( ldomNode * enode, LFormattedText * txform, RenderRectAcce int f_half_leading = (line_h - fh) / 2; txform->setStrut(line_h, fb + f_half_leading); } - else if ( style->cr_hint == css_cr_hint_strut_confined ) { + else if ( STYLE_HAS_CR_HINT(style, STRUT_CONFINED) ) { // Previous branch for the top final node has set the strut. // Inline nodes having "-cr-hint: strut-confined" will be confined // inside that strut. @@ -3533,7 +3533,8 @@ void copystyle( css_style_ref_t source, css_style_ref_t dest ) dest->clear = source->clear; dest->direction = source->direction; dest->content = source->content ; - dest->cr_hint = source->cr_hint; + dest->cr_hint.type = source->cr_hint.type ; + dest->cr_hint.value = source->cr_hint.value ; } // Only used by renderBlockElementLegacy() @@ -3771,7 +3772,7 @@ int renderBlockElementLegacy( LVRendPageContext & context, ldomNode * enode, int lString16 footnoteId; // Allow displaying footnote content at the bottom of all pages that contain a link // to it, when -cr-hint: footnote-inpage is set on the footnote block container. - if ( style->cr_hint == css_cr_hint_footnote_inpage && + if ( STYLE_HAS_CR_HINT(style, FOOTNOTE_INPAGE) && enode->getDocument()->getDocFlag(DOC_FLAG_ENABLE_FOOTNOTES)) { footnoteId = enode->getFirstInnerAttributeValue(attr_id); if ( !footnoteId.empty() ) @@ -6080,7 +6081,7 @@ void renderBlockElementEnhanced( FlowState * flow, ldomNode * enode, int x, int lString16 footnoteId; // Allow displaying footnote content at the bottom of all pages that contain a link // to it, when -cr-hint: footnote-inpage is set on the footnote block container. - if ( style->cr_hint == css_cr_hint_footnote_inpage && + if ( STYLE_HAS_CR_HINT(style, FOOTNOTE_INPAGE) && enode->getDocument()->getDocFlag(DOC_FLAG_ENABLE_FOOTNOTES)) { footnoteId = enode->getFirstInnerAttributeValue(attr_id); if ( !footnoteId.empty() ) @@ -9036,6 +9037,18 @@ void setNodeStyle( ldomNode * enode, css_style_ref_t parent_style, LVFontRef par pstyle->font_features.value |= parent_style->font_features.value; pstyle->font_features.type = css_val_unspecified; + // cr_hint is also a bitmap, and only some bits are inherited. + // A node starts with (css_val_inherited, 0), but if some + // stylesheet has applied some -cr-hint to it, we meet it + // here with (css_val_unspecified, bitmap) and we report the + // inheritable bits from the parent. + // Unless "-cr-hint: none" has been applied to the node, which + // prevents inheritance + if ( !STYLE_HAS_CR_HINT(pstyle, NONE_NO_INHERIT) ) { + pstyle->cr_hint.value |= (parent_style->cr_hint.value & CSS_CR_HINT_INHERITABLE_MASK); + pstyle->cr_hint.type = css_val_unspecified; + } + //UPDATE_LEN_FIELD( text_indent ); spreadParent( pstyle->text_indent, parent_style->text_indent ); switch( pstyle->font_weight ) diff --git a/crengine/src/lvstsheet.cpp b/crengine/src/lvstsheet.cpp index 1db96aadc..e0abe3c2e 100644 --- a/crengine/src/lvstsheet.cpp +++ b/crengine/src/lvstsheet.cpp @@ -1789,40 +1789,6 @@ static const char * css_dir_names[] = NULL }; -// -cr-hint names (non standard property for providing hints to crengine via style tweaks) -static const char * css_cr_hint_names[]={ - "inherit", - "none", - // For footnote popup detection: - "noteref", // link is to a footnote - "noteref-ignore", // link is not to a footnote (even if everything else indicates it is) - "footnote", // block is a footnote (must be a full footnote block container) - "footnote-ignore", // block is not a footnote (even if everything else indicates it is) - "footnote-inpage", // block is a footnote (must be a full footnote block container), and to be - // displayed at the bottom of all pages that contain a link to it. - "toc-level1", // to be considered as TOC item of level N when building alternate TOC - "toc-level2", - "toc-level3", - "toc-level4", - "toc-level5", - "toc-level6", - "toc-ignore", // ignore these H1...H6 when building alternate TOC - - // Next one is not really a hint, but might have some active effect on rendering/layout. - // It has effect on inline nodes only, while the ones above mostly apply to block - // nodes. So, provide it with a lower specificity if those above also need to be used. - "strut-confined", // text and images should not overflow/modify their paragraph strut - // baseline and height (it could have been a non-standard named - // value for line-height:, but we want to be able to not override - // existing line-height: values) - - // Tweak text selection when traversing a node with these hints - "text-selection-inline", // don't add a '\n' before inner text, even if the node happens to be block - "text-selection-block", // add a '\n' before inner text even if the node happens to be inline - "text-selection-skip", // don't include inner text in text selection - NULL -}; - static const char * css_cr_only_if_names[]={ "any", "always", @@ -1973,7 +1939,54 @@ bool LVCssDeclaration::parse( const char * &decl, bool higher_importance, lxmlDo break; // non standard property for providing hints via style tweaks case cssd_cr_hint: - n = parse_name( decl, css_cr_hint_names, -1 ); + { + // All values are mapped into a single style->cr_hint 31 bits bitmap + int hints = 0; // "none" = no hint + int nb_parsed = 0; + int nb_invalid = 0; + while ( *decl && *decl !=';' && *decl!='}') { + // Details in crengine/include/cssdef.h (checks ordered by most likely to be seen) + if ( substr_icompare("none", decl) ) { + // Forget everything parsed previously, and prevent inheritance + hints = CSS_CR_HINT_NONE_NO_INHERIT; + } + else if ( substr_icompare("footnote-inpage", decl) ) hints |= CSS_CR_HINT_FOOTNOTE_INPAGE; + else if ( substr_icompare("strut-confined", decl) ) hints |= CSS_CR_HINT_STRUT_CONFINED; + else if ( substr_icompare("text-selection-skip", decl) ) hints |= CSS_CR_HINT_TEXT_SELECTION_SKIP; + else if ( substr_icompare("text-selection-inline", decl) ) hints |= CSS_CR_HINT_TEXT_SELECTION_INLINE; + else if ( substr_icompare("text-selection-block", decl) ) hints |= CSS_CR_HINT_TEXT_SELECTION_BLOCK; + else if ( substr_icompare("toc-level1", decl) ) hints |= CSS_CR_HINT_TOC_LEVEL1; + else if ( substr_icompare("toc-level2", decl) ) hints |= CSS_CR_HINT_TOC_LEVEL2; + else if ( substr_icompare("toc-level3", decl) ) hints |= CSS_CR_HINT_TOC_LEVEL3; + else if ( substr_icompare("toc-level4", decl) ) hints |= CSS_CR_HINT_TOC_LEVEL4; + else if ( substr_icompare("toc-level5", decl) ) hints |= CSS_CR_HINT_TOC_LEVEL5; + else if ( substr_icompare("toc-level6", decl) ) hints |= CSS_CR_HINT_TOC_LEVEL6; + else if ( substr_icompare("toc-ignore", decl) ) hints |= CSS_CR_HINT_TOC_IGNORE; + else if ( substr_icompare("noteref", decl) ) hints |= CSS_CR_HINT_NOTEREF; + else if ( substr_icompare("noteref-ignore", decl) ) hints |= CSS_CR_HINT_NOTEREF_IGNORE; + else if ( substr_icompare("footnote", decl) ) hints |= CSS_CR_HINT_FOOTNOTE; + else if ( substr_icompare("footnote-ignore", decl) ) hints |= CSS_CR_HINT_FOOTNOTE_IGNORE; + // + else if ( parse_important(decl) ) { + parsed_important = IMPORTANT_DECL_SET; + break; // stop looking for more + } + else { // unsupported or invalid named value + nb_invalid++; + // Walk over unparsed value, and continue checking + while (*decl && *decl !=' ' && *decl !=';' && *decl!='}') + decl++; + } + nb_parsed++; + skip_spaces( decl ); + } + if ( nb_parsed - nb_invalid > 0 ) { // at least one valid named value seen + buf<<(lUInt32) (prop_code | importance | parsed_important); + buf<<(lUInt32) css_val_unspecified; // len.type + buf<<(lUInt32) hints; // len.value + // (css_val_unspecified just says this value has no unit) + } + } break; case cssd_display: n = parse_name( decl, css_d_names, -1 ); @@ -3158,7 +3171,18 @@ void LVCssDeclaration::apply( css_style_rec_t * style ) style->Apply( (css_direction_t) *p++, &style->direction, imp_bit_direction, is_important ); break; case cssd_cr_hint: - style->Apply( (css_cr_hint_t) *p++, &style->cr_hint, imp_bit_cr_hint, is_important ); + { + // We want to 'OR' the bitmap from any declaration that is to be applied to this node + // (while still ensuring !important) - unless this declaration had "-cr-hint: none" + // in which case we should reset previously set bits + css_length_t cr_hint = read_length(p); + if ( cr_hint.value & CSS_CR_HINT_NONE_NO_INHERIT ) { + style->Apply( cr_hint, &style->cr_hint, imp_bit_cr_hint, is_important ); + } + else { + style->ApplyAsBitmapOr( cr_hint, &style->cr_hint, imp_bit_cr_hint, is_important ); + } + } break; case cssd_content: { diff --git a/crengine/src/lvstyles.cpp b/crengine/src/lvstyles.cpp index a16ec0f6f..4aacb80ce 100644 --- a/crengine/src/lvstyles.cpp +++ b/crengine/src/lvstyles.cpp @@ -105,7 +105,7 @@ lUInt32 calcHash(css_style_rec_t & rec) + (lUInt32)rec.float_) * 31 + (lUInt32)rec.clear) * 31 + (lUInt32)rec.direction) * 31 - + (lUInt32)rec.cr_hint) * 31 + + (lUInt32)rec.cr_hint.pack()) * 31 + (lUInt32)rec.font_name.getHash() + (lUInt32)rec.background_image.getHash() + (lUInt32)rec.content.getHash()); @@ -363,7 +363,7 @@ bool css_style_rec_t::serialize( SerialBuf & buf ) ST_PUT_ENUM(clear); ST_PUT_ENUM(direction); buf << content; - ST_PUT_ENUM(cr_hint); + ST_PUT_LEN(cr_hint); lUInt32 hash = calcHash(*this); buf << hash; return !buf.error(); @@ -424,7 +424,7 @@ bool css_style_rec_t::deserialize( SerialBuf & buf ) ST_GET_ENUM(css_clear_t, clear); ST_GET_ENUM(css_direction_t, direction); buf>>content; - ST_GET_ENUM(css_cr_hint_t, cr_hint); + ST_GET_LEN(cr_hint); lUInt32 hash = 0; buf >> hash; // printf("imp: %llx oldhash: %lx ", important, hash); diff --git a/crengine/src/lvtinydom.cpp b/crengine/src/lvtinydom.cpp index bce575064..a5a4b8be0 100644 --- a/crengine/src/lvtinydom.cpp +++ b/crengine/src/lvtinydom.cpp @@ -84,7 +84,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.43k" +#define CACHE_FILE_FORMAT_VERSION "3.05.44k" /// increment following value to force re-formatting of old book after load #define FORMATTING_VERSION_ID 0x0025 @@ -4372,7 +4372,8 @@ bool ldomDocument::setRenderProps( int width, int dy, bool /*showCover*/, int /* s->float_ = css_f_none; s->clear = css_c_none; s->direction = css_dir_inherit; - s->cr_hint = css_cr_hint_none; + s->cr_hint.type = css_val_unspecified; + s->cr_hint.value = CSS_CR_HINT_NONE; //lUInt32 defStyleHash = (((_stylesheet.getHash() * 31) + calcHash(_def_style))*31 + calcHash(_def_font)); //defStyleHash = defStyleHash * 31 + getDocFlags(); if ( _last_docflags != getDocFlags() ) { @@ -12173,15 +12174,15 @@ class ldomTextCollector : public ldomNodeCallback #if BUILD_LITE!=1 ldomNode * elem = (ldomNode *)ptr->getNode(); // Allow tweaking that with hints - css_cr_hint_t hint = elem->getStyle()->cr_hint; - if ( hint == css_cr_hint_text_selection_skip ) { + css_style_ref_t style = elem->getStyle(); + if ( STYLE_HAS_CR_HINT(style, TEXT_SELECTION_SKIP) ) { return false; } - else if ( hint == css_cr_hint_text_selection_inline ) { + else if ( STYLE_HAS_CR_HINT(style, TEXT_SELECTION_INLINE) ) { newBlock = false; return true; } - else if ( hint == css_cr_hint_text_selection_block ) { + else if ( STYLE_HAS_CR_HINT(style, TEXT_SELECTION_BLOCK) ) { newBlock = true; return true; } @@ -12197,7 +12198,7 @@ class ldomTextCollector : public ldomNodeCallback // For other rendering methods (that would bring newBlock=true), // look at the initial CSS display, as we might have boxed some // inline-like elements for rendering purpose. - css_display_t d = elem->getStyle()->display; + css_display_t d = style->display; if ( d <= css_d_inline || d == css_d_inline_block || d == css_d_inline_table ) { // inline, ruby; consider inline-block/-table as inline, in case // they don't contain much (if they do, some inner block element @@ -17416,11 +17417,18 @@ static inline void makeTocFromCrHintsOrHeadings( ldomNode * node, bool ensure_cr { int level; if ( ensure_cr_hints ) { - css_cr_hint_t hint = node->getStyle()->cr_hint; - if ( hint == css_cr_hint_toc_ignore ) + css_style_ref_t style = node->getStyle(); + if ( STYLE_HAS_CR_HINT(style, TOC_IGNORE) ) return; // requested to be ignored via style tweaks - if ( hint >= css_cr_hint_toc_level1 && hint <= css_cr_hint_toc_level6 ) - level = hint - css_cr_hint_toc_level1 + 1; + if ( STYLE_HAS_CR_HINT(style, TOC_LEVELS_MASK) ) { + if ( STYLE_HAS_CR_HINT(style, TOC_LEVEL1) ) level = 1; + else if ( STYLE_HAS_CR_HINT(style, TOC_LEVEL2) ) level = 2; + else if ( STYLE_HAS_CR_HINT(style, TOC_LEVEL3) ) level = 3; + else if ( STYLE_HAS_CR_HINT(style, TOC_LEVEL4) ) level = 4; + else if ( STYLE_HAS_CR_HINT(style, TOC_LEVEL5) ) level = 5; + else if ( STYLE_HAS_CR_HINT(style, TOC_LEVEL6) ) level = 6; + else level = 7; // should not be reached + } else if ( node->getNodeId() >= el_h1 && node->getNodeId() <= el_h6 ) // el_h1 .. el_h6 are consecutive and ordered in include/fb2def.h level = node->getNodeId() - el_h1 + 1; @@ -17854,7 +17862,8 @@ void runBasicTinyDomUnitTests() style1->text_indent.value = 0; style1->line_height.type = css_val_unspecified; style1->line_height.value = css_generic_normal; // line-height: normal - style1->cr_hint = css_cr_hint_none; + style1->cr_hint.type = css_val_unspecified; + style1->cr_hint.value = CSS_CR_HINT_NONE; css_style_ref_t style2; style2 = css_style_ref_t( new css_style_rec_t ); @@ -17886,7 +17895,8 @@ void runBasicTinyDomUnitTests() style2->text_indent.value = 0; style2->line_height.type = css_val_unspecified; style2->line_height.value = css_generic_normal; // line-height: normal - style2->cr_hint = css_cr_hint_none; + style2->cr_hint.type = css_val_unspecified; + style2->cr_hint.value = CSS_CR_HINT_NONE; css_style_ref_t style3; style3 = css_style_ref_t( new css_style_rec_t ); @@ -17918,7 +17928,8 @@ void runBasicTinyDomUnitTests() style3->text_indent.value = 0; style3->line_height.type = css_val_unspecified; style3->line_height.value = css_generic_normal; // line-height: normal - style3->cr_hint = css_cr_hint_none; + style3->cr_hint.type = css_val_unspecified; + style3->cr_hint.value = CSS_CR_HINT_NONE; el1->setStyle(style1); css_style_ref_t s1 = el1->getStyle();