← Back to Skills Marketplace
yon-gjun

Code To Images

by yon-gjun · GitHub ↗ · v1.0.0 · MIT-0
cross-platform ✓ Security Clean
39
Downloads
1
Stars
0
Active Installs
1
Versions
Install in OpenClaw
/install code-to-images
Description
Convert code files to A4-ratio PNG/SVG images with line numbers and syntax highlighting, then merge to PDF
README (SKILL.md)

Code → A4 Images + PDF

Convert source code files into multi-page A4-ratio images (SVG+PNG) then merge into a single PDF, with:

  • Line numbers (starting from 1)
  • Syntax highlighting (keywords, registers, macros, numbers, comments)
  • File name header with page info per sheet
  • Light theme (white background)
  • 50 lines per page, 14px Cascadia Code font
  • Final output: one PDF per source file

Prerequisites

  • Python 3 (tested with 3.14+)
  • Node.js and @resvg/resvg-js for SVG→PNG conversion
  • Python packages: img2pdf
npm install -g @resvg/resvg-js
pip install img2pdf

Workflow

1. Get the source code

Ask the user to provide the source code file(s). Save them to the workspace.

2. Generate SVGs + PNGs + PDF (all-in-one)

Run the batch script below. It reads every source code file and for each one:

  1. Splits code into pages (50 lines/page)
  2. Generates SVG for each page (syntax-highlighted, light theme, A4 ratio)
  3. Renders each SVG to PNG via @resvg/resvg-js
  4. Merges all PNG pages into a single PDF via img2pdf

Output structure:

filename.c/
├── code_page_1.svg
├── code_page_1.png
├── code_page_2.svg
├── code_page_2.png
└── ...
filename.c.pdf    ← merged PDF

Python Batch Script

Save as gen_code_pdfs.py and run with python gen_code_pdfs.py:

"""Batch convert code files → images + PDF. One PDF per source file."""
import os, sys, subprocess, json

# === CONFIG ===
FILES = [
    # List source files here, e.g.:
    # 'main.c', 'key.c', 'KEY.h', 'adc.h', 'ADC.c',
    # 'DS1302.c', 'DS1302.h', 'EEPROM.c', 'EEPROM.h',
]
BASE = os.getcwd()
LINES_PER_PAGE = 50

# Colors (light theme)
KW = {'void','char','int','u8','u16','u32','uchar','unsigned',
      'for','if','else','while','switch','case','break','return',
      'static','bit','sbit','xdata','idata','code','interrupt',
      'do','default','continue','struct','typedef','enum','const',
      '#ifndef','#define','#endif','#ifdef','extern'}
RG = {'P0','P1','P2','P3','P4','P5','P6','P7','RST','SCLK','IO','SCK'}
CO = {'keyword':'#d63384','register':'#e8590c','macro':'#099268',
      'number':'#2b8a3e','string':'#099268','comment':'#868e96',
      'var':'#1971c2','text':'#212529'}
BG='#ffffff'; HDR_BG='#f1f3f5'; HDR_BORDER='#dee2e6'
GT_BG='#f8f9fa'; GT_BORDER='#e9ecef'; LN_COLOR='#868e96'

def tokenize(line):
    t,i,n=[],0,len(line)
    while i\x3Cn:
        if line[i]=='#':
            j=i
            while j\x3Cn and line[j]!='\
': j+=1
            t.append((line[i:j],'macro')); break
        if line[i:i+2]=='//': t.append((line[i:],'comment')); break
        if line[i:i+2]=='/*':
            j=i+2
            while j\x3Cn and line[j:j+2]!='*/': j+=1
            if j\x3Cn: j+=2
            t.append((line[i:j],'comment')); break
        if line[i]=='"':
            j=i+1
            while j\x3Cn and line[j]!='"':
                if line[j]=='\\': j+=1
                j+=1
            if j\x3Cn: j+=1
            t.append((line[i:j],'string')); i=j; continue
        if line[i] in ' 	':
            j=i
            while j\x3Cn and line[j] in ' 	': j+=1
            t.append((line[i:j],'space')); i=j; continue
        if line[i].isdigit() or (line[i]=='0' and i+1\x3Cn and line[i+1] in 'xX'):
            if line[i]=='0'and i+1\x3Cn and line[i+1]in'xX': j=i+2
            else: j=i
            while j\x3Cn and (line[j].isalnum() or line[j] in '.xXa-fA-F'): j+=1
            t.append((line[i:j],'number')); i=j; continue
        if line[i].isalpha() or line[i]=='_':
            j=i
            while j\x3Cn and (line[j].isalnum() or line[j]=='_'): j+=1
            w=line[i:j]
            if w in KW: t.append((w,'keyword'))
            elif w in RG: t.append((w,'register'))
            else: t.append((w,'text'))
            i=j; continue
        t.append((line[i],'text')); i+=1
    if not t: t.append(('','space'))
    return t

