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

Font rendering issue: can only concatenate str (not "float") to str #910

Closed
danielepolencic opened this issue Jul 25, 2019 · 9 comments
Closed
Labels
crash Problems preventing documents from being rendered

Comments

@danielepolencic
Copy link

It took me a while to isolate the issue, but I think I have a snippet of HTML that consistently breaks weasyprint with the following error:

Traceback (most recent call last):
  File "/usr/local/bin/weasyprint", line 10, in <module>
    sys.exit(main())
  File "/usr/local/lib/python3.7/site-packages/weasyprint/__main__.py", line 190, in main
    getattr(html, 'write_' + format_)(output, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/__init__.py", line 211, in write_pdf
    font_config=font_config).write_pdf(
  File "/usr/local/lib/python3.7/site-packages/weasyprint/__init__.py", line 168, in render
    font_config)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/document.py", line 377, in _render
    [Page(page_box, enable_hinting) for page_box in page_boxes],
  File "/usr/local/lib/python3.7/site-packages/weasyprint/document.py", line 377, in <listcomp>
    [Page(page_box, enable_hinting) for page_box in page_boxes],
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/__init__.py", line 130, in layout_document
    pages = list(make_all_pages(context, root_box, html, pages, style_for))
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/pages.py", line 798, in make_all_pages
    i, context, root_box, html, style_for)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/pages.py", line 736, in remake_page
    page_number, page_state)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/pages.py", line 554, in make_page
    positioned_boxes, positioned_boxes, adjoining_margins)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/blocks.py", line 65, in block_level_layout
    adjoining_margins)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/blocks.py", line 81, in block_level_layout_switch
    adjoining_margins)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/blocks.py", line 135, in block_box_layout
    page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/blocks.py", line 519, in block_container_layout
    adjoining_margins)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/blocks.py", line 65, in block_level_layout
    adjoining_margins)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/blocks.py", line 81, in block_level_layout_switch
    adjoining_margins)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/blocks.py", line 135, in block_box_layout
    page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/blocks.py", line 519, in block_container_layout
    adjoining_margins)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/blocks.py", line 65, in block_level_layout
    adjoining_margins)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/blocks.py", line 81, in block_level_layout_switch
    adjoining_margins)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/blocks.py", line 135, in block_box_layout
    page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/blocks.py", line 519, in block_container_layout
    adjoining_margins)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/blocks.py", line 65, in block_level_layout
    adjoining_margins)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/blocks.py", line 81, in block_level_layout_switch
    adjoining_margins)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/blocks.py", line 135, in block_box_layout
    page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/blocks.py", line 382, in block_container_layout
    for line, resume_at in lines_iterator:
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/inlines.py", line 56, in iter_line_boxes
    device_size, absolute_boxes, fixed_boxes, first_letter_style)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/inlines.py", line 105, in get_next_linebox
    waiting_floats, line_children=[])
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/inlines.py", line 841, in split_inline_box
    line_children))
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/inlines.py", line 614, in split_inline_level
    waiting_floats, line_children)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/inlines.py", line 718, in split_inline_box
    absolute_boxes, fixed_boxes)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/float.py", line 84, in float_layout
    box = find_float_position(context, box, containing_block)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/float.py", line 106, in find_float_position
    context, box, containing_block)
  File "/usr/local/lib/python3.7/site-packages/weasyprint/layout/float.py", line 148, in avoid_collisions
    shape_margin_height = shape.margin_height()
  File "/usr/local/lib/python3.7/site-packages/weasyprint/formatting_structure/boxes.py", line 159, in margin_height
    return self.border_height() + self.margin_top + self.margin_bottom
  File "/usr/local/lib/python3.7/site-packages/weasyprint/formatting_structure/boxes.py", line 150, in border_height
    return self.padding_height() + self.border_top_width + \
  File "/usr/local/lib/python3.7/site-packages/weasyprint/formatting_structure/boxes.py", line 141, in padding_height
    return self.height + self.padding_top + self.padding_bottom
TypeError: can only concatenate str (not "float") to str

The HTML snippet is available here: https://gist.github.com/danielepolencic/04d506fde37f0e81b9e365fdd1908339

I also found the fix. If I remove the class from the <body> tag, the error disappears.
I can also remove the float from:

      .chapter+p:first-letter {
        font-size: 4.5em;
        /* float: left; */
        margin-top: 0.5rem;
        font-family: georgia, times, serif;
        line-height: 0.5;
        padding: 0.8rem 1.2rem;
      }

and that seems to work too.
My limited Python proficiency didn't help me diagnose the bug further.

I'm not sure how fonts and floating are related.

@liZe liZe added the crash Problems preventing documents from being rendered label Jul 25, 2019
@Tontyna
Copy link
Contributor

Tontyna commented Jul 26, 2019

Ah, that's another crossing-the-margin bug. This time related to the floating department.
Minimal snippet to reproduce the crash:

div {
  font-family: sans-serif;
  max-width: 13em; 
}
div:first-letter {
  float: left;
}
</style>
<div>
   <span>Xpart that fits into one line</span>
   more text required to trigger the crash
</div>

Font family and max-width ensure that "part that fits into one line" fills the line. The additional X doesn't fit. Thats the prerequisite.
Putting the floating X into another, separate span prevents the crash.

@Tontyna
Copy link
Contributor

Tontyna commented Jul 26, 2019

I dunno why the Error Message is "TypeError: must be str, not int" but it's definitely raised by a <BlockBox span::first-letter> having a height of 'auto'

@Tontyna
Copy link
Contributor

Tontyna commented Jul 26, 2019

Ah, stupid me: 'auto' is a string and adding the padding numbers fails of course

@Tontyna
Copy link
Contributor

Tontyna commented Jul 26, 2019

In float_layout() the <BlockBox span::first-letter> is added to the context's excluded_shapes with the correct height. But when it comes to avoid_collisions() the height is reset to auto. Probably because resolve_percentages() is called again with a cb_height of auto.

BTW: Adding a height property to the first-letter definition also prevents the crash.

@danielepolencic
Copy link
Author

I tried setting the height, but it seems to introduce some other issues.

image

The issue appears only when the first letter is enclosed in another (inline) tag.

<p class="lh-copy measure f4 mv1 tj">
  <em class="i">All the diagrams you studied so far didn't picture any server.</em>
All of them were focussed on the logical architecture of your application and not the physical infrastructure which is necessary to deploy containers, Pods, Services and Ingresses.
</p>

I have an example without the extra inline tag and it works:

image

@Tontyna
Copy link
Contributor

Tontyna commented Jul 27, 2019

I tried setting the height, but it seems to introduce some other issues.

Yes, the height only prevents the crash.

The issue appears only when the first letter is enclosed in another (inline) tag.

Right. Unless you put it in an inline tag that (without the fisrst letter) is shorter or longer than the available width. Wrapping the first letter in a dummy-span also renders perfectly:

    <p class="lh-copy measure f4 mv1 tj"><em class="i"><span>A</span>ll the diagrams you studied so far didn't picture any
        server.</em>
      All of them were focussed on the logical architecture of your application and not the physical infrastructure
      which is necessary to deploy containers, Pods, Services and Ingresses.</p>

@liZe - as always: I don't get what happens when and why in the thoroughgoing recursively layout source.

I see that find_float_position() creates the <BlockBox span::first-letter> with the correct height, but afterwards the box is sent through resolve_percentages() which calls resolve_one_percentage() to resolve the height, which resets the boxes height to 'auto' (aka used value). But the real height in pixels is never re-calculated and leads to the crash.

When the first letter is put in its own inline tag (or an inline box that's shorter or longer than the width) the BlockBox created by find_float_position() is not sent through resolve_percentages() and preserves its height. Only its children (the InlineBox and the TextBox get their percentages resolved/reset.

Have no idea where the decision is made, to resolve_percentages or not to resolve_percentages for the floating (excluded_shape) BlockBox.

@Tontyna
Copy link
Contributor

Tontyna commented Jul 28, 2019

The real problem isn't the crash / the height being reset to 'auto'. The real bug is that when split_inline_box detects that the first line must be split (again!) due to the first letter.
It calls split_inline_level with a copy of the InlineBox that contains the <BlockBox span::first-letter> which meanwhile is part of the context's excluded_shapes. The copy is split -- but instead of ignoring the first letter, its width is added to position_x and the float is added to the excluded_shapes a second time. Weirdness continues in the next lines.

First floating letter with height and some deco added:
visualized

Inserting

        elif child in context.excluded_shapes:
            waiting_children.append((index, child))
            continue

into split_inline_box() right before

elif child.is_floated():

almost fixes the bug:
almostfixed

@liZe
Copy link
Member

liZe commented Sep 13, 2019

The problem is:

# TODO: we should take care of children added into
# absolute_boxes, fixed_boxes and other lists.

split_inline_level(
context, child, child.position_x, max_x,
None, box, absolute_boxes, fixed_boxes,
line_placeholders, waiting_floats,
line_children))

Absolute, relative and floating boxes have already been gathered in the previous split_inline_level call. Calling split_inline_level again makes some boxes appear (at least) twice in absolute_boxes, fixed_boxes or context.excluded_shapes.

The layout of the first letter box is done twice, causing the crash.

We could add more workarounds to avoid this, but the real solution is to rewrite split_inline_box.

@liZe
Copy link
Member

liZe commented Aug 17, 2021

This bug has actually been fixed in version 52.

@liZe liZe closed this as completed Aug 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
crash Problems preventing documents from being rendered
Projects
None yet
Development

No branches or pull requests

3 participants