1
1
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:
Guillaume Ayoub 2023-08-19 16:30:43 +02:00 committed by GitHub
commit 4e9ad95499
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 102 additions and 14 deletions

View File

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

View File

@ -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``."""
# Dont 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 = ''

View File

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