Tuesday, January 09, 2007

 

Back to the Pasteboard

OK, I've confirmed my suspicion that the current version of the script hits a run-time error if a locked layer holds a pasteboard item. I'm inclined to take the easy way out here and simply exclude any such layers from this game. If the user wants to add it back in, all he need do is unlock the layer and run the script again.

So, now let's get down to the hard part: how to prevent items from colliding with pages?

I think I'm going to assume that only left/right collisions are possible. I don't know enough about the dtptools plug-in that allows different sized pages, but since I don't own it and I'm writing this script as much for me as anyone else (so few people having taken any interest), I might as well not worry about things that aren't going to affect me.

So, for any item, I have three rectangles to worry about:
  1. The geometric bounds of the item
  2. The bounds of the source spread
  3. The bounds of the target spread
The only items I have to worry about are those that are wholly to the left or right of the source spread. I want them to appear the same distance to the left or right of the target spread. Notice that any item on the pasteboard above or below the pages that is not wholly to the left or right will not slide when moved to the target. Being where they are, they can't possibly collide with a page.

Of course, getting the bounds of a spread is non-trivial unless you're using Spread as the ruler origin for your document, so I'm going to have to switch to spread at the start and switch back afterwards. Hmm, that means we'd better reset the zero point too and restore it. That means that for an item to be wholly left, it's right-bounds value must be negative because the spread left bound will always be zero; still, it's probably safer to compare just in case.

Oh wait a cotton-picking minute! Because of the way that the spread-based coordinates work, the left side looks after itself. The coordinates are such that an item to the left of the spread will always be the same distance to the left. It's only the right side we have to worry about. Which means that all I have to worry about is the difference in width between the two spreads. So, perhaps I shouldn't even worry about whether or not an item is wholly right, just if it is right of the zero-X I should move it by the difference in widths.

Well, what do you know? It seems to work!
//DESCRIPTION: Moves all Pasteboard Items to Pasteboard of Current Spread

if (app.documents.length == 0) { exit() }
movePBitems(app.documents[0]);

function movePBitems(myDoc) {
  if (app.activeWindow.constructor.name == "StoryWindow") { return }
  var uO = myDoc.viewPreferences.rulerOrigin;
  var uZP = myDoc.zeroPoint;
  myDoc.viewPreferences.rulerOrigin = RulerOrigin.spreadOrigin;
  myDoc.zeroPoint = [0,0];
  myObjs = myDoc.pageItems.everyItem().parent;
  var moveables = new Array();
  for (var j = myObjs.length - 1; j >= 0; j--) {
    if (myObjs[j].constructor.name == "Page") { continue }
    moveables.push(myDoc.pageItems[j].id);
  }
  var mySpread = app.activeWindow.activeSpread;
  var targSpreadWidth = getSpreadWidth(mySpread);
  for (var j = moveables.length - 1; j >= 0; j--) {
    var myObj = myDoc.pageItems.itemByID(moveables[j]);
    var sourceSpread = myObj.parent;
    if (sourceSpread == mySpread) { continue }
    var sourceWidth = getSpreadWidth(sourceSpread);
    var Shift = 0;
    if (myObj.geometricBounds[3] > 0) {
      Shift = targSpreadWidth - sourceWidth;
    }
    var myBounds = myObj.geometricBounds;
    if (myObj.locked) { continue }
    try {
      myObj.move(mySpread);
    } catch(e) { continue } // myObj must be on locked layer
    myObj.move([myBounds[1] + Shift, myBounds[0]]);
  }
  myDoc.viewPreferences.rulerOrigin = uO;
  myDoc.zeroPoint = uZP;
  
  function getSpreadWidth(spread) {
    var my1stBounds = spread.pages[0].bounds;
    var myLastBounds = spread.pages[-1].bounds;
    return myLastBounds[3] - my1stBounds[1];
  }
} // end function movePBitems

Comments:
Wonderful!
I have tested the script with locked layers and multipage-spreads. It works very well.

Your fiddling with the position of the moved page item is fine. Good idea with the spread as ruler origin.
I have been wondering how you treat a right-hand page one as target without another page left of it. The second move-command solves this very well.

There is one thing I am wondering about. You store myDoc.pageItems.everyItem().parent to the var myObjs. Is therefor a certain reason? I expected to store myDoc.pageItems in myObjs and later to look at myObjs[j].parent.constructor.name:
if (myObjs[j].parent.constructor.name == "Page")

