From de17c7c3a075d800b1b1304b49462b5f055f6dee Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Mon, 18 Jan 2021 17:40:32 +0100 Subject: [PATCH 01/16] Support the "continue" property --- weasyprint/css/properties.py | 1 + weasyprint/css/validation/properties.py | 7 +++++++ weasyprint/layout/blocks.py | 3 +++ 3 files changed, 11 insertions(+) diff --git a/weasyprint/css/properties.py b/weasyprint/css/properties.py index 19af719be..c6fb7b441 100644 --- a/weasyprint/css/properties.py +++ b/weasyprint/css/properties.py @@ -184,6 +184,7 @@ 'text_decoration_style': 'solid', # Overflow Module 3 (WD): https://www.w3.org/TR/css-overflow-3/ + 'continue': 'auto', 'overflow': 'visible', 'text_overflow': 'clip', diff --git a/weasyprint/css/validation/properties.py b/weasyprint/css/validation/properties.py index d3aadd882..9e2fe5130 100644 --- a/weasyprint/css/validation/properties.py +++ b/weasyprint/css/validation/properties.py @@ -326,6 +326,13 @@ def box_decoration_break(keyword): return keyword in ('slice', 'clone') +@property('continue', unstable=True) +@single_keyword +def continue_(keyword): + """``continue`` property validation.""" + return keyword in ('auto', 'discard') + + @property(unstable=True) @single_keyword def margin_break(keyword): diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index b487bec4f..e265f5226 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -595,6 +595,9 @@ def block_container_layout(context, box, max_position_y, skip_stack, else: resume_at = None + if box.style['continue'] == 'discard': + resume_at = None + if (resume_at is not None and box.style['break_inside'] in ('avoid', 'avoid-page') and not page_is_empty): From 74ce9ad8a37367fee01cd090bfae07397eeedf2a Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Mon, 18 Jan 2021 18:18:47 +0100 Subject: [PATCH 02/16] Naive implementation of max-lines --- weasyprint/css/properties.py | 1 + weasyprint/css/validation/properties.py | 11 +++++++++++ weasyprint/layout/blocks.py | 8 +++++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/weasyprint/css/properties.py b/weasyprint/css/properties.py index c6fb7b441..58ed9cb47 100644 --- a/weasyprint/css/properties.py +++ b/weasyprint/css/properties.py @@ -185,6 +185,7 @@ # Overflow Module 3 (WD): https://www.w3.org/TR/css-overflow-3/ 'continue': 'auto', + 'max_lines': 'none', 'overflow': 'visible', 'text_overflow': 'clip', diff --git a/weasyprint/css/validation/properties.py b/weasyprint/css/validation/properties.py index 9e2fe5130..637bb2a8b 100644 --- a/weasyprint/css/validation/properties.py +++ b/weasyprint/css/validation/properties.py @@ -333,6 +333,17 @@ def continue_(keyword): return keyword in ('auto', 'discard') +@property(unstable=True) +@single_token +def max_lines(token): + if token.type == 'number' and token.int_value is not None: + if token.int_value >= 1: + return token.int_value + keyword = get_keyword(token) + if keyword == 'none': + return keyword + + @property(unstable=True) @single_keyword def margin_break(keyword): diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index e265f5226..6c88e2505 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -371,7 +371,7 @@ def block_container_layout(context, box, max_position_y, skip_stack, new_containing_block, absolute_boxes, fixed_boxes, first_letter_style) is_page_break = False - for line, resume_at in lines_iterator: + for i, (line, resume_at) in enumerate(lines_iterator): line.resume_at = resume_at new_position_y = line.position_y + line.height @@ -431,6 +431,12 @@ def block_container_layout(context, box, max_position_y, skip_stack, new_children.append(line) position_y = new_position_y skip_stack = resume_at + + # Break box if we reached max-lines + if box.style['max_lines'] != 'none': + if i >= box.style['max_lines'] - 1: + break + if new_children: resume_at = (index, new_children[-1].resume_at) if is_page_break: From d24c6246fafdfdc1aa5c864af7f25ca12270e10f Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Fri, 22 Jan 2021 16:48:26 +0100 Subject: [PATCH 03/16] Fix box fragmentation when "continue" is set to "discard" --- weasyprint/layout/blocks.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 6c88e2505..aa2fa9c1e 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -601,10 +601,11 @@ def block_container_layout(context, box, max_position_y, skip_stack, else: resume_at = None + box_is_fragmented = resume_at is not None if box.style['continue'] == 'discard': resume_at = None - if (resume_at is not None and + if (box_is_fragmented and box.style['break_inside'] in ('avoid', 'avoid-page') and not page_is_empty): return ( @@ -648,7 +649,7 @@ def block_container_layout(context, box, max_position_y, skip_stack, adjoining_margins = [] new_box = box.copy_with_children(new_children) - new_box.remove_decoration(start=not is_start, end=resume_at is not None) + new_box.remove_decoration(start=not is_start, end=box_is_fragmented) # TODO: See corner cases in # http://www.w3.org/TR/CSS21/visudet.html#normal-block @@ -672,13 +673,13 @@ def block_container_layout(context, box, max_position_y, skip_stack, if not isinstance(new_box, boxes.BlockBox): context.finish_block_formatting_context(new_box) - if resume_at is None: + if not box_is_fragmented: # After finish_block_formatting_context which may increment # new_box.height new_box.height = max( min(new_box.height, new_box.max_height), new_box.min_height) - else: + elif max_position_y < float('inf'): # Make the box fill the blank space at the bottom of the page # https://www.w3.org/TR/css-break-3/#box-splitting new_box.height = ( From bb2d1e14b54c6616d8dd7d92ca1527405b107c54 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Fri, 22 Jan 2021 16:54:44 +0100 Subject: [PATCH 04/16] Validate block-ellipsis --- weasyprint/css/properties.py | 2 ++ weasyprint/css/validation/properties.py | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/weasyprint/css/properties.py b/weasyprint/css/properties.py index 58ed9cb47..b838775de 100644 --- a/weasyprint/css/properties.py +++ b/weasyprint/css/properties.py @@ -184,6 +184,7 @@ 'text_decoration_style': 'solid', # Overflow Module 3 (WD): https://www.w3.org/TR/css-overflow-3/ + 'block-ellipsis': 'none', 'continue': 'auto', 'max_lines': 'none', 'overflow': 'visible', @@ -220,6 +221,7 @@ # link: click events normally bubble up to link ancestors # See http://lists.w3.org/Archives/Public/www-style/2012Jun/0315.html INHERITED = { + 'block-ellipsis', 'border_collapse', 'border_spacing', 'caption_side', diff --git a/weasyprint/css/validation/properties.py b/weasyprint/css/validation/properties.py index 637bb2a8b..dd2ccba36 100644 --- a/weasyprint/css/validation/properties.py +++ b/weasyprint/css/validation/properties.py @@ -326,6 +326,18 @@ def box_decoration_break(keyword): return keyword in ('slice', 'clone') +@property() +@single_token +def block_ellipsis(token): + """``box-ellipsis`` property validation.""" + if token.type == 'string': + return ('string', token.value) + else: + keyword = get_keyword(token) + if keyword in ('none', 'auto'): + return keyword + + @property('continue', unstable=True) @single_keyword def continue_(keyword): From 301aa7912ce73d849ea27db439dd2d94d28c1cd5 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Fri, 22 Jan 2021 17:04:37 +0100 Subject: [PATCH 05/16] Handle line-clamp --- weasyprint/css/validation/expanders.py | 32 ++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/weasyprint/css/validation/expanders.py b/weasyprint/css/validation/expanders.py index 7dd88f3c0..7033d623f 100644 --- a/weasyprint/css/validation/expanders.py +++ b/weasyprint/css/validation/expanders.py @@ -16,11 +16,12 @@ from .descriptors import expand_font_variant from .properties import ( background_attachment, background_image, background_position, - background_repeat, background_size, border_style, border_width, box, - column_count, column_width, flex_basis, flex_direction, flex_grow_shrink, - flex_wrap, font_family, font_size, font_stretch, font_style, font_weight, - line_height, list_style_image, list_style_position, list_style_type, - other_colors, overflow_wrap, validate_non_shorthand) + background_repeat, background_size, block_ellipsis, border_style, + border_width, box, column_count, column_width, flex_basis, flex_direction, + flex_grow_shrink, flex_wrap, font_family, font_size, font_stretch, + font_style, font_weight, line_height, list_style_image, + list_style_position, list_style_type, other_colors, overflow_wrap, + validate_non_shorthand) EXPANDERS = {} @@ -607,3 +608,24 @@ def expand_flex_flow(base_url, name, tokens): raise InvalidValues else: raise InvalidValues + + +@expander('line-clamp') +def expand_line_clamp(base_url, name, tokens): + """Expand the ``line-clamp`` property.""" + if len(tokens) == 1: + keyword = get_single_keyword(tokens) + if keyword == 'none': + yield 'max_lines', 'none' + yield 'continue', 'auto' + yield 'block-ellipsis', 'none' + elif tokens[0].type == 'integer': + yield 'max_lines', tokens[0].value + yield 'continue', 'discard' + yield 'block-ellipsis', 'auto' + elif len(tokens) == 2: + ellipsis = block_ellipsis([tokens[1]]) + if tokens[0].type == 'integer' and ellipsis is not None: + yield 'max_lines', tokens[0].value + yield 'continue', 'discard' + yield 'block-ellipsis', ellipsis From 5920bf41fcb3aaa0efe9387d13ccf866290f4003 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Fri, 22 Jan 2021 17:27:15 +0100 Subject: [PATCH 06/16] Fix line-clamp and block-ellipsis validation --- weasyprint/css/properties.py | 4 ++-- weasyprint/css/validation/expanders.py | 16 +++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/weasyprint/css/properties.py b/weasyprint/css/properties.py index b838775de..c529b2bdd 100644 --- a/weasyprint/css/properties.py +++ b/weasyprint/css/properties.py @@ -184,7 +184,7 @@ 'text_decoration_style': 'solid', # Overflow Module 3 (WD): https://www.w3.org/TR/css-overflow-3/ - 'block-ellipsis': 'none', + 'block_ellipsis': 'none', 'continue': 'auto', 'max_lines': 'none', 'overflow': 'visible', @@ -221,7 +221,7 @@ # link: click events normally bubble up to link ancestors # See http://lists.w3.org/Archives/Public/www-style/2012Jun/0315.html INHERITED = { - 'block-ellipsis', + 'block_ellipsis', 'border_collapse', 'border_spacing', 'caption_side', diff --git a/weasyprint/css/validation/expanders.py b/weasyprint/css/validation/expanders.py index 7033d623f..379c9a735 100644 --- a/weasyprint/css/validation/expanders.py +++ b/weasyprint/css/validation/expanders.py @@ -619,13 +619,15 @@ def expand_line_clamp(base_url, name, tokens): yield 'max_lines', 'none' yield 'continue', 'auto' yield 'block-ellipsis', 'none' - elif tokens[0].type == 'integer': - yield 'max_lines', tokens[0].value + elif tokens[0].type == 'number' and tokens[0].int_value is not None: + yield 'max_lines', tokens[0].int_value yield 'continue', 'discard' yield 'block-ellipsis', 'auto' elif len(tokens) == 2: - ellipsis = block_ellipsis([tokens[1]]) - if tokens[0].type == 'integer' and ellipsis is not None: - yield 'max_lines', tokens[0].value - yield 'continue', 'discard' - yield 'block-ellipsis', ellipsis + if tokens[0].type == 'number': + max_lines = tokens[0].int_value + ellipsis = block_ellipsis([tokens[1]]) + if max_lines and ellipsis is not None: + yield 'max_lines', tokens[0].value + yield 'continue', 'discard' + yield 'block-ellipsis', ellipsis From 75971401b0fdf3a898feeebb7eab409e218fe09b Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Fri, 22 Jan 2021 17:38:32 +0100 Subject: [PATCH 07/16] Set text-overflow on last line when block-ellipsis is not "none" --- weasyprint/layout/blocks.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index aa2fa9c1e..58bf434ba 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -435,6 +435,8 @@ def block_container_layout(context, box, max_position_y, skip_stack, # Break box if we reached max-lines if box.style['max_lines'] != 'none': if i >= box.style['max_lines'] - 1: + if box.style['block_ellipsis'] != 'none': + line.text_overflow = 'ellipsis' break if new_children: From cbc632202293492c738b3468f4937de877610ef9 Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Fri, 22 Jan 2021 19:40:59 +0100 Subject: [PATCH 08/16] Split block-ellipsis and text-overflow implementations The two properties work in very different ways, as described by the specification. text-overflow only draws ellipsis at the end of the line, but block-ellipsis requires a clean line break at word boundaries. --- weasyprint/draw.py | 21 +++++++---- weasyprint/formatting_structure/boxes.py | 1 + weasyprint/layout/blocks.py | 8 +++-- weasyprint/text.py | 45 +++++++++++++++++++----- 4 files changed, 59 insertions(+), 16 deletions(-) diff --git a/weasyprint/draw.py b/weasyprint/draw.py index c196d2d7a..2844b57fb 100644 --- a/weasyprint/draw.py +++ b/weasyprint/draw.py @@ -991,7 +991,8 @@ def draw_replacedbox(context, box): context, draw_width, draw_height, box.style['image_rendering']) -def draw_inline_level(context, page, box, offset_x=0, text_overflow='clip'): +def draw_inline_level(context, page, box, offset_x=0, text_overflow='clip', + block_ellipsis='none'): if isinstance(box, StackingContext): stacking_context = box assert isinstance( @@ -1003,8 +1004,13 @@ def draw_inline_level(context, page, box, offset_x=0, text_overflow='clip'): if isinstance(box, (boxes.InlineBox, boxes.LineBox)): if isinstance(box, boxes.LineBox): text_overflow = box.text_overflow + block_ellipsis = box.block_ellipsis in_text = False - for child in box.children: + ellipsis = 'none' + for i, child in enumerate(box.children): + if i == len(box.children) - 1: + # Last child + ellipsis = block_ellipsis if isinstance(child, StackingContext): child_offset_x = offset_x else: @@ -1014,13 +1020,16 @@ def draw_inline_level(context, page, box, offset_x=0, text_overflow='clip'): if not in_text: context.begin_text() in_text = True - draw_text(context, child, child_offset_x, text_overflow) + draw_text( + context, child, child_offset_x, text_overflow, + ellipsis) else: if in_text: in_text = False context.end_text() draw_inline_level( - context, page, child, child_offset_x, text_overflow) + context, page, child, child_offset_x, text_overflow, + ellipsis) if in_text: context.end_text() elif isinstance(box, boxes.InlineReplacedBox): @@ -1033,7 +1042,7 @@ def draw_inline_level(context, page, box, offset_x=0, text_overflow='clip'): context.end_text() -def draw_text(context, textbox, offset_x, text_overflow): +def draw_text(context, textbox, offset_x, text_overflow, block_ellipsis): """Draw a textbox to a pydyf stream.""" # Pango crashes with font-size: 0 assert textbox.style['font_size'] @@ -1046,7 +1055,7 @@ def draw_text(context, textbox, offset_x, text_overflow): context.set_alpha(textbox.style['color'][3]) textbox.pango_layout.reactivate(textbox.style) - show_first_line(context, textbox, text_overflow, x, y) + show_first_line(context, textbox, text_overflow, block_ellipsis, x, y) values = textbox.style['text_decoration_line'] diff --git a/weasyprint/formatting_structure/boxes.py b/weasyprint/formatting_structure/boxes.py index 72d54bd3a..8ab289c15 100644 --- a/weasyprint/formatting_structure/boxes.py +++ b/weasyprint/formatting_structure/boxes.py @@ -409,6 +409,7 @@ class LineBox(ParentBox): """ text_overflow = 'clip' + block_ellipsis = 'none' @classmethod def anonymous_from(cls, parent, *args, **kwargs): diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 58bf434ba..7c8d35c44 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -435,8 +435,7 @@ def block_container_layout(context, box, max_position_y, skip_stack, # Break box if we reached max-lines if box.style['max_lines'] != 'none': if i >= box.style['max_lines'] - 1: - if box.style['block_ellipsis'] != 'none': - line.text_overflow = 'ellipsis' + line.block_ellipsis = box.style['block_ellipsis'] break if new_children: @@ -650,6 +649,11 @@ def block_container_layout(context, box, max_position_y, skip_stack, position_y += collapse_margin(adjoining_margins) adjoining_margins = [] + if box_is_fragmented and new_children: + last_child = new_children[-1] + if isinstance(last_child, boxes.LineBox): + last_child.block_ellipsis = box.style['block_ellipsis'] + new_box = box.copy_with_children(new_children) new_box.remove_decoration(start=not is_start, end=box_is_fragmented) diff --git a/weasyprint/text.py b/weasyprint/text.py index b579c5b19..e72121650 100644 --- a/weasyprint/text.py +++ b/weasyprint/text.py @@ -1298,20 +1298,40 @@ def split_first_line(text, style, context, max_width, justification_spacing, style['hyphenate_character']) -def show_first_line(context, textbox, text_overflow, x, y): +def show_first_line(context, textbox, text_overflow, block_ellipsis, x, y): """Draw the given ``textbox`` line to the document ``context``.""" pango.pango_layout_set_single_paragraph_mode( textbox.pango_layout.layout, True) - if text_overflow == 'ellipsis': + if text_overflow == 'ellipsis' or block_ellipsis != 'none': assert textbox.pango_layout.max_width is not None max_width = textbox.pango_layout.max_width 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() + if text_overflow == 'ellipsis': + pango.pango_layout_set_ellipsize( + textbox.pango_layout.layout, pango.PANGO_ELLIPSIZE_END) + else: + if block_ellipsis == 'auto': + ellipsis = '…' + else: + assert block_ellipsis[0] == 'string' + ellipsis = block_ellipsis[1] + textbox.pango_layout.set_text(textbox.pango_layout.text + ellipsis) + + first_line, second_line = textbox.pango_layout.get_first_line() + + if block_ellipsis != 'none': + while second_line: + last_word_end = get_last_word_end( + textbox.pango_layout.text[:-len(ellipsis)], + textbox.style['lang']) + if last_word_end is None: + break + new_text = utf8_slice( + textbox.pango_layout.text, slice(0, last_word_end)) + textbox.pango_layout.set_text(new_text + ellipsis) + first_line, second_line = textbox.pango_layout.get_first_line() font_size = textbox.style['font_size'] utf8_text = textbox.text.encode('utf-8') @@ -1393,8 +1413,8 @@ def show_first_line(context, textbox, text_overflow, x, y): # Mapping between glyphs and characters if glyph not in font.cmap and glyph != pango.PANGO_GLYPH_EMPTY: - utf8_slice = slice(previous_utf8_position, utf8_position) - font.cmap[glyph] = utf8_text[utf8_slice].decode('utf-8') + slice_ = slice(previous_utf8_position, utf8_position) + font.cmap[glyph] = utf8_text[slice_].decode('utf-8') previous_utf8_position = utf8_position # Close the last glyphs list, remove if empty @@ -1446,3 +1466,12 @@ def get_next_word_boundaries(text, lang): else: return None return word_start, word_end + + +def get_last_word_end(text, lang): + if not text or len(text) < 2: + return None + bytestring, log_attrs = get_log_attrs(text, lang) + for i, attr in enumerate(list(log_attrs)[::-1]): + if i and attr.is_word_end: + return len(bytestring) - i From d8454db2951c666b52edaca8f95b576495d0291f Mon Sep 17 00:00:00 2001 From: Lucie Anglade Date: Sun, 14 Feb 2021 18:58:50 +0100 Subject: [PATCH 09/16] Tests for line-clamp --- tests/test_draw/test_text.py | 67 ++++++++++++++++++++++++++++++++++++ tests/test_text.py | 52 ++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/tests/test_draw/test_text.py b/tests/test_draw/test_text.py index 897096fa7..864e017eb 100644 --- a/tests/test_draw/test_text.py +++ b/tests/test_draw/test_text.py @@ -116,3 +116,70 @@ def test_text_align_rtl_trailing_whitespace():

