created variable title svg & png export module
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
# Le Shed SVG Export
|
||||
|
||||
Exporte en SVG (vectoriel) et PNG (haute définition) la typographie
|
||||
générative lettre par lettre du thème `leshed` : le logo du site et tout
|
||||
titre marqué `.variable-title` — tel qu'affiché à l'instant précis du clic
|
||||
(tirage de graisses/italiques en cours, retours à ligne, mise en majuscule
|
||||
CSS…), pas une version figée/officielle.
|
||||
|
||||
## Contexte
|
||||
|
||||
Le thème `leshed` découpe ces textes en spans par caractère (Splitting.js)
|
||||
et tire aléatoirement, à chaque affichage, une graisse (`font-variation-settings:
|
||||
'wght'`, police variable Epilogue) et un état italique par lettre — voir
|
||||
`styleWordChars()` dans `assets/js/main.js` du thème. Rien n'est jamais
|
||||
figé côté thème ; ce module permet de capturer et exporter un tirage
|
||||
précis pour un usage print.
|
||||
|
||||
## Ce que ce module ne fait pas
|
||||
|
||||
- Il ne modifie rien dans le thème `leshed`. Il consomme uniquement un
|
||||
contrat DOM déjà exposé par le thème : la classe `.variable-title` sur
|
||||
un élément, et des spans `.char` à l'intérieur, stylés inline en
|
||||
`font-variation-settings` / `font-style`. Si le thème actif n'expose
|
||||
jamais cette structure, le module reste installable mais n'a aucun
|
||||
effet visible.
|
||||
- Il n'exporte pas de texte libre/arbitraire — seulement ce que le thème a
|
||||
déjà marqué `.variable-title`. Aujourd'hui, ça se limite au logo du site
|
||||
et aux titres de node "projet" (voir le sélecteur dans `initTitles()` du
|
||||
thème) : pas encore les pages de taxonomie ni les autres types de
|
||||
contenu, faute de marquage côté thème.
|
||||
- Il ne vendorise aucune police. Voir "Résolution dynamique de la police"
|
||||
ci-dessous.
|
||||
- Il ne vendorise pas non plus son moteur de rendu de police (harfbuzzjs) :
|
||||
installé via Composer, pas commité dans les sources du module. Voir
|
||||
"Installation" ci-dessous.
|
||||
|
||||
## Architecture
|
||||
|
||||
Toute la conversion texte → tracés vectoriels se fait **côté client**,
|
||||
dans le navigateur. Le module Drupal ne fait que :
|
||||
|
||||
1. Déclarer la permission `export leshed typography`.
|
||||
2. Décider, dans `hook_page_attachments()` (`leshed_svg_export.module`),
|
||||
si la librairie JS est attachée à la page — uniquement si l'utilisateur
|
||||
courant a la permission. Un utilisateur non autorisé ne charge donc
|
||||
strictement aucun asset (wasm, police, JS) de ce module.
|
||||
3. Fournir ses propres fichiers JS/CSS et l'URL vers le moteur `harfbuzzjs`
|
||||
installé par ailleurs via Composer (voir "Installation").
|
||||
|
||||
## Installation
|
||||
|
||||
Le moteur `harfbuzzjs` (WebAssembly, MIT) n'est **pas commité** dans ce
|
||||
module — ça paraîtrait bizarre de vendoriser un moteur générique dans les
|
||||
sources d'un module métier, et ça découplerait sa mise à jour du reste du
|
||||
projet. Il est déclaré comme dépendance Composer, exactement comme les
|
||||
modules contrib :
|
||||
|
||||
- `composer.json` déclare le dépôt `https://asset-packagist.org` (miroir
|
||||
Composer du registre npm) et requiert `npm-asset/harfbuzzjs`, ainsi que
|
||||
`oomphinc/composer-installers-extender` (nécessaire pour que
|
||||
`composer/installers` sache installer un paquet de type `npm-asset` —
|
||||
package marqué "abandonné" sur Packagist mais toujours fonctionnel ;
|
||||
aucun remplaçant n'est proposé à ce jour, à surveiller).
|
||||
- `extra.installer-types`/`installer-paths` mappent `type:npm-asset` vers
|
||||
`web/libraries/{$name}`, comme le fait déjà `type:drupal-library`.
|
||||
- Résultat : `composer install`/`composer update` installe `harfbuzzjs`
|
||||
dans `web/libraries/harfbuzzjs/` — **aucune étape d'installation
|
||||
supplémentaire** par rapport au reste du projet.
|
||||
- `leshed_svg_export.module` expose l'URL correspondante
|
||||
(`web/libraries/harfbuzzjs/dist/index.mjs`) via
|
||||
`drupalSettings.leshedSvgExport.harfbuzzUrl`.
|
||||
|
||||
**Prérequis serveur (nginx)** : le fichier `dist/index.mjs` du paquet doit
|
||||
être servi avec un type MIME JavaScript. Beaucoup de configurations nginx
|
||||
(dont celle de ce projet à l'origine) n'ont pas de mapping pour l'extension
|
||||
`.mjs` et répondent `application/octet-stream`, que les navigateurs
|
||||
refusent de charger comme module ES (Firefox notamment). Ajouter dans
|
||||
`Docker/nginx/default.conf` :
|
||||
|
||||
```nginx
|
||||
location ~ \.mjs$ {
|
||||
default_type application/javascript;
|
||||
}
|
||||
```
|
||||
|
||||
### `js/svg-export.js`
|
||||
|
||||
Comportement Drupal (`Drupal.behaviors.leshedSvgExport`) :
|
||||
|
||||
- Surveille le DOM via `MutationObserver` pour repérer tout élément
|
||||
`.variable-title` (existant ou ajouté/marqué plus tard — pas d'hypothèse
|
||||
d'ordre de chargement avec le script du thème).
|
||||
- Pour chaque titre trouvé, crée un petit badge avec deux boutons ("SVG",
|
||||
"PNG") et l'ajoute comme **enfant DOM du titre lui-même**
|
||||
(`titleEl.appendChild(widget)`), positionné en `position: absolute`
|
||||
(`top: 0; left: 0` — superposé au coin haut-gauche du titre, jamais au
|
||||
reste). Le titre reçoit aussi la classe `leshed-svg-export-target` (marqueur
|
||||
propre au module, indépendant de `.variable-title` — utilisé par le CSS
|
||||
ci-dessous, et disponible pour cibler "un titre équipé du bouton export"
|
||||
sans dépendre du nom de classe du thème). Il reçoit `position: relative`
|
||||
en JS uniquement s'il ne l'a pas déjà (visuellement neutre, ne bouge/ne
|
||||
redimensionne rien) pour servir de contexte de positionnement. Étant en
|
||||
`position: absolute`, le badge est retiré du flux normal : il ne peut
|
||||
donc jamais influencer la mise en page du front, et le navigateur le
|
||||
garde attaché au titre automatiquement au scroll/resize — aucune
|
||||
synchronisation JS de position n'est nécessaire.
|
||||
- Visibilité **entièrement en CSS**
|
||||
(`.leshed-svg-export-target:hover > .leshed-svg-export`, `:focus-within`,
|
||||
voir `css/svg-export.css`), sans aucun JS de détection de
|
||||
survol, et **sans jamais recouvrir le reste du titre** d'un calque
|
||||
invisible — un titre peut être un vrai lien ailleurs (le logo, un
|
||||
teaser) et doit rester entièrement cliquable.
|
||||
|
||||
**Limite connue, assumée** : ce hover ne peut pas se déclencher sur un
|
||||
titre que le thème a lui-même rendu non interactif
|
||||
(`pointer-events: none` — ex. le h2 décoratif surdimensionné d'une page
|
||||
"projet" en vue complète). Un élément à `pointer-events: none` ne reçoit
|
||||
jamais d'événement de survol, quoi qu'on écrive en CSS ; le contourner
|
||||
demanderait soit de modifier le CSS du thème, soit de recouvrir le titre
|
||||
d'un calque — les deux sont exclus. Sur tout titre sans cette règle
|
||||
(l'immense majorité), le survol fonctionne normalement.
|
||||
- Au clic : capture la largeur du conteneur du titre, puis pour chaque
|
||||
`.char` dans l'ordre du DOM — position (`getBoundingClientRect()`),
|
||||
style calculé (`font-family`, `font-style`, tous les axes de
|
||||
`font-variation-settings`, `text-transform`) — et convertit `.char.textContent`
|
||||
selon le `text-transform` réellement appliqué (uppercase / lowercase /
|
||||
capitalize, ce dernier basé sur le premier `.char` de chaque `.word`)
|
||||
avant de demander le tracé du bon caractère.
|
||||
- Assemble le SVG (fond transparent, remplissage noir uniquement — décision
|
||||
volontaire, pas configurable) et déclenche le téléchargement (`Blob` +
|
||||
`<a download>`).
|
||||
- Pour le PNG : rasterise le SVG assemblé via un `<canvas>` à ×4 la taille
|
||||
affichée (`PNG_SCALE`), puis `canvas.toBlob('image/png')`.
|
||||
|
||||
### `js/harfbuzz-export.js`
|
||||
|
||||
Résolution dynamique de la police et extraction des tracés, via
|
||||
[harfbuzzjs](https://github.com/harfbuzz/harfbuzzjs) (HarfBuzz compilé en
|
||||
WebAssembly, MIT — installé via Composer, voir "Installation" ci-dessus).
|
||||
`loadHarfbuzz(harfbuzzUrl)` importe directement l'URL fournie par le module
|
||||
PHP (`web/libraries/harfbuzzjs/dist/index.mjs`).
|
||||
|
||||
**Aucune police n'est embarquée dans ce module.** Pour chaque caractère :
|
||||
|
||||
1. Lit son style calculé (`font-family`, `font-style`, les axes de
|
||||
`font-variation-settings`).
|
||||
2. Cherche, dans `document.styleSheets` (CSSOM), la règle `@font-face`
|
||||
dont `font-family`/`font-style` correspondent, et en extrait l'URL du
|
||||
descripteur `src`.
|
||||
3. `fetch()` cette URL (peu importe le conteneur réel — ttf/otf/woff2 —
|
||||
HarfBuzz travaille sur les octets bruts), met en cache par URL.
|
||||
4. Instancie la police à la variation exacte lue (`font.setVariations()`),
|
||||
shape le caractère, extrait son tracé (`font.glyphToPath()`).
|
||||
|
||||
Conséquence : si le thème change de police demain (nouvelle famille,
|
||||
nouveaux axes variables, retour à une police statique), l'export suit
|
||||
automatiquement — rien à modifier dans ce module.
|
||||
|
||||
## Permission
|
||||
|
||||
`export leshed typography` — à assigner aux rôles voulus (aucun rôle par
|
||||
défaut). Contrôle uniquement l'attachement de la librairie ; ne dépend
|
||||
d'aucune configuration de thème.
|
||||
|
||||
## Style du widget
|
||||
|
||||
Volontairement **non harmonisé avec le thème front** : le widget imite le
|
||||
thème d'administration Gin en mode sombre (`css/svg-export.css`), avec les
|
||||
valeurs de couleur copiées directement des tokens Gin (`--gin-bg-app`,
|
||||
accent "blue" en mode sombre, etc. — voir
|
||||
`web/themes/contrib/gin/dist/css/theme/{variables,accent}.css`), en dur,
|
||||
puisque le CSS de Gin n'est pas chargé sur les pages front.
|
||||
|
||||
## Limites connues
|
||||
|
||||
- Sur un titre que le thème a rendu non interactif (`pointer-events: none`
|
||||
— ex. le h2 décoratif de la page "projet" en vue complète), le bouton
|
||||
n'apparaît jamais au survol souris (il reste accessible au clavier via
|
||||
Tab/`:focus-within`). Voir la note dans `js/svg-export.js`.
|
||||
- Les césures logicielles insérées par le thème (`hyphenateWord()`, U+00AD)
|
||||
ne sont pas des `.char` et ne sont donc jamais exportées.
|
||||
- Positionnement du texte basé sur `getBoundingClientRect()` +
|
||||
métriques de police (ascender/unitsPerEm) plutôt que sur une mesure
|
||||
exacte de ligne de base — fidèle en pratique mais pas garanti au pixel
|
||||
près sur toutes les combinaisons police/navigateur.
|
||||
Reference in New Issue
Block a user