Skip to content

Commit 0b61ade

Browse files
authored
Merge pull request #27 from OUDON/develop
release: v0.3.0
2 parents 7aad53e + ea7b4eb commit 0b61ade

23 files changed

+1201
-117
lines changed

Makefile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
.PHONY: install
2+
install:
3+
pip install -e ".[dev]"
4+
5+
.PHONY: uninstall
6+
uninstall:
7+
yes Y | pip uninstall rmqrcode
8+
9+
.PHONY: test
10+
test:
11+
python -m pytest
12+
113
.PHONY: lint
214
lint:
315
flake8 src

README.md

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# An rMQR Code Generator
1+
# Rectangular Micro QR Code (rMQR Code) Generator
22
![reop-url](https://user-images.githubusercontent.com/14174940/172978619-accbf9d0-9dd8-4b19-b47e-ad139a68dcc9.png)
33

44

5-
The rMQR Code is a rectangular two-dimensional barcode. This package can generate an rMQR Code image. This is implemented based on [ISO/IEC 23941: Rectangular Micro QR Code (rMQR) bar code symbology specification](https://www.iso.org/standard/77404.html).
5+
The rMQR Code is a rectangular two-dimensional barcode. This is easy to print in narrow space compared to conventional QR Code. This package can generate an rMQR Code image. This is implemented based on [ISO/IEC 23941: Rectangular Micro QR Code (rMQR) bar code symbology specification](https://www.iso.org/standard/77404.html).
66

77
[![pytest](https://github.com/OUDON/rmqrcode-python/actions/workflows/python-app.yml/badge.svg?branch=main)](https://github.com/OUDON/rmqrcode-python/actions/workflows/python-app.yml)
88
![PyPI](https://img.shields.io/pypi/v/rmqrcode?color=blue)
@@ -48,7 +48,7 @@ optional arguments:
4848
Strategy how to determine rMQR Code size.
4949
```
5050

51-
### Generate rMQR Code
51+
### Generate rMQR Code in scripts
5252
Alternatively, you can also use in python scripts:
5353
```py
5454
from rmqrcode import rMQR
@@ -71,7 +71,7 @@ The `fit_strategy` parameter is enum value of `rmqrcode.FitStrategy` to specify
7171
- **`FitStrategy.MINIMIZE_HEIGHT`**: Try to minimize height.
7272
- **`FitStrategy.BALANCED`**: Try to keep balance of width and height.
7373

74-
Here is an example of images genereated by each fit strategies for data `Test test test`:
74+
Here is an example of images generated by each fit strategies for data `Test test test`:
7575
![Example of fit strategies](https://user-images.githubusercontent.com/14174940/175759120-7fb5ec71-c258-4646-9b91-6865b3eeac3f.png)
7676

7777
### Save as image
@@ -88,8 +88,8 @@ image.save("my_qr.png")
8888
### Select rMQR Code size manually
8989
To select rMQR Code size manually, use `rMQR()` constructor.
9090
```py
91+
from rmqrcode import rMQR, ErrorCorrectionLevel
9192
qr = rMQR('R11x139', ErrorCorrectionLevel.H)
92-
qr.make("https://oudon.xyz")
9393
```
9494

9595
`R11x139` means 11 rows and 139 columns. The following table shows available combinations.
@@ -103,22 +103,43 @@ qr.make("https://oudon.xyz")
103103
|R15|||||||
104104
|R17|||||||
105105

106+
### Encoding Modes and Segments
106107

107-
## 🛠️ Under the Hood
108-
### Encoding modes
108+
The rMQR Code has the four encoding modes Numeric, Alphanumeric, Byte and Kanji to convert data efficiently. We can select encoding mode for each data segment separately.
109+
The following example shows how to encode data "123Abc". The first segment is for "123" in the Numeric mode. The second segment is for "Abc" in the Byte mode.
110+
We can select an encoding mode by passing the `encoder_class` argument to the `rMQR#add_segment` method. In this example, the length of bits after encoding is 47 in the case combined with the Numeric mode and the Byte mode, which is shorter than 56 in the Byte mode only.
109111

110-
The rMQR Code has the four encoding modes Numeric, Alphanumeric, Byte and Kanji to convert data efficiently. However, this package supoprts only Byte mode currently.
112+
```py
113+
from rmqrcode import rMQR, ErrorCorrectionLevel, encoder
114+
qr = rMQR('R7x43', ErrorCorrectionLevel.M)
115+
qr.add_segment("123", encoder_class=encoder.NumericEncoder)
116+
qr.add_segment("Abc", encoder_class=encoder.ByteEncoder)
117+
qr.make()
118+
```
119+
120+
The value for `encoder_class` is listed in the below table.
121+
122+
|Mode|Value of encoder_class|Characters|
123+
|-|-|-|
124+
|Numeric|NumericEncoder|0-9|
125+
|Alphanumeric|AlphanumericEncoder|0-9 A-Z \s $ % * + - . / :|
126+
|Byte|ByteEncoder|Any|
127+
|Kanji|KanjiEncoder|from 0x8140 to 0x9FFC, from 0xE040 to 0xEBBF in Shift JIS value|
128+
129+
### Optimal Segmentation
130+
The `rMQR.fit` method mentioned above computes the optimal segmentation.
131+
For example, the data "123Abc" is divided into the following two segments.
111132

112-
|Mode|Supported?|
113-
|-|:-:|
114-
|Numeric| |
115-
|Alphanumeric||
116-
|Byte||
117-
|Kanji||
133+
|Segment No.|Data|Encoding Mode|
134+
|-|-|-|
135+
|Segment1|123|Numeric|
136+
|Segment2|Abc|Byte|
118137

138+
In the case of other segmentation like "123A bc", the length of the bit string after
139+
encoding will be longer than the above optimal case.
119140

120-
## 🤝 Contiributing
121-
Any suggestions are welcome! If you are interesting in contiributing, please read [CONTRIBUTING](https://github.com/OUDON/rmqrcode-python/blob/develop/CONTRIBUTING.md).
141+
## 🤝 Contributing
142+
Any suggestions are welcome! If you are interesting in contributing, please read [CONTRIBUTING](https://github.com/OUDON/rmqrcode-python/blob/develop/CONTRIBUTING.md).
122143

123144

124145
## 📚 References

example.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from rmqrcode import ErrorCorrectionLevel
33
from rmqrcode import QRImage
44
from rmqrcode import FitStrategy
5+
from rmqrcode import encoder
56

67
import logging
78

@@ -24,9 +25,11 @@ def main():
2425
print(qr)
2526

2627
# Determine rMQR version manually
27-
# version = 'R13x99'
28+
# version = 'R7x43'
2829
# qr = rMQR(version, error_correction_level)
29-
# qr.make(data)
30+
# qr.add_segment("123", encoder_class=encoder.NumericEncoder)
31+
# qr.add_segment("Abc", encoder_class=encoder.ByteEncoder)
32+
# qr.make()
3033
# print(qr)
3134

3235
# Save as png

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = rmqrcode
3-
version = 0.2.0
3+
version = 0.3.0
44
author = Takahiro Tomita
55
author_email = ttp8101@gmail.com
66
description = An rMQR Code Generetor

src/rmqrcode/__init__.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
1+
from . import encoder
12
from .format.error_correction_level import ErrorCorrectionLevel
23
from .qr_image import QRImage
3-
from .rmqrcode import DataTooLongError, FitStrategy, IllegalVersionError, rMQR
4+
from .rmqrcode import (
5+
DataTooLongError,
6+
FitStrategy,
7+
IllegalVersionError,
8+
NoSegmentError,
9+
rMQR,
10+
)
411

5-
__all__ = ("rMQR", "DataTooLongError", "FitStrategy", "IllegalVersionError", "QRImage", "ErrorCorrectionLevel")
12+
__all__ = (
13+
"rMQR",
14+
"DataTooLongError",
15+
"FitStrategy",
16+
"IllegalVersionError",
17+
"NoSegmentError",
18+
"QRImage",
19+
"ErrorCorrectionLevel",
20+
"encoder",
21+
)

src/rmqrcode/console.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ def _make_qr(data, ecc, version, fit_strategy):
2525
qr = rMQR(version, ecc)
2626
except IllegalVersionError:
2727
_show_error_and_exit("Error: Illegal version.")
28-
qr.make(data)
29-
28+
qr.add_segment(data)
29+
qr.make()
3030
return qr
3131

3232

src/rmqrcode/encoder/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from .alphanumeric_encoder import AlphanumericEncoder
2+
from .byte_encoder import ByteEncoder
3+
from .encoder_base import IllegalCharacterError
4+
from .kanji_encoder import KanjiEncoder
5+
from .numeric_encoder import NumericEncoder
6+
7+
__all__ = ("ByteEncoder", "NumericEncoder", "IllegalCharacterError", "AlphanumericEncoder", "KanjiEncoder")
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import re
2+
3+
from .encoder_base import EncoderBase
4+
5+
6+
class AlphanumericEncoder(EncoderBase):
7+
CHARACTER_MAP = {
8+
"0": 0,
9+
"1": 1,
10+
"2": 2,
11+
"3": 3,
12+
"4": 4,
13+
"5": 5,
14+
"6": 6,
15+
"7": 7,
16+
"8": 8,
17+
"9": 9,
18+
"A": 10,
19+
"B": 11,
20+
"C": 12,
21+
"D": 13,
22+
"E": 14,
23+
"F": 15,
24+
"G": 16,
25+
"H": 17,
26+
"I": 18,
27+
"J": 19,
28+
"K": 20,
29+
"L": 21,
30+
"M": 22,
31+
"N": 23,
32+
"O": 24,
33+
"P": 25,
34+
"Q": 26,
35+
"R": 27,
36+
"S": 28,
37+
"T": 29,
38+
"U": 30,
39+
"V": 31,
40+
"W": 32,
41+
"X": 33,
42+
"Y": 34,
43+
"Z": 35,
44+
" ": 36,
45+
"$": 37,
46+
"%": 38,
47+
"*": 39,
48+
"+": 40,
49+
"-": 41,
50+
".": 42,
51+
"/": 43,
52+
":": 44,
53+
}
54+
55+
@classmethod
56+
def mode_indicator(cls):
57+
return "010"
58+
59+
@classmethod
60+
def _encoded_bits(cls, data):
61+
res = ""
62+
data_grouped = cls._group_by_2characters(data)
63+
for s in data_grouped:
64+
if len(s) == 2:
65+
value = cls.CHARACTER_MAP[s[0]] * 45 + cls.CHARACTER_MAP[s[1]]
66+
res += bin(value)[2:].zfill(11)
67+
elif len(s) == 1:
68+
value = cls.CHARACTER_MAP[s[0]]
69+
res += bin(value)[2:].zfill(6)
70+
return res
71+
72+
@classmethod
73+
def _group_by_2characters(cls, data):
74+
res = []
75+
while data != "":
76+
res.append(data[:2])
77+
data = data[2:]
78+
return res
79+
80+
@classmethod
81+
def length(cls, data, character_count_indicator_length):
82+
return (
83+
len(cls.mode_indicator()) + character_count_indicator_length + 11 * (len(data) // 2) + 6 * (len(data) % 2)
84+
)
85+
86+
@classmethod
87+
def characters_num(cls, data):
88+
return len(data)
89+
90+
@classmethod
91+
def is_valid_characters(cls, data):
92+
return bool(re.match(r"^[0-9A-Z\s\$\%\*\+\-\.\/\:]*$", data))

src/rmqrcode/encoder/byte_encoder.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
1-
class ByteEncoder:
2-
MODE_INDICATOR = "011"
1+
from .encoder_base import EncoderBase
32

4-
@staticmethod
5-
def _encoded_bits(s):
3+
4+
class ByteEncoder(EncoderBase):
5+
@classmethod
6+
def mode_indicator(cls):
7+
return "011"
8+
9+
@classmethod
10+
def _encoded_bits(cls, s):
611
res = ""
712
encoded = s.encode("utf-8")
813
for byte in encoded:
914
res += bin(byte)[2:].zfill(8)
1015
return res
1116

12-
@staticmethod
13-
def encode(data, character_count_length):
14-
res = ByteEncoder.MODE_INDICATOR
15-
res += bin(len(data))[2:].zfill(character_count_length)
16-
res += ByteEncoder._encoded_bits(data)
17-
return res
17+
@classmethod
18+
def length(cls, data, character_count_indicator_length):
19+
return len(cls.mode_indicator()) + character_count_indicator_length + 8 * len(data.encode("utf-8"))
1820

19-
@staticmethod
20-
def length(data):
21+
@classmethod
22+
def characters_num(cls, data):
2123
return len(data.encode("utf-8"))
24+
25+
@classmethod
26+
def is_valid_characters(cls, data):
27+
return True # Any characters can encode in the Byte Mode

0 commit comments

Comments
 (0)