def draw_gradient(svg, node, gradient, font_size, opacity, stroke):
    """Draw given gradient node."""
    # TODO: merge with Gradient.draw
    positions = []
    colors = []
    for child in gradient:
        positions.append(max(
            positions[-1] if positions else 0,
            size(child.get('offset'), font_size, 1)))
        stop_opacity = alpha_value(child.get('stop-opacity', 1)) * opacity
        stop_color = color(child.get('stop-color', 'black'))
        stop_color.alpha *= stop_opacity
        colors.append(stop_color)

    if not colors:
        return False
    elif len(colors) == 1:
        svg.stream.set_color(colors[0])
        return True

    bounding_box = svg.calculate_bounding_box(node, font_size, stroke)
    if not is_valid_bounding_box(bounding_box):
        return False
    if gradient.get('gradientUnits') == 'userSpaceOnUse':
        width, height = svg.inner_width, svg.inner_height
        matrix = Matrix()
    else:
        width, height = 1, 1
        e, f, a, d = bounding_box
        matrix = Matrix(a=a, d=d, e=e, f=f)

    spread = gradient.get('spreadMethod', 'pad')
    if spread in ('repeat', 'reflect'):
        if positions[0] > 0:
            positions.insert(0, 0)
            colors.insert(0, colors[0])
        if positions[-1] < 1:
            positions.append(1)
            colors.append(colors[-1])
    else:
        # Add explicit colors at boundaries if needed, because PDF doesn’t
        # extend color stops that are not displayed
        if positions[0] == positions[1]:
            if gradient.tag == 'radialGradient':
                # Avoid negative radius for radial gradients
                positions.insert(0, 0)
            else:
                positions.insert(0, positions[0] - 1)
            colors.insert(0, colors[0])
        if positions[-2] == positions[-1]:
            positions.append(positions[-1] + 1)
            colors.append(colors[-1])

    if 'gradientTransform' in gradient.attrib:
        transform_matrix = transform(
            gradient.get('gradientTransform'), font_size,
            svg.normalized_diagonal)
        matrix = transform_matrix @ matrix

    if gradient.tag == 'linearGradient':
        shading_type = 2
        x1, y1 = (
            size(gradient.get('x1', 0), font_size, width),
            size(gradient.get('y1', 0), font_size, height))
        x2, y2 = (
            size(gradient.get('x2', '100%'), font_size, width),
            size(gradient.get('y2', 0), font_size, height))
        positions, colors, coords = spread_linear_gradient(
            spread, positions, colors, x1, y1, x2, y2, bounding_box, matrix)
    else:
        assert gradient.tag == 'radialGradient'
        shading_type = 3
        cx, cy = (
            size(gradient.get('cx', '50%'), font_size, width),
            size(gradient.get('cy', '50%'), font_size, height))
        r = size(gradient.get('r', '50%'), font_size, hypot(width, height))
        fx, fy = (
            size(gradient.get('fx', cx), font_size, width),
            size(gradient.get('fy', cy), font_size, height))
        fr = size(gradient.get('fr', 0), font_size, hypot(width, height))
        positions, colors, coords = spread_radial_gradient(
            spread, positions, colors, fx, fy, fr, cx, cy, r, width, height,
            matrix)

    alphas = [color[3] for color in colors]
    alpha_couples = [
        (alphas[i], alphas[i + 1])
        for i in range(len(alphas) - 1)]
    color_couples = [
        [colors[i][:3], colors[i + 1][:3], 1]
        for i in range(len(colors) - 1)]

    # Premultiply colors
    for i, alpha in enumerate(alphas):
        if alpha == 0:
            if i > 0:
                color_couples[i - 1][1] = color_couples[i - 1][0]
            if i < len(colors) - 1:
                color_couples[i][0] = color_couples[i][1]
    for i, (a0, a1) in enumerate(alpha_couples):
        if 0 not in (a0, a1) and (a0, a1) != (1, 1):
            color_couples[i][2] = a0 / a1

    bx1, by1 = 0, 0
    if 'gradientTransform' in gradient.attrib:
        bx1, by1 = transform_matrix.invert.transform_point(bx1, by1)
        bx2, by2 = transform_matrix.invert.transform_point(width, height)
        width, height = bx2 - bx1, by2 - by1

        # Ensure that width and height are positive to please some PDF readers
        if bx1 > bx2:
            width = -width
            bx1, bx2 = bx2, bx1
        if by1 > by2:
            height = -height
            by1, by2 = by2, by1

    pattern = svg.stream.add_pattern(
        bx1, by1, width, height, width, height, matrix @ svg.stream.ctm)
    group = pattern.add_group(bx1, by1, width, height)

    domain = (positions[0], positions[-1])
    extend = spread not in ('repeat', 'reflect')
    encode = (len(colors) - 1) * (0, 1)
    bounds = positions[1:-1]
    sub_functions = (
        group.create_interpolation_function(domain, c0, c1, n)
        for c0, c1, n in color_couples)
    function = group.create_stitching_function(
        domain, encode, bounds, sub_functions)
    shading = group.add_shading(
        shading_type, 'RGB', domain, coords, extend, function)

    if any(alpha != 1 for alpha in alphas):
        alpha_stream = group.set_alpha_state(bx1, by1, width, height)
        domain = (positions[0], positions[-1])
        extend = spread not in ('repeat', 'reflect')
        encode = (len(colors) - 1) * (0, 1)
        bounds = positions[1:-1]
        sub_functions = (
            group.create_interpolation_function((0, 1), [c0], [c1], 1)
            for c0, c1 in alpha_couples)
        function = group.create_stitching_function(
            domain, encode, bounds, sub_functions)
        alpha_shading = alpha_stream.add_shading(
            shading_type, 'Gray', domain, coords, extend, function)
        alpha_stream.stream = [f'/{alpha_shading.id} sh']

    group.paint_shading(shading.id)
    pattern.set_alpha(1)
    pattern.draw_x_object(group.id)
    svg.stream.set_color_space('Pattern', stroke=stroke)
    svg.stream.set_color_special(pattern.id, stroke=stroke)
    return True
Draw given gradient node.
weasyprint/svg/defs.py