Ahkab Illustrator Text — texto nativo de Illustrator (un párrafo = un cuadro de texto)
Por Ahmad Abdullahi Kabeer · Graphcent
Por qué
Illustrator siempre divide el texto SVG y PDF importado en un objeto de texto por línea, agrupados. Ningún marcado SVG lo evita. Por lo tanto: conserva las páginas SVG para la ilustración y reconstruye TODO el texto de forma nativa dentro de Illustrator con un script de ExtendScript (.jsx) que el usuario ejecuta una vez (Archivo > Scripts > Otro script...). El script guarda un .ai genuino.
Flujo de trabajo
- Registra el texto mientras generas páginas. Donde el generador de páginas emita
<text>, también almacena un registro por página:{id, kind: 'area'|'point', layer: '05', x, y (primera línea base, SVG y abajo, pt), w (ancho del cuadro), lead, n (recuento de líneas envueltas), size, anchor: 'start'|'middle'|'end', ls (espaciado entre letras pt), runs: [[texto, peso, '#RRGGBB'], …], name}- Párrafos envueltos (y títulos de varias líneas) ->
area. Etiquetas de una línea, números, subtítulos ->point. - Usa exactamente los x/y/ancho/interlineado utilizados para dibujar el SVG, para que las posiciones coincidan con el PDF.
- Asigna nombres significativos (por ejemplo,
Nombre — ranura 01,Biografía — ranura 04).
- Párrafos envueltos (y títulos de varias líneas) ->
- Requisitos de la página SVG: archivos
pages/NN_Nombre.svg(NN = número de página); grupos de nivel superiorid="L01_Fondos",L02_…etc. condata-name= nombre de capa legible. Mantén el texto también en los SVG (respaldo + renderizado PDF). - Genera el .jsx incrustando los registros como un literal de objeto JS (
json.dumps(..., ensure_ascii=True)— ExtendScript es ES3, no necesita JSON.parse). - Haz una prueba en seco del .jsx generado en Node contra un mock del DOM de Illustrator (app, documents.add, open, textFrames.areaText/pointText, pathItems.rectangle, artboards, textFonts.getByName, enums). Verifica: recuento de marcos de área/punto, cada marco tiene contenido + fuente + nombre, las coordenadas del rect son números, los artboards = páginas.
- Documenta en el README que el .ai editable proviene de ejecutar el script; abrir SVG/PDF directamente sigue dando líneas divididas. Indica claramente que el script se probó en un mock, no en Illustrator mismo.
Lógica central del script (ExtendScript)
#target illustrator
var PAGE_TEXT = /* {"1":[registros…], "2":[…]} */;
app.userInteractionLevel = UserInteractionLevel.DONTDISPLAYALERTS;
app.coordinateSystem = CoordinateSystem.DOCUMENTCOORDINATESYSTEM; // y aumenta hacia arriba
// 1) doc = app.documents.add(DocumentColorSpace.RGB, W, H); crea capas con nombre (doc.layers.add() añade encima).
// 2) por página p: rect del artboard [left, top, left+W, top-H] con left = col*(W+GAP), top = -row*(H+GAP).
// src = app.open(svgFile); elimina el texto importado:
// for (var i = src.textFrames.length-1; i >= 0; i--) src.textFrames[i].remove();
// encuentra grupos/subcapas cuyo nombre coincida con /^L?(0[1-9])/ y duplícalos en la capa correspondiente:
// item.duplicate(targetLayer, ElementPlacement.PLACEATBEGINNING).translate(left - sr[0], top - sr[1]);
// (itera container.pageItems, filtra typename === 'GroupItem'; no compares objetos DOM con ===)
// src.close(SaveOptions.DONOTSAVECHANGES); luego 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"] }; // pon los nombres PostScript del proyecto primero
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); // +1 línea de margen para cambios de fuente
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);
// tramos enriquecidos: para tiradas después de la primera cuyo peso/color difieran, estilo caracteres [pos, pos+len):
// try { var rr=tf.textRange.characters[pos]; rr.length=len; styleRange(rr,…); } catch(e){ bucle por carácter }
tf.name = t.name || t.id; } }
// 3) IllustratorSaveOptions {pdfCompatible:true, compressed:true, embedLinkedFiles:true}; doc.saveAs(File(root+'/<Nombre>.ai'));
// Muestra una alerta final con cualquier nota (p. ej., fuentes faltantes). Envuelve todo en try/catch y restaura userInteractionLevel.
Verificaciones antes de entregar
- La prueba en seco del mock pasa; el recuento de párrafos coincide con el número de bloques de texto envueltos en el generador.
- Los cuadros de área usan el ancho original (+2pt) para que los saltos de línea coincidan con el PDF; con una fuente de reemplazo más ancha, se reajustan dentro del cuadro.
- El README explica: ejecuta el script para el .ai editable; Buscar/Reemplazar fuente si se cambian tipografías; el script no fue ejecutado dentro de Illustrator por Claude.