mirror of
https://github.com/SebastianWendel/nixpkgs.git
synced 2024-09-20 20:39:04 +02:00
Merge pull request #254149 from nbraud/noto-emoji
This commit is contained in:
commit
7163f125e5
|
@ -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 ];
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -37,7 +37,7 @@ in
|
|||
gyre-fonts # TrueType substitutes for standard PostScript fonts
|
||||
liberation_ttf
|
||||
unifont
|
||||
noto-fonts-emoji
|
||||
noto-fonts-color-emoji
|
||||
]);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" ];
|
||||
|
|
|
@ -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) \
|
||||
|
|
|
@ -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";
|
||||
|
|
7
pkgs/data/fonts/noto-fonts/noto-emoji.hashes.json
Normal file
7
pkgs/data/fonts/noto-fonts/noto-emoji.hashes.json
Normal file
|
@ -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="
|
||||
}
|
30
pkgs/data/fonts/noto-fonts/noto-emoji.json
Normal file
30
pkgs/data/fonts/noto-fonts/noto-emoji.json
Normal file
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
183
pkgs/data/fonts/noto-fonts/noto-emoji.py
Executable file
183
pkgs/data/fonts/noto-fonts/noto-emoji.py
Executable file
|
@ -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
|
|
@ -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/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 { };
|
||||
|
||||
|
|
Loading…
Reference in a new issue