Insérer une image dans un fichier Word généré

Nous y voilà ! Jusqu’à maintenant, on s’est bien amusé, mais on n’a pas encore fait grand chose… Du simple rechercher/remplacer, en fait; on a profité du fait que, avec l’ouverture des fichiers Word au format XML, le contenu des fichiers Word est devenu complètement lisible. Bon, ok, ce n’est pas tout à fait vrai, il a fallu jongler un peu avec les tableaux…

Mais là, vous allez voir ce que vous allez voir, nous allons maintenant insérer des images dans les fichiers Word, et j’aime autant vous dire d’emblée deux choses :

  1. ce n’est pas, a priori, une affaire simple;
  2. il est très facile de ne pas optimiser la chose…

Comment les images sont insérées dans un fichier Word ?

Encore une fois, il est aujourd’hui aisé de trouver l’information sur internet; cela l’était moins par le passé. Si on se concentre sur le format XML (ce qui est toujours le cas…pour le format le plus récent .docx, on verra plus tard), il suffit de réaliser l’opération suivante :

  • on crée un document Word vierge;
  • on insère une image sans rien faire d’autre (on insère via le menu insertion, ou bien en glissant une image);
  • on enregistre au format xml (pour notre exemple, xml Word 2003);
  • on regarde dans notre éditeur préféré.

Ok, c’est fait ! Que vois-je ?

On zappe toute l’introduction du fichier (jusqu’au tag <w:body> en fait). On a alors un bloc <w:pict> (pict pour…picture ! mais ma parole, vous êtes bilingue !) qu’on peut décomposer schématiquement de la façon suivante :

