mirror of
https://github.com/Kozea/WeasyPrint.git
synced 2024-10-05 00:21:15 +03:00
Rewrite border drawing without the figure classes.
This commit is contained in:
parent
f4c2146fea
commit
d4e1ff9dd6
@ -1,140 +0,0 @@
|
||||
# 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/>.
|
||||
|
||||
|
||||
"""
|
||||
Classes defining geometrical figures.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import division
|
||||
|
||||
|
||||
class Point(object):
|
||||
"""Couple of integer coordinates."""
|
||||
def __init__(self, x, y):
|
||||
self.x = round(x)
|
||||
self.y = round(y)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s (%d, %d)>' % (type(self).__name__, self.x, self.y)
|
||||
|
||||
def move_to(self, x, y):
|
||||
"""Change the coordinates."""
|
||||
self.x += x
|
||||
self.y += y
|
||||
|
||||
def copy(self):
|
||||
"""Return a copy of the point."""
|
||||
return type(self)(self.x, self.y)
|
||||
|
||||
|
||||
class Line(object):
|
||||
"""Couple of :class:`Point` objects."""
|
||||
def __init__(self, point1, point2):
|
||||
self.first_point = point1
|
||||
self.second_point = point2
|
||||
self.type = 'solid'
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s (%s, %s)>' % (
|
||||
type(self).__name__, self.first_point, self.second_point)
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
"""Distance between the 2 points of the line."""
|
||||
diff_x = self.second_point.x - self.first_point.x
|
||||
diff_y = self.second_point.y - self.first_point.y
|
||||
return (diff_x ** 2 + diff_y ** 2) ** 0.5
|
||||
|
||||
def draw_path(self, context):
|
||||
"""Draw the line path on the ``context``."""
|
||||
context.move_to(self.first_point.x, self.first_point.y)
|
||||
context.line_to(self.second_point.x, self.second_point.y)
|
||||
|
||||
def copy(self):
|
||||
"""Return a copy of the line with a copy of its points."""
|
||||
return type(self)(self.first_point.copy(), self.second_point.copy())
|
||||
|
||||
|
||||
class Trapezoid(object):
|
||||
"""Horizontal or vertical trapezoid."""
|
||||
def __init__(self, line1, line2):
|
||||
if line1.length > line2.length:
|
||||
self.long_base, self.small_base = line1, line2
|
||||
else:
|
||||
self.long_base, self.small_base = line2, line1
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s (%s, %s)>' % (
|
||||
type(self).__name__, self.long_base, self.small_base)
|
||||
|
||||
def get_points(self):
|
||||
"""Get the 4 points of the trapezoid."""
|
||||
return [self.long_base.first_point, self.small_base.first_point,
|
||||
self.small_base.second_point, self.long_base.second_point]
|
||||
|
||||
def get_all_lines(self):
|
||||
"""Get the 4 lines of the trapezoid."""
|
||||
points = list(self.get_points())
|
||||
lines_number = len(points)
|
||||
for i in range(lines_number):
|
||||
yield Line(points[i], points[(i + 1) % lines_number])
|
||||
|
||||
def get_side_lines(self):
|
||||
"""Get the non horizontal or vertical sides of the trapezoid."""
|
||||
points = list(self.get_points())
|
||||
lines_number = len(points)
|
||||
for i in range(lines_number):
|
||||
if not i % 2:
|
||||
yield Line(points[i], points[(i + 1) % lines_number])
|
||||
|
||||
def get_middle_line(self):
|
||||
r"""Get the middle line of trapezoid.
|
||||
|
||||
Here is what the middle line is for an horizontal trapezoid::
|
||||
|
||||
+---------------+
|
||||
\ /
|
||||
=================
|
||||
\ /
|
||||
+-------+
|
||||
|
||||
The middle line is the line drawn by the equal '=' sign.
|
||||
|
||||
"""
|
||||
if self.long_base.first_point.x != self.long_base.second_point.x:
|
||||
x1 = self.long_base.first_point.x
|
||||
x2 = self.long_base.second_point.x
|
||||
else:
|
||||
x1 = self.long_base.first_point.x + self.small_base.first_point.x
|
||||
x1 = x2 = x1 / 2
|
||||
if self.long_base.first_point.y != self.long_base.second_point.y:
|
||||
y1 = self.long_base.first_point.y
|
||||
y2 = self.long_base.second_point.y
|
||||
else:
|
||||
y1 = self.long_base.first_point.y + self.small_base.first_point.y
|
||||
y1 = y2 = y1 / 2
|
||||
return Line(Point(x1, y1), Point(x2, y2))
|
||||
|
||||
def draw_path(self, context):
|
||||
"""Draw the path of the trapezoid on the ``context``."""
|
||||
for i, line in enumerate(self.get_all_lines()):
|
||||
if i == 0:
|
||||
context.move_to(line.first_point.x, line.first_point.y)
|
||||
context.line_to(line.second_point.x, line.second_point.y)
|
@ -28,7 +28,6 @@ import urllib
|
||||
import cairo
|
||||
from StringIO import StringIO
|
||||
|
||||
from .figures import Point, Line, Trapezoid
|
||||
from ..text import TextFragment
|
||||
from ..formatting_structure import boxes
|
||||
from ..css.values import get_percentage_value
|
||||
@ -235,6 +234,26 @@ def absolute_background_position(css_values, bg_dimensions, image_dimensions):
|
||||
yield value
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def draw_border(context, box):
|
||||
"""Draw the box border to a ``cairo.Context``."""
|
||||
if all(getattr(box, 'border_%s_width' % side) == 0
|
||||
@ -242,68 +261,67 @@ def draw_border(context, box):
|
||||
# No border, return early.
|
||||
return
|
||||
|
||||
def get_edge(x, y, width, height):
|
||||
"""Get the 4 points corresponding to the given parameters."""
|
||||
return (Point(x, y), Point(x + width, y),
|
||||
Point(x + width, y + height), Point(x, y + height))
|
||||
|
||||
def get_border_area():
|
||||
"""Get the border area of ``box``."""
|
||||
# Border area
|
||||
x = box.position_x + box.margin_left
|
||||
y = box.position_y + box.margin_top
|
||||
border_edge = get_edge(x, y, box.border_width(), box.border_height())
|
||||
|
||||
# Padding area
|
||||
x = x + box.border_left_width
|
||||
y = y + box.border_top_width
|
||||
padding_edge = get_edge(
|
||||
x, y, box.padding_width(), box.padding_height())
|
||||
|
||||
return border_edge, padding_edge
|
||||
|
||||
def get_lines(rectangle):
|
||||
"""Get the 4 lines of ``rectangle``."""
|
||||
lines_number = len(rectangle)
|
||||
for i in range(lines_number):
|
||||
yield Line(rectangle[i], rectangle[(i + 1) % lines_number])
|
||||
|
||||
def get_trapezoids():
|
||||
"""Get the 4 trapezoids of ``context``."""
|
||||
border_lines, padding_lines = [
|
||||
get_lines(area) for area in get_border_area()]
|
||||
for line1, line2 in zip(border_lines, padding_lines):
|
||||
yield Trapezoid(line1, line2)
|
||||
|
||||
def draw_border_side(side, trapezoid):
|
||||
"""Draw ``trapezoid`` at the box's ``side``."""
|
||||
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(),
|
||||
),
|
||||
):
|
||||
width = getattr(box, 'border_%s_width' % side)
|
||||
if width == 0:
|
||||
return
|
||||
continue
|
||||
color = box.style['border_%s_color' % side]
|
||||
if color.alpha == 0:
|
||||
continue
|
||||
style = box.style['border_%s_style' % side]
|
||||
if color.alpha > 0:
|
||||
with context.stacked():
|
||||
# TODO: implement other styles.
|
||||
if not style in ['dotted', 'dashed']:
|
||||
trapezoid.draw_path(context)
|
||||
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)
|
||||
line = trapezoid.get_middle_line()
|
||||
line.draw_path(context)
|
||||
context.set_source_colorvalue(color)
|
||||
context.set_line_width(width)
|
||||
context.stroke()
|
||||
with context.stacked():
|
||||
"""
|
||||
Both edges form a trapezoid. This is the top one:
|
||||
|
||||
trapezoids_side = zip(['top', 'right', 'bottom', 'left'], get_trapezoids())
|
||||
+---------------+
|
||||
\ /
|
||||
=================
|
||||
\ /
|
||||
+-------+
|
||||
|
||||
for side, trapezoid in trapezoids_side:
|
||||
draw_border_side(side, trapezoid)
|
||||
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()
|
||||
|
||||
|
||||
def draw_replacedbox(context, box):
|
||||
|
Loading…
Reference in New Issue
Block a user