Copy link to clipboard
Copied
hello all you smart guys.
I get a big complex files from another designers every month for further processing. Among other not-so-good habits, they usually do not use layers.
To make layout a bit more organized, I move text to its own layer - simple task, have script for it. But it does the job straightforward - exactly 'as advertised' (Jongware's expression - I like it so )
Unfortunately, those designers didn't bother much with 'frame content' option. They can draw a frame with a text tool, fill it with color and use it as a background for some artwork or whatever...
my script 'text frames to separate layer' creates a mess on such pages, and locate it - not always is an easy task. Document usually has over 200 pages...
So, I'm dreaming about some magic script, which is able to "prepare" text for moving, I mean, to find and process:
case1: empty text frame with no fill/outline - remove (delete)
case2: empty text frame with any fill/outline - convert to 'unassigned'
case3: text frame with the same color applied to text and the frame itself (yes, it happens!) - well, maybe just 'select and stop'? I'm afraid it's too complicated to remove text from such a frame and then treat the frame as 'case2'?
any suggestions would be greatly appreciated...
@winterm:
You can try this using script menu actions. This will only work, if the certain menu action is available.
It is not available if there is contents in the text frame (e.g. a blank character) or (much more important from a scripters point of view) if a threaded text frame is empty because it's contents cannot fit inside it and it's contents is an empty string.
Following code will single out empty text frames, select them and invoke a script menu action to set them to "Unassigned":
...var _d = a
Copy link to clipboard
Copied
@Colly – look at my screenshot in answer #9 of this thread. Yes, with threaded text frames there are constellations where the layout could change and overset text would be the result.
Did I already mention tables, that run through more than one text frame?
Also, something one could consider; but maybe a bit nit-picking…
Uwe
Copy link to clipboard
Copied
I think I might try something simpler next time... climbing Mt Everest perhaps.
have made a testfile that I am sure covers any instance where a clear box should stay/be deleted: https://dl.dropboxusercontent.com/u/55743036/empty%20textbox%20test.idml
the script so far leaves items mentioned by Uwe (tables that run through more than one text frame, and text boxes that have nothing in them due to overset text shifting the story onto the next textbox), and am glad to say also:
however, it did NOT delete graphic/unassigned rectangles/ovals/polygons that have no fill/stroke/textwrap/path on type that are anchored objects within a text frame.
An old forum features a script from harbs (http://forums.adobe.com/message/2668865) that removes any no graphic/text anchored objects, but it's too powerful (will delete the anchored object even if it has a fill or stroke or wrap or text on path). I've since modded his script and added it to the one i've been developing. without placing the entire code, i'll just include the modded snippet:
var myDocument = app.activeDocument;
var inlines = myDocument.stories.everyItem().pageItems.everyItem().getElements();
while(fr=inlines.pop()){
if(fr instanceof Group){continue}
if(fr instanceof TextFrame){
if(fr.contents == "" && fr.strokeWeight == "0" && fr.fillColor.name == "None" && fr.textWrapPreferences.textWrapMode === TextWrapModes.NONE && fr.textPaths.length == 0){fr.remove()}
continue;
}
if(fr.graphics.length==0 && fr.strokeWeight == "0" && fr.fillColor.name == "None" && fr.textWrapPreferences.textWrapMode === TextWrapModes.NONE && fr.textPaths.length == 0){fr.remove()}
}
But as I look at this modded script, I have a feeling that if I made a copy of the script but replaced the line:
var inlines = myDocument.stories.everyItem().pageItems.everyItem().getElements();
and changed it to
var inlines = myDocument.stories.everyItem().getElements();
That resulting script would effectively do what the following portion of the script does, but with less syntax:
var myStories = app.activeDocument.stories.everyItem().getElements();
for (i = myStories.length - 1; i >= 0; i--){
var myTextFrames = myStories.textFrames;
for (j = myTextFrames.length - 1; j >= 0; j--) {
var stroke = myTextFrames
.strokeWeight; var color = myTextFrames
.fillColor.name; var tpath = myTextFrames
.textPaths.length; var wrap = myTextFrames
.textWrapPreferences.textWrapMode; //alert (color)
if (myTextFrames
.contents === "" && stroke == "0" && color == "None" && wrap === TextWrapModes.NONE && tpath == 0){ //alert ("yes")
myTextFrames
.remove(); }
}
}
is that correct?
Colly
Copy link to clipboard
Copied
@Colly – I know I'm a little late for commenting your answer #26, but my answer is lengthy 😉
Your "condensed" version* of the script will do nothing (or at least not much), because:
//EDIT: After re-reading the whole thread, I can see that this last snippet you posted in answer #26 is not your's. Sorry. However, I did the following test with that snippet.
1. It does not work with the text container(s) of a particular story, but only with text frames that sit inside the text of the stories (anchored or inline text frames).
I ran your script (slightly altered: instead of removing I did a fill with "Magenta") on the following situation:
After running the script:
(the Magenta filled ones would be removed)
2. And even if this script would remove the two text frames hosting the anchored ones:
Text frames with tables that span several threaded text frames are considered empty but the one text frame that contains the start of the table.
Let's test #2 on a small example with the following code that is running on a selection of text frames:
//Some threaded text frames selected.
//They contain a table that spans several text containers.
var sel = app.selection;
//EASY TEST on emptyness with contents property:
//Running the loop backwards in case we want to remove something
//and so mixing up the length of the
for(var n=sel.length-1;n>=0;n--){
if(sel
.contents == ""){sel .fillColor = "Magenta"}; };
Example screen shots:
Before
After running the above snippet:
The Magenta filled text frames are considered empty (as far as this script's logic is going)!
To tackle problems #1 and also #2 we could:
1. Work with the textContainers array of a story object.
That means we are working of an array of arrays, if the document contains more than one story.
2. Duplicate all text frames or the parent of a text path (= a graphic line, a text frame, a spline item …) and check for emptyness of the duplicate and/or its text path.
Background: if you duplicate a text frame containing one part of a table that is otherwise occupying a number of text frames and its contents property is suggesting it's empty, you'll get always that portion of the table, that is shown in the original text frame.
Below a more elaborate code for detecting empty* text frames and removing them.
However, there is one flaw in this code:
If it occurs, that a button object contains a state with empty text containers only, the whole button will or can be removed!
Depends on the number of states and if the removed object sits in an active state.
Explanation: The Story object of the document is a deep-level object and the collection of all story objects will summerize every text frame or every text path in the document. Even in nested objects like groups, MSOs, etc. and well, also buttons.
And there is another flaw (a tiny one), which I camouflaged with a "try/catch -> continue" scenario. Some objects might not exist anymore in the moment I want to remove them. I did not implement a optimization routine on double ID numbers of the array, that is responsible for removing the text containers or in case of text paths their parent objects.
Warning: The script could create situations where text overflows like shown in answer #9 here.
There is no steady control of overflow during the script's action.
Code:
//REMOVE_EmptyTextContainers_DuplicateMethod_STORIES-TEXTCONTAINERS_v2.jsx
//Uwe Laubender
/**
* @@@BUILDINFO@@@ REMOVE_EmptyTextContainers_DuplicateMethod_STORIES-TEXTCONTAINERS_v2.jsx !Version! Sat Jan 04 2014 20:24:54 GMT+0100
*/
//THERE IS NO TEST ON EFFECTS !!
//Effects on otherwise empty objects are not visible. They need at least a stroke weight or a fill color to get in effect.
//If I'm wrong here, empty objects with visible effects are removed as well.
//Foreseeable issues:
//BUTTONS COULD BE REMOVED,
//if one of the states contain empty text frames only OR empty text paths on otherwise empty parent objects only!
//MSOs WILL BE REMOVED (A MESSAGE WILL POP UP),
//if the MSO's empty text frames are removed
//and only one state would be left over
//however, all text frames with contents will survive!
//WHAT IS NOT CONSIDERED EMPTY:
//1. Frames with a fillColor.name other than "None"
//2. Frames with a strokeWeight other than "0"
//3. Graphic lines with a text path with a strokeWeight other than "0"
//4. Text frames and text paths with a contents other than "".
//ALL TEXTFRAMES OR TEXTPATHS ARE EXAMINED AS NOT THREADED.
app.scriptPreferences.userInteractionLevel = UserInteractionLevels.interactWithAll;
app.doScript(_RemoveEmptyTextContainers, ScriptLanguage.JAVASCRIPT, [], UndoModes.ENTIRE_SCRIPT, "Remove empty text containers");
function _RemoveEmptyTextContainers(){
var d=app.documents[0];
var counterOne = 0;
var counterTwo = 0;
var IDsOfallTextContainers = new Array();
var IDsOfTextContainersToRemove = new Array();
var arrayOfArraysOfTextContainers = d.stories.everyItem().textContainers;
for(var n=0;n<arrayOfArraysOfTextContainers.length;n++){
var arrayOfTextContainers = arrayOfArraysOfTextContainers
; for(c=0;c<arrayOfTextContainers.length;c++){
IDsOfallTextContainers[counterOne] = arrayOfTextContainers
.getElements()[0].id; ++counterOne;
};
};
for(var n=0;n<IDsOfallTextContainers.length;n++){
var myOriginal = d.pageItems.itemByID(IDsOfallTextContainers
).getElements()[0]; $.writeln(myOriginal);
//Here is more testing on "emptyness" needed:
//Whitespace in contents
//. . . ???
//Column breaks, page breaks in contents
//Overset text possible !!!
//Case 1: myOriginal is textFrame
if(myOriginal.constructor.name === "TextFrame"){
var myDup = myOriginal.duplicate();
if(
myDup.contents === ""
&& myDup.fillColor.name === "None"
&& myDup.strokeWeight === 0
&& myDup.textWrapPreferences.textWrapMode === TextWrapModes.NONE
){
IDsOfTextContainersToRemove[counterTwo] = myOriginal.id;
};
++counterTwo;
myDup.remove();
};
//Case 2: myOriginal is textPath
if(myOriginal.constructor.name === "TextPath"){
var myDup = myOriginal.parent.duplicate();
if(
myDup.textPaths[0].contents === ""
&& myDup.constructor.name === "GraphicLine"
&& myDup.strokeWeight === 0
&& myDup.textWrapPreferences.textWrapMode === TextWrapModes.NONE
){
IDsOfTextContainersToRemove[counterTwo] = myOriginal.id;
++counterTwo;
};
if(
myDup.textPaths[0].contents === ""
&& myDup.constructor.name != "GraphicLine"
&& myDup.strokeWeight === 0
&& myDup.fillColor.name === "None"
&& myDup.textWrapPreferences.textWrapMode === TextWrapModes.NONE
){
IDsOfTextContainersToRemove[counterTwo] = myOriginal.id;
++counterTwo;
};
myDup.remove();
};
};
//Array could be optimized for removing doubles in ID numbers:
for(var n=0;n<IDsOfTextContainersToRemove.length;n++){
try{
d.pageItems.itemByID(IDsOfTextContainersToRemove
).remove(); }catch(e){
//~ $.writeln(e.message);
continue;
};
};
}; //END of function _RemoveEmptyTextContainers()
Uwe
Message was edited by: Laubender
Copy link to clipboard
Copied
I updated the post above.
Uwe
Copy link to clipboard
Copied
An annotation to post #27 – the first situation where an empty text frame is anchored to an otherwise empty text frame.
It's more tricky, than I first thought 😉
If the anchored text frame is removed, the host is considered empty, but might not be removed. It depends on the order of detecting empty frames and the order of removing them.
So in a situation like this:
It might be better to do a search from a deeper level of nesting to not nested.
A lot of effort.
Or the user (or the script) is running the script more than once. Perhaps with in a while loop until no empty frame is found.
Uwe
Message was edited by: Laubender
Copy link to clipboard
Copied
And we should think about: is it desirable to remove empty inline text frames at all?
Text composition will be changed, if we do so.
Uwe
//EDIT: the previous version did not show inline text frames but inline rectangles
Message was edited by: Laubender
Copy link to clipboard
Copied
Here a test of anchoredness on an object by Hans-Gerd Claßen at:
http://forums.adobe.com/message/4745348#4745348
I just added a try/catch for objects that are not anchored at all:
//NotAnchored_ANCHORED_Inline_Above_Custom_TEST-ON-SELECTION.jsx
//Hans-Gerd Claßen
//http://forums.adobe.com/message/4745348#4745348
//TEST ON A SELECTION OF A SINGLE OBJECT:
var myObject = app.selection[0];
//Uwe Laubender
//Try/catch added for examples that are not anchored at all:
try{
myObject.anchoredObjectSettings.anchoredPosition;
}catch(e){
alert("Not anchored!");
exit();
};
var myAnchorPosition = myObject.anchoredObjectSettings.anchoredPosition;
switch(myAnchorPosition)
{
case AnchorPosition.INLINE_POSITION:
alert("Inline");
// get inline settings
break;
case AnchorPosition.ABOVE_LINE:
alert("Above");
// get above line settings
break;
case AnchorPosition.ANCHORED:
alert("Custom");
// get custom settings
break;
default : break;
}
To continue what I said in the post above: we could well leave empty text frames untouched that are anchored with:
"INLINE_POSITION" or "ABOVE_LINE"
Uwe
Copy link to clipboard
Copied
Thank you for all your efforts Uwe. I am beginning to think that I have opened Pandora's Box (or Frame) here.
The concept started out simply enough, any empty text box should be deleted.
Then it became any empty text box with no fill or stroke should be deleted.
Then it became any empty text box with no fill or stroke or text wrap or type on a path or empty data merge legacy should be be deleted.
Then it became any empty text box with no fill or stroke or text wrap or type on a path or empty data merge legacy or that may/may not be an anchored object or within an anchored object or inline graphic should be be deleted.
THEN the script... blah blah blah... and also looked for not just text frames, but graphic and unassigned frames of any shape...
And now of course the use I had not considered - multi-state objects or buttons... *sigh*
The list of scenarios where an empty text frame may have an alternate use is growing larger and larger. There are also scenarios where the removal of an empty text frame are subjective. An example is post 30 of this thread where empty boxes were used to indent text instead of adjusting the first line indent in the paragraph palette. In that instance I would agree that the script should leave those boxes alone, however if the script was used to tidy up a catalogue created using data merge, then I would argue that the script should remove those boxes. If a situation arose where both phenomena existed in the same file, then I put my head in my hands and cry.
To that degree I'm satisfied with the script that is cobbled together at the moment: https://dl.dropboxusercontent.com/u/55743036/emptyframeremover.jsx for dealing with print purpose files, given that it successfully removes the text boxes in this test file that should be deleted with the exception of threaded text that I would like left alone: https://dl.dropboxusercontent.com/u/55743036/empty%20textbox%20test.idml . I would like to try this script on the snippets in your last posts, are you able to PM me with links to those files?
Copy link to clipboard
Copied
@Colly – Indeed: Pandora's Box 😉
Here a link to my InDesign/IDML file:
https://www.dropbox.com/s/ar7w9sja4bp9433/E2-TextFramesOfAnySort_AlsoNested.zip
E2-TextFramesOfAnySort_AlsoNested.zip
E2-TextFramesOfAnySort_AlsoNested.indd
E2-TextFramesOfAnySort_AlsoNested.idml
Of course, I'm not sure, if all *critical* cases are covered 😉
Uwe
Copy link to clipboard
Copied
Thank you for your test file Uwe. I have run my cobbled together script on the file and am satisfied with the results. Buttons and MSOs were left alone; grouped objects were not touched; empty inlines within other empty inlines were deleted, and threaded text was ONLY deleted if (within the entire threaded story) there is no text... otherwise the threaded frames remain... but I can live with that. Whether it fulfils the requirements of anyone else is subjective, but for my purposes it does what it needs to do... until further texting reveals more bugs
Colin
Copy link to clipboard
Copied
@ cdflash
@ Laubender
very impressive!
thank you both for your valuable input and... sharing.
the thread is pretty old and was marked as answered years ago, so I just marked as helpful some of "key points"...
thanks again,
respectfully -
winterm
-----------------
just one small addition: for my needs it’s also safe to delete text frames, containing non-printing characters only. Maybe not a good idea always and for everyone, but I consider them as 'empty', so added at the beginning of Your script this:
app.findGrepPreferences.findWhat = „\\A\\s+\\Z”;
app.changeGrepPreferences.changeTo = „”;
app.activeDocument.changeGrep();
app.findGrepPreferences = app.changeGrepPreferences = null;
Message was edited by: winterm
Copy link to clipboard
Copied
@Winterm – just a small thing.
I found it valuable to write my GREPs this way:
app.findGrepPreferences.findWhat = /\A\s+\Z/.source;
Don't know, if I saw it the first time with a script by John Hawkinson or Peter Kahrel…
Very handy, you need not to escape the \ signs.
Also generally good for strings like that:
alert( /He said: "What should I say? It works…"/.source );
Uwe
Copy link to clipboard
Copied
Despite a year of having the script and looking like it was working flawlessly, I have discovered two problems with it:
1) If there are no text boxes in the document at all, the script throws up this error when run
That particular piece of code has to deal with the handling of inline graphics. I think I know the cause, and that is there is no else statement in the function to say "if there is no text frames at all, then do nothing". The code currently looks like this:
var inlines = myDocument.stories.everyItem().pageItems.everyItem().getElements();
while(fr=inlines.pop()){
if(fr instanceof Group){continue}
if(fr instanceof TextFrame){
if(fr.contents == "" && fr.strokeWeight == "0" && fr.fillColor.name == "None" && fr.textWrapPreferences.textWrapMode === TextWrapModes.NONE && fr.textPaths.length == 0){fr.remove()}
continue;
}
if(fr.graphics.length==0 && fr.strokeWeight == "0" && fr.fillColor.name == "None" && fr.textWrapPreferences.textWrapMode === TextWrapModes.NONE && fr.textPaths.length == 0){fr.remove()}
}
How would I add that else statement to this function?
2) The script will remove frames that have frames within them. Let me demonstrate. In this screen capture, there are typically three kinds of frames in InDesign - unassigned, graphic and text.
However, when I paste a triangle drawn in indesign into a rectangle using "paste into", or a picture inside a frame into a frame (again using paste into), it doesn't seem to fall into any of these categories.
So this is fouling up a part of my script that searches for all rectangles, ovals and polygons looking for no stroke/fill/text on paths/text wrap. The function is written three times, once for rectangles, once for ovals, once for polygons. Here is the sample for rectangles:
var myGraphicFrames = app.activeDocument.rectangles;
for (i=myGraphicFrames.length-1; i>=0; i--) {
var stroke = myGraphicFrames.strokeWeight;
var color = myGraphicFrames.fillColor.name;
var tpath = myGraphicFrames.textPaths.length;
var wrap = myGraphicFrames.textWrapPreferences.textWrapMode;
if (myGraphicFrames.graphics.length < 1 && stroke == "0" && color == "None" && wrap === TextWrapModes.NONE && tpath == 0)
myGraphicFrames.remove();
}
What am I missing that would be able to identify these frames as having content and leave them alone?
Colin
Copy link to clipboard
Copied
There is part 2 of this thread...
Cleaning up (not only) text frames, Pt. 2
I believe, some problems solved there...
Copy link to clipboard
Copied
Thank you winterm, looks like you encountered exactly the same problem that I faced today. Great that there is a fix though.
With the more minor issue of the error being thrown when there is no text boxes in the document, is there any way that this issue can be resolved? It's unlikely that the script would be run without text being in a document somewhere, but if it can be fixed, then i'd like to fix it.
Colin
Copy link to clipboard
Copied
cdflash wrote:
With the more minor issue of the error being thrown when there is no text boxes in the document, is there any way that this issue can be resolved? It's unlikely that the script would be run without text being in a document somewhere, but if it can be fixed, then i'd like to fix it.
Yes, I mentioned this in my very first post in that 'second' thread. It's more theoretical problem. As you said it's unlikely you ever run this script in a doc with no text frames, however, I've solved it for myself by wrapping lines 16-24 in try/catch.
Copy link to clipboard
Copied
No worries. Will admit that I had not written a try/catch wrapper before, but was pretty easy. I've adjusted that part of the script to now look like this:
try {
var inlines = myDocument.stories.everyItem().pageItems.everyItem().getElements();
while(fr=inlines.pop()){
if(fr instanceof Group){continue}
if(fr instanceof TextFrame){
if(fr.contents == "" && fr.strokeWeight == "0" && fr.fillColor.name == "None" && fr.textWrapPreferences.textWrapMode === TextWrapModes.NONE && fr.textPaths.length == 0){fr.remove()}
continue;
}
if(fr.graphics.length==0 && fr.strokeWeight == "0" && fr.fillColor.name == "None" && fr.textWrapPreferences.textWrapMode === TextWrapModes.NONE && fr.textPaths.length == 0){fr.remove()}
}
}
catch(err) {
alert("There are no text boxes in this document");
}
Just out of curiosity, has anyone else experienced issues or errors with the script?
Colin