]> git.wh0rd.org - tt-rss.git/blame - lib/phpqrcode/qrinput.php
support disabling of e-mail digests entirely
[tt-rss.git] / lib / phpqrcode / qrinput.php
CommitLineData
fb70f26e
AD
1<?php\r
2/*\r
3 * PHP QR Code encoder\r
4 *\r
5 * Input encoding class\r
6 *\r
7 * Based on libqrencode C library distributed under LGPL 2.1\r
8 * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net>\r
9 *\r
10 * PHP QR Code is distributed under LGPL 3\r
11 * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>\r
12 *\r
13 * This library is free software; you can redistribute it and/or\r
14 * modify it under the terms of the GNU Lesser General Public\r
15 * License as published by the Free Software Foundation; either\r
16 * version 3 of the License, or any later version.\r
17 *\r
18 * This library is distributed in the hope that it will be useful,\r
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
21 * Lesser General Public License for more details.\r
22 *\r
23 * You should have received a copy of the GNU Lesser General Public\r
24 * License along with this library; if not, write to the Free Software\r
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA\r
26 */\r
27 \r
28 define('STRUCTURE_HEADER_BITS', 20);\r
29 define('MAX_STRUCTURED_SYMBOLS', 16);\r
30\r
31 class QRinputItem {\r
32 \r
33 public $mode;\r
34 public $size;\r
35 public $data;\r
36 public $bstream;\r
37\r
38 public function __construct($mode, $size, $data, $bstream = null) \r
39 {\r
40 $setData = array_slice($data, 0, $size);\r
41 \r
42 if (count($setData) < $size) {\r
43 $setData = array_merge($setData, array_fill(0,$size-count($setData),0));\r
44 }\r
45 \r
46 if(!QRinput::check($mode, $size, $setData)) {\r
47 throw new Exception('Error m:'.$mode.',s:'.$size.',d:'.join(',',$setData));\r
48 return null;\r
49 }\r
50 \r
51 $this->mode = $mode;\r
52 $this->size = $size;\r
53 $this->data = $setData;\r
54 $this->bstream = $bstream;\r
55 }\r
56 \r
57 //----------------------------------------------------------------------\r
58 public function encodeModeNum($version)\r
59 {\r
60 try {\r
61 \r
62 $words = (int)($this->size / 3);\r
63 $bs = new QRbitstream();\r
64 \r
65 $val = 0x1;\r
66 $bs->appendNum(4, $val);\r
67 $bs->appendNum(QRspec::lengthIndicator(QR_MODE_NUM, $version), $this->size);\r
68\r
69 for($i=0; $i<$words; $i++) {\r
70 $val = (ord($this->data[$i*3 ]) - ord('0')) * 100;\r
71 $val += (ord($this->data[$i*3+1]) - ord('0')) * 10;\r
72 $val += (ord($this->data[$i*3+2]) - ord('0'));\r
73 $bs->appendNum(10, $val);\r
74 }\r
75\r
76 if($this->size - $words * 3 == 1) {\r
77 $val = ord($this->data[$words*3]) - ord('0');\r
78 $bs->appendNum(4, $val);\r
79 } else if($this->size - $words * 3 == 2) {\r
80 $val = (ord($this->data[$words*3 ]) - ord('0')) * 10;\r
81 $val += (ord($this->data[$words*3+1]) - ord('0'));\r
82 $bs->appendNum(7, $val);\r
83 }\r
84\r
85 $this->bstream = $bs;\r
86 return 0;\r
87 \r
88 } catch (Exception $e) {\r
89 return -1;\r
90 }\r
91 }\r
92 \r
93 //----------------------------------------------------------------------\r
94 public function encodeModeAn($version)\r
95 {\r
96 try {\r
97 $words = (int)($this->size / 2);\r
98 $bs = new QRbitstream();\r
99 \r
100 $bs->appendNum(4, 0x02);\r
101 $bs->appendNum(QRspec::lengthIndicator(QR_MODE_AN, $version), $this->size);\r
102\r
103 for($i=0; $i<$words; $i++) {\r
104 $val = (int)QRinput::lookAnTable(ord($this->data[$i*2 ])) * 45;\r
105 $val += (int)QRinput::lookAnTable(ord($this->data[$i*2+1]));\r
106\r
107 $bs->appendNum(11, $val);\r
108 }\r
109\r
110 if($this->size & 1) {\r
111 $val = QRinput::lookAnTable(ord($this->data[$words * 2]));\r
112 $bs->appendNum(6, $val);\r
113 }\r
114 \r
115 $this->bstream = $bs;\r
116 return 0;\r
117 \r
118 } catch (Exception $e) {\r
119 return -1;\r
120 }\r
121 }\r
122 \r
123 //----------------------------------------------------------------------\r
124 public function encodeMode8($version)\r
125 {\r
126 try {\r
127 $bs = new QRbitstream();\r
128\r
129 $bs->appendNum(4, 0x4);\r
130 $bs->appendNum(QRspec::lengthIndicator(QR_MODE_8, $version), $this->size);\r
131\r
132 for($i=0; $i<$this->size; $i++) {\r
133 $bs->appendNum(8, ord($this->data[$i]));\r
134 }\r
135\r
136 $this->bstream = $bs;\r
137 return 0;\r
138 \r
139 } catch (Exception $e) {\r
140 return -1;\r
141 }\r
142 }\r
143 \r
144 //----------------------------------------------------------------------\r
145 public function encodeModeKanji($version)\r
146 {\r
147 try {\r
148\r
149 $bs = new QRbitrtream();\r
150 \r
151 $bs->appendNum(4, 0x8);\r
152 $bs->appendNum(QRspec::lengthIndicator(QR_MODE_KANJI, $version), (int)($this->size / 2));\r
153\r
154 for($i=0; $i<$this->size; $i+=2) {\r
155 $val = (ord($this->data[$i]) << 8) | ord($this->data[$i+1]);\r
156 if($val <= 0x9ffc) {\r
157 $val -= 0x8140;\r
158 } else {\r
159 $val -= 0xc140;\r
160 }\r
161 \r
162 $h = ($val >> 8) * 0xc0;\r
163 $val = ($val & 0xff) + $h;\r
164\r
165 $bs->appendNum(13, $val);\r
166 }\r
167\r
168 $this->bstream = $bs;\r
169 return 0;\r
170 \r
171 } catch (Exception $e) {\r
172 return -1;\r
173 }\r
174 }\r
175\r
176 //----------------------------------------------------------------------\r
177 public function encodeModeStructure()\r
178 {\r
179 try {\r
180 $bs = new QRbitstream();\r
181 \r
182 $bs->appendNum(4, 0x03);\r
183 $bs->appendNum(4, ord($this->data[1]) - 1);\r
184 $bs->appendNum(4, ord($this->data[0]) - 1);\r
185 $bs->appendNum(8, ord($this->data[2]));\r
186\r
187 $this->bstream = $bs;\r
188 return 0;\r
189 \r
190 } catch (Exception $e) {\r
191 return -1;\r
192 }\r
193 }\r
194 \r
195 //----------------------------------------------------------------------\r
196 public function estimateBitStreamSizeOfEntry($version)\r
197 {\r
198 $bits = 0;\r
199\r
200 if($version == 0) \r
201 $version = 1;\r
202\r
203 switch($this->mode) {\r
204 case QR_MODE_NUM: $bits = QRinput::estimateBitsModeNum($this->size); break;\r
205 case QR_MODE_AN: $bits = QRinput::estimateBitsModeAn($this->size); break;\r
206 case QR_MODE_8: $bits = QRinput::estimateBitsMode8($this->size); break;\r
207 case QR_MODE_KANJI: $bits = QRinput::estimateBitsModeKanji($this->size);break;\r
208 case QR_MODE_STRUCTURE: return STRUCTURE_HEADER_BITS; \r
209 default:\r
210 return 0;\r
211 }\r
212\r
213 $l = QRspec::lengthIndicator($this->mode, $version);\r
214 $m = 1 << $l;\r
215 $num = (int)(($this->size + $m - 1) / $m);\r
216\r
217 $bits += $num * (4 + $l);\r
218\r
219 return $bits;\r
220 }\r
221 \r
222 //----------------------------------------------------------------------\r
223 public function encodeBitStream($version)\r
224 {\r
225 try {\r
226 \r
227 unset($this->bstream);\r
228 $words = QRspec::maximumWords($this->mode, $version);\r
229 \r
230 if($this->size > $words) {\r
231 \r
232 $st1 = new QRinputItem($this->mode, $words, $this->data);\r
233 $st2 = new QRinputItem($this->mode, $this->size - $words, array_slice($this->data, $words));\r
234\r
235 $st1->encodeBitStream($version);\r
236 $st2->encodeBitStream($version);\r
237 \r
238 $this->bstream = new QRbitstream();\r
239 $this->bstream->append($st1->bstream);\r
240 $this->bstream->append($st2->bstream);\r
241 \r
242 unset($st1);\r
243 unset($st2);\r
244 \r
245 } else {\r
246 \r
247 $ret = 0;\r
248 \r
249 switch($this->mode) {\r
250 case QR_MODE_NUM: $ret = $this->encodeModeNum($version); break;\r
251 case QR_MODE_AN: $ret = $this->encodeModeAn($version); break;\r
252 case QR_MODE_8: $ret = $this->encodeMode8($version); break;\r
253 case QR_MODE_KANJI: $ret = $this->encodeModeKanji($version);break;\r
254 case QR_MODE_STRUCTURE: $ret = $this->encodeModeStructure(); break;\r
255 \r
256 default:\r
257 break;\r
258 }\r
259 \r
260 if($ret < 0)\r
261 return -1;\r
262 }\r
263\r
264 return $this->bstream->size();\r
265 \r
266 } catch (Exception $e) {\r
267 return -1;\r
268 }\r
269 }\r
270 };\r
271 \r
272 //##########################################################################\r
273\r
274 class QRinput {\r
275\r
276 public $items;\r
277 \r
278 private $version;\r
279 private $level;\r
280 \r
281 //----------------------------------------------------------------------\r
282 public function __construct($version = 0, $level = QR_ECLEVEL_L)\r
283 {\r
284 if ($version < 0 || $version > QRSPEC_VERSION_MAX || $level > QR_ECLEVEL_H) {\r
285 throw new Exception('Invalid version no');\r
286 return NULL;\r
287 }\r
288 \r
289 $this->version = $version;\r
290 $this->level = $level;\r
291 }\r
292 \r
293 //----------------------------------------------------------------------\r
294 public function getVersion()\r
295 {\r
296 return $this->version;\r
297 }\r
298 \r
299 //----------------------------------------------------------------------\r
300 public function setVersion($version)\r
301 {\r
302 if($version < 0 || $version > QRSPEC_VERSION_MAX) {\r
303 throw new Exception('Invalid version no');\r
304 return -1;\r
305 }\r
306\r
307 $this->version = $version;\r
308\r
309 return 0;\r
310 }\r
311 \r
312 //----------------------------------------------------------------------\r
313 public function getErrorCorrectionLevel()\r
314 {\r
315 return $this->level;\r
316 }\r
317\r
318 //----------------------------------------------------------------------\r
319 public function setErrorCorrectionLevel($level)\r
320 {\r
321 if($level > QR_ECLEVEL_H) {\r
322 throw new Exception('Invalid ECLEVEL');\r
323 return -1;\r
324 }\r
325\r
326 $this->level = $level;\r
327\r
328 return 0;\r
329 }\r
330 \r
331 //----------------------------------------------------------------------\r
332 public function appendEntry(QRinputItem $entry)\r
333 {\r
334 $this->items[] = $entry;\r
335 }\r
336 \r
337 //----------------------------------------------------------------------\r
338 public function append($mode, $size, $data)\r
339 {\r
340 try {\r
341 $entry = new QRinputItem($mode, $size, $data);\r
342 $this->items[] = $entry;\r
343 return 0;\r
344 } catch (Exception $e) {\r
345 return -1;\r
346 }\r
347 }\r
348 \r
349 //----------------------------------------------------------------------\r
350 \r
351 public function insertStructuredAppendHeader($size, $index, $parity)\r
352 {\r
353 if( $size > MAX_STRUCTURED_SYMBOLS ) {\r
354 throw new Exception('insertStructuredAppendHeader wrong size');\r
355 }\r
356 \r
357 if( $index <= 0 || $index > MAX_STRUCTURED_SYMBOLS ) {\r
358 throw new Exception('insertStructuredAppendHeader wrong index');\r
359 }\r
360\r
361 $buf = array($size, $index, $parity);\r
362 \r
363 try {\r
364 $entry = new QRinputItem(QR_MODE_STRUCTURE, 3, buf);\r
365 array_unshift($this->items, $entry);\r
366 return 0;\r
367 } catch (Exception $e) {\r
368 return -1;\r
369 }\r
370 }\r
371\r
372 //----------------------------------------------------------------------\r
373 public function calcParity()\r
374 {\r
375 $parity = 0;\r
376 \r
377 foreach($this->items as $item) {\r
378 if($item->mode != QR_MODE_STRUCTURE) {\r
379 for($i=$item->size-1; $i>=0; $i--) {\r
380 $parity ^= $item->data[$i];\r
381 }\r
382 }\r
383 }\r
384\r
385 return $parity;\r
386 }\r
387 \r
388 //----------------------------------------------------------------------\r
389 public static function checkModeNum($size, $data)\r
390 {\r
391 for($i=0; $i<$size; $i++) {\r
392 if((ord($data[$i]) < ord('0')) || (ord($data[$i]) > ord('9'))){\r
393 return false;\r
394 }\r
395 }\r
396\r
397 return true;\r
398 }\r
399\r
400 //----------------------------------------------------------------------\r
401 public static function estimateBitsModeNum($size)\r
402 {\r
403 $w = (int)$size / 3;\r
404 $bits = $w * 10;\r
405 \r
406 switch($size - $w * 3) {\r
407 case 1:\r
408 $bits += 4;\r
409 break;\r
410 case 2:\r
411 $bits += 7;\r
412 break;\r
413 default:\r
414 break;\r
415 }\r
416\r
417 return $bits;\r
418 }\r
419 \r
420 //----------------------------------------------------------------------\r
421 public static $anTable = array(\r
422 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\r
423 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\r
424 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43,\r
425 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1,\r
426 -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,\r
427 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1,\r
428 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\r
429 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1\r
430 );\r
431 \r
432 //----------------------------------------------------------------------\r
433 public static function lookAnTable($c)\r
434 {\r
435 return (($c > 127)?-1:self::$anTable[$c]);\r
436 }\r
437 \r
438 //----------------------------------------------------------------------\r
439 public static function checkModeAn($size, $data)\r
440 {\r
441 for($i=0; $i<$size; $i++) {\r
442 if (self::lookAnTable(ord($data[$i])) == -1) {\r
443 return false;\r
444 }\r
445 }\r
446\r
447 return true;\r
448 }\r
449 \r
450 //----------------------------------------------------------------------\r
451 public static function estimateBitsModeAn($size)\r
452 {\r
453 $w = (int)($size / 2);\r
454 $bits = $w * 11;\r
455 \r
456 if($size & 1) {\r
457 $bits += 6;\r
458 }\r
459\r
460 return $bits;\r
461 }\r
462 \r
463 //----------------------------------------------------------------------\r
464 public static function estimateBitsMode8($size)\r
465 {\r
466 return $size * 8;\r
467 }\r
468 \r
469 //----------------------------------------------------------------------\r
470 public function estimateBitsModeKanji($size)\r
471 {\r
472 return (int)(($size / 2) * 13);\r
473 }\r
474 \r
475 //----------------------------------------------------------------------\r
476 public static function checkModeKanji($size, $data)\r
477 {\r
478 if($size & 1)\r
479 return false;\r
480\r
481 for($i=0; $i<$size; $i+=2) {\r
482 $val = (ord($data[$i]) << 8) | ord($data[$i+1]);\r
483 if( $val < 0x8140 \r
484 || ($val > 0x9ffc && $val < 0xe040) \r
485 || $val > 0xebbf) {\r
486 return false;\r
487 }\r
488 }\r
489\r
490 return true;\r
491 }\r
492\r
493 /***********************************************************************\r
494 * Validation\r
495 **********************************************************************/\r
496\r
497 public static function check($mode, $size, $data)\r
498 {\r
499 if($size <= 0) \r
500 return false;\r
501\r
502 switch($mode) {\r
503 case QR_MODE_NUM: return self::checkModeNum($size, $data); break;\r
504 case QR_MODE_AN: return self::checkModeAn($size, $data); break;\r
505 case QR_MODE_KANJI: return self::checkModeKanji($size, $data); break;\r
506 case QR_MODE_8: return true; break;\r
507 case QR_MODE_STRUCTURE: return true; break;\r
508 \r
509 default:\r
510 break;\r
511 }\r
512\r
513 return false;\r
514 }\r
515 \r
516 \r
517 //----------------------------------------------------------------------\r
518 public function estimateBitStreamSize($version)\r
519 {\r
520 $bits = 0;\r
521\r
522 foreach($this->items as $item) {\r
523 $bits += $item->estimateBitStreamSizeOfEntry($version);\r
524 }\r
525\r
526 return $bits;\r
527 }\r
528 \r
529 //----------------------------------------------------------------------\r
530 public function estimateVersion()\r
531 {\r
532 $version = 0;\r
533 $prev = 0;\r
534 do {\r
535 $prev = $version;\r
536 $bits = $this->estimateBitStreamSize($prev);\r
537 $version = QRspec::getMinimumVersion((int)(($bits + 7) / 8), $this->level);\r
538 if ($version < 0) {\r
539 return -1;\r
540 }\r
541 } while ($version > $prev);\r
542\r
543 return $version;\r
544 }\r
545 \r
546 //----------------------------------------------------------------------\r
547 public static function lengthOfCode($mode, $version, $bits)\r
548 {\r
549 $payload = $bits - 4 - QRspec::lengthIndicator($mode, $version);\r
550 switch($mode) {\r
551 case QR_MODE_NUM:\r
552 $chunks = (int)($payload / 10);\r
553 $remain = $payload - $chunks * 10;\r
554 $size = $chunks * 3;\r
555 if($remain >= 7) {\r
556 $size += 2;\r
557 } else if($remain >= 4) {\r
558 $size += 1;\r
559 }\r
560 break;\r
561 case QR_MODE_AN:\r
562 $chunks = (int)($payload / 11);\r
563 $remain = $payload - $chunks * 11;\r
564 $size = $chunks * 2;\r
565 if($remain >= 6) \r
566 $size++;\r
567 break;\r
568 case QR_MODE_8:\r
569 $size = (int)($payload / 8);\r
570 break;\r
571 case QR_MODE_KANJI:\r
572 $size = (int)(($payload / 13) * 2);\r
573 break;\r
574 case QR_MODE_STRUCTURE:\r
575 $size = (int)($payload / 8);\r
576 break;\r
577 default:\r
578 $size = 0;\r
579 break;\r
580 }\r
581 \r
582 $maxsize = QRspec::maximumWords($mode, $version);\r
583 if($size < 0) $size = 0;\r
584 if($size > $maxsize) $size = $maxsize;\r
585\r
586 return $size;\r
587 }\r
588 \r
589 //----------------------------------------------------------------------\r
590 public function createBitStream()\r
591 {\r
592 $total = 0;\r
593\r
594 foreach($this->items as $item) {\r
595 $bits = $item->encodeBitStream($this->version);\r
596 \r
597 if($bits < 0) \r
598 return -1;\r
599 \r
600 $total += $bits;\r
601 }\r
602\r
603 return $total;\r
604 }\r
605 \r
606 //----------------------------------------------------------------------\r
607 public function convertData()\r
608 {\r
609 $ver = $this->estimateVersion();\r
610 if($ver > $this->getVersion()) {\r
611 $this->setVersion($ver);\r
612 }\r
613\r
614 for(;;) {\r
615 $bits = $this->createBitStream();\r
616 \r
617 if($bits < 0) \r
618 return -1;\r
619 \r
620 $ver = QRspec::getMinimumVersion((int)(($bits + 7) / 8), $this->level);\r
621 if($ver < 0) {\r
622 throw new Exception('WRONG VERSION');\r
623 return -1;\r
624 } else if($ver > $this->getVersion()) {\r
625 $this->setVersion($ver);\r
626 } else {\r
627 break;\r
628 }\r
629 }\r
630\r
631 return 0;\r
632 }\r
633 \r
634 //----------------------------------------------------------------------\r
635 public function appendPaddingBit(&$bstream)\r
636 {\r
637 $bits = $bstream->size();\r
638 $maxwords = QRspec::getDataLength($this->version, $this->level);\r
639 $maxbits = $maxwords * 8;\r
640\r
641 if ($maxbits == $bits) {\r
642 return 0;\r
643 }\r
644\r
645 if ($maxbits - $bits < 5) {\r
646 return $bstream->appendNum($maxbits - $bits, 0);\r
647 }\r
648\r
649 $bits += 4;\r
650 $words = (int)(($bits + 7) / 8);\r
651\r
652 $padding = new QRbitstream();\r
653 $ret = $padding->appendNum($words * 8 - $bits + 4, 0);\r
654 \r
655 if($ret < 0) \r
656 return $ret;\r
657\r
658 $padlen = $maxwords - $words;\r
659 \r
660 if($padlen > 0) {\r
661 \r
662 $padbuf = array();\r
663 for($i=0; $i<$padlen; $i++) {\r
664 $padbuf[$i] = ($i&1)?0x11:0xec;\r
665 }\r
666 \r
667 $ret = $padding->appendBytes($padlen, $padbuf);\r
668 \r
669 if($ret < 0)\r
670 return $ret;\r
671 \r
672 }\r
673\r
674 $ret = $bstream->append($padding);\r
675 \r
676 return $ret;\r
677 }\r
678\r
679 //----------------------------------------------------------------------\r
680 public function mergeBitStream()\r
681 {\r
682 if($this->convertData() < 0) {\r
683 return null;\r
684 }\r
685\r
686 $bstream = new QRbitstream();\r
687 \r
688 foreach($this->items as $item) {\r
689 $ret = $bstream->append($item->bstream);\r
690 if($ret < 0) {\r
691 return null;\r
692 }\r
693 }\r
694\r
695 return $bstream;\r
696 }\r
697\r
698 //----------------------------------------------------------------------\r
699 public function getBitStream()\r
700 {\r
701\r
702 $bstream = $this->mergeBitStream();\r
703 \r
704 if($bstream == null) {\r
705 return null;\r
706 }\r
707 \r
708 $ret = $this->appendPaddingBit($bstream);\r
709 if($ret < 0) {\r
710 return null;\r
711 }\r
712\r
713 return $bstream;\r
714 }\r
715 \r
716 //----------------------------------------------------------------------\r
717 public function getByteStream()\r
718 {\r
719 $bstream = $this->getBitStream();\r
720 if($bstream == null) {\r
721 return null;\r
722 }\r
723 \r
724 return $bstream->toByte();\r
725 }\r
726 }\r
727 \r
728 \r
729