Local and cloud-based workflows for extracting and updating text layers in PSD files via ExtendScript and Adobe PS API. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
291 lines
No EOL
12 KiB
JavaScript
291 lines
No EOL
12 KiB
JavaScript
// Extract Internal Layer IDs from Photoshop Document
|
|
// This script extracts the actual internal IDs that Adobe's API uses
|
|
// to identify text layers in a PSD file.
|
|
#target photoshop
|
|
|
|
// Function to extract text layers with their internal IDs
|
|
function extractTextLayersWithInternalIDs() {
|
|
if (!app.documents.length) {
|
|
return {
|
|
error: "No document open"
|
|
};
|
|
}
|
|
|
|
var doc = app.activeDocument;
|
|
var result = {
|
|
documentName: doc.name,
|
|
psdPath: doc.fullName.fsName,
|
|
extractedAt: new Date().toString(),
|
|
dimensions: {
|
|
width: doc.width.as('px'),
|
|
height: doc.height.as('px')
|
|
},
|
|
textLayerCount: 0,
|
|
textLayers: []
|
|
};
|
|
|
|
// Utility function to traverse layers and extract info
|
|
function traverseLayers(layers, path) {
|
|
path = path || "";
|
|
for (var i = 0; i < layers.length; i++) {
|
|
var layer = layers[i];
|
|
|
|
if (layer.typename === "LayerSet") {
|
|
// This is a layer group, traverse its children
|
|
var groupPath = path ? path + "/" + layer.name : layer.name;
|
|
traverseLayers(layer.layers, groupPath);
|
|
} else if (layer.kind === LayerKind.TEXT) {
|
|
// This is a text layer, extract its information
|
|
extractTextLayerInfo(layer, path);
|
|
}
|
|
}
|
|
}
|
|
|
|
function extractTextLayerInfo(layer, path) {
|
|
try {
|
|
var layerPath = path ? path + "/" + layer.name : layer.name;
|
|
|
|
// Get the layer's ID directly from the layer object
|
|
var directID = layer.id;
|
|
|
|
// Get the layer's internal ID using ActionManager
|
|
var internalID = getInternalLayerID(layer);
|
|
|
|
// Extract text content
|
|
var text = layer.textItem.contents;
|
|
|
|
// Extract basic style information
|
|
var styleInfo = {
|
|
font: layer.textItem.font,
|
|
size: layer.textItem.size.value,
|
|
color: null, // Will be populated if available
|
|
alignment: getTextAlignment(layer)
|
|
};
|
|
|
|
// Try to extract text color
|
|
try {
|
|
if (layer.textItem.color.rgb) {
|
|
styleInfo.color = [
|
|
layer.textItem.color.rgb.red,
|
|
layer.textItem.color.rgb.green,
|
|
layer.textItem.color.rgb.blue
|
|
];
|
|
}
|
|
} catch (e) {
|
|
// Color not defined or error accessing it
|
|
}
|
|
|
|
// Extract detailed formatting info if available
|
|
var richTextInfo = extractRichTextInfo(layer);
|
|
if (richTextInfo && richTextInfo.styles && richTextInfo.styles.length > 0) {
|
|
styleInfo.styles = richTextInfo.styles;
|
|
var hasRichTextFormatting = richTextInfo.styles.length > 1;
|
|
} else {
|
|
styleInfo.styles = [];
|
|
var hasRichTextFormatting = false;
|
|
}
|
|
|
|
// Create layer info object with both direct and internal IDs
|
|
var layerInfo = {
|
|
id: directID, // The ID we normally use
|
|
internalID: internalID, // The ID Adobe API might use
|
|
name: layer.name,
|
|
path: layerPath,
|
|
text: text,
|
|
visible: layer.visible,
|
|
styleInfo: styleInfo,
|
|
hasRichTextFormatting: hasRichTextFormatting
|
|
};
|
|
|
|
// Add to results
|
|
result.textLayers.push(layerInfo);
|
|
result.textLayerCount++;
|
|
|
|
} catch (e) {
|
|
// Log error but continue processing other layers
|
|
$.writeln("Error processing layer '" + layer.name + "': " + e.message);
|
|
}
|
|
}
|
|
|
|
// Function to get internal layer ID using ActionManager
|
|
function getInternalLayerID(layer) {
|
|
try {
|
|
// Select the layer
|
|
var idslct = charIDToTypeID("slct");
|
|
var desc = new ActionDescriptor();
|
|
var ref = new ActionReference();
|
|
ref.putIdentifier(charIDToTypeID("Lyr "), layer.id);
|
|
desc.putReference(charIDToTypeID("null"), ref);
|
|
executeAction(idslct, desc, DialogModes.NO);
|
|
|
|
// Get the layer's item index
|
|
var ref = new ActionReference();
|
|
ref.putProperty(charIDToTypeID("Prpr"), charIDToTypeID("ItmI"));
|
|
ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
|
|
var itemIndexDesc = executeActionGet(ref);
|
|
var itemIndex = itemIndexDesc.getInteger(charIDToTypeID("ItmI"));
|
|
|
|
// Get the layer's ID
|
|
var ref = new ActionReference();
|
|
ref.putProperty(charIDToTypeID("Prpr"), stringIDToTypeID("layerID"));
|
|
ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
|
|
var layerDesc = executeActionGet(ref);
|
|
|
|
// Check if the layer has a layerID property
|
|
if (layerDesc.hasKey(stringIDToTypeID("layerID"))) {
|
|
return layerDesc.getInteger(stringIDToTypeID("layerID"));
|
|
} else {
|
|
// Fallback to another method of ID extraction
|
|
return layer.id; // Use direct ID as fallback
|
|
}
|
|
} catch (e) {
|
|
$.writeln("Error getting internal ID for layer '" + layer.name + "': " + e.message);
|
|
return layer.id; // Use direct ID if we can't get the internal ID
|
|
}
|
|
}
|
|
|
|
// Function to get text alignment
|
|
function getTextAlignment(layer) {
|
|
try {
|
|
var align = layer.textItem.justification;
|
|
if (align === Justification.LEFT) {
|
|
return "left";
|
|
} else if (align === Justification.CENTER) {
|
|
return "center";
|
|
} else if (align === Justification.RIGHT) {
|
|
return "right";
|
|
} else {
|
|
return "left"; // Default
|
|
}
|
|
} catch (e) {
|
|
return "left"; // Default if error
|
|
}
|
|
}
|
|
|
|
// Function to extract rich text information
|
|
function extractRichTextInfo(layer) {
|
|
try {
|
|
var styles = [];
|
|
|
|
// Get a reference to the layer
|
|
var ref = new ActionReference();
|
|
ref.putIdentifier(charIDToTypeID("Lyr "), layer.id);
|
|
var desc = executeActionGet(ref);
|
|
|
|
// Check if textKey exists
|
|
if (desc.hasKey(stringIDToTypeID("textKey"))) {
|
|
var textKey = desc.getObjectValue(stringIDToTypeID("textKey"));
|
|
|
|
// Check if textStyleRange exists
|
|
if (textKey.hasKey(stringIDToTypeID("textStyleRange"))) {
|
|
var styleRanges = textKey.getList(stringIDToTypeID("textStyleRange"));
|
|
|
|
// Iterate through each style range
|
|
for (var i = 0; i < styleRanges.count; i++) {
|
|
var range = styleRanges.getObjectValue(i);
|
|
var from = range.getInteger(stringIDToTypeID("from"));
|
|
var to = range.getInteger(stringIDToTypeID("to"));
|
|
var styleRef = range.getObjectValue(stringIDToTypeID("textStyle"));
|
|
|
|
var rangeText = layer.textItem.contents.substring(from, to);
|
|
var fontName = layer.textItem.font;
|
|
var fontSize = layer.textItem.size.value;
|
|
var fontStyle = "Regular";
|
|
var fontColor = null;
|
|
|
|
// Extract font name
|
|
try {
|
|
if (styleRef.hasKey(stringIDToTypeID("fontName"))) {
|
|
fontName = styleRef.getString(stringIDToTypeID("fontName"));
|
|
}
|
|
} catch (e) {}
|
|
|
|
// Extract font style
|
|
try {
|
|
if (styleRef.hasKey(stringIDToTypeID("fontStyleName"))) {
|
|
fontStyle = styleRef.getString(stringIDToTypeID("fontStyleName"));
|
|
}
|
|
} catch (e) {}
|
|
|
|
// Extract font size
|
|
try {
|
|
if (styleRef.hasKey(stringIDToTypeID("size"))) {
|
|
fontSize = styleRef.getUnitDoubleValue(stringIDToTypeID("size"));
|
|
}
|
|
} catch (e) {}
|
|
|
|
// Extract color
|
|
try {
|
|
if (styleRef.hasKey(stringIDToTypeID("color"))) {
|
|
var colorObj = styleRef.getObjectValue(stringIDToTypeID("color"));
|
|
var colorValues = colorObj.getObjectValue(stringIDToTypeID("color"));
|
|
|
|
// Check color model
|
|
if (colorValues.hasKey(stringIDToTypeID("red"))) {
|
|
fontColor = [
|
|
Math.round(colorValues.getDouble(stringIDToTypeID("red"))),
|
|
Math.round(colorValues.getDouble(stringIDToTypeID("green"))),
|
|
Math.round(colorValues.getDouble(stringIDToTypeID("blue")))
|
|
];
|
|
}
|
|
}
|
|
} catch (e) {}
|
|
|
|
// Add style info to the array
|
|
styles.push({
|
|
start: from,
|
|
end: to,
|
|
text: rangeText,
|
|
font: fontName,
|
|
style: fontStyle,
|
|
size: fontSize,
|
|
color: fontColor
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return { styles: styles };
|
|
} catch (e) {
|
|
$.writeln("Error extracting rich text info: " + e.message);
|
|
return { styles: [] };
|
|
}
|
|
}
|
|
|
|
// Start the layer traversal
|
|
traverseLayers(doc.layers);
|
|
|
|
return result;
|
|
}
|
|
|
|
// Main function
|
|
function main() {
|
|
try {
|
|
var result = extractTextLayersWithInternalIDs();
|
|
|
|
// Convert result to formatted JSON
|
|
var jsonString = JSON.stringify(result, null, 2);
|
|
|
|
// Save the result to a file
|
|
var docPath = app.activeDocument.path;
|
|
var fileName = app.activeDocument.name.replace(/\.[^\.]+$/, '') + "_internal_ids.json";
|
|
var outputFile = new File(docPath + "/" + fileName);
|
|
|
|
if (outputFile.open("w")) {
|
|
outputFile.write(jsonString);
|
|
outputFile.close();
|
|
alert("Layer ID information saved to:\n" + outputFile.fsName);
|
|
} else {
|
|
alert("Error saving output file");
|
|
}
|
|
|
|
// Also return the JSON string
|
|
return jsonString;
|
|
} catch (e) {
|
|
alert("Error: " + e.message);
|
|
return JSON.stringify({ error: e.message });
|
|
}
|
|
}
|
|
|
|
// Run the script
|
|
main(); |