2011-07-02 14:27:13 +04:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# coding: utf8
|
|
|
|
# WeasyPrint converts web documents (HTML, CSS, ...) to PDF.
|
|
|
|
# Copyright (C) 2011 Simon Sapin
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Affero General Public License as
|
|
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
|
|
# License, or (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU Affero General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
import gtk
|
|
|
|
import pango
|
|
|
|
|
|
|
|
ALIGN_PROPERTIES = {'left':pango.ALIGN_LEFT,
|
|
|
|
'center':pango.ALIGN_RIGHT,
|
|
|
|
'right':pango.ALIGN_CENTER}
|
|
|
|
|
|
|
|
STYLE_PROPERTIES = {'normal':pango.STYLE_NORMAL,
|
|
|
|
'italic':pango.STYLE_ITALIC,
|
|
|
|
'oblique':pango.STYLE_OBLIQUE}
|
|
|
|
|
|
|
|
VARIANT_PROPERTIES = {'normal':pango.VARIANT_NORMAL,
|
|
|
|
'small-caps':pango.VARIANT_SMALL_CAPS}
|
|
|
|
|
|
|
|
|
2011-07-07 18:40:27 +04:00
|
|
|
class TextFragment(object):
|
2011-07-12 19:59:31 +04:00
|
|
|
def __init__(self, text="", width=None):
|
2011-07-07 18:40:27 +04:00
|
|
|
self.context = gtk.DrawingArea().create_pango_context()
|
|
|
|
self.layout = pango.Layout(self.context)
|
2011-07-08 20:15:07 +04:00
|
|
|
self.set_text(text)
|
2011-07-12 19:59:31 +04:00
|
|
|
if width is not None:
|
|
|
|
self.set_width(width)
|
2011-07-08 20:15:07 +04:00
|
|
|
# self._set_attribute(pango.AttrFallback(True, 0, -1))
|
|
|
|
|
2011-07-02 14:27:13 +04:00
|
|
|
# Other properties
|
|
|
|
self.layout.set_wrap(pango.WRAP_WORD)
|
|
|
|
|
2011-07-12 19:59:31 +04:00
|
|
|
def set_textbox(self, textbox):
|
|
|
|
self.set_text(textbox.text)
|
2011-07-13 20:26:39 +04:00
|
|
|
self.set_font_family(textbox.style.font_family)
|
|
|
|
#self.set_font_variant(textbox.style.font_variant)
|
|
|
|
#self.set_font_weight(textbox.style.font_weight)
|
|
|
|
#self.set_font_style(textbox.style.font_style)
|
|
|
|
#self.set_letter_spacing(textbox.style.letter_spacing)
|
2011-07-12 19:59:31 +04:00
|
|
|
|
2011-07-07 18:40:27 +04:00
|
|
|
def _update_font(self):
|
2011-07-02 14:27:13 +04:00
|
|
|
self.layout.set_font_description(self._font)
|
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def _get_attributes(self):
|
|
|
|
attributes = self.layout.get_attributes()
|
|
|
|
if attributes:
|
|
|
|
return attributes
|
|
|
|
else:
|
|
|
|
return pango.AttrList()
|
|
|
|
|
|
|
|
def _set_attribute(self, value):
|
|
|
|
attributes = self.layout.get_attributes()
|
|
|
|
if attributes:
|
|
|
|
try:
|
|
|
|
attributes.change(value)
|
|
|
|
except:
|
|
|
|
attributes.insert(value)
|
|
|
|
else:
|
|
|
|
attributes = pango.AttrList()
|
|
|
|
attributes.insert(value)
|
|
|
|
self.layout.set_attributes(attributes)
|
|
|
|
|
2011-07-10 16:06:22 +04:00
|
|
|
def get_layout(self):
|
|
|
|
return self.layout.copy()
|
|
|
|
|
2011-07-07 18:40:27 +04:00
|
|
|
@property
|
2011-07-08 20:15:07 +04:00
|
|
|
def font(self):
|
|
|
|
self._font = self.layout.get_font_description()
|
|
|
|
if self._font is None:
|
|
|
|
self._font = self.layout.get_context().get_font_description()
|
|
|
|
return self._font
|
|
|
|
|
|
|
|
@font.setter
|
|
|
|
def font(self, font_desc):
|
|
|
|
self.layout.set_font_description(pango.FontDescription(font_desc))
|
|
|
|
|
|
|
|
def get_availables_font(self):
|
2011-07-07 18:40:27 +04:00
|
|
|
for font in gtk.DrawingArea().create_pango_context().list_families():
|
|
|
|
yield font.get_name()
|
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def get_text(self):
|
2011-07-07 12:08:22 +04:00
|
|
|
return self.layout.get_text().decode('utf-8')
|
2011-07-08 20:15:07 +04:00
|
|
|
|
|
|
|
def set_text(self, value):
|
2011-07-07 12:08:22 +04:00
|
|
|
self.layout.set_text(value.encode("utf-8"))
|
2011-07-08 20:15:07 +04:00
|
|
|
|
|
|
|
def get_size(self):
|
2011-07-02 14:27:13 +04:00
|
|
|
""" Return the real text area size in px unit """
|
|
|
|
return self.layout.get_pixel_size()
|
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def get_width(self):
|
2011-07-02 14:27:13 +04:00
|
|
|
""" Return the width. It's different to real width of text area """
|
|
|
|
return self._width
|
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def set_width(self, value):
|
2011-07-02 14:27:13 +04:00
|
|
|
self._width = value
|
|
|
|
self.layout.set_width(pango.SCALE * value)
|
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def get_spacing(self):
|
2011-07-07 12:08:22 +04:00
|
|
|
""" Return the spacing. """
|
2011-07-02 14:27:13 +04:00
|
|
|
return self.layout.get_spacing() / pango.SCALE
|
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def set_spacing(self, value):
|
2011-07-02 14:27:13 +04:00
|
|
|
self.layout.set_spacing(pango.SCALE * value)
|
2011-07-07 12:08:22 +04:00
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def get_alignment(self):
|
2011-07-07 12:08:22 +04:00
|
|
|
alignment = self.layout.get_alignment()
|
|
|
|
for key in ALIGN_PROPERTIES:
|
|
|
|
if ALIGN_PROPERTIES[key] == alignment:
|
|
|
|
return key
|
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def set_alignment(self, alignment):
|
2011-07-02 14:27:13 +04:00
|
|
|
if alignment in ("left", "center", "right"):
|
|
|
|
self.layout.set_alignment(ALIGN_PROPERTIES[alignment])
|
2011-07-07 12:08:22 +04:00
|
|
|
else:
|
|
|
|
raise ValueError('The alignment property must be in %s' \
|
|
|
|
% ALIGN_PROPERTIES.keys())
|
2011-07-02 14:27:13 +04:00
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def get_justify(self):
|
2011-07-02 14:27:13 +04:00
|
|
|
self.layout.get_justify()
|
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def set_justify(self, value):
|
2011-07-02 14:27:13 +04:00
|
|
|
self.layout.set_justify(value)
|
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def get_font_family(self):
|
2011-07-07 12:08:22 +04:00
|
|
|
return self.font.get_family().decode('utf-8')
|
2011-07-02 14:27:13 +04:00
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def set_font_family(self, value):
|
|
|
|
"""
|
|
|
|
value is list of font like this :
|
|
|
|
set_font_family("Al Mawash Bold, Comic sans MS")
|
|
|
|
"""
|
2011-07-07 12:08:22 +04:00
|
|
|
self.font.set_family(value.encode("utf-8"))
|
2011-07-07 18:40:27 +04:00
|
|
|
self._update_font()
|
2011-07-02 14:27:13 +04:00
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def get_font_style(self):
|
2011-07-07 12:08:22 +04:00
|
|
|
style = self.font.get_style()
|
|
|
|
for key in STYLE_PROPERTIES:
|
|
|
|
if STYLE_PROPERTIES[key] == style:
|
|
|
|
return key
|
2011-07-02 14:27:13 +04:00
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def set_font_style(self, value):
|
2011-07-02 14:27:13 +04:00
|
|
|
"""
|
|
|
|
The value of style must be either
|
|
|
|
pango.STYLE_NORMAL
|
|
|
|
pango.STYLE_ITALIC
|
2011-07-07 12:08:22 +04:00
|
|
|
pango.STYLE_OBLIQUE
|
2011-07-08 20:15:07 +04:00
|
|
|
but not both >> like css
|
2011-07-02 14:27:13 +04:00
|
|
|
|
|
|
|
...and font matching in Pango will match italic specifications
|
|
|
|
with oblique fonts and vice-versa if an exact match is not found.
|
|
|
|
"""
|
|
|
|
if value in STYLE_PROPERTIES.keys():
|
|
|
|
self.font.set_style(value)
|
2011-07-07 18:40:27 +04:00
|
|
|
self._update_font()
|
2011-07-02 14:27:13 +04:00
|
|
|
else:
|
|
|
|
raise ValueError('The style property must be in %s' \
|
|
|
|
% STYLE_PROPERTIES.keys())
|
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def get_font_size(self):
|
2011-07-02 14:27:13 +04:00
|
|
|
return self.font.get_size() / pango.SCALE
|
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def set_font_size(self, value):
|
2011-07-02 14:27:13 +04:00
|
|
|
"""The value of size is specified in px units."""
|
|
|
|
self.font.set_size(pango.SCALE * value)
|
2011-07-07 18:40:27 +04:00
|
|
|
self._update_font()
|
2011-07-02 14:27:13 +04:00
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def get_font_variant(self):
|
2011-07-07 12:08:22 +04:00
|
|
|
variant = self.font.get_variant()
|
|
|
|
for key in VARIANT_PROPERTIES:
|
|
|
|
if VARIANT_PROPERTIES[key] == variant:
|
|
|
|
return key
|
2011-07-02 14:27:13 +04:00
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def set_font_variant(self, value):
|
2011-07-02 14:27:13 +04:00
|
|
|
"""
|
|
|
|
variant : the variant type for the font description.
|
|
|
|
The set_variant() method sets the variant attribute field of a font
|
|
|
|
description to the value specified by variant. The value of variant
|
|
|
|
must be either
|
|
|
|
pango.VARIANT_NORMAL
|
2011-07-07 18:40:27 +04:00
|
|
|
pango.VARIANT_SMALL_CAPS
|
2011-07-02 14:27:13 +04:00
|
|
|
"""
|
|
|
|
if value in VARIANT_PROPERTIES.keys():
|
|
|
|
self.font.set_variant(value)
|
2011-07-07 18:40:27 +04:00
|
|
|
self._update_font()
|
2011-07-02 14:27:13 +04:00
|
|
|
else:
|
|
|
|
raise ValueError('The style property must be in %s' \
|
|
|
|
% VARIANT_PROPERTIES.keys())
|
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def get_font_weight(self):
|
2011-07-07 18:40:27 +04:00
|
|
|
return int(float(self.font.get_weight()))
|
2011-07-02 14:27:13 +04:00
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def set_font_weight(self, value):
|
2011-07-02 14:27:13 +04:00
|
|
|
"""
|
|
|
|
The value of weight specifies how bold or light the font should be
|
|
|
|
in a range from 100 to 900. The predefined values of weight are :
|
|
|
|
|
|
|
|
pango.WEIGHT_ULTRALIGHT the ultralight weight (= 200)
|
|
|
|
pango.WEIGHT_LIGHT the light weight (=300)
|
|
|
|
pango.WEIGHT_NORMAL the default weight (= 400)
|
|
|
|
pango.WEIGHT_BOLD the bold weight (= 700)
|
|
|
|
pango.WEIGHT_HEAVY the heavy weight (= 900)
|
|
|
|
pango.WEIGHT_ULTRABOLD the ultrabold weight (= 800)
|
|
|
|
"""
|
|
|
|
self.font.set_weight(value)
|
2011-07-07 18:40:27 +04:00
|
|
|
self._update_font()
|
2011-07-08 20:15:07 +04:00
|
|
|
|
|
|
|
def set_foreground(self, spec):
|
|
|
|
"""
|
|
|
|
The string in spec can either one of a large set of standard names.
|
|
|
|
(Taken from the X11 rgb.txt file), or it can be a hex value in the
|
|
|
|
form 'rgb' 'rrggbb' 'rrrgggbbb' or 'rrrrggggbbbb' where 'r', 'g' and
|
2011-07-10 16:06:22 +04:00
|
|
|
'b' are hex digits of the red, green, and blue components of the
|
|
|
|
color, respectively. (White in the four forms is 'fff' 'ffffff'
|
2011-07-08 20:15:07 +04:00
|
|
|
'fffffffff' and 'ffffffffffff')
|
|
|
|
"""
|
|
|
|
color = pango.Color(spec)
|
2011-07-10 16:06:22 +04:00
|
|
|
fg_color = pango.AttrForeground(color.red, color.blue, color.green,
|
2011-07-08 20:15:07 +04:00
|
|
|
0, -1)
|
|
|
|
self._set_attribute(fg_color)
|
|
|
|
|
|
|
|
def set_background(self, spec):
|
|
|
|
color = pango.Color(spec)
|
2011-07-10 16:06:22 +04:00
|
|
|
bg_color = pango.AttrBackground(color.red, color.blue, color.green,
|
2011-07-08 20:15:07 +04:00
|
|
|
0, -1)
|
|
|
|
self._set_attribute(bg_color)
|
|
|
|
|
|
|
|
def set_underline(self, boolean):
|
|
|
|
if boolean:
|
|
|
|
underline = pango.AttrUnderline(pango.UNDERLINE_SINGLE, 0, -1)
|
|
|
|
else:
|
|
|
|
underline = pango.AttrUnderline(pango.UNDERLINE_NONE, 0, -1)
|
|
|
|
self._set_attribute(underline)
|
|
|
|
|
|
|
|
def set_overline(self, boolean):
|
|
|
|
raise NotImplemented("Overline is not implemented in pango ?")
|
|
|
|
|
|
|
|
def set_line_through(self, boolean):
|
|
|
|
self._set_attribute(pango.AttrStrikethrough(boolean, 0, -1))
|
|
|
|
|
|
|
|
def set_underline_color(self, spec):
|
|
|
|
color = pango.Color(spec)
|
2011-07-10 16:06:22 +04:00
|
|
|
color = pango.AttrUnderlineColor(color.red, color.blue, color.green,
|
2011-07-08 20:15:07 +04:00
|
|
|
0, -1)
|
|
|
|
self._set_attribute(color)
|
|
|
|
|
|
|
|
def set_line_through_color(self, spec):
|
|
|
|
color = pango.Color(spec)
|
2011-07-10 16:06:22 +04:00
|
|
|
color = pango.AttrStrikethroughColor(color.red,color.blue,color.green,
|
2011-07-08 20:15:07 +04:00
|
|
|
0, -1)
|
|
|
|
self._set_attribute(color)
|
|
|
|
|
|
|
|
def set_letter_spacing(self, value):
|
|
|
|
ls = pango.AttrLetterSpacing(value * pango.SCALE, 0, -1)
|
|
|
|
self._set_attribute(ls)
|
|
|
|
|
|
|
|
def set_rise(self, value):
|
|
|
|
rise = pango.AttrRise(value * pango.SCALE, 0,1)
|
|
|
|
self._set_attribute(rise)
|
|
|
|
|
2011-07-10 16:06:22 +04:00
|
|
|
class TextLineFragment(TextFragment):
|
2011-07-13 20:26:39 +04:00
|
|
|
def __init__(self, text="", width=None):
|
2011-07-10 16:06:22 +04:00
|
|
|
super(TextLineFragment, self).__init__(text, width)
|
|
|
|
|
|
|
|
def get_layout(self):
|
|
|
|
layout = super(TextLineFragment, self).get_layout()
|
|
|
|
layout.set_text(self.get_text())
|
|
|
|
return layout
|
|
|
|
|
|
|
|
def get_parent_layout(self):
|
|
|
|
return super(TextLineFragment, self).get_layout()
|
2011-07-08 20:15:07 +04:00
|
|
|
|
|
|
|
def get_remaining_text(self):
|
2011-07-02 14:27:13 +04:00
|
|
|
first_line = self.layout.get_line(0)
|
2011-07-13 20:26:39 +04:00
|
|
|
return self.layout.get_text()[first_line.length:].decode('utf-8')
|
2011-07-02 14:27:13 +04:00
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def get_text(self):
|
2011-07-02 14:27:13 +04:00
|
|
|
first_line = self.layout.get_line(0)
|
2011-07-13 20:26:39 +04:00
|
|
|
return self.layout.get_text()[:first_line.length].decode('utf-8')
|
2011-07-02 14:27:13 +04:00
|
|
|
|
2011-07-08 20:15:07 +04:00
|
|
|
def get_size(self):
|
2011-07-07 12:08:22 +04:00
|
|
|
""" Return the real text area dimension for this line in px unit """
|
2011-07-10 16:06:22 +04:00
|
|
|
# extents = self.layout.get_line(0).get_pixel_extents()[1]
|
|
|
|
# return (extents[2]-extents[0], extents[3]-extents[1])
|
|
|
|
# What's the size ? the size of the line, or the size of a layoutLine ?
|
|
|
|
return self.get_layout().get_pixel_size()
|
|
|
|
|
|
|
|
def get_logical_extents(self):
|
|
|
|
return self.layout.get_line(0).get_pixel_extents()[1]
|