PHP: una classe per disegnare un orologio analogico

Orologio analogico Questo è un piccolo esercizio per imparare ad utilizzare le funzioni per disegnare con le lbrerieGD di PHP. La classe Analogclock utilizza tali funzioni per generare un’immagine PNG che rappresenta un orologio analogico. Come valori di input vengono passati al costruttore la larghezza dell’immagine, e al metodo display() i valori dell’ora, dei minuti e dei secondi che si desidera rappresentare. Volendo dare un effetto dinamico all’immagine, si può ricorrere ad uno script Javascript che ricarica la pagina, o meglio la sola immagine, ogni secondo, fornendo i parametri di ora, minuti e secondi aggiornati. E’ possibile impostare la classe in modo da escludere dalla visualizzazione la lancetta dei secondi, la visualizzazione digitale e infine, prelevare direttamente l’orario del server. Il confronto fra le due modalità di funzionamento è visibile in questo esempio.

La rappresentazione delle lancette dell’orologio non presenta grosse difficoltà perché le coordinate dei punti di inizio e di fine della linea non sono altro che l’origine del cerchio che rappresenta il quadrante, e, rispettivamente il coseno e il seno dell’angolo della lancetta. Poichè l’origine dell’immagine (il punto x = 0, y = 0) è fissato in alto a sinistra, per prima cosa bisogna traslare la circonferenza di riferimento di Π + Π /2. Il secondo aspetto da tenere presente è che la nostra immagine è un quadrato nel quale è inscritto il cerchio che rappresenta il quadrante dell’orologio. Dunque il raggio della circonferenza è la metà del lato del quadrato.

Vediamo ad esempio il codice per disegnare la lancetta dei secondi:

1
2
3
4
5
6
7
8
9
10
// ....
$delta = M_PI + M_PI / 2 ;  
// 2Π / 60 = Π / 30 !
$delta += (M_PI * 1 / 30) * $seconds ;
// circle centre coordinates: x = radius, y = radius.
$newX0 = $radius + cos($delta) * $radius;
$newY0 = $radius + sin($delta) * $radius ;
// $img is the resourse image and $colorSec is the allocated line color
imageLine($img, $radius, $radius, $newX0, $newY0, $colorSec);
// ....

L’intero codice sorgente della classe è disponibile qui
Il pacchetto completo del file di esempio è scaricabile qui

Vediamo come utilizzare la classe in pratica, questo il codice del file che genera l’immagine PNG con alcune opzioni attivate:

1
<!--?php require_once('analogclock.php'); $myClock = new Analogclock; //client mode: (remove comments): //$actualHour = intval($_GET['hour']); //$actualMin = intval($_GET['min']); //$actualSec = intval($_GET['sec']); //$myClock-&gt;showSeconds(false); //$myClock-&gt;display($actualHour, $actualMin, $actualSec); //server mode: $myClock-&gt;setAutotime(true); $myClock-&gt;display(); ?-->

E questo invece è il codice della pagina html nella quale viene visualizzato l’orologio con un po’ di javascript per ricaricare l’immagine ogni secondo:

