• part1a.png
  • part1b.png

partie 1 | partie 2 | partie 3

Périmètre de l’échiquier

La composition d’ouvrages multilingues ou à typographie chiadée n’effraie pas, en général, le praticien d’InDesign. Entré de plain-pied dans le royaume OpenType, il ratisse goulûment l’espace des caractères via la table des glyphes, détecte les polices défectueuses par un contrôle en amont impitoyable, bref, maîtrise (presque) tout ce qui fait la complexité de la typographie numérique contemporaine. Cependant, forte de ses atouts, l’interface logicielle cantonne à une relative pénombre le cosmos Unicode — bien que le moteur de l’application l’ait parfaitement digéré(1).

(1) Et avec quelques longueurs d’avance sur la concurrence ! Dès l’origine, InDesign et InCopy supportaient le standard Unicode dans ses grandes lignes. La documentation d’Adobe rappelle que, pour les versions 1.x et 2.x, l’encodage interne des caractères reposait sur un identifiant 16 bits (type textchar) reflétant leur rang Unicode 1.0. InDesign a ainsi accéléré la gestion avancée de textes multilingues et, bien sûr, l’utilisation des polices « Unicode-friendly » introduites par le format OpenType (création conjointe de Microsoft et Adobe au voisinage de l’an 2000). InDesign CS a ensuite accru la compatibilité avec Unicode (3.0), notamment en ouvrant le compas sur l’ensemble des caractères descriptibles en UTF-16. Le mode UTF-32 est géré à partir de la version CS3, si j’en crois Thomas Phinney de Typblography.

En résumé, le standard Unicode déploie une sorte de casse internationale décrivant chaque caractère comme un objet formel et l’identifiant par une position unique. Ce rang ou point de code utilise la notation hexadécimale, favorisant un repérage et une navigation commodes dans un vaste espace de signes découpé en compartiments linguistiques (plans et blocs). En gros, un rang Unicode donne les coordonnées d’un caractère dans le système, un peu comme à la bataille navale. La métaphore de la casse ne va guère plus loin, car les caractères ainsi indexés sont abstraits de leur représentation (appelée glyphe). En théorie, Unicode ne se préoccupe que de la manière dont les caractères cohabitent, se combinent, s’agencent, se substituent, en amont de leur actualisation graphique. Le dessin d’un caractère est assumé, le cas échéant, par les logiciels et les polices implémentant le standard. Par ailleurs, le fait qu’un rang Unicode soit interprétable comme un entier ouvre la possibilité de divers traitements et codages intermédiaires, par exemple le fameux UTF-8, un moyen parmi d’autres d’adresser des caractères Unicode.

On le voit, la théorie du codage des caractères dresse une pyramide de savoirs extrêmement touffue. Mes maigres lumières dans cette sorte de botanique ne me permettent pas de gravir l’édifice et encore moins d’y guider les escaladeurs débutants. C’est donc plutôt en utilisateur bidouilleur que j’aborde ici le sujet, à la recherche de solutions empiriques et pas forcément définitives pour compenser, en Javascript, les faiblesses que j’ai cru détecter dans l’interface actuelle d’InDesign (peut-être que la version CS3 rehausse le niveau, mais je ne l’ai pas encore sous la main).

Une des choses effarantes qui frappe immédiatement le maquettiste, c’est que la palette Glyphes ne permet pas d’accéder directement à un caractère en tapant simplement son identifiant Unicode. Si vous cherchez à insérer dans le texte un Č (C CARON MAJUSCULE, rang U+010C), sauf à avoir préalablement constitué et mémorisé un jeu personnalisé de glyphes, vous devez vous farcir la table de la police de travail (laquelle peut contenir des milliers de glyphes) tout en espérant que ledit caractère soit effectivement pris en charge... De dépit(2), on se rabat sur des logiciels satellites tels que BabelMap pour vérifier la disponibilité d’un caractère Unicode dans telle police et le copier-coller vers InDesign. Pas très productif !

