Terminal QR Code with Unicode characters

TL;DR

A wrapper around Text::QRCode to print QR codes on the terminal.

So it seems that Text::QRCode bite the XS bullet to interface with libqrencode and has then been used by a lot of modules (including Term::QRCode, introduced here).

The output of that module is a bit weird, in that it is a 2-dimensional matrix (array of arrays) where each cell contains a single character, that can be either a star * or a blank space.

I think it’s weird because I would have expected…

  • … arrays of 1 and 0 (or otherwise true and false values), OR
  • … an array of strings for each line, OR
  • … a single string with nelines inside.

Anyway.

I thought to use the Unicode characters discovered from QRcode.show to produce something good for the terminal:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
#!/usr/bin/env perl
use v5.24;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';

use Text::QRCode;

sub terminalize ($encoded, $reverse = 1) {
   state $direct_char_for = [
      ' ',                    # 0
      "\N{LOWER HALF BLOCK}", # 1
      "\N{UPPER HALF BLOCK}", # 2
      "\N{FULL BLOCK}",       # 3
   ];
   state $c2i = sub ($c) { $c eq ' ' ? 0 : 1 };

   my @char_for = $direct_char_for->@*;
   @char_for = reverse @char_for if $reverse;

   my $first_row_id = 0;
   my @output;
   while ($first_row_id <= $encoded->$#*) {
      my $first_row = $encoded->[$first_row_id++];
      my $second_row = $first_row_id <= $encoded->$#*
         ? $encoded->[$first_row_id++]
         : [ (' ') x scalar($first_row->@*) ];
      push @output, join '', ($char_for[0] x 2), map {
         my $id = $c2i->($first_row->[$_]) * 2 + $c2i->($second_row->[$_]);
         $char_for[$id];
      } 0 .. $first_row->$#*;
      $output[-1] .= $char_for[0] x 2;
   }
   my $blank = $output[0] =~ s{.}{$char_for[0]}grmxs;
   return [$blank, @output, $blank];
}

binmode STDOUT, ':encoding(utf-8)';
say for terminalize(Text::QRCode->new->plot(shift // '!'))->@*;

Local version here.

The input is the encoded form produced by Text::QRCode, and the output is an array of lines.

A few remarks:

  • each pair if input lines of pixels is encoded into a single line of characters, to account for the difference in width vs. height in the terminal (this is somehow the opposite choice taken by Term::QRCode, where each input pixel is represented by two characters);
  • we’re adding the quiet zone around the produced output, to avoid confusing the decoders.

Example result:

example QR Code in the terminal

Pretty neat, uh?

I guess it’s all for today… stay safe folks!


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