<script type="text/javascript">
<!-- Begin function reFresh() { //location.reload(true); var today = new Date(); document.images['myClock'].src = 'test.php?hour=' + today.getHours() + '&amp;min=' + today.getMinutes() + '&amp;sec=' + today.getSeconds(); } // 1 minute = 60000 milliseconds. window.setInterval("reFresh()",1000); // End -->
</script>
<h1>Prova orologio</h1>
<script type="text/javascript">
    var today = new Date();
    document.write("<img src='test.php?hour=" + today.getHours() + "&amp;min=" + today.getMinutes() + "&amp;sec=" + today.getSeconds() + "' name='myClock' alt='Server Clock'/>");      
  </script>

Anche se in modalità server non sarebbe necessario passare al file test.php la querystring con i valori di ora, minuti e secondi (in quanto abbiamo attivato autotime) in realtà questo ci serve a forzare il browser a ricaricare l’immagine invece di prenderla dalla cache.

Conclusioni:

Come ho accennato all’inizio, questo vuole essere soprattutto un esercizio. Alcuni esempi di orologi analogici lato client molto più accattivanti del mio si trovano qui: CoolClock – The Javascript Analog Clock che però utilizzano canvas che non è ancora ben supportato da tutti i browser.
Avendo maggiore dimestichezza con il PHP piuttosto che con Javascript, ho preferito costruire un orologio lato server, che può benissimo funzionare con l’orario del client. Rimangono inoltre tutti gli altri vantaggi di uno script lato server tra i quali quello della piena compatibilità con tutti i browser!

Riferimenti ed approfondimenti:


Mario Spada ha scritto:

Ho leggermente modificato il codice per visualizzare in maniera fluida il movimento della lancetta delle ore. Adesso non si verifica più lo scatto fra un’ora e l’altra, il passaggio è progressivo. Anche il pacchetto per il download è stato aggiornato.
07.07.08 19:28
Peppe ha scritto:

Ciao! Perché nella barra del titolo mi fa il caricamento della pagina? Vorrei eliminarlo…
30.09.08 18:36
Mario Spada ha scritto:

Il caricamento che si nota sulla barra è inevitabile con javascript, perché è necessario ricaricare l’immagine (che è il risultato di uno script php) ogni secondo in modo da rendere efficacemente la lancetta dei secondi. L’unica cosa che mi viene in mente per ridurre l’effetto è quello di utilizzare la tecnologia AJAX.
07.10.08 19:39
Luca ha scritto:

Ciao sto utilizzando l’orologio ma noto che lo sfondo e’ bianco e quadrato c’e’ un modo per farlo diventare un cerchio o in alternativa avere lo sfondo trasparente?
Grazie bella classe ;-)

PHP: un datagrid XHTML valido con paginazione per applicazioni web

Esempio di datagrid
Esempio di datagrid

Questo datagrid è un aggiornamento di quello presentato in questo post. Ho apportato alcune sostanziali modifiche per renderlo più flessibile ed accessibile, pur mantenendo le stesse prerogative che mi ero imposto inizialmente.
La modifica più urgente era quella di implementare un sistema di paginazione. Questo ha comportato inevitabilmente il cambiamento della modalità di passaggio dei dati al costruttore della classe. Mentre prima era sufficiente fornire il riferimento al risultato di una query, adesso è necessario passare il link alla connessione, il nome del database ed infine la query in formato stringa.

Le caratteristiche iniziali, che sono state ulteriormente migliorate, sono:

  • XHTML valido
  • Personalizzabile tramite fogli stile CSS
  • colonna aggiuntiva di link per azioni tipo “modifica”
  • Aspetto gradevole e alternanza di colorazione delle singole righe
  • Compatibile sia con PHP4 che con PHP5

Le nuove caratteristiche:

  • Semplice sistema di paginazione nel <tfoot> della tabella. Il numero di record per pagina da visualizzare vengono impostati come parametro del costruttore, default=0
  • Autoriconoscimento della o delle chiave primarie. Nel caso in cui l’autoriconoscimento fallisca, è possibile impostarle manualmente come parametro del costruttore
  • Il link nella colonna aggiuntiva può contenere una querystring, in questo caso il link alla chiave primaria viene appeso con “&” altrimenti con “?”
  • Possibilità di utilizzare il datagrid in una pagina che già contiene una querystring. In questo caso è necessario passare la querystring esistente come parametro al costruttore.
  • Possibilità di impostare numerose proprietà dei tag HTML

I parametri del costruttore:

  1. $link (obbligatorio) – riferimento alla connessione al db
  2. $db (obbligatorio) – stringa con il nome del database
  3. $query (obbligatorio) – stringa contentente la query di tipo SELECT
  4. $recPerPage=0 – Numero di record per pagina. Default=0 (tutti i record senza paginazione)
  5. $pk = NULL – Nome della chiave primaria (se più di una, separate da virgola) da immettere manualmente, se NULL (default) il riconoscimento è automatico
  6. $qs = NULL – Stringa contenente l’eventuale querystring associata alla pagina in cui viene pubblicato il datagrid.