And there is another thing I am wondering about: the structure of your code:
Several times you use something like "if (...) {exit}" without "else {...}"
or
"if (...) {return}" without "else {...}"
or
"if (...) {continue}" without "else {...}"

This has made it difficult for me to find the end of the if-command. Is there a certain reason that you avoid the extended syntax with "else{}" or is it just a method to save characters or time?

Thanks
Martin
 
I use everyItem() to get all the parent references in one go. I think it is a bit more efficient. It could be that a document has a huge number of pageitems so efficiency can matter.

As for the if statements, if the "true" part of the logic breaks the flow, the else is not necessary because you only get past the if when the else condition holds. So, the else isn't needed. I hate it when scripts have lots of indents, so I do all I can to keep the indents to a minimum.
 
Hi Dave,

Great work!

I tested it on a Right to Left doc and it moves the items to the right of the first page to the left instead of to the right.

Why is the first page different?

Harbs
 
Harbs,

As usual, I didn't think about right-to-left documents. Does the x-axis go from right-to-left in such a document? Or is it just the text that goes from right to left? Where is the spine in such a document? Is it to the left or the right? How would the script recognize that it is working on a right-to-left document?

See what a sheltered life I've led?

I'm not sure about your question about the first page because I'm not sure I understand what difference you're talking about.

Dave
 
No problem. :) I didn't expect you to think of right to left docs.

The x axis goes from left to right as far as I've been able to tell. I've had some funny things in the past with things moving in unexpected ways. I'm not sure if it was because of bad code on my part, or they really did something funny with the axis.

Determining doc's direction is something I've not been able to figure out. There should be a doc direction attribute (at least in the ME version), but I haven't been able to find it. There are paragraph and character direction attributes.

I checked into it more, and I figured out the problem. In the getSpreadWidth function, the [0] page and the [-1] page need to be reversed. (Because the pages are in order from right to left). Otherwise the function returns a negative number.

I was seing it on the first page, but it's a problem on any page which doesn't match the destination (and has to be moved.)

As I am writing this, I realized a method of determining the doc direction! (In most situations). If the page[0]'s side is right, it's usually a right to left doc. (unless of course the entire spread is one-sided). A better way is probably to determine which page's bounds[1] (or [3]) is greater. If page[-1] is greater, it's a left bound doc. If page[0] is greater, it's right bound.

Wow! This realization will be very useful for me.

Thanks,
Harbs
 
Harbs,

I think I understand. In a right-to-left document, what is returned by:

app.documents[0].pages[0].bounds

And, assuming there's a text frame on the page, what is returned by:

app.documents[0].pages[0].textFrames[0].geometricBounds

Thanks,

Dave
 
Hi Dave,

I've been only half-hazerdly following this topic.

The first one returns:

0,0,666,468

The second one returns:

134.25,99.4285714285714,544.5,366.75

Fred
 
Fred is correct. The result will be the same in both a right to left doc and a left to right doc. The difference is the page order. In a RTL doc, the pages in a spread are ordered from right to left. An additional diff. is the first page. In a RTL doc, the first page is a left hand one. (As the spine is on the right instead of the left.)

in this situation this fixes the problem:

function getSpreadWidth(spread) {
if (spread.pages[0].bounds[1] < spread.pages[-1].bounds[1]){
var my1stBounds = spread.pages[0].bounds;
var myLastBounds = spread.pages[-1].bounds;
}else{
var my1stBounds = spread.pages[-1].bounds;
var myLastBounds = spread.pages[0].bounds;
}
return myLastBounds[3] - my1stBounds[1];
}
 
The only thing with my fix: The name of the variables don't make as much sense. I would change them to:myLftBounds instead of my1stBounds and myRtBounds instead of myLastBounds.
 
I think I found a bug. If you have a one page spread with an object near the edge of the pasteboard (on the wide side), the script moves the object right off the pasteboard!

Harbs
 
Thanks for the feedback, guys. But I have more questions:

Does the ME version support both Left-to-Right and Right-to-Left documents?

Harbs,

Your suggestion only works for a certain kind of document. I need something that will do the job for any document. I have an idea, but the answer to the question above will influence the way I go about it. If all documents are Right-to-Left, then we could install a Script Label on to app.characterStyles[0] that would have one of three values:

"" -- need to go calculate the orientation
"LtR" -- this is a left-to-right installation
"RtL" -- this is a right-to-left installation.

That way, the task of determining the orientation is very easy once you've done it once. If this is an installation-wide setting, then we can quickly sort things out by creating a new, hidden document and setting it up the way we need it to know the answer to the question rather than examining the individual document.

