586 lines
22 KiB
PHP
Executable File
586 lines
22 KiB
PHP
Executable File
<?php
|
|
/*
|
|
* PHP QR Code encoder
|
|
*
|
|
* Area finding for SVG and CANVAS output
|
|
*
|
|
* Based on libqrencode C library distributed under LGPL 2.1
|
|
* Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net>
|
|
*
|
|
* PHP QR Code is distributed under LGPL 3
|
|
* Copyright (C) 2010-2013 Dominik Dzienia <deltalab at poczta dot fm>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 3 of the License, or any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
// N
|
|
// W E
|
|
// S
|
|
|
|
define('QR_AREA_N', 0);
|
|
define('QR_AREA_E', 1);
|
|
define('QR_AREA_S', 2);
|
|
define('QR_AREA_W', 3);
|
|
|
|
define('QR_AREA_X', 0);
|
|
define('QR_AREA_Y', 1);
|
|
|
|
define('QR_AREA_TRACKER', 0);
|
|
define('QR_AREA_PATH', 1);
|
|
define('QR_AREA_POINT', 2);
|
|
define('QR_AREA_RECT', 3);
|
|
define('QR_AREA_LSHAPE', 4);
|
|
|
|
/** @addtogroup OutputGroup */
|
|
/** @{ */
|
|
|
|
class QRareaGroup {
|
|
public $total = 0;
|
|
public $vertical = false;
|
|
public $horizontal = false;
|
|
public $points = array();
|
|
public $id = 0;
|
|
public $paths = array();
|
|
|
|
//----------------------------------------------------------------------
|
|
public function __construct($selfId, $sx, $sy)
|
|
{
|
|
$this->total = 1;
|
|
$this->points = array(array($sx,$sy,false));
|
|
$this->id = $selfId;
|
|
}
|
|
|
|
}
|
|
|
|
//##########################################################################
|
|
|
|
class QRarea {
|
|
|
|
public $width = 0;
|
|
private $tab = array();
|
|
private $tab_edges = array();
|
|
private $groups = array();
|
|
private $curr_group = 0;
|
|
public $paths = array();
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
public function __construct($source_tab)
|
|
{
|
|
$py = 0;
|
|
$this->width = count($source_tab);
|
|
$this->tab = array();
|
|
$this->tab_edges = array();
|
|
$this->paths = array();
|
|
|
|
foreach ($source_tab as $line) {
|
|
$arr = array();
|
|
$arr_edge = array();
|
|
$px=0;
|
|
|
|
foreach (str_split($line) as $item) {
|
|
|
|
if ($py<7 && $px<7)
|
|
$item = 0;
|
|
|
|
if ($py<7 && $px>=($this->width-7))
|
|
$item = 0;
|
|
|
|
if ($py>=($this->width-7) && $px<7)
|
|
$item = 0;
|
|
|
|
$arr[] = (int)$item;
|
|
$arr_edge[] = array(false, false, false, false);
|
|
|
|
$px++;
|
|
}
|
|
|
|
$this->tab[] = $arr;
|
|
$this->tab_edges[] = $arr_edge;
|
|
$py++;
|
|
}
|
|
|
|
$this->paths[] = array(QR_AREA_TRACKER, 0,0);
|
|
$this->paths[] = array(QR_AREA_TRACKER, 0,($this->width-7));
|
|
$this->paths[] = array(QR_AREA_TRACKER, ($this->width-7),0);
|
|
|
|
$this->groups = array();
|
|
$this->curr_group = 1;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
public function getGroups()
|
|
{
|
|
return $this->groups;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
public function getPaths()
|
|
{
|
|
return $this->paths;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
public function getWidth()
|
|
{
|
|
return $this->width;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
public function dumpTab()
|
|
{
|
|
echo "<style>";
|
|
echo "td { height: 2.5em; color: black; font-size: 8px;
|
|
border-top: 1px solid silver; border-left: 1px solid silver }";
|
|
echo "table { border-bottom: 1px solid silver; border-right: 1px solid silver }";
|
|
echo "</style>";
|
|
echo "<table border=0 cellpadding=0 cellspacing=0>";
|
|
|
|
$colorTab = array();
|
|
|
|
foreach($this->tab as $line) {
|
|
foreach($line as $item) {
|
|
if (!isset($colorTab[$item])) {
|
|
$colorTab[$item] = 'hsl('.mt_rand(0, 360).', '.floor((mt_rand(0, 25))+75).'%, 50%)';
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach($this->tab as $line) {
|
|
echo "<tr>";
|
|
foreach($line as $item) {
|
|
if ($item == 0) {
|
|
echo "<td> </td>";
|
|
} else {
|
|
echo "<td style='text-align:center;width: 4em;background:".$colorTab[$item]."'>".$item."</td>";
|
|
}
|
|
}
|
|
echo "</tr>";
|
|
}
|
|
echo "</table>";
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
public function dumpEdges()
|
|
{
|
|
$style_off = '1px dotted silver;';
|
|
$style_on = '3px solid red;';
|
|
|
|
$colorAlloc = array();
|
|
|
|
echo "<table border='0'>";
|
|
$py = 0;
|
|
foreach($this->tab_edges as $line) {
|
|
$px = 0;
|
|
echo "<tr>";
|
|
foreach($line as $item) {
|
|
|
|
$styles = 'border-top:';
|
|
if ($item[QR_AREA_N])
|
|
$styles .= $style_on;
|
|
else $styles .= $style_off;
|
|
|
|
$styles .= 'border-bottom:';
|
|
if ($item[QR_AREA_S])
|
|
$styles .= $style_on;
|
|
else $styles .= $style_off;
|
|
|
|
$styles .= 'border-right:';
|
|
if ($item[QR_AREA_E])
|
|
$styles .= $style_on;
|
|
else $styles .= $style_off;
|
|
|
|
$styles .= 'border-left:';
|
|
if ($item[QR_AREA_W])
|
|
$styles .= $style_on;
|
|
else $styles .= $style_off;
|
|
|
|
$color = '';
|
|
$grp = $this->tab[$py][$px];
|
|
|
|
if ($grp>0) {
|
|
if (!isset($colorAlloc[$grp])) {
|
|
$colorAlloc[$grp] = 'hsl('.mt_rand(0, 360).', '.floor((mt_rand(0, 25))+75).'%, 50%)';
|
|
}
|
|
|
|
$color = 'background:'.$colorAlloc[$grp];
|
|
}
|
|
|
|
if ($grp == 0)
|
|
$grp = ' ';
|
|
|
|
echo "<td style='text-align:center;width:1.5em;".$styles.$color."'>".$grp."</td>";
|
|
$px++;
|
|
}
|
|
echo "</tr>";
|
|
$py++;
|
|
}
|
|
echo "</table>";
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
private static function rle(&$stringData)
|
|
{
|
|
$outArray = array();
|
|
$symbolArray = str_split($stringData);
|
|
$last = '';
|
|
$run = 1;
|
|
|
|
while (count($symbolArray) > 0) {
|
|
$symbol = array_shift($symbolArray);
|
|
|
|
if ($symbol != $last) {
|
|
if ($run > 1)
|
|
$outArray[] = $run;
|
|
|
|
if ($last != '')
|
|
$outArray[] = $last;
|
|
|
|
$run = 1;
|
|
$last = $symbol;
|
|
} else {
|
|
$run++;
|
|
}
|
|
}
|
|
|
|
if ($run > 1)
|
|
$outArray[] = $run;
|
|
|
|
$outArray[] = $last;
|
|
|
|
$stringData = $outArray;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
private function getAt($posx, $posy)
|
|
{
|
|
if (($posx<0)||($posy<0)||($posx>=$this->width)||($posy>=$this->width))
|
|
return 0;
|
|
|
|
return $this->tab[$posy][$posx];
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
private function getOnElem($elem, $deltax = 0, $deltay = 0)
|
|
{
|
|
$posx = $elem[0]+$deltax;
|
|
$posy = $elem[1]+$deltay;
|
|
|
|
if (($posx<0)||($posy<0)||($posx>=$this->width)||($posy>=$this->width))
|
|
return 0;
|
|
|
|
return $this->tab[$posy][$posx];
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
private function addGroupElement($groupId, $h, $v, $sx, $sy)
|
|
{
|
|
$this->groups[$groupId]->total++;
|
|
if ($h)
|
|
$this->groups[$groupId]->horizontal = true;
|
|
if ($v)
|
|
$this->groups[$groupId]->vertical = true;
|
|
$this->groups[$groupId]->points[] = array($sx, $sy, false);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
public function detectGroups()
|
|
{
|
|
for ($sy = 0; $sy < $this->width; $sy++) {
|
|
for ($sx = 0; $sx < $this->width; $sx++) {
|
|
|
|
if ($this->tab[$sy][$sx] == 1) { // non-allocated
|
|
|
|
$gid_left = 0;
|
|
$gid_top = 0;
|
|
|
|
$grouped = false;
|
|
|
|
if ($sx>0) {
|
|
|
|
$gid_left = $this->tab[$sy][$sx-1]; // previous on left
|
|
|
|
if ($gid_left > 1) { // if already in group
|
|
$this->tab[$sy][$sx] = $gid_left;
|
|
$grouped = true;
|
|
$this->addGroupElement($gid_left, true, false, $sx, $sy);
|
|
}
|
|
}
|
|
|
|
if ($sy > 0) {
|
|
|
|
$gid_top = $this->tab[$sy-1][$sx]; // previous on top
|
|
|
|
if ($gid_top > 1) { //if in group
|
|
if (!$grouped) { // and not grouped
|
|
|
|
$this->tab[$sy][$sx] = $gid_top;
|
|
$grouped = true;
|
|
|
|
$this->addGroupElement($gid_top, false, true, $sx, $sy);
|
|
|
|
} else if($gid_top != $gid_left) { // was in left group
|
|
|
|
$grouped = true;
|
|
|
|
$this->groups[$gid_top]->vertical = true;
|
|
$this->groups[$gid_top]->horizontal = true;
|
|
|
|
$this->groups[$gid_top]->total = $this->groups[$gid_top]->total + $this->groups[$gid_left]->total;
|
|
|
|
foreach($this->groups[$gid_left]->points as $elem)
|
|
$this->tab[$elem[1]][$elem[0]] = $gid_top;
|
|
|
|
$this->groups[$gid_top]->points = array_values(array_merge($this->groups[$gid_top]->points, $this->groups[$gid_left]->points));
|
|
unset($this->groups[$gid_left]);
|
|
|
|
//refarb group
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$grouped) {
|
|
$this->curr_group++;
|
|
$this->tab[$sy][$sx] = $this->curr_group;
|
|
$this->groups[$this->curr_group] = new QRareaGroup($this->curr_group, $sx, $sy);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
private function detectSquare($group)
|
|
{
|
|
$max_x = 0;
|
|
$max_y = 0;
|
|
$min_x = $this->width;
|
|
$min_y = $this->width;
|
|
|
|
foreach($group->points as $elem) {
|
|
$min_x = min($min_x, $elem[QR_AREA_X]);
|
|
$max_x = max($max_x, $elem[QR_AREA_X]);
|
|
$min_y = min($min_y, $elem[QR_AREA_Y]);
|
|
$max_y = max($max_y, $elem[QR_AREA_Y]);
|
|
}
|
|
|
|
return array($min_x, $min_y, $max_x+1, $max_y+1);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
public function detectAreas()
|
|
{
|
|
$squares = array();
|
|
$points = array();
|
|
$lshapes = array();
|
|
|
|
foreach ($this->groups as $groupId=>&$group) {
|
|
if ($group->total > 3) {
|
|
|
|
if ((!$group->vertical)||(!$group->horizontal)) {
|
|
|
|
$squareCoord = $this->detectSquare($group);
|
|
array_unshift($squareCoord, QR_AREA_RECT);
|
|
|
|
$this->paths[] = $squareCoord;
|
|
|
|
} else {
|
|
|
|
$this->detectPaths($group);
|
|
unset($group->points);
|
|
|
|
foreach($group->paths as &$path)
|
|
self::rle($path[2]);
|
|
|
|
$this->paths[] = array(QR_AREA_PATH, $group->paths);
|
|
}
|
|
} else if (($group->total == 3)&&($group->vertical)&&($group->horizontal)) {
|
|
$squareCoord = $this->detectSquare($group);
|
|
$variant = 0;
|
|
|
|
if ($this->getOnElem($squareCoord, 0, 0) != $group->id)
|
|
$variant = 0;
|
|
|
|
if ($this->getOnElem($squareCoord, 1, 0) != $group->id)
|
|
$variant = 1;
|
|
|
|
if ($this->getOnElem($squareCoord, 0, 1) != $group->id)
|
|
$variant = 2;
|
|
|
|
if ($this->getOnElem($squareCoord, 1, 1) != $group->id)
|
|
$variant = 3;
|
|
|
|
$lshapes[] = $squareCoord[QR_AREA_X];
|
|
$lshapes[] = $squareCoord[QR_AREA_Y];
|
|
$lshapes[] = $variant;
|
|
|
|
} else if ($group->total >= 2) {
|
|
$squareCoord = $this->detectSquare($group);
|
|
$squares = array_merge($squares, $squareCoord);
|
|
} else if ($group->total == 1) {
|
|
$points[] = $group->points[0][0];
|
|
$points[] = $group->points[0][1];
|
|
}
|
|
}
|
|
|
|
if (count($points) > 0) {
|
|
array_unshift($points, QR_AREA_POINT);
|
|
$this->paths[] = $points;
|
|
}
|
|
|
|
if (count($squares) > 0) {
|
|
array_unshift($squares, QR_AREA_RECT);
|
|
$this->paths[] = $squares;
|
|
}
|
|
|
|
if (count($lshapes) > 0) {
|
|
array_unshift($lshapes, QR_AREA_LSHAPE);
|
|
$this->paths[] = $lshapes;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
private function reserveEdgeOnElem($elem, $edgeNo)
|
|
{
|
|
$this->tab_edges[$elem[QR_AREA_Y]][$elem[QR_AREA_X]][$edgeNo] = true;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
private function reserveEdge($px, $py, $edgeNo)
|
|
{
|
|
$this->tab_edges[$py][$px][$edgeNo] = true;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
private function markAdjacentEdges($group)
|
|
{
|
|
foreach($group->points as $elem) {
|
|
if ($this->getOnElem($elem, -1, 0) == $group->id)
|
|
$this->reserveEdgeOnElem($elem, QR_AREA_W);
|
|
|
|
if ($this->getOnElem($elem, +1, 0) == $group->id)
|
|
$this->reserveEdgeOnElem($elem, QR_AREA_E);
|
|
|
|
if ($this->getOnElem($elem, 0, -1) == $group->id)
|
|
$this->reserveEdgeOnElem($elem, QR_AREA_N);
|
|
|
|
if ($this->getOnElem($elem, 0, +1) == $group->id)
|
|
$this->reserveEdgeOnElem($elem, QR_AREA_S);
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
private function detectPaths(&$group)
|
|
{
|
|
$this->markAdjacentEdges($group);
|
|
|
|
$elem = $group->points[0];
|
|
$waylist = $this->findPath($group, $elem[QR_AREA_X], $elem[QR_AREA_Y]);
|
|
$group->paths[] = array($elem[QR_AREA_X], $elem[QR_AREA_Y], $waylist);
|
|
|
|
$tab = array();
|
|
foreach($group->points as $elem) {
|
|
|
|
$edgeTab = $this->tab_edges[$elem[QR_AREA_Y]][$elem[QR_AREA_X]];
|
|
|
|
if (!( $edgeTab[QR_AREA_N]
|
|
&& $edgeTab[QR_AREA_E]
|
|
&& $edgeTab[QR_AREA_S]
|
|
&& $edgeTab[QR_AREA_W])) {
|
|
|
|
if (!$edgeTab[QR_AREA_S]) {
|
|
|
|
$waylistw = $this->findPath($group, $elem[QR_AREA_X], $elem[QR_AREA_Y]+1);
|
|
$group->paths[] = array($elem[QR_AREA_X], $elem[QR_AREA_Y]+1, $waylistw);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
private function findPath($group, $sx, $sy)
|
|
{
|
|
$px = $sx;
|
|
$py = $sy;
|
|
|
|
$waylist = '';
|
|
$dir = '';
|
|
$lastdir = '';
|
|
|
|
$moves = array(
|
|
// magic :)
|
|
0=>'', 1=>'L', 2=>'T', 3=>'L', 4=>'B', 5=>'B', 6=>'B,T', 7=>'B'
|
|
,8=>'R', 9=>'R,L', 10=>'T', 11=>'L',12=>'R',13=>'R',14=>'T',15=>''
|
|
);
|
|
|
|
do
|
|
{
|
|
$Q = ($this->getAt($px-1, $py-1) == $group->id)?1:0;
|
|
$Q += ($this->getAt($px, $py-1) == $group->id)?2:0;
|
|
$Q += ($this->getAt($px-1, $py) == $group->id)?4:0;
|
|
$Q += ($this->getAt($px, $py) == $group->id)?8:0;
|
|
|
|
if ($moves[$Q] == '')
|
|
throw new Exception('It should NEVER happened!');
|
|
|
|
$move_expl = explode(',', $moves[$Q]);
|
|
$have_way = false;
|
|
|
|
$dir = '';
|
|
|
|
while ((count($move_expl) > 0)&&($have_way == false)) {
|
|
$way = array_shift($move_expl);
|
|
|
|
if (($have_way==false)&&($way=='R')&&($this->tab_edges[$py][$px][QR_AREA_N]==false)) {
|
|
$have_way = true;
|
|
$dir = $way;
|
|
$this->reserveEdge($px, $py, QR_AREA_N);
|
|
$px++;
|
|
}
|
|
|
|
if (($have_way==false)&&($way=='B')&&($this->tab_edges[$py][$px-1][QR_AREA_E]==false)) {
|
|
$have_way = true;
|
|
$dir = $way;
|
|
$this->reserveEdge($px-1, $py, QR_AREA_E);
|
|
$py++;
|
|
}
|
|
|
|
if (($have_way==false)&&($way=='L')&&($this->tab_edges[$py-1][$px-1][QR_AREA_S]==false)) {
|
|
$have_way = true;
|
|
$dir = $way;
|
|
$this->reserveEdge($px-1, $py-1, QR_AREA_S);
|
|
$px--;
|
|
}
|
|
|
|
if (($have_way==false)&&($way=='T')&&($this->tab_edges[$py-1][$px][QR_AREA_W]==false)) {
|
|
$have_way = true;
|
|
$dir = $way;
|
|
$this->reserveEdge($px, $py-1, QR_AREA_W);
|
|
$py--;
|
|
}
|
|
}
|
|
|
|
$waylist .= $dir;
|
|
|
|
} while (!(($px==$sx)&&($py==$sy)));
|
|
|
|
return $waylist;
|
|
}
|
|
}
|
|
|
|
/** @} */ |