Skip to content

Commit

Permalink
Avoid int overflows when converting units from/to double
Browse files Browse the repository at this point in the history
Fix #2262.
  • Loading branch information
liZe committed Oct 5, 2024
1 parent d0ac723 commit 1aae145
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 39 deletions.
25 changes: 25 additions & 0 deletions tests/draw/test_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -875,3 +875,28 @@ def test_otb_font(assert_pixels):
}
</style>
AaA''')


def test_huge_justification(assert_pixels):
# Test regression: https://github.com/Kozea/WeasyPrint/issues/2262
assert_pixels('''
____
_RR_
_RR_
____
''', '''
<style>
@page {
size: 4px 4px;
margin: 1px;
}
body {
color: red;
font-family: weasyprint;
font-size: 2px;
line-height: 1;
text-align: justify-all;
width: 100000px;
}
</style>
A B''')
6 changes: 3 additions & 3 deletions weasyprint/css/computed_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from tinycss2.color3 import parse_color

from ..logger import LOGGER
from ..text.ffi import ffi, pango, units_to_double
from ..text.ffi import FROM_UNITS, ffi, pango
from ..text.line_break import Layout, first_line_metrics
from ..urls import get_link_attribute
from .properties import INITIAL_VALUES, ZERO_PIXELS, Dimension
Expand Down Expand Up @@ -815,9 +815,9 @@ def character_ratio(style, character):
logical_extents = ffi.new('PangoRectangle *')
pango.pango_layout_line_get_extents(line, ink_extents, logical_extents)
if character == 'x':
measure = -units_to_double(ink_extents.y)
measure = -ink_extents.y * FROM_UNITS
else:
measure = units_to_double(logical_extents.width)
measure = logical_extents.width * FROM_UNITS
ffi.release(ink_extents)
ffi.release(logical_extents)

Expand Down
14 changes: 6 additions & 8 deletions weasyprint/draw/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from ..images import RasterImage, SVGImage
from ..matrix import Matrix
from ..text.ffi import ffi, pango, units_from_double, units_to_double
from ..text.ffi import FROM_UNITS, TO_UNITS, ffi, pango
from ..text.fonts import get_hb_object_data
from ..text.line_break import get_last_word_end
from .border import draw_line
Expand Down Expand Up @@ -89,7 +89,7 @@ def draw_first_line(stream, textbox, text_overflow, block_ellipsis, matrix):
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))
textbox.pango_layout.layout, int(max_width * TO_UNITS))
if text_overflow == 'ellipsis':
pango.pango_layout_set_ellipsize(
textbox.pango_layout.layout, pango.PANGO_ELLIPSIZE_END)
Expand Down Expand Up @@ -194,12 +194,11 @@ def draw_first_line(stream, textbox, text_overflow, block_ellipsis, matrix):
pango.pango_font_get_glyph_extents(
pango_font, glyph, stream.ink_rect, stream.logical_rect)
font.widths[glyph] = int(round(
units_to_double(stream.logical_rect.width * 1000) /
font_size))
stream.logical_rect.width * 1000 * FROM_UNITS / font_size))

# Set kerning, word spacing, letter spacing.
kerning = int(
font.widths[glyph] - units_to_double(width * 1000) / font_size + offset)
font.widths[glyph] + offset - width * 1000 * FROM_UNITS / font_size)
if kerning:
string += f'>{kerning}<'

Expand Down Expand Up @@ -237,9 +236,8 @@ def draw_first_line(stream, textbox, text_overflow, block_ellipsis, matrix):
pango.pango_font_get_glyph_extents(
pango_font, glyph, stream.ink_rect,
stream.logical_rect)
f = units_to_double(
(-stream.logical_rect.y - stream.logical_rect.height))
f = f / font_size - font_size
f = -stream.logical_rect.y - stream.logical_rect.height
f = f * FROM_UNITS / font_size - font_size
emojis.append([image, font, a, d, x_advance, f])

x_advance += (font.widths[glyph] + offset - kerning) / 1000
Expand Down
4 changes: 2 additions & 2 deletions weasyprint/pdf/fonts.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from ..logger import LOGGER, capture_logs
from ..text.constants import PANGO_STRETCH_PERCENT
from ..text.ffi import ffi, harfbuzz, harfbuzz_subset, pango, units_to_double
from ..text.ffi import FROM_UNITS, ffi, harfbuzz, harfbuzz_subset, pango
from ..text.fonts import get_hb_object_data, get_pango_font_hb_face


Expand Down Expand Up @@ -121,7 +121,7 @@ def clean(self, cmap, hinting):
self.description)
self.variations['wght'] = weight
if 'opsz' not in self.variations:
self.variations['opsz'] = units_to_double(self.font_size)
self.variations['opsz'] = self.font_size * FROM_UNITS
if 'slnt' not in self.variations:
slnt = 0
if self.style == 1:
Expand Down
5 changes: 3 additions & 2 deletions weasyprint/text/ffi.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,8 +463,9 @@ def _dlopen(ffi, *names, allow_fail=False):

gobject.g_type_init()

units_to_double = pango.pango_units_to_double
units_from_double = pango.pango_units_from_double
# Call once to avoid int overflows.
TO_UNITS = pango.pango_units_from_double(1)
FROM_UNITS = pango.pango_units_to_double(1)


def unicode_to_char_p(string):
Expand Down
5 changes: 2 additions & 3 deletions weasyprint/text/fonts.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
CAPS_KEYS, EAST_ASIAN_KEYS, FONTCONFIG_STRETCH, FONTCONFIG_STYLE,
FONTCONFIG_WEIGHT, LIGATURE_KEYS, NUMERIC_KEYS, PANGO_STRETCH, PANGO_STYLE)
from .ffi import ( # isort:skip
ffi, fontconfig, gobject, harfbuzz, pango, pangoft2, unicode_to_char_p,
units_from_double)
TO_UNITS, ffi, fontconfig, gobject, harfbuzz, pango, pangoft2, unicode_to_char_p)


def _check_font_configuration(font_config): # pragma: no cover
Expand Down Expand Up @@ -317,7 +316,7 @@ def get_font_description(style):
pango.pango_font_description_set_stretch(font_description, font_stretch)
font_weight = style['font_weight']
pango.pango_font_description_set_weight(font_description, font_weight)
font_size = units_from_double(style['font_size'])
font_size = int(style['font_size'] * TO_UNITS)
pango.pango_font_description_set_absolute_size(font_description, font_size)
if style['font_variation_settings'] != 'normal':
string = ','.join(
Expand Down
35 changes: 14 additions & 21 deletions weasyprint/text/line_break.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@
import pyphen

from .constants import LST_TO_ISO, PANGO_WRAP_MODE
from .ffi import FROM_UNITS, TO_UNITS, ffi, gobject, pango, pangoft2, unicode_to_char_p
from .fonts import font_features, get_font_description

from .ffi import ( # isort:skip
ffi, gobject, pango, pangoft2, unicode_to_char_p, units_from_double,
units_to_double)


def line_size(line, style):
"""Get logical width and height of the given ``line``.
Expand All @@ -21,8 +18,8 @@ def line_size(line, style):
"""
logical_extents = ffi.new('PangoRectangle *')
pango.pango_layout_line_get_extents(line, ffi.NULL, logical_extents)
width = units_to_double(logical_extents.width)
height = units_to_double(logical_extents.height)
width = logical_extents.width * FROM_UNITS
height = logical_extents.height * FROM_UNITS
ffi.release(logical_extents)
if style['letter_spacing'] != 'normal':
width += style['letter_spacing']
Expand Down Expand Up @@ -52,7 +49,7 @@ def first_line_metrics(first_line, text, layout, resume_at, space_collapse,
length = first_line.length if first_line is not None else 0

width, height = line_size(first_line, style)
baseline = units_to_double(pango.pango_layout_get_baseline(layout.layout))
baseline = pango.pango_layout_get_baseline(layout.layout) * FROM_UNITS
layout.deactivate()
return layout, length, resume_at, width, height, baseline

Expand Down Expand Up @@ -107,15 +104,15 @@ def setup(self, context, style):
pango.pango_context_get_metrics(
pango_context, font_description, self.language),
pango.pango_font_metrics_unref)
self.ascent = units_to_double(
self.ascent = FROM_UNITS * (
pango.pango_font_metrics_get_ascent(metrics))
self.underline_position = units_to_double(
self.underline_position = FROM_UNITS * (
pango.pango_font_metrics_get_underline_position(metrics))
self.strikethrough_position = units_to_double(
self.strikethrough_position = FROM_UNITS * (
pango.pango_font_metrics_get_strikethrough_position(metrics))
self.underline_thickness = units_to_double(
self.underline_thickness = FROM_UNITS * (
pango.pango_font_metrics_get_underline_thickness(metrics))
self.strikethrough_thickness = units_to_double(
self.strikethrough_thickness = FROM_UNITS * (
pango.pango_font_metrics_get_strikethrough_thickness(metrics))
else:
self.ascent = None
Expand Down Expand Up @@ -179,7 +176,7 @@ def add_attr(start, end, spacing):
pango.pango_attr_list_change(attr_list, attr)

if letter_spacing:
letter_spacing = units_from_double(letter_spacing)
letter_spacing = int(letter_spacing * TO_UNITS)
add_attr(0, len(bytestring), letter_spacing)

if word_spacing:
Expand All @@ -189,8 +186,7 @@ def add_attr(start, end, spacing):
text, bytestring = unicode_to_char_p(self.text)
pango.pango_layout_set_text(self.layout, text, -1)

space_spacing = (
units_from_double(word_spacing) + letter_spacing)
space_spacing = int(word_spacing * TO_UNITS + letter_spacing)
position = bytestring.find(b' ')
# Pango gives only half of word-spacing on boundaries
boundary_positions = (0, len(bytestring) - 1)
Expand Down Expand Up @@ -243,8 +239,7 @@ def create_layout(text, style, context, max_width, justification_spacing):
# signed integer. Treat bigger values same as None: unconstrained width.
text_wrap = style['white_space'] in ('normal', 'pre-wrap', 'pre-line')
if max_width is not None and text_wrap and max_width < 2 ** 21:
pango.pango_layout_set_width(
layout.layout, units_from_double(max(0, max_width)))
pango.pango_layout_set_width(layout.layout, int(max(0, max_width) * TO_UNITS))

layout.set_text(text)
return layout
Expand Down Expand Up @@ -473,10 +468,8 @@ def split_first_line(text, style, context, max_width, justification_spacing,
# instance) from keeping their shape when wrapped on the next line with
# pango layout. Maybe insert Unicode shaping characters in text?
layout.set_text(text)
pango.pango_layout_set_width(
layout.layout, units_from_double(max_width))
pango.pango_layout_set_wrap(
layout.layout, PANGO_WRAP_MODE['WRAP_CHAR'])
pango.pango_layout_set_width(layout.layout, int(max_width * TO_UNITS))
pango.pango_layout_set_wrap(layout.layout, PANGO_WRAP_MODE['WRAP_CHAR'])
first_line, index = layout.get_first_line()
resume_index = index or first_line.length
if resume_index >= len(text.encode()):
Expand Down

0 comments on commit 1aae145

Please sign in to comment.