From 82b2908231c05159a3b6a0230491017642ec60b1 Mon Sep 17 00:00:00 2001 From: dm1sh Date: Sat, 11 Dec 2021 23:28:59 +0300 Subject: [PATCH] Completed encode method in Encoder. Added metadata composition, public accessors for version and data, not computed method error annotations and tests --- QRCodeLibrary/Encoder.cpp | 72 +++++++++++++++++++++++++++++++++++---- QRCodeLibrary/Encoder.hpp | 19 +++++++---- QRCodeLibrary/Tables.cpp | 14 +++++++- QRCodeLibrary/Tables.hpp | 5 ++- tests/Encoder_test.cpp | 38 +++++++++++++++++++++ 5 files changed, 132 insertions(+), 16 deletions(-) diff --git a/QRCodeLibrary/Encoder.cpp b/QRCodeLibrary/Encoder.cpp index 0b5b3c9..1386431 100644 --- a/QRCodeLibrary/Encoder.cpp +++ b/QRCodeLibrary/Encoder.cpp @@ -3,20 +3,42 @@ #include "Encoder.hpp" #include "Tables.hpp" -unsigned char Encoder::determite_version() +#include + +BitArray Encoder::encode() +{ + unsigned encoded_bit_num = calculate_encoded_input_size(input.size(), method); + unsigned metadata_bit_num = calculate_metadata_size(method, ((version < 0) ? 0 : version)); + + if (version < 0) { + version = 0; + while (metadata_bit_num + encoded_bit_num >= Tables::max_capability.at(corr_lvl).at(version)) { + version = determite_version(metadata_bit_num + encoded_bit_num, corr_lvl); + metadata_bit_num = calculate_metadata_size(method, version); + } + } + else if (metadata_bit_num + encoded_bit_num >= Tables::max_capability.at(corr_lvl).at(version)) + throw std::invalid_argument("This version is too low for input of this size. Set negative version to calculate it dynamicly"); + + e.resize(metadata_bit_num + encoded_bit_num); + + write_metadata(input.size(), metadata_bit_num - 4, method, e); + encode_input(metadata_bit_num); + + return e; +} + +unsigned char Encoder::determite_version(unsigned size, CorrectionLevel corr_lvl) { const auto& sizes = Tables::max_capability.at(corr_lvl); - return upper_index(sizes, input.size()); -} - -void Encoder::encode() -{ - unsigned bit_num = calculate_encoded_input_size(input.size(), method); + return upper_index(sizes, size); } unsigned Encoder::calculate_encoded_input_size(unsigned input_size, QRCodeMethod method) { + if (method == QRCodeMethod::Dynamic) throw std::runtime_error("Specify correct method"); + unsigned bit_num = 0; switch (method) { @@ -40,6 +62,28 @@ unsigned Encoder::calculate_encoded_input_size(unsigned input_size, QRCodeMethod return bit_num; } +unsigned Encoder::calculate_metadata_size(QRCodeMethod method, unsigned char version) +{ + if (method == QRCodeMethod::Dynamic) throw std::runtime_error("Specify correct method"); + + unsigned char size = 0; + + auto lengths = Tables::data_amount_lengths.at(method); + + for (int i = 0; i < 2 && lengths[i].first <= version; i++) + size = lengths[i].second; + + return size + 4; +} + +void Encoder::write_metadata(unsigned input_size, unsigned input_bits_amount_size, QRCodeMethod method, BitArray& out) +{ + if (method == QRCodeMethod::Dynamic) throw std::runtime_error("Specify correct method"); + + out.set(0, Tables::mode_indicator.at(method), 4); + out.set(4, input_size, input_bits_amount_size); +} + void Encoder::encode_numeric(const string& input, BitArray& out, unsigned offset) { for (unsigned i = 0; i < input.size() / 3; i++) { @@ -82,3 +126,17 @@ unsigned char Encoder::encode_char(char ch) throw std::runtime_error("No such character in alphabet. Use bytes QR code method."); } + +unsigned char Encoder::get_version() +{ + if (version < 0) throw std::runtime_error("Determite version before getting it"); + + return version; +} + +BitArray Encoder::get_data() +{ + if (e.size == 0) throw std::runtime_error("Data is not calculated yet"); + + return e; +} diff --git a/QRCodeLibrary/Encoder.hpp b/QRCodeLibrary/Encoder.hpp index be6d033..c2ed7dd 100644 --- a/QRCodeLibrary/Encoder.hpp +++ b/QRCodeLibrary/Encoder.hpp @@ -11,15 +11,18 @@ using namespace std; class Encoder { public: - Encoder(const string& input_, CorrectionLevel corr_lvl_, QRCodeMethod method_, unsigned char version_) : input{ input_ }, corr_lvl{ corr_lvl_ }, method{ method_ }, version{ version_ } {}; + Encoder(const string& input_, CorrectionLevel corr_lvl_ = CorrectionLevel::M, QRCodeMethod method_ = QRCodeMethod::Dynamic, char version_ = -1) : input{ input_ }, corr_lvl{ corr_lvl_ }, method{ method_ }, version{ version_ } {}; - unsigned char determite_version(); + BitArray encode(); - void encode(); + static unsigned char determite_version(unsigned size, CorrectionLevel corr_lvl); static unsigned calculate_encoded_input_size(unsigned input_size, QRCodeMethod method); + static unsigned calculate_metadata_size(QRCodeMethod method, unsigned char version); - constexpr void encode_input() { + static void write_metadata(unsigned input_size, unsigned input_bits_amount_size, QRCodeMethod method, BitArray& out); + + constexpr void encode_input(unsigned offset) { switch (method) { case QRCodeMethod::Numeric: encode_numeric(input, e, offset); @@ -39,14 +42,16 @@ public: static unsigned char encode_char(char ch); + unsigned char get_version(); + BitArray get_data(); + private: - const string& input; + const string input; CorrectionLevel corr_lvl; const QRCodeMethod method; - unsigned char version; + char version; BitArray e; - unsigned offset = 0; }; template diff --git a/QRCodeLibrary/Tables.cpp b/QRCodeLibrary/Tables.cpp index ab12379..367da28 100644 --- a/QRCodeLibrary/Tables.cpp +++ b/QRCodeLibrary/Tables.cpp @@ -15,4 +15,16 @@ const std::map> Tables::max_capa { CorrectionLevel::M, {{128, 224, 352, 512, 688, 864, 992, 1232, 1456, 1728, 2032, 2320, 2672, 2920, 3320, 3624, 4056, 4504, 5016, 5352}} }, { CorrectionLevel::Q, {{104, 176, 272, 384, 496, 608, 704, 880, 1056, 1232, 1440, 1648, 1952, 2088, 2360, 2600, 2936, 3176, 3560, 3880}} }, { CorrectionLevel::H, {{72, 128, 208, 288, 368, 480, 528, 688, 800, 976, 1120, 1264, 1440, 1576, 1784, 2024, 2264, 2504, 2728, 3080}} } -}; \ No newline at end of file +}; + +const std::map Tables::mode_indicator{ + { QRCodeMethod::Numeric, 0b0001 }, + { QRCodeMethod::Alphabetic, 0b0010 }, + { QRCodeMethod::Byte, 0b0100 } +}; + +const std::map, 3>> Tables::data_amount_lengths{ + { QRCodeMethod::Numeric, {{ {0, 10}, {8, 12}, {25, 14} }} }, + { QRCodeMethod::Alphabetic, {{ {0, 9}, {8, 11}, {25, 13} }} } , + { QRCodeMethod::Byte, {{ {0, 8}, {8, 16}, {25, 16} }} } +}; diff --git a/QRCodeLibrary/Tables.hpp b/QRCodeLibrary/Tables.hpp index 40b7c65..76759ad 100644 --- a/QRCodeLibrary/Tables.hpp +++ b/QRCodeLibrary/Tables.hpp @@ -9,5 +9,8 @@ class Tables { public: static const std::arrayalphabetic; - static const std::map> max_capability; + static const std::map>max_capability; + + static const std::mapmode_indicator; + static const std::map, 3>>data_amount_lengths; }; \ No newline at end of file diff --git a/tests/Encoder_test.cpp b/tests/Encoder_test.cpp index c4acd88..552b767 100644 --- a/tests/Encoder_test.cpp +++ b/tests/Encoder_test.cpp @@ -4,6 +4,8 @@ #include "../QRCodeLibrary/Encoder.hpp" +/* upper_index function */ + TEST(UpperIndexTests, FindsExactMatch) { array arr1{ 1, 2, 3, 4, 5 }; @@ -30,6 +32,8 @@ TEST(UpperIndexTests, IfNothingFoundReturnsArrSize) { EXPECT_EQ(upper_index(arr, 10), 5); } +/* Encoder class */ + TEST(EncoderTests, CalculatesEncodedInputSize) { EXPECT_EQ(Encoder::calculate_encoded_input_size(3, QRCodeMethod::Numeric), 10); EXPECT_EQ(Encoder::calculate_encoded_input_size(3 + 1, QRCodeMethod::Numeric), 10 + 4); @@ -45,6 +49,26 @@ TEST(EncoderTests, CalculatesEncodedInputSize) { EXPECT_EQ(Encoder::calculate_encoded_input_size(10, QRCodeMethod::Byte), 10*8); } +TEST(EncoderTests, CalculatesMetadataSize) { + EXPECT_EQ(Encoder::calculate_metadata_size(QRCodeMethod::Numeric, 0), 14); + EXPECT_EQ(Encoder::calculate_metadata_size(QRCodeMethod::Alphabetic, 5), 13); + EXPECT_EQ(Encoder::calculate_metadata_size(QRCodeMethod::Numeric, 10), 16); +} + +TEST(EncoderTests, DetermitesVersion) { + EXPECT_EQ(Encoder::determite_version(1600, CorrectionLevel::M), 9); +} + +TEST(EncoderTests, WritesMetadata) { + string expected("010001100100"); + + BitArray arr(expected.size()); + + Encoder::write_metadata(100, expected.size() - 4, QRCodeMethod::Byte, arr); + + EXPECT_EQ(std::string(arr), expected); +} + TEST(EncoderTests, EncodesNumeric) { BitArray tmp(string("00110110001110000100101001").size()); Encoder::encode_numeric("8675309", tmp, 2); @@ -64,4 +88,18 @@ TEST(EncoderTests, EncodesBytes) { Encoder::encode_byte(u8"Дмитрий Шишков", tmp, 4); EXPECT_EQ(std::string(tmp), "0000110100001001010011010000101111001101000010111000110100011000001011010001100000001101000010111000110100001011100100100000110100001010100011010000101110001101000110001000110100001011101011010000101111101101000010110010"); +} + +TEST(EncoderTests, EncodesInput) { + Encoder e1("8675309", CorrectionLevel::M, QRCodeMethod::Numeric); + e1.encode(); + EXPECT_EQ(std::string(e1.get_data()), "00010000000111110110001110000100101001"); + + Encoder e2("HELLO WORLD", CorrectionLevel::M, QRCodeMethod::Alphabetic); + e2.encode(); + EXPECT_EQ(std::string(e2.get_data()), "00100000010110110000101101111000110100010111001011011100010011010100001101"); + + Encoder e3(u8"Дмитрий Шишков", CorrectionLevel::M, QRCodeMethod::Byte); + e3.encode(); + EXPECT_EQ(std::string(e3.get_data()), "010000011011110100001001010011010000101111001101000010111000110100011000001011010001100000001101000010111000110100001011100100100000110100001010100011010000101110001101000110001000110100001011101011010000101111101101000010110010"); } \ No newline at end of file