(2) Sous Windows XP et versions antérieures, les caractères quadrillés par le jeu Ansi-Win (CP1252) peuvent être produits (dans InDesign comme ailleurs) par la célèbre combinaison alt + 0XXX (où XXX représente le code Ansi), mais cela ne couvre qu’une infime fraction des besoins... Sous Mac (jeu MacRoman), on s’appuie ordinairement sur des raccourcis mnémotechniques pour accéder à la plage des caractères dits spéciaux (ALT O pour le digramme œetc.). Lorsque le digicode du caractère à produire est inconnu, on utilise le menu Edition / Caractères spéciaux de l’interface logicielle ou bien on passe par le Visualisateur de clavier — à moins encore d’installer un menu de saisie permanent via les Préférences système. Des logiciels pour se faciliter la vie, tels que PopChar pour OS X, arrivent à la rescousse des plus nécessiteux, mais aucune de ces parades ne semble racheter InDesign de son étonnant déficit.

L’Unicode émergé dans InDesign

Avant de faire rugir nos féroces rustines (objet de la deuxième partie de ce dossier), listons quelques fonctionnalités saillantes d’InDesign en matière de gestion des caractères. La meilleure preuve que l’application parle secrètement l’Unicode dans le texte se niche dans la palette Informations, lorsqu’un caractère (un seul) est sélectionné :

Figure 1 - Rang Unicode d’un caractère dans la palette Informations

La figure 1 montre l’identification du rang Unicode en notation hexadécimale. Le caractère sélectionné est U+221B (RACINE CUBIQUE) tel que le représente la police Arial Unicode. Incidemment, on découvrira que la notation 0x221B correspond, en Javascript, à l’écriture hexadécimale d’une constante entière. Si vous codez dans un script les deux instructions suivantes :

var n = 0x221B; // le prefixe '0x' indique une ecriture hexa
alert(n);

la boîte d’alerte affichera 8731 — et non pas le caractère racine cubique comme certains en rêvaient peut-être ! En effet, 0x221B n’est qu’une autre façon d’écrire le nombre 8731, le préfixe 0x indiquant seulement à Javascript qu’on lui cause en base hexadécimale. Il faut donc comprendre que l’affectation n = 0x221B est strictement équivalente à n = 8731 et n’a en soi, pour l’instant, aucun rapport avec Unicode...

La palette Glyphes, malgré ses limitations fonctionnelles, nous fraye un autre chemin prometteur :

Figure 2 - Rang Unicode aperçu dans la palette Glyphes

Au survol d’un glyphe avec la souris, une bulle d’information surgit qui révèle le rang Unicode du caractère et précise son... GID. Si j’ai bien compris, ce « Glyph ID » correspond à l’indice décimal du glyphe au sein de la police de caractères. Il ne semble donc pas qu’on puisse l’exploiter pour adresser un caractère indépendamment de la police active, quoique la documentation d’Adobe entretienne une terminologie confuse à ce sujet. On lit par exemple, page 42 du tout frais InDesign CS3 Scripting Guide : [...] you can use the JavaScript method of explicitly entering Unicode characters by their glyph ID number : \unnnn (where nnnn is the Unicode code for the character). Dans cet extrait, l’expression glyph ID number n’est visiblement pas synonyme du GID au sens InDesign puisqu’elle est directement identifiée au rang Unicode — je reviendrai plus loin sur la notation \unnnn.

Jusqu’ici, Unicode ne transparaît dans InDesign qu’en mode consultatif (lecture seule). L’utilisateur est sans doute ravi d’extraire le code ésotérique des caractères mais ne peut pas en faire grand chose. Il existe cependant un minuscule espace d’interaction... au fin fond de la boîte de dialogue Rechercher / Remplacer ! Là, vous pouvez traquer ou substituer(3) des caractères Unicode en spécifiant leur rang entre crochets, par exemple <203B> pour choper la marque de référence (U+203B), comme l’illustre ci-dessous la figure 3.

Figure 3 - Caractères Unicode dans un rechercher/remplacer

(3) Pour info, InDesign CS3 introduit le support des expressions régulières (au sens de la commande grep Unix), mais ceci est une autre histoire.

