ByteStream, kezako ?

Imaginez-vous en archéologue du numérique. Au lieu de fouiller d'anciennes ruines, vous voilà à excaver les trésors enfouis dans des fichiers binaires. La classe ByteStream d'IdExtenso est votre pioche, votre ciseau et votre loupe, le tout réuni en un seul outil.

Que vous fassiez de la rétro-ingénierie sur un format de fichier propriétaire, que vous construisiez un flux d'export personnalisé ou que vous ayez seulement besoin de « faire parler » ce mystérieux blob binaire qui a atterri sur votre établi, ByteStream transforme en une promenade champêtre ce qui était jusqu'alors une pénible procession dans un puzzle d'octets.

Mais ne brûlons pas les étapes ! Avant de plonger dans la magie, voici déjà comment inscrire ByteStream dans votre projet IdExtenso :

// Inclure le framework IdExtenso
#include 'path/to/$$.jsxinc'
 
// Utiliser la classe ByteStream
#include 'path/to/etc/$$.ByteStream.jsxlib'
 
// Charger le framework
$$.load();
 
// Votre code ByteStream ici...
 
// ...et n'oubliez pas de nettoyer en sortant !
$$.unload();
 

La magie des chaînes de format

Le cœur nucléaire de ByteStream réside dans sa syntaxe basée sur des chaînes de format. Pensez-y comme à une recette qui indiquerait exactement quels ingrédients extraire de votre soupe binaire :

// Lire l'en-tête d'un fichier en une ligne
var myHeader = $$.ByteStream(myBinaryData);
var ret = {};
 
myHeader.read(
    "TAG:signature ASC*4:version U32:size U16*2:dimensions",
    ret);
 
// `ret` contient maintenant :
// ret.signature, ret.version, ret.size, ret.dimensions[]
 

ByteStream parle couramment le binaire avec le support de toutes les structures usuelles :

Entiers : I08, U08, I16, U16, I24, U24, I32, U32 (signés/non-signés, 8 à 32 bits)
Virgule flottante : F32, F64 (floats et doubles IEEE754)
Virgule fixe : FXP, UFX, F2D (formats spéciaux selon spécif.)
Chaînes : STR, ASC, LAT, HEX (brutes, ASCII, Latin, hexadécimales)
TAG (identificateurs à 4 caractères Adobe, OTF…)

L'endianness (boutisme) simplifié

Les temps sombres de la gestion manuelle de l'ordre des octets (big-endian vs. little-endian) sont derrière nous : ByteStream rend cela aussi simple que d'ajouter un > (BE) ou < (LE) à votre chaîne de format :

// Lire les mêmes données selon différents ordres d'octets...
var myBE = myStream.readU32();     // Par défaut : big-endian
var myLE = myStream.readU32(true); // Explicite : little-endian
 
// ...ou via les chaînes de format (plusieurs valeurs) :
var myData = {};
myStream.read("U32>:bigValue U32<:littleValue", myData);
 

Analyse chirurgicale d'un fichier OTF

Mettons que vous souhaitiez extraire le nom de police d'un fichier OpenType (OTF). Voici comment ByteStream permet d'aborder ce genre de tâches :

// Lire ce mystérieux fichier .otf
var myFontStream = $$.ByteStream($$.File.readBinary("./myfont.otf"));
 
// Aller à la table 'name' (code simplifié pour la démo)
const NAME_TB_START = 416; // <= Ici il vous faudra l'index effectif
myFontStream.jump(NAME_TB_START);
 
// Lecture de l'en-tête de table
var myHead = {};
myFontStream.read("U16:format U16:count U16:storageOffset", myHead);
 
// Créer un sous-flux pour la zone de stockage des chaînes
var mySubStream = myFontStream.copy(NAME_TB_START + myHead.storageOffset);
 
// Parcours des 'name records' pour trouver le nom de police (nameID = 1 ou 4)
var myFontName="", rec={}, i;
for( i=-1 ; ++i < myHead.count && !myFontName ; )
{
    myFontStream.read(
      "U16:pID U16:encID U16:langID U16:nameID U16:length U16:offset",
      rec);
 
    // Chercher le nom de la famille (nameID 1) ou complet (nameID 4)
    if (rec.nameID === 1 || rec.nameID === 4)
    {
        // Saut vers la chaîne (dans le sous-flux)
        mySubStream.jump(rec.offset);
        myFontName = mySubStream.read("STR*" + rec.length);
        break;
    }
}
 
alert( "Nom de police : " + myFontName );
 

Note. — Dans une implémentation réelle, l'offset NAME_TB_START serait obtenu en scannant au préalable l'en-tête OpenType et la table de répertoire pour localiser l'entrée de la table name. Cela implique de lire l'en-tête sfnt initial et d'itérer à travers les enregistrements du répertoire de tables jusqu'à trouver celui avec le tag 'name', puis d'utiliser sa valeur d'offset. Ici cet offset est codé en dur pour alléger la démonstration, mais la logique permettant de le déterminer (via ByteStream) est rigoureusement la même que celle illustrée dans le code.

