From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../freetype2/tests/scripts/download-test-fonts.py | 302 +++++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100755 modules/freetype2/tests/scripts/download-test-fonts.py (limited to 'modules/freetype2/tests/scripts/download-test-fonts.py') diff --git a/modules/freetype2/tests/scripts/download-test-fonts.py b/modules/freetype2/tests/scripts/download-test-fonts.py new file mode 100755 index 0000000000..52b742e22c --- /dev/null +++ b/modules/freetype2/tests/scripts/download-test-fonts.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python3 + +"""Download test fonts used by the FreeType regression test programs. These +will be copied to $FREETYPE/tests/data/ by default.""" + +import argparse +import collections +import hashlib +import io +import os +import requests +import sys +import zipfile + +from typing import Callable, List, Optional, Tuple + +# The list of download items describing the font files to install. Each +# download item is a dictionary with one of the following schemas: +# +# - File item: +# +# file_url +# Type: URL string. +# Required: Yes. +# Description: URL to download the file from. +# +# install_name +# Type: file name string +# Required: No +# Description: Installation name for the font file, only provided if +# it must be different from the original URL's basename. +# +# hex_digest +# Type: hexadecimal string +# Required: No +# Description: Digest of the input font file. +# +# - Zip items: +# +# These items correspond to one or more font files that are embedded in a +# remote zip archive. Each entry has the following fields: +# +# zip_url +# Type: URL string. +# Required: Yes. +# Description: URL to download the zip archive from. +# +# zip_files +# Type: List of file entries (see below) +# Required: Yes +# Description: A list of entries describing a single font file to be +# extracted from the archive +# +# Apart from that, some schemas are used for dictionaries used inside +# download items: +# +# - File entries: +# +# These are dictionaries describing a single font file to extract from an +# archive. +# +# filename +# Type: file path string +# Required: Yes +# Description: Path of source file, relative to the archive's +# top-level directory. +# +# install_name +# Type: file name string +# Required: No +# Description: Installation name for the font file; only provided if +# it must be different from the original filename value. +# +# hex_digest +# Type: hexadecimal string +# Required: No +# Description: Digest of the input source file +# +_DOWNLOAD_ITEMS = [ + { + "zip_url": "https://github.com/python-pillow/Pillow/files/6622147/As.I.Lay.Dying.zip", + "zip_files": [ + { + "filename": "As I Lay Dying.ttf", + "install_name": "As.I.Lay.Dying.ttf", + "hex_digest": "ef146bbc2673b387", + }, + ], + }, +] + + +def digest_data(data: bytes): + """Compute the digest of a given input byte string, which are the first + 8 bytes of its sha256 hash.""" + m = hashlib.sha256() + m.update(data) + return m.digest()[:8] + + +def check_existing(path: str, hex_digest: str): + """Return True if |path| exists and matches |hex_digest|.""" + if not os.path.exists(path) or hex_digest is None: + return False + + with open(path, "rb") as f: + existing_content = f.read() + + return bytes.fromhex(hex_digest) == digest_data(existing_content) + + +def install_file(content: bytes, dest_path: str): + """Write a byte string to a given destination file. + + Args: + content: Input data, as a byte string + dest_path: Installation path + """ + parent_path = os.path.dirname(dest_path) + if not os.path.exists(parent_path): + os.makedirs(parent_path) + + with open(dest_path, "wb") as f: + f.write(content) + + +def download_file(url: str, expected_digest: Optional[bytes] = None): + """Download a file from a given URL. + + Args: + url: Input URL + expected_digest: Optional digest of the file + as a byte string + Returns: + URL content as binary string. + """ + r = requests.get(url, allow_redirects=True) + content = r.content + if expected_digest is not None: + digest = digest_data(r.content) + if digest != expected_digest: + raise ValueError( + "%s has invalid digest %s (expected %s)" + % (url, digest.hex(), expected_digest.hex()) + ) + + return content + + +def extract_file_from_zip_archive( + archive: zipfile.ZipFile, + archive_name: str, + filepath: str, + expected_digest: Optional[bytes] = None, +): + """Extract a file from a given zipfile.ZipFile archive. + + Args: + archive: Input ZipFile objec. + archive_name: Archive name or URL, only used to generate a + human-readable error message. + + filepath: Input filepath in archive. + expected_digest: Optional digest for the file. + Returns: + A new File instance corresponding to the extract file. + Raises: + ValueError if expected_digest is not None and does not match the + extracted file. + """ + file = archive.open(filepath) + if expected_digest is not None: + digest = digest_data(archive.open(filepath).read()) + if digest != expected_digest: + raise ValueError( + "%s in zip archive at %s has invalid digest %s (expected %s)" + % (filepath, archive_name, digest.hex(), expected_digest.hex()) + ) + return file.read() + + +def _get_and_install_file( + install_path: str, + hex_digest: Optional[str], + force_download: bool, + get_content: Callable[[], bytes], +) -> bool: + if not force_download and hex_digest is not None \ + and os.path.exists(install_path): + with open(install_path, "rb") as f: + content: bytes = f.read() + if bytes.fromhex(hex_digest) == digest_data(content): + return False + + content = get_content() + install_file(content, install_path) + return True + + +def download_and_install_item( + item: dict, install_dir: str, force_download: bool +) -> List[Tuple[str, bool]]: + """Download and install one item. + + Args: + item: Download item as a dictionary, see above for schema. + install_dir: Installation directory. + force_download: Set to True to force download and installation, even + if the font file is already installed with the right content. + + Returns: + A list of (install_name, status) tuples, where 'install_name' is the + file's installation name under 'install_dir', and 'status' is a + boolean that is True to indicate that the file was downloaded and + installed, or False to indicate that the file is already installed + with the right content. + """ + if "file_url" in item: + file_url = item["file_url"] + install_name = item.get("install_name", os.path.basename(file_url)) + install_path = os.path.join(install_dir, install_name) + hex_digest = item.get("hex_digest") + + def get_content(): + return download_file(file_url, hex_digest) + + status = _get_and_install_file( + install_path, hex_digest, force_download, get_content + ) + return [(install_name, status)] + + if "zip_url" in item: + # One or more files from a zip archive. + archive_url = item["zip_url"] + archive = zipfile.ZipFile(io.BytesIO(download_file(archive_url))) + + result = [] + for f in item["zip_files"]: + filename = f["filename"] + install_name = f.get("install_name", filename) + hex_digest = f.get("hex_digest") + + def get_content(): + return extract_file_from_zip_archive( + archive, + archive_url, + filename, + bytes.fromhex(hex_digest) if hex_digest else None, + ) + + status = _get_and_install_file( + os.path.join(install_dir, install_name), + hex_digest, + force_download, + get_content, + ) + result.append((install_name, status)) + + return result + + else: + raise ValueError("Unknown download item schema: %s" % item) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + + # Assume this script is under tests/scripts/ and tests/data/ + # is the default installation directory. + install_dir = os.path.normpath( + os.path.join(os.path.dirname(__file__), "..", "data") + ) + + parser.add_argument( + "--force", + action="store_true", + default=False, + help="Force download and installation of font files", + ) + + parser.add_argument( + "--install-dir", + default=install_dir, + help="Specify installation directory [%s]" % install_dir, + ) + + args = parser.parse_args() + + for item in _DOWNLOAD_ITEMS: + for install_name, status in download_and_install_item( + item, args.install_dir, args.force + ): + print("%s %s" % (install_name, + "INSTALLED" if status else "UP-TO-DATE")) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) + +# EOF -- cgit v1.2.3