rqrcode_core 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc2b513b4aa28453c231e4fa52d53c4406bf03bbba9df84ab87eed0e22193389
4
- data.tar.gz: fc7b7bfc2ad40315031899501b94a1b2a35c8987b6eeff9cd9c71d77b4e5d3d4
3
+ metadata.gz: 160d94f790053a577012156c6d6dc7b5e6aa6f88766590d0494f06f9896fe89f
4
+ data.tar.gz: 2a653148523a2a04acad3abd81d62cd8d28835a43dc1dfe6821b9ab6c8c99609
5
5
  SHA512:
6
- metadata.gz: de4b6fdb7ab8f356fb55aedbc93fb949f51f1f68acebdc1aa7d4b82ecf90cf785065104bbf68170ea39f20671ff513c5eb212cbbdb42f38f099f133612d70d17
7
- data.tar.gz: 0a2e8ea5a6e0a2bf43a9d94a3333cc95d6558f47f6369dd5a07107e12079db85b8a21dec57ef31920a855648c96e630fc68d40942e3f9e4570a6f2f07b4c1ee5
6
+ metadata.gz: 7b27968f77e89d07980174c98961abd1255efa317993f246161d940b53dcd185567c6710e567c6596cb1af8789fb2ee584aa2d14fdbc1b5ac3351f5552429c7d
7
+ data.tar.gz: 953c1d8de80226d141049519128c25dc345b6661b44fdc1b263c1590e4554b45cbc84a59a10c67b8c9f5e980cad0a3b8d58e5b81030532291b9d87097d96c98b
@@ -0,0 +1,4 @@
1
+ # These are supported funding model platforms
2
+
3
+ github: whomwah
4
+ buy_me_a_coffee: duncanrobertson
@@ -3,11 +3,10 @@ name: rqrcode_core
3
3
  on:
4
4
  push:
5
5
  branches:
6
- - master
7
- - release/*
8
- pull_request:
9
- branches:
10
- - master
6
+ - main
7
+ - release
8
+ pull_request: # Runs on any PR regardless of target branch
9
+ workflow_dispatch:
11
10
 
12
11
  jobs:
13
12
  Build:
@@ -15,15 +14,15 @@ jobs:
15
14
  fail-fast: false
16
15
  matrix:
17
16
  os: [ubuntu-latest, macos-latest]
18
- ruby: [2.5, 2.6, 2.7, 3.0]
17
+ ruby: ["3.0", "3.1", "3.2", "3.3", "3.4"]
19
18
  runs-on: ${{ matrix.os }}
20
19
  steps:
21
- - uses: actions/checkout@v1
22
- - uses: ruby/setup-ruby@v1
23
- with:
24
- ruby-version: ${{ matrix.ruby }}
25
- bundler-cache: true # runs 'bundle install' and caches installed gems automatically
26
- - name: Run Tests
27
- run: bundle exec rake test
28
- - name: StandardRB Check
29
- run: bundle exec standardrb --format progress
20
+ - uses: actions/checkout@v3
21
+ - uses: ruby/setup-ruby@v1
22
+ with:
23
+ ruby-version: ${{ matrix.ruby }}
24
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
25
+ - name: Run Tests
26
+ run: bundle exec rake test
27
+ - name: StandardRB Check
28
+ run: bundle exec standardrb --format progress
data/CHANGELOG.md CHANGED
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.0.0] - 2025-04-24
11
+
12
+ - Various README updates
13
+ - Fix for issue #43
14
+ - Fix for issue #42
15
+ - Drop Ruby < 3 to keep up with dependency updates
16
+ - Add option to use ENV `RQRCODE_CORE_ARCH_BITS` to override the bits value (32 or 64) used during the encoding process.
17
+ This has been shown to greatly reduce the memory usage but I can't prove it doesn't break anything for all people.
18
+ Use at your own risk.
19
+ - Fixed the 'Do Your Own Rendering' example code to reflect the current interface.
20
+
21
+ ## [1.2.0] - 2021-08-26
22
+
23
+ - Added Multi Mode Support which allows for multi-segment encoding. Thanks to [@ssayer](https://github.com/ssayer)
24
+
10
25
  ## [1.1.0] - 2021-07-01
11
26
 
12
27
  - Add a basic benchmark file
@@ -25,7 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
25
40
 
26
41
  ### Breaking Changes
27
42
 
28
- - Very niche but a breaking change never the less. The `to_s` method *no longer* accepts the `:true` and `:false` arguments, but prefers `:dark` and `:light`.
43
+ - Very niche but a breaking change never the less. The `to_s` method _no longer_ accepts the `:true` and `:false` arguments, but prefers `:dark` and `:light`.
29
44
 
30
45
  ## [0.2.0] - 2020-12-26
31
46
 
@@ -33,7 +48,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
33
48
 
34
49
  - fix `required_ruby_version` for Ruby 3 support
35
50
 
36
- [unreleased]: https://github.com/whomwah/rqrcode_core/compare/v1.1.0...HEAD
51
+ [unreleased]: https://github.com/whomwah/rqrcode_core/compare/v2.0.0...HEAD
52
+ [2.0.0]: https://github.com/whomwah/rqrcode_core/compare/v1.2.0...2.0.0
53
+ [1.2.0]: https://github.com/whomwah/rqrcode_core/compare/v1.1.0...v1.2.0
37
54
  [1.1.0]: https://github.com/whomwah/rqrcode_core/compare/v1.0.0...v1.1.0
38
55
  [1.0.0]: https://github.com/whomwah/rqrcode_core/compare/v0.2.0...v1.0.0
39
56
  [0.2.0]: https://github.com/whomwah/rqrcode_core/compare/v0.1.2...v0.2.0
data/Gemfile.lock CHANGED
@@ -1,43 +1,56 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rqrcode_core (1.1.0)
4
+ rqrcode_core (2.0.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
9
  ast (2.4.2)
10
- minitest (5.14.4)
11
- parallel (1.20.1)
12
- parser (3.0.1.0)
10
+ json (2.7.2)
11
+ language_server-protocol (3.17.0.3)
12
+ lint_roller (1.1.0)
13
+ minitest (5.25.1)
14
+ parallel (1.26.3)
15
+ parser (3.3.5.0)
13
16
  ast (~> 2.4.1)
14
- rainbow (3.0.0)
15
- rake (13.0.3)
16
- regexp_parser (2.1.1)
17
- rexml (3.2.5)
18
- rubocop (1.12.1)
17
+ racc
18
+ racc (1.8.1)
19
+ rainbow (3.1.1)
20
+ rake (13.2.1)
21
+ regexp_parser (2.9.2)
22
+ rubocop (1.66.1)
23
+ json (~> 2.3)
24
+ language_server-protocol (>= 3.17.0)
19
25
  parallel (~> 1.10)
20
- parser (>= 3.0.0.0)
26
+ parser (>= 3.3.0.2)
21
27
  rainbow (>= 2.2.2, < 4.0)
22
- regexp_parser (>= 1.8, < 3.0)
23
- rexml
24
- rubocop-ast (>= 1.2.0, < 2.0)
28
+ regexp_parser (>= 2.4, < 3.0)
29
+ rubocop-ast (>= 1.32.2, < 2.0)
25
30
  ruby-progressbar (~> 1.7)
26
- unicode-display_width (>= 1.4.0, < 3.0)
27
- rubocop-ast (1.4.1)
28
- parser (>= 2.7.1.5)
29
- rubocop-performance (1.10.1)
30
- rubocop (>= 0.90.0, < 2.0)
31
- rubocop-ast (>= 0.4.0)
32
- ruby-progressbar (1.11.0)
33
- standard (1.0.5)
34
- rubocop (= 1.12.1)
35
- rubocop-performance (= 1.10.1)
36
- standardrb (1.0.0)
37
- standard
38
- unicode-display_width (2.0.0)
31
+ unicode-display_width (>= 2.4.0, < 3.0)
32
+ rubocop-ast (1.32.3)
33
+ parser (>= 3.3.1.0)
34
+ rubocop-performance (1.22.1)
35
+ rubocop (>= 1.48.1, < 2.0)
36
+ rubocop-ast (>= 1.31.1, < 2.0)
37
+ ruby-progressbar (1.13.0)
38
+ standard (1.41.0)
39
+ language_server-protocol (~> 3.17.0.2)
40
+ lint_roller (~> 1.0)
41
+ rubocop (~> 1.66.0)
42
+ standard-custom (~> 1.0.0)
43
+ standard-performance (~> 1.5)
44
+ standard-custom (1.0.2)
45
+ lint_roller (~> 1.0)
46
+ rubocop (~> 1.50)
47
+ standard-performance (1.5.0)
48
+ lint_roller (~> 1.1)
49
+ rubocop-performance (~> 1.22.0)
50
+ unicode-display_width (2.6.0)
39
51
 
40
52
  PLATFORMS
53
+ aarch64-linux
41
54
  ruby
42
55
 
43
56
  DEPENDENCIES
@@ -45,7 +58,7 @@ DEPENDENCIES
45
58
  minitest (~> 5.0)
46
59
  rake (~> 13.0)
47
60
  rqrcode_core!
48
- standardrb (~> 1.0)
61
+ standard (~> 1.41)
49
62
 
50
63
  BUNDLED WITH
51
- 2.2.15
64
+ 2.5.16
data/README.md CHANGED
@@ -3,16 +3,17 @@
3
3
 
4
4
  # RQRCodeCore
5
5
 
6
- `rqrcode_core` is a library for encoding QR codes in pure Ruby. It has a simple interface with all the standard qrcode options. It was originally adapted in 2008 from a Javascript library by [Kazuhiko Arase](https://github.com/kazuhikoarase).
6
+ `rqrcode_core` is a library for encoding QR Codes in pure Ruby. It has a simple interface with all the standard qrcode options. It was originally adapted in 2008 from a Javascript library by [Kazuhiko Arase](https://github.com/kazuhikoarase).
7
7
 
8
8
  Features:
9
9
 
10
- * `rqrcode_core` is a Ruby only library. It requires no native libraries. Just Ruby!
11
- * It is an encoding library. You can't decode QR codes with it.
12
- * The interface is simple and assumes you just want to encode a string into a QR code.
13
- * QR code is trademarked by Denso Wave inc.
10
+ - `rqrcode_core` is a Ruby only library. It requires no 3rd party libraries. Just Ruby!
11
+ - It is an encoding library. You can't decode QR Codes with it.
12
+ - The interface is simple and assumes you just want to encode a string into a QR Code, but also allows for encoding multiple segments.
13
+ - QR Code is trademarked by Denso Wave inc.
14
+ - Minimum Ruby version is `>= 3.0.0`
14
15
 
15
- `rqrcode_core` is the basis of the popular `rqrcode` gem [https://github.com/whomwah/rqrcode]. This gem allows you to generate different renderings of your QR code, including `png`, `svg` and `ansi`.
16
+ `rqrcode_core` is the basis of the popular `rqrcode` gem [https://github.com/whomwah/rqrcode]. This gem allows you to generate different renderings of your QR Code, including `png`, `svg` and `ansi`.
16
17
 
17
18
  ## Installation
18
19
 
@@ -47,13 +48,26 @@ x xxx x xxxxx x xx x xxx x
47
48
  ... etc
48
49
  ```
49
50
 
51
+ ## Multiple Encoding Support
52
+
53
+ ```ruby
54
+ $ require "rqrcode_core"
55
+ $ qr = RQRCodeCore::QRCode.new([
56
+ {data: "byteencoded", mode: :byte_8bit},
57
+ {data: "A1" * 100, mode: :alphanumeric},
58
+ {data: "1" * 500, mode: :number}
59
+ ])
60
+ ```
61
+
62
+ This will create a QR Code with byte encoded, alphanumeric and number segments. Any combination of encodings/segments will work provided it fits within size limits.
63
+
50
64
  ## Doing your own rendering
51
65
 
52
66
  ```ruby
53
67
  require "rqrcode_core"
54
68
 
55
69
  qr = RQRCodeCore::QRCode.new("https://kyan.com")
56
- qr.rows.each do |row|
70
+ qr.modules.each do |row|
57
71
  row.each do |col|
58
72
  print col ? "#" : " "
59
73
  end
@@ -64,12 +78,14 @@ end
64
78
 
65
79
  ### Options
66
80
 
67
- The library expects a string to be parsed in, other args are optional.
81
+ The library expects a string or array (for multiple encodings) to be parsed in, other args are optional.
68
82
 
69
83
  ```
70
- string - the string you wish to encode
84
+ data - the string or array you wish to encode
85
+
86
+ size - the size (integer) of the QR Code (defaults to smallest size needed to encode the string)
71
87
 
72
- size - the size (integer) of the qrcode (defaults to smallest size needed to encode the string)
88
+ max_size - the max_size (Integer) of the QR Code (default RQRCodeCore::QRUtil.max_size)
73
89
 
74
90
  level - the error correction level, can be:
75
91
  * Level :l 7% of code can be restored
@@ -77,17 +93,16 @@ level - the error correction level, can be:
77
93
  * Level :q 25% of code can be restored
78
94
  * Level :h 30% of code can be restored (default :h)
79
95
 
80
- mode - the mode of the qrcode (defaults to alphanumeric or byte_8bit, depending on the input data):
96
+ mode - the mode of the QR Code (defaults to alphanumeric or byte_8bit, depending on the input data, only used when data is a string):
81
97
  * :number
82
98
  * :alphanumeric
83
99
  * :byte_8bit
84
- * :kanji
85
100
  ```
86
101
 
87
102
  #### Example
88
103
 
89
104
  ```ruby
90
- RQRCodeCore::QRCode.new("http://kyan.com", size: 1, level: :m, mode: :alphanumeric)
105
+ RQRCodeCore::QRCode.new("http://kyan.com", size: 2, level: :m, mode: :byte_8bit)
91
106
  ```
92
107
 
93
108
  ## Development
@@ -117,6 +132,10 @@ $ rake standard # check
117
132
  $ rake standard:fix # fix
118
133
  ```
119
134
 
135
+ ## Experimental
136
+
137
+ On 64 bit systems when generating lots of QR Codes the lib will consume more memory than on a 32 bit systems during the internal "right shift zero fill" steps (this is expected). In tests though, it's shown that by forcing the lib to think you're on a 32 systems greatly reduces the memory footprint. This could of course have undesired consequences too! but if you're happy to try, you can use the `RQRCODE_CORE_ARCH_BITS` ENV to make this change. e.g `RQRCODE_CORE_ARCH_BITS=32`.
138
+
120
139
  ## Contributing
121
140
 
122
141
  Bug reports and pull requests are welcome on GitHub at https://github.com/whomwah/rqrcode_core.
@@ -15,12 +15,12 @@ module RQRCodeCore
15
15
 
16
16
  def get(index)
17
17
  buf_index = (index / 8).floor
18
- ((QRUtil.rszf(@buffer[buf_index], 7 - index % 8)) & 1) == 1
18
+ (QRUtil.rszf(@buffer[buf_index], 7 - index % 8) & 1) == 1
19
19
  end
20
20
 
21
21
  def put(num, length)
22
22
  (0...length).each do |i|
23
- put_bit(((QRUtil.rszf(num, length - i - 1)) & 1) == 1)
23
+ put_bit((QRUtil.rszf(num, length - i - 1) & 1) == 1)
24
24
  end
25
25
  end
26
26
 
@@ -2,15 +2,16 @@
2
2
 
3
3
  module RQRCodeCore
4
4
  QRMODE = {
5
- mode_number: 1 << 0,
6
- mode_alpha_numk: 1 << 1,
7
- mode_8bit_byte: 1 << 2
5
+ mode_number: 1 << 0, # 1 (binary: 0001)
6
+ mode_alpha_numk: 1 << 1, # 2 (binary: 0010)
7
+ mode_8bit_byte: 1 << 2 # 4 (binary: 0100)
8
8
  }.freeze
9
9
 
10
10
  QRMODE_NAME = {
11
11
  number: :mode_number,
12
12
  alphanumeric: :mode_alpha_numk,
13
- byte_8bit: :mode_8bit_byte
13
+ byte_8bit: :mode_8bit_byte,
14
+ multi: :mode_multi
14
15
  }.freeze
15
16
 
16
17
  QRERRORCORRECTLEVEL = {
@@ -49,89 +50,19 @@ module RQRCodeCore
49
50
  # http://web.archive.org/web/20110710094955/http://www.denso-wave.com/qrcode/vertable2-e.html
50
51
  # http://web.archive.org/web/20110710094955/http://www.denso-wave.com/qrcode/vertable3-e.html
51
52
  # http://web.archive.org/web/20110710094955/http://www.denso-wave.com/qrcode/vertable4-e.html
52
- # Each array contains levels max chars from level 1 to level 40
53
- QRMAXDIGITS = {
54
- l: {
55
- mode_number: [
56
- 41, 77, 127, 187, 255, 322, 370, 461, 552, 652, 772,
57
- 883, 1022, 1101, 1250, 1408, 1548, 1725, 1903, 2061,
58
- 2232, 2409, 2620, 2812, 3057, 3283, 3514, 3669, 3909, 4158,
59
- 4417, 4686, 4965, 5253, 5529, 5836, 6153, 6479, 6743, 7089
60
- ],
61
- mode_alpha_numk: [
62
- 25, 47, 77, 114, 154, 195, 224, 279, 335, 395,
63
- 468, 535, 619, 667, 758, 854, 938, 1046, 1153, 1249,
64
- 1352, 1460, 1588, 1704, 1853, 1990, 2132, 2223, 2369, 2520,
65
- 2677, 2840, 3009, 3183, 3351, 3537, 3729, 3927, 4087, 4296
66
- ],
67
- mode_8bit_byte: [
68
- 17, 32, 53, 78, 106, 134, 154, 192, 230, 271,
69
- 321, 367, 425, 458, 520, 586, 644, 718, 792, 858,
70
- 929, 1003, 1091, 1171, 1273, 1367, 1465, 1528, 1628, 1732,
71
- 1840, 1952, 2068, 2188, 2303, 2431, 2563, 2699, 2809, 2953
72
- ]
73
- },
74
- m: {
75
- mode_number: [
76
- 34, 63, 101, 149, 202, 255, 293, 365, 432, 513,
77
- 604, 691, 796, 871, 991, 1082, 1212, 1346, 1500, 1600,
78
- 1708, 1872, 2059, 2188, 2395, 2544, 2701, 2857, 3035, 3289,
79
- 3486, 3693, 3909, 4134, 4343, 4588, 4775, 5039, 5313, 5596
80
- ],
81
- mode_alpha_numk: [
82
- 20, 38, 61, 90, 122, 154, 178, 221, 262, 311,
83
- 366, 419, 483, 528, 600, 656, 734, 816, 909, 970,
84
- 1035, 1134, 1248, 1326, 1451, 1542, 1637, 1732, 1839, 1994,
85
- 2113, 2238, 2369, 2506, 2632, 2780, 2894, 3054, 3220, 3391
86
- ],
87
- mode_8bit_byte: [
88
- 14, 26, 42, 62, 84, 106, 122, 152, 180, 213,
89
- 251, 287, 331, 362, 412, 450, 504, 560, 624, 666,
90
- 711, 779, 857, 911, 997, 1059, 1125, 1190, 1264, 1370,
91
- 1452, 1538, 1628, 1722, 1809, 1911, 1989, 2099, 2213, 2331
92
- ]
93
- },
94
- q: {
95
- mode_number: [
96
- 27, 48, 77, 111, 144, 178, 207, 259, 312, 364,
97
- 427, 489, 580, 621, 703, 775, 876, 948, 1063, 1159,
98
- 1224, 1358, 1468, 1588, 1718, 1804, 1933, 2085, 2181, 2358,
99
- 2473, 2670, 2805, 2949, 3081, 3244, 3417, 3599, 3791, 3993
100
- ],
101
- mode_alpha_numk: [
102
- 16, 29, 47, 67, 87, 108, 125, 157, 189, 221,
103
- 259, 296, 352, 376, 426, 470, 531, 574, 644, 702,
104
- 742, 823, 890, 963, 1041, 1094, 1172, 1263, 1322, 1429,
105
- 1499, 1618, 1700, 1787, 1867, 1966, 2071, 2181, 2298, 2420
106
- ],
107
- mode_8bit_byte: [
108
- 11, 20, 32, 46, 60, 74, 86, 108, 130, 151,
109
- 177, 203, 241, 258, 292, 22, 364, 394, 442, 482,
110
- 509, 565, 611, 661, 715, 751, 805, 868, 908, 982,
111
- 1030, 1112, 1168, 1228, 1283, 1351, 1423, 1499, 1579, 1663
112
- ]
113
- },
114
- h: {
115
- mode_number: [
116
- 17, 34, 58, 82, 106, 139, 154, 202, 235, 288, 331, 374, 427, 468, 530, 602, 674,
117
- 331, 374, 427, 468, 530, 602, 674, 746, 813, 919,
118
- 969, 1056, 1108, 1228, 1286, 1425, 1501, 1581, 1677, 1782,
119
- 1897, 2022, 2157, 2301, 2361, 2524, 2625, 2735, 2927, 3057
120
-
121
- ],
122
- mode_alpha_numk: [
123
- 10, 20, 35, 50, 64, 84, 93, 122, 143, 174, 200,
124
- 200, 227, 259, 283, 321, 365, 408, 452, 493, 557,
125
- 587, 640, 672, 744, 779, 864, 910, 958, 1016, 1080,
126
- 1150, 1226, 1307, 1394, 1431, 1530, 1591, 1658, 1774, 1852
127
- ],
128
- mode_8bit_byte: [
129
- 7, 14, 24, 34, 44, 58, 64, 84, 98, 119,
130
- 137, 155, 177, 194, 220, 250, 280, 310, 338, 382,
131
- 403, 439, 461, 511, 535, 593, 625, 658, 698, 742,
132
- 790, 842, 898, 958, 983, 1051, 1093, 1139, 1219, 1273
133
- ]
134
- }
53
+ QRMAXBITS = {
54
+ l: [152, 272, 440, 640, 864, 1088, 1248, 1552, 1856, 2192, 2592, 2960, 3424, 3688, 4184,
55
+ 4712, 5176, 5768, 6360, 6888, 7456, 8048, 8752, 9392, 10_208, 10_960, 11_744, 12_248,
56
+ 13_048, 13_880, 14_744, 15_640, 16_568, 17_528, 18_448, 19_472, 20_528, 21_616, 22_496, 23_648],
57
+ m: [128, 224, 352, 512, 688, 864, 992, 1232, 1456, 1728, 2032, 2320, 2672, 2920, 3320, 3624,
58
+ 4056, 4504, 5016, 5352, 5712, 6256, 6880, 7312, 8000, 8496, 9024, 9544, 10_136, 10_984,
59
+ 11_640, 12_328, 13_048, 13_800, 14_496, 15_312, 15_936, 16_816, 17_728, 18_672],
60
+ q: [104, 176, 272, 384, 496, 608, 704, 880, 1056, 1232, 1440, 1648, 1952, 2088, 2360, 2600, 2936,
61
+ 3176, 3560, 3880, 4096, 4544, 4912, 5312, 5744, 6032, 6464, 6968, 7288, 7880, 8264, 8920, 9368,
62
+ 9848, 10288, 10832, 11408, 12016, 12656, 13328],
63
+ h: [72, 128, 208, 288, 368, 480, 528, 688, 800, 976, 1120, 1264, 1440, 1576, 1784,
64
+ 2024, 2264, 2504, 2728, 3080, 3248, 3536, 3712, 4112, 4304, 4768, 5024, 5288, 5608, 5960,
65
+ 6344, 6760, 7208, 7688, 7888, 8432, 8768, 9136, 9776, 10_208]
135
66
  }.freeze
136
67
 
137
68
  # StandardErrors
@@ -152,69 +83,58 @@ module RQRCodeCore
152
83
  class QRCode
153
84
  attr_reader :modules, :module_count, :version
154
85
 
155
- # Expects a string to be parsed in, other args are optional
86
+ # Expects a string or array (for multi-segment encoding) to be parsed in, other args are optional
156
87
  #
157
- # # string - the string you wish to encode
158
- # # size - the size (Integer) of the qrcode (defaults to smallest size needed to encode the string)
159
- # # level - the error correction level, can be:
88
+ # # data - the string, QRSegment or array of Hashes (with data:, mode: keys) you wish to encode
89
+ # # size - the size (Integer) of the QR Code (defaults to smallest size needed to encode the data)
90
+ # # max_size - the max_size (Integer) of the QR Code (default RQRCodeCore::QRUtil.max_size)
91
+ # # level - the error correction level, can be:
160
92
  # * Level :l 7% of code can be restored
161
93
  # * Level :m 15% of code can be restored
162
94
  # * Level :q 25% of code can be restored
163
95
  # * Level :h 30% of code can be restored (default :h)
164
- # # mode - the mode of the qrcode (defaults to alphanumeric or byte_8bit, depending on the input data):
96
+ # # mode - the mode of the QR Code (defaults to alphanumeric or byte_8bit, depending on the input data, only used when data is a string):
165
97
  # * :number
166
98
  # * :alphanumeric
167
99
  # * :byte_8bit
168
- # * :kanji
169
100
  #
170
101
  # qr = RQRCodeCore::QRCode.new('hello world', size: 1, level: :m, mode: :alphanumeric)
171
- #
172
-
173
- def initialize(string, *args)
174
- if !string.is_a? String
175
- raise QRCodeArgumentError, "The passed data is #{string.class}, not String"
176
- end
102
+ # segment_qr = QRCodeCore::QRCode.new({ data: 'foo', mode: :byte_8bit })
103
+ # multi_qr = RQRCodeCore::QRCode.new([{ data: 'foo', mode: :byte_8bit }, { data: 'bar1', mode: :alphanumeric }])
177
104
 
105
+ def initialize(data, *args)
178
106
  options = extract_options!(args)
179
- level = (options[:level] || :h).to_sym
180
107
 
181
- if !QRERRORCORRECTLEVEL.has_key?(level)
182
- raise QRCodeArgumentError, "Unknown error correction level `#{level.inspect}`"
108
+ level = (options[:level] || :h).to_sym
109
+ max_size = options[:max_size] || QRUtil.max_size
110
+
111
+ @data = case data
112
+ when String
113
+ QRSegment.new(data: data, mode: options[:mode])
114
+ when Array
115
+ raise QRCodeArgumentError, "Array must contain Hashes with :data and :mode keys" unless data.all? { |seg| seg.is_a?(Hash) && %i[data mode].all? { |s| seg.key? s } }
116
+ data.map { |seg| QRSegment.new(**seg) }
117
+ when QRSegment
118
+ data
119
+ else
120
+ raise QRCodeArgumentError, "data must be a String, QRSegment, or an Array"
183
121
  end
122
+ @error_correct_level = QRERRORCORRECTLEVEL[level]
184
123
 
185
- @data = string
186
-
187
- mode = QRMODE_NAME[(options[:mode] || "").to_sym]
188
- # If mode is not explicitely given choose mode according to data type
189
- mode ||= if RQRCodeCore::QRNumeric.valid_data?(@data)
190
- QRMODE_NAME[:number]
191
- elsif QRAlphanumeric.valid_data?(@data)
192
- QRMODE_NAME[:alphanumeric]
193
- else
194
- QRMODE_NAME[:byte_8bit]
124
+ unless @error_correct_level
125
+ raise QRCodeArgumentError, "Unknown error correction level `#{level.inspect}`"
195
126
  end
196
127
 
197
- max_size_array = QRMAXDIGITS[level][mode]
198
- size = options[:size] || smallest_size_for(string, max_size_array)
128
+ size = options[:size] || minimum_version(limit: max_size)
199
129
 
200
- if size > QRUtil.max_size
130
+ if size > max_size
201
131
  raise QRCodeArgumentError, "Given size greater than maximum possible size of #{QRUtil.max_size}"
202
132
  end
203
133
 
204
- @error_correct_level = QRERRORCORRECTLEVEL[level]
205
134
  @version = size
206
135
  @module_count = @version * 4 + QRPOSITIONPATTERNLENGTH
207
136
  @modules = Array.new(@module_count)
208
- @data_list =
209
- case mode
210
- when :mode_number
211
- QRNumeric.new(@data)
212
- when :mode_alpha_numk
213
- QRAlphanumeric.new(@data)
214
- else
215
- QR8bitByte.new(@data)
216
- end
217
-
137
+ @data_list = multi_segment? ? QRMulti.new(@data) : @data.writer
218
138
  @data_cache = nil
219
139
  make
220
140
  end
@@ -293,6 +213,11 @@ module RQRCodeCore
293
213
  QRERRORCORRECTLEVEL.invert[@error_correct_level]
294
214
  end
295
215
 
216
+ # Return true if this QR Code includes multiple encoded segments
217
+ def multi_segment?
218
+ @data.is_a?(Array)
219
+ end
220
+
296
221
  # Return a symbol in QRMODE.keys for current mode used
297
222
  def mode
298
223
  case @data_list
@@ -307,14 +232,14 @@ module RQRCodeCore
307
232
 
308
233
  protected
309
234
 
310
- def make #:nodoc:
235
+ def make # :nodoc:
311
236
  prepare_common_patterns
312
237
  make_impl(false, get_best_mask_pattern)
313
238
  end
314
239
 
315
240
  private
316
241
 
317
- def prepare_common_patterns #:nodoc:
242
+ def prepare_common_patterns # :nodoc:
318
243
  @modules.map! { |row| Array.new(@module_count) }
319
244
 
320
245
  place_position_probe_pattern(0, 0)
@@ -326,7 +251,7 @@ module RQRCodeCore
326
251
  @common_patterns = @modules.map(&:clone)
327
252
  end
328
253
 
329
- def make_impl(test, mask_pattern) #:nodoc:
254
+ def make_impl(test, mask_pattern) # :nodoc:
330
255
  @modules = @common_patterns.map(&:clone)
331
256
 
332
257
  place_format_info(test, mask_pattern)
@@ -341,15 +266,15 @@ module RQRCodeCore
341
266
  map_data(@data_cache, mask_pattern)
342
267
  end
343
268
 
344
- def place_position_probe_pattern(row, col) #:nodoc:
269
+ def place_position_probe_pattern(row, col) # :nodoc:
345
270
  (-1..7).each do |r|
346
271
  next unless (row + r).between?(0, @module_count - 1)
347
272
 
348
273
  (-1..7).each do |c|
349
274
  next unless (col + c).between?(0, @module_count - 1)
350
275
 
351
- is_vert_line = (r.between?(0, 6) && (c == 0 || c == 6))
352
- is_horiz_line = (c.between?(0, 6) && (r == 0 || r == 6))
276
+ is_vert_line = r.between?(0, 6) && (c == 0 || c == 6)
277
+ is_horiz_line = c.between?(0, 6) && (r == 0 || r == 6)
353
278
  is_square = r.between?(2, 4) && c.between?(2, 4)
354
279
 
355
280
  is_part_of_probe = is_vert_line || is_horiz_line || is_square
@@ -358,11 +283,11 @@ module RQRCodeCore
358
283
  end
359
284
  end
360
285
 
361
- def get_best_mask_pattern #:nodoc:
286
+ def get_best_mask_pattern # :nodoc:
362
287
  min_lost_point = 0
363
288
  pattern = 0
364
289
 
365
- (0...8).each do |i|
290
+ 8.times do |i|
366
291
  make_impl(true, i)
367
292
  lost_point = QRUtil.get_lost_points(modules)
368
293
 
@@ -374,13 +299,13 @@ module RQRCodeCore
374
299
  pattern
375
300
  end
376
301
 
377
- def place_timing_pattern #:nodoc:
302
+ def place_timing_pattern # :nodoc:
378
303
  (8...@module_count - 8).each do |i|
379
304
  @modules[i][6] = @modules[6][i] = i % 2 == 0
380
305
  end
381
306
  end
382
307
 
383
- def place_position_adjust_pattern #:nodoc:
308
+ def place_position_adjust_pattern # :nodoc:
384
309
  positions = QRUtil.get_pattern_positions(@version)
385
310
 
386
311
  positions.each do |row|
@@ -389,7 +314,7 @@ module RQRCodeCore
389
314
 
390
315
  (-2..2).each do |r|
391
316
  (-2..2).each do |c|
392
- is_part_of_pattern = (r.abs == 2 || c.abs == 2 || (r == 0 && c == 0))
317
+ is_part_of_pattern = r.abs == 2 || c.abs == 2 || (r == 0 && c == 0)
393
318
  @modules[row + r][col + c] = is_part_of_pattern
394
319
  end
395
320
  end
@@ -397,22 +322,22 @@ module RQRCodeCore
397
322
  end
398
323
  end
399
324
 
400
- def place_version_info(test) #:nodoc:
325
+ def place_version_info(test) # :nodoc:
401
326
  bits = QRUtil.get_bch_version(@version)
402
327
 
403
- (0...18).each do |i|
404
- mod = (!test && ((bits >> i) & 1) == 1)
328
+ 18.times do |i|
329
+ mod = !test && ((bits >> i) & 1) == 1
405
330
  @modules[(i / 3).floor][ i % 3 + @module_count - 8 - 3 ] = mod
406
331
  @modules[i % 3 + @module_count - 8 - 3][ (i / 3).floor ] = mod
407
332
  end
408
333
  end
409
334
 
410
- def place_format_info(test, mask_pattern) #:nodoc:
335
+ def place_format_info(test, mask_pattern) # :nodoc:
411
336
  data = (@error_correct_level << 3 | mask_pattern)
412
337
  bits = QRUtil.get_bch_format_info(data)
413
338
 
414
339
  QRFORMATINFOLENGTH.times do |i|
415
- mod = (!test && ((bits >> i) & 1) == 1)
340
+ mod = !test && ((bits >> i) & 1) == 1
416
341
 
417
342
  # vertical
418
343
  row = if i < 6
@@ -439,7 +364,7 @@ module RQRCodeCore
439
364
  @modules[@module_count - 8][8] = !test
440
365
  end
441
366
 
442
- def map_data(data, mask_pattern) #:nodoc:
367
+ def map_data(data, mask_pattern) # :nodoc:
443
368
  inc = -1
444
369
  row = @module_count - 1
445
370
  bit_index = 7
@@ -449,7 +374,7 @@ module RQRCodeCore
449
374
  col -= 1 if col <= 6
450
375
 
451
376
  loop do
452
- (0...2).each do |c|
377
+ 2.times do |c|
453
378
  if @modules[row][col - c].nil?
454
379
  dark = false
455
380
  if byte_index < data.size && !data[byte_index].nil?
@@ -478,19 +403,24 @@ module RQRCodeCore
478
403
  end
479
404
  end
480
405
 
481
- def smallest_size_for(string, max_size_array) #:nodoc:
482
- l = string.bytesize
483
- ver = max_size_array.index { |i| i >= l }
484
- raise QRCodeRunTimeError, "code length overflow. (#{l} digits > any version capacity)" unless ver
485
- ver + 1
406
+ def minimum_version(limit: QRUtil.max_size, version: 1)
407
+ raise QRCodeRunTimeError, "Data length exceed maximum capacity of version #{limit}" if version > limit
408
+
409
+ max_size_bits = QRMAXBITS[error_correction_level][version - 1]
410
+
411
+ size_bits = multi_segment? ? @data.sum { |seg| seg.size(version) } : @data.size(version)
412
+
413
+ return version if size_bits < max_size_bits
414
+
415
+ minimum_version(limit: limit, version: version + 1)
486
416
  end
487
417
 
488
- def extract_options!(arr) #:nodoc:
418
+ def extract_options!(arr) # :nodoc:
489
419
  arr.last.is_a?(::Hash) ? arr.pop : {}
490
420
  end
491
421
 
492
422
  class << self
493
- def count_max_data_bits(rs_blocks) #:nodoc:
423
+ def count_max_data_bits(rs_blocks) # :nodoc:
494
424
  max_data_bytes = rs_blocks.reduce(0) do |sum, rs_block|
495
425
  sum + rs_block.data_count
496
426
  end
@@ -498,7 +428,7 @@ module RQRCodeCore
498
428
  max_data_bytes * 8
499
429
  end
500
430
 
501
- def create_data(version, error_correct_level, data_list) #:nodoc:
431
+ def create_data(version, error_correct_level, data_list) # :nodoc:
502
432
  rs_blocks = QRRSBlock.get_rs_blocks(version, error_correct_level)
503
433
  max_data_bits = QRCode.count_max_data_bits(rs_blocks)
504
434
  buffer = QRBitBuffer.new(version)
@@ -515,7 +445,7 @@ module RQRCodeCore
515
445
  QRCode.create_bytes(buffer, rs_blocks)
516
446
  end
517
447
 
518
- def create_bytes(buffer, rs_blocks) #:nodoc:
448
+ def create_bytes(buffer, rs_blocks) # :nodoc:
519
449
  offset = 0
520
450
  max_dc_count = 0
521
451
  max_ec_count = 0
@@ -542,7 +472,7 @@ module RQRCodeCore
542
472
  ecdata_block = Array.new(rs_poly.get_length - 1)
543
473
  ecdata_block.size.times do |i|
544
474
  mod_index = i + mod_poly.get_length - ecdata_block.size
545
- ecdata_block[i] = mod_index >= 0 ? mod_poly.get(mod_index) : 0
475
+ ecdata_block[i] = (mod_index >= 0) ? mod_poly.get(mod_index) : 0
546
476
  end
547
477
  ecdata[r] = ecdata_block
548
478
  end
@@ -6,7 +6,7 @@ module RQRCodeCore
6
6
  exp_table = Array.new(256)
7
7
  log_table = Array.new(256)
8
8
 
9
- (0...8).each do |i|
9
+ 8.times do |i|
10
10
  exp_table[i] = 1 << i
11
11
  end
12
12
 
@@ -17,7 +17,7 @@ module RQRCodeCore
17
17
  ^ exp_table[i - 8]
18
18
  end
19
19
 
20
- (0...255).each do |i|
20
+ 255.times do |i|
21
21
  log_table[exp_table[i]] = i
22
22
  end
23
23
 
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RQRCodeCore
4
+ class QRMulti
5
+ def initialize(data)
6
+ @data = data
7
+ end
8
+
9
+ def write(buffer)
10
+ @data.each { |seg| seg.writer.write(buffer) }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RQRCodeCore
4
+ class QRSegment
5
+ attr_reader :data, :mode
6
+
7
+ def initialize(data:, mode: nil)
8
+ @data = data
9
+ @mode = QRMODE_NAME.dig(mode&.to_sym)
10
+
11
+ # If mode is not explicitly found choose mode according to data type
12
+ @mode ||= if RQRCodeCore::QRNumeric.valid_data?(@data)
13
+ QRMODE_NAME[:number]
14
+ elsif QRAlphanumeric.valid_data?(@data)
15
+ QRMODE_NAME[:alphanumeric]
16
+ else
17
+ QRMODE_NAME[:byte_8bit]
18
+ end
19
+ end
20
+
21
+ def size(version)
22
+ 4 + header_size(version) + content_size
23
+ end
24
+
25
+ def header_size(version)
26
+ QRUtil.get_length_in_bits(QRMODE[mode], version)
27
+ end
28
+
29
+ def content_size
30
+ chunk_size, bit_length, extra = case mode
31
+ when :mode_number
32
+ [3, QRNumeric::NUMBER_LENGTH[3], QRNumeric::NUMBER_LENGTH[data_length % 3] || 0]
33
+ when :mode_alpha_numk
34
+ [2, 11, 6]
35
+ when :mode_8bit_byte
36
+ [1, 8, 0]
37
+ end
38
+
39
+ (data_length / chunk_size) * bit_length + (((data_length % chunk_size) == 0) ? 0 : extra)
40
+ end
41
+
42
+ def writer
43
+ case mode
44
+ when :mode_number
45
+ QRNumeric.new(data)
46
+ when :mode_alpha_numk
47
+ QRAlphanumeric.new(data)
48
+ when :mode_multi
49
+ QRMulti.new(data)
50
+ else
51
+ QR8bitByte.new(data)
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def data_length
58
+ data.bytesize
59
+ end
60
+ end
61
+ end
@@ -57,10 +57,17 @@ module RQRCodeCore
57
57
  BITS_FOR_MODE = {
58
58
  QRMODE[:mode_number] => [10, 12, 14],
59
59
  QRMODE[:mode_alpha_numk] => [9, 11, 13],
60
- QRMODE[:mode_8bit_byte] => [8, 16, 16],
61
- QRMODE[:mode_kanji] => [8, 10, 12]
60
+ QRMODE[:mode_8bit_byte] => [8, 16, 16]
62
61
  }.freeze
63
62
 
63
+ # This value is used during the right shift zero fill step. It is
64
+ # auto set to 32 or 64 depending on the arch of your system running.
65
+ # 64 consumes a LOT more memory. In tests it's shown changing it to 32
66
+ # on 64 bit systems greatly reduces the memory footprint. You can use
67
+ # RQRCODE_CORE_ARCH_BITS to make this change but beware it may also
68
+ # have unintended consequences so use at your own risk.
69
+ ARCH_BITS = ENV.fetch("RQRCODE_CORE_ARCH_BITS", nil)&.to_i || 1.size * 8
70
+
64
71
  def self.max_size
65
72
  PATTERN_POSITION_TABLE.count
66
73
  end
@@ -74,8 +81,8 @@ module RQRCodeCore
74
81
  end
75
82
 
76
83
  def self.rszf(num, count)
77
- # zero fill right shift
78
- (num >> count) & ((2**((num.size * 8) - count)) - 1)
84
+ # right shift zero fill
85
+ (num >> count) & ((1 << (ARCH_BITS - count)) - 1)
79
86
  end
80
87
 
81
88
  def self.get_bch_version(data)
@@ -247,7 +254,8 @@ module RQRCodeCore
247
254
  sum + col.count(true)
248
255
  end
249
256
 
250
- ratio = dark_count / (modules.size * modules.size)
257
+ # Convert to float to prevent integer division
258
+ ratio = dark_count.to_f / (modules.size * modules.size)
251
259
  ratio_delta = (100 * ratio - 50).abs / 5
252
260
 
253
261
  ratio_delta * DEMERIT_POINTS_4
@@ -5,7 +5,9 @@ require "rqrcode_core/qrcode/qr_alphanumeric"
5
5
  require "rqrcode_core/qrcode/qr_bit_buffer"
6
6
  require "rqrcode_core/qrcode/qr_code"
7
7
  require "rqrcode_core/qrcode/qr_math"
8
+ require "rqrcode_core/qrcode/qr_multi"
8
9
  require "rqrcode_core/qrcode/qr_numeric"
9
10
  require "rqrcode_core/qrcode/qr_polynomial"
10
11
  require "rqrcode_core/qrcode/qr_rs_block"
12
+ require "rqrcode_core/qrcode/qr_segment"
11
13
  require "rqrcode_core/qrcode/qr_util"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RQRCodeCore
4
- VERSION = "1.1.0"
4
+ VERSION = "2.0.0"
5
5
  end
data/rqrcode_core.gemspec CHANGED
@@ -16,6 +16,10 @@ Gem::Specification.new do |spec|
16
16
  EOF
17
17
  spec.homepage = "https://github.com/whomwah/rqrcode_core"
18
18
  spec.license = "MIT"
19
+ spec.metadata = {
20
+ "bug_tracker_uri" => "https://github.com/whomwah/rqrcode_core/issues",
21
+ "changelog_uri" => "https://github.com/whomwah/rqrcode_core/blob/main/CHANGELOG.md"
22
+ }
19
23
 
20
24
  spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
21
25
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
@@ -24,9 +28,9 @@ Gem::Specification.new do |spec|
24
28
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
29
  spec.require_paths = ["lib"]
26
30
 
27
- spec.required_ruby_version = ">= 2.3"
31
+ spec.required_ruby_version = "~> 3.0"
28
32
  spec.add_development_dependency "bundler", "~> 2.0"
29
33
  spec.add_development_dependency "rake", "~> 13.0"
30
34
  spec.add_development_dependency "minitest", "~> 5.0"
31
- spec.add_development_dependency "standardrb", "~> 1.0"
35
+ spec.add_development_dependency "standard", "~> 1.41"
32
36
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rqrcode_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Duncan Robertson
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-07-01 00:00:00.000000000 Z
11
+ date: 2025-04-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -53,19 +53,19 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '5.0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: standardrb
56
+ name: standard
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '1.0'
61
+ version: '1.41'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '1.0'
68
+ version: '1.41'
69
69
  description: |
70
70
  rqrcode_core is a Ruby library for encoding QR Codes. The simple
71
71
  interface (with no runtime dependencies) allows you to create QR Code data structures.
@@ -75,6 +75,7 @@ executables: []
75
75
  extensions: []
76
76
  extra_rdoc_files: []
77
77
  files:
78
+ - ".github/FUNDING.yml"
78
79
  - ".github/workflows/ruby.yml"
79
80
  - ".gitignore"
80
81
  - CHANGELOG.md
@@ -92,33 +93,37 @@ files:
92
93
  - lib/rqrcode_core/qrcode/qr_bit_buffer.rb
93
94
  - lib/rqrcode_core/qrcode/qr_code.rb
94
95
  - lib/rqrcode_core/qrcode/qr_math.rb
96
+ - lib/rqrcode_core/qrcode/qr_multi.rb
95
97
  - lib/rqrcode_core/qrcode/qr_numeric.rb
96
98
  - lib/rqrcode_core/qrcode/qr_polynomial.rb
97
99
  - lib/rqrcode_core/qrcode/qr_rs_block.rb
100
+ - lib/rqrcode_core/qrcode/qr_segment.rb
98
101
  - lib/rqrcode_core/qrcode/qr_util.rb
99
102
  - lib/rqrcode_core/version.rb
100
103
  - rqrcode_core.gemspec
101
104
  homepage: https://github.com/whomwah/rqrcode_core
102
105
  licenses:
103
106
  - MIT
104
- metadata: {}
105
- post_install_message:
107
+ metadata:
108
+ bug_tracker_uri: https://github.com/whomwah/rqrcode_core/issues
109
+ changelog_uri: https://github.com/whomwah/rqrcode_core/blob/main/CHANGELOG.md
110
+ post_install_message:
106
111
  rdoc_options: []
107
112
  require_paths:
108
113
  - lib
109
114
  required_ruby_version: !ruby/object:Gem::Requirement
110
115
  requirements:
111
- - - ">="
116
+ - - "~>"
112
117
  - !ruby/object:Gem::Version
113
- version: '2.3'
118
+ version: '3.0'
114
119
  required_rubygems_version: !ruby/object:Gem::Requirement
115
120
  requirements:
116
121
  - - ">="
117
122
  - !ruby/object:Gem::Version
118
123
  version: '0'
119
124
  requirements: []
120
- rubygems_version: 3.2.15
121
- signing_key:
125
+ rubygems_version: 3.5.11
126
+ signing_key:
122
127
  specification_version: 4
123
128
  summary: A library to encode QR Codes
124
129
  test_files: []