But, if the orientation is document-specific, then we can use the same trick but for each document. We should avoid making this evaluation every time the script runs. But, if it is a document-specific issue and we have a document that is single-sided or which starts with an even-numbered page, it might be a challenge to work out.

We might need to temporarily create extra spreads to do the analysis.

Dave
 
Harbs,

Oh crud! Indeed, you're right. I wonder if there's any way of working out the actual size of the pasteboard to prevent that from happening.

We cross-posted.

Thanks,

Dave
 
I think I worked out a fix for the off-the-pasteboard problem. It changes the functionality slightly. (It would only move objects away from the page if they are on the page, and not the pasteboard):

if ((myObj.parent.constructor.name == "Page")||(Shift<0)) {
myObj.move([myBounds[1] + Shift, myBounds[0]]);
}
}

Wadayathink?

Harbs
 
Hi Dave,

I think my solution (the second one) will work for all docs. Here's why: First of all ME supports both RTL and LTR docs. (It even has a nifty plug-in for reversing layout-to switch back and forth!) RTL binding is a document wide setting. Basically any spread with one page is identical (besides the "side" attribute) whether it is RTL or LTR. The only difference is when there are more than one page on the spread. In that case, in a RTL doc if you have the origin on the spread, the one at 0,0 is the last page on the spread. It goes down until the leftmost one which is the first page on the spread. In short, the only difference is the page order within each spread. If we determine which page in the spread has the lowest x coordinates, we know whether the whole doc is RTL or LTR. (if [0] it's LTR, if[-1] it's RTL.)

I hope this was clear.

I don't think your idea will work even on the doc level, because the direction can be changed even after the fact. I don't think it's a problem calculating on the script level like I did. (all you need is a multi-page spread somewhere in the doc.)

Harbs
 
You are actually changing the functionality in a manner you might not realize. As I have it now, items on the pasteboard to the right, move in and out as the width of the spread changes. Your change will move the item out if needed, but won't bring it back in again for a narrower spread, making the items out there harder to get at.

That was a deliberate part of the strategy.

Did you look into what happens to an item near the extreme left of the pasteboard on a single-page spread when moving to a double-page spread (in a L-t-R document)?

I'm on a couple of urgent projects today so I don't have much time for this right now.

Dave
 
Actually it will move it in. That's why I put in the "||(Shift<0)" part!

Good luck on your projects. :-)

Harbs
 
Here's a general purpose function I worked out for getting the doc direction. The only situation which might be problematic, would be, if there are no multipage spreads when the function is run, AND the script adds multipage spreads afterwards.

function getDocDirection (myDoc){
   myDocDirection = "none";
   for(i=0;i<myDoc.spreads.length;i++){
      if (myDoc.spreads[i].pages.length>1){
          if (myDoc.spreads[i].pages[0].bounds[1] < myDoc.spreads[i].pages[-1].bounds[1]){
             myDocDirection = "left";
             }else{
             myDocDirection = "right";
             }
          }
      if (myDocDirection != "none"){break}
      }
   return myDocDirection;
   }

Harbs
 
Hi Dave.

I see what you mean about the left side. I ignored the fact that the shift was 0 for those objects...

Here's a better solution:

var myNewBounds = myObj.geometricBounds;
if (((myObj.parent.constructor.name=="Page")||(Shift<0))||((myNewBounds[3]<0)&&(myNewBounds[3]<myBounds[3]))) {
myObj.move([myBounds[1] + Shift, myBounds[0]]);
}

Although I found another bug! :-( if you move from a single right hand page, to a single left hand page, the objects on the right don't move. (shift is 0!)

Also... here's a better version of my function for getting the doc direction:

function getDocDirection (myDoc){
   var myDocDirection = "none";
   var myNewPage1 = myDoc.pages.add(LocationOptions.atEnd);
   var myNewPage2 = myDoc.pages.add(LocationOptions.atEnd);
   for(var i=0;i<myDoc.spreads.length;i++){
      if (myDoc.spreads[i].pages.length>1){
          if (myDoc.spreads[i].pages[0].bounds[1] < myDoc.spreads[i].pages[-1].bounds[1]){
             var myDocDirection = "left";
             }else{
             var myDocDirection = "right";
             }
          }
      if (myDocDirection != "none"){break}
      }
   myNewPage1.remove();
   myNewPage2.remove();
   return myDocDirection;
   }//end getDocDirection

The only situation it won't handle, is if the doc is not facing pages, and the script will add multi-page spreads (which is something I can't even figure out how to do!) Either way it's extremely unlikely such a thing will happen.

