Community图像github.com

4hm4di/ahkab-illustrator-text

用于交付 Illustrator 可编辑的版面(杂志、简介、手册),将 SVG/PDF 中的每个段落构建为独立的区域文本框,而非分组线条。

ahkab-illustrator-text 是什么?

Ahkab Illustrator Text 是一项 AI 智能体技能,专为解决 Illustrator 导入 SVG 或 PDF 文件时自动将文本拆分为按行分组对象的固有问题而设计。该技能通过生成一个 ExtendScript (.jsx) 构建脚本,使 AI 助手(如 Claude Code、Cursor)能够指示用户在 Illustrator 中运行该脚本,从而将所有文本重新构建为原生的区域或点文本框——每个段落对应一个独立文本框架,并保留原始布局中的位置、宽度、行距、字体、颜色、字符间距和段落对齐方式。技能要求页面生成器在输出 SVG 时同时记录每段文本的元数据(包括类型、坐标、宽度、行距、字体粗细、颜色等),并将这些数据嵌入 .jsx 脚本中的 JavaScript 对象字面量。脚本会创建新文档、复制 SVG 中的非文本图形元素,然后根据记录数据逐一生成文本框。它支持多页版面(通过画板实现)、命名图层、自定义字体回退机制(优先使用多个 PostScript 名称候选),并提供了基于 Node.js 模拟 Illustrator DOM 的干运行检查流程,以验证所有文本框架的数量、内容和坐标正确性。最终输出的 .ai 文件可直接在 Illustrator 中编辑,每个段落独立完整,大幅提升排版编辑效率。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/4hm4di/ahkab-illustrator-text/tree/HEAD/ahkab-illustrator-text

Installed? Explore more 图像 skills: steipete/songsee, affaan-m/frontend-design, mattpocock/improve-codebase-architecture · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Ahkab Illustrator Text — 原生 Illustrator 文本(一个段落 = 一个文本框)

作者:Ahmad Abdullahi Kabeer · Graphcent

为什么

Illustrator 始终将导入的 SVG 和 PDF 文本分割为每个一行一个文本对象,并分组在一起。没有任何 SVG 标记可以阻止这种情况。因此:保留 SVG 页面用于图稿,并使用一个 ExtendScript (.jsx) 构建脚本在 Illustrator 内部原生重建所有文本,用户只需运行一次(文件 > 脚本 > 其他脚本...)。该脚本会保存一个真正的 .ai 文件。

工作流程

  1. 在生成页面时记录文本。 无论页面生成器在何处输出 <text>,同时按页存储一条记录: {id, kind: 'area'|'point', layer: '05', x, y(第一条基线,SVG 向下为正,单位 pt), w(框宽度), lead, n(换行行数), size, anchor: 'start'|'middle'|'end', ls(字母间距 pt), runs: [[text, weight, '#RRGGBB'], …], name}
    • 换行段落(以及多行标题)→ area。单行标签、数字、引题 → point。
    • 使用与绘制 SVG 完全相同的 x/y/宽度/行距,确保位置与 PDF 匹配。
    • 赋予有意义的名称(例如 Name — slot 01、Biography — slot 04)。
  2. SVG 页面要求: 文件 pages/NN_Name.svg(NN = 页码);顶级组 id="L01_Backgrounds"、L02_… 等,带有 data-name = 可读图层名称。SVG 中也要保留文本(作为后备并用于 PDF 渲染)。
  3. 生成 .jsx 脚本,将记录嵌入为 JS 对象字面量(json.dumps(..., ensure_ascii=True) — ExtendScript 是 ES3,无需 JSON.parse)。
  4. 干运行 在 Node.js 中针对 Illustrator DOM 的模拟(app、documents.add、open、textFrames.areaText/pointText、pathItems.rectangle、artboards、textFonts.getByName、enums)检查生成的 .jsx。检查:区域/点文本框的数量、每个框的内容+字体+名称、矩形坐标是否为数字、画板数量是否等于页数。
  5. 在 README 中说明,可编辑的 .ai 文件来自运行脚本;直接打开 SVG/PDF 仍会得到分割的行。明确说明脚本已在模拟环境中测试,未在 Illustrator 自身中运行。

核心脚本逻辑 (ExtendScript)

#target illustrator
var PAGE_TEXT = /* {"1":[records…], "2":[…]} */;
app.userInteractionLevel = UserInteractionLevel.DONTDISPLAYALERTS;
app.coordinateSystem = CoordinateSystem.DOCUMENTCOORDINATESYSTEM;   // y 向上递增
// 1) doc = app.documents.add(DocumentColorSpace.RGB, W, H); 创建命名图层(doc.layers.add() 在顶部添加)。
// 2) 每页 p:画板矩形 [left, top, left+W, top-H],其中 left = col*(W+GAP), top = -row*(H+GAP)。
//    src = app.open(svgFile); 删除导入的文本:
//      for (var i = src.textFrames.length-1; i >= 0; i--) src.textFrames[i].remove();
//    查找名称匹配 /^L?(0[1-9])/ 的组/子图层,复制到匹配的图层中:
//      item.duplicate(targetLayer, ElementPlacement.PLACEATBEGINNING).translate(left - sr[0], top - sr[1]);
//    (遍历 container.pageItems,过滤 typename === 'GroupItem';不要使用 === 比较 DOM 对象)
//    src.close(SaveOptions.DONOTSAVECHANGES); 然后 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"] };  // 将项目的 PostScript 名称放在首位
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 行余量用于字体替换
      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);
    // 富跨度:对于后续批次中粗细/颜色不同的部分,设置字符样式 [pos, pos+len):
    //   try { var rr=tf.textRange.characters[pos]; rr.length=len; styleRange(rr,…); } catch(e){ 逐字符循环 }
    tf.name = t.name || t.id; } }
// 3) IllustratorSaveOptions {pdfCompatible:true, compressed:true, embedLinkedFiles:true}; doc.saveAs(File(root+'/<Name>.ai'));
//    显示最终警报,包含任何注释(例如缺少字体)。将所有内容包裹在 try/catch 中,并恢复 userInteractionLevel。

交付前检查

  • 模拟干运行通过;段落数量与生成器中的换行文本块数量匹配。
  • 区域框使用原始宽度(+2pt),以确保换行与 PDF 匹配;使用较宽的替换字体时,文本会在框内重新流动。
  • README 说明:运行脚本以获得可编辑的 .ai 文件;如果需要更换字体,使用“查找/替换字体”;脚本未由 Claude 在 Illustrator 内部运行。

相关技能