Пример #1
0
def _create_glyph(color_glyph: ColorGlyph,
                  painted_layer: PaintedLayer) -> Glyph:
    ufo = color_glyph.ufo

    glyph = ufo.newGlyph(
        _next_name(ufo, lambda i: f"{color_glyph.glyph_name}.{i}"))
    glyph_names = [glyph.name]
    glyph.width = ufo.info.unitsPerEm

    svg_units_to_font_units = color_glyph.transform_for_font_space()

    if painted_layer.reuses:
        # Shape repeats, form a composite
        base_glyph = ufo.newGlyph(
            _next_name(ufo, lambda i: f"{glyph.name}.component.{i}"))
        glyph_names.append(base_glyph.name)

        draw_svg_path(painted_layer.path, base_glyph.getPen(),
                      svg_units_to_font_units)

        glyph.components.append(
            Component(baseGlyph=base_glyph.name,
                      transformation=Affine2D.identity()))

        for transform in painted_layer.reuses:
            # We already redrew the component into font space, don't redo it
            # scale x/y translation and flip y movement to match font space
            transform = transform._replace(
                e=transform.e * svg_units_to_font_units.a,
                f=transform.f * svg_units_to_font_units.d,
            )
            glyph.components.append(
                Component(baseGlyph=base_glyph.name, transformation=transform))
    else:
        # Not a composite, just draw directly on the glyph
        draw_svg_path(painted_layer.path, glyph.getPen(),
                      svg_units_to_font_units)

    ufo.glyphOrder += glyph_names

    return glyph
Пример #2
0
def _migrate_paths_to_ufo_glyphs(color_glyph: ColorGlyph,
                                 glyph_cache: GlyphReuseCache) -> ColorGlyph:
    svg_units_to_font_units = color_glyph.transform_for_font_space()

    # Walk through the color glyph, where we see a PaintGlyph take the path out of it,
    # move the path into font coordinates, generate a ufo glyph, and push the name of
    # the ufo glyph into the PaintGlyph
    def _update_paint_glyph(paint):
        if paint.format != PaintGlyph.format:
            return paint

        if glyph_cache.is_known_glyph(paint.glyph):
            return paint

        assert paint.glyph.startswith(
            "M"), f"{paint.glyph} doesn't look like a path"
        path_in_font_space = (SVGPath(
            d=paint.glyph).apply_transform(svg_units_to_font_units).d)

        reuse_result = glyph_cache.try_reuse(path_in_font_space)
        if reuse_result is not None:
            # TODO: when is it more compact to use a new transforming glyph?
            child_transform = Affine2D.identity()
            child_paint = paint.paint
            if is_transform(child_paint):
                child_transform = child_paint.gettransform()
                child_paint = child_paint.paint

            # sanity check: GlyphReuseCache.try_reuse would return None if overflowed
            assert fixed_safe(*reuse_result.transform)
            overflows = False

            # TODO: handle gradient anywhere in subtree, not only as direct child of
            # PaintGlyph or PaintTransform
            if is_gradient(child_paint):
                # We have a gradient so we need to reverse the effect of the
                # reuse_result.transform. First we try to apply the combined transform
                # to the gradient's geometry; but this may overflow OT integer bounds,
                # in which case we pass through gradient unscaled
                transform = Affine2D.compose_ltr(
                    (child_transform, reuse_result.transform.inverse()))
                # skip reuse if combined transform overflows OT int bounds
                overflows = not fixed_safe(*transform)
                if not overflows:
                    try:
                        child_paint = child_paint.apply_transform(transform)
                    except OverflowError:
                        child_paint = transformed(transform, child_paint)

            if not overflows:
                return transformed(
                    reuse_result.transform,
                    PaintGlyph(
                        glyph=reuse_result.glyph_name,
                        paint=child_paint,
                    ),
                )

        glyph = _create_glyph(color_glyph, paint, path_in_font_space)
        glyph_cache.add_glyph(glyph.name, path_in_font_space)

        return dataclasses.replace(paint, glyph=glyph.name)

    return color_glyph.mutating_traverse(_update_paint_glyph)
