Skip to content

Commit

Permalink
Merge pull request #1660 from Kozea/line-clamp
Browse files Browse the repository at this point in the history
Support nested line-clamp
  • Loading branch information
liZe authored Jun 17, 2022
2 parents d8533da + d3883c4 commit c031a40
Show file tree
Hide file tree
Showing 10 changed files with 180 additions and 70 deletions.
78 changes: 78 additions & 0 deletions tests/draw/test_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,84 @@ def test_line_clamp_number(assert_pixels):
''')


def test_line_clamp_nested(assert_pixels):
assert_pixels('''
BBBB__BB__
BBBB__BB__
BBBB__BB__
BBBB__BB__
BBBBBBBBBB
BBBBBBBBBB
__________
__________
__________
__________
''', '''
<style>
@page {size: 10px 10px;}
@font-face {src: url(weasyprint.otf); font-family: weasyprint}
div {
color: blue;
font-family: weasyprint;
font-size: 2px;
line-clamp: 3 "(…)";
}
</style>
<div>
aa a
<p>
bb b
cc c
dddd
eeee
ffff
gggg
hhhh
</p>
</div>
''')


def test_line_clamp_nested_after(assert_pixels):
assert_pixels('''
BBBB__BB__
BBBB__BB__
BBBB__BB__
BBBB__BB__
BBBBBBBBBB
BBBBBBBBBB
__________
__________
__________
__________
''', '''
<style>
@page {size: 10px 10px;}
@font-face {src: url(weasyprint.otf); font-family: weasyprint}
div {
color: blue;
font-family: weasyprint;
font-size: 2px;
line-clamp: 3 "(…)";
}
</style>
<div>
aa a
<p>
bb b
</p>
cc c
dddd
eeee
ffff
gggg
hhhh
</div>
''')


@pytest.mark.xfail
def test_ellipsis_nested(assert_pixels):
assert_pixels('''
Expand Down
4 changes: 2 additions & 2 deletions weasyprint/layout/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,9 +356,9 @@ def _update_footnote_area(self):
if self.current_footnote_area.children:
footnote_area = build.create_anonymous_boxes(
self.current_footnote_area.deepcopy())
footnote_area, _, _, _, _ = block_level_layout(
footnote_area, _, _, _, _, _ = block_level_layout(
self, footnote_area, -inf, None,
self.current_footnote_area.page, True, [], [], [], False)
self.current_footnote_area.page, True, [], [], [], False, None)
self.current_footnote_area.height = footnote_area.height
self.page_bottom -= footnote_area.margin_height()
else:
Expand Down
4 changes: 2 additions & 2 deletions weasyprint/layout/absolute.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,10 @@ def absolute_block(context, box, containing_block, fixed_boxes, bottom_space,
table_wrapper_width(context, box, (cb_width, cb_height))

if isinstance(box, (boxes.BlockBox)):
new_box, resume_at, _, _, _ = block_container_layout(
new_box, resume_at, _, _, _, _ = block_container_layout(
context, box, bottom_space, skip_stack, page_is_empty=True,
absolute_boxes=absolute_boxes, fixed_boxes=fixed_boxes,
adjoining_margins=None, discard=False)
adjoining_margins=None, discard=False, max_lines=None)
else:
new_box, resume_at, _, _, _ = flex_layout(
context, box, bottom_space, skip_stack, containing_block,
Expand Down
102 changes: 66 additions & 36 deletions weasyprint/layout/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

def block_level_layout(context, box, bottom_space, skip_stack,
containing_block, page_is_empty, absolute_boxes,
fixed_boxes, adjoining_margins, discard):
fixed_boxes, adjoining_margins, discard, max_lines):
"""Lay out the block-level ``box``."""
if not isinstance(box, boxes.TableBox):
resolve_percentages(box, containing_block)
Expand Down Expand Up @@ -51,35 +51,38 @@ def block_level_layout(context, box, bottom_space, skip_stack,

return block_level_layout_switch(
context, box, bottom_space, skip_stack, containing_block,
page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins, discard)
page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins, discard,
max_lines)


def block_level_layout_switch(context, box, bottom_space, skip_stack,
containing_block, page_is_empty, absolute_boxes,
fixed_boxes, adjoining_margins, discard):
fixed_boxes, adjoining_margins, discard,
max_lines):
"""Call the layout function corresponding to the ``box`` type."""
if isinstance(box, boxes.TableBox):
return table_layout(
result = table_layout(
context, box, bottom_space, skip_stack, containing_block,
page_is_empty, absolute_boxes, fixed_boxes)
elif isinstance(box, boxes.BlockBox):
return block_box_layout(
context, box, bottom_space, skip_stack, containing_block,
page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins,
discard)
discard, max_lines)
elif isinstance(box, boxes.BlockReplacedBox):
return block_replaced_box_layout(context, box, containing_block)
result = block_replaced_box_layout(context, box, containing_block)
elif isinstance(box, boxes.FlexBox):
return flex_layout(
result = flex_layout(
context, box, bottom_space, skip_stack, containing_block,
page_is_empty, absolute_boxes, fixed_boxes)
else: # pragma: no cover
raise TypeError(f'Layout for {type(box).__name__} not handled yet')
return result + (None,)


def block_box_layout(context, box, bottom_space, skip_stack,
containing_block, page_is_empty, absolute_boxes,
fixed_boxes, adjoining_margins, discard):
fixed_boxes, adjoining_margins, discard, max_lines):
"""Lay out the block ``box``."""
if (box.style['column_width'] != 'auto' or
box.style['column_count'] != 'auto'):
Expand All @@ -99,15 +102,15 @@ def block_box_layout(context, box, bottom_space, skip_stack,
context, box, bottom_space, skip_stack,
containing_block, page_is_empty, absolute_boxes,
fixed_boxes, adjoining_margins)
return result
return result + (None,)
elif box.is_table_wrapper:
table_wrapper_width(
context, box, (containing_block.width, containing_block.height))
block_level_width(box, containing_block)