Dans son mémento Scripting InDesign with JavaScript (O’Reilly), Peter Kahrel décèle un autre point d’entrée des identifiants Unicode sous InDesign : le format d’import/export intitulé Texte référencé (tagged text). En voie d’obsolescence depuis l’arrivée du format INX (InDesign Interchange), le tagged text représente le contenu d’un article sous la forme d’un texte balisé conservant les spécifications de styles. Pour le tester, sélectionnez dans un bloc un bout de texte contenant des caractères non-Ascii et appelez Fichier / Exporter... Choisissez le type « Texte référencé InDesign » et exigez un codage Ascii. Ce faisant, vous obligez InDesign à encoder l’information d’une façon universelle. Vu dans un simple éditeur, le fichier texte résultant s’apparente à un épais gloubi-boulga, sauf que le fouineur averti y repérera aussitôt le cryptage des caractères Unicode : un É (e majuscule accent aigu, U+00C9) sera par exemple traduit en <0x00C9>. Ce n’est autre que l’écriture adoptée (v. supra) dans la palette Informations(4) ! Si vous changez ce <0x00C9> en <0x00E9> et que vous réimportez le texte référencé dans un bloc InDesign, le É (majuscule) mutera donc en é (minuscule), à savoir le caractère U+00E9 (LETTRE MINUSCULE LATINE E ACCENT AIGU).

(4) À ceci près que dans le format tagged text, la balise <0xHHHH> restitue toujours, à la suite du préfixe 0x, les 4 chiffres hexadécimaux du rang Unicode : 0x00C9. La palette Informations se contenterait de la forme abrégée 0xC9.

Enquête sur les « caractères spéciaux »

Dans un monde où Unicode règne en grand horloger, le concept traditionnel de caractères spéciaux semble un rien dérisoire, sinon carrément périmé. Pourquoi maintenir arbitrairement un accès privilégié à une trentaine de signes cosmopolites (puce, symbole de copyright, pied-de-mouche, tirets, guillemets...) quand on dispose en vérité d’une armée multimillionnaire de caractères tout aussi spéciaux ? Les typographes sont-ils à ce point réactionnaires qu’ils considèrent la « marque de paragraphe », alias le pied-de-mouche (U+00B6) comme fondamentalement plus vitale que le POINT D’INTERROGATION RENVERSÉ (U+00BF), le symbole pour mille (U+2030) ou tout autre signe de ponctuation étendue ?

Figure 4 - Insertion de caractères spéciaux dans InDesign

Non, ces bons vieux caractères spéciaux ont le cuir dur pour une autre raison : InDesign les soumet à un codage perso (et totalement opaque pour l’utilisateur), soit parce que leur correspondant Unicode n’est pas ou n’a pas toujours été univoque (cas d’école : la « tabulation de retrait à droite »), soit parce qu’ils sont assortis de fonctionnalités additives propres à l’application (j’imagine qu’il en va ainsi de la « puce » d’énumération, U+2020), soit enfin parce qu’ils ne possèdent aucun correspondant Unicode sémantiquement valide, en raison de leur « comportement » déviant, tels certains caractères de sauts ou le marqueur de « style imbriqué de fin » (end nested style)... À l’extrême, les marques de numéros de page (courante, suivante, précédente) et la marque de section(5) peuvent-elles même être apparentées à de véritables « caractères » ?

(5) À y bien réfléchir, ces pseudo-caractères officient comme des marque-places, dont le contenu est dynamiquement substitué par d’autres caractères. Analogie possible avec les codes de champ (variables de documents) déployés dans les traitements de texte, voie vers laquelle se dirige nettement la version CS3 d’InDesign.

Ce grouillement souterrain intéresse au premier chef le javascripteur. S’il n’y prend garde, le traitement des contenus de texte (Text.contents) en lecture et/ou en écriture lui réserve des surprises mortelles. En effet, un authentique dissident à Unicode, le type énuméré SpecialCharacters, sommeille au cœur du Javascript InDesign. C’est avec lui qu’il faudra savoir négocier, à l’occasion, pour gérer ces foutus caractères spéciaux(6).

