Points clés de l’article

  • En marchant comme d’habitude main dans la main avec ChatGPT – ou plutôt en me contentant de donner des instructions – j’ai voulu créer l’éditeur SVG /tools/svgedit/, mais je n’ai cessé de trébucher : voici les principaux points de douleur.
  • Je n’aurais jamais pu y arriver seul. La capacité de codage des IA génératives reste impressionnante.
  • Pour autant, tout lui confier ne suffisait pas non plus. J’ai dû coordonner l’ensemble, corriger les exigences erronées ou les malentendus, indiquer la direction lors des incidents : un véritable rôle de manager opérationnel était requis.
  • À ce titre, cette expérience livre peut-être aussi des enseignements pour survivre à l’ère de l’IA.

Implémentation et spécifications d’un éditeur SVG qui tourne entièrement dans le navigateur

Qu’est-ce que SVG ?

SVG (Scalable Vector Graphics) est un format d’image vectorielle basé sur XML normalisé par le W3C. Contrairement aux images matricielles (PNG, JPEG, etc.), il conserve sa qualité lorsqu’on l’agrandit ou le réduit. Comme il peut être intégré directement au sein d’un document HTML en tant que partie du DOM, on peut le manipuler dynamiquement ou modifier son style à l’aide de JavaScript et CSS. La spécification suit les recommandations du W3C et offre un large éventail d’expressions : formes, textes, tracés, dégradés, filtres, etc.

SVG est largement utilisé dans les cas suivants :

  • Afficher des icônes ou des graphiques sur des pages Web
  • Générer dynamiquement des diagrammes et des graphiques
  • Créer des applications de dessin avec interaction utilisateur
  • Produire des supports d’impression ou de design d’interface

Aperçu de l’éditeur SVG de cette fois

L’éditeur est un outil entièrement côté client qui fonctionne uniquement dans le navigateur. L’utilisateur peut créer non seulement des formes de base comme le rectangle ou l’ellipse, mais aussi des étoiles, bulles, nuages, cœurs, ainsi que des courbes libres grâce à un mode stylo. Les formes créées peuvent être déplacées, redimensionnées, pivotées ; l’outil propose l’alignement, l’accrochage, la mise en groupe et une liste de calques. L’illustration finale peut être exportée en SVG ou en PNG.

Les sections suivantes détaillent comment j’ai transposé la spécification SVG en JavaScript.

Ce qui a coincé lors de la mise en œuvre des spécifications

Ajout de formes et gestion des attributs

Dans la spécification SVG, chaque forme dispose de ses propres éléments et attributs. Les rectangles utilisent <rect>, les ellipses <ellipse>, les polygones <polygon>, et chacun requiert les attributs adéquats comme x, y, width, height, cx, cy, rx, ry, ou points. Les formes complexes telles que les étoiles ou les cœurs sont générées via des éléments <path> combinant courbes de Bézier et commandes d’arc.

Exemple d’implémentation (ajout d’un rectangle)

function addRectangle(x, y, width, height) {
  const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
  rect.setAttribute("x", x);
  rect.setAttribute("y", y);
  rect.setAttribute("width", width);
  rect.setAttribute("height", height);
  rect.setAttribute("fill", "transparent");
  rect.setAttribute("stroke", "#000");
  svg.appendChild(rect);
}

Les rectangles ou ellipses sont simples, mais les polygones ou formes sur mesure nécessitent du calcul de coordonnées. Pour dessiner une étoile, par exemple, on divise l’angle central pour obtenir les sommets, puis on les fournit à l’attribut points.

function createStar(cx, cy, spikes, outerR, innerR) {
  let points = "";
  const step = Math.PI / spikes;
  for (let i = 0; i < 2 * spikes; i++) {
    const r = i % 2 === 0 ? outerR : innerR;
    const x = cx + r * Math.cos(i * step);
    const y = cy + r * Math.sin(i * step);
    points += `${x},${y} `;
  }
  const polygon = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
  polygon.setAttribute("points", points.trim());
  polygon.setAttribute("stroke", "#000");
  polygon.setAttribute("fill", "transparent");
  svg.appendChild(polygon);
}

Transformations et gestion des systèmes de coordonnées

