receta: las fuentes del mundo — la distro mostraba cuadraditos a la mayor parte de la humanidad
El hueco más grande que medía `censo-distro.py`, y el que mejor muestra para qué sirve ese censo:
con el tablero ENTERO en verde —1158/1158 selladas, deuda 0, clausura 10 de 10— las únicas fuentes
del corpus eran `dejavu-fonts` y su variante nerd. DejaVu cubre latino, griego y cirílico. O sea
que chino, japonés, coreano, árabe, hindi, bengalí, tamil, tailandés, hebreo y los emoji salían
como cuadraditos. Ninguna otra métrica del repo podía ver eso.
Tres recetas porque son tres repositorios distintos, y ninguna sustituye a otra:
noto-fonts 321 ficheros · 48 MB Regular+Bold hinted de TODAS las familias
noto-fonts-cjk 2 .ttc · 86 MB zh-Hans, zh-Hant, ja y ko (cada .ttc trae los cuatro)
noto-fonts-emoji 1 .ttf · 10 MB NotoColorEmoji (CBDT/CBLC)
**Las selecciones están medidas sobre los tarballs antes de escribir las recetas**, no elegidas de
memoria. El de Noto general trae **7.937 `.ttf`** (cada familia en hinted/unhinted × ttf/slim/full ×
una docena de pesos): el criterio «Regular y Bold, hinted, de todas las familias» se eligió
justamente para NO juzgar qué idioma importa, que es la decisión que produjo el hueco. Sólo Regular
serían 226 ficheros y 33 MB, y se descartó porque sin Bold un título en hindi se sintetiza y sale
mal. El CJK trae siete pesos a ~43 MB cada uno (298 MB): entran dos, y los otros cinco son 215 MB
por imagen de escritorio que nada pide hoy. El de emoji trae ocho `.ttf` y cada descarte tiene su
motivo escrito —el COLRv1 vectorial se deja fuera a propósito: exige que el stack de texto lo
soporte, y esta distro tiene varios caminos de dibujo (Pango/cairo, Qt, llimphi/wgpu) que no se
midieron; con los dos puestos, cada toolkit elegiría uno y el mismo emoji se vería distinto según
la app.
⚠ EL FORMATO DECIDIÓ LAS FUENTES, y es un límite nuestro medido: **el `fetch` desempaqueta con
`tar -x`** (`fetch.rs:685`) y no abre `.zip`, que es como upstream publica el CJK. Por eso el CJK
sale de un **re-hospedaje de Alpine**, pineado por sha256 — está dicho en la receta con sus dos
salidas: que el `fetch` aprenda zip (media docena de líneas, desbloquea a muchos upstreams) o el
espejo de fuentes del ADR 0013. Es el punto feo y queda escrito en vez de escondido.
Las tres llevan **guardián de cuenta en el `install`**: si upstream reorganiza el árbol, el `find`
saldría vacío y el artefacto quedaría como un directorio sin fuentes — con contenido, reproducible y
mintiendo. Es la regla 3 del CLAUDE.md, y este repo ya la pagó.
⚠ Y lo que esto NO cierra, dicho en la receta de emoji: **un emoji no se ve sólo por estar la
fuente**. Hace falta un `.conf` que lo ordene por delante en su rango, o el resultado típico no es
el cuadradito sino el emoji en blanco y negro del fallback. Esa configuración es del SISTEMA, no de
un paquete de datos, y es su propia unidad de trabajo.
Sin construir todavía: se publican primero para que el clon que siembra al worker no muela recetas
que no están en ningún commit (el guardián de `cosecha-cron.sh` lo gritaría, y con razón).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Delta semántico
Ninguna definición cambió: el cambio es de texto, documentación o datos.
3 archivos · +218 −0
- recipes/noto-fonts-cjk.toml +69 −0
- recipes/noto-fonts-emoji.toml +69 −0
- recipes/noto-fonts.toml +80 −0
diff --git a/recipes/noto-fonts-cjk.toml b/recipes/noto-fonts-cjk.toml
new file mode 100644
index 00000000..7eab5649
--- /dev/null
+++ b/recipes/noto-fonts-cjk.toml
@@ -0,0 +1,69 @@
+# noto-fonts-cjk — chino, japonés y coreano. La mitad del hueco de fuentes que `noto-fonts` no tapa.
+#
+# ══ POR QUÉ ES UNA RECETA APARTE Y NO UNA LÍNEA EN `noto-fonts` ════════════════════════════════
+# El CJK de Noto vive en OTRO repositorio (`notofonts/noto-cjk`) y se publica en **`.zip`**. El
+# `fetch` de takana desempaqueta con `tar -x` (`takana-build/src/fetch.rs:685`): autodetecta el
+# compresor por la cabecera, pero no abre zip. Medido antes de escribir esto, no supuesto.
+#
+# ⚠ **Y DE AHÍ SALE EL ÚNICO PUNTO FEO DE ESTA RECETA, que conviene que esté escrito:** la fuente
+# es un **re-hospedaje de Alpine** (`dev.alpinelinux.org/archive/`), que es lo que ellos hicieron
+# por este mismo problema (`community/font-noto-cjk`). Está pineado por sha256, así que no puede
+# cambiar bajo nuestros pies — pero si Alpine lo retira, esta receta no puede reconstruirse desde
+# upstream sin resolver antes el zip.
+#
+# Las dos salidas, por si el día llega, y en orden de preferencia:
+# 1. **Que el `fetch` sepa abrir zip.** Es media docena de líneas en `fetch.rs` y desbloquea a
+# muchos upstreams, no sólo a éste. Es la cura de raíz.
+# 2. El espejo de fuentes del ADR 0013 ya cubre la desaparición del origen: una vez bajado, el
+# tarball vive en el espejo y el build no depende de que Alpine siga publicando.
+#
+# ══ QUÉ SE INSTALA: dos de siete, y es una decisión de PESO medida ═════════════════════════════
+# El tarball trae los **siete pesos** de `NotoSansCJK` como `.ttc` (Thin, Light, DemiLight,
+# Regular, Medium, Bold, Black): **298 MB descomprimidos, ~43 MB cada uno**. Un `.ttc` es una
+# colección: ese único fichero cubre chino simplificado, chino tradicional, japonés y coreano a la
+# vez.
+#
+# Se instalan **Regular y Bold** — ~86 MB. Los otros cinco pesos son tipografía fina que ninguna
+# app de la distro pide hoy, y 215 MB en cada imagen de escritorio para tenerlos no se paga solo.
+# Subirlo es una línea el día que algo los pida.
+#
+# ⚠ No hay Serif CJK en este tarball. Es un hueco conocido y menor: sin Sans CJK no se LEE nada;
+# sin Serif CJK se lee todo y un titular sale en palo seco.
+#
+# Data-only: fontconfig escanea `/usr/share/fonts` recursivamente, igual que con `dejavu-fonts`.
+name = "noto-fonts-cjk"
+version = "0_git20220127"
+license = "OFL-1.1"
+
+[source]
+tarball = "https://dev.alpinelinux.org/archive/font-noto-cjk/font-noto-cjk-0_git20220127.tar.xz"
+sha256 = "fa2551276a47c4a8fa15b451318a3e8e3aedb9b99f4f55028bd024a1f026bd13"
+
+[build]
+compiler = "zig-cc" # inerte: acá no se compila nada
+target = "x86_64-linux-musl"
+link = "dynamic"
+
+[build.phases]
+configure = "true"
+compile = "true"
+# El guardián exige los DOS: un `.ttc` que no esté deja la mitad del caso sin cubrir y el artefacto
+# igual sellaría —con contenido, reproducible y mintiendo—, que es la figura de la regla 3.
+install = '''
+set -e
+mkdir -p /out/usr/share/fonts/noto-cjk
+n=0
+for peso in Regular Bold; do
+ f="NotoSansCJK-$peso.ttc"
+ if [ ! -f "$f" ]; then
+ echo "noto-fonts-cjk: falta $f en el árbol — ¿cambió el tarball? NO sello un artefacto a medias" >&2
+ exit 1
+ fi
+ cp "$f" /out/usr/share/fonts/noto-cjk/
+ n=$((n + 1))
+done
+echo "noto-fonts-cjk: $n colecciones instaladas (zh-Hans, zh-Hant, ja y ko en cada una)"
+'''
+
+[deps]
+build = []
diff --git a/recipes/noto-fonts-emoji.toml b/recipes/noto-fonts-emoji.toml
new file mode 100644
index 00000000..6eb01170
--- /dev/null
+++ b/recipes/noto-fonts-emoji.toml
@@ -0,0 +1,69 @@
+# noto-fonts-emoji — los emoji. La tercera parte del hueco de fuentes.
+#
+# ══ POR QUÉ HACE FALTA, Y POR QUÉ NO LO TAPA `noto-fonts` ══════════════════════════════════════
+# Es un repositorio de Google distinto (`googlefonts/noto-emoji`) y no entra en el tarball mensual
+# de `notofonts.github.io`. Sin esto, cualquier texto con un emoji —un mensaje, un commit, el
+# nombre de una rama, media web— sale con el glifo de reemplazo. Medido con
+# `scripts/censo-distro.py` el 2026-10-19: la categoría «escritura del mundo» estaba 4/4 ausente.
+#
+# ══ UN FICHERO DE OCHO, Y CADA DESCARTE TIENE MOTIVO ═══════════════════════════════════════════
+# El repo trae ocho `.ttf` en `fonts/` y la diferencia entre ellos no es cosmética:
+#
+# NotoColorEmoji.ttf 10,2 MB ← ÉSTE
+# NotoColorEmoji_WindowsCompatible.ttf 10,2 MB formato para el rasterizador de Windows
+# NotoColorEmoji-emojicompat.ttf 10,4 MB lleva los metadatos de EmojiCompat de Android
+# NotoColorEmoji-noflags.ttf 9,4 MB sin banderas (por política de tiendas de apps)
+# NotoColorEmoji-flagsonly.ttf 0,8 MB sólo banderas
+# Noto-COLRv1.ttf / -noflags / -emojicompat el perfil COLRv1 vectorial
+#
+# Se instala **`NotoColorEmoji.ttf`**: es CBDT/CBLC (bitmap incrustado), que es lo que FreeType
+# rasteriza en color sin ayuda. El COLRv1 es vectorial y más moderno, y **se descartó a propósito**:
+# necesita que el stack de texto sepa COLRv1, y esta distro tiene varios caminos de dibujo
+# distintos —Pango/cairo en GTK, Qt en KDE, llimphi/wgpu en las apps propias— que no se midieron.
+# Poner los dos invita a que cada toolkit elija uno y el mismo emoji se vea distinto según la app.
+#
+# ⚠ Un emoji no se ve sólo por estar la fuente: hace falta que fontconfig la ordene por delante
+# para el rango de emoji, y eso es un `.conf`. Sin él, el resultado típico no es el cuadradito sino
+# el emoji en BLANCO Y NEGRO del fallback. **Esta receta NO trae ese `.conf`** y conviene decirlo:
+# la configuración de fontconfig es del SISTEMA, no del paquete de datos, y ponerla acá la haría
+# invisible para quien la busque. Es su propia unidad de trabajo, y sin ella esto queda a medio
+# camino — se declara igual, porque sin la fuente no hay nada que ordenar.
+#
+# Data-only: fontconfig escanea `/usr/share/fonts` recursivamente, igual que con `dejavu-fonts`.
+name = "noto-fonts-emoji"
+version = "2.051"
+license = "OFL-1.1"
+
+[source]
+tarball = "https://github.com/googlefonts/noto-emoji/archive/v2.051/font-noto-emoji-2.051.tar.gz"
+sha256 = "04f3d1e5605edebebac00a7a0becb390a4a3ead015066905b27935b30c18e745"
+
+[build]
+compiler = "zig-cc" # inerte: acá no se compila nada
+target = "x86_64-linux-musl"
+link = "dynamic"
+
+[build.phases]
+configure = "true"
+compile = "true"
+# El tamaño mínimo es un guardián con número: el `.ttf` de color pesa ~10 MB, y si upstream moviera
+# el fichero y quedara otro en su sitio, un `cp` silencioso sellaría un artefacto que no sirve.
+install = '''
+set -e
+f=fonts/NotoColorEmoji.ttf
+if [ ! -f "$f" ]; then
+ echo "noto-fonts-emoji: no está $f — ¿cambió el árbol de upstream? NO sello un artefacto a medias" >&2
+ exit 1
+fi
+bytes=$(wc -c < "$f")
+if [ "$bytes" -lt 5000000 ]; then
+ echo "noto-fonts-emoji: $f pesa $bytes bytes, esperaba >5 MB — eso no es la fuente de color" >&2
+ exit 1
+fi
+mkdir -p /out/usr/share/fonts/noto-emoji
+cp "$f" /out/usr/share/fonts/noto-emoji/
+echo "noto-fonts-emoji: NotoColorEmoji.ttf instalado ($bytes bytes)"
+'''
+
+[deps]
+build = []
diff --git a/recipes/noto-fonts.toml b/recipes/noto-fonts.toml
new file mode 100644
index 00000000..d5ce2008
--- /dev/null
+++ b/recipes/noto-fonts.toml
@@ -0,0 +1,80 @@
+# noto-fonts — las fuentes de TODOS los alfabetos que no son latino, griego ni cirílico.
+#
+# ══ POR QUÉ ENTRA, Y ES EL HUECO MÁS GRANDE QUE TENÍA LA DISTRO ════════════════════════════════
+# Medido el 2026-10-19 con `scripts/censo-distro.py`: las ÚNICAS fuentes del corpus eran
+# `dejavu-fonts` y su variante nerd. DejaVu cubre latino, griego, cirílico y algo de árabe/hebreo
+# — y nada más. O sea que la distro, con el tablero entero en verde (1158/1158 selladas, clausura
+# 10 de 10), **mostraba cuadraditos en chino, japonés, coreano, árabe, hindi, bengalí, tamil,
+# tailandés, hebreo, coreano y emoji**: la mayor parte de la humanidad.
+#
+# Esto no es una app que alguien pida: es la diferencia entre una distro y una demo en inglés.
+#
+# ══ LA SELECCIÓN: `hinted/ttf`, Regular y Bold, DE TODOS LOS ALFABETOS ═════════════════════════
+# El tarball trae **7.937 ficheros `.ttf`** — cada familia en hinted/unhinted × ttf/slim/full × una
+# docena de pesos. Instalarlo entero es cientos de megas de variantes que nadie elige.
+#
+# El criterio es deliberado y se eligió para NO tener que juzgar qué idioma importa, porque ésa es
+# exactamente la decisión que produjo el hueco: **Regular y Bold, hinted, de todas las familias**.
+# Medido sobre el tarball antes de escribir esto: **321 ficheros, 48 MB**. Con sólo Regular serían
+# 226 y 33 MB, y se descartó porque sin Bold un título en hindi se sintetiza y sale mal.
+#
+# · `hinted` y no `unhinted`: hay rasterización por CPU en el camino (llvmpipe) y el hinting es
+# lo que hace legible un cuerpo pequeño ahí.
+# · `ttf` y no `slim`/`full`: `slim` recorta glifos y `full` trae variantes que el shaper no pide.
+#
+# ⚠ **ESTO NO TRAE CJK NI EMOJI.** Son dos recetas hermanas y por razones distintas: el CJK de Noto
+# se publica en otro repositorio (y en `.zip`, ver `noto-fonts-cjk.toml`), y el emoji es un repo
+# aparte de Google. Las tres hacen falta; ninguna sustituye a otra.
+#
+# ══ POR QUÉ ESTE TARBALL Y NO EL RELEASE DE UPSTREAM ═══════════════════════════════════════════
+# Upstream publica los `.zip` de cada familia por separado. **El `fetch` de takana desempaqueta con
+# `tar -x`** (`takana-build/src/fetch.rs:685`), que autodetecta el compresor por la cabecera pero NO
+# abre zip: una receta con un `.zip` muere al desempaquetar. El tarball mensual de
+# `notofonts.github.io` es `.tar.gz` y trae todas las familias juntas, que es lo que Alpine usa
+# también (`community/font-noto`, misma URL con otra versión).
+#
+# Es el archive generado por GitHub a partir de un TAG, no un release subido: su sha256 es estable
+# mientras el tag no se mueva, y el tag de una release mensual no se mueve. (La reserva del ADR 0006
+# sobre los `/archive/` vale para tags que se reescriben; una release fechada no es ese caso.)
+#
+# Data-only: no compila nada. El `.pc` no existe y no hace falta — fontconfig escanea
+# `/usr/share/fonts` recursivamente, igual que con `dejavu-fonts`.
+name = "noto-fonts"
+version = "2026.10.01"
+license = "OFL-1.1"
+
+[source]
+tarball = "https://github.com/notofonts/notofonts.github.io/archive/refs/tags/noto-monthly-release-2026.10.01.tar.gz"
+sha256 = "65493b5967fd11372c1566e87f38a05c8c80a0e7f1fee4644fcaad23731a7b8e"
+
+[build]
+compiler = "zig-cc" # inerte: acá no se compila nada, pero el campo es obligatorio
+target = "x86_64-linux-musl"
+link = "dynamic"
+
+[build.phases]
+configure = "true"
+compile = "true"
+# ⚠ El `find` va con `-path '*/hinted/ttf/*'` y NO con `-name`, porque el árbol tiene la misma
+# familia repetida en `unhinted/` y en `slim/`: filtrar por nombre traería cuatro copias de cada
+# fichero y el artefacto saldría cuatro veces más gordo sin añadir un glifo.
+# El guardián de cuenta no es adorno: si upstream reorganiza el árbol —y este repo se reorganizó
+# al menos una vez— el `find` saldría VACÍO, el `install` saldría 0 y el artefacto quedaría como un
+# directorio sin fuentes, que es exactamente un cache-hit envenenado (regla 3 del CLAUDE.md).
+install = '''
+set -e
+mkdir -p /out/usr/share/fonts/noto
+n=0
+for f in $(find fonts -path '*/hinted/ttf/*' \( -name '*-Regular.ttf' -o -name '*-Bold.ttf' \) | sort); do
+ cp "$f" /out/usr/share/fonts/noto/
+ n=$((n + 1))
+done
+echo "noto-fonts: $n ficheros instalados"
+if [ "$n" -lt 250 ]; then
+ echo "noto-fonts: esperaba >=250 ficheros y encontré $n — ¿cambió el árbol de upstream? NO sello un artefacto a medias" >&2
+ exit 1
+fi
+'''
+
+[deps]
+build = []