WIP
diff --git a/android_connection/apply_to_android.py b/android_connection/apply_to_android.py
index 8eff732..ba75dc1 100644
--- a/android_connection/apply_to_android.py
+++ b/android_connection/apply_to_android.py
@@ -1,13 +1,30 @@
+from absl import app
+from absl import flags
+import filecmp
+from lxml import etree
from pathlib import Path
-import sys
+import shutil
+
+
+FLAGS = flags.FLAGS
+
+
+flags.DEFINE_string("android_root", None, "Root of android repo")
+flags.DEFINE_bool("dry_run", True, "If False actually update files")
def _require_exists(a_file: Path) -> Path:
if not a_file.is_file():
- raise ValueError(f"{a_file} not found")
+ raise ValueError(f"{a_file} missing or not a file")
return a_file
+def _require_dir_exists(a_dir: Path) -> Path:
+ if not a_dir.is_dir():
+ raise ValueError(f"{a_dir} missing or not a dir")
+ return a_dir
+
+
def _fonts_xml(android_dir: Path) -> Path:
return _require_exists(android_dir / "frameworks" / "base" / "data" / "fonts" / "fonts.xml")
@@ -16,18 +33,106 @@
return _require_exists(android_dir / "external" / "noto-fonts" / "fonts.mk")
+def _font_dir(android_dir: Path) -> Path:
+ return _require_dir_exists(android_dir / "external" / "noto-fonts" / "other")
+
+
+def _repo_root() -> Path:
+ root = (Path(__file__).parent.parent).absolute()
+ if not (root / "LICENSE").is_file():
+ raise IOError(f"{root} does not contain LICENSE")
+ return root
+
+
+def noto_4_android_path() -> Path:
+ xml_file = _repo_root() / "android_connection" / "noto-fonts-4-android.xml"
+ if not xml_file.is_file():
+ raise IOError(f"No file {xml_file}")
+ return xml_file
+
+
+def font_file(font_el) -> str:
+ return ("".join(font_el.itertext())).strip()
+
+
+def font_path(font_el) -> Path:
+ name = font_file(font_el)
+ path = font_el.attrib["path"]
+ return _require_exists(_repo_root() / path / name)
+
+
def _validate_android_path(android_dir: Path):
assert android_dir.is_dir(), f"{android_dir} should be a directory"
- _fonts_xml(android_dir) # just to trigger it's checks
- _fonts_mk(android_dir) # just to trigger it's checks
+ # just to trigger existance validation
+ _fonts_xml(android_dir)
+ _fonts_mk(android_dir)
+ _font_dir(android_dir)
-def main():
- if len(sys.argv) != 2:
- raise ValueError("Must have one arg, path to an Android checkout")
- android_dir = Path(sys.argv[1])
+def main(_):
+ if not FLAGS.android_root:
+ raise ValueError("Must provide --android_root")
+ android_dir = Path(FLAGS.android_root)
_validate_android_path(android_dir)
+ # gather fonts that should be copied to Android
+ noto_for_android = etree.parse(str(noto_4_android_path()))
+ new_paths = {}
+ for font_el in noto_for_android.xpath("//font[@path]"):
+ path = font_path(font_el)
+ if new_paths.get(path.name, path) != path:
+ raise IOError(f"Multiple paths for {path.name}")
+ new_paths[path.name] = path
+ old_paths = {p.name: p for p in _font_dir(android_dir).glob("Noto*.[ot]t[fc]")}
+
+ new_names = set(new_paths.keys())
+ old_names = set(old_paths.keys())
+
+ delta_sz = 0
+
+ deleted_files = old_names - new_names
+ print(f"{len(deleted_files)} DELETED")
+ for delete_me in sorted(deleted_files):
+ print(f" {delete_me}")
+ if not FLAGS.dry_run:
+ old_paths[delete_me].unlink()
+ del delete_me
+ del deleted_files
+ print()
+
+ added_files = new_names - old_names
+ print(f"{len(added_files)} ADDED")
+ for add_me in sorted(added_files):
+ dest = _font_dir(android_dir) / add_me
+ print(f" {add_me}")
+ if not FLAGS.dry_run:
+ shutil.copy(new_paths[add_me], dest)
+ del add_me
+ del added_files
+ print()
+
+ updated_files = new_names & old_names
+ untouched = 0
+ print(f"{len(updated_files)} UPDATED")
+ for update_me in sorted(updated_files):
+ if filecmp.cmp(new_paths[update_me], old_paths[update_me], shallow=False):
+ untouched += 1
+ continue
+
+ dest = _font_dir(android_dir) / update_me
+ print(f" {update_me}")
+ if not FLAGS.dry_run:
+ shutil.copy(new_paths[update_me], dest)
+ del update_me
+ del updated_files
+ print()
+
+ print(f"{untouched} did not change")
+ print()
+
+ print(f"Done updating files, you should manually update {_fonts_xml(android_dir)}"
+ f" from {noto_4_android_path()}")
+
if __name__ == "__main__":
- main()
\ No newline at end of file
+ app.run(main)
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 982dcb7..351a20f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,4 @@
+absl-py>=0.9.0
fonttools>=4.28.5
lxml>=4.7.1
pytest>=6.2.5
diff --git a/tests/apply_to_android_test.py b/tests/apply_to_android_test.py
index f7d7d88..1ba652d 100644
--- a/tests/apply_to_android_test.py
+++ b/tests/apply_to_android_test.py
@@ -1,4 +1,5 @@
-from android_connection import apply_to_android
+from android_connection.apply_to_android import _validate_android_path
+from lxml import etree
from pathlib import Path
import pytest
@@ -11,10 +12,11 @@
return _testdata_dir() / "fake_android"
-def test_apply_to_bad_dir():
- with pytest.raises(ValueError, match="not found"):
- apply_to_android._validate_android_path(_testdata_dir())
+def test_validate_bad_dir():
+ with pytest.raises(ValueError, match="missing"):
+ _validate_android_path(_testdata_dir())
-def test_apply_to_good_dir():
- apply_to_android._validate_android_path(_fake_android_dir())
+def test_validate_good_dir():
+ _validate_android_path(_fake_android_dir())
+
diff --git a/tests/noto_fonts_for_android_test.py b/tests/noto_fonts_for_android_test.py
index 2a1bacd..6cb34a9 100644
--- a/tests/noto_fonts_for_android_test.py
+++ b/tests/noto_fonts_for_android_test.py
@@ -4,6 +4,11 @@
from pathlib import Path
import pytest
from typing import Tuple
+from android_connection.apply_to_android import (
+ font_file,
+ font_path,
+ noto_4_android_path,
+)
_KNOWN_PATHLESS = {
@@ -14,37 +19,12 @@
}
-
-def _repo_root() -> Path:
- root = (Path(__file__).parent / "..").absolute()
- if not (root / "LICENSE").is_file():
- raise IOError(f"{root} does not contain LICENSE")
- return root
-
-
-def _noto_4_android_file() -> Path:
- xml_file = _repo_root() / "android_connection" / "noto-fonts-4-android.xml"
- if not xml_file.is_file():
- raise IOError(f"No file {xml_file}")
- return xml_file
-
-
-def _font_file(font_el) -> str:
- return ("".join(font_el.itertext())).strip()
-
-
-def _font_path(font_el) -> Path:
- name = _font_file(font_el)
- path = font_el.attrib["path"]
- return _repo_root() / path / name
-
-
def _is_collection(font_el) -> bool:
- return _font_file(font_el).lower().endswith(".ttc")
+ return font_file(font_el).lower().endswith(".ttc")
def _open_font(font_el) -> ttLib.TTFont:
- path = _font_path(font_el)
+ path = font_path(font_el)
if not path.is_file():
raise IOError(f"No such file: {path}")
@@ -80,35 +60,35 @@
def test_fonts_have_path():
- root = etree.parse(str(_noto_4_android_file()))
+ root = etree.parse(str(noto_4_android_path()))
bad = []
for font in root.iter("font"):
- font_file = _font_file(font)
- if font_file in _KNOWN_PATHLESS:
- assert "path" not in font.attrib, f"{font_file} not expected to have path. Correct _KNOWN_PATHLESS if you just added path"
+ name = font_file(font)
+ if name in _KNOWN_PATHLESS:
+ assert "path" not in font.attrib, f"{name} not expected to have path. Correct _KNOWN_PATHLESS if you just added path"
continue
if not font.attrib.get("path", ""):
- bad.append(font_file)
+ bad.append(name)
assert not bad, "Missing path attribute: " + ", ".join(bad)
def test_ttcs_have_index():
- root = etree.parse(str(_noto_4_android_file()))
+ root = etree.parse(str(noto_4_android_path()))
bad = []
for font in root.iter("font"):
if not _is_collection(font):
continue
if "index" not in font.attrib:
- bad.append(_font_file(font))
+ bad.append(font_file(font))
assert not bad, "Missing index attribute: " + ", ".join(bad)
def test_font_paths_are_valid():
- root = etree.parse(str(_noto_4_android_file()))
+ root = etree.parse(str(noto_4_android_path()))
bad = []
for font in root.xpath("//font[@path]"):
- path = _font_path(font)
+ path = font_path(font)
if not path.is_file():
bad.append(str(path))
assert not bad, "No such file: " + ", ".join(bad)
@@ -120,17 +100,17 @@
"NotoNastaliqUrdu-Bold.ttf weight 700 outside font capability 400..400",
"NotoSerifMyanmar-Bold.ttf weight 700 outside font capability 400..400"
}
- root = etree.parse(str(_noto_4_android_file()))
+ root = etree.parse(str(noto_4_android_path()))
errors = []
for font_el in root.xpath("//font[@path]"):
xml_weight = int(font_el.attrib["weight"])
- path = _font_path(font_el)
+ path = font_path(font_el)
font = _open_font(font_el)
min_wght, default_wght, max_weight = _weight(font)
if xml_weight < min_wght or xml_weight > max_weight:
- error_str = f"{_font_file(font_el)} weight {xml_weight} outside font capability {min_wght}..{max_weight}"
+ error_str = f"{font_file(font_el)} weight {xml_weight} outside font capability {min_wght}..{max_weight}"
if error_str not in expected_errors:
errors.append(error_str)
@@ -138,21 +118,23 @@
def test_font_full_weight_coverage():
- root = etree.parse(str(_noto_4_android_file()))
+ root = etree.parse(str(noto_4_android_path()))
errors = []
for family in root.iter("family"):
font_to_xml_weights = collections.defaultdict(set)
for font in family.xpath("//font[@path]"):
- font_to_xml_weights[(_font_path(font), font.attrib.get("index", -1))].add(int(font.attrib["weight"]))
+ path = font_path(font)
+ ttc_idx = font.attrib.get("index", -1)
+ font_to_xml_weights[(path, ttc_idx)].add(int(font.attrib["weight"]))
# now you have a map of font path => set of weights in xml
- for (font_path, font_number), xml_weights in font_to_xml_weights.items():
+ for (path, ttc_idx), xml_weights in font_to_xml_weights.items():
# open the font, compute the 100 weights between it's min/max weight
# if xml_weights != computed weights add this to the error list
- font = _open_font_path(font_path, font_number)
+ font = _open_font_path(path, ttc_idx)
min_wght, default_wght, max_weight = _weight(font)
if min(xml_weights) > min_wght or max(xml_weights) < max_weight:
- errors.append(f"{font_path} weight range {min(xml_weights)}..{max(xml_weights)} could be expanded to {min_wght}..{max_weight}")
+ errors.append(f"{path} weight range {min(xml_weights)}..{max(xml_weights)} could be expanded to {min_wght}..{max_weight}")
assert not errors, ", ".join(errors)
diff --git a/tests/testdata/fake_android/external/noto-fonts/other/NotoExisting.ttf b/tests/testdata/fake_android/external/noto-fonts/other/NotoExisting.ttf
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/testdata/fake_android/external/noto-fonts/other/NotoExisting.ttf