2011-07-12 19:53:15 +04:00
|
|
|
|
# coding: utf8
|
2012-03-22 02:19:27 +04:00
|
|
|
|
"""
|
|
|
|
|
weasyprint.draw
|
|
|
|
|
---------------
|
2011-07-12 19:53:15 +04:00
|
|
|
|
|
2012-09-12 17:41:07 +04:00
|
|
|
|
Take an "after layout" box tree and draw it onto a cairo context.
|
2011-07-12 19:53:15 +04:00
|
|
|
|
|
2013-04-03 18:23:48 +04:00
|
|
|
|
:copyright: Copyright 2011-2013 Simon Sapin and contributors, see AUTHORS.
|
2012-03-22 02:19:27 +04:00
|
|
|
|
:license: BSD, see LICENSE for details.
|
2011-08-22 19:36:07 +04:00
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
2012-02-17 21:49:58 +04:00
|
|
|
|
from __future__ import division, unicode_literals
|
|
|
|
|
|
2011-10-14 21:07:13 +04:00
|
|
|
|
import contextlib
|
2012-02-08 18:44:03 +04:00
|
|
|
|
import math
|
2012-07-11 20:21:20 +04:00
|
|
|
|
import operator
|
2011-08-09 12:15:53 +04:00
|
|
|
|
|
2012-12-29 04:00:30 +04:00
|
|
|
|
import cairocffi as cairo
|
2011-07-12 19:53:15 +04:00
|
|
|
|
|
2011-10-14 21:07:13 +04:00
|
|
|
|
from .formatting_structure import boxes
|
2012-09-13 13:19:40 +04:00
|
|
|
|
from .layout.backgrounds import box_rectangle
|
2012-05-11 16:10:11 +04:00
|
|
|
|
from .stacking import StackingContext
|
2012-06-21 17:10:17 +04:00
|
|
|
|
from .text import show_first_line
|
2012-07-11 20:21:20 +04:00
|
|
|
|
from .compat import xrange
|
2011-08-10 14:40:28 +04:00
|
|
|
|
|
2012-01-31 14:45:24 +04:00
|
|
|
|
|
2012-09-12 17:41:07 +04:00
|
|
|
|
@contextlib.contextmanager
|
|
|
|
|
def stacked(context):
|
|
|
|
|
"""Save and restore the context when used with the ``with`` keyword."""
|
|
|
|
|
context.save()
|
|
|
|
|
try:
|
|
|
|
|
yield
|
|
|
|
|
finally:
|
|
|
|
|
context.restore()
|
2011-10-14 21:07:13 +04:00
|
|
|
|
|
|
|
|
|
|
2012-03-24 16:39:31 +04:00
|
|
|
|
def lighten(color, offset):
|
2012-03-25 01:10:38 +04:00
|
|
|
|
"""Return a lighter color (or darker, for negative offsets)."""
|
2012-03-24 16:39:31 +04:00
|
|
|
|
return (
|
|
|
|
|
color.red + offset,
|
|
|
|
|
color.green + offset,
|
|
|
|
|
color.blue + offset,
|
|
|
|
|
color.alpha)
|
|
|
|
|
|
|
|
|
|
|
2012-09-12 20:36:00 +04:00
|
|
|
|
def draw_page(page, context, enable_hinting):
|
2012-09-12 17:41:07 +04:00
|
|
|
|
"""Draw the given PageBox."""
|
2012-05-11 16:10:11 +04:00
|
|
|
|
stacking_context = StackingContext.from_page(page)
|
2012-09-13 13:19:40 +04:00
|
|
|
|
draw_background(context, stacking_context.box.background, enable_hinting)
|
|
|
|
|
draw_background(context, page.canvas_background, enable_hinting)
|
2012-09-12 19:14:51 +04:00
|
|
|
|
draw_border(context, page, enable_hinting)
|
2012-09-12 20:36:00 +04:00
|
|
|
|
draw_stacking_context(context, stacking_context, enable_hinting)
|
2012-05-11 16:10:11 +04:00
|
|
|
|
|
|
|
|
|
|
2012-09-12 20:36:00 +04:00
|
|
|
|
def draw_box_background_and_border(context, page, box, enable_hinting):
|
2012-09-13 13:19:40 +04:00
|
|
|
|
draw_background(context, box.background, enable_hinting)
|
2012-07-11 20:21:20 +04:00
|
|
|
|
if not isinstance(box, boxes.TableBox):
|
2012-09-12 19:14:51 +04:00
|
|
|
|
draw_border(context, box, enable_hinting)
|
2012-07-11 20:21:20 +04:00
|
|
|
|
else:
|
2012-05-11 16:10:11 +04:00
|
|
|
|
for column_group in box.column_groups:
|
2012-09-13 13:19:40 +04:00
|
|
|
|
draw_background(context, column_group.background, enable_hinting)
|
2012-05-11 16:10:11 +04:00
|
|
|
|
for column in column_group.children:
|
2012-09-13 13:19:40 +04:00
|
|
|
|
draw_background(context, column.background, enable_hinting)
|
2012-05-11 16:10:11 +04:00
|
|
|
|
for row_group in box.children:
|
2012-09-13 13:19:40 +04:00
|
|
|
|
draw_background(context, row_group.background, enable_hinting)
|
2012-05-11 16:10:11 +04:00
|
|
|
|
for row in row_group.children:
|
2012-09-13 13:19:40 +04:00
|
|
|
|
draw_background(context, row.background, enable_hinting)
|
2012-05-11 16:10:11 +04:00
|
|
|
|
for cell in row.children:
|
2012-09-13 13:19:40 +04:00
|
|
|
|
draw_background(context, cell.background, enable_hinting)
|
2012-07-11 20:21:20 +04:00
|
|
|
|
if box.style.border_collapse == 'separate':
|
2012-09-12 19:14:51 +04:00
|
|
|
|
draw_border(context, box, enable_hinting)
|
2012-07-11 20:21:20 +04:00
|
|
|
|
for row_group in box.children:
|
|
|
|
|
for row in row_group.children:
|
|
|
|
|
for cell in row.children:
|
2012-09-12 19:14:51 +04:00
|
|
|
|
draw_border(context, cell, enable_hinting)
|
2012-07-11 20:21:20 +04:00
|
|
|
|
else:
|
2012-09-12 19:14:51 +04:00
|
|
|
|
draw_collapsed_borders(context, box, enable_hinting)
|
2012-05-11 16:10:11 +04:00
|
|
|
|
|
|
|
|
|
|
2012-09-12 20:36:00 +04:00
|
|
|
|
def draw_stacking_context(context, stacking_context, enable_hinting):
|
2012-05-11 16:10:11 +04:00
|
|
|
|
"""Draw a ``stacking_context`` on ``context``."""
|
|
|
|
|
# See http://www.w3.org/TR/CSS2/zindex.html
|
2012-09-12 17:41:07 +04:00
|
|
|
|
with stacked(context):
|
2012-08-03 19:19:04 +04:00
|
|
|
|
box = stacking_context.box
|
|
|
|
|
if box.is_absolutely_positioned() and box.style.clip:
|
|
|
|
|
top, right, bottom, left = box.style.clip
|
2012-05-11 16:10:11 +04:00
|
|
|
|
if top == 'auto':
|
|
|
|
|
top = 0
|
|
|
|
|
if right == 'auto':
|
|
|
|
|
right = 0
|
|
|
|
|
if bottom == 'auto':
|
2012-08-03 19:19:04 +04:00
|
|
|
|
bottom = box.border_height()
|
2012-05-11 16:10:11 +04:00
|
|
|
|
if left == 'auto':
|
2012-08-03 19:19:04 +04:00
|
|
|
|
left = box.border_width()
|
2012-05-11 16:10:11 +04:00
|
|
|
|
context.rectangle(
|
2012-08-03 19:19:04 +04:00
|
|
|
|
box.border_box_x() + right,
|
|
|
|
|
box.border_box_y() + top,
|
2012-05-11 16:10:11 +04:00
|
|
|
|
left - right,
|
|
|
|
|
bottom - top)
|
|
|
|
|
context.clip()
|
2011-08-20 17:07:14 +04:00
|
|
|
|
|
2012-08-03 19:19:04 +04:00
|
|
|
|
if box.style.opacity < 1:
|
2012-02-07 21:26:23 +04:00
|
|
|
|
context.push_group()
|
|
|
|
|
|
2012-10-06 13:26:55 +04:00
|
|
|
|
if box.transformation_matrix:
|
|
|
|
|
context.transform(box.transformation_matrix)
|
2011-08-09 18:08:58 +04:00
|
|
|
|
|
2012-05-11 16:10:11 +04:00
|
|
|
|
# Point 1 is done in draw_page
|
2012-01-27 19:53:16 +04:00
|
|
|
|
|
2012-05-11 16:10:11 +04:00
|
|
|
|
# Point 2
|
2012-08-03 19:19:04 +04:00
|
|
|
|
if isinstance(box, (boxes.BlockBox, boxes.MarginBox,
|
|
|
|
|
boxes.InlineBlockBox)):
|
2012-05-11 16:10:11 +04:00
|
|
|
|
# The canvas background was removed by set_canvas_background
|
2012-09-12 19:14:51 +04:00
|
|
|
|
draw_box_background_and_border(
|
2012-09-12 20:36:00 +04:00
|
|
|
|
context, stacking_context.page, box, enable_hinting)
|
2011-11-29 15:43:42 +04:00
|
|
|
|
|
2013-06-05 20:45:16 +04:00
|
|
|
|
if box.style.overflow != 'visible':
|
|
|
|
|
# Only clip the content and the children:
|
|
|
|
|
# - the background is already clipped
|
|
|
|
|
# - the border must *not* be clipped
|
|
|
|
|
context.rectangle(*box_rectangle(box, 'padding-box'))
|
|
|
|
|
context.clip()
|
|
|
|
|
|
2012-05-11 16:10:11 +04:00
|
|
|
|
# Point 3
|
|
|
|
|
for child_context in stacking_context.negative_z_contexts:
|
2012-09-12 20:36:00 +04:00
|
|
|
|
draw_stacking_context(context, child_context, enable_hinting)
|
2012-02-07 21:06:59 +04:00
|
|
|
|
|
2012-05-11 16:10:11 +04:00
|
|
|
|
# Point 4
|
2012-08-03 19:19:04 +04:00
|
|
|
|
for block in stacking_context.block_level_boxes:
|
2012-05-11 16:10:11 +04:00
|
|
|
|
draw_box_background_and_border(
|
2012-09-12 20:36:00 +04:00
|
|
|
|
context, stacking_context.page, block, enable_hinting)
|
2012-01-27 19:53:16 +04:00
|
|
|
|
|
2012-05-11 16:10:11 +04:00
|
|
|
|
# Point 5
|
|
|
|
|
for child_context in stacking_context.float_contexts:
|
2012-09-12 20:36:00 +04:00
|
|
|
|
draw_stacking_context(context, child_context, enable_hinting)
|
2012-05-11 16:10:11 +04:00
|
|
|
|
|
|
|
|
|
# Point 6
|
2012-08-03 19:19:04 +04:00
|
|
|
|
if isinstance(box, boxes.InlineBox):
|
2012-09-12 19:14:51 +04:00
|
|
|
|
draw_inline_level(
|
2012-09-12 20:36:00 +04:00
|
|
|
|
context, stacking_context.page, box, enable_hinting)
|
2012-05-11 16:10:11 +04:00
|
|
|
|
|
|
|
|
|
# Point 7
|
2012-08-03 19:19:04 +04:00
|
|
|
|
for block in [box] + stacking_context.blocks_and_cells:
|
|
|
|
|
marker_box = getattr(block, 'outside_list_marker', None)
|
2012-05-11 16:10:11 +04:00
|
|
|
|
if marker_box:
|
|
|
|
|
draw_inline_level(
|
2012-09-12 20:36:00 +04:00
|
|
|
|
context, stacking_context.page, marker_box, enable_hinting)
|
2012-05-11 16:10:11 +04:00
|
|
|
|
|
2012-08-03 19:19:04 +04:00
|
|
|
|
if isinstance(block, boxes.ReplacedBox):
|
|
|
|
|
draw_replacedbox(context, block)
|
2012-02-08 13:43:04 +04:00
|
|
|
|
else:
|
2012-08-03 19:19:04 +04:00
|
|
|
|
for child in block.children:
|
2012-05-11 16:10:11 +04:00
|
|
|
|
if isinstance(child, boxes.LineBox):
|
|
|
|
|
# TODO: draw inline tables
|
|
|
|
|
draw_inline_level(
|
2012-09-12 19:14:51 +04:00
|
|
|
|
context, stacking_context.page, child,
|
2012-09-12 20:36:00 +04:00
|
|
|
|
enable_hinting)
|
2012-02-08 13:43:04 +04:00
|
|
|
|
|
2012-05-11 16:10:11 +04:00
|
|
|
|
# Point 8
|
|
|
|
|
for child_context in stacking_context.zero_z_contexts:
|
2012-09-12 20:36:00 +04:00
|
|
|
|
draw_stacking_context(context, child_context, enable_hinting)
|
2012-02-07 21:06:59 +04:00
|
|
|
|
|
2012-05-11 16:10:11 +04:00
|
|
|
|
# Point 9
|
|
|
|
|
for child_context in stacking_context.positive_z_contexts:
|
2012-09-12 20:36:00 +04:00
|
|
|
|
draw_stacking_context(context, child_context, enable_hinting)
|
2012-02-07 21:06:59 +04:00
|
|
|
|
|
2012-08-03 18:21:47 +04:00
|
|
|
|
# Point 10
|
2012-09-12 19:14:51 +04:00
|
|
|
|
draw_outlines(context, box, enable_hinting)
|
2012-08-03 18:21:47 +04:00
|
|
|
|
|
2012-08-03 19:19:04 +04:00
|
|
|
|
if box.style.opacity < 1:
|
2012-02-07 21:26:23 +04:00
|
|
|
|
context.pop_group_to_source()
|
2012-08-03 19:19:04 +04:00
|
|
|
|
context.paint_with_alpha(box.style.opacity)
|
2012-05-11 16:10:11 +04:00
|
|
|
|
|
|
|
|
|
|
2012-09-13 13:19:40 +04:00
|
|
|
|
def draw_background(context, bg, enable_hinting):
|
2012-01-27 19:53:16 +04:00
|
|
|
|
"""Draw the background color and image to a ``cairo.Context``."""
|
2012-09-13 13:19:40 +04:00
|
|
|
|
if bg is None:
|
2011-08-10 14:40:28 +04:00
|
|
|
|
return
|
2011-08-10 12:01:56 +04:00
|
|
|
|
|
2013-03-26 17:07:44 +04:00
|
|
|
|
# Background color
|
|
|
|
|
if bg.color.alpha > 0:
|
|
|
|
|
with stacked(context):
|
|
|
|
|
if enable_hinting:
|
|
|
|
|
# Prefer crisp edges on background rectangles.
|
|
|
|
|
context.set_antialias(cairo.ANTIALIAS_NONE)
|
|
|
|
|
|
|
|
|
|
painting_area = bg.layers[-1].painting_area
|
|
|
|
|
if painting_area:
|
|
|
|
|
context.rectangle(*painting_area)
|
|
|
|
|
context.clip()
|
|
|
|
|
#else: unrestricted, whole page box
|
2011-08-10 14:40:28 +04:00
|
|
|
|
|
2012-09-13 13:19:40 +04:00
|
|
|
|
context.set_source_rgba(*bg.color)
|
2011-08-08 23:20:43 +04:00
|
|
|
|
context.paint()
|
2011-08-10 14:40:28 +04:00
|
|
|
|
|
2013-03-26 17:07:44 +04:00
|
|
|
|
# Paint in reversed order: first layer is "closest" to the viewer.
|
|
|
|
|
for layer in reversed(bg.layers):
|
2013-04-03 19:26:46 +04:00
|
|
|
|
draw_background_image(context, layer, bg.image_rendering)
|
2011-08-10 18:52:34 +04:00
|
|
|
|
|
2013-03-26 16:56:11 +04:00
|
|
|
|
|
2013-04-03 19:26:46 +04:00
|
|
|
|
def draw_background_image(context, layer, image_rendering):
|
2013-03-19 21:29:58 +04:00
|
|
|
|
# Background image
|
|
|
|
|
if layer.image is None:
|
|
|
|
|
return
|
2013-03-26 16:56:11 +04:00
|
|
|
|
|
2013-03-31 02:32:14 +04:00
|
|
|
|
painting_x, painting_y, painting_width, painting_height = (
|
|
|
|
|
layer.painting_area)
|
2013-04-03 19:46:14 +04:00
|
|
|
|
positioning_x, positioning_y, positioning_width, positioning_height = (
|
|
|
|
|
layer.positioning_area)
|
|
|
|
|
position_x, position_y = layer.position
|
2013-03-31 01:49:47 +04:00
|
|
|
|
repeat_x, repeat_y = layer.repeat
|
2013-04-03 19:46:14 +04:00
|
|
|
|
image_width, image_height = layer.size
|
2013-03-31 02:32:14 +04:00
|
|
|
|
|
2013-03-31 01:49:47 +04:00
|
|
|
|
if repeat_x == 'no-repeat':
|
|
|
|
|
repeat_width = painting_width * 2
|
2013-03-31 02:32:14 +04:00
|
|
|
|
elif repeat_x in ('repeat', 'round'):
|
2013-03-31 01:49:47 +04:00
|
|
|
|
repeat_width = image_width
|
|
|
|
|
else:
|
|
|
|
|
assert repeat_x == 'space'
|
|
|
|
|
n_repeats = math.floor(positioning_width / image_width)
|
|
|
|
|
if n_repeats >= 2:
|
|
|
|
|
repeat_width = (positioning_width - image_width) / (n_repeats - 1)
|
|
|
|
|
position_x = 0 # Ignore background-position for this dimension
|
|
|
|
|
else:
|
|
|
|
|
repeat_width = image_width
|
|
|
|
|
|
|
|
|
|
if repeat_y == 'no-repeat':
|
|
|
|
|
repeat_height = painting_height * 2
|
2013-03-31 02:32:14 +04:00
|
|
|
|
elif repeat_y in ('repeat', 'round'):
|
2013-03-31 01:49:47 +04:00
|
|
|
|
repeat_height = image_height
|
|
|
|
|
else:
|
|
|
|
|
assert repeat_y == 'space'
|
|
|
|
|
n_repeats = math.floor(positioning_height / image_height)
|
|
|
|
|
if n_repeats >= 2:
|
|
|
|
|
repeat_height = (
|
|
|
|
|
positioning_height - image_height) / (n_repeats - 1)
|
2013-03-31 02:32:14 +04:00
|
|
|
|
position_y = 0 # Ignore background-position for this dimension
|
2013-03-31 01:49:47 +04:00
|
|
|
|
else:
|
|
|
|
|
repeat_height = image_height
|
|
|
|
|
|
|
|
|
|
sub_surface = cairo.PDFSurface(None, repeat_width, repeat_height)
|
|
|
|
|
sub_context = cairo.Context(sub_surface)
|
|
|
|
|
sub_context.rectangle(0, 0, image_width, image_height)
|
2013-04-03 18:00:31 +04:00
|
|
|
|
sub_context.clip()
|
2013-04-03 19:26:46 +04:00
|
|
|
|
layer.image.draw(sub_context, image_width, image_height, image_rendering)
|
2013-03-31 01:49:47 +04:00
|
|
|
|
pattern = cairo.SurfacePattern(sub_surface)
|
|
|
|
|
pattern.set_extend(cairo.EXTEND_REPEAT)
|
|
|
|
|
|
2013-03-19 21:29:58 +04:00
|
|
|
|
with stacked(context):
|
2013-03-31 01:49:47 +04:00
|
|
|
|
if not layer.unbounded:
|
|
|
|
|
context.rectangle(painting_x, painting_y,
|
|
|
|
|
painting_width, painting_height)
|
2013-03-26 17:07:44 +04:00
|
|
|
|
context.clip()
|
|
|
|
|
#else: unrestricted, whole page box
|
|
|
|
|
|
2013-03-26 16:56:11 +04:00
|
|
|
|
context.translate(positioning_x + position_x,
|
|
|
|
|
positioning_y + position_y)
|
2012-01-13 21:16:27 +04:00
|
|
|
|
context.set_source(pattern)
|
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-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-12-13 19:18:28 +04:00
|
|
|
|
def xy_offset(x, y, offset_x, offset_y, offset):
|
|
|
|
|
"""Increment X and Y coordinates by the given offsets."""
|
|
|
|
|
return x + offset_x * offset, y + offset_y * offset
|
|
|
|
|
|
|
|
|
|
|
2012-09-12 19:14:51 +04:00
|
|
|
|
def draw_border(context, box, enable_hinting):
|
2011-08-22 19:36:07 +04:00
|
|
|
|
"""Draw the box border to a ``cairo.Context``."""
|
2012-05-11 16:10:11 +04:00
|
|
|
|
if box.style.visibility == 'hidden':
|
|
|
|
|
return
|
2011-12-30 20:19:02 +04:00
|
|
|
|
if all(getattr(box, 'border_%s_width' % side) == 0
|
2011-08-22 19:36:07 +04:00
|
|
|
|
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
|
|
|
|
|
2012-07-11 20:21:20 +04:00
|
|
|
|
for side, border_edge, padding_edge in zip(
|
2011-10-14 20:57:17 +04:00
|
|
|
|
['top', 'right', 'bottom', 'left'],
|
|
|
|
|
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-12-30 20:19:02 +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
|
2013-01-23 19:52:47 +04:00
|
|
|
|
color = box.style.get_color('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]
|
2012-09-12 19:14:51 +04:00
|
|
|
|
draw_border_segment(context, enable_hinting, style, width, color,
|
2012-07-11 20:21:20 +04:00
|
|
|
|
side, border_edge, padding_edge)
|
2011-12-13 18:42:14 +04:00
|
|
|
|
|
|
|
|
|
|
2012-09-12 19:14:51 +04:00
|
|
|
|
def draw_border_segment(context, enable_hinting, style, width, color, side,
|
2012-07-11 20:21:20 +04:00
|
|
|
|
border_edge, padding_edge):
|
2012-09-12 17:41:07 +04:00
|
|
|
|
with stacked(context):
|
2013-04-30 13:50:32 +04:00
|
|
|
|
if enable_hinting and style != 'dotted' and (
|
2012-07-11 20:21:20 +04:00
|
|
|
|
# Borders smaller than 1 device unit would disappear
|
|
|
|
|
# without anti-aliasing.
|
|
|
|
|
math.hypot(*context.user_to_device(width, 0)) >= 1 and
|
|
|
|
|
math.hypot(*context.user_to_device(0, width)) >= 1):
|
2013-04-30 19:08:33 +04:00
|
|
|
|
# Avoid an artifact in the corner joining two solid borders
|
2012-07-11 20:21:20 +04:00
|
|
|
|
# of the same color.
|
|
|
|
|
context.set_antialias(cairo.ANTIALIAS_NONE)
|
2011-12-13 18:42:14 +04:00
|
|
|
|
|
2013-04-26 17:00:58 +04:00
|
|
|
|
if style in ('inset', 'outset'):
|
|
|
|
|
do_lighten = (side in ('top', 'left')) ^ (style == 'inset')
|
|
|
|
|
factor = 0.5 if do_lighten else -0.5
|
|
|
|
|
context.set_source_rgba(*lighten(color, factor))
|
|
|
|
|
else:
|
|
|
|
|
context.set_source_rgba(*color)
|
|
|
|
|
|
|
|
|
|
x_offset, y_offset = {'top': (0, 1), 'bottom': (0, -1),
|
|
|
|
|
'left': (1, 0), 'right': (-1, 0)}[side]
|
|
|
|
|
|
2012-07-11 20:21:20 +04:00
|
|
|
|
if style not in ('dotted', 'dashed'):
|
|
|
|
|
# Clip on the trapezoid shape
|
|
|
|
|
"""
|
2013-04-23 18:24:33 +04:00
|
|
|
|
Clip the angles the trapezoid formed by the border edge (longer)
|
2012-07-11 20:21:20 +04:00
|
|
|
|
and the padding edge (shorter).
|
|
|
|
|
|
|
|
|
|
This is on the top side:
|
|
|
|
|
|
2013-04-23 18:24:33 +04:00
|
|
|
|
+---------------+ <= border edge ^
|
|
|
|
|
\ / |
|
|
|
|
|
\ / | top border width
|
|
|
|
|
\ / |
|
|
|
|
|
+-------+ <= padding edge v
|
2012-07-11 20:21:20 +04:00
|
|
|
|
|
2013-04-23 18:24:33 +04:00
|
|
|
|
<--> <--> <= left and right border widths
|
|
|
|
|
|
|
|
|
|
The clip shape is:
|
|
|
|
|
|
|
|
|
|
1---------------------------2
|
|
|
|
|
\ /
|
|
|
|
|
\ /
|
|
|
|
|
\ /
|
|
|
|
|
+...................+
|
|
|
|
|
\ /
|
|
|
|
|
\ /
|
|
|
|
|
\ /
|
|
|
|
|
+...........+
|
|
|
|
|
\ /
|
|
|
|
|
\ /
|
|
|
|
|
\ /
|
|
|
|
|
4---3
|
2012-07-11 20:21:20 +04:00
|
|
|
|
|
|
|
|
|
"""
|
2013-04-23 18:24:33 +04:00
|
|
|
|
|
|
|
|
|
def double_vector(p1, p2):
|
|
|
|
|
x1, y1 = p1
|
|
|
|
|
x2, y2 = p2
|
2013-04-26 17:00:58 +04:00
|
|
|
|
return 2 * x2 - x1, 2 * y2 - y1
|
2013-04-23 18:24:33 +04:00
|
|
|
|
|
2012-07-11 20:21:20 +04:00
|
|
|
|
border_start, border_stop = border_edge
|
|
|
|
|
padding_start, padding_stop = padding_edge
|
2013-04-23 18:24:33 +04:00
|
|
|
|
points = [double_vector(padding_start, border_start),
|
|
|
|
|
double_vector(padding_stop, border_stop),
|
|
|
|
|
double_vector(border_stop, padding_stop),
|
|
|
|
|
double_vector(border_start, padding_start)]
|
|
|
|
|
context.move_to(*points[-1])
|
|
|
|
|
for point in points:
|
2012-07-11 20:21:20 +04:00
|
|
|
|
context.line_to(*point)
|
|
|
|
|
context.clip()
|
2011-12-13 18:42:14 +04:00
|
|
|
|
|
2013-04-26 17:00:58 +04:00
|
|
|
|
if style in ('groove', 'ridge'):
|
2012-07-11 20:21:20 +04:00
|
|
|
|
# TODO: these would look better with more color stops
|
|
|
|
|
"""
|
|
|
|
|
Divide the width in 2 and stroke lines in different colors
|
|
|
|
|
+-------------+
|
|
|
|
|
1\ /2
|
|
|
|
|
1'\ / 2'
|
|
|
|
|
+-------+
|
|
|
|
|
"""
|
|
|
|
|
do_lighten = (side in ('top', 'left')) ^ (style == 'groove')
|
|
|
|
|
factor = 1 if do_lighten else -1
|
|
|
|
|
context.set_line_width(width / 2)
|
|
|
|
|
(x1, y1), (x2, y2) = border_edge
|
|
|
|
|
# from the border edge to the center of the first line
|
|
|
|
|
x1, y1 = xy_offset(x1, y1, x_offset, y_offset, width / 4)
|
|
|
|
|
x2, y2 = xy_offset(x2, y2, x_offset, y_offset, width / 4)
|
|
|
|
|
context.move_to(x1, y1)
|
|
|
|
|
context.line_to(x2, y2)
|
|
|
|
|
context.set_source_rgba(*lighten(color, 0.5 * factor))
|
|
|
|
|
context.stroke()
|
|
|
|
|
# Between the centers of both lines. 1/4 + 1/4 = 1/2
|
|
|
|
|
x1, y1 = xy_offset(x1, y1, x_offset, y_offset, width / 2)
|
|
|
|
|
x2, y2 = xy_offset(x2, y2, x_offset, y_offset, width / 2)
|
|
|
|
|
context.move_to(x1, y1)
|
|
|
|
|
context.line_to(x2, y2)
|
|
|
|
|
context.set_source_rgba(*lighten(color, -0.5 * factor))
|
|
|
|
|
context.stroke()
|
|
|
|
|
elif style == 'double':
|
|
|
|
|
"""
|
|
|
|
|
Divide the width in 3 and stroke both outer lines
|
|
|
|
|
+---------------+
|
|
|
|
|
1\ /2
|
|
|
|
|
\ /
|
|
|
|
|
1' \ / 2'
|
|
|
|
|
+-------+
|
|
|
|
|
"""
|
|
|
|
|
context.set_line_width(width / 3)
|
|
|
|
|
(x1, y1), (x2, y2) = border_edge
|
|
|
|
|
# from the border edge to the center of the first line
|
|
|
|
|
x1, y1 = xy_offset(x1, y1, x_offset, y_offset, width / 6)
|
|
|
|
|
x2, y2 = xy_offset(x2, y2, x_offset, y_offset, width / 6)
|
|
|
|
|
context.move_to(x1, y1)
|
|
|
|
|
context.line_to(x2, y2)
|
|
|
|
|
context.stroke()
|
|
|
|
|
# Between the centers of both lines. 1/6 + 1/3 + 1/6 = 2/3
|
|
|
|
|
x1, y1 = xy_offset(x1, y1, x_offset, y_offset, 2 * width / 3)
|
|
|
|
|
x2, y2 = xy_offset(x2, y2, x_offset, y_offset, 2 * width / 3)
|
|
|
|
|
context.move_to(x1, y1)
|
|
|
|
|
context.line_to(x2, y2)
|
|
|
|
|
context.stroke()
|
|
|
|
|
else:
|
|
|
|
|
(x1, y1), (x2, y2) = border_edge
|
|
|
|
|
if style == 'dotted':
|
|
|
|
|
# Half-way from the extremities of the border and padding
|
|
|
|
|
# edges.
|
|
|
|
|
(px1, py1), (px2, py2) = padding_edge
|
|
|
|
|
x1 = (x1 + px1) / 2
|
|
|
|
|
x2 = (x2 + px2) / 2
|
|
|
|
|
y1 = (y1 + py1) / 2
|
|
|
|
|
y2 = (y2 + py2) / 2
|
2011-12-13 18:42:14 +04:00
|
|
|
|
"""
|
2012-07-11 20:21:20 +04:00
|
|
|
|
+---------------+
|
|
|
|
|
\ /
|
|
|
|
|
1 2
|
|
|
|
|
\ /
|
|
|
|
|
+-------+
|
2011-12-13 19:40:27 +04:00
|
|
|
|
"""
|
2013-04-23 18:24:33 +04:00
|
|
|
|
else: # solid, dashed
|
2012-07-11 20:21:20 +04:00
|
|
|
|
# From the border edge to the middle:
|
2011-12-13 19:40:27 +04:00
|
|
|
|
x1, y1 = xy_offset(x1, y1, x_offset, y_offset, width / 2)
|
|
|
|
|
x2, y2 = xy_offset(x2, y2, x_offset, y_offset, width / 2)
|
2011-12-13 19:18:28 +04:00
|
|
|
|
"""
|
|
|
|
|
+---------------+
|
2012-07-11 20:21:20 +04:00
|
|
|
|
\ /
|
|
|
|
|
1 \ / 2
|
|
|
|
|
\ /
|
2011-12-13 19:18:28 +04:00
|
|
|
|
+-------+
|
|
|
|
|
"""
|
2013-04-26 17:00:58 +04:00
|
|
|
|
|
2013-01-23 19:41:46 +04:00
|
|
|
|
length = ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5
|
2012-07-11 20:21:20 +04:00
|
|
|
|
dash = 2 * width
|
|
|
|
|
if style == 'dotted':
|
|
|
|
|
if context.user_to_device_distance(width, 0)[0] > 3:
|
|
|
|
|
# Round so that dash is a divisor of length,
|
|
|
|
|
# but not in the dots are too small.
|
2013-03-18 16:44:12 +04:00
|
|
|
|
dash = length / max(1, round(length / dash))
|
2012-07-11 20:21:20 +04:00
|
|
|
|
context.set_line_cap(cairo.LINE_CAP_ROUND)
|
|
|
|
|
context.set_dash([0, dash])
|
2013-04-23 18:24:33 +04:00
|
|
|
|
elif style == 'dashed':
|
2012-07-11 20:21:20 +04:00
|
|
|
|
# Round so that 2*dash is a divisor of length
|
2013-03-18 16:44:12 +04:00
|
|
|
|
dash = length / (2 * max(1, round(length / (2 * dash))))
|
2012-07-11 20:21:20 +04:00
|
|
|
|
context.set_dash([dash])
|
|
|
|
|
# Stroke along the line in === above, as wide as the border
|
|
|
|
|
context.move_to(x1, y1)
|
|
|
|
|
context.line_to(x2, y2)
|
|
|
|
|
context.set_line_width(width)
|
|
|
|
|
context.stroke()
|
|
|
|
|
|
|
|
|
|
|
2012-09-12 19:14:51 +04:00
|
|
|
|
def draw_outlines(context, box, enable_hinting):
|
2012-08-03 18:21:47 +04:00
|
|
|
|
width = box.style.outline_width
|
2013-01-23 19:52:47 +04:00
|
|
|
|
color = box.style.get_color('outline_color')
|
2012-08-03 18:21:47 +04:00
|
|
|
|
style = box.style.outline_style
|
|
|
|
|
if box.style.visibility != 'hidden' and width != 0 and color.alpha != 0:
|
|
|
|
|
border_box = (box.border_box_x(), box.border_box_y(),
|
|
|
|
|
box.border_width(), box.border_height())
|
|
|
|
|
outline_box = (border_box[0] - width, border_box[1] - width,
|
2013-01-23 19:41:46 +04:00
|
|
|
|
border_box[2] + 2 * width, border_box[3] + 2 * width)
|
2012-08-03 18:21:47 +04:00
|
|
|
|
for side, border_edge, padding_edge in zip(
|
|
|
|
|
['top', 'right', 'bottom', 'left'],
|
|
|
|
|
get_rectangle_edges(*outline_box),
|
|
|
|
|
get_rectangle_edges(*border_box),
|
|
|
|
|
):
|
2012-09-12 19:14:51 +04:00
|
|
|
|
draw_border_segment(context, enable_hinting, style, width, color,
|
2012-08-03 18:21:47 +04:00
|
|
|
|
side, border_edge, padding_edge)
|
|
|
|
|
|
|
|
|
|
if isinstance(box, boxes.ParentBox):
|
|
|
|
|
for child in box.children:
|
|
|
|
|
if isinstance(child, boxes.Box):
|
2012-09-12 19:14:51 +04:00
|
|
|
|
draw_outlines(context, child, enable_hinting)
|
2012-08-03 18:21:47 +04:00
|
|
|
|
|
|
|
|
|
|
2012-09-12 19:14:51 +04:00
|
|
|
|
def draw_collapsed_borders(context, table, enable_hinting):
|
2012-07-11 20:21:20 +04:00
|
|
|
|
row_heights = [row.height for row_group in table.children
|
2013-04-11 14:08:53 +04:00
|
|
|
|
for row in row_group.children]
|
2012-07-11 20:21:20 +04:00
|
|
|
|
column_widths = table.column_widths
|
|
|
|
|
if not (row_heights and column_widths):
|
|
|
|
|
# One of the list is empty: don’t bother with empty tables
|
|
|
|
|
return
|
|
|
|
|
row_positions = [row.position_y for row_group in table.children
|
2013-04-11 14:08:53 +04:00
|
|
|
|
for row in row_group.children]
|
2013-06-14 16:32:15 +04:00
|
|
|
|
column_positions = []
|
|
|
|
|
column_positions.extend(table.column_positions)
|
2012-07-11 20:21:20 +04:00
|
|
|
|
grid_height = len(row_heights)
|
|
|
|
|
grid_width = len(column_widths)
|
|
|
|
|
assert grid_width == len(column_positions)
|
|
|
|
|
# Add the end of the last column, but make a copy from the table attr.
|
|
|
|
|
column_positions += [column_positions[-1] + column_widths[-1]]
|
|
|
|
|
# Add the end of the last row. No copy here, we own this list
|
|
|
|
|
row_positions.append(row_positions[-1] + row_heights[-1])
|
|
|
|
|
vertical_borders, horizontal_borders = table.collapsed_border_grid
|
2013-04-30 12:56:43 +04:00
|
|
|
|
if table.children[0].is_header:
|
|
|
|
|
header_rows = len(table.children[0].children)
|
|
|
|
|
else:
|
|
|
|
|
header_rows = 0
|
|
|
|
|
if table.children[-1].is_footer:
|
|
|
|
|
footer_rows = len(table.children[-1].children)
|
|
|
|
|
else:
|
|
|
|
|
footer_rows = 0
|
2012-07-11 21:19:13 +04:00
|
|
|
|
skipped_rows = table.skipped_rows
|
2013-04-30 12:56:43 +04:00
|
|
|
|
if skipped_rows:
|
|
|
|
|
body_rows_offset = skipped_rows - header_rows
|
|
|
|
|
else:
|
|
|
|
|
body_rows_offset = 0
|
|
|
|
|
if header_rows == 0:
|
|
|
|
|
header_rows = -1
|
|
|
|
|
if footer_rows:
|
|
|
|
|
first_footer_row = grid_height - footer_rows - 1
|
|
|
|
|
else:
|
|
|
|
|
first_footer_row = grid_height + 1
|
|
|
|
|
original_grid_height = len(vertical_borders)
|
|
|
|
|
footer_rows_offset = original_grid_height - grid_height
|
|
|
|
|
|
|
|
|
|
def row_number(y, horizontal):
|
|
|
|
|
if y < (header_rows + int(horizontal)):
|
|
|
|
|
return y
|
|
|
|
|
elif y >= (first_footer_row + int(horizontal)):
|
|
|
|
|
return y + footer_rows_offset
|
|
|
|
|
else:
|
|
|
|
|
return y + body_rows_offset
|
2012-07-11 20:21:20 +04:00
|
|
|
|
|
|
|
|
|
segments = []
|
|
|
|
|
|
2012-07-11 21:33:11 +04:00
|
|
|
|
def half_max_width(border_list, yx_pairs, vertical=True):
|
|
|
|
|
result = 0
|
|
|
|
|
for y, x in yx_pairs:
|
|
|
|
|
if (
|
|
|
|
|
(0 <= y < grid_height and 0 <= x <= grid_width)
|
|
|
|
|
if vertical else
|
|
|
|
|
(0 <= y <= grid_height and 0 <= x < grid_width)
|
|
|
|
|
):
|
2013-04-30 12:56:43 +04:00
|
|
|
|
yy = row_number(y, horizontal=not vertical)
|
|
|
|
|
_, (_, width, _) = border_list[yy][x]
|
2012-07-11 21:33:11 +04:00
|
|
|
|
result = max(result, width)
|
|
|
|
|
return result / 2
|
|
|
|
|
|
2012-07-11 20:21:20 +04:00
|
|
|
|
def add_vertical(x, y):
|
2013-04-30 12:56:43 +04:00
|
|
|
|
yy = row_number(y, horizontal=False)
|
|
|
|
|
score, (style, width, color) = vertical_borders[yy][x]
|
2012-07-11 20:21:20 +04:00
|
|
|
|
if width == 0 or color.alpha == 0:
|
|
|
|
|
return
|
|
|
|
|
half_width = width / 2
|
|
|
|
|
pos_x = column_positions[x]
|
2012-07-11 21:33:11 +04:00
|
|
|
|
pos_y_1 = row_positions[y] - half_max_width(horizontal_borders, [
|
|
|
|
|
(y, x - 1), (y, x)], vertical=False)
|
|
|
|
|
pos_y_2 = row_positions[y + 1] + half_max_width(horizontal_borders, [
|
|
|
|
|
(y + 1, x - 1), (y + 1, x)], vertical=False)
|
2012-07-11 20:21:20 +04:00
|
|
|
|
edge_1 = (pos_x - half_width, pos_y_1), (pos_x - half_width, pos_y_2)
|
|
|
|
|
edge_2 = (pos_x + half_width, pos_y_1), (pos_x + half_width, pos_y_2)
|
|
|
|
|
segments.append((score, style, width, color, 'left', edge_1, edge_2))
|
|
|
|
|
|
|
|
|
|
def add_horizontal(x, y):
|
2013-04-30 12:56:43 +04:00
|
|
|
|
yy = row_number(y, horizontal=True)
|
|
|
|
|
score, (style, width, color) = horizontal_borders[yy][x]
|
2012-07-11 20:21:20 +04:00
|
|
|
|
if width == 0 or color.alpha == 0:
|
|
|
|
|
return
|
|
|
|
|
half_width = width / 2
|
|
|
|
|
pos_y = row_positions[y]
|
2012-07-11 21:33:11 +04:00
|
|
|
|
# TODO: change signs for rtl when we support rtl tables?
|
|
|
|
|
pos_x_1 = column_positions[x] - half_max_width(vertical_borders, [
|
|
|
|
|
(y - 1, x), (y, x)])
|
|
|
|
|
pos_x_2 = column_positions[x + 1] + half_max_width(vertical_borders, [
|
|
|
|
|
(y - 1, x + 1), (y, x + 1)])
|
2012-07-11 20:21:20 +04:00
|
|
|
|
edge_1 = (pos_x_1, pos_y - half_width), (pos_x_2, pos_y - half_width)
|
|
|
|
|
edge_2 = (pos_x_1, pos_y + half_width), (pos_x_2, pos_y + half_width)
|
|
|
|
|
segments.append((score, style, width, color, 'top', edge_1, edge_2))
|
|
|
|
|
|
|
|
|
|
for x in xrange(grid_width):
|
|
|
|
|
add_horizontal(x, 0)
|
|
|
|
|
for y in xrange(grid_height):
|
|
|
|
|
add_vertical(0, y)
|
|
|
|
|
for x in xrange(grid_width):
|
|
|
|
|
add_vertical(x + 1, y)
|
|
|
|
|
add_horizontal(x, y + 1)
|
|
|
|
|
|
|
|
|
|
# Sort bigger scores last (painted later, on top)
|
|
|
|
|
# Since the number of different scores is expected to be small compared
|
|
|
|
|
# to the number of segments, there should be little changes and Timsort
|
2012-07-11 21:33:11 +04:00
|
|
|
|
# should be closer to O(n) than O(n * log(n))
|
2012-07-11 20:21:20 +04:00
|
|
|
|
segments.sort(key=operator.itemgetter(0))
|
|
|
|
|
|
|
|
|
|
for segment in segments:
|
2012-09-12 19:14:51 +04:00
|
|
|
|
draw_border_segment(context, enable_hinting, *segment[1:])
|
2011-08-05 13:31:16 +04:00
|
|
|
|
|
2013-03-31 01:49:47 +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``."""
|
2013-04-03 18:00:31 +04:00
|
|
|
|
if box.style.visibility == 'hidden' or box.width == 0 or box.height == 0:
|
2012-05-11 16:10:11 +04:00
|
|
|
|
return
|
|
|
|
|
|
2013-04-03 18:00:31 +04:00
|
|
|
|
with stacked(context):
|
|
|
|
|
context.translate(box.content_box_x(), box.content_box_y())
|
|
|
|
|
context.rectangle(0, 0, box.width, box.height)
|
|
|
|
|
context.clip()
|
|
|
|
|
box.replacement.draw(
|
|
|
|
|
context, box.width, box.height, box.style.image_rendering)
|
2011-08-24 16:58:25 +04:00
|
|
|
|
|
2012-05-11 00:09:04 +04:00
|
|
|
|
|
2012-09-12 20:36:00 +04:00
|
|
|
|
def draw_inline_level(context, page, box, enable_hinting):
|
2012-06-04 16:37:00 +04:00
|
|
|
|
if isinstance(box, StackingContext):
|
|
|
|
|
stacking_context = box
|
|
|
|
|
assert isinstance(stacking_context.box, boxes.InlineBlockBox)
|
2012-09-12 20:36:00 +04:00
|
|
|
|
draw_stacking_context(context, stacking_context, enable_hinting)
|
2012-06-04 16:37:00 +04:00
|
|
|
|
else:
|
2012-09-13 13:19:40 +04:00
|
|
|
|
draw_background(context, box.background, enable_hinting)
|
2012-09-12 19:14:51 +04:00
|
|
|
|
draw_border(context, box, enable_hinting)
|
2012-06-04 20:49:13 +04:00
|
|
|
|
if isinstance(box, (boxes.InlineBox, boxes.LineBox)):
|
|
|
|
|
for child in box.children:
|
|
|
|
|
if isinstance(child, boxes.TextBox):
|
2012-09-12 19:14:51 +04:00
|
|
|
|
draw_text(context, child, enable_hinting)
|
2012-06-04 20:49:13 +04:00
|
|
|
|
else:
|
2012-09-12 20:36:00 +04:00
|
|
|
|
draw_inline_level(context, page, child, enable_hinting)
|
2012-06-04 20:49:13 +04:00
|
|
|
|
elif isinstance(box, boxes.InlineReplacedBox):
|
|
|
|
|
draw_replacedbox(context, box)
|
|
|
|
|
else:
|
|
|
|
|
assert isinstance(box, boxes.TextBox)
|
|
|
|
|
# Should only happen for list markers
|
2012-09-12 19:14:51 +04:00
|
|
|
|
draw_text(context, box, enable_hinting)
|
2012-05-11 16:10:11 +04:00
|
|
|
|
|
|
|
|
|
|
2012-09-12 19:14:51 +04:00
|
|
|
|
def draw_text(context, textbox, enable_hinting):
|
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
|
|
|
|
|
2012-05-11 16:10:11 +04:00
|
|
|
|
if textbox.style.visibility == 'hidden':
|
|
|
|
|
return
|
|
|
|
|
|
2011-10-08 22:57:59 +04:00
|
|
|
|
context.move_to(textbox.position_x, textbox.position_y + textbox.baseline)
|
2012-03-24 16:39:31 +04:00
|
|
|
|
context.set_source_rgba(*textbox.style.color)
|
2012-09-12 19:14:51 +04:00
|
|
|
|
show_first_line(context, textbox.pango_layout, enable_hinting)
|
2011-08-24 16:58:25 +04:00
|
|
|
|
values = textbox.style.text_decoration
|
2013-05-17 20:52:05 +04:00
|
|
|
|
|
|
|
|
|
metrics = textbox.pango_layout.get_font_metrics()
|
2013-06-03 20:26:08 +04:00
|
|
|
|
thickness = textbox.style.font_size / 18 # That's what other browsers do
|
2013-05-17 20:52:05 +04:00
|
|
|
|
if enable_hinting and thickness < 1:
|
|
|
|
|
thickness = 1
|
|
|
|
|
|
2012-04-04 15:57:20 +04:00
|
|
|
|
if 'overline' in values:
|
2013-04-11 14:08:53 +04:00
|
|
|
|
draw_text_decoration(
|
|
|
|
|
context, textbox,
|
2013-05-17 20:52:05 +04:00
|
|
|
|
textbox.baseline - metrics.ascent + thickness / 2,
|
2013-06-03 20:26:08 +04:00
|
|
|
|
thickness, enable_hinting)
|
2013-05-24 21:07:53 +04:00
|
|
|
|
if 'underline' in values:
|
2013-04-11 14:08:53 +04:00
|
|
|
|
draw_text_decoration(
|
|
|
|
|
context, textbox,
|
2013-05-17 20:52:05 +04:00
|
|
|
|
textbox.baseline - metrics.underline_position + thickness / 2,
|
2013-06-03 20:26:08 +04:00
|
|
|
|
thickness, enable_hinting)
|
2013-05-24 21:07:53 +04:00
|
|
|
|
if 'line-through' in values:
|
2013-05-17 20:52:05 +04:00
|
|
|
|
draw_text_decoration(
|
|
|
|
|
context, textbox,
|
|
|
|
|
textbox.baseline - metrics.strikethrough_position,
|
2013-06-03 20:26:08 +04:00
|
|
|
|
thickness, enable_hinting)
|
2011-08-24 16:58:25 +04:00
|
|
|
|
|
|
|
|
|
|
2013-06-03 20:26:08 +04:00
|
|
|
|
def draw_text_decoration(context, textbox, offset_y, thickness,
|
|
|
|
|
enable_hinting):
|
2011-08-24 16:58:25 +04:00
|
|
|
|
"""Draw text-decoration of ``textbox`` to a ``cairo.Context``."""
|
2012-09-12 17:41:07 +04:00
|
|
|
|
with stacked(context):
|
2012-09-12 19:14:51 +04:00
|
|
|
|
if enable_hinting:
|
2012-06-22 17:13:23 +04:00
|
|
|
|
context.set_antialias(cairo.ANTIALIAS_NONE)
|
2012-03-24 16:39:31 +04:00
|
|
|
|
context.set_source_rgba(*textbox.style.color)
|
2013-05-17 20:52:05 +04:00
|
|
|
|
context.set_line_width(thickness)
|
2012-04-04 15:57:20 +04:00
|
|
|
|
context.move_to(textbox.position_x, textbox.position_y + offset_y)
|
2011-10-11 18:04:00 +04:00
|
|
|
|
context.rel_line_to(textbox.width, 0)
|
2011-08-24 16:58:25 +04:00
|
|
|
|
context.stroke()
|