Пример #3
0
def _add_glyph(svg: SVG, color_glyph: ColorGlyph, reuse_cache: ReuseCache):
    svg_defs = svg.xpath_one("//svg:defs")

    # each glyph gets a group of its very own
    svg_g = svg.append_to("/svg:svg", etree.Element("g"))
    svg_g.attrib["id"] = f"glyph{color_glyph.glyph_id}"

    view_box = color_glyph.svg.view_box()
    if view_box is None:
        raise ValueError(f"{color_glyph.filename} must declare view box")

    # https://github.com/googlefonts/nanoemoji/issues/58: group needs transform
    svg_g.attrib["transform"] = _svg_matrix(
        color_glyph.transform_for_otsvg_space())

    vbox_to_upem = color_glyph.transform_for_font_space()
    upem_to_vbox = vbox_to_upem.inverse()

    # copy the shapes into our svg
    el_by_path = {(): svg_g}
    complete_paths = set()
    nth_paint_glyph = 0

    for root in color_glyph.painted_layers:
        for context in root.breadth_first():
            if any(c == context.path[:len(c)] for c in complete_paths):
                continue

            parent_el = svg_g
            path = context.path
            while path:
                if path in el_by_path:
                    parent_el = el_by_path[path]
                    break
                path = path[:-1]

            if isinstance(context.paint, PaintGlyph):
                glyph_name = _paint_glyph_name(color_glyph, nth_paint_glyph)
                assert (glyph_name in reuse_cache.glyph_elements
                        ), f"Missing entry for {glyph_name}"

                reuse_result = reuse_cache.reuse_results.get(glyph_name, None)

                if reuse_result:
                    reused_glyph_name = reuse_result.glyph_name
                    reused_el = reuse_cache.glyph_elements[reused_glyph_name]
                    reused_el_tag = etree.QName(reused_el.tag).localname
                    if reused_el_tag == "use":
                        # if reused_el is a <use> it means _migrate_to_defs has already
                        # replaced a parent-less <path> with a <use> pointing to it, and
                        # has appended the reused path to <defs>. Assert that's the case
                        assert _use_href(reused_el) == reused_glyph_name
                        reused_el = svg.xpath_one(
                            f'//svg:defs/svg:path[@id="{reused_glyph_name}"]',
                        )
                    elif reused_el_tag == "path":
                        # we need to refer to you, it's important you have identity
                        reused_el.attrib["id"] = reused_glyph_name
                    else:
                        raise AssertionError(reused_el_tag)

                    svg_use = _create_use_element(svg, parent_el, reuse_result)
                    _apply_paint(
                        svg_defs,
                        svg_use,
                        context.paint.paint,  # pytype: disable=attribute-error
                        upem_to_vbox,
                        reuse_cache,
                    )

                    # In two cases, we need to push the reused element to the outer
                    # <defs> and replace its first occurence with a <use>:
                    # 1) If reuse spans multiple glyphs, as Adobe Illustrator
                    #    doesn't support direct references between glyphs:
                    #    https://github.com/googlefonts/nanoemoji/issues/264
                    # 2) If the reused_el has attributes <use> cannot override
                    #    https://github.com/googlefonts/nanoemoji/issues/337
                    if color_glyph.glyph_name != _color_glyph_name(
                            reused_glyph_name) or _attrib_apply_paint_uses(
                                reused_el):
                        _migrate_to_defs(svg, reused_el, reuse_cache,
                                         reuse_result)

                else:
                    el = reuse_cache.glyph_elements[glyph_name]
                    _apply_paint(
                        svg_defs,
                        el,
                        context.paint.paint,  # pytype: disable=attribute-error
                        upem_to_vbox,
                        reuse_cache,
                    )
                    parent_el.append(el)  # pytype: disable=attribute-error

                # don't update el_by_path because we're declaring this path complete
                complete_paths.add(context.path + (context.paint, ))
                nth_paint_glyph += 1

            elif isinstance(context.paint, PaintColrLayers):
                pass

            elif isinstance(context.paint, PaintSolid):
                _apply_solid_paint(parent_el, context.paint)

            elif _is_svg_supported_composite(context.paint):
                el = etree.SubElement(parent_el, f"{{{svg_meta.svgns()}}}g")
                el_by_path[context.path + (context.paint, )] = el

            # TODO: support transform types, either by introducing <g> or by applying context.transform to Paint

            else:
                raise ValueError(f"What do we do with {context}")