Avec l’attribut transform, SVG permet de combiner translate, rotate, scale, etc., pour manipuler les formes. La difficulté est apparue lorsqu’il a fallu obtenir la boîte englobante d’une forme sélectionnée : il fallait harmoniser coordonnées locales et globales. J’ai résolu le problème avec getScreenCTM() afin de convertir le tout dans un repère global avant de calculer le rectangle.

Exemple d’implémentation (application des transformations de coordonnées)

function getTransformedBBox(element) {
  const bbox = element.getBBox();
  const matrix = element.getScreenCTM();
  const points = [
    svg.createSVGPoint(), svg.createSVGPoint(),
    svg.createSVGPoint(), svg.createSVGPoint()
  ];
  points[0].x = bbox.x; points[0].y = bbox.y;
  points[1].x = bbox.x + bbox.width; points[1].y = bbox.y;
  points[2].x = bbox.x; points[2].y = bbox.y + bbox.height;
  points[3].x = bbox.x + bbox.width; points[3].y = bbox.y + bbox.height;
  return points.map(p => p.matrixTransform(matrix));
}

Grâce à cette approche, le cadre de sélection reste correct même après rotations et mises à l’échelle.

Fonction d’accrochage et grille

La grille est dessinée en arrière-plan grâce à l’élément <pattern> de SVG. Le traitement d’accrochage arrondit les coordonnées en JavaScript selon un intervalle défini.

function snapToGrid(value, gridSize) {
  return Math.round(value / gridSize) * gridSize;
}

L’accrochage sur d’autres objets parcourt les centres et extrémités des formes voisines, détecte la plus proche et affiche des lignes guides pour attirer l’objet. La précision de l’interface s’en est retrouvée grandement améliorée.

Édition de texte

L’élément <text> de SVG ne s’édite pas directement : j’ai donc adopté la méthode consistant à superposer temporairement un <input> HTML pour l’édition inline.

function editTextElement(textElem) {
  const input = document.createElement("input");
  input.type = "text";
  input.value = textElem.textContent;
  document.body.appendChild(input);
  const bbox = textElem.getBoundingClientRect();
  input.style.position = "absolute";
  input.style.left = bbox.x + "px";
  input.style.top = bbox.y + "px";
  input.focus();
  input.addEventListener("blur", () => {
    textElem.textContent = input.value;
    document.body.removeChild(input);
  });
}

Tracés et courbes libres

L’élément <path> de SVG assemble des commandes comme M, L, C, A pour dessiner. J’ai commencé par une édition basée sur les segments : chaque clic ajoute de nouvelles coordonnées.

let pathData = "M100,100";
function addPathPoint(x, y) {
  pathData += ` L${x},${y}`;
  path.setAttribute("d", pathData);
}

Export (SVG/PNG)

Au moment d’enregistrer, je supprime les éléments d’assistance avant l’export. Pour la conversion en PNG, je passe par un canvas.

function exportToPNG(svgElement) {
  const serializer = new XMLSerializer();
  const source = serializer.serializeToString(svgElement);
  const img = new Image();
  const svgBlob = new Blob([source], { type: "image/svg+xml;charset=utf-8" });
  const url = URL.createObjectURL(svgBlob);
  img.onload = () => {
    const canvas = document.createElement("canvas");
    canvas.width = img.width;
    canvas.height = img.height;
    const ctx = canvas.getContext("2d");
    ctx.drawImage(img, 0, 0);
    canvas.toBlob(blob => {
      const a = document.createElement("a");
      a.href = URL.createObjectURL(blob);
      a.download = "drawing.png";
      a.click();
    });
  };
  img.src = url;
}

Gestion du défilement du canevas

Pour manipuler de grands SVG, je les enveloppe dans un <div style="overflow:auto"> afin d’afficher des barres de défilement lorsque nécessaire.

Conclusion

La spécification SVG est puissante, mais sa mise en œuvre demande de maîtriser transformations de coordonnées, définition des tracés, etc. Cet éditeur organise ces difficultés et permet :

  • La manipulation d’attributs pour rect, ellipse, polygon, path
  • La gestion correcte de transform et des systèmes de coordonnées
  • L’implémentation de la grille et de l’accrochage
  • L’édition de texte inline
  • L’édition de tracés (basée sur les segments)
  • L’export aux formats PNG et SVG
  • Le support des grandes surfaces avec défilement

J’ai ainsi pu tirer parti de tout le potentiel expressif de SVG pour construire un éditeur riche fonctionnant uniquement dans le navigateur.