result = block_container_layout(
context, box, bottom_space, skip_stack, page_is_empty,
absolute_boxes, fixed_boxes, adjoining_margins, discard)
absolute_boxes, fixed_boxes, adjoining_margins, discard, max_lines)
new_box = result[0]
if new_box and new_box.is_table_wrapper:
# Don't collide with floats
Expand Down Expand Up @@ -290,7 +293,7 @@ def _break_line(context, box, line, new_children, lines_iterator,
def _linebox_layout(context, box, index, child, new_children, page_is_empty,
absolute_boxes, fixed_boxes, adjoining_margins,
bottom_space, position_y, skip_stack, first_letter_style,
draw_bottom_decoration):
draw_bottom_decoration, max_lines):
abort = stop = False
resume_at = None
new_footnotes = []
Expand All @@ -304,6 +307,14 @@ def _linebox_layout(context, box, index, child, new_children, page_is_empty,
context, child, position_y, bottom_space, skip_stack,
new_containing_block, absolute_boxes, fixed_boxes, first_letter_style)
for i, (line, resume_at) in enumerate(lines_iterator):
# Break box if we reached max-lines
if max_lines is not None:
if max_lines == 0:
new_children[-1].block_ellipsis = box.style['block_ellipsis']
break
max_lines -= 1

# Update line resume_at and position_y
line.resume_at = resume_at
new_position_y = line.position_y + line.height

Expand Down Expand Up @@ -378,23 +389,17 @@ def _linebox_layout(context, box, index, child, new_children, page_is_empty,
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:
line.block_ellipsis = box.style['block_ellipsis']
break

if new_children:
resume_at = {index: new_children[-1].resume_at}

return abort, stop, resume_at, position_y, new_footnotes
return abort, stop, resume_at, position_y, new_footnotes, max_lines


def _in_flow_layout(context, box, index, child, new_children, page_is_empty,
absolute_boxes, fixed_boxes, adjoining_margins,
bottom_space, position_y, skip_stack, first_letter_style,
draw_bottom_decoration, collapsing_with_children, discard,
next_page):
next_page, max_lines):
abort = stop = False

last_in_flow_child = find_last_in_flow_child(new_children)
Expand All @@ -409,7 +414,7 @@ def _in_flow_layout(context, box, index, child, new_children, page_is_empty,
stop = True
return (
abort, stop, resume_at, position_y, adjoining_margins,
next_page, new_children)
next_page, new_children, max_lines)
else:
page_break = 'auto'

Expand Down Expand Up @@ -457,10 +462,10 @@ def _in_flow_layout(context, box, index, child, new_children, page_is_empty,
if not getattr(child, 'first_letter_style', None):
child.first_letter_style = first_letter_style
(new_child, resume_at, next_page, next_adjoining_margins,
collapsing_through) = block_level_layout(
collapsing_through, max_lines) = block_level_layout(
context, child, bottom_space, skip_stack,
new_containing_block, page_is_empty_with_no_children, absolute_boxes,
fixed_boxes, adjoining_margins, discard)
fixed_boxes, adjoining_margins, discard, max_lines)

