Welcome Dialog

Welcome to the Community!

We have a brand new look! Take a tour with us and explore the latest updates on Adobe Support Community.


[BUG] copying threaded text-frames.

Adobe Community Professional ,
Mar 22, 2019 Mar 22, 2019

Copy link to clipboard

Copied

I discovered an unfortunate bug when trying to copy a group containing threaded text frames from one document to another using the duplicate() command.

#target illustrator

function test(){

  var doc = app.documents.getByName("Test-1.ai");

  doc.activate();

  var group = doc.groupItems[0]; // contains two text frames which are threaded to each-other

  var otherDoc = app.documents.getByName("Test-2.ai");

  var groupCopied = group.duplicate(otherDoc.layers[0], ElementPlacement.INSIDE);

  otherDoc.activate();

};

test();

Basically you want to copy something with two threaded textframes into another document, they become un-threaded. Really hard to be aware of if not paying attention and expecting the duplicate() function to work exactly like copy/paste command.

I have an idea of how to deal with it so I hope this will remind me to try it out: I'll look at documenting any threads of text into tags and then reconstructing the threads in the destination document. Ideally the tags would be auto applied and removes so as to not ever get copied and pasted along with an art item into other user documents.

TOPICS
Scripting

Views

1.5K

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Mar 23, 2019 Mar 23, 2019

Copy link to clipboard

Copied

same here, thanks for sharing.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Mar 27, 2019 Mar 27, 2019

Copy link to clipboard

Copied

Silly-V  wrote

… Basically you want to copy something with two threaded textframes into another document, they become un-threaded. Really hard to be aware of if not paying attention and expecting the duplicate() function to work exactly like copy/paste command.

Hi,

thank you very much for this!

I can confirm the bug.

Did my tests with a group of threaded text paths.


I also would expect* that the threaded frames of a group stay threaded if the group is duplicated.

*

Duplicating to a different layer in the same document with method duplicate() has no issue with unthreaded text paths or frames.

UI: Dragging a bunch of threaded frames from one doc to another will work as well.

FWIW: InDesign's group.duplicate() would work accross documents:

// InDesign script:

// Duplicate group of active doc to page 1 of document with index 1.

// Group contains threaded text frames.

app.documents[0].groups[0].duplicate( app.documents[1].pages[0] );

Regards,
Uwe

EDIT: All my tests with German Illustrator CC 2019 version 23.0.2 on Windows 10.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Mar 27, 2019 Mar 27, 2019

Copy link to clipboard

Copied

I think it's because it's got to do with stories and stories are something that are a document-level item.

And hence, when you take some objects out of one document to another, the story isn't transported.

Well, I ended up just doing tagging - then sorting through the text frames, etc.

function recordStoriesInTags (artGroup) {

  // artGroup must be named art group

  var allStories = app.activeDocument.stories;

  var thisStory, thisFrame, thisTag, timestamp;

  timestamp = new Date().getTime();

  for (var i = 0; i < allStories.length; i++) {

    thisStory = allStories[i];

    if (thisStory.textFrames.length > 1) {

      for (var j = 0; j < thisStory.textFrames.length; j++) {

        thisFrame = thisStory.textFrames[j];

        if (searchForParentGroup(thisFrame, function (inItem) {

          return inItem.name == artGroup.name;

        }) == null) {

          continue;

        }

        // clear any previous such tags

        try {

          thisTag = thisFrame.tags.getByName("THREADED");

          thisTag.remove();

        } catch (e) {

        

        }

        thisTag = thisFrame.tags.add();

        thisTag.name = "THREADED";

        thisTag.value = timestamp + "|" + (i + 1) + "-" + (j + 1);

      }

    }

  }

};


