1
1
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:
Simon Sapin 2011-10-14 18:57:17 +02:00
parent f4c2146fea
commit d4e1ff9dd6
2 changed files with 75 additions and 197 deletions

View File

@ -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)

View File

@ -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 Trapezoids 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):