Les graphistes les plus chevronnés savent sans doute déjà qu’il est possible de scripter illustrator avec ExtendedScriptToolKit (ESKT) via du javascript, avec comme support un DOM créé de toutes pièces par Adobe. Cependant, depuis la suite CS3 on peut voir quelques panneaux riches, tels que kuler (sur Illustrator), réalisé en flash.
Patchpanel est une techno Adobe permettant de créér des scripts actionscript 3 pour les 3 principales applications de la Creative Suite (3 & 4). Similaire a SwitchBoard (qui, PatchPanel est encore plutot a l’état de beta ; on approche une v0.8. La techno peut se décrire comme un framework as3 (.swc) ayant divers objets similaire au DOM Adobe et permettant de créér ses propres panneaux en flash.
A vrai dire, et assez peu étonnement en y réfléchissant bien, il y a 4 swcs correspondants à PatchPanel : un swc CS3, un swc 4, tous deux déclinés pour Windows et Mac OSX. Je possède un mac, et une version boulot Illustrator CS3 et CS4 sur 2 postes distincts.
L’intéret majeur que je vois là dedans, c’est de pouvoir “personnaliser” jusqu’au bout son outils, Photoshop, Indesign, Illustrator, afin de ne plus jamais avoir a se dire “cette fonctionnalité me manque trop”.
Comme je cotois pas mal de chefs de projets qui devraient travailler sur un meme fichier de sitemap, et qui travaillent a 2 de leur coté et qu’ils “mergent” leur travail au fur et a mesure, je me suis dit que je pourrais leur coder un petit truc, histoire de me faire la main sur patchpanel.
La première idée fut la suivante :
On va faire une appli client/serveur, avec un tout petit serveur red5 et quelques SharedObjects, afin de pouvoir updater constamment les DOM des 2 documents ouverts.
Bon en fait, après quelques jours de recherches, le support javascript est assez limité et – malheureusement à mon goût – il n’est pas possible d’écouter facilement le changement ou la modification d’une sélection.
J’ai donc décidé de réduire le scope de mon panneau à un simple channel de discussion, dans le genre d’irc, où l’on pourrait envoyer des morceaux de vecteurs comme on envoie une pièce jointe par messagerie instantanée.
Pour les utilisateurs, c’est l’occasion d’éviter de pourrir leur boite mail en s’envoyant “module navigation v1.ai” ou “header déplié v2a-b-1.ai”… Ils n’auront plus qu’a faire une sélection, cliquer sur un bouton pour envoyer, et toutes les personnes du channel qui le veulent pourront récupérer cet objet et l’inclure dans leur document d’un seul bouton. Un genre de copier/coller par le réseau !
1/ Récupérer la sélection
C’est sans doute ce qui m’a posé le plus de problème ![]()
La doc fournie avec PatchPanel indique pourtant simplement :
Illustrator.app.selection:Object; Illustrator.app.activeDocument.selection:Object;
Un bête trace sur ces valeurs me renvoit un objet vide constamment… La doc aide pas vraiment, car un type Object est un peu la base de tout et de rien. En cherchant un peu plus loin, je tombe sur la doc javascript d’illustrator et la ressemblance avec le DOM javascript s’est confirmée plus que nécéssaire. C’est – au passage – le document qu’il vous faudra posséder si vous voulez de vrais renseignements sur la structure du DOM Illustrator et donc, de PatchPanel.
La doc JS nous dit donc :
Illustrator.app.selection:Object > Array of Object; Illustrator.app.activeDocument.selection:Array > Array of Object;
C’est déjà beaucoup mieux
Après quelques tests infructueux de describeType, je réduis mon code à l’essentiel :
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import com.adobe.cs3.Illustrator.*;
//import com.adobe.cs4.Illustrator.*;
private function click():void
{
csl.text += "active doc selection : " + Illustrator.app.activeDocument.selection + "\n";
csl.text += "illustrator selection : " + Illustrator.app.selection + "\n";
}
]]>
</mx:Script>
<mx:TextArea width="100%" height="100%" id="csl" />
<mx:HBox width="100%">
<mx:Spacer width="100%" />
<mx:LinkButton label="trace" click="click()" />
</mx:HBox>
</mx:Application>
Toujours rien… J’ai commencé à travailler sur une ou deux méthodes histoire de contourner le bug, en bouclant sur l’ensemble des éléments d’un layer en regardant s’ils sont sélectionnés ou non ; ce genre de script était beaucoup trop contraignant en terme de performance pour être utilisé. Je dédice alors de demander de l’aide, et ça tombe impec’, ya un forum dédié au projet sur la plateforme Adobe.
Je poste mon code, et explique mon problème. Quelques jours plus tard, Bernd Paradies – j’imagine qu’il fait parti du staff de PatchPanel – me réponds en me disant que j’ai découvert un bug, et qu’en testant mon code, il en a découvert un deuxième !
Le premier donc, concernant la sélection. Le workaround est efficace, et n’étant que novice dans l’utilisation des namespaces, je n’aurai jamais été capable de le mettre en place :
import com.adobe.PatchPanel.patchpanel_internal;
private function getSelection( obj : IllustratorHostObject ) : Array
{
// enables the access to PatchPanel's hidden HostObject functions like $get()
use namespace patchpanel_internal;
// The following line tells PatchPanel's glue code to return an Array (not an Object).
return obj.$get( "selection", Array );
}
J’ai un peu retravaillé la fonction, notamment en la réécrivant en tant que getter d’une classe d’utilitaire, et également en écrivant en dur Illustrator.app.activeDocument à la place de la variabilisation par “obj”. Sinon, ca marche IMPEC
Le deuxième concerne un nom de classe qui, apparement, est mal répertoriée dans le framework. Il y a un usage constant de TextFrameItem, au lieu de TextFrame ; il suffit de faire une classe simple héritant de TextFrameItem pour contourner ca.
La voici, à placer dans le package com.adobe.
Me voila donc avec une sélection flambant neuve, et qui vrombit comme il se doit.
2 / Recréér la selection.
On est TRES loin de flash. Tu croyais dans ton fort intérieur que new PathItem et addChild ca marcherait ? Mouahaha
Les objets graphiques sont tous répertoriés et classés dans des objets, des genre de tableaux avec quelques méthodes en plus. Le SEUL moyen de créér un objet est d’enrichir ce tableau en utilisant la méthode add() :
Illustrator.app.activeDocument.activeLayer.pathItems.add():PathItem > Insert a new path into the document
Ceci étant dit, il devient donc impossible de faire un simple addChild du contenu de la sélection, une fois le tableau transféré via un SharedObject. Il ne reste plus qu’a… envoyer la sélection, et la dumper.
3/ Dumper un objet
Là encore, venu du monde de l’as3, où l’on peut voir du IExternalizable, ou même du Proxy, des outils en pagaille pour manipuler les objets facilement, je suis tombé de haut
Les objets graphiques sur un document Illustrator se décomposent en plusieurs types d’objets :
- PageItem : C’est l’objet de base, qui pourrait correspondre à une classe abstraite.
- PathItem : Un tracé, c’est le plus simple
- CompoundPathItem : Un tracé composé de plusieurs tracés
- MeshItem : Un dégradé
- TextFrame : Un texte
- GroupItem : Un groupe d’objets ; Un genre de layer. Quelques subtilités m’échappent encore dans la distinction Layer/GroupItem niveau objet.
- RasterItem : Une illustration pixelisée
- PluginItem : Je sais pas trop encore ; vu les exemples, je pense à une illustration vectorisée à l’aide de livetrace
Pour l’utilisation que j’en ai, l’usage de PathItem, de TextFrame et de GroupItem suffit.
La encore, un petit tour sur la doc :
toSource():String > a script to reproduce the object
“Royal
” !
Bah en fait, non :\
Le script renvoie toujours “({})”, va savoir pourquoi ; la méthode ne doit pas être totalement implémentée. Le toString() renvoie symboliquement le type d’objet, c’est LE truc qui aide à mort
Par feignantise, je n’avoue ne pas avoir essayé d’utiliser le namespace spécial de la classe proxy dont tous les objets d’illustrator héritent, afin de parcourir l’objet comme un tableau et le dumper (j’espère secrètement que cela donne un low-access aux variables, bypassant les getters/setters mis en place ; on verra ca plus tard).
Je me suis donc retrouvé a lister toutes les propriétés significatives d’un objet afin de le créér. Et y en a… plein
La même structure que PathItems.add() est présente partout, notamment dans PathItem.pathPoints, qui représente l’ensemble des points d’un tracé.
4/ Les TextFrames
Là, c’est le coeur du boulot de ma journée. La lecture d’un objet de type texte est vraiment fastidieuse, surtout au niveau des styles de texte. C’est le point que j’aurai à perfectionner et j’aurai fini, pour une release !
Conclusions :
Compte tenu de “mon passé” avec PP, je comprends à peine pourquoi ces librairies peuvent sortir dans cet état. J’aurai beaucoup apprécié un framework plus proche de la structure as3 et de la display list. Heureusement que ma fierté de pas baisser les bras face à un framework est suffisament grande, sinon j’aurai laissé tomber