Copy link to clipboard
Copied
Hi community, is there a way to break apart text in Illustrator like Corel does, if anyone is familiar?
I saw this post since 2009. I do try like the post says, select the text and then: Flatten transparancy, but not all the text breaks up to seperate it indvidually.
https://community.adobe.com/t5/illustrator-discussions/break-apart-in-illustrator/m-p/1201629#M672
Hi Stefan, I think you already have answers to this, but for others who come to see this question in the future, I've written a splitting-text-into-characters script that has some extra capabilities. I took Carlos' script for inspiration and made the following modifications:
1. It handles area type.
2. It handles multi-line text.
3. It handles left, center, right justification.
4. It handles kerning, baseline shift, different fonts and point sizes.
5. It can split textframes into lines, words or
...Here is the action, Mark:
The file contains an action set file (char_splitter_1.aia) and a sample Illustrator file (char_splitter_sample.ai) with ten different type objects for test purposes.
Instruction:
- Unzip the file and open char_splitter_sample.ai
- In the Actions palette import char_splitter_1.aia
- Select one or a couple of type objects
- Run the action
You may also check the type objects with your script. The script will sometimes make funny things.
Copy link to clipboard
Copied
There's no built-in method, but did you try Carlos's scripts, linked in that topic?
Copy link to clipboard
Copied
I tried using his script method, but get this error:
Copy link to clipboard
Copied
Hi Stefan, I think you already have answers to this, but for others who come to see this question in the future, I've written a splitting-text-into-characters script that has some extra capabilities. I took Carlos' script for inspiration and made the following modifications:
1. It handles area type.
2. It handles multi-line text.
3. It handles left, center, right justification.
4. It handles kerning, baseline shift, different fonts and point sizes.
5. It can split textframes into lines, words or characters.
EDIT: Updated code with some improvements, including a warning if you've selected lots of text (thanks Kurt!) also I found an obscure bug in converted overset area text frames that continue to advertise the original overset text even though it's no longer presentāI had to explicitly remove overset text before converting.
Edit: another update, I improved some things, added a UI, worked around Illustrator bug with parsing of words when punctuation is adjacent, added "Discard punctuation" option, and "Monospace" option for character spacing (this is for no reasonājust because I can).
Edit: just cleaned up structure a bit.
Update: I've had a look at @Kurt Gold's char_splitter action posted in this thread and it's *really* good for splitting text into characters. Which to use char_splitter action or splitTextFrame script? Have a look at this table:
|
char_splitter_1 action |
splitTextFrame script |
Handle point text |
ā |
ā |
Handle rectangular area text with one cell |
ā |
ā |
Handle area text with columns or rows |
ā |
ā |
Handle path text |
ā |
ā |
Handle text inside non-rectangular frame |
ā |
ā |
Break text into Lines |
ā |
ā |
Break text into Words |
ā |
ā |
Break Text into Characters |
ā |
ā |
Monospace characters |
ā |
ā |
- Mark
/*
Split TextFrames.js
For Adobe Illustrator
by m1b
here: https://community.adobe.com/t5/illustrator-discussions/script-error/m-p/12730864/thread-id/308920
⢠Inspired by CarlosCanto's splitSelectedWordsIntoCharacters.jsx
⢠Can split textframe into lines, words or characters.
⢠The `Discard punctuation` option discards non-alphanumeric characters
⢠The `Monospace` option spaces each character by a specified number of points.
*/
var SplitType = {
LINES: 0,
WORDS: 1,
CHARACTERS: 2
};
// global defaults
var settings = {
// split into lines, words or characters
splitType: SplitType.CHARACTERS,
// when splitting into words, ignore non alpha-numeric characters
discardPunctuation: false,
// when splitting into characters,
// space each character by n points
// 0 = do not space
monoSpace: false,
monoSpacePts: 10,
// show UI, or just go with settings we have here
showUI: true
};
// Wrd class just gave me a little help to break content into words
Wrd = function (contents, start, end, type) {
this.contents = contents;
this.start = start;
this.end = end;
this.type = type;
}
Wrd.type = {
NONWORD: 0,
WORD: 1,
}
Wrd.prototype.toString = function () {
return '['
+ this.contents + ' '
+ this.start + '-' + this.end + ' '
+ ['NONWORD', 'WORD'][this.type]
+ ']';
}
main();
function main() {
var doc = app.activeDocument,
items = doc.selection;
if (items.length < 1) {
alert('Please select one or more text frames and try again.');
return;
}
settings.items = items;
settings.action = splitTextFrames;
settings.getActionParams = function () { return [settings.items, settings.splitType] };
// show the UI
if (settings.showUI) {
invokeUI();
} else {
settings.action.apply(null, settings.getActionParams());
}
}
function splitTextFrames(items, splitType) {
// split into lines, words or characters
var splitFunction;
if (splitType === SplitType.WORDS) {
splitFunction = splitTextFrameLineIntoWords;
} else if (splitType === SplitType.CHARACTERS || splitType == undefined) {
splitFunction = splitTextFrameLineIntoCharacters;
}
// always work with array
if (!items.hasOwnProperty('0')) items = [items];
// check if large number of splits
var numberOfSplits = 0,
splitThis;
if (splitType === SplitType.LINES) {
splitThis = 'lines';
} else if (splitType === SplitType.WORDS) {
splitThis = 'words';
} else if (splitType === SplitType.CHARACTERS) {
splitThis = 'characters';
}
for (var i = items.length - 1; i >= 0; i--)
numberOfSplits += items[i][splitThis].length;
// warn user if large number of splits
if (numberOfSplits > 999)
if (!(confirm('Are you sure you wish to make ' + numberOfSplits + ' splits? It may take a long time and can\'t be cancelled once started.')))
return;
// loop over items
for (var i = items.length - 1; i >= 0; i--) {
var item = items[i];
// ignore everything except textFrames
if (item.constructor.name != 'TextFrame') continue;
// sorry can't handle text on path
if (item.kind == TextType.PATHTEXT) continue;
// change to point text, not area text
var item = convertAreaTextToPointText(item);
// split into lines
var itemLines = splitTextFrameIntoLines(item);
// stop here if we just want lines
if (splitFunction == undefined) continue;
// split each line further
for (var j = itemLines.length - 1; j >= 0; j--) {
splitFunction(itemLines[j], settings.discardPunctuation, settings.monoSpace && settings.monoSpacePts);
}
}
}
function splitTextFrameIntoLines(item) {
var y = item.position[1],
itemLines = [];
removeAutoLeading(item);
// loop over lines
for (i = item.lines.length - 1; i >= 0; i--) {
var h = item.height;
// ignore empty lines
if (item.lines[i].contents.length == 0) continue;
var _line = item.duplicate();
// remove other lines
for (var j = _line.lines.length - 1; j >= 0; j--) {
if (j != i) _line.lines[j].remove();
}
// remove a line for height calculation
item.lines[item.lines.length - 1].remove();
// remove \u0003 or height will be wrong
while (
item.lines.length > 0
&& item.characters[item.characters.length - 1].contents == '\u0003'
)
item.characters[item.characters.length - 1].remove();
// remove empty lines at start
while (_line.lines[0].contents.length == 0)
_line.characters[0].remove();
// remove \u0003 from end of line
while (_line.characters[_line.characters.length - 1].contents == '\u0003')
_line.characters[_line.characters.length - 1].remove();
// position
_line.top = y - h + _line.height;
itemLines.push(_line);
}
// remove the original item
item.remove();
return itemLines;
}
function splitTextFrameLineIntoWords(item, discardPunctuation) {
var x = item.position[0],
itemWords = [],
wordCharacters = /[A-Za-zĆ-ĆĆ-öø-Ćæ0-9-]+/g,
wordAndPunctuationCharacters = /[^\s]+/g,
matcher = discardPunctuation ? wordCharacters : wordAndPunctuationCharacters,
matched,
wrds = [],
lastStart = 0;
// match words in item.contents
// (using Wrd objects to store start, end and type)
matcher.lastIndex = 0;
while ((matched = matcher.exec(item.contents)) !== null) {
if (lastStart + matcher.lastIndex - matched[0].length != lastStart) {
// add non-word at start
wrds.push(new Wrd(
/* contents */ item.contents.substr(lastStart, matcher.lastIndex - matched[0].length - lastStart),
/* start */ lastStart,
/* end */ matcher.lastIndex - matched[0].length - 1,
/* type */ Wrd.type.NONWORD
));
}
// add word
wrds.push(new Wrd(
/* contents */ matched[0],
/* start */ matcher.lastIndex - matched[0].length,
/* end */ matcher.lastIndex - 1,
/* type */ Wrd.type.WORD
));
lastStart = matcher.lastIndex;
}
// add non-word at end
if (matcher.lastIndex > 0 && matcher.lastIndex <= item.contents.length - 1) {
wrds.push(new Wrd(
/* contents */ item.contents.substr(matcher.lastIndex, item.contents.length - matcher.lastIndex),
/* start */ matcher.lastIndex,
/* end */ item.contents.length - 1,
/* type */ Wrd.type.NONWORD
));
}
// loop over wrds
var wrd;
while (wrd = wrds.pop()) {
var w = item.width;
if (wrd.type == Wrd.type.WORD) {
// duplicate textFrame
var dup = item.duplicate();
// remove characters before wrd
for (var i = wrd.start - 1; i >= 0; i--)
dup.characters[i].remove();
// position
dup.left = x + w - dup.width;
}
// trim wrd from item
for (var i = wrd.end; i >= wrd.start; i--)
item.characters[i].remove();
itemWords.push(dup);
}
// remove the original item
item.remove();
return itemWords;
}
function splitTextFrameLineIntoCharacters(item, discardPunctuation, monoSpace) {
var x = item.position[0],
wordCharacters = /[A-Za-zĆ-ĆĆ-öø-Ćæ0-9-]+/g,
wordAndPunctuationCharacters = /[^\s]+/g,
matcher = discardPunctuation ? wordCharacters : wordAndPunctuationCharacters,
itemCharacters = [];
// loop over characters
for (i = item.characters.length - 1; i >= 0; i--) {
var w = item.width;
// ignore spaces and linefeeds
if (matcher.test(item.characters[i].contents)) {
// duplicate textFrame
var _character = item.duplicate();
// remove all other chars
for (var j = _character.characters.length - 1; j >= 0; j--) {
if (j != i) _character.characters[j].remove();
}
// position
if (monoSpace != false) {
_character.left = x + (monoSpace * i);
} else {
_character.left = x + w - _character.width;
}
}
// remove character from original item
item.characters[i].remove();
itemCharacters.push(_character);
}
// remove the original item
item.remove();
return itemCharacters;
}
function discardOversetText(item) {
var lastVisibleChar = item.lines[item.lines.length - 1].characters[item.lines[item.lines.length - 1].characters.length - 1],
lastOffset = lastVisibleChar.characterOffset;
// remove overset characters
for (var i = item.characters.length - 1; i >= lastOffset; i--)
item.characters[i].remove();
}
function convertAreaTextToPointText(item) {
if (item.kind == TextType.AREATEXT) {
var temp = item.duplicate();
// oversetText causes errors later
discardOversetText(temp);
// convert
temp.convertAreaObjectToPointObject();
// get fresh reference to converted textFrame
for (var i = 1; i < item.layer.textFrames.length; i++) {
if (item.layer.textFrames[i].uuid == item.uuid) {
var convertedItem = item.layer.textFrames[i - 1];
item.remove();
item = convertedItem;
break;
}
}
// remove unnecessary spaces before \u0003
for (var i = item.characters.length - 1; i >= 0; i--) {
if (item.characters[i].contents == '\u0003') {
if (i > 1 && item.characters[i - 1].contents == ' ')
item.characters[i - 1].remove();
}
}
if (item.characters[item.characters.length - 1].contents == ' ')
item.characters[item.characters.length - 1].remove();
}
return item;
}
function removeAutoLeading(item) {
var items = [];
if (item.hasOwnProperty('paragraphs')) {
items = item.paragraphs;
} else if (item.hasOwnProperty('lines')) {
items = item.lines;
} else if (item.hasOwnProperty('characters')) {
items = item.characters;
}
for (var i = 0; i < items.length; i++) {
if (items[i].contents.length == 0) continue;
var hasAutoLeading = items[i].characterAttributes.autoLeading;
if (hasAutoLeading) {
items[i].characterAttributes.leading = items[i].paragraphAttributes.autoLeadingAmount / 100 * items[i].characterAttributes.size;
items[i].characterAttributes.autoLeading = false;
}
}
}
// just UI from here
function invokeUI(p) {
var dialog = makeUI(p)
dialog.center();
var result = dialog.show();
dialog = null;
if (result === -1) {
// cancelled
return;
} else if (result === 1) {
// do action
settings.action.apply(null, settings.getActionParams());
}
function makeUI(p) {
var menuItems = ['Lines', 'Words', 'Characters'],
w = new Window("dialog", 'Split TextFrame'),
menuRow = w.add("Group {orientation:'row', alignment:['fill','top']}"),
discardPunctuationGroup = w.add("Group {orientation:'column', alignment:['fill','top'], margins:[0,7,0,0] }"),
stack = w.add("Group {orientation:'stack', alignment:['fill','fill']}"),
buttonRow = w.add("Group {orientation:'row', alignment:['right','top']}"),
mainMenu = menuRow.add("Dropdownlist {alignment:['left','center']}"),
monoSpaceGroup = stack.add("Group {orientation:'row', alignment:['fill','top']}"),
monoSpaceCheckbox = monoSpaceGroup.add("Group {orientation:'column', alignment:['fill','top'], margins:[0,7,0,0] }")
.add("Checkbox { alignment:['left','fill'], text:'Monospace', value:false, margins:[0,7,0,0] }"),
monoSpaceEditText = monoSpaceGroup.add("edittext", undefined, "0"),
discardPunctuationCheckbox = discardPunctuationGroup.add("Checkbox { alignment:['left','fill'], text:'Discard punctuation', value:false }"),
cancelButton = buttonRow.add('button', undefined, 'Cancel', { name: 'cancel' }),
okButton = buttonRow.add('button', undefined, 'Split', { name: 'ok' });
monoSpaceGroup.add("statictext", undefined, "pts");
for (var i = 0; i < menuItems.length; i++)
mainMenu.add('item', menuItems[i]);
discardPunctuationCheckbox.value = settings.discardPunctuation;
monoSpaceCheckbox.value = settings.monoSpace;
monoSpaceEditText.text = settings.monoSpacePts;
monoSpaceEditText.characters = 3;
mainMenu.preferredSize.width = 180;
mainMenu.title = 'Split into';
mainMenu.selection = settings.splitType;
updateUI();
okButton.onClick = splitButtonClicked;
cancelButton.onClick = cancelButtonClicked;
mainMenu.onChange = updateUI;
monoSpaceCheckbox.onClick = updateUI;
return w;
function splitButtonClicked() {
settings.splitType = mainMenu.selection.index;
settings.discardPunctuation = discardPunctuationCheckbox.value;
settings.monoSpace = monoSpaceCheckbox.value;
settings.monoSpacePts = Number(monoSpaceEditText.text);
w.close(1);
};
function cancelButtonClicked() {
w.close(-1);
}
function updateUI() {
var s = mainMenu.selection ? mainMenu.selection.index : 0;
monoSpaceGroup.visible = (s == SplitType.CHARACTERS);
monoSpaceEditText.enabled = monoSpaceCheckbox.value
discardPunctuationGroup.visible = (s != SplitType.LINES);
}
}
}
Copy link to clipboard
Copied
Works fine, thanks!
Copy link to clipboard
Copied
That's a very good contribution, Mark.
One thing you may consider is to include an alert that informs about a possible very long execution time in case the type objects contain a lot of text.
Just did a test (yes, a rather cruel one) with a pretty large area type object and had to force quit Illustrator because I'm sure it will take half a decade to complete the script's activity.
Just a well-meant idea.
Copy link to clipboard
Copied
Haha, thanks Kurt! Once again you have pushed one of my scripts to its (very modest) limits. š
I think soon I will post a question to the forum about performance improvements and efficiently processing large numbers of objects. It is one of my weakest areas.
For now your suggestion is sensible. I will add it.
- Mark
Copy link to clipboard
Copied
Thanks so much for this!
Kind regards.
Copy link to clipboard
Copied
Your modified version of the script is a very good improvement, Mark. Thanks for sharing it.
As for the performance issues, I would not care too much. I don't know many reasons why one wants to split really large type objects into single characters.
By the way, the exercise can also be done with a dirty action. Unlike the script at its current state the action would properly support multiple column/row textframes, rotated type objects and type on a path objects. But it may as well be slow with a lot of text (probably a bit slower than your excellent script).
Copy link to clipboard
Copied
I've given it another updateānow has a rudimentary UI. As always I'm fascinated with your "dirty actions" haha. My brain just doesn't seem to work that way. I'd like to see something like that, or at least an idea of the technique behind it.
- Mark
Copy link to clipboard
Copied
Thanks for the modified version of your script, Mark.
I think in line 415 there is a little lapse:
okButton = buttonRow.add('button', undefined, buttonTitle, { name: 'ok' });
Probably you will have to change it to something like this:
okButton = buttonRow.add('button', undefined, 'OK', { name: 'ok' });
Otherwise the script won't run.
As for the dirty action (yes, it is really dirty, but works in almost all cases): I will post it this evening or tomorrow.
Copy link to clipboard
Copied
Thanks Kurt. Bug fixed. No hurry of course for the action, but I'm curious. š
- Mark
Copy link to clipboard
Copied
Here is the action, Mark:
The file contains an action set file (char_splitter_1.aia) and a sample Illustrator file (char_splitter_sample.ai) with ten different type objects for test purposes.
Instruction:
- Unzip the file and open char_splitter_sample.ai
- In the Actions palette import char_splitter_1.aia
- Select one or a couple of type objects
- Run the action
You may also check the type objects with your script. The script will sometimes make funny things.
Copy link to clipboard
Copied
I tried your action @Kurt Gold and it's amazingāsome magic in there! You are right that your action splits all different types of text frames whereas my script just handles absolute vanilla, one-cell, rectangular textframes and point text. char_splitter_1 is definitely the winner! š
By the way, I'm embarrassed to say that I didn't know there was a rotate field in the Text Panel! I assume it's been there for decades. Haha.
- Mark
Copy link to clipboard
Copied
Very smart Kurt!
Copy link to clipboard
Copied
Thanks for testing, Mark.
I think it's not really important to declare a winner. My approach is just another one of my bumpy actions. Your script is excellent and I'm pretty sure that it could be improved, such that it supports all kinds of type objects.
On the other hand, I also think that Illustrator already has a sophisticated tool to handle type objects as if they were split. It's the Touch Type tool. In many cases it can do things without actually splitting the type objects into separate characters.
Well, but of course it's always fun to find some ways to really split things.
Copy link to clipboard
Copied
Haha, I've never used the touch type tool before either! It's fun! I've so much to learn.
As far as winners and losers go, yeah it's not important, but I like to end up with the best solution to a problem people have, even if it isn't mine š I'm doing this for fun mostly, but I also like to build things people can use in the future, as I suspect you do too.
Your stress-testing always helps make a better script, and I'm always blown-away by your dirty magic. Haha. sorry to call it that, but it's so funny.
- Mark