QRate - High Level Design (provisional)


The main design for QRate, as it stands now.

I’m starting this little project with a few requirements:

  • Encoding: this is where we take an input file to QRate and produce as many QR images as needed to get all data.
    • I’m not anticipating the need to handle very big files, so I assume that having the whole content in memory as a Perl variable is fine.
    • I’d like to take a modular “pipeline-like” approach to be able to experiment with the insertion of additional steps, e.g. compression.
    • The overall output should be a PDF file, so that it can be easily printed.
    • Order of pages in the PDF MUST NOT matter. This is handy for a possible scanning phase later, so that the sheets might get scrambled but still be put together back in the right order.
    • Protection level MUST be set to H (the maximum available).
    • Data must be saved Base-64 encoded.
  • Decoding: this is where we take something scanned and convert it back to a file.
    • images can be fed either as individual image files, in inside a single PDF.
    • order of appearance of the images MUST NOT matter.

For the implementation:

  • Encoding:
    • the input file is optionally compressed with some IO::Compress module.
    • The result is encoded with [MIME::Base64][], which produces lines that are at most 77 octets long (76 of encoded payload plus a newline).
    • This encoded form is divided into slices of at most 16 lines each, which corresponds to 1232 octets, leaving plenty of space (36 octets) for a header.
    • The header will match the regular expression qr{(?mxs: \A (?<n> 0 | [1-9]\d*) (?<more> [+.]) \n (?<data>.*) \z}, where the n named capture represents the number of the slice (to allow for ordered reconstruction) and more is either + (indicating that more chunks are expected after this one) or . (indicating the last chunk).
  • Decoding: well… everything in reverse order!

This will be the higher level program:

# ...
help_die() unless @ARGV == 3;
my ($command, $input, $output) = @ARGV;

if ($command eq 'encode') {
   encode($input, $output);
elsif ($command eq 'decode') {
   decode($input, $output);
else {

sub help_die {
   die "$0 <encode|decode> <input-file> <output-file>\n";

sub encode ($input, $output) {
   my $data = compress(path($input)->slurp_raw, 9) or die "compress()\n";
   return 0;

sub decode ($input, $output) {
   my $data = assemble_data(slice_reader_it(pdf_reader_it($input)));
   return 0;

The two $data variables in encode() and decode() should hold the same data, i.e. the compressed payload.

Cheers and stay safe!

Comments? Octodon, Twitter, GitHub, Reddit, or drop me a line!