Je ne sais pas vous, mais moi je passe mon temps à jongler avec des blocs-textes, des polygones, des images, tout au long de la conception d’une mise en page sous InDesign. Il n’est pas rare d’esquisser un élément sur la table de montage puis de vouloir le tester à la place d’un autre, déjà inscrit dans la maquette. Mais comment mettre un objet A à la place d’un objet B ? Question con mais pas si commode.
Pour simplifier, disons que vous avez deux rectangles A et B de mêmes dimensions, A sur fond anis, B sur fond bleu, tels que dans la figure 1 :

Malgré la trivialité apparente de la tâche, la permutation manuelle de A et de B requiert un nombre effarant d’opérations élémentaires. Dans le cas le plus favorable, vous lancerez le script AutoRepères afin de baliser les deux objets, facilitant ainsi les deux déplacements réciproques au moyen de la souris. Mais, en général, vous ne disposez pas de repères adéquats, parce que les choses sont encore instables, vous peaufinez, vous tâtonnez, vous ajustez. Ce faisant, vous exigez quand même une certaine précision durant vos tentatives, alors il va falloir jouer subtilement de la palette Alignement, et même, ça ne suffira pas...
La seule démarche universelle que je puisse imaginer — détrompez-moi s’il y a mieux ! — consiste à prendre les abscisse et ordonnée de A et de B sur un calepin, puis à les réaffecter réciproquement. Les plus inventifs feront des copier-coller de ces informations à partir de la palette de contrôle vers un bloc-texte temporaire, avant de les recopier-recoller dans les zones X et Y selon le nouveau casting. À un instant critique de ce long protocole d’inversion, A et B risquent de se retrouver au même endroit, ce qui va compliquer momentanément leur visibilité et leur sélection. Venant à bout de ce petit exercice grâce à des Ctrl Clic bien placés, vous serez enfin parvenus à échanger A et B.
Juste pour voir, prenez un chronomètre et mesurez le temps dilapidé par la réalisation de cette pathétique prouesse :

Quand on y réfléchit, tout cela est idiot. Sélectionnons deux objets quelconques. Leurs coordonnées respectives sont accessibles sans difficulté dans l’architecture JavaScript d’InDesign. On va simplement devoir collecter la propriété PageItem::geometricBounds de chacun (cf. détails utiles dans le script « Equalizer »). Ensuite, on réattribuera les positions, enfin, plus exactement, on déplacera les objets par la méthode PageItem::move() selon deux vecteurs opposés. Et voilà, le script le plus simple de l’univers est terminé !
En fait non. Car la question-piège vient d’arriver par pneumatique : que se passe-t-il quand A et B sont des objets complètement différents en forme et en dimensions ? Dit autrement : à quoi l’utilisateur s’attend-il dans pareil cas ?
De prime intuition, il pouvait s’attendre à ce que A et B fussent permutés du point de vue de leur centre géométrique. Mais l’usage quasi quotidien de ce script m’a révélé un besoin plus ciblé : le point de référence vis-à-vis duquel s’opère la permutation doit rester sous le contrôle de l’utilisateur.
Illustrons cela par un exemple :

Dans la figure 3, on souhaite échanger le disque A et le rectangle B selon leurs centres géométriques. Dans la palette de contrôle, le point de référence a donc été calé au milieu de la petite matrice. Notre script sera capable d’en déduire l’attente de l’utilisateur et d’œuvrer selon le vecteur de permutation reliant les centres des deux cadres-objets.
Voyons maintenant une toute autre situation :

