From 4e6d449c5c12a43091cb937239995ca7a6752b3f Mon Sep 17 00:00:00 2001 From: Guillaume Ayoub Date: Wed, 21 Nov 2018 22:16:52 +0100 Subject: [PATCH] Don't store Pango layout between page layout and drawing steps Creating Pango layouts during page layout and recreating them when drawing pages is a little bit slower but frees A LOT of memory. --- weasyprint/draw.py | 2 +- weasyprint/layout/inlines.py | 2 +- weasyprint/text.py | 37 ++++++++++++++++++++++++------------ 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/weasyprint/draw.py b/weasyprint/draw.py index 9e9f1928..e146fca5 100644 --- a/weasyprint/draw.py +++ b/weasyprint/draw.py @@ -1015,7 +1015,7 @@ def draw_text(context, textbox, enable_hinting): context.move_to(textbox.position_x, textbox.position_y + textbox.baseline) context.set_source_rgba(*textbox.style['color']) - show_first_line(context, textbox.pango_layout) + show_first_line(context, textbox) values = textbox.style['text_decoration'] thickness = textbox.style['font_size'] / 18 # Like other browsers do diff --git a/weasyprint/layout/inlines.py b/weasyprint/layout/inlines.py index 2be38c8e..ec9034ae 100644 --- a/weasyprint/layout/inlines.py +++ b/weasyprint/layout/inlines.py @@ -970,7 +970,7 @@ def split_text_box(context, box, available_width, skip): # No need to encode what’s after resume_at (if set) or length (if # resume_at is not set). One code point is one or more byte, so # UTF-8 indexes are always bigger or equal to Unicode indexes. - new_text = layout.text_bytes.decode('utf8') + new_text = layout.text encoded = text.encode('utf8') if resume_at is not None: between = encoded[length:resume_at].decode('utf8') diff --git a/weasyprint/text.py b/weasyprint/text.py index 088f5113..ab68fa98 100644 --- a/weasyprint/text.py +++ b/weasyprint/text.py @@ -139,6 +139,8 @@ ffi.cdef(''' PangoLayout *layout, const PangoFontDescription *desc); void pango_layout_set_wrap ( PangoLayout *layout, PangoWrapMode wrap); + void pango_layout_set_single_paragraph_mode ( + PangoLayout *layout, gboolean setting); int pango_layout_get_baseline (PangoLayout *layout); PangoLayoutIter * pango_layout_get_iter (PangoLayout *layout); @@ -600,7 +602,7 @@ def first_line_metrics(first_line, text, layout, resume_at, space_collapse, if u'\u00ad' in first_line_text: if first_line_text[0] == u'\u00ad': length += 2 # len(u'\u00ad'.encode('utf8')) - for i in range(len(layout.text_bytes.decode('utf8'))): + for i in range(len(layout.text)): while i + soft_hyphens + 1 < len(first_line_text): if first_line_text[i + soft_hyphens + 1] == u'\u00ad': soft_hyphens += 1 @@ -609,12 +611,16 @@ def first_line_metrics(first_line, text, layout, resume_at, space_collapse, length += soft_hyphens * 2 # len(u'\u00ad'.encode('utf8')) width, height = get_size(first_line, style) baseline = units_to_double(pango.pango_layout_get_baseline(layout.layout)) + layout.deactivate() return layout, length, resume_at, width, height, baseline class Layout(object): """Object holding PangoLayout-related cdata pointers.""" def __init__(self, context, font_size, style): + self.setup(context, font_size, style) + + def setup(self, context, font_size, style): from .fonts import ZERO_FONTSIZE_CRASHES_CAIRO # Cairo crashes with font-size: 0 when using Win32 API @@ -676,8 +682,7 @@ class Layout(object): # Keep only the first two lines, we don't need the other ones text, bytestring = unicode_to_char_p( '\n'.join(text.split('\n', 3)[:2])) - self.text = text - self.text_bytes = bytestring + self.text = bytestring.decode('utf-8') pango.pango_layout_set_text(self.layout, text, -1) def get_font_metrics(self): @@ -705,6 +710,15 @@ class Layout(object): pango.pango_tab_array_free) pango.pango_layout_set_tabs(self.layout, array) + def deactivate(self): + del self.layout + del self.font + del self.language + + def reactivate(self, style): + self.setup(self.context, style['font_size'], style) + self.set_text(self.text) + class FontMetrics(object): def __init__(self, context, font, language): @@ -850,7 +864,7 @@ def create_layout(text, style, context, max_width, justification_spacing): pango.pango_layout_set_width( layout.layout, units_from_double(max_width)) - text_bytes = layout.text_bytes + text_bytes = layout.text.encode('utf-8') # Word and letter spacings word_spacing = style['word_spacing'] + justification_spacing @@ -1149,15 +1163,14 @@ def split_first_line(text, style, context, max_width, justification_spacing, style['hyphenate_character']) -def show_first_line(context, pango_layout): - """Draw the given ``line`` to the Cairo ``context``.""" +def show_first_line(context, textbox): + """Draw the given ``textbox`` line to the Cairo ``context``.""" + textbox.pango_layout.reactivate(textbox.style) + pango.pango_layout_set_single_paragraph_mode( + textbox.pango_layout.layout, True) + first_line, _ = textbox.pango_layout.get_first_line() context = ffi.cast('cairo_t *', context._pointer) - # Set an infinite width as we don't want to break lines when drawing, the - # lines have already been split and the size may differ for example because - # of hinting. - pango.pango_layout_set_width(pango_layout.layout, -1) - pangocairo.pango_cairo_show_layout_line( - context, pango_layout.get_first_line()[0]) + pangocairo.pango_cairo_show_layout_line(context, first_line) def can_break_text(text, lang):