1<?php
2/* Copyright (C) 2004-2016 Laurent Destailleur     <eldy@users.sourceforge.net>
3 * Copyright (C) 2004-2010 Folke Ashberg: Some lines of code were inspired from work
4 *                         of Folke Ashberg into PHP-Barcode 0.3pl2, available as GPL
5 *                         source code at http://www.ashberg.de/bar.
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21/**
22 *	\file       htdocs/core/lib/barcode.lib.php
23 *	\brief      Set of functions used for barcode generation (internal lib, also code 'phpbarcode')
24 *	\ingroup    core
25 */
26
27/* ******************************************************************** */
28/*                          COLORS                                      */
29/* ******************************************************************** */
30$bar_color = array(0, 0, 0);
31$bg_color = array(255, 255, 255);
32$text_color = array(0, 0, 0);
33
34
35/* ******************************************************************** */
36/*                          FONT FILE                                   */
37/* ******************************************************************** */
38if (defined('DOL_DEFAULT_TTF_BOLD')) {
39	$font_loc = constant('DOL_DEFAULT_TTF_BOLD');
40}
41// Automatic-Detection of Font if running Windows
42// @CHANGE LDR
43if (isset($_SERVER['WINDIR']) && @file_exists($_SERVER['WINDIR'])) {
44	$font_loc = $_SERVER['WINDIR'].'\Fonts\arialbd.ttf';
45}
46if (empty($font_loc)) {
47	die('DOL_DEFAULT_TTF_BOLD must de defined with full path to a TTF font.');
48}
49
50
51/* ******************************************************************** */
52/*                          GENBARCODE                                  */
53/* ******************************************************************** */
54/* location of 'genbarcode'
55 * leave blank if you don't have them :(
56* genbarcode is needed to render encodings other than EAN-12/EAN-13/ISBN
57*/
58
59if (defined('PHP-BARCODE_PATH_COMMAND')) {
60	$genbarcode_loc = constant('PHP-BARCODE_PATH_COMMAND');
61} else {
62	$genbarcode_loc = $conf->global->GENBARCODE_LOCATION;
63}
64
65
66
67
68/**
69 * Print barcode
70 *
71 * @param	string	       $code		Code
72 * @param	string	       $encoding	Encoding ('EAN13', 'ISBN', 'C128', 'UPC', 'CBR', 'QRCODE', 'DATAMATRIX', 'ANY'...)
73 * @param	integer	       $scale		Scale
74 * @param	string	       $mode		'png' or 'jpg' ...
75 * @return	array|string   $bars		array('encoding': the encoding which has been used, 'bars': the bars, 'text': text-positioning info) or string with error message
76 */
77function barcode_print($code, $encoding = "ANY", $scale = 2, $mode = "png")
78{
79	dol_syslog("barcode.lib.php::barcode_print $code $encoding $scale $mode");
80
81	$bars = barcode_encode($code, $encoding);
82	if (!$bars || !empty($bars['error'])) {
83		// Return error message instead of array
84		if (empty($bars['error'])) {
85			$error = 'Bad Value '.$code.' for encoding '.$encoding;
86		} else {
87			$error = $bars['error'];
88		}
89		dol_syslog('barcode.lib.php::barcode_print '.$error, LOG_ERR);
90		return $error;
91	}
92	if (!$mode) {
93		$mode = "png";
94	}
95	//if (preg_match("/^(text|txt|plain)$/i",$mode)) print barcode_outtext($bars['text'],$bars['bars']);
96	//elseif (preg_match("/^(html|htm)$/i",$mode)) print barcode_outhtml($bars['text'],$bars['bars'], $scale,0, 0);
97	//else
98	barcode_outimage($bars['text'], $bars['bars'], $scale, $mode);
99	return $bars;
100}
101
102/**
103 * Encodes $code with $encoding using genbarcode OR built-in encoder if you don't have genbarcode only EAN-13/ISBN or UPC is possible
104 *
105 * You can use the following encodings (when you have genbarcode):
106 *   ANY    choose best-fit (default)
107 *   EAN    8 or 13 EAN-Code
108 *   UPC    12-digit EAN
109 *   ISBN   isbn numbers (still EAN-13)
110 *   39     code 39
111 *   128    code 128 (a,b,c: autoselection)
112 *   128C   code 128 (compact form for digits)
113 *   128B   code 128, full printable ascii
114 *   I25    interleaved 2 of 5 (only digits)
115 *   128RAW Raw code 128 (by Leonid A. Broukhis)
116 *   CBR    Codabar (by Leonid A. Broukhis)
117 *   MSI    MSI (by Leonid A. Broukhis)
118 *   PLS    Plessey (by Leonid A. Broukhis)
119 *
120 * @param	string	$code		Code
121 * @param	string	$encoding	Encoding
122 * @return	array|false			array('encoding': the encoding which has been used, 'bars': the bars, 'text': text-positioning info)
123 */
124function barcode_encode($code, $encoding)
125{
126	global $genbarcode_loc;
127
128	if ((preg_match("/^upc$/i", $encoding))
129	&& (preg_match("/^[0-9]{11,12}$/", $code))
130	) {
131		/* use built-in UPC-Encoder */
132		dol_syslog("barcode.lib.php::barcode_encode Use barcode_encode_upc");
133		$bars = barcode_encode_upc($code, $encoding);
134	} elseif ((preg_match("/^ean$/i", $encoding))
135
136	|| (($encoding) && (preg_match("/^isbn$/i", $encoding))
137	&& ((strlen($code) == 9 || strlen($code) == 10) ||
138	(((preg_match("/^978/", $code) && strlen($code) == 12) ||
139	(strlen($code) == 13)))))
140
141	|| ((!isset($encoding) || !$encoding || (preg_match("/^ANY$/i", $encoding)))
142	&& (preg_match("/^[0-9]{12,13}$/", $code)))
143	) {
144		/* use built-in EAN-Encoder */
145		dol_syslog("barcode.lib.php::barcode_encode Use barcode_encode_ean");
146		$bars = barcode_encode_ean($code, $encoding);
147	} elseif (file_exists($genbarcode_loc)) {	// For example C39
148		/* use genbarcode */
149		dol_syslog("barcode.lib.php::barcode_encode Use genbarcode ".$genbarcode_loc." code=".$code." encoding=".$encoding);
150		$bars = barcode_encode_genbarcode($code, $encoding);
151	} else {
152		print "barcode_encode needs an external program for encodings other then EAN/ISBN (code=".dol_escape_htmltag($code).", encoding=".dol_escape_htmltag($encoding).")<BR>\n";
153		print "<UL>\n";
154		print "<LI>download gnu-barcode from <A href=\"https://www.gnu.org/software/barcode/\">www.gnu.org/software/barcode/</A>\n";
155		print "<LI>compile and install them\n";
156		print "<LI>specify path the genbarcode in barcode module setup\n";
157		print "</UL>\n";
158		print "<BR>\n";
159		return false;
160	}
161
162	return $bars;
163}
164
165
166/**
167 * Calculate EAN sum
168 *
169 * @param	string	$ean	EAN to encode
170 * @return	integer			Sum
171 */
172function barcode_gen_ean_sum($ean)
173{
174	$even = true;
175	$esum = 0;
176	$osum = 0;
177	$ln = strlen($ean) - 1;
178	for ($i = $ln; $i >= 0; $i--) {
179		if ($even) {
180			$esum += $ean[$i];
181		} else {
182			$osum += $ean[$i];
183		}
184		$even = !$even;
185	}
186	return (10 - ((3 * $esum + $osum) % 10)) % 10;
187}
188
189
190/**
191 * Generate EAN bars
192 *
193 * @param	string	$ean	EAN to encode
194 * @return	string			Encoded EAN
195 */
196function barcode_gen_ean_bars($ean)
197{
198	$digits = array(3211, 2221, 2122, 1411, 1132, 1231, 1114, 1312, 1213, 3112);
199	$mirror = array("000000", "001011", "001101", "001110", "010011", "011001", "011100", "010101", "010110", "011010");
200	$guards = array("9a1a", "1a1a1", "a1a7");
201
202	$line = $guards[0];
203	for ($i = 1; $i < 13; $i++) {
204		$str = $digits[$ean[$i]];
205		if ($i < 7 && $mirror[$ean[0]][$i - 1] == 1) {
206			$line .= strrev($str);
207		} else {
208			$line .= $str;
209		}
210		if ($i == 6) {
211			$line .= $guards[1];
212		}
213	}
214	$line .= $guards[2];
215
216	return $line;
217}
218
219/**
220 * Encode EAN
221 *
222 * @param	string	$ean		Code
223 * @param	string	$encoding	Encoding
224 * @return	array				array('encoding': the encoding which has been used, 'bars': the bars, 'text': text-positioning info, 'error': error message if error)
225 */
226function barcode_encode_ean($ean, $encoding = "EAN-13")
227{
228	$ean = trim($ean);
229	if (preg_match("/[^0-9]/i", $ean)) {
230		return array("error"=>"Invalid encoding/code. encoding=".$encoding." code=".$ean." (not a numeric)", "text"=>"Invalid encoding/code. encoding=".$encoding." code=".$ean." (not a numeric)");
231	}
232	$encoding = strtoupper($encoding);
233	if ($encoding == "ISBN") {
234		if (!preg_match("/^978/", $ean)) {
235			$ean = "978".$ean;
236		}
237	}
238	if (preg_match("/^97[89]/", $ean)) {
239		$encoding = "ISBN";
240	}
241	if (strlen($ean) < 12 || strlen($ean) > 13) {
242		return array("error"=>"Invalid encoding/code. encoding=".$encoding." code=".$ean." (must have 12/13 numbers)", "text"=>"Invalid encoding/code. encoding=".$encoding." code=".$ean." (must have 12/13 numbers)");
243	}
244
245	$ean = substr($ean, 0, 12);
246	$eansum = barcode_gen_ean_sum($ean);
247	$ean .= $eansum;
248	$bars = barcode_gen_ean_bars($ean);
249
250	/* create text */
251	$pos = 0;
252	$text = "";
253	for ($a = 0; $a < 13; $a++) {
254		if ($a > 0) {
255			$text .= " ";
256		}
257		$text .= "$pos:12:{$ean[$a]}";
258		if ($a == 0) {
259			$pos += 12;
260		} elseif ($a == 6) {
261			$pos += 12;
262		} else {
263			$pos += 7;
264		}
265	}
266
267	return array(
268		"error" => '',
269		"encoding" => $encoding,
270		"bars" => $bars,
271		"text" => $text
272	);
273}
274
275/**
276 * Encode UPC
277 *
278 * @param	string	$upc		Code
279 * @param	string	$encoding	Encoding
280 * @return	array				array('encoding': the encoding which has been used, 'bars': the bars, 'text': text-positioning info, 'error': error message if error)
281 */
282function barcode_encode_upc($upc, $encoding = "UPC")
283{
284	$upc = trim($upc);
285	if (preg_match("/[^0-9]/i", $upc)) {
286		return array("error"=>"Invalid encoding/code. encoding=".$encoding." code=".$upc." (not a numeric)", "text"=>"Invalid encoding/code. encoding=".$encoding." code=".$upc." (not a numeric)");
287	}
288	$encoding = strtoupper($encoding);
289	if (strlen($upc) < 11 || strlen($upc) > 12) {
290		return array("error"=>"Invalid encoding/code. encoding=".$encoding." code=".$upc." (must have 11/12 numbers)", "text"=>"Invalid encoding/code. encoding=".$encoding." code=".$upc." (must have 11/12 numbers)");
291	}
292
293	$upc = substr("0".$upc, 0, 12);
294	$eansum = barcode_gen_ean_sum($upc);
295	$upc .= $eansum;
296	$bars = barcode_gen_ean_bars($upc);
297
298	/* create text */
299	$pos = 0;
300	$text = "";
301	for ($a = 1; $a < 13; $a++) {
302		if ($a > 1) {
303			$text .= " ";
304		}
305		$text .= "$pos:12:{$upc[$a]}";
306		if ($a == 1) {
307			$pos += 15;
308		} elseif ($a == 6) {
309			$pos += 17;
310		} elseif ($a == 11) {
311			$pos += 15;
312		} else {
313			$pos += 7;
314		}
315	}
316
317	return array(
318		"error" => '',
319		"encoding" => $encoding,
320		"bars" => $bars,
321		"text" => $text
322	);
323}
324
325/**
326 * Encode result of genbarcode command
327 *
328 * @param	string	$code		Code
329 * @param	string	$encoding	Encoding
330 * @return	array|false			array('encoding': the encoding which has been used, 'bars': the bars, 'text': text-positioning info)
331 */
332function barcode_encode_genbarcode($code, $encoding)
333{
334	global $genbarcode_loc;
335
336	// Clean parameters
337	if (preg_match("/^ean$/i", $encoding) && strlen($code) == 13) {
338		$code = substr($code, 0, 12);
339	}
340	if (!$encoding) {
341		$encoding = "ANY";
342	}
343	$encoding = preg_replace("/[\\\|]/", "_", $encoding);
344	$code = preg_replace("/[\\\|]/", "_", $code);
345
346	$command = escapeshellarg($genbarcode_loc);
347	//$paramclear=" \"".str_replace("\"", "\\\"",$code)."\" \"".str_replace("\"", "\\\"",strtoupper($encoding))."\"";
348	$paramclear = " ".escapeshellarg($code)." ".escapeshellarg(strtoupper($encoding));
349
350	$fullcommandclear = $command." ".$paramclear." 2>&1";
351	//print $fullcommandclear."<br>\n";exit;
352
353	dol_syslog("Run command ".$fullcommandclear);
354	$fp = popen($fullcommandclear, "r");
355	if ($fp) {
356		$bars = fgets($fp, 1024);
357		$text = fgets($fp, 1024);
358		$encoding = fgets($fp, 1024);
359		pclose($fp);
360	} else {
361		dol_syslog("barcode.lib.php::barcode_encode_genbarcode failed to run popen ".$fullcommandclear, LOG_ERR);
362		return false;
363	}
364	//var_dump($bars);
365	$ret = array(
366		"bars" => trim($bars),
367		"text" => trim($text),
368		"encoding" => trim($encoding),
369		"error" => ""
370	);
371	//var_dump($ret);
372	if (preg_match('/permission denied/i', $ret['bars'])) {
373		$ret['error'] = $ret['bars'];
374		$ret['bars'] = '';
375		return $ret;
376	}
377	if (!$ret['bars']) {
378		return false;
379	}
380	if (!$ret['text']) {
381		return false;
382	}
383	if (!$ret['encoding']) {
384		return false;
385	}
386	return $ret;
387}
388
389/**
390 * Output image onto standard output, or onto disk if global filebarcode is defined
391 *
392 * @param	string	$text		the text-line (<position>:<font-size>:<character> ...)
393 * @param	string	$bars   	where to place the bars  (<space-width><bar-width><space-width><bar-width>...)
394 * @param	int		$scale		scale factor ( 1 < scale < unlimited (scale 50 will produce 5400x300 pixels when using EAN-13!!!))
395 * @param	string	$mode   	png,gif,jpg (default='png')
396 * @param	int		$total_y	the total height of the image ( default: scale * 60 )
397 * @param	array	$space		default:  $space[top]   = 2 * $scale; $space[bottom]= 2 * $scale;  $space[left]  = 2 * $scale;  $space[right] = 2 * $scale;
398 * @return	string|null
399 */
400function barcode_outimage($text, $bars, $scale = 1, $mode = "png", $total_y = 0, $space = '')
401{
402	global $bar_color, $bg_color, $text_color;
403	global $font_loc, $filebarcode;
404
405	//print "$text, $bars, $scale, $mode, $total_y, $space, $font_loc, $filebarcode<br>";
406	//var_dump($text);
407	//var_dump($bars);
408	//var_dump($font_loc);
409
410	/* set defaults */
411	if ($scale < 1) {
412		$scale = 2;
413	}
414	$total_y = (int) $total_y;
415	if ($total_y < 1) {
416		$total_y = (int) $scale * 60;
417	}
418	if (!$space) {
419		$space = array('top'=>2 * $scale, 'bottom'=>2 * $scale, 'left'=>2 * $scale, 'right'=>2 * $scale);
420	}
421
422	/* count total width */
423	$xpos = 0;
424	$width = true;
425	$ln = strlen($bars);
426	for ($i = 0; $i < $ln; $i++) {
427		$val = strtolower($bars[$i]);
428		if ($width) {
429			$xpos += $val * $scale;
430			$width = false;
431			continue;
432		}
433		if (preg_match("/[a-z]/", $val)) {
434			/* tall bar */
435			$val = ord($val) - ord('a') + 1;
436		}
437		$xpos += $val * $scale;
438		$width = true;
439	}
440
441	/* allocate the image */
442	$total_x = ($xpos) + $space['right'] + $space['right'];
443	$xpos = $space['left'];
444	if (!function_exists("imagecreate")) {
445		print "You don't have the gd2 extension enabled<br>\n";
446		return "";
447	}
448	$im = imagecreate($total_x, $total_y);
449	/* create two images */
450	$col_bg = ImageColorAllocate($im, $bg_color[0], $bg_color[1], $bg_color[2]);
451	$col_bar = ImageColorAllocate($im, $bar_color[0], $bar_color[1], $bar_color[2]);
452	$col_text = ImageColorAllocate($im, $text_color[0], $text_color[1], $text_color[2]);
453	$height = round($total_y - ($scale * 10));
454	$height2 = round($total_y - $space['bottom']);
455
456	/* paint the bars */
457	$width = true;
458	$ln = strlen($bars);
459	for ($i = 0; $i < $ln; $i++) {
460		$val = strtolower($bars[$i]);
461		if ($width) {
462			$xpos += $val * $scale;
463			$width = false;
464			continue;
465		}
466		if (preg_match("/[a-z]/", $val)) {
467			/* tall bar */
468			$val = ord($val) - ord('a') + 1;
469			$h = $height2;
470		} else {
471			$h = $height;
472		}
473		imagefilledrectangle($im, $xpos, $space['top'], $xpos + ($val * $scale) - 1, $h, $col_bar);
474		$xpos += $val * $scale;
475		$width = true;
476	}
477
478	$chars = explode(" ", $text);
479	foreach ($chars as $v) {
480		if (trim($v)) {
481			$inf = explode(":", $v);
482			$fontsize = $scale * ($inf[1] / 1.8);
483			$fontheight = $total_y - ($fontsize / 2.7) + 2;
484			imagettftext($im, $fontsize, 0, $space['left'] + ($scale * $inf[0]) + 2, $fontheight, $col_text, $font_loc, $inf[2]);
485		}
486	}
487
488	/* output the image */
489	$mode = strtolower($mode);
490	if ($mode == 'jpg' || $mode == 'jpeg') {
491		header("Content-Type: image/jpeg; name=\"barcode.jpg\"");
492		imagejpeg($im);
493	} elseif ($mode == 'gif') {
494		header("Content-Type: image/gif; name=\"barcode.gif\"");
495		imagegif($im);
496	} elseif (!empty($filebarcode)) {
497		// To wxrite into  afile onto disk
498		imagepng($im, $filebarcode);
499	} else {
500		header("Content-Type: image/png; name=\"barcode.png\"");
501		imagepng($im);
502	}
503}
504