Problématique
N’avez-vous jamais galéré pour imprimer quelque chose autrement que via la configuration par défaut de votre imprimante ? Vous essayer de mettre une image en petit, malheureusement un des éléments de la chaine de fabrication de votre feuille imprimé a décider que la mettre au centre est la meilleure des solutions. Vous voulez imprimer en 4 feuilles par pages en paysage, malheureusement en fonction du logiciel, le résultat sera plus ou moins exotique. Pire, imaginez que vous voulez imprimer les pages 3 et 5 en sur la même page, en noir et blanc (pour l’encre, évidement) et mettre un filigramme Confidentiel sur chaque pages mais vous n’avez qu’un pdf protégé… Bon courage.
Introduction
Toutes les interfaces d’impression proposent un certain nombre d’outils qui s’applique à l’ensemble du fichier. Il en résulte que les combinaisons entre ces options est totalement arbitraire si elles ne sont pas commutatives (par exemple, marges de 2cm + 2 par pages). Je propose un programme qui offre une interface différente.
L’idée serait de configurer l’impression via des nodes appliquant des fonctions différentes au document. Une interface graphique permettrait de manière intuitive et puissante de configurer l’impression.
Pour illustrer un peu le concepts, on peut regarder ce qu’a fait Blender pour la configuration du rendu :
http://www.freewebs.com/pwnage303/screens/bloom_DOF.jpg
http://e-learning.tul.cz/~hnidek/pics/blender_render_node_compositor.png
Le but : Pouvoir faire facilement les choses actuellement difficiles et pouvoir faire les choses actuellement impossibles.
Présentation
Le logiciel serait composé de 3 parties :
- Une bibliothèque comportant le cœur d’interprétation du fichier de configuration et générant les sorties.
- Une interface graphique permettant de configurer son impression ou de développer des nouvelles fonctionnalités d’impression.
- Un interpréteur de commande pour exécuter des conversions via un fichier de configuration d’impression.
Idéalement, le programme prendrait en entrée un ensemble de document et génèrerait un ensemble de document.
Cœur d’interprétation
Le cœur d’interprétation met à disposition un ensemble de fonctions qui font chacune une opération simple. Une fonction est un modèle de node et un node est une utilisation d’une fonction (« Perroquet » est une fonction est « coco » est un node de type « Perroquet »). Une fonction prend des paramètres en entrée et génère des paramètres de sortie. Ces paramètres sont typés.
Exemple : coco est un Perroquet. Un Perroquet prend en entrée une phrase et un niveau d’entêtement et génère des phrases.
Pour présenter rapidement un perroquet, j’utiliserai la syntaxe suivante :
-
Perroquet(string(phrase),number(entetement)=1->string(out)) // répète ‘entetement’ fois ‘phrase’ dans out. En absence de paramêtre ‘entetement’ celui-ci prend la valeur 1
Pour utiliser rapidement un perroquet et afficher qu’il dit :
-
coco.print
-
input=InArgument(pos(0))
-
toString=FileToString(input.out)
-
coco=Perroquet(phrase("plop"),entetement(10))
-
fromString=StringToFile(in(coco.out))
-
output=OutCout(fromString.out)
print -i coco.print « plop » donne : plop plop plop plop plop plop plop plop plop plop
Les types
Voici une liste non exaustive des types d’entrée et de sortie des fonctions :
- file : flux d’octet
- document : ensemble de pages
- number : un nombre
- dimension : dimension réelle, par exemple 2.34cm
- sequence : ensemble de position, par exemple 1-5;10;20-30
- string : chaine de character
- boolean : booléen
- setting : réglage
- function :fonction
Les fonctions
Voici quelques exemples de fonctions :
Entrée/Sorties
Ouverture de fichier via l’entrée stardart. Utilisable uniquement en ligne de commande
-
nArgument(number(argument)->file(out))
Ouverture de fichier dont le chemin correspond au paramètre numéro ‘argument’ du programme
-
InFile(string(path)->file(out))
Ouverture de fichier dont le chemin est ‘path’
InExec(string(command)->file(out))
Ouverture de fichier dont le contenu est le résultat de l’execution de la commande ‘command’
FileToPdf(file(in)->document(out))
Convertion d’un fichier PDF en document
FileToDvi(file(in)->document(out))
Convertion d’un fichier DVI en document
FileToPs(file(in)->document(out))
Convertion d’un fichier PostScript en document
FileToOdt(file(in)->document(out))
Convertion d’un fichier ODT en document
FileToJpg(file(in)->document(out))
Convertion d’un fichier JPEG en document
OutCout(file(in)->)
Envoie du fichier vers la sortie standart
OutFile(file(in),string(path)->)
Envoie du fichier vers le fichier ‘path’
OutPrint(document(in),setting*(setting)->)
Imprime le document. (le ‘*’ sur le type indique la possibité de paramètre multiples)
FileFromPdf(document(in)->file(out))
Convertit un document en fichier PDF
FileFromDvi(document(in)->file(out))
Convertit un document en fichier DVI
FileFromPs(document(in)->file(out))
Convertit un document en fichier PostScript
FileFromJpg(document(in),number(page)=0->file(out))
Convertit la page ‘page’ de ‘in’ en image jpg
Filtre
Negative(document(in)->document(out))
Passage en négatif
Luminosity(document(in),number(intensity)=0->document(out))
Constast(document(in),number(intensity)=0->document(out))
GreyScale(document(in)->document(out))
Passage en niveau de gris
Sepia(document(in)->document(out))
Passage en sépia
BlackAndWhite(document(in),number(ceil)=128->document(out))
Passage en noir et blanc. Ce qui est au dessus de ceil est blanc, le reste est noir
Structure
SubDocument(document(in),sequence(position)->document(out)
Récupération d’une partie du document, comme une page, ou un ensemble de page
Insert(document(in),document(docToInsert),number(position)=docToInsert.nb_page->document(out))
Insertion d’un document au sein d’un autre document
Concat(document(doc1),document(doc2)->document(out))<code>
Mise bout à bout de 2 documents
<code>Split(document(in),number(position)->document(part1),document(part2));
Coupe ‘in’ a la position ‘position’. Il en résulte ‘part1′ et ‘part2′
Composition
Filigram(document(in),document(filigram),number(opacity)->document(out)
Superpose ‘filigrame’ à ‘in’ avec une opacité de ‘opacity’
WhitePage(->document(out))
Génère une page blanche
Template(document(main),dimension**(template)->document(out))
Réagence les pages d’un document
Type
StringToBoolean(string(in)->boolean(out))
Not(boolean(in)->boolean(out))
LessThan(number(arg1),number(arg2)->boolean(out))
Equal(number(arg1),number(arg2)->boolean(out))
…
Avancé
ApplyFunction(document(in),function(fnc(document->document))->document(out))
Applique ‘fnc’ à ‘in’
Setting(boolean(rectoVerso),boolean(draft))
Évidement, on peut imaginer beaucoup d’autre fonctions
Exécution
Un script est donc la description d’un ensemble de node exerçant une fonction et reliés entre eux par les paramètres d’entrée et de sortie, formant ainsi un espèce de graphe.
Lors de l’exécution, les nœuds de sorties (ceux de la catégorie « out ») sont identifiés. Toute le processus d’exécution va consister à calculer ces nœuds en résolvant récursivement leur paramètres d’entrés, et donc les nœuds correspondant. Ces nœuds sont identifié puis calculé un par un dans l’ordre adéquate. Enfin, les sorties sont générés (fichier, affichage, …).
Interface graphique
Le but de ce système est de simplifier d’impression. Taper des scripts ne simplifie la vie qu’a une petite partie d’utilisateur. L’interface que je trouverais la plus adéquate à ce genre de travail est une interface ressemblante au « node editor » de Blender.

Le principe est de représenter chaque node du script par une boite. Sur de coté gauche de la boite des symboles représentent les paramètres d’entrée, et sur la droite, les paramètres de sortie. Des lignes indiquent les relations entre les différents node et une symbolique permet de différencier les différents types.
Les fonctions composés de plusieurs fonctions native peuvent être représenté comme une seule boite, plus ou moins détaillée :

Les liens se créent par simples glissez-déplacer et l’ajout de node par des menus.
Ligne de commande
Il peut être parfois intéressant d’automatiser la production de document. La CLI (command line interface) permet d’exécuter un script développé indépendamment.
Voici un exemple d’utilisation de la CLI :
print2print -i script.print toto.pdf
Le fichier script.print est un format XML dont un exemple de syntaxe est décrit dans la partie « Exemples ».
Lors de l’utilisation de la CLI, les fonctions « InCin » , « InArgument » et « OutCout » peuvent devenir utiles.
Exemples
Représentation XML de la fonction Insert
Voici un exemple de la manière de coder la fonction Insert(document(main), document(docToInsert), number(position)=docToInsert.nb_page-> document(out))
-
<function>
-
<name>Insert</name>
-
<input_list>
-
<input>
-
<name>main</name>
-
<type>document</document>
-
</input>
-
<input>
-
<name>docToInsert</name>
-
<type>document</document>
-
</input>
-
<input>
-
<name>position</name>
-
<type>integer</document>
-
<default>docToInsert.nb_page</default>
-
</input>
-
</input_list>
-
<output_list>
-
<output>
-
<name>out</name>
-
<type>document</document>
-
<process>concatSecondPart.out</process>
-
<disable>main</disable>
-
</output>
-
</output_list>
-
<nodes>
-
<node name="firstPart">
-
<function>SubDocument</function>
-
<input name="main">main</input>
-
<input name="sequence">"0-"+position</input>
-
</node>
-
<node name="secondPart">
-
<function>SubDocument</function>
-
<input name="main">main</input>
-
<input name="sequence">position+"-"+main.nbPage</input>
-
</node>
-
<node name="concatFirstPart">
-
<function>Concat</function>
-
<input name="doc1">first.out</input>
-
<input name="doc2">docToInsert</input>
-
</node>
-
<node name="concatSecondPart">
-
<function>Concat</function>
-
<input name="doc1">concatFirstPart.out</input>
-
<input name="doc2">secondPart.out</input>
-
</node>
-
</nodes>
-
</function>
(notez que cette fonction aurait pu être plus simple avec l’utilisation de ‘Split’ apparu après)
Exemple d’impression brouillon, recto verso, 2 par pages,noir et blanc du fichier test.pdft
Logique
-
input = InFile(file("test.pdf"))
-
pdf_file = FileToPdf(in(input.out))
-
noirEtBlanc = BlackAndWhite(in(pdf_file.out))
-
deuxParPages = Template(main(noirEtBlanc.out),
-
template("((100%,0%),(100%,50%),(0%,50%),(0%,0%))"
-
"((100%,50%),(100%,100%),(0%,100%),(0%,50%))"))
-
rectoVerso = Setting(rectoVerso(true));
-
draft = Setting(draft(true));
-
OutPrint(main(deuxParPages.out), setting(rectoVerso,draft))
XML
-
-
<print>
-
<node name="input">
-
<function>InFile</function>
-
<input name="file">"test.pdf"</input>
-
</node>
-
<node name="pdf_file">
-
<function>FileToPdf</function>
-
<input name="in">input.out</input>
-
</node>
-
<node name="noirEtBlanc">
-
<function>BlackAndWhite</function>
-
<input name="in">pdf_file.out</input>
-
</node>
-
<node name="deuxParPages">
-
<function>Template</function>
-
<input name="main">noirEtBlanc.out</input>
-
<input name="template">
-
"((100%,0%),(100%,50%),(0%,50%),(0%,0%))"
-
"((100%,50%),(100%,100%),(0%,100%),(0%,50%))"
-
</input>
-
</node>
-
-
<node name="rectoVerso">
-
<function>Setting</function>
-
<input name="rectoVerso">true</input>
-
</node>
-
<node name="draft">
-
<function>Setting</function>
-
<input name="draft">true</input>
-
</node>
-
-
<node>
-
<function>OutPrint</function>
-
<input name="main">deuxParPages.out</input>
-
<input name="setting">rectoVerso.out,draft.out</input>
-
</node>
-
</print>
-
Axes d’améliorations
Conditions
Pour plus de flexibilité, il peut être interessant d’avoir des conditions. Voici un mécanisme permetant de les implémeter :
Chaque fonction a automatiquement un paramètre d’entrée « boolean(enable) » qui permet de désactiver ou activer une fonction. Une fonction désactivée va essayer de retourner l’identité. (la fonction codée ci dessus en exemple retourne ‘main’ si elle est désactivée)
Par exemple voici de fonction en code logique pour passer le document en noir et blanc si le document fait plus de 5 pages, sinon, le passer en sepia :
-
SepiaOrNB(document(in)->document(out)=in)
-
{
-
moreThanFive = TestSup(in.nb_pages,5)
-
sepia = Sepia(in(in),enable(!moreThanFive))
-
out = BlackAndWhite(in(sepia),enable(moreThanFive))
-
}
-
Boucles
Les boucles peuvent être utiles pour effectuer des tache répetitive. Malheuresement, le modéle en node actuel permet difficilement d’ajouter simplement la notion de boucle.
Une des manières de procéder est que le cœur d’analyse mette à disposition une fonction For(number(from),number(to)->number(i)). Cette fonction aurait un effet très spécial sur le graphe. Rien de mieux qu’un petit exemple :
-
-
docComplet = GetDoc() // Document de 10 pages
-
for = For(from(0),to(docComplet.out.nb_pages))
-
file = FileFromJpg(in(page.out),page(for.i))
-
OutFile(in(file.out),path("image"+for.i+".jpg")
-
Ce code permet de génerer 1 image jpeg par page du document de base. En fait, le cœur d’analyse va creer 10 instance de la branche d’arbre qui à pour dépendance le for, chacune ayant une valeur différente pour « for.i ».
Parfois, la boucle peut avoir un intérêt autre part qu’a la sortie du processus comme sur le script ci-dessus. Dans ce cas, on veut pouvoir éviter que tout l’arbre soit dupliqué. Pour cela, il faut ajouter la fonction du cœur « JoinDocument(document(in),number(position)->document(out)) » qui va permettre de fusionner toutes les instances liés à ce nœud.
-
-
for = For(from(5),to(10)) /
-
file = InFile("plop"+for.i+".jpg")
-
page = FileToJpeg(in(file.out))
-
document = JoinDocument(in(page.out),position(for.i))
-
out_file = FileFromPdf(in(document.out))
-
OutFile(in(file.out),path("plop.pdf")
-
Dans ce script, les 3 premiers nœud sont évalué 5 fois mais le nœud « document » et les suivants ne le sont qu’une seul fois : la fonction « JointDocument » a fusionné les 5 instances de « page » en un seul document.
Ce support des boucles est primitif et ne fonctionne pas forcement, mais propose une idée. Pour un usage plus confortable il faut surement implémenter d’autres fonctions de type Join
Conclusion
Un système de ce type pourrait être bien sympa pour produire des documents. Malheureusement, pour l’utiliser, il faut l’implémenter et je n’en ai pas le temps. Mais si un développeur qui s’enuis ce lance dans cette périlleuse tâche (via un projet open source bien sûr), je serais ravi de l’aider.
PS : Cet article est trop long pour que j’ai le courage de le relire. Si certaines fautes vous éborgnent, n’hésitez pas à m’en faire part.