9.5 KiB
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-titlesur un élément, et des spans.charà l'intérieur, stylés inline enfont-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 dansinitTitles()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 :
- Déclarer la permission
export leshed typography. - 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. - Fournir ses propres fichiers JS/CSS et l'URL vers le moteur
harfbuzzjsinstallé 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.jsondéclare le dépôthttps://asset-packagist.org(miroir Composer du registre npm) et requiertnpm-asset/harfbuzzjs, ainsi queoomphinc/composer-installers-extender(nécessaire pour quecomposer/installerssache installer un paquet de typenpm-asset— package marqué "abandonné" sur Packagist mais toujours fonctionnel ; aucun remplaçant n'est proposé à ce jour, à surveiller).extra.installer-types/installer-pathsmappenttype:npm-assetversweb/libraries/{$name}, comme le fait déjàtype:drupal-library.- Résultat :
composer install/composer updateinstalleharfbuzzjsdansweb/libraries/harfbuzzjs/— aucune étape d'installation supplémentaire par rapport au reste du projet. leshed_svg_export.moduleexpose l'URL correspondante (web/libraries/harfbuzzjs/dist/index.mjs) viadrupalSettings.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 :
location ~ \.mjs$ {
default_type application/javascript;
}
js/svg-export.js
Comportement Drupal (Drupal.behaviors.leshedSvgExport) :
-
Surveille le DOM via
MutationObserverpour 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é enposition: absolute(top: 0; left: 0— superposé au coin haut-gauche du titre, jamais au reste). Le titre reçoit aussi la classeleshed-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çoitposition: relativeen JS uniquement s'il ne l'a pas déjà (visuellement neutre, ne bouge/ne redimensionne rien) pour servir de contexte de positionnement. Étant enposition: 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, voircss/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: nonene 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
.chardans l'ordre du DOM — position (getBoundingClientRect()), style calculé (font-family,font-style, tous les axes defont-variation-settings,text-transform) — et convertit.char.textContentselon letext-transformréellement appliqué (uppercase / lowercase / capitalize, ce dernier basé sur le premier.charde 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), puiscanvas.toBlob('image/png').
js/harfbuzz-export.js
Résolution dynamique de la police et extraction des tracés, via
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 :
- Lit son style calculé (
font-family,font-style, les axes defont-variation-settings). - Cherche, dans
document.styleSheets(CSSOM), la règle@font-facedontfont-family/font-stylecorrespondent, et en extrait l'URL du descripteursrc. fetch()cette URL (peu importe le conteneur réel — ttf/otf/woff2 — HarfBuzz travaille sur les octets bruts), met en cache par URL.- 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 dansjs/svg-export.js. - Les césures logicielles insérées par le thème (
hyphenateWord(), U+00AD) ne sont pas des.charet 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.