function reThreadTextFramesInGroup (newArtGroup) {

  var textBoxesInGroup, threadedBox;

  textBoxesInGroup = artNodeSearch(newArtGroup, function (item) {

    try {

      item.tags.getByName("THREADED");

      return item.typename == "TextFrame";

    } catch (e) {

      return false;

    }

  });


  var threadedBoxGroupNames = [], thisTagValue, groupName, order;

  for (var j = 0; j < textBoxesInGroup.length; j++) {

    threadedBox = textBoxesInGroup[j];

    thisTagValue = threadedBox.tags.getByName("THREADED").value;

    groupName = thisTagValue.split("-")[0];

    order = thisTagValue.split("-")[1];

    if (threadedBoxGroupNames.indexOf(groupName) == -1) {

      threadedBoxGroupNames.push(groupName);

    }

  }


  var threadedGroupObj = {};

  for (var j = 0; j < threadedBoxGroupNames.length; j++) {

    for (var k = 0; k < textBoxesInGroup.length; k++) {

      threadedBox = textBoxesInGroup[k];

      thisTagValue = threadedBox.tags.getByName("THREADED").value;

      groupName = thisTagValue.split("-")[0];

      order = thisTagValue.split("-")[1];


      if (groupName == threadedBoxGroupNames[j]) {

        if (!(groupName in threadedGroupObj)) {

          threadedGroupObj[groupName] = [];

        }

        threadedGroupObj[groupName].push({

          element : threadedBox,

          groupName : groupName,

          order : order

        });

      }

    }

  }

  var sortedTextFrames, nextBox;

  for (var all in threadedGroupObj) {

    sortedTextFrames = threadedGroupObj[all].sort(function (a, b) {

      return a.order > b.order;

    });

    // threadedBox now an object of info

    for (var j = 0; j < sortedTextFrames.length; j++) {

      threadedBox = sortedTextFrames[j];

      if (j < sortedTextFrames.length - 1) {

        nextBox = sortedTextFrames[j + 1];

        threadedBox.element.nextFrame = nextBox.element;

      }

      try {

        threadedBox.element.tags.getByName("THREADED").remove();

      } catch (e) {


      }

    }

  }

};

function searchForParentGroup (node, nodeFunc) {

  if (node.typename == "Layer") {

    throw ("The Parent-node search cannot run on a Layer.");

  }

  var foundItem = null, thisParent = node.parent;

  while (thisParent.typename == "GroupItem" && foundItem == null) {

    if (nodeFunc(thisParent)) {

      foundItem = thisParent;

      return foundItem;

    } else {

      thisParent = thisParent.parent;

    }

  }

  return null;

};

function artNodeSearch (parent, nodeFunc) {

  if (parent.typename == "Layer") {

    throw ("Only Group containers are allowed when performing a node search on an art nodes.");

  }

  var arr = [];

  function findFunc (node) {

    if (nodeFunc(node)) {

      arr.push(node);

    }

    var thisPageItem;

    if (node.typename == "GroupItem") {

      for (var i = 0; i < node.pageItems.length; i++) {

        thisPageItem = node.pageItems[i];

        findFunc(thisPageItem);

      }

    }

  };

  findFunc(parent);

  return arr;

};

What I found was that the record stories function, the area 'return inItem.name == artGroup.name;' used to be just 'return inItem == artGroup;'  - which worked in one document but would cause a CRASH when I used it in a document where there were already items in it... where I pasted things to.. anyways - hope it helps someone and please report any bugs with the above if you use it.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Mar 27, 2019 Mar 27, 2019

Copy link to clipboard

Copied

Hi Silly-V ,

oh boy!

Wouldn't a set of menu commands do?

Like select, copy, paste.

Something like the code below is working in my test and threading of text paths or text frames is maintained in the target document:

function test(){

    var doc = app.documents.getByName("Test-1.ai");

    doc.activate();

    var group = doc.groupItems[0]; // contains two text frames which are threaded to each-other

    group.selected = true ;

    app.executeMenuCommand( "copy" );

    var otherDoc = app.documents.getByName("Test-2.ai");

    otherDoc.activate();

    app.executeMenuCommand( "pasteInPlace" );

};

test();

Regards,
Uwe

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Mar 27, 2019 Mar 27, 2019

Copy link to clipboard

Copied

holly smokes, that's overkill vasily.

I'll go with Uwe's copy/paste even though I avoid it like the plague.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Mar 28, 2019 Mar 28, 2019

Copy link to clipboard

Copied

Aha, if only it were all so easy, my dearest colleagues.

