Skip to content

Commit

Permalink
Playing with the idea of making glyph mapping replaceable
Browse files Browse the repository at this point in the history
  • Loading branch information
rsheeter committed Oct 20, 2021
1 parent 5fe55d6 commit 18764c6
Show file tree
Hide file tree
Showing 15 changed files with 412 additions and 84 deletions.
9 changes: 6 additions & 3 deletions src/nanoemoji/codepoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ def from_filename(filename):
return tuple(int(s, 16) for s in match.captures(1))


def string(codepoints):
return ",".join("%04x" % c for c in codepoints)


def csv_line(filename):
filename = os.path.basename(filename)
codepoints = ",".join("%04x" % c for c in from_filename(filename))
return f"{filename},{codepoints}"
return f"{filename},{string(from_filename(filename))}"


def parse_csv_line(line):
Expand All @@ -39,4 +42,4 @@ def parse_csv_line(line):

def parse_csv(filename):
with open(filename) as f:
return [parse_csv_line(l) for l in f]
return tuple(parse_csv_line(l) for l in f)
5 changes: 2 additions & 3 deletions src/nanoemoji/color_glyph.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from lxml import etree # type: ignore
from nanoemoji.colors import Color
from nanoemoji.config import FontConfig
from nanoemoji import glyph
from nanoemoji.paint import (
Extend,
ColorStop,
Expand Down Expand Up @@ -409,11 +408,11 @@ def create(
ufo: ufoLib2.Font,
filename: str,
glyph_id: int,
codepoints: Tuple[int],
glyph_name: str,
codepoints: Tuple[int, ...],
svg: SVG,
) -> "ColorGlyph":
logging.debug(" ColorGlyph for %s (%s)", filename, codepoints)
glyph_name = glyph.glyph_name(codepoints)
base_glyph = ufo.newGlyph(glyph_name)

# non-square aspect ratio == proportional width; square == monospace
Expand Down
14 changes: 9 additions & 5 deletions src/nanoemoji/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,11 @@
"Whether to prefer pretty printed content whenever possible (for testing).",
)
flags.DEFINE_string("fea_file", None, "Feature file.")
flags.DEFINE_string("codepointmap_file", None, "Codepoint map file.")
flags.DEFINE_string(
"glyphmap_generator",
None,
"A program that takes a list of filenames and outputs a file csv whose rows contain filename, codepoint(s), glyph name.",
)