Dans la figure 4, le point de référence a été placé en haut à gauche, ce qui indique une attente différente : cette fois, le vecteur de permutation est défini en fonction des sommets supérieurs gauches des deux éléments. Vous constatez que le résultat n’est aucunement équivalent à celui d’une permutation « centrale » (1).
(1) Le seul cas où la position du point de référence est sans incidence sur la permutation est celui où les deux objets possèdent les mêmes dimensions au sens d’InDesign (i.e. : s’inscrivent dans des cadres-conteneurs de mêmes dimensions), car les vecteurs de déplacement sont alors identiques quel que soit le point d’ancrage utilisé.Sur ces entrefaites, le scénario de notre script se dicte de lui-même :
1) Après avoir éventuellement ajusté le point de référence à ses désirs, l’utilisateur sélectionne deux objets, puis lance le script ;
2) On vérifie que la sélection (app.activeWindow.selection) contient effectivement deux objets ;
3) On interroge la propriété LayoutWindow::transformReferencePoint qui énumère les 9 positions possibles du point d’ancrage et on règle notre méthode de calcul en conséquence ;
4) On récupère les coordonnées utiles des deux objets sélectionnés (utiles au regard du point de référence) et on calcule les deux vecteurs (symétriques) spécifiant la permutation ;
5) On procède aux deux déplacements réciproques via PageItem::move(undefined,vecteur) (le premier argument n’est pas utilisé car le déplacement, tel que notre script le calcule, est relatif).
L’essentiel du code (étapes 3-4-5) tient dans la méthode suivante :
LayoutWindow.prototype.permuteSelected = function()
//----------------------------------------------------------
// Permute les 2 objets actuellemt selectionnes
// (l'appelant est responsable de la conformite de la selection)
{
// recupere le point de reference de la permutation
var refPoint = this.transformReferencePoint;
var dRef = new Array(2);
switch(refPoint)
{
case AnchorPoint.topLeftAnchor :
dRef = [0, 0];
break;
case AnchorPoint.topCenterAnchor :
dRef = [1, 0];
break;
case AnchorPoint.topRightAnchor :
dRef = [2, 0];
break;
case AnchorPoint.leftCenterAnchor :
dRef = [0, 1];
break;
case AnchorPoint.centerAnchor :
dRef = [1, 1];
break;
case AnchorPoint.rightCenterAnchor :
dRef = [2, 1];
break;
case AnchorPoint.bottomLeftAnchor :
dRef = [0, 2];
break;
case AnchorPoint.bottomCenterAnchor :
dRef = [1, 2];
break;
case AnchorPoint.bottomRightAnchor :
dRef = [2, 2];
break;
}
// recupere les 2 objets selectionnes
// et leurs coordonnees "utiles"
var obj1 = this.selection[0];
var pos1 = obj1.geometricBounds.extractCoords(dRef);
var obj2 = this.selection[1];
var pos2 = obj2.geometricBounds.extractCoords(dRef);
// calcule le vecteur de translation 1 -> 2
var vect_1_2 = [ pos2[0]-pos1[0], pos2[1]-pos1[1] ];
try
{
obj1.move(undefined, vect_1_2);
obj2.move(undefined, vect_1_2.sym());
}
catch(ex)
{
exitMessage(ex);
}
}
Comme vous l’apercevez, j’ai mis en place quelques méthodes d’appoint — Array::extractCoords() et Array::sym() — histoire de rationaliser certains aspects du calcul. Le tableau baptisé dRef est un petit bricolage destiné à encrypter la position du point d’ancrage de façon plus arithmétique. Cette variable est transmise à Array::extractCoords(), dont la fonction est de transformer le fruste quadruplet geometricBounds (Top, Left, Bottom, Right) en un couple (X,Y) — c’est-à-dire en un tableau [ 0 : X , 1 : Y ] — contenant les coordonnées utiles des objets. Lorsque le point d’ancrage est en position médiane, lesdites coordonnées sont éventuellement calculées en tant que milieux : (Left + Right) / 2 et/ou (Top + Bottom) / 2 selon les cas.
L’ésotérie de la méthode extracCoords s’explique par mon obsession d’éviter la réécriture de code et le surnombre des if-else :
Array.prototype.extractCoords = function(coordsRef)
//----------------------------------------------------------
// Renvoie le tableau [0=>X, 1=>Y] a partir de this[0=>T, 1=>L, 2=>B, 3=>R]
// en appliquant le code de coordsRef[0] pour X et coordsRef[1] pour Y
// codes : 0 -> L / T; 1 -> centre / milieu; 2 -> R / B
{
var coords = new Array(2);
var dB = 0;
for (var i=0; i<= 1; i++)
{
// Top = 0; Left = 1; Bot = 2; Right = 3;
dB = 1 - i;
if ( coordsRef[i]==1 )
coords[i] = ( this[dB] + this[dB+2] ) / 2;
else
coords[i] = this[coordsRef[i] + dB];
}
return(coords);
}
Quant à Array::sym(), c’est un simple symétriseur de tableau : il renvoie un tableau isomorphe comportant l’opposé des valeurs rangées dans this. Cette méthode sert uniquement à externaliser le calcul du vecteur réciproque de la permutation. Beaucoup de mots compliqués pour des concepts en vérité des plus palpables.
J’avais programmé il y a quelques mois une version ultra-rudimentaire de « SwapItems » et j’ai pu mesurer combien cet outil rend la vie facile. Je vous recommande chaudement de lui associer un raccourci-clavier (personnellement, j’ai choisi Ctrl Alt X, X pour eXchange). Et je ne compte plus le nombre de Ctrl Alt X que j’opère tout au long d’une composition ! Une propriété remarquable de ce script est son idempotence (d’ordre 2), c’est-à-dire qu’en l’appliquant deux fois de suite vous retrouvez la configuration initiale, ce qui revient à annuler la permutation.
Bien que le principe de « SwapItems » impose la sélection préalable de deux objects exactement, il est aisé de permuter deux ensembles d’objets, du moment que ceux-ci sont groupés. N’oubliez pas cependant que ce sont les deux groupes, en tant qu’objets consolidés dans leur propre cadre, qui seront échangés, et non pas leurs éléments deux à deux. Il serait envisageable d’étendre les fonctionnalités de « SwapItems » dans le sens d’une permutation deux à deux, mais encore faudrait-il s’assurer que les deux groupes considérés possèdent le même nombre de composants (2).
(2) Si cette extension vous tente, des techniques de groupage et de dégroupage sont examinées dans le script CentrerSelection.Dans le même ordre d’idées, il est tentant d’implémenter une version généralisée du script, qui opérerait une permutation circulaire de N éléments sélectionnés, avec N ≥ 2. Le projet m’a effleuré, mais je ne lui ai guère vu d’intérêt pratique !
Les instructions d’installation (pour InDesign CS ou CS2) sont données en entête dans le script lui-même, SwapItems.js, inclus dans ce fichier zip téléchargeable.
Ce programme est bien sûr en libre usage, mais n’oubliez pas d’indiquer la source et l’auteur si vous le mentionnez ou le réexploitez. Merci de signaler tout bug éventuel à l’adresse du rédacteur en chef.
Télécharger le script