2011-07-12 19:53:15 +04:00
|
|
|
|
# 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/>.
|
|
|
|
|
|
|
|
|
|
|
2011-08-22 19:36:07 +04:00
|
|
|
|
"""
|
|
|
|
|
Various drawing helpers.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
2011-08-09 12:15:53 +04:00
|
|
|
|
from __future__ import division
|
2011-08-10 12:01:56 +04:00
|
|
|
|
import urllib
|
2011-08-09 12:15:53 +04:00
|
|
|
|
|
2011-07-12 19:53:15 +04:00
|
|
|
|
import cairo
|
2011-08-17 20:21:52 +04:00
|
|
|
|
from StringIO import StringIO
|
2011-07-12 19:53:15 +04:00
|
|
|
|
|
2011-08-22 19:36:07 +04:00
|
|
|
|
from ..formatting_structure import boxes
|
2011-10-08 16:41:12 +04:00
|
|
|
|
from ..css.values import get_percentage_value
|
2011-08-09 12:15:53 +04:00
|
|
|
|
|
|
|
|
|
|
2011-08-22 20:33:20 +04:00
|
|
|
|
SUPPORTED_IMAGES = ['image/png', 'image/gif', 'image/jpeg', 'image/bmp']
|
2011-08-17 20:21:52 +04:00
|
|
|
|
|
2011-08-23 13:42:40 +04:00
|
|
|
|
|
2011-08-10 16:51:18 +04:00
|
|
|
|
def get_image_surface_from_uri(uri):
|
2011-08-22 19:36:07 +04:00
|
|
|
|
"""Get a :class:`cairo.ImageSurface`` from an image URI."""
|
2011-10-10 18:39:41 +04:00
|
|
|
|
fileimage = urllib.FancyURLopener().open(uri)
|
|
|
|
|
info = fileimage.info()
|
|
|
|
|
if hasattr(info, 'get_content_type'):
|
|
|
|
|
# Python 3
|
|
|
|
|
mime_type = info.get_content_type()
|
|
|
|
|
else:
|
|
|
|
|
# Python 2
|
|
|
|
|
mime_type = info.gettype()
|
2011-08-25 19:29:16 +04:00
|
|
|
|
# TODO: implement image type sniffing?
|
|
|
|
|
# http://www.w3.org/TR/html5/fetching-resources.html#content-type-sniffing:-image
|
2011-08-22 12:17:37 +04:00
|
|
|
|
if mime_type in SUPPORTED_IMAGES:
|
|
|
|
|
if mime_type == "image/png":
|
|
|
|
|
image = fileimage
|
|
|
|
|
else:
|
2011-10-11 14:08:22 +04:00
|
|
|
|
from PIL import Image
|
2011-08-23 13:42:40 +04:00
|
|
|
|
content = fileimage.read()
|
|
|
|
|
pil_image = Image.open(StringIO(content))
|
2011-08-22 12:17:37 +04:00
|
|
|
|
image = StringIO()
|
2011-08-23 13:42:40 +04:00
|
|
|
|
pil_image = pil_image.convert('RGBA')
|
|
|
|
|
pil_image.save(image, "PNG")
|
2011-08-22 12:17:37 +04:00
|
|
|
|
image.seek(0)
|
|
|
|
|
return cairo.ImageSurface.create_from_png(image)
|
2011-08-09 18:08:58 +04:00
|
|
|
|
|
2011-08-10 14:40:28 +04:00
|
|
|
|
|
2011-08-09 18:08:58 +04:00
|
|
|
|
def draw_box(context, box):
|
2011-08-22 19:36:07 +04:00
|
|
|
|
"""Draw a ``box`` on ``context``."""
|
2011-08-09 18:08:58 +04:00
|
|
|
|
if has_background(box):
|
|
|
|
|
draw_background(context, box)
|
|
|
|
|
|
2011-08-20 17:07:14 +04:00
|
|
|
|
marker_box = getattr(box, 'outside_list_marker', None)
|
|
|
|
|
if marker_box:
|
|
|
|
|
draw_box(context, marker_box)
|
|
|
|
|
|
2011-08-09 18:08:58 +04:00
|
|
|
|
if isinstance(box, boxes.TextBox):
|
|
|
|
|
draw_text(context, box)
|
|
|
|
|
return
|
2011-08-05 13:31:16 +04:00
|
|
|
|
|
2011-08-09 18:08:58 +04:00
|
|
|
|
if isinstance(box, boxes.ReplacedBox):
|
|
|
|
|
draw_replacedbox(context, box)
|
|
|
|
|
|
|
|
|
|
if isinstance(box, boxes.ParentBox):
|
|
|
|
|
for child in box.children:
|
|
|
|
|
draw_box(context, child)
|
|
|
|
|
|
|
|
|
|
draw_border(context, box)
|
2011-08-05 13:31:16 +04:00
|
|
|
|
|
2011-08-10 14:40:28 +04:00
|
|
|
|
|
2011-08-08 23:20:43 +04:00
|
|
|
|
def has_background(box):
|
2011-08-22 19:36:07 +04:00
|
|
|
|
"""Return whether the given box has any background."""
|
2011-10-08 16:41:12 +04:00
|
|
|
|
return box.style.background_color.alpha > 0 or \
|
|
|
|
|
box.style.background_image != 'none'
|
2011-08-08 23:20:43 +04:00
|
|
|
|
|
|
|
|
|
|
2011-08-24 17:40:13 +04:00
|
|
|
|
def draw_page_background(context, page):
|
|
|
|
|
"""Draw the backgrounds for the page box (from @page style) and for the
|
|
|
|
|
page area (from the root element).
|
2011-08-10 14:40:28 +04:00
|
|
|
|
|
2011-08-24 17:40:13 +04:00
|
|
|
|
If the root element is "html" and has no background, the page area’s
|
2011-08-10 14:40:28 +04:00
|
|
|
|
background is taken from its "body" child.
|
|
|
|
|
|
|
|
|
|
In both cases the background position is the same as if it was drawn on
|
|
|
|
|
the element.
|
|
|
|
|
|
|
|
|
|
See http://www.w3.org/TR/CSS21/colors.html#background
|
2011-08-22 19:36:07 +04:00
|
|
|
|
|
2011-08-10 14:40:28 +04:00
|
|
|
|
"""
|
2011-08-24 17:40:13 +04:00
|
|
|
|
# TODO: this one should have its origin at (0, 0), not the border box
|
|
|
|
|
# of the page.
|
|
|
|
|
# TODO: more tests for this, see
|
|
|
|
|
# http://www.w3.org/TR/css3-page/#page-properties
|
|
|
|
|
draw_background(context, page, clip=False)
|
2011-08-10 14:40:28 +04:00
|
|
|
|
if has_background(page.root_box):
|
2011-08-24 17:40:13 +04:00
|
|
|
|
draw_background(context, page.root_box, clip=False)
|
2011-08-10 14:40:28 +04:00
|
|
|
|
elif page.root_box.element.tag.lower() == 'html':
|
|
|
|
|
for child in page.root_box.children:
|
|
|
|
|
if child.element.tag.lower() == 'body':
|
|
|
|
|
# This must be drawn now, before anything on the root element.
|
2011-08-24 17:40:13 +04:00
|
|
|
|
draw_background(context, child, clip=False)
|
2011-08-08 23:20:43 +04:00
|
|
|
|
|
|
|
|
|
|
2011-08-24 17:40:13 +04:00
|
|
|
|
def draw_background(context, box, clip=True):
|
2011-08-22 19:36:07 +04:00
|
|
|
|
"""Draw the box background color and image to a ``cairo.Context``."""
|
2011-08-10 14:40:28 +04:00
|
|
|
|
if getattr(box, 'background_drawn', False):
|
2011-08-08 23:20:43 +04:00
|
|
|
|
return
|
|
|
|
|
|
2011-08-10 14:40:28 +04:00
|
|
|
|
box.background_drawn = True
|
|
|
|
|
|
|
|
|
|
if not has_background(box):
|
|
|
|
|
return
|
2011-08-10 12:01:56 +04:00
|
|
|
|
|
2011-08-08 23:20:43 +04:00
|
|
|
|
with context.stacked():
|
2011-08-11 17:55:01 +04:00
|
|
|
|
bg_x = box.border_box_x()
|
|
|
|
|
bg_y = box.border_box_y()
|
2011-08-10 18:52:34 +04:00
|
|
|
|
bg_width = box.border_width()
|
|
|
|
|
bg_height = box.border_height()
|
|
|
|
|
|
2011-10-08 16:41:12 +04:00
|
|
|
|
bg_attachement = box.style.background_attachment
|
2011-10-06 17:36:19 +04:00
|
|
|
|
if bg_attachement == 'fixed':
|
|
|
|
|
# There should not be any clip yet
|
|
|
|
|
x1, y1, x2, y2 = context.clip_extents()
|
|
|
|
|
page_width = x2 - x1
|
|
|
|
|
page_height = y2 - y1
|
|
|
|
|
|
2011-08-24 17:40:13 +04:00
|
|
|
|
if clip:
|
2011-08-11 17:55:01 +04:00
|
|
|
|
context.rectangle(bg_x, bg_y, bg_width, bg_height)
|
2011-08-08 23:20:43 +04:00
|
|
|
|
context.clip()
|
2011-08-10 14:40:28 +04:00
|
|
|
|
|
2011-08-10 12:01:56 +04:00
|
|
|
|
# Background color
|
2011-10-08 16:41:12 +04:00
|
|
|
|
bg_color = box.style.background_color
|
2011-08-08 23:20:43 +04:00
|
|
|
|
if bg_color.alpha > 0:
|
|
|
|
|
context.set_source_colorvalue(bg_color)
|
|
|
|
|
context.paint()
|
2011-08-10 14:40:28 +04:00
|
|
|
|
|
2011-08-11 17:55:01 +04:00
|
|
|
|
if bg_attachement == 'scroll':
|
|
|
|
|
# Change coordinates to make the rest easier.
|
|
|
|
|
context.translate(bg_x, bg_y)
|
|
|
|
|
else:
|
|
|
|
|
assert bg_attachement == 'fixed'
|
2011-10-06 17:36:19 +04:00
|
|
|
|
bg_width = page_width
|
2011-10-07 13:36:08 +04:00
|
|
|
|
bg_height = page_height
|
2011-08-11 17:55:01 +04:00
|
|
|
|
|
2011-08-10 12:01:56 +04:00
|
|
|
|
# Background image
|
2011-10-08 16:41:12 +04:00
|
|
|
|
bg_image = box.style.background_image
|
|
|
|
|
if bg_image == 'none':
|
2011-08-10 18:52:34 +04:00
|
|
|
|
return
|
|
|
|
|
|
2011-10-08 16:41:12 +04:00
|
|
|
|
surface = box.document.get_image_surface_from_uri(bg_image)
|
2011-08-26 00:16:04 +04:00
|
|
|
|
if surface is None:
|
2011-08-10 18:52:34 +04:00
|
|
|
|
return
|
|
|
|
|
|
2011-08-10 21:33:16 +04:00
|
|
|
|
image_width = surface.get_width()
|
|
|
|
|
image_height = surface.get_height()
|
|
|
|
|
|
2011-10-08 16:41:12 +04:00
|
|
|
|
bg_position = box.style.background_position
|
2011-08-11 13:15:41 +04:00
|
|
|
|
bg_position_x, bg_position_y = absolute_background_position(
|
|
|
|
|
bg_position, (bg_width, bg_height), (image_width, image_height))
|
|
|
|
|
context.translate(bg_position_x, bg_position_y)
|
2011-08-10 21:33:16 +04:00
|
|
|
|
|
2011-10-08 16:41:12 +04:00
|
|
|
|
bg_repeat = box.style.background_repeat
|
2011-08-10 18:52:34 +04:00
|
|
|
|
if bg_repeat != 'repeat':
|
|
|
|
|
# Get the current clip rectangle
|
|
|
|
|
clip_x1, clip_y1, clip_x2, clip_y2 = context.clip_extents()
|
|
|
|
|
clip_width = clip_x2 - clip_x1
|
|
|
|
|
clip_height = clip_y2 - clip_y1
|
|
|
|
|
|
|
|
|
|
if bg_repeat in ('no-repeat', 'repeat-x'):
|
|
|
|
|
# Limit the drawn area vertically
|
2011-08-11 17:55:01 +04:00
|
|
|
|
clip_y1 = 0 # because of the last context.translate()
|
2011-08-10 21:33:16 +04:00
|
|
|
|
clip_height = image_height
|
2011-08-10 18:52:34 +04:00
|
|
|
|
|
|
|
|
|
if bg_repeat in ('no-repeat', 'repeat-y'):
|
|
|
|
|
# Limit the drawn area horizontally
|
2011-08-11 17:55:01 +04:00
|
|
|
|
clip_x1 = 0 # because of the last context.translate()
|
2011-08-10 21:33:16 +04:00
|
|
|
|
clip_width = image_width
|
2011-08-10 18:52:34 +04:00
|
|
|
|
|
|
|
|
|
# Second clip for the background image
|
|
|
|
|
context.rectangle(clip_x1, clip_y1, clip_width, clip_height)
|
|
|
|
|
context.clip()
|
|
|
|
|
|
2011-10-11 13:26:20 +04:00
|
|
|
|
context.set_source_surface(surface)
|
|
|
|
|
context.get_source().set_extend(cairo.EXTEND_REPEAT)
|
2011-08-10 18:52:34 +04:00
|
|
|
|
context.paint()
|
2011-07-12 19:53:15 +04:00
|
|
|
|
|
2011-08-05 13:31:16 +04:00
|
|
|
|
|
2011-08-11 13:15:41 +04:00
|
|
|
|
def absolute_background_position(css_values, bg_dimensions, image_dimensions):
|
2011-08-22 19:36:07 +04:00
|
|
|
|
"""Return the background's ``position_x, position_y`` in pixels.
|
2011-08-10 21:33:16 +04:00
|
|
|
|
|
|
|
|
|
http://www.w3.org/TR/CSS21/colors.html#propdef-background-position
|
2011-08-11 13:15:41 +04:00
|
|
|
|
|
|
|
|
|
:param css_values: a list of one or two cssutils Value objects.
|
2011-08-22 19:36:07 +04:00
|
|
|
|
:param bg_dimensions: ``width, height`` of the background positionning area
|
|
|
|
|
:param image_dimensions: ``width, height`` of the background image
|
|
|
|
|
|
2011-08-10 21:33:16 +04:00
|
|
|
|
"""
|
2011-08-11 13:15:41 +04:00
|
|
|
|
values = list(css_values)
|
|
|
|
|
|
2011-08-10 21:33:16 +04:00
|
|
|
|
if len(css_values) == 1:
|
2011-10-08 16:41:12 +04:00
|
|
|
|
values.append('center')
|
2011-08-10 21:33:16 +04:00
|
|
|
|
else:
|
|
|
|
|
assert len(css_values) == 2
|
|
|
|
|
|
2011-10-08 16:41:12 +04:00
|
|
|
|
if values[1] in ('left', 'right') or values[0] in ('top', 'bottom'):
|
2011-08-11 13:15:41 +04:00
|
|
|
|
values.reverse()
|
|
|
|
|
# Order is now [horizontal, vertical]
|
|
|
|
|
|
|
|
|
|
kw_to_percentage = dict(top=0, left=0, center=50, bottom=100, right=100)
|
|
|
|
|
|
2011-10-08 16:41:12 +04:00
|
|
|
|
for value, bg_dimension, image_dimension in zip(
|
|
|
|
|
values, bg_dimensions, image_dimensions):
|
|
|
|
|
percentage = kw_to_percentage.get(value, get_percentage_value(value))
|
2011-08-11 13:15:41 +04:00
|
|
|
|
if percentage is not None:
|
|
|
|
|
yield (bg_dimension - image_dimension) * percentage / 100.
|
|
|
|
|
else:
|
2011-10-08 16:41:12 +04:00
|
|
|
|
yield value
|
2011-08-10 21:33:16 +04:00
|
|
|
|
|
|
|
|
|
|
2011-10-14 20:57:17 +04:00
|
|
|
|
def get_rectangle_edges(x, y, width, height):
|
|
|
|
|
"""Return the 4 edges of a rectangle as a list.
|
|
|
|
|
|
|
|
|
|
Edges are in clock-wise order, starting from the top.
|
|
|
|
|
|
|
|
|
|
Each edge is returned as ``(start_point, end_point)`` and each point
|
|
|
|
|
as ``(x, y)`` coordinates.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
# In clock-wise order, starting on top left
|
|
|
|
|
corners = [
|
|
|
|
|
(x, y),
|
|
|
|
|
(x + width, y),
|
|
|
|
|
(x + width, y + height),
|
|
|
|
|
(x, y + height)]
|
|
|
|
|
# clock-wise order, starting on top right
|
|
|
|
|
shifted_corners = corners[1:] + corners[:1]
|
|
|
|
|
return zip(corners, shifted_corners)
|
|
|
|
|
|
|
|
|
|
|
2011-08-05 13:31:16 +04:00
|
|
|
|
def draw_border(context, box):
|
2011-08-22 19:36:07 +04:00
|
|
|
|
"""Draw the box border to a ``cairo.Context``."""
|
|
|
|
|
if all(getattr(box, 'border_%s_width' % side) == 0
|
|
|
|
|
for side in ['top', 'right', 'bottom', 'left']):
|
2011-08-21 02:14:49 +04:00
|
|
|
|
# No border, return early.
|
|
|
|
|
return
|
2011-08-05 13:31:16 +04:00
|
|
|
|
|
2011-10-14 20:57:17 +04:00
|
|
|
|
for side, x_offset, y_offset, border_edge, padding_edge in zip(
|
|
|
|
|
['top', 'right', 'bottom', 'left'],
|
|
|
|
|
[0, -1, 0, 1],
|
|
|
|
|
[1, 0, -1, 0],
|
|
|
|
|
get_rectangle_edges(
|
|
|
|
|
box.border_box_x(), box.border_box_y(),
|
|
|
|
|
box.border_width(), box.border_height(),
|
|
|
|
|
),
|
|
|
|
|
get_rectangle_edges(
|
|
|
|
|
box.padding_box_x(), box.padding_box_y(),
|
|
|
|
|
box.padding_width(), box.padding_height(),
|
|
|
|
|
),
|
|
|
|
|
):
|
2011-08-22 19:36:07 +04:00
|
|
|
|
width = getattr(box, 'border_%s_width' % side)
|
2011-08-21 02:14:49 +04:00
|
|
|
|
if width == 0:
|
2011-10-14 20:57:17 +04:00
|
|
|
|
continue
|
2011-10-08 17:46:41 +04:00
|
|
|
|
color = box.style['border_%s_color' % side]
|
2011-10-14 20:57:17 +04:00
|
|
|
|
if color.alpha == 0:
|
|
|
|
|
continue
|
2011-10-08 17:46:41 +04:00
|
|
|
|
style = box.style['border_%s_style' % side]
|
2011-10-14 20:57:17 +04:00
|
|
|
|
with context.stacked():
|
|
|
|
|
"""
|
|
|
|
|
Both edges form a trapezoid. This is the top one:
|
|
|
|
|
|
|
|
|
|
+---------------+
|
|
|
|
|
\ /
|
|
|
|
|
=================
|
|
|
|
|
\ /
|
|
|
|
|
+-------+
|
|
|
|
|
|
|
|
|
|
We clip on its outline on draw on the big line on the middle.
|
|
|
|
|
"""
|
|
|
|
|
# TODO: implement other styles.
|
|
|
|
|
if not style in ['dotted', 'dashed']:
|
|
|
|
|
border_start, border_stop = border_edge
|
|
|
|
|
padding_start, padding_stop = padding_edge
|
|
|
|
|
# Move to one of the Trapezoid’s corner
|
|
|
|
|
context.move_to(*border_start)
|
|
|
|
|
for point in [border_stop, padding_stop,
|
|
|
|
|
padding_start, border_start]:
|
|
|
|
|
context.line_to(*point)
|
|
|
|
|
context.clip()
|
|
|
|
|
elif style == 'dotted':
|
|
|
|
|
# TODO: find a way to make a real dotted border
|
|
|
|
|
context.set_dash([width], 0)
|
|
|
|
|
elif style == 'dashed':
|
|
|
|
|
# TODO: find a way to make a real dashed border
|
|
|
|
|
context.set_dash([4 * width], 0)
|
|
|
|
|
(x1, y1), (x2, y2) = border_edge
|
|
|
|
|
offset = width / 2
|
|
|
|
|
x_offset *= offset
|
|
|
|
|
y_offset *= offset
|
|
|
|
|
x1 += x_offset
|
|
|
|
|
x2 += x_offset
|
|
|
|
|
y1 += y_offset
|
|
|
|
|
y2 += y_offset
|
|
|
|
|
context.move_to(x1, y1)
|
|
|
|
|
context.line_to(x2, y2)
|
|
|
|
|
context.set_source_colorvalue(color)
|
|
|
|
|
context.set_line_width(width)
|
|
|
|
|
context.stroke()
|
2011-08-05 13:31:16 +04:00
|
|
|
|
|
|
|
|
|
|
2011-08-09 12:15:53 +04:00
|
|
|
|
def draw_replacedbox(context, box):
|
2011-08-22 19:36:07 +04:00
|
|
|
|
"""Draw the given :class:`boxes.ReplacedBox` to a ``cairo.context``."""
|
2011-08-09 18:08:58 +04:00
|
|
|
|
x, y = box.padding_box_x(), box.padding_box_y()
|
2011-08-09 12:15:53 +04:00
|
|
|
|
width, height = box.width, box.height
|
2011-08-10 14:48:16 +04:00
|
|
|
|
with context.stacked():
|
|
|
|
|
context.translate(x, y)
|
|
|
|
|
context.rectangle(0, 0, width, height)
|
|
|
|
|
context.clip()
|
2011-08-22 19:36:07 +04:00
|
|
|
|
scale_width = width / box.replacement.intrinsic_width()
|
|
|
|
|
scale_height = height / box.replacement.intrinsic_height()
|
2011-08-10 14:48:16 +04:00
|
|
|
|
context.scale(scale_width, scale_height)
|
|
|
|
|
box.replacement.draw(context)
|
2011-08-24 16:58:25 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def draw_text(context, textbox):
|
2011-09-02 03:10:55 +04:00
|
|
|
|
"""Draw ``textbox`` to a ``cairo.Context`` from ``PangoCairo.Context``."""
|
2011-09-02 19:42:00 +04:00
|
|
|
|
# Pango crashes with font-size: 0
|
2011-10-11 18:04:00 +04:00
|
|
|
|
assert textbox.style.font_size
|
2011-09-02 19:42:00 +04:00
|
|
|
|
|
2011-10-08 22:57:59 +04:00
|
|
|
|
context.move_to(textbox.position_x, textbox.position_y + textbox.baseline)
|
2011-10-13 20:23:49 +04:00
|
|
|
|
context.set_source_colorvalue(textbox.style.color)
|
2011-10-08 22:38:33 +04:00
|
|
|
|
textbox.show_line(context)
|
2011-08-24 16:58:25 +04:00
|
|
|
|
values = textbox.style.text_decoration
|
|
|
|
|
for value in values:
|
2011-10-08 16:41:12 +04:00
|
|
|
|
if value == 'overline':
|
2011-08-24 16:58:25 +04:00
|
|
|
|
draw_overline(context, textbox)
|
2011-10-08 16:41:12 +04:00
|
|
|
|
elif value == 'underline':
|
2011-08-24 16:58:25 +04:00
|
|
|
|
draw_underline(context, textbox)
|
2011-10-08 16:41:12 +04:00
|
|
|
|
elif value == 'line-through':
|
2011-08-24 16:58:25 +04:00
|
|
|
|
draw_line_through(context, textbox)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def draw_overline(context, textbox):
|
|
|
|
|
"""Draw overline of ``textbox`` to a ``cairo.Context``."""
|
2011-10-08 16:41:12 +04:00
|
|
|
|
font_size = textbox.style.font_size
|
2011-08-24 16:58:25 +04:00
|
|
|
|
position_y = textbox.baseline + textbox.position_y - (font_size * 0.15)
|
|
|
|
|
draw_text_decoration(context, position_y, textbox)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def draw_underline(context, textbox):
|
|
|
|
|
"""Draw underline of ``textbox`` to a ``cairo.Context``."""
|
2011-10-08 16:41:12 +04:00
|
|
|
|
font_size = textbox.style.font_size
|
2011-08-24 16:58:25 +04:00
|
|
|
|
position_y = textbox.baseline + textbox.position_y + (font_size * 0.15)
|
|
|
|
|
draw_text_decoration(context, position_y, textbox)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def draw_line_through(context, textbox):
|
|
|
|
|
"""Draw line-through of ``textbox`` to a ``cairo.Context``."""
|
|
|
|
|
position_y = textbox.position_y + (textbox.height * 0.5)
|
|
|
|
|
draw_text_decoration(context, position_y, textbox)
|
|
|
|
|
|
|
|
|
|
def draw_text_decoration(context, position_y, textbox):
|
|
|
|
|
"""Draw text-decoration of ``textbox`` to a ``cairo.Context``."""
|
|
|
|
|
with context.stacked():
|
2011-10-11 18:04:00 +04:00
|
|
|
|
context.set_source_colorvalue(textbox.style.color)
|
|
|
|
|
context.set_line_width(1) # TODO: make this proportional to font_size?
|
|
|
|
|
context.move_to(textbox.position_x, position_y)
|
|
|
|
|
context.rel_line_to(textbox.width, 0)
|
2011-08-24 16:58:25 +04:00
|
|
|
|
context.stroke()
|