Add existing to tracked
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
"""
|
||||
Text drawers.
|
||||
|
||||
"""
|
||||
|
||||
from math import cos, radians, sin
|
||||
|
||||
from .bounding_box import (
|
||||
EMPTY_BOUNDING_BOX, extend_bounding_box, is_valid_bounding_box)
|
||||
from .helpers import distance, normalize, point_angle, size, zip_letters
|
||||
from .surface import cairo
|
||||
from .url import parse_url
|
||||
|
||||
|
||||
def path_length(path):
|
||||
"""Get the length of ``path``."""
|
||||
total_length = 0
|
||||
for item in path:
|
||||
if item[0] == cairo.PATH_MOVE_TO:
|
||||
old_point = item[1]
|
||||
elif item[0] == cairo.PATH_LINE_TO:
|
||||
new_point = item[1]
|
||||
length = distance(
|
||||
old_point[0], old_point[1], new_point[0], new_point[1])
|
||||
total_length += length
|
||||
old_point = new_point
|
||||
return total_length
|
||||
|
||||
|
||||
def point_following_path(path, width):
|
||||
"""Get the point at ``width`` distance on ``path``."""
|
||||
total_length = 0
|
||||
for item in path:
|
||||
if item[0] == cairo.PATH_MOVE_TO:
|
||||
old_point = item[1]
|
||||
elif item[0] == cairo.PATH_LINE_TO:
|
||||
new_point = item[1]
|
||||
length = distance(
|
||||
old_point[0], old_point[1], new_point[0], new_point[1])
|
||||
total_length += length
|
||||
if total_length < width:
|
||||
old_point = new_point
|
||||
else:
|
||||
length -= total_length - width
|
||||
angle = point_angle(
|
||||
old_point[0], old_point[1], new_point[0], new_point[1])
|
||||
x = cos(angle) * length + old_point[0]
|
||||
y = sin(angle) * length + old_point[1]
|
||||
return x, y
|
||||
|
||||
|
||||
def text(surface, node, draw_as_text=False):
|
||||
"""Draw a text ``node``."""
|
||||
font_family = (
|
||||
(node.get('font-family') or 'sans-serif').split(',')[0].strip('"\' '))
|
||||
font_style = getattr(
|
||||
cairo, f'font_slant_{node.get("font-style")}'.upper(),
|
||||
cairo.FONT_SLANT_NORMAL)
|
||||
node_font_weight = node.get('font-weight')
|
||||
if (node_font_weight and node_font_weight.isdigit()
|
||||
and int(node_font_weight) >= 550):
|
||||
node_font_weight = 'bold'
|
||||
font_weight = getattr(
|
||||
cairo, (f'font_weight_{node_font_weight}'.upper()),
|
||||
cairo.FONT_WEIGHT_NORMAL)
|
||||
surface.context.select_font_face(font_family, font_style, font_weight)
|
||||
surface.context.set_font_size(surface.font_size)
|
||||
ascent, descent, _, max_x_advance, max_y_advance = (
|
||||
surface.context.font_extents())
|
||||
|
||||
text_path_href = parse_url(node.get_href() or node.parent.get_href() or '')
|
||||
if text_path_href.fragment:
|
||||
text_path = surface.paths.get(text_path_href.fragment)
|
||||
else:
|
||||
text_path = None
|
||||
letter_spacing = size(surface, node.get('letter-spacing'))
|
||||
x_bearing, y_bearing, width, height = (
|
||||
surface.context.text_extents(node.text)[:4])
|
||||
|
||||
x, y, dx, dy, rotate = [], [], [], [], [0]
|
||||
if 'x' in node:
|
||||
x = [size(surface, i, 'x')
|
||||
for i in normalize(node['x']).strip().split(' ')]
|
||||
if 'y' in node:
|
||||
y = [size(surface, i, 'y')
|
||||
for i in normalize(node['y']).strip().split(' ')]
|
||||
if 'dx' in node:
|
||||
dx = [size(surface, i, 'x')
|
||||
for i in normalize(node['dx']).strip().split(' ')]
|
||||
if 'dy' in node:
|
||||
dy = [size(surface, i, 'y')
|
||||
for i in normalize(node['dy']).strip().split(' ')]
|
||||
if 'rotate' in node:
|
||||
rotate = [radians(float(i)) if i else 0
|
||||
for i in normalize(node['rotate']).strip().split(' ')]
|
||||
last_r = rotate[-1]
|
||||
letters_positions = zip_letters(x, y, dx, dy, rotate, node.text)
|
||||
|
||||
x_align = 0
|
||||
y_align = 0
|
||||
|
||||
text_anchor = node.get('text-anchor')
|
||||
if text_anchor == 'middle':
|
||||
x_align = - (width / 2 + x_bearing)
|
||||
if letter_spacing and node.text:
|
||||
x_align -= (len(node.text) - 1) * letter_spacing / 2
|
||||
elif text_anchor == 'end':
|
||||
x_align = - (width + x_bearing)
|
||||
if letter_spacing and node.text:
|
||||
x_align -= (len(node.text) - 1) * letter_spacing
|
||||
|
||||
# TODO: This is a hack. The rest of the baseline alignment tags of the SVG
|
||||
# 1.1 spec (section 10.9.2) are not supported. We only try to align things
|
||||
# that look like Western horizontal fonts.
|
||||
# Finally, we add a "display-anchor" attribute for aligning the specific
|
||||
# text rather than the font baseline.
|
||||
# Nonetheless, there are times when one needs to align text vertically, and
|
||||
# this will at least make that possible.
|
||||
if max_x_advance > 0 and max_y_advance == 0:
|
||||
display_anchor = node.get('display-anchor')
|
||||
alignment_baseline = (node.get('dominant-baseline') or
|
||||
node.get('alignment-baseline'))
|
||||
if display_anchor == 'middle':
|
||||
y_align = -height / 2 - y_bearing
|
||||
elif display_anchor == 'top':
|
||||
y_align = -y_bearing
|
||||
elif display_anchor == 'bottom':
|
||||
y_align = -height - y_bearing
|
||||
elif (alignment_baseline == 'central' or
|
||||
alignment_baseline == 'middle'):
|
||||
# TODO: This is wrong, Cairo gives no reasonable access to x-height
|
||||
# information, so we use font top-to-bottom
|
||||
y_align = (ascent + descent) / 2 - descent
|
||||
elif (alignment_baseline == 'text-before-edge' or
|
||||
alignment_baseline == 'before_edge' or
|
||||
alignment_baseline == 'top' or
|
||||
alignment_baseline == 'hanging' or
|
||||
alignment_baseline == 'text-top'):
|
||||
y_align = ascent
|
||||
elif (alignment_baseline == 'text-after-edge' or
|
||||
alignment_baseline == 'after_edge' or
|
||||
alignment_baseline == 'bottom' or
|
||||
alignment_baseline == 'text-bottom'):
|
||||
y_align = -descent
|
||||
|
||||
bounding_box = EMPTY_BOUNDING_BOX
|
||||
if text_path:
|
||||
surface.context.new_path()
|
||||
surface.stroke_and_fill = False
|
||||
surface.draw(text_path)
|
||||
surface.stroke_and_fill = True
|
||||
cairo_path = surface.context.copy_path_flat()
|
||||
surface.context.new_path()
|
||||
length = path_length(cairo_path) + x_bearing
|
||||
start_offset = size(surface, node.get('startOffset', 0), length)
|
||||
if node.tag == 'textPath':
|
||||
surface.text_path_width += start_offset
|
||||
surface.text_path_width += x_align
|
||||
bounding_box = extend_bounding_box(bounding_box, ((start_offset, 0),))
|
||||
|
||||
if node.text:
|
||||
for i, ((x, y, dx, dy, r), letter) in enumerate(letters_positions):
|
||||
if x:
|
||||
surface.cursor_d_position[0] = 0
|
||||
if y:
|
||||
surface.cursor_d_position[1] = 0
|
||||
surface.cursor_d_position[0] += dx or 0
|
||||
surface.cursor_d_position[1] += dy or 0
|
||||
text_extents = surface.context.text_extents(letter)
|
||||
extents = text_extents[4]
|
||||
if text_path:
|
||||
start = surface.text_path_width + surface.cursor_d_position[0]
|
||||
start_point = point_following_path(cairo_path, start)
|
||||
middle = start + extents / 2
|
||||
middle_point = point_following_path(cairo_path, middle)
|
||||
end = start + extents
|
||||
end_point = point_following_path(cairo_path, end)
|
||||
if i:
|
||||
extents += letter_spacing
|
||||
surface.text_path_width += extents
|
||||
if not all((start_point, middle_point, end_point)):
|
||||
continue
|
||||
if not 0 <= middle <= length:
|
||||
continue
|
||||
surface.context.save()
|
||||
surface.context.translate(*start_point)
|
||||
surface.context.rotate(point_angle(*(start_point + end_point)))
|
||||
surface.context.translate(0, surface.cursor_d_position[1])
|
||||
surface.context.move_to(0, 0)
|
||||
bounding_box = extend_bounding_box(
|
||||
bounding_box, ((end_point[0], text_extents[3]),))
|
||||
else:
|
||||
surface.context.save()
|
||||
x = surface.cursor_position[0] if x is None else x
|
||||
y = surface.cursor_position[1] if y is None else y
|
||||
if i:
|
||||
x += letter_spacing
|
||||
surface.context.move_to(x, y)
|
||||
cursor_position = x + extents, y
|
||||
surface.context.rel_move_to(*surface.cursor_d_position)
|
||||
surface.context.rel_move_to(x_align, y_align)
|
||||
surface.context.rotate(last_r if r is None else r)
|
||||
points = (
|
||||
(cursor_position[0] + x_align +
|
||||
surface.cursor_d_position[0],
|
||||
cursor_position[1] + y_align +
|
||||
surface.cursor_d_position[1]),
|
||||
(cursor_position[0] + x_align + text_extents[4] +
|
||||
surface.cursor_d_position[0],
|
||||
cursor_position[1] + y_align + text_extents[3] +
|
||||
surface.cursor_d_position[1]))
|
||||
bounding_box = extend_bounding_box(bounding_box, points)
|
||||
|
||||
# Only draw characters with 'content' (workaround for bug in cairo)
|
||||
if not letter.isspace():
|
||||
if draw_as_text:
|
||||
surface.context.show_text(letter)
|
||||
else:
|
||||
surface.context.text_path(letter)
|
||||
surface.context.restore()
|
||||
if not text_path:
|
||||
surface.cursor_position = cursor_position
|
||||
else:
|
||||
x = x[0] if x else surface.cursor_position[0]
|
||||
y = y[0] if y else surface.cursor_position[1]
|
||||
dx = dx[0] if dx else 0
|
||||
dy = dy[0] if dy else 0
|
||||
surface.cursor_position = (x + dx, y + dy)
|
||||
|
||||
# If a valid bounding box is calculated store it in the node
|
||||
if is_valid_bounding_box(bounding_box):
|
||||
node['text_bounding_box'] = bounding_box
|
||||
Reference in New Issue
Block a user