Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
3855bf3
doc: Update README.md
OUDON Aug 11, 2022
a098a44
doc: Update README.md
OUDON Aug 11, 2022
901d5e0
feat: Use EnocderBase
OUDON Aug 14, 2022
9481e98
doc: Update README.md
OUDON Aug 14, 2022
ccc0389
doc: Fix typo
OUDON Aug 14, 2022
67c4dee
Merge branch 'develop' of github.com:OUDON/rmqr-generator into feat/m…
OUDON Aug 14, 2022
e6638e6
doc: Fix typo
OUDON Aug 14, 2022
98354f5
Merge branch 'develop' of github.com:OUDON/rmqr-generator into feat/m…
OUDON Aug 14, 2022
29997ba
feat: Add the length of character_count_length for ByteEncoder
OUDON Aug 14, 2022
2cd0c2b
refactor: Rename from character_count_length to character_count_indic…
OUDON Aug 14, 2022
014c5c9
ci: Add the command to Makefile
OUDON Aug 14, 2022
8a43c91
Merge branch 'develop' into feat/multi-encode-mode
OUDON Aug 14, 2022
18edc75
feat: Implement NumericEncoder
OUDON Aug 14, 2022
ded632a
feat: Raises IllegalCharacterError in NumericalEncoder.encode
OUDON Aug 14, 2022
86ea516
chore: Fix lint
OUDON Aug 14, 2022
bfe9dcc
chore: Run make format
OUDON Aug 14, 2022
ef85bff
chore: Add newline to last line
OUDON Aug 14, 2022
0fbcdf3
feat: Add a method ByteEncoder.is_valid_characters
OUDON Aug 14, 2022
74f87f4
feat: Add the argument `encoder_class` into rMQR.make
OUDON Aug 15, 2022
821b0ce
feat: Add number_of_data_bits to DataCapacities
OUDON Aug 15, 2022
f67eeb3
fix: Fix number_of_data_bits
OUDON Aug 15, 2022
4cc1a39
doc: Update README.md
OUDON Aug 15, 2022
e57325c
feat: Implement AlphanumericEncoder
OUDON Aug 15, 2022
c8d4294
doc: Update README.md
OUDON Aug 15, 2022
a5f62f8
feat: Implement KanjiEncoder
OUDON Aug 16, 2022
09cab6c
doc: Update README.md
OUDON Aug 16, 2022
6c0e963
feat: Add character_count_indicator_length for AlphaNumeric and ByteE…
OUDON Aug 16, 2022
f46a3cc
fix: Fix wrong key
OUDON Aug 16, 2022
97374d4
refactor: Move encoder tests to tests/encoder dir
OUDON Aug 16, 2022
791e3ee
refactor: Move the implement of FooEncoder.encode into BaseEncoder.en…
OUDON Aug 16, 2022
f0e5eb6
refactor: Use regular expression in is_valid_characters
OUDON Aug 17, 2022
431b403
fix: Fix regexp
OUDON Aug 17, 2022
0d797ff
Merge pull request #21 from OUDON/feat/multi-encode-mode
OUDON Aug 17, 2022
d6073f4
doc: Update README.md
OUDON Aug 17, 2022
dbc9311
fix: Add encoder into rmqrcode/__init__.py
OUDON Aug 17, 2022
53299ea
Merge branch 'develop' of github.com:OUDON/rmqr-generator into develop
OUDON Aug 17, 2022
70e132b
doc: Update README.md
OUDON Aug 17, 2022
30fca84
feat: Supports multiple segments
OUDON Aug 18, 2022
201e941
fix: Fix call rMQR#_encode_data twice
OUDON Aug 18, 2022
3a90f03
doc: Add docstrings
OUDON Aug 18, 2022
ec78501
fix: Fix max length check
OUDON Aug 18, 2022
414bb48
doc: Update README.md
OUDON Aug 18, 2022
98ee427
doc: Update README.md
OUDON Aug 18, 2022
4b50b2e
feat: Update example.py
OUDON Aug 18, 2022
4aa0f79
Merge pull request #23 from OUDON/feat/multiple-segment
OUDON Aug 18, 2022
0210a6d
feat: Implement optimize segmentation algorithm
OUDON Aug 19, 2022
118c6bf
fix: Fix incorrect indent
OUDON Aug 19, 2022
8091e0a
feat: Pre-check character limit in _compute_optimize_segmentation()
OUDON Aug 22, 2022
2ff35ed
refactor: Move the implementation of compute_optimize_segmentation to…
OUDON Aug 22, 2022
087ee28
chore: Apply formatter
OUDON Aug 22, 2022
b5f0c23
doc: Add docstrings to SegmentOptimizer
OUDON Aug 22, 2022
134ae67
doc: Update docstrings
OUDON Aug 22, 2022
8fde26d
feat: Use SegmentOptimizer in rMQR.fit
OUDON Aug 22, 2022
dbe93ac
chore: Apply formatter and linter
OUDON Aug 22, 2022
f3bd9a4
fix: Fix the issue caused INF is too small
OUDON Aug 22, 2022
4e7c94b
refactor: Move implementaion compute the sum of length of segments to…
OUDON Aug 23, 2022
c4fc973
fix: Fix missing prefix
OUDON Aug 23, 2022
de32439
chore: Rename length to unfilled_length in segments
OUDON Aug 23, 2022
88d1778
doc: Update docstrings
OUDON Aug 23, 2022
6433612
chore: Remove debug prints
OUDON Aug 25, 2022
c5d7cbe
doc: Update README.md
OUDON Aug 25, 2022
d0482ef
doc: Update README.md
OUDON Aug 25, 2022
54f2b9c
doc: Update README.md
OUDON Aug 25, 2022
f7a84a6
doc: Fix typo
OUDON Aug 25, 2022
c9ed31e
Merge branch 'develop' of github.com:OUDON/rmqr-generator into feat/o…
OUDON Aug 25, 2022
b354536
Merge pull request #24 from OUDON/feat/optimize-segmentation
OUDON Aug 26, 2022
19bb376
fix: Fix issue KanjiEncoder.is_invalid_characters raises UnicodeEncod…
OUDON Aug 27, 2022
26b97ad
feat: Implement characters_num
OUDON Aug 28, 2022
a15f0e7
doc: Add docstrings
OUDON Aug 28, 2022
927377a
chore: Apply formatter
OUDON Aug 28, 2022
17e4f22
chore: Insert a new line at last line
OUDON Aug 29, 2022
bbc141d
Merge pull request #26 from OUDON/fix/multi-byte-character-issue
OUDON Aug 29, 2022
ea7b4eb
chore: Bumps to 0.3.0
OUDON Aug 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
.PHONY: install
install:
pip install -e ".[dev]"

