Thu, 16 Jul
Batik: factorier votre code
Introduction
Dans un précédent article nous avons fait nos premier pas avec Batik en créant quelques
formes assez simples.
Dans le code produit, la gestion du document SVG et son intégration au Java2D est un peu disparate !
C'est donc le moment de factoriser notre code pour nous faciliter un petit peu la création de formes vectorielles.
Quelques rappels
Pour pouvoir créer un graphique 2D vectoriel, nous avons besoin d'un document de base auquel , à travers un
DOM, nous appliquons la DTD
du SVG.
Notre nouveau document ainsi obtenu , nous sert à générer un objet SVGGraphics2D (java2D) surquel nous allons dessiner.
Une fois le dessin terminé, nous devons propager notre document à notre canvas (partie de notre fenêtre d'application sur lequel nous affichons le SVG).
Bien entendu ce canvas est attaché à cette fenêtre d'application (JFrame).
Sans cela celle-ci n'affichera rien du tout ...
En regardant d'un peu plus près, on s'aperçoit rapidement que nous avons besoins de 3 concepts différents :
- une vue
- un document
- un objet java2D
La vue
Qui est notre fenêtre d'application.
Elle comporte différents éléments (objets) tels le canvas et le Panel.
Le document
Qui suit la structure du SVG Dom et qui une fois créé nous sert à propager le dessin vers sa zone d'affichage : le canvas.
L'objet 2D
Notre objet de dessin SVGGraphics2D qui nous permet de travailler directement avec les méhodes java2D.
Il se sert du document qu'on a passé à son constructeur, pour générer automatiquement la structure de rendu.Mais
cela est fait en transparence et ne nous regarde pas ... !
Ce que nous désirons
Notre but est de dessiner sans nous soucier de la manière dont est géré la création du document et de la façon dont il est
passé à la vue.
Ce qui revient à gérer dans notre méthode principale :
- la création d'un objet SVG 2D hérité de java2D
- les méthodes de dessin
- l'affichage de notre dessin
Ce qui revient à un code relativement simple également pour notre class Main :
public class Main{
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
Controller2D myApp = new Controller2D();
myApp.setPaint(Color.green);
Shape rectangle = new Rectangle(0,0,200,200);
myApp.fill(rectangle);
myApp.setPaint(Color.red);
Shape cercle = new Ellipse2D.Double(80,80,50,50);
myApp.fill(cercle);
myApp.setView();
myApp.setVisible();
}
}
Lorsque nous avons notre objet Controller2D , nous utilisons les méthodes offertes par l'API Java2D pour dessiner, puis
nous l'envoyons à la vue pour l'affichage.
Nous rendons notre application visible dans la méthode main, mais cela peut etre fait tout aussi bien dans la classe vue.
L'important ici est que nous n'avons pas à nous soucier dyu SVG ; Cela est fait en transparence.
Les classes que nous allons créer vont s'en occuper pour nous!
Un peu de logique
Ce que nous devons factoriser c'est la gestion du document et le rendu en svg.
A nouveau un petit rappel du code "svg" qui se balladait dans notre ancien code.
....
DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
doc = (SVGDocument) impl.createDocument(SVGDOMImplementation.SVG_NAMESPACE_URI,
"svg",
null);
...
SVG2D = new SVGGraphics2D(doc);
...
Element root = doc.getDocumentElement();
SVG2D.getRoot(root);
canvas.setSVGDocument(doc);
....
Ok ... nous devons donc gérer la création d'un document (class SvgDocument) ; Construire un objet 2D avec comme argument le document (class Controller2D) ; Passer le résultat après dessin à la vue (class View).
Un peu de papier et un crayon pour modéliser tout cela (et je suis vraiment pas expert UML !!!).
Reprenons point à point
Notre application Controller2D est appelée dans la méthode 'main'.
Celle-ci étend SVGGraphics2D pour en profiter un maximum.
On a besoin de passer un un document svg à notre constructeur.Nous utilisons donc la méthode statique
getSvgDocument de l'objet SvgDocument.
Notre controlleur a besoin de passer le résultat à la vue. Nous initialisons donc un objet View dans notre constructeur.
Nous créons une méthode setView qui va passer tout ce petit monde au canvas (rappellez vous du code de
l'article précédent).
Pour retrouver notre Canvas, on a bien sûr créer une methode getCanvas dans notre Objet View.
Nous avons notre code pour le Controller2D :
public class Controller2D extends SVGGraphics2D{
//private SVGGraphics2D svg2d = null;
private View view = null;
public Controller2D(){
super(SvgDocument.getSvgDocument());
this.view = new View();
}
public void setView(){
Element root = SvgDocument.getSvgDocument().getDocumentElement();
getRoot(root);
View.getCanvas().setSVGDocument(SvgDocument.getSvgDocument());
}
public View getView(){
return this.view;
}
public void setVisible(){
this.view.setVisible(true);
}
}
Super(SvgDocument.getSvgDocument())
Nous passons en paramètre au constructeur un objet de type SvgDocument ; Petit rappel de l'ancien code :
"SVG2D = new SVGGraphics2D(doc); ".
Pour comprendre ce que nous faisons , il faut jetter un oeil à l'API de SVGGraphics2D.
Et dans notre cas, c'est le premier "type de constructeur qui nous intéresse.
public SVGGraphics2D(Document domFactory) Parameters: domFactory - Factory which will produce Elements for the DOM tree this Graphics2D generates.
C'est ici le nerf de notre factorisation ; Pour passer en paramètre le bon objet on utilise la méthode statique de notre Objet SvgDocument getSvgDocument(). C'est cette classe qui fait initialiser en transparence le document SVG et le retourner quand on l'appelle (un objet bien discipliné en somme !).
Notre Objet SvgDocument
Le code n'est pas bien difficile. On utilise le pattern Singleton pour retourner l'objet SvgDocument càd retourne l'instance de svgDocument s'il existe sinon crée une instance et retourne le !
public class SvgDocument {
private static SVGDocument svgDocument = null;
public SvgDocument(){
DOMImplementation dom = SVGDOMImplementation.getDOMImplementation();
String svgns = SVGDOMImplementation.SVG_NAMESPACE_URI;
svgDocument = (SVGDocument)dom.createDocument(svgns, "svg", null);
}
public static SVGDocument getSvgDocument(){
if(SvgDocument.svgDocument != null)
return SvgDocument.svgDocument;
else{
SvgDocument svg = new SvgDocument();
return SvgDocument.svgDocument;
}
}
}
De cette façon nous ne travaillons que sur un seul objet SVG à la fois !
Si nous voulons travailler avec 2 documents SVG nous initialiserons alors 2 Objets différents dans notre Main.
La vue
Nontrer le résultat de notre graphique c'est un peu le but de notre code !!!
Pour cela à nouveau rien de bien sorcier puisque nous utilisons les Objets de Java2D.
Le seul problème à résoudre ici c'est de passer au canvas notre document SVG.
Comme c'est notre objet Controller2D qui s'occupe de dessiner notre graphique, c'est à lui qu'incombe le rôle de
passer à la vue le résultat. Il est le seul à savoir quand le dessin est terminé (en plus de vous bien sûr).
Notre vue ne fait donc rien de plus que de créer un élément Jframe et un élément JSVGCanvas qui est notre
espace de rendu.
Le controller2D dessine le graphique et passe le résultat au canvas. Pour cela il doit le récupérer à la classe View.
public class View extends JFrame{
private static JSVGCanvas canvas = null;
private JPanel panel = null;
public View(){
super("myapp");
View.canvas = new JSVGCanvas();
View.canvas.setPreferredSize(new Dimension(400,400));
this.setSize(new Dimension(400,400));
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.panel = new JPanel();
this.panel.add(View.getCanvas());
this.setContentPane(this.panel);
}
public static JSVGCanvas getCanvas(){
return View.canvas;
}
}
C'est la méthode static getCanvas qui nous servira depuis le Controller2D pour récupérer une instance de notre canvas et qui lui passera le résultat à afficher.
Je reviens sur le code de notre Controller2D qui se sert de getCanvas.
public void setView(){
Element root = SvgDocument.getSvgDocument().getDocumentElement();
getRoot(root);
View.getCanvas().setSVGDocument(SvgDocument.getSvgDocument());
}
Nous utilisons à nouveau notre méthode getSvgDocument pour récupérer l'unique instance de l'objet SvgDocument et initialisons
l'objet Element root.
Rien de nouveau non plus ici par rapport à notre ancien code à par que nous l'avons factoriser. L'appel à cette méthode setView va faire
le boulot d'associer la zone de dessin JSVGCanvas et notre document SVG.
Et voilà notre factorisation terminée.
Maintenant vous dessinez directement depuis la méthode main comme si vous travailliez avec un "simple" objet Java2D. Le rendu
se fera automatiquement en SVG.
Pour le fun
public static void main(String[] args) {
// TODO code application logic here
Controller2D myApp = new Controller2D();
myApp.setPaint(new GradientPaint(0, 0, Color.white, 110, 40, Color.black, true));
Shape s1 = new Ellipse2D.Double(10,0,100,100);
//myApp.draw(s1);
//myApp.setPaint(Color.black);
Shape s2 = new Ellipse2D.Double(50,0,100,100);
//myApp.draw(s2);
Shape s3 = new Ellipse2D.Double(70,50,50,50);
myApp.fill(s3);
//myApp.setPaint(Color.black);
Area a1 = new Area(s1);
Area a2 = new Area(s2);
a2.subtract(a1);
myApp.fill(a2);
myApp.setPaint(Color.white);
Shape s4 = new Ellipse2D.Double(70,10,50,50);
myApp.fill(s4);
myApp.setView();
myApp.setVisible();
}