(6) Le terme de métacaractère est utilisé conjointement pour désigner, dans le contexte du dialogue rechercher/remplacer, les codes d’échappement de certains caractères spéciaux. Par exemple : ^# pour le folio automatique (Auto Page Numbering), ^s pour l’espace insécable fixe (Nonbreaking Space), etc. Un récapitulatif des métacaractères est dressé avec notre cartographie (v. plus bas).

Pour bien comprendre cette sombre affaire, supposons que votre script soit appelé à insérer un tiret cadratin dans un objet Character ou InsertionPoint (cf. hiérachie Javascript). Vous avez deux solutions : soit charger dans la propriété .contents le caractère tel quel en tant que chaîne (en direct ou via son rang Unicode, nous verrons comment), soit y charger SpecialCharacters.emDash. La seconde solution fonctionne aussi bien que la première :

// Test d'ecriture via le type SpecialCharacters
// ---------------------------------------------
// On suppose que le script est appele depuis un bloc-texte
// sur point d'insertion (ou selection de caractere)
 
var selText = app.activeDocument.selection[0];
selText.contents = SpecialCharacters.emDash; // insere un tiret cadratin

En fait, cette technique d’insertion est payante pour n’importe quel élément de l’énumération SpecialCharacters, à l’exception de .footnoteSymbol (appel de note) qui nécessite l’artillerie de la collection Footnotes (à partir d’InDesign CS2, donc).

Jusqu’ici, tout va bien. Mais l’opération réciproque introduit un dilemme fonctionnel : lorsqu’un caractère réputé spécial est lu par un script dans un objet Character isolé, la propriété .contents restitue, non pas un caractère-chaîne, mais sa valeur d’énumération dans SpecialCharacters, ce qui ne manquera pas de dérouter le néophyte :

// Test de lecture d'un membre de SpecialCharacters
// ---------------------------------------------
// Le caractere selectionne est suppose etre un
// caractere special, par exemple un tiret cadratin
 
var selText = app.activeDocument.selection[0];
alert(selText.contents); // affiche un... nombre !

Et il n’a pas fini d’être dérouté ! Car, a contrario, si la sélection contient plusieurs caractères, avec ou sans éléments de type SpecialCharacters, la propriété .contents affichera sans moufeter la chaîne de caractères lisible et transparente !

Voyons les deux cas de figure :

Figure 5 - Traitement hétérogène des caractères spéciaux

Ce traitement hétérogène pose évidemment un problème. Le type SpecialCharacters surgit sous certains conditions, peu maîtrisables en ce qu’elles dépendent du contexte-utilisateur. De surcroît, les investigations que j’ai pu mener concernant cet « énumérateur » m’ont conduit sur des pentes improbables.

Ainsi, la « classe » SpecialCharacters n’est pas réellement adressable en JavaScript. Elle s’appuie sur un type sous-jacent intitulé Enumeration qui n’est rigoureusement documenté nulle part. En fouinant un peu, on découvre que SpecialCharacters est analysé par le moteur Javascript comme LiveObject("Enumeration").property(0x1ff10000), expression ésotérique qu’on pourrait traduire en langage ordinaire par « Circulez, y a rien à voir ! » L’écriture SpecialCharacters recouvre de toute évidence un objet dynamique interne, inexplorable, bien planqué sous les fondations. Conséquence, le programmeur Javascript aura toutes les peines du monde à customiser son prototype, surcharger ses propriétés ou simplement les interroger. Mais il n’est pas besoin d’être grand clerc pour deviner qu’un type énumérateur ne consiste finalement qu’en un ensemble de nombres indexé par des identifiants. C’est donc une sorte de tableau associatif figé, non instanciable et dépouillé de méthodes d’accès — n’allez pas croire que SpecialCharactes.length puisse produire autre chose qu’une erreur d’exécution !

L’accès « normal » aux caractères de l’ensemble SpecialCharacters est donc sévérement balisé par la syntaxe de référence. On utilise les identifiants mnémotechniques : .emDash, .enDash, .autoPageNumber, .pageBreak, etc. Au total, 38 caractères spéciaux sont ainsi homologués dans InDesign CS2. Sachant que les propriétés d’un objet sont toujours accessibles, alternativement, par des index associatifs, l’expression SpecialCharacters.emDash peut s’écrire également SpecialCharacters["emDash"]. Ceci ouvre une possibilité, certes assez misérable, de traverser l’ensemble par une boucle, en présumant toutefois que les 38 identifiants, connus a priori, soient rangés au préalable dans un tableau de chaînes :