.PHONY: uninstall
uninstall:
yes Y | pip uninstall rmqrcode

.PHONY: test
test:
python -m pytest

.PHONY: lint
lint:
flake8 src
Expand Down
53 changes: 37 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# An rMQR Code Generator
# Rectangular Micro QR Code (rMQR Code) Generator
![reop-url](https://user-images.githubusercontent.com/14174940/172978619-accbf9d0-9dd8-4b19-b47e-ad139a68dcc9.png)


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).
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).

[![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)
![PyPI](https://img.shields.io/pypi/v/rmqrcode?color=blue)
Expand Down Expand Up @@ -48,7 +48,7 @@ optional arguments:
Strategy how to determine rMQR Code size.
```

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

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

### Save as image
Expand All @@ -88,8 +88,8 @@ image.save("my_qr.png")
### Select rMQR Code size manually
To select rMQR Code size manually, use `rMQR()` constructor.
```py
from rmqrcode import rMQR, ErrorCorrectionLevel
qr = rMQR('R11x139', ErrorCorrectionLevel.H)
qr.make("https://oudon.xyz")
```

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

### Encoding Modes and Segments

## 🛠️ Under the Hood
### Encoding modes
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.
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.
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.

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.
```py
from rmqrcode import rMQR, ErrorCorrectionLevel, encoder
qr = rMQR('R7x43', ErrorCorrectionLevel.M)
qr.add_segment("123", encoder_class=encoder.NumericEncoder)
qr.add_segment("Abc", encoder_class=encoder.ByteEncoder)
qr.make()
```

The value for `encoder_class` is listed in the below table.

|Mode|Value of encoder_class|Characters|
|-|-|-|
|Numeric|NumericEncoder|0-9|
|Alphanumeric|AlphanumericEncoder|0-9 A-Z \s $ % * + - . / :|
|Byte|ByteEncoder|Any|
|Kanji|KanjiEncoder|from 0x8140 to 0x9FFC, from 0xE040 to 0xEBBF in Shift JIS value|

### Optimal Segmentation
The `rMQR.fit` method mentioned above computes the optimal segmentation.
For example, the data "123Abc" is divided into the following two segments.

|Mode|Supported?|
|-|:-:|
|Numeric| |
|Alphanumeric||
|Byte|✅|
|Kanji||
|Segment No.|Data|Encoding Mode|
|-|-|-|
|Segment1|123|Numeric|
|Segment2|Abc|Byte|

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

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


## 📚 References
Expand Down
7 changes: 5 additions & 2 deletions example.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from rmqrcode import ErrorCorrectionLevel
from rmqrcode import QRImage
from rmqrcode import FitStrategy
from rmqrcode import encoder

import logging

Expand All @@ -24,9 +25,11 @@ def main():
print(qr)

# Determine rMQR version manually
# version = 'R13x99'
# version = 'R7x43'
# qr = rMQR(version, error_correction_level)
# qr.make(data)
# qr.add_segment("123", encoder_class=encoder.NumericEncoder)
# qr.add_segment("Abc", encoder_class=encoder.ByteEncoder)
# qr.make()
# print(qr)

# Save as png
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = rmqrcode
version = 0.2.0
version = 0.3.0
author = Takahiro Tomita
author_email = ttp8101@gmail.com
description = An rMQR Code Generetor
Expand Down
20 changes: 18 additions & 2 deletions src/rmqrcode/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
from . import encoder
from .format.error_correction_level import ErrorCorrectionLevel
from .qr_image import QRImage
from .rmqrcode import DataTooLongError, FitStrategy, IllegalVersionError, rMQR
from .rmqrcode import (
DataTooLongError,
FitStrategy,
IllegalVersionError,
NoSegmentError,
rMQR,
)

__all__ = ("rMQR", "DataTooLongError", "FitStrategy", "IllegalVersionError", "QRImage", "ErrorCorrectionLevel")
__all__ = (
"rMQR",
"DataTooLongError",
"FitStrategy",
"IllegalVersionError",
"NoSegmentError",
"QRImage",
"ErrorCorrectionLevel",
"encoder",
)
4 changes: 2 additions & 2 deletions src/rmqrcode/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ def _make_qr(data, ecc, version, fit_strategy):
qr = rMQR(version, ecc)
except IllegalVersionError:
_show_error_and_exit("Error: Illegal version.")
qr.make(data)

qr.add_segment(data)
qr.make()
return qr


Expand Down
7 changes: 7 additions & 0 deletions src/rmqrcode/encoder/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .alphanumeric_encoder import AlphanumericEncoder
from .byte_encoder import ByteEncoder
from .encoder_base import IllegalCharacterError
from .kanji_encoder import KanjiEncoder
from .numeric_encoder import NumericEncoder

__all__ = ("ByteEncoder", "NumericEncoder", "IllegalCharacterError", "AlphanumericEncoder", "KanjiEncoder")
92 changes: 92 additions & 0 deletions src/rmqrcode/encoder/alphanumeric_encoder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import re

from .encoder_base import EncoderBase


class AlphanumericEncoder(EncoderBase):
CHARACTER_MAP = {
"0": 0,
"1": 1,
"2": 2,
"3": 3,
"4": 4,
"5": 5,
"6": 6,
"7": 7,
"8": 8,
"9": 9,
"A": 10,
"B": 11,
"C": 12,
"D": 13,
"E": 14,
"F": 15,
"G": 16,
"H": 17,
"I": 18,
"J": 19,
"K": 20,
"L": 21,
"M": 22,
"N": 23,
"O": 24,
"P": 25,
"Q": 26,
"R": 27,
"S": 28,
"T": 29,
"U": 30,
"V": 31,
"W": 32,
"X": 33,
"Y": 34,
"Z": 35,
" ": 36,
"$": 37,
"%": 38,
"*": 39,
"+": 40,
"-": 41,
".": 42,
"/": 43,
":": 44,
}

@classmethod
def mode_indicator(cls):
return "010"

@classmethod
def _encoded_bits(cls, data):
res = ""
data_grouped = cls._group_by_2characters(data)
for s in data_grouped:
if len(s) == 2:
value = cls.CHARACTER_MAP[s[0]] * 45 + cls.CHARACTER_MAP[s[1]]
res += bin(value)[2:].zfill(11)
elif len(s) == 1:
value = cls.CHARACTER_MAP[s[0]]
res += bin(value)[2:].zfill(6)
return res

@classmethod
def _group_by_2characters(cls, data):
res = []
while data != "":
res.append(data[:2])
data = data[2:]
return res

@classmethod
def length(cls, data, character_count_indicator_length):
return (
len(cls.mode_indicator()) + character_count_indicator_length + 11 * (len(data) // 2) + 6 * (len(data) % 2)
)

@classmethod
def characters_num(cls, data):
return len(data)

@classmethod
def is_valid_characters(cls, data):
return bool(re.match(r"^[0-9A-Z\s\$\%\*\+\-\.\/\:]*$", data))
30 changes: 18 additions & 12 deletions src/rmqrcode/encoder/byte_encoder.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
class ByteEncoder:
MODE_INDICATOR = "011"
from .encoder_base import EncoderBase

@staticmethod
def _encoded_bits(s):

class ByteEncoder(EncoderBase):
@classmethod
def mode_indicator(cls):
return "011"

@classmethod
def _encoded_bits(cls, s):
res = ""
encoded = s.encode("utf-8")
for byte in encoded:
res += bin(byte)[2:].zfill(8)
return res

@staticmethod
def encode(data, character_count_length):
res = ByteEncoder.MODE_INDICATOR
res += bin(len(data))[2:].zfill(character_count_length)
res += ByteEncoder._encoded_bits(data)
return res
@classmethod
def length(cls, data, character_count_indicator_length):
return len(cls.mode_indicator()) + character_count_indicator_length + 8 * len(data.encode("utf-8"))

@staticmethod
def length(data):
@classmethod
def characters_num(cls, data):
return len(data.encode("utf-8"))

@classmethod
def is_valid_characters(cls, data):
return True # Any characters can encode in the Byte Mode
Loading