Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-weasyprint for
openSUSE:Factory checked in at 2026-03-14 22:22:28
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-weasyprint (Old)
and /work/SRC/openSUSE:Factory/.python-weasyprint.new.8177 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-weasyprint"
Sat Mar 14 22:22:28 2026 rev:22 rq:1338811 version:68.1
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-weasyprint/python-weasyprint.changes
2026-01-21 14:15:19.233782124 +0100
+++
/work/SRC/openSUSE:Factory/.python-weasyprint.new.8177/python-weasyprint.changes
2026-03-14 22:23:38.116212798 +0100
@@ -1,0 +2,15 @@
+Fri Mar 13 20:41:23 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 68.1:
+ * #2662: Don’t crash when SVG clip paths are not in defs tags
+ * #2665: Fix position of box bounding box
+ * #2663: Fix transparency with Acrobat and Edge
+ * #2666: Don’t rely on random default font to define test page
+ size
+ * #2670: Fix pattern detection of URL schemes
+ * #2671: Improve API compatibility between URLFetcherResponse
+ and addinfourl
+ * #2672: Fix charset for old URL fetcher requests
+ * #2675, #2673: Fix calc for many properties
+
+-------------------------------------------------------------------
Old:
----
weasyprint-68.0.tar.gz
New:
----
weasyprint-68.1.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-weasyprint.spec ++++++
--- /var/tmp/diff_new_pack.AiOAUL/_old 2026-03-14 22:23:39.124254557 +0100
+++ /var/tmp/diff_new_pack.AiOAUL/_new 2026-03-14 22:23:39.124254557 +0100
@@ -35,7 +35,7 @@
%{?sle15_python_module_pythons}
Name: python-weasyprint
-Version: 68.0
+Version: 68.1
Release: 0
Summary: Python module to convert web documents to PDF
License: BSD-3-Clause
++++++ weasyprint-68.0.tar.gz -> weasyprint-68.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/weasyprint-68.0/PKG-INFO new/weasyprint-68.1/PKG-INFO
--- old/weasyprint-68.0/PKG-INFO 1970-01-01 01:00:00.000000000 +0100
+++ new/weasyprint-68.1/PKG-INFO 1970-01-01 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: weasyprint
-Version: 68.0
+Version: 68.1
Summary: The Awesome Document Factory
Keywords: html,css,pdf,converter
Author-email: Simon Sapin <[email protected]>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/weasyprint-68.0/docs/changelog.rst
new/weasyprint-68.1/docs/changelog.rst
--- old/weasyprint-68.0/docs/changelog.rst 2026-01-19 15:54:27.767018000
+0100
+++ new/weasyprint-68.1/docs/changelog.rst 2026-02-06 16:03:54.102264200
+0100
@@ -2,6 +2,60 @@
=========
+Version 68.1
+------------
+
+Released on 2026-02-06.
+
+Bug fixes:
+
+* `#2662 <https://github.com/Kozea/WeasyPrint/issues/2662>`_:
+ Don’t crash when SVG clip paths are not in defs tags
+* `#2665 <https://github.com/Kozea/WeasyPrint/issues/2665>`_:
+ Fix position of box bounding box
+* `#2663 <https://github.com/Kozea/WeasyPrint/issues/2663>`_:
+ Fix transparency with Acrobat and Edge
+* `#2666 <https://github.com/Kozea/WeasyPrint/issues/2666>`_:
+ Don’t rely on random default font to define test page size
+* `#2670 <https://github.com/Kozea/WeasyPrint/issues/2670>`_:
+ Fix pattern detection of URL schemes
+* `#2671 <https://github.com/Kozea/WeasyPrint/pull/2671>`_:
+ Improve API compatibility between URLFetcherResponse and addinfourl
+* `#2672 <https://github.com/Kozea/WeasyPrint/issues/2672>`_:
+ Fix charset for old URL fetcher requests
+* `#2675 <https://github.com/Kozea/WeasyPrint/pull/2675>`_,
+ `#2673 <https://github.com/Kozea/WeasyPrint/issues/2673>`_:
+ Fix calc for many properties
+
+Contributors:
+
+* Guillaume Ayoub
+
+Backers and sponsors:
+
+* Spacinov
+* Syslifters
+* Kobalt
+* Simon Sapin
+* Grip Angebotssoftware
+* Manuel Barkhau
+* Simonsoft
+* KontextWork
+* Menutech
+* TrainingSparkle
+* Healthchecks.io
+* Method B
+* FieldHub
+* Hammerbacher
+* Yanal-Yves Fargialla
+* Morntag
+* Piloterr
+* Xavid
+* Charlie S.
+* Prothesis Dental Solutions
+* Kai DeLorenzo
+
+
Version 68.0
------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/weasyprint-68.0/tests/css/test_math.py
new/weasyprint-68.1/tests/css/test_math.py
--- old/weasyprint-68.0/tests/css/test_math.py 2026-01-19 15:54:27.768018000
+0100
+++ new/weasyprint-68.1/tests/css/test_math.py 2026-02-06 16:03:54.104264300
+0100
@@ -94,6 +94,7 @@
'calc(100px, 100px)',
'calc(100px * 100px)',
'calc(100 * 100)',
+ 'calc(calc(100vw))',
'calc(0.1)',
'calc(-1)',
'min()',
@@ -106,6 +107,7 @@
'max("10px")',
'max(10, 50px)',
'calc(max(100, 5px) * 10px)',
+ 'calc(100* - max(56px, 1rem)',
'clamp()',
'clamp(10px)',
'clamp(10px, 50px)',
@@ -236,6 +238,37 @@
assert len(math_logs) == len(logs)
[email protected]('display', [
+ 'block', 'inline', 'flex', 'grid',
+ 'list', 'list-item',
+ 'table', 'table-row-group', 'table-cell',
+ 'inline-block', 'inline-table', 'inline-flex', 'inline-grid',
+])
+def test_math_functions_display_size(display):
+ # Regression test for #2673.
+ render_pages(f'''
+ <div style="display: {display};
+ min-width: calc(50% + 1em); max-width: calc(50% + 1em); width: calc(50% +
1em);
+ min-height: calc(50% + 1em); max-height: calc(50% + 1em); height:
calc(50% + 1em)
+ ">
+ <div style="
+ min-width: calc(50% + 1em); max-width: calc(50% + 1em); width: calc(50%
+ 1em);
+ min-height: calc(50% + 1em); max-height: calc(50% + 1em); height:
calc(50% + 1em)
+ "></div>
+ </div>
+ ''')
+
+
+@assert_no_logs
+def test_math_functions_hyphenate():
+ render_pages('''
+ <div lang="en"
+ style="hyphens: auto; hyphenate-limit-zone: calc(1em + 100%); width:
2em">
+ absolute
+ </div>
+ ''')
+
+
@assert_no_logs
def test_math_functions_gradient():
render_pages('''
@@ -262,3 +295,81 @@
rgba(10, 20, calc(30), calc(80%)) 10%,
hsl(calc(10 + 10), 20%, 20%) 80%"></div>
''')
+
+
+@assert_no_logs
+def test_math_image_min_content_calc():
+ render_pages('''
+ <table>
+ <td>
+ <img src="pattern.png" style="
+ height: calc(10% + 1em);
+ width: calc(10% + 1em);
+ max-height: calc(10% + 1em);
+ max-width: calc(10% + 1em);
+ min-height: calc(10% + 1em);
+ min-width: calc(10% + 1em);
+ ">
+ ''')
+
+
+@assert_no_logs
+def test_math_image_min_content_auto_width_calc():
+ render_pages('''
+ <table>
+ <td>
+ <img src="pattern.png" style="
+ height: calc(10% + 1em);
+ max-height: calc(10% + 1em);
+ max-width: calc(10% + 1em);
+ min-height: calc(10% + 1em);
+ min-width: calc(10% + 1em);
+ ">
+ ''')
+
+
+@assert_no_logs
+def test_math_image_min_content_auto_width_height_calc():
+ render_pages('''
+ <table>
+ <td>
+ <img src="pattern.png" style="
+ max-height: calc(10% + 1em);
+ max-width: calc(10% + 1em);
+ min-height: calc(10% + 1em);
+ min-width: calc(10% + 1em);
+ ">
+ ''')
+
+
+@assert_no_logs
+def test_math_table_margin():
+ render_pages('<table style="margin: calc(1em + 10%)">')
+
+
+@assert_no_logs
+def test_math_grid_padding():
+ render_pages('''
+ <article style="display: grid">
+ <div style="box-sizing: border-box; border: 1px solid;
+ padding: calc(2px + 10%); width: 7px">a</div>
+ </article>
+ ''')
+
+
+@assert_no_logs
+def test_math_table_column():
+ render_pages('''
+ <table style="width: 200px">
+ <colgroup style="width: calc(1em + 10%)">
+ <col />
+ </colgroup>
+ <col style="width: calc(1em + 10%)" />
+ <tbody>
+ <tr>
+ <td>a</td>
+ <td>a</td>
+ </tr>
+ </tbody>
+ </table>
+ ''')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/weasyprint-68.0/tests/draw/svg/test_clip.py
new/weasyprint-68.1/tests/draw/svg/test_clip.py
--- old/weasyprint-68.0/tests/draw/svg/test_clip.py 2026-01-19
15:54:27.768018000 +0100
+++ new/weasyprint-68.1/tests/draw/svg/test_clip.py 2026-02-06
16:03:54.104264300 +0100
@@ -96,3 +96,31 @@
</g>
</svg>
''')
+
+
+@assert_no_logs
+def test_clip_path_outside_defs(assert_pixels):
+ # Regression test for #2662.
+ assert_pixels('''
+ _________
+ _________
+ __RRRRR__
+ __RBBBR__
+ __RBBBR__
+ __RBBBR__
+ __RRRRR__
+ _________
+ _________
+ ''', '''
+ <style>
+ @page { size: 9px }
+ svg { display: block }
+ </style>
+ <svg width="9px" height="9px" xmlns="http://www.w3.org/2000/svg">
+ <clipPath id="clip">
+ <rect x="2" y="2" width="5" height="5" />
+ </clipPath>
+ <rect x="2" y="2" width="5" height="5" stroke-width="2"
+ stroke="red" fill="blue" clip-path="url(#clip)" />
+ </svg>
+ ''')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/weasyprint-68.0/tests/draw/svg/test_text.py
new/weasyprint-68.1/tests/draw/svg/test_text.py
--- old/weasyprint-68.0/tests/draw/svg/test_text.py 2026-01-19
15:54:27.769018000 +0100
+++ new/weasyprint-68.1/tests/draw/svg/test_text.py 2026-02-06
16:03:54.105264200 +0100
@@ -30,7 +30,7 @@
_BBBBBBBBBBBB_______
''', '''
<style>
- @page { font-size: 1px; size: 20em 8ex }
+ @page { font-size: 1px; size: 20em 4em }
svg { display: block }
</style>
<svg width="20px" height="4px" xmlns="http://www.w3.org/2000/svg">
@@ -687,3 +687,26 @@
</svg>
''',
)
+
+
+@assert_no_logs
+def test_text_fill_opacity(assert_pixels):
+ # Regression text for #2665.
+ assert_pixels('''
+ ______
+ _ssss_
+ _ssss_
+ _ssss_
+ _ssss_
+ ______
+ ''', '''
+ <style>
+ @page { size: 6px 6px }
+ svg { display: block }
+ </style>
+ <svg width="6px" height="6px" xmlns="http://www.w3.org/2000/svg">
+ <text x="1" y="4" font="4px weasyprint" fill="red" opacity="0.5">
+ A
+ </text>
+ </svg>
+ ''')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/weasyprint-68.0/tests/draw/test_text.py
new/weasyprint-68.1/tests/draw/test_text.py
--- old/weasyprint-68.0/tests/draw/test_text.py 2026-01-19 15:54:27.769018000
+0100
+++ new/weasyprint-68.1/tests/draw/test_text.py 2026-02-06 16:03:54.106264000
+0100
@@ -667,6 +667,34 @@
<div>abc</div>''')
+def test_text_underline_offset_calc(assert_pixels):
+ assert_pixels('''
+ _____________
+ _zzzzzzzzzzz_
+ _zRRRRRRRRRz_
+ _zRRRRRRRRRz_
+ _zzzzzzzzzzz_
+ _zzzzzzzzzzz_
+ _zBBBBBBBBBz_
+ _zzzzzzzzzzz_
+ _____________
+ ''', '''
+ <style>
+ @page {
+ size: 13px 9px;
+ margin: 2px;
+ }
+ body {
+ color: red;
+ font-family: weasyprint;
+ font-size: 3px;
+ text-decoration: underline blue;
+ text-underline-offset: calc(0.5em + 20%);
+ }
+ </style>
+ <div>abc</div>''')
+
+
def test_text_underline_thickness(assert_pixels):
assert_pixels('''
_____________
@@ -720,6 +748,34 @@
text-underline-offset: 2px;
}
</style>
+ <div>abc</div>''')
+
+
+def test_text_underline_thickness_calc(assert_pixels):
+ assert_pixels('''
+ _____________
+ _zzzzzzzzzzz_
+ _zRRRRRRRRRz_
+ _zRRRRRRRRRz_
+ _zzzzzzzzzzz_
+ _zzzzzzzzzzz_
+ _zBBBBBBBBBz_
+ _zBBBBBBBBBz_
+ _zzzzzzzzzzz_
+ ''', '''
+ <style>
+ @page {
+ size: 13px 9px;
+ margin: 2px;
+ }
+ body {
+ color: red;
+ font-family: weasyprint;
+ font-size: 3px;
+ text-decoration: underline blue calc(0.5em + 50%);
+ text-underline-offset: 2px;
+ }
+ </style>
<div>abc</div>''')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/weasyprint-68.0/tests/test_api.py
new/weasyprint-68.1/tests/test_api.py
--- old/weasyprint-68.0/tests/test_api.py 2026-01-19 15:54:27.780017900
+0100
+++ new/weasyprint-68.1/tests/test_api.py 2026-02-06 16:03:54.117264300
+0100
@@ -10,6 +10,7 @@
import unicodedata
import wsgiref.simple_server
import zlib
+from base64 import b64encode
from functools import partial
from pathlib import Path
from urllib.parse import urljoin, uses_relative
@@ -704,6 +705,17 @@
_run(command, f'<img
src="{path2url(resource_path("pattern.png"))}">'.encode())
+@assert_no_logs
[email protected]('command', [
+ '- -',
+ '--allowed-protocols data - -',
+ '--allowed-protocols File,Data - -',
+])
+def test_allowed_protocols_data(command):
+ data = b64encode(resource_path('pattern.png').read_bytes()).decode()
+ _run(command, f'<img src="data:image/png;base64,{data}">'.encode())
+
+
@pytest.mark.parametrize('command', [
'--allowed-protocols http - -',
'--allowed-protocols http,https - -',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/weasyprint-68.0/weasyprint/__init__.py
new/weasyprint-68.1/weasyprint/__init__.py
--- old/weasyprint-68.0/weasyprint/__init__.py 2026-01-19 15:54:27.780017900
+0100
+++ new/weasyprint-68.1/weasyprint/__init__.py 2026-02-06 16:03:54.118264200
+0100
@@ -14,7 +14,7 @@
import tinycss2
import tinyhtml5
-VERSION = __version__ = '68.0'
+VERSION = __version__ = '68.1'
#: Default values for command-line and Python API rendering options. See
#: :func:`__main__.main` to learn more about specific options for
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/weasyprint-68.0/weasyprint/css/__init__.py
new/weasyprint-68.1/weasyprint/css/__init__.py
--- old/weasyprint-68.0/weasyprint/css/__init__.py 2026-01-19
15:54:27.781017800 +0100
+++ new/weasyprint-68.1/weasyprint/css/__init__.py 2026-02-06
16:03:54.118264200 +0100
@@ -779,7 +779,6 @@
return
args = []
- original_token = token
function = Function(token)
if function.name is None:
return
@@ -795,7 +794,7 @@
if function.name == 'calc':
result = _resolve_calc_sum(computed, args[0], property_name, refer_to)
if result is None:
- return original_token
+ return
else:
return tokenize(result)
@@ -1194,10 +1193,10 @@
try:
token = resolve_math(function, self, key)
except PercentageInMath:
- token = None
- if token is None:
solved_tokens.append(function)
else:
+ if token is None:
+ raise Exception
solved_tokens.append(token)
original_key = key.replace('_', '-')
value = validate_non_shorthand(solved_tokens,
original_key)[0][1]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/weasyprint-68.0/weasyprint/draw/text.py
new/weasyprint-68.1/weasyprint/draw/text.py
--- old/weasyprint-68.0/weasyprint/draw/text.py 2026-01-19 15:54:27.782018000
+0100
+++ new/weasyprint-68.1/weasyprint/draw/text.py 2026-02-06 16:03:54.120264300
+0100
@@ -17,6 +17,8 @@
def draw_text(stream, textbox, offset_x, text_overflow, block_ellipsis):
"""Draw a textbox to a pydyf stream."""
+ from ..layout.percent import percentage
+
# Pango crashes with font-size: 0.
assert textbox.style['font_size']
@@ -30,11 +32,10 @@
if 'underline' in text_decoration_values or 'overline' in
text_decoration_values:
if textbox.style['text_decoration_thickness'] in ('auto', 'from-font'):
thickness = textbox.pango_layout.underline_thickness
- elif textbox.style['text_decoration_thickness'].unit == '%':
- ratio = textbox.style['text_decoration_thickness'].value / 100
- thickness = textbox.style['font_size'] * ratio
else:
- thickness = textbox.style['text_decoration_thickness'].value
+ thickness = percentage(
+ textbox.style['text_decoration_thickness'], textbox.style,
+ textbox.style['font_size'])
if 'overline' in text_decoration_values:
offset_y = (
textbox.baseline - textbox.pango_layout.ascent + thickness / 2)
@@ -44,11 +45,10 @@
if 'underline' in text_decoration_values:
if textbox.style['text_underline_offset'] == 'auto':
underline_offset = - textbox.pango_layout.underline_position
- elif textbox.style['text_underline_offset'].unit == '%':
- ratio = textbox.style['text_underline_offset'].value / 100
- underline_offset = textbox.style['font_size'] * ratio
else:
- underline_offset = textbox.style['text_underline_offset'].value
+ underline_offset = percentage(
+ textbox.style['text_underline_offset'], textbox.style,
+ textbox.style['font_size'])
offset_y = textbox.baseline + underline_offset + thickness / 2
draw_text_decoration(
stream, textbox, offset_x, offset_y, thickness,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/weasyprint-68.0/weasyprint/layout/block.py
new/weasyprint-68.1/weasyprint/layout/block.py
--- old/weasyprint-68.0/weasyprint/layout/block.py 2026-01-19
15:54:27.783017900 +0100
+++ new/weasyprint-68.1/weasyprint/layout/block.py 2026-02-06
16:03:54.121264200 +0100
@@ -301,7 +301,7 @@
return stop, resume_at, new_child, out_of_flow_resume_at
-def _break_line(context, box, line, new_children, next_lines, page_is_empty,
index,
+def _break_line(context, box, line, new_children, needed, page_is_empty, index,
skip_stack, resume_at, absolute_boxes, fixed_boxes):
"""Break line where allowed by orphans and widows.
@@ -316,7 +316,6 @@
return True, False, resume_at
# How many lines we need on the next page to satisfy widows
# -1 for the current line.
- needed = max(box.style['widows'] - 1 - next_lines, 0)
if needed > over_orphans and not page_is_empty:
# Total number of lines < orphans + widows
remove_placeholders(context, line.children, absolute_boxes,
fixed_boxes)
@@ -377,15 +376,21 @@
# If we couldn’t break the line before but can break now, first
try to
# report footnotes and see if we don’t overflow.
could_break_before = can_break_now = True
- next_lines = len(tuple(lines_iterator))
+ needed = box.style['widows'] - 1
+ for _ in lines_iterator:
+ needed -= 1
+ # Don’t iterate over all lines as it can be long.
+ if needed == -1:
+ break
if len(new_children) + 1 < box.style['orphans']:
can_break_now = False
- elif next_lines < box.style['widows']:
+ elif needed >= 0:
can_break_now = False
if len(new_children) < box.style['orphans']:
could_break_before = False
- elif next_lines + 1 < box.style['widows']:
+ elif needed > 0:
could_break_before = False
+ needed = max(0, needed)
report = not context.in_column and can_break_now and not
could_break_before
reported_footnotes = 0
while report and context.current_page_footnotes:
@@ -397,9 +402,8 @@
break
else:
abort, stop, resume_at = _break_line(
- context, box, line, new_children, next_lines,
- page_is_empty, index, skip_stack, resume_at,
absolute_boxes,
- fixed_boxes)
+ context, box, line, new_children, needed, page_is_empty,
index,
+ skip_stack, resume_at, absolute_boxes, fixed_boxes)
# Revert reported footnotes, as they’ve been reported starting
from the last
# one.
@@ -414,8 +418,7 @@
# "When an unforced page break occurs here, both the adjoining
# ‘margin-top’ and ‘margin-bottom’ are set to zero."
# See issue #115.
- elif page_is_empty and context.overflows_page(
- bottom_space, new_position_y):
+ elif page_is_empty and context.overflows_page(bottom_space,
new_position_y):
# Remove the top border when a page is empty and the box is
# too high to be drawn in one page
new_position_y -= box.margin_top
@@ -433,8 +436,7 @@
overflow = (
overflow or
context.reported_footnotes or
- context.overflows_page(
- bottom_space, new_position_y + offset_y))
+ context.overflows_page(bottom_space, new_position_y +
offset_y))
if overflow:
context.report_footnote(footnote)
# If we've put other content on this page, then we may want
@@ -443,11 +445,15 @@
# even try.
if new_children or not page_is_empty:
if footnote.style['footnote_policy'] == 'line':
- next_lines = len(tuple(lines_iterator))
+ if needed := box.style['widows'] - 1:
+ for _ in lines_iterator:
+ needed -= 1
+ # Don’t iterate over all lines as it can
be long.
+ if needed == 0:
+ break
abort, stop, resume_at = _break_line(
- context, box, line, new_children,
- next_lines, page_is_empty, index,
- skip_stack, resume_at, absolute_boxes,
+ context, box, line, new_children, needed,
page_is_empty,
+ index, skip_stack, resume_at, absolute_boxes,
fixed_boxes)
break_linebox = True
break
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/weasyprint-68.0/weasyprint/layout/preferred.py
new/weasyprint-68.1/weasyprint/layout/preferred.py
--- old/weasyprint-68.0/weasyprint/layout/preferred.py 2026-01-19
15:54:27.784017800 +0100
+++ new/weasyprint-68.1/weasyprint/layout/preferred.py 2026-02-06
16:03:54.122264100 +0100
@@ -109,7 +109,7 @@
for value in ('padding_left', 'padding_right'):
style_value = box.style[value]
- if style_value != 'auto':
+ if style_value != 'auto' and not check_math(style_value):
if style_value.unit.lower() == 'px':
width -= style_value.value
else:
@@ -175,7 +175,7 @@
(['margin_right', 'padding_right'] if right else [])
):
style_value = box.style[value]
- if style_value != 'auto':
+ if style_value != 'auto' and not check_math(style_value):
if style_value.unit.lower() == 'px':
width += style_value.value
else:
@@ -263,7 +263,7 @@
def column_group_content_width(context, box):
"""Return the *-content width for a ``TableColumnGroupBox``."""
width = box.style['width']
- if width == 'auto' or width.unit == '%':
+ if width == 'auto' or check_math(width) or width.unit == '%':
width = 0
else:
assert width.unit.lower() == 'px'
@@ -597,21 +597,22 @@
# Define constrainedness
constrainedness = [False for i in range(grid_width)]
for i in range(grid_width):
- if (column_groups[i] and column_groups[i].style['width'] != 'auto' and
- column_groups[i].style['width'].unit != '%'):
- constrainedness[i] = True
- continue
- if (columns[i] and columns[i].style['width'] != 'auto' and
- columns[i].style['width'].unit != '%'):
- constrainedness[i] = True
- continue
- for cell in zipped_grid[i]:
- if (cell and cell.colspan == 1 and
- cell.style['width'] != 'auto' and
- not check_math(cell.style['width']) and
- cell.style['width'].unit != '%'):
+ if column_groups[i]:
+ width = column_groups[i].style['width']
+ if width != 'auto' and not check_math(width) and width.unit != '%':
constrainedness[i] = True
- break
+ continue
+ if columns[i]:
+ width = columns[i].style['width']
+ if width != 'auto' and not check_math(width) and width.unit != '%':
+ constrainedness[i] = True
+ continue
+ for cell in zipped_grid[i]:
+ if cell and cell.colspan == 1:
+ width = cell.style['width']
+ if width != 'auto' and not check_math(width) and width.unit !=
'%':
+ constrainedness[i] = True
+ break
intrinsic_percentages = [
min(percentage, 100 - sum(intrinsic_percentages[:i]))
@@ -679,7 +680,8 @@
sum(max_content_widths), large_percentage_contribution,
*small_percentage_contributions]))
- if table.style['width'] != 'auto' and table.style['width'].unit.lower() ==
'px':
+ width = table.style['width']
+ if width != 'auto' and not check_math(width) and width.unit.lower() ==
'px':
# "percentages on the following properties are treated instead as
# though they were the following: width: auto"
# https://dbaron.org/css/intrinsic/#outer-intrinsic
@@ -714,12 +716,16 @@
width = box.style['width']
if width == 'auto':
height = box.style['height']
- if height == 'auto' or height.unit == '%':
+ if height == 'auto' or check_math(height) or height.unit == '%':
height = 'auto'
else:
assert height.unit.lower() == 'px'
height = height.value
- if box.style['max_width'] != 'auto' and box.style['max_width'].unit ==
'%':
+ unknown_max_width = (
+ box.style['max_width'] != 'auto' and
+ not check_math(box.style['max_width']) and
+ box.style['max_width'].unit == '%')
+ if unknown_max_width:
# See https://drafts.csswg.org/css-sizing/#intrinsic-contribution
width = 0
else:
@@ -730,7 +736,7 @@
width, _ = default_image_sizing(
intrinsic_width, intrinsic_height, intrinsic_ratio, 'auto',
height, default_width=0, default_height=0)
- elif box.style['width'].unit == '%':
+ elif check_math(box.style['width']) or box.style['width'].unit == '%':
# See https://drafts.csswg.org/css-sizing/#intrinsic-contribution
width = 0
else:
@@ -744,7 +750,7 @@
width = box.style['width']
if width == 'auto':
height = box.style['height']
- if height == 'auto' or height.unit == '%':
+ if height == 'auto' or check_math(height) or height.unit == '%':
height = 'auto'
else:
assert height.unit.lower() == 'px'
@@ -756,7 +762,7 @@
width, _ = default_image_sizing(
intrinsic_width, intrinsic_height, intrinsic_ratio, 'auto', height,
default_width=300, default_height=150)
- elif box.style['width'].unit == '%':
+ elif check_math(box.style['width']) or box.style['width'].unit == '%':
# See https://drafts.csswg.org/css-sizing/#intrinsic-contribution
width = 0
else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/weasyprint-68.0/weasyprint/pdf/stream.py
new/weasyprint-68.1/weasyprint/pdf/stream.py
--- old/weasyprint-68.0/weasyprint/pdf/stream.py 2026-01-19
15:54:27.785017700 +0100
+++ new/weasyprint-68.1/weasyprint/pdf/stream.py 2026-02-06
16:03:54.123264300 +0100
@@ -201,6 +201,8 @@
'Group': pydyf.Dictionary({
'Type': '/Group',
'S': '/Transparency',
+ 'I': 'true',
+ 'CS': '/DeviceRGB',
}),
})
group = self.clone(resources=resources, extra=extra)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/weasyprint-68.0/weasyprint/svg/__init__.py
new/weasyprint-68.1/weasyprint/svg/__init__.py
--- old/weasyprint-68.0/weasyprint/svg/__init__.py 2026-01-19
15:54:27.785017700 +0100
+++ new/weasyprint-68.1/weasyprint/svg/__init__.py 2026-02-06
16:03:54.123264300 +0100
@@ -9,7 +9,7 @@
from ..urls import get_url_attribute
from .css import parse_declarations, parse_stylesheets
-from .defs import apply_filters, clip_path, draw_gradient_or_pattern,
paint_mask, use
+from .defs import apply_filters, draw_gradient_or_pattern, paint_mask, use
from .images import image, svg
from .path import path
from .shapes import circle, ellipse, line, polygon, polyline, rect
@@ -24,7 +24,6 @@
TAGS = {
'a': text,
'circle': circle,
- 'clipPath': clip_path,
'ellipse': ellipse,
'image': image,
'line': line,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/weasyprint-68.0/weasyprint/svg/defs.py
new/weasyprint-68.1/weasyprint/svg/defs.py
--- old/weasyprint-68.0/weasyprint/svg/defs.py 2026-01-19 15:54:27.785017700
+0100
+++ new/weasyprint-68.1/weasyprint/svg/defs.py 2026-02-06 16:03:54.123264300
+0100
@@ -517,9 +517,3 @@
svg.stream = svg.stream.set_alpha_state(x, y, width, height)
svg.draw_node(mask, font_size)
svg.stream = svg_stream
-
-
-def clip_path(svg, node, font_size):
- """Store a clip path definition."""
- if 'id' in node.attrib:
- svg.paths[node.attrib['id']] = node
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/weasyprint-68.0/weasyprint/svg/text.py
new/weasyprint-68.1/weasyprint/svg/text.py
--- old/weasyprint-68.0/weasyprint/svg/text.py 2026-01-19 15:54:27.785017700
+0100
+++ new/weasyprint-68.1/weasyprint/svg/text.py 2026-02-06 16:03:54.124264200
+0100
@@ -141,7 +141,7 @@
svg.cursor_d_position[1] = 0
svg.cursor_d_position[0] += dx or 0
svg.cursor_d_position[1] += dy or 0
- layout, _, _, width, height, _ = split_first_line(
+ layout, _, _, width, height, baseline = split_first_line(
letter, style, svg.context, inf, 0)
x = svg.cursor_position[0] if x is None else x
y = svg.cursor_position[1] if y is None else y
@@ -154,8 +154,9 @@
y_position = y + svg.cursor_d_position[1] + y_align
angle = last_r if r is None else r
points = (
- (x_position, y_position),
- (x_position + width, y_position - height))
+ (x_position, y_position - baseline),
+ (x_position + width, y_position - baseline + height))
+ # TODO: Use ink extents instead of logical from line_break.line_size().
node.text_bounding_box = extend_bounding_box(
node.text_bounding_box, points)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/weasyprint-68.0/weasyprint/text/line_break.py
new/weasyprint-68.1/weasyprint/text/line_break.py
--- old/weasyprint-68.0/weasyprint/text/line_break.py 2026-01-19
15:54:27.786018000 +0100
+++ new/weasyprint-68.1/weasyprint/text/line_break.py 2026-02-06
16:03:54.124264200 +0100
@@ -256,6 +256,8 @@
``baseline``: baseline in pixels of the first line
"""
+ from ..layout.percent import percentage
+
# See https://www.w3.org/TR/css-text-3/#white-space-property
text_wrap = style['white_space'] in ('normal', 'pre-wrap', 'pre-line')
space_collapse = style['white_space'] in ('normal', 'nowrap', 'pre-line')
@@ -392,11 +394,8 @@
# This word is long enough.
first_line_width, _ = line_size(first_line, style)
space = max_width - first_line_width
- if style['hyphenate_limit_zone'].unit == '%':
- limit_zone = (
- max_width * style['hyphenate_limit_zone'].value /
100)
- else:
- limit_zone = style['hyphenate_limit_zone'].value
+ limit_zone = percentage(
+ style['hyphenate_limit_zone'], style, max_width)
if space > limit_zone or space < 0:
# Available space is worth the try, or the line is
even too long
# to fit: try to hyphenate.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/weasyprint-68.0/weasyprint/urls.py
new/weasyprint-68.1/weasyprint/urls.py
--- old/weasyprint-68.0/weasyprint/urls.py 2026-01-19 15:54:27.786018000
+0100
+++ new/weasyprint-68.1/weasyprint/urls.py 2026-02-06 16:03:54.124264200
+0100
@@ -330,17 +330,18 @@
"""
# Discard URLs with no or invalid protocol.
- if not UNICODE_SCHEME_RE.match(url): # pragma: no cover
+ if not (match := UNICODE_SCHEME_RE.match(url)): # pragma: no cover
raise ValueError(f'Not an absolute URI: {url}')
+ scheme = match[1].lower()
# Discard URLs with forbidden protocol.
if self._allowed_protocols is not None:
- if url.split('://', 1)[0].lower() not in self._allowed_protocols:
+ if scheme not in self._allowed_protocols:
raise ValueError(f'URI uses disallowed protocol: {url}')
# Remove query and fragment parts from file URLs.
# See https://bugs.python.org/issue34702.
- if url.lower().startswith('file://'):
+ if scheme == 'file':
url = url.split('?')[0]
# Transform Unicode IRI to ASCII URI.
@@ -385,7 +386,7 @@
:param body: The body of the HTTP response.
:type headers: dict or email.message.EmailMessage
:param headers: The headers of the HTTP response.
- :param str status: The status of the HTTP response.
+ :param int status: The status of the HTTP response.
Has the same interface as :class:`urllib.response.addinfourl`.
@@ -395,7 +396,7 @@
is used elsewhere, the file object has to be closed manually.
"""
- def __init__(self, url, body=None, headers=None, status='200 OK',
**kwargs):
+ def __init__(self, url, body=None, headers=None, status=200, **kwargs):
self.url = url
self.status = status
@@ -404,7 +405,10 @@
else:
self.headers = EmailMessage()
for key, value in (headers or {}).items():
- self.headers[key] = value
+ try:
+ self.headers[key] = value
+ except ValueError:
+ pass # Ignore forbidden duplicated headers.
if hasattr(body, 'read'):
self._file_obj = body
@@ -440,6 +444,19 @@
def charset(self):
return self.headers.get_param('charset')
+ def geturl(self):
+ return self.url
+
+ def info(self):
+ return self.headers
+
+ @property
+ def code(self):
+ return self.status
+
+ def getcode(self):
+ return self.status
+
@contextlib.contextmanager
def fetch(url_fetcher, url):
@@ -467,7 +484,7 @@
resource['body'] = resource.get('file_obj', resource.get('string'))
content_type = resource.get('mime_type', 'application/octet-stream')
if charset := resource.get('encoding'):
- content_type += f';{charset}'
+ content_type += f'; charset={charset}'
resource['headers'] = {'Content-Type': content_type}
resource = URLFetcherResponse(**resource)