abc

‏abc

''') + + +def test_max_lines_ellipsis(): + assert_pixels('max_lines_ellipsis', 10, 10, ''' + BBBBBBBB__ + BBBBBBBB__ + BBBBBBBBBB + BBBBBBBBBB + __________ + __________ + __________ + __________ + __________ + __________ + ''', ''' + +

+ abcd efgh ijkl +

+ ''') + + +def test_line_clamp(): + assert_pixels('line_clamp', 10, 10, ''' + BBBBBBBB__ + BBBBBBBB__ + BBBBBBBB__ + BBBBBBBB__ + BBBBBBBBBB + BBBBBBBBBB + __________ + __________ + __________ + __________ + ''', ''' + + +

+ aaaa + bbbb + cccc + dddd + eeee + ffff + gggg + hhhh +

+ ''') diff --git a/tests/test_text.py b/tests/test_text.py index 804e3c942..04a87814c 100644 --- a/tests/test_text.py +++ b/tests/test_text.py @@ -1050,3 +1050,55 @@ def test_text_floating_pre_line():
This is oh this end
''') + + +@assert_no_logs +def test_max_lines(): + page, = render_pages(''' + +

+ abcd efgh ijkl +

+ ''') + html, = page.children + body, = html.children + p, = body.children + line1, line2 = p.children + text1, = line1.children + text2, = line2.children + assert text1.text == 'abcd' + assert text2.text == 'efgh' + + +@assert_no_logs +def test_continue(): + page, = render_pages(''' + +
+ abcd efgh ijkl +
+ ''') + html, = page.children + body, = html.children + p, = body.children + line1, line2 = p.children + text1, = line1.children + text2, = line2.children + assert text1.text == 'abcd' + assert text2.text == 'efgh' From a9306cc58ef1c55d29cf3496c26274937c2008a7 Mon Sep 17 00:00:00 2001 From: Lucie Anglade Date: Sun, 14 Feb 2021 19:06:57 +0100 Subject: [PATCH 10/16] Better ellipsis test --- tests/test_draw/test_text.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/test_draw/test_text.py b/tests/test_draw/test_text.py index 864e017eb..18fa6560a 100644 --- a/tests/test_draw/test_text.py +++ b/tests/test_draw/test_text.py @@ -150,10 +150,10 @@ def test_max_lines_ellipsis(): def test_line_clamp(): assert_pixels('line_clamp', 10, 10, ''' - BBBBBBBB__ - BBBBBBBB__ - BBBBBBBB__ - BBBBBBBB__ + BBBB__BB__ + BBBB__BB__ + BBBB__BB__ + BBBB__BB__ BBBBBBBBBB BBBBBBBBBB __________ @@ -173,9 +173,9 @@ def test_line_clamp():

- aaaa - bbbb - cccc + aa a + bb b + cc c dddd eeee ffff From 81dc9f48b5f7fe444b279a02aa0f5bdcaccc46fe Mon Sep 17 00:00:00 2001 From: Lucie Anglade Date: Sun, 14 Feb 2021 19:35:38 +0100 Subject: [PATCH 11/16] Add failing tests for line-clamp --- tests/test_draw/test_text.py | 84 ++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/tests/test_draw/test_text.py b/tests/test_draw/test_text.py index 18fa6560a..35a6c66fc 100644 --- a/tests/test_draw/test_text.py +++ b/tests/test_draw/test_text.py @@ -6,6 +6,8 @@ """ +import pytest + from . import assert_pixels @@ -148,6 +150,52 @@ def test_max_lines_ellipsis(): ''') +@pytest.mark.xfail +def test_max_lines_nested(): + assert_pixels('max_lines_nested', 10, 10, ''' + BBBBBBBBBB + BBBBBBBBBB + BBBBBBBBBB + BBBBBBBBBB + rrrrrrrrrr + rrrrrrrrrr + rrrrrrrrrr + rrrrrrrrrr + BBBBBBBBBB + BBBBBBBBBB + ''', ''' + +

+ aaaaa + aaaaa +
+ bbbbb + bbbbb + bbbbb + bbbbb +
+ aaaaa + aaaaa +
+ ''') + + def test_line_clamp(): assert_pixels('line_clamp', 10, 10, ''' BBBB__BB__ @@ -183,3 +231,39 @@ def test_line_clamp(): hhhh

''') + + +@pytest.mark.xfail +def test_ellipsis_nested(): + assert_pixels('ellipsis_nested', 10, 10, ''' + BBBBBB____ + BBBBBB____ + BBBBBB____ + BBBBBB____ + BBBBBB____ + BBBBBB____ + BBBBBB____ + BBBBBB____ + BBBBBBBB__ + BBBBBBBB__ + ''', ''' + +
+

aaa

+

aaa

+

aaa

+

aaa

+

aaa

+

aaa

+
+ ''') From c4afd1fcebdf7f7ed87f59fb7d10114864c69dd0 Mon Sep 17 00:00:00 2001 From: Lucie Anglade Date: Sun, 14 Feb 2021 19:43:49 +0100 Subject: [PATCH 12/16] =?UTF-8?q?Taller=20image=20to=20be=20sure=20it?= =?UTF-8?q?=E2=80=99s=20cut=20because=20of=20max-lines=20and=20not=20the?= =?UTF-8?q?=20end=20of=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_draw/test_text.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_draw/test_text.py b/tests/test_draw/test_text.py index 35a6c66fc..06bce93c7 100644 --- a/tests/test_draw/test_text.py +++ b/tests/test_draw/test_text.py @@ -152,7 +152,7 @@ def test_max_lines_ellipsis(): @pytest.mark.xfail def test_max_lines_nested(): - assert_pixels('max_lines_nested', 10, 10, ''' + assert_pixels('max_lines_nested', 10, 12, ''' BBBBBBBBBB BBBBBBBBBB BBBBBBBBBB @@ -163,9 +163,11 @@ def test_max_lines_nested(): rrrrrrrrrr BBBBBBBBBB BBBBBBBBBB + __________ + __________ ''', ''' +
+
a
+
b
+
c
+
d
+
e
+
f
+
''') + html, = page_1.children + body, = html.children + article, = body.children + assert article.height == 3 * 25 + div_1, div_2, div_3 = article.children + assert div_1.position_y == 1 + assert div_2.position_y == 1 + 25 + assert div_3.position_y == 1 + 25 * 2 + assert article.border_bottom_width == 1 diff --git a/weasyprint/formatting_structure/boxes.py b/weasyprint/formatting_structure/boxes.py index 8ab289c15..041bfc9b1 100644 --- a/weasyprint/formatting_structure/boxes.py +++ b/weasyprint/formatting_structure/boxes.py @@ -310,7 +310,8 @@ def _reset_spacing(self, side): setattr(self, f'border_{side}_width', 0) def remove_decoration(self, start, end): - if self.style['box_decoration_break'] == 'clone': + if (self.style['box_decoration_break'] == 'clone' or + self.style['continue'] == 'discard'): return if start: self._reset_spacing('top') diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 7c8d35c44..5a4952c19 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -276,7 +276,10 @@ def block_container_layout(context, box, max_position_y, skip_stack, if adjoining_margins is None: adjoining_margins = [] - if box.style['box_decoration_break'] == 'clone': + repeat_box_decoration = ( + box.style['box_decoration_break'] == 'clone' or + box.style['continue'] == 'discard') + if repeat_box_decoration: max_position_y -= ( box.padding_bottom + box.border_bottom_width + box.margin_bottom) @@ -377,8 +380,11 @@ def block_container_layout(context, box, max_position_y, skip_stack, # Add bottom padding and border to the bottom position of the # box if needed - if resume_at is None or ( - box.style['box_decoration_break'] == 'clone'): + repeat_box_decoration = ( + resume_at is None or + box.style['box_decoration_break'] == 'clone' or + box.style['continue'] == 'discard') + if repeat_box_decoration: offset_y = box.border_bottom_width + box.padding_bottom else: offset_y = 0 @@ -688,9 +694,10 @@ def block_container_layout(context, box, max_position_y, skip_stack, elif max_position_y < float('inf'): # Make the box fill the blank space at the bottom of the page # https://www.w3.org/TR/css-break-3/#box-splitting - new_box.height = ( - max_position_y - new_box.position_y - - (new_box.margin_height() - new_box.height)) + if box.style['continue'] != 'discard': + new_box.height = ( + max_position_y - new_box.position_y - + (new_box.margin_height() - new_box.height)) if box.style['box_decoration_break'] == 'clone': new_box.height += ( box.padding_bottom + box.border_bottom_width + From da1219248a2383b2ad83136c43c32c8c22602efc Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Tue, 2 Mar 2021 13:06:53 +0100 Subject: [PATCH 15/16] =?UTF-8?q?Prevent=20discarded=20boxes=E2=80=99=20ch?= =?UTF-8?q?ildren=20from=20extending=20to=20the=20page=20bottom?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_layout/test_block.py | 32 ++++++++++++++++ weasyprint/formatting_structure/boxes.py | 3 +- weasyprint/layout/absolute.py | 2 +- weasyprint/layout/blocks.py | 48 ++++++++++++------------ weasyprint/layout/columns.py | 11 +++--- weasyprint/layout/flex.py | 10 ++--- weasyprint/layout/float.py | 2 +- weasyprint/layout/inlines.py | 2 +- weasyprint/layout/pages.py | 5 ++- weasyprint/layout/tables.py | 9 ++--- 10 files changed, 78 insertions(+), 46 deletions(-) diff --git a/tests/test_layout/test_block.py b/tests/test_layout/test_block.py index fb78a4580..8c35417d8 100644 --- a/tests/test_layout/test_block.py +++ b/tests/test_layout/test_block.py @@ -802,3 +802,35 @@ def test_continue_discard(): assert div_2.position_y == 1 + 25 assert div_3.position_y == 1 + 25 * 2 assert article.border_bottom_width == 1 + + +@assert_no_logs +def test_continue_discard_children(): + page_1, = parse(''' + +
+
+
a
+
b
+
c
+
d
+
e
+
f
+
+
''') + html, = page_1.children + body, = html.children + article, = body.children + assert article.height == 2 + 3 * 25 + section, = article.children + assert section.height == 3 * 25 + div_1, div_2, div_3 = section.children + assert div_1.position_y == 2 + assert div_2.position_y == 2 + 25 + assert div_3.position_y == 2 + 25 * 2 + assert article.border_bottom_width == 1 diff --git a/weasyprint/formatting_structure/boxes.py b/weasyprint/formatting_structure/boxes.py index 3cab0bda7..fe8d97528 100644 --- a/weasyprint/formatting_structure/boxes.py +++ b/weasyprint/formatting_structure/boxes.py @@ -312,8 +312,7 @@ def _reset_spacing(self, side): setattr(self, f'border_{side}_width', 0) def remove_decoration(self, start, end): - if (self.style['box_decoration_break'] == 'clone' or - self.style['continue'] == 'discard'): + if self.style['box_decoration_break'] == 'clone': return if start: self._reset_spacing('top') diff --git a/weasyprint/layout/absolute.py b/weasyprint/layout/absolute.py index 84e52eb76..a5062b443 100644 --- a/weasyprint/layout/absolute.py +++ b/weasyprint/layout/absolute.py @@ -206,7 +206,7 @@ def absolute_block(context, box, containing_block, fixed_boxes): new_box, _, _, _, _ = block_container_layout( context, box, max_position_y=float('inf'), skip_stack=None, page_is_empty=False, absolute_boxes=absolute_boxes, - fixed_boxes=fixed_boxes, adjoining_margins=None) + fixed_boxes=fixed_boxes, adjoining_margins=None, discard=False) for child_placeholder in absolute_boxes: absolute_layout(context, child_placeholder, new_box, fixed_boxes) diff --git a/weasyprint/layout/blocks.py b/weasyprint/layout/blocks.py index 5a4952c19..1250fc92a 100644 --- a/weasyprint/layout/blocks.py +++ b/weasyprint/layout/blocks.py @@ -21,7 +21,7 @@ def block_level_layout(context, box, max_position_y, skip_stack, containing_block, page_is_empty, absolute_boxes, - fixed_boxes, adjoining_margins): + fixed_boxes, adjoining_margins, discard): """Lay out the block-level ``box``. :param max_position_y: the absolute vertical position (as in @@ -57,12 +57,12 @@ def block_level_layout(context, box, max_position_y, skip_stack, return block_level_layout_switch( context, box, max_position_y, skip_stack, containing_block, - page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins) + page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins, discard) def block_level_layout_switch(context, box, max_position_y, skip_stack, containing_block, page_is_empty, absolute_boxes, - fixed_boxes, adjoining_margins): + fixed_boxes, adjoining_margins, discard): """Call the layout function corresponding to the ``box`` type.""" if isinstance(box, boxes.TableBox): return table_layout( @@ -71,7 +71,8 @@ def block_level_layout_switch(context, box, max_position_y, skip_stack, elif isinstance(box, boxes.BlockBox): return block_box_layout( context, box, max_position_y, skip_stack, containing_block, - page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins) + page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins, + discard) elif isinstance(box, boxes.BlockReplacedBox): box = block_replaced_box_layout(box, containing_block) # Don't collide with floats @@ -93,7 +94,7 @@ def block_level_layout_switch(context, box, max_position_y, skip_stack, def block_box_layout(context, box, max_position_y, skip_stack, containing_block, page_is_empty, absolute_boxes, - fixed_boxes, adjoining_margins): + fixed_boxes, adjoining_margins, discard): """Lay out the block ``box``.""" if (box.style['column_width'] != 'auto' or box.style['column_count'] != 'auto'): @@ -124,7 +125,7 @@ def block_box_layout(context, box, max_position_y, skip_stack, new_box, resume_at, next_page, adjoining_margins, collapsing_through = \ block_container_layout( context, box, max_position_y, skip_stack, page_is_empty, - absolute_boxes, fixed_boxes, adjoining_margins) + absolute_boxes, fixed_boxes, adjoining_margins, discard) if new_box and new_box.is_table_wrapper: # Don't collide with floats # http://www.w3.org/TR/CSS21/visuren.html#floats @@ -256,7 +257,7 @@ def relative_positioning(box, containing_block): def block_container_layout(context, box, max_position_y, skip_stack, page_is_empty, absolute_boxes, fixed_boxes, - adjoining_margins=None): + adjoining_margins, discard): """Set the ``box`` height.""" # TODO: boxes.FlexBox is allowed here because flex_layout calls # block_container_layout, there's probably a better solution. @@ -273,13 +274,14 @@ def block_container_layout(context, box, max_position_y, skip_stack, is_start = skip_stack is None box.remove_decoration(start=not is_start, end=False) + discard |= box.style['continue'] == 'discard' + draw_bottom_decoration = ( + discard or box.style['box_decoration_break'] == 'clone') + if adjoining_margins is None: adjoining_margins = [] - repeat_box_decoration = ( - box.style['box_decoration_break'] == 'clone' or - box.style['continue'] == 'discard') - if repeat_box_decoration: + if draw_bottom_decoration: max_position_y -= ( box.padding_bottom + box.border_bottom_width + box.margin_bottom) @@ -380,11 +382,8 @@ def block_container_layout(context, box, max_position_y, skip_stack, # Add bottom padding and border to the bottom position of the # box if needed - repeat_box_decoration = ( - resume_at is None or - box.style['box_decoration_break'] == 'clone' or - box.style['continue'] == 'discard') - if repeat_box_decoration: + draw_bottom_decoration |= resume_at is None + if draw_bottom_decoration: offset_y = box.border_bottom_width + box.padding_bottom else: offset_y = 0 @@ -518,7 +517,7 @@ def block_container_layout(context, box, max_position_y, skip_stack, collapsing_through) = block_level_layout( context, child, max_position_y, skip_stack, new_containing_block, page_is_empty_with_no_children, - absolute_boxes, fixed_boxes, adjoining_margins) + absolute_boxes, fixed_boxes, adjoining_margins, discard) skip_stack = None if new_child is not None: @@ -655,13 +654,15 @@ def block_container_layout(context, box, max_position_y, skip_stack, position_y += collapse_margin(adjoining_margins) adjoining_margins = [] + # Add block ellipsis if box_is_fragmented and new_children: last_child = new_children[-1] if isinstance(last_child, boxes.LineBox): last_child.block_ellipsis = box.style['block_ellipsis'] new_box = box.copy_with_children(new_children) - new_box.remove_decoration(start=not is_start, end=box_is_fragmented) + new_box.remove_decoration( + start=not is_start, end=box_is_fragmented and not discard) # TODO: See corner cases in # http://www.w3.org/TR/CSS21/visudet.html#normal-block @@ -685,7 +686,7 @@ def block_container_layout(context, box, max_position_y, skip_stack, if not isinstance(new_box, boxes.BlockBox): context.finish_block_formatting_context(new_box) - if not box_is_fragmented: + if discard or not box_is_fragmented: # After finish_block_formatting_context which may increment # new_box.height new_box.height = max( @@ -694,11 +695,10 @@ def block_container_layout(context, box, max_position_y, skip_stack, elif max_position_y < float('inf'): # Make the box fill the blank space at the bottom of the page # https://www.w3.org/TR/css-break-3/#box-splitting - if box.style['continue'] != 'discard': - new_box.height = ( - max_position_y - new_box.position_y - - (new_box.margin_height() - new_box.height)) - if box.style['box_decoration_break'] == 'clone': + new_box.height = ( + max_position_y - new_box.position_y - + (new_box.margin_height() - new_box.height)) + if draw_bottom_decoration: new_box.height += ( box.padding_bottom + box.border_bottom_width + box.margin_bottom) diff --git a/weasyprint/layout/columns.py b/weasyprint/layout/columns.py index 618ffdf83..458e8563c 100644 --- a/weasyprint/layout/columns.py +++ b/weasyprint/layout/columns.py @@ -124,7 +124,7 @@ def create_column_box(children): new_child, _, _, adjoining_margins, _ = block_level_layout( context, block, original_max_position_y, skip_stack, containing_block, page_is_empty, absolute_boxes, fixed_boxes, - adjoining_margins) + adjoining_margins, discard=False) new_children.append(new_child) current_position_y = ( new_child.border_height() + new_child.border_box_y()) @@ -142,7 +142,7 @@ def create_column_box(children): column_box = create_column_box(column_children) new_child, _, _, _, _ = block_box_layout( context, column_box, float('inf'), skip_stack, containing_block, - page_is_empty, [], [], []) + page_is_empty, [], [], [], discard=False) height = new_child.margin_height() if style['column_fill'] == 'balance': height /= count @@ -163,7 +163,7 @@ def create_column_box(children): new_box, resume_at, next_page, _, _ = block_box_layout( context, column_box, box.content_box_y() + height, column_skip_stack, containing_block, page_is_empty, - [], [], []) + [], [], [], discard=False) if new_box is None: # We didn't render anything. Give up and use the max # content height. @@ -184,7 +184,8 @@ def create_column_box(children): # Get the minimum size needed to render the next box next_box, _, _, _, _ = block_box_layout( context, column_box, box.content_box_y(), - column_skip_stack, containing_block, True, [], [], []) + column_skip_stack, containing_block, True, [], [], [], + discard=False) for child in next_box.children: if child.is_in_normal_flow(): next_box_size = child.margin_height() @@ -246,7 +247,7 @@ def create_column_box(children): block_box_layout( context, column_box, max_position_y, skip_stack, containing_block, page_is_empty, absolute_boxes, - fixed_boxes, None)) + fixed_boxes, None, discard=False)) if new_child is None: break next_page = column_next_page diff --git a/weasyprint/layout/flex.py b/weasyprint/layout/flex.py index 029d6e6f4..c6d99d747 100644 --- a/weasyprint/layout/flex.py +++ b/weasyprint/layout/flex.py @@ -154,7 +154,7 @@ def flex_layout(context, box, max_position_y, skip_stack, containing_block, new_child.style['max_height'] = Dimension(float('inf'), 'px') new_child = blocks.block_level_layout( context, new_child, float('inf'), child_skip_stack, - parent_box, page_is_empty, [], [], [])[0] + parent_box, page_is_empty, [], [], [], False)[0] content_size = new_child.height child.min_height = min(specified_size, content_size) @@ -214,7 +214,7 @@ def flex_layout(context, box, max_position_y, skip_stack, containing_block, new_child = blocks.block_level_layout( context, new_child, float('inf'), child_skip_stack, parent_box, page_is_empty, absolute_boxes, fixed_boxes, - adjoining_margins=[])[0] + adjoining_margins=[], discard=False)[0] child.flex_base_size = new_child.margin_height() elif child.style[axis] == 'min-content': child.style[axis] = 'auto' @@ -229,7 +229,7 @@ def flex_layout(context, box, max_position_y, skip_stack, containing_block, new_child = blocks.block_level_layout( context, new_child, float('inf'), child_skip_stack, parent_box, page_is_empty, absolute_boxes, fixed_boxes, - adjoining_margins=[])[0] + adjoining_margins=[], discard=False)[0] child.flex_base_size = new_child.margin_height() else: assert child.style[axis].unit == 'px' @@ -463,7 +463,7 @@ def flex_layout(context, box, max_position_y, skip_stack, containing_block, blocks.block_level_layout_switch( context, child_copy, float('inf'), child_skip_stack, parent_box, page_is_empty, absolute_boxes, fixed_boxes, - adjoining_margins=[])) + adjoining_margins=[], discard=False)) child._baseline = find_in_flow_baseline(new_child) or 0 if cross == 'height': @@ -842,7 +842,7 @@ def flex_layout(context, box, max_position_y, skip_stack, containing_block, new_child, child_resume_at = blocks.block_level_layout_switch( context, child, max_position_y, child_skip_stack, box, page_is_empty, absolute_boxes, fixed_boxes, - adjoining_margins=[])[:2] + adjoining_margins=[], discard=False)[:2] if new_child is None: if resume_at and resume_at[0]: resume_at = (resume_at[0] + i - 1, None) diff --git a/weasyprint/layout/float.py b/weasyprint/layout/float.py index 23b8bfcc1..4eed8af30 100644 --- a/weasyprint/layout/float.py +++ b/weasyprint/layout/float.py @@ -66,7 +66,7 @@ def float_layout(context, box, containing_block, absolute_boxes, fixed_boxes): context, box, max_position_y=float('inf'), skip_stack=None, page_is_empty=False, absolute_boxes=absolute_boxes, fixed_boxes=fixed_boxes, - adjoining_margins=None) + adjoining_margins=None, discard=False) context.finish_block_formatting_context(box) elif isinstance(box, boxes.FlexContainerBox): box, _, _, _, _ = flex_layout( diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index 3090ee13d..711de4b9a 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -535,7 +535,7 @@ def inline_block_box_layout(context, box, position_x, skip_stack, box, _, _, _, _ = block_container_layout( context, box, max_position_y=float('inf'), skip_stack=skip_stack, page_is_empty=True, absolute_boxes=absolute_boxes, - fixed_boxes=fixed_boxes) + fixed_boxes=fixed_boxes, adjoining_margins=None, discard=False) box.baseline = inline_block_baseline(box) return box diff --git a/weasyprint/layout/pages.py b/weasyprint/layout/pages.py index 01483a8e6..f8f4b4dba 100644 --- a/weasyprint/layout/pages.py +++ b/weasyprint/layout/pages.py @@ -437,7 +437,8 @@ def margin_box_content_layout(context, page, box): box, resume_at, next_page, _, _ = block_container_layout( context, box, max_position_y=float('inf'), skip_stack=None, - page_is_empty=True, absolute_boxes=[], fixed_boxes=[]) + page_is_empty=True, absolute_boxes=[], fixed_boxes=[], + adjoining_margins=None, discard=False) assert resume_at is None vertical_align = box.style['vertical_align'] @@ -548,7 +549,7 @@ def make_page(context, root_box, page_type, resume_at, page_number, root_box, resume_at, next_page, _, _ = block_level_layout( context, root_box, page_content_bottom, resume_at, initial_containing_block, page_is_empty, positioned_boxes, - positioned_boxes, adjoining_margins) + positioned_boxes, adjoining_margins, discard=False) assert root_box page.fixed_boxes = [ diff --git a/weasyprint/layout/tables.py b/weasyprint/layout/tables.py index cbb7bd8ff..5e9c0ca4b 100644 --- a/weasyprint/layout/tables.py +++ b/weasyprint/layout/tables.py @@ -142,12 +142,11 @@ def group_layout(group, position_y, max_position_y, cell.computed_height = cell.height cell.height = 'auto' cell, _, _, _, _ = block_container_layout( - context, cell, - max_position_y=float('inf'), - skip_stack=None, - page_is_empty=False, + context, cell, max_position_y=float('inf'), + skip_stack=None, page_is_empty=False, absolute_boxes=absolute_boxes, - fixed_boxes=fixed_boxes) + fixed_boxes=fixed_boxes, adjoining_margins=None, + discard=False) cell.empty = not any( child.is_floated() or child.is_in_normal_flow() for child in cell.children) From 080dae8c6a617e92a46dfc227a8e6316bcfdf0aa Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Tue, 2 Mar 2021 15:23:01 +0100 Subject: [PATCH 16/16] Update the documentation of the overflow module --- docs/features.rst | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/docs/features.rst b/docs/features.rst index 5138e7a9f..2a949ce7b 100644 --- a/docs/features.rst +++ b/docs/features.rst @@ -494,22 +494,36 @@ The ``image-orientation`` property is **not** supported. .. _Image Values and Replaced Content Module Level 4: http://www.w3.org/TR/css4-images/ -CSS Basic User Interface Module Level 3 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +CSS Box Sizing Module Level 3 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The `CSS Box Sizing Module Level 3`_ is a candidate recommendation extending +"the CSS sizing properties with keywords that represent content-based +'intrinsic' sizes and context-based 'extrinsic' sizes." + +The new property defined in this document is implemented in WeasyPrint: +``box-sizing``. + +The ``min-content``, ``max-content`` and ``fit-content()`` sizing values are +**not** supported. + +.. _CSS Box Sizing Module Level 3: https://www.w3.org/TR/css-sizing-3/ -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." -Two new properties defined in this document are implemented in WeasyPrint: -the ``box-sizing`` and ``text-overflow`` properties. +CSS Overflow Module Level 3 +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The `CSS Overflow Module Level 3`_ is a working draft containing "the features +of CSS relating to scrollable overflow handling in visual media." -Some of the properties do not apply for WeasyPrint: ``cursor``, ``resize``, -``caret-color``, ``nav-(up|right|down|left)``. +The ``overflow`` property is supported, as defined in CSS2. ``overflow-x``, +``overflow-y``, ``overflow-clip-margin``, ``overflow-inline`` and +``overflow-block`` are **not** supported. -The ``outline-offset`` property is **not** implemented. +The ``text-overflow``, ``block-ellipsis``, ``line-clamp``, ``max-lines`` and +``continue`` properties are supported. -.. _CSS Basic User Interface Module Level 3: http://www.w3.org/TR/css-ui-3/ +.. _CSS Overflow Module Level 3: https://www.w3.org/TR/2020/WD-css-overflow-3-20200603/ CSS Values and Units Module Level 3