You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Adobe Illustrator JavaScript实现按颜色添加数字的脚本需求

按颜色为Illustrator图形添加数字编号解决方案

需求说明

需要为Illustrator文档中同一颜色的所有图形添加相同的数字编号(比如绿色标1、黄色标2),替代原脚本按图层编号的逻辑。

原脚本(仅按图层编号)

(function() {
var doc = app.activeDocument,
    lays = doc.layers,
    WORK_LAY = lays.add(),
    NUM_LAY = lays.add(),
    i = lays.length - 1,
    lay;

// main working loop
for (; i > 1; i--) {
    //process each layer
    lay = lays[i];
    lay.name = lay.name + " Num:" + (i - 1); // i-1 as 2 layers beed added.
    process(lay.pathItems, false);
    process(lay.compoundPathItems, true); // if any
}
//clean up
NUM_LAY.name = "Numbers";
WORK_LAY.remove();

function process(items, isCompound) {
    var j = 0,
        b, xy, s, p, op;

    for (; j < items.length; j++) {
        // process each pathItem
        op = items[j];
        // add stroke
        if (isCompound) {
            strokeComPath(op);
        } else {
            !op.closed && op.closed = true;
            op.filled = false;
            op.stroked = true;
        };
        b = getCenterBounds(op);
        xy = [b[0] + (b[2] - b[0]) / 2, b[1] + (b[3] - b[1]) / 2];
        s = (
            Math.min(op.height, op.width) < 20 ||
            (op.area && Math.abs(op.area) < 150)
            ) ? 4 : 6; // adjust font size for small area paths.
        add_nums(i - 1, xy, s);
    }
}

function getMinVisibleSize(b) {
    var s = Math.min(b[2] - b[0], b[1] - b[3]);
    return Math.abs(s);
}

function getGeometricCenter(p) {
    var b = p.geometricBounds;
    return [(b[0] + b[2]) / 2, (b[1] + b[3]) / 2];
}

// returns square of distance between p1 and p2
function getDist2(p1, p2) {
    return Math.pow(p1[0] + p2[0], 2) + Math.pow(p1[1] + p2[1], 2);
}

// returns visibleBounds of a path in a compoundPath p
// which is closest to center of the original path op
function findBestBounds(op, p) {
    var opc = getGeometricCenter(op);
    var idx = 0,
        d;
    var minD = getDist2(opc, getGeometricCenter(p.pathItems[0]));
    for (var i = 0, iEnd = p.pathItems.length; i < iEnd; i++) {
        d = getDist2(opc, getGeometricCenter(p.pathItems[i]));
        if (d < minD) {
            minD = d;
            idx = i;
        }
    }
    return p.pathItems[idx].visibleBounds;
}

function applyOffset(op, checkBounds) {
    var p = op.duplicate(WORK_LAY, ElementPlacement.PLACEATBEGINNING),
        // offset value the small the better, but meantime more slow. 
        offset = function() {
            var minsize = Math.min(p.width, p.height);
            if (minsize >= 50) {
                return '-1'
            } else if (20 < minsize && minsize < 50) {
                return '-0.5'
            } else {
                return '-0.2' // 0.2 * 2 (both side ) * 50 (Times) = 20
            }
        },
        xmlstring = '<LiveEffect name="Adobe Offset Path"><Dict data="I jntp 2 R mlim 4 R ofst #offset"/></LiveEffect>'
        .replace('#offset', offset()),
        TIMES = 100; // if shapes are too large, should increase the value.

    if (checkBounds) {
        // check its size only if it needs, because it's too slow
        while (TIMES-- && getMinVisibleSize(p.visibleBounds) > 3) p.applyEffect(xmlstring);
    } else {
        while (TIMES--) p.applyEffect(xmlstring);
    }
    return p;
}

function getCenterBounds(op) {
    var originalMinSize = getMinVisibleSize(op.visibleBounds);

    var p = applyOffset(op, false);

    if (getMinVisibleSize(p.visibleBounds) > originalMinSize) {
        // in some cases, path p becomes larger for some unknown reason
        p.remove();
        p = applyOffset(op, true);
    }

    var b = p.visibleBounds;

    if (getMinVisibleSize(b) > 10) {
        activeDocument.selection = [p];
        executeMenuCommand("expandStyle");
        p = activeDocument.selection[0];
        if (p.typename == "CompoundPathItem") {
            b = findBestBounds(op, p);
        }
    }

    p.remove();
    return b;
}

function add_nums(n, xy, s) {
    var txt = NUM_LAY.textFrames.add();

    txt.contents = n;
    txt.textRange.justification = Justification.CENTER;
    txt.textRange.characterAttributes.size = s;
    txt.position = [xy[0] - txt.width / 2, xy[1] + txt.height / 2];
}

function strokeComPath(compoundPath) {
    var p = compoundPath.pathItems,
        l = p.length,
        i = 0;

    for (; i < l; i++) {
        !p[i].closed && p[i].closed = true;
        p[i].stroked = true;
        p[i].filled = false;
    };
}
})();

修改后的脚本(按颜色编号)

