正在显示
31 个修改的文件
包含
3968 行增加
和
724 行删除
addons/example/.addonrc
已删除
100755 → 0
1 | -{"files":["application\\admin\\controller\\example\\Bootstraptable.php","application\\admin\\controller\\example\\Colorbadge.php","application\\admin\\controller\\example\\Controllerjump.php","application\\admin\\controller\\example\\Customform.php","application\\admin\\controller\\example\\Customsearch.php","application\\admin\\controller\\example\\Cxselect.php","application\\admin\\controller\\example\\Echarts.php","application\\admin\\controller\\example\\Multitable.php","application\\admin\\controller\\example\\Relationmodel.php","application\\admin\\controller\\example\\Tablelink.php","application\\admin\\controller\\example\\Tabletemplate.php","application\\admin\\model\\Area.php","application\\admin\\view\\example\\bootstraptable\\detail.html","application\\admin\\view\\example\\bootstraptable\\edit.html","application\\admin\\view\\example\\bootstraptable\\index.html","application\\admin\\view\\example\\colorbadge\\index.html","application\\admin\\view\\example\\controllerjump\\index.html","application\\admin\\view\\example\\customform\\index.html","application\\admin\\view\\example\\customsearch\\index.html","application\\admin\\view\\example\\cxselect\\index.html","application\\admin\\view\\example\\echarts\\index.html","application\\admin\\view\\example\\multitable\\index.html","application\\admin\\view\\example\\relationmodel\\index.html","application\\admin\\view\\example\\tablelink\\index.html","application\\admin\\view\\example\\tabletemplate\\index.html","public\\assets\\js\\backend\\example\\bootstraptable.js","public\\assets\\js\\backend\\example\\colorbadge.js","public\\assets\\js\\backend\\example\\controllerjump.js","public\\assets\\js\\backend\\example\\customform.js","public\\assets\\js\\backend\\example\\customsearch.js","public\\assets\\js\\backend\\example\\cxselect.js","public\\assets\\js\\backend\\example\\echarts.js","public\\assets\\js\\backend\\example\\multitable.js","public\\assets\\js\\backend\\example\\relationmodel.js","public\\assets\\js\\backend\\example\\tablelink.js","public\\assets\\js\\backend\\example\\tabletemplate.js","public\\assets\\addons\\example\\css\\common.css","public\\assets\\addons\\example\\img\\200x200.png","public\\assets\\addons\\example\\img\\plus.png","public\\assets\\addons\\example\\js\\async.js"],"license":"regular","licenseto":"15629","licensekey":"tPF5COLi1AuI7ScR 5YrH\/jKQmS3gnZuosjc0sw==","domains":["cardverification.com"],"licensecodes":[],"validations":["867a7ee97b89c3a1f31235c70da23ecf"],"menus":["example","example\/bootstraptable","example\/bootstraptable\/index","example\/bootstraptable\/detail","example\/bootstraptable\/change","example\/bootstraptable\/del","example\/bootstraptable\/multi","example\/customsearch","example\/customsearch\/index","example\/customsearch\/del","example\/customsearch\/multi","example\/customform","example\/customform\/index","example\/tablelink","example\/tablelink\/index","example\/colorbadge","example\/colorbadge\/index","example\/colorbadge\/del","example\/colorbadge\/multi","example\/controllerjump","example\/controllerjump\/index","example\/controllerjump\/del","example\/controllerjump\/multi","example\/cxselect","example\/cxselect\/index","example\/cxselect\/del","example\/cxselect\/multi","example\/multitable","example\/multitable\/index","example\/multitable\/del","example\/multitable\/multi","example\/relationmodel","example\/relationmodel\/index","example\/relationmodel\/del","example\/relationmodel\/multi","example\/tabletemplate","example\/tabletemplate\/index","example\/tabletemplate\/detail","example\/tabletemplate\/del","example\/tabletemplate\/multi","example\/echarts","example\/echarts\/index"]} |
addons/example/Example.php
已删除
100755 → 0
1 | -<?php | ||
2 | - | ||
3 | -namespace addons\example; | ||
4 | - | ||
5 | -use app\common\library\Menu; | ||
6 | -use think\Addons; | ||
7 | - | ||
8 | -/** | ||
9 | - * Example | ||
10 | - */ | ||
11 | -class Example extends Addons | ||
12 | -{ | ||
13 | - | ||
14 | - /** | ||
15 | - * 插件安装方法 | ||
16 | - * @return bool | ||
17 | - */ | ||
18 | - public function install() | ||
19 | - { | ||
20 | - $menu = [ | ||
21 | - [ | ||
22 | - 'name' => 'example', | ||
23 | - 'title' => '开发示例管理', | ||
24 | - 'icon' => 'fa fa-magic', | ||
25 | - 'sublist' => [ | ||
26 | - [ | ||
27 | - 'name' => 'example/bootstraptable', | ||
28 | - 'title' => '表格完整示例', | ||
29 | - 'icon' => 'fa fa-table', | ||
30 | - 'sublist' => [ | ||
31 | - ['name' => 'example/bootstraptable/index', 'title' => '查看'], | ||
32 | - ['name' => 'example/bootstraptable/detail', 'title' => '详情'], | ||
33 | - ['name' => 'example/bootstraptable/change', 'title' => '变更'], | ||
34 | - ['name' => 'example/bootstraptable/del', 'title' => '删除'], | ||
35 | - ['name' => 'example/bootstraptable/multi', 'title' => '批量更新'], | ||
36 | - ] | ||
37 | - ], | ||
38 | - [ | ||
39 | - 'name' => 'example/customsearch', | ||
40 | - 'title' => '自定义搜索', | ||
41 | - 'icon' => 'fa fa-table', | ||
42 | - 'sublist' => [ | ||
43 | - ['name' => 'example/customsearch/index', 'title' => '查看'], | ||
44 | - ['name' => 'example/customsearch/del', 'title' => '删除'], | ||
45 | - ['name' => 'example/customsearch/multi', 'title' => '批量更新'], | ||
46 | - ] | ||
47 | - ], | ||
48 | - [ | ||
49 | - 'name' => 'example/customform', | ||
50 | - 'title' => '自定义表单示例', | ||
51 | - 'icon' => 'fa fa-edit', | ||
52 | - 'sublist' => [ | ||
53 | - ['name' => 'example/customform/index', 'title' => '查看'], | ||
54 | - ] | ||
55 | - ], | ||
56 | - [ | ||
57 | - 'name' => 'example/tablelink', | ||
58 | - 'title' => '表格联动示例', | ||
59 | - 'icon' => 'fa fa-table', | ||
60 | - 'remark' => '点击左侧日志列表,右侧的表格数据会显示指定管理员的日志列表', | ||
61 | - 'sublist' => [ | ||
62 | - ['name' => 'example/tablelink/index', 'title' => '查看'], | ||
63 | - ] | ||
64 | - ], | ||
65 | - [ | ||
66 | - 'name' => 'example/colorbadge', | ||
67 | - 'title' => '彩色角标', | ||
68 | - 'icon' => 'fa fa-table', | ||
69 | - 'remark' => '左侧彩色的角标会根据当前数据量的大小进行更新', | ||
70 | - 'sublist' => [ | ||
71 | - ['name' => 'example/colorbadge/index', 'title' => '查看'], | ||
72 | - ['name' => 'example/colorbadge/del', 'title' => '删除'], | ||
73 | - ['name' => 'example/colorbadge/multi', 'title' => '批量更新'], | ||
74 | - ] | ||
75 | - ], | ||
76 | - [ | ||
77 | - 'name' => 'example/controllerjump', | ||
78 | - 'title' => '控制器间跳转', | ||
79 | - 'icon' => 'fa fa-table', | ||
80 | - 'remark' => '点击IP地址可以跳转到新的选项卡中查看指定IP的数据', | ||
81 | - 'sublist' => [ | ||
82 | - ['name' => 'example/controllerjump/index', 'title' => '查看'], | ||
83 | - ['name' => 'example/controllerjump/del', 'title' => '删除'], | ||
84 | - ['name' => 'example/controllerjump/multi', 'title' => '批量更新'], | ||
85 | - ] | ||
86 | - ], | ||
87 | - [ | ||
88 | - 'name' => 'example/cxselect', | ||
89 | - 'title' => '多级联动', | ||
90 | - 'icon' => 'fa fa-table', | ||
91 | - 'remark' => '基于jquery.cxselect实现的多级联动', | ||
92 | - 'sublist' => [ | ||
93 | - ['name' => 'example/cxselect/index', 'title' => '查看'], | ||
94 | - ['name' => 'example/cxselect/del', 'title' => '删除'], | ||
95 | - ['name' => 'example/cxselect/multi', 'title' => '批量更新'], | ||
96 | - ] | ||
97 | - ], | ||
98 | - [ | ||
99 | - 'name' => 'example/multitable', | ||
100 | - 'title' => '多表格示例', | ||
101 | - 'icon' => 'fa fa-table', | ||
102 | - 'remark' => '展示在一个页面显示多个Bootstrap-table表格', | ||
103 | - 'sublist' => [ | ||
104 | - ['name' => 'example/multitable/index', 'title' => '查看'], | ||
105 | - ['name' => 'example/multitable/del', 'title' => '删除'], | ||
106 | - ['name' => 'example/multitable/multi', 'title' => '批量更新'], | ||
107 | - ] | ||
108 | - ], | ||
109 | - [ | ||
110 | - 'name' => 'example/relationmodel', | ||
111 | - 'title' => '关联模型示例', | ||
112 | - 'icon' => 'fa fa-table', | ||
113 | - 'remark' => '列表中的头像、用户名和昵称字段均从关联表中取出', | ||
114 | - 'sublist' => [ | ||
115 | - ['name' => 'example/relationmodel/index', 'title' => '查看'], | ||
116 | - ['name' => 'example/relationmodel/del', 'title' => '删除'], | ||
117 | - ['name' => 'example/relationmodel/multi', 'title' => '批量更新'], | ||
118 | - ] | ||
119 | - ], | ||
120 | - [ | ||
121 | - 'name' => 'example/tabletemplate', | ||
122 | - 'title' => '表格模板示例', | ||
123 | - 'icon' => 'fa fa-table', | ||
124 | - 'remark' => '', | ||
125 | - 'sublist' => [ | ||
126 | - ['name' => 'example/tabletemplate/index', 'title' => '查看'], | ||
127 | - ['name' => 'example/tabletemplate/detail', 'title' => '详情'], | ||
128 | - ['name' => 'example/tabletemplate/del', 'title' => '删除'], | ||
129 | - ['name' => 'example/tabletemplate/multi', 'title' => '批量更新'], | ||
130 | - ] | ||
131 | - ], | ||
132 | - [ | ||
133 | - 'name' => 'example/echarts', | ||
134 | - 'title' => '统计图表示例', | ||
135 | - 'icon' => 'fa fa-bar-chart', | ||
136 | - 'sublist' => [ | ||
137 | - ['name' => 'example/echarts/index', 'title' => '查看'], | ||
138 | - ] | ||
139 | - ], | ||
140 | - ] | ||
141 | - ] | ||
142 | - ]; | ||
143 | - Menu::create($menu); | ||
144 | - return true; | ||
145 | - } | ||
146 | - | ||
147 | - /** | ||
148 | - * 插件卸载方法 | ||
149 | - * @return bool | ||
150 | - */ | ||
151 | - public function uninstall() | ||
152 | - { | ||
153 | - Menu::delete('example'); | ||
154 | - return true; | ||
155 | - } | ||
156 | - | ||
157 | - /** | ||
158 | - * 插件启用方法 | ||
159 | - */ | ||
160 | - public function enable() | ||
161 | - { | ||
162 | - Menu::enable('example'); | ||
163 | - } | ||
164 | - | ||
165 | - /** | ||
166 | - * 插件禁用方法 | ||
167 | - */ | ||
168 | - public function disable() | ||
169 | - { | ||
170 | - Menu::disable('example'); | ||
171 | - } | ||
172 | - | ||
173 | -} |
addons/example/config.php
已删除
100755 → 0
1 | -<?php | ||
2 | - | ||
3 | -return [ | ||
4 | - [ | ||
5 | - 'name' => 'condition1', | ||
6 | - 'title' => '条件1', | ||
7 | - 'type' => 'radio', | ||
8 | - 'group' => '选项组一', | ||
9 | - 'content' => [ | ||
10 | - 'value1' => '值1', | ||
11 | - 'value2' => '值2', | ||
12 | - ], | ||
13 | - 'value' => 'value2', | ||
14 | - 'rule' => 'required', | ||
15 | - 'msg' => '', | ||
16 | - 'tip' => '', | ||
17 | - 'ok' => '', | ||
18 | - 'extend' => '', | ||
19 | - ], | ||
20 | - [ | ||
21 | - 'name' => 'condition2', | ||
22 | - 'title' => '条件2', | ||
23 | - 'type' => 'checkbox', | ||
24 | - 'group' => '选项组一', | ||
25 | - 'visible' => 'condition1=value1', | ||
26 | - 'content' => [ | ||
27 | - 'value1' => '值1', | ||
28 | - 'value2' => '值2', | ||
29 | - 'value3' => '值3', | ||
30 | - ], | ||
31 | - 'value' => 'value1,value2', | ||
32 | - 'rule' => 'required', | ||
33 | - 'msg' => '', | ||
34 | - 'tip' => '', | ||
35 | - 'ok' => '', | ||
36 | - 'extend' => '', | ||
37 | - ], | ||
38 | - [ | ||
39 | - 'name' => 'condition3', | ||
40 | - 'title' => '条件3', | ||
41 | - 'type' => 'select', | ||
42 | - 'group' => '选项组一', | ||
43 | - 'visible' => 'condition1=value2', | ||
44 | - 'content' => [ | ||
45 | - 'value1' => '值1', | ||
46 | - 'value2' => '值2', | ||
47 | - ], | ||
48 | - 'value' => 'value1', | ||
49 | - 'rule' => 'required', | ||
50 | - 'msg' => '', | ||
51 | - 'tip' => '', | ||
52 | - 'ok' => '', | ||
53 | - 'extend' => '', | ||
54 | - ], | ||
55 | - [ | ||
56 | - 'name' => 'condition4', | ||
57 | - 'title' => '条件4', | ||
58 | - 'type' => 'selects', | ||
59 | - 'group' => '选项组一', | ||
60 | - 'content' => [ | ||
61 | - 'value1' => '值1', | ||
62 | - 'value2' => '值2', | ||
63 | - 'value3' => '值3', | ||
64 | - ], | ||
65 | - 'value' => 'value1,value2', | ||
66 | - 'rule' => 'required', | ||
67 | - 'msg' => '', | ||
68 | - 'tip' => '', | ||
69 | - 'ok' => '', | ||
70 | - 'extend' => '', | ||
71 | - ], | ||
72 | - [ | ||
73 | - 'name' => 'title', | ||
74 | - 'title' => '标题', | ||
75 | - 'type' => 'string', | ||
76 | - 'group' => '选项组一', | ||
77 | - 'visible' => 'condition3=value1', | ||
78 | - 'content' => [], | ||
79 | - 'value' => '3x', | ||
80 | - 'rule' => 'required', | ||
81 | - 'msg' => '', | ||
82 | - 'tip' => '', | ||
83 | - 'ok' => '', | ||
84 | - 'extend' => '', | ||
85 | - ], | ||
86 | - [ | ||
87 | - 'name' => 'domain', | ||
88 | - 'title' => '绑定二级域名前缀', | ||
89 | - 'type' => 'string', | ||
90 | - 'group' => '选项组二', | ||
91 | - 'content' => [], | ||
92 | - 'value' => '', | ||
93 | - 'rule' => 'required', | ||
94 | - 'msg' => '', | ||
95 | - 'tip' => '', | ||
96 | - 'ok' => '', | ||
97 | - 'extend' => '', | ||
98 | - ], | ||
99 | - [ | ||
100 | - 'name' => 'rewrite', | ||
101 | - 'title' => '伪静态', | ||
102 | - 'type' => 'array', | ||
103 | - 'group' => '选项组二', | ||
104 | - 'content' => [], | ||
105 | - 'value' => [ | ||
106 | - 'index/index' => '/example$', | ||
107 | - 'demo/index' => '/example/d/[:name]', | ||
108 | - 'demo/demo1' => '/example/d1/[:name]', | ||
109 | - 'demo/demo2' => '/example/d2/[:name]', | ||
110 | - ], | ||
111 | - 'rule' => 'required', | ||
112 | - 'msg' => '', | ||
113 | - 'tip' => '', | ||
114 | - 'ok' => '', | ||
115 | - 'extend' => '', | ||
116 | - ], | ||
117 | - [ | ||
118 | - 'name' => '__tips__', | ||
119 | - 'title' => '温馨提示', | ||
120 | - 'type' => 'string', | ||
121 | - 'content' => [ | ||
122 | - ], | ||
123 | - 'value' => '这里是提示的文本内容', | ||
124 | - 'rule' => '', | ||
125 | - 'msg' => '', | ||
126 | - 'tip' => '', | ||
127 | - 'ok' => '', | ||
128 | - 'extend' => '', | ||
129 | - ], | ||
130 | -]; |
addons/example/controller/Demo.php
已删除
100755 → 0
1 | -<?php | ||
2 | - | ||
3 | -namespace addons\example\controller; | ||
4 | - | ||
5 | -use think\addons\Controller; | ||
6 | - | ||
7 | -/** | ||
8 | - * 测试控制器 | ||
9 | - */ | ||
10 | -class Demo extends Controller | ||
11 | -{ | ||
12 | - | ||
13 | - protected $layout = 'default'; | ||
14 | - protected $noNeedLogin = ['index', 'demo1']; | ||
15 | - protected $noNeedRight = ['*']; | ||
16 | - | ||
17 | - public function index() | ||
18 | - { | ||
19 | - return $this->view->fetch(); | ||
20 | - } | ||
21 | - | ||
22 | - public function demo1() | ||
23 | - { | ||
24 | - return $this->view->fetch(); | ||
25 | - } | ||
26 | - | ||
27 | - public function demo2() | ||
28 | - { | ||
29 | - return $this->view->fetch(); | ||
30 | - } | ||
31 | - | ||
32 | -} |
addons/example/controller/Index.php
已删除
100755 → 0
addons/example/install.sql
已删除
100755 → 0
此 diff 太大无法显示。
addons/example/view/demo/demo1.html
已删除
100755 → 0
1 | -<!-- Page Content --> | ||
2 | - <div class="container"> | ||
3 | - | ||
4 | - <!-- Page Heading/Breadcrumbs --> | ||
5 | - <div class="row"> | ||
6 | - <div class="col-lg-12"> | ||
7 | - <h1 class="page-header">无需登录页面 | ||
8 | - <small>开发者示例</small> | ||
9 | - </h1> | ||
10 | - <ol class="breadcrumb"> | ||
11 | - <li><a href="{:addon_url('example/index/index')}">插件首页</a> | ||
12 | - </li> | ||
13 | - <li class="active">无需登录页面</li> | ||
14 | - </ol> | ||
15 | - </div> | ||
16 | - </div> | ||
17 | - <!-- /.row --> | ||
18 | - | ||
19 | - <!-- Content Row --> | ||
20 | - <div class="row"> | ||
21 | - <div class="col-lg-12"> | ||
22 | - <p class="well">当前登录页面无需登录即可查看,当前请求的name值为:{$Request.param.name|htmlentities}</p> | ||
23 | - {if $user} | ||
24 | - <p class="well text-danger">但是如果你登录后可以浏览到这段隐藏的信息</p> | ||
25 | - {/if} | ||
26 | - </div> | ||
27 | - </div> | ||
28 | - <!-- /.row --> | ||
29 | - | ||
30 | - <hr> | ||
31 | - | ||
32 | - </div> | ||
33 | - <!-- /.container --> |
addons/example/view/demo/demo2.html
已删除
100755 → 0
1 | -<!-- Page Content --> | ||
2 | -<div class="container"> | ||
3 | - | ||
4 | - <!-- Page Heading/Breadcrumbs --> | ||
5 | - <div class="row"> | ||
6 | - <div class="col-lg-12"> | ||
7 | - <h1 class="page-header">需登录页面 | ||
8 | - <small>开发者示例</small> | ||
9 | - </h1> | ||
10 | - <ol class="breadcrumb"> | ||
11 | - <li><a href="{:addon_url('example/index/index')}">插件首页</a> | ||
12 | - </li> | ||
13 | - <li class="active">需登录页面</li> | ||
14 | - </ol> | ||
15 | - </div> | ||
16 | - </div> | ||
17 | - <!-- /.row --> | ||
18 | - | ||
19 | - <!-- Content Row --> | ||
20 | - <div class="row"> | ||
21 | - <div class="col-lg-12"> | ||
22 | - <p class="well">当前登录页面需要登录后才可以查看,你可以退出后再访问此页面,会提醒登录,当前请求的name值为:{$Request.param.name|htmlentities}</p> | ||
23 | - <p class="well">你好!{$user.nickname|htmlentities},<a href="{:url('index/user/logout')}">注销登录</a></p> | ||
24 | - </div> | ||
25 | - </div> | ||
26 | - <!-- /.row --> | ||
27 | - | ||
28 | - <hr> | ||
29 | - | ||
30 | -</div> | ||
31 | -<!-- /.container --> |
addons/example/view/demo/index.html
已删除
100755 → 0
1 | -<!-- Page Content --> | ||
2 | - <div class="container"> | ||
3 | - | ||
4 | - <!-- Page Heading/Breadcrumbs --> | ||
5 | - <div class="row"> | ||
6 | - <div class="col-lg-12"> | ||
7 | - <h1 class="page-header">使用模板标签和变量 | ||
8 | - <small>开发者示例</small> | ||
9 | - </h1> | ||
10 | - <ol class="breadcrumb"> | ||
11 | - <li><a href="{:addon_url('example/index/index')}">插件首页</a> | ||
12 | - </li> | ||
13 | - <li class="active">使用模板标签和变量</li> | ||
14 | - </ol> | ||
15 | - </div> | ||
16 | - </div> | ||
17 | - <!-- /.row --> | ||
18 | - | ||
19 | - <!-- Content Row --> | ||
20 | - <div class="row"> | ||
21 | - <div class="col-lg-12"> | ||
22 | - <p class="well">当前请求的name值为:{$Request.param.name|htmlentities}</p> | ||
23 | - {literal} | ||
24 | - <pre> | ||
25 | -在插件视图中可以使用所有ThinkPHP5内支持的模板标签和变量,如 | ||
26 | - | ||
27 | -{$Think.server.script_name} // 输出$_SERVER['SCRIPT_NAME']变量 | ||
28 | -{$Think.session.user_id} // 输出$_SESSION['user_id']变量 | ||
29 | -{$Think.get.pageNumber} // 输出$_GET['pageNumber']变量 | ||
30 | -{$Think.cookie.name} // 输出$_COOKIE['name']变量 | ||
31 | - | ||
32 | -// 调用Request对象的get方法 传入参数为id | ||
33 | -{$Request.get.id} | ||
34 | -// 调用Request对象的param方法 传入参数为name | ||
35 | -{$Request.param.name} | ||
36 | -// 调用Request对象的param方法 传入参数为user.nickname | ||
37 | -{$Request.param.user.nickname} | ||
38 | -// 调用Request对象的root方法 | ||
39 | -{$Request.root} | ||
40 | -// 调用Request对象的root方法,并且传入参数true | ||
41 | -{$Request.root.true} | ||
42 | -// 调用Request对象的path方法 | ||
43 | -{$Request.path} | ||
44 | -// 调用Request对象的module方法 | ||
45 | -{$Request.module} | ||
46 | -// 调用Request对象的controller方法 | ||
47 | -{$Request.controller} | ||
48 | -// 调用Request对象的action方法 | ||
49 | -{$Request.action} | ||
50 | -// 调用Request对象的ext方法 | ||
51 | -{$Request.ext} | ||
52 | -// 调用Request对象的host方法 | ||
53 | -{$Request.host} | ||
54 | -// 调用Request对象的ip方法 | ||
55 | -{$Request.ip} | ||
56 | -// 调用Request对象的header方法 | ||
57 | -{$Request.header.accept-encoding} | ||
58 | - </pre> | ||
59 | - {/literal} | ||
60 | - </div> | ||
61 | - </div> | ||
62 | - <!-- /.row --> | ||
63 | - | ||
64 | - <hr> | ||
65 | - | ||
66 | - </div> | ||
67 | - <!-- /.container --> |
addons/example/view/index/index.html
已删除
100755 → 0
1 | -<!-- Header Carousel --> | ||
2 | -<header id="myCarousel" class="carousel slide"> | ||
3 | - <!-- Indicators --> | ||
4 | - <ol class="carousel-indicators"> | ||
5 | - <li data-target="#myCarousel" data-slide-to="0" class="active"></li> | ||
6 | - </ol> | ||
7 | - | ||
8 | - <!-- Wrapper for slides --> | ||
9 | - <div class="carousel-inner"> | ||
10 | - <div class="item active"> | ||
11 | - <a href="javascript:"> | ||
12 | - <div class="fill" style="background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzOTMiIGhlaWdodD0iMjI2Ij48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSJyZ2IoNjMsIDE0MywgODUpIi8+PHBvbHlsaW5lIHBvaW50cz0iMCwgMCwgNjUuNTI5MjU1NTUzMDIzLCAzNy44MzMzMzMzMzMzMzMsIDAsIDc1LjY2NjY2NjY2NjY2NywgMCwgMCIgZmlsbD0iI2RkZCIgZmlsbC1vcGFjaXR5PSIwLjE0MTMzMzMzMzMzMzMzIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAtMzcuODMzMzMzMzMzMzMzKSByb3RhdGUoMTgwLCAzMi43NjQ2Mjc3NzY1MTEsIDM3LjgzMzMzMzMzMzMzMykiLz48cG9seWxpbmUgcG9pbnRzPSIwLCAwLCA2NS41MjkyNTU1NTMwMjMsIDM3LjgzMzMzMzMzMzMzMywgMCwgNzUuNjY2NjY2NjY2NjY3LCAwLCAwIiBmaWxsPSIjZGRkIiBmaWxsLW9wYWNpdHk9IjAuMTQxMzMzMzMzMzMzMzMiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDE4OS4xNjY2NjY2NjY2Nykgcm90YXRlKDE4MCwgMzIuNzY0NjI3Nzc2NTExLCAzNy44MzMzMzMzMzMzMzMpIi8+PHBvbHlsaW5lIHBvaW50cz0iMCwgMCwgNjUuNTI5MjU1NTUzMDIzLCAzNy44MzMzMzMzMzMzMzMsIDAsIDc1LjY2NjY2NjY2NjY2NywgMCwgMCIgZmlsbD0iIzIyMiIgZmlsbC1vcGFjaXR5PSIwLjA2MzMzMzMzMzMzMzMzMyIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNjUuNTI5MjU1NTUzMDIzLCAtMzcuODMzMzMzMzMzMzMzKSByb3RhdGUoMCwgMzIuNzY0NjI3Nzc2NTExLCAzNy44MzMzMzMzMzMzMzMpIi8+PHBvbHlsaW5lIHBvaW50cz0iMCwgMCwgNjUuNTI5MjU1NTUzMDIzLCAzNy44MzMzMzMzMzMzMzMsIDAsIDc1LjY2NjY2NjY2NjY2NywgMCwgMCIgZmlsbD0iIzIyMiIgZmlsbC1vcGFjaXR5PSIwLjA2MzMzMzMzMzMzMzMzMyIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNjUuNTI5MjU1NTUzMDIzLCAxODkuMTY2NjY2NjY2NjcpIHJvdGF0ZSgwLCAzMi43NjQ2Mjc3NzY1MTEsIDM3LjgzMzMzMzMzMzMzMykiLz48cG9seWxpbmUgcG9pbnRzPSIwLCAwLCA2NS41MjkyNTU1NTMwMjMsIDM3LjgzMzMzMzMzMzMzMywgMCwgNzUuNjY2NjY2NjY2NjY3LCAwLCAwIiBmaWxsPSIjMjIyIiBmaWxsLW9wYWNpdHk9IjAuMDgwNjY2NjY2NjY2NjY3IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMzEuMDU4NTExMTA2MDUsIC0zNy44MzMzMzMzMzMzMzMpIHJvdGF0ZSgxODAsIDMyLjc2NDYyNzc3NjUxMSwgMzcuODMzMzMzMzMzMzMzKSIvPjxwb2x5bGluZSBwb2ludHM9IjAsIDAsIDY1LjUyOTI1NTU1MzAyMywgMzcuODMzMzMzMzMzMzMzLCAwLCA3NS42NjY2NjY2NjY2NjcsIDAsIDAiIGZpbGw9IiMyMjIiIGZpbGwtb3BhY2l0eT0iMC4wODA2NjY2NjY2NjY2NjciIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEzMS4wNTg1MTExMDYwNSwgMTg5LjE2NjY2NjY2NjY3KSByb3RhdGUoMTgwLCAzMi43NjQ2Mjc3NzY1MTEsIDM3LjgzMzMzMzMzMzMzMykiLz48cG9seWxpbmUgcG9pbnRzPSIwLCAwLCA2NS41MjkyNTU1NTMwMjMsIDM3LjgzMzMzMzMzMzMzMywgMCwgNzUuNjY2NjY2NjY2NjY3LCAwLCAwIiBmaWxsPSIjMjIyIiBmaWxsLW9wYWNpdHk9IjAuMDQ2IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxOTYuNTg3NzY2NjU5MDcsIC0zNy44MzMzMzMzMzMzMzMpIHJvdGF0ZSgwLCAzMi43NjQ2Mjc3NzY1MTEsIDM3LjgzMzMzMzMzMzMzMykiLz48cG9seWxpbmUgcG9pbnRzPSIwLCAwLCA2NS41MjkyNTU1NTMwMjMsIDM3LjgzMzMzMzMzMzMzMywgMCwgNzUuNjY2NjY2NjY2NjY3LCAwLCAwIiBmaWxsPSIjMjIyIiBmaWxsLW9wYWNpdHk9IjAuMDQ2IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxOTYuNTg3NzY2NjU5MDcsIDE4OS4xNjY2NjY2NjY2Nykgcm90YXRlKDAsIDMyLjc2NDYyNzc3NjUxMSwgMzcuODMzMzMzMzMzMzMzKSIvPjxwb2x5bGluZSBwb2ludHM9IjAsIDAsIDY1LjUyOTI1NTU1MzAyMywgMzcuODMzMzMzMzMzMzMzLCAwLCA3NS42NjY2NjY2NjY2NjcsIDAsIDAiIGZpbGw9IiNkZGQiIGZpbGwtb3BhY2l0eT0iMC4wODkzMzMzMzMzMzMzMzMiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI2Mi4xMTcwMjIyMTIwOSwgLTM3LjgzMzMzMzMzMzMzMykgcm90YXRlKDE4MCwgMzIuNzY0NjI3Nzc2NTExLCAzNy44MzMzMzMzMzMzMzMpIi8+PHBvbHlsaW5lIHBvaW50cz0iMCwgMCwgNjUuNTI5MjU1NTUzMDIzLCAzNy44MzMzMzMzMzMzMzMsIDAsIDc1LjY2NjY2NjY2NjY2NywgMCwgMCIgZmlsbD0iI2RkZCIgZmlsbC1vcGFjaXR5PSIwLjA4OTMzMzMzMzMzMzMzMyIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjYyLjExNzAyMjIxMjA5LCAxODkuMTY2NjY2NjY2NjcpIHJvdGF0ZSgxODAsIDMyLjc2NDYyNzc3NjUxMSwgMzcuODMzMzMzMzMzMzMzKSIvPjxwb2x5bGluZSBwb2ludHM9IjAsIDAsIDY1LjUyOTI1NTU1MzAyMywgMzcuODMzMzMzMzMzMzMzLCAwLCA3NS42NjY2NjY2NjY2NjcsIDAsIDAiIGZpbGw9IiNkZGQiIGZpbGwtb3BhY2l0eT0iMC4xNDEzMzMzMzMzMzMzMyIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMzI3LjY0NjI3Nzc2NTExLCAtMzcuODMzMzMzMzMzMzMzKSByb3RhdGUoMCwgMzIuNzY0NjI3Nzc2NTExLCAzNy44MzMzMzMzMzMzMzMpIi8+PHBvbHlsaW5lIHBvaW50cz0iMCwgMCwgNjUuNTI5MjU1NTUzMDIzLCAzNy44MzMzMzMzMzMzMzMsIDAsIDc1LjY2NjY2NjY2NjY2NywgMCwgMCIgZmlsbD0iI2RkZCIgZmlsbC1vcGFjaXR5PSIwLjE0MTMzMzMzMzMzMzMzIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzMjcuNjQ2Mjc3NzY1MTEsIDE4OS4xNjY2NjY2NjY2Nykgcm90YXRlKDAsIDMyLjc2NDYyNzc3NjUxMSwgMzcuODMzMzMzMzMzMzMzKSIvPjxwb2x5bGluZSBwb2ludHM9IjAsIDAsIDY1LjUyOTI1NTU1MzAyMywgMzcuODMzMzMzMzMzMzMzLCAwLCA3NS42NjY2NjY2NjY2NjcsIDAsIDAiIGZpbGw9IiNkZGQiIGZpbGwtb3BhY2l0eT0iMC4wMiIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMCkgcm90YXRlKDAsIDMyLjc2NDYyNzc3NjUxMSwgMzcuODMzMzMzMzMzMzMzKSIvPjxwb2x5bGluZSBwb2ludHM9IjAsIDAsIDY1LjUyOTI1NTU1MzAyMywgMzcuODMzMzMzMzMzMzMzLCAwLCA3NS42NjY2NjY2NjY2NjcsIDAsIDAiIGZpbGw9IiMyMjIiIGZpbGwtb3BhY2l0eT0iMC4wMjg2NjY2NjY2NjY2NjciIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDY1LjUyOTI1NTU1MzAyMywgMCkgcm90YXRlKDE4MCwgMzIuNzY0NjI3Nzc2NTExLCAzNy44MzMzMzMzMzMzMzMpIi8+PHBvbHlsaW5lIHBvaW50cz0iMCwgMCwgNjUuNTI5MjU1NTUzMDIzLCAzNy44MzMzMzMzMzMzMzMsIDAsIDc1LjY2NjY2NjY2NjY2NywgMCwgMCIgZmlsbD0iIzIyMiIgZmlsbC1vcGFjaXR5PSIwLjExNTMzMzMzMzMzMzMzIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMzEuMDU4NTExMTA2MDUsIDApIHJvdGF0ZSgwLCAzMi43NjQ2Mjc3NzY1MTEsIDM3LjgzMzMzMzMzMzMzMykiLz48cG9seWxpbmUgcG9pbnRzPSIwLCAwLCA2NS41MjkyNTU1NTMwMjMsIDM3LjgzMzMzMzMzMzMzMywgMCwgNzUuNjY2NjY2NjY2NjY3LCAwLCAwIiBmaWxsPSIjMjIyIiBmaWxsLW9wYWNpdHk9IjAuMDgwNjY2NjY2NjY2NjY3IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxOTYuNTg3NzY2NjU5MDcsIDApIHJvdGF0ZSgxODAsIDMyLjc2NDYyNzc3NjUxMSwgMzcuODMzMzMzMzMzMzMzKSIvPjxwb2x5bGluZSBwb2ludHM9IjAsIDAsIDY1LjUyOTI1NTU1MzAyMywgMzcuODMzMzMzMzMzMzMzLCAwLCA3NS42NjY2NjY2NjY2NjcsIDAsIDAiIGZpbGw9IiMyMjIiIGZpbGwtb3BhY2l0eT0iMC4wODA2NjY2NjY2NjY2NjciIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI2Mi4xMTcwMjIyMTIwOSwgMCkgcm90YXRlKDAsIDMyLjc2NDYyNzc3NjUxMSwgMzcuODMzMzMzMzMzMzMzKSIvPjxwb2x5bGluZSBwb2ludHM9IjAsIDAsIDY1LjUyOTI1NTU1MzAyMywgMzcuODMzMzMzMzMzMzMzLCAwLCA3NS42NjY2NjY2NjY2NjcsIDAsIDAiIGZpbGw9IiNkZGQiIGZpbGwtb3BhY2l0eT0iMC4wMiIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMzI3LjY0NjI3Nzc2NTExLCAwKSByb3RhdGUoMTgwLCAzMi43NjQ2Mjc3NzY1MTEsIDM3LjgzMzMzMzMzMzMzMykiLz48cG9seWxpbmUgcG9pbnRzPSIwLCAwLCA2NS41MjkyNTU1NTMwMjMsIDM3LjgzMzMzMzMzMzMzMywgMCwgNzUuNjY2NjY2NjY2NjY3LCAwLCAwIiBmaWxsPSIjMjIyIiBmaWxsLW9wYWNpdHk9IjAuMDgwNjY2NjY2NjY2NjY3IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAzNy44MzMzMzMzMzMzMzMpIHJvdGF0ZSgxODAsIDMyLjc2NDYyNzc3NjUxMSwgMzcuODMzMzMzMzMzMzMzKSIvPjxwb2x5bGluZSBwb2ludHM9IjAsIDAsIDY1LjUyOTI1NTU1MzAyMywgMzcuODMzMzMzMzMzMzMzLCAwLCA3NS42NjY2NjY2NjY2NjcsIDAsIDAiIGZpbGw9IiNkZGQiIGZpbGwtb3BhY2l0eT0iMC4wMiIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNjUuNTI5MjU1NTUzMDIzLCAzNy44MzMzMzMzMzMzMzMpIHJvdGF0ZSgwLCAzMi43NjQ2Mjc3NzY1MTEsIDM3LjgzMzMzMzMzMzMzMykiLz48cG9seWxpbmUgcG9pbnRzPSIwLCAwLCA2NS41MjkyNTU1NTMwMjMsIDM3LjgzMzMzMzMzMzMzMywgMCwgNzUuNjY2NjY2NjY2NjY3LCAwLCAwIiBmaWxsPSIjMjIyIiBmaWxsLW9wYWNpdHk9IjAuMDk4IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMzEuMDU4NTExMTA2MDUsIDM3LjgzMzMzMzMzMzMzMykgcm90YXRlKDE4MCwgMzIuNzY0NjI3Nzc2NTExLCAzNy44MzMzMzMzMzMzMzMpIi8+PHBvbHlsaW5lIHBvaW50cz0iMCwgMCwgNjUuNTI5MjU1NTUzMDIzLCAzNy44MzMzMzMzMzMzMzMsIDAsIDc1LjY2NjY2NjY2NjY2NywgMCwgMCIgZmlsbD0iIzIyMiIgZmlsbC1vcGFjaXR5PSIwLjE1IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxOTYuNTg3NzY2NjU5MDcsIDM3LjgzMzMzMzMzMzMzMykgcm90YXRlKDAsIDMyLjc2NDYyNzc3NjUxMSwgMzcuODMzMzMzMzMzMzMzKSIvPjxwb2x5bGluZSBwb2ludHM9IjAsIDAsIDY1LjUyOTI1NTU1MzAyMywgMzcuODMzMzMzMzMzMzMzLCAwLCA3NS42NjY2NjY2NjY2NjcsIDAsIDAiIGZpbGw9IiMyMjIiIGZpbGwtb3BhY2l0eT0iMC4wMjg2NjY2NjY2NjY2NjciIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI2Mi4xMTcwMjIyMTIwOSwgMzcuODMzMzMzMzMzMzMzKSByb3RhdGUoMTgwLCAzMi43NjQ2Mjc3NzY1MTEsIDM3LjgzMzMzMzMzMzMzMykiLz48cG9seWxpbmUgcG9pbnRzPSIwLCAwLCA2NS41MjkyNTU1NTMwMjMsIDM3LjgzMzMzMzMzMzMzMywgMCwgNzUuNjY2NjY2NjY2NjY3LCAwLCAwIiBmaWxsPSIjMjIyIiBmaWxsLW9wYWNpdHk9IjAuMDQ2IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzMjcuNjQ2Mjc3NzY1MTEsIDM3LjgzMzMzMzMzMzMzMykgcm90YXRlKDAsIDMyLjc2NDYyNzc3NjUxMSwgMzcuODMzMzMzMzMzMzMzKSIvPjxwb2x5bGluZSBwb2ludHM9IjAsIDAsIDY1LjUyOTI1NTU1MzAyMywgMzcuODMzMzMzMzMzMzMzLCAwLCA3NS42NjY2NjY2NjY2NjcsIDAsIDAiIGZpbGw9IiNkZGQiIGZpbGwtb3BhY2l0eT0iMC4wMzczMzMzMzMzMzMzMzMiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsIDc1LjY2NjY2NjY2NjY2Nykgcm90YXRlKDAsIDMyLjc2NDYyNzc3NjUxMSwgMzcuODMzMzMzMzMzMzMzKSIvPjxwb2x5bGluZSBwb2ludHM9IjAsIDAsIDY1LjUyOTI1NTU1MzAyMywgMzcuODMzMzMzMzMzMzMzLCAwLCA3NS42NjY2NjY2NjY2NjcsIDAsIDAiIGZpbGw9IiNkZGQiIGZpbGwtb3BhY2l0eT0iMC4wMiIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNjUuNTI5MjU1NTUzMDIzLCA3NS42NjY2NjY2NjY2NjcpIHJvdGF0ZSgxODAsIDMyLjc2NDYyNzc3NjUxMSwgMzcuODMzMzMzMzMzMzMzKSIvPjxwb2x5bGluZSBwb2ludHM9IjAsIDAsIDY1LjUyOTI1NTU1MzAyMywgMzcuODMzMzMzMzMzMzMzLCAwLCA3NS42NjY2NjY2NjY2NjcsIDAsIDAiIGZpbGw9IiMyMjIiIGZpbGwtb3BhY2l0eT0iMC4xNSIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTMxLjA1ODUxMTEwNjA1LCA3NS42NjY2NjY2NjY2NjcpIHJvdGF0ZSgwLCAzMi43NjQ2Mjc3NzY1MTEsIDM3LjgzMzMzMzMzMzMzMykiLz48cG9seWxpbmUgcG9pbnRzPSIwLCAwLCA2NS41MjkyNTU1NTMwMjMsIDM3LjgzMzMzMzMzMzMzMywgMCwgNzUuNjY2NjY2NjY2NjY3LCAwLCAwIiBmaWxsPSIjZGRkIiBmaWxsLW9wYWNpdHk9IjAuMDIiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDE5Ni41ODc3NjY2NTkwNywgNzUuNjY2NjY2NjY2NjY3KSByb3RhdGUoMTgwLCAzMi43NjQ2Mjc3NzY1MTEsIDM3LjgzMzMzMzMzMzMzMykiLz48cG9seWxpbmUgcG9pbnRzPSIwLCAwLCA2NS41MjkyNTU1NTMwMjMsIDM3LjgzMzMzMzMzMzMzMywgMCwgNzUuNjY2NjY2NjY2NjY3LCAwLCAwIiBmaWxsPSIjMjIyIiBmaWxsLW9wYWNpdHk9IjAuMDk4IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNjIuMTE3MDIyMjEyMDksIDc1LjY2NjY2NjY2NjY2Nykgcm90YXRlKDAsIDMyLjc2NDYyNzc3NjUxMSwgMzcuODMzMzMzMzMzMzMzKSIvPjxwb2x5bGluZSBwb2ludHM9IjAsIDAsIDY1LjUyOTI1NTU1MzAyMywgMzcuODMzMzMzMzMzMzMzLCAwLCA3NS42NjY2NjY2NjY2NjcsIDAsIDAiIGZpbGw9IiMyMjIiIGZpbGwtb3BhY2l0eT0iMC4wNjMzMzMzMzMzMzMzMzMiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDMyNy42NDYyNzc3NjUxMSwgNzUuNjY2NjY2NjY2NjY3KSByb3RhdGUoMTgwLCAzMi43NjQ2Mjc3NzY1MTEsIDM3LjgzMzMzMzMzMzMzMykiLz48cG9seWxpbmUgcG9pbnRzPSIwLCAwLCA2NS41MjkyNTU1NTMwMjMsIDM3LjgzMzMzMzMzMzMzMywgMCwgNzUuNjY2NjY2NjY2NjY3LCAwLCAwIiBmaWxsPSIjZGRkIiBmaWxsLW9wYWNpdHk9IjAuMTI0IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAxMTMuNSkgcm90YXRlKDE4MCwgMzIuNzY0NjI3Nzc2NTExLCAzNy44MzMzMzMzMzMzMzMpIi8+PHBvbHlsaW5lIHBvaW50cz0iMCwgMCwgNjUuNTI5MjU1NTUzMDIzLCAzNy44MzMzMzMzMzMzMzMsIDAsIDc1LjY2NjY2NjY2NjY2NywgMCwgMCIgZmlsbD0iIzIyMiIgZmlsbC1vcGFjaXR5PSIwLjE1IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg2NS41MjkyNTU1NTMwMjMsIDExMy41KSByb3RhdGUoMCwgMzIuNzY0NjI3Nzc2NTExLCAzNy44MzMzMzMzMzMzMzMpIi8+PHBvbHlsaW5lIHBvaW50cz0iMCwgMCwgNjUuNTI5MjU1NTUzMDIzLCAzNy44MzMzMzMzMzMzMzMsIDAsIDc1LjY2NjY2NjY2NjY2NywgMCwgMCIgZmlsbD0iI2RkZCIgZmlsbC1vcGFjaXR5PSIwLjEyNCIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTMxLjA1ODUxMTEwNjA1LCAxMTMuNSkgcm90YXRlKDE4MCwgMzIuNzY0NjI3Nzc2NTExLCAzNy44MzMzMzMzMzMzMzMpIi8+PHBvbHlsaW5lIHBvaW50cz0iMCwgMCwgNjUuNTI5MjU1NTUzMDIzLCAzNy44MzMzMzMzMzMzMzMsIDAsIDc1LjY2NjY2NjY2NjY2NywgMCwgMCIgZmlsbD0iI2RkZCIgZmlsbC1vcGFjaXR5PSIwLjEwNjY2NjY2NjY2NjY3IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxOTYuNTg3NzY2NjU5MDcsIDExMy41KSByb3RhdGUoMCwgMzIuNzY0NjI3Nzc2NTExLCAzNy44MzMzMzMzMzMzMzMpIi8+PHBvbHlsaW5lIHBvaW50cz0iMCwgMCwgNjUuNTI5MjU1NTUzMDIzLCAzNy44MzMzMzMzMzMzMzMsIDAsIDc1LjY2NjY2NjY2NjY2NywgMCwgMCIgZmlsbD0iI2RkZCIgZmlsbC1vcGFjaXR5PSIwLjEyNCIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjYyLjExNzAyMjIxMjA5LCAxMTMuNSkgcm90YXRlKDE4MCwgMzIuNzY0NjI3Nzc2NTExLCAzNy44MzMzMzMzMzMzMzMpIi8+PHBvbHlsaW5lIHBvaW50cz0iMCwgMCwgNjUuNTI5MjU1NTUzMDIzLCAzNy44MzMzMzMzMzMzMzMsIDAsIDc1LjY2NjY2NjY2NjY2NywgMCwgMCIgZmlsbD0iIzIyMiIgZmlsbC1vcGFjaXR5PSIwLjEzMjY2NjY2NjY2NjY3IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgzMjcuNjQ2Mjc3NzY1MTEsIDExMy41KSByb3RhdGUoMCwgMzIuNzY0NjI3Nzc2NTExLCAzNy44MzMzMzMzMzMzMzMpIi8+PHBvbHlsaW5lIHBvaW50cz0iMCwgMCwgNjUuNTI5MjU1NTUzMDIzLCAzNy44MzMzMzMzMzMzMzMsIDAsIDc1LjY2NjY2NjY2NjY2NywgMCwgMCIgZmlsbD0iI2RkZCIgZmlsbC1vcGFjaXR5PSIwLjEwNjY2NjY2NjY2NjY3IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAxNTEuMzMzMzMzMzMzMzMpIHJvdGF0ZSgwLCAzMi43NjQ2Mjc3NzY1MTEsIDM3LjgzMzMzMzMzMzMzMykiLz48cG9seWxpbmUgcG9pbnRzPSIwLCAwLCA2NS41MjkyNTU1NTMwMjMsIDM3LjgzMzMzMzMzMzMzMywgMCwgNzUuNjY2NjY2NjY2NjY3LCAwLCAwIiBmaWxsPSIjMjIyIiBmaWxsLW9wYWNpdHk9IjAuMDYzMzMzMzMzMzMzMzMzIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg2NS41MjkyNTU1NTMwMjMsIDE1MS4zMzMzMzMzMzMzMykgcm90YXRlKDE4MCwgMzIuNzY0NjI3Nzc2NTExLCAzNy44MzMzMzMzMzMzMzMpIi8+PHBvbHlsaW5lIHBvaW50cz0iMCwgMCwgNjUuNTI5MjU1NTUzMDIzLCAzNy44MzMzMzMzMzMzMzMsIDAsIDc1LjY2NjY2NjY2NjY2NywgMCwgMCIgZmlsbD0iIzIyMiIgZmlsbC1vcGFjaXR5PSIwLjA4MDY2NjY2NjY2NjY2NyIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utb3BhY2l0eT0iMC4wMiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTMxLjA1ODUxMTEwNjA1LCAxNTEuMzMzMzMzMzMzMzMpIHJvdGF0ZSgwLCAzMi43NjQ2Mjc3NzY1MTEsIDM3LjgzMzMzMzMzMzMzMykiLz48cG9seWxpbmUgcG9pbnRzPSIwLCAwLCA2NS41MjkyNTU1NTMwMjMsIDM3LjgzMzMzMzMzMzMzMywgMCwgNzUuNjY2NjY2NjY2NjY3LCAwLCAwIiBmaWxsPSIjZGRkIiBmaWxsLW9wYWNpdHk9IjAuMDIiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDE5Ni41ODc3NjY2NTkwNywgMTUxLjMzMzMzMzMzMzMzKSByb3RhdGUoMTgwLCAzMi43NjQ2Mjc3NzY1MTEsIDM3LjgzMzMzMzMzMzMzMykiLz48cG9seWxpbmUgcG9pbnRzPSIwLCAwLCA2NS41MjkyNTU1NTMwMjMsIDM3LjgzMzMzMzMzMzMzMywgMCwgNzUuNjY2NjY2NjY2NjY3LCAwLCAwIiBmaWxsPSIjMjIyIiBmaWxsLW9wYWNpdHk9IjAuMDk4IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjAyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNjIuMTE3MDIyMjEyMDksIDE1MS4zMzMzMzMzMzMzMykgcm90YXRlKDAsIDMyLjc2NDYyNzc3NjUxMSwgMzcuODMzMzMzMzMzMzMzKSIvPjxwb2x5bGluZSBwb2ludHM9IjAsIDAsIDY1LjUyOTI1NTU1MzAyMywgMzcuODMzMzMzMzMzMzMzLCAwLCA3NS42NjY2NjY2NjY2NjcsIDAsIDAiIGZpbGw9IiNkZGQiIGZpbGwtb3BhY2l0eT0iMC4xMjQiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9IjAuMDIiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDMyNy42NDYyNzc3NjUxMSwgMTUxLjMzMzMzMzMzMzMzKSByb3RhdGUoMTgwLCAzMi43NjQ2Mjc3NzY1MTEsIDM3LjgzMzMzMzMzMzMzMykiLz48L3N2Zz4=');"></div> | ||
13 | - <div class="carousel-body"> | ||
14 | - <div class="container"> | ||
15 | - <h1 class="display-1 text-white">开发示例</h1> | ||
16 | - <h2 class="display-4 text-white">表格、登录相关示例</h2> | ||
17 | - </div> | ||
18 | - </div> | ||
19 | - </a> | ||
20 | - </div> | ||
21 | - </div> | ||
22 | -</header> | ||
23 | - | ||
24 | -<!-- Page Content --> | ||
25 | -<div class="container"> | ||
26 | - | ||
27 | - <!-- Marketing Icons Section --> | ||
28 | - <div class="row"> | ||
29 | - <div class="col-lg-12"> | ||
30 | - <h2 class="page-header"> | ||
31 | - 基础模块 | ||
32 | - </h2> | ||
33 | - </div> | ||
34 | - <div class="col-md-4"> | ||
35 | - <div class="panel panel-default"> | ||
36 | - <div class="panel-heading"> | ||
37 | - <h4><i class="fa fa-fw fa-user"></i> 前台模块</h4> | ||
38 | - </div> | ||
39 | - <div class="panel-body"> | ||
40 | - <p>前台模块中包含基础的会员模块,前台模块中的会员账号和API模块中的会员账号是同一账号体系</p> | ||
41 | - <a href="{:url('index/index/index')}" target="_blank" class="btn btn-primary">立即访问</a> | ||
42 | - <a href="{:url('index/user/index')}" target="_blank" class="btn btn-default">会员中心</a> | ||
43 | - </div> | ||
44 | - </div> | ||
45 | - </div> | ||
46 | - <div class="col-md-4"> | ||
47 | - <div class="panel panel-default"> | ||
48 | - <div class="panel-heading"> | ||
49 | - <h4><i class="fa fa-fw fa-gift"></i> API模块</h4> | ||
50 | - </div> | ||
51 | - <div class="panel-body"> | ||
52 | - <p>API模块中包含基础的会员模块,初始化模块、短信发送模块、验证模块</p> | ||
53 | - <a href="{:url('api/index/index')}" target="_blank" class="btn btn-primary">立即访问</a> | ||
54 | - <a href="{:url('api/common/init')}?version=1.0.0" target="_blank" class="btn btn-default">初始化接口</a> | ||
55 | - </div> | ||
56 | - </div> | ||
57 | - </div> | ||
58 | - <div class="col-md-4"> | ||
59 | - <div class="panel panel-default"> | ||
60 | - <div class="panel-heading"> | ||
61 | - <h4><i class="fa fa-fw fa-compass"></i> API文档</h4> | ||
62 | - </div> | ||
63 | - <div class="panel-body"> | ||
64 | - <p>当写完API接口以后,可通过执行php think api一键生成我们所需要的API文档,并且可以直接在线测试</p> | ||
65 | - <a href="{:url('/')}api.html" target="_blank" class="btn btn-primary">立即访问</a> | ||
66 | - </div> | ||
67 | - </div> | ||
68 | - </div> | ||
69 | - </div> | ||
70 | - <div class="row"> | ||
71 | - <div class="col-lg-12"> | ||
72 | - <h2 class="page-header"> | ||
73 | - 功能示例 | ||
74 | - </h2> | ||
75 | - </div> | ||
76 | - <div class="col-md-4"> | ||
77 | - <div class="panel panel-default"> | ||
78 | - <div class="panel-heading"> | ||
79 | - <h4><i class="fa fa-fw fa-check"></i> 使用模板标签和变量</h4> | ||
80 | - </div> | ||
81 | - <div class="panel-body"> | ||
82 | - <a href="{:addon_url('example/demo/index',[':name'=>'s1'])}" class="btn btn-success">查看示例</a> | ||
83 | - </div> | ||
84 | - </div> | ||
85 | - </div> | ||
86 | - <div class="col-md-4"> | ||
87 | - <div class="panel panel-default"> | ||
88 | - <div class="panel-heading"> | ||
89 | - <h4><i class="fa fa-fw fa-gift"></i> 访问不需要登录的页面</h4> | ||
90 | - </div> | ||
91 | - <div class="panel-body"> | ||
92 | - <a href="{:addon_url('example/demo/demo1',[':name'=>'s2'])}" class="btn btn-success">立即访问</a> | ||
93 | - </div> | ||
94 | - </div> | ||
95 | - </div> | ||
96 | - <div class="col-md-4"> | ||
97 | - <div class="panel panel-default"> | ||
98 | - <div class="panel-heading"> | ||
99 | - <h4><i class="fa fa-fw fa-compass"></i> 访问需要登录的页面</h4> | ||
100 | - </div> | ||
101 | - <div class="panel-body"> | ||
102 | - <a href="{:addon_url('example/demo/demo2',[':name'=>'s3'])}" class="btn btn-success">立即访问</a> | ||
103 | - </div> | ||
104 | - </div> | ||
105 | - </div> | ||
106 | - </div> | ||
107 | - <!-- /.row --> | ||
108 | - | ||
109 | - <hr> | ||
110 | - | ||
111 | -</div> |
addons/example/view/layout/default.html
已删除
100755 → 0
1 | -<!DOCTYPE html> | ||
2 | -<html lang="en"> | ||
3 | - | ||
4 | - <head> | ||
5 | - | ||
6 | - <meta charset="utf-8"> | ||
7 | - <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||
8 | - <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
9 | - <meta name="description" content=""> | ||
10 | - <meta name="author" content=""> | ||
11 | - | ||
12 | - <title>开发示例 - {$site.name|htmlentities}</title> | ||
13 | - | ||
14 | - <!-- Bootstrap Core CSS --> | ||
15 | - <link href="__CDN__/assets/libs/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet"> | ||
16 | - | ||
17 | - <!-- Custom CSS --> | ||
18 | - <link href="__ADDON__/css/common.css" rel="stylesheet"> | ||
19 | - | ||
20 | - <!-- Custom Fonts --> | ||
21 | - <link href="__CDN__/assets/libs/font-awesome/css/font-awesome.min.css" rel="stylesheet"> | ||
22 | - | ||
23 | - <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> | ||
24 | - <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> | ||
25 | - <!--[if lt IE 9]> | ||
26 | - <script src="https://cdn.bootcdn.net/ajax/libs/html5shiv/3.7.0/html5shiv.min.js"></script> | ||
27 | - <script src="https://cdn.bootcdn.net/ajax/libs/respond.js/1.4.2/respond.min.js"></script> | ||
28 | - <![endif]--> | ||
29 | - | ||
30 | - </head> | ||
31 | - | ||
32 | - <body> | ||
33 | - | ||
34 | - <!-- Navigation --> | ||
35 | - <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation"> | ||
36 | - <div class="container"> | ||
37 | - <!-- Brand and toggle get grouped for better mobile display --> | ||
38 | - <div class="navbar-header"> | ||
39 | - <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1"> | ||
40 | - <span class="sr-only">Toggle navigation</span> | ||
41 | - <span class="icon-bar"></span> | ||
42 | - <span class="icon-bar"></span> | ||
43 | - <span class="icon-bar"></span> | ||
44 | - </button> | ||
45 | - <a class="navbar-brand" href="{:addon_url('example/index/index')}">{$site.name|htmlentities}</a> | ||
46 | - </div> | ||
47 | - <!-- Collect the nav links, forms, and other content for toggling --> | ||
48 | - <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> | ||
49 | - <ul class="nav navbar-nav navbar-right"> | ||
50 | - <li> | ||
51 | - <a href="{:addon_url('example/index/index')}">插件首页</a> | ||
52 | - </li> | ||
53 | - <li> | ||
54 | - <a href="{:addon_url('example/demo/demo1', [':name'=>'s1'])}">无需登录页面</a> | ||
55 | - </li> | ||
56 | - <li> | ||
57 | - <a href="{:addon_url('example/demo/demo2', [':name'=>'s2'])}">需登录页面</a> | ||
58 | - </li> | ||
59 | - {if $user} | ||
60 | - <li class="dropdown"> | ||
61 | - <a href="#" class="dropdown-toggle" data-toggle="dropdown">欢迎你! {$user.nickname|htmlentities}<b class="caret"></b></a> | ||
62 | - <ul class="dropdown-menu"> | ||
63 | - <li> | ||
64 | - <a href="{:url('index/user/index')}">会员中心</a> | ||
65 | - </li> | ||
66 | - <li> | ||
67 | - <a href="{:url('index/user/profile')}">个人资料</a> | ||
68 | - </li> | ||
69 | - <li> | ||
70 | - <a href="{:url('index/user/logout')}">退出登录</a> | ||
71 | - </li> | ||
72 | - </ul> | ||
73 | - </li> | ||
74 | - {else /} | ||
75 | - <li class="dropdown"> | ||
76 | - <a href="#" class="dropdown-toggle" data-toggle="dropdown">会员中心 <b class="caret"></b></a> | ||
77 | - <ul class="dropdown-menu"> | ||
78 | - <li> | ||
79 | - <a href="{:url('index/user/login')}">登录</a> | ||
80 | - </li> | ||
81 | - <li> | ||
82 | - <a href="{:url('index/user/register')}">注册</a> | ||
83 | - </li> | ||
84 | - </ul> | ||
85 | - </li> | ||
86 | - {/if} | ||
87 | - </ul> | ||
88 | - </div> | ||
89 | - <!-- /.navbar-collapse --> | ||
90 | - </div> | ||
91 | - <!-- /.container --> | ||
92 | - </nav> | ||
93 | - | ||
94 | - {__CONTENT__} | ||
95 | - | ||
96 | - <div class="container"> | ||
97 | - <!-- Footer --> | ||
98 | - <footer> | ||
99 | - <div class="row"> | ||
100 | - <div class="col-lg-12"> | ||
101 | - <p>Copyright © {$site.name|htmlentities} 2022</p> | ||
102 | - </div> | ||
103 | - </div> | ||
104 | - </footer> | ||
105 | - | ||
106 | - </div> | ||
107 | - <!-- /.container --> | ||
108 | - | ||
109 | - <!-- jQuery --> | ||
110 | - <script src="__CDN__/assets/libs/jquery/dist/jquery.min.js"></script> | ||
111 | - | ||
112 | - <!-- Bootstrap Core JavaScript --> | ||
113 | - <script src="__CDN__/assets/libs/bootstrap/dist/js/bootstrap.min.js"></script> | ||
114 | - | ||
115 | - <!-- Script to Activate the Carousel --> | ||
116 | - <script> | ||
117 | - $('.carousel').carousel({ | ||
118 | - interval: 5000 //changes the speed | ||
119 | - }) | ||
120 | - </script> | ||
121 | - | ||
122 | - </body> | ||
123 | - | ||
124 | -</html> |
addons/qrcode/.addonrc
0 → 100644
1 | +{"files":[],"license":"regular","licenseto":"15629","licensekey":"KFf0LdCRl92WXAMO iichcugSItakwphUbFYvYw==","domains":["cardverification.com"],"licensecodes":[],"validations":["867a7ee97b89c3a1f31235c70da23ecf"]} |
addons/qrcode/Qrcode.php
0 → 100644
1 | +<?php | ||
2 | + | ||
3 | +namespace addons\qrcode; | ||
4 | + | ||
5 | +use think\Addons; | ||
6 | +use think\Loader; | ||
7 | + | ||
8 | +/** | ||
9 | + * 二维码生成 | ||
10 | + */ | ||
11 | +class Qrcode extends Addons | ||
12 | +{ | ||
13 | + | ||
14 | + /** | ||
15 | + * 插件安装方法 | ||
16 | + * @return bool | ||
17 | + */ | ||
18 | + public function install() | ||
19 | + { | ||
20 | + return true; | ||
21 | + } | ||
22 | + | ||
23 | + /** | ||
24 | + * 插件卸载方法 | ||
25 | + * @return bool | ||
26 | + */ | ||
27 | + public function uninstall() | ||
28 | + { | ||
29 | + return true; | ||
30 | + } | ||
31 | + | ||
32 | + /** | ||
33 | + * 添加命名空间 | ||
34 | + */ | ||
35 | + public function appInit() | ||
36 | + { | ||
37 | + if (!class_exists('\BaconQrCode\Writer')) { | ||
38 | + Loader::addNamespace('BaconQrCode', ADDON_PATH . 'qrcode' . DS . 'library' . DS . 'BaconQrCode' . DS); | ||
39 | + } | ||
40 | + if (!class_exists('\Endroid\QrCode\QrCode')) { | ||
41 | + Loader::addNamespace('Endroid', ADDON_PATH . 'qrcode' . DS . 'library' . DS . 'Endroid' . DS); | ||
42 | + } | ||
43 | + if (!class_exists('\MyCLabs\Enum\Enum')) { | ||
44 | + Loader::addNamespace('MyCLabs', ADDON_PATH . 'qrcode' . DS . 'library' . DS . 'MyCLabs' . DS); | ||
45 | + } | ||
46 | + if (!class_exists('\DASPRiD\Enum\EnumMap')) { | ||
47 | + Loader::addNamespace('DASPRiD', ADDON_PATH . 'qrcode' . DS . 'library' . DS . 'DASPRiD' . DS); | ||
48 | + } | ||
49 | + } | ||
50 | + | ||
51 | +} |
addons/qrcode/config.php
0 → 100644
1 | +<?php | ||
2 | + | ||
3 | +return [ | ||
4 | + [ | ||
5 | + 'name' => 'text', | ||
6 | + 'title' => '默认文本', | ||
7 | + 'type' => 'string', | ||
8 | + 'content' => [], | ||
9 | + 'value' => 'Hello world!', | ||
10 | + 'rule' => 'required', | ||
11 | + 'msg' => '', | ||
12 | + 'tip' => '', | ||
13 | + 'ok' => '', | ||
14 | + 'extend' => '', | ||
15 | + ], | ||
16 | + [ | ||
17 | + 'name' => 'size', | ||
18 | + 'title' => '默认宽高', | ||
19 | + 'type' => 'number', | ||
20 | + 'content' => [], | ||
21 | + 'value' => '300', | ||
22 | + 'rule' => 'required', | ||
23 | + 'msg' => '', | ||
24 | + 'tip' => '', | ||
25 | + 'ok' => '', | ||
26 | + 'extend' => '', | ||
27 | + ], | ||
28 | + [ | ||
29 | + 'name' => 'padding', | ||
30 | + 'title' => '默认边距', | ||
31 | + 'type' => 'number', | ||
32 | + 'content' => [], | ||
33 | + 'value' => '15', | ||
34 | + 'rule' => 'required', | ||
35 | + 'msg' => '', | ||
36 | + 'tip' => '', | ||
37 | + 'ok' => '', | ||
38 | + 'extend' => '', | ||
39 | + ], | ||
40 | + [ | ||
41 | + 'name' => 'format', | ||
42 | + 'title' => '默认格式', | ||
43 | + 'type' => 'radio', | ||
44 | + 'content' => [ | ||
45 | + 'png' => 'PNG', | ||
46 | + 'svg' => 'SVG(不支持标签)', | ||
47 | + ], | ||
48 | + 'value' => 'png', | ||
49 | + 'rule' => 'required', | ||
50 | + 'msg' => '', | ||
51 | + 'tip' => '', | ||
52 | + 'ok' => '', | ||
53 | + 'extend' => '', | ||
54 | + ], | ||
55 | + [ | ||
56 | + 'name' => 'errorlevel', | ||
57 | + 'title' => '容错级别', | ||
58 | + 'type' => 'radio', | ||
59 | + 'content' => [ | ||
60 | + 'low' => '低', | ||
61 | + 'medium' => '中', | ||
62 | + 'quartile' => '高', | ||
63 | + 'high' => '超高', | ||
64 | + ], | ||
65 | + 'value' => 'medium', | ||
66 | + 'rule' => 'required', | ||
67 | + 'msg' => '', | ||
68 | + 'tip' => '', | ||
69 | + 'ok' => '', | ||
70 | + 'extend' => '', | ||
71 | + ], | ||
72 | + [ | ||
73 | + 'name' => 'foreground', | ||
74 | + 'title' => '前景色', | ||
75 | + 'type' => 'string', | ||
76 | + 'content' => [], | ||
77 | + 'value' => '#000000', | ||
78 | + 'rule' => 'required', | ||
79 | + 'msg' => '', | ||
80 | + 'tip' => '', | ||
81 | + 'ok' => '', | ||
82 | + 'extend' => '', | ||
83 | + ], | ||
84 | + [ | ||
85 | + 'name' => 'background', | ||
86 | + 'title' => '背景色', | ||
87 | + 'type' => 'string', | ||
88 | + 'content' => [], | ||
89 | + 'value' => '#ffffff', | ||
90 | + 'rule' => 'required', | ||
91 | + 'msg' => '', | ||
92 | + 'tip' => '', | ||
93 | + 'ok' => '', | ||
94 | + 'extend' => '', | ||
95 | + ], | ||
96 | + [ | ||
97 | + 'name' => 'label', | ||
98 | + 'title' => '默认标签', | ||
99 | + 'type' => 'string', | ||
100 | + 'content' => [], | ||
101 | + 'value' => '', | ||
102 | + 'rule' => '', | ||
103 | + 'msg' => '', | ||
104 | + 'tip' => '', | ||
105 | + 'ok' => '', | ||
106 | + 'extend' => '', | ||
107 | + ], | ||
108 | + [ | ||
109 | + 'name' => 'labelfontsize', | ||
110 | + 'title' => '标签字体大小', | ||
111 | + 'type' => 'number', | ||
112 | + 'content' => [], | ||
113 | + 'value' => '14', | ||
114 | + 'rule' => '', | ||
115 | + 'msg' => '', | ||
116 | + 'tip' => '', | ||
117 | + 'ok' => '', | ||
118 | + 'extend' => '', | ||
119 | + ], | ||
120 | + [ | ||
121 | + 'name' => 'labelfontpath', | ||
122 | + 'title' => '标签字体', | ||
123 | + 'type' => 'file', | ||
124 | + 'content' => [], | ||
125 | + 'value' => '/assets/fonts/SourceHanSansK-Regular.ttf', | ||
126 | + 'rule' => 'required', | ||
127 | + 'msg' => '', | ||
128 | + 'tip' => '', | ||
129 | + 'ok' => '', | ||
130 | + 'extend' => '', | ||
131 | + ], | ||
132 | + [ | ||
133 | + 'name' => 'labelalignment', | ||
134 | + 'title' => '标签对齐方式', | ||
135 | + 'type' => 'radio', | ||
136 | + 'content' => [ | ||
137 | + 'left' => '左', | ||
138 | + 'center' => '居中', | ||
139 | + 'right' => '右', | ||
140 | + ], | ||
141 | + 'value' => 'center', | ||
142 | + 'rule' => '', | ||
143 | + 'msg' => '', | ||
144 | + 'tip' => '', | ||
145 | + 'ok' => '', | ||
146 | + 'extend' => '', | ||
147 | + ], | ||
148 | + [ | ||
149 | + 'name' => 'logo', | ||
150 | + 'title' => '默认显示Logo', | ||
151 | + 'type' => 'radio', | ||
152 | + 'content' => [ | ||
153 | + '否', | ||
154 | + '是', | ||
155 | + ], | ||
156 | + 'value' => '0', | ||
157 | + 'rule' => 'required', | ||
158 | + 'msg' => '', | ||
159 | + 'tip' => '', | ||
160 | + 'ok' => '', | ||
161 | + 'extend' => '', | ||
162 | + ], | ||
163 | + [ | ||
164 | + 'name' => 'logopath', | ||
165 | + 'title' => 'Logo图片', | ||
166 | + 'type' => 'image', | ||
167 | + 'content' => [], | ||
168 | + 'value' => '/assets/img/qrcode.png', | ||
169 | + 'rule' => 'required', | ||
170 | + 'msg' => '', | ||
171 | + 'tip' => '', | ||
172 | + 'ok' => '', | ||
173 | + 'extend' => '', | ||
174 | + ], | ||
175 | + [ | ||
176 | + 'name' => 'logosize', | ||
177 | + 'title' => 'Logo大小', | ||
178 | + 'type' => 'number', | ||
179 | + 'content' => [], | ||
180 | + 'value' => '50', | ||
181 | + 'rule' => 'required', | ||
182 | + 'msg' => '', | ||
183 | + 'tip' => '', | ||
184 | + 'ok' => '', | ||
185 | + 'extend' => '', | ||
186 | + ], | ||
187 | + [ | ||
188 | + 'name' => 'writefile', | ||
189 | + 'title' => '写入文件', | ||
190 | + 'type' => 'radio', | ||
191 | + 'content' => [ | ||
192 | + '否', | ||
193 | + '是', | ||
194 | + ], | ||
195 | + 'value' => '1', | ||
196 | + 'rule' => 'required', | ||
197 | + 'msg' => '', | ||
198 | + 'tip' => '', | ||
199 | + 'ok' => '', | ||
200 | + 'extend' => '', | ||
201 | + ], | ||
202 | + [ | ||
203 | + 'name' => 'limitreferer', | ||
204 | + 'title' => '防盗链配置', | ||
205 | + 'type' => 'radio', | ||
206 | + 'content' => [ | ||
207 | + '否', | ||
208 | + '是', | ||
209 | + ], | ||
210 | + 'value' => '0', | ||
211 | + 'rule' => 'required', | ||
212 | + 'msg' => '', | ||
213 | + 'tip' => '', | ||
214 | + 'ok' => '', | ||
215 | + 'extend' => '', | ||
216 | + ], | ||
217 | + [ | ||
218 | + 'name' => 'allowemptyreferer', | ||
219 | + 'title' => '允许空referer', | ||
220 | + 'visible' => 'limitreferer=1', | ||
221 | + 'type' => 'radio', | ||
222 | + 'content' => [ | ||
223 | + '否', | ||
224 | + '是', | ||
225 | + ], | ||
226 | + 'value' => '0', | ||
227 | + 'rule' => 'required', | ||
228 | + 'msg' => '', | ||
229 | + 'tip' => '', | ||
230 | + 'ok' => '', | ||
231 | + 'extend' => '', | ||
232 | + ], | ||
233 | + [ | ||
234 | + 'name' => 'allowrefererlist', | ||
235 | + 'title' => '允许的域名列表', | ||
236 | + 'visible' => 'limitreferer=1', | ||
237 | + 'type' => 'text', | ||
238 | + 'content' => [], | ||
239 | + 'value' => '', | ||
240 | + 'rule' => '', | ||
241 | + 'msg' => '', | ||
242 | + 'tip' => '一行一个域名,支持泛域名,*表示所有域名', | ||
243 | + 'ok' => '', | ||
244 | + 'extend' => '', | ||
245 | + ], | ||
246 | + [ | ||
247 | + 'name' => 'rewrite', | ||
248 | + 'title' => '伪静态', | ||
249 | + 'type' => 'array', | ||
250 | + 'content' => [], | ||
251 | + 'value' => [ | ||
252 | + 'index/index' => '/qrcode$', | ||
253 | + 'index/build' => '/qrcode/build$', | ||
254 | + ], | ||
255 | + 'rule' => 'required', | ||
256 | + 'msg' => '', | ||
257 | + 'tip' => '', | ||
258 | + 'ok' => '', | ||
259 | + 'extend' => '', | ||
260 | + ], | ||
261 | +]; |
addons/qrcode/controller/Index.php
0 → 100644
1 | +<?php | ||
2 | + | ||
3 | +namespace addons\qrcode\controller; | ||
4 | + | ||
5 | +use think\addons\Controller; | ||
6 | +use think\exception\HttpResponseException; | ||
7 | +use think\Response; | ||
8 | + | ||
9 | +/** | ||
10 | + * 二维码生成 | ||
11 | + * | ||
12 | + */ | ||
13 | +class Index extends Controller | ||
14 | +{ | ||
15 | + public function index() | ||
16 | + { | ||
17 | + return $this->view->fetch(); | ||
18 | + } | ||
19 | + | ||
20 | + // 生成二维码 | ||
21 | + public function build() | ||
22 | + { | ||
23 | + $config = get_addon_config('qrcode'); | ||
24 | + if (isset($config['limitreferer']) && $config['limitreferer']) { | ||
25 | + $referer = $this->request->server('HTTP_REFERER', ''); | ||
26 | + $refererInfo = parse_url($referer); | ||
27 | + $refererHost = $referer && $refererInfo ? $refererInfo['host'] : ''; | ||
28 | + $refererHostArr = explode('.', $refererHost); | ||
29 | + $wildcardDomain = ''; | ||
30 | + if (count($refererHostArr) > 2) { | ||
31 | + $refererHostArr[0] = '*'; | ||
32 | + $wildcardDomain = implode('.', $refererHostArr); | ||
33 | + } | ||
34 | + $allowRefererList = $config['allowrefererlist'] ?? ''; | ||
35 | + $domainArr = explode("\n", str_replace("\r", "", $allowRefererList)); | ||
36 | + $domainArr = array_filter(array_unique($domainArr)); | ||
37 | + $domainArr[] = request()->host(true); | ||
38 | + | ||
39 | + $inAllowList = false; | ||
40 | + | ||
41 | + if (in_array('*', $domainArr) || ($refererHost && in_array($refererHost, $domainArr)) || ($wildcardDomain && in_array($wildcardDomain, $domainArr))) { | ||
42 | + $inAllowList = true; | ||
43 | + } | ||
44 | + | ||
45 | + if (!$inAllowList && (!$referer && $config['allowemptyreferer'])) { | ||
46 | + $inAllowList = true; | ||
47 | + } | ||
48 | + | ||
49 | + if (!$inAllowList) { | ||
50 | + $response = Response::create('暂无权限', 'html', 403); | ||
51 | + throw new HttpResponseException($response); | ||
52 | + } | ||
53 | + } | ||
54 | + $params = $this->request->get(); | ||
55 | + $params = array_intersect_key($params, array_flip(['text', 'size', 'padding', 'errorlevel', 'foreground', 'background', 'logo', 'logosize', 'logopath', 'label', 'labelfontsize', 'labelalignment'])); | ||
56 | + | ||
57 | + $params['text'] = $this->request->get('text', $config['text'], 'trim'); | ||
58 | + $params['label'] = $this->request->get('label', $config['label'], 'trim'); | ||
59 | + | ||
60 | + $qrCode = \addons\qrcode\library\Service::qrcode($params); | ||
61 | + | ||
62 | + $mimetype = $config['format'] == 'png' ? 'image/png' : 'image/svg+xml'; | ||
63 | + | ||
64 | + $response = Response::create()->header("Content-Type", $mimetype); | ||
65 | + | ||
66 | + // 直接显示二维码 | ||
67 | + header('Content-Type: ' . $qrCode->getContentType()); | ||
68 | + $response->content($qrCode->writeString()); | ||
69 | + | ||
70 | + // 写入到文件 | ||
71 | + if ($config['writefile']) { | ||
72 | + $qrcodePath = ROOT_PATH . 'public/uploads/qrcode/'; | ||
73 | + if (!is_dir($qrcodePath)) { | ||
74 | + @mkdir($qrcodePath); | ||
75 | + } | ||
76 | + if (is_really_writable($qrcodePath)) { | ||
77 | + $filePath = $qrcodePath . md5(implode('', $params)) . '.' . $config['format']; | ||
78 | + $qrCode->writeFile($filePath); | ||
79 | + } | ||
80 | + } | ||
81 | + | ||
82 | + return $response; | ||
83 | + } | ||
84 | +} |
addons/example/info.ini
→
addons/qrcode/info.ini
100755 → 100644
1 | -name = example | ||
2 | -title = 开发示例 | ||
3 | -intro = FastAdmin多个开发示例 | 1 | +name = qrcode |
2 | +title = 二维码生成 | ||
3 | +intro = 生成二维码插件 | ||
4 | author = FastAdmin | 4 | author = FastAdmin |
5 | website = https://www.fastadmin.net | 5 | website = https://www.fastadmin.net |
6 | -version = 1.1.2 | 6 | +version = 1.0.7 |
7 | state = 1 | 7 | state = 1 |
8 | -url = /addons/example | 8 | +url = /addons/qrcode |
9 | license = regular | 9 | license = regular |
10 | licenseto = 15629 | 10 | licenseto = 15629 |
1 | +<?php | ||
2 | +declare(strict_types = 1); | ||
3 | + | ||
4 | +namespace BaconQrCode\Common; | ||
5 | + | ||
6 | +use BaconQrCode\Exception\InvalidArgumentException; | ||
7 | +use SplFixedArray; | ||
8 | + | ||
9 | +/** | ||
10 | + * A simple, fast array of bits. | ||
11 | + */ | ||
12 | +final class BitArray | ||
13 | +{ | ||
14 | + /** | ||
15 | + * Bits represented as an array of integers. | ||
16 | + * | ||
17 | + * @var SplFixedArray<int> | ||
18 | + */ | ||
19 | + private $bits; | ||
20 | + | ||
21 | + /** | ||
22 | + * Size of the bit array in bits. | ||
23 | + * | ||
24 | + * @var int | ||
25 | + */ | ||
26 | + private $size; | ||
27 | + | ||
28 | + /** | ||
29 | + * Creates a new bit array with a given size. | ||
30 | + */ | ||
31 | + public function __construct(int $size = 0) | ||
32 | + { | ||
33 | + $this->size = $size; | ||
34 | + $this->bits = SplFixedArray::fromArray(array_fill(0, ($this->size + 31) >> 3, 0)); | ||
35 | + } | ||
36 | + | ||
37 | + /** | ||
38 | + * Gets the size in bits. | ||
39 | + */ | ||
40 | + public function getSize() : int | ||
41 | + { | ||
42 | + return $this->size; | ||
43 | + } | ||
44 | + | ||
45 | + /** | ||
46 | + * Gets the size in bytes. | ||
47 | + */ | ||
48 | + public function getSizeInBytes() : int | ||
49 | + { | ||
50 | + return ($this->size + 7) >> 3; | ||
51 | + } | ||
52 | + | ||
53 | + /** | ||
54 | + * Ensures that the array has a minimum capacity. | ||
55 | + */ | ||
56 | + public function ensureCapacity(int $size) : void | ||
57 | + { | ||
58 | + if ($size > count($this->bits) << 5) { | ||
59 | + $this->bits->setSize(($size + 31) >> 5); | ||
60 | + } | ||
61 | + } | ||
62 | + | ||
63 | + /** | ||
64 | + * Gets a specific bit. | ||
65 | + */ | ||
66 | + public function get(int $i) : bool | ||
67 | + { | ||
68 | + return 0 !== ($this->bits[$i >> 5] & (1 << ($i & 0x1f))); | ||
69 | + } | ||
70 | + | ||
71 | + /** | ||
72 | + * Sets a specific bit. | ||
73 | + */ | ||
74 | + public function set(int $i) : void | ||
75 | + { | ||
76 | + $this->bits[$i >> 5] = $this->bits[$i >> 5] | 1 << ($i & 0x1f); | ||
77 | + } | ||
78 | + | ||
79 | + /** | ||
80 | + * Flips a specific bit. | ||
81 | + */ | ||
82 | + public function flip(int $i) : void | ||
83 | + { | ||
84 | + $this->bits[$i >> 5] ^= 1 << ($i & 0x1f); | ||
85 | + } | ||
86 | + | ||
87 | + /** | ||
88 | + * Gets the next set bit position from a given position. | ||
89 | + */ | ||
90 | + public function getNextSet(int $from) : int | ||
91 | + { | ||
92 | + if ($from >= $this->size) { | ||
93 | + return $this->size; | ||
94 | + } | ||
95 | + | ||
96 | + $bitsOffset = $from >> 5; | ||
97 | + $currentBits = $this->bits[$bitsOffset]; | ||
98 | + $bitsLength = count($this->bits); | ||
99 | + $currentBits &= ~((1 << ($from & 0x1f)) - 1); | ||
100 | + | ||
101 | + while (0 === $currentBits) { | ||
102 | + if (++$bitsOffset === $bitsLength) { | ||
103 | + return $this->size; | ||
104 | + } | ||
105 | + | ||
106 | + $currentBits = $this->bits[$bitsOffset]; | ||
107 | + } | ||
108 | + | ||
109 | + $result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits); | ||
110 | + return $result > $this->size ? $this->size : $result; | ||
111 | + } | ||
112 | + | ||
113 | + /** | ||
114 | + * Gets the next unset bit position from a given position. | ||
115 | + */ | ||
116 | + public function getNextUnset(int $from) : int | ||
117 | + { | ||
118 | + if ($from >= $this->size) { | ||
119 | + return $this->size; | ||
120 | + } | ||
121 | + | ||
122 | + $bitsOffset = $from >> 5; | ||
123 | + $currentBits = ~$this->bits[$bitsOffset]; | ||
124 | + $bitsLength = count($this->bits); | ||
125 | + $currentBits &= ~((1 << ($from & 0x1f)) - 1); | ||
126 | + | ||
127 | + while (0 === $currentBits) { | ||
128 | + if (++$bitsOffset === $bitsLength) { | ||
129 | + return $this->size; | ||
130 | + } | ||
131 | + | ||
132 | + $currentBits = ~$this->bits[$bitsOffset]; | ||
133 | + } | ||
134 | + | ||
135 | + $result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits); | ||
136 | + return $result > $this->size ? $this->size : $result; | ||
137 | + } | ||
138 | + | ||
139 | + /** | ||
140 | + * Sets a bulk of bits. | ||
141 | + */ | ||
142 | + public function setBulk(int $i, int $newBits) : void | ||
143 | + { | ||
144 | + $this->bits[$i >> 5] = $newBits; | ||
145 | + } | ||
146 | + | ||
147 | + /** | ||
148 | + * Sets a range of bits. | ||
149 | + * | ||
150 | + * @throws InvalidArgumentException if end is smaller than start | ||
151 | + */ | ||
152 | + public function setRange(int $start, int $end) : void | ||
153 | + { | ||
154 | + if ($end < $start) { | ||
155 | + throw new InvalidArgumentException('End must be greater or equal to start'); | ||
156 | + } | ||
157 | + | ||
158 | + if ($end === $start) { | ||
159 | + return; | ||
160 | + } | ||
161 | + | ||
162 | + --$end; | ||
163 | + | ||
164 | + $firstInt = $start >> 5; | ||
165 | + $lastInt = $end >> 5; | ||
166 | + | ||
167 | + for ($i = $firstInt; $i <= $lastInt; ++$i) { | ||
168 | + $firstBit = $i > $firstInt ? 0 : $start & 0x1f; | ||
169 | + $lastBit = $i < $lastInt ? 31 : $end & 0x1f; | ||
170 | + | ||
171 | + if (0 === $firstBit && 31 === $lastBit) { | ||
172 | + $mask = 0x7fffffff; | ||
173 | + } else { | ||
174 | + $mask = 0; | ||
175 | + | ||
176 | + for ($j = $firstBit; $j < $lastBit; ++$j) { | ||
177 | + $mask |= 1 << $j; | ||
178 | + } | ||
179 | + } | ||
180 | + | ||
181 | + $this->bits[$i] = $this->bits[$i] | $mask; | ||
182 | + } | ||
183 | + } | ||
184 | + | ||
185 | + /** | ||
186 | + * Clears the bit array, unsetting every bit. | ||
187 | + */ | ||
188 | + public function clear() : void | ||
189 | + { | ||
190 | + $bitsLength = count($this->bits); | ||
191 | + | ||
192 | + for ($i = 0; $i < $bitsLength; ++$i) { | ||
193 | + $this->bits[$i] = 0; | ||
194 | + } | ||
195 | + } | ||
196 | + | ||
197 | + /** | ||
198 | + * Checks if a range of bits is set or not set. | ||
199 | + | ||
200 | + * @throws InvalidArgumentException if end is smaller than start | ||
201 | + */ | ||
202 | + public function isRange(int $start, int $end, bool $value) : bool | ||
203 | + { | ||
204 | + if ($end < $start) { | ||
205 | + throw new InvalidArgumentException('End must be greater or equal to start'); | ||
206 | + } | ||
207 | + | ||
208 | + if ($end === $start) { | ||
209 | + return true; | ||
210 | + } | ||
211 | + | ||
212 | + --$end; | ||
213 | + | ||
214 | + $firstInt = $start >> 5; | ||
215 | + $lastInt = $end >> 5; | ||
216 | + | ||
217 | + for ($i = $firstInt; $i <= $lastInt; ++$i) { | ||
218 | + $firstBit = $i > $firstInt ? 0 : $start & 0x1f; | ||
219 | + $lastBit = $i < $lastInt ? 31 : $end & 0x1f; | ||
220 | + | ||
221 | + if (0 === $firstBit && 31 === $lastBit) { | ||
222 | + $mask = 0x7fffffff; | ||
223 | + } else { | ||
224 | + $mask = 0; | ||
225 | + | ||
226 | + for ($j = $firstBit; $j <= $lastBit; ++$j) { | ||
227 | + $mask |= 1 << $j; | ||
228 | + } | ||
229 | + } | ||
230 | + | ||
231 | + if (($this->bits[$i] & $mask) !== ($value ? $mask : 0)) { | ||
232 | + return false; | ||
233 | + } | ||
234 | + } | ||
235 | + | ||
236 | + return true; | ||
237 | + } | ||
238 | + | ||
239 | + /** | ||
240 | + * Appends a bit to the array. | ||
241 | + */ | ||
242 | + public function appendBit(bool $bit) : void | ||
243 | + { | ||
244 | + $this->ensureCapacity($this->size + 1); | ||
245 | + | ||
246 | + if ($bit) { | ||
247 | + $this->bits[$this->size >> 5] = $this->bits[$this->size >> 5] | (1 << ($this->size & 0x1f)); | ||
248 | + } | ||
249 | + | ||
250 | + ++$this->size; | ||
251 | + } | ||
252 | + | ||
253 | + /** | ||
254 | + * Appends a number of bits (up to 32) to the array. | ||
255 | + | ||
256 | + * @throws InvalidArgumentException if num bits is not between 0 and 32 | ||
257 | + */ | ||
258 | + public function appendBits(int $value, int $numBits) : void | ||
259 | + { | ||
260 | + if ($numBits < 0 || $numBits > 32) { | ||
261 | + throw new InvalidArgumentException('Num bits must be between 0 and 32'); | ||
262 | + } | ||
263 | + | ||
264 | + $this->ensureCapacity($this->size + $numBits); | ||
265 | + | ||
266 | + for ($numBitsLeft = $numBits; $numBitsLeft > 0; $numBitsLeft--) { | ||
267 | + $this->appendBit((($value >> ($numBitsLeft - 1)) & 0x01) === 1); | ||
268 | + } | ||
269 | + } | ||
270 | + | ||
271 | + /** | ||
272 | + * Appends another bit array to this array. | ||
273 | + */ | ||
274 | + public function appendBitArray(self $other) : void | ||
275 | + { | ||
276 | + $otherSize = $other->getSize(); | ||
277 | + $this->ensureCapacity($this->size + $other->getSize()); | ||
278 | + | ||
279 | + for ($i = 0; $i < $otherSize; ++$i) { | ||
280 | + $this->appendBit($other->get($i)); | ||
281 | + } | ||
282 | + } | ||
283 | + | ||
284 | + /** | ||
285 | + * Makes an exclusive-or comparision on the current bit array. | ||
286 | + * | ||
287 | + * @throws InvalidArgumentException if sizes don't match | ||
288 | + */ | ||
289 | + public function xorBits(self $other) : void | ||
290 | + { | ||
291 | + $bitsLength = count($this->bits); | ||
292 | + $otherBits = $other->getBitArray(); | ||
293 | + | ||
294 | + if ($bitsLength !== count($otherBits)) { | ||
295 | + throw new InvalidArgumentException('Sizes don\'t match'); | ||
296 | + } | ||
297 | + | ||
298 | + for ($i = 0; $i < $bitsLength; ++$i) { | ||
299 | + $this->bits[$i] = $this->bits[$i] ^ $otherBits[$i]; | ||
300 | + } | ||
301 | + } | ||
302 | + | ||
303 | + /** | ||
304 | + * Converts the bit array to a byte array. | ||
305 | + * | ||
306 | + * @return SplFixedArray<int> | ||
307 | + */ | ||
308 | + public function toBytes(int $bitOffset, int $numBytes) : SplFixedArray | ||
309 | + { | ||
310 | + $bytes = new SplFixedArray($numBytes); | ||
311 | + | ||
312 | + for ($i = 0; $i < $numBytes; ++$i) { | ||
313 | + $byte = 0; | ||
314 | + | ||
315 | + for ($j = 0; $j < 8; ++$j) { | ||
316 | + if ($this->get($bitOffset)) { | ||
317 | + $byte |= 1 << (7 - $j); | ||
318 | + } | ||
319 | + | ||
320 | + ++$bitOffset; | ||
321 | + } | ||
322 | + | ||
323 | + $bytes[$i] = $byte; | ||
324 | + } | ||
325 | + | ||
326 | + return $bytes; | ||
327 | + } | ||
328 | + | ||
329 | + /** | ||
330 | + * Gets the internal bit array. | ||
331 | + * | ||
332 | + * @return SplFixedArray<int> | ||
333 | + */ | ||
334 | + public function getBitArray() : SplFixedArray | ||
335 | + { | ||
336 | + return $this->bits; | ||
337 | + } | ||
338 | + | ||
339 | + /** | ||
340 | + * Reverses the array. | ||
341 | + */ | ||
342 | + public function reverse() : void | ||
343 | + { | ||
344 | + $newBits = new SplFixedArray(count($this->bits)); | ||
345 | + | ||
346 | + for ($i = 0; $i < $this->size; ++$i) { | ||
347 | + if ($this->get($this->size - $i - 1)) { | ||
348 | + $newBits[$i >> 5] = $newBits[$i >> 5] | (1 << ($i & 0x1f)); | ||
349 | + } | ||
350 | + } | ||
351 | + | ||
352 | + $this->bits = $newBits; | ||
353 | + } | ||
354 | + | ||
355 | + /** | ||
356 | + * Returns a string representation of the bit array. | ||
357 | + */ | ||
358 | + public function __toString() : string | ||
359 | + { | ||
360 | + $result = ''; | ||
361 | + | ||
362 | + for ($i = 0; $i < $this->size; ++$i) { | ||
363 | + if (0 === ($i & 0x07)) { | ||
364 | + $result .= ' '; | ||
365 | + } | ||
366 | + | ||
367 | + $result .= $this->get($i) ? 'X' : '.'; | ||
368 | + } | ||
369 | + | ||
370 | + return $result; | ||
371 | + } | ||
372 | +} |
1 | +<?php | ||
2 | +declare(strict_types = 1); | ||
3 | + | ||
4 | +namespace BaconQrCode\Common; | ||
5 | + | ||
6 | +use BaconQrCode\Exception\InvalidArgumentException; | ||
7 | +use SplFixedArray; | ||
8 | + | ||
9 | +/** | ||
10 | + * Bit matrix. | ||
11 | + * | ||
12 | + * Represents a 2D matrix of bits. In function arguments below, and throughout | ||
13 | + * the common module, x is the column position, and y is the row position. The | ||
14 | + * ordering is always x, y. The origin is at the top-left. | ||
15 | + */ | ||
16 | +class BitMatrix | ||
17 | +{ | ||
18 | + /** | ||
19 | + * Width of the bit matrix. | ||
20 | + * | ||
21 | + * @var int | ||
22 | + */ | ||
23 | + private $width; | ||
24 | + | ||
25 | + /** | ||
26 | + * Height of the bit matrix. | ||
27 | + * | ||
28 | + * @var int | ||
29 | + */ | ||
30 | + private $height; | ||
31 | + | ||
32 | + /** | ||
33 | + * Size in bits of each individual row. | ||
34 | + * | ||
35 | + * @var int | ||
36 | + */ | ||
37 | + private $rowSize; | ||
38 | + | ||
39 | + /** | ||
40 | + * Bits representation. | ||
41 | + * | ||
42 | + * @var SplFixedArray<int> | ||
43 | + */ | ||
44 | + private $bits; | ||
45 | + | ||
46 | + /** | ||
47 | + * @throws InvalidArgumentException if a dimension is smaller than zero | ||
48 | + */ | ||
49 | + public function __construct(int $width, int $height = null) | ||
50 | + { | ||
51 | + if (null === $height) { | ||
52 | + $height = $width; | ||
53 | + } | ||
54 | + | ||
55 | + if ($width < 1 || $height < 1) { | ||
56 | + throw new InvalidArgumentException('Both dimensions must be greater than zero'); | ||
57 | + } | ||
58 | + | ||
59 | + $this->width = $width; | ||
60 | + $this->height = $height; | ||
61 | + $this->rowSize = ($width + 31) >> 5; | ||
62 | + $this->bits = SplFixedArray::fromArray(array_fill(0, $this->rowSize * $height, 0)); | ||
63 | + } | ||
64 | + | ||
65 | + /** | ||
66 | + * Gets the requested bit, where true means black. | ||
67 | + */ | ||
68 | + public function get(int $x, int $y) : bool | ||
69 | + { | ||
70 | + $offset = $y * $this->rowSize + ($x >> 5); | ||
71 | + return 0 !== (BitUtils::unsignedRightShift($this->bits[$offset], ($x & 0x1f)) & 1); | ||
72 | + } | ||
73 | + | ||
74 | + /** | ||
75 | + * Sets the given bit to true. | ||
76 | + */ | ||
77 | + public function set(int $x, int $y) : void | ||
78 | + { | ||
79 | + $offset = $y * $this->rowSize + ($x >> 5); | ||
80 | + $this->bits[$offset] = $this->bits[$offset] | (1 << ($x & 0x1f)); | ||
81 | + } | ||
82 | + | ||
83 | + /** | ||
84 | + * Flips the given bit. | ||
85 | + */ | ||
86 | + public function flip(int $x, int $y) : void | ||
87 | + { | ||
88 | + $offset = $y * $this->rowSize + ($x >> 5); | ||
89 | + $this->bits[$offset] = $this->bits[$offset] ^ (1 << ($x & 0x1f)); | ||
90 | + } | ||
91 | + | ||
92 | + /** | ||
93 | + * Clears all bits (set to false). | ||
94 | + */ | ||
95 | + public function clear() : void | ||
96 | + { | ||
97 | + $max = count($this->bits); | ||
98 | + | ||
99 | + for ($i = 0; $i < $max; ++$i) { | ||
100 | + $this->bits[$i] = 0; | ||
101 | + } | ||
102 | + } | ||
103 | + | ||
104 | + /** | ||
105 | + * Sets a square region of the bit matrix to true. | ||
106 | + * | ||
107 | + * @throws InvalidArgumentException if left or top are negative | ||
108 | + * @throws InvalidArgumentException if width or height are smaller than 1 | ||
109 | + * @throws InvalidArgumentException if region does not fit into the matix | ||
110 | + */ | ||
111 | + public function setRegion(int $left, int $top, int $width, int $height) : void | ||
112 | + { | ||
113 | + if ($top < 0 || $left < 0) { | ||
114 | + throw new InvalidArgumentException('Left and top must be non-negative'); | ||
115 | + } | ||
116 | + | ||
117 | + if ($height < 1 || $width < 1) { | ||
118 | + throw new InvalidArgumentException('Width and height must be at least 1'); | ||
119 | + } | ||
120 | + | ||
121 | + $right = $left + $width; | ||
122 | + $bottom = $top + $height; | ||
123 | + | ||
124 | + if ($bottom > $this->height || $right > $this->width) { | ||
125 | + throw new InvalidArgumentException('The region must fit inside the matrix'); | ||
126 | + } | ||
127 | + | ||
128 | + for ($y = $top; $y < $bottom; ++$y) { | ||
129 | + $offset = $y * $this->rowSize; | ||
130 | + | ||
131 | + for ($x = $left; $x < $right; ++$x) { | ||
132 | + $index = $offset + ($x >> 5); | ||
133 | + $this->bits[$index] = $this->bits[$index] | (1 << ($x & 0x1f)); | ||
134 | + } | ||
135 | + } | ||
136 | + } | ||
137 | + | ||
138 | + /** | ||
139 | + * A fast method to retrieve one row of data from the matrix as a BitArray. | ||
140 | + */ | ||
141 | + public function getRow(int $y, BitArray $row = null) : BitArray | ||
142 | + { | ||
143 | + if (null === $row || $row->getSize() < $this->width) { | ||
144 | + $row = new BitArray($this->width); | ||
145 | + } | ||
146 | + | ||
147 | + $offset = $y * $this->rowSize; | ||
148 | + | ||
149 | + for ($x = 0; $x < $this->rowSize; ++$x) { | ||
150 | + $row->setBulk($x << 5, $this->bits[$offset + $x]); | ||
151 | + } | ||
152 | + | ||
153 | + return $row; | ||
154 | + } | ||
155 | + | ||
156 | + /** | ||
157 | + * Sets a row of data from a BitArray. | ||
158 | + */ | ||
159 | + public function setRow(int $y, BitArray $row) : void | ||
160 | + { | ||
161 | + $bits = $row->getBitArray(); | ||
162 | + | ||
163 | + for ($i = 0; $i < $this->rowSize; ++$i) { | ||
164 | + $this->bits[$y * $this->rowSize + $i] = $bits[$i]; | ||
165 | + } | ||
166 | + } | ||
167 | + | ||
168 | + /** | ||
169 | + * This is useful in detecting the enclosing rectangle of a 'pure' barcode. | ||
170 | + * | ||
171 | + * @return int[]|null | ||
172 | + */ | ||
173 | + public function getEnclosingRectangle() : ?array | ||
174 | + { | ||
175 | + $left = $this->width; | ||
176 | + $top = $this->height; | ||
177 | + $right = -1; | ||
178 | + $bottom = -1; | ||
179 | + | ||
180 | + for ($y = 0; $y < $this->height; ++$y) { | ||
181 | + for ($x32 = 0; $x32 < $this->rowSize; ++$x32) { | ||
182 | + $bits = $this->bits[$y * $this->rowSize + $x32]; | ||
183 | + | ||
184 | + if (0 !== $bits) { | ||
185 | + if ($y < $top) { | ||
186 | + $top = $y; | ||
187 | + } | ||
188 | + | ||
189 | + if ($y > $bottom) { | ||
190 | + $bottom = $y; | ||
191 | + } | ||
192 | + | ||
193 | + if ($x32 * 32 < $left) { | ||
194 | + $bit = 0; | ||
195 | + | ||
196 | + while (($bits << (31 - $bit)) === 0) { | ||
197 | + $bit++; | ||
198 | + } | ||
199 | + | ||
200 | + if (($x32 * 32 + $bit) < $left) { | ||
201 | + $left = $x32 * 32 + $bit; | ||
202 | + } | ||
203 | + } | ||
204 | + } | ||
205 | + | ||
206 | + if ($x32 * 32 + 31 > $right) { | ||
207 | + $bit = 31; | ||
208 | + | ||
209 | + while (0 === BitUtils::unsignedRightShift($bits, $bit)) { | ||
210 | + --$bit; | ||
211 | + } | ||
212 | + | ||
213 | + if (($x32 * 32 + $bit) > $right) { | ||
214 | + $right = $x32 * 32 + $bit; | ||
215 | + } | ||
216 | + } | ||
217 | + } | ||
218 | + } | ||
219 | + | ||
220 | + $width = $right - $left; | ||
221 | + $height = $bottom - $top; | ||
222 | + | ||
223 | + if ($width < 0 || $height < 0) { | ||
224 | + return null; | ||
225 | + } | ||
226 | + | ||
227 | + return [$left, $top, $width, $height]; | ||
228 | + } | ||
229 | + | ||
230 | + /** | ||
231 | + * Gets the most top left set bit. | ||
232 | + * | ||
233 | + * This is useful in detecting a corner of a 'pure' barcode. | ||
234 | + * | ||
235 | + * @return int[]|null | ||
236 | + */ | ||
237 | + public function getTopLeftOnBit() : ?array | ||
238 | + { | ||
239 | + $bitsOffset = 0; | ||
240 | + | ||
241 | + while ($bitsOffset < count($this->bits) && 0 === $this->bits[$bitsOffset]) { | ||
242 | + ++$bitsOffset; | ||
243 | + } | ||
244 | + | ||
245 | + if (count($this->bits) === $bitsOffset) { | ||
246 | + return null; | ||
247 | + } | ||
248 | + | ||
249 | + $x = intdiv($bitsOffset, $this->rowSize); | ||
250 | + $y = ($bitsOffset % $this->rowSize) << 5; | ||
251 | + | ||
252 | + $bits = $this->bits[$bitsOffset]; | ||
253 | + $bit = 0; | ||
254 | + | ||
255 | + while (0 === ($bits << (31 - $bit))) { | ||
256 | + ++$bit; | ||
257 | + } | ||
258 | + | ||
259 | + $x += $bit; | ||
260 | + | ||
261 | + return [$x, $y]; | ||
262 | + } | ||
263 | + | ||
264 | + /** | ||
265 | + * Gets the most bottom right set bit. | ||
266 | + * | ||
267 | + * This is useful in detecting a corner of a 'pure' barcode. | ||
268 | + * | ||
269 | + * @return int[]|null | ||
270 | + */ | ||
271 | + public function getBottomRightOnBit() : ?array | ||
272 | + { | ||
273 | + $bitsOffset = count($this->bits) - 1; | ||
274 | + | ||
275 | + while ($bitsOffset >= 0 && 0 === $this->bits[$bitsOffset]) { | ||
276 | + --$bitsOffset; | ||
277 | + } | ||
278 | + | ||
279 | + if ($bitsOffset < 0) { | ||
280 | + return null; | ||
281 | + } | ||
282 | + | ||
283 | + $x = intdiv($bitsOffset, $this->rowSize); | ||
284 | + $y = ($bitsOffset % $this->rowSize) << 5; | ||
285 | + | ||
286 | + $bits = $this->bits[$bitsOffset]; | ||
287 | + $bit = 0; | ||
288 | + | ||
289 | + while (0 === BitUtils::unsignedRightShift($bits, $bit)) { | ||
290 | + --$bit; | ||
291 | + } | ||
292 | + | ||
293 | + $x += $bit; | ||
294 | + | ||
295 | + return [$x, $y]; | ||
296 | + } | ||
297 | + | ||
298 | + /** | ||
299 | + * Gets the width of the matrix, | ||
300 | + */ | ||
301 | + public function getWidth() : int | ||
302 | + { | ||
303 | + return $this->width; | ||
304 | + } | ||
305 | + | ||
306 | + /** | ||
307 | + * Gets the height of the matrix. | ||
308 | + */ | ||
309 | + public function getHeight() : int | ||
310 | + { | ||
311 | + return $this->height; | ||
312 | + } | ||
313 | +} |
1 | +<?php | ||
2 | +declare(strict_types = 1); | ||
3 | + | ||
4 | +namespace BaconQrCode\Common; | ||
5 | + | ||
6 | +/** | ||
7 | + * General bit utilities. | ||
8 | + * | ||
9 | + * All utility methods are based on 32-bit integers and also work on 64-bit | ||
10 | + * systems. | ||
11 | + */ | ||
12 | +final class BitUtils | ||
13 | +{ | ||
14 | + private function __construct() | ||
15 | + { | ||
16 | + } | ||
17 | + | ||
18 | + /** | ||
19 | + * Performs an unsigned right shift. | ||
20 | + * | ||
21 | + * This is the same as the unsigned right shift operator ">>>" in other | ||
22 | + * languages. | ||
23 | + */ | ||
24 | + public static function unsignedRightShift(int $a, int $b) : int | ||
25 | + { | ||
26 | + return ( | ||
27 | + $a >= 0 | ||
28 | + ? $a >> $b | ||
29 | + : (($a & 0x7fffffff) >> $b) | (0x40000000 >> ($b - 1)) | ||
30 | + ); | ||
31 | + } | ||
32 | + | ||
33 | + /** | ||
34 | + * Gets the number of trailing zeros. | ||
35 | + */ | ||
36 | + public static function numberOfTrailingZeros(int $i) : int | ||
37 | + { | ||
38 | + $lastPos = strrpos(str_pad(decbin($i), 32, '0', STR_PAD_LEFT), '1'); | ||
39 | + return $lastPos === false ? 32 : 31 - $lastPos; | ||
40 | + } | ||
41 | +} |
1 | +<?php | ||
2 | +declare(strict_types = 1); | ||
3 | + | ||
4 | +namespace BaconQrCode\Common; | ||
5 | + | ||
6 | +use BaconQrCode\Exception\InvalidArgumentException; | ||
7 | +use DASPRiD\Enum\AbstractEnum; | ||
8 | + | ||
9 | +/** | ||
10 | + * Encapsulates a Character Set ECI, according to "Extended Channel Interpretations" 5.3.1.1 of ISO 18004. | ||
11 | + * | ||
12 | + * @method static self CP437() | ||
13 | + * @method static self ISO8859_1() | ||
14 | + * @method static self ISO8859_2() | ||
15 | + * @method static self ISO8859_3() | ||
16 | + * @method static self ISO8859_4() | ||
17 | + * @method static self ISO8859_5() | ||
18 | + * @method static self ISO8859_6() | ||
19 | + * @method static self ISO8859_7() | ||
20 | + * @method static self ISO8859_8() | ||
21 | + * @method static self ISO8859_9() | ||
22 | + * @method static self ISO8859_10() | ||
23 | + * @method static self ISO8859_11() | ||
24 | + * @method static self ISO8859_12() | ||
25 | + * @method static self ISO8859_13() | ||
26 | + * @method static self ISO8859_14() | ||
27 | + * @method static self ISO8859_15() | ||
28 | + * @method static self ISO8859_16() | ||
29 | + * @method static self SJIS() | ||
30 | + * @method static self CP1250() | ||
31 | + * @method static self CP1251() | ||
32 | + * @method static self CP1252() | ||
33 | + * @method static self CP1256() | ||
34 | + * @method static self UNICODE_BIG_UNMARKED() | ||
35 | + * @method static self UTF8() | ||
36 | + * @method static self ASCII() | ||
37 | + * @method static self BIG5() | ||
38 | + * @method static self GB18030() | ||
39 | + * @method static self EUC_KR() | ||
40 | + */ | ||
41 | +final class CharacterSetEci extends AbstractEnum | ||
42 | +{ | ||
43 | + protected const CP437 = [[0, 2]]; | ||
44 | + protected const ISO8859_1 = [[1, 3], 'ISO-8859-1']; | ||
45 | + protected const ISO8859_2 = [[4], 'ISO-8859-2']; | ||
46 | + protected const ISO8859_3 = [[5], 'ISO-8859-3']; | ||
47 | + protected const ISO8859_4 = [[6], 'ISO-8859-4']; | ||
48 | + protected const ISO8859_5 = [[7], 'ISO-8859-5']; | ||
49 | + protected const ISO8859_6 = [[8], 'ISO-8859-6']; | ||
50 | + protected const ISO8859_7 = [[9], 'ISO-8859-7']; | ||
51 | + protected const ISO8859_8 = [[10], 'ISO-8859-8']; | ||
52 | + protected const ISO8859_9 = [[11], 'ISO-8859-9']; | ||
53 | + protected const ISO8859_10 = [[12], 'ISO-8859-10']; | ||
54 | + protected const ISO8859_11 = [[13], 'ISO-8859-11']; | ||
55 | + protected const ISO8859_12 = [[14], 'ISO-8859-12']; | ||
56 | + protected const ISO8859_13 = [[15], 'ISO-8859-13']; | ||
57 | + protected const ISO8859_14 = [[16], 'ISO-8859-14']; | ||
58 | + protected const ISO8859_15 = [[17], 'ISO-8859-15']; | ||
59 | + protected const ISO8859_16 = [[18], 'ISO-8859-16']; | ||
60 | + protected const SJIS = [[20], 'Shift_JIS']; | ||
61 | + protected const CP1250 = [[21], 'windows-1250']; | ||
62 | + protected const CP1251 = [[22], 'windows-1251']; | ||
63 | + protected const CP1252 = [[23], 'windows-1252']; | ||
64 | + protected const CP1256 = [[24], 'windows-1256']; | ||
65 | + protected const UNICODE_BIG_UNMARKED = [[25], 'UTF-16BE', 'UnicodeBig']; | ||
66 | + protected const UTF8 = [[26], 'UTF-8']; | ||
67 | + protected const ASCII = [[27, 170], 'US-ASCII']; | ||
68 | + protected const BIG5 = [[28]]; | ||
69 | + protected const GB18030 = [[29], 'GB2312', 'EUC_CN', 'GBK']; | ||
70 | + protected const EUC_KR = [[30], 'EUC-KR']; | ||
71 | + | ||
72 | + /** | ||
73 | + * @var int[] | ||
74 | + */ | ||
75 | + private $values; | ||
76 | + | ||
77 | + /** | ||
78 | + * @var string[] | ||
79 | + */ | ||
80 | + private $otherEncodingNames; | ||
81 | + | ||
82 | + /** | ||
83 | + * @var array<int, self>|null | ||
84 | + */ | ||
85 | + private static $valueToEci; | ||
86 | + | ||
87 | + /** | ||
88 | + * @var array<string, self>|null | ||
89 | + */ | ||
90 | + private static $nameToEci; | ||
91 | + | ||
92 | + public function __construct(array $values, string ...$otherEncodingNames) | ||
93 | + { | ||
94 | + $this->values = $values; | ||
95 | + $this->otherEncodingNames = $otherEncodingNames; | ||
96 | + } | ||
97 | + | ||
98 | + /** | ||
99 | + * Returns the primary value. | ||
100 | + */ | ||
101 | + public function getValue() : int | ||
102 | + { | ||
103 | + return $this->values[0]; | ||
104 | + } | ||
105 | + | ||
106 | + /** | ||
107 | + * Gets character set ECI by value. | ||
108 | + * | ||
109 | + * Returns the representing ECI of a given value, or null if it is legal but unsupported. | ||
110 | + * | ||
111 | + * @throws InvalidArgumentException if value is not between 0 and 900 | ||
112 | + */ | ||
113 | + public static function getCharacterSetEciByValue(int $value) : ?self | ||
114 | + { | ||
115 | + if ($value < 0 || $value >= 900) { | ||
116 | + throw new InvalidArgumentException('Value must be between 0 and 900'); | ||
117 | + } | ||
118 | + | ||
119 | + $valueToEci = self::valueToEci(); | ||
120 | + | ||
121 | + if (! array_key_exists($value, $valueToEci)) { | ||
122 | + return null; | ||
123 | + } | ||
124 | + | ||
125 | + return $valueToEci[$value]; | ||
126 | + } | ||
127 | + | ||
128 | + /** | ||
129 | + * Returns character set ECI by name. | ||
130 | + * | ||
131 | + * Returns the representing ECI of a given name, or null if it is legal but unsupported | ||
132 | + */ | ||
133 | + public static function getCharacterSetEciByName(string $name) : ?self | ||
134 | + { | ||
135 | + $nameToEci = self::nameToEci(); | ||
136 | + $name = strtolower($name); | ||
137 | + | ||
138 | + if (! array_key_exists($name, $nameToEci)) { | ||
139 | + return null; | ||
140 | + } | ||
141 | + | ||
142 | + return $nameToEci[$name]; | ||
143 | + } | ||
144 | + | ||
145 | + private static function valueToEci() : array | ||
146 | + { | ||
147 | + if (null !== self::$valueToEci) { | ||
148 | + return self::$valueToEci; | ||
149 | + } | ||
150 | + | ||
151 | + self::$valueToEci = []; | ||
152 | + | ||
153 | + foreach (self::values() as $eci) { | ||
154 | + foreach ($eci->values as $value) { | ||
155 | + self::$valueToEci[$value] = $eci; | ||
156 | + } | ||
157 | + } | ||
158 | + | ||
159 | + return self::$valueToEci; | ||
160 | + } | ||
161 | + | ||
162 | + private static function nameToEci() : array | ||
163 | + { | ||
164 | + if (null !== self::$nameToEci) { | ||
165 | + return self::$nameToEci; | ||
166 | + } | ||
167 | + | ||
168 | + self::$nameToEci = []; | ||
169 | + | ||
170 | + foreach (self::values() as $eci) { | ||
171 | + self::$nameToEci[strtolower($eci->name())] = $eci; | ||
172 | + | ||
173 | + foreach ($eci->otherEncodingNames as $name) { | ||
174 | + self::$nameToEci[strtolower($name)] = $eci; | ||
175 | + } | ||
176 | + } | ||
177 | + | ||
178 | + return self::$nameToEci; | ||
179 | + } | ||
180 | +} |
1 | +<?php | ||
2 | +declare(strict_types = 1); | ||
3 | + | ||
4 | +namespace BaconQrCode\Common; | ||
5 | + | ||
6 | +/** | ||
7 | + * Encapsulates the parameters for one error-correction block in one symbol version. | ||
8 | + * | ||
9 | + * This includes the number of data codewords, and the number of times a block with these parameters is used | ||
10 | + * consecutively in the QR code version's format. | ||
11 | + */ | ||
12 | +final class EcBlock | ||
13 | +{ | ||
14 | + /** | ||
15 | + * How many times the block is used. | ||
16 | + * | ||
17 | + * @var int | ||
18 | + */ | ||
19 | + private $count; | ||
20 | + | ||
21 | + /** | ||
22 | + * Number of data codewords. | ||
23 | + * | ||
24 | + * @var int | ||
25 | + */ | ||
26 | + private $dataCodewords; | ||
27 | + | ||
28 | + public function __construct(int $count, int $dataCodewords) | ||
29 | + { | ||
30 | + $this->count = $count; | ||
31 | + $this->dataCodewords = $dataCodewords; | ||
32 | + } | ||
33 | + | ||
34 | + /** | ||
35 | + * Returns how many times the block is used. | ||
36 | + */ | ||
37 | + public function getCount() : int | ||
38 | + { | ||
39 | + return $this->count; | ||
40 | + } | ||
41 | + | ||
42 | + /** | ||
43 | + * Returns the number of data codewords. | ||
44 | + */ | ||
45 | + public function getDataCodewords() : int | ||
46 | + { | ||
47 | + return $this->dataCodewords; | ||
48 | + } | ||
49 | +} |
1 | +<?php | ||
2 | +declare(strict_types = 1); | ||
3 | + | ||
4 | +namespace BaconQrCode\Common; | ||
5 | + | ||
6 | +/** | ||
7 | + * Encapsulates a set of error-correction blocks in one symbol version. | ||
8 | + * | ||
9 | + * Most versions will use blocks of differing sizes within one version, so, this encapsulates the parameters for each | ||
10 | + * set of blocks. It also holds the number of error-correction codewords per block since it will be the same across all | ||
11 | + * blocks within one version. | ||
12 | + */ | ||
13 | +final class EcBlocks | ||
14 | +{ | ||
15 | + /** | ||
16 | + * Number of EC codewords per block. | ||
17 | + * | ||
18 | + * @var int | ||
19 | + */ | ||
20 | + private $ecCodewordsPerBlock; | ||
21 | + | ||
22 | + /** | ||
23 | + * List of EC blocks. | ||
24 | + * | ||
25 | + * @var EcBlock[] | ||
26 | + */ | ||
27 | + private $ecBlocks; | ||
28 | + | ||
29 | + public function __construct(int $ecCodewordsPerBlock, EcBlock ...$ecBlocks) | ||
30 | + { | ||
31 | + $this->ecCodewordsPerBlock = $ecCodewordsPerBlock; | ||
32 | + $this->ecBlocks = $ecBlocks; | ||
33 | + } | ||
34 | + | ||
35 | + /** | ||
36 | + * Returns the number of EC codewords per block. | ||
37 | + */ | ||
38 | + public function getEcCodewordsPerBlock() : int | ||
39 | + { | ||
40 | + return $this->ecCodewordsPerBlock; | ||
41 | + } | ||
42 | + | ||
43 | + /** | ||
44 | + * Returns the total number of EC block appearances. | ||
45 | + */ | ||
46 | + public function getNumBlocks() : int | ||
47 | + { | ||
48 | + $total = 0; | ||
49 | + | ||
50 | + foreach ($this->ecBlocks as $ecBlock) { | ||
51 | + $total += $ecBlock->getCount(); | ||
52 | + } | ||
53 | + | ||
54 | + return $total; | ||
55 | + } | ||
56 | + | ||
57 | + /** | ||
58 | + * Returns the total count of EC codewords. | ||
59 | + */ | ||
60 | + public function getTotalEcCodewords() : int | ||
61 | + { | ||
62 | + return $this->ecCodewordsPerBlock * $this->getNumBlocks(); | ||
63 | + } | ||
64 | + | ||
65 | + /** | ||
66 | + * Returns the EC blocks included in this collection. | ||
67 | + * | ||
68 | + * @return EcBlock[] | ||
69 | + */ | ||
70 | + public function getEcBlocks() : array | ||
71 | + { | ||
72 | + return $this->ecBlocks; | ||
73 | + } | ||
74 | +} |
1 | +<?php | ||
2 | +declare(strict_types = 1); | ||
3 | + | ||
4 | +namespace BaconQrCode\Common; | ||
5 | + | ||
6 | +use BaconQrCode\Exception\OutOfBoundsException; | ||
7 | +use DASPRiD\Enum\AbstractEnum; | ||
8 | + | ||
9 | +/** | ||
10 | + * Enum representing the four error correction levels. | ||
11 | + * | ||
12 | + * @method static self L() ~7% correction | ||
13 | + * @method static self M() ~15% correction | ||
14 | + * @method static self Q() ~25% correction | ||
15 | + * @method static self H() ~30% correction | ||
16 | + */ | ||
17 | +final class ErrorCorrectionLevel extends AbstractEnum | ||
18 | +{ | ||
19 | + protected const L = [0x01]; | ||
20 | + protected const M = [0x00]; | ||
21 | + protected const Q = [0x03]; | ||
22 | + protected const H = [0x02]; | ||
23 | + | ||
24 | + /** | ||
25 | + * @var int | ||
26 | + */ | ||
27 | + private $bits; | ||
28 | + | ||
29 | + protected function __construct(int $bits) | ||
30 | + { | ||
31 | + $this->bits = $bits; | ||
32 | + } | ||
33 | + | ||
34 | + /** | ||
35 | + * @throws OutOfBoundsException if number of bits is invalid | ||
36 | + */ | ||
37 | + public static function forBits(int $bits) : self | ||
38 | + { | ||
39 | + switch ($bits) { | ||
40 | + case 0: | ||
41 | + return self::M(); | ||
42 | + | ||
43 | + case 1: | ||
44 | + return self::L(); | ||
45 | + | ||
46 | + case 2: | ||
47 | + return self::H(); | ||
48 | + | ||
49 | + case 3: | ||
50 | + return self::Q(); | ||
51 | + } | ||
52 | + | ||
53 | + throw new OutOfBoundsException('Invalid number of bits'); | ||
54 | + } | ||
55 | + | ||
56 | + /** | ||
57 | + * Returns the two bits used to encode this error correction level. | ||
58 | + */ | ||
59 | + public function getBits() : int | ||
60 | + { | ||
61 | + return $this->bits; | ||
62 | + } | ||
63 | +} |
1 | +<?php | ||
2 | +/** | ||
3 | + * BaconQrCode | ||
4 | + * | ||
5 | + * @link http://github.com/Bacon/BaconQrCode For the canonical source repository | ||
6 | + * @copyright 2013 Ben 'DASPRiD' Scholzen | ||
7 | + * @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License | ||
8 | + */ | ||
9 | + | ||
10 | +namespace BaconQrCode\Common; | ||
11 | + | ||
12 | +/** | ||
13 | + * Encapsulates a QR Code's format information, including the data mask used and error correction level. | ||
14 | + */ | ||
15 | +class FormatInformation | ||
16 | +{ | ||
17 | + /** | ||
18 | + * Mask for format information. | ||
19 | + */ | ||
20 | + private const FORMAT_INFO_MASK_QR = 0x5412; | ||
21 | + | ||
22 | + /** | ||
23 | + * Lookup table for decoding format information. | ||
24 | + * | ||
25 | + * See ISO 18004:2006, Annex C, Table C.1 | ||
26 | + */ | ||
27 | + private const FORMAT_INFO_DECODE_LOOKUP = [ | ||
28 | + [0x5412, 0x00], | ||
29 | + [0x5125, 0x01], | ||
30 | + [0x5e7c, 0x02], | ||
31 | + [0x5b4b, 0x03], | ||
32 | + [0x45f9, 0x04], | ||
33 | + [0x40ce, 0x05], | ||
34 | + [0x4f97, 0x06], | ||
35 | + [0x4aa0, 0x07], | ||
36 | + [0x77c4, 0x08], | ||
37 | + [0x72f3, 0x09], | ||
38 | + [0x7daa, 0x0a], | ||
39 | + [0x789d, 0x0b], | ||
40 | + [0x662f, 0x0c], | ||
41 | + [0x6318, 0x0d], | ||
42 | + [0x6c41, 0x0e], | ||
43 | + [0x6976, 0x0f], | ||
44 | + [0x1689, 0x10], | ||
45 | + [0x13be, 0x11], | ||
46 | + [0x1ce7, 0x12], | ||
47 | + [0x19d0, 0x13], | ||
48 | + [0x0762, 0x14], | ||
49 | + [0x0255, 0x15], | ||
50 | + [0x0d0c, 0x16], | ||
51 | + [0x083b, 0x17], | ||
52 | + [0x355f, 0x18], | ||
53 | + [0x3068, 0x19], | ||
54 | + [0x3f31, 0x1a], | ||
55 | + [0x3a06, 0x1b], | ||
56 | + [0x24b4, 0x1c], | ||
57 | + [0x2183, 0x1d], | ||
58 | + [0x2eda, 0x1e], | ||
59 | + [0x2bed, 0x1f], | ||
60 | + ]; | ||
61 | + | ||
62 | + /** | ||
63 | + * Offset i holds the number of 1 bits in the binary representation of i. | ||
64 | + * | ||
65 | + * @var array | ||
66 | + */ | ||
67 | + private const BITS_SET_IN_HALF_BYTE = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4]; | ||
68 | + | ||
69 | + /** | ||
70 | + * Error correction level. | ||
71 | + * | ||
72 | + * @var ErrorCorrectionLevel | ||
73 | + */ | ||
74 | + private $ecLevel; | ||
75 | + | ||
76 | + /** | ||
77 | + * Data mask. | ||
78 | + * | ||
79 | + * @var int | ||
80 | + */ | ||
81 | + private $dataMask; | ||
82 | + | ||
83 | + protected function __construct(int $formatInfo) | ||
84 | + { | ||
85 | + $this->ecLevel = ErrorCorrectionLevel::forBits(($formatInfo >> 3) & 0x3); | ||
86 | + $this->dataMask = $formatInfo & 0x7; | ||
87 | + } | ||
88 | + | ||
89 | + /** | ||
90 | + * Checks how many bits are different between two integers. | ||
91 | + */ | ||
92 | + public static function numBitsDiffering(int $a, int $b) : int | ||
93 | + { | ||
94 | + $a ^= $b; | ||
95 | + | ||
96 | + return ( | ||
97 | + self::BITS_SET_IN_HALF_BYTE[$a & 0xf] | ||
98 | + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 4) & 0xf)] | ||
99 | + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 8) & 0xf)] | ||
100 | + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 12) & 0xf)] | ||
101 | + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 16) & 0xf)] | ||
102 | + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 20) & 0xf)] | ||
103 | + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 24) & 0xf)] | ||
104 | + + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 28) & 0xf)] | ||
105 | + ); | ||
106 | + } | ||
107 | + | ||
108 | + /** | ||
109 | + * Decodes format information. | ||
110 | + */ | ||
111 | + public static function decodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self | ||
112 | + { | ||
113 | + $formatInfo = self::doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2); | ||
114 | + | ||
115 | + if (null !== $formatInfo) { | ||
116 | + return $formatInfo; | ||
117 | + } | ||
118 | + | ||
119 | + // Should return null, but, some QR codes apparently do not mask this info. Try again by actually masking the | ||
120 | + // pattern first. | ||
121 | + return self::doDecodeFormatInformation( | ||
122 | + $maskedFormatInfo1 ^ self::FORMAT_INFO_MASK_QR, | ||
123 | + $maskedFormatInfo2 ^ self::FORMAT_INFO_MASK_QR | ||
124 | + ); | ||
125 | + } | ||
126 | + | ||
127 | + /** | ||
128 | + * Internal method for decoding format information. | ||
129 | + */ | ||
130 | + private static function doDecodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self | ||
131 | + { | ||
132 | + $bestDifference = PHP_INT_MAX; | ||
133 | + $bestFormatInfo = 0; | ||
134 | + | ||
135 | + foreach (self::FORMAT_INFO_DECODE_LOOKUP as $decodeInfo) { | ||
136 | + $targetInfo = $decodeInfo[0]; | ||
137 | + | ||
138 | + if ($targetInfo === $maskedFormatInfo1 || $targetInfo === $maskedFormatInfo2) { | ||
139 | + // Found an exact match | ||
140 | + return new self($decodeInfo[1]); | ||
141 | + } | ||
142 | + | ||
143 | + $bitsDifference = self::numBitsDiffering($maskedFormatInfo1, $targetInfo); | ||
144 | + | ||
145 | + if ($bitsDifference < $bestDifference) { | ||
146 | + $bestFormatInfo = $decodeInfo[1]; | ||
147 | + $bestDifference = $bitsDifference; | ||
148 | + } | ||
149 | + | ||
150 | + if ($maskedFormatInfo1 !== $maskedFormatInfo2) { | ||
151 | + // Also try the other option | ||
152 | + $bitsDifference = self::numBitsDiffering($maskedFormatInfo2, $targetInfo); | ||
153 | + | ||
154 | + if ($bitsDifference < $bestDifference) { | ||
155 | + $bestFormatInfo = $decodeInfo[1]; | ||
156 | + $bestDifference = $bitsDifference; | ||
157 | + } | ||
158 | + } | ||
159 | + } | ||
160 | + | ||
161 | + // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match. | ||
162 | + if ($bestDifference <= 3) { | ||
163 | + return new self($bestFormatInfo); | ||
164 | + } | ||
165 | + | ||
166 | + return null; | ||
167 | + } | ||
168 | + | ||
169 | + /** | ||
170 | + * Returns the error correction level. | ||
171 | + */ | ||
172 | + public function getErrorCorrectionLevel() : ErrorCorrectionLevel | ||
173 | + { | ||
174 | + return $this->ecLevel; | ||
175 | + } | ||
176 | + | ||
177 | + /** | ||
178 | + * Returns the data mask. | ||
179 | + */ | ||
180 | + public function getDataMask() : int | ||
181 | + { | ||
182 | + return $this->dataMask; | ||
183 | + } | ||
184 | + | ||
185 | + /** | ||
186 | + * Hashes the code of the EC level. | ||
187 | + */ | ||
188 | + public function hashCode() : int | ||
189 | + { | ||
190 | + return ($this->ecLevel->getBits() << 3) | $this->dataMask; | ||
191 | + } | ||
192 | + | ||
193 | + /** | ||
194 | + * Verifies if this instance equals another one. | ||
195 | + */ | ||
196 | + public function equals(self $other) : bool | ||
197 | + { | ||
198 | + return ( | ||
199 | + $this->ecLevel === $other->ecLevel | ||
200 | + && $this->dataMask === $other->dataMask | ||
201 | + ); | ||
202 | + } | ||
203 | +} |
1 | +<?php | ||
2 | +declare(strict_types = 1); | ||
3 | + | ||
4 | +namespace BaconQrCode\Common; | ||
5 | + | ||
6 | +use DASPRiD\Enum\AbstractEnum; | ||
7 | + | ||
8 | +/** | ||
9 | + * Enum representing various modes in which data can be encoded to bits. | ||
10 | + * | ||
11 | + * @method static self TERMINATOR() | ||
12 | + * @method static self NUMERIC() | ||
13 | + * @method static self ALPHANUMERIC() | ||
14 | + * @method static self STRUCTURED_APPEND() | ||
15 | + * @method static self BYTE() | ||
16 | + * @method static self ECI() | ||
17 | + * @method static self KANJI() | ||
18 | + * @method static self FNC1_FIRST_POSITION() | ||
19 | + * @method static self FNC1_SECOND_POSITION() | ||
20 | + * @method static self HANZI() | ||
21 | + */ | ||
22 | +final class Mode extends AbstractEnum | ||
23 | +{ | ||
24 | + protected const TERMINATOR = [[0, 0, 0], 0x00]; | ||
25 | + protected const NUMERIC = [[10, 12, 14], 0x01]; | ||
26 | + protected const ALPHANUMERIC = [[9, 11, 13], 0x02]; | ||
27 | + protected const STRUCTURED_APPEND = [[0, 0, 0], 0x03]; | ||
28 | + protected const BYTE = [[8, 16, 16], 0x04]; | ||
29 | + protected const ECI = [[0, 0, 0], 0x07]; | ||
30 | + protected const KANJI = [[8, 10, 12], 0x08]; | ||
31 | + protected const FNC1_FIRST_POSITION = [[0, 0, 0], 0x05]; | ||
32 | + protected const FNC1_SECOND_POSITION = [[0, 0, 0], 0x09]; | ||
33 | + protected const HANZI = [[8, 10, 12], 0x0d]; | ||
34 | + | ||
35 | + /** | ||
36 | + * @var int[] | ||
37 | + */ | ||
38 | + private $characterCountBitsForVersions; | ||
39 | + | ||
40 | + /** | ||
41 | + * @var int | ||
42 | + */ | ||
43 | + private $bits; | ||
44 | + | ||
45 | + protected function __construct(array $characterCountBitsForVersions, int $bits) | ||
46 | + { | ||
47 | + $this->characterCountBitsForVersions = $characterCountBitsForVersions; | ||
48 | + $this->bits = $bits; | ||
49 | + } | ||
50 | + | ||
51 | + /** | ||
52 | + * Returns the number of bits used in a specific QR code version. | ||
53 | + */ | ||
54 | + public function getCharacterCountBits(Version $version) : int | ||
55 | + { | ||
56 | + $number = $version->getVersionNumber(); | ||
57 | + | ||
58 | + if ($number <= 9) { | ||
59 | + $offset = 0; | ||
60 | + } elseif ($number <= 26) { | ||
61 | + $offset = 1; | ||
62 | + } else { | ||
63 | + $offset = 2; | ||
64 | + } | ||
65 | + | ||
66 | + return $this->characterCountBitsForVersions[$offset]; | ||
67 | + } | ||
68 | + | ||
69 | + /** | ||
70 | + * Returns the four bits used to encode this mode. | ||
71 | + */ | ||
72 | + public function getBits() : int | ||
73 | + { | ||
74 | + return $this->bits; | ||
75 | + } | ||
76 | +} |
1 | +<?php | ||
2 | +declare(strict_types = 1); | ||
3 | + | ||
4 | +namespace BaconQrCode\Common; | ||
5 | + | ||
6 | +use BaconQrCode\Exception\InvalidArgumentException; | ||
7 | +use BaconQrCode\Exception\RuntimeException; | ||
8 | +use SplFixedArray; | ||
9 | + | ||
10 | +/** | ||
11 | + * Reed-Solomon codec for 8-bit characters. | ||
12 | + * | ||
13 | + * Based on libfec by Phil Karn, KA9Q. | ||
14 | + */ | ||
15 | +final class ReedSolomonCodec | ||
16 | +{ | ||
17 | + /** | ||
18 | + * Symbol size in bits. | ||
19 | + * | ||
20 | + * @var int | ||
21 | + */ | ||
22 | + private $symbolSize; | ||
23 | + | ||
24 | + /** | ||
25 | + * Block size in symbols. | ||
26 | + * | ||
27 | + * @var int | ||
28 | + */ | ||
29 | + private $blockSize; | ||
30 | + | ||
31 | + /** | ||
32 | + * First root of RS code generator polynomial, index form. | ||
33 | + * | ||
34 | + * @var int | ||
35 | + */ | ||
36 | + private $firstRoot; | ||
37 | + | ||
38 | + /** | ||
39 | + * Primitive element to generate polynomial roots, index form. | ||
40 | + * | ||
41 | + * @var int | ||
42 | + */ | ||
43 | + private $primitive; | ||
44 | + | ||
45 | + /** | ||
46 | + * Prim-th root of 1, index form. | ||
47 | + * | ||
48 | + * @var int | ||
49 | + */ | ||
50 | + private $iPrimitive; | ||
51 | + | ||
52 | + /** | ||
53 | + * RS code generator polynomial degree (number of roots). | ||
54 | + * | ||
55 | + * @var int | ||
56 | + */ | ||
57 | + private $numRoots; | ||
58 | + | ||
59 | + /** | ||
60 | + * Padding bytes at front of shortened block. | ||
61 | + * | ||
62 | + * @var int | ||
63 | + */ | ||
64 | + private $padding; | ||
65 | + | ||
66 | + /** | ||
67 | + * Log lookup table. | ||
68 | + * | ||
69 | + * @var SplFixedArray | ||
70 | + */ | ||
71 | + private $alphaTo; | ||
72 | + | ||
73 | + /** | ||
74 | + * Anti-Log lookup table. | ||
75 | + * | ||
76 | + * @var SplFixedArray | ||
77 | + */ | ||
78 | + private $indexOf; | ||
79 | + | ||
80 | + /** | ||
81 | + * Generator polynomial. | ||
82 | + * | ||
83 | + * @var SplFixedArray | ||
84 | + */ | ||
85 | + private $generatorPoly; | ||
86 | + | ||
87 | + /** | ||
88 | + * @throws InvalidArgumentException if symbol size ist not between 0 and 8 | ||
89 | + * @throws InvalidArgumentException if first root is invalid | ||
90 | + * @throws InvalidArgumentException if num roots is invalid | ||
91 | + * @throws InvalidArgumentException if padding is invalid | ||
92 | + * @throws RuntimeException if field generator polynomial is not primitive | ||
93 | + */ | ||
94 | + public function __construct( | ||
95 | + int $symbolSize, | ||
96 | + int $gfPoly, | ||
97 | + int $firstRoot, | ||
98 | + int $primitive, | ||
99 | + int $numRoots, | ||
100 | + int $padding | ||
101 | + ) { | ||
102 | + if ($symbolSize < 0 || $symbolSize > 8) { | ||
103 | + throw new InvalidArgumentException('Symbol size must be between 0 and 8'); | ||
104 | + } | ||
105 | + | ||
106 | + if ($firstRoot < 0 || $firstRoot >= (1 << $symbolSize)) { | ||
107 | + throw new InvalidArgumentException('First root must be between 0 and ' . (1 << $symbolSize)); | ||
108 | + } | ||
109 | + | ||
110 | + if ($numRoots < 0 || $numRoots >= (1 << $symbolSize)) { | ||
111 | + throw new InvalidArgumentException('Num roots must be between 0 and ' . (1 << $symbolSize)); | ||
112 | + } | ||
113 | + | ||
114 | + if ($padding < 0 || $padding >= ((1 << $symbolSize) - 1 - $numRoots)) { | ||
115 | + throw new InvalidArgumentException( | ||
116 | + 'Padding must be between 0 and ' . ((1 << $symbolSize) - 1 - $numRoots) | ||
117 | + ); | ||
118 | + } | ||
119 | + | ||
120 | + $this->symbolSize = $symbolSize; | ||
121 | + $this->blockSize = (1 << $symbolSize) - 1; | ||
122 | + $this->padding = $padding; | ||
123 | + $this->alphaTo = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false); | ||
124 | + $this->indexOf = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false); | ||
125 | + | ||
126 | + // Generate galous field lookup table | ||
127 | + $this->indexOf[0] = $this->blockSize; | ||
128 | + $this->alphaTo[$this->blockSize] = 0; | ||
129 | + | ||
130 | + $sr = 1; | ||
131 | + | ||
132 | + for ($i = 0; $i < $this->blockSize; ++$i) { | ||
133 | + $this->indexOf[$sr] = $i; | ||
134 | + $this->alphaTo[$i] = $sr; | ||
135 | + | ||
136 | + $sr <<= 1; | ||
137 | + | ||
138 | + if ($sr & (1 << $symbolSize)) { | ||
139 | + $sr ^= $gfPoly; | ||
140 | + } | ||
141 | + | ||
142 | + $sr &= $this->blockSize; | ||
143 | + } | ||
144 | + | ||
145 | + if (1 !== $sr) { | ||
146 | + throw new RuntimeException('Field generator polynomial is not primitive'); | ||
147 | + } | ||
148 | + | ||
149 | + // Form RS code generator polynomial from its roots | ||
150 | + $this->generatorPoly = SplFixedArray::fromArray(array_fill(0, $numRoots + 1, 0), false); | ||
151 | + $this->firstRoot = $firstRoot; | ||
152 | + $this->primitive = $primitive; | ||
153 | + $this->numRoots = $numRoots; | ||
154 | + | ||
155 | + // Find prim-th root of 1, used in decoding | ||
156 | + for ($iPrimitive = 1; ($iPrimitive % $primitive) !== 0; $iPrimitive += $this->blockSize) { | ||
157 | + } | ||
158 | + | ||
159 | + $this->iPrimitive = intdiv($iPrimitive, $primitive); | ||
160 | + | ||
161 | + $this->generatorPoly[0] = 1; | ||
162 | + | ||
163 | + for ($i = 0, $root = $firstRoot * $primitive; $i < $numRoots; ++$i, $root += $primitive) { | ||
164 | + $this->generatorPoly[$i + 1] = 1; | ||
165 | + | ||
166 | + for ($j = $i; $j > 0; $j--) { | ||
167 | + if ($this->generatorPoly[$j] !== 0) { | ||
168 | + $this->generatorPoly[$j] = $this->generatorPoly[$j - 1] ^ $this->alphaTo[ | ||
169 | + $this->modNn($this->indexOf[$this->generatorPoly[$j]] + $root) | ||
170 | + ]; | ||
171 | + } else { | ||
172 | + $this->generatorPoly[$j] = $this->generatorPoly[$j - 1]; | ||
173 | + } | ||
174 | + } | ||
175 | + | ||
176 | + $this->generatorPoly[$j] = $this->alphaTo[$this->modNn($this->indexOf[$this->generatorPoly[0]] + $root)]; | ||
177 | + } | ||
178 | + | ||
179 | + // Convert generator poly to index form for quicker encoding | ||
180 | + for ($i = 0; $i <= $numRoots; ++$i) { | ||
181 | + $this->generatorPoly[$i] = $this->indexOf[$this->generatorPoly[$i]]; | ||
182 | + } | ||
183 | + } | ||
184 | + | ||
185 | + /** | ||
186 | + * Encodes data and writes result back into parity array. | ||
187 | + */ | ||
188 | + public function encode(SplFixedArray $data, SplFixedArray $parity) : void | ||
189 | + { | ||
190 | + for ($i = 0; $i < $this->numRoots; ++$i) { | ||
191 | + $parity[$i] = 0; | ||
192 | + } | ||
193 | + | ||
194 | + $iterations = $this->blockSize - $this->numRoots - $this->padding; | ||
195 | + | ||
196 | + for ($i = 0; $i < $iterations; ++$i) { | ||
197 | + $feedback = $this->indexOf[$data[$i] ^ $parity[0]]; | ||
198 | + | ||
199 | + if ($feedback !== $this->blockSize) { | ||
200 | + // Feedback term is non-zero | ||
201 | + $feedback = $this->modNn($this->blockSize - $this->generatorPoly[$this->numRoots] + $feedback); | ||
202 | + | ||
203 | + for ($j = 1; $j < $this->numRoots; ++$j) { | ||
204 | + $parity[$j] = $parity[$j] ^ $this->alphaTo[ | ||
205 | + $this->modNn($feedback + $this->generatorPoly[$this->numRoots - $j]) | ||
206 | + ]; | ||
207 | + } | ||
208 | + } | ||
209 | + | ||
210 | + for ($j = 0; $j < $this->numRoots - 1; ++$j) { | ||
211 | + $parity[$j] = $parity[$j + 1]; | ||
212 | + } | ||
213 | + | ||
214 | + if ($feedback !== $this->blockSize) { | ||
215 | + $parity[$this->numRoots - 1] = $this->alphaTo[$this->modNn($feedback + $this->generatorPoly[0])]; | ||
216 | + } else { | ||
217 | + $parity[$this->numRoots - 1] = 0; | ||
218 | + } | ||
219 | + } | ||
220 | + } | ||
221 | + | ||
222 | + /** | ||
223 | + * Decodes received data. | ||
224 | + */ | ||
225 | + public function decode(SplFixedArray $data, SplFixedArray $erasures = null) : ?int | ||
226 | + { | ||
227 | + // This speeds up the initialization a bit. | ||
228 | + $numRootsPlusOne = SplFixedArray::fromArray(array_fill(0, $this->numRoots + 1, 0), false); | ||
229 | + $numRoots = SplFixedArray::fromArray(array_fill(0, $this->numRoots, 0), false); | ||
230 | + | ||
231 | + $lambda = clone $numRootsPlusOne; | ||
232 | + $b = clone $numRootsPlusOne; | ||
233 | + $t = clone $numRootsPlusOne; | ||
234 | + $omega = clone $numRootsPlusOne; | ||
235 | + $root = clone $numRoots; | ||
236 | + $loc = clone $numRoots; | ||
237 | + | ||
238 | + $numErasures = (null !== $erasures ? count($erasures) : 0); | ||
239 | + | ||
240 | + // Form the Syndromes; i.e., evaluate data(x) at roots of g(x) | ||
241 | + $syndromes = SplFixedArray::fromArray(array_fill(0, $this->numRoots, $data[0]), false); | ||
242 | + | ||
243 | + for ($i = 1; $i < $this->blockSize - $this->padding; ++$i) { | ||
244 | + for ($j = 0; $j < $this->numRoots; ++$j) { | ||
245 | + if ($syndromes[$j] === 0) { | ||
246 | + $syndromes[$j] = $data[$i]; | ||
247 | + } else { | ||
248 | + $syndromes[$j] = $data[$i] ^ $this->alphaTo[ | ||
249 | + $this->modNn($this->indexOf[$syndromes[$j]] + ($this->firstRoot + $j) * $this->primitive) | ||
250 | + ]; | ||
251 | + } | ||
252 | + } | ||
253 | + } | ||
254 | + | ||
255 | + // Convert syndromes to index form, checking for nonzero conditions | ||
256 | + $syndromeError = 0; | ||
257 | + | ||
258 | + for ($i = 0; $i < $this->numRoots; ++$i) { | ||
259 | + $syndromeError |= $syndromes[$i]; | ||
260 | + $syndromes[$i] = $this->indexOf[$syndromes[$i]]; | ||
261 | + } | ||
262 | + | ||
263 | + if (! $syndromeError) { | ||
264 | + // If syndrome is zero, data[] is a codeword and there are no errors to correct, so return data[] | ||
265 | + // unmodified. | ||
266 | + return 0; | ||
267 | + } | ||
268 | + | ||
269 | + $lambda[0] = 1; | ||
270 | + | ||
271 | + if ($numErasures > 0) { | ||
272 | + // Init lambda to be the erasure locator polynomial | ||
273 | + $lambda[1] = $this->alphaTo[$this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[0]))]; | ||
274 | + | ||
275 | + for ($i = 1; $i < $numErasures; ++$i) { | ||
276 | + $u = $this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[$i])); | ||
277 | + | ||
278 | + for ($j = $i + 1; $j > 0; --$j) { | ||
279 | + $tmp = $this->indexOf[$lambda[$j - 1]]; | ||
280 | + | ||
281 | + if ($tmp !== $this->blockSize) { | ||
282 | + $lambda[$j] = $lambda[$j] ^ $this->alphaTo[$this->modNn($u + $tmp)]; | ||
283 | + } | ||
284 | + } | ||
285 | + } | ||
286 | + } | ||
287 | + | ||
288 | + for ($i = 0; $i <= $this->numRoots; ++$i) { | ||
289 | + $b[$i] = $this->indexOf[$lambda[$i]]; | ||
290 | + } | ||
291 | + | ||
292 | + // Begin Berlekamp-Massey algorithm to determine error+erasure locator polynomial | ||
293 | + $r = $numErasures; | ||
294 | + $el = $numErasures; | ||
295 | + | ||
296 | + while (++$r <= $this->numRoots) { | ||
297 | + // Compute discrepancy at the r-th step in poly form | ||
298 | + $discrepancyR = 0; | ||
299 | + | ||
300 | + for ($i = 0; $i < $r; ++$i) { | ||
301 | + if ($lambda[$i] !== 0 && $syndromes[$r - $i - 1] !== $this->blockSize) { | ||
302 | + $discrepancyR ^= $this->alphaTo[ | ||
303 | + $this->modNn($this->indexOf[$lambda[$i]] + $syndromes[$r - $i - 1]) | ||
304 | + ]; | ||
305 | + } | ||
306 | + } | ||
307 | + | ||
308 | + $discrepancyR = $this->indexOf[$discrepancyR]; | ||
309 | + | ||
310 | + if ($discrepancyR === $this->blockSize) { | ||
311 | + $tmp = $b->toArray(); | ||
312 | + array_unshift($tmp, $this->blockSize); | ||
313 | + array_pop($tmp); | ||
314 | + $b = SplFixedArray::fromArray($tmp, false); | ||
315 | + continue; | ||
316 | + } | ||
317 | + | ||
318 | + $t[0] = $lambda[0]; | ||
319 | + | ||
320 | + for ($i = 0; $i < $this->numRoots; ++$i) { | ||
321 | + if ($b[$i] !== $this->blockSize) { | ||
322 | + $t[$i + 1] = $lambda[$i + 1] ^ $this->alphaTo[$this->modNn($discrepancyR + $b[$i])]; | ||
323 | + } else { | ||
324 | + $t[$i + 1] = $lambda[$i + 1]; | ||
325 | + } | ||
326 | + } | ||
327 | + | ||
328 | + if (2 * $el <= $r + $numErasures - 1) { | ||
329 | + $el = $r + $numErasures - $el; | ||
330 | + | ||
331 | + for ($i = 0; $i <= $this->numRoots; ++$i) { | ||
332 | + $b[$i] = ( | ||
333 | + $lambda[$i] === 0 | ||
334 | + ? $this->blockSize | ||
335 | + : $this->modNn($this->indexOf[$lambda[$i]] - $discrepancyR + $this->blockSize) | ||
336 | + ); | ||
337 | + } | ||
338 | + } else { | ||
339 | + $tmp = $b->toArray(); | ||
340 | + array_unshift($tmp, $this->blockSize); | ||
341 | + array_pop($tmp); | ||
342 | + $b = SplFixedArray::fromArray($tmp, false); | ||
343 | + } | ||
344 | + | ||
345 | + $lambda = clone $t; | ||
346 | + } | ||
347 | + | ||
348 | + // Convert lambda to index form and compute deg(lambda(x)) | ||
349 | + $degLambda = 0; | ||
350 | + | ||
351 | + for ($i = 0; $i <= $this->numRoots; ++$i) { | ||
352 | + $lambda[$i] = $this->indexOf[$lambda[$i]]; | ||
353 | + | ||
354 | + if ($lambda[$i] !== $this->blockSize) { | ||
355 | + $degLambda = $i; | ||
356 | + } | ||
357 | + } | ||
358 | + | ||
359 | + // Find roots of the error+erasure locator polynomial by Chien search. | ||
360 | + $reg = clone $lambda; | ||
361 | + $reg[0] = 0; | ||
362 | + $count = 0; | ||
363 | + $i = 1; | ||
364 | + | ||
365 | + for ($k = $this->iPrimitive - 1; $i <= $this->blockSize; ++$i, $k = $this->modNn($k + $this->iPrimitive)) { | ||
366 | + $q = 1; | ||
367 | + | ||
368 | + for ($j = $degLambda; $j > 0; $j--) { | ||
369 | + if ($reg[$j] !== $this->blockSize) { | ||
370 | + $reg[$j] = $this->modNn($reg[$j] + $j); | ||
371 | + $q ^= $this->alphaTo[$reg[$j]]; | ||
372 | + } | ||
373 | + } | ||
374 | + | ||
375 | + if ($q !== 0) { | ||
376 | + // Not a root | ||
377 | + continue; | ||
378 | + } | ||
379 | + | ||
380 | + // Store root (index-form) and error location number | ||
381 | + $root[$count] = $i; | ||
382 | + $loc[$count] = $k; | ||
383 | + | ||
384 | + if (++$count === $degLambda) { | ||
385 | + break; | ||
386 | + } | ||
387 | + } | ||
388 | + | ||
389 | + if ($degLambda !== $count) { | ||
390 | + // deg(lambda) unequal to number of roots: uncorrectable error detected | ||
391 | + return null; | ||
392 | + } | ||
393 | + | ||
394 | + // Compute err+eras evaluate poly omega(x) = s(x)*lambda(x) (modulo x**numRoots). In index form. Also find | ||
395 | + // deg(omega). | ||
396 | + $degOmega = $degLambda - 1; | ||
397 | + | ||
398 | + for ($i = 0; $i <= $degOmega; ++$i) { | ||
399 | + $tmp = 0; | ||
400 | + | ||
401 | + for ($j = $i; $j >= 0; --$j) { | ||
402 | + if ($syndromes[$i - $j] !== $this->blockSize && $lambda[$j] !== $this->blockSize) { | ||
403 | + $tmp ^= $this->alphaTo[$this->modNn($syndromes[$i - $j] + $lambda[$j])]; | ||
404 | + } | ||
405 | + } | ||
406 | + | ||
407 | + $omega[$i] = $this->indexOf[$tmp]; | ||
408 | + } | ||
409 | + | ||
410 | + // Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = inv(X(l))**(firstRoot-1) and | ||
411 | + // den = lambda_pr(inv(X(l))) all in poly form. | ||
412 | + for ($j = $count - 1; $j >= 0; --$j) { | ||
413 | + $num1 = 0; | ||
414 | + | ||
415 | + for ($i = $degOmega; $i >= 0; $i--) { | ||
416 | + if ($omega[$i] !== $this->blockSize) { | ||
417 | + $num1 ^= $this->alphaTo[$this->modNn($omega[$i] + $i * $root[$j])]; | ||
418 | + } | ||
419 | + } | ||
420 | + | ||
421 | + $num2 = $this->alphaTo[$this->modNn($root[$j] * ($this->firstRoot - 1) + $this->blockSize)]; | ||
422 | + $den = 0; | ||
423 | + | ||
424 | + // lambda[i+1] for i even is the formal derivativelambda_pr of lambda[i] | ||
425 | + for ($i = min($degLambda, $this->numRoots - 1) & ~1; $i >= 0; $i -= 2) { | ||
426 | + if ($lambda[$i + 1] !== $this->blockSize) { | ||
427 | + $den ^= $this->alphaTo[$this->modNn($lambda[$i + 1] + $i * $root[$j])]; | ||
428 | + } | ||
429 | + } | ||
430 | + | ||
431 | + // Apply error to data | ||
432 | + if ($num1 !== 0 && $loc[$j] >= $this->padding) { | ||
433 | + $data[$loc[$j] - $this->padding] = $data[$loc[$j] - $this->padding] ^ ( | ||
434 | + $this->alphaTo[ | ||
435 | + $this->modNn( | ||
436 | + $this->indexOf[$num1] + $this->indexOf[$num2] + $this->blockSize - $this->indexOf[$den] | ||
437 | + ) | ||
438 | + ] | ||
439 | + ); | ||
440 | + } | ||
441 | + } | ||
442 | + | ||
443 | + if (null !== $erasures) { | ||
444 | + if (count($erasures) < $count) { | ||
445 | + $erasures->setSize($count); | ||
446 | + } | ||
447 | + | ||
448 | + for ($i = 0; $i < $count; $i++) { | ||
449 | + $erasures[$i] = $loc[$i]; | ||
450 | + } | ||
451 | + } | ||
452 | + | ||
453 | + return $count; | ||
454 | + } | ||
455 | + | ||
456 | + /** | ||
457 | + * Computes $x % GF_SIZE, where GF_SIZE is 2**GF_BITS - 1, without a slow divide. | ||
458 | + */ | ||
459 | + private function modNn(int $x) : int | ||
460 | + { | ||
461 | + while ($x >= $this->blockSize) { | ||
462 | + $x -= $this->blockSize; | ||
463 | + $x = ($x >> $this->symbolSize) + ($x & $this->blockSize); | ||
464 | + } | ||
465 | + | ||
466 | + return $x; | ||
467 | + } | ||
468 | +} |
1 | +<?php | ||
2 | +declare(strict_types = 1); | ||
3 | + | ||
4 | +namespace BaconQrCode\Common; | ||
5 | + | ||
6 | +use BaconQrCode\Exception\InvalidArgumentException; | ||
7 | +use SplFixedArray; | ||
8 | + | ||
9 | +/** | ||
10 | + * Version representation. | ||
11 | + */ | ||
12 | +final class Version | ||
13 | +{ | ||
14 | + private const VERSION_DECODE_INFO = [ | ||
15 | + 0x07c94, | ||
16 | + 0x085bc, | ||
17 | + 0x09a99, | ||
18 | + 0x0a4d3, | ||
19 | + 0x0bbf6, | ||
20 | + 0x0c762, | ||
21 | + 0x0d847, | ||
22 | + 0x0e60d, | ||
23 | + 0x0f928, | ||
24 | + 0x10b78, | ||
25 | + 0x1145d, | ||
26 | + 0x12a17, | ||
27 | + 0x13532, | ||
28 | + 0x149a6, | ||
29 | + 0x15683, | ||
30 | + 0x168c9, | ||
31 | + 0x177ec, | ||
32 | + 0x18ec4, | ||
33 | + 0x191e1, | ||
34 | + 0x1afab, | ||
35 | + 0x1b08e, | ||
36 | + 0x1cc1a, | ||
37 | + 0x1d33f, | ||
38 | + 0x1ed75, | ||
39 | + 0x1f250, | ||
40 | + 0x209d5, | ||
41 | + 0x216f0, | ||
42 | + 0x228ba, | ||
43 | + 0x2379f, | ||
44 | + 0x24b0b, | ||
45 | + 0x2542e, | ||
46 | + 0x26a64, | ||
47 | + 0x27541, | ||
48 | + 0x28c69, | ||
49 | + ]; | ||
50 | + | ||
51 | + /** | ||
52 | + * Version number of this version. | ||
53 | + * | ||
54 | + * @var int | ||
55 | + */ | ||
56 | + private $versionNumber; | ||
57 | + | ||
58 | + /** | ||
59 | + * Alignment pattern centers. | ||
60 | + * | ||
61 | + * @var SplFixedArray | ||
62 | + */ | ||
63 | + private $alignmentPatternCenters; | ||
64 | + | ||
65 | + /** | ||
66 | + * Error correction blocks. | ||
67 | + * | ||
68 | + * @var EcBlocks[] | ||
69 | + */ | ||
70 | + private $ecBlocks; | ||
71 | + | ||
72 | + /** | ||
73 | + * Total number of codewords. | ||
74 | + * | ||
75 | + * @var int | ||
76 | + */ | ||
77 | + private $totalCodewords; | ||
78 | + | ||
79 | + /** | ||
80 | + * Cached version instances. | ||
81 | + * | ||
82 | + * @var array<int, self>|null | ||
83 | + */ | ||
84 | + private static $versions; | ||
85 | + | ||
86 | + /** | ||
87 | + * @param int[] $alignmentPatternCenters | ||
88 | + */ | ||
89 | + private function __construct( | ||
90 | + int $versionNumber, | ||
91 | + array $alignmentPatternCenters, | ||
92 | + EcBlocks ...$ecBlocks | ||
93 | + ) { | ||
94 | + $this->versionNumber = $versionNumber; | ||
95 | + $this->alignmentPatternCenters = $alignmentPatternCenters; | ||
96 | + $this->ecBlocks = $ecBlocks; | ||
97 | + | ||
98 | + $totalCodewords = 0; | ||
99 | + $ecCodewords = $ecBlocks[0]->getEcCodewordsPerBlock(); | ||
100 | + | ||
101 | + foreach ($ecBlocks[0]->getEcBlocks() as $ecBlock) { | ||
102 | + $totalCodewords += $ecBlock->getCount() * ($ecBlock->getDataCodewords() + $ecCodewords); | ||
103 | + } | ||
104 | + | ||
105 | + $this->totalCodewords = $totalCodewords; | ||
106 | + } | ||
107 | + | ||
108 | + /** | ||
109 | + * Returns the version number. | ||
110 | + */ | ||
111 | + public function getVersionNumber() : int | ||
112 | + { | ||
113 | + return $this->versionNumber; | ||
114 | + } | ||
115 | + | ||
116 | + /** | ||
117 | + * Returns the alignment pattern centers. | ||
118 | + * | ||
119 | + * @return int[] | ||
120 | + */ | ||
121 | + public function getAlignmentPatternCenters() : array | ||
122 | + { | ||
123 | + return $this->alignmentPatternCenters; | ||
124 | + } | ||
125 | + | ||
126 | + /** | ||
127 | + * Returns the total number of codewords. | ||
128 | + */ | ||
129 | + public function getTotalCodewords() : int | ||
130 | + { | ||
131 | + return $this->totalCodewords; | ||
132 | + } | ||
133 | + | ||
134 | + /** | ||
135 | + * Calculates the dimension for the current version. | ||
136 | + */ | ||
137 | + public function getDimensionForVersion() : int | ||
138 | + { | ||
139 | + return 17 + 4 * $this->versionNumber; | ||
140 | + } | ||
141 | + | ||
142 | + /** | ||
143 | + * Returns the number of EC blocks for a specific EC level. | ||
144 | + */ | ||
145 | + public function getEcBlocksForLevel(ErrorCorrectionLevel $ecLevel) : EcBlocks | ||
146 | + { | ||
147 | + return $this->ecBlocks[$ecLevel->ordinal()]; | ||
148 | + } | ||
149 | + | ||
150 | + /** | ||
151 | + * Gets a provisional version number for a specific dimension. | ||
152 | + * | ||
153 | + * @throws InvalidArgumentException if dimension is not 1 mod 4 | ||
154 | + */ | ||
155 | + public static function getProvisionalVersionForDimension(int $dimension) : self | ||
156 | + { | ||
157 | + if (1 !== $dimension % 4) { | ||
158 | + throw new InvalidArgumentException('Dimension is not 1 mod 4'); | ||
159 | + } | ||
160 | + | ||
161 | + return self::getVersionForNumber(intdiv($dimension - 17, 4)); | ||
162 | + } | ||
163 | + | ||
164 | + /** | ||
165 | + * Gets a version instance for a specific version number. | ||
166 | + * | ||
167 | + * @throws InvalidArgumentException if version number is out of range | ||
168 | + */ | ||
169 | + public static function getVersionForNumber(int $versionNumber) : self | ||
170 | + { | ||
171 | + if ($versionNumber < 1 || $versionNumber > 40) { | ||
172 | + throw new InvalidArgumentException('Version number must be between 1 and 40'); | ||
173 | + } | ||
174 | + | ||
175 | + return self::versions()[$versionNumber - 1]; | ||
176 | + } | ||
177 | + | ||
178 | + /** | ||
179 | + * Decodes version information from an integer and returns the version. | ||
180 | + */ | ||
181 | + public static function decodeVersionInformation(int $versionBits) : ?self | ||
182 | + { | ||
183 | + $bestDifference = PHP_INT_MAX; | ||
184 | + $bestVersion = 0; | ||
185 | + | ||
186 | + foreach (self::VERSION_DECODE_INFO as $i => $targetVersion) { | ||
187 | + if ($targetVersion === $versionBits) { | ||
188 | + return self::getVersionForNumber($i + 7); | ||
189 | + } | ||
190 | + | ||
191 | + $bitsDifference = FormatInformation::numBitsDiffering($versionBits, $targetVersion); | ||
192 | + | ||
193 | + if ($bitsDifference < $bestDifference) { | ||
194 | + $bestVersion = $i + 7; | ||
195 | + $bestDifference = $bitsDifference; | ||
196 | + } | ||
197 | + } | ||
198 | + | ||
199 | + if ($bestDifference <= 3) { | ||
200 | + return self::getVersionForNumber($bestVersion); | ||
201 | + } | ||
202 | + | ||
203 | + return null; | ||
204 | + } | ||
205 | + | ||
206 | + /** | ||
207 | + * Builds the function pattern for the current version. | ||
208 | + */ | ||
209 | + public function buildFunctionPattern() : BitMatrix | ||
210 | + { | ||
211 | + $dimension = $this->getDimensionForVersion(); | ||
212 | + $bitMatrix = new BitMatrix($dimension); | ||
213 | + | ||
214 | + // Top left finder pattern + separator + format | ||
215 | + $bitMatrix->setRegion(0, 0, 9, 9); | ||
216 | + // Top right finder pattern + separator + format | ||
217 | + $bitMatrix->setRegion($dimension - 8, 0, 8, 9); | ||
218 | + // Bottom left finder pattern + separator + format | ||
219 | + $bitMatrix->setRegion(0, $dimension - 8, 9, 8); | ||
220 | + | ||
221 | + // Alignment patterns | ||
222 | + $max = count($this->alignmentPatternCenters); | ||
223 | + | ||
224 | + for ($x = 0; $x < $max; ++$x) { | ||
225 | + $i = $this->alignmentPatternCenters[$x] - 2; | ||
226 | + | ||
227 | + for ($y = 0; $y < $max; ++$y) { | ||
228 | + if (($x === 0 && ($y === 0 || $y === $max - 1)) || ($x === $max - 1 && $y === 0)) { | ||
229 | + // No alignment patterns near the three finder paterns | ||
230 | + continue; | ||
231 | + } | ||
232 | + | ||
233 | + $bitMatrix->setRegion($this->alignmentPatternCenters[$y] - 2, $i, 5, 5); | ||
234 | + } | ||
235 | + } | ||
236 | + | ||
237 | + // Vertical timing pattern | ||
238 | + $bitMatrix->setRegion(6, 9, 1, $dimension - 17); | ||
239 | + // Horizontal timing pattern | ||
240 | + $bitMatrix->setRegion(9, 6, $dimension - 17, 1); | ||
241 | + | ||
242 | + if ($this->versionNumber > 6) { | ||
243 | + // Version info, top right | ||
244 | + $bitMatrix->setRegion($dimension - 11, 0, 3, 6); | ||
245 | + // Version info, bottom left | ||
246 | + $bitMatrix->setRegion(0, $dimension - 11, 6, 3); | ||
247 | + } | ||
248 | + | ||
249 | + return $bitMatrix; | ||
250 | + } | ||
251 | + | ||
252 | + /** | ||
253 | + * Returns a string representation for the version. | ||
254 | + */ | ||
255 | + public function __toString() : string | ||
256 | + { | ||
257 | + return (string) $this->versionNumber; | ||
258 | + } | ||
259 | + | ||
260 | + /** | ||
261 | + * Build and cache a specific version. | ||
262 | + * | ||
263 | + * See ISO 18004:2006 6.5.1 Table 9. | ||
264 | + * | ||
265 | + * @return array<int, self> | ||
266 | + */ | ||
267 | + private static function versions() : array | ||
268 | + { | ||
269 | + if (null !== self::$versions) { | ||
270 | + return self::$versions; | ||
271 | + } | ||
272 | + | ||
273 | + return self::$versions = [ | ||
274 | + new self( | ||
275 | + 1, | ||
276 | + [], | ||
277 | + new EcBlocks(7, new EcBlock(1, 19)), | ||
278 | + new EcBlocks(10, new EcBlock(1, 16)), | ||
279 | + new EcBlocks(13, new EcBlock(1, 13)), | ||
280 | + new EcBlocks(17, new EcBlock(1, 9)) | ||
281 | + ), | ||
282 | + new self( | ||
283 | + 2, | ||
284 | + [6, 18], | ||
285 | + new EcBlocks(10, new EcBlock(1, 34)), | ||
286 | + new EcBlocks(16, new EcBlock(1, 28)), | ||
287 | + new EcBlocks(22, new EcBlock(1, 22)), | ||
288 | + new EcBlocks(28, new EcBlock(1, 16)) | ||
289 | + ), | ||
290 | + new self( | ||
291 | + 3, | ||
292 | + [6, 22], | ||
293 | + new EcBlocks(15, new EcBlock(1, 55)), | ||
294 | + new EcBlocks(26, new EcBlock(1, 44)), | ||
295 | + new EcBlocks(18, new EcBlock(2, 17)), | ||
296 | + new EcBlocks(22, new EcBlock(2, 13)) | ||
297 | + ), | ||
298 | + new self( | ||
299 | + 4, | ||
300 | + [6, 26], | ||
301 | + new EcBlocks(20, new EcBlock(1, 80)), | ||
302 | + new EcBlocks(18, new EcBlock(2, 32)), | ||
303 | + new EcBlocks(26, new EcBlock(3, 24)), | ||
304 | + new EcBlocks(16, new EcBlock(4, 9)) | ||
305 | + ), | ||
306 | + new self( | ||
307 | + 5, | ||
308 | + [6, 30], | ||
309 | + new EcBlocks(26, new EcBlock(1, 108)), | ||
310 | + new EcBlocks(24, new EcBlock(2, 43)), | ||
311 | + new EcBlocks(18, new EcBlock(2, 15), new EcBlock(2, 16)), | ||
312 | + new EcBlocks(22, new EcBlock(2, 11), new EcBlock(2, 12)) | ||
313 | + ), | ||
314 | + new self( | ||
315 | + 6, | ||
316 | + [6, 34], | ||
317 | + new EcBlocks(18, new EcBlock(2, 68)), | ||
318 | + new EcBlocks(16, new EcBlock(4, 27)), | ||
319 | + new EcBlocks(24, new EcBlock(4, 19)), | ||
320 | + new EcBlocks(28, new EcBlock(4, 15)) | ||
321 | + ), | ||
322 | + new self( | ||
323 | + 7, | ||
324 | + [6, 22, 38], | ||
325 | + new EcBlocks(20, new EcBlock(2, 78)), | ||
326 | + new EcBlocks(18, new EcBlock(4, 31)), | ||
327 | + new EcBlocks(18, new EcBlock(2, 14), new EcBlock(4, 15)), | ||
328 | + new EcBlocks(26, new EcBlock(4, 13), new EcBlock(1, 14)) | ||
329 | + ), | ||
330 | + new self( | ||
331 | + 8, | ||
332 | + [6, 24, 42], | ||
333 | + new EcBlocks(24, new EcBlock(2, 97)), | ||
334 | + new EcBlocks(22, new EcBlock(2, 38), new EcBlock(2, 39)), | ||
335 | + new EcBlocks(22, new EcBlock(4, 18), new EcBlock(2, 19)), | ||
336 | + new EcBlocks(26, new EcBlock(4, 14), new EcBlock(2, 15)) | ||
337 | + ), | ||
338 | + new self( | ||
339 | + 9, | ||
340 | + [6, 26, 46], | ||
341 | + new EcBlocks(30, new EcBlock(2, 116)), | ||
342 | + new EcBlocks(22, new EcBlock(3, 36), new EcBlock(2, 37)), | ||
343 | + new EcBlocks(20, new EcBlock(4, 16), new EcBlock(4, 17)), | ||
344 | + new EcBlocks(24, new EcBlock(4, 12), new EcBlock(4, 13)) | ||
345 | + ), | ||
346 | + new self( | ||
347 | + 10, | ||
348 | + [6, 28, 50], | ||
349 | + new EcBlocks(18, new EcBlock(2, 68), new EcBlock(2, 69)), | ||
350 | + new EcBlocks(26, new EcBlock(4, 43), new EcBlock(1, 44)), | ||
351 | + new EcBlocks(24, new EcBlock(6, 19), new EcBlock(2, 20)), | ||
352 | + new EcBlocks(28, new EcBlock(6, 15), new EcBlock(2, 16)) | ||
353 | + ), | ||
354 | + new self( | ||
355 | + 11, | ||
356 | + [6, 30, 54], | ||
357 | + new EcBlocks(20, new EcBlock(4, 81)), | ||
358 | + new EcBlocks(30, new EcBlock(1, 50), new EcBlock(4, 51)), | ||
359 | + new EcBlocks(28, new EcBlock(4, 22), new EcBlock(4, 23)), | ||
360 | + new EcBlocks(24, new EcBlock(3, 12), new EcBlock(8, 13)) | ||
361 | + ), | ||
362 | + new self( | ||
363 | + 12, | ||
364 | + [6, 32, 58], | ||
365 | + new EcBlocks(24, new EcBlock(2, 92), new EcBlock(2, 93)), | ||
366 | + new EcBlocks(22, new EcBlock(6, 36), new EcBlock(2, 37)), | ||
367 | + new EcBlocks(26, new EcBlock(4, 20), new EcBlock(6, 21)), | ||
368 | + new EcBlocks(28, new EcBlock(7, 14), new EcBlock(4, 15)) | ||
369 | + ), | ||
370 | + new self( | ||
371 | + 13, | ||
372 | + [6, 34, 62], | ||
373 | + new EcBlocks(26, new EcBlock(4, 107)), | ||
374 | + new EcBlocks(22, new EcBlock(8, 37), new EcBlock(1, 38)), | ||
375 | + new EcBlocks(24, new EcBlock(8, 20), new EcBlock(4, 21)), | ||
376 | + new EcBlocks(22, new EcBlock(12, 11), new EcBlock(4, 12)) | ||
377 | + ), | ||
378 | + new self( | ||
379 | + 14, | ||
380 | + [6, 26, 46, 66], | ||
381 | + new EcBlocks(30, new EcBlock(3, 115), new EcBlock(1, 116)), | ||
382 | + new EcBlocks(24, new EcBlock(4, 40), new EcBlock(5, 41)), | ||
383 | + new EcBlocks(20, new EcBlock(11, 16), new EcBlock(5, 17)), | ||
384 | + new EcBlocks(24, new EcBlock(11, 12), new EcBlock(5, 13)) | ||
385 | + ), | ||
386 | + new self( | ||
387 | + 15, | ||
388 | + [6, 26, 48, 70], | ||
389 | + new EcBlocks(22, new EcBlock(5, 87), new EcBlock(1, 88)), | ||
390 | + new EcBlocks(24, new EcBlock(5, 41), new EcBlock(5, 42)), | ||
391 | + new EcBlocks(30, new EcBlock(5, 24), new EcBlock(7, 25)), | ||
392 | + new EcBlocks(24, new EcBlock(11, 12), new EcBlock(7, 13)) | ||
393 | + ), | ||
394 | + new self( | ||
395 | + 16, | ||
396 | + [6, 26, 50, 74], | ||
397 | + new EcBlocks(24, new EcBlock(5, 98), new EcBlock(1, 99)), | ||
398 | + new EcBlocks(28, new EcBlock(7, 45), new EcBlock(3, 46)), | ||
399 | + new EcBlocks(24, new EcBlock(15, 19), new EcBlock(2, 20)), | ||
400 | + new EcBlocks(30, new EcBlock(3, 15), new EcBlock(13, 16)) | ||
401 | + ), | ||
402 | + new self( | ||
403 | + 17, | ||
404 | + [6, 30, 54, 78], | ||
405 | + new EcBlocks(28, new EcBlock(1, 107), new EcBlock(5, 108)), | ||
406 | + new EcBlocks(28, new EcBlock(10, 46), new EcBlock(1, 47)), | ||
407 | + new EcBlocks(28, new EcBlock(1, 22), new EcBlock(15, 23)), | ||
408 | + new EcBlocks(28, new EcBlock(2, 14), new EcBlock(17, 15)) | ||
409 | + ), | ||
410 | + new self( | ||
411 | + 18, | ||
412 | + [6, 30, 56, 82], | ||
413 | + new EcBlocks(30, new EcBlock(5, 120), new EcBlock(1, 121)), | ||
414 | + new EcBlocks(26, new EcBlock(9, 43), new EcBlock(4, 44)), | ||
415 | + new EcBlocks(28, new EcBlock(17, 22), new EcBlock(1, 23)), | ||
416 | + new EcBlocks(28, new EcBlock(2, 14), new EcBlock(19, 15)) | ||
417 | + ), | ||
418 | + new self( | ||
419 | + 19, | ||
420 | + [6, 30, 58, 86], | ||
421 | + new EcBlocks(28, new EcBlock(3, 113), new EcBlock(4, 114)), | ||
422 | + new EcBlocks(26, new EcBlock(3, 44), new EcBlock(11, 45)), | ||
423 | + new EcBlocks(26, new EcBlock(17, 21), new EcBlock(4, 22)), | ||
424 | + new EcBlocks(26, new EcBlock(9, 13), new EcBlock(16, 14)) | ||
425 | + ), | ||
426 | + new self( | ||
427 | + 20, | ||
428 | + [6, 34, 62, 90], | ||
429 | + new EcBlocks(28, new EcBlock(3, 107), new EcBlock(5, 108)), | ||
430 | + new EcBlocks(26, new EcBlock(3, 41), new EcBlock(13, 42)), | ||
431 | + new EcBlocks(30, new EcBlock(15, 24), new EcBlock(5, 25)), | ||
432 | + new EcBlocks(28, new EcBlock(15, 15), new EcBlock(10, 16)) | ||
433 | + ), | ||
434 | + new self( | ||
435 | + 21, | ||
436 | + [6, 28, 50, 72, 94], | ||
437 | + new EcBlocks(28, new EcBlock(4, 116), new EcBlock(4, 117)), | ||
438 | + new EcBlocks(26, new EcBlock(17, 42)), | ||
439 | + new EcBlocks(28, new EcBlock(17, 22), new EcBlock(6, 23)), | ||
440 | + new EcBlocks(30, new EcBlock(19, 16), new EcBlock(6, 17)) | ||
441 | + ), | ||
442 | + new self( | ||
443 | + 22, | ||
444 | + [6, 26, 50, 74, 98], | ||
445 | + new EcBlocks(28, new EcBlock(2, 111), new EcBlock(7, 112)), | ||
446 | + new EcBlocks(28, new EcBlock(17, 46)), | ||
447 | + new EcBlocks(30, new EcBlock(7, 24), new EcBlock(16, 25)), | ||
448 | + new EcBlocks(24, new EcBlock(34, 13)) | ||
449 | + ), | ||
450 | + new self( | ||
451 | + 23, | ||
452 | + [6, 30, 54, 78, 102], | ||
453 | + new EcBlocks(30, new EcBlock(4, 121), new EcBlock(5, 122)), | ||
454 | + new EcBlocks(28, new EcBlock(4, 47), new EcBlock(14, 48)), | ||
455 | + new EcBlocks(30, new EcBlock(11, 24), new EcBlock(14, 25)), | ||
456 | + new EcBlocks(30, new EcBlock(16, 15), new EcBlock(14, 16)) | ||
457 | + ), | ||
458 | + new self( | ||
459 | + 24, | ||
460 | + [6, 28, 54, 80, 106], | ||
461 | + new EcBlocks(30, new EcBlock(6, 117), new EcBlock(4, 118)), | ||
462 | + new EcBlocks(28, new EcBlock(6, 45), new EcBlock(14, 46)), | ||
463 | + new EcBlocks(30, new EcBlock(11, 24), new EcBlock(16, 25)), | ||
464 | + new EcBlocks(30, new EcBlock(30, 16), new EcBlock(2, 17)) | ||
465 | + ), | ||
466 | + new self( | ||
467 | + 25, | ||
468 | + [6, 32, 58, 84, 110], | ||
469 | + new EcBlocks(26, new EcBlock(8, 106), new EcBlock(4, 107)), | ||
470 | + new EcBlocks(28, new EcBlock(8, 47), new EcBlock(13, 48)), | ||
471 | + new EcBlocks(30, new EcBlock(7, 24), new EcBlock(22, 25)), | ||
472 | + new EcBlocks(30, new EcBlock(22, 15), new EcBlock(13, 16)) | ||
473 | + ), | ||
474 | + new self( | ||
475 | + 26, | ||
476 | + [6, 30, 58, 86, 114], | ||
477 | + new EcBlocks(28, new EcBlock(10, 114), new EcBlock(2, 115)), | ||
478 | + new EcBlocks(28, new EcBlock(19, 46), new EcBlock(4, 47)), | ||
479 | + new EcBlocks(28, new EcBlock(28, 22), new EcBlock(6, 23)), | ||
480 | + new EcBlocks(30, new EcBlock(33, 16), new EcBlock(4, 17)) | ||
481 | + ), | ||
482 | + new self( | ||
483 | + 27, | ||
484 | + [6, 34, 62, 90, 118], | ||
485 | + new EcBlocks(30, new EcBlock(8, 122), new EcBlock(4, 123)), | ||
486 | + new EcBlocks(28, new EcBlock(22, 45), new EcBlock(3, 46)), | ||
487 | + new EcBlocks(30, new EcBlock(8, 23), new EcBlock(26, 24)), | ||
488 | + new EcBlocks(30, new EcBlock(12, 15), new EcBlock(28, 16)) | ||
489 | + ), | ||
490 | + new self( | ||
491 | + 28, | ||
492 | + [6, 26, 50, 74, 98, 122], | ||
493 | + new EcBlocks(30, new EcBlock(3, 117), new EcBlock(10, 118)), | ||
494 | + new EcBlocks(28, new EcBlock(3, 45), new EcBlock(23, 46)), | ||
495 | + new EcBlocks(30, new EcBlock(4, 24), new EcBlock(31, 25)), | ||
496 | + new EcBlocks(30, new EcBlock(11, 15), new EcBlock(31, 16)) | ||
497 | + ), | ||
498 | + new self( | ||
499 | + 29, | ||
500 | + [6, 30, 54, 78, 102, 126], | ||
501 | + new EcBlocks(30, new EcBlock(7, 116), new EcBlock(7, 117)), | ||
502 | + new EcBlocks(28, new EcBlock(21, 45), new EcBlock(7, 46)), | ||
503 | + new EcBlocks(30, new EcBlock(1, 23), new EcBlock(37, 24)), | ||
504 | + new EcBlocks(30, new EcBlock(19, 15), new EcBlock(26, 16)) | ||
505 | + ), | ||
506 | + new self( | ||
507 | + 30, | ||
508 | + [6, 26, 52, 78, 104, 130], | ||
509 | + new EcBlocks(30, new EcBlock(5, 115), new EcBlock(10, 116)), | ||
510 | + new EcBlocks(28, new EcBlock(19, 47), new EcBlock(10, 48)), | ||
511 | + new EcBlocks(30, new EcBlock(15, 24), new EcBlock(25, 25)), | ||
512 | + new EcBlocks(30, new EcBlock(23, 15), new EcBlock(25, 16)) | ||
513 | + ), | ||
514 | + new self( | ||
515 | + 31, | ||
516 | + [6, 30, 56, 82, 108, 134], | ||
517 | + new EcBlocks(30, new EcBlock(13, 115), new EcBlock(3, 116)), | ||
518 | + new EcBlocks(28, new EcBlock(2, 46), new EcBlock(29, 47)), | ||
519 | + new EcBlocks(30, new EcBlock(42, 24), new EcBlock(1, 25)), | ||
520 | + new EcBlocks(30, new EcBlock(23, 15), new EcBlock(28, 16)) | ||
521 | + ), | ||
522 | + new self( | ||
523 | + 32, | ||
524 | + [6, 34, 60, 86, 112, 138], | ||
525 | + new EcBlocks(30, new EcBlock(17, 115)), | ||
526 | + new EcBlocks(28, new EcBlock(10, 46), new EcBlock(23, 47)), | ||
527 | + new EcBlocks(30, new EcBlock(10, 24), new EcBlock(35, 25)), | ||
528 | + new EcBlocks(30, new EcBlock(19, 15), new EcBlock(35, 16)) | ||
529 | + ), | ||
530 | + new self( | ||
531 | + 33, | ||
532 | + [6, 30, 58, 86, 114, 142], | ||
533 | + new EcBlocks(30, new EcBlock(17, 115), new EcBlock(1, 116)), | ||
534 | + new EcBlocks(28, new EcBlock(14, 46), new EcBlock(21, 47)), | ||
535 | + new EcBlocks(30, new EcBlock(29, 24), new EcBlock(19, 25)), | ||
536 | + new EcBlocks(30, new EcBlock(11, 15), new EcBlock(46, 16)) | ||
537 | + ), | ||
538 | + new self( | ||
539 | + 34, | ||
540 | + [6, 34, 62, 90, 118, 146], | ||
541 | + new EcBlocks(30, new EcBlock(13, 115), new EcBlock(6, 116)), | ||
542 | + new EcBlocks(28, new EcBlock(14, 46), new EcBlock(23, 47)), | ||
543 | + new EcBlocks(30, new EcBlock(44, 24), new EcBlock(7, 25)), | ||
544 | + new EcBlocks(30, new EcBlock(59, 16), new EcBlock(1, 17)) | ||
545 | + ), | ||
546 | + new self( | ||
547 | + 35, | ||
548 | + [6, 30, 54, 78, 102, 126, 150], | ||
549 | + new EcBlocks(30, new EcBlock(12, 121), new EcBlock(7, 122)), | ||
550 | + new EcBlocks(28, new EcBlock(12, 47), new EcBlock(26, 48)), | ||
551 | + new EcBlocks(30, new EcBlock(39, 24), new EcBlock(14, 25)), | ||
552 | + new EcBlocks(30, new EcBlock(22, 15), new EcBlock(41, 16)) | ||
553 | + ), | ||
554 | + new self( | ||
555 | + 36, | ||
556 | + [6, 24, 50, 76, 102, 128, 154], | ||
557 | + new EcBlocks(30, new EcBlock(6, 121), new EcBlock(14, 122)), | ||
558 | + new EcBlocks(28, new EcBlock(6, 47), new EcBlock(34, 48)), | ||
559 | + new EcBlocks(30, new EcBlock(46, 24), new EcBlock(10, 25)), | ||
560 | + new EcBlocks(30, new EcBlock(2, 15), new EcBlock(64, 16)) | ||
561 | + ), | ||
562 | + new self( | ||
563 | + 37, | ||
564 | + [6, 28, 54, 80, 106, 132, 158], | ||
565 | + new EcBlocks(30, new EcBlock(17, 122), new EcBlock(4, 123)), | ||
566 | + new EcBlocks(28, new EcBlock(29, 46), new EcBlock(14, 47)), | ||
567 | + new EcBlocks(30, new EcBlock(49, 24), new EcBlock(10, 25)), | ||
568 | + new EcBlocks(30, new EcBlock(24, 15), new EcBlock(46, 16)) | ||
569 | + ), | ||
570 | + new self( | ||
571 | + 38, | ||
572 | + [6, 32, 58, 84, 110, 136, 162], | ||
573 | + new EcBlocks(30, new EcBlock(4, 122), new EcBlock(18, 123)), | ||
574 | + new EcBlocks(28, new EcBlock(13, 46), new EcBlock(32, 47)), | ||
575 | + new EcBlocks(30, new EcBlock(48, 24), new EcBlock(14, 25)), | ||
576 | + new EcBlocks(30, new EcBlock(42, 15), new EcBlock(32, 16)) | ||
577 | + ), | ||
578 | + new self( | ||
579 | + 39, | ||
580 | + [6, 26, 54, 82, 110, 138, 166], | ||
581 | + new EcBlocks(30, new EcBlock(20, 117), new EcBlock(4, 118)), | ||
582 | + new EcBlocks(28, new EcBlock(40, 47), new EcBlock(7, 48)), | ||
583 | + new EcBlocks(30, new EcBlock(43, 24), new EcBlock(22, 25)), | ||
584 | + new EcBlocks(30, new EcBlock(10, 15), new EcBlock(67, 16)) | ||
585 | + ), | ||
586 | + new self( | ||
587 | + 40, | ||
588 | + [6, 30, 58, 86, 114, 142, 170], | ||
589 | + new EcBlocks(30, new EcBlock(19, 118), new EcBlock(6, 119)), | ||
590 | + new EcBlocks(28, new EcBlock(18, 47), new EcBlock(31, 48)), | ||
591 | + new EcBlocks(30, new EcBlock(34, 24), new EcBlock(34, 25)), | ||
592 | + new EcBlocks(30, new EcBlock(20, 15), new EcBlock(61, 16)) | ||
593 | + ), | ||
594 | + ]; | ||
595 | + } | ||
596 | +} |
1 | +<?php | ||
2 | +declare(strict_types = 1); | ||
3 | + | ||
4 | +namespace BaconQrCode\Encoder; | ||
5 | + | ||
6 | +use SplFixedArray; | ||
7 | + | ||
8 | +/** | ||
9 | + * Block pair. | ||
10 | + */ | ||
11 | +final class BlockPair | ||
12 | +{ | ||
13 | + /** | ||
14 | + * Data bytes in the block. | ||
15 | + * | ||
16 | + * @var SplFixedArray<int> | ||
17 | + */ | ||
18 | + private $dataBytes; | ||
19 | + | ||
20 | + /** | ||
21 | + * Error correction bytes in the block. | ||
22 | + * | ||
23 | + * @var SplFixedArray<int> | ||
24 | + */ | ||
25 | + private $errorCorrectionBytes; | ||
26 | + | ||
27 | + /** | ||
28 | + * Creates a new block pair. | ||
29 | + * | ||
30 | + * @param SplFixedArray<int> $data | ||
31 | + * @param SplFixedArray<int> $errorCorrection | ||
32 | + */ | ||
33 | + public function __construct(SplFixedArray $data, SplFixedArray $errorCorrection) | ||
34 | + { | ||
35 | + $this->dataBytes = $data; | ||
36 | + $this->errorCorrectionBytes = $errorCorrection; | ||
37 | + } | ||
38 | + | ||
39 | + /** | ||
40 | + * Gets the data bytes. | ||
41 | + * | ||
42 | + * @return SplFixedArray<int> | ||
43 | + */ | ||
44 | + public function getDataBytes() : SplFixedArray | ||
45 | + { | ||
46 | + return $this->dataBytes; | ||
47 | + } | ||
48 | + | ||
49 | + /** | ||
50 | + * Gets the error correction bytes. | ||
51 | + * | ||
52 | + * @return SplFixedArray<int> | ||
53 | + */ | ||
54 | + public function getErrorCorrectionBytes() : SplFixedArray | ||
55 | + { | ||
56 | + return $this->errorCorrectionBytes; | ||
57 | + } | ||
58 | +} |
1 | +<?php | ||
2 | +declare(strict_types = 1); | ||
3 | + | ||
4 | +namespace BaconQrCode\Encoder; | ||
5 | + | ||
6 | +use SplFixedArray; | ||
7 | +use Traversable; | ||
8 | + | ||
9 | +/** | ||
10 | + * Byte matrix. | ||
11 | + */ | ||
12 | +final class ByteMatrix | ||
13 | +{ | ||
14 | + /** | ||
15 | + * Bytes in the matrix, represented as array. | ||
16 | + * | ||
17 | + * @var SplFixedArray<SplFixedArray<int>> | ||
18 | + */ | ||
19 | + private $bytes; | ||
20 | + | ||
21 | + /** | ||
22 | + * Width of the matrix. | ||
23 | + * | ||
24 | + * @var int | ||
25 | + */ | ||
26 | + private $width; | ||
27 | + | ||
28 | + /** | ||
29 | + * Height of the matrix. | ||
30 | + * | ||
31 | + * @var int | ||
32 | + */ | ||
33 | + private $height; | ||
34 | + | ||
35 | + public function __construct(int $width, int $height) | ||
36 | + { | ||
37 | + $this->height = $height; | ||
38 | + $this->width = $width; | ||
39 | + $this->bytes = new SplFixedArray($height); | ||
40 | + | ||
41 | + for ($y = 0; $y < $height; ++$y) { | ||
42 | + $this->bytes[$y] = SplFixedArray::fromArray(array_fill(0, $width, 0)); | ||
43 | + } | ||
44 | + } | ||
45 | + | ||
46 | + /** | ||
47 | + * Gets the width of the matrix. | ||
48 | + */ | ||
49 | + public function getWidth() : int | ||
50 | + { | ||
51 | + return $this->width; | ||
52 | + } | ||
53 | + | ||
54 | + /** | ||
55 | + * Gets the height of the matrix. | ||
56 | + */ | ||
57 | + public function getHeight() : int | ||
58 | + { | ||
59 | + return $this->height; | ||
60 | + } | ||
61 | + | ||
62 | + /** | ||
63 | + * Gets the internal representation of the matrix. | ||
64 | + * | ||
65 | + * @return SplFixedArray<SplFixedArray<int>> | ||
66 | + */ | ||
67 | + public function getArray() : SplFixedArray | ||
68 | + { | ||
69 | + return $this->bytes; | ||
70 | + } | ||
71 | + | ||
72 | + /** | ||
73 | + * @return Traversable<int> | ||
74 | + */ | ||
75 | + public function getBytes() : Traversable | ||
76 | + { | ||
77 | + foreach ($this->bytes as $row) { | ||
78 | + foreach ($row as $byte) { | ||
79 | + yield $byte; | ||
80 | + } | ||
81 | + } | ||
82 | + } | ||
83 | + | ||
84 | + /** | ||
85 | + * Gets the byte for a specific position. | ||
86 | + */ | ||
87 | + public function get(int $x, int $y) : int | ||
88 | + { | ||
89 | + return $this->bytes[$y][$x]; | ||
90 | + } | ||
91 | + | ||
92 | + /** | ||
93 | + * Sets the byte for a specific position. | ||
94 | + */ | ||
95 | + public function set(int $x, int $y, int $value) : void | ||
96 | + { | ||
97 | + $this->bytes[$y][$x] = $value; | ||
98 | + } | ||
99 | + | ||
100 | + /** | ||
101 | + * Clears the matrix with a specific value. | ||
102 | + */ | ||
103 | + public function clear(int $value) : void | ||
104 | + { | ||
105 | + for ($y = 0; $y < $this->height; ++$y) { | ||
106 | + for ($x = 0; $x < $this->width; ++$x) { | ||
107 | + $this->bytes[$y][$x] = $value; | ||
108 | + } | ||
109 | + } | ||
110 | + } | ||
111 | + | ||
112 | + public function __clone() | ||
113 | + { | ||
114 | + $this->bytes = clone $this->bytes; | ||
115 | + | ||
116 | + foreach ($this->bytes as $index => $row) { | ||
117 | + $this->bytes[$index] = clone $row; | ||
118 | + } | ||
119 | + } | ||
120 | + | ||
121 | + /** | ||
122 | + * Returns a string representation of the matrix. | ||
123 | + */ | ||
124 | + public function __toString() : string | ||
125 | + { | ||
126 | + $result = ''; | ||
127 | + | ||
128 | + for ($y = 0; $y < $this->height; $y++) { | ||
129 | + for ($x = 0; $x < $this->width; $x++) { | ||
130 | + switch ($this->bytes[$y][$x]) { | ||
131 | + case 0: | ||
132 | + $result .= ' 0'; | ||
133 | + break; | ||
134 | + | ||
135 | + case 1: | ||
136 | + $result .= ' 1'; | ||
137 | + break; | ||
138 | + | ||
139 | + default: | ||
140 | + $result .= ' '; | ||
141 | + break; | ||
142 | + } | ||
143 | + } | ||
144 | + | ||
145 | + $result .= "\n"; | ||
146 | + } | ||
147 | + | ||
148 | + return $result; | ||
149 | + } | ||
150 | +} |
1 | +<?php | ||
2 | +declare(strict_types = 1); | ||
3 | + | ||
4 | +namespace BaconQrCode\Encoder; | ||
5 | + | ||
6 | +use BaconQrCode\Common\BitArray; | ||
7 | +use BaconQrCode\Common\CharacterSetEci; | ||
8 | +use BaconQrCode\Common\ErrorCorrectionLevel; | ||
9 | +use BaconQrCode\Common\Mode; | ||
10 | +use BaconQrCode\Common\ReedSolomonCodec; | ||
11 | +use BaconQrCode\Common\Version; | ||
12 | +use BaconQrCode\Exception\WriterException; | ||
13 | +use SplFixedArray; | ||
14 | + | ||
15 | +/** | ||
16 | + * Encoder. | ||
17 | + */ | ||
18 | +final class Encoder | ||
19 | +{ | ||
20 | + /** | ||
21 | + * Default byte encoding. | ||
22 | + */ | ||
23 | + public const DEFAULT_BYTE_MODE_ECODING = 'ISO-8859-1'; | ||
24 | + | ||
25 | + /** | ||
26 | + * The original table is defined in the table 5 of JISX0510:2004 (p.19). | ||
27 | + */ | ||
28 | + private const ALPHANUMERIC_TABLE = [ | ||
29 | + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x00-0x0f | ||
30 | + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x10-0x1f | ||
31 | + 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, // 0x20-0x2f | ||
32 | + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, // 0x30-0x3f | ||
33 | + -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 0x40-0x4f | ||
34 | + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, // 0x50-0x5f | ||
35 | + ]; | ||
36 | + | ||
37 | + /** | ||
38 | + * Codec cache. | ||
39 | + * | ||
40 | + * @var array | ||
41 | + */ | ||
42 | + private static $codecs = []; | ||
43 | + | ||
44 | + /** | ||
45 | + * Encodes "content" with the error correction level "ecLevel". | ||
46 | + */ | ||
47 | + public static function encode( | ||
48 | + string $content, | ||
49 | + ErrorCorrectionLevel $ecLevel, | ||
50 | + string $encoding = self::DEFAULT_BYTE_MODE_ECODING | ||
51 | + ) : QrCode { | ||
52 | + // Pick an encoding mode appropriate for the content. Note that this | ||
53 | + // will not attempt to use multiple modes / segments even if that were | ||
54 | + // more efficient. Would be nice. | ||
55 | + $mode = self::chooseMode($content, $encoding); | ||
56 | + | ||
57 | + // This will store the header information, like mode and length, as well | ||
58 | + // as "header" segments like an ECI segment. | ||
59 | + $headerBits = new BitArray(); | ||
60 | + | ||
61 | + // Append ECI segment if applicable | ||
62 | + if (Mode::BYTE() === $mode && self::DEFAULT_BYTE_MODE_ECODING !== $encoding) { | ||
63 | + $eci = CharacterSetEci::getCharacterSetEciByName($encoding); | ||
64 | + | ||
65 | + if (null !== $eci) { | ||
66 | + self::appendEci($eci, $headerBits); | ||
67 | + } | ||
68 | + } | ||
69 | + | ||
70 | + // (With ECI in place,) Write the mode marker | ||
71 | + self::appendModeInfo($mode, $headerBits); | ||
72 | + | ||
73 | + // Collect data within the main segment, separately, to count its size | ||
74 | + // if needed. Don't add it to main payload yet. | ||
75 | + $dataBits = new BitArray(); | ||
76 | + self::appendBytes($content, $mode, $dataBits, $encoding); | ||
77 | + | ||
78 | + // Hard part: need to know version to know how many bits length takes. | ||
79 | + // But need to know how many bits it takes to know version. First we | ||
80 | + // take a guess at version by assuming version will be the minimum, 1: | ||
81 | + $provisionalBitsNeeded = $headerBits->getSize() | ||
82 | + + $mode->getCharacterCountBits(Version::getVersionForNumber(1)) | ||
83 | + + $dataBits->getSize(); | ||
84 | + $provisionalVersion = self::chooseVersion($provisionalBitsNeeded, $ecLevel); | ||
85 | + | ||
86 | + // Use that guess to calculate the right version. I am still not sure | ||
87 | + // this works in 100% of cases. | ||
88 | + $bitsNeeded = $headerBits->getSize() | ||
89 | + + $mode->getCharacterCountBits($provisionalVersion) | ||
90 | + + $dataBits->getSize(); | ||
91 | + $version = self::chooseVersion($bitsNeeded, $ecLevel); | ||
92 | + | ||
93 | + $headerAndDataBits = new BitArray(); | ||
94 | + $headerAndDataBits->appendBitArray($headerBits); | ||
95 | + | ||
96 | + // Find "length" of main segment and write it. | ||
97 | + $numLetters = (Mode::BYTE() === $mode ? $dataBits->getSizeInBytes() : strlen($content)); | ||
98 | + self::appendLengthInfo($numLetters, $version, $mode, $headerAndDataBits); | ||
99 | + | ||
100 | + // Put data together into the overall payload. | ||
101 | + $headerAndDataBits->appendBitArray($dataBits); | ||
102 | + $ecBlocks = $version->getEcBlocksForLevel($ecLevel); | ||
103 | + $numDataBytes = $version->getTotalCodewords() - $ecBlocks->getTotalEcCodewords(); | ||
104 | + | ||
105 | + // Terminate the bits properly. | ||
106 | + self::terminateBits($numDataBytes, $headerAndDataBits); | ||
107 | + | ||
108 | + // Interleave data bits with error correction code. | ||
109 | + $finalBits = self::interleaveWithEcBytes( | ||
110 | + $headerAndDataBits, | ||
111 | + $version->getTotalCodewords(), | ||
112 | + $numDataBytes, | ||
113 | + $ecBlocks->getNumBlocks() | ||
114 | + ); | ||
115 | + | ||
116 | + // Choose the mask pattern. | ||
117 | + $dimension = $version->getDimensionForVersion(); | ||
118 | + $matrix = new ByteMatrix($dimension, $dimension); | ||
119 | + $maskPattern = self::chooseMaskPattern($finalBits, $ecLevel, $version, $matrix); | ||
120 | + | ||
121 | + // Build the matrix. | ||
122 | + MatrixUtil::buildMatrix($finalBits, $ecLevel, $version, $maskPattern, $matrix); | ||
123 | + | ||
124 | + return new QrCode($mode, $ecLevel, $version, $maskPattern, $matrix); | ||
125 | + } | ||
126 | + | ||
127 | + /** | ||
128 | + * Gets the alphanumeric code for a byte. | ||
129 | + */ | ||
130 | + private static function getAlphanumericCode(int $code) : int | ||
131 | + { | ||
132 | + if (isset(self::ALPHANUMERIC_TABLE[$code])) { | ||
133 | + return self::ALPHANUMERIC_TABLE[$code]; | ||
134 | + } | ||
135 | + | ||
136 | + return -1; | ||
137 | + } | ||
138 | + | ||
139 | + /** | ||
140 | + * Chooses the best mode for a given content. | ||
141 | + */ | ||
142 | + private static function chooseMode(string $content, string $encoding = null) : Mode | ||
143 | + { | ||
144 | + if (null !== $encoding && 0 === strcasecmp($encoding, 'SHIFT-JIS')) { | ||
145 | + return self::isOnlyDoubleByteKanji($content) ? Mode::KANJI() : Mode::BYTE(); | ||
146 | + } | ||
147 | + | ||
148 | + $hasNumeric = false; | ||
149 | + $hasAlphanumeric = false; | ||
150 | + $contentLength = strlen($content); | ||
151 | + | ||
152 | + for ($i = 0; $i < $contentLength; ++$i) { | ||
153 | + $char = $content[$i]; | ||
154 | + | ||
155 | + if (ctype_digit($char)) { | ||
156 | + $hasNumeric = true; | ||
157 | + } elseif (-1 !== self::getAlphanumericCode(ord($char))) { | ||
158 | + $hasAlphanumeric = true; | ||
159 | + } else { | ||
160 | + return Mode::BYTE(); | ||
161 | + } | ||
162 | + } | ||
163 | + | ||
164 | + if ($hasAlphanumeric) { | ||
165 | + return Mode::ALPHANUMERIC(); | ||
166 | + } elseif ($hasNumeric) { | ||
167 | + return Mode::NUMERIC(); | ||
168 | + } | ||
169 | + | ||
170 | + return Mode::BYTE(); | ||
171 | + } | ||
172 | + | ||
173 | + /** | ||
174 | + * Calculates the mask penalty for a matrix. | ||
175 | + */ | ||
176 | + private static function calculateMaskPenalty(ByteMatrix $matrix) : int | ||
177 | + { | ||
178 | + return ( | ||
179 | + MaskUtil::applyMaskPenaltyRule1($matrix) | ||
180 | + + MaskUtil::applyMaskPenaltyRule2($matrix) | ||
181 | + + MaskUtil::applyMaskPenaltyRule3($matrix) | ||
182 | + + MaskUtil::applyMaskPenaltyRule4($matrix) | ||
183 | + ); | ||
184 | + } | ||
185 | + | ||
186 | + /** | ||
187 | + * Checks if content only consists of double-byte kanji characters. | ||
188 | + */ | ||
189 | + private static function isOnlyDoubleByteKanji(string $content) : bool | ||
190 | + { | ||
191 | + $bytes = @iconv('utf-8', 'SHIFT-JIS', $content); | ||
192 | + | ||
193 | + if (false === $bytes) { | ||
194 | + return false; | ||
195 | + } | ||
196 | + | ||
197 | + $length = strlen($bytes); | ||
198 | + | ||
199 | + if (0 !== $length % 2) { | ||
200 | + return false; | ||
201 | + } | ||
202 | + | ||
203 | + for ($i = 0; $i < $length; $i += 2) { | ||
204 | + $byte = $bytes[$i] & 0xff; | ||
205 | + | ||
206 | + if (($byte < 0x81 || $byte > 0x9f) && $byte < 0xe0 || $byte > 0xeb) { | ||
207 | + return false; | ||
208 | + } | ||
209 | + } | ||
210 | + | ||
211 | + return true; | ||
212 | + } | ||
213 | + | ||
214 | + /** | ||
215 | + * Chooses the best mask pattern for a matrix. | ||
216 | + */ | ||
217 | + private static function chooseMaskPattern( | ||
218 | + BitArray $bits, | ||
219 | + ErrorCorrectionLevel $ecLevel, | ||
220 | + Version $version, | ||
221 | + ByteMatrix $matrix | ||
222 | + ) : int { | ||
223 | + $minPenalty = PHP_INT_MAX; | ||
224 | + $bestMaskPattern = -1; | ||
225 | + | ||
226 | + for ($maskPattern = 0; $maskPattern < QrCode::NUM_MASK_PATTERNS; ++$maskPattern) { | ||
227 | + MatrixUtil::buildMatrix($bits, $ecLevel, $version, $maskPattern, $matrix); | ||
228 | + $penalty = self::calculateMaskPenalty($matrix); | ||
229 | + | ||
230 | + if ($penalty < $minPenalty) { | ||
231 | + $minPenalty = $penalty; | ||
232 | + $bestMaskPattern = $maskPattern; | ||
233 | + } | ||
234 | + } | ||
235 | + | ||
236 | + return $bestMaskPattern; | ||
237 | + } | ||
238 | + | ||
239 | + /** | ||
240 | + * Chooses the best version for the input. | ||
241 | + * | ||
242 | + * @throws WriterException if data is too big | ||
243 | + */ | ||
244 | + private static function chooseVersion(int $numInputBits, ErrorCorrectionLevel $ecLevel) : Version | ||
245 | + { | ||
246 | + for ($versionNum = 1; $versionNum <= 40; ++$versionNum) { | ||
247 | + $version = Version::getVersionForNumber($versionNum); | ||
248 | + $numBytes = $version->getTotalCodewords(); | ||
249 | + | ||
250 | + $ecBlocks = $version->getEcBlocksForLevel($ecLevel); | ||
251 | + $numEcBytes = $ecBlocks->getTotalEcCodewords(); | ||
252 | + | ||
253 | + $numDataBytes = $numBytes - $numEcBytes; | ||
254 | + $totalInputBytes = intdiv($numInputBits + 8, 8); | ||
255 | + | ||
256 | + if ($numDataBytes >= $totalInputBytes) { | ||
257 | + return $version; | ||
258 | + } | ||
259 | + } | ||
260 | + | ||
261 | + throw new WriterException('Data too big'); | ||
262 | + } | ||
263 | + | ||
264 | + /** | ||
265 | + * Terminates the bits in a bit array. | ||
266 | + * | ||
267 | + * @throws WriterException if data bits cannot fit in the QR code | ||
268 | + * @throws WriterException if bits size does not equal the capacity | ||
269 | + */ | ||
270 | + private static function terminateBits(int $numDataBytes, BitArray $bits) : void | ||
271 | + { | ||
272 | + $capacity = $numDataBytes << 3; | ||
273 | + | ||
274 | + if ($bits->getSize() > $capacity) { | ||
275 | + throw new WriterException('Data bits cannot fit in the QR code'); | ||
276 | + } | ||
277 | + | ||
278 | + for ($i = 0; $i < 4 && $bits->getSize() < $capacity; ++$i) { | ||
279 | + $bits->appendBit(false); | ||
280 | + } | ||
281 | + | ||
282 | + $numBitsInLastByte = $bits->getSize() & 0x7; | ||
283 | + | ||
284 | + if ($numBitsInLastByte > 0) { | ||
285 | + for ($i = $numBitsInLastByte; $i < 8; ++$i) { | ||
286 | + $bits->appendBit(false); | ||
287 | + } | ||
288 | + } | ||
289 | + | ||
290 | + $numPaddingBytes = $numDataBytes - $bits->getSizeInBytes(); | ||
291 | + | ||
292 | + for ($i = 0; $i < $numPaddingBytes; ++$i) { | ||
293 | + $bits->appendBits(0 === ($i & 0x1) ? 0xec : 0x11, 8); | ||
294 | + } | ||
295 | + | ||
296 | + if ($bits->getSize() !== $capacity) { | ||
297 | + throw new WriterException('Bits size does not equal capacity'); | ||
298 | + } | ||
299 | + } | ||
300 | + | ||
301 | + /** | ||
302 | + * Gets number of data- and EC bytes for a block ID. | ||
303 | + * | ||
304 | + * @return int[] | ||
305 | + * @throws WriterException if block ID is too large | ||
306 | + * @throws WriterException if EC bytes mismatch | ||
307 | + * @throws WriterException if RS blocks mismatch | ||
308 | + * @throws WriterException if total bytes mismatch | ||
309 | + */ | ||
310 | + private static function getNumDataBytesAndNumEcBytesForBlockId( | ||
311 | + int $numTotalBytes, | ||
312 | + int $numDataBytes, | ||
313 | + int $numRsBlocks, | ||
314 | + int $blockId | ||
315 | + ) : array { | ||
316 | + if ($blockId >= $numRsBlocks) { | ||
317 | + throw new WriterException('Block ID too large'); | ||
318 | + } | ||
319 | + | ||
320 | + $numRsBlocksInGroup2 = $numTotalBytes % $numRsBlocks; | ||
321 | + $numRsBlocksInGroup1 = $numRsBlocks - $numRsBlocksInGroup2; | ||
322 | + $numTotalBytesInGroup1 = intdiv($numTotalBytes, $numRsBlocks); | ||
323 | + $numTotalBytesInGroup2 = $numTotalBytesInGroup1 + 1; | ||
324 | + $numDataBytesInGroup1 = intdiv($numDataBytes, $numRsBlocks); | ||
325 | + $numDataBytesInGroup2 = $numDataBytesInGroup1 + 1; | ||
326 | + $numEcBytesInGroup1 = $numTotalBytesInGroup1 - $numDataBytesInGroup1; | ||
327 | + $numEcBytesInGroup2 = $numTotalBytesInGroup2 - $numDataBytesInGroup2; | ||
328 | + | ||
329 | + if ($numEcBytesInGroup1 !== $numEcBytesInGroup2) { | ||
330 | + throw new WriterException('EC bytes mismatch'); | ||
331 | + } | ||
332 | + | ||
333 | + if ($numRsBlocks !== $numRsBlocksInGroup1 + $numRsBlocksInGroup2) { | ||
334 | + throw new WriterException('RS blocks mismatch'); | ||
335 | + } | ||
336 | + | ||
337 | + if ($numTotalBytes !== | ||
338 | + (($numDataBytesInGroup1 + $numEcBytesInGroup1) * $numRsBlocksInGroup1) | ||
339 | + + (($numDataBytesInGroup2 + $numEcBytesInGroup2) * $numRsBlocksInGroup2) | ||
340 | + ) { | ||
341 | + throw new WriterException('Total bytes mismatch'); | ||
342 | + } | ||
343 | + | ||
344 | + if ($blockId < $numRsBlocksInGroup1) { | ||
345 | + return [$numDataBytesInGroup1, $numEcBytesInGroup1]; | ||
346 | + } else { | ||
347 | + return [$numDataBytesInGroup2, $numEcBytesInGroup2]; | ||
348 | + } | ||
349 | + } | ||
350 | + | ||
351 | + /** | ||
352 | + * Interleaves data with EC bytes. | ||
353 | + * | ||
354 | + * @throws WriterException if number of bits and data bytes does not match | ||
355 | + * @throws WriterException if data bytes does not match offset | ||
356 | + * @throws WriterException if an interleaving error occurs | ||
357 | + */ | ||
358 | + private static function interleaveWithEcBytes( | ||
359 | + BitArray $bits, | ||
360 | + int $numTotalBytes, | ||
361 | + int $numDataBytes, | ||
362 | + int $numRsBlocks | ||
363 | + ) : BitArray { | ||
364 | + if ($bits->getSizeInBytes() !== $numDataBytes) { | ||
365 | + throw new WriterException('Number of bits and data bytes does not match'); | ||
366 | + } | ||
367 | + | ||
368 | + $dataBytesOffset = 0; | ||
369 | + $maxNumDataBytes = 0; | ||
370 | + $maxNumEcBytes = 0; | ||
371 | + | ||
372 | + $blocks = new SplFixedArray($numRsBlocks); | ||
373 | + | ||
374 | + for ($i = 0; $i < $numRsBlocks; ++$i) { | ||
375 | + list($numDataBytesInBlock, $numEcBytesInBlock) = self::getNumDataBytesAndNumEcBytesForBlockId( | ||
376 | + $numTotalBytes, | ||
377 | + $numDataBytes, | ||
378 | + $numRsBlocks, | ||
379 | + $i | ||
380 | + ); | ||
381 | + | ||
382 | + $size = $numDataBytesInBlock; | ||
383 | + $dataBytes = $bits->toBytes(8 * $dataBytesOffset, $size); | ||
384 | + $ecBytes = self::generateEcBytes($dataBytes, $numEcBytesInBlock); | ||
385 | + $blocks[$i] = new BlockPair($dataBytes, $ecBytes); | ||
386 | + | ||
387 | + $maxNumDataBytes = max($maxNumDataBytes, $size); | ||
388 | + $maxNumEcBytes = max($maxNumEcBytes, count($ecBytes)); | ||
389 | + $dataBytesOffset += $numDataBytesInBlock; | ||
390 | + } | ||
391 | + | ||
392 | + if ($numDataBytes !== $dataBytesOffset) { | ||
393 | + throw new WriterException('Data bytes does not match offset'); | ||
394 | + } | ||
395 | + | ||
396 | + $result = new BitArray(); | ||
397 | + | ||
398 | + for ($i = 0; $i < $maxNumDataBytes; ++$i) { | ||
399 | + foreach ($blocks as $block) { | ||
400 | + $dataBytes = $block->getDataBytes(); | ||
401 | + | ||
402 | + if ($i < count($dataBytes)) { | ||
403 | + $result->appendBits($dataBytes[$i], 8); | ||
404 | + } | ||
405 | + } | ||
406 | + } | ||
407 | + | ||
408 | + for ($i = 0; $i < $maxNumEcBytes; ++$i) { | ||
409 | + foreach ($blocks as $block) { | ||
410 | + $ecBytes = $block->getErrorCorrectionBytes(); | ||
411 | + | ||
412 | + if ($i < count($ecBytes)) { | ||
413 | + $result->appendBits($ecBytes[$i], 8); | ||
414 | + } | ||
415 | + } | ||
416 | + } | ||
417 | + | ||
418 | + if ($numTotalBytes !== $result->getSizeInBytes()) { | ||
419 | + throw new WriterException( | ||
420 | + 'Interleaving error: ' . $numTotalBytes . ' and ' . $result->getSizeInBytes() . ' differ' | ||
421 | + ); | ||
422 | + } | ||
423 | + | ||
424 | + return $result; | ||
425 | + } | ||
426 | + | ||
427 | + /** | ||
428 | + * Generates EC bytes for given data. | ||
429 | + * | ||
430 | + * @param SplFixedArray<int> $dataBytes | ||
431 | + * @return SplFixedArray<int> | ||
432 | + */ | ||
433 | + private static function generateEcBytes(SplFixedArray $dataBytes, int $numEcBytesInBlock) : SplFixedArray | ||
434 | + { | ||
435 | + $numDataBytes = count($dataBytes); | ||
436 | + $toEncode = new SplFixedArray($numDataBytes + $numEcBytesInBlock); | ||
437 | + | ||
438 | + for ($i = 0; $i < $numDataBytes; $i++) { | ||
439 | + $toEncode[$i] = $dataBytes[$i] & 0xff; | ||
440 | + } | ||
441 | + | ||
442 | + $ecBytes = new SplFixedArray($numEcBytesInBlock); | ||
443 | + $codec = self::getCodec($numDataBytes, $numEcBytesInBlock); | ||
444 | + $codec->encode($toEncode, $ecBytes); | ||
445 | + | ||
446 | + return $ecBytes; | ||
447 | + } | ||
448 | + | ||
449 | + /** | ||
450 | + * Gets an RS codec and caches it. | ||
451 | + */ | ||
452 | + private static function getCodec(int $numDataBytes, int $numEcBytesInBlock) : ReedSolomonCodec | ||
453 | + { | ||
454 | + $cacheId = $numDataBytes . '-' . $numEcBytesInBlock; | ||
455 | + | ||
456 | + if (isset(self::$codecs[$cacheId])) { | ||
457 | + return self::$codecs[$cacheId]; | ||
458 | + } | ||
459 | + | ||
460 | + return self::$codecs[$cacheId] = new ReedSolomonCodec( | ||
461 | + 8, | ||
462 | + 0x11d, | ||
463 | + 0, | ||
464 | + 1, | ||
465 | + $numEcBytesInBlock, | ||
466 | + 255 - $numDataBytes - $numEcBytesInBlock | ||
467 | + ); | ||
468 | + } | ||
469 | + | ||
470 | + /** | ||
471 | + * Appends mode information to a bit array. | ||
472 | + */ | ||
473 | + private static function appendModeInfo(Mode $mode, BitArray $bits) : void | ||
474 | + { | ||
475 | + $bits->appendBits($mode->getBits(), 4); | ||
476 | + } | ||
477 | + | ||
478 | + /** | ||
479 | + * Appends length information to a bit array. | ||
480 | + * | ||
481 | + * @throws WriterException if num letters is bigger than expected | ||
482 | + */ | ||
483 | + private static function appendLengthInfo(int $numLetters, Version $version, Mode $mode, BitArray $bits) : void | ||
484 | + { | ||
485 | + $numBits = $mode->getCharacterCountBits($version); | ||
486 | + | ||
487 | + if ($numLetters >= (1 << $numBits)) { | ||
488 | + throw new WriterException($numLetters . ' is bigger than ' . ((1 << $numBits) - 1)); | ||
489 | + } | ||
490 | + | ||
491 | + $bits->appendBits($numLetters, $numBits); | ||
492 | + } | ||
493 | + | ||
494 | + /** | ||
495 | + * Appends bytes to a bit array in a specific mode. | ||
496 | + * | ||
497 | + * @throws WriterException if an invalid mode was supplied | ||
498 | + */ | ||
499 | + private static function appendBytes(string $content, Mode $mode, BitArray $bits, string $encoding) : void | ||
500 | + { | ||
501 | + switch ($mode) { | ||
502 | + case Mode::NUMERIC(): | ||
503 | + self::appendNumericBytes($content, $bits); | ||
504 | + break; | ||
505 | + | ||
506 | + case Mode::ALPHANUMERIC(): | ||
507 | + self::appendAlphanumericBytes($content, $bits); | ||
508 | + break; | ||
509 | + | ||
510 | + case Mode::BYTE(): | ||
511 | + self::append8BitBytes($content, $bits, $encoding); | ||
512 | + break; | ||
513 | + | ||
514 | + case Mode::KANJI(): | ||
515 | + self::appendKanjiBytes($content, $bits); | ||
516 | + break; | ||
517 | + | ||
518 | + default: | ||
519 | + throw new WriterException('Invalid mode: ' . $mode); | ||
520 | + } | ||
521 | + } | ||
522 | + | ||
523 | + /** | ||
524 | + * Appends numeric bytes to a bit array. | ||
525 | + */ | ||
526 | + private static function appendNumericBytes(string $content, BitArray $bits) : void | ||
527 | + { | ||
528 | + $length = strlen($content); | ||
529 | + $i = 0; | ||
530 | + | ||
531 | + while ($i < $length) { | ||
532 | + $num1 = (int) $content[$i]; | ||
533 | + | ||
534 | + if ($i + 2 < $length) { | ||
535 | + // Encode three numeric letters in ten bits. | ||
536 | + $num2 = (int) $content[$i + 1]; | ||
537 | + $num3 = (int) $content[$i + 2]; | ||
538 | + $bits->appendBits($num1 * 100 + $num2 * 10 + $num3, 10); | ||
539 | + $i += 3; | ||
540 | + } elseif ($i + 1 < $length) { | ||
541 | + // Encode two numeric letters in seven bits. | ||
542 | + $num2 = (int) $content[$i + 1]; | ||
543 | + $bits->appendBits($num1 * 10 + $num2, 7); | ||
544 | + $i += 2; | ||
545 | + } else { | ||
546 | + // Encode one numeric letter in four bits. | ||
547 | + $bits->appendBits($num1, 4); | ||
548 | + ++$i; | ||
549 | + } | ||
550 | + } | ||
551 | + } | ||
552 | + | ||
553 | + /** | ||
554 | + * Appends alpha-numeric bytes to a bit array. | ||
555 | + * | ||
556 | + * @throws WriterException if an invalid alphanumeric code was found | ||
557 | + */ | ||
558 | + private static function appendAlphanumericBytes(string $content, BitArray $bits) : void | ||
559 | + { | ||
560 | + $length = strlen($content); | ||
561 | + $i = 0; | ||
562 | + | ||
563 | + while ($i < $length) { | ||
564 | + $code1 = self::getAlphanumericCode(ord($content[$i])); | ||
565 | + | ||
566 | + if (-1 === $code1) { | ||
567 | + throw new WriterException('Invalid alphanumeric code'); | ||
568 | + } | ||
569 | + | ||
570 | + if ($i + 1 < $length) { | ||
571 | + $code2 = self::getAlphanumericCode(ord($content[$i + 1])); | ||
572 | + | ||
573 | + if (-1 === $code2) { | ||
574 | + throw new WriterException('Invalid alphanumeric code'); | ||
575 | + } | ||
576 | + | ||
577 | + // Encode two alphanumeric letters in 11 bits. | ||
578 | + $bits->appendBits($code1 * 45 + $code2, 11); | ||
579 | + $i += 2; | ||
580 | + } else { | ||
581 | + // Encode one alphanumeric letter in six bits. | ||
582 | + $bits->appendBits($code1, 6); | ||
583 | + ++$i; | ||
584 | + } | ||
585 | + } | ||
586 | + } | ||
587 | + | ||
588 | + /** | ||
589 | + * Appends regular 8-bit bytes to a bit array. | ||
590 | + * | ||
591 | + * @throws WriterException if content cannot be encoded to target encoding | ||
592 | + */ | ||
593 | + private static function append8BitBytes(string $content, BitArray $bits, string $encoding) : void | ||
594 | + { | ||
595 | + $bytes = @iconv('utf-8', $encoding, $content); | ||
596 | + | ||
597 | + if (false === $bytes) { | ||
598 | + throw new WriterException('Could not encode content to ' . $encoding); | ||
599 | + } | ||
600 | + | ||
601 | + $length = strlen($bytes); | ||
602 | + | ||
603 | + for ($i = 0; $i < $length; $i++) { | ||
604 | + $bits->appendBits(ord($bytes[$i]), 8); | ||
605 | + } | ||
606 | + } | ||
607 | + | ||
608 | + /** | ||
609 | + * Appends KANJI bytes to a bit array. | ||
610 | + * | ||
611 | + * @throws WriterException if content does not seem to be encoded in SHIFT-JIS | ||
612 | + * @throws WriterException if an invalid byte sequence occurs | ||
613 | + */ | ||
614 | + private static function appendKanjiBytes(string $content, BitArray $bits) : void | ||
615 | + { | ||
616 | + if (strlen($content) % 2 > 0) { | ||
617 | + // We just do a simple length check here. The for loop will check | ||
618 | + // individual characters. | ||
619 | + throw new WriterException('Content does not seem to be encoded in SHIFT-JIS'); | ||
620 | + } | ||
621 | + | ||
622 | + $length = strlen($content); | ||
623 | + | ||
624 | + for ($i = 0; $i < $length; $i += 2) { | ||
625 | + $byte1 = ord($content[$i]) & 0xff; | ||
626 | + $byte2 = ord($content[$i + 1]) & 0xff; | ||
627 | + $code = ($byte1 << 8) | $byte2; | ||
628 | + | ||
629 | + if ($code >= 0x8140 && $code <= 0x9ffc) { | ||
630 | + $subtracted = $code - 0x8140; | ||
631 | + } elseif ($code >= 0xe040 && $code <= 0xebbf) { | ||
632 | + $subtracted = $code - 0xc140; | ||
633 | + } else { | ||
634 | + throw new WriterException('Invalid byte sequence'); | ||
635 | + } | ||
636 | + | ||
637 | + $encoded = (($subtracted >> 8) * 0xc0) + ($subtracted & 0xff); | ||
638 | + | ||
639 | + $bits->appendBits($encoded, 13); | ||
640 | + } | ||
641 | + } | ||
642 | + | ||
643 | + /** | ||
644 | + * Appends ECI information to a bit array. | ||
645 | + */ | ||
646 | + private static function appendEci(CharacterSetEci $eci, BitArray $bits) : void | ||
647 | + { | ||
648 | + $mode = Mode::ECI(); | ||
649 | + $bits->appendBits($mode->getBits(), 4); | ||
650 | + $bits->appendBits($eci->getValue(), 8); | ||
651 | + } | ||
652 | +} |
1 | +<?php | ||
2 | +declare(strict_types = 1); | ||
3 | + | ||
4 | +namespace BaconQrCode\Encoder; | ||
5 | + | ||
6 | +use BaconQrCode\Common\BitUtils; | ||
7 | +use BaconQrCode\Exception\InvalidArgumentException; | ||
8 | + | ||
9 | +/** | ||
10 | + * Mask utility. | ||
11 | + */ | ||
12 | +final class MaskUtil | ||
13 | +{ | ||
14 | + /**#@+ | ||
15 | + * Penalty weights from section 6.8.2.1 | ||
16 | + */ | ||
17 | + const N1 = 3; | ||
18 | + const N2 = 3; | ||
19 | + const N3 = 40; | ||
20 | + const N4 = 10; | ||
21 | + /**#@-*/ | ||
22 | + | ||
23 | + private function __construct() | ||
24 | + { | ||
25 | + } | ||
26 | + | ||
27 | + /** | ||
28 | + * Applies mask penalty rule 1 and returns the penalty. | ||
29 | + * | ||
30 | + * Finds repetitive cells with the same color and gives penalty to them. | ||
31 | + * Example: 00000 or 11111. | ||
32 | + */ | ||
33 | + public static function applyMaskPenaltyRule1(ByteMatrix $matrix) : int | ||
34 | + { | ||
35 | + return ( | ||
36 | + self::applyMaskPenaltyRule1Internal($matrix, true) | ||
37 | + + self::applyMaskPenaltyRule1Internal($matrix, false) | ||
38 | + ); | ||
39 | + } | ||
40 | + | ||
41 | + /** | ||
42 | + * Applies mask penalty rule 2 and returns the penalty. | ||
43 | + * | ||
44 | + * Finds 2x2 blocks with the same color and gives penalty to them. This is | ||
45 | + * actually equivalent to the spec's rule, which is to find MxN blocks and | ||
46 | + * give a penalty proportional to (M-1)x(N-1), because this is the number of | ||
47 | + * 2x2 blocks inside such a block. | ||
48 | + */ | ||
49 | + public static function applyMaskPenaltyRule2(ByteMatrix $matrix) : int | ||
50 | + { | ||
51 | + $penalty = 0; | ||
52 | + $array = $matrix->getArray(); | ||
53 | + $width = $matrix->getWidth(); | ||
54 | + $height = $matrix->getHeight(); | ||
55 | + | ||
56 | + for ($y = 0; $y < $height - 1; ++$y) { | ||
57 | + for ($x = 0; $x < $width - 1; ++$x) { | ||
58 | + $value = $array[$y][$x]; | ||
59 | + | ||
60 | + if ($value === $array[$y][$x + 1] | ||
61 | + && $value === $array[$y + 1][$x] | ||
62 | + && $value === $array[$y + 1][$x + 1] | ||
63 | + ) { | ||
64 | + ++$penalty; | ||
65 | + } | ||
66 | + } | ||
67 | + } | ||
68 | + | ||
69 | + return self::N2 * $penalty; | ||
70 | + } | ||
71 | + | ||
72 | + /** | ||
73 | + * Applies mask penalty rule 3 and returns the penalty. | ||
74 | + * | ||
75 | + * Finds consecutive cells of 00001011101 or 10111010000, and gives penalty | ||
76 | + * to them. If we find patterns like 000010111010000, we give penalties | ||
77 | + * twice (i.e. 40 * 2). | ||
78 | + */ | ||
79 | + public static function applyMaskPenaltyRule3(ByteMatrix $matrix) : int | ||
80 | + { | ||
81 | + $penalty = 0; | ||
82 | + $array = $matrix->getArray(); | ||
83 | + $width = $matrix->getWidth(); | ||
84 | + $height = $matrix->getHeight(); | ||
85 | + | ||
86 | + for ($y = 0; $y < $height; ++$y) { | ||
87 | + for ($x = 0; $x < $width; ++$x) { | ||
88 | + if ($x + 6 < $width | ||
89 | + && 1 === $array[$y][$x] | ||
90 | + && 0 === $array[$y][$x + 1] | ||
91 | + && 1 === $array[$y][$x + 2] | ||
92 | + && 1 === $array[$y][$x + 3] | ||
93 | + && 1 === $array[$y][$x + 4] | ||
94 | + && 0 === $array[$y][$x + 5] | ||
95 | + && 1 === $array[$y][$x + 6] | ||
96 | + && ( | ||
97 | + ( | ||
98 | + $x + 10 < $width | ||
99 | + && 0 === $array[$y][$x + 7] | ||
100 | + && 0 === $array[$y][$x + 8] | ||
101 | + && 0 === $array[$y][$x + 9] | ||
102 | + && 0 === $array[$y][$x + 10] | ||
103 | + ) | ||
104 | + || ( | ||
105 | + $x - 4 >= 0 | ||
106 | + && 0 === $array[$y][$x - 1] | ||
107 | + && 0 === $array[$y][$x - 2] | ||
108 | + && 0 === $array[$y][$x - 3] | ||
109 | + && 0 === $array[$y][$x - 4] | ||
110 | + ) | ||
111 | + ) | ||
112 | + ) { | ||
113 | + $penalty += self::N3; | ||
114 | + } | ||
115 | + | ||
116 | + if ($y + 6 < $height | ||
117 | + && 1 === $array[$y][$x] | ||
118 | + && 0 === $array[$y + 1][$x] | ||
119 | + && 1 === $array[$y + 2][$x] | ||
120 | + && 1 === $array[$y + 3][$x] | ||
121 | + && 1 === $array[$y + 4][$x] | ||
122 | + && 0 === $array[$y + 5][$x] | ||
123 | + && 1 === $array[$y + 6][$x] | ||
124 | + && ( | ||
125 | + ( | ||
126 | + $y + 10 < $height | ||
127 | + && 0 === $array[$y + 7][$x] | ||
128 | + && 0 === $array[$y + 8][$x] | ||
129 | + && 0 === $array[$y + 9][$x] | ||
130 | + && 0 === $array[$y + 10][$x] | ||
131 | + ) | ||
132 | + || ( | ||
133 | + $y - 4 >= 0 | ||
134 | + && 0 === $array[$y - 1][$x] | ||
135 | + && 0 === $array[$y - 2][$x] | ||
136 | + && 0 === $array[$y - 3][$x] | ||
137 | + && 0 === $array[$y - 4][$x] | ||
138 | + ) | ||
139 | + ) | ||
140 | + ) { | ||
141 | + $penalty += self::N3; | ||
142 | + } | ||
143 | + } | ||
144 | + } | ||
145 | + | ||
146 | + return $penalty; | ||
147 | + } | ||
148 | + | ||
149 | + /** | ||
150 | + * Applies mask penalty rule 4 and returns the penalty. | ||
151 | + * | ||
152 | + * Calculates the ratio of dark cells and gives penalty if the ratio is far | ||
153 | + * from 50%. It gives 10 penalty for 5% distance. | ||
154 | + */ | ||
155 | + public static function applyMaskPenaltyRule4(ByteMatrix $matrix) : int | ||
156 | + { | ||
157 | + $numDarkCells = 0; | ||
158 | + | ||
159 | + $array = $matrix->getArray(); | ||
160 | + $width = $matrix->getWidth(); | ||
161 | + $height = $matrix->getHeight(); | ||
162 | + | ||
163 | + for ($y = 0; $y < $height; ++$y) { | ||
164 | + $arrayY = $array[$y]; | ||
165 | + | ||
166 | + for ($x = 0; $x < $width; ++$x) { | ||
167 | + if (1 === $arrayY[$x]) { | ||
168 | + ++$numDarkCells; | ||
169 | + } | ||
170 | + } | ||
171 | + } | ||
172 | + | ||
173 | + $numTotalCells = $height * $width; | ||
174 | + $darkRatio = $numDarkCells / $numTotalCells; | ||
175 | + $fixedPercentVariances = (int) (abs($darkRatio - 0.5) * 20); | ||
176 | + | ||
177 | + return $fixedPercentVariances * self::N4; | ||
178 | + } | ||
179 | + | ||
180 | + /** | ||
181 | + * Returns the mask bit for "getMaskPattern" at "x" and "y". | ||
182 | + * | ||
183 | + * See 8.8 of JISX0510:2004 for mask pattern conditions. | ||
184 | + * | ||
185 | + * @throws InvalidArgumentException if an invalid mask pattern was supplied | ||
186 | + */ | ||
187 | + public static function getDataMaskBit(int $maskPattern, int $x, int $y) : bool | ||
188 | + { | ||
189 | + switch ($maskPattern) { | ||
190 | + case 0: | ||
191 | + $intermediate = ($y + $x) & 0x1; | ||
192 | + break; | ||
193 | + | ||
194 | + case 1: | ||
195 | + $intermediate = $y & 0x1; | ||
196 | + break; | ||
197 | + | ||
198 | + case 2: | ||
199 | + $intermediate = $x % 3; | ||
200 | + break; | ||
201 | + | ||
202 | + case 3: | ||
203 | + $intermediate = ($y + $x) % 3; | ||
204 | + break; | ||
205 | + | ||
206 | + case 4: | ||
207 | + $intermediate = (BitUtils::unsignedRightShift($y, 1) + ($x / 3)) & 0x1; | ||
208 | + break; | ||
209 | + | ||
210 | + case 5: | ||
211 | + $temp = $y * $x; | ||
212 | + $intermediate = ($temp & 0x1) + ($temp % 3); | ||
213 | + break; | ||
214 | + | ||
215 | + case 6: | ||
216 | + $temp = $y * $x; | ||
217 | + $intermediate = (($temp & 0x1) + ($temp % 3)) & 0x1; | ||
218 | + break; | ||
219 | + | ||
220 | + case 7: | ||
221 | + $temp = $y * $x; | ||
222 | + $intermediate = (($temp % 3) + (($y + $x) & 0x1)) & 0x1; | ||
223 | + break; | ||
224 | + | ||
225 | + default: | ||
226 | + throw new InvalidArgumentException('Invalid mask pattern: ' . $maskPattern); | ||
227 | + } | ||
228 | + | ||
229 | + return 0 == $intermediate; | ||
230 | + } | ||
231 | + | ||
232 | + /** | ||
233 | + * Helper function for applyMaskPenaltyRule1. | ||
234 | + * | ||
235 | + * We need this for doing this calculation in both vertical and horizontal | ||
236 | + * orders respectively. | ||
237 | + */ | ||
238 | + private static function applyMaskPenaltyRule1Internal(ByteMatrix $matrix, bool $isHorizontal) : int | ||
239 | + { | ||
240 | + $penalty = 0; | ||
241 | + $iLimit = $isHorizontal ? $matrix->getHeight() : $matrix->getWidth(); | ||
242 | + $jLimit = $isHorizontal ? $matrix->getWidth() : $matrix->getHeight(); | ||
243 | + $array = $matrix->getArray(); | ||
244 | + | ||
245 | + for ($i = 0; $i < $iLimit; ++$i) { | ||
246 | + $numSameBitCells = 0; | ||
247 | + $prevBit = -1; | ||
248 | + | ||
249 | + for ($j = 0; $j < $jLimit; $j++) { | ||
250 | + $bit = $isHorizontal ? $array[$i][$j] : $array[$j][$i]; | ||
251 | + | ||
252 | + if ($bit === $prevBit) { | ||
253 | + ++$numSameBitCells; | ||
254 | + } else { | ||
255 | + if ($numSameBitCells >= 5) { | ||
256 | + $penalty += self::N1 + ($numSameBitCells - 5); | ||
257 | + } | ||
258 | + | ||
259 | + $numSameBitCells = 1; | ||
260 | + $prevBit = $bit; | ||
261 | + } | ||
262 | + } | ||
263 | + | ||
264 | + if ($numSameBitCells >= 5) { | ||
265 | + $penalty += self::N1 + ($numSameBitCells - 5); | ||
266 | + } | ||
267 | + } | ||
268 | + | ||
269 | + return $penalty; | ||
270 | + } | ||
271 | +} |
-
请 注册 或 登录 后发表评论