Elenco dei metodi:
NomeTipoDefaultNote
setPkIsVisible()booleanfalseDetermina se le colonne relative alla chiave primaria sono visibili o no
setLinkPage()stringNULLImposta il link alla pagina da richiamare per le modifiche (Riferimento alla chiave primaria). Se NULL, la colonna aggiunitva non viene visualizzata.
setLinkLabel()string“Edit”Imposta il testo da visualizzare nella colonna dei link. Può contenere codice HTML per caricare un’icona.
setLinkTitle()string“Edit”Imposta il titolo del tag <a> nella colonna dei link.
setPrev()string“prev”Imposta il testo da visualizzare per la paginazione indietro. Può contenere codice HTML per caricare un’icona.
setNext()string“next”Imposta il testo da visualizzare per la paginazione in avanti. Può contenere codice HTML per caricare un’icona.
setSummaryTable()string“Data table”Imposta il testo per la descrizione “summary” della tabella.
setCaptionTable()string“Table”Imposta il testo per l’intestazione della tabella.
makeTable()Stampa la tabella

Un esempio di utilizzo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Database connection params
$host = 'localhost';
$user = 'root';
$password = '';
$database = 'prova';
$query = 'select * from es_libri';
// open the connection to the db server
$link = mysql_connect($host,$user,$password);
include ('datagrid.class.php');
// datagrid class with some options
$mydatagrid = &amp; new datagrid($link,$database,$query,3); // 3 rec per page
$mydatagrid-&gt;setLinkPage("test.php");
$mydatagrid-&gt;setLinkLabel("<img src="./mod.gif" alt="edit" />");
$mydatagrid-&gt;setLinkTitle("edit");
$mydatagrid-&gt;setPrev("<img src="./prev.gif" alt="previous" />");
$mydatagrid-&gt;setNext("<img src="./next.gif" alt="next" />");
$mydatagrid-&gt;setPkIsVisible(true);
$mydatagrid-&gt;setSummaryTable("Elenco dei libri in biblioteca del settore informatica");
$mydatagrid-&gt;setCaptionTable("Elenco libri informatica");
$mydatagrid-&gt;makeTable();

Per vedere l’esempio, cliccare qui.
Questo, invece è il pacchetto contenente la classe, il file di esempio, il foglio stile, le gif e l’sql per la creazione della tabella “es_libri”, (da creare in un db chiamato “prova”).

Note:

Per quanto abbia fatto diverse prove, non posso dire di aver coperto tutti i possibili errori, fate qualche prova prima di metterlo in produzione!.

Conclusioni:

Per il futuro mi riprometto di aggiungere la caratteristica di autoriconoscimento dei tipi dei campi e la formattazione automatica con allineamento a destra per i numeri

Riferimenti ed approfondimenti:

P4A3 framework: un helper per pulire la directory tmp in ambiente Windows

P4A 3 Logo

Ho scritto questo piccolo “helper” per ovviare ad un piccolo inconveniente che avviene con P4A3, esclusivamente in ambiente Windows. Succede che, se viene utilizzata la funzione: P4A_Output_File(), ad esempio per generare un file pdf, il file temporaneo non viene più cancellato. Ho potuto verificare che in ambiente Linux questo non succede, e tutti i file generati vengono eliminati una volta passati al browser.

Non sono riuscito a capire esattamente il motivo ma credo che il problema risieda ancora una volta nel differente trattamento da parte di Windows dei separatori nei percorsi assoluti dei path (Windows utilizza i \ invece dei /).
Il problema, anche se può risultare fastidioso alla lunga, per l’accumularsi di un eccessivo numero di files nella directory /uploads/tmp, non è di certo grave, e soprattutto, è molto semplice da risolvere. E’ sufficiente infatti lanciare un metodo che ripulisca” la cartella dai file temporanei più vecchi di n ore (o giorni, o minuti).

Questo è il codice della funzione, che per comodità può essere gestito come “helper”, ovvero un metodo personalizzato prontamente disponibile da qualsiasi maschera della nostra applicazione:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function p4a_mask_clean_old_tmp_files($obj, $olderThan)
{
 $fileDeleted = 0;
 $expire_time = empty($olderThan[0]) || !is_numeric($olderThan[0])? 24: $olderThan[0];
 $fileTypes = array("_p4a_*","cache_*");
 if (P4A_OS == 'windows') {
  define('TEMPDIR','\tmp\\');
  $dirToClean =  P4A_UPLOADS_DIR.TEMPDIR;
  foreach ($fileTypes as $fileType){
   foreach (glob($dirToClean . $fileType) as $filename) {
   $fileCreationTime = filectime($filename);
   $fileAge = time() - $fileCreationTime;
    if ($fileAge > ($expire_time * 3600)){
     if (unlink($filename)){
      $fileDeleted++;
     }
    }
   }
  }
 }
 return $fileDeleted;
}