A side benefit to this function, is it lets you know if the doc doesn't have multipage spreads (and it's not facing pages).

Harbs
 
Martin,

This is the first chance I've had to catch up with what you've been doing. It's a bit hard for me to get into the LtR/RtL issues because I don't have access to RtL documents.

Looking afresh at the situation where an item drops off the edge of the pasteboard, the good news is that it doesn't disappear into the ether. It still exists even though it is off the pasteboard. Ah, but if you select all with an item off the pasteboard, it severely limits how much you can move the selection, particularly if you have items at or near both edges of the pasteboard.

Hmm, the left side of the pasteboard is much more secure than the right side (in a LtR document). If you add pages to a spread, the pages eat into the space until there is less than a page of spare space on the right, but then it adds width to the pasteboard on both sides.

I have to wonder how much effort is actually worth putting into these odd-ball cases, although that's always the problem with scripts made available to the public. Do you try to deal with every contingency or just warn them that if they do this, odd things might happen.

I gather that the version of the script you're now using has moved on quite some way from mine, so I'm not sure how much I can follow some of your later messages. Is the bug you've found because of the extra tests you've been inserting? I rather think so.

Dave
 
Hi Dave,

No the bug has nothing to do with what I added. It's not as bad as the off the pasteboard bug, but it doesn't do what you want in all situations.

To Illustrate my point, make a doc with an even number of pages with facing pages. place an object to the right of page 1, and have the script move it to the last page. It will be all the way out to the right because the shift is 0.

Here is the complete code as I have it now. I added very little:

//DESCRIPTION: Moves all Pasteboard Items to Pasteboard of Current Spread

if (app.documents.length == 0) { exit() }
movePBitems(app.documents[0]);

function movePBitems(myDoc) {
if (app.activeWindow.constructor.name == "StoryWindow") { return }
var uO = myDoc.viewPreferences.rulerOrigin;
var uZP = myDoc.zeroPoint;
myDoc.viewPreferences.rulerOrigin = RulerOrigin.spreadOrigin;
myDoc.zeroPoint = [0,0];
var myObjs = myDoc.pageItems.everyItem().parent;
var moveables = new Array();
for (var j = myObjs.length - 1; j >= 0; j--) {
if (myObjs[j].constructor.name == "Page") { continue }
moveables.push(myDoc.pageItems[j].id);
}
var mySpread = app.activeWindow.activeSpread;
var targSpreadWidth = getSpreadWidth(mySpread);
for (var j = moveables.length - 1; j >= 0; j--) {
var myObj = myDoc.pageItems.itemByID(moveables[j]);
var sourceSpread = myObj.parent;
if (sourceSpread == mySpread) { continue }
var sourceWidth = getSpreadWidth(sourceSpread);
var Shift = 0;
if (myObj.geometricBounds[3] > 0) {
Shift = targSpreadWidth - sourceWidth;
}
var myBounds = myObj.geometricBounds;
if (myObj.locked) { continue }
try {
myObj.move(mySpread);
} catch(e) { continue } // myObj must be on locked layer
var myNewBounds = myObj.geometricBounds;
if (((myObj.parent.constructor.name=="Page")||(Shift<0))||((myNewBounds[3]<0)&&(myNewBounds[3]<myBounds[3]))) {
myObj.move([myBounds[1] + Shift, myBounds[0]]);
}
}
myDoc.viewPreferences.rulerOrigin = uO;
myDoc.zeroPoint = uZP;

function getSpreadWidth(spread) {
if (spread.pages[0].bounds[1] < spread.pages[-1].bounds[1]){
var myLftBounds = spread.pages[0].bounds;
var myRtBounds = spread.pages[-1].bounds;
}else{
var myLftBounds = spread.pages[-1].bounds;
var myRtBounds = spread.pages[0].bounds;
}
return myRtBounds[3] - myLftBounds[1];
}
} // end function movePBitems
 
I just tried your experiment with my original code and with the version you posted. My original works. Your version doesn't.

I am backed up with work at the moment so I can't explore further right now. Maybe later today.

Dave
 
Oh! Woops my mistake!

Here's the blunder:

in my fix, this: ||(Shift<0))||

should be this: ||(Shift<=0))||

I missed that even if the shift is 0, it moves the object.

All's good :-)

Harbs
 
the settings for the first page are different from those of the other page! why???????

Free PS3
 
Post a Comment

<< Home

This page is powered by Blogger. Isn't yours?