Ahkab Illustrator Text — texto nativo do Illustrator (um parágrafo = uma caixa de texto)
Por Ahmad Abdullahi Kabeer · Graphcent
Por que
O Illustrator sempre divide o texto importado de SVG e PDF em um objeto de texto por linha, agrupados. Nenhuma marcação SVG impede isso. Portanto: mantenha as páginas SVG para a arte e reconstrua TODO o texto nativamente dentro do Illustrator com um script de construção ExtendScript (.jsx) que o usuário executa uma vez (File > Scripts > Other Script...). O script salva um .ai genuíno.
Fluxo de trabalho
- Gravar texto ao gerar páginas. Onde quer que o gerador de página emita
<text>, armazene também um registro por página:{id, kind: 'area'|'point', layer: '05', x, y (primeira linha de base, SVG y para baixo, pt), w (largura da caixa), lead, n (contagem de linhas quebradas), size, anchor: 'start'|'middle'|'end', ls (espaçamento entre letras pt), runs: [[texto, peso, '#RRGGBB'], …], name}- Parágrafos quebrados (e títulos com várias linhas) ->
area. Rótulos de linha única, números, cartolas ->point. - Use exatamente os mesmos x/y/largura/entrelinhamento usados para desenhar o SVG, para que as posições correspondam ao PDF.
- Dê nomes significativos (ex.:
Nome — slot 01,Biografia — slot 04).
- Parágrafos quebrados (e títulos com várias linhas) ->
- Requisitos da página SVG: arquivos
pages/NN_Name.svg(NN = número da página); grupos de alto nívelid="L01_Backgrounds",L02_…etc. comdata-name= nome de camada legível. Mantenha o texto também nos SVGs (fallback + renderização PDF). - Gerar o .jsx incorporando os registros como um literal de objeto JS (
json.dumps(..., ensure_ascii=True)— ExtendScript é ES3, não precisa de JSON.parse). - Teste em seco o .jsx gerado no Node contra um mock do DOM do Illustrator (app, documents.add, open, textFrames.areaText/pointText, pathItems.rectangle, artboards, textFonts.getByName, enums). Verifique: contagens de quadros de área/ponto, cada quadro tem conteúdo + fonte + nome, coordenadas do retângulo são números, artboards = páginas.
- Documente no README que o .ai editável vem da execução do script; abrir SVG/PDF diretamente ainda dá linhas divididas. Diga claramente que o script foi testado em um mock, não no próprio Illustrator.
Lógica central do script (ExtendScript)
#target illustrator
var PAGE_TEXT = /* {"1":[registros…], "2":[…] } */;
app.userInteractionLevel = UserInteractionLevel.DONTDISPLAYALERTS;
app.coordinateSystem = CoordinateSystem.DOCUMENTCOORDINATESYSTEM; // y aumenta para cima
// 1) doc = app.documents.add(DocumentColorSpace.RGB, W, H); criar camadas nomeadas (doc.layers.add() adiciona no topo).
// 2) por página p: retângulo do artboard [left, top, left+W, top-H] com left = col*(W+GAP), top = -row*(H+GAP).
// src = app.open(svgFile); deletar texto importado:
// for (var i = src.textFrames.length-1; i >= 0; i--) src.textFrames[i].remove();
// encontrar grupos/sub-camadas cujo nome corresponda a /^L?(0[1-9])/ e duplicá-los na camada correspondente:
// item.duplicate(targetLayer, ElementPlacement.PLACEATBEGINNING).translate(left - sr[0], top - sr[1]);
// (iterar container.pageItems, filtrar typename === 'GroupItem'; não comparar objetos DOM com ===)
// src.close(SaveOptions.DONOTSAVECHANGES); depois 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"] }; // colocar os nomes PostScript do projeto primeiro
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); // folga de +1 linha para trocas de fonte
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 ricos: para execuções após a primeira cujo peso/cor diferem, estilizar caracteres [pos, pos+len):
// try { var rr=tf.textRange.characters[pos]; rr.length=len; styleRange(rr,…); } catch(e){ loop por caractere }
tf.name = t.name || t.id; } }
// 3) IllustratorSaveOptions {pdfCompatible:true, compressed:true, embedLinkedFiles:true}; doc.saveAs(File(root+'/<Nome>.ai'));
// Mostrar um alerta final com quaisquer notas (ex.: fontes faltantes). Envolver tudo em try/catch e restaurar userInteractionLevel.
Verificações antes de entregar
- O teste em seco do mock passa; a contagem de parágrafos corresponde ao número de blocos de texto quebrados no gerador.
- As caixas de área usam a largura original (+2pt) para que as quebras de linha correspondam ao PDF; com uma fonte de substituição mais larga, elas refluem dentro da caixa.
- O README explica: execute o script para obter o .ai editável; Find/Replace Font se trocar as fontes; o script não foi executado dentro do Illustrator pelo Claude.