def gen_svg_page(pg_lines, start_ln, page_num, total_pages, total_lines, fname):
    lh=24; fs=14; cw=8.4; gutter=56; xc=gutter+16; hh=60; mt=2; mb=4; mw=794
    max_l=max(len(l) for l in pg_lines) if pg_lines else 10
    w=max(mw, xc+max_l*cw+30)
    h=hh+len(pg_lines)*lh+mt+mb+20
    out=[]
    out.append(f'\x3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {h}">')
    out.append(f'\x3Cstyle>text{{font-family:"Cascadia Code","Fira Code","JetBrains Mono","Consolas","Courier New",monospace;font-size:{fs}px;}}\x3C/style>')
    out.append(f'\x3Crect width="{w}" height="{h}" fill="{BG}"/>')
    out.append(f'\x3Crect x="0" y="0" width="{w}" height="{hh}" fill="{HDR_BG}"/>')
    out.append(f'\x3Cline x1="0" y1="{hh}" x2="{w}" y2="{hh}" stroke="{HDR_BORDER}" stroke-width="1.5"/>')
    out.append(f'\x3Ctext x="20" y="24" fill="#343a40" font-size="16" font-family="Arial,Helvetica,sans-serif" font-weight="700">📄 {fname}\x3C/text>')
    out.append(f'\x3Ctext x="20" y="48" fill="#868e96" font-size="12" font-family="Arial,Helvetica,sans-serif">第 {page_num} 页 · 共 {total_pages} 页 · {total_lines} 行\x3C/text>')
    cy=hh+mt
    out.append(f'\x3Crect x="0" y="{hh}" width="{xc-8}" height="{h-hh}" fill="{GT_BG}"/>')
    out.append(f'\x3Cline x1="{xc-8}" y1="{hh}" x2="{xc-8}" y2="{h}" stroke="{GT_BORDER}" stroke-width="1"/>')
    for idx,line in enumerate(pg_lines):
        y=cy+idx*lh; ln=start_ln+idx
        out.append(f'\x3Ctext x="{xc-14}" y="{y+lh//2+4}" fill="{LN_COLOR}" text-anchor="end" font-size="12">{ln}\x3C/text>')
        if not line.strip(): continue
        toks=tokenize(line); x=xc
        for val,typ in toks:
            if typ=='space': x+=(val.count(' ')+val.count('	')*4)*cw
            else:
                c=CO.get(typ,'#212529'); e=val.replace('&','&').replace('\x3C','<').replace('>','>')
                out.append(f'\x3Ctext x="{x:.1f}" y="{y+lh//2+4}" fill="{c}">{e}\x3C/text>')
                x+=len(val)*cw
    out.append(f'\x3Ctext x="{w//2}" y="{h-8}" text-anchor="middle" fill="#adb5bd" font-size="10" font-family="Arial,Helvetica,sans-serif">- {page_num} -\x3C/text>')
    out.append('\x3C/svg>')
    return '\
'.join(out)