<w:pict>
    <v:shapetype>
        [un certain nombre de tags]
    </v:shapetype>
    <w:binData>
        [du code absolument illisible mais d'une largeur apparemment fixe de 77 colonnes]
    </w:binData>
    <v:shape>
        <v:imagedata/>
    </v:shape>
</w:pict>

On va maintenant voir de quoi il s’agit, en détails…

Tout d’abord, le namespace v

En regardant l’entête du document, on constate que la déclaration du namespace v (qu’on trouve dans les tags <v:shapetype>, dans ses sous-tags (v:formulas, v:f) et dans <v:shape>) correspond à un langage nommé vml. Le VML, ou Vector Markup Language, est un langage XML conçu pour définir des images vectorielles. Alors vous pouvez vous demander à quoi cela sert, lorsqu’on insère une image binaire !! C’est toute la beauté (sic) de Word de prévoir cela et de le prérenseigner. Pour vous en convaincre, je vous invite à prendre votre fichier Word et à supprimer l’intégralité du tag <v:shapetype>…</v:shapetype> dans votre éditeur de texte; sauvegardez et rouvrez dans Word…Ô joie ! notre image n’est pas perdue, et ce tag ne nous sert donc, en l’espèce, à rien.

Attention, ceci dit, <v:shape> va nous servir, comme nous l’allons voir, mais pour une raison qui n’a rien à voir (ou pas grand chose) avec le contenu même de l’image; mais occupons-nous d’abord du binData…

<w:binData>, le coeur de l’ouvrage

Un oeil averti et expérimenté (mais dieu seul sait dans quelles expériences) aura reconnu l’encodage; il s’agit de Base64 !

Je répète, pour qu’on soit bien clair : une image insérée dans un fichier Word est intégralement encodée directement dans le fichier (en Base64, donc), en tout cas pour les versions de Word précédant le format docx.

Enfin, le tag <v:shape>

Ce qui va nous intéresser, avec le tag shape, c’est surtout son sous-tag <v:imagedata>; ce tag va nous permettre d’identifier une image en lui donnant une « adresse » (entre guillemets, car ce n’est pas tant un adressage à proprement parler qu’un référencement) ainsi qu’un nom de fichier.

Dans votre fichier, vous devez voir quelque chose comme :

<v:shape id="_x0000_i1025" type="#_x0000_t75" style="width:505pt;height:488pt">
    <v:imagedata src="wordml://03000001.png" o:title="leNomDeMonFichierImage.png"/>
</v:shape>

Okay, générons l’insertion d’une image, en PHP

Supposons que :

  • nous avons une image : monImage.png;
  • nous connaissons ses dimensions , ou bien nous pouvons les connaître (par exemple par les fonctions natives de la librairie gd de PHP) — je précise que ce qui nous intéresse ici, c’est surtout le rapport largeur/hauteur;
  • le format de cette image est un format d’image standard (méfiance avec les « images » au format PDF, PSD, EPS…).

on va constituer l’image de la façon suivante :

$noImage = 1; //on incrémentera pour chaque image différente
$extImage = explode(".",$imageName);
$extImage = $extImage[count($extImage)-1] //on récupère l'extension de l'image
$image  = '<w:pict>\n';
$image .= '<w:binData w:name="wordml://03000'.str_pad($noImage,3,"0",STR_PAD_LEFT).'.'.$extensionImage.'" xml:space="preserve">';
$content = file_get_contents("/cheminDeMonImage/monImage.png");
$image .= base64encode($content);
$image .= '\n</w:binData>\n';
$image .= '<v:shape id="_x0000_i' . $unNombreUniqueParImageDistincte
       . '" type="#_x0000_t75" style="width:'.$width.'pt;height:'.$height.'pt">\n';
$image .= '<v:imagedata src="wordml://03000'.str_pad($noImage,3,"0",STR_PAD_LEFT).'.'.$extensionImage.'" o:title="monImage.png"/>';
$image .= '</v:shape>\n</w:pict>\n';

Avec ça, on est paré.

Je précise quand même quelques informations importantes :

  • l’id de shape doit être unique par image distincte;
  • le type de shape, en revanche, est bien constant : #_x0000_t75 (par exemple);

je fais une parenthèse sur ce dernier point qui est simplement hallucinant; le type de « <v:shape> » fait référence à l’id du « <v:shapetype> »…celui-là même qu’on a par ailleurs supprimé (sic). Vive les langages stricts !!

  • dernier point, concernant width et height : vous mettez ce que vous voulez, l’idée étant de respecter le ratio largeur/hauteur (une bête homothétie, quoi…sauf si vous souhaitez déformer l’image).

Ok, et maintenant ?

Maintenant, je comprends bien le préambule « ce n’est pas, a priori, une affaire simple »; tout ce déballage d’info avait l’air bien compliqué, mais au final c’est très simple…

Maintenant se pose la question du second commentaire du préambule « il est très facile de ne pas optimiser la chose »…tout d’abord, la formule est alambiquée, et il est évident qu’on souhaite optimiser, mais optimiser quoi ? Et comment ?

Insérer plusieurs fois la même image

Nous y voilà; prenez un fichier word vide, dans lequel vous allez insérer une image de 300 ko (par exemple); ensuite, copiez/coller cette image dans le même document Word, juste après la première image; enregistrez en XML et regardez un peu votre fichier…

Ça peut sembler incroyable, mais bien qu’on ait bien respecté Word en copiant/collant directement dans l’application elle-même, l’image a tout bonnement été dupliquée (doublant ainsi la taille finale du fichier). C’est con, mais c’est ainsi (toujours avant le docx).

Si Word ne fait pas mieux, comment diable pourrait-on…

…ne finissez pas cette phrase ! Si vous pensez que ce n’est pas possible, je ne peux plus rien pour vous.

Voilà ce que vous allez faire :

  • prenez un fichier word, qui ne contient qu’une image, au format XML;
  • supprimez bien évidemment le tag <v:shapetype>;
  • dupliquez tout le tag <w:pict>;
  • supprimez, dans le second <w:pict>, toute la partie <w:binData>;
  • sauvegardez et ouvrez dans Word.

Ô, grâce soit rendue au blogueur fou qui n’avait visiblement rien d’autre à faire, il y a quelques années, que d’optimiser des fichiers Word. Nous n’avons, pour ainsi dire, pas modifié la taille de notre fichier, et nous pouvons reproduire notre image autant de fois que nous le voulons. C’est dans cet esprit que j’ai, à plusieurs reprises durant cet article, bien indiqué les valeurs uniques par image distincte au sein du document.

Je ne vais pas vous faire l’affront de dire comment faire cela en PHP; il suffit de faire la même chose qu’auparavant, moins la partie binData.

En conclusion

En conclusion, on voit qu’il est à peu près aisé, lorsqu’on sait où l’on met les pieds, d’insérer des images; il suffit maintenant d’avoir dans votre document Word un mot-clé comme par exemple @IMAGE@ que vous remplacerez à la volée par l’image que vous souhaitez insérer (je rappelle au passage qu’il est possible de créer, from scratch ou non, des images en PHP grâce à la librairie gd).

Le prochain article nous permettra de simplifier encore le process à l’attention des clients, notamment pour qu’ils puissent, sans se tromper, créer eux-mêmes des modèles de document Word. Une fois cela fait, nous aurons fait le tour du cas relativement simple dans lequel on part d’un modèle de document existant, en XML, et on pourra alors se lancer dans la vraie génération de documents docx…pour de vrai.

 

 

Étape précédente : Générer un document Word à partir d’un modèle

Étape suivante : Comprendre les fichiers .docx

Une réponse à to “Insérer une image dans un fichier Word généré”

  • locehen:

    Bonjour et surtout Bonne année (principalement la santé le reste c’est du bonus :))

    je tente de réaliser la génération de document word depuis php, votre tuto précédant m’a permis de le faire (donc merci :))

    par contre je n’arrive pas à faire la jonction entre le code permettant l’ajout d’une image et le précédant.

    Pourriez-vous m’aider ou me fournir un exemple de code complet ? je n’ai besoin que d’une image, donc j’avais enlevé $noImage

    je vous propose de voir le miens (j’ai enlevé la partie ajoutant l’image) :

    //GENERATION DU WORD
    //On récupère le contenu brut du fichier xml modèle
    $myContent = file_get_contents(« MODELE_CCR.xml »);

    $myContent = str_replace(« @DATE@ »,strftime(« %d/%m/%y »),$myContent);
    $myContent = str_replace(« @ANNEE@ », »2014″,$myContent);
    $myContent = str_replace(« @DATE_DEBUT@ », »01/01/2014″,$myContent);
    $myContent = str_replace(« @DATE_FIN@ », »31/12/2014″,$myContent);
    $myContent = str_replace(« @TOTAL_TICKET@ »,$total_tickets,$myContent);
    $myContent = str_replace(« @MOYENNE_MENSUELLE@ »,$moyenne_ticket_mensuelle,$myContent);
    $myContent = str_replace(« @DMP@ »,$moyennedelaiprisecharge,$myContent);
    $myContent = str_replace(« @DMT@ »,$moyennedelaitraitement,$myContent);
    //$myContent = str_replace(« @GRAPHE@ »,$image,$myContent);

    //On crée le fichier généré
    $newFileHandler = fopen(« words/ ».$id_societe. »_CCR_ ».$annee. ».docx », »a »);
    fwrite($newFileHandler,$myContent);
    fclose($newFileHandler);

    Cordialement,