2015-11-25 10:38:01 +03:00
|
|
|
|
# coding: utf-8
|
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
|
|
|
|
|
2014-01-10 18:27:02 +04:00
|
|
|
|
:copyright: Copyright 2011-2014 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
|
|
|
|
|
2017-03-25 02:33:36 +03:00
|
|
|
|
from .compat import xrange
|
2011-10-14 21:07:13 +04:00
|
|
|
|
from .formatting_structure import boxes
|
2017-10-04 19:21:13 +03:00
|
|
|
|
from .images import SVGImage
|
2017-09-05 16:44:50 +03:00
|
|
|
|
from .layout.backgrounds import BackgroundLayer
|
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-01-31 14:45:24 +04:00
|
|
|
|
|
2013-06-07 18:52:31 +04:00
|
|
|
|
SIDES = ('top', 'right', 'bottom', 'left')
|
2017-09-05 16:44:50 +03:00
|
|
|
|
CROP = '''
|
2017-09-21 13:03:09 +03:00
|
|
|
|
<!-- horizontal top left -->
|
2017-10-05 09:45:50 +03:00
|
|
|
|
<path d="M0,{bleed_top} h{half_bleed_left}" />
|
2017-09-21 13:03:09 +03:00
|
|
|
|
<!-- horizontal top right -->
|
2017-10-05 09:45:50 +03:00
|
|
|
|
<path d="M0,{bleed_top} h{half_bleed_right}"
|
2017-09-05 16:44:50 +03:00
|
|
|
|
transform="translate({width},0) scale(-1,1)" />
|
2017-09-21 13:03:09 +03:00
|
|
|
|
<!-- horizontal bottom right -->
|
2017-10-05 09:45:50 +03:00
|
|
|
|
<path d="M0,{bleed_bottom} h{half_bleed_right}"
|
2017-09-05 16:44:50 +03:00
|
|
|
|
transform="translate({width},{height}) scale(-1,-1)" />
|
2017-09-21 13:03:09 +03:00
|
|
|
|
<!-- horizontal bottom left -->
|
2017-10-05 09:45:50 +03:00
|
|
|
|
<path d="M0,{bleed_bottom} h{half_bleed_left}"
|
2017-09-05 16:44:50 +03:00
|
|
|
|
transform="translate(0,{height}) scale(1,-1)" />
|
2017-09-21 13:03:09 +03:00
|
|
|
|
<!-- vertical top left -->
|
2017-10-05 09:45:50 +03:00
|
|
|
|
<path d="M{bleed_left},0 v{half_bleed_top}" />
|
2017-09-21 13:03:09 +03:00
|
|
|
|
<!-- vertical bottom right -->
|
2017-10-05 09:45:50 +03:00
|
|
|
|
<path d="M{bleed_right},0 v{half_bleed_bottom}"
|
2017-09-05 16:44:50 +03:00
|
|
|
|
transform="translate({width},{height}) scale(-1,-1)" />
|
2017-09-21 13:03:09 +03:00
|
|
|
|
<!-- vertical bottom left -->
|
2017-10-05 09:45:50 +03:00
|
|
|
|
<path d="M{bleed_left},0 v{half_bleed_bottom}"
|
2017-09-21 13:03:09 +03:00
|
|
|
|
transform="translate(0,{height}) scale(1,-1)" />
|
|
|
|
|
<!-- vertical top right -->
|
2017-10-05 09:45:50 +03:00
|
|
|
|
<path d="M{bleed_right},0 v{half_bleed_top}"
|
2017-09-05 16:44:50 +03:00
|
|
|
|
transform="translate({width},0) scale(-1,1)" />
|
|
|
|
|
'''
|
|
|
|
|
CROSS = '''
|
2017-09-21 13:03:09 +03:00
|
|
|
|
<!-- top -->
|
2017-10-05 09:45:50 +03:00
|
|
|
|
<circle r="{half_bleed_top}"
|
2017-09-21 13:03:09 +03:00
|
|
|
|
transform="scale(0.5)
|
2017-10-05 09:45:50 +03:00
|
|
|
|
translate({width},{half_bleed_top}) scale(0.5)" />
|
|
|
|
|
<path d="M-{half_bleed_top},{half_bleed_top} h{bleed_top}
|
|
|
|
|
M0,0 v{bleed_top}"
|
2017-09-05 16:44:50 +03:00
|
|
|
|
transform="scale(0.5) translate({width},0)" />
|
2017-09-21 13:03:09 +03:00
|
|
|
|
<!-- bottom -->
|
2017-10-05 09:45:50 +03:00
|
|
|
|
<circle r="{half_bleed_bottom}"
|
2017-09-05 16:44:50 +03:00
|
|
|
|
transform="translate(0,{height}) scale(0.5)
|
2017-10-05 09:45:50 +03:00
|
|
|
|
translate({width},-{half_bleed_bottom}) scale(0.5)" />
|
|
|
|
|
<path d="M-{half_bleed_bottom},-{half_bleed_bottom} h{bleed_bottom}
|
|
|
|
|
M0,0 v-{bleed_bottom}"
|
2017-09-05 16:44:50 +03:00
|
|
|
|
transform="translate(0,{height}) scale(0.5) translate({width},0)" />
|
2017-09-21 13:03:09 +03:00
|
|
|
|
<!-- left -->
|
2017-10-05 09:45:50 +03:00
|
|
|
|
<circle r="{half_bleed_left}"
|
2017-09-05 16:44:50 +03:00
|
|
|
|
transform="scale(0.5)
|
2017-10-05 09:45:50 +03:00
|
|
|
|
translate({half_bleed_left},{height}) scale(0.5)" />
|
|
|
|
|
<path d="M{half_bleed_left},-{half_bleed_left} v{bleed_left}
|
|
|
|
|
M0,0 h{bleed_left}"
|
2017-09-05 16:44:50 +03:00
|
|
|
|
transform="scale(0.5) translate(0,{height})" />
|
2017-09-21 13:03:09 +03:00
|
|
|
|
<!-- right -->
|
2017-10-05 09:45:50 +03:00
|
|
|
|
<circle r="{half_bleed_right}"
|
2017-09-05 16:44:50 +03:00
|
|
|
|
transform="translate({width},0) scale(0.5)
|
2017-10-05 09:45:50 +03:00
|
|
|
|
translate(-{half_bleed_right},{height}) scale(0.5)" />
|
|
|
|
|
<path d="M-{half_bleed_right},-{half_bleed_right} v{bleed_right}
|
|
|
|
|
M0,0 h-{bleed_right}"
|
2017-09-05 16:44:50 +03:00
|
|
|
|
transform="translate({width},0)
|
|
|
|
|
scale(0.5) translate(0,{height})" />
|
|
|
|
|
'''
|
2013-06-05 20:56:57 +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
|
|
|
|
|
|
|
|
|
|
2013-12-14 16:40:44 +04:00
|
|
|
|
def hsv2rgb(hue, saturation, value):
|
|
|
|
|
"""Transform a HSV color to a RGB color."""
|
|
|
|
|
c = value * saturation
|
|
|
|
|
x = c * (1 - abs((hue / 60) % 2 - 1))
|
|
|
|
|
m = value - c
|
|
|
|
|
if 0 <= hue < 60:
|
|
|
|
|
return c + m, x + m, m
|
|
|
|
|
elif 60 <= hue < 120:
|
|
|
|
|
return x + m, c + m, m
|
|
|
|
|
elif 120 <= hue < 180:
|
|
|
|
|
return m, c + m, x + m
|
|
|
|
|
elif 180 <= hue < 240:
|
|
|
|
|
return m, x + m, c + m
|
|
|
|
|
elif 240 <= hue < 300:
|
|
|
|
|
return x + m, m, c + m
|
|
|
|
|
elif 300 <= hue < 360:
|
|
|
|
|
return c + m, m, x + m
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def rgb2hsv(red, green, blue):
|
|
|
|
|
"""Transform a RGB color to a HSV color."""
|
|
|
|
|
cmax = max(red, green, blue)
|
|
|
|
|
cmin = min(red, green, blue)
|
|
|
|
|
delta = cmax - cmin
|
|
|
|
|
if delta == 0:
|
|
|
|
|
hue = 0
|
|
|
|
|
elif cmax == red:
|
|
|
|
|
hue = 60 * ((green - blue) / delta % 6)
|
|
|
|
|
elif cmax == green:
|
|
|
|
|
hue = 60 * ((blue - red) / delta + 2)
|
|
|
|
|
elif cmax == blue:
|
|
|
|
|
hue = 60 * ((red - green) / delta + 4)
|
|
|
|
|
saturation = 0 if delta == 0 else delta / cmax
|
|
|
|
|
return hue, saturation, cmax
|
|
|
|
|
|
|
|
|
|
|
2018-01-13 19:41:08 +03:00
|
|
|
|
def get_color(style, key):
|
|
|
|
|
value = style[key]
|
|
|
|
|
return value if value != 'currentColor' else style['color']
|
|
|
|
|
|
|
|
|
|
|
2013-12-14 16:40:44 +04:00
|
|
|
|
def darken(color):
|
|
|
|
|
"""Return a darker color."""
|
|
|
|
|
hue, saturation, value = rgb2hsv(color.red, color.green, color.blue)
|
|
|
|
|
value /= 1.5
|
|
|
|
|
saturation /= 1.25
|
|
|
|
|
return hsv2rgb(hue, saturation, value) + (color.alpha,)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def lighten(color):
|
|
|
|
|
"""Return a lighter color."""
|
|
|
|
|
hue, saturation, value = rgb2hsv(color.red, color.green, color.blue)
|
|
|
|
|
value = 1 - (1 - value) / 1.5
|
|
|
|
|
if saturation:
|
|
|
|
|
saturation = 1 - (1 - saturation) / 1.25
|
|
|
|
|
return hsv2rgb(hue, saturation, value) + (color.alpha,)
|
2012-03-24 16:39:31 +04:00
|
|
|
|
|
|
|
|
|
|
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."""
|
2017-10-05 09:45:50 +03:00
|
|
|
|
bleed = {
|
|
|
|
|
side: page.style['bleed_%s' % side].value
|
|
|
|
|
for side in ('top', 'right', 'bottom', 'left')}
|
2017-09-05 16:44:50 +03:00
|
|
|
|
marks = page.style['marks']
|
2012-05-11 16:10:11 +04:00
|
|
|
|
stacking_context = StackingContext.from_page(page)
|
2013-06-06 17:58:00 +04:00
|
|
|
|
draw_background(
|
|
|
|
|
context, stacking_context.box.background, enable_hinting,
|
2017-09-05 16:44:50 +03:00
|
|
|
|
clip_box=False, bleed=bleed, marks=marks)
|
2013-06-06 18:21:45 +04:00
|
|
|
|
draw_background(
|
|
|
|
|
context, page.canvas_background, enable_hinting, clip_box=False)
|
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)
|
2013-12-13 15:42:14 +04:00
|
|
|
|
if isinstance(box, boxes.TableBox):
|
2016-01-16 19:33:52 +03:00
|
|
|
|
draw_table_backgrounds(context, page, box, enable_hinting)
|
2018-01-13 19:05:23 +03: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:
|
2018-01-13 19:05:23 +03:00
|
|
|
|
if (cell.style['empty_cells'] == 'show' or
|
|
|
|
|
not cell.empty):
|
2016-01-24 16:38:29 +03: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)
|
2013-12-13 15:42:14 +04:00
|
|
|
|
else:
|
|
|
|
|
draw_border(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
|
2018-01-13 19:05:23 +03:00
|
|
|
|
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
|
|
|
|
|
2018-01-13 19:05:23 +03: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:
|
2016-05-08 16:53:58 +03:00
|
|
|
|
try:
|
|
|
|
|
box.transformation_matrix.copy().invert()
|
|
|
|
|
except cairo.CairoError:
|
|
|
|
|
return
|
|
|
|
|
else:
|
|
|
|
|
context.transform(box.transformation_matrix)
|
|
|
|
|
|
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,
|
2013-12-13 15:42:14 +04:00
|
|
|
|
boxes.InlineBlockBox, boxes.TableCellBox)):
|
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-07 18:52:31 +04:00
|
|
|
|
with stacked(context):
|
2018-01-13 19:05:23 +03:00
|
|
|
|
if box.style['overflow'] != 'visible':
|
2013-06-07 18:52:31 +04:00
|
|
|
|
# Only clip the content and the children:
|
|
|
|
|
# - the background is already clipped
|
|
|
|
|
# - the border must *not* be clipped
|
|
|
|
|
rounded_box_path(context, box.rounded_padding_box())
|
|
|
|
|
context.clip()
|
|
|
|
|
|
|
|
|
|
# Point 3
|
|
|
|
|
for child_context in stacking_context.negative_z_contexts:
|
|
|
|
|
draw_stacking_context(context, child_context, enable_hinting)
|
|
|
|
|
|
|
|
|
|
# Point 4
|
|
|
|
|
for block in stacking_context.block_level_boxes:
|
|
|
|
|
draw_box_background_and_border(
|
|
|
|
|
context, stacking_context.page, block, enable_hinting)
|
|
|
|
|
|
|
|
|
|
# Point 5
|
|
|
|
|
for child_context in stacking_context.float_contexts:
|
|
|
|
|
draw_stacking_context(context, child_context, enable_hinting)
|
|
|
|
|
|
|
|
|
|
# Point 6
|
|
|
|
|
if isinstance(box, boxes.InlineBox):
|
2012-05-11 16:10:11 +04:00
|
|
|
|
draw_inline_level(
|
2013-06-07 18:52:31 +04:00
|
|
|
|
context, stacking_context.page, box, enable_hinting)
|
|
|
|
|
|
|
|
|
|
# Point 7
|
|
|
|
|
for block in [box] + stacking_context.blocks_and_cells:
|
|
|
|
|
marker_box = getattr(block, 'outside_list_marker', None)
|
|
|
|
|
if marker_box:
|
|
|
|
|
draw_inline_level(
|
|
|
|
|
context, stacking_context.page, marker_box,
|
|
|
|
|
enable_hinting)
|
|
|
|
|
|
|
|
|
|
if isinstance(block, boxes.ReplacedBox):
|
|
|
|
|
draw_replacedbox(context, block)
|
|
|
|
|
else:
|
|
|
|
|
for child in block.children:
|
|
|
|
|
if isinstance(child, boxes.LineBox):
|
|
|
|
|
# TODO: draw inline tables
|
|
|
|
|
draw_inline_level(
|
|
|
|
|
context, stacking_context.page, child,
|
|
|
|
|
enable_hinting)
|
|
|
|
|
|
|
|
|
|
# Point 8
|
|
|
|
|
for child_context in stacking_context.zero_z_contexts:
|
|
|
|
|
draw_stacking_context(context, child_context, enable_hinting)
|
|
|
|
|
|
|
|
|
|
# Point 9
|
|
|
|
|
for child_context in stacking_context.positive_z_contexts:
|
|
|
|
|
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
|
|
|
|
|
2018-01-13 19:05:23 +03:00
|
|
|
|
if box.style['opacity'] < 1:
|
2012-02-07 21:26:23 +04:00
|
|
|
|
context.pop_group_to_source()
|
2018-01-13 19:05:23 +03:00
|
|
|
|
context.paint_with_alpha(box.style['opacity'])
|
2012-05-11 16:10:11 +04:00
|
|
|
|
|
|
|
|
|
|
2013-06-06 18:21:45 +04:00
|
|
|
|
def rounded_box_path(context, radii):
|
2013-06-06 17:58:00 +04:00
|
|
|
|
"""Draw the path of the border radius box.
|
|
|
|
|
|
|
|
|
|
``widths`` is a tuple of the inner widths (top, right, bottom, left) from
|
|
|
|
|
the border box. Radii are adjusted from these values. Default is (0, 0, 0,
|
|
|
|
|
0).
|
2013-06-05 20:56:57 +04:00
|
|
|
|
|
|
|
|
|
Inspired by Cairo Cookbook
|
|
|
|
|
http://cairographics.org/cookbook/roundedrectangles/
|
|
|
|
|
|
|
|
|
|
"""
|
2013-06-06 17:58:00 +04:00
|
|
|
|
x, y, w, h, tl, tr, br, bl = radii
|
2013-06-05 20:56:57 +04:00
|
|
|
|
|
|
|
|
|
if 0 in tl:
|
|
|
|
|
tl = (0, 0)
|
|
|
|
|
if 0 in tr:
|
|
|
|
|
tr = (0, 0)
|
|
|
|
|
if 0 in br:
|
|
|
|
|
br = (0, 0)
|
|
|
|
|
if 0 in bl:
|
|
|
|
|
bl = (0, 0)
|
|
|
|
|
|
|
|
|
|
if (tl, tr, br, bl) == 4 * ((0, 0),):
|
2013-06-06 17:58:00 +04:00
|
|
|
|
# No radius, draw a rectangle
|
|
|
|
|
context.rectangle(x, y, w, h)
|
2013-06-05 20:56:57 +04:00
|
|
|
|
return
|
|
|
|
|
|
2013-12-07 18:53:52 +04:00
|
|
|
|
context.move_to(x, y)
|
2017-10-24 23:09:00 +03:00
|
|
|
|
context.new_sub_path()
|
2013-12-07 18:53:52 +04:00
|
|
|
|
for i, (w, h, (rx, ry)) in enumerate((
|
|
|
|
|
(0, 0, tl), (w, 0, tr), (w, h, br), (0, h, bl))):
|
|
|
|
|
context.save()
|
|
|
|
|
context.translate(x + w, y + h)
|
|
|
|
|
radius = max(rx, ry)
|
|
|
|
|
if radius:
|
|
|
|
|
context.scale(min(rx / ry, 1), min(ry / rx, 1))
|
|
|
|
|
context.arc(
|
|
|
|
|
(-1 if w else 1) * radius, (-1 if h else 1) * radius, radius,
|
|
|
|
|
(2 + i) * math.pi / 2, (3 + i) * math.pi / 2)
|
|
|
|
|
context.restore()
|
2013-06-05 20:56:57 +04:00
|
|
|
|
|
|
|
|
|
|
2017-09-21 13:03:09 +03:00
|
|
|
|
def draw_background(context, bg, enable_hinting, clip_box=True, bleed=None,
|
2017-09-05 16:44:50 +03:00
|
|
|
|
marks=()):
|
2013-06-06 17:58:00 +04:00
|
|
|
|
"""Draw the background color and image to a ``cairo.Context``.
|
|
|
|
|
|
|
|
|
|
If ``clip_box`` is set to ``False``, the background is not clipped to the
|
|
|
|
|
border box of the background, but only to the painting area.
|
2013-06-05 20:56:57 +04:00
|
|
|
|
|
2013-06-06 17:58:00 +04:00
|
|
|
|
"""
|
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-06-05 20:56:57 +04:00
|
|
|
|
with stacked(context):
|
2013-06-07 18:52:31 +04:00
|
|
|
|
if enable_hinting:
|
|
|
|
|
# Prefer crisp edges on background rectangles.
|
|
|
|
|
context.set_antialias(cairo.ANTIALIAS_NONE)
|
|
|
|
|
|
2013-06-06 17:58:00 +04:00
|
|
|
|
if clip_box:
|
2016-01-16 19:33:52 +03:00
|
|
|
|
for box in bg.layers[-1].clipped_boxes:
|
|
|
|
|
rounded_box_path(context, box)
|
2013-06-06 18:21:45 +04:00
|
|
|
|
context.clip()
|
2013-06-05 20:56:57 +04:00
|
|
|
|
|
|
|
|
|
# Background color
|
|
|
|
|
if bg.color.alpha > 0:
|
|
|
|
|
with stacked(context):
|
|
|
|
|
painting_area = bg.layers[-1].painting_area
|
|
|
|
|
if painting_area:
|
2017-09-05 16:44:50 +03:00
|
|
|
|
if bleed:
|
|
|
|
|
# Painting area is the PDF BleedBox
|
|
|
|
|
x, y, width, height = painting_area
|
|
|
|
|
painting_area = (
|
2017-10-05 09:45:50 +03:00
|
|
|
|
x - bleed['left'], y - bleed['top'],
|
|
|
|
|
width + bleed['left'] + bleed['right'],
|
|
|
|
|
height + bleed['top'] + bleed['bottom'])
|
2013-06-05 20:56:57 +04:00
|
|
|
|
context.rectangle(*painting_area)
|
|
|
|
|
context.clip()
|
|
|
|
|
context.set_source_rgba(*bg.color)
|
|
|
|
|
context.paint()
|
|
|
|
|
|
2017-09-05 16:44:50 +03:00
|
|
|
|
if bleed and marks:
|
|
|
|
|
x, y, width, height = bg.layers[-1].painting_area
|
2017-10-05 09:45:50 +03:00
|
|
|
|
x -= bleed['left']
|
|
|
|
|
y -= bleed['top']
|
|
|
|
|
width += bleed['left'] + bleed['right']
|
|
|
|
|
height += bleed['top'] + bleed['bottom']
|
2017-09-05 16:44:50 +03:00
|
|
|
|
svg = '''
|
|
|
|
|
<svg height="{height}" width="{width}"
|
|
|
|
|
fill="transparent" stroke="black" stroke-width="1"
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
|
|
|
xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
|
|
|
'''
|
|
|
|
|
if 'crop' in marks:
|
|
|
|
|
svg += CROP
|
|
|
|
|
if 'cross' in marks:
|
|
|
|
|
svg += CROSS
|
|
|
|
|
svg += '</svg>'
|
2017-10-05 09:45:50 +03:00
|
|
|
|
half_bleed = {key: value * 0.5 for key, value in bleed.items()}
|
2017-09-05 16:44:50 +03:00
|
|
|
|
image = SVGImage(svg.format(
|
|
|
|
|
height=height, width=width,
|
2017-10-05 09:45:50 +03:00
|
|
|
|
bleed_left=bleed['left'], bleed_right=bleed['right'],
|
|
|
|
|
bleed_top=bleed['top'], bleed_bottom=bleed['bottom'],
|
|
|
|
|
half_bleed_left=half_bleed['left'],
|
|
|
|
|
half_bleed_right=half_bleed['right'],
|
|
|
|
|
half_bleed_top=half_bleed['top'],
|
|
|
|
|
half_bleed_bottom=half_bleed['bottom'],
|
2017-09-21 13:03:09 +03:00
|
|
|
|
), '', None)
|
2017-09-05 16:44:50 +03:00
|
|
|
|
# Painting area is the PDF media box
|
|
|
|
|
size = (width, height)
|
|
|
|
|
position = (x, y)
|
|
|
|
|
repeat = ('no-repeat', 'no-repeat')
|
|
|
|
|
unbounded = True
|
|
|
|
|
painting_area = position + size
|
|
|
|
|
positioning_area = (0, 0, width, height)
|
|
|
|
|
clipped_boxes = []
|
|
|
|
|
layer = BackgroundLayer(
|
|
|
|
|
image, size, position, repeat, unbounded, painting_area,
|
|
|
|
|
positioning_area, clipped_boxes)
|
|
|
|
|
bg.layers.insert(0, layer)
|
2013-06-05 20:56:57 +04:00
|
|
|
|
# Paint in reversed order: first layer is "closest" to the viewer.
|
|
|
|
|
for layer in reversed(bg.layers):
|
|
|
|
|
draw_background_image(context, layer, bg.image_rendering)
|
2011-08-10 18:52:34 +04:00
|
|
|
|
|
2013-03-26 16:56:11 +04:00
|
|
|
|
|
2016-01-16 19:33:52 +03:00
|
|
|
|
def draw_table_backgrounds(context, page, table, enable_hinting):
|
|
|
|
|
"""Draw the background color and image of the table children."""
|
|
|
|
|
for column_group in table.column_groups:
|
|
|
|
|
draw_background(context, column_group.background, enable_hinting)
|
|
|
|
|
for column in column_group.children:
|
|
|
|
|
draw_background(context, column.background, enable_hinting)
|
|
|
|
|
for row_group in table.children:
|
|
|
|
|
draw_background(context, row_group.background, enable_hinting)
|
|
|
|
|
for row in row_group.children:
|
|
|
|
|
draw_background(context, row.background, enable_hinting)
|
|
|
|
|
for cell in row.children:
|
2018-01-13 19:05:23 +03:00
|
|
|
|
if (table.style['border_collapse'] == 'collapse' or
|
|
|
|
|
cell.style['empty_cells'] == 'show' or
|
|
|
|
|
not cell.empty):
|
2016-01-24 16:38:29 +03:00
|
|
|
|
draw_background(context, cell.background, enable_hinting)
|
2016-01-16 19:33:52 +03: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()
|
2014-04-27 15:29:55 +04:00
|
|
|
|
# else: unrestricted, whole page box
|
2013-03-26 17:07:44 +04:00
|
|
|
|
|
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-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
|
|
|
|
|
|
|
|
|
|
|
2013-06-07 21:29:37 +04:00
|
|
|
|
def styled_color(style, color, side):
|
|
|
|
|
if style in ('inset', 'outset'):
|
|
|
|
|
do_lighten = (side in ('top', 'left')) ^ (style == 'inset')
|
2013-12-14 16:40:44 +04:00
|
|
|
|
return (lighten if do_lighten else darken)(color)
|
2013-06-07 21:46:35 +04:00
|
|
|
|
elif style in ('ridge', 'groove'):
|
2013-12-14 16:40:44 +04:00
|
|
|
|
if (side in ('top', 'left')) ^ (style == 'ridge'):
|
|
|
|
|
return lighten(color), darken(color)
|
|
|
|
|
else:
|
|
|
|
|
return darken(color), lighten(color)
|
2013-06-07 21:29:37 +04:00
|
|
|
|
return color
|
|
|
|
|
|
|
|
|
|
|
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``."""
|
2013-06-06 17:58:00 +04:00
|
|
|
|
# We need a plan to draw beautiful borders, and that's difficult, no need
|
|
|
|
|
# to lie. Let's try to find the cases that we can handle in a smart way.
|
|
|
|
|
|
2016-08-15 20:22:05 +03:00
|
|
|
|
def draw_column_border():
|
|
|
|
|
"""Draw column borders."""
|
|
|
|
|
columns = (
|
2018-01-13 19:05:23 +03:00
|
|
|
|
box.style['column_width'] != 'auto' or
|
|
|
|
|
box.style['column_count'] != 'auto')
|
|
|
|
|
if columns and box.style['column_rule_width']:
|
|
|
|
|
border_widths = (0, 0, 0, box.style['column_rule_width'])
|
2016-08-15 20:22:05 +03:00
|
|
|
|
for child in box.children[1:]:
|
|
|
|
|
with stacked(context):
|
|
|
|
|
position_x = (child.position_x - (
|
2018-01-13 19:05:23 +03:00
|
|
|
|
box.style['column_rule_width'] +
|
|
|
|
|
box.style['column_gap']) / 2)
|
2016-08-15 20:22:05 +03:00
|
|
|
|
border_box = (
|
|
|
|
|
position_x, child.position_y,
|
2018-01-13 19:05:23 +03:00
|
|
|
|
box.style['column_rule_width'], box.height)
|
2016-08-15 20:22:05 +03:00
|
|
|
|
clip_border_segment(
|
2018-01-13 19:05:23 +03:00
|
|
|
|
context, enable_hinting,
|
|
|
|
|
box.style['column_rule_style'],
|
|
|
|
|
box.style['column_rule_width'], 'left', border_box,
|
2016-08-15 20:22:05 +03:00
|
|
|
|
border_widths)
|
|
|
|
|
draw_rect_border(
|
|
|
|
|
context, border_box, border_widths,
|
2018-01-13 19:05:23 +03:00
|
|
|
|
box.style['column_rule_style'], styled_color(
|
|
|
|
|
box.style['column_rule_style'],
|
2018-01-13 19:41:08 +03:00
|
|
|
|
get_color(box.style, 'column_rule_color'), 'left'))
|
2016-08-15 04:34:40 +03:00
|
|
|
|
|
2013-06-06 17:58:00 +04:00
|
|
|
|
# The box is hidden, easy.
|
2018-01-13 19:05:23 +03:00
|
|
|
|
if box.style['visibility'] != 'visible':
|
2016-08-15 20:22:05 +03:00
|
|
|
|
draw_column_border()
|
2012-05-11 16:10:11 +04:00
|
|
|
|
return
|
2013-06-06 17:58:00 +04:00
|
|
|
|
|
2013-06-07 18:52:31 +04:00
|
|
|
|
widths = [getattr(box, 'border_%s_width' % side) for side in SIDES]
|
2013-06-06 17:58:00 +04:00
|
|
|
|
|
|
|
|
|
# No border, return early.
|
|
|
|
|
if all(width == 0 for width in widths):
|
2016-08-15 20:22:05 +03:00
|
|
|
|
draw_column_border()
|
2013-06-06 17:58:00 +04:00
|
|
|
|
return
|
|
|
|
|
|
2018-01-13 19:41:08 +03:00
|
|
|
|
colors = [get_color(box.style, 'border_%s_color' % side) for side in SIDES]
|
2013-06-06 17:58:00 +04:00
|
|
|
|
styles = [
|
|
|
|
|
colors[i].alpha and box.style['border_%s_style' % side]
|
2013-06-07 18:52:31 +04:00
|
|
|
|
for (i, side) in enumerate(SIDES)]
|
2013-06-06 17:58:00 +04:00
|
|
|
|
|
|
|
|
|
# The 4 sides are solid or double, and they have the same color. Oh yeah!
|
|
|
|
|
# We can draw them so easily!
|
2013-12-10 16:32:51 +04:00
|
|
|
|
if set(styles) in (set(('solid',)), set(('double',))) and (
|
|
|
|
|
len(set(colors)) == 1):
|
2013-06-07 21:03:59 +04:00
|
|
|
|
draw_rounded_border(context, box, styles[0], colors[0])
|
2016-08-15 20:22:05 +03:00
|
|
|
|
draw_column_border()
|
2011-08-21 02:14:49 +04:00
|
|
|
|
return
|
2011-08-05 13:31:16 +04:00
|
|
|
|
|
2013-06-06 17:58:00 +04:00
|
|
|
|
# We're not smart enough to find a good way to draw the borders :/. We must
|
|
|
|
|
# draw them side by side.
|
2013-06-07 18:52:31 +04:00
|
|
|
|
for side, width, color, style in zip(SIDES, widths, colors, styles):
|
2013-06-07 15:13:27 +04:00
|
|
|
|
if width == 0 or not color:
|
2011-10-14 20:57:17 +04:00
|
|
|
|
continue
|
2013-06-07 18:52:31 +04:00
|
|
|
|
with stacked(context):
|
|
|
|
|
clip_border_segment(
|
|
|
|
|
context, enable_hinting, style, width, side,
|
|
|
|
|
box.rounded_border_box()[:4], widths,
|
|
|
|
|
box.rounded_border_box()[4:])
|
2013-06-07 21:29:37 +04:00
|
|
|
|
draw_rounded_border(
|
|
|
|
|
context, box, style, styled_color(style, color, side))
|
2011-12-13 18:42:14 +04:00
|
|
|
|
|
2016-08-15 20:22:05 +03:00
|
|
|
|
draw_column_border()
|
|
|
|
|
|
2011-12-13 18:42:14 +04:00
|
|
|
|
|
2013-06-07 18:52:31 +04:00
|
|
|
|
def clip_border_segment(context, enable_hinting, style, width, side,
|
|
|
|
|
border_box, border_widths=None, radii=None):
|
|
|
|
|
"""Clip one segment of box border.
|
2011-12-13 18:42:14 +04:00
|
|
|
|
|
2013-06-07 18:52:31 +04:00
|
|
|
|
The strategy is to remove the zones not needed because of the style or the
|
|
|
|
|
side before painting.
|
2013-06-07 15:13:27 +04:00
|
|
|
|
|
|
|
|
|
"""
|
2013-06-07 18:52:31 +04:00
|
|
|
|
if enable_hinting and style != 'dotted' and (
|
|
|
|
|
# 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):
|
|
|
|
|
# Avoid an artifact in the corner joining two solid borders
|
|
|
|
|
# of the same color.
|
|
|
|
|
context.set_antialias(cairo.ANTIALIAS_NONE)
|
|
|
|
|
|
|
|
|
|
bbx, bby, bbw, bbh = border_box
|
|
|
|
|
(tlh, tlv), (trh, trv), (brh, brv), (blh, blv) = radii or 4 * ((0, 0),)
|
|
|
|
|
bt, br, bb, bl = border_widths or 4 * (width,)
|
2013-06-07 15:13:27 +04:00
|
|
|
|
|
2013-12-09 22:14:38 +04:00
|
|
|
|
def transition_point(x1, y1, x2, y2):
|
2013-06-07 15:13:27 +04:00
|
|
|
|
"""Get the point use for border transition.
|
|
|
|
|
|
2013-07-22 18:11:59 +04:00
|
|
|
|
The extra boolean returned is ``True`` if the point is in the padding
|
|
|
|
|
box (ie. the padding box is rounded).
|
|
|
|
|
|
2013-06-07 15:13:27 +04:00
|
|
|
|
This point is not specified. We must be sure to be inside the rounded
|
|
|
|
|
padding box, and in the zone defined in the "transition zone" allowed
|
|
|
|
|
by the specification. We chose the corner of the transition zone. It's
|
|
|
|
|
easy to get and gives quite good results, but it seems to be different
|
|
|
|
|
from what other browsers do.
|
|
|
|
|
|
|
|
|
|
"""
|
2013-12-09 22:14:38 +04:00
|
|
|
|
return (
|
|
|
|
|
((x1, y1), True) if abs(x1) > abs(x2) and abs(y1) > abs(y2)
|
|
|
|
|
else ((x2, y2), False))
|
2013-07-22 18:11:59 +04:00
|
|
|
|
|
|
|
|
|
def corner_half_length(a, b):
|
|
|
|
|
"""Return the length of the half of one ellipsis corner.
|
|
|
|
|
|
|
|
|
|
Inspired by [Ramanujan, S., "Modular Equations and Approximations to
|
|
|
|
|
pi" Quart. J. Pure. Appl. Math., vol. 45 (1913-1914), pp. 350-372],
|
|
|
|
|
wonderfully explained by Dr Rob.
|
|
|
|
|
|
2013-12-09 15:52:26 +04:00
|
|
|
|
http://mathforum.org/dr.math/faq/formulas/
|
2013-07-22 18:11:59 +04:00
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
x = (a - b) / (a + b)
|
|
|
|
|
return math.pi / 8 * (a + b) * (
|
|
|
|
|
1 + 3 * x ** 2 / (10 + math.sqrt(4 - 3 * x ** 2)))
|
2013-06-07 15:13:27 +04:00
|
|
|
|
|
2013-06-07 18:52:31 +04:00
|
|
|
|
if side == 'top':
|
2013-12-09 22:14:38 +04:00
|
|
|
|
(px1, py1), rounded1 = transition_point(tlh, tlv, bl, bt)
|
|
|
|
|
(px2, py2), rounded2 = transition_point(-trh, trv, -br, bt)
|
2013-06-11 01:47:10 +04:00
|
|
|
|
width = bt
|
2013-11-19 19:02:52 +04:00
|
|
|
|
way = 1
|
|
|
|
|
angle = 1
|
|
|
|
|
main_offset = bby
|
2013-06-07 18:52:31 +04:00
|
|
|
|
elif side == 'right':
|
2013-12-09 22:14:38 +04:00
|
|
|
|
(px1, py1), rounded1 = transition_point(-trh, trv, -br, bt)
|
|
|
|
|
(px2, py2), rounded2 = transition_point(-brh, -brv, -br, -bb)
|
2013-06-11 01:47:10 +04:00
|
|
|
|
width = br
|
2013-11-19 19:02:52 +04:00
|
|
|
|
way = 1
|
2013-12-06 14:12:03 +04:00
|
|
|
|
angle = 2
|
|
|
|
|
main_offset = bbx + bbw
|
2013-06-07 18:52:31 +04:00
|
|
|
|
elif side == 'bottom':
|
2013-12-09 22:14:38 +04:00
|
|
|
|
(px1, py1), rounded1 = transition_point(blh, -blv, bl, -bb)
|
|
|
|
|
(px2, py2), rounded2 = transition_point(-brh, -brv, -br, -bb)
|
2013-06-11 01:47:10 +04:00
|
|
|
|
width = bb
|
2013-11-19 19:02:52 +04:00
|
|
|
|
way = -1
|
|
|
|
|
angle = 3
|
|
|
|
|
main_offset = bby + bbh
|
2013-06-07 18:52:31 +04:00
|
|
|
|
elif side == 'left':
|
2013-12-09 22:14:38 +04:00
|
|
|
|
(px1, py1), rounded1 = transition_point(tlh, tlv, bl, bt)
|
|
|
|
|
(px2, py2), rounded2 = transition_point(blh, -blv, bl, -bb)
|
2013-06-11 01:47:10 +04:00
|
|
|
|
width = bl
|
2013-11-19 19:02:52 +04:00
|
|
|
|
way = -1
|
2013-12-06 14:12:03 +04:00
|
|
|
|
angle = 4
|
|
|
|
|
main_offset = bbx
|
2013-06-11 01:47:10 +04:00
|
|
|
|
|
2013-12-10 00:42:33 +04:00
|
|
|
|
if side in ('top', 'bottom'):
|
|
|
|
|
a1, b1 = px1 - bl / 2, way * py1 - width / 2
|
|
|
|
|
a2, b2 = -px2 - br / 2, way * py2 - width / 2
|
|
|
|
|
line_length = bbw - px1 + px2
|
|
|
|
|
length = bbw
|
|
|
|
|
context.move_to(bbx + bbw, main_offset)
|
|
|
|
|
context.rel_line_to(-bbw, 0)
|
|
|
|
|
context.rel_line_to(px1, py1)
|
|
|
|
|
context.rel_line_to(-px1 + bbw + px2, -py1 + py2)
|
|
|
|
|
elif side in ('left', 'right'):
|
|
|
|
|
a1, b1 = -way * px1 - width / 2, py1 - bt / 2
|
|
|
|
|
a2, b2 = -way * px2 - width / 2, -py2 - bb / 2
|
|
|
|
|
line_length = bbh - py1 + py2
|
|
|
|
|
length = bbh
|
|
|
|
|
context.move_to(main_offset, bby + bbh)
|
|
|
|
|
context.rel_line_to(0, -bbh)
|
|
|
|
|
context.rel_line_to(px1, py1)
|
|
|
|
|
context.rel_line_to(-px1 + px2, -py1 + bbh + py2)
|
|
|
|
|
|
2013-07-22 18:11:59 +04:00
|
|
|
|
context.set_fill_rule(cairo.FILL_RULE_EVEN_ODD)
|
2013-06-11 01:47:10 +04:00
|
|
|
|
if style in ('dotted', 'dashed'):
|
|
|
|
|
dash = width if style == 'dotted' else 3 * width
|
2013-07-22 18:11:59 +04:00
|
|
|
|
if rounded1 or rounded2:
|
|
|
|
|
# At least one of the two corners is rounded
|
2013-12-09 22:51:59 +04:00
|
|
|
|
chl1 = corner_half_length(a1, b1)
|
|
|
|
|
chl2 = corner_half_length(a2, b2)
|
|
|
|
|
length = line_length + chl1 + chl2
|
|
|
|
|
dash_length = round(length / dash)
|
|
|
|
|
if rounded1 and rounded2:
|
|
|
|
|
# 2x dashes
|
|
|
|
|
dash = length / (dash_length + dash_length % 2)
|
|
|
|
|
else:
|
|
|
|
|
# 2x - 1/2 dashes
|
|
|
|
|
dash = length / (dash_length + dash_length % 2 - 0.5)
|
|
|
|
|
dashes1 = int(math.ceil((chl1 - dash / 2) / dash))
|
|
|
|
|
dashes2 = int(math.ceil((chl2 - dash / 2) / dash))
|
|
|
|
|
line = int(math.floor(line_length / dash))
|
|
|
|
|
|
2013-12-10 00:31:54 +04:00
|
|
|
|
def draw_dots(dashes, line, way, x, y, px, py, chl):
|
|
|
|
|
if not dashes:
|
2013-12-10 12:22:34 +04:00
|
|
|
|
return line + 1, 0
|
2013-12-10 00:31:54 +04:00
|
|
|
|
for i in range(0, dashes, 2):
|
2013-12-09 22:14:38 +04:00
|
|
|
|
i += 0.5 # half dash
|
|
|
|
|
angle1 = (
|
2016-01-15 14:47:03 +03:00
|
|
|
|
((2 * angle - way) + i * way * dash / chl) /
|
|
|
|
|
4 * math.pi)
|
2013-12-09 22:51:59 +04:00
|
|
|
|
angle2 = (min if way > 0 else max)(
|
2016-01-15 14:47:03 +03:00
|
|
|
|
((2 * angle - way) + (i + 1) * way * dash / chl) /
|
|
|
|
|
4 * math.pi,
|
2013-12-09 22:14:38 +04:00
|
|
|
|
angle * math.pi / 2)
|
2013-12-09 22:51:59 +04:00
|
|
|
|
if side in ('top', 'bottom'):
|
2013-12-10 00:31:54 +04:00
|
|
|
|
context.move_to(x + px, main_offset + py)
|
2013-12-09 22:51:59 +04:00
|
|
|
|
context.line_to(
|
2013-12-10 00:31:54 +04:00
|
|
|
|
x + px - way * px * 1 / math.tan(angle2),
|
2013-12-09 22:51:59 +04:00
|
|
|
|
main_offset)
|
|
|
|
|
context.line_to(
|
2013-12-10 00:31:54 +04:00
|
|
|
|
x + px - way * px * 1 / math.tan(angle1),
|
2013-12-09 22:51:59 +04:00
|
|
|
|
main_offset)
|
|
|
|
|
elif side in ('left', 'right'):
|
2013-12-10 00:31:54 +04:00
|
|
|
|
context.move_to(main_offset + px, y + py)
|
2013-12-09 22:14:38 +04:00
|
|
|
|
context.line_to(
|
|
|
|
|
main_offset,
|
2013-12-10 00:31:54 +04:00
|
|
|
|
y + py + way * py * math.tan(angle2))
|
2013-12-09 22:14:38 +04:00
|
|
|
|
context.line_to(
|
|
|
|
|
main_offset,
|
2013-12-10 00:31:54 +04:00
|
|
|
|
y + py + way * py * math.tan(angle1))
|
2013-12-09 22:51:59 +04:00
|
|
|
|
if angle2 == angle * math.pi / 2:
|
|
|
|
|
offset = (angle1 - angle2) / ((
|
2016-01-15 14:47:03 +03:00
|
|
|
|
((2 * angle - way) + (i + 1) * way * dash / chl) /
|
|
|
|
|
4 * math.pi) - angle1)
|
2013-12-09 22:51:59 +04:00
|
|
|
|
line += 1
|
|
|
|
|
break
|
2013-12-06 14:12:03 +04:00
|
|
|
|
else:
|
2013-12-09 22:51:59 +04:00
|
|
|
|
offset = 1 - (
|
|
|
|
|
(angle * math.pi / 2 - angle2) / (angle2 - angle1))
|
2013-12-10 00:31:54 +04:00
|
|
|
|
return line, offset
|
|
|
|
|
|
|
|
|
|
line, offset = draw_dots(
|
|
|
|
|
dashes1, line, way, bbx, bby, px1, py1, chl1)
|
|
|
|
|
line = draw_dots(
|
|
|
|
|
dashes2, line, -way, bbx + bbw, bby + bbh, px2, py2, chl2)[0]
|
2013-12-09 22:51:59 +04:00
|
|
|
|
|
|
|
|
|
if line_length > 1e-6:
|
|
|
|
|
for i in range(0, line, 2):
|
|
|
|
|
i += offset
|
|
|
|
|
if side in ('top', 'bottom'):
|
|
|
|
|
x1 = max(bbx + px1 + i * dash, bbx + px1)
|
|
|
|
|
x2 = min(bbx + px1 + (i + 1) * dash, bbx + bbw + px2)
|
|
|
|
|
y1 = main_offset - (width if way < 0 else 0)
|
|
|
|
|
y2 = y1 + width
|
|
|
|
|
elif side in ('left', 'right'):
|
2013-12-09 22:14:38 +04:00
|
|
|
|
y1 = max(bby + py1 + i * dash, bby + py1)
|
|
|
|
|
y2 = min(bby + py1 + (i + 1) * dash, bby + bbh + py2)
|
|
|
|
|
x1 = main_offset - (width if way > 0 else 0)
|
|
|
|
|
x2 = x1 + width
|
2013-12-09 22:51:59 +04:00
|
|
|
|
context.rectangle(x1, y1, x2 - x1, y2 - y1)
|
2013-07-22 18:11:59 +04:00
|
|
|
|
else:
|
|
|
|
|
# 2x + 1 dashes
|
2013-12-10 13:28:31 +04:00
|
|
|
|
context.clip()
|
2013-06-11 01:47:10 +04:00
|
|
|
|
dash = length / (
|
2013-12-18 18:30:59 +04:00
|
|
|
|
round(length / dash) - (round(length / dash) + 1) % 2) or 1
|
2013-12-10 13:28:31 +04:00
|
|
|
|
for i in range(0, int(round(length / dash)), 2):
|
2013-07-22 18:11:59 +04:00
|
|
|
|
if side == 'top':
|
|
|
|
|
context.rectangle(
|
|
|
|
|
bbx + i * dash, bby, dash, width)
|
|
|
|
|
elif side == 'right':
|
|
|
|
|
context.rectangle(
|
|
|
|
|
bbx + bbw - width, bby + i * dash, width, dash)
|
|
|
|
|
elif side == 'bottom':
|
|
|
|
|
context.rectangle(
|
|
|
|
|
bbx + i * dash, bby + bbh - width, dash, width)
|
|
|
|
|
elif side == 'left':
|
|
|
|
|
context.rectangle(
|
|
|
|
|
bbx, bby + i * dash, width, dash)
|
2013-09-14 13:13:53 +04:00
|
|
|
|
context.clip()
|
2013-06-11 01:47:10 +04:00
|
|
|
|
|
2013-06-07 18:52:31 +04:00
|
|
|
|
|
2013-06-07 19:14:32 +04:00
|
|
|
|
def draw_rounded_border(context, box, style, color):
|
2013-06-07 21:46:35 +04:00
|
|
|
|
context.set_fill_rule(cairo.FILL_RULE_EVEN_ODD)
|
2013-06-07 18:52:31 +04:00
|
|
|
|
rounded_box_path(context, box.rounded_padding_box())
|
2013-06-07 21:46:35 +04:00
|
|
|
|
if style in ('ridge', 'groove'):
|
2013-12-10 18:45:33 +04:00
|
|
|
|
rounded_box_path(context, box.rounded_box_ratio(1 / 2))
|
2013-06-07 21:46:35 +04:00
|
|
|
|
context.set_source_rgba(*color[0])
|
|
|
|
|
context.fill()
|
2013-12-10 18:45:33 +04:00
|
|
|
|
rounded_box_path(context, box.rounded_box_ratio(1 / 2))
|
2013-06-07 21:46:35 +04:00
|
|
|
|
rounded_box_path(context, box.rounded_border_box())
|
|
|
|
|
context.set_source_rgba(*color[1])
|
|
|
|
|
context.fill()
|
|
|
|
|
return
|
2013-06-07 18:52:31 +04:00
|
|
|
|
if style == 'double':
|
2013-12-10 18:45:33 +04:00
|
|
|
|
rounded_box_path(context, box.rounded_box_ratio(1 / 3))
|
|
|
|
|
rounded_box_path(context, box.rounded_box_ratio(2 / 3))
|
2013-06-07 18:52:31 +04:00
|
|
|
|
rounded_box_path(context, box.rounded_border_box())
|
2013-06-07 19:14:32 +04:00
|
|
|
|
context.set_source_rgba(*color)
|
|
|
|
|
context.fill()
|
2013-06-07 18:52:31 +04:00
|
|
|
|
|
|
|
|
|
|
2013-06-07 19:14:32 +04:00
|
|
|
|
def draw_rect_border(context, box, widths, style, color):
|
2013-06-07 21:46:35 +04:00
|
|
|
|
context.set_fill_rule(cairo.FILL_RULE_EVEN_ODD)
|
2013-06-07 18:52:31 +04:00
|
|
|
|
bbx, bby, bbw, bbh = box
|
|
|
|
|
bt, br, bb, bl = widths
|
|
|
|
|
context.rectangle(*box)
|
2013-06-07 21:46:35 +04:00
|
|
|
|
if style in ('ridge', 'groove'):
|
|
|
|
|
context.rectangle(
|
|
|
|
|
bbx + bl / 2, bby + bt / 2,
|
|
|
|
|
bbw - (bl + br) / 2, bbh - (bt + bb) / 2)
|
|
|
|
|
context.set_source_rgba(*color[0])
|
|
|
|
|
context.fill()
|
|
|
|
|
context.rectangle(
|
|
|
|
|
bbx + bl / 2, bby + bt / 2,
|
|
|
|
|
bbw - (bl + br) / 2, bbh - (bt + bb) / 2)
|
|
|
|
|
context.rectangle(
|
|
|
|
|
bbx + bl, bby + bt, bbw - bl - br, bbh - bt - bb)
|
|
|
|
|
context.set_source_rgba(*color[1])
|
|
|
|
|
context.fill()
|
|
|
|
|
return
|
2013-06-07 18:52:31 +04:00
|
|
|
|
if style == 'double':
|
|
|
|
|
context.rectangle(
|
|
|
|
|
bbx + bl / 3, bby + bt / 3,
|
|
|
|
|
bbw - (bl + br) / 3, bbh - (bt + bb) / 3)
|
|
|
|
|
context.rectangle(
|
|
|
|
|
bbx + bl * 2 / 3, bby + bt * 2 / 3,
|
|
|
|
|
bbw - (bl + br) * 2 / 3, bbh - (bt + bb) * 2 / 3)
|
|
|
|
|
context.rectangle(bbx + bl, bby + bt, bbw - bl - br, bbh - bt - bb)
|
|
|
|
|
context.set_source_rgba(*color)
|
|
|
|
|
context.fill()
|
2012-07-11 20:21:20 +04:00
|
|
|
|
|
|
|
|
|
|
2012-09-12 19:14:51 +04:00
|
|
|
|
def draw_outlines(context, box, enable_hinting):
|
2018-01-13 19:05:23 +03:00
|
|
|
|
width = box.style['outline_width']
|
2018-01-13 19:41:08 +03:00
|
|
|
|
color = get_color(box.style, 'outline_color')
|
2018-01-13 19:05:23 +03:00
|
|
|
|
style = box.style['outline_style']
|
|
|
|
|
if box.style['visibility'] == 'visible' and width and color.alpha:
|
2013-06-07 18:52:31 +04:00
|
|
|
|
outline_box = (
|
|
|
|
|
box.border_box_x() - width, box.border_box_y() - width,
|
|
|
|
|
box.border_width() + 2 * width, box.border_height() + 2 * width)
|
|
|
|
|
for side in SIDES:
|
|
|
|
|
with stacked(context):
|
|
|
|
|
clip_border_segment(
|
|
|
|
|
context, enable_hinting, style, width, side, outline_box)
|
2013-06-07 19:14:32 +04:00
|
|
|
|
draw_rect_border(
|
2013-06-07 21:29:37 +04:00
|
|
|
|
context, outline_box, 4 * (width,), style,
|
|
|
|
|
styled_color(style, color, side))
|
2012-08-03 18:21:47 +04:00
|
|
|
|
|
|
|
|
|
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):
|
2013-06-07 18:52:31 +04:00
|
|
|
|
"""Draw borders of table cells when they collapse."""
|
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-18 17:47:39 +04:00
|
|
|
|
column_positions = list(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
|
|
|
|
|
pos_x = column_positions[x]
|
2013-06-07 18:52:31 +04:00
|
|
|
|
pos_y1 = row_positions[y] - half_max_width(horizontal_borders, [
|
2012-07-11 21:33:11 +04:00
|
|
|
|
(y, x - 1), (y, x)], vertical=False)
|
2013-06-07 18:52:31 +04:00
|
|
|
|
pos_y2 = row_positions[y + 1] + half_max_width(horizontal_borders, [
|
2012-07-11 21:33:11 +04:00
|
|
|
|
(y + 1, x - 1), (y + 1, x)], vertical=False)
|
2013-06-07 18:52:31 +04:00
|
|
|
|
segments.append((
|
|
|
|
|
score, style, width, color, 'left',
|
|
|
|
|
(pos_x - width / 2, pos_y1, 0, pos_y2 - pos_y1)))
|
2012-07-11 20:21:20 +04:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
pos_y = row_positions[y]
|
2012-07-11 21:33:11 +04:00
|
|
|
|
# TODO: change signs for rtl when we support rtl tables?
|
2013-06-07 18:52:31 +04:00
|
|
|
|
pos_x1 = column_positions[x] - half_max_width(vertical_borders, [
|
2012-07-11 21:33:11 +04:00
|
|
|
|
(y - 1, x), (y, x)])
|
2013-06-07 18:52:31 +04:00
|
|
|
|
pos_x2 = column_positions[x + 1] + half_max_width(vertical_borders, [
|
2012-07-11 21:33:11 +04:00
|
|
|
|
(y - 1, x + 1), (y, x + 1)])
|
2013-06-07 18:52:31 +04:00
|
|
|
|
segments.append((
|
|
|
|
|
score, style, width, color, 'top',
|
|
|
|
|
(pos_x1, pos_y - width / 2, pos_x2 - pos_x1, 0)))
|
2012-07-11 20:21:20 +04:00
|
|
|
|
|
|
|
|
|
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:
|
2013-06-07 18:52:31 +04:00
|
|
|
|
_, style, width, color, side, border_box = segment
|
|
|
|
|
if side == 'top':
|
|
|
|
|
widths = (width, 0, 0, 0)
|
|
|
|
|
else:
|
|
|
|
|
widths = (0, 0, 0, width)
|
|
|
|
|
with stacked(context):
|
|
|
|
|
clip_border_segment(
|
|
|
|
|
context, enable_hinting, style, width, side, border_box,
|
|
|
|
|
widths)
|
2013-06-07 21:29:37 +04:00
|
|
|
|
draw_rect_border(
|
|
|
|
|
context, border_box, widths, style,
|
|
|
|
|
styled_color(style, color, side))
|
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``."""
|
2018-01-13 19:05:23 +03:00
|
|
|
|
if box.style['visibility'] != 'visible' or not box.width or not box.height:
|
2012-05-11 16:10:11 +04:00
|
|
|
|
return
|
|
|
|
|
|
2013-04-03 18:00:31 +04:00
|
|
|
|
with stacked(context):
|
2015-04-25 21:00:20 +03:00
|
|
|
|
rounded_box_path(context, box.rounded_content_box())
|
2013-04-03 18:00:31 +04:00
|
|
|
|
context.clip()
|
2015-04-25 21:00:20 +03:00
|
|
|
|
context.translate(box.content_box_x(), box.content_box_y())
|
2013-04-03 18:00:31 +04:00
|
|
|
|
box.replacement.draw(
|
2018-01-13 19:05:23 +03:00
|
|
|
|
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
|
2018-01-13 19:05:23 +03:00
|
|
|
|
assert textbox.style['font_size']
|
2011-09-02 19:42:00 +04:00
|
|
|
|
|
2018-01-13 19:05:23 +03:00
|
|
|
|
if textbox.style['visibility'] != 'visible':
|
2012-05-11 16:10:11 +04:00
|
|
|
|
return
|
|
|
|
|
|
2011-10-08 22:57:59 +04:00
|
|
|
|
context.move_to(textbox.position_x, textbox.position_y + textbox.baseline)
|
2018-01-13 19:05:23 +03: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)
|
2018-01-13 19:05:23 +03:00
|
|
|
|
values = textbox.style['text_decoration']
|
2013-05-17 20:52:05 +04:00
|
|
|
|
|
2018-01-13 19:05:23 +03:00
|
|
|
|
thickness = textbox.style['font_size'] / 18 # Like other browsers do
|
2013-05-17 20:52:05 +04:00
|
|
|
|
if enable_hinting and thickness < 1:
|
|
|
|
|
thickness = 1
|
|
|
|
|
|
2016-11-02 14:55:01 +03:00
|
|
|
|
if ('overline' in values or
|
|
|
|
|
'line-through' in values or
|
|
|
|
|
'underline' in values):
|
|
|
|
|
metrics = textbox.pango_layout.get_font_metrics()
|
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)
|
2018-01-13 19:05:23 +03: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()
|