Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion tests/draw/svg/test_gradients.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def test_linear_gradient_multicolor_userspace(assert_pixels):
assert_pixels('''
__________
BBBBBBBBBB
BBBBBBBBBB
zBBBBBBBBz
RRRRRRRRRR
RRRRRRRRRR
GGGGGGGGGG
Expand Down Expand Up @@ -810,6 +810,39 @@ def test_radial_gradient_userspace(assert_pixels):
''')


@assert_no_logs
def test_radial_gradient_negative_rect_userspace(assert_pixels):
assert_pixels('''
____________
rrrrrrrrrrr_
rrrrrrrrrrr_
rrrrrBBrrrr_
rrrrBBBBrrr_
rrrBBBBBBrr_
rrrBBBBBBrr_
rrrrBBBBrrr_
rrrrrBBrrrr_
rrrrrrrrrrr_
rrrrrrrrrrr_
____________
''', '''
<style>
@page { size: 12px }
svg { display: block }
</style>
<svg width="12px" height="12px" xmlns="http://www.w3.org/2000/svg">
<defs>
<radialGradient id="grad" cx="6" cy="6" r="5" fx="6" fy="6" fr="2"
gradientUnits="userSpaceOnUse">
<stop stop-color="blue" offset="25%"></stop>
<stop stop-color="red" offset="25%"></stop>
</radialGradient>
</defs>
<rect x="-9" y="1" width="20" height="10" fill="url(#grad)" />
</svg>
''')


@assert_no_logs
def test_radial_gradient_userspace_transform(assert_pixels):
assert_pixels('''
Expand Down
8 changes: 8 additions & 0 deletions weasyprint/draw/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,14 @@ def draw_first_line(stream, textbox, text_overflow, block_ellipsis, matrix):
f = -stream.logical_rect.y
f = f * FROM_UNITS / font_size - font_size
emojis.append([image, font, a, d, x_advance, f])
elif font.colr:
svg_data = get_hb_object_data(font.hb_font, 'colr', glyph_id)
if svg_data:
tree = ElementTree.fromstring(svg_data)
image = SVGImage(tree, None, None, None)
a = d = 1
e = x_advance - kerning
emojis.append([image, font, a, d, e, -textbox.baseline])

x_advance += (logical_width + offset - kerning) / 1000

Expand Down
18 changes: 15 additions & 3 deletions weasyprint/pdf/fonts.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@

from ..logger import LOGGER
from ..text.constants import PANGO_STRETCH_PERCENT
from ..text.ffi import FROM_UNITS, ffi, harfbuzz, harfbuzz_subset, pango
from ..text.fonts import get_hb_object_data, get_pango_font_hb_face

from ..text.ffi import ( # isort:skip
FROM_UNITS, ffi, harfbuzz, harfbuzz_subset, harfbuzz_vector, pango)


class Font:
def __init__(self, pango_font, description, font_size):
Expand Down Expand Up @@ -97,6 +99,16 @@ def __init__(self, pango_font, description, font_size):
self.upem = harfbuzz.hb_face_get_upem(self.hb_face)
self.png = harfbuzz.hb_ot_color_has_png(self.hb_face)
self.svg = harfbuzz.hb_ot_color_has_svg(self.hb_face)
if harfbuzz.hb_version_atleast(7, 0, 0):
self.colr = (
harfbuzz.hb_ot_color_has_paint(self.hb_face) or
harfbuzz.hb_ot_color_has_layers(self.hb_face))
if self.colr and not harfbuzz_vector:
LOGGER.warning(
'Please install harfbuzz-vector to display '
f'"{self.family}" COLR emoji fonts.')
else:
self.colr = False
self.glyph_count = harfbuzz.hb_face_get_glyph_count(self.hb_face)
self.stemv = 80
self.stemh = 80
Expand Down Expand Up @@ -160,7 +172,7 @@ def clean(self, to_unicode, hinting):
self.file_content = partial_font.getvalue()

# Remove images.
if self.png or self.svg:
if self.png or self.svg or self.colr:
full_font = io.BytesIO(self.file_content)
ttfont = TTFont(full_font, fontNumber=self.index)
try:
Expand All @@ -175,7 +187,7 @@ def clean(self, to_unicode, hinting):
else:
for glyph in ttfont['glyf'].glyphs:
ttfont['glyf'][glyph] = ttFont.getTableModule('glyf').Glyph()
for table_name in ('CBDT', 'CBLC', 'SVG '):
for table_name in ('CBDT', 'CBLC', 'SVG ', 'COLR'):
if table_name in ttfont:
del ttfont[table_name]
output_font = io.BytesIO()
Expand Down
6 changes: 2 additions & 4 deletions weasyprint/svg/defs.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,11 @@ def draw_gradient(svg, node, gradient, font_size, opacity, stroke):
if not is_valid_bounding_box(bounding_box):
return False
if gradient.get('gradientUnits') == 'userSpaceOnUse':
width, height = svg.inner_width, svg.inner_height
bx1, by1 = bounding_box[:2]
bx1, by1, width, height = bounding_box
matrix = Matrix()
else:
width, height = 1, 1
bx1, by1, width, height = 0, 0, 1, 1
e, f, a, d = bounding_box
bx1, by1 = 0, 0
matrix = Matrix(a=a, d=d, e=e, f=f)

spread = gradient.get('spreadMethod', 'pad')
Expand Down
9 changes: 5 additions & 4 deletions weasyprint/svg/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def text(svg, node, font_size):
(x_position + width, y_position - baseline + height))
# TODO: Use ink extents instead of logical from line_break.line_size().
node.text_bounding_box = extend_bounding_box(node.text_bounding_box, points)
layouts.append((layout, x_position, y_position, x, y, angle))
layouts.append((layout, x_position, y_position, x, y, angle, baseline))

# Set state after we have bounding box for gradients and patterns, before drawing.
svg.stream.push_state()
Expand All @@ -165,15 +165,16 @@ def text(svg, node, font_size):

# Draw letters.
emoji_lines = []
for layout, x_position, y_position, x, y, angle in layouts:
for layout, x_position, y_position, x, y, angle, baseline in layouts:
layout.reactivate(style)
svg.fill_stroke(fill, stroke, 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', matrix)
text_box = TextBox(layout, style)
text_box.baseline = baseline
emojis = draw_first_line(svg.stream, text_box, 'none', 'none', matrix)
emoji_lines.append((x, y, emojis))

svg.stream.end_text()
Expand Down
27 changes: 27 additions & 0 deletions weasyprint/text/ffi.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
hb_blob_t * hb_ot_color_glyph_reference_png (hb_font_t *font, hb_codepoint_t glyph);
bool hb_ot_color_has_svg (hb_face_t *face);
hb_blob_t * hb_ot_color_glyph_reference_svg (hb_face_t *face, hb_codepoint_t glyph);
bool hb_ot_color_has_layers (hb_face_t *face);
bool hb_ot_color_has_paint (hb_face_t *face);
void hb_blob_destroy (hb_blob_t *blob);
unsigned int hb_face_get_table_tags (
const hb_face_t *face, unsigned int start_offset, unsigned int *table_count,
Expand Down Expand Up @@ -80,6 +82,27 @@
hb_set_t * hb_subset_input_set (
hb_subset_input_t *input, hb_subset_sets_t set_type);

// Harfbuzz Vector

typedef enum {
HB_VECTOR_FORMAT_INVALID = 0,
HB_VECTOR_FORMAT_SVG = 1937139488, /* hb_tag('svg ') */
HB_VECTOR_FORMAT_PDF = 1885627936, /* hb_tag('pdf ') */
} hb_vector_format_t;

typedef enum {
HB_VECTOR_EXTENTS_MODE_NONE,
HB_VECTOR_EXTENTS_MODE_EXPAND,
} hb_vector_extents_mode_t;

typedef ... hb_vector_paint_t;

hb_vector_paint_t * hb_vector_paint_create_or_fail (hb_vector_format_t format);
void hb_vector_paint_glyph(
hb_vector_paint_t *paint, hb_font_t *font, hb_codepoint_t glyph,
hb_vector_extents_mode_t extents_mode);
hb_blob_t * hb_vector_paint_render (hb_vector_paint_t *paint);

// Pango

typedef unsigned int guint;
Expand Down Expand Up @@ -489,6 +512,10 @@ def _dlopen(ffi, *names, allow_fail=False):
ffi, 'libharfbuzz-subset-0', 'harfbuzz-subset', 'harfbuzz-subset-0.0',
'libharfbuzz-subset.so.0', 'libharfbuzz-subset.0.dylib', 'libharfbuzz-subset-0.dll',
allow_fail=True)
harfbuzz_vector = _dlopen(
ffi, 'libharfbuzz-vector-0', 'harfbuzz-vector', 'harfbuzz-vector-0.0',
'libharfbuzz-vector.so.0', 'libharfbuzz-vector.0.dylib', 'libharfbuzz-vector-0.dll',
allow_fail=True)
fontconfig = _dlopen(
ffi, 'libfontconfig-1', 'fontconfig-1', 'fontconfig',
'libfontconfig.so.1', 'libfontconfig.1.dylib', 'libfontconfig-1.dll')
Expand Down
12 changes: 10 additions & 2 deletions weasyprint/text/fonts.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
CAPS_KEYS, EAST_ASIAN_KEYS, FONTCONFIG_STRETCH, FONTCONFIG_STYLE, FONTCONFIG_WEIGHT,
LIGATURE_KEYS, NUMERIC_KEYS, PANGO_STRETCH, PANGO_STYLE, PANGO_VARIANT)
from .ffi import ( # isort:skip
FROM_UNITS, TO_UNITS, ffi, fontconfig, gobject, harfbuzz, pango, pangoft2,
unicode_to_char_p)
FROM_UNITS, TO_UNITS, ffi, fontconfig, gobject, harfbuzz, harfbuzz_vector, pango,
pangoft2, unicode_to_char_p)

PREFERRED_ENCODING = getpreferredencoding(False)

Expand Down Expand Up @@ -367,6 +367,14 @@ def get_hb_object_data(hb_object, ot_color=None, glyph=None):
hb_blob = harfbuzz.hb_ot_color_glyph_reference_png(hb_object, glyph)
elif ot_color == 'svg':
hb_blob = harfbuzz.hb_ot_color_glyph_reference_svg(hb_object, glyph)
elif ot_color == 'colr':
if not harfbuzz_vector:
return
paint = harfbuzz_vector.hb_vector_paint_create_or_fail(
harfbuzz_vector.HB_VECTOR_FORMAT_SVG)
harfbuzz_vector.hb_vector_paint_glyph(
paint, hb_object, glyph, harfbuzz.HB_VECTOR_EXTENTS_MODE_EXPAND)
hb_blob = harfbuzz_vector.hb_vector_paint_render(paint)
else:
hb_blob = harfbuzz.hb_face_reference_blob(hb_object)
with ffi.new('unsigned int *') as length:
Expand Down
Loading