Ahkab Illustrator Text — texte Illustrator natif (un paragraphe = une zone de texte)
Par Ahmad Abdullahi Kabeer · Graphcent
Pourquoi
Illustrator divise toujours le texte SVG et PDF importé en un objet texte par ligne, groupés ensemble. Aucun balisage SVG ne peut empêcher cela. Solution : conserver les pages SVG pour les éléments graphiques, puis reconstruire TOUT le texte nativement dans Illustrator avec un script ExtendScript (.jsx) que l'utilisateur exécute une fois (Fichier > Scripts > Autre script…). Le script sauvegarde un véritable fichier .ai.
Flux de travail
- Enregistrer le texte lors de la génération des pages. Partout où le générateur de pages émet
<text>, stocker un enregistrement par page :{id, kind: 'area'|'point', layer: '05', x, y (première ligne de base, SVG y vers le bas, pt), w (largeur de la zone), lead, n (nombre de lignes renvoyées), size, anchor: 'start'|'middle'|'end', ls (espacement des lettres en pt), runs: [[texte, poids, '#RRGGBB'], …], name}- Paragraphes renvoyés (et titres multi-lignes) ->
area. Étiquettes, numéros, accroches sur une seule ligne ->point. - Utiliser exactement les valeurs x/y/largeur/interligne utilisées pour dessiner le SVG, afin que les positions correspondent au PDF.
- Donner des noms significatifs (par ex.
Nom — emplacement 01,Biographie — emplacement 04).
- Paragraphes renvoyés (et titres multi-lignes) ->
- Exigences pour les pages SVG : fichiers
pages/NN_Nom.svg(NN = numéro de page) ; groupes de premier niveauid="L01_Arrière-plans",L02_…, etc. avecdata-name= nom de calque lisible. Conserver le texte dans les SVG également (fallback + rendu PDF). - Générer le .jsx en intégrant les enregistrements sous forme de littéral d'objet JS (
json.dumps(..., ensure_ascii=True)— ExtendScript est ES3, pas besoin de JSON.parse). - Test préliminaire du .jsx généré dans Node contre une maquette du DOM Illustrator (app, documents.add, open, textFrames.areaText/pointText, pathItems.rectangle, artboards, textFonts.getByName, enums). Vérifier : nombre de zones area/point, chaque zone a du contenu + police + nom, les coordonnées des rectangles sont des nombres, les artboards = pages.
- Documenter dans le README que le .ai modifiable provient de l'exécution du script ; l'ouverture directe du SVG/PDF donne toujours des lignes séparées. Indiquer clairement que le script a été testé sur une maquette, pas dans Illustrator lui-même.
Logique principale du script (ExtendScript)
#target illustrator
var PAGE_TEXT = /* {"1":[enregistrements…], "2":[…]} */;
app.userInteractionLevel = UserInteractionLevel.DONTDISPLAYALERTS;
app.coordinateSystem = CoordinateSystem.DOCUMENTCOORDINATESYSTEM; // y augmente vers le haut
// 1) doc = app.documents.add(DocumentColorSpace.RGB, W, H); créer des calques nommés (doc.layers.add() ajoute en haut).
// 2) par page p : rectangle artboard [left, top, left+W, top-H] avec left = col*(W+GAP), top = -row*(H+GAP).
// src = app.open(svgFile); supprimer le texte importé :
// for (var i = src.textFrames.length-1; i >= 0; i--) src.textFrames[i].remove();
// trouver les groupes/sous-calques dont le nom correspond à /^L?(0[1-9])/ et les dupliquer dans le calque correspondant :
// item.duplicate(targetLayer, ElementPlacement.PLACEATBEGINNING).translate(left - sr[0], top - sr[1]);
// (parcourir container.pageItems, filtrer typename === 'GroupItem' ; ne pas comparer les objets DOM avec ===)
// src.close(SaveOptions.DONOTSAVECHANGES); puis buildText(PAGE_TEXT[pageNo], left, top).
var FONT_CANDIDATES = { 300:["Kreadon-L","Outfit-Light"], 400:["Kreadon-R","Kreadon-M","Outfit-Regular"],
500:["Kreadon-M","Outfit-Medium"], 600:["Kreadon-Demi","Kreadon-D","Outfit-SemiBold"],
700:["Kreadon-B","Outfit-Bold"], 800:["Kreadon-EB","Outfit-ExtraBold"] }; // mettre les noms PostScript du projet en premier
function fontFor(w){ var n=FONT_CANDIDATES[w]||FONT_CANDIDATES[400]; for(var i=0;i<n.length;i++){ try{ return app.textFonts.getByName(n[i]); }catch(e){} } return null; }
function rgb(h){ var c=new RGBColor(); h=h.replace('#',''); c.red=parseInt(h.substr(0,2),16); c.green=parseInt(h.substr(2,2),16); c.blue=parseInt(h.substr(4,2),16); return c; }
function styleRange(r,w,size,col,lsPt,lead){ var a=r.characterAttributes, f=fontFor(w); if(f) a.textFont=f; a.size=size;
if(lead){ a.autoLeading=false; a.leading=lead; } a.fillColor=rgb(col); a.tracking=Math.round((lsPt||0)/size*1000); }
function buildText(recs, left, top){
for (var i=0;i<recs.length;i++){ var t=recs[i], layer=layerByKey[t.layer]||layerByKey['06'], s='', tf;
for (var r=0;r<t.runs.length;r++) s+=t.runs[r][0];
if (t.kind==='area'){ var lead=t.lead||t.size*1.4;
var rect=layer.pathItems.rectangle(top-(t.y-lead), left+t.x-1, t.w+2, (t.n+1)*lead); // marge de +1 ligne pour les changements de police
tf=layer.textFrames.areaText(rect); tf.contents=s; styleRange(tf.textRange,t.runs[0][1],t.size,t.runs[0][2],t.ls,lead);
try{ tf.firstBaseline=FirstBaselineType.LEADING; }catch(e){ tf.translate(0,-(lead-t.size*0.95)); }
} else { tf=layer.textFrames.pointText([left+t.x, top-t.y]); tf.contents=s; styleRange(tf.textRange,t.runs[0][1],t.size,t.runs[0][2],t.ls,null); }
tf.textRange.paragraphAttributes.justification = t.anchor==='middle'?Justification.CENTER:(t.anchor==='end'?Justification.RIGHT:Justification.LEFT);
// spans enrichis : pour les runs suivants dont le poids/couleur diffèrent, styliser les caractères [pos, pos+len) :
// try { var rr=tf.textRange.characters[pos]; rr.length=len; styleRange(rr,…); } catch(e){ boucle par caractère }
tf.name = t.name || t.id; } }
// 3) IllustratorSaveOptions {pdfCompatible:true, compressed:true, embedLinkedFiles:true}; doc.saveAs(File(root+'/<Nom>.ai'));
// Afficher une alerte finale avec les éventuelles notes (polices manquantes). Tout encapsuler dans try/catch et restaurer userInteractionLevel.
Vérifications avant la livraison
- Le test préliminaire sur maquette passe ; le nombre de paragraphes correspond au nombre de blocs de texte renvoyés dans le générateur.
- Les zones de texte utilisent la largeur d'origine (+2pt) pour que les retours à la ligne correspondent au PDF ; avec une police de remplacement plus large, le texte se réorganise dans la zone.
- Le README explique : exécuter le script pour obtenir le .ai modifiable ; Rechercher/Remplacer la police si le changement de police est nécessaire ; le script n'a pas été exécuté dans Illustrator par Claude.