diff --git a/c/qrcodegen-demo.c b/c/qrcodegen-demo.c index d925f4e..9175602 100644 --- a/c/qrcodegen-demo.c +++ b/c/qrcodegen-demo.c @@ -1,12 +1,12 @@ -/* +/* * QR Code generator demo (C) - * + * * Run this command-line program with no arguments. The program * computes a demonstration QR Codes and print it to the console. - * + * * Copyright (c) Project Nayuki. (MIT License) * https://www.nayuki.io/page/qr-code-generator-library - * + * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to @@ -39,9 +39,126 @@ static void doSegmentDemo(void); static void doMaskDemo(void); static void printQr(const uint8_t qrcode[]); +static int border = 4; + +typedef struct output_formatting { + char const *begin_code; + char const *begin_line; + char const *light; + char const *dark; + char const *same; + char const *end_line; + char const *end_code; + bool braille_map; + uint32_t *charMap; + int num_across; + int num_down; +} OFM; + +typedef enum { + blackOnWhite, + whiteOnBlack, + ansiColour, + unicodeHalfBlock, + unicodeBraille, +} TermMode; + +static TermMode outputMode = blackOnWhite; + +static OFM ofmt[] = { + + [blackOnWhite] = (OFM){ + /* this assumes dark text on light background */ + .light = " ", + .dark = "##", + }, + + [whiteOnBlack] = (OFM){ + /* this assumes light text on dark background */ + .light = "##", + .dark = " ", + }, + + [ansiColour] = (OFM){ + .begin_line = "", + .light = "\033[47m ", + .dark = "\033[40m ", + .same = " ", + .end_line = "\033[m\n", + }, + + [unicodeHalfBlock] = (OFM){ + .begin_line = "\033[30;47m", + .end_line = "\033[39;49m\n", + .num_across = 1, + .num_down = 2, + .charMap = (uint32_t[4]){ + ' ', /* clear bottom & top */ + 0x2580, /* ▀ clear bottom, marked top */ + 0x2584, /* ▄ marked bottom, clear top */ + 0x2588, /* █ marked bottom & top */ + }, + }, + + [unicodeBraille] = (OFM){ + .begin_line = "\033[30;47m", + .end_line = "\033[39;49m\n", + .braille_map = true, + .num_across = 2, + .num_down = 4, + }, + +}; // The main application program. -int main(void) { +int main(int argc/*unused*/, char const*const*argv) { + char const*argv0 = *argv++; + for (;;) { + char const*arg = *argv++; + if (!arg || *arg++ != '-') break; /* "-" as a non-option arg */ + char opt = *arg++; + if (!opt) { --argv; break; } + if (opt == '-') { + if (!*arg) break; /* "--" marks end of options */ + if (!strcmp(arg, "border" )) opt = 'b'; + else if (!strcmp(arg, "plain" )) opt = 'p'; + else if (!strcmp(arg, "plain=bow")) opt = 'p'; + else if (!strcmp(arg, "plain=wob")) opt = 'P'; + else if (!strcmp(arg, "unicode=braille")) opt = 'Q'; + else if (!strcmp(arg, "unicode=halfblock")) opt = 'R'; + else if (!strcmp(arg, "ansi" )) opt = 'S'; + else goto bad_opt; + arg = NULL; + } + switch (opt) { + case 'P': outputMode = blackOnWhite; break; + case 'Q': outputMode = unicodeBraille; break; + case 'R': outputMode = unicodeHalfBlock; break; + case 'S': outputMode = ansiColour; break; + case 'p': outputMode = whiteOnBlack; break; + case 'b': arg = arg && *arg ? arg : *argv++; + if (!arg) goto missing_value; + border = strtol(arg, (char**)&arg, 10); + if (*arg) goto bad_value; + arg = NULL; + break; + default: goto bad_opt; + } + if (arg && *arg) { + fprintf(stderr, "Bundled options not supported at '%s'\n", arg); + return EXIT_FAILURE; + } + continue; + bad_opt: + fprintf(stderr, "Invalid option '%s'\n", argv[-1]); + return EXIT_FAILURE; + missing_value: + fprintf(stderr, "Missing value for option '%s'\n", argv[-1]); + return EXIT_FAILURE; + bad_value: + fprintf(stderr, "Improper numeric value '%s'\n", argv[-1]); + return EXIT_FAILURE; + } doBasicDemo(); doVarietyDemo(); doSegmentDemo(); @@ -57,7 +174,7 @@ int main(void) { static void doBasicDemo(void) { const char *text = "Hello, world!"; // User-supplied text enum qrcodegen_Ecc errCorLvl = qrcodegen_Ecc_LOW; // Error correction level - + // Make and print the QR Code symbol uint8_t qrcode[qrcodegen_BUFFER_LEN_MAX]; uint8_t tempBuffer[qrcodegen_BUFFER_LEN_MAX]; @@ -78,7 +195,7 @@ static void doVarietyDemo(void) { if (ok) printQr(qrcode); } - + { // Alphanumeric mode encoding (5.5 bits per character) uint8_t qrcode[qrcodegen_BUFFER_LEN_MAX]; uint8_t tempBuffer[qrcodegen_BUFFER_LEN_MAX]; @@ -87,7 +204,7 @@ static void doVarietyDemo(void) { if (ok) printQr(qrcode); } - + { // Unicode text as UTF-8 const char *text = "\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1wa\xE3\x80\x81" "\xE4\xB8\x96\xE7\x95\x8C\xEF\xBC\x81\x20\xCE\xB1\xCE\xB2\xCE\xB3\xCE\xB4"; @@ -98,7 +215,7 @@ static void doVarietyDemo(void) { if (ok) printQr(qrcode); } - + { // Moderately large QR Code using longer text (from Lewis Carroll's Alice in Wonderland) const char *text = "Alice was beginning to get very tired of sitting by her sister on the bank, " @@ -158,7 +275,7 @@ static void doSegmentDemo(void) { printQr(qrcode); } } - + { // Illustration "golden" const char *golden0 = "Golden ratio \xCF\x86 = 1."; const char *golden1 = "6180339887498948482045868343656381177203091798057628621354486227052604628189024497072072041893911374"; @@ -210,7 +327,7 @@ static void doSegmentDemo(void) { printQr(qrcode); } } - + { // Illustration "Madoka": kanji, kana, Cyrillic, full-width Latin, Greek characters uint8_t qrcode[qrcodegen_BUFFER_LEN_MAX]; uint8_t tempBuffer[qrcodegen_BUFFER_LEN_MAX]; @@ -270,18 +387,18 @@ static void doMaskDemo(void) { uint8_t qrcode[qrcodegen_BUFFER_LEN_MAX]; uint8_t tempBuffer[qrcodegen_BUFFER_LEN_MAX]; bool ok; - + ok = qrcodegen_encodeText("https://www.nayuki.io/", tempBuffer, qrcode, qrcodegen_Ecc_HIGH, qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true); if (ok) printQr(qrcode); - + ok = qrcodegen_encodeText("https://www.nayuki.io/", tempBuffer, qrcode, qrcodegen_Ecc_HIGH, qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_3, true); if (ok) printQr(qrcode); } - + { // Chinese text as UTF-8 const char *text = "\xE7\xB6\xAD\xE5\x9F\xBA\xE7\x99\xBE\xE7\xA7\x91\xEF\xBC\x88\x57\x69\x6B\x69\x70" @@ -294,22 +411,22 @@ static void doMaskDemo(void) { uint8_t qrcode[qrcodegen_BUFFER_LEN_MAX]; uint8_t tempBuffer[qrcodegen_BUFFER_LEN_MAX]; bool ok; - + ok = qrcodegen_encodeText(text, tempBuffer, qrcode, qrcodegen_Ecc_MEDIUM, qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_0, true); if (ok) printQr(qrcode); - + ok = qrcodegen_encodeText(text, tempBuffer, qrcode, qrcodegen_Ecc_MEDIUM, qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_1, true); if (ok) printQr(qrcode); - + ok = qrcodegen_encodeText(text, tempBuffer, qrcode, qrcodegen_Ecc_MEDIUM, qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_5, true); if (ok) printQr(qrcode); - + ok = qrcodegen_encodeText(text, tempBuffer, qrcode, qrcodegen_Ecc_MEDIUM, qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_7, true); if (ok) @@ -321,15 +438,79 @@ static void doMaskDemo(void) { /*---- Utilities ----*/ +static inline void wputchar(uint32_t ch) { + if (ch < 0x80) + putchar(ch); + else if (ch < 0x800) + putchar(0xc0 | ch >> 6 & 0x1f), + putchar(0x80 | ch & 0x3f); + else if (ch < 0x10000) + putchar(0xe0 | ch >> 12 & 0x0f), + putchar(0x80 | ch >> 6 & 0x3f), + putchar(0x80 | ch & 0x3f); + else if (ch < 0x200000) + putchar(0xf0 | ch >> 18 & 0x07), + putchar(0x80 | ch >> 12 & 0x3f), + putchar(0x80 | ch >> 6 & 0x3f), + putchar(0x80 | ch & 0x3f); + else + putchar('?'); +} + // Prints the given QR Code to the console. static void printQr(const uint8_t qrcode[]) { int size = qrcodegen_getSize(qrcode); - int border = 4; - for (int y = -border; y < size + border; y++) { - for (int x = -border; x < size + border; x++) { - fputs((qrcodegen_getModule(qrcode, x, y) ? "##" : " "), stdout); + struct output_formatting *of = &ofmt[outputMode]; + if (of->begin_code) + fputs(of->begin_code, stdout); + int const num_across = of->num_across; + int const num_down = of->num_down; + if (num_across && num_down) + for (int y = -border; y < size + border; y += num_down) { + if (of->begin_line) + fputs(of->begin_line, stdout); + for (int x = -border; x < size + border; x += num_across) { + uint32_t cellBits = 0; + for (int dx = num_across; dx-- > 0;) { + for (int dy = num_down; dy-- > 0;) { + cellBits <<= 1; + bool bit = qrcodegen_getModule(qrcode, x+dx, y+dy); + cellBits |= bit; + } + } + wchar_t cellChar = '?'; + if (of->braille_map) { + /* + * cellChar Braille + * 0 4 ⇒ must be ⇒ 0 3 + * 1 5 ⇒ rearranged to ⇒ 1 4 + * 2 6 ⇒ this order ⇒ 2 5 + * 3 7 ⇒ ⇒ 6 7 + */ + cellChar = cellBits & 0x87 + | cellBits << 3 & 0x40 + | cellBits >> 1 & 0x38 + | 0x2800; /* start of Braille range in Unicode */ + } else if (of->charMap) + cellChar = of->charMap[cellBits]; + else + cellChar = cellBits; + wputchar(cellChar); + } + fputs(of->end_line ?: "\n", stdout); } - fputs("\n", stdout); - } - fputs("\n", stdout); + else + for (int y = -border; y < size + border; y++) { + int prevBit = -1; /* neither true nor false */ + if (of->begin_line) + fputs(of->begin_line, stdout); + for (int x = -border; x < size + border; x++) { + bool thisBit = qrcodegen_getModule(qrcode, x, y); + fputs( ! of->same || prevBit != thisBit ? thisBit ? of->dark : of->light : of->same, + stdout ); + prevBit = thisBit; + } + fputs(of->end_line ?: "\n", stdout); + } + fputs(of->end_code ?: "\n", stdout); }