23-01-2007, 03:03 PM
Yep, c'est pas mal de calcul pour trouver la formule à utiliser pour calculer la position des points ^^ Mais c'est amusant (enfin je trouve :-p)
Sinon, voici la version 0.2b des classes, qui sont maintenant au nombre de trois. Cette nouvelle version apporte pas mal de chose, notement la correction de plusieurs bugs ou erreurs de code (non-utilisation de la fonction addBalise(), par exemple, et un gros bug dans la fermeture des balises qui se déclarait pour les balises qui ne se fermaient pas seules), un nouveau moyen de déclarer les styles de base, à priori plus simple, et surtout la possibilité d'ajouter du texte ! Cela peut se faire de trois manières : en commentaire dans le fichier svg, en tant que texte affiché (le svg 1.1 ne gère pas nativement le texte sur plusieurs lignes, mais la classe corrige de manière transparente le problème : il suffit de séparer les lignes par un classique \n et la classe se charge du reste) ou en tant que titre (à mon grand regret, Firefox ne gère pas cette fonctionnalité...). En vrac, j'ai aussi ajouté la gestion des <defs> et plus particulierement des dégradés, qui peuvent maintenant assez facilement être créés par la classe.
Voici donc le code :
Sinon, voici la version 0.2b des classes, qui sont maintenant au nombre de trois. Cette nouvelle version apporte pas mal de chose, notement la correction de plusieurs bugs ou erreurs de code (non-utilisation de la fonction addBalise(), par exemple, et un gros bug dans la fermeture des balises qui se déclarait pour les balises qui ne se fermaient pas seules), un nouveau moyen de déclarer les styles de base, à priori plus simple, et surtout la possibilité d'ajouter du texte ! Cela peut se faire de trois manières : en commentaire dans le fichier svg, en tant que texte affiché (le svg 1.1 ne gère pas nativement le texte sur plusieurs lignes, mais la classe corrige de manière transparente le problème : il suffit de séparer les lignes par un classique \n et la classe se charge du reste) ou en tant que titre (à mon grand regret, Firefox ne gère pas cette fonctionnalité...). En vrac, j'ai aussi ajouté la gestion des <defs> et plus particulierement des dégradés, qui peuvent maintenant assez facilement être créés par la classe.
Voici donc le code :
Code PHP :
<?php
class svg_drawer
{
private $last = 0;
private $balises = array();
public function __construct($height, $width)
{
//On créé la balise SVG
$this->addBalise('svg', true);
//On ajoute les attributs
$this->set_attr('xmlns', 'http://www.w3.org/2000/svg');
$this->set_attr('version', '1.1');
$this->set_attr('width', $width);
$this->set_attr('height', $height);
//On passe à la balise suivante
$this->nextBalise();
}
public function set_attr($attribut, $valeure, $separateur = ' ', $balise = false)
{
//On assigne éventuellement une valeure à $balise
if($balise === false)
{
$balise = $this->last;
}
//On vérifie qu'une balise soit bien ouverte
if(!is_object($this->balises[$balise]))
{
return;
}
//On assigne un attribut
$this->balises[$balise]->extendAttr($attribut, $valeure, $separateur);
}
public function resetAttr($attribut, $balise = false)
{
//On assigne éventuellement une valeure à $balise
if($balise === false)
{
$balise = $this->last;
}
//On remet l'attribut à 0
$this->balises[$balise]->addAttr($attribut, '');
}
public function addStyle($propriete, $valeur, $balise = false)
{
//On assigne éventuellement une valeure à $balise
if($balise === false)
{
$balise = $this->last;
}
//On ajoute le style
$this->balises[$balise]->addStyle($propriete, $valeur);
}
public function addBalise($baliseName, $toClose)
{
//On vérifie qu'il n'y ai pas une balise ouverte
if(isset($this->balises[$this->last]))
{
$this->closeBalise();
}
//On ajoute la balise
$this->balises[$this->last] = new balise($baliseName, $toClose);
}
public function addText($texte, $type)
{
//On vérifie qu'il n'y ai pas une balise ouverte
if(isset($this->balises[$this->last]))
{
$this->closeBalise();
}
//On ajoute la balise
$this->balises[$this->last] = new texte($texte, $type);
}
public function closeBalise()
{
if(isset($this->balises[$this->last]))
{
$this->balises[$this->last + 1] = 'CLOSE';
$this->last += 2;
} else {
$this->balises[$this->last] = 'CLOSE';
$this->last += 1;
}
}
public function nextBalise()
{
$this->last++;
}
public function drawRect($x, $y, $height, $width, $rx = 0, $ry = 0)
{
//On créé la balise
$this->addBalise('rect', false);
//On ajoute les attributs
$this->set_attr('x', $x);
$this->set_attr('y', $y);
$this->set_attr('height', $height);
$this->set_attr('width', $width);
//On vérifie pour les coins arrondis
if($rx != 0 || $ry != 0)
{
//On en met un
$this->set_attr('rx', $rx);
//Si les deux attributs sont égaux, inutile de mettre l'autre
if($rx != $ry)
{
$this->set_attr('ry', $ry);
}
}
//On récupère le numéro de la balise
$num = $this->last;
//On ferme la balise (on pourra toujours y ajouter des attributs, et elle n'est pas censée contenir d'autres balises)
$this->closeBalise();
//On renvoi le numéro
return $num;
}
public function drawCarre($x, $y, $cote, $rx = 0, $ry = 0)
{
//On fait un rectangle particulier
return $this->drawRect($x, $y, $cote, $cote, $rx, $ry);
}
public function drawEllipse($x, $y, $rx, $ry)
{
//On créé la balise
$this->addBalise('ellipse', false);
//On ajoute les attributs
$this->set_attr('cx', $x);
$this->set_attr('cy', $y);
$this->set_attr('rx', $rx);
$this->set_attr('ry', $ry);
//On récupère le numéro de la balise
$num = $this->last;
//On ferme la balise (on pourra toujours y ajouter des attributs, et elle n'est pas censée contenir d'autres balises)
$this->closeBalise();
//On renvoi le numéro
return $num;
}
public function drawCercle($x, $y, $r)
{
//On créé la balise
$this->addBalise('circle', false);
//On ajoute les attributs
$this->set_attr('cx', $x);
$this->set_attr('cy', $y);
$this->set_attr('r', $r);
//On récupère le numéro de la balise
$num = $this->last;
//On ferme la balise (on pourra toujours y ajouter des attributs, et elle n'est pas censée contenir d'autres balises)
$this->closeBalise();
//On renvoi le numéro
return $num;
}
public function drawLigne($x1, $y1, $x2, $y2, $couleur)
{
//On créé la balise
$this->addBalise('line', false);
//On ajoute les attributs
$this->set_attr('x1', $x1);
$this->set_attr('y1', $y1);
$this->set_attr('x2', $x2);
$this->set_attr('y2', $y2);
//On ajoute ensuite la couleure
$this->addStyle('stroke', $couleur);
//On récupère le numéro de la balise
$num = $this->last;
//On ferme la balise (on pourra toujours y ajouter des attributs, et elle n'est pas censée contenir d'autres balises)
$this->closeBalise();
//On renvoi le numéro
return $num;
}
public function drawPolyline()
{
//On fait juste une balise ici
$this->addBalise('polyline', false);
//On récupère le numéro de la balise
$num = $this->last;
//On ferme la balise (on pourra toujours y ajouter des attributs, et elle n'est pas censée contenir d'autres balises)
$this->closeBalise();
//On renvoi le numéro
return $num;
}
public function drawPolygone()
{
//On fait juste une balise ici
$this->addBalise('polygon', false);
//On récupère le numéro de la balise
$num = $this->last;
//On ferme la balise (on pourra toujours y ajouter des attributs, et elle n'est pas censée contenir d'autres balises)
$this->closeBalise();
//On renvoi le numéro
return $num;
}
public function addPoint($x, $y, $balise)
{
//On ajoute le point à la balise
$this->set_attr('points', $x . ',' . $y, ' ', $balise);
}
public function drawBox($x, $y, $height, $width, $hauteur, $angle, $ligneCouleur)
{
//Cette figure est assez compliquée.
//En gros, c'est un polygone complexe, dans lequel on ajoute des lignes.
//Donc on va calculer la position des différents points. Les plus simples sont ceux de base : ceux du rectangle de base.
$point1 = array($x, $y); // Le point d'origine.
$point2 = array(($x + $width), $y); //Le second point, en haut à droite.
$point3 = array(($x + $width), ($y + $height)); //Le troisième point, en bas à droite.
$point4 = array($x, ($y + $height)); //Le troisième point, en bas à gauche.
//Maintenant un peu plus compliqué. Il nous faut calculer les trois points supplémentaires.
//Le plus simple à calculer est celui en haut à gauche : on utilise le théorème d'Al-Kashi pour calculer ses coordonnées.
$point5 = array(round(($x - ($hauteur * cos(deg2rad($angle))))), round(($y - ($hauteur * cos(deg2rad(90 - $angle)))))); //En haut à gauche
//Puis les autres sur cette base
$point6 = array(round(($x - ($hauteur * cos(deg2rad($angle))))), round((($y - ($hauteur * cos(deg2rad(90 - $angle)))) + $height))); //En bas à gauche
$point7 = array(round((($x - ($hauteur * cos(deg2rad($angle)))) + $width)), round(($y - ($hauteur * cos(deg2rad(90 - $angle)))))); //En haut à droite
//Maintenant on a tous nos points.
//On va donc les tracer.
//On commence par le polyline
$lstFig = array();
$lstFig[] = $this->drawPolygone();
//On défini son style
$this->addStyle('stroke', $ligneCouleur, $lstFig[0]);
//On ajoute les différents points, dans l'ordre
$this->addPoint($point4[0], $point4[1], $lstFig[0]);
$this->addPoint($point3[0], $point3[1], $lstFig[0]);
$this->addPoint($point2[0], $point2[1], $lstFig[0]);
$this->addPoint($point7[0], $point7[1], $lstFig[0]);
$this->addPoint($point5[0], $point5[1], $lstFig[0]);
$this->addPoint($point6[0], $point6[1], $lstFig[0]);
//On va maintenant tracer les trois lignes qui nous manque pour donner l'impression de 3d
$lstFig[] = $this->drawLigne($point1[0], $point1[1], $point4[0], $point4[1], $ligneCouleur);
$lstFig[] = $this->drawLigne($point1[0], $point1[1], $point2[0], $point2[1], $ligneCouleur);
$lstFig[] = $this->drawLigne($point1[0], $point1[1], $point5[0], $point5[1], $ligneCouleur);
//On va renvoyer l'ensemble des numéros des figures, c'est plus pratique
return $lstFig;
}
public function drawTexte($texte, $fontSize, $x, $y, $interligne = 1)
{
//On commence par regarder si il y a plusieurs lignes de texte
//Note: Cette façon d'afficher plusieurs lignes de texte n'est pas correcte d'un point de vue sémantique, mais moins complexe à coder...
if(strstr($texte, "\n") !== false)
{
//On va séparer les différentes lignes
$texte = explode("\n", $texte);
//On prépare le tableau de résultats
$lstFig = array();
$t = 0;
//On ajoute chaque ligne de texte
foreach($texte as $phrase)
{
$lstFig[] = $this->drawTexte($phrase, $fontSize, $x, ($y + ($t * $fontSize) + $interligne));
$t++;
}
return $lstFig;
} else {
//On ouvre une balise de texte
$this->addBalise('text', true);
//On lui donne les propriétés X et Y
$this->set_attr('x', $x);
$this->set_attr('y', $y);
//Et on ajoute le style de caractère
$this->addStyle('font-size', $fontSize . 'px');
//Puis on récupère le numéro
$num = $this->last;
//On passe à la balise suivante
$this->nextBalise();
//On ajoute ensuite la pseudo-balise de texte.
$this->addText(htmlentities($texte), texte::CDATA);
//On ferme les deux balises
$this->closeBalise();
$this->closeBalise();
//On renvoi le résultat
//On ne renvoi pas le numéro de la pseudo-balise de texte, qui ne peux de toutes façons pas être modifiée
return $num;
}
}
public function addComment($commentaire)
{
//On ajoute simplement un texte de type commentaire
$this->addText($commentaire, texte::COMMENT);
//On récupère le numéro
$num = $this->last;
//On ferme la balise
$this->closeBalise();
//On renvoi le numéro
return $num;
}
public function addDefs()
{
//On ajoute la balise
$this->addBalise('defs', true);
//On récupère le numéro
$num = $this->last;
//On passe à la balise suivante
$this->nextBalise();
//On renvoi le numéro
return $num;
}
public function addTitre($titre)
{
//Attention, si cette fonction est appellée en-dehors d'une balise <defs>, je ne garantie rien...
//On ajoute d'abord une balise titre
$this->addBalise('title', true);
//On passe ensuite à la balise suivante, sans fermer la balise titre
$this->nextBalise();
//On ajoute le texte
$this->addText($titre, texte::LIBRE);
//On ferme les deux balise (la balise <title> et la pseudo-balise de texte)
$this->closeBalise();
$this->closeBalise();
}
public function addLinearGradient($id, $x1, $y1, $x2, $y2)
{
//Attention, si cette fonction est appellée en-dehors d'une balise <defs>, je ne garantie rien...
//On ajoute la balise
$this->addBalise('linearGradient', true);
//On ajoute les cinq attributs
$this->set_attr('id', $id);
$this->set_attr('x1', $x1 . '%');
$this->set_attr('y1', $y1 . '%');
$this->set_attr('x2', $x2 . '%');
$this->set_attr('y2', $y2 . '%');
//On récupère le numéro
$num = $this->last;
//On passe à la balise suivante
$this->nextBalise();
//On renvoi le numéro
return $num;
}
public function addRadialGradient($id, $r, $fx, $fy, $cx, $cy)
{
//Attention, si cette fonction est appellée en-dehors d'une balise <defs>, je ne garantie rien...
//On ajoute la balise
$this->addBalise('radialGradient', true);
//On ajoute les six attributs
$this->set_attr('id', $id);
$this->set_attr('r', $r . '%');
$this->set_attr('fx', $fx . '%');
$this->set_attr('fy', $fy . '%');
$this->set_attr('cx', $cx . '%');
$this->set_attr('cy', $cy . '%');
//On récupère le numéro
$num = $this->last;
//On passe à la balise suivante
$this->nextBalise();
//On renvoi le numéro
return $num;
}
public function addStop($offset, $couleur, $opacity = 1)
{
//A utiliser uniquement dans un dégradé, à priori !
//On vérifie que l'opacité soit entre 0 et 1
//On vérifie que $valeur soit entre 0 et 1
if($opacity < 0.0 || $opacity > 1.0)
{
return;
}
//Contrairement aux dégradés, ces balises sont auto-fermantes.
$this->addBalise('stop', false);
//On ajoute la propriété offset
$this->set_attr('offset', $offset . '%');
//Et les deux propriétés de remplissage passées en style
$this->addStyle('stop-color', $couleur);
$this->addStyle('stop-opacity', $opacity);
//On garde l'id
$num = $this->last;
//On ferme la balise puisqu'elle n'est pas censée contenir autre chose
$this->closeBalise();
//Et on renvoi l'id
return $num;
}
public function styleFill($couleur, $balise = false)
{
//On assigne éventuellement une valeure à $balise
if($balise === false)
{
$balise = $this->last;
}
$this->addStyle('fill', $couleur, $balise);
}
public function styleStroke($couleur, $balise = false)
{
//On assigne éventuellement une valeure à $balise
if($balise === false)
{
$balise = $this->last;
}
$this->addStyle('stroke', $couleur, $balise);
}
public function styleFillOpacity($valeur, $balise = false)
{
//On vérifie que $valeur soit entre 0 et 1
if($valeur < 0.0 || $valeur > 1.0)
{
return;
}
//On assigne éventuellement une valeure à $balise
if($balise === false)
{
$balise = $this->last;
}
$this->addStyle('fill-opacity', $valeur, $balise);
}
public function styleStrokeOpacity($valeur, $balise = false)
{
//On vérifie que $valeur soit entre 0 et 1
if($valeur < 0.0 || $valeur > 1.0)
{
return;
}
//On assigne éventuellement une valeure à $balise
if($balise === false)
{
$balise = $this->last;
}
$this->addStyle('stroke-opacity', $valeur, $balise);
}
public function styleUseID($IDName)
{
return 'url(#' . $IDName . ')';
}
public function save($fileName)
{
//On ouvre le fichier
$fHandle = fopen($fileName, 'w+');
//On écrit d'abord le header
fwrite($fHandle, '<?xml version="1.0" standalone="no"?>' . "\n");
//Le DTD
fwrite($fHandle, '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">' . "\n");
//On ajoute toutes les balises une après l'autre
$closeList = array();
foreach ($this->balises as $objBalise)
{
if(is_object($objBalise))
{
//On doit ecrire une balise
$balise = $objBalise->make();
fwrite($fHandle, $balise . "\n");
$closeList[] = $objBalise->close();
} else {
//On doit fermer une balise
//Pour ça, on récupère la clef du dernier element
$close = end($closeList);
//On ecrit la fermeture
fwrite($fHandle, $close . "\n");
//Et on détruit l'élement
unset($closeList[key($closeList)]);
}
}
//On fini de fermer ce qui reste à fermer
if(count($closeList) > 0)
{
for($t = count($closeList) - 1; $t >= 0; $t--)
{
fwrite($fHandle, $closeList[$t] . "\n");
}
}
//On a fini, on ferme le fichier
fclose($fHandle);
}
}
class balise
{
private $baliseName;
private $toClose;
private $attrList = array();
public function __construct($baliseName, $toClose)
{
$this->baliseName = (string) $baliseName;
$this->toClose = (bool) $toClose;
}
public function addAttr($attribut, $value)
{
$this->attrList[$attribut] = $value;
}
public function extendAttr($attribut, $value, $separateur = ' ')
{
if(isset($this->attrList[$attribut]))
{
$this->attrList[$attribut] .= $separateur . $value;
} else {
$this->addAttr($attribut, $value);
}
}
public function addStyle($propriete, $valeur)
{
$style = $propriete . ': ' . $valeur;
$this->extendAttr('style', $style, ';');
}
public function close()
{
if($this->toClose)
{
return '</' . $this->baliseName . '>';
} else {
return '';
}
}
public function make()
{
$balise = '<' . $this->baliseName;
//On vérifie si il y a des arguements
if(count($this->attrList) > 0)
{
foreach ($this->attrList as $attribut => $valeure)
{
$balise .= ' ' . $attribut . '="' . $valeure . '"';
}
}
//On ferme la balise
$balise .= ($this->toClose) ? '>' : ' />';
//On renvoi le tout
return $balise;
}
}
class texte
{
private $type;
private $value;
const LIBRE = 0;
const CDATA = 1;
const COMMENT = 2;
public function __construct($texte, $type)
{
$this->value = $texte;
$this->type = $type;
}
public function close()
{
return '';
}
public function make()
{
switch ($this->type)
{
case self::LIBRE :
return $this->value;
break;
case self::CDATA :
return '<![CDATA[' . "\n" . $this->value . "\n" . ']]>';
break;
case self::COMMENT :
return '<!-- ' . "\n" . $this->value . "\n" . '-->';
break;
}
}
}
?>
Et un fichier d'exemple (les classes sont supposées contenues dans le fichier classe_svg.php) :
Code PHP :
<?php
include('classe_svg.php');
//On créé un svg
$svg = new svg_drawer(500, 500);
//On y met les définitions
$svg->addDefs();
//On y ajoute un titre
$svg->addTitre('SVG Drawer version 0.2a');
//On ajoute un dégradé
$svg->addLinearGradient('degrade', 0, 0, 100, 100);
//On y met les couleurs
$svg->addStop(0, '#FFFFFF');
$svg->addStop(50, '#FFDDAA');
$svg->addStop(100, '#FFFFFF');
//On ferme le dégradé et les définitions
$svg->closeBalise();
$svg->closeBalise();
//Un petit commentaire pour tester
$svg->addComment('On devrait avoir fermé les defs ici, on passe au dessin');
//On trace un fond
$fond = $svg->drawCarre(0, 0, 500);
//On le rempli de noir
$svg->styleFill('#000000', $fond);
//On ajoute une boite
$lstFig = $svg->drawBox(80, 80, 300, 300, 80, 30, '#000000');
//On lui applique le dégradé
$svg->addStyle('fill', $svg->styleUseID('degrade'), $lstFig[0]);
//On créé un petit rectangle pour y mettre du texte
$texteRect = $svg->drawRect(50, 40, 40, 300, 10, 10);
//On le rempli de blanc transparent
$svg->styleFill('#FFFFFF', $texteRect);
$svg->styleFillOpacity('0.9', $texteRect);
//On y écrit ensuite du texte
$svg->drawTexte('SVG Drawer version 0.2b.' . "\n" . 'Le dessin vectoriel facile.', 15, 55, 55, 5);
//Et on enregistre le tout
$svg->save('monfichier.svg');
//On affiche un gentil lien
echo '<a href="monfichier.svg">Petit exemple</a>';
?>