// Acces associatif aux SpecialCharacters
// ---------------------------------------------
var idNames = [ "emDash", "enDash", "emSpace", "enSpace"]; // etc.
 
var sz = idNames.length;
var idName = null;
for (var i=0; i < sz; i++)
      {
      idName = idNames[i];
      alert( idName + " : " + SpecialCharacters[idName] );
      }

L’inconvénient, c’est que le programmeur devra de toute façon « coder en dur », en extension, l’ensemble des identifiants requis. D’où grosse difficulté de concevoir des scripts à long terme. En effet, comment connaître par avance les futurs SpecialCharacters qu’Adobe pourrait glisser subrepticement dans la collection ?

À partir d’InDesign CS2, un moyen officieux d’accéder en compréhension aux membres d’un énumérateur a commencé à circuler sur les forums. On exploite pour cela une propriété cachée, .reflect, permettant d’agréger les propriétés invisibles d’un objet dans un tableau automatique. Ainsi, l’expression SpecialCharacters.reflect.properties renvoie un objet Array qui liste notamment tous les identifiants d’énumération. Pour vous en convaincre, essayez le code suivant :

// Recuperation des SpecialCharacters Id (CS2 only!)
// ---------------------------------------------
var idNames = SpecialCharacters.reflect.properties;
alert(idNames.join("\r"));

dont le résultat est figuré ci-dessous :

Figure 6 - Identifiants des caractères spéciaux (ID CS2)

Si vous décidez d’utiliser cette technique d’accès, méfiez-vous de la propriété parasite __proto__, apparemment située en fin de tableau et qu’il conviendra d’ignorer. Vous noterez incidemment que les identifiant « internes » pratiqués par InDesign sont formés de capitales et caractère de soulignement (underscore). Il sont absolument synonymes des identifiants publiés dans la documentation, écrits eux en « casse chameau » (camel caps). Par exemple, SpecialCharacters["autoPageNumber"] équivaut à SpecialCharacters["AUTO_PAGE_NUMBER"] et vous pouvez par conséquent employer dans votre code cette seconde notation. Nous avons finalement la quadruple égalité :

SpecialCharacters.autoPageNumber == SpecialCharacters["autoPageNumber"]
== SpecialCharacters.AUTO_PAGE_NUMBER == SpecialCharacters["AUTO_PAGE_NUMBER"]

Pour clore le débat sur le type SpecialCharacters, retenez que les membres de cet ensemble sont rigoureusement identifiés à des nombres (objets Number). Il s’ensuit que si vous connaissez l’identifiant numérique d’un caractère spécial, vous pouvez charger ce nombre dans un .contents et obtiendrez le résultat escompté. Mais si vous chargez un nombre qui n’est pas identifié, alors vous êtes bon pour le runtime error :

// Envoi direct d'un special car. en tant que Number
// ---------------------------------------------
var selText = app.activeDocument.selection[0];
var idBullet = 0x53426C74;
selText.contents = idBullet;      // insere une puce - OK
 
var idUndefined = idBullet + 1;
selText.contents = idUndefined;      // erreur!!!

Comment récupérer un « SpecialCharacter » en tant que String

Nous avons constaté l’hétérogénéité du traitement des caractères spéciaux, en lecture du moins, selon qu’ils sont seuls ou accompagnés. Cela crée des dangers terribles dans tous les scripts susceptibles de manipuler du texte. D’une façon générale, il faut s’assurer que la propriété .contents d’un objet Text(7) renvoie bien une chaîne (String) si l’on désire la traiter comme telle. Dans le cas contraire, il est souvent indispensable de procéder à une conversion. D’ailleurs, bien qu’elle omette de préciser le point révélé ci-dessus, la documentation de référence indique clairement l’ambivalence de la propriété .contents :

Text::contents

      Type : String, SpecialCharacters enumeration

      Access : r/w

      Description : The text contents.