class Axis(NamedTuple):
Expand Down Expand Up @@ -138,7 +142,7 @@ class FontConfig(NamedTuple):
clip_to_viewbox: bool = True
clipbox_quantization: Optional[int] = None
fea_file: str = "features.fea"
codepointmap_file: str = "codepointmap.csv"
glyphmap_generator: str = "nanoemoji.write_glyphmap"
pretty_print: bool = False
axes: Tuple[Axis, ...] = ()
masters: Tuple[MasterConfig, ...] = ()
Expand Down Expand Up @@ -194,7 +198,7 @@ def write(dest: Path, config: FontConfig):
"clipbox_quantization": config.clipbox_quantization,
"pretty_print": config.pretty_print,
"fea_file": config.fea_file,
"codepointmap_file": config.codepointmap_file,
"glyphmap_generator": config.glyphmap_generator,
"axis": {
a.axisTag: {
"name": a.name,
Expand Down Expand Up @@ -278,7 +282,7 @@ def load(
clipbox_quantization = _pop_flag(config, "clipbox_quantization")
pretty_print = _pop_flag(config, "pretty_print")
fea_file = _pop_flag(config, "fea_file")
codepointmap_file = _pop_flag(config, "codepointmap_file")
glyphmap_generator = _pop_flag(config, "glyphmap_generator")

axes = []
for axis_tag, axis_config in config.pop("axis").items():
Expand Down Expand Up @@ -356,7 +360,7 @@ def load(
clipbox_quantization=clipbox_quantization,
pretty_print=pretty_print,
fea_file=fea_file,
codepointmap_file=codepointmap_file,
glyphmap_generator=glyphmap_generator,
axes=tuple(axes),
masters=tuple(masters),
source_names=tuple(sorted(source_names)),
Expand Down
55 changes: 55 additions & 0 deletions src/nanoemoji/glyphmap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import csv
from nanoemoji import codepoints
from pathlib import Path
from typing import NamedTuple, Optional, Tuple


class GlyphMapping(NamedTuple):
svg_file: Path
codepoints: Tuple[int, ...]
glyph_name: str

def csv_line(self):
cp_str = ""
if self.codepoints:
cp_str = codepoints.string(self.codepoints)
return f"{self.svg_file}, {self.glyph_name}, {cp_str}"


def load_from(file):
results = []
reader = csv.reader(file, skipinitialspace=True)
for row in reader:
try:
svg_file, glyph_name, *cps = row
except ValueError as e:
raise ValueError(f"Error parsing {row} from {file}") from e

svg_file = Path(svg_file)

if cps and cps != [""]:
cps = tuple(int(cp, 16) for cp in cps)
else:
cps = ()
results.append(GlyphMapping(svg_file, cps, glyph_name))

return tuple(results)


def parse_csv(filename):
with open(filename) as f:
return load_from(f)
135 changes: 91 additions & 44 deletions src/nanoemoji/nanoemoji.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from ninja import ninja_syntax
import os
from pathlib import Path
import re
import subprocess
import sys
from typing import List, NamedTuple, Optional, Tuple, Set, Sequence
Expand Down Expand Up @@ -95,12 +96,11 @@ def _source_name_file(font_config: FontConfig) -> Path:
return _per_config_file(font_config, ".source_names.txt")


def _codepoint_map_file(font_config: FontConfig) -> Path:
return _per_config_file(font_config, ".codepointmap.csv")


def _fea_file(font_config: FontConfig) -> Path:
return _per_config_file(font_config, ".fea")
def _fea_file(font_config: FontConfig, master: MasterConfig) -> Path:
master_part = ""
if _is_vf(font_config):
master_part = "." + master.output_ufo
return _per_config_file(font_config, master_part + ".fea")


def _font_rule(font_config: FontConfig) -> str:
Expand All @@ -124,38 +124,80 @@ def _ufo_config(font_config: FontConfig, master: MasterConfig) -> Path:
return _per_config_file(font_config, "." + master.output_ufo + ".toml")


def _glyphmap_rule(font_config: FontConfig, master: MasterConfig) -> str:
master_part = ""
if _is_vf(font_config):
master_part = "_" + master.style_name.lower()
return "write_" + Path(font_config.output_file).stem + master_part + "_glyphmap"


def _glyphmap_file(font_config: FontConfig, master: MasterConfig) -> Path:
master_part = ""
if _is_vf(font_config):
master_part = "." + master.output_ufo
return _per_config_file(font_config, master_part + ".glyphmap")


def module_rule(
nw, mod_name, arg_pattern, rspfile=None, rspfile_content=None, rule_name=None
nw,
mod_name,
arg_pattern,
rspfile=None,
rspfile_content=None,
rule_name=None,
allow_external=False,
):
if not rule_name:
rule_name = mod_name
if not allow_external:
mod_name = "nanoemoji." + mod_name
nw.rule(
rule_name,
f"{sys.executable} -m nanoemoji.{mod_name} -v {FLAGS.verbosity} {arg_pattern}",
f"{sys.executable} -m {mod_name} -v {FLAGS.verbosity} {arg_pattern}",
rspfile=rspfile,
rspfile_content=rspfile_content,
)


def write_font_rule(nw, font_config: FontConfig, master: Optional[MasterConfig] = None):
if master is None:
rule_name = _font_rule(font_config)
config_file = _config_file(font_config)
else:
def write_font_rule(nw, font_config: FontConfig, master: MasterConfig):
if _is_vf(font_config):
rule_name = _ufo_rule(font_config, master)
config_file = _ufo_config(font_config, master)
else:
rule_name = _font_rule(font_config)
config_file = _config_file(font_config)

module_rule(
nw,
"write_font",
f" --config_file {rel_build(config_file)} --fea_file {rel_build(_fea_file(font_config))} --codepointmap_file {rel_build(_codepoint_map_file(font_config))} @$out.rsp",
" ".join(
(
f"--config_file {rel_build(config_file)}",
f"--fea_file {rel_build(_fea_file(font_config, master))}",
f"--glyphmap_file {rel_build(_glyphmap_file(font_config, master))}",
"@$out.rsp",
)
),
rspfile="$out.rsp",
rspfile_content="$in",
rule_name=rule_name,
)
nw.newline()


def write_glyphmap_rule(nw, font_config: FontConfig, master: MasterConfig):
module_rule(
nw,
font_config.glyphmap_generator,
f"--output_file $out $in",
rspfile="$out.rsp",
rspfile_content="$in",
rule_name=_glyphmap_rule(font_config, master),
allow_external=True,
)
nw.newline()


def write_preamble(nw):
nw.comment("Generated by nanoemoji")
nw.newline()
Expand Down Expand Up @@ -192,11 +234,9 @@ def write_preamble(nw):


def write_config_preamble(nw, font_config: FontConfig):
if len(font_config.masters) == 1:
write_font_rule(nw, font_config)
else:
for master in font_config.masters:
write_font_rule(nw, font_config, master)
for master in font_config.masters:
write_font_rule(nw, font_config, master)
if _is_vf(font_config):
module_rule(
nw,
"write_variable_font",
Expand Down Expand Up @@ -278,20 +318,13 @@ def write_source_names(font_config: FontConfig):
f.write("\n")


def write_codepointmap_build(nw: ninja_syntax.Writer, font_config: FontConfig):
nw.build(
str(rel_build(_codepoint_map_file(font_config))),
"write_codepoints",
[str(rel_build(_source_name_file(font_config)))],
)
nw.newline()


def write_fea_build(nw: ninja_syntax.Writer, font_config: FontConfig):
def write_fea_build(
nw: ninja_syntax.Writer, font_config: FontConfig, master: MasterConfig
):
nw.build(
str(rel_build(_fea_file(font_config))),
str(rel_build(_fea_file(font_config, master))),
"write_fea",
str(rel_build(_codepoint_map_file(font_config))),
str(rel_build(_glyphmap_file(font_config, master))),
)
nw.newline()

Expand Down Expand Up @@ -353,6 +386,19 @@ def _update_sources(font_config: FontConfig) -> FontConfig:
)


def write_glyphmap_build(
nw: ninja_syntax.Writer,
font_config: FontConfig,
master: MasterConfig,
):
nw.build(
str(rel_build(_glyphmap_file(font_config, master))),
_glyphmap_rule(font_config, master),
_input_svgs(font_config, master),
)
nw.newline()


def write_ufo_build(
nw: ninja_syntax.Writer, font_config: FontConfig, master: MasterConfig
):
Expand All @@ -372,7 +418,10 @@ def write_static_font_build(nw: ninja_syntax.Writer, font_config: FontConfig):
nw.build(
font_config.output_file,
_font_rule(font_config),
[str(_config_file(font_config))]
[
str(rel_build(_config_file(font_config))),
str(rel_build(_glyphmap_file(font_config, font_config.masters[0]))),
]
+ _input_svgs(font_config, font_config.masters[0]),
)
nw.newline()
Expand All @@ -387,14 +436,6 @@ def write_variable_font_build(nw: ninja_syntax.Writer, font_config: FontConfig):
nw.newline()


def _update_config_for_build(font_config: FontConfig) -> FontConfig:
font_config = font_config._replace(
fea_file=str(rel_build(_fea_file(font_config))),
codepointmap_file=str(rel_build(_codepoint_map_file(font_config))),
)
return font_config


def _write_config_for_build(font_config: FontConfig):
# Dump config with defaults, CLI args, etc resolved to build
# and sources updated to point to build picosvgs
Expand Down Expand Up @@ -439,8 +480,6 @@ def _run(argv):

logging.info(f"Proceeding with {len(font_configs)} config(s)")

font_configs = tuple(_update_config_for_build(c) for c in font_configs)

for font_config in font_configs:
if _is_vf(font_config) and _is_svg(font_config):
raise ValueError("svg formats cannot have multiple masters")
Expand All @@ -453,14 +492,22 @@ def _run(argv):
nw = ninja_syntax.Writer(f)
write_preamble(nw)

# Separate loops for separate content to keep related rules together

for font_config in font_configs:
for master in font_config.masters:
write_glyphmap_rule(nw, font_config, master)

for font_config in font_configs:
write_config_preamble(nw, font_config)

for font_config in font_configs:
write_codepointmap_build(nw, font_config)
for master in font_config.masters:
write_fea_build(nw, font_config, master)

for font_config in font_configs:
write_fea_build(nw, font_config)
for master in font_config.masters:
write_glyphmap_build(nw, font_config, master)

picosvg_builds = set()
for font_config in font_configs:
Expand Down
Loading

0 comments on commit 18764c6

Please sign in to comment.