about summary refs log tree commit diff
path: root/nixpkgs/pkgs/data/fonts
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/pkgs/data/fonts')
-rw-r--r--nixpkgs/pkgs/data/fonts/noto-fonts/default.nix48
-rw-r--r--nixpkgs/pkgs/data/fonts/noto-fonts/noto-emoji.hashes.json7
-rw-r--r--nixpkgs/pkgs/data/fonts/noto-fonts/noto-emoji.json30
-rwxr-xr-xnixpkgs/pkgs/data/fonts/noto-fonts/noto-emoji.py183
-rw-r--r--nixpkgs/pkgs/data/fonts/twitter-color-emoji/default.nix10
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/