Flux d'entrée et de sortie

ByteStream est en réalité deux classes déguisées en une :

Flux d'Entrée (IStreams) → LECTURE

// À créer à partir d'un flux d'octets (tableau ou chaîne)
var myReader = $$.ByteStream(myBinaryArray); // ou string
myReader.peek("F32*3"); // Prélever l'info sans déplacer le pointeur
myReader.read("F32*3"); // Lire et avancer
myReader.backup();      // Sauvegarder la position
myReader.restore();     // Revenir à la position enregistrée
 

Flux de Sortie (OStreams) → ÉCRITURE

// Création à vide (pour l'écriture)
var myWriter = $$.ByteStream();
myWriter.write("F32*3", [1.0, 2.5, 3.14]);
myWriter.writeU16(42);
var myBytes = myWriter.getBytes(); // Obtenir le tableau d'octets final
 

Note. — Le préfixe new est superflu (i.e. implicite) lors de la création d'une instance de $$.ByteStream.

Sorcellerie avancée : données structurées

Bijou d'orfèvrerie et de concision syntaxique, il est possible de combiner nos clés de formats avec des compteurs (*N) pour chaque type de donnée :

// Analyser d'un coup une structure composite
var myImageData = {};
myStream.read(
  "TAG:signature STR*4:version U32:width U32:height U08*768:palette",
  myImageData);
 
// Typiquement, `myImageData` contient :
//   myImageData.signature = "PNG "
//   myImageData.version = "1.0 "
//   myImageData.width = 1920
//   myImageData.height = 1080
//   myImageData.palette = [r1,v1,b1, r2,v2,b2, ...]
// REM : 256 couleurs × 3 octets = 768 octets
 

Quelques astuces en passant :

1. Utilisez les raccourcis pour les lectures simples:
    readU16() est plus rapide que read("U16").

2. Groupez les opérations:
    read("U16*10") synthétise dix appels à readU16().

3. Copy vs. Clone:
   Utilisez copy() pour les flux partagés, clone() pour extraire un sous-flux autonome.

4. Encodage statique:
    $$.ByteStream.encode(myData, "U32*2") permet d'opérer une conversion rapide (méthode statique) sans instancier un flux proprement dit.

Concernant la gestion d'erreurs, ByteStream ne vous laissera pas en plan si les choses devaient mal tourner :

// Vérifier la validité d'une chaîne de format
if( !$$.ByteStream.isFormat("U16*3 F32") )
{
    alert( "Houston, nous avons un problème !" );
}
 
// Calculer a priori le nombre d'octets requis :
var myByteCount = $$.ByteStream.sizeOf("STR*20 U32*5");
// Renvoie 40
 

Il arrive que l'on doive traiter des données hexadécimales « littérales ». Utilisez alors le tag HEX :

// Lire 3 octets comme une chaîne hexa de 6 caractères
var myColorHex = myStream.read("HEX*3"); // "FF0080" (magenta vif)
 
// Écrire une chaîne hexa littéralement (sous forme d'octets)
var myColorStream = $$.ByteStream();
myColorStream.write("HEX*3", "FF0080"); // Écrit [0xFF, 0x00, 0x80]
 

Exemple Complet

Voici un exemple concret montrant ByteStream dans ses œuvres :

try
{
    // Créer des données binaires : Point Fixe + chaîne + octet + double
    var myData = '\x01\xFF\x3F\xFF' + 'abc' +
      '\x10' + String.fromBytes([64,9,33,251,84,68,45,24]);
 
    // Flux d'entrée (pour lecture)
    var myIStream = $$.ByteStream(myData);
 
    // Analyse des données structurées
    var myResult = {};
    myIStream.read(
      "FXP:fixedValue STR*3:name U08:count F64:piValue",
      myResult);
 
    // Affichage via le formateur JSON d'IdExtenso
    alert( "Données reçues :\r" + $$.JSON(myResult) );
    // Résultat : {"fixedValue": 511.249984741211, "name": "abc",
    //               "count":16,"piValue":3.14159265358979 }
 
    // Création d'un flux de sortie et écriture
    var myOStream = $$.ByteStream();
    myOStream.write("F64 STR:name FXP:fixedValue", myResult);
 
    // Octets en fin de course :
    alert("Séquence d'octets :\r" + myOStream.getBytes());
 
}
catch(e)
{
    $$.receiveError(e);
}
 

Dans le contexte d'un script InDesign, la classe ByteStream vous rendra les octets moins ténébreux. C'est un instrument pratique et efficace pour la rétro-ingénierie de formats de fichiers ou l'écriture de flux binaires dont la spécification est connue (OTF, PNG, etc.)

Prêt(e) à plonger plus en profondeur ? Le code source de la classe ByteStream est disponible sur GitHub. Fidèle à ses promesses, le framework IdExtenso continue d'enrichir votre outillage pour développer des scripts InDesign très au-dessus de la moyenne.

Liens GitHub :
Notice ByteStream (en)
ByteStreamDemo.jsx (sous-dossier /tests)
IdExtenso (page racine)