diff options
Diffstat (limited to 'nixpkgs/pkgs/data/fonts')
-rw-r--r-- | nixpkgs/pkgs/data/fonts/noto-fonts/default.nix | 48 | ||||
-rw-r--r-- | nixpkgs/pkgs/data/fonts/noto-fonts/noto-emoji.hashes.json | 7 | ||||
-rw-r--r-- | nixpkgs/pkgs/data/fonts/noto-fonts/noto-emoji.json | 30 | ||||
-rwxr-xr-x | nixpkgs/pkgs/data/fonts/noto-fonts/noto-emoji.py | 183 | ||||
-rw-r--r-- | nixpkgs/pkgs/data/fonts/twitter-color-emoji/default.nix | 10 |
5 files changed, 271 insertions, 7 deletions
diff --git a/nixpkgs/pkgs/data/fonts/noto-fonts/default.nix b/nixpkgs/pkgs/data/fonts/noto-fonts/default.nix index 65daffe0ee58..79288cb5f4c8 100644 --- a/nixpkgs/pkgs/data/fonts/noto-fonts/default.nix +++ b/nixpkgs/pkgs/data/fonts/noto-fonts/default.nix @@ -164,7 +164,7 @@ rec { sha256 = "sha256-y1103SS0qkZMhEL5+7kQZ+OBs5tRaqkqOcs4796Fzhg="; }; - noto-fonts-emoji = + noto-fonts-color-emoji = let version = "2.038"; emojiPythonEnv = @@ -217,7 +217,7 @@ rec { ''; meta = with lib; { - description = "Color and Black-and-White emoji fonts"; + description = "Color emoji font"; homepage = "https://github.com/googlefonts/noto-emoji"; license = with licenses; [ ofl asl20 ]; platforms = platforms.all; @@ -225,6 +225,50 @@ rec { }; }; + noto-fonts-monochrome-emoji = + # Metadata fetched from + # https://www.googleapis.com/webfonts/v1/webfonts?key=${GOOGLE_FONTS_TOKEN}&family=Noto+Emoji + let metadata = with builtins; head (fromJSON (readFile ./noto-emoji.json)).items; + urlHashes = with builtins; fromJSON (readFile ./noto-emoji.hashes.json); + + in + stdenvNoCC.mkDerivation { + pname = "noto-fonts-monochrome-emoji"; + version = "${lib.removePrefix "v" metadata.version}.${metadata.lastModified}"; + preferLocalBuild = true; + + dontUnpack = true; + srcs = let + weightNames = { + "300" = "Light"; + regular = "Regular"; + "500" = "Medium"; + "600" = "SemiBold"; + "700" = "Bold"; + }; + in lib.mapAttrsToList + (variant: url: fetchurl { name = "NotoEmoji-${weightNames.${variant}}.ttf"; + hash = urlHashes.${url}; + inherit url; } ) + metadata.files; + + installPhase = '' + for src in $srcs; do + install -D $src $out/share/fonts/noto/$(stripHash $src) + done + ''; + + meta = with lib; { + description = "Monochrome emoji font"; + homepage = "https://fonts.google.com/noto/specimen/Noto+Emoji"; + license = [ licenses.ofl ]; + maintainers = [ maintainers.nicoo ]; + + platforms = platforms.all; + sourceProvenance = [ sourceTypes.binaryBytecode ]; + }; + }; + noto-fonts-emoji-blob-bin = let pname = "noto-fonts-emoji-blob-bin"; diff --git a/nixpkgs/pkgs/data/fonts/noto-fonts/noto-emoji.hashes.json b/nixpkgs/pkgs/data/fonts/noto-fonts/noto-emoji.hashes.json new file mode 100644 index 000000000000..2f22a24a34c0 --- /dev/null +++ b/nixpkgs/pkgs/data/fonts/noto-fonts/noto-emoji.hashes.json @@ -0,0 +1,7 @@ +{ + "http://fonts.gstatic.com/s/notoemoji/v46/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob_10jwvS-FGJCMY.ttf": "sha256-9ndQqJJzsCkR6KcYRNVW3wXWMxcH+0QzFgQQdCG8vSo=", + "http://fonts.gstatic.com/s/notoemoji/v46/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob-r0jwvS-FGJCMY.ttf": "sha256-AXGLdWebddyJhTKMW/D/6tW8ODcaXrUM96m2hN9wYlg=", + "http://fonts.gstatic.com/s/notoemoji/v46/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob-Z0jwvS-FGJCMY.ttf": "sha256-wzF9kKNMeQTYZ2QUT5pIgauhl2qMpZ2nMLNTeAJuqtQ=", + "http://fonts.gstatic.com/s/notoemoji/v46/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob911TwvS-FGJCMY.ttf": "sha256-NIelE8X+lKtH6yT3eFPZV7zYUR3Y5GnNobAbf7AckR0=", + "http://fonts.gstatic.com/s/notoemoji/v46/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob9M1TwvS-FGJCMY.ttf": "sha256-zkJuJ8YlTrUV+28wHIqny3yQvjvZqEPG4WXYmaLcY8A=" +} diff --git a/nixpkgs/pkgs/data/fonts/noto-fonts/noto-emoji.json b/nixpkgs/pkgs/data/fonts/noto-fonts/noto-emoji.json new file mode 100644 index 000000000000..66b0292906b5 --- /dev/null +++ b/nixpkgs/pkgs/data/fonts/noto-fonts/noto-emoji.json @@ -0,0 +1,30 @@ +{ + "kind": "webfonts#webfontList", + "items": [ + { + "family": "Noto Emoji", + "variants": [ + "300", + "regular", + "500", + "600", + "700" + ], + "subsets": [ + "emoji" + ], + "version": "v46", + "lastModified": "2023-09-07", + "files": { + "300": "http://fonts.gstatic.com/s/notoemoji/v46/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob_10jwvS-FGJCMY.ttf", + "regular": "http://fonts.gstatic.com/s/notoemoji/v46/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob-r0jwvS-FGJCMY.ttf", + "500": "http://fonts.gstatic.com/s/notoemoji/v46/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob-Z0jwvS-FGJCMY.ttf", + "600": "http://fonts.gstatic.com/s/notoemoji/v46/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob911TwvS-FGJCMY.ttf", + "700": "http://fonts.gstatic.com/s/notoemoji/v46/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob9M1TwvS-FGJCMY.ttf" + }, + "category": "sans-serif", + "kind": "webfonts#webfont", + "menu": "http://fonts.gstatic.com/s/notoemoji/v46/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob-r0gwuQeU.ttf" + } + ] +} diff --git a/nixpkgs/pkgs/data/fonts/noto-fonts/noto-emoji.py b/nixpkgs/pkgs/data/fonts/noto-fonts/noto-emoji.py new file mode 100755 index 000000000000..9f1eadd95bca --- /dev/null +++ b/nixpkgs/pkgs/data/fonts/noto-fonts/noto-emoji.py @@ -0,0 +1,183 @@ +#!/usr/bin/env nix-shell +#! nix-shell -i "python3 -I" -p python3 + +from contextlib import contextmanager +from pathlib import Path +from typing import Iterable, Optional +from urllib import request + +import hashlib, json + + +def getMetadata(apiKey: str, family: str = "Noto Emoji"): + '''Fetch the Google Fonts metadata for a given family. + + An API key can be obtained by anyone with a Google account (🚮) from + `https://developers.google.com/fonts/docs/developer_api#APIKey` + ''' + from urllib.parse import urlencode + + with request.urlopen( + "https://www.googleapis.com/webfonts/v1/webfonts?" + + urlencode({ 'key': apiKey, 'family': family }) + ) as req: + return json.load(req) + +def getUrls(metadata) -> Iterable[str]: + '''Fetch all files' URLs from Google Fonts' metadata. + + The metadata must obey the API v1 schema, and can be obtained from: + https://www.googleapis.com/webfonts/v1/webfonts?key=${GOOGLE_FONTS_TOKEN}&family=${FAMILY} + ''' + return ( url for i in metadata['items'] for _, url in i['files'].items() ) + + +def hashUrl(url: str, *, hash: str = 'sha256'): + '''Compute the hash of the data from HTTP GETing a given `url`. + + The `hash` must be an algorithm name `hashlib.new` accepts. + ''' + with request.urlopen(url) as req: + return hashlib.new(hash, req.read()) + + +def sriEncode(h) -> str: + '''Encode a hash in the SRI format. + + Takes a `hashlib` object, and produces a string that + nixpkgs' `fetchurl` accepts as `hash` parameter. + ''' + from base64 import b64encode + return f"{h.name}-{b64encode(h.digest()).decode()}" + +def validateSRI(sri: Optional[str]) -> Optional[str]: + '''Decode an SRI hash, return `None` if invalid. + + This is not a full SRI hash parser, hash options aren't supported. + ''' + from base64 import b64decode + + if sri is None: + return None + + try: + hashName, b64 = sri.split('-', 1) + + h = hashlib.new(hashName) + digest = b64decode(b64, validate=True) + assert len(digest) == h.digest_size + + except: + return None + else: + return sri + + +def hashUrls( + urls: Iterable[str], + knownHashes: dict[str, str] = {}, +) -> dict[str, str]: + '''Generate a `dict` mapping URLs to SRI-encoded hashes. + + The `knownHashes` optional parameter can be used to avoid + re-downloading files whose URL have not changed. + ''' + return { + url: validateSRI(knownHashes.get(url)) or sriEncode(hashUrl(url)) + for url in urls + } + + +@contextmanager +def atomicFileUpdate(target: Path): + '''Atomically replace the contents of a file. + + Yields an open file to write into; upon exiting the context, + the file is closed and (atomically) replaces the `target`. + + Guarantees that the `target` was either successfully overwritten + with new content and no exception was raised, or the temporary + file was cleaned up. + ''' + from tempfile import mkstemp + fd, _p = mkstemp( + dir = target.parent, + prefix = target.name, + ) + tmpPath = Path(_p) + + try: + with open(fd, 'w') as f: + yield f + + tmpPath.replace(target) + + except Exception: + tmpPath.unlink(missing_ok = True) + raise + + +if __name__ == "__main__": + from os import environ + from urllib.error import HTTPError + + environVar = 'GOOGLE_FONTS_TOKEN' + currentDir = Path(__file__).parent + metadataPath = currentDir / 'noto-emoji.json' + + try: + apiToken = environ[environVar] + metadata = getMetadata(apiToken) + + except (KeyError, HTTPError) as exn: + # No API key in the environment, or the query was rejected. + match exn: + case KeyError if exn.args[0] == environVar: + print(f"No '{environVar}' in the environment, " + "skipping metadata update") + + case HTTPError if exn.getcode() == 403: + print("Got HTTP 403 (Forbidden)") + if apiToken != '': + print("Your Google API key appears to be valid " + "but does not grant access to the fonts API.") + print("Aborting!") + raise SystemExit(1) + + case HTTPError if exn.getcode() == 400: + # Printing the supposed token should be fine, as this is + # what the API returns on invalid tokens. + print(f"Got HTTP 400 (Bad Request), is this really an API token: '{apiToken}' ?") + case _: + # Unknown error, let's bubble it up + raise + + # In that case just use the existing metadata + with metadataPath.open() as metadataFile: + metadata = json.load(metadataFile) + + lastModified = metadata["items"][0]["lastModified"]; + print(f"Using metadata from file, last modified {lastModified}") + + else: + # If metadata was successfully fetched, validate and persist it + lastModified = metadata["items"][0]["lastModified"]; + print(f"Fetched current metadata, last modified {lastModified}") + with atomicFileUpdate(metadataPath) as metadataFile: + json.dump(metadata, metadataFile, indent = 2) + metadataFile.write("\n") # Pacify nixpkgs' dumb editor config check + + hashPath = currentDir / 'noto-emoji.hashes.json' + try: + with hashPath.open() as hashFile: + hashes = json.load(hashFile) + except FileNotFoundError: + hashes = {} + + with atomicFileUpdate(hashPath) as hashFile: + json.dump( + hashUrls(getUrls(metadata), knownHashes = hashes), + hashFile, + indent = 2, + ) + hashFile.write("\n") # Pacify nixpkgs' dumb editor config check diff --git a/nixpkgs/pkgs/data/fonts/twitter-color-emoji/default.nix b/nixpkgs/pkgs/data/fonts/twitter-color-emoji/default.nix index cb9021d68f3f..c3e41cca36dd 100644 --- a/nixpkgs/pkgs/data/fonts/twitter-color-emoji/default.nix +++ b/nixpkgs/pkgs/data/fonts/twitter-color-emoji/default.nix @@ -10,7 +10,7 @@ , python3 , which , zopfli -, noto-fonts-emoji +, noto-fonts-color-emoji }: let @@ -33,15 +33,15 @@ stdenv.mkDerivation rec { inherit version; srcs = [ - noto-fonts-emoji.src + noto-fonts-color-emoji.src twemojiSrc ]; - sourceRoot = noto-fonts-emoji.src.name; + sourceRoot = noto-fonts-color-emoji.src.name; postUnpack = '' chmod -R +w ${twemojiSrc.name} - mv ${twemojiSrc.name} ${noto-fonts-emoji.src.name} + mv ${twemojiSrc.name} ${noto-fonts-color-emoji.src.name} ''; nativeBuildInputs = [ @@ -67,7 +67,7 @@ stdenv.mkDerivation rec { "s#http://scripts.sil.org/OFL#http://creativecommons.org/licenses/by/4.0/#" ]; in '' - ${noto-fonts-emoji.postPatch} + ${noto-fonts-color-emoji.postPatch} sed '${templateSubstitutions}' NotoColorEmoji.tmpl.ttx.tmpl > TwitterColorEmoji.tmpl.ttx.tmpl pushd ${twemojiSrc.name}/assets/72x72/ |