Community이미지github.com

4hm4di/ahkab-illustrator-text

일러스트레이터에서 편집 가능한 레이아웃(잡지, 프로필, 브로셔)을 SVG/PDF로 제공할 때 각 문단이 그룹화된 줄이 아닌 하나의 편집 가능한 영역형 텍스트 상자가 되도록 합니다.

ahkab-illustrator-text란 무엇인가요?

이 스킬은 Adobe Illustrator에서 SVG 또는 PDF를 가져올 때 발생하는 텍스트 분할 문제를 해결합니다. Illustrator는 가져온 SVG/PDF의 텍스트를 각 줄마다 개별 텍스트 객체로 그룹화하여 제공하므로, 이를 하나의 문단이 하나의 편집 가능한 영역형 텍스트 상자가 되도록 재구성합니다. 주로 Claude Code, Cursor, Codex와 같은 AI 코딩 도우미에서 사용되며, ExtendScript(.jsx) 파일을 생성하여 Illustrator 내에서 실행하면 원본 SVG 페이지의 아트워크를 유지하면서 모든 텍스트를 네이티브 텍스트 프레임으로 변환합니다. 이 스킬은 잡지, 프로필, 브로셔 등 편집 가능한 레이아웃이 필요한 디자인 작업에 특히 유용하며, Node.js에서 드라이런을 지원하여 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에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Ahkab Illustrator Text — 네이티브 일러스트레이터 텍스트 (문단 하나 = 텍스트 상자 하나)

By Ahmad Abdullahi Kabeer · Graphcent

이유

Illustrator는 가져온 SVG 및 PDF 텍스트를 항상 한 줄씩 하나의 텍스트 객체로 분할하여 그룹화합니다. 어떤 SVG 마크업도 이를 막을 수 없습니다. 따라서: 아트워크용 SVG 페이지를 유지하고, 사용자가 한 번 실행하는 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. 레코드를 JS 객체 리터럴로 포함하여 .jsx를 생성합니다 (json.dumps(..., ensure_ascii=True) — ExtendScript는 ES3이므로 JSON.parse가 필요 없음).
  4. 생성된 .jsx를 Node에서 Illustrator DOM 모의(mock)에 대해 드라이런합니다 (app, documents.add, open, textFrames.areaText/pointText, pathItems.rectangle, artboards, textFonts.getByName, enums). 확인: area/point 프레임 수, 모든 프레임에 내용 + 글꼴 + 이름이 있는지, rect 좌표가 숫자인지, 아트보드 = 페이지.
  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마다: 아트보드 rect [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);
    // 리치 스팬: 첫 번째 이후의 실행(run) 중 무게/색상이 다른 경우, 문자 [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 내부에서 실행되지 않았음.

관련 스킬