diff --git a/Lib/gftools/packager/__init__.py b/Lib/gftools/packager/__init__.py index 24cede4e..7e909cff 100644 --- a/Lib/gftools/packager/__init__.py +++ b/Lib/gftools/packager/__init__.py @@ -32,7 +32,6 @@ import gftools.fonts_public_pb2 as fonts_pb2 from gftools.gfgithub import GitHubClient from gftools.scripts.add_font import main as add_font -from gftools.tags import GFTags from gftools.util import google_fonts as fonts from gftools.utils import ( download_file, @@ -83,7 +82,6 @@ PR_CHECKLIST = """ ## PR Checklist: -- [x] Family categorization tags collected from the type design team with the Categories Form - [ ] `minisite_url` definition in the METADATA.pb file for commissioned projects - [ ] `primary_script` definition in the METADATA.pb file for all projects that have a primary non-Latin based language support target - [ ] `subsets` definitions in the METADATA.pb reflect the actual subsets and languages present in the font files (in alphabetic order). For **CJK fonts**, only include one of the following subsets `chinese-hongkong`, `chinese-simplified`, `chinese-traditional`, `korean`, `japanese`. @@ -613,6 +611,11 @@ def make_package( issue_number=None, **kwargs, ): + if skip_tags: + log.warning( + f"skip_tags is deprecated since we now have a tagging webapp, " + "https://google.github.io/fonts/tags.html" + ) if pr and not has_gh_token(): raise ValueError( f"Tool requires 'GH_TOKEN' environment variable in order to make " @@ -675,19 +678,6 @@ def make_package( "in the METADATA.pb file and rerun tool with the same commands." ) - # All font families must have tagging data. This data helps users on Google - # Fonts find font families. It's enabled by default since it's a hard - # requirements set by management. - if not skip_tags and not GFTags().has_family(metadata.name): - raise ValueError( - f"'{metadata.name}' does not have family tagging data! " - "Please complete the following form, " - "https://forms.gle/jcp3nDv63LaV1rxH6. Once tags have been added, " - "you may need to wait around five minutes in order for the tags " - "to be registered before rerunning the tool. This is a hard " - "requirement set by Google Fonts management." - ) - with current_git_state(repo, family_path): branch = create_git_branch(metadata, repo, head_repo) packaged = package_family( diff --git a/Lib/gftools/scripts/font_tags.py b/Lib/gftools/scripts/font_tags.py deleted file mode 100644 index d5a22f28..00000000 --- a/Lib/gftools/scripts/font_tags.py +++ /dev/null @@ -1,61 +0,0 @@ -""" -gftools font-tags - -Export Font classification tags to csv, or check the spreadsheet -is still structured correctly. - -Usage: -# Write tags csv file to google/fonts/tags/all/families.csv -gftools font-tags write path/to/google/fonts - -# Check Google Sheet is still structured correctly -gftools font-tags lint path/to/google/fonts -""" - -import os -from pathlib import Path -import sys -from gftools.tags import GFTags -from argparse import ArgumentParser -from gftools.utils import is_google_fonts_repo - - -def main(args=None): - parser = ArgumentParser() - subparsers = parser.add_subparsers( - dest="command", required=True, metavar='"write" or "lint"' - ) - universal_options_parser = ArgumentParser(add_help=False) - universal_options_parser.add_argument("gf_path", type=Path) - - write_parser = subparsers.add_parser( - "write", - parents=[universal_options_parser], - help="Write Google Sheet to google/fonts csv file", - ) - lint_parser = subparsers.add_parser( - "lint", - parents=[universal_options_parser], - help="Check Google Sheet is structured correctly", - ) - args = parser.parse_args(args) - - if not is_google_fonts_repo(args.gf_path): - raise ValueError( - f"'{args.gf_path.absolute()}' is not a path to a valid google/fonts repo" - ) - - gf_tags = GFTags() - - if args.command == "write": - out_dir = args.gf_path / "tags" / "all" - if not out_dir.exists(): - os.makedirs(out_dir) - out = out_dir / "families.csv" - gf_tags.to_csv(out) - elif args.command == "lint": - gf_tags.check_structure() - - -if __name__ == "__main__": - main() diff --git a/Lib/gftools/tags.py b/Lib/gftools/tags.py deleted file mode 100644 index 12fe991f..00000000 --- a/Lib/gftools/tags.py +++ /dev/null @@ -1,278 +0,0 @@ -""" -tags.py - -This module contains objects to work with the "Google Fonts 2023 -Typographic Categories" Google Sheet, -https://docs.google.com/spreadsheets/d/1Nc5DUsgVLbJ3P58Ttyhr5r-KYVnJgrj47VvUm1Rs8Fw/edit#gid=0 - -This sheet contains all the font tagging information which is used in -the Google Fonts website to help users find font families. -""" - -import csv -import requests -from io import StringIO -from difflib import Differ - - -class SheetStructureChange(Exception): - pass - - -class GFTags(object): - # Original sheet tagging data created by Universal Thirst for the whole GF collection - SHEET1_URL = "https://docs.google.com/spreadsheets/d/e/2PACX-1vQVM--FKzKTWL-8w0l5AE1e087uU_OaQNHR3_kkxxymoZV5XUnHzv9TJIdy7vcd0Saf4m8CMTMFqGcg/pub?gid=1193923458&single=true&output=csv" - # Submissions from designers via form https://forms.gle/jcp3nDv63LaV1rxH6 - SHEET2_URL = "https://docs.google.com/spreadsheets/d/e/2PACX-1vQVM--FKzKTWL-8w0l5AE1e087uU_OaQNHR3_kkxxymoZV5XUnHzv9TJIdy7vcd0Saf4m8CMTMFqGcg/pub?gid=378442772&single=true&output=csv" - CATEGORIES = { - "Serif": [ - "Humanist Venetian", - "Old Style Garalde", - "Transitional", - "Modern", - "Scotch", - "Didone", - "Fat Face", - ], - "Sans": [ - "Humanist", - "Grotesque", - "Neo Grotesque", - "Geometric", - "Rounded", - "Superellipse", - "Glyphic", - ], - "Slab": ["Geometric", "Humanist", "Clarendon"], - "Script": ["Formal", "Informal", "Handwritten", "Upright Script"], - "Monospace": ["Monospace"], - "Theme": [ - "Blackletter", - "Wacky", - "Blobby", - "Woodtype", - "Stencil", - "Inline", - "Distressed", - "Shaded", - "Techno", - "Art Nouveau", - "Tuscan", - "Art Deco", - "Medieval", - "Brush", - "Pixel", - "Brush", - ], - "Arabic": [ - "Kufi", - "Naskh", - "Nastaliq", - "Maghribi", - "Ruqah", - "Diwani", - "Bihari", - "Warsh", - "Sudani", - "West African", - ], - "Hebrew": ["Normal", "Ashurit", "Cursive", "Rashi"], - "South East Asian (Thai, Khmer, Lao)": [ - "Looped", - "Loopless", - "Moul (Khmer)", - "Chrieng (Khmer)", - ], - "Sinhala": [ - "Traditional", - "Contemporary", - "Low contrast", - ], - "Indic": [ - "Traditional", - "Contemporary", - "Low contrast", - "Sign Painting", - "Reverse-contrast", - ], - "Expressive": [ - "Competent", - "Business", - "Sincere", - "Loud", - "Awkward", - "Innovative", - "Playful", - "Excited", - "Happy", - "Loud", - "Rugged", - "Vintage", - "Stiff", - "Futuristic", - "Calm", - "Childlike", - "Active", - "Cute", - "Sophisticated", - "Fancy", - "Artistic", - ], - "Not text": [ - "Experimental", - "Emojis", - "Symbols", - ], - "Expressive": [ - "Business", - "Sincere", - "Loud", - "Vintage", - "Calm", - "Calm/simple", - "Stiff", - "Competent", - "Happy", - "Childlike", - "Excited", - "Playful", - "Awkward", - "Innovative", - "Rugged", - "Futuristic", - "Artistic", - "Cute", - "Fancy", - "Sophisticated", - "Active", - ], - } - - def __init__(self): - self.sheet1_data = self._get_sheet_data(self.SHEET1_URL) - self.sheet2_data = self._get_sheet_data(self.SHEET2_URL) - self.seen_families = set() - self.duplicate_families = set() - self.data = self._parse_sheets_csv() - - def _get_sheet_data(self, sheet_url): - req = requests.get(sheet_url) - return list(csv.reader(StringIO(req.text))) - - def _parse_csv(self, data, skip_rows=[], skip_columns=[], family_name_col=0): - """Convert the tabular sheet data into - [ - {"Family": str, "Group/Tag": str, "Weight": int}, - ... - ]""" - res = [] - for i in range(len(data)): - if i in skip_rows: - continue - family = data[i][family_name_col] - if family in self.seen_families: - self.duplicate_families.add(family) - continue - self.seen_families.add(family) - for j in range(len(data[i])): - if j in skip_columns: - continue - if not data[i][j].isnumeric(): - continue - value = int(data[i][j]) - if value == 0: - continue - category = data[0][j] - # If no tag exists for a value, it means a value has been assigned - # to the whole group such as Sans, Sans Serif etc. We don't want to - # include these since we can deduce it ourselves according to Evan. - sub_category = data[1][j].split("\n")[0] - if sub_category == "": - continue - if category not in self.CATEGORIES: - raise ValueError( - f"'{category}' not in known categories, '{self.CATEGORIES.keys()}'" - ) - if sub_category not in self.CATEGORIES[category]: - raise ValueError( - f"'{sub_category}' not in known sub categories, '{self.CATEGORIES[category]}'" - ) - res.append( - { - "Family": family, - "Group/Tag": f"/{category}/{sub_category}", - "Weight": value, - } - ) - if self.duplicate_families: - raise ValueError( - f"Duplicate families found in sheet: {self.duplicate_families}. Please remove them." - ) - res.sort(key=lambda k: (k["Family"], k["Group/Tag"])) - return res - - def _parse_sheets_csv(self): - sheet1 = self._parse_csv( - self.sheet1_data, - skip_rows=[0, 1, 2, 3], - skip_columns=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - ) - sheet2 = self._parse_csv( - self.sheet2_data, - skip_rows=[0, 1], - skip_columns=[0, 1, 2, 3, 4, 5, 6], - family_name_col=2, - ) - return sheet1 + sheet2 - - def to_csv(self, fp): - """Export the Google Sheet into a csv format suitable for the - google/fonts git repo.""" - with open(fp, "w", encoding="utf-8") as out_doc: - out_csv = csv.DictWriter(out_doc, ["Family", "Group/Tag", "Weight"]) - out_csv.writeheader() - out_csv.writerows(self.data) - - def has_family(self, name): - return any([i["Family"] == name for i in self.data]) - - def check_structure(self): - # Check a few families to determine whether the spreadsheet is broken - test_tags = [ - # sheet1 row 0 - {"Family": "ABeeZee", "Group/Tag": "/Sans/Geometric", "Weight": 10}, - # sheet1 row 330 - {"Family": "Bonbon", "Group/Tag": "/Script/Handwritten", "Weight": 100}, - # sheet1 row 577 - { - "Family": "Cormorant SC", - "Group/Tag": "/Serif/Old Style Garalde", - "Weight": 100, - }, - # sheet1 row 900 - {"Family": "Gochi Hand", "Group/Tag": "/Script/Informal", "Weight": 100}, - # sheet1 row 1354 - { - "Family": "Zilla Slab Highlight", - "Group/Tag": "/Slab/Geometric", - "Weight": 20, - }, - # sheet2 row 1 - { - "Family": "Noto Serif Hentaigana", - "Group/Tag": "/Script/Formal", - "Weight": 20, - }, - # sheet2 row 2 - { - "Family": "Platypi", - "Group/Tag": "/Serif/Humanist Venetian", - "Weight": 20, - }, - {"Family": "Platypi", "Group/Tag": "/Theme/Art Nouveau", "Weight": 5}, - {"Family": "Sedan", "Group/Tag": "/Serif/Old Style Garalde", "Weight": 90}, - ] - for tag in test_tags: - if tag not in self.data: - raise ValueError(f"{tag} should exist spreadsheet") - print("Google Sheet's structure is intact") diff --git a/tests/test_tags.py b/tests/test_tags.py deleted file mode 100644 index cfd57696..00000000 --- a/tests/test_tags.py +++ /dev/null @@ -1,33 +0,0 @@ -import pytest -import tempfile -import subprocess -import os -from pathlib import Path -import csv - - -@pytest.fixture(scope="session") -def items(): - with tempfile.TemporaryDirectory() as tmp_dir: - gf_path = Path(tmp_dir) / "google" / "fonts" - os.makedirs(gf_path / "ofl" / "abel") - subprocess.run(["gftools", "font-tags", "write", gf_path]) - - csv_path = gf_path / "tags" / "all" / "families.csv" - with open(csv_path, encoding="utf-8") as doc: - return list( - csv.DictReader(doc, ["Family", "Group/Tag", "Weight"], strict=True) - ) - - -@pytest.mark.parametrize( - "item", - [ - {"Family": "Handlee", "Group/Tag": "/Script/Handwritten", "Weight": "100"}, - {"Family": "Karla", "Group/Tag": "/Sans/Grotesque", "Weight": "100"}, - {"Family": "Family", "Group/Tag": "Group/Tag", "Weight": "Weight"}, - {"Family": "Aleo", "Group/Tag": "/Slab/Humanist", "Weight": "100"}, - ], -) -def test_write_font_tags(items, item): - assert item in items