Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Make Glyphs production names available #1318

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 69 additions & 11 deletions glyphs-reader/data/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from lxml import etree
from pathlib import Path
from textwrap import dedent
from typing import Iterable, Mapping, Optional, Tuple
from typing import Iterable, Mapping, MutableSet, Optional, Tuple


@dataclass(frozen=True)
Expand All @@ -26,6 +26,7 @@ class GlyphInfo:
name: str
category: str
subcategory: Optional[str]
production_name: Optional[str]


def codename(name: Optional[str]) -> Optional[str]:
Expand All @@ -48,6 +49,7 @@ def read_glyph_info(file: str) -> Tuple[GlyphInfo]:
e.attrib["name"].strip(),
codename(e.attrib["category"]),
codename(e.attrib.get("subCategory", None)),
e.attrib.get("production", None),
)
if info.name not in by_name:
by_name[info.name] = info
Expand All @@ -62,13 +64,16 @@ def read_glyph_info(file: str) -> Tuple[GlyphInfo]:
print(f'Ignoring alt name "{alt_name}", already taken')
continue
by_name[alt_name] = dataclasses.replace(
by_name[e.attrib["name"]], name=alt_name, codepoint=None
by_name[e.attrib["name"]],
name=alt_name,
codepoint=None,
production_name=None,
)

return tuple(by_name.values())


def minimal_names(names_taken: set(), names: Iterable[str]) -> Mapping[str, str]:
def minimal_names(names_taken: MutableSet, names: Iterable[str]) -> Mapping[str, str]:
counts = defaultdict(int)
for name in names:
counts[name] += 1
Expand Down Expand Up @@ -121,6 +126,22 @@ def main():
f"Multiple names are assigned 0x{codepoint:04x}, using the first one we saw"
)

production_name_to_info_idx = {}
blank = 0
uprefix = 0
uniprefix = 0
interesting = 0
for i, gi in enumerate(glyph_infos):
if gi.production_name is None:
blank += 1
continue
if gi.production_name in production_name_to_info_idx:
print(
f"Multiple names are assigned {gi.production_name}, using the first one we saw"
)
else:
production_name_to_info_idx[gi.production_name] = i

dest_file = Path(__file__).parent.parent / "src" / "glyphslib_data.rs"

with open(dest_file, "w") as f:
Expand All @@ -131,7 +152,7 @@ def main():
f.write(f"//! {len(glyph_infos)} glyph metadata records taken from glyphsLib\n")
f.write("\n")
f.write(
"use crate::glyphdata::{qr, q1, q2, q3, Category, QueryResult, Subcategory};\n"
"use crate::glyphdata::{qr, qru, qrv, qrc, q1, q1c, q2, q2c, q2u, q2v, q3, q3c, Category, QueryResult, Subcategory};\n"
)
f.write("\n")

Expand All @@ -150,24 +171,44 @@ def main():

# map to shorthand
if (None, None) == (gi.subcategory, gi.codepoint):
entry = f"q1({category})"
fn = "q1"
args = (category,)
elif gi.subcategory is None:
# codepoint must not be
entry = f"q2({category}, 0x{gi.codepoint})"
fn = "q2"
args = (category, f"0x{gi.codepoint}")
elif gi.codepoint is None:
# subcategory must not be
entry = f"q3({category}, {min_subcategories[gi.subcategory]})"
fn = "q3"
args = (category, min_subcategories[gi.subcategory])
else:
# We must have all the things!
entry = f"qr({category}, {min_subcategories[gi.subcategory]}, 0x{gi.codepoint})"
fn = "qr"
args = (category, min_subcategories[gi.subcategory], f"0x{gi.codepoint}")

# Do we need to track a production name?
# If so modify which shorthand fn we call
if gi.production_name is not None:
use_custom = True
if gi.codepoint is not None:
codepoint = int(gi.codepoint, 16)
# Uni-prefix is most common so given the shortest prefix. See ProductioName enum.
if gi.production_name == f"uni{codepoint:04X}":
use_custom = False
fn += "u"
if gi.production_name == f"u{codepoint:04X}":
use_custom = False
fn += "v"
if use_custom:
fn += "c"
args += (f"\"{gi.production_name}\"",)

entry = fn + "(" + ",".join(args) + ")"

codepoint = "None"
if gi.codepoint is not None:
codepoint = f"Some(0x{gi.codepoint})"

subcategory = "None"
if gi.subcategory is not None:
subcategory = f"Some({min_subcategories[gi.subcategory]})"
fragment = f'("{gi.name}", {entry}),'
if (len(lines[-1]) + len(fragment)) > 100:
lines[-1] += "\n"
Expand Down Expand Up @@ -196,6 +237,23 @@ def main():
f.write("\n")
f.write("];\n")

f.write(
"// Sorted by production name, has unique production names, therefore safe to bsearch\n"
)
f.write("pub(crate) const PRODUCTION_NAME_TO_INFO_IDX: &[(&str, usize)] = &[\n")
lines = [""]
for name, i in sorted(production_name_to_info_idx.items()):
fragment = f'("{name}", {i}),'
if (len(lines[-1]) + len(fragment)) > 100:
lines[-1] += "\n"
lines.append("")
lines[-1] += fragment

for line in lines:
f.write(line)
f.write("\n")
f.write("];\n")


if __name__ == "__main__":
main()
Loading
Loading