def _add_glyph(svg: SVG, color_glyph: ColorGlyph, reuse_cache: ReuseCache): # 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}" # https://github.com/googlefonts/nanoemoji/issues/58: group needs transform svg_g.attrib["transform"] = _svg_matrix( color_glyph.transform_for_otsvg_space()) # copy the shapes into our svg for painted_layer in color_glyph.as_painted_layers(): view_box = color_glyph.picosvg.view_box() if view_box is None: raise ValueError(f"{color_glyph.filename} must declare view box") reuse_key = _inter_glyph_reuse_key(view_box, painted_layer) if reuse_key not in reuse_cache.shapes: el = to_element(painted_layer.path) match = regex.match(r"url\(#([^)]+)*\)", el.attrib.get("fill", "")) if match: el.attrib[ "fill"] = f"url(#{reuse_cache.old_to_new_id.get(match.group(1), match.group(1))})" svg_g.append(el) reuse_cache.shapes[reuse_key] = el for reuse in painted_layer.reuses: _ensure_has_id(el) svg_use = etree.SubElement(svg_g, "use") svg_use.attrib["href"] = f'#{el.attrib["id"]}' tx, ty = reuse.gettranslate() if tx: svg_use.attrib["x"] = _ntos(tx) if ty: svg_use.attrib["y"] = _ntos(ty) transform = reuse.translate(-tx, -ty) if transform != Affine2D.identity(): # TODO apply scale and rotation. Just slap a transform on the <use>? raise NotImplementedError( "TODO apply scale & rotation to use") else: el = reuse_cache.shapes[reuse_key] _ensure_has_id(el) svg_use = etree.SubElement(svg_g, "use") svg_use.attrib["href"] = f'#{el.attrib["id"]}'
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}")