Il file si deve trovare nella directory /libraries all’interno della directory principale della nostra applicazione, e si deve chiamare: p4a_mask_clean_old_tmp_files.php. Il default è impostato su 24 ore.
Per cambiare la scala del tempo trascorso da ore a minuti è necessario cambiare questa riga:
if ($fileAge > ($expire_time * 3600)){
in
if ($fileAge > ($expire_time * 60)){

Il metodo viene richiamato in questo modo:

$res = clean_old_tmp_files(1); // cancella i files più vecchi di un'ora

La funzione restituisce il numero di files cancellati oppure 0

Conclusioni:

Ho già segnalato questa piccola anomalia a Fabrizio Balliano in questo commento, e credo che ben presto, magari con la release ufficiale di P4A3 annunciata per il 24/06/2008, il problema verrà risolto. Nel frattempo, gli utenti Windows, possono usare questo semplice “helper”.

Riferimenti ed approfondimenti:


Fabrizio Balliano ha scritto:

hum, io aveto testato e vedendo che funzionava correttamente non mi sono piu’ interessato alla questione, ma non sapevo che il problema fosse su windows… ora per martedi’ sara’ complicato risolverlo, vedo che posso fare.
22.06.08 11:42
Fabrizio Balliano ha scritto:

bug risolto :-)
22.06.08 13:04
Mario Spada ha scritto:

Grande Fabrizio, e a tempo di record!!

PHP e MySQL: una classe per stampare alberi gerarchici con liste annidate

Lista Modified Preorder Tree TraversalLa soluzione denominata “Modified Preorder Tree Traversal”, che mal si traduce in italiano, è probabilmente la soluzione più efficace per immagazzinare dati di tipo gerarchico in un database. La soluzione tipica alternativa è il modello a liste di adiacenza. Quest’ultimo modello soffre di alcune limitazioni e debolezze, come ad esempio la particolar cura che deve essere presa nella cancellazione di sotto-alberi che può portare a potenziali figli orfani, e la difficoltà di stesura delle query SQL.
In questo articolo mi riferirò quindi esclusivamente al modello Modified Preorder Tree Traversal. Questo modello è molto ben descritto in questo articolo di Mike Hillyer sul sito ufficiale MySQL nel paragrafo denominato: “Nested Set Model”. Il concetto chiave che sta alla base del modello è di non considerare più la struttura gerarchica come un insieme di nodi e di linee, ma come una serie di insiemi e sottoinsiemi annidati. Il concetto sarà più chiaro dopo aver letto l’articolo e la figura esplicativa della struttura, riportata qui sotto.
treeGraphLe varie query necessarie a gestire il database sono state riprese e tradotte in funzioni PHP da Daevid Vincent in questo thread su questo forum. Il lavoro di Daevid si è rivelato davvero prezioso, e mi ha fatto risparmiare davvero molto tempo, però non ero completamente soddisfatto del sistema di stampa HTML dell’albero. Infatti la funzione utilizza “blank space” per la rappresentazione dell’indentazione, mentre sarebbe più corretto ed elegante utilizzare liste annidate di tipo <UL>. Ho fatto quindi alcune modifiche alla funzione per raggiungere questo scopo.

Queste sono le query per creare il database campione e popolarlo con alcuni dati di esempio:

1
2
3
4
5
6
7
8
9
10
11
12
CREATE TABLE nested_category (
 category_id INT AUTO_INCREMENT PRIMARY KEY,
 name VARCHAR(20) NOT NULL,
 lft INT NOT NULL,
 rgt INT NOT NULL
);
 
INSERT INTO nested_category
VALUES(1,'ELECTRONICS',1,20),(2,'TELEVISIONS',2,9),(3,'TUBE',3,4),
(4,'LCD',5,6),(5,'PLASMA',7,8),(6,'PORTABLE ELECTRONICS',10,19),
(7,'MP3 PLAYERS',11,14),(8,'FLASH',12,13),
(9,'CD PLAYERS',15,16),(10,'2 WAY RADIOS',17,18);

E questo il codice della classe modificata:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class preorderModelTree {
 
  var $dbConn;
 
  function preorderModelTree ($resConn) {
    $this-&gt;dbConn = $resConn;
  }
 
  function dbQuery($sql){
    $rs = @mysql_query($sql,$this-&gt;dbConn);
    return $rs;
  }
 
  function fetchRow($rs) {
    if ( $row = @mysql_fetch_array($rs,MYSQL_ASSOC) ) {
        return $row;
    } else {
        return false;
    }
  }
 
  function print_nested_tree_list($pageLink = "?id=", $id = 1)
  {
    $sth = $this-&gt;dbQuery("SELECT node.category_id as id, node.name AS name,
    (COUNT(parent.name) - 1) as indent
    FROM nested_category AS node, nested_category
    AS parent
    WHERE node.lft BETWEEN parent.lft
    AND parent.rgt  
    GROUP BY node.name
    ORDER BY node.lft");
 
    if ($sth) {
    $oldRowIndent = -1;
 
    while($row = $this-&gt;fetchRow($sth))
    {
      if($row['id'] == $id){
        echo("

“, $oldRowIndent-$row[‘indent’]); echo(”

“); echo str_repeat(”

“, $oldRowIndent); echo(”

“); } } }

Come si evince dal codice, la chiave di tutto è la query ben fatta. Il campo calcolato indent fornisce la misura dell’indentazione che è rappresentata dal numero di gusci in cui la categoria è compresa. Tramite questo valore è possibile costruire il corretto annidamento di elementi <ul> e <li>.

Il pacchetto contenente la classe, il codice sql e un file di esempio è scaricabile qui.

Un ringraziamento particolare per le “imbeccate” ed i consigli, all’amico e collega Alessandro Scoscia.

Conclusioni:

Un ulteriore miglioramento della rappresentazione dell’albero gerarchico potrebbe venire dall’utilizzo di JQuery. Tramite questa libreria javascript si potrebbe implementare un’azione tipo toggle con la quale mostrare o nascondere i sotto-alberi selezionati.

Riferimenti ed approfondimenti:


evilripper ha scritto:

preziossimo articolo!!
grazie per tutte le informazioni!!
ciao

PHP: stampa di codici a barre EAN13 e UPC-A con le librerie EzPDF

Codice EAN13 con ezPdfLa libreria di classi R&OS non dispone di metodi per la creazione di codici a barre, è necessartio ricorrere a soluzioni esterne. Esistono principalmente due differenti modi di stampare i codici a barre in un documento PDF. Uno si basa sulla generazione di un immagine bitmap e la successiva importazione nel documento attraverso i metodi di visualizzazione delle immagini. Il secondo invece, si basa sulla generazione della sequenza di barre utilizzando l’apposito algoritmo e i metodi di disegno dei rettangoli della classe PDF.
Se si preferisce il primo metodo, si può utilizzare PHP-Barcode 0.3pl1, oppure il pacchetto PEAR-PHP: Image_Barcode. Entrambi fanno uso delle librerie grafiche PHP GD per generare immagini del codice a barre.

Pur considerando che il primo metodo ha i suoi vantaggi, trovo che la possibiltà di generare direttamente le barre sia il sistema più efficiente. Il problema principale era dover costruire l’algoritmo per il disegno delle barre, ma una breve ricerca sul forum di EzPDF (PDF-PHP) mi ha facilitato il compito: esiste infatti uno script già pronto per l’utilizzo con le librerie FPDF, si tratta quindi solo di modificarlo per farlo girare con i metodi di EzPDF. Questo script implementa la generazione di codici di tipo EAN13 e UPC-A, che sono peraltro i più diffusi, rispettivamente in Europa e negli Stati Uniti.

La conversione dello script è stata piuttosto semplice perché i metodi di FPDF hanno i corrispettivi nelle librerie EzPDF. Praticamente, l’unica correzione è stata fatta sulla scala dei punti. Il risultato finale è una classe che estende le librerie EzPDF ed integra i metodi per la generazione dei codici a barre. Il codice rimane così molto pulito senza la necessità di ricorrere a terze parti o programmi esterni:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<!--?php require('class.ezpdf.php'); class PDF extends Cezpdf { function EAN13($x,$y,$barcode,$h=45.3543307087,$w=0.992125984252) //$h=16 * 72/25.4; $w=.35 * 72/25.4; { $this-&gt;Barcode($x,$y,$barcode,$h,$w,13);
   }
 function UPC_A($x,$y,$barcode,$h=45.3543307087,$w=0.992125984252)
   //$h=16 * 72/25.4; $w=.35 * 72/25.4;
   {
     $this-&gt;Barcode($x,$y,$barcode,$h,$w,12);
   }
 function GetCheckDigit($barcode) {
   //Compute the check digit
   $sum=0;
   for($i=1;$i&lt;=11;$i+=2)
     $sum+=3*$barcode{$i};
   for($i=0;$i&lt;=10;$i+=2) $sum+=$barcode{$i}; $r=$sum%10; if($r&gt;0)
     $r=10-$r;
   return $r;
 }  
 function TestCheckDigit($barcode) {
  //Test validity of check digit
  $sum=0;
  for($i=1;$i&lt;=11;$i+=2)
   $sum+=3*$barcode{$i};
  for($i=0;$i&lt;=10;$i+=2) $sum+=$barcode{$i}; return ($sum+$barcode{12})%10==0; } function Barcode($x,$y,$barcode,$h,$w,$len) { //Padding $barcode=str_pad($barcode,$len-1,'0',STR_PAD_LEFT); if($len==12) $barcode='0'.$barcode; //Add or control the check digit if(strlen($barcode)==12) $barcode.=$this-&gt;GetCheckDigit($barcode);
  elseif(!$this-&gt;TestCheckDigit($barcode))
   die('Incorrect check digit');
  //Convert digits to bars
  $codes=array(
   'A'=&gt;array(
   '0'=&gt;'0001101','1'=&gt;'0011001','2'=&gt;'0010011','3'=&gt;'0111101','4'=&gt;'0100011',
   '5'=&gt;'0110001','6'=&gt;'0101111','7'=&gt;'0111011','8'=&gt;'0110111','9'=&gt;'0001011'),
   'B'=&gt;array(
   '0'=&gt;'0100111','1'=&gt;'0110011','2'=&gt;'0011011','3'=&gt;'0100001','4'=&gt;'0011101',
   '5'=&gt;'0111001','6'=&gt;'0000101','7'=&gt;'0010001','8'=&gt;'0001001','9'=&gt;'0010111'),
   'C'=&gt;array(
   '0'=&gt;'1110010','1'=&gt;'1100110','2'=&gt;'1101100','3'=&gt;'1000010','4'=&gt;'1011100',
   '5'=&gt;'1001110','6'=&gt;'1010000','7'=&gt;'1000100','8'=&gt;'1001000','9'=&gt;'1110100')
  );
  $parities=array(
   '0'=&gt;array('A','A','A','A','A','A'),
   '1'=&gt;array('A','A','B','A','B','B'),
   '2'=&gt;array('A','A','B','B','A','B'),
   '3'=&gt;array('A','A','B','B','B','A'),
   '4'=&gt;array('A','B','A','A','B','B'),
   '5'=&gt;array('A','B','B','A','A','B'),
   '6'=&gt;array('A','B','B','B','A','A'),
   '7'=&gt;array('A','B','A','B','A','B'),
   '8'=&gt;array('A','B','A','B','B','A'),
   '9'=&gt;array('A','B','B','A','B','A')
  );
  $code='101';
  $p=$parities[$barcode{0}];
  for($i=1;$i&lt;=6;$i++)
   $code.=$codes[$p[$i-1]][$barcode{$i}];
  $code.='01010';
  for($i=7;$i&lt;=12;$i++)
   $code.=$codes['C'][$barcode{$i}];
  $code.='101';
  //Draw bars
  for($i=0;$i&lt;strlen($code);$i++) { if($code{$i}=='1') $this-&gt;filledRectangle($x+$i*$w,$y,$w,$h);
  }
  $last_x = $x+$i*$w;
  //Print text uder barcode
  $this-&gt;selectFont('./fonts/Courier');
  $font_size = 12;
  $start_text = $x + (($last_x - $x) - $font_size*.6*$len) /2;
  $this-&gt;addText($start_text,$y-11,$font_size,substr($barcode,-$len)); 
 }
}
?-->

Un esempio di codice per l’applicazione della classe:

1
2
3
4
<!--?php require('ezbarcode.php'); $pdf =&amp;amp; new PDF('a4','portrait'); $pdf-&gt;selectFont('./fonts/Courier');
$pdf-&gt;EAN13(40,700,'123456789012');
$pdf-&gt;stream();
?-->

Questo è il risultato, il file è scaricabile qui.

Ho ritoccato la stampa del testo sotto il codice in modo che la stringa venga centrata. Per fare questo ho utilizzato il font Courier che ha la proprietà di avere i caratteri di lunghezza fissa.

Conclusioni:

Mi sembra una soluzione semplice ed efficace per quanti abitualmente utilizzano le librerie PDF di R&OS. Potrà essere utile anche a chi utilizza queste librerie per la stampa di PDF con il framework PHP: P4A2. Gli utilizzatori di P4A3 potrebbero essere anche interessati a questo progetto dello Zend Framework.

Riferimenti ed approfondimenti:


Alberto ha scritto:

Ciao, complimenti per l’articolo molto ben fatto!!!
Non sono molto pratico della libreria PDF come posso fare se volessi aggiungere più codici a barre sulla stessa pagina? grazie!!!!

Alberto
21.04.09 10:23
Mario Spada ha scritto:

Ciao Alberto, per mettere più codici sulla stessa pagina basta inserire il metodo $pdf->EAN13() in un ciclo. Per esempio:

foreach ($data as $riga){
$pdf->EAN13($x,$y,$riga[‘barcode’]);
$x+= $a;
$y-=$b;
}

dove $a e $b sono rispettivamente gli shift delle coordinate della pagina e $data è un array multidimensionale che contiene i dati, e ‘barcode’ è il nome del campo che contiene i valori dei codici a barre. Ovviamente devi controllare $a e $b per avere il numero di colonne che desideri e fare il salto pagina dove vuoi tu…!

P4A3 framework: stampa di pdf con le librerie Zend_Pdf

logo pdfNelle precedenti versioni del framework P4A non c’era nessuna libreria interna per la generazione dei documenti pdf.
Era quindi necessario adottare librerie esterne quali ezPdf o FPDF.
La versione 3, invece, incorpora la libreria Zend_Pdf che è integrata nello Zend framework.
A dire il vero, mi ero ben abituato ad ezPdf, e nulla vieta di utilizzarlo ancora, ma mi è sembrato doveroso provare anche le funzionalità delle classi Zend, che oltretutto hanno l’innegabile vantaggio di essere comprese nel framework. Da una prima analisi sommaria, mi sembra che hanno un discreto numero di funzioni utili per il disegno, ma non altrettanto per la manipolazione e gestione del testo. Anche per la formattazione delle tabelle, non mi sembra che ci sia nulla di simile a quanto offre ezPdf.

Passiamo all’esempio pratico: per la prova ho utilizzato la solita applicazione di esempio “Products catalogue”, implementando un pulsante che stampa la scheda del prodotto selezionato. Qundi nel costruttore di products.php ho inserito il seguente codice per creare il bottone e associargli l’evento per la stampa della scheda.

...
// Pdf print button ---------------------------
$this-&gt;build("p4a_button", "printProduct")
	-&gt;setLabel("Print pdf")
	-&gt;implement("onclick", $this, "printDoc");
$this-&gt;build("p4a_fieldset", "fs_pdf")
	-&gt;setLabel("Print product card")
	-&gt;anchor($this-&gt;printProduct);
// --------------------------------------------
...

Vediamo ora il codice del metodo per la stampa vera e propria del pdf:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public function printDoc()
{
  require_once(P4A_SERVER_DIR."/p4a/".P4A_LIBRARIES_PATH."/Zend/Pdf.php");
 
  $imageName = $this-&gt;fields-&gt;picture-&gt;getNewValue(0);
  if (!empty($imageName)){
    $imagePath = P4A_SERVER_DIR . P4A_UPLOADS_PATH .
                  "/" . $imageName;
    $image = Zend_Pdf_Image::imageWithPath($imagePath);
  }
  $brand = $this-&gt;fields-&gt;brand-&gt;getNewValue();
  $model = $this-&gt;fields-&gt;model-&gt;getNewValue();
  $price = $this-&gt;fields-&gt;price-&gt;getNewValue();
 
  $description = $this-&gt;fields-&gt;description-&gt;getNewValue();
  $description = strip_tags($description);
  $wrapDescription = wordwrap($description, 42, "§");    
  $arrDescription = split("§",$wrapDescription);
 
  $doc = new Zend_Pdf; 
  $page1 = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4); 
  $page1-&gt;setFont(Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_COURIER),14); 
  $doc-&gt;pages[] = $page1; 
  if (!empty($imageName)){
    $page1-&gt;drawImage($image, 50, 720, 150, 750);
  }
  $page1-&gt;drawText("Marca: ".$brand,50,700,'UTF-8'); 
  $page1-&gt;drawText("Modello: ".$model,50,680,'UTF-8');
  $page1-&gt;drawText("Prezzo: ".$price,50,660,'UTF-8');  
  $page1-&gt;drawText("Descrizione: ",50,640,'UTF-8');
  $y = 640;
  foreach ($arrDescription as $segment){
    $page1-&gt;drawText($segment,160,$y,'UTF-8');
    $y -= 20;
  }
  $pdfData = $doc-&gt;render();
  P4A_Output_File($pdfData,"product.pdf"); 
}

Il campo “description” potrebbe essere anche piuttosto lungo, e poiché non ho trovato nessuna funzione o parametri per fissare un box di stampa entro il quale il testo possa essere formattato, ho dovuto preparare la stringa di testo dividendola in elementi di un array di stringhe di lunghezza prefissata. Per farlo senza spezzare le parole, ho usato la funzione php wordwrap() impostando come interruzione di testo un carattere molto poco usato: “§“. Quindi ho usato questo carattere come separatore dei segmenti di testo. Non è proprio una soluzione robustissima, sarebbe opportuno scrivere un metodo apposito più preciso, nell’attesa che le classi Zend_Pdf vengano arrichite come lascierebbe intendere questo post.

Ecco l’elenco dei formati pagina disponibili:

  • Zend_Pdf_Page::SIZE_A4
  • Zend_Pdf_Page::SIZE_A4_LANDSCAPE
  • Zend_Pdf_Page::SIZE_LETTER
  • Zend_Pdf_Page::SIZE_LETTER_LANDSCAPE

E’ anche possibile specificare un formato personalizzato passando direttamente i valori ($x, $y) in punti di 1/72 di pollice.

Questi invece sono alcuni dei font disponibili:

  • Zend_Pdf_Font::FONT_COURIER
  • Zend_Pdf_Font::FONT_COURIER_BOLD
  • Zend_Pdf_Font::FONT_COURIER_ITALIC
  • Zend_Pdf_Font::FONT_COURIER_BOLD_ITALIC
  • Zend_Pdf_Font::FONT_TIMES
  • Zend_Pdf_Font::FONT_TIMES_BOLD
  • Zend_Pdf_Font::FONT_TIMES_ITALIC
  • Zend_Pdf_Font::FONT_TIMES_BOLD_ITALIC
  • Zend_Pdf_Font::FONT_HELVETICA
  • Zend_Pdf_Font::FONT_HELVETICA_BOLD
  • Zend_Pdf_Font::FONT_HELVETICA_ITALIC
  • Zend_Pdf_Font::FONT_HELVETICA_BOLD_ITALIC
  • Zend_Pdf_Font::FONT_SYMBOL
  • Zend_Pdf_Font::FONT_ZAPFDINGBATS

Infine, un esempio del documento pdf generato.

Per maggiori dettagli vi rimando ai link nel paragrafo: “Riferimenti ed approfondimenti”

Note:

E’ necessario scaricare l’ultima versione (2.99.9) preview di P4A3 che però non è ancora troppo stabile e soffre di qualche imperfezione. Quelle già note sono state corrette, quindi è necessario attingere al SVN per il corretto funzionamento del framework. In particolare, al momento è necessario prelevare il seguente sorgente dal SVN: p4a_db.php rev. 1762 che risolve un problema relativo agli apici usati nelle query. In alternativa si deve aspettare il nuovo imminente rilascio.
Infine, la funzione code>P4A_Output_File() genera un file temporaneo nella directory /uploads/tmp che quindi deve essere presente e avere i permessi di scrittura. Purtroppo non ho ancora capito perché i file temporanei non vengano eliminati alla fine della sessione, come mi sarei aspettato. Indagherò su questa anomalia, nel frattempo bisogna eliminarli manualmente per evitare che la directory si riempia di pdf!

Riferimenti ed approfondimenti: