From c813d45d611d2bd98343a58e0579fc15a54dd56f Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Wed, 3 Jul 2019 15:37:29 +0200 Subject: [PATCH 1/4] First naive version of text-overflow support --- weasyprint/css/properties.py | 5 ++++- weasyprint/css/validation/properties.py | 7 +++++++ weasyprint/text.py | 21 +++++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/weasyprint/css/properties.py b/weasyprint/css/properties.py index 6b7f074de..bb9752475 100644 --- a/weasyprint/css/properties.py +++ b/weasyprint/css/properties.py @@ -45,7 +45,6 @@ 'margin_left': Dimension(0, 'px'), 'max_height': Dimension(float('inf'), 'px'), # parsed value for 'none' 'max_width': Dimension(float('inf'), 'px'), - 'overflow': 'visible', 'padding_top': Dimension(0, 'px'), 'padding_right': Dimension(0, 'px'), 'padding_bottom': Dimension(0, 'px'), @@ -196,6 +195,10 @@ 'text_decoration_color': 'currentColor', 'text_decoration_style': 'solid', + # Overflow Module 3 (WD): https://www.w3.org/TR/css-overflow-3/ + 'overflow': 'visible', + 'text_overflow': 'clip', + # Proprietary 'anchor': None, # computed value of 'none' 'link': None, # computed value of 'none' diff --git a/weasyprint/css/validation/properties.py b/weasyprint/css/validation/properties.py index 95df1a038..f64bd4aa5 100644 --- a/weasyprint/css/validation/properties.py +++ b/weasyprint/css/validation/properties.py @@ -960,6 +960,13 @@ def overflow(keyword): return keyword in ('auto', 'visible', 'hidden', 'scroll') +@property() +@single_keyword +def text_overflow(keyword): + """Validation for the ``text-overflow`` property.""" + return keyword in ('clip', 'ellipsis') + + @property() @single_keyword def position(keyword): diff --git a/weasyprint/text.py b/weasyprint/text.py index f1a69d4e7..96a79736a 100644 --- a/weasyprint/text.py +++ b/weasyprint/text.py @@ -1222,6 +1222,27 @@ def show_first_line(context, textbox): pango.pango_layout_set_single_paragraph_mode( textbox.pango_layout.layout, True) first_line, _ = textbox.pango_layout.get_first_line() + + text_overflow = False + if text_overflow: + first_line_width, _ = get_size(first_line, textbox.style) + max_width = context.clip_extents()[2] - textbox.position_x + space = max_width - first_line_width + if space < 0: + first_line_text = textbox.text + while len(first_line_text) > 1: + first_line_text = first_line_text[:-1] + textbox.pango_layout.set_text(first_line_text + '…') + first_line, index = textbox.pango_layout.get_first_line() + first_line_width, _ = get_size(first_line, textbox.style) + space = max_width - first_line_width + if space >= 0: + print(repr(first_line_text)) + text = first_line_text + '…' + break + else: + textbox.pango_layout.set_text(text) + context = ffi.cast('cairo_t *', context._pointer) pangocairo.pango_cairo_show_layout_line(context, first_line) From e88190fc144e26efdebf161439e99c85daeaaba1 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Wed, 3 Jul 2019 16:20:58 +0200 Subject: [PATCH 2/4] Use Pango's set_ellipsize --- weasyprint/text.py | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/weasyprint/text.py b/weasyprint/text.py index 96a79736a..90e9b7369 100644 --- a/weasyprint/text.py +++ b/weasyprint/text.py @@ -96,6 +96,13 @@ PANGO_TAB_LEFT } PangoTabAlign; + typedef enum { + PANGO_ELLIPSIZE_NONE, + PANGO_ELLIPSIZE_START, + PANGO_ELLIPSIZE_MIDDLE, + PANGO_ELLIPSIZE_END + } PangoEllipsizeMode; + typedef struct { const PangoAttrClass *klass; guint start_index; @@ -218,6 +225,9 @@ PangoRectangle *ink_rect, PangoRectangle *logical_rect); PangoContext * pango_layout_get_context (PangoLayout *layout); + void pango_layout_set_ellipsize ( + PangoLayout *layout, + PangoEllipsizeMode ellipsize); void pango_get_log_attrs ( const char *text, int length, int level, PangoLanguage *language, @@ -1221,28 +1231,16 @@ def show_first_line(context, textbox): """Draw the given ``textbox`` line to the cairo ``context``.""" pango.pango_layout_set_single_paragraph_mode( textbox.pango_layout.layout, True) - first_line, _ = textbox.pango_layout.get_first_line() text_overflow = False if text_overflow: - first_line_width, _ = get_size(first_line, textbox.style) max_width = context.clip_extents()[2] - textbox.position_x - space = max_width - first_line_width - if space < 0: - first_line_text = textbox.text - while len(first_line_text) > 1: - first_line_text = first_line_text[:-1] - textbox.pango_layout.set_text(first_line_text + '…') - first_line, index = textbox.pango_layout.get_first_line() - first_line_width, _ = get_size(first_line, textbox.style) - space = max_width - first_line_width - if space >= 0: - print(repr(first_line_text)) - text = first_line_text + '…' - break - else: - textbox.pango_layout.set_text(text) + pango.pango_layout_set_width( + textbox.pango_layout.layout, units_from_double(max_width)) + pango.pango_layout_set_ellipsize( + textbox.pango_layout.layout, pango.PANGO_ELLIPSIZE_END) + first_line, _ = textbox.pango_layout.get_first_line() context = ffi.cast('cairo_t *', context._pointer) pangocairo.pango_cairo_show_layout_line(context, first_line) From 6dcc17f151d074702d0ed263b4e35851bcf1663b Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 4 Jul 2019 11:26:41 +0200 Subject: [PATCH 3/4] Take care of text-overflow to set ellipsis Fix #874. --- weasyprint/draw.py | 18 +++-- weasyprint/formatting_structure/boxes.py | 8 ++ weasyprint/tests/test_draw/test_text.py | 93 ++++++++++++++++++++++++ weasyprint/text.py | 5 +- 4 files changed, 114 insertions(+), 10 deletions(-) create mode 100644 weasyprint/tests/test_draw/test_text.py diff --git a/weasyprint/draw.py b/weasyprint/draw.py index 7d77a92ea..3ea4441c3 100644 --- a/weasyprint/draw.py +++ b/weasyprint/draw.py @@ -996,7 +996,8 @@ def draw_replacedbox(context, box): context, draw_width, draw_height, box.style['image_rendering']) -def draw_inline_level(context, page, box, enable_hinting, offset_x=0): +def draw_inline_level(context, page, box, enable_hinting, offset_x=0, + text_overflow='clip'): if isinstance(box, StackingContext): stacking_context = box assert isinstance( @@ -1006,6 +1007,8 @@ def draw_inline_level(context, page, box, enable_hinting, offset_x=0): draw_background(context, box.background, enable_hinting) draw_border(context, box, enable_hinting) if isinstance(box, (boxes.InlineBox, boxes.LineBox)): + if isinstance(box, boxes.LineBox): + text_overflow = box.text_overflow for child in box.children: if isinstance(child, StackingContext): child_offset_x = offset_x @@ -1015,20 +1018,21 @@ def draw_inline_level(context, page, box, enable_hinting, offset_x=0): if isinstance(child, boxes.TextBox): draw_text( context, child, enable_hinting, - offset_x=child_offset_x) + child_offset_x, text_overflow) else: draw_inline_level( - context, page, child, enable_hinting, - offset_x=child_offset_x) + context, page, child, enable_hinting, child_offset_x, + text_overflow) elif isinstance(box, boxes.InlineReplacedBox): draw_replacedbox(context, box) else: assert isinstance(box, boxes.TextBox) # Should only happen for list markers - draw_text(context, box, enable_hinting, offset_x=offset_x) + draw_text(context, box, enable_hinting, offset_x, text_overflow) -def draw_text(context, textbox, enable_hinting, offset_x=0): +def draw_text(context, textbox, enable_hinting, offset_x=0, + text_overflow='clip'): """Draw ``textbox`` to a ``cairo.Context`` from ``PangoCairo.Context``.""" # Pango crashes with font-size: 0 assert textbox.style['font_size'] @@ -1040,7 +1044,7 @@ def draw_text(context, textbox, enable_hinting, offset_x=0): context.set_source_rgba(*textbox.style['color']) textbox.pango_layout.reactivate(textbox.style) - show_first_line(context, textbox) + show_first_line(context, textbox, text_overflow) values = textbox.style['text_decoration_line'] diff --git a/weasyprint/formatting_structure/boxes.py b/weasyprint/formatting_structure/boxes.py index 8531b5c13..cdf766105 100644 --- a/weasyprint/formatting_structure/boxes.py +++ b/weasyprint/formatting_structure/boxes.py @@ -416,6 +416,14 @@ class LineBox(ParentBox): be split into multiple line boxes, one for each actual line. """ + text_overflow = 'clip' + + @classmethod + def anonymous_from(cls, parent, *args, **kwargs): + box = super().anonymous_from(parent, *args, **kwargs) + if parent.style['overflow'] != 'visible': + box.text_overflow = parent.style['text_overflow'] + return box class InlineLevelBox(Box): diff --git a/weasyprint/tests/test_draw/test_text.py b/weasyprint/tests/test_draw/test_text.py new file mode 100644 index 000000000..9b4f90266 --- /dev/null +++ b/weasyprint/tests/test_draw/test_text.py @@ -0,0 +1,93 @@ +""" + weasyprint.tests.test_draw.test_text + ------------------------------------ + + Test how text is drawn. + + :copyright: Copyright 2011-2019 Simon Sapin and contributors, see AUTHORS. + :license: BSD, see LICENSE for details. + +""" + +from . import assert_pixels + + +def test_text_overflow_clip(): + assert_pixels('text_overflow', 9, 7, ''' + _________ + _RRRRRRR_ + _RRRRRRR_ + _________ + _RR__RRR_ + _RR__RRR_ + _________ + ''', ''' + +
abcde
+
a bcde
''') + + +def test_text_overflow_ellipsis(): + assert_pixels('text_overflow', 9, 16, ''' + _________ + _RRRRRR__ + _RRRRRR__ + _________ + _RR__RR__ + _RR__RR__ + _________ + _RRRRRR__ + _RRRRRR__ + _________ + _RRRRRRR_ + _RRRRRRR_ + _________ + _RRRRRRR_ + _RRRRRRR_ + _________ + ''', ''' + +
abcde
+
a bcde
+
abcde
+
abcde
+
abcde
+''') diff --git a/weasyprint/text.py b/weasyprint/text.py index 90e9b7369..f57fc2d71 100644 --- a/weasyprint/text.py +++ b/weasyprint/text.py @@ -1227,13 +1227,12 @@ def split_first_line(text, style, context, max_width, justification_spacing, style['hyphenate_character']) -def show_first_line(context, textbox): +def show_first_line(context, textbox, text_overflow): """Draw the given ``textbox`` line to the cairo ``context``.""" pango.pango_layout_set_single_paragraph_mode( textbox.pango_layout.layout, True) - text_overflow = False - if text_overflow: + if text_overflow == 'ellipsis': max_width = context.clip_extents()[2] - textbox.position_x pango.pango_layout_set_width( textbox.pango_layout.layout, units_from_double(max_width)) From a0d912e0c6ec1aded441f9e8ca8df873d6d1124f Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Thu, 4 Jul 2019 14:28:57 +0200 Subject: [PATCH 4/4] Update documentation about text-overflow --- docs/features.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/features.rst b/docs/features.rst index c915393c5..0fcd4422d 100644 --- a/docs/features.rst +++ b/docs/features.rst @@ -500,14 +500,13 @@ The `CSS Basic User Interface Module Level 3`_ also known as CSS3 UI is a candidate recommendation describing "CSS properties which enable authors to style user interface related properties and values." -Only one new property defined in this document is implemented in WeasyPrint: -the ``box-sizing`` property. +Two new properties defined in this document are implemented in WeasyPrint: +the ``box-sizing`` and ``text-overflow`` properties. Some of the properties do not apply for WeasyPrint: ``cursor``, ``resize``, ``caret-color``, ``nav-(up|right|down|left)``. -The other properties are **not** implemented: ``outline-offset`` and -``text-overflow``. +The ``outline-offset`` property is **not** implemented. .. _CSS Basic User Interface Module Level 3: http://www.w3.org/TR/css-ui-3/