Adobe InDesign CS2 Scripting Reference, p. 1104

(7) L’exploration de la classe Text n’étant pas l’objet de cet article, je rappelle simplement qu’elle chapeaute (surclasse) les types Character, Word, Line, Paragraph, TextColumn, Cell, chacun correspondant à une certaine segmentation du texte. De même que PageItem vis-à-vis des composants paginés, Text peut être considéré comme une classe générique déclarant les propriétés et les méthodes communes à tous les composants textuels. On y retrouve également la philosophie de collections récursives (poupées russes) adoptée dans PageItem : chaque objet Text (ou dérivé de) adresse des collections .characters, .words, .lines, etc.

Exercice : comment récupérer la propriété .contents sous forme de chaîne dans tous les cas de figure ? Le fait qu’un SpecialCharacters énuméré soit automatiquement transformé en String lorsqu’il est accompagné ouvre la voie à une première « rustine ». L’astuce est la suivante : si .contents renvoie un nombre énumérateur au lieu d’une chaîne (xxx.contents.constructor.name != "String"), on crée un bloc-texte temporaire dans lequel on recopie xxx.contents, puis on insère un caractère arbitraire, par exemple l’espace, avant le caractère récalcitrant. On prélève alors le nouveau .contents, métamorphosé en véritable chaîne, dont on renvoie enfin le second caractère.

Mettons en pratique cette stratégie sur les objets de type Character (la plus petite unité assimilable à Text) :

Character.prototype.getContentsAsString = function()
//----------------------------------------------------------
// Renvoie le contenu d'un caractere en tant que chaine (String)
// meme si contents est de type SpecialCharacters.id (cad Number)
{
var tf = app.activeDocument.textFrames.add();
tf.contents = this.contents;
 
tf.insertionPoints[0].contents = " ";
var cStr = tf.parentStory.contents.substring(1);
tf.remove();
return(cStr);
}

Point capital : le contenu après traitement est prélevé dans TextFrame.parentStory et non pas directement dans TextFrame. Pourquoi ? Parce que certains caractères spéciaux (saut de page, etc.) peuvent provoquer un débordement immédiat du bloc-texte. Seul l’article parent (Story) possède une vue globale du contenu.

Exemple d’utilisation :

// Usage possible de la methode Character::getContentsAsString
//----------------------------------------------------------
// on suppose que la selection contient un caractere
 
var selText = app.activeDocument.selection[0];
 
// affichage du contenu
 
if ( selText.contents.constructor.name!= "String" )
      {
      // "decrypte" un caractere special
      alert("Caractere special : " + selText.getContentsAsString());
      }
else
      {
      // affiche directement un caractere-chaine
      alert("Caractere-chaine : " + selText.contents);
      }

Cartographie SpecialChars / Unicode

Pour garder les idées claires après ce long mais nécessaire détour par les caractères spéciaux, dressons une cartographie générale de cette population, puis recherchons, lorsqu’elles existent, les correspondances que propose InDesign dans l’espace Unicode. Pour ce faire, j’ai utilisé la palette Informations comme indiqué plus haut (la consultation de la palettes Glyphes n’aurait donné que des résultats parcellaires, car beaucoup de caractères spéciaux n’ont pas de glyphe associé).

Comme vous allez le voir, le résultat de mes investigations (opérées dans InDesign CS2) est édifiant :

Figure 7 - Tableaux de caractères spéciaux InDesign CS2

Voir aussi : Table mise à jour pour InDesign CS3

Au-delà des exceptions qu’il met en lumière quant au traitement interne de certains caractères, le tableau visé à la figure 7 (téléchargeable en PDF) montre combien les politiques de codage visant à analyser et traiter des caractères dans InDesign nécessitent de précautions. Je vous recommande d’imprimer ce document, en couleurs s’il vous plaît, pour bien nous suivre dans les développements à venir.


partie 1 | partie 2 | partie 3

BlogNot! est une émission produite par Marc Autret depuis 2004, à consommer de préférence en cuves acclimatées aux spécifications XHTML et CSS.
Pour harceler la rédaction : marcautret(at)free(point)fr