Community影像github.com

4hm4di/ahkab-illustrator-text

用於交付 Illustrator 可編輯的版面(雜誌、個人簡介、手冊),這些版面以 SVG/PDF 構建,使每個段落變成一個可編輯的區域文字框,而非群組化的獨立行。

ahkab-illustrator-text 是什麼?

此技能解決了一個常見的 Illustrator 問題:當導入 SVG 或 PDF 檔案時,文字會被拆分為每行一個獨立的文字物件,並群組在一起,導致編輯困難。Ahkab Illustrator Text 提供了一個完整的解決方案,其工作流程為:在頁面生成過程中記錄文字屬性(位置、寬度、字重、顏色等),然後生成一個 ExtendScript (.jsx) 腳本。使用者只需在 Illustrator 中執行此腳本一次,即可根據記錄的佈局重建所有文字,將其轉換為真正的可編輯區域文字框(適用於段落)或點文字(適用於單行標籤)。腳本還能保留原始 SVG 中的頁面圖稿,並建立正確的圖層和畫板。此技能支援 Claude Code 等 AI 代理,透過 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 頁面作為圖稿,並透過一個 Illustrator 使用者執行一次的 ExtendScript (.jsx) 構建腳本(檔案 > 指令碼 > 其他指令碼…)在 Illustrator 內部原生重建所有文字。該腳本會儲存一個真正的 .ai 檔案。

工作流程

  1. 在生成頁面時記錄文字。 每當頁面生成器產生 <text> 時,也為每個頁面儲存一條記錄: {id, kind: 'area'|'point', layer: '05', x, y(第一基線,SVG y 向下,單位 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 中對 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 內部執行。

相關技能