if new_child is not None:
# We need to do this after the child layout to have the
Expand Down Expand Up @@ -494,10 +499,11 @@ def _in_flow_layout(context, box, index, child, new_children, page_is_empty,
bottom_space += (
new_child.padding_bottom + new_child.border_bottom_width)
(new_child, resume_at, next_page, next_adjoining_margins,
collapsing_through) = block_level_layout(
collapsing_through, max_lines) = block_level_layout(
context, child, bottom_space, skip_stack,
new_containing_block, page_is_empty_with_no_children,
absolute_boxes, fixed_boxes, adjoining_margins, discard)
absolute_boxes, fixed_boxes, adjoining_margins, discard,
max_lines)
if new_child:
position_y = (
new_child.border_box_y() + new_child.border_height())
Expand All @@ -524,7 +530,7 @@ def _in_flow_layout(context, box, index, child, new_children, page_is_empty,
stop = True
return (
abort, stop, resume_at, position_y, adjoining_margins,
next_page, new_children)
next_page, new_children, max_lines)
else:
# We did not find any page break opportunity
if not page_is_empty:
Expand All @@ -534,7 +540,7 @@ def _in_flow_layout(context, box, index, child, new_children, page_is_empty,
abort = True
return (
abort, stop, resume_at, position_y, adjoining_margins,
next_page, new_children)
next_page, new_children, max_lines)
# else:
# ignore this 'avoid' and break anyway.

Expand All @@ -554,7 +560,7 @@ def _in_flow_layout(context, box, index, child, new_children, page_is_empty,
abort = True
return (
abort, stop, resume_at, position_y, adjoining_margins, next_page,
new_children)
new_children, max_lines)

# index in its non-laid-out parent, not in future new parent
# May be used in find_earlier_page_break()
Expand All @@ -566,12 +572,12 @@ def _in_flow_layout(context, box, index, child, new_children, page_is_empty,

return (
abort, stop, resume_at, position_y, adjoining_margins, next_page,
new_children)
new_children, max_lines)


def block_container_layout(context, box, bottom_space, skip_stack,
page_is_empty, absolute_boxes, fixed_boxes,
adjoining_margins, discard):
adjoining_margins, discard, max_lines):
"""Set the ``box`` height."""
# TODO: boxes.FlexBox is allowed here because flex_layout calls
# block_container_layout, there's probably a better solution.
Expand Down Expand Up @@ -622,6 +628,9 @@ def block_container_layout(context, box, bottom_space, skip_stack,

last_in_flow_child = None

if box.style['max_lines'] != 'none':
max_lines = min(box.style['max_lines'], max_lines or inf)

if is_start:
skip = 0
first_letter_style = getattr(box, 'first_letter_style', None)
Expand All @@ -644,30 +653,49 @@ def block_container_layout(context, box, bottom_space, skip_stack,

elif isinstance(child, boxes.LineBox):
(abort, stop, resume_at, position_y,
new_footnotes) = _linebox_layout(
new_footnotes, max_lines) = _linebox_layout(
context, box, index, child, new_children, page_is_empty,
absolute_boxes, fixed_boxes, adjoining_margins, bottom_space,
position_y, skip_stack, first_letter_style,
draw_bottom_decoration)
draw_bottom_decoration, max_lines)
draw_bottom_decoration |= resume_at is None
adjoining_margins = []
all_footnotes += new_footnotes

else:
(abort, stop, resume_at, position_y, adjoining_margins,
next_page, new_children) = _in_flow_layout(
next_page, new_children, new_max_lines) = _in_flow_layout(
context, box, index, child, new_children, page_is_empty,
absolute_boxes, fixed_boxes, adjoining_margins, bottom_space,
position_y, skip_stack, first_letter_style,
draw_bottom_decoration, collapsing_with_children, discard,
next_page)
next_page, max_lines)
skip_stack = None

if None not in (new_max_lines, max_lines):
max_lines = new_max_lines
if max_lines <= 0:
stop = True
last_child = (child == box.children[-1])
if not last_child:
children = new_children
while children:
last_child = children[-1]
if isinstance(last_child, boxes.LineBox):
last_child.block_ellipsis = (
box.style['block_ellipsis'])
elif isinstance(last_child, boxes.ParentBox):
children = last_child.children
continue
break

if abort:
page = child.page_values()[0]
for footnote in new_footnotes:
context.unlayout_footnote(footnote)
return None, None, {'break': 'any', 'page': page}, [], False
return (
None, None, {'break': 'any', 'page': page}, [], False,
max_lines)
elif stop:
adjoining_margins = []
break
Expand All @@ -685,7 +713,7 @@ def block_container_layout(context, box, bottom_space, skip_stack,
for footnote in all_footnotes:
context.unlayout_footnote(footnote)
return (
None, None, {'break': 'any', 'page': None}, [], False)
None, None, {'break': 'any', 'page': None}, [], False, max_lines)

context.broken_out_of_flow.extend(broken_out_of_flow)

Expand Down Expand Up @@ -774,7 +802,9 @@ def block_container_layout(context, box, bottom_space, skip_stack,
if next_page['page'] is None:
next_page['page'] = new_box.page_values()[1]

return new_box, resume_at, next_page, adjoining_margins, collapsing_through
return (
new_box, resume_at, next_page, adjoining_margins, collapsing_through,
max_lines)


def collapse_margin(adjoining_margins):
Expand Down
Loading

0 comments on commit c031a40

Please sign in to comment.