# === Main loop ===
for fname in FILES:
    src = os.path.join(BASE, fname)
    if not os.path.exists(src):
        print(f'SKIP {fname}'); continue

    with open(src, 'r', encoding='utf-8') as f:
        lines = f.readlines()
    total = len(lines)
    np = (total + LINES_PER_PAGE - 1) // LINES_PER_PAGE
    out_dir = os.path.join(BASE, fname + '_images')
    os.makedirs(out_dir, exist_ok=True)

    # Generate SVGs
    for p in range(1, np + 1):
        s = (p-1)*LINES_PER_PAGE; e = min(s+LINES_PER_PAGE, total)
        svg = gen_svg_page(lines[s:e], s+1, p, np, total, fname)
        with open(os.path.join(out_dir, f'code_page_{p}.svg'), 'w', encoding='utf-8') as f:
            f.write(svg)
    print(f'{fname}: {total} lines, {np} pages -> SVG OK')

    # Convert SVGs to PNGs via node + @resvg/resvg-js
    js = (
        'const fs=require("fs");const{Resvg}=require("@resvg/resvg-js");'
        'const dir=' + json.dumps(out_dir.replace('\\','\\\\')) + ';'
        'for(let p=1;p\x3C=' + str(np) + ';p++){'
        'const s=dir+"\\\\code_page_"+p+".svg";'
        'const pn=dir+"\\\\code_page_"+p+".png";'
        'try{const d=fs.readFileSync(s,"utf8");const r=new Resvg(d,{background:"#ffffff"});'
        'const b=r.render();fs.writeFileSync(pn,b.asPng())}'
        'catch(e){console.log("  ERR:"+p+" "+e.message)}}'
    )
    subprocess.run(['node','-e',js], check=True)
    print(f'{fname}: PNG OK')

    # Merge PNGs into a single PDF via img2pdf
    import img2pdf
    png_files = sorted(
        [os.path.join(out_dir, f) for f in os.listdir(out_dir) if f.endswith('.png')],
        key=lambda x: int(os.path.basename(x).replace('code_page_','').replace('.png',''))
    )
    pdf_path = os.path.join(BASE, fname + '.pdf')
    with open(pdf_path, 'wb') as f:
        f.write(img2pdf.convert(png_files))
    kb = os.path.getsize(pdf_path) // 1024
    print(f'{fname}: PDF -> {pdf_path} ({kb}KB)')

print('\
=== All done! ===')

Customization

Parameter Default Description
LINES_PER_PAGE 50 Lines per page
font_size 14 Base font size (px)
line_height 24 Line spacing (px)
COLOR_* Tweak colors for theme
REGS P0..P7 Register/pin names to highlight

Tips

  • Always unpack tokens as for val, typ in tokens (not tt, tv) — val = code text, typ = type name for color lookup
  • SVG viewBox keeps content at 1x; resvg renders at native A4 resolution
  • If node PNG conversion is slow, break into smaller batches
  • Keep the _images/ folders (SVG+PNG) for later editing; the PDF is the deliverable
Usage Guidance
Install only if you are comfortable running local Python and Node tooling. Prefer a virtual environment or project-local npm install, review the generated script before running it, and only add source files you intend to convert.
Capability Assessment
Purpose & Capability
The stated purpose, generated script, and outputs all align around rendering source files into SVG/PNG pages and merging them into PDFs.
Instruction Scope
Runtime instructions are limited to user-provided source files listed in the script and workspace-local output generation; no hidden network use, credential access, or unrelated actions were found.
Install Mechanism
The skill tells users to install @resvg/resvg-js globally with npm and img2pdf with pip, which is disclosed but not version-pinned or isolated.
Credentials
Reading code files and writing image/PDF artifacts in the workspace is proportionate to the conversion task.
Persistence & Privilege
The only persistence is generated output folders and PDF files; there is no background worker, startup hook, privilege escalation, or credential/session handling.
How to Use
  1. Make sure OpenClaw is installed (local or Docker)
  2. Run the install command in chat: /install code-to-images
  3. After installation, invoke the skill by name or use /code-to-images
  4. Provide required inputs per the skill's parameter spec and get structured output
Version History
v1.0.0
- Initial release of "code-to-images" skill. - Converts code files to syntax-highlighted, line-numbered A4-ratio images (SVG and PNG) with light theme. - Generates one multipage PDF per source file, each page showing 50 lines of code in 14px Cascadia Code font. - Output includes file name headers, per-page info, whitespace handling, and preserves code formatting. - Requires Python 3, Node.js, @resvg/resvg-js, and img2pdf; usage and workflow detailed in SKILL.md.
Metadata
Slug code-to-images
Version 1.0.0
License MIT-0
All-time Installs 0
Active Installs 0
Total Versions 1
Frequently Asked Questions

What is Code To Images?

Convert code files to A4-ratio PNG/SVG images with line numbers and syntax highlighting, then merge to PDF. It is an AI Agent Skill for Claude Code / OpenClaw, with 39 downloads so far.

How do I install Code To Images?

Run "/install code-to-images" in the OpenClaw or Claude Code chat to install it in one step — no extra setup required.

Is Code To Images free?

Yes, Code To Images is completely free, licensed under MIT-0. You can download, install and use it at no cost.

Which platforms does Code To Images support?

Code To Images is cross-platform and runs anywhere OpenClaw / Claude Code is available (cross-platform).

Who created Code To Images?

It is built and maintained by yon-gjun (@yon-gjun); the current version is v1.0.0.

💬 Comments