The story behind all this is, in another project I have learned through a painful experience one fateful afternoon that copying and pasting when done in a batch context caused Illustrator to crash. This was after successfully assembling separate pieces of a main function by using copy/paste in various individual contexts. When it came time to string the whole glorious routine together... boom crash. Now you can also imagine me scrambling to re-work the entire thing to use .duplicate() which seemed like the working alternative - and as a result of this experience I've conditioned myself to only use duplicate() in all situations where this is being done as a series.

And that is my story!

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Mar 29, 2019 Mar 29, 2019

Copy link to clipboard

Copied

of course, I should have guessed there was a story

can't you check if you have threaded items before duplicating? then if so, thread them back right after duplicating?

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Mar 29, 2019 Mar 29, 2019

Copy link to clipboard

Copied

Yea that's what the 'overkill' code does

Well, to elaborate: (and you just had to go there ) - there's a 'issue' with threaded frames in Illustrator similar to how there's an 'issue' with the polarity of a path acting a little 'weird'... ( from that other topic on here ).

In this case, when you think you can just walk through all the text frames in a group and find all threaded ones by using the "previousFrame" and "nextFrame" - and you think it's going to work like normal - that's when it doesn't.

The failure in this case is there's an 'infinite link' between the threaded textframes as far as scripting is concerned, so whereas a regular person may assume the very first text frame has a previousFrame of null, no it actually points right back to the last text frame.

[EDIT] - Per Uwe the previousFrame indeed does return null! It's the nextFrame which is problematic.

As these text frames can be in different stacking order - and in fact, in different sub-groups of the group, it's not acceptable to assume a rigid order within these kinds of usages I'm looking at.. so the bottom line is we have to go by stories.

Stories don't lie. They have only a limited amount of textFrames, and there's one with an index of beginning and one of the end.

So while stories allow you to get a survey of all threaded text frames (stories whose textFrames are more than just the one) and isolate some into a trackable piece of data in memory, the duplication of art from one document to another means having to find those textFrames all over again.

To me it was an alternative between some code creating and returning some kind of node-tree as a set of instructions for re-threading, and what seems to be the more generic option of using tags. This way the items can be in any different place, and the re-threading function can still weave the separate items together based on sorting the text frames via their tag content.

Of course it's stuff like this that may help some super awesome project be saved from death for somebody out there, and I wish them well

But as for me, as much as I love Illustrator and scripting trivia, believe it or not - I never actually anticipated that this would be the case.. because I simply expected the stuff to work a 'normal' way. But here we are, as you can see... I didn't choose the thug life; it chose me!

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Mar 30, 2019 Mar 30, 2019

Copy link to clipboard

Copied

Silly-V  wrote

…The failure in this case is there's an 'infinite link' between the threaded textframes as far as scripting is concerned, so whereas a regular person may assume the very first text frame has a previousFrame of null, no it actually points right back to the last text frame. …

That's really cruel.

I'd consider this as bug!

Regards,
Uwe

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Mar 30, 2019 Mar 30, 2019

Copy link to clipboard

Copied

Laubender  wrote

… I'd consider this as bug!

Hm. I was wrong, there is no bug.

If I test the first text frame in a story with:

app.selection[0].previousFrame

or:

app.activeDocument.stories[0].textFrames[0].previousFrame

it returns indeed null.

PreviousFrame-returns-null.PNG

Tested with my German Illustrator CC 2019 version 23.0.2 on Windows 10.

Regards,
Uwe

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Mar 30, 2019 Mar 30, 2019

Copy link to clipboard

Copied

You are correct! Indeed this is so, but it is in fact the nextFrame which has the problem as it always points to something.

#target illustrator

function test(){

var doc = app.activeDocument;

var t = doc.textFrames[1];

alert(t.nextFrame.nextFrame.nextFrame);

};

test();

In this example it alerted a text frame although the document only has two text frames in it.

CarlosCanto​ - addendum:

However, whether we had to use stories or not, the other issue is when copying items over from one document to the next, any previous references have to be re-referenced as their new copies in the destination document. So unless the copied structure is some sort of standard that can be accommodated via code, a more generic method has to be used. In the case of having a template which has the same art structure reliably, this code would be trivial - but in my case the users can make their own templates and those text frames could be anywhere.

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Mar 30, 2019 Mar 30, 2019

