mirror of
https://github.com/Kozea/WeasyPrint.git
synced 2024-09-11 20:47:56 +03:00
Merge pull request #1922 from oshmoun/master
Add support for textLength and lengthAdjust in SVG text elements
This commit is contained in:
commit
4e9ad95499
@ -301,3 +301,73 @@ def test_text_rotate(assert_pixels):
|
||||
rotate="180" letter-spacing="2">abc</text>
|
||||
</svg>
|
||||
''')
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_text_text_length(assert_pixels):
|
||||
assert_pixels('''
|
||||
__RRRRRR____________
|
||||
__RRRRRR____________
|
||||
__BB__BB__BB________
|
||||
__BB__BB__BB________
|
||||
''', '''
|
||||
<style>
|
||||
@font-face { src: url(weasyprint.otf); font-family: weasyprint }
|
||||
@page { size: 20px 4px }
|
||||
svg { display: block }
|
||||
</style>
|
||||
<svg width="20px" height="4px" xmlns="http://www.w3.org/2000/svg">
|
||||
<text x="2" y="1.5" font-family="weasyprint" font-size="2" fill="red">
|
||||
abc
|
||||
</text>
|
||||
<text x="2" y="3.5" font-family="weasyprint" font-size="2" fill="blue"
|
||||
textLength="10">abc</text>
|
||||
</svg>
|
||||
''')
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_text_length_adjust_glyphs_only(assert_pixels):
|
||||
assert_pixels('''
|
||||
__RRRRRR____________
|
||||
__RRRRRR____________
|
||||
__BBBBBBBBBBBB______
|
||||
__BBBBBBBBBBBB______
|
||||
''', '''
|
||||
<style>
|
||||
@font-face { src: url(weasyprint.otf); font-family: weasyprint }
|
||||
@page { size: 20px 4px }
|
||||
svg { display: block }
|
||||
</style>
|
||||
<svg width="20px" height="4px" xmlns="http://www.w3.org/2000/svg">
|
||||
<text x="2" y="1.5" font-family="weasyprint" font-size="2" fill="red">
|
||||
abc
|
||||
</text>
|
||||
<text x="2" y="3.5" font-family="weasyprint" font-size="2" fill="blue"
|
||||
textLength="12" lengthAdjust="spacingAndGlyphs">abc</text>
|
||||
</svg>
|
||||
''')
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_text_length_adjust_spacing_and_glyphs(assert_pixels):
|
||||
assert_pixels('''
|
||||
__RR_RR_RR__________
|
||||
__RR_RR_RR__________
|
||||
__BBBB__BBBB__BBBB__
|
||||
__BBBB__BBBB__BBBB__
|
||||
''', '''
|
||||
<style>
|
||||
@font-face { src: url(weasyprint.otf); font-family: weasyprint }
|
||||
@page { size: 20px 4px }
|
||||
svg { display: block }
|
||||
</style>
|
||||
<svg width="20px" height="4px" xmlns="http://www.w3.org/2000/svg">
|
||||
<text x="2" y="1.5" font-family="weasyprint" font-size="2" fill="red"
|
||||
letter-spacing="1">abc</text>
|
||||
<text x="2" y="3.5" font-family="weasyprint" font-size="2" fill="blue"
|
||||
letter-spacing="1" textLength="16" lengthAdjust="spacingAndGlyphs">
|
||||
abc
|
||||
</text>
|
||||
</svg>
|
||||
''')
|
||||
|
@ -4,7 +4,7 @@ import contextlib
|
||||
import operator
|
||||
from colorsys import hsv_to_rgb, rgb_to_hsv
|
||||
from io import BytesIO
|
||||
from math import ceil, cos, floor, pi, sin, sqrt, tan
|
||||
from math import ceil, floor, pi, sqrt, tan
|
||||
from xml.etree import ElementTree
|
||||
|
||||
from PIL import Image
|
||||
@ -1073,7 +1073,7 @@ def draw_text(stream, textbox, offset_x, text_overflow, block_ellipsis):
|
||||
textbox.pango_layout.reactivate(textbox.style)
|
||||
stream.begin_text()
|
||||
emojis = draw_first_line(
|
||||
stream, textbox, text_overflow, block_ellipsis, x, y)
|
||||
stream, textbox, text_overflow, block_ellipsis, Matrix(d=-1, e=x, f=y))
|
||||
stream.end_text()
|
||||
|
||||
draw_emojis(stream, textbox.style['font_size'], x, y, emojis)
|
||||
@ -1097,8 +1097,7 @@ def draw_emojis(stream, font_size, x, y, emojis):
|
||||
stream.pop_state()
|
||||
|
||||
|
||||
def draw_first_line(stream, textbox, text_overflow, block_ellipsis, x, y,
|
||||
angle=0):
|
||||
def draw_first_line(stream, textbox, text_overflow, block_ellipsis, matrix):
|
||||
"""Draw the given ``textbox`` line to the document ``stream``."""
|
||||
# Don’t draw lines with only invisible characters
|
||||
if not textbox.text.strip():
|
||||
@ -1152,12 +1151,6 @@ def draw_first_line(stream, textbox, text_overflow, block_ellipsis, x, y,
|
||||
|
||||
utf8_text = textbox.pango_layout.text.encode()
|
||||
previous_utf8_position = 0
|
||||
|
||||
matrix = Matrix(1, 0, 0, -1, x, y)
|
||||
if angle:
|
||||
a, c = cos(angle), sin(angle)
|
||||
b, d = -c, a
|
||||
matrix = Matrix(a, b, c, d) @ matrix
|
||||
stream.text_matrix(*matrix.values)
|
||||
last_font = None
|
||||
string = ''
|
||||
|
@ -1,7 +1,8 @@
|
||||
"""Draw text."""
|
||||
|
||||
from math import inf, radians
|
||||
from math import cos, inf, radians, sin
|
||||
|
||||
from ..matrix import Matrix
|
||||
from .bounding_box import EMPTY_BOUNDING_BOX, extend_bounding_box
|
||||
from .utils import normalize, size
|
||||
|
||||
@ -68,9 +69,28 @@ def text(svg, node, font_size):
|
||||
([pl.pop(0) if pl else None for pl in (x, y, dx, dy, rotate)], char)
|
||||
for char in node.text]
|
||||
|
||||
letter_spacing = svg.length(node.get('letter-spacing'), font_size)
|
||||
text_length = svg.length(node.get('textLength'), font_size)
|
||||
scale_x = 1
|
||||
if text_length and node.text:
|
||||
# calculate the number of spaces to be considered for the text
|
||||
spaces_count = len(node.text) - 1
|
||||
if normalize(node.attrib.get('lengthAdjust')) == 'spacingAndGlyphs':
|
||||
# scale letter_spacing up/down to textLength
|
||||
width_with_spacing = width + spaces_count * letter_spacing
|
||||
letter_spacing *= text_length / width_with_spacing
|
||||
# calculate the glyphs scaling factor by:
|
||||
# - deducting the scaled letter_spacing from textLength
|
||||
# - dividing the calculated value by the original width
|
||||
spaceless_text_length = text_length - spaces_count * letter_spacing
|
||||
scale_x = spaceless_text_length / width
|
||||
elif spaces_count:
|
||||
# adjust letter spacing to fit textLength
|
||||
letter_spacing = (text_length - width) / spaces_count
|
||||
width = text_length
|
||||
|
||||
# Align text box horizontally
|
||||
x_align = 0
|
||||
letter_spacing = svg.length(node.get('letter-spacing'), font_size)
|
||||
text_anchor = node.get('text-anchor')
|
||||
# TODO: use real values
|
||||
ascent, descent = font_size * .8, font_size * .2
|
||||
@ -134,8 +154,10 @@ def text(svg, node, font_size):
|
||||
letter, style, svg.context, inf, 0)
|
||||
x = svg.cursor_position[0] if x is None else x
|
||||
y = svg.cursor_position[1] if y is None else y
|
||||
width *= scale_x
|
||||
if i:
|
||||
x += letter_spacing
|
||||
|
||||
x_position = x + svg.cursor_d_position[0] + x_align
|
||||
y_position = y + svg.cursor_d_position[1] + y_align
|
||||
cursor_position = x + width, y
|
||||
@ -150,9 +172,12 @@ def text(svg, node, font_size):
|
||||
|
||||
layout.reactivate(style)
|
||||
svg.fill_stroke(node, font_size, text=True)
|
||||
matrix = Matrix(a=scale_x, d=-1, e=x_position, f=y_position)
|
||||
if angle:
|
||||
a, c = cos(angle), sin(angle)
|
||||
matrix = Matrix(a, -c, c, a) @ matrix
|
||||
emojis = draw_first_line(
|
||||
svg.stream, TextBox(layout, style), 'none', 'none',
|
||||
x_position, y_position, angle)
|
||||
svg.stream, TextBox(layout, style), 'none', 'none', matrix)
|
||||
emoji_lines.append((font_size, x, y, emojis))
|
||||
svg.cursor_position = cursor_position
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user