Beispiel #1
0
def _apply_paint(
        svg_defs: etree.Element,
        el: etree.Element,
        paint: Paint,
        upem_to_vbox: Affine2D,
        reuse_cache: ReuseCache,
        transform: Affine2D = Affine2D.identity(),
):
    # If you modify the attributes _apply_paint can set also modify _PAINT_ATTRIB_APPLY_PAINT_MAY_SET
    if isinstance(paint, PaintSolid):
        _apply_solid_paint(el, paint)
    elif isinstance(paint, (PaintLinearGradient, PaintRadialGradient)):
        # Gradient paint coordinates are in UPEM space, we want them in SVG viewBox
        # so that they match the SVGPath.d coordinates (that we copy unmodified).
        paint = _map_gradient_coordinates(paint, upem_to_vbox)
        # Likewise transforms refer to UPEM so they must be adjusted for SVG
        if transform != Affine2D.identity():
            transform = Affine2D.compose_ltr(
                (upem_to_vbox.inverse(), transform, upem_to_vbox))
        _apply_gradient_paint(svg_defs, el, paint, reuse_cache, transform)
    elif is_transform(paint):
        transform @= paint.gettransform()
        child = paint.paint  # pytype: disable=attribute-error
        _apply_paint(svg_defs, el, child, upem_to_vbox, reuse_cache, transform)
    else:
        raise NotImplementedError(type(paint))
Beispiel #2
0
def _apply_gradient_common_parts(
        gradient: etree.Element,
        paint: _GradientPaint,
        transform: Affine2D = Affine2D.identity(),
):
    gradient.attrib["gradientUnits"] = "userSpaceOnUse"
    for stop in paint.stops:
        stop_el = etree.SubElement(gradient, "stop")
        stop_el.attrib["offset"] = _ntos(stop.stopOffset)
        stop_el.attrib["stop-color"] = stop.color.opaque().to_string()
        if stop.color.alpha != 1.0:
            stop_el.attrib["stop-opacity"] = _ntos(stop.color.alpha)
    if paint.extend != Extend.PAD:
        gradient.attrib["spreadMethod"] = paint.extend.name.lower()

    transform = transform.round(_DEFAULT_ROUND_NDIGITS)
    if transform != Affine2D.identity():
        # Safari has a bug which makes it reject a gradient if gradientTransform
        # contains an 'involutory matrix' (i.e. matrix whose inverse equals itself,
        # such that M @ M == Identity, e.g. reflection), hence the following hack:
        # https://github.com/googlefonts/nanoemoji/issues/268
        # https://en.wikipedia.org/wiki/Involutory_matrix
        # TODO: Remove once the bug gets fixed
        if transform @ transform == Affine2D.identity():
            transform = transform._replace(a=transform.a + 0.00001)
            assert transform.inverse() != transform
        gradient.attrib["gradientTransform"] = transform.tostring()
Beispiel #3
0
def _radial_gradient_paint(
    svg_defs: etree.Element,
    svg_path: etree.Element,
    ttfont: ttLib.TTFont,
    view_box: Rect,
    stops: Sequence[_ColorStop],
    extend: Extend,
    c0: Point,
    c1: Point,
    r0: int,
    r1: int,
    transform: Affine2D,
):
    # map centres and radii from UPEM to SVG space
    upem_to_vbox = _emsquare_to_viewbox(ttfont["head"].unitsPerEm, view_box)
    c0 = upem_to_vbox.map_point(c0)
    c1 = upem_to_vbox.map_point(c1)
    # _emsquare_to_viewbox guarantees view_box is square so scaling radii is ok
    r0 = upem_to_vbox.map_point((r0, 0)).x
    r1 = upem_to_vbox.map_point((r1, 0)).x

    # COLRv1 centre points aren't affected by the gradient Affine2x2, whereas in SVG
    # gradientTransform applies to everything; to prevent that, we must also map
    # the centres with the inverse of gradientTransform, so they won't move.
    inverse_transform = transform.inverse()
    fx, fy = inverse_transform.map_point(c0)
    cx, cy = inverse_transform.map_point(c1)

    gradient = etree.SubElement(svg_defs, "radialGradient")
    gradient_id = gradient.attrib["id"] = f"g{len(svg_defs)}"
    gradient.attrib["gradientUnits"] = "userSpaceOnUse"
    gradient.attrib["fx"] = _ntos(fx)
    gradient.attrib["fy"] = _ntos(fy)
    gradient.attrib["fr"] = _ntos(r0)
    gradient.attrib["cx"] = _ntos(cx)
    gradient.attrib["cy"] = _ntos(cy)
    gradient.attrib["r"] = _ntos(r1)
    if transform != Affine2D.identity():
        gradient.attrib["gradientTransform"] = _svg_matrix(transform)
    if extend != Extend.PAD:
        gradient.attrib["spreadMethod"] = extend.name.lower()

    palette = ttfont["CPAL"].palettes[0]
    for stop in stops:
        stop_el = etree.SubElement(gradient, "stop")
        stop_el.attrib["offset"] = _ntos(stop.offset)
        cpal_color = palette[stop.palette_index]
        svg_color, svg_opacity = _svg_color_and_opacity(cpal_color, stop.alpha)
        stop_el.attrib["stop-color"] = svg_color
        if svg_opacity:
            stop_el.attrib["stop-opacity"] = svg_opacity

    svg_path.attrib["fill"] = f"url(#{gradient_id})"
Beispiel #4
0
def _colr_v1_paint_to_svg(
        ttfont: ttLib.TTFont,
        glyph_set: Mapping[str, Any],
        parent_el: etree.Element,
        svg_defs: etree.Element,
        font_to_vbox: Affine2D,
        ot_paint: otTables.Paint,
        reuse_cache: ReuseCache,
        transform: Affine2D = Affine2D.identity(),
):
    def descend(parent: etree.Element, paint: otTables.Paint):
        _colr_v1_paint_to_svg(
            ttfont,
            glyph_set,
            parent,
            svg_defs,
            font_to_vbox,
            paint,
            reuse_cache,
            transform=transform,
        )

    if ot_paint.Format == PaintSolid.format:
        _apply_solid_ot_paint(parent_el, ttfont, ot_paint)
    elif ot_paint.Format in _GRADIENT_PAINT_FORMATS:
        _apply_gradient_ot_paint(svg_defs, parent_el, ttfont, font_to_vbox,
                                 ot_paint, reuse_cache, transform)
    elif ot_paint.Format == PaintGlyph.format:
        layer_glyph = ot_paint.Glyph
        svg_path = etree.SubElement(parent_el, "path")

        # This only occurs if path is reused; we could wire up use. But for now ... not.
        if transform != Affine2D.identity():
            svg_transform = Affine2D.compose_ltr(
                (font_to_vbox.inverse(), transform, font_to_vbox))
            svg_path.attrib["transform"] = _svg_matrix(svg_transform)
            # we must reset the current user space when setting the 'transform'
            # attribute on a <path>, since that already affects the gradients used
            # and we don't want the transform to be applied twice to gradients:
            # https://github.com/googlefonts/nanoemoji/issues/334
            transform = Affine2D.identity()

        descend(svg_path, ot_paint.Paint)

        _draw_svg_path(svg_path, glyph_set, layer_glyph, font_to_vbox)
    elif is_transform(ot_paint.Format):
        paint = Paint.from_ot(ot_paint)
        transform @= paint.gettransform()
        descend(parent_el, ot_paint.Paint)
    elif ot_paint.Format == PaintColrLayers.format:
        layerList = ttfont["COLR"].table.LayerList.Paint
        assert layerList, "Paint layers without a layer list :("
        for child_paint in layerList[ot_paint.
                                     FirstLayerIndex:ot_paint.FirstLayerIndex +
                                     ot_paint.NumLayers]:
            descend(parent_el, child_paint)

    elif ot_paint.Format == PaintComposite.format and (
            ot_paint.CompositeMode == CompositeMode.SRC_IN
            and ot_paint.BackdropPaint.Format == PaintSolid.format):
        # Only simple group opacity for now
        color = _color(
            ttfont,
            ot_paint.BackdropPaint.PaletteIndex,
            ot_paint.BackdropPaint.Alpha,
        )
        if color[:3] != (0, 0, 0):
            raise NotImplementedError(color)
        g = etree.SubElement(parent_el, "g")
        g.attrib["opacity"] = ntos(color.alpha)
        descend(g, ot_paint.SourcePaint)

    else:
        raise NotImplementedError(ot_paint.Format)