1#!/usr/bin/env python 2 3# 4# Copyright (C) 2024 The Android Open Source Project 5# 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18 19"""Build Font instance with validating JSON contents.""" 20 21import dataclasses 22 23from custom_json import _load_json_with_comment 24from validators import check_enum_or_none 25from validators import check_float 26from validators import check_int_or_none 27from validators import check_str 28from validators import check_str_or_none 29from validators import check_tag 30from validators import check_weight_or_none 31 32 33@dataclasses.dataclass 34class Font: 35 file: str 36 weight: int | None 37 style: str | None 38 index: int | None 39 supported_axes: str | None 40 post_script_name: str | None 41 axes: dict[str | float] 42 43 44_FONT_KEYS = set([ 45 "file", 46 "weight", 47 "style", 48 "index", 49 "supportedAxes", 50 "postScriptName", 51 "axes", 52]) 53 54 55def _check_axes(axes) -> dict[str | float] | None: 56 """Sanitize the variation axes.""" 57 if axes is None: 58 return None 59 assert isinstance(axes, dict), "axes must be dict" 60 61 sanitized = {} 62 for key in axes.keys(): 63 sanitized[check_tag(key)] = check_float(axes, key) 64 65 return sanitized 66 67 68def _parse_font(obj, for_sanitization_test=False) -> Font: 69 """Convert given dict object to Font instance.""" 70 unknown_keys = obj.keys() - _FONT_KEYS 71 assert not unknown_keys, "Unknown keys found: %s" % unknown_keys 72 font = Font( 73 file=check_str(obj, "file"), 74 weight=check_weight_or_none(obj, "weight"), 75 style=check_enum_or_none(obj, "style", ["normal", "italic"]), 76 index=check_int_or_none(obj, "index"), 77 supported_axes=check_enum_or_none( 78 obj, "supportedAxes", ["wght", "wght,ital"] 79 ), 80 post_script_name=check_str_or_none(obj, "postScriptName"), 81 axes=_check_axes(obj.get("axes")), 82 ) 83 84 if not for_sanitization_test: 85 assert font.file, "file must be specified" 86 if not font.supported_axes: 87 assert font.weight, ( 88 "If supported_axes is not specified, weight should be specified: %s" 89 % obj 90 ) 91 assert font.style, ( 92 "If supported_axes is not specified, style should be specified: %s" 93 % obj 94 ) 95 96 return font 97 98 99def parse_fonts(objs) -> Font: 100 assert isinstance(objs, list), "fonts must be list: %s" % (objs) 101 assert objs, "At least one font should be added." 102 return [_parse_font(obj) for obj in objs] 103 104 105def parse_font_from_json_for_sanitization_test(json_str: str) -> Font: 106 """For testing purposes.""" 107 return _parse_font( 108 _load_json_with_comment(json_str), for_sanitization_test=False 109 ) 110 111 112def parse_fonts_from_json_for_validation_test(json_str: str) -> [Font]: 113 """For testing purposes.""" 114 return parse_fonts(_load_json_with_comment(json_str)) 115