From 3cb1b4570052441d8a79f8374b48e45221d8d53c Mon Sep 17 00:00:00 2001 From: Olli Meier Date: Tue, 26 Apr 2022 17:36:00 +0200 Subject: [PATCH] add new QA HTML Test tool for comparing a font family. --- Lib/gftools/html.py | 6 +- Lib/gftools/templates/family.html | 85 +++++++++++++++++++++++++++ Lib/gftools/tests/test_html_family.py | 81 +++++++++++++++++++++++++ Lib/gftools/utils.py | 39 +++++++++++- 4 files changed, 206 insertions(+), 5 deletions(-) create mode 100644 Lib/gftools/templates/family.html create mode 100644 Lib/gftools/tests/test_html_family.py diff --git a/Lib/gftools/html.py b/Lib/gftools/html.py index 52f1f4168..1efff50f6 100644 --- a/Lib/gftools/html.py +++ b/Lib/gftools/html.py @@ -22,6 +22,7 @@ font_is_italic, partition, get_encoded_glyphs, + get_encoded_glyphs_from_fonts, ) @@ -496,7 +497,8 @@ def __init__( self.sample_text = " ".join(font_sample_text(self.ttFonts[0])) # TODO to collect unencoded glyphs, we need to make a better version # of hbinput - self.glyphs = get_encoded_glyphs(self.ttFonts[0]) + self.glyphs = get_encoded_glyphs_from_fonts(self.ttFonts) + self.encoded_glyphs = {css_class: get_encoded_glyphs(self.ttFonts[i]) for i, css_class in enumerate(self.css_font_classes)} def partition(self): with tempfile.TemporaryDirectory() as tmp_out: @@ -553,7 +555,7 @@ def __init__( self.too_big_for_browserstack = len(self.css_font_classes_before) > 4 self.sample_text = " ".join(font_sample_text(self.ttFonts_before[0])) - self.glyphs = get_encoded_glyphs(self.ttFonts_before[0]) + self.glyphs = get_encoded_glyphs_fonts(self.ttFonts_before) def _match_css_font_classes(self): """Match css font classes by full names for static fonts and diff --git a/Lib/gftools/templates/family.html b/Lib/gftools/templates/family.html new file mode 100644 index 000000000..86475200e --- /dev/null +++ b/Lib/gftools/templates/family.html @@ -0,0 +1,85 @@ +{% extends "_base.html" %} +{% block title %}Glyphs{% endblock %} +{% block style %} +{{ super() }} +.cell{ + position: relative; + z-index: -1; + float: left; + background-color: #fcfcfc; + display: block; + text-align: center; + padding: 5pt; + margin: 5pt; + width: {{ pt_size | int * 1.5 }}px; + line-height: {{ pt_size | int * 1.5}}px; +} +.box-title-family{ + width: {{ pt_size }}pt; + float: left; + font-size: 8pt; + font-weight: 700; + padding-top: 5px; + margin-bottom: 10pt; + display: block; +} +.wrapper_box{ + display: flex; + padding: 5pt; + margin: 5pt; + width: 24px; + top: 240px; +} +.wrapper_title{ + display: flex; + padding: 5pt; + margin: 5pt; + width: 24px; + /* position: fixed; */ + top: 200px; +} +.wrapper_content{ + display: block; +} +.title_rotated { + transform: rotate(-90deg); + white-space:nowrap; + display:block; +} +.error{ + background-color: #D0342C; + color: #fcfcfc; +} + +{% endblock %} + +{% block content_name %} +
+ Font Family Overview +
+{% endblock %} + +{% block content %} +
+
+ {% for font_class in css_font_classes or css_font_classes_before or css_font_classes_after %} +
{{ font_class.selector }}
+ {% endfor %} +
+
+ {% for font_class in css_font_classes or css_font_classes_before or css_font_classes_after %} +
+ + {% for glyph in glyphs %} + {% if glyph in encoded_glyphs[font_class] %} +
{{ glyph }}
+ {% else %} +
{{ glyph }}
+ {% endif %} + {% endfor %} +
+
+ {% endfor %} +
+
+{% endblock %} diff --git a/Lib/gftools/tests/test_html_family.py b/Lib/gftools/tests/test_html_family.py new file mode 100644 index 000000000..5dc23ab8c --- /dev/null +++ b/Lib/gftools/tests/test_html_family.py @@ -0,0 +1,81 @@ +import pytest +from glob import glob +from fontTools.ttLib import TTFont +import tempfile +import os +from gftools.html import * +import re +from copy import deepcopy + + +TEST_DATA = os.path.join("data", "test") + + +@pytest.fixture +def SimpleTemplate(): + class SimpleTemplate(HtmlTemplater): + def __init__(self, out, template_dir): + super().__init__(out=out, template_dir=template_dir) + return SimpleTemplate + + +@pytest.fixture +def static_fonts(): + return [f for f in glob(os.path.join("data", "test", "mavenpro", "*.ttf"))] + + +@pytest.fixture +def static_ttfonts(): + return [TTFont(f) for f in glob(os.path.join("data", "test", "mavenpro", "*.ttf"))] + + +@pytest.fixture +def var_font(): + return os.path.join(TEST_DATA, "Inconsolata[wdth,wght].ttf") + + +@pytest.fixture +def var_ttfont(): + return TTFont(os.path.join(TEST_DATA, "Inconsolata[wdth,wght].ttf")) + + +@pytest.fixture +def var_font2(): + return os.path.join(TEST_DATA, "MavenPro[wght].ttf") + + +def _string_to_file(string, dst): + with open(dst, "w") as doc: + doc.write(string) + return dst + + +def _file_to_string(fp): + with open(fp) as f: + return f.read() + +def get_fonts(path): + font_suffixes = {'ttf', 'otf'} + fonts = [] + for filename in os.listdir(path): + full_path = os.path.join(path, filename) + suffix = filename.split('.')[-1] + + if suffix.lower() in font_suffixes: + fonts.append(TTFont(full_path)) + return fonts + +def test_html_family(): + base_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))) + from gftools.utils import mkdir, get_sorted_font_indices + font_path = os.path.join(base_path, "data", "test", "mavenpro") + static_ttfonts = get_fonts(font_path) + sorted_indices = get_sorted_font_indices(static_ttfonts) + + out = os.path.join(base_path, TEST_DATA, "mavenpro", "browser_previews") + mkdir(out) + html = HtmlProof( + out=out, + fonts=[static_ttfonts[i].reader.file.name for i in sorted_indices] + ) + html.build_pages(["family.html"], pt_size=16) \ No newline at end of file diff --git a/Lib/gftools/utils.py b/Lib/gftools/utils.py index fd0795df5..14e3a0c47 100644 --- a/Lib/gftools/utils.py +++ b/Lib/gftools/utils.py @@ -411,6 +411,12 @@ def get_encoded_glyphs(ttFont): """Collect all encoded glyphs""" return list(map(chr, ttFont.getBestCmap().keys())) +def get_encoded_glyphs_from_fonts(ttFonts): + """Collect all encoded glyphs from a list of fonts""" + encoded_glyphs = set() + for ttFont in ttFonts: + encoded_glyphs.update(set(get_encoded_glyphs(ttFont))) + return sorted(encoded_glyphs) def get_unencoded_glyphs(font): """ Check if font has unencoded glyphs """ @@ -442,9 +448,13 @@ def has_mac_names(ttfont): def font_is_italic(ttfont): - """Check if the font has the word "Italic" in its stylename.""" - stylename = ttfont["name"].getName(2, 3, 1, 0x409).toUnicode() - return True if "Italic" in stylename else False + try: + from fontbakery.profiles.shared_conditions import is_italic + return is_italic(ttfont) + except: + """Check if the font has the word "Italic" in its stylename.""" + stylename = ttfont["name"].getName(2, 3, 1, 0x409).toUnicode() + return True if "Italic" in stylename else False def font_sample_text(ttFont): @@ -513,3 +523,26 @@ def read_proto(fp, schema): data = text_format.Parse(f.read(), schema) return data +def get_sorted_font_indices(font_objs): + """ + Returns a tuple with indices sorted by + font family, italic, width and weight values. + """ + font_dict= dict() + for i, font_obj in enumerate(font_objs): + if "OS/2" not in font_obj or "name" not in font_obj: + # fonts must include the font tables OS/2 and name + # for figuring out a valid sorting. + continue + + OS2 = font_obj["OS/2"] + name = font_obj["name"] + + fam_name = name.getBestFamilyName() + wght_value = OS2.usWeightClass + wdth_value = OS2.usWidthClass + ital_value = font_is_italic(font_obj) + + font_dict[(fam_name, ital_value, wdth_value, wght_value, i)] = i + + return tuple([v for k, v in sorted(font_dict.items())])