Copy link to clipboard

Copied

Hi Silly-V ,

interesting!

So something like the code below should work if we do not care about stacking order:

var sourceDoc = app.documents.getByName("Source.ai");

var targetDoc = app.documents.getByName("Target.ai");

var sourceStory = sourceDoc.stories[0];

var sourceFrames = sourceStory.textFrames;

var currentFrame = sourceFrames[0].duplicate( targetDoc , ElementPlacement.INSIDE );

for( var n=1; n<sourceFrames.length; n++ )

{

    var dup = sourceFrames.duplicate( targetDoc , ElementPlacement.INSIDE );

    currentFrame.nextFrame = dup;

  

    // Redifining currentFrame:

    currentFrame = dup;

};

Big Question: Where did you look up the enumerator ElementPlacement.INSIDE ?

Searched the DOM documentation and couldn't find it. In the OMV the link from ElemenentPlacement to a specific discription did not work.

Fun fact: In my tests I was able to do two threaded text frames where the last frame in the story was threaded to the first one so this all became circular.

This happened accidently. Now I can recreate it.

Two threaded frames of a story in the document.

Nothing unusual for now:

CircularThreaddingTextFrames-1.PNG

Run this snippet code:

var story = app.activeDocument.stories[0];

story.textFrames[1].nextFrame = story.textFrames[0];

Result:

CircularThreaddingTextFrames-2.PNG

EDIT: Or even more absurd would be:

var story = app.activeDocument.stories[0];

story.textFrames[ story.textFrames.length-1 ].nextFrame = story.textFrames[ story.textFrames.length-1 ].nextFrame;

But that yields the same result.

Regards,
Uwe

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Mar 30, 2019 Mar 30, 2019

Copy link to clipboard

Copied

Or see into this case.

Two threaded text frames.

Yellow text on the left > Cyan text on the right

With both texts I applied effect Multiply.

CircularThreaddingTextFrames-3.PNG

Running:

var story = app.activeDocument.stories[0];

story.textFrames[ story.textFrames.length-1 ].nextFrame =

story.textFrames[ story.textFrames.length-1 ].nextFrame;

Result:

CircularThreaddingTextFrames-4.PNG

Overlapping text rendering in the left frame.

But this special kind of text rendering will not travel well to an exported PDF/X4:

CircularThreaddingTextFrames-5-PDF-Exported.PNG

Regards,
Uwe

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Mar 30, 2019 Mar 30, 2019

Copy link to clipboard

Copied

It's even possible to thread a text frame with itself:

CircularThreaddingTextFrames-6.PNG

FWIW: Illustrator could crash on something like that.

Regards,
Uwe

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Mar 30, 2019 Mar 30, 2019

Copy link to clipboard

Copied

Nice testings! This is exactly like astronomers who discover a new asteroid or biologist who uncovers a new species of amoeba.. or doctors who discover a new disease - more like

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Mar 30, 2019 Mar 30, 2019

Copy link to clipboard

Copied

Laubender  wrote


Big Question: Where did you look up the enumerator ElementPlacement.INSIDE ?

Can answer this myself now.

It is documented in the Illustrator JavaScript Scripting Reference PDF.

The OMV does not list it.

ElementPlacement

INSIDE

PLACEATBEGINNING

PLACEATEND

PLACEBEFORE

PLACEAFTER

Regards,
Uwe

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Adobe Community Professional ,
Apr 15, 2019 Apr 15, 2019

Copy link to clipboard

Copied

LATEST

Finally I had the time reporting the issue with nextFrame is not null for the last frame of a story:

Last frame in a story: textFrameItem.nextFrame is not null – Adobe Illustrator Feedback

Add your vote for fixing the issue if you are affected by this absurdity.

I also reported the issue with method group.duplicate() where the group contains threaded text frame items and the once threaded items are not threaded anymore in the target document:

Using method duplicate() on a group of threaded text frame items will lose threading – Adobe Illustr...

Also vote for fixing this bug, please.

Regards,
Uwe

Likes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines