diff --git a/doc/builders/packages/ibus.section.md b/doc/builders/packages/ibus.section.md index 4eb74c0b6912a..817e55d56f1f9 100644 --- a/doc/builders/packages/ibus.section.md +++ b/doc/builders/packages/ibus.section.md @@ -29,12 +29,12 @@ _Note: each language passed to `langs` must be an attribute name in `pkgs.hunspe ## Built-in emoji picker {#sec-ibus-typing-booster-emoji-picker} -The `ibus-engines.typing-booster` package contains a program named `emoji-picker`. To display all emojis correctly, a special font such as `noto-fonts-emoji` is needed: +The `ibus-engines.typing-booster` package contains a program named `emoji-picker`. To display all emojis correctly, a special font such as `noto-fonts-color-emoji` is needed: On NixOS, it can be installed using the following expression: ```nix { pkgs, ... }: { - fonts.packages = with pkgs; [ noto-fonts-emoji ]; + fonts.packages = with pkgs; [ noto-fonts-color-emoji ]; } ``` diff --git a/nixos/doc/manual/release-notes/rl-2311.section.md b/nixos/doc/manual/release-notes/rl-2311.section.md index a59dccfbc42bf..a2041db2a8744 100644 --- a/nixos/doc/manual/release-notes/rl-2311.section.md +++ b/nixos/doc/manual/release-notes/rl-2311.section.md @@ -193,6 +193,10 @@ - The `hail` NixOS module was removed, as `hail` was unmaintained since 2017. +- Package `noto-fonts-emoji` was renamed to `noto-fonts-color-emoji`; + see [#221181](/~https://github.com/NixOS/nixpkgs/issues/221181). + + ## Other Notable Changes {#sec-release-23.11-notable-changes} - The Cinnamon module now enables XDG desktop integration by default. If you are experiencing collisions related to xdg-desktop-portal-gtk you can safely remove `xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];` from your NixOS configuration. diff --git a/nixos/modules/config/fonts/packages.nix b/nixos/modules/config/fonts/packages.nix index 46907d5411ca5..37b705ecb345a 100644 --- a/nixos/modules/config/fonts/packages.nix +++ b/nixos/modules/config/fonts/packages.nix @@ -37,7 +37,7 @@ in gyre-fonts # TrueType substitutes for standard PostScript fonts liberation_ttf unifont - noto-fonts-emoji + noto-fonts-color-emoji ]); }; } diff --git a/nixos/tests/fontconfig-default-fonts.nix b/nixos/tests/fontconfig-default-fonts.nix index d8c6ea0f721b5..293dc43f91f38 100644 --- a/nixos/tests/fontconfig-default-fonts.nix +++ b/nixos/tests/fontconfig-default-fonts.nix @@ -9,7 +9,7 @@ import ./make-test-python.nix ({ lib, ... }: nodes.machine = { config, pkgs, ... }: { fonts.enableDefaultPackages = true; # Background fonts fonts.packages = with pkgs; [ - noto-fonts-emoji + noto-fonts-color-emoji cantarell-fonts twitter-color-emoji source-code-pro diff --git a/nixos/tests/noto-fonts.nix b/nixos/tests/noto-fonts.nix index edbb0db4cb7aa..b871f5f517294 100644 --- a/nixos/tests/noto-fonts.nix +++ b/nixos/tests/noto-fonts.nix @@ -11,7 +11,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: { noto-fonts noto-fonts-cjk-sans noto-fonts-cjk-serif - noto-fonts-emoji + noto-fonts-color-emoji ]; fontconfig.defaultFonts = { serif = [ "Noto Serif" "Noto Serif CJK SC" ]; diff --git a/pkgs/applications/networking/instant-messengers/deltachat-desktop/default.nix b/pkgs/applications/networking/instant-messengers/deltachat-desktop/default.nix index d6f0dae7038b6..5af78b5327ebc 100644 --- a/pkgs/applications/networking/instant-messengers/deltachat-desktop/default.nix +++ b/pkgs/applications/networking/instant-messengers/deltachat-desktop/default.nix @@ -8,7 +8,7 @@ , libdeltachat , makeDesktopItem , makeWrapper -, noto-fonts-emoji +, noto-fonts-color-emoji , pkg-config , python3 , roboto @@ -85,7 +85,7 @@ buildNpmPackage rec { install -D build/icon.png \ $out/share/icons/hicolor/scalable/apps/deltachat.png - ln -sf ${noto-fonts-emoji}/share/fonts/noto/NotoColorEmoji.ttf \ + ln -sf ${noto-fonts-color-emoji}/share/fonts/noto/NotoColorEmoji.ttf \ $out/lib/node_modules/deltachat-desktop/html-dist/fonts/noto/emoji for font in $out/lib/node_modules/deltachat-desktop/html-dist/fonts/Roboto-*.ttf; do ln -sf ${roboto}/share/fonts/truetype/$(basename $font) \ diff --git a/pkgs/data/fonts/noto-fonts/default.nix b/pkgs/data/fonts/noto-fonts/default.nix index 65daffe0ee588..79288cb5f4c80 100644 --- a/pkgs/data/fonts/noto-fonts/default.nix +++ b/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/pkgs/data/fonts/noto-fonts/noto-emoji.hashes.json b/pkgs/data/fonts/noto-fonts/noto-emoji.hashes.json new file mode 100644 index 0000000000000..2f22a24a34c0b --- /dev/null +++ b/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/pkgs/data/fonts/noto-fonts/noto-emoji.json b/pkgs/data/fonts/noto-fonts/noto-emoji.json new file mode 100644 index 0000000000000..66b0292906b59 --- /dev/null +++ b/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/pkgs/data/fonts/noto-fonts/noto-emoji.py b/pkgs/data/fonts/noto-fonts/noto-emoji.py new file mode 100755 index 0000000000000..9f1eadd95bca5 --- /dev/null +++ b/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/pkgs/data/fonts/twitter-color-emoji/default.nix b/pkgs/data/fonts/twitter-color-emoji/default.nix index cb9021d68f3fa..c3e41cca36dd1 100644 --- a/pkgs/data/fonts/twitter-color-emoji/default.nix +++ b/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/ diff --git a/pkgs/top-level/aliases.nix b/pkgs/top-level/aliases.nix index 5a48833edd6f4..863c931c82395 100644 --- a/pkgs/top-level/aliases.nix +++ b/pkgs/top-level/aliases.nix @@ -1229,6 +1229,7 @@ mapAliases ({ nomad_1_3 = throw "nomad_1_3 has been removed because it's outdated. Use a a newer version instead"; # Added 2023-09-02 nordic-polar = throw "nordic-polar was removed on 2021-05-27, now integrated in nordic"; # Added 2021-05-27 noto-fonts-cjk = noto-fonts-cjk-sans; # Added 2021-12-16 + noto-fonts-emoji = noto-fonts-color-emoji; # Added 2023-09-09 noto-fonts-extra = noto-fonts; # Added 2023-04-08 nottetris2 = throw "nottetris2 was removed because it is unmaintained by upstream and broken"; # Added 2022-01-15 now-cli = throw "now-cli has been replaced with nodePackages.vercel"; # Added 2021-08-05 diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 065dba26fd229..d8fd43bebcb13 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -29924,8 +29924,10 @@ with pkgs; noto-fonts-lgc-plus noto-fonts-cjk-sans noto-fonts-cjk-serif - noto-fonts-emoji - noto-fonts-emoji-blob-bin; + noto-fonts-color-emoji + noto-fonts-emoji-blob-bin + noto-fonts-monochrome-emoji + ; nuclear = callPackage ../applications/audio/nuclear { };