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

perf: cache calculations for 'ch' and 'ex' units #1587

Merged
merged 2 commits into from
Mar 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions weasyprint/css/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
None, 'before', 'after', 'marker', 'first-line', 'first-letter',
'footnote-call', 'footnote-marker')

DEFAULT_CACHE = {
'ratio_ch': {},
'ratio_ex': {},
}


PageType = namedtuple('PageType', ['side', 'blank', 'first', 'index', 'name'])

Expand Down Expand Up @@ -600,6 +605,10 @@ def __init__(self, parent_style):
})
self.parent_style = parent_style
self.specified = self
if parent_style:
self.cache = parent_style.cache
else:
self.cache = DEFAULT_CACHE.copy()

def copy(self):
copy = AnonymousStyle(self.parent_style)
Expand Down Expand Up @@ -629,6 +638,10 @@ def __init__(self, parent_style, cascaded, element, pseudo_type,
self.pseudo_type = pseudo_type
self.root_style = root_style
self.base_url = base_url
if parent_style:
self.cache = parent_style.cache
else:
self.cache = DEFAULT_CACHE.copy()

def copy(self):
copy = ComputedStyle(
Expand Down
57 changes: 42 additions & 15 deletions weasyprint/css/computed_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from ..logger import LOGGER
from ..text.ffi import ffi, pango, units_to_double
from ..text.line_break import Layout, first_line_metrics, line_size
from ..text.line_break import Layout, first_line_metrics
from ..urls import get_link_attribute
from .properties import (
INHERITED, INITIAL_NOT_COMPUTED, INITIAL_VALUES, Dimension)
Expand Down Expand Up @@ -178,6 +178,22 @@ def _resolve_var(computed, variable_name, default, parent_style):
return computed_value


def _font_style_cache_key(style):
return str((
style['font_family'],
style['font_style'],
style['font_stretch'],
style['font_weight'],
style['font_variant_ligatures'],
style['font_variant_position'],
style['font_variant_caps'],
style['font_variant_numeric'],
style['font_variant_alternates'],
style['font_variant_east_asian'],
style['font_feature_settings'],
))


def register_computer(name):
"""Decorator registering a property ``name`` for a function."""
name = name.replace('-', '_')
Expand Down Expand Up @@ -321,18 +337,26 @@ def length(style, name, value, font_size=None, pixels_only=False):
if font_size is None:
font_size = style['font_size']
if unit == 'ex':
# TODO: cache
result = value.value * font_size * ex_ratio(style)
cache_key = _font_style_cache_key(style)

if cache_key in style.cache['ratio_ex']:
ratio = style.cache['ratio_ex'][cache_key]
else:
ratio = _character_ratio(style, 'x')
style.cache['ratio_ex'][cache_key] = ratio

result = value.value * font_size * ratio
elif unit == 'ch':
# TODO: cache
# TODO: use context to use @font-face fonts
layout = Layout(
context=None, font_size=font_size,
style=style)
layout.set_text('0')
line, _ = layout.get_first_line()
logical_width, _ = line_size(line, style)
result = value.value * logical_width
cache_key = _font_style_cache_key(style)

if cache_key in style.cache['ratio_ch']:
ratio = style.cache['ratio_ch'][cache_key]
else:
ratio = _character_ratio(style, '0')
style.cache['ratio_ch'][cache_key] = ratio

result = value.value * font_size * ratio
elif unit == 'em':
result = value.value * font_size
elif unit == 'rem':
Expand Down Expand Up @@ -745,7 +769,7 @@ def strut_layout(style, context=None):
return result


def ex_ratio(style):
def _character_ratio(style, character):
"""Return the ratio 1ex/font_size, according to given style."""
# TODO: use context to use @font-face fonts

Expand All @@ -758,14 +782,17 @@ def ex_ratio(style):
font_size = 1000

layout = Layout(context=None, font_size=font_size, style=style)
layout.set_text('x')
layout.set_text(character)
line, _ = layout.get_first_line()

ink_extents = ffi.new('PangoRectangle *')
pango.pango_layout_line_get_extents(line, ink_extents, ffi.NULL)
height_above_baseline = units_to_double(ink_extents.y)
if character == 'x':
measure = units_to_double(ink_extents.y)
else:
measure = units_to_double(ink_extents.width)
ffi.release(ink_extents)

# Zero means some kind of failure, fallback is 0.5.
# We round to try keeping exact values that were altered by Pango.
return round(-height_above_baseline / font_size, 5) or 0.5
return round(measure / font_size, 5) or 0.5
8 changes: 6 additions & 2 deletions weasyprint/layout/inline.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
from math import inf

from ..css import computed_from_cascaded
from ..css.computed_values import ex_ratio, strut_layout
from ..css.computed_values import length as computed_length
from ..css.computed_values import strut_layout
from ..css.properties import Dimension
from ..formatting_structure import boxes
from ..text.line_break import can_break_text, create_layout, split_first_line
from .absolute import AbsolutePlaceholder, absolute_layout
Expand Down Expand Up @@ -1048,7 +1050,9 @@ def inline_box_verticality(box, top_bottom_subtrees, baseline_y):
if vertical_align == 'baseline':
child_baseline_y = baseline_y
elif vertical_align == 'middle':
one_ex = box.style['font_size'] * ex_ratio(box.style)
one_ex = computed_length(
box.style, 'height', Dimension(1, 'em'),
box.style['font_size'], pixels_only=True)
top = baseline_y - (one_ex + child.margin_height()) / 2
child_baseline_y = top + child.baseline
elif vertical_align == 'text-top':
Expand Down