(function() {
    var doc = app.activeDocument;
    var NUM_LAY = doc.layers.add();
    NUM_LAY.name = "颜色编号";
    
    // 存储颜色分组:key为RGB字符串,value为图形数组
    var colorGroups = {};
    // 存储颜色对应的编号
    var colorNumberMap = {};
    var currentNumber = 1;

    // 遍历所有图层的路径和复合路径
    function collectColoredItems() {
        for (var layIdx = 0; layIdx < doc.layers.length; layIdx++) {
            var layer = doc.layers[layIdx];
            // 处理普通路径
            for (var pathIdx = 0; pathIdx < layer.pathItems.length; pathIdx++) {
                var path = layer.pathItems[pathIdx];
                if (path.filled) {
                    addToColorGroup(path);
                }
            }
            // 处理复合路径
            for (var compIdx = 0; compIdx < layer.compoundPathItems.length; compIdx++) {
                var compPath = layer.compoundPathItems[compIdx];
                if (compPath.filled) {
                    addToColorGroup(compPath);
                }
            }
        }
    }

    // 将图形按填充色分组
    function addToColorGroup(item) {
        var fillColor = item.fillColor;
        // 仅处理RGB颜色(可扩展支持CMYK等)
        if (fillColor.typename === "RGBColor") {
            var rgbKey = `${fillColor.red},${fillColor.green},${fillColor.blue}`;
            if (!colorGroups[rgbKey]) {
                colorGroups[rgbKey] = [];
                colorNumberMap[rgbKey] = currentNumber++;
            }
            colorGroups[rgbKey].push(item);
        }
    }

    // 为每组颜色的图形添加编号
    function addNumbersToGroups() {
        for (var rgbKey in colorGroups) {
            var number = colorNumberMap[rgbKey];
            var items = colorGroups[rgbKey];
            items.forEach(function(item) {
                var center = getGeometricCenter(item);
                var size = Math.min(item.height, item.width);
                var fontSize = size < 20 || (item.area && Math.abs(item.area) < 150) ? 4 : 6;
                
                addNumberText(number, center, fontSize);
            });
        }
    }

    // 以下复用原脚本的工具函数
    function getGeometricCenter(p) {
        var b = p.geometricBounds;
        return [(b[0] + b[2]) / 2, (b[1] + b[3]) / 2];
    }

    function getMinVisibleSize(b) {
        var s = Math.min(b[2] - b[0], b[1] - b[3]);
        return Math.abs(s);
    }

    function getDist2(p1, p2) {
        return Math.pow(p1[0] + p2[0], 2) + Math.pow(p1[1] + p2[1], 2);
    }

    function findBestBounds(op, p) {
        var opc = getGeometricCenter(op);
        var idx = 0, d;
        var minD = getDist2(opc, getGeometricCenter(p.pathItems[0]));
        for (var i = 0, iEnd = p.pathItems.length; i < iEnd; i++) {
            d = getDist2(opc, getGeometricCenter(p.pathItems[i]));
            if (d < minD) {
                minD = d;
                idx = i;
            }
        }
        return p.pathItems[idx].visibleBounds;
    }

    function applyOffset(op, checkBounds) {
        var WORK_LAY = doc.layers.add();
        var p = op.duplicate(WORK_LAY, ElementPlacement.PLACEATBEGINNING);
        var offset = function() {
            var minsize = Math.min(p.width, p.height);
            if (minsize >= 50) return '-1';
            else if (20 < minsize && minsize < 50) return '-0.5';
            else return '-0.2';
        };
        var xmlstring = '<LiveEffect name="Adobe Offset Path"><Dict data="I jntp 2 R mlim 4 R ofst #offset"/></LiveEffect>'
            .replace('#offset', offset());
        var TIMES = 100;

        if (checkBounds) {
            while (TIMES-- && getMinVisibleSize(p.visibleBounds) > 3) p.applyEffect(xmlstring);
        } else {
            while (TIMES--) p.applyEffect(xmlstring);
        }
        
        var result = p;
        WORK_LAY.remove();
        return result;
    }

    function getCenterBounds(op) {
        var originalMinSize = getMinVisibleSize(op.visibleBounds);
        var p = applyOffset(op, false);

        if (getMinVisibleSize(p.visibleBounds) > originalMinSize) {
            p.remove();
            p = applyOffset(op, true);
        }

        var b = p.visibleBounds;

        if (getMinVisibleSize(b) > 10) {
            doc.selection = [p];
            executeMenuCommand("expandStyle");
            p = doc.selection[0];
            if (p.typename == "CompoundPathItem") {
                b = findBestBounds(op, p);
            }
        }

        p.remove();
        return b;
    }

    function addNumberText(number, center, fontSize) {
        var txt = NUM_LAY.textFrames.add();
        txt.contents = number;
        txt.textRange.justification = Justification.CENTER;
        txt.textRange.characterAttributes.size = fontSize;
        txt.position = [center[0] - txt.width / 2, center[1] + txt.height / 2];
    }

    // 执行流程
    collectColoredItems();
    addNumbersToGroups();
})();

关键说明

  • 脚本自动遍历文档中所有带填充色的路径和复合路径,忽略无填充的图形
  • 基于RGB颜色值精确分组,完全相同的RGB颜色会被分配同一个编号
  • 编号位置、字体大小逻辑复用原脚本,适配不同尺寸的图形
  • 所有编号会集中放在名为"颜色编号"的独立图层,方便管理

内容的提问来源于stack exchange,提问作者Teodoro Angel Dehesa Mitre

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 20:55:19