#!/usr/bin/env python # # Copyright (C) 2024 The Android Open Source Project # # 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 random import sys import unittest from alias_builder import parse_aliases_from_json from commandline import CommandlineArgs from fallback_builder import parse_fallback_from_json from family_builder import parse_family_from_json from xml_builder import FallbackOrder from xml_builder import generate_xml _SANS_SERIF = parse_family_from_json("""{ "name": "sans-serif", "fonts": [{ "file": "Roboto-Regular.ttf", "supportedAxes": "wght,ital", "axes": { "wdth": "100" } }] }""") _SERIF = parse_family_from_json("""{ "name": "serif", "fonts": [{ "file": "NotoSerif-Regular.ttf", "postScriptName": "NotoSerif", "weight": "400", "style": "normal" }, { "file": "NotoSerif-Bold.ttf", "weight": "700", "style": "normal" }, { "file": "NotoSerif-Italic.ttf", "weight": "400", "style": "italic" }, { "file": "NotoSerif-BoldItalic.ttf", "weight": "700", "style": "italic" }] }""") _ROBOTO_FLEX = parse_family_from_json("""{ "name": "roboto-flex", "fonts": [{ "file": "RobotoFlex-Regular.ttf", "supportedAxes": "wght", "axes": { "wdth": "100" } }] }""") _ARABIC = parse_family_from_json("""{ "lang": "und-Arab", "variant": "elegant", "fonts": [{ "file": "NotoNaskhArabic-Regular.ttf", "postScriptName": "NotoNaskhArabic", "weight": "400", "style": "normal" }, { "file": "NotoNaskhArabic-Bold.ttf", "weight": "700", "style": "normal" }] }""") _ARABIC_UI = parse_family_from_json("""{ "lang": "und-Arab", "variant": "compact", "fonts": [{ "file": "NotoNaskhArabicUI-Regular.ttf", "postScriptName": "NotoNaskhArabicUI", "weight": "400", "style": "normal" }, { "file": "NotoNaskhArabicUI-Bold.ttf", "weight": "700", "style": "normal" }] }""") _HANS = parse_family_from_json("""{ "lang": "zh-Hans", "fonts": [{ "file": "NotoSansCJK-Regular.ttc", "postScriptName": "NotoSansCJKJP-Regular", "weight": "400", "style": "normal", "supportedAxes": "wght", "axes": { "wght": "400" }, "index": "2" }], "id": "NotoSansCJK_zh-Hans" }""") _JA = parse_family_from_json("""{ "lang": "ja", "fonts": [{ "file": "NotoSansCJK-Regular.ttc", "postScriptName": "NotoSansCJKJP-Regular", "weight": "400", "style": "normal", "supportedAxes": "wght", "axes": { "wght": "400" }, "index": "0" }], "id": "NotoSansCJK_ja" }""") _JA_HENTAIGANA = parse_family_from_json("""{ "lang": "ja", "priority": 100, "fonts": [{ "file": "NotoSerifHentaigana.ttf", "postScriptName": "NotoSerifHentaigana-ExtraLight", "supportedAxes": "wght", "axes": { "wght": "400" } }] }""") _HANS_SERIF = parse_family_from_json("""{ "lang": "zh-Hans", "fonts": [{ "file": "NotoSerifCJK-Regular.ttc", "postScriptName": "NotoSerifCJKjp-Regular", "weight": "400", "style": "normal", "index": "2" }], "fallbackFor": "serif", "target": "NotoSansCJK_zh-Hans" }""") _JA_SERIF = parse_family_from_json("""{ "lang": "ja", "fonts": [{ "file": "NotoSerifCJK-Regular.ttc", "postScriptName": "NotoSerifCJKjp-Regular", "weight": "400", "style": "normal", "index": "0" }], "fallbackFor": "serif", "target": "NotoSansCJK_ja" }""") _FALLBACK = parse_fallback_from_json("""[ { "lang": "und-Arab" }, { "lang": "zh-Hans" }, { "lang": "ja" } ]""") _ALIASES = parse_aliases_from_json("""[ { "name": "sans-serif-thin", "to" : "sans-serif", "weight": 100 } ]""") class FallbackOrderTest(unittest.TestCase): def test_fallback_order(self): order = FallbackOrder(_FALLBACK) # Arabic and Arabic UI are prioritized over Simplified Chinese self.assertTrue(order(_ARABIC) < order(_HANS)) self.assertTrue(order(_ARABIC_UI) < order(_HANS)) # Simplified Chinese is prioritized over Japanese self.assertTrue(order(_HANS) < order(_JA)) def test_fallback_order_variant(self): order = FallbackOrder(_FALLBACK) # Arabic is prioritize over Arabic UI self.assertTrue(order(_ARABIC) < order(_ARABIC_UI)) def test_fallback_order_unknown_priority(self): order = FallbackOrder(parse_fallback_from_json("""[ { "lang": "zh-Hans" } ]""")) self.assertRaises(AssertionError, order, _ARABIC) def test_fallback_order_id_and_lang(self): order = FallbackOrder(_FALLBACK) # If both ID and lang matches the fallback, the ID is used. self.assertTrue(order(_HANS) < order(_JA)) class XmlBuilderTest(unittest.TestCase): def test_no_duplicate_families(self): self.assertRaises( AssertionError, generate_xml, fallback=_FALLBACK, aliases=[], families=[_SANS_SERIF, _ROBOTO_FLEX, _ROBOTO_FLEX], ) def test_mandatory_sans_serif(self): self.assertRaises( AssertionError, generate_xml, fallback=_FALLBACK, aliases=[], families=[_ARABIC, _ARABIC_UI, _HANS, _JA], ) def test_missing_fallback_target(self): # serif family is necessary for fallback. self.assertRaises( AssertionError, generate_xml, fallback=_FALLBACK, aliases=[], families=[_SANS_SERIF, _HANS_SERIF], ) # target family is necessary for fallback. self.assertRaises( AssertionError, generate_xml, fallback=_FALLBACK, aliases=[], families=[_SANS_SERIF, _SERIF, _HANS_SERIF], ) def test_missing_alias_target(self): self.assertRaises( AssertionError, generate_xml, fallback=_FALLBACK, aliases=parse_aliases_from_json("""[{ "name": "serif-thin", "to" : "serif", "weight": 100 }]"""), families=[_SANS_SERIF, _HANS_SERIF], ) def test_duplicated_alias(self): self.assertRaises( AssertionError, generate_xml, fallback=_FALLBACK, aliases=parse_aliases_from_json("""[{ "name": "serif-thin", "to" : "serif", "weight": 100 },{ "name": "serif-thin", "to" : "serif", "weight": 100 }]"""), families=[_SANS_SERIF, _SERIF, _HANS_SERIF], ) def test_same_priority(self): self.assertRaises( AssertionError, generate_xml, fallback=_FALLBACK, aliases=[], families=[_SANS_SERIF, _JA, _JA], ) def test_generate_xml(self): xml = generate_xml( fallback=_FALLBACK, aliases=_ALIASES, families=[ _SANS_SERIF, _SERIF, _ARABIC, _ARABIC_UI, _HANS, _HANS_SERIF, _JA, _JA_SERIF, _JA_HENTAIGANA, ], ) self.expect_xml(xml) def test_generate_xml_reordered(self): families = [ _SANS_SERIF, _SERIF, _ARABIC, _ARABIC_UI, _HANS, _HANS_SERIF, _JA, _JA_SERIF, _JA_HENTAIGANA, ] for i in range(0, 10): random.shuffle(families) xml = generate_xml( fallback=_FALLBACK, aliases=_ALIASES, families=families ) self.expect_xml(xml) def expect_xml(self, xml): self.assertEqual("sans-serif", xml.families[0].name) # _SANS_SERIF self.assertEqual("serif", xml.families[1].name) # _SERIF self.assertEqual("und-Arab", xml.families[2].lang) # __ARABIC self.assertEqual("elegant", xml.families[2].variant) self.assertEqual("und-Arab", xml.families[3].lang) # _ARABIC_UI self.assertEqual("zh-Hans", xml.families[4].lang) # _HANS (_HANS_SERIF) self.assertEqual(2, len(xml.families[4].fonts)) self.assertEqual("serif", xml.families[4].fonts[1].fallback_for) self.assertEqual("ja", xml.families[5].lang) # _HANS (_HANS_SERIF) self.assertEqual("serif", xml.families[5].fonts[1].fallback_for) if __name__ == "__main__": unittest.main(verbosity=2)