2011-07-02 14:27:13 +04:00
|
|
|
# coding: utf8
|
2012-03-22 02:19:27 +04:00
|
|
|
"""
|
|
|
|
weasyprint.text
|
|
|
|
---------------
|
2011-08-19 18:52:56 +04:00
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
Interface with Pango to decide where to do line breaks and to draw text.
|
2011-08-19 18:52:56 +04:00
|
|
|
|
2012-03-22 02:19:27 +04:00
|
|
|
:copyright: Copyright 2011-2012 Simon Sapin and contributors, see AUTHORS.
|
|
|
|
:license: BSD, see LICENSE for details.
|
2011-08-19 18:52:56 +04:00
|
|
|
|
|
|
|
"""
|
|
|
|
|
2012-02-17 21:49:58 +04:00
|
|
|
from __future__ import division, unicode_literals
|
2011-09-30 13:54:56 +04:00
|
|
|
|
2012-01-30 15:34:58 +04:00
|
|
|
from cgi import escape
|
2011-09-02 21:47:29 +04:00
|
|
|
from gi.repository import Pango, PangoCairo # pylint: disable=E0611
|
2011-07-02 14:27:13 +04:00
|
|
|
|
2011-08-11 19:26:08 +04:00
|
|
|
|
2011-10-13 20:23:49 +04:00
|
|
|
PANGO_VARIANT = {
|
|
|
|
'normal': Pango.Variant.NORMAL,
|
|
|
|
'small-caps': Pango.Variant.SMALL_CAPS,
|
|
|
|
}
|
|
|
|
|
|
|
|
PANGO_STYLE = {
|
|
|
|
'normal': Pango.Style.NORMAL,
|
|
|
|
'italic': Pango.Style.ITALIC,
|
|
|
|
'oblique': Pango.Style.OBLIQUE,
|
|
|
|
}
|
|
|
|
|
2011-07-07 18:40:27 +04:00
|
|
|
class TextFragment(object):
|
2011-08-19 18:52:56 +04:00
|
|
|
"""Text renderer using Pango.
|
|
|
|
|
2011-09-30 13:54:56 +04:00
|
|
|
This class is used to render the text from a TextBox.
|
2011-08-19 18:52:56 +04:00
|
|
|
|
2011-07-29 03:13:07 +04:00
|
|
|
"""
|
2012-02-01 19:13:47 +04:00
|
|
|
def __init__(self, text, style, context, width=None):
|
2011-09-30 13:54:56 +04:00
|
|
|
self.layout = PangoCairo.create_layout(context)
|
2011-10-13 20:23:49 +04:00
|
|
|
font = Pango.FontDescription()
|
|
|
|
font.set_family(', '.join(style.font_family))
|
|
|
|
font.set_variant(PANGO_VARIANT[style.font_variant])
|
|
|
|
font.set_style(PANGO_STYLE[style.font_style])
|
|
|
|
font.set_absolute_size(Pango.units_from_double(style.font_size))
|
|
|
|
font.set_weight(style.font_weight)
|
|
|
|
self.layout.set_font_description(font)
|
2011-11-08 16:09:19 +04:00
|
|
|
self.layout.set_text(text, -1)
|
2011-09-02 03:10:55 +04:00
|
|
|
self.layout.set_wrap(Pango.WrapMode.WORD)
|
2012-02-01 19:13:47 +04:00
|
|
|
word_spacing = style.word_spacing
|
2012-02-01 20:13:08 +04:00
|
|
|
letter_spacing = style.letter_spacing
|
|
|
|
if letter_spacing == 'normal':
|
|
|
|
letter_spacing = 0
|
|
|
|
if text and (word_spacing != 0 or letter_spacing != 0):
|
2012-01-26 14:33:49 +04:00
|
|
|
word_spacing = Pango.units_from_double(word_spacing)
|
2012-02-01 20:13:08 +04:00
|
|
|
letter_spacing = Pango.units_from_double(letter_spacing)
|
2012-01-30 15:34:58 +04:00
|
|
|
markup = escape(text).replace(
|
2012-02-01 20:13:08 +04:00
|
|
|
' ', '<span letter_spacing="%i"> </span>' % (
|
|
|
|
word_spacing + letter_spacing,))
|
|
|
|
markup = '<span letter_spacing="%i">%s</span>' % (
|
|
|
|
letter_spacing , markup)
|
2012-01-26 14:33:49 +04:00
|
|
|
attributes_list = Pango.parse_markup(markup, -1, '\x00')[1]
|
|
|
|
self.layout.set_attributes(attributes_list)
|
2011-09-30 13:54:56 +04:00
|
|
|
if width is not None:
|
2011-10-08 20:31:15 +04:00
|
|
|
self.layout.set_width(Pango.units_from_double(width))
|
2011-07-18 12:38:56 +04:00
|
|
|
|
2011-09-27 17:19:31 +04:00
|
|
|
# TODO: use get_line instead of get_lines when it is not broken anymore
|
|
|
|
def split_first_line(self):
|
2011-10-08 20:31:15 +04:00
|
|
|
"""Fit as much as possible in the available width for one line of text.
|
2011-07-18 12:38:56 +04:00
|
|
|
|
2011-10-08 22:38:33 +04:00
|
|
|
Return ``(show_line, length, width, height, resume_at)``.
|
2011-07-18 12:38:56 +04:00
|
|
|
|
2011-10-08 22:38:33 +04:00
|
|
|
``show_line``: a closure that takes a cairo Context and draws the
|
|
|
|
first line.
|
2011-10-08 20:31:15 +04:00
|
|
|
``length``: length in UTF-8 bytes of the first line
|
|
|
|
``width``: width in pixels of the first line
|
|
|
|
``height``: height in pixels of the first line
|
|
|
|
``baseline``: baseline in pixels of the first line
|
|
|
|
``resume_at``: The number of UTF-8 bytes to skip for the next line.
|
|
|
|
May be ``None`` if the whole text fits in one line.
|
|
|
|
This may be greater than ``length`` in case of preserved
|
|
|
|
newline characters.
|
2011-09-30 13:54:56 +04:00
|
|
|
|
|
|
|
"""
|
2011-10-11 14:36:23 +04:00
|
|
|
lines = self.layout.get_lines_readonly()
|
2011-10-08 20:31:15 +04:00
|
|
|
first_line = lines[0]
|
|
|
|
length = first_line.length
|
|
|
|
_ink_extents, logical_extents = first_line.get_extents()
|
|
|
|
width = Pango.units_to_double(logical_extents.width)
|
|
|
|
height = Pango.units_to_double(logical_extents.height)
|
2011-10-13 19:37:09 +04:00
|
|
|
baseline = Pango.units_to_double(self.layout.get_baseline())
|
2011-10-08 20:31:15 +04:00
|
|
|
if len(lines) >= 2:
|
|
|
|
resume_at = lines[1].start_index
|
|
|
|
else:
|
|
|
|
resume_at = None
|
2011-10-08 22:38:33 +04:00
|
|
|
|
|
|
|
def show_line(context):
|
|
|
|
"""Draw the given ``line`` to the Cairo ``context``."""
|
|
|
|
PangoCairo.update_layout(context, self.layout)
|
|
|
|
PangoCairo.show_layout_line(context, first_line)
|
|
|
|
|
|
|
|
return show_line, length, width, height, baseline, resume_at
|
2012-01-03 13:56:02 +04:00
|
|
|
|
|
|
|
def line_widths(self):
|
|
|
|
"""Return the width for each line."""
|
|
|
|
lines = self.layout.get_lines_readonly()
|
|
|
|
for line in lines:
|
|
|
|
_ink_extents, logical_extents = line.get_extents()
|
|
|
|
yield Pango.units_to_double(logical_extents.width)
|