Skip to content

Shape outside support#2668

Open
itlackey wants to merge 3 commits intoKozea:mainfrom
itlackey:feat/shape-outside
Open

Shape outside support#2668
itlackey wants to merge 3 commits intoKozea:mainfrom
itlackey:feat/shape-outside

Conversation

@itlackey
Copy link

Add CSS Shapes Level 1 documentation to API reference

Documents the newly implemented shape-outside feature including:

  • Box keywords (margin-box, border-box, padding-box, content-box)
  • Shape functions (circle, ellipse, polygon, inset)
  • Shape-margin property and reference box combinations
  • Example CSS usage
  • List of unsupported features (shape-image-threshold, url(), path())

Enhance shape boundary handling for text wrapping and border-radius support

  • Update test_inset_with_margin_text_wrap to clarify shape boundary usage.
  • Improve avoid_collisions to sample bounds for curved shapes at multiple Y positions.
  • Modify _create_base_boundary to create InsetBoundary for elements with border-radius.
  • Add _get_border_radii function to extract border-radius values for shape calculations.
  • Add shape-image-threshold property and implement ImageBoundary class for alpha channel extraction
  • Add CSS Shapes Level 1 features and documentation to changelog

Tests: 117 tests

  • Fix critical edge cases in shape boundary classes
  • CircleBoundary: Return None for zero/negative radius
  • EllipseBoundary: Return None for zero/negative rx or ry
  • PolygonBoundary: Mark polygons with < 3 points as degenerate
  • InsetBoundary: Guard sqrt operations with max(0, ...) for floating-point precision

@itlackey
Copy link
Author

Pushed a branch with a new book example I used for testing: https://github.com/itlackey/weasyprint-samples/tree/dev/shape-outside

@itlackey itlackey marked this pull request as ready for review January 23, 2026 02:34
Copy link
Member

@liZe liZe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, it’s pretty cool!

I like the choice to avoid path() (and shape()?). Your comment and the documentaton include shape-image-threshold and url(), but they’re supported, right? I think that SVGs are also unsupported, and that’s a good idea if we don’t want to actually spend weeks on that. 😄 That’s a good way to cover most of the common use cases fast.

Many comments are just small details.

The main problem for me is the boundary detection logic. I think that it would be better to let the box handle this complexity (and have not much in float), and that the box API should give the max boundaries for a range instead of a single y value. Do you agree with that?

For the tests, it may be interesting to dispatch them into different files, and to add drawing tests. If you don’t want to spend more time on them, tell me, I’ll do that later.

I’ll review shapes.py and the parsing details later.


- ``path()`` function

Example usage:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don’t have many examples in this page, we prefer to keep links to specifications, you can remove this part.

=========


Unreleased
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We write the Changelog when the release is ready, you can remove this change.

/docs/_build
/pytest_cache
/tests/draw/results
/venv
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to change this newline. 😄

@@ -0,0 +1,2854 @@
"""Tests for CSS shape-outside property with box keyword values."""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to keep here tests that are actually related to the layout, ie that test the position of the boxes. These tests should ideally not rely on WeasyPrint internals (*Boundary classes for example).

You can test boundary classes in a separate file, not in layout.

CSS validation tests can go to css/test_validation.py where the assert_invalid and get_value functions will be useful.

It would also be useful to test real rendering in draw.

# At center y=100, bounds should be at x=50 to x=150
bounds = boundary.get_bounds_at_y(100)
assert bounds is not None
assert abs(bounds[0] - 50) < 0.001
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use math.isclose.

if len(lines) >= 2:
# First lines at top of float may have more indent due to curve
# Lines in middle should have less indent (or end where float ends)
pass # Test structure verified; exact positioning depends on font metrics
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s possible to use the WeasyPrint font to test text.

'right': 'auto',
'shape_outside': 'none',
'shape_margin': ZERO_PIXELS,
'shape_image_threshold': 0.0,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 is enough. 😄

elif shape_outside == 'content-box':
return box.content_box_x(), box.width
else:
# Fallback to margin box for any unknown values
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any other value is an error, we should remove the first if and assert shape_outside in ('none', 'margin-box').

# the exclusion area around floated elements.
left_bounds = []
right_bounds = []
for shape in colliding_shapes:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we should use another logic here.

Instead of testing top/middle/bottom, we could have a function that gives the max left/right bound of the shape between top and bottom y. This function could be available in all boxes (in Box) and create the shape boundary on demand, to avoid the hasattr logic here.

What do you think of that?

@itlackey
Copy link
Author

itlackey commented Jan 30, 2026

Just checking in. I found a memory/performance regression when working on large documents with high resolution images. I hope to get back to debugging this weekend.

*I will also address the comments/issues highlighted above

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants