diff --git a/weasyprint/css/__init__.py b/weasyprint/css/__init__.py index a51fc9240..8cae9e985 100644 --- a/weasyprint/css/__init__.py +++ b/weasyprint/css/__init__.py @@ -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']) @@ -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) @@ -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( diff --git a/weasyprint/css/computed_values.py b/weasyprint/css/computed_values.py index 87cccc2b0..6447e99dc 100644 --- a/weasyprint/css/computed_values.py +++ b/weasyprint/css/computed_values.py @@ -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) @@ -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('-', '_') @@ -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': @@ -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 @@ -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 diff --git a/weasyprint/layout/inline.py b/weasyprint/layout/inline.py index 36627e193..81770f5b3 100644 --- a/weasyprint/layout/inline.py +++ b/weasyprint/layout/inline.py @@ -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 @@ -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':