Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSS: support for pseudo elements ::before & ::after #345

Merged
merged 11 commits into from
Jun 5, 2020
4 changes: 4 additions & 0 deletions cr3gui/data/epub.css
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ u, ins { text-decoration: underline; }
del, s, strike { text-decoration: line-through; }
a { text-decoration: underline; color: gray; }

/* No support for the "quotes:" property, these will use default quote chars */
q::before { content: open-quote; }
q::after { content: close-quote; }

nobr {
display: inline;
hyphens: none;
Expand Down
52 changes: 48 additions & 4 deletions crengine/include/cssdef.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,52 @@ enum css_display_t {
css_d_none
};

/// white-space property values
// https://www.w3.org/TR/CSS2/text.html#white-space-prop
// https://florian.rivoal.net/talks/line-breaking/
// https://developer.mozilla.org/en-US/docs/Web/CSS/white-space
// Behaviors: New lines Spaces/tabs End-of-line spaces Text wrap
// normal Collapse Collapse Remove Wrap
// nowrap Collapse Collapse Remove No wrap
// pre-line Preserve Collapse Remove Wrap
// pre Preserve Preserve Preserve No wrap
// pre-wrap Preserve Preserve Hang Wrap
// break-spaces Preserve Preserve Wrap Wrap
//
// crengine ensures the 3 first behaviors at XML parsing time, initially only for:
// 'normal' : replace new lines and tabs by spaces, replace consecutive spaces
// by only one, remove spaces at start and end if "display: block"
// 'pre' : preserve spaces and newlines, expands tabs to 8-spaces tabstops
// A change of the white-space value for a single node will make the DOM stalled,
// and a full reload should be done to get the correct result.
//
// The last behavior (text wrap) happens at text rendering time, and
// we always wrap to fit text into the container or screen width.
//
// We can approximate support for the other values:
// 'nowrap' is mostly like 'normal', but need some additional care:
// - in lvtextfm, to prevent wrap where it would be allowed, but
// still accounting for normal wrap points to be used if no other
// non-nowrap text node on the line provides a wrap opportunity.
// - in getRenderedWidths(), where it can impact the widths of
// table cells and floats
// 'pre-line' might be parsed just like 'pre', but rendered just
// like normal (lvtextfm will collapse spaces and wrap on \n)
// 'pre-wrap' is just like 'pre', as we would always wrap to fit
// in the container/screen width
// 'break-spaces' is very similar to 'pre-wrap', except that spaces
// should not be dropped on wrap. We don't ensure that.
//
/// white-space property values: keep them ordered this way for easier checks
enum css_white_space_t {
css_ws_inherit,
css_ws_normal,
css_ws_nowrap,
/* parse XML as 'normal' before this, as 'pre' after this */
css_ws_pre_line,
/* render text as 'normal' before this, as 'pre' after this */
css_ws_pre,
css_ws_nowrap
css_ws_pre_wrap,
css_ws_break_spaces
};

/// text-align property values
Expand All @@ -61,7 +101,8 @@ enum css_text_align_t {
css_ta_center,
css_ta_justify,
css_ta_start, // = left if LTR, right if RTL
css_ta_end // = right if LTR, left if LTR
css_ta_end, // = right if LTR, left if LTR
css_ta_auto // only accepted with text-align-last
};

/// vertical-align property values
Expand Down Expand Up @@ -309,7 +350,10 @@ enum css_cr_hint_t {
css_cr_hint_toc_level5,
css_cr_hint_toc_level6,
css_cr_hint_toc_ignore,
css_cr_hint_strut_confined
css_cr_hint_strut_confined,
css_cr_hint_text_selection_inline,
css_cr_hint_text_selection_block,
css_cr_hint_text_selection_skip
};

/// css length value
Expand Down
12 changes: 12 additions & 0 deletions crengine/include/fb2def.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
//=====================================================
XS_BEGIN_TAGS

// Boxing elements (inserted in the DOM tree between original parent and children):
//
// Internal element for block wrapping inline elements (without a proper parent
// block container) among proper block siblings (would be better named "blockBox")
XS_TAG1T( autoBoxing )
Expand All @@ -38,6 +40,14 @@ XS_TAG1T( tabularBox )
XS_TAG1T( floatBox )
// Internal element for inline-block and inline-table rendering
XS_TAG1I( inlineBox )

// Internal element created for CSS pseudo elements ::before and ::after :
// - defaults to "display: none", but will be set to "inline" when style is applied
// - it doesn't have a text node child, the content will be fetched from
// its style->content when rendering and drawing text.
// It does not box anything and has no child, so it's not considered a boxing node.
XS_TAG1D( pseudoElem, false, css_d_none, css_ws_normal )

// Internal element for EPUB, containing each individual HTML file
XS_TAG1( DocFragment )

Expand Down Expand Up @@ -256,6 +266,8 @@ XS_ATTR( role )
XS_ATTR( dir )
XS_ATTR( lang )
XS_ATTR( recindex ) // used with mobi images
XS_ATTR( Before ) // for pseudoElem internal element
XS_ATTR( After ) // for pseudoElem internal element
// Other classic attributes present in html5.css
XS_ATTR2( accept_charset, "accept-charset" )
XS_ATTR( alt )
Expand Down
3 changes: 2 additions & 1 deletion crengine/include/lvdocview.h
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ class LVDocView : public CacheLoadingCallback

LVArray<int> m_section_bounds;
bool m_section_bounds_valid;
bool m_section_bounds_externally_updated;

LVMutex _mutex;
#if CR_ENABLE_PAGE_IMAGE_CACHE==1
Expand Down Expand Up @@ -627,7 +628,7 @@ class LVDocView : public CacheLoadingCallback
/// returns true if document is opened
bool isDocumentOpened();
/// returns section bounds, in 1/100 of percent
LVArray<int> & getSectionBounds( );
LVArray<int> & getSectionBounds( bool for_external_update=false );
/// sets battery state
virtual bool setBatteryState( int newState );
/// returns battery state
Expand Down
21 changes: 14 additions & 7 deletions crengine/include/lvfnt.h
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,8 @@ lUInt16 lvfontMeasureText( const lvfont_handle pfont,
// 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.
// When not using libunibreak: it is set on '-' and other unicode hyphens.
// When using libunibreak: set on all text inside "white-space: nowrap"
#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
Expand All @@ -238,24 +239,30 @@ lUInt16 lvfontMeasureText( const lvfont_handle pfont,
// (This one is actually not set by lvfntman)
#define LCHAR_LOCKED_SPACING 0x0040 ///< flag: forbid any letter spacing tweak on this char
// (for cursive scripts like arabic, and special cases)
#define LCHAR__AVAILABLE_BIT_08__ 0x0080

/// 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__AVAILABLE_BIT_13__ 0x1000
#define LCHAR__AVAILABLE_BIT_14__ 0x2000
#define LCHAR__AVAILABLE_BIT_15__ 0x4000
#define LCHAR__AVAILABLE_BIT_16__ 0x8000

#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
// Some idea, if needed:
// #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
\return 1 if character is space, 0 otherwise
Expand Down
9 changes: 4 additions & 5 deletions crengine/include/lvfntman.h
Original file line number Diff line number Diff line change
Expand Up @@ -215,11 +215,10 @@ enum kerning_mode_t {
#define LFNT_HINT_IS_FALLBACK_FONT 0x0010 /// set on recursive Harfbuzz rendering/drawing with a fallback font

// These 4 translate from LTEXT_TD_* equivalents (see lvtextfm.h). Keep them in sync.
#define LFNT_DRAW_UNDERLINE 0x0100 /// underlined text
#define LFNT_DRAW_OVERLINE 0x0200 /// overlined text
#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
#define LFNT_DRAW_UNDERLINE 0x1000 /// underlined text
#define LFNT_DRAW_OVERLINE 0x2000 /// overlined text
#define LFNT_DRAW_LINE_THROUGH 0x4000 /// striked through text
#define LFNT_DRAW_DECORATION_MASK 0x7000


// CSS font-variant and font-feature-settings properties:
Expand Down
6 changes: 3 additions & 3 deletions crengine/include/lvrend.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,9 @@ void initFormatData( ldomNode * node );
/// initializes rendering method for node
int initRendMethod( ldomNode * node, bool recurseChildren, bool allowAutoboxing );
/// converts style to text formatting API flags
int styleToTextFmtFlags( const css_style_ref_t & style, int oldflags, int direction=REND_DIRECTION_UNSET );
lUInt32 styleToTextFmtFlags( const css_style_ref_t & style, lUInt32 oldflags, int direction=REND_DIRECTION_UNSET );
/// renders block as single text formatter object
void renderFinalBlock( ldomNode * node, LFormattedText * txform, RenderRectAccessor * fmt, int & flags,
void renderFinalBlock( ldomNode * node, LFormattedText * txform, RenderRectAccessor * fmt, lUInt32 & flags,
int indent, int line_h, TextLangCfg * lang_cfg=NULL, int valign_dy=0, bool * is_link_start=NULL );
/// renders block which contains subblocks (with gRenderBlockRenderingFlags as flags)
int renderBlockElement( LVRendPageContext & context, ldomNode * enode, int x, int y, int width, int direction=REND_DIRECTION_UNSET, int * baseline=NULL );
Expand All @@ -147,7 +147,7 @@ void DrawDocument( LVDrawBuf & drawbuf, ldomNode * node, int x0, int y0, int dx,
// full function for recursive use:
void getRenderedWidths(ldomNode * node, int &maxWidth, int &minWidth, int direction, bool ignorePadding, int rendFlags,
int &curMaxWidth, int &curWordWidth, bool &collapseNextSpace, int &lastSpaceWidth,
int indent, TextLangCfg * lang_cfg, bool isStartNode=false);
int indent, TextLangCfg * lang_cfg, bool processNodeAsText=false, bool isStartNode=false);
// simpler function for first call:
void getRenderedWidths(ldomNode * node, int &maxWidth, int &minWidth, int direction=REND_DIRECTION_UNSET, bool ignorePadding=false, int rendFlags=0);

Expand Down
40 changes: 36 additions & 4 deletions crengine/include/lvstsheet.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@

#include "cssdef.h"
#include "lvstyles.h"
#include "textlang.h"

class lxmlDocBase;
class ldomNode;
Expand Down Expand Up @@ -130,6 +131,20 @@ static const char * css_pseudo_classes[] =
NULL
};

// https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements
enum LVCssSelectorPseudoElement
{
csspe_before = 1, // ::before
csspe_after = 2, // ::after
};

static const char * css_pseudo_elements[] =
{
"before",
"after",
NULL
};

enum LVCssSelectorRuleType
{
cssrt_universal, // *
Expand Down Expand Up @@ -198,22 +213,33 @@ class LVCssSelector {
lUInt16 _id;
LVCssDeclRef _decl;
int _specificity;
int _pseudo_elem; // from enum LVCssSelectorPseudoElement, or 0
LVCssSelector * _next;
LVCssSelectorRule * _rules;
void insertRuleStart( LVCssSelectorRule * rule );
void insertRuleAfterStart( LVCssSelectorRule * rule );
public:
LVCssSelector( LVCssSelector & v );
LVCssSelector() : _id(0), _specificity(0), _next(NULL), _rules(NULL) { }
LVCssSelector(int specificity) : _id(0), _specificity(specificity), _next(NULL), _rules(NULL) { }
LVCssSelector() : _id(0), _specificity(0), _pseudo_elem(0), _next(NULL), _rules(NULL) { }
LVCssSelector(int specificity) : _id(0), _specificity(specificity), _pseudo_elem(0), _next(NULL), _rules(NULL) { }
~LVCssSelector() { if (_next) delete _next; if (_rules) delete _rules; }
bool parse( const char * &str, lxmlDocBase * doc );
lUInt16 getElementNameId() { return _id; }
bool check( const ldomNode * node ) const;
void applyToPseudoElement( const ldomNode * node, css_style_rec_t * style ) const;
void apply( const ldomNode * node, css_style_rec_t * style ) const
{
if (check( node ))
_decl->apply(style);
if (check( node )) {
if ( _pseudo_elem > 0 ) {
applyToPseudoElement(node, style);
}
else {
_decl->apply(style);
}
// style->flags |= STYLE_REC_FLAG_MATCHED;
// Done in applyToPseudoElement() as currently only needed there.
// Uncomment if more generic usage needed.
}
}
void setDeclaration( LVCssDeclRef decl ) { _decl = decl; }
int getSpecificity() { return _specificity; }
Expand Down Expand Up @@ -305,6 +331,12 @@ class LVStyleSheet {
/// parse color value like #334455, #345 or red
bool parse_color_value( const char * & str, css_length_t & value );

/// update (if needed) a style->content (parsed from the CSS declaration) before
// applying to a node's style
void update_style_content_property( css_style_rec_t * style, ldomNode * node );
/// get the computed final text value for a node from its style->content
lString16 get_applied_content_property( ldomNode * node );

/// extract @import filename from beginning of CSS
bool LVProcessStyleSheetImport( const char * &str, lString8 & import_file );
/// load stylesheet from file, with processing of import
Expand Down
27 changes: 22 additions & 5 deletions crengine/include/lvstyles.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,20 +82,27 @@ enum css_style_rec_important_bit {
imp_bit_float = 1ULL << 55,
imp_bit_clear = 1ULL << 56,
imp_bit_direction = 1ULL << 57,
imp_bit_cr_hint = 1ULL << 58
imp_bit_content = 1ULL << 58,
imp_bit_cr_hint = 1ULL << 59
};

// Style handling flags
#define STYLE_REC_FLAG_MATCHED 0x01 // This style has had some stylesheet declaration matched and applied.
// Currently only used for a pseudo element style,
// see LVCssSelector::apply() if more generic usage needed.

/**
\brief Element style record.

Contains set of style properties.
*/
typedef struct css_style_rec_tag {
typedef struct css_style_rec_tag css_style_rec_t;
struct css_style_rec_tag {
int refCount; // for reference counting
lUInt32 hash; // cache calculated hash value here
lUInt64 important; // bitmap for !important (used only by LVCssDeclaration)
// we have currently below 59 css properties
// lvstsheet knows about 81, which are mapped to these 59
// we have currently below 60 css properties
// lvstsheet knows about 82, which are mapped to these 60
// update bits above if you add new properties below
lUInt64 importance; // bitmap for important bit's importance/origin
// (allows for 2 level of !important importance)
Expand Down Expand Up @@ -144,7 +151,14 @@ typedef struct css_style_rec_tag {
css_float_t float_; // "float" is a C++ keyword...
css_clear_t clear;
css_direction_t direction;
lString16 content;
css_cr_hint_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_*
css_style_rec_t * pseudo_elem_before_style;
css_style_rec_t * pseudo_elem_after_style;

css_style_rec_tag()
: refCount(0)
, hash(0)
Expand Down Expand Up @@ -189,6 +203,9 @@ typedef struct css_style_rec_tag {
, clear(css_c_none)
, direction(css_dir_inherit)
, cr_hint(css_cr_hint_none)
, flags(0)
, pseudo_elem_before_style(NULL)
, pseudo_elem_after_style(NULL)
{
// css_length_t fields are initialized by css_length_tag()
// to (css_val_screen_px, 0)
Expand Down Expand Up @@ -233,7 +250,7 @@ typedef struct css_style_rec_tag {
if (is_important == 0x3) importance |= bit;
}
};
} css_style_rec_t;
};

/// style record reference type
typedef LVFastRef< css_style_rec_t > css_style_ref_t;
Expand Down
Loading