Le but de ce TD est de se familiariser avec la notion d'héritage. Nous allons construire progressivement des classes organisées selon l'arbre d'héritage illustré ci-dessous :
Ces classes représentent des éléments de typographie.
Pour les travaux dirigés du cours INF431, il est recommandé d'utiliser l'environnement de programmation Eclipse. Pour commencer, il faut définir un espace de travail (ou workspace), nommé par exemple INF431. Dans cet espace de travail, on va créer un projet pour chaque séance de TD, aujourd'hui ce sera par exemple TD2.
Une boîte est caractérisée par trois grandeurs : sa largeur, sa hauteur au dessus de la ligne de base (la ligne sur laquelle on écrit le texte) et sa profondeur en dessous de cette même ligne de base. En notant width, ascent et descent ces trois grandeurs, on peut schématiser ainsi une boîte typographique (la ligne de base étant représentée en pointillés) :
Pour une boîte représentant un espace étirable, width représente la longueur minimale de l'espace et une autre grandeur, stretchingCapacity, indique la possibilité, plus ou moins grande, d'étirer cet espace.
Les longueurs width, ascent et descent sont exprimées en points et stretchingCapacity est un coefficient. Toutes ces grandeurs seront des flottants (type double).
Comme point de départ, on se donne une classe abstraite Box, vide pour l'instant :
package typo; import java.awt.Color; import java.awt.Graphics; public abstract class Box { // vide pour l'instant }
package typo; import java.awt.Font; public class Glyph extends Box { final private static FontRenderContext frc = new FontRenderContext(null, false, false); final private Font font; final private char[] chars; final private Rectangle2D bounds; // classe à compléter public String toString() { return "Glyph(" + chars[0] + ")" + "[mW=" + getMinimalWidth() + ",a=" + getAscent() + ",d=" + getDescent() + ",sC=" + getStretchingCapacity() + "]"; } }Pour compléter la classe Glyph :
TextLayout layout = new TextLayout(""+chars[0], font, frc); bounds = layout.getBounds();
// ascent = - bounds.getY() // descent = bounds.getHeight() + bounds.getY() // width = bounds.getWidth();
static void test1() { Font f = new Font("SansSerif", Font.PLAIN, 70); Glyph g = new Glyph(f, 'g'); System.out.println(g); }qui doit donner la sortie suivante (les valeurs peuvent légèrement différer) :
Glyph(g)[mW=33.53125,a=37.9375,d=14.328125,sC=0.0]Déposer ici votre fichier Glyph.java :
final static boolean debug = false; public final void draw(Graphics g, double x, double y, double w) { if (debug) { g.setColor(Color.red); g.drawRect((int) x, (int) y, (int) w, (int) (getAscent() + getDescent())); g.setColor(Color.black); } doDraw(g, x, y, w); }
static void test2() { Font f = new Font("SansSerif", Font.PLAIN, 70); Glyph g = new Glyph(f, 'g'); System.out.println(g); new Page(g, 150, 150); }qui doit donner la même sortie que précédemment et l'image suivante :
Déposer ici votre fichier Box.java :
Déposer ici votre fichier Glyph.java :
Simplifier ensuite le code de la méthode toString de Glyph en utilisant super.toString.
On pourra réutiliser test2 pour tester.
Déposer ici votre fichier Box.java :
Déposer ici votre fichier Glyph.java :
Définir ensuite deux sous-classes FixedSpace et RelativeSpace de Space, représentant respectivement un espace non étirable et un espace proportionnel à la taille d'une police de caractères. Le constructeur de FixedSpace prendra une dimension de type double en argument. Le constructeur de RelativeSpace prendra un coefficient c de type double et une police de caractères f de type Font, pour construire un espace de dimension c * f.getSize() et de capacité d'étirement 1. Ces deux constructeurs feront appel au constructeur de la classe Space, avec la syntaxe super(...);. On ne demande pas de redéfinir toString dans ces deux classes.
Écrire une méthode test4 dans la classe Test, qui construit trois objets dans les classes Space, FixedSpace et RelativeSpace, et les affiche avec System.out.println. On doit obtenir quelque chose comme
Space[mW=2.0,a=0.0,d=0.0,sC=3.0] Space[mW=5.0,a=0.0,d=0.0,sC=0.0] Space[mW=35.0,a=0.0,d=0.0,sC=1.0]
Déposer ici votre fichier Space.java :
Déposer ici votre fichier FixedSpace.java :
Déposer ici votre fichier RelativeSpace.java :
Déposer ici votre fichier Test.java :
protected final LinkedList<Box> list = new LinkedList<Box>();Écrire pour les objets de la classe Group une méthode add qui permet d'ajouter une nouvelle boîte à la fin de leur liste.
Note : à ce point, on ne peut pas encore calculer les valeurs renvoyées par les méthodes getMinimalWidth, getAscent, getDescent et getStretchingCapacity. Plutôt que les laisser encore abstraites, on va écrire des méthodes qui renvoient les valeurs mémorisées dans quatre champs ascent, descent, minimalWidth et stretchingCapacity (tous protected), qui seront affectés depuis les sous-classes.
Redéfinir la méthode toString pour afficher le groupe sous la forme
[mw=...]{ boite 1, ... boite n, }On rappelle qu'on peut parcourir les éléments de la liste list avec la syntaxe for (Box b: list) .... On rappelle également qu'on peut insérer un retour-chariot dans une chaîne de caractères avec la syntaxe \n.
Écrire une méthode test5 dans la classe Test qui produit quelque chose comme
[mW=0.0,a=0.0,d=0.0,sC=0.0]{ Space[mW=2.0,a=0.0,d=0.0,sC=3.0], Space[mW=5.0,a=0.0,d=0.0,sC=0.0], Space[mW=35.0,a=0.0,d=0.0,sC=1.0], }
Déposer ici vos fichiers Group.java et Test.java :
Note : On peut choisir de faire de la classe Group une classe abstraite (à ce point, on ne sait pas encore comment un groupe doit être dessiné). Dans ce cas, il faut déclarer doDraw comme abstraite et supprimer le test5 qui ne servait qu'à tester la méthode toString et qu'il n'est plus possible d'écrire maintenant.
On définit la capacité d'étirement de la boîte comme la somme des capacités d'étirement de ses composantes.
Redéfinir la méthode add pour qu'elle remplisse la liste avec super.add et mette à jour les quatre champs ascent, descent, minimalWidth et stretchingCapacity.
Écrire la méthode doDraw. Le dessin d'une boîte horizontale (méthode doDraw) se fait de la manière suivante. Le dernier paramètre de doDraw, w, spécifie la largeur désirée. La largeur minimale est obtenue par getMinimalWidth ; appelons-la mw. Si mw>w alors la boîte ne peut pas tenir dans la largeur w, ce que l'on signale par un message sur la console, tout en la dessinant tout de même (sur une largeur mw). Si en revanche w>=mw alors on va répartir la différence w-mw sur tous les espaces étirables contenus à l'intérieur de la boîte horizontale, proportionnellement à la capacité d'étirement de chacun. Si par exemple la boîte contient deux espaces de capacités 1 et 2 respectivement, alors un tiers de l'espace supplémentaire sera donné au premier et deux tiers au second. Comme on a justement attribué aux glyphes une capacité d'étirement 0, le traitement peut être fait de manière uniforme, sans avoir à connaître la nature de chaque composante.
Redéfinir la méthode toString pour indiquer qu'il s'agit d'une boîte horizontale.
On pourra tester avec le code suivant
static void test6a() { Hbox h = new Hbox(); System.out.println(h); Font f = new Font("SansSerif", Font.PLAIN, 40); h.add(new Glyph(f, 'a')); System.out.println(h); h.add(new Space(2., 3.)); System.out.println(h); }qui doit donner une sortie de la forme
Hbox[mW=0.0,a=0.0,d=0.0,sC=0.0]{ } Hbox[mW=18.46875,a=22.40625,d=0.5625,sC=0.0]{ Glyph(a)[mW=18.46875,a=22.40625,d=0.5625,sC=0.0], } Hbox[mW=20.46875,a=22.40625,d=0.5625,sC=3.0]{ Glyph(a)[mW=18.46875,a=22.40625,d=0.5625,sC=0.0], Space[mW=2.0,a=0.0,d=0.0,sC=3.0], }
On pourra tester ensuite avec le code suivant
static Hbox lineFromString(Font f, String s) { Hbox line = new Hbox(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c == ' ') line.add(new RelativeSpace(0.5, f)); else { line.add(new Glyph(f, c)); if (i < s.length()-1) line.add(new FixedSpace(2)); } } return line; } static void test6b() { Font f = new Font("SansSerif", Font.PLAIN, 40); Box t = lineFromString(f, "Typographie sans peine"); System.out.println(t); new Page(t, 450, 150); }qui doit donner une sortie de la forme
Hbox[mW=410.046875,a=30.84375,d=8.1875,sC=2.0]{ Glyph(T)[mW=24.609375,a=28.90625,d=0.0,sC=0.0], Space[mW=2.0,a=0.0,d=0.0,sC=0.0], ...et le résultat suivant :
Ce dernier test peut révéler des erreurs d'arithmétique :
static void test6c() { Font f = new Font("SansSerif", Font.PLAIN, 40); Box t = lineFromString(f, "Test"); System.out.println(t); new Page(t, 450, 150); }
Déposer ici votre fichier Hbox.java :
On définit la capacité d'étirement de la boîte verticale comme le maximum des capacités d'étirement de ses composantes.
Comme pour Hbox, redéfinir la méthode add pour qu'elle remplisse la liste avec super.add et mette à jour les quatre champs ascent, descent, minimalWidth et stretchingCapacity.
Écrire la méthode doDraw. Le dessin d'une boîte verticale (méthode doDraw) se fait tout simplement en dessinant les boîtes les unes au dessus des autres, en partant du haut et en espaçant les boîtes de la dimension indiquée par lineSkip. On rappelle que les coordonnées Y croissent vers le bas.
Redéfinir la méthode toString pour indiquer qu'il s'agit d'une boîte verticale.
On pourra tester avec le code suivant
final static Box hfill = new Space(0, Double.POSITIVE_INFINITY); static Vbox fromString(Font f, String s) { Vbox text = new Vbox(5); int len = s.length(); for (int i = 0; i < len; ) { int idx = s.indexOf('\n', i); if (idx == -1) idx = len; Hbox line = lineFromString(f, s.substring(i, idx)); if (idx == len) line.add(hfill); // ajoute un ressort infini à la fin de la dernière ligne text.add(line); i = idx+1; } return text; } static void test7a() { Font f = new Font("SansSerif", Font.PLAIN, 40); Box t = fromString(f, "L'homme n'est qu'un\nroseau, le plus faible\nde la nature ; mais\nc'est un roseau pensant."); new Page(t, 450); }qui doit donner le résultat suivant :
static void test7b() { Font f = new Font("SansSerif", Font.PLAIN, 30); Font lettrinef = new Font("SansSerif", Font.PLAIN, 120); Vbox t = new Vbox(5); Hbox h = new Hbox(); h.add(new Glyph(lettrinef, 'L')); h.add(new Space(3, 1)); Vbox l = new Vbox(5); l.add(lineFromString(f, "'homme n'est qu'un roseau, le")); l.add(lineFromString(f, "plus faible de la nature ; mais")); l.add(lineFromString(f, "c'est un roseau pensant. Il ne")); h.add(l); t.add(h); t.add(lineFromString(f, "faut pas que l'univers entier s'arme")); t.add(lineFromString(f, "pour l'écraser : une vapeur, une")); t.add(fromString(f, "goutte d'eau, suffit pour le tuer.")); new Page(t, 500); }qui doit ressembler à ceci :
Déposer ici votre fichier Vbox.java :