├── .gitignore ├── .gitmodules ├── Makefile ├── .luacheckrc ├── .github └── workflows │ ├── luacheck.yml │ └── test.yml ├── scripts ├── qrcode-profiler.lua └── profiler.lua ├── locco ├── README ├── locco.css ├── template.lua ├── locco.lua ├── luabalanced.lua └── markdown.lua ├── License.md ├── README.md ├── qrblackbox.lua ├── qrcode.lua ├── qrtest.lua └── qrencode.lua /.gitignore: -------------------------------------------------------------------------------- 1 | docs 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ghpages"] 2 | path = ghpages 3 | url = git@github.com:speedata/luaqrcode.git 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | doc: 2 | lua locco/locco.lua qrencode.lua 3 | 4 | profile: 5 | lua scripts/qrcode-profiler.lua 6 | 7 | test: 8 | lua qrtest.lua 9 | lua qrblackbox.lua 10 | 11 | 12 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | std = "max" 2 | include_files = { 3 | "**/*.lua", 4 | "*.rockspec", 5 | ".luacheckrc" 6 | } 7 | exclude_files = { 8 | ".luarocks", 9 | "locco/*" 10 | } 11 | globals = { 12 | "testing", 13 | "debugging" 14 | } 15 | max_line_length = false 16 | -------------------------------------------------------------------------------- /.github/workflows/luacheck.yml: -------------------------------------------------------------------------------- 1 | name: Luacheck 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | 7 | luacheck: 8 | runs-on: ubuntu-22.04 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v3 12 | - name: Luacheck 13 | uses: lunarmodules/luacheck@v1 14 | -------------------------------------------------------------------------------- /scripts/qrcode-profiler.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | package.path = package.path .. ";./scripts/?.lua" 4 | 5 | local profiler = require("profiler") 6 | local qrencode = require("qrencode") 7 | 8 | 9 | profiler.start() 10 | 11 | for _ = 1, 10 do 12 | qrencode.qrcode("The quick brown fox jumps over the lazy dog.") 13 | end 14 | 15 | profiler.stop() 16 | -- show top 20 lines 17 | profiler.report(20) 18 | 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | 7 | test: 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | luaVersion: ['luajit', '5.1', '5.2', '5.3', '5.4.4'] 12 | runs-on: ubuntu-22.04 13 | name: test lua ${{ matrix.luaVersion }} 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 19 | - name: Setup ‘lua’ 20 | uses: leafo/gh-actions-lua@v12 21 | with: 22 | luaVersion: ${{ matrix.luaVersion }} 23 | buildCache: false 24 | - name: Test 25 | run: | 26 | lua qrtest.lua 27 | -------------------------------------------------------------------------------- /locco/README: -------------------------------------------------------------------------------- 1 | (Note from Patrick Gundlach: this is a slightly modified version of locco, 2 | I like to use three dashes for locco documentation, two dashes for normal 3 | Lua comments.) 4 | 5 | ______ 6 | ___ / ________________________ 7 | __ / _ __ \ ___/ ___/ __ \ 8 | _ /___/ /_/ / /__ / /__ / /_/ / 9 | /_____/\____/\___/ \___/ \____/ 10 | 11 | 12 | Locco is a Lua port of Docco: the original quick-and-dirty, hundred-line- 13 | long, literate-programming-style documentation generator. For more information, 14 | see the generated docs: 15 | 16 | 17 | 18 | Other languages: 19 | 20 | CoffeeScript - 21 | 22 | Python - 23 | 24 | Ruby - 25 | 26 | Sh - 27 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2020, Patrick Gundlach (SPEEDATA GMBH) and contributors, see https://github.com/speedata/luaqrcode. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of SPEEDATA nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL SPEEDATA BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | See the homepage at http://speedata.github.io/luaqrcode/ for more information. 2 | 3 | Special thanks to all contributors. Everything helps: bug reports, patches etc. 4 | 5 | License: 3-clause BSD license
6 | Usability status: mature, used in production
7 | Maintenance status: maintained (bug fixes)
8 | 9 | Part of the [speedata Publisher](https://www.speedata.de/). 10 | 11 | ## Development 12 | 13 | ### Running tests 14 | 15 | A basic test suite is available and can be run with: 16 | 17 | ```sh 18 | make test 19 | ``` 20 | 21 | This will execute the Lua test script provided in the repository and is intended to catch regressions and verify the behaviour of non-trivial functions. Contributions that change core logic should ideally extend or update these tests. 22 | 23 | #### Black-box QR testcases 24 | 25 | Black-box tests live in `qrblackbox.lua` and load sample data from `qrblackbox_data.lua`. Each test case is on a single line to make appending easy. 26 | 27 | - Generate a new case: `lua qrblackbox.lua --generate "your text"`; this prints a ready-to-paste line. 28 | - Append that line to the end of `qrblackbox_data.lua`. 29 | - Run the tests: `lua qrblackbox.lua` or `make test`. 30 | 31 | ### Profiling / hotspot analysis 32 | 33 | For performance work there is a small profiling script that can be invoked via: 34 | 35 | ```sh 36 | make profile 37 | ``` 38 | 39 | This runs a simple benchmark/profiling setup to identify hotspots in typical usage scenarios. The results should be treated as a rough guide only, not as a precise or stable performance reference. They are mainly intended to help decide where optimizations are worthwhile and to avoid micro-optimizations in non-critical code paths. 40 | 41 | -------------------------------------------------------------------------------- /scripts/profiler.lua: -------------------------------------------------------------------------------- 1 | local profiler = { 2 | running = false, 3 | data = {} 4 | } 5 | 6 | local clock = os.clock 7 | 8 | local function hook(event) 9 | local info = debug.getinfo(2, "nS") -- 2 = caller, n = name, S = source 10 | if not info then return end 11 | 12 | local key = string.format("%s:%s:%d", 13 | info.short_src or "unknown", 14 | info.name or "", 15 | info.linedefined or 0 16 | ) 17 | 18 | local entry = profiler.data[key] 19 | if not entry then 20 | entry = { calls = 0, time = 0, last = 0 } 21 | profiler.data[key] = entry 22 | end 23 | 24 | if event == "call" then 25 | entry.calls = entry.calls + 1 26 | entry.last = clock() 27 | elseif event == "return" then 28 | if entry.last ~= 0 then 29 | entry.time = entry.time + (clock() - entry.last) 30 | entry.last = 0 31 | end 32 | end 33 | end 34 | 35 | function profiler.start() 36 | if profiler.running then return end 37 | profiler.running = true 38 | debug.sethook(hook, "cr") -- c = call, r = return 39 | end 40 | 41 | function profiler.stop() 42 | if not profiler.running then return end 43 | profiler.running = false 44 | debug.sethook() 45 | end 46 | 47 | function profiler.report(limit) 48 | limit = limit or 30 49 | local list = {} 50 | 51 | for key, entry in pairs(profiler.data) do 52 | list[#list+1] = { 53 | key = key, 54 | calls = entry.calls, 55 | time = entry.time 56 | } 57 | end 58 | 59 | table.sort(list, function(a,b) 60 | return a.time > b.time 61 | end) 62 | 63 | print(string.format("%-50s %10s %10s", "Function", "Calls", "Time (s)")) 64 | print(("="):rep(76)) 65 | 66 | local n = math.min(limit, #list) 67 | for i = 1, n do 68 | local e = list[i] 69 | print(string.format("%-50s %10d %10.6f", e.key, e.calls, e.time)) 70 | end 71 | end 72 | 73 | return profiler 74 | -------------------------------------------------------------------------------- /qrblackbox.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | -- Black-box tests for qrencode.qrcode. 4 | -- Test data lives in qrblackbox_data.lua; each sample is kept on a single line 5 | -- to make appending new cases straightforward. 6 | 7 | local qrencode = dofile("qrencode.lua") 8 | local samples = dofile("qrblackbox_data.lua") 9 | 10 | local failed = false 11 | 12 | local function err(fmt, ...) 13 | print(string.format(fmt, ...)) 14 | end 15 | 16 | local function assert_equal(a, b, label) 17 | if a ~= b then 18 | err("Assertion failed: %s: %q ~= %q", label, tostring(a), tostring(b)) 19 | failed = true 20 | end 21 | end 22 | 23 | local function flatten_matrix(tab) 24 | local rows = {} 25 | local size = #tab 26 | for y = 1, size do 27 | local row = {} 28 | for x = 1, size do 29 | row[#row + 1] = tab[x][y] > 0 and "1" or "0" 30 | end 31 | rows[#rows + 1] = table.concat(row) 32 | end 33 | return rows 34 | end 35 | 36 | local function generate_test(input) 37 | local ok, matrix_or_msg = qrencode.qrcode(input) 38 | if not ok then 39 | return nil, matrix_or_msg 40 | end 41 | 42 | local rows = flatten_matrix(matrix_or_msg) 43 | local quoted_rows = {} 44 | for i = 1, #rows do 45 | quoted_rows[i] = string.format("%q", rows[i]) 46 | end 47 | 48 | local line = string.format('{input=%q,rows={%s}},', input, table.concat(quoted_rows, ",")) 49 | return line, rows 50 | end 51 | 52 | local function run_samples(data) 53 | failed = false 54 | 55 | for idx, sample in ipairs(data) do 56 | local ok, tab_or_msg = qrencode.qrcode(sample.input) 57 | assert_equal(ok, true, string.format("sample %d: qrcode success", idx)) 58 | if ok then 59 | local rows = flatten_matrix(tab_or_msg) 60 | for i = 1, #sample.rows do 61 | assert_equal(rows[i], sample.rows[i], string.format("sample %d row %d", idx, i)) 62 | end 63 | end 64 | end 65 | 66 | return not failed 67 | end 68 | 69 | local function usage(prog) 70 | err("Usage: %s [--generate ]", prog) 71 | err(" Without flags, runs black-box QR tests using qrblackbox_data.lua") 72 | err(" --generate prints a one-line sample you can paste into qrblackbox_data.lua") 73 | end 74 | 75 | if arg[1] == "--generate" then 76 | local input = arg[2] 77 | if not input then 78 | usage(arg[0] or "qrblackbox.lua") 79 | os.exit(1) 80 | end 81 | 82 | local line, err_or_rows = generate_test(input) 83 | if not line then 84 | err("Could not generate QR for %q: %s", input, tostring(err_or_rows)) 85 | os.exit(1) 86 | end 87 | print(line) 88 | os.exit(0) 89 | elseif arg[1] == "--help" then 90 | usage(arg[0] or "qrblackbox.lua") 91 | os.exit(0) 92 | end 93 | 94 | local ok = run_samples(samples) 95 | if ok then 96 | print("Black-box QR tests passed") 97 | os.exit(0) 98 | else 99 | print("Black-box QR tests failed") 100 | os.exit(1) 101 | end 102 | -------------------------------------------------------------------------------- /qrcode.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | local qrencode = dofile("qrencode.lua") 4 | 5 | 6 | -- padding: number of padding rows/columns around QR code 7 | local function matrix_to_string( tab, padding,padding_char,white_pixel,black_pixel ) 8 | local padding_string 9 | local str_tab = {} -- hold each row of the qr code in a cell 10 | 11 | -- Add (padding) blank columns at the left of the matrix 12 | -- (left of each row string), inserting an extra (padding) 13 | -- rows at the top and bottom 14 | padding_string = string.rep(padding_char,padding) 15 | for i=1,#tab + 2*padding do 16 | str_tab[i] = padding_string 17 | end 18 | 19 | for x=1,#tab do 20 | for y=1,#tab do 21 | if tab[x][y] > 0 then 22 | -- using y + padding because we added (padding) blank columns at the left for each string in str_tab array 23 | str_tab[y + padding] = str_tab[y + padding] .. black_pixel 24 | elseif tab[x][y] < 0 then 25 | str_tab[y + padding] = str_tab[y + padding] .. white_pixel 26 | else 27 | str_tab[y + padding] = str_tab[y + padding] .. " X" 28 | end 29 | end 30 | end 31 | 32 | padding_string = string.rep(padding_char,#tab) 33 | for i=1,padding do 34 | -- fills in padding rows at top of matrix 35 | str_tab[i] = str_tab[i] .. padding_string 36 | -- fills in padding rows at bottom of matrix 37 | str_tab[#tab + padding + i] = str_tab[#tab + padding + i] .. padding_string 38 | end 39 | 40 | -- Add (padding) blank columns to right of matrix (to the end of each row string) 41 | padding_string = string.rep(padding_char,padding) 42 | for i=1,#tab + 2*padding do 43 | str_tab[i] = str_tab[i] .. padding_string 44 | end 45 | 46 | return str_tab 47 | end 48 | 49 | 50 | local use_ansi = false 51 | local padding = 1 52 | local padding_char 53 | local black_pixel = "X" 54 | local white_pixel = " " 55 | local codeword 56 | 57 | while true do 58 | if arg[1] == nil then 59 | break 60 | elseif arg[1] == "-h" or arg[1] == "--help" then 61 | codeword = nil 62 | break 63 | elseif arg[1] == "-a" then 64 | use_ansi = true 65 | elseif arg[1] == "-p" then 66 | padding = arg[2] 67 | table.remove(arg,2) 68 | elseif arg[1] == "-c" then 69 | padding_char = arg[2] 70 | table.remove(arg,2) 71 | elseif arg[1] == "-b" then 72 | black_pixel = arg[2] 73 | table.remove(arg,2) 74 | elseif arg[1] == "-w" then 75 | white_pixel = arg[2] 76 | table.remove(arg,2) 77 | else 78 | codeword = arg[1] 79 | end 80 | table.remove(arg,1) 81 | end 82 | 83 | if use_ansi then 84 | black_pixel = "\27[40m \27[0m" 85 | white_pixel = "\27[1;47m \27[0m" 86 | end 87 | 88 | padding_char = padding_char or white_pixel 89 | 90 | if codeword then 91 | local ok, tab_or_message = qrencode.qrcode(codeword) 92 | if not ok then 93 | print(tab_or_message) 94 | else 95 | local rows 96 | rows = matrix_to_string(tab_or_message,padding,padding_char,white_pixel,black_pixel) 97 | for i=1,#rows do -- prints each "row" of the QR code on a line, one at a time 98 | print(rows[i]) 99 | end 100 | end 101 | else 102 | print("Usage:") 103 | print(arg[0] .. " [-a] [-p ] [-c ] [-b ] [-w ] ") 104 | print("-a : use ansi colors (don't do this on a dos box)") 105 | print("-p : use padding of width (default: 1)") 106 | print("-b : use for black pixel (default: 'X')") 107 | print("-w : use for white pixel (default: ' ')") 108 | print("-c : use for padding (default: the white pixel)") 109 | end 110 | -------------------------------------------------------------------------------- /locco/locco.css: -------------------------------------------------------------------------------- 1 | /*--------------------- Layout and Typography ----------------------------*/ 2 | img { 3 | max-width: 100%; 4 | } 5 | 6 | body { 7 | font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; 8 | font-size: 15px; 9 | line-height: 22px; 10 | color: #252519; 11 | margin: 0; padding: 0; 12 | } 13 | a { 14 | color: #261a3b; 15 | } 16 | a:visited { 17 | color: #261a3b; 18 | } 19 | p { 20 | margin: 0 0 15px 0; 21 | } 22 | h1, h2, h3, h4, h5, h6 { 23 | margin: 0px 0 15px 0; 24 | line-height: 110%; 25 | } 26 | h1 { 27 | margin-top: 40px; 28 | } 29 | #container { 30 | position: relative; 31 | } 32 | #background { 33 | position: fixed; 34 | top: 0; left: 525px; right: 0; bottom: 0; 35 | background: #f5f5ff; 36 | border-left: 1px solid #e5e5ee; 37 | z-index: -1; 38 | } 39 | #jump_to, #jump_page { 40 | background: white; 41 | -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; 42 | -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; 43 | font: 10px Arial; 44 | text-transform: uppercase; 45 | cursor: pointer; 46 | text-align: right; 47 | } 48 | #jump_to, #jump_wrapper { 49 | position: fixed; 50 | right: 0; top: 0; 51 | padding: 5px 10px; 52 | } 53 | #jump_wrapper { 54 | padding: 0; 55 | display: none; 56 | } 57 | #jump_to:hover #jump_wrapper { 58 | display: block; 59 | } 60 | #jump_page { 61 | padding: 5px 0 3px; 62 | margin: 0 0 25px 25px; 63 | } 64 | #jump_page .source { 65 | display: block; 66 | padding: 5px 10px; 67 | text-decoration: none; 68 | border-top: 1px solid #eee; 69 | } 70 | #jump_page .source:hover { 71 | background: #f5f5ff; 72 | } 73 | #jump_page .source:first-child { 74 | } 75 | table td { 76 | border: 0; 77 | outline: 0; 78 | } 79 | 80 | td.docs > table { 81 | padding-left: 3px; 82 | padding-right: 3px; 83 | } 84 | 85 | table.body { 86 | vertical-align: top; 87 | text-align: left; 88 | } 89 | 90 | td.docs, th.docs { 91 | max-width: 450px; 92 | min-width: 450px; 93 | min-height: 5px; 94 | padding: 10px 25px 1px 50px; 95 | overflow-x: hidden; 96 | vertical-align: top; 97 | text-align: left; 98 | } 99 | .docs pre { 100 | margin: 15px 0 15px; 101 | padding-left: 15px; 102 | } 103 | .docs p tt, .docs p code { 104 | background: #f8f8ff; 105 | border: 1px solid #dedede; 106 | font-size: 12px; 107 | padding: 0 0.2em; 108 | } 109 | .pilwrap { 110 | position: relative; 111 | } 112 | .pilcrow { 113 | font: 12px Arial; 114 | text-decoration: none; 115 | color: #454545; 116 | position: absolute; 117 | top: 3px; left: -20px; 118 | padding: 1px 2px; 119 | opacity: 0; 120 | -webkit-transition: opacity 0.2s linear; 121 | } 122 | td.docs:hover .pilcrow { 123 | opacity: 1; 124 | } 125 | td.code, th.code { 126 | padding: 14px 15px 16px 25px; 127 | width: 100%; 128 | vertical-align: top; 129 | background: #f5f5ff; 130 | border-left: 1px solid #e5e5ee; 131 | } 132 | pre, tt, code { 133 | font-size: 12px; line-height: 18px; 134 | font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; 135 | margin: 0; padding: 0; 136 | } 137 | 138 | 139 | /*---------------------- Syntax Highlighting -----------------------------*/ 140 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 141 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 142 | body .hll { background-color: #ffffcc } 143 | body .c { color: #408080; font-style: italic } /* Comment */ 144 | body .err { border: 1px solid #FF0000 } /* Error */ 145 | body .k { color: #954121 } /* Keyword */ 146 | body .o { color: #666666 } /* Operator */ 147 | body .cm { color: #408080; font-style: italic } /* Comment.Multiline */ 148 | body .cp { color: #BC7A00 } /* Comment.Preproc */ 149 | body .c1 { color: #408080; font-style: italic } /* Comment.Single */ 150 | body .cs { color: #408080; font-style: italic } /* Comment.Special */ 151 | body .gd { color: #A00000 } /* Generic.Deleted */ 152 | body .ge { font-style: italic } /* Generic.Emph */ 153 | body .gr { color: #FF0000 } /* Generic.Error */ 154 | body .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 155 | body .gi { color: #00A000 } /* Generic.Inserted */ 156 | body .go { color: #808080 } /* Generic.Output */ 157 | body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 158 | body .gs { font-weight: bold } /* Generic.Strong */ 159 | body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 160 | body .gt { color: #0040D0 } /* Generic.Traceback */ 161 | body .kc { color: #954121 } /* Keyword.Constant */ 162 | body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */ 163 | body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */ 164 | body .kp { color: #954121 } /* Keyword.Pseudo */ 165 | body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */ 166 | body .kt { color: #B00040 } /* Keyword.Type */ 167 | body .m { color: #666666 } /* Literal.Number */ 168 | body .s { color: #219161 } /* Literal.String */ 169 | body .na { color: #7D9029 } /* Name.Attribute */ 170 | body .nb { color: #954121 } /* Name.Builtin */ 171 | body .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 172 | body .no { color: #880000 } /* Name.Constant */ 173 | body .nd { color: #AA22FF } /* Name.Decorator */ 174 | body .ni { color: #999999; font-weight: bold } /* Name.Entity */ 175 | body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 176 | body .nf { color: #0000FF } /* Name.Function */ 177 | body .nl { color: #A0A000 } /* Name.Label */ 178 | body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 179 | body .nt { color: #954121; font-weight: bold } /* Name.Tag */ 180 | body .nv { color: #19469D } /* Name.Variable */ 181 | body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 182 | body .w { color: #bbbbbb } /* Text.Whitespace */ 183 | body .mf { color: #666666 } /* Literal.Number.Float */ 184 | body .mh { color: #666666 } /* Literal.Number.Hex */ 185 | body .mi { color: #666666 } /* Literal.Number.Integer */ 186 | body .mo { color: #666666 } /* Literal.Number.Oct */ 187 | body .sb { color: #219161 } /* Literal.String.Backtick */ 188 | body .sc { color: #219161 } /* Literal.String.Char */ 189 | body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */ 190 | body .s2 { color: #219161 } /* Literal.String.Double */ 191 | body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 192 | body .sh { color: #219161 } /* Literal.String.Heredoc */ 193 | body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 194 | body .sx { color: #954121 } /* Literal.String.Other */ 195 | body .sr { color: #BB6688 } /* Literal.String.Regex */ 196 | body .s1 { color: #219161 } /* Literal.String.Single */ 197 | body .ss { color: #19469D } /* Literal.String.Symbol */ 198 | body .bp { color: #954121 } /* Name.Builtin.Pseudo */ 199 | body .vc { color: #19469D } /* Name.Variable.Class */ 200 | body .vg { color: #19469D } /* Name.Variable.Global */ 201 | body .vi { color: #19469D } /* Name.Variable.Instance */ 202 | body .il { color: #666666 } /* Literal.Number.Integer.Long */ 203 | -------------------------------------------------------------------------------- /locco/template.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M.header = [[ 4 | 5 | 6 | 7 | %title% 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | %jump% 16 | 17 | 18 | 19 | 24 | 26 | 27 | 28 | 29 | ]] 30 | 31 | M.jump_start = [[ 32 |
33 | Jump To … 34 |
35 |
36 | ]] 37 | 38 | M.jump = [[ 39 | %jump_lua% 40 | ]] 41 | 42 | M.jump_end = [[ 43 |
44 |
45 |
46 | ]] 47 | 48 | M.table_entry = [[ 49 | 50 | 56 | 59 | ]] 60 | 61 | M.footer = [[ 62 |
20 |

21 | %title% 22 |

23 |
25 |
51 |
52 | 53 |
54 | %docs_html% 55 |
57 | %code_html% 58 |
63 |
64 | 65 | ]] 66 | 67 | M.css = [[/*--------------------- Layout and Typography ----------------------------*/ 68 | body { 69 | font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; 70 | font-size: 15px; 71 | line-height: 22px; 72 | color: #252519; 73 | margin: 0; padding: 0; 74 | } 75 | a { 76 | color: #261a3b; 77 | } 78 | a:visited { 79 | color: #261a3b; 80 | } 81 | p { 82 | margin: 0 0 15px 0; 83 | } 84 | h1, h2, h3, h4, h5, h6 { 85 | margin: 0px 0 15px 0; 86 | } 87 | h1 { 88 | margin-top: 40px; 89 | } 90 | #container { 91 | position: relative; 92 | } 93 | #background { 94 | position: fixed; 95 | top: 0; left: 525px; right: 0; bottom: 0; 96 | background: #f5f5ff; 97 | border-left: 1px solid #e5e5ee; 98 | z-index: -1; 99 | } 100 | #jump_to, #jump_page { 101 | background: white; 102 | -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; 103 | -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; 104 | font: 10px Arial; 105 | text-transform: uppercase; 106 | cursor: pointer; 107 | text-align: right; 108 | } 109 | #jump_to, #jump_wrapper { 110 | position: fixed; 111 | right: 0; top: 0; 112 | padding: 5px 10px; 113 | } 114 | #jump_wrapper { 115 | padding: 0; 116 | display: none; 117 | } 118 | #jump_to:hover #jump_wrapper { 119 | display: block; 120 | } 121 | #jump_page { 122 | padding: 5px 0 3px; 123 | margin: 0 0 25px 25px; 124 | } 125 | #jump_page .source { 126 | display: block; 127 | padding: 5px 10px; 128 | text-decoration: none; 129 | border-top: 1px solid #eee; 130 | } 131 | #jump_page .source:hover { 132 | background: #f5f5ff; 133 | } 134 | #jump_page .source:first-child { 135 | } 136 | table td { 137 | border: 0; 138 | outline: 0; 139 | } 140 | td.docs, th.docs { 141 | max-width: 450px; 142 | min-width: 450px; 143 | min-height: 5px; 144 | padding: 10px 25px 1px 50px; 145 | overflow-x: hidden; 146 | vertical-align: top; 147 | text-align: left; 148 | } 149 | .docs pre { 150 | margin: 15px 0 15px; 151 | padding-left: 15px; 152 | } 153 | .docs p tt, .docs p code { 154 | background: #f8f8ff; 155 | border: 1px solid #dedede; 156 | font-size: 12px; 157 | padding: 0 0.2em; 158 | } 159 | .pilwrap { 160 | position: relative; 161 | } 162 | .pilcrow { 163 | font: 12px Arial; 164 | text-decoration: none; 165 | color: #454545; 166 | position: absolute; 167 | top: 3px; left: -20px; 168 | padding: 1px 2px; 169 | opacity: 0; 170 | -webkit-transition: opacity 0.2s linear; 171 | } 172 | td.docs:hover .pilcrow { 173 | opacity: 1; 174 | } 175 | td.code, th.code { 176 | padding: 14px 15px 16px 25px; 177 | width: 100%; 178 | vertical-align: top; 179 | background: #f5f5ff; 180 | border-left: 1px solid #e5e5ee; 181 | } 182 | pre, tt, code { 183 | font-size: 12px; line-height: 18px; 184 | font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; 185 | margin: 0; padding: 0; 186 | } 187 | 188 | 189 | /*---------------------- Syntax Highlighting -----------------------------*/ 190 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 191 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 192 | body .hll { background-color: #ffffcc } 193 | body .c { color: #408080; font-style: italic } /* Comment */ 194 | body .err { border: 1px solid #FF0000 } /* Error */ 195 | body .k { color: #954121 } /* Keyword */ 196 | body .o { color: #666666 } /* Operator */ 197 | body .cm { color: #408080; font-style: italic } /* Comment.Multiline */ 198 | body .cp { color: #BC7A00 } /* Comment.Preproc */ 199 | body .c1 { color: #408080; font-style: italic } /* Comment.Single */ 200 | body .cs { color: #408080; font-style: italic } /* Comment.Special */ 201 | body .gd { color: #A00000 } /* Generic.Deleted */ 202 | body .ge { font-style: italic } /* Generic.Emph */ 203 | body .gr { color: #FF0000 } /* Generic.Error */ 204 | body .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 205 | body .gi { color: #00A000 } /* Generic.Inserted */ 206 | body .go { color: #808080 } /* Generic.Output */ 207 | body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 208 | body .gs { font-weight: bold } /* Generic.Strong */ 209 | body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 210 | body .gt { color: #0040D0 } /* Generic.Traceback */ 211 | body .kc { color: #954121 } /* Keyword.Constant */ 212 | body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */ 213 | body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */ 214 | body .kp { color: #954121 } /* Keyword.Pseudo */ 215 | body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */ 216 | body .kt { color: #B00040 } /* Keyword.Type */ 217 | body .m { color: #666666 } /* Literal.Number */ 218 | body .s { color: #219161 } /* Literal.String */ 219 | body .na { color: #7D9029 } /* Name.Attribute */ 220 | body .nb { color: #954121 } /* Name.Builtin */ 221 | body .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 222 | body .no { color: #880000 } /* Name.Constant */ 223 | body .nd { color: #AA22FF } /* Name.Decorator */ 224 | body .ni { color: #999999; font-weight: bold } /* Name.Entity */ 225 | body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 226 | body .nf { color: #0000FF } /* Name.Function */ 227 | body .nl { color: #A0A000 } /* Name.Label */ 228 | body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 229 | body .nt { color: #954121; font-weight: bold } /* Name.Tag */ 230 | body .nv { color: #19469D } /* Name.Variable */ 231 | body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 232 | body .w { color: #bbbbbb } /* Text.Whitespace */ 233 | body .mf { color: #666666 } /* Literal.Number.Float */ 234 | body .mh { color: #666666 } /* Literal.Number.Hex */ 235 | body .mi { color: #666666 } /* Literal.Number.Integer */ 236 | body .mo { color: #666666 } /* Literal.Number.Oct */ 237 | body .sb { color: #219161 } /* Literal.String.Backtick */ 238 | body .sc { color: #219161 } /* Literal.String.Char */ 239 | body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */ 240 | body .s2 { color: #219161 } /* Literal.String.Double */ 241 | body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 242 | body .sh { color: #219161 } /* Literal.String.Heredoc */ 243 | body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 244 | body .sx { color: #954121 } /* Literal.String.Other */ 245 | body .sr { color: #BB6688 } /* Literal.String.Regex */ 246 | body .s1 { color: #219161 } /* Literal.String.Single */ 247 | body .ss { color: #19469D } /* Literal.String.Symbol */ 248 | body .bp { color: #954121 } /* Name.Builtin.Pseudo */ 249 | body .vc { color: #19469D } /* Name.Variable.Class */ 250 | body .vg { color: #19469D } /* Name.Variable.Global */ 251 | body .vi { color: #19469D } /* Name.Variable.Instance */ 252 | body .il { color: #666666 } /* Literal.Number.Integer.Long */ 253 | ]] 254 | 255 | return M 256 | -------------------------------------------------------------------------------- /locco/locco.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | -- __Locco__ is a Lua port of [Docco](http://jashkenas.github.com/docco/), 3 | -- the quick-and-dirty, hundred-line-long, literate-programming-style 4 | -- documentation generator. It produces HTML that displays your comments 5 | -- alongside your code. Comments are passed through 6 | -- [Markdown](http://daringfireball.net/projects/markdown/), and code is 7 | -- syntax highlighted. 8 | -- This page is the result of running Locco against its own source file: 9 | -- locco.lua locco.lua 10 | -- 11 | -- For its syntax highlighting Locco relies on the help of 12 | -- [David Manura](http://lua-users.org/wiki/DavidManura)'s 13 | -- [Lua Balanced](https://github.com/davidm/lua-balanced) to split 14 | -- up the code. As a markdown engine it ships with 15 | -- [Niklas Frykholm](http://www.frykholm.se/)'s 16 | -- [markdown.lua](http://www.frykholm.se/files/markdown.lua) in the [Lua 5.2 17 | -- compatible version](https://github.com/speedata/luamarkdown) from 18 | -- [Patrick Gundlach](https://github.com/pgundlach). Otherwise there 19 | -- are no external dependencies. 20 | -- 21 | -- The generated HTML documentation for the given source files is saved 22 | -- into a `docs` directory. If you have Locco on your path you can run it from 23 | -- the command-line: 24 | -- locco.lua project/*.lua 25 | -- 26 | -- Locco is monolingual, but there are many projects written in 27 | -- and with support for other languages, see the 28 | -- [Docco](http://jashkenas.github.com/docco/) page for a list.
29 | -- The [source for Locco](https://github.com/rgieseke/locco) is available on 30 | -- GitHub, and released under the MIT 31 | -- license. 32 | 33 | -- ### Setup & Helpers 34 | 35 | -- Add script path to package path to find submodules. 36 | local script_path = arg[0]:match('(.+)/.+') 37 | package.path = table.concat({ 38 | script_path..'/?.lua', 39 | package.path 40 | }, ';') 41 | 42 | -- Load markdown.lua. 43 | local md = require 'markdown' 44 | -- Load Lua Balanced. 45 | local lb = require 'luabalanced' 46 | -- Load HTML templates. 47 | local template = require 'template' 48 | 49 | -- Ensure the `docs` directory exists and return the _path_ of the source file.
50 | -- Parameter:
51 | -- _source_: The source file for which documentation is generated.
52 | local function ensure_directory(source) 53 | local path = source:match('(.+)/.+$') 54 | if not path then path = '.' end 55 | os.execute('mkdir -p '..path..'/ghpages/docs') 56 | return path 57 | end 58 | 59 | -- Insert HTML entities in a string.
60 | -- Parameter:
61 | -- _s_: String to escape.
62 | local function escape(s) 63 | s = s:gsub('&', '&') 64 | s = s:gsub('<', '<') 65 | s = s:gsub('>', '>') 66 | s = s:gsub('%%', '%') 67 | return s 68 | end 69 | 70 | --local function replace_percent(s) 71 | -- s = s:gsub('%%', '%%%%') 72 | -- return s 73 | --end 74 | 75 | -- Define the Lua keywords, built-in functions and operators that should 76 | -- be highlighted. 77 | local keywords = { 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 78 | 'function', 'if', 'in', 'local', 'nil', 'repeat', 'return', 79 | 'then', 'true', 'until', 'while' } 80 | local functions = { 'assert', 'collectgarbage', 'dofile', 'error', 'getfenv', 81 | 'getmetatable', 'ipairs', 'load', 'loadfile', 'loadstring', 82 | 'module', 'next', 'pairs', 'pcall', 'print', 'rawequal', 83 | 'rawget', 'rawset', 'require', 'setfenv', 'setmetatable', 84 | 'tonumber', 'tostring', 'type', 'unpack', 'xpcall' } 85 | local operators = { 'and', 'not', 'or' } 86 | 87 | -- Wrap an item from a list of Lua keywords in a span template or return the 88 | -- unchanged item.
89 | -- Parameters:
90 | -- _item_: An item of a code snippet.
91 | -- _item\_list_: List of keywords or functions.
92 | -- _span\_class_: Style sheet class.
93 | local function wrap_in_span(item, item_list, span_class) 94 | for i=1, #item_list do 95 | if item_list[i] == item then 96 | item = ''..item..'' 97 | break 98 | end 99 | end 100 | return item 101 | end 102 | 103 | -- Quick and dirty source code highlighting. A chunk of code is split into 104 | -- comments (at the end of a line), strings and code using the 105 | -- [Lua Balanced](https://github.com/davidm/lua-balanced/blob/master/luabalanced.lua) 106 | -- module. The code is then split again and matched against lists 107 | -- of Lua keywords, functions or operators. All Lua items are wrapped into 108 | -- a span having one of the classes defined in the Locco style sheet.
109 | -- Parameter:
110 | -- _code_: Chunk of code to highlight.
111 | local function highlight_lua(code) 112 | local out = lb.gsub(code, 113 | function(u, s) 114 | local sout 115 | if u == 'c' then -- Comments. 116 | sout = ''..escape(s)..'' 117 | elseif u == 's' then -- Strings. 118 | sout = ''..escape(s)..'' 119 | elseif u == 'e' then -- Code. 120 | s = escape(s) 121 | -- First highlight function names. 122 | s = s:gsub('function ([%w_:%.]+)', 'function %1') 123 | -- There might be a non-keyword at the beginning of the snippet. 124 | sout = s:match('^(%A+)') or '' 125 | -- Iterate through Lua items and try to wrap operators, 126 | -- keywords and built-in functions in span elements. 127 | -- If nothing was highlighted go to the next category. 128 | for item, sep in s:gmatch('([%a_]+)(%A+)') do 129 | local span, n = wrap_in_span(item, operators, 'o') 130 | if span == item then 131 | span, n = wrap_in_span(item, keywords, 'k') 132 | end 133 | if span == item then 134 | span, n = wrap_in_span(item, functions, 'nt') 135 | end 136 | sout = sout..span..sep 137 | end 138 | end 139 | return sout 140 | end) 141 | out = '
'..out..'
' 142 | return out 143 | end 144 | 145 | 146 | -- ### Main Documentation Generation Functions 147 | 148 | -- Given a string of source code, parse out each comment and the code that 149 | -- follows it, and create an individual section for it. Sections take the form: 150 | -- 151 | -- { 152 | -- docs_text = ..., 153 | -- docs_html = ..., 154 | -- code_text = ..., 155 | -- code_html = ..., 156 | -- } 157 | -- 158 | -- Parameter:
159 | -- _source_: The source file to process.
160 | local function parse(source) 161 | local sections = {} 162 | local has_code = false 163 | local docs_text, code_text = '', '' 164 | for line in io.lines(source) do 165 | if line:match('^%s*%-%-%-') then 166 | if has_code then 167 | code_text = code_text:gsub('\n\n$', '\n') -- remove empty trailing line 168 | sections[#sections + 1] = { ['docs_text'] = docs_text, 169 | ['code_text'] = code_text } 170 | has_code = false 171 | docs_text, code_text = '', '' 172 | end 173 | docs_text = docs_text..line:gsub('%s*(%-%-%-%s?)', '', 1)..'\n' 174 | else 175 | if not line:match('^#!') then -- ignore #!/usr/bin/lua 176 | has_code = true 177 | code_text = code_text..line..'\n' 178 | end 179 | end 180 | end 181 | sections[#sections + 1] = { ['docs_text'] = docs_text, 182 | ['code_text'] = code_text } 183 | return sections 184 | end 185 | 186 | -- Loop through a table of split sections and convert the documentation 187 | -- from Markdown to HTML and pass the code through Locco's syntax 188 | -- highlighting. Add _docs\_html_ and _code\_html_ elements to the sections 189 | -- table.
190 | -- Parameter:
191 | -- _sections_: A table with split sections.
192 | local function highlight(sections) 193 | for i=1, #sections do 194 | sections[i]['docs_html'] = md.markdown(sections[i]['docs_text']) 195 | sections[i]['code_html'] = highlight_lua(sections[i]['code_text']) 196 | end 197 | return sections 198 | end 199 | 200 | -- After the highlighting is done, the template is filled with the documentation 201 | -- and code snippets and an HTML file is written.
202 | -- Parameters:
203 | -- _source_: The source file.
204 | -- _path_: Path of the source file.
205 | -- _filename_: The filename of the source file.
206 | -- _sections_: A table with the original sections and rendered as HTML.
207 | -- _jump\_to_: A HTML chunk with links to other documentation files. 208 | local function generate_html(source, path, filename, sections, jump_to) 209 | local f, err = io.open(path..'/ghpages/docs/'..filename:gsub('lua$', 'html'), 'wb') 210 | if err then print(err) end 211 | local h = template.header:gsub('%%title%%', source) 212 | h = h:gsub('%%jump%%', jump_to) 213 | f:write(h) 214 | for i=1, #sections do 215 | local t = template.table_entry:gsub('%%index%%', i..'') 216 | t = t:gsub('%%docs_html%%', sections[i]['docs_html']) 217 | t = t:gsub('%%code_html%%', sections[i]['code_html']) 218 | f:write(t) 219 | end 220 | f:write(template.footer) 221 | f:close() 222 | end 223 | 224 | -- Generate the documentation for a source file by reading it in, 225 | -- splitting it up into comment/code sections, highlighting and merging 226 | -- them into an HTML template.
227 | -- Parameters:
228 | -- _source_: The source file to process.
229 | -- _path_: Path of the source file.
230 | -- _filename_: The filename of the source file.
231 | -- _jump\_to_: A HTML chunk with links to other documentation files. 232 | local function generate_documentation(source, path, filename, jump_to) 233 | local sections = parse(source) 234 | local sections = highlight(sections) 235 | generate_html(source, path, filename, sections, jump_to) 236 | end 237 | 238 | 239 | -- Run the script. 240 | 241 | -- Generate HTML links to other files in the documentation. 242 | local jump_to = '' 243 | if #arg > 1 then 244 | jump_to = template.jump_start 245 | for i=1, #arg do 246 | local link = arg[i]:gsub('lua$', 'html') 247 | link = link:match('.+/(.+)$') or link 248 | local t = template.jump:gsub('%%jump_html%%', link) 249 | t = t:gsub('%%jump_lua%%', arg[i]) 250 | jump_to = jump_to..t 251 | end 252 | jump_to = jump_to..template.jump_end 253 | end 254 | 255 | -- Make sure the output directory exists, generate the HTML files for each 256 | -- source file, print what's happening and write the style sheet. 257 | local path = ensure_directory(arg[1]) 258 | for i=1, #arg do 259 | local filename = arg[i]:match('.+/(.+)$') or arg[i] 260 | generate_documentation(arg[i], path, filename, jump_to) 261 | print(arg[i]..' --> '..path..'/ghpages/docs/'..filename:gsub('lua$', 'html')) 262 | end 263 | local f, err = io.open(path..'/'..'ghpages/docs/locco.css', 'wb') 264 | if err then print(err) end 265 | f:write(template.css) 266 | f:close() 267 | -------------------------------------------------------------------------------- /locco/luabalanced.lua: -------------------------------------------------------------------------------- 1 | --[==[ 2 | 3 | LUA MODULE 4 | 5 | luabalanced v$(_VERSION) - Functions for matching delimited snippets of Lua code in a string 6 | 7 | SYNOPSIS 8 | 9 | local LB = require "luabalanced" 10 | -- Extract Lua expression starting at position 4. 11 | print(LB.match_expression("if x^2 + x > 5 then print(x) end", 4)) 12 | --> x^2 + x > 5 16 13 | -- Extract Lua string starting at (default) position 1. 14 | print(LB.match_string([["test\"123" .. "more"]])) 15 | --> "test\"123" 12 16 | -- Break Lua code into code types. 17 | LB.gsub([[ 18 | local x = 1 -- test 19 | print("x=", x) 20 | ]], function(u, s) 21 | print(u .. '[' .. s .. ']') 22 | end) 23 | --[[output: 24 | e[ local x = 1 ] 25 | c[-- test 26 | ] 27 | e[ print(] 28 | s["x="] 29 | e[, x) 30 | ] 31 | ]] 32 | 33 | DESCRIPTION 34 | 35 | This module can, for example, match a Lua string, Lua comment, or Lua 36 | expression. It is useful in particular for source filters or parsing 37 | Lua snippets embedded in another language. It is inspired by Damian Conway's 38 | Text::Balanced [1] in Perl. The unique feature of this implementation 39 | is that that it does not rigorously lex and parse the Lua grammar. 40 | It doesn't need to. It assumes during the parse that the Lua code is 41 | syntactically correct (which can be verified later using loadstring). 42 | By assuming this, extraction of delimited sequences is significantly 43 | simplified yet can still be robust, and it also supports supersets 44 | of the Lua grammar. The code, which is written entirely in Lua, 45 | is just under 200 lines of Lua code (compare to Yueliang used in 46 | MetaLua, where the lexer alone is a few hundred lines). 47 | 48 | API 49 | 50 | LB.match_string(s, pos) --> string, posnew 51 | 52 | Match Lua string in string starting at position `pos`. 53 | Returns `string`, `posnew`, where `string` is the matched 54 | string (or nil on no match) and `posnew` is the character 55 | following the match (or `pos` on no match). 56 | Supports all Lua string syntax: "...", '...', [[...]], [=[...]=], etc. 57 | 58 | LB.match_bracketed(s, pos) --> string, posnew 59 | 60 | Match bracketed Lua expression, e.g. "(...)", "{...}", "[...]", "[[...]]", 61 | [=[...]=], etc. 62 | Function interface is similar to `match_string`. 63 | 64 | LB.match_comment(s, pos) --> string, posnew 65 | 66 | Match Lua comment, e.g. "--...\n", "--[[...]]", "--[=[...]=]", etc. 67 | Function interface is similar to `match_string`. 68 | 69 | LB.match_expression(s, pos) --> string, posnew 70 | 71 | Match Lua expression, e.g. "a + b * c[e]". 72 | Function interface is similar to match_string. 73 | 74 | LB.match_namelist(s, pos) --> array, posnew 75 | 76 | Match name list (zero or more names). E.g. "a,b,c" 77 | Function interface is similar to match_string, 78 | but returns array as match. 79 | 80 | M.match_explist(s, pos) --> array, posnew 81 | 82 | Match expression list (zero or more expressions). E.g. "a+b,b*c". 83 | Function interface is similar to match_string, 84 | but returns array as match. 85 | 86 | M.gsub(s, f) 87 | 88 | Replace snippets of code in Lua code string `s` 89 | using replacement function `f(u,sin) --> sout`. 90 | `u` is the type of snippet ('c' = comment, 's' = string, 91 | 'e' = any other code). 92 | Snippet is replaced with `sout` (unless `sout` is `nil` or `false`, in 93 | which case the original snippet is kept) 94 | This is somewhat analogous to `string.gsub`. 95 | 96 | DEPENDENCIES 97 | 98 | None (other than Lua 5.1 or 5.2). 99 | 100 | HOME PAGE 101 | 102 | http://lua-users.org/wiki/LuaBalanced 103 | https://github.com/davidm/lua-balanced 104 | 105 | DOWNLOAD/INSTALL 106 | 107 | If using LuaRocks: 108 | luarocks install lua-balanced 109 | 110 | Otherwise, download 111 | and unzip. Alternately, if using git: 112 | git clone git://github.com/davidm/lua-balanced.git 113 | cd lua-balanced 114 | Optionally unpack: 115 | ./util.mk 116 | or unpack and install in LuaRocks: 117 | ./util.mk install 118 | 119 | REFERENCES 120 | 121 | [1] http://lua-users.org/wiki/LuaBalanced 122 | [2] http://search.cpan.org/dist/Text-Balanced/lib/Text/Balanced.pm 123 | 124 | LICENSE 125 | 126 | (c) 2008-2011 David Manura. Licensed under the same terms as Lua (MIT). 127 | 128 | Permission is hereby granted, free of charge, to any person obtaining a copy 129 | of this software and associated documentation files (the "Software"), to deal 130 | in the Software without restriction, including without limitation the rights 131 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 132 | copies of the Software, and to permit persons to whom the Software is 133 | furnished to do so, subject to the following conditions: 134 | 135 | The above copyright notice and this permission notice shall be included in 136 | all copies or substantial portions of the Software. 137 | 138 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 139 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 140 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 141 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 142 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 143 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 144 | THE SOFTWARE. 145 | (end license) 146 | 147 | --]==]--------------------------------------------------------------------- 148 | 149 | local M = {_TYPE='module', _NAME='luabalanaced', _VERSION='0.1.1.20120323'} 150 | 151 | local assert = assert 152 | 153 | -- map opening brace <-> closing brace. 154 | local ends = { ['('] = ')', ['{'] = '}', ['['] = ']' } 155 | local begins = {}; for k,v in pairs(ends) do begins[v] = k end 156 | 157 | 158 | local function match_string(s, pos) 159 | pos = pos or 1 160 | local posa = pos 161 | local c = s:sub(pos,pos) 162 | if c == '"' or c == "'" then 163 | pos = pos + 1 164 | while 1 do 165 | pos = assert(s:find("[" .. c .. "\\]", pos), 'syntax error') 166 | if s:sub(pos,pos) == c then 167 | local part = s:sub(posa, pos) 168 | return part, pos + 1 169 | else 170 | pos = pos + 2 171 | end 172 | end 173 | else 174 | local sc = s:match("^%[(=*)%[", pos) 175 | if sc then 176 | local _; _, pos = s:find("%]" .. sc .. "%]", pos) 177 | assert(pos) 178 | local part = s:sub(posa, pos) 179 | return part, pos + 1 180 | else 181 | return nil, pos 182 | end 183 | end 184 | end 185 | M.match_string = match_string 186 | 187 | 188 | local function match_bracketed(s, pos) 189 | pos = pos or 1 190 | local posa = pos 191 | local ca = s:sub(pos,pos) 192 | if not ends[ca] then 193 | return nil, pos 194 | end 195 | local stack = {} 196 | while 1 do 197 | pos = s:find('[%(%{%[%)%}%]\"\']', pos) 198 | assert(pos, 'syntax error: unbalanced') 199 | local c = s:sub(pos,pos) 200 | if c == '"' or c == "'" then 201 | local part; part, pos = match_string(s, pos) 202 | assert(part) 203 | elseif ends[c] then -- open 204 | local mid, posb 205 | if c == '[' then mid, posb = s:match('^%[(=*)%[()', pos) end 206 | if mid then 207 | pos = s:match('%]' .. mid .. '%]()', posb) 208 | assert(pos, 'syntax error: long string not terminated') 209 | if #stack == 0 then 210 | local part = s:sub(posa, pos-1) 211 | return part, pos 212 | end 213 | else 214 | stack[#stack+1] = c 215 | pos = pos + 1 216 | end 217 | else -- close 218 | assert(stack[#stack] == assert(begins[c]), 'syntax error: unbalanced') 219 | stack[#stack] = nil 220 | if #stack == 0 then 221 | local part = s:sub(posa, pos) 222 | return part, pos+1 223 | end 224 | pos = pos + 1 225 | end 226 | end 227 | end 228 | M.match_bracketed = match_bracketed 229 | 230 | 231 | local function match_comment(s, pos) 232 | pos = pos or 1 233 | if s:sub(pos, pos+1) ~= '--' then 234 | return nil, pos 235 | end 236 | pos = pos + 2 237 | local partt, post = match_string(s, pos) 238 | if partt then 239 | return '--' .. partt, post 240 | end 241 | local part; part, pos = s:match('^([^\n]*\n?)()', pos) 242 | return '--' .. part, pos 243 | end 244 | 245 | 246 | local wordop = {['and']=true, ['or']=true, ['not']=true} 247 | local is_compare = {['>']=true, ['<']=true, ['~']=true} 248 | local function match_expression(s, pos) 249 | pos = pos or 1 250 | local posa = pos 251 | local lastident 252 | local poscs, posce 253 | while pos do 254 | local c = s:sub(pos,pos) 255 | if c == '"' or c == "'" or c == '[' and s:find('^[=%[]', pos+1) then 256 | local part; part, pos = match_string(s, pos) 257 | assert(part, 'syntax error') 258 | elseif c == '-' and s:sub(pos+1,pos+1) == '-' then 259 | -- note: handle adjacent comments in loop to properly support 260 | -- backtracing (poscs/posce). 261 | poscs = pos 262 | while s:sub(pos,pos+1) == '--' do 263 | local part; part, pos = match_comment(s, pos) 264 | assert(part) 265 | pos = s:match('^%s*()', pos) 266 | posce = pos 267 | end 268 | elseif c == '(' or c == '{' or c == '[' then 269 | local part; part, pos = match_bracketed(s, pos) 270 | elseif c == '=' and s:sub(pos+1,pos+1) == '=' then 271 | pos = pos + 2 -- skip over two-char op containing '=' 272 | elseif c == '=' and is_compare[s:sub(pos-1,pos-1)] then 273 | pos = pos + 1 -- skip over two-char op containing '=' 274 | elseif c:match'^[%)%}%];,=]' then 275 | local part = s:sub(posa, pos-1) 276 | return part, pos 277 | elseif c:match'^[%w_]' then 278 | local newident,newpos = s:match('^([%w_]+)()', pos) 279 | if pos ~= posa and not wordop[newident] then -- non-first ident 280 | local pose = ((posce == pos) and poscs or pos) - 1 281 | while s:match('^%s', pose) do pose = pose - 1 end 282 | local ce = s:sub(pose,pose) 283 | if ce:match'[%)%}\'\"%]]' or 284 | ce:match'[%w_]' and not wordop[lastident] 285 | then 286 | local part = s:sub(posa, pos-1) 287 | return part, pos 288 | end 289 | end 290 | lastident, pos = newident, newpos 291 | else 292 | pos = pos + 1 293 | end 294 | pos = s:find('[%(%{%[%)%}%]\"\';,=%w_%-]', pos) 295 | end 296 | local part = s:sub(posa, #s) 297 | return part, #s+1 298 | end 299 | M.match_expression = match_expression 300 | 301 | 302 | local function match_namelist(s, pos) 303 | pos = pos or 1 304 | local list = {} 305 | while 1 do 306 | local c = #list == 0 and '^' or '^%s*,%s*' 307 | local item, post = s:match(c .. '([%a_][%w_]*)%s*()', pos) 308 | if item then pos = post else break end 309 | list[#list+1] = item 310 | end 311 | return list, pos 312 | end 313 | M.match_namelist = match_namelist 314 | 315 | 316 | local function match_explist(s, pos) 317 | pos = pos or 1 318 | local list = {} 319 | while 1 do 320 | if #list ~= 0 then 321 | local post = s:match('^%s*,%s*()', pos) 322 | if post then pos = post else break end 323 | end 324 | local item; item, pos = match_expression(s, pos) 325 | assert(item, 'syntax error') 326 | list[#list+1] = item 327 | end 328 | return list, pos 329 | end 330 | M.match_explist = match_explist 331 | 332 | 333 | local function gsub(s, f) 334 | local pos = 1 335 | local posa = 1 336 | local ts = {} 337 | while 1 do 338 | pos = s:find('[%-\'\"%[]', pos) 339 | if not pos then break end 340 | if s:match('^%-%-', pos) then 341 | local exp = s:sub(posa, pos-1) 342 | if #exp > 0 then ts[#ts+1] = (f('e', exp) or exp) end 343 | local comment; comment, pos = match_comment(s, pos) 344 | ts[#ts+1] = (f('c', assert(comment)) or comment) 345 | posa = pos 346 | else 347 | local posb = s:find('^[\'\"%[]', pos) 348 | local str 349 | if posb then str, pos = match_string(s, posb) end 350 | if str then 351 | local exp = s:sub(posa, posb-1) 352 | if #exp > 0 then ts[#ts+1] = (f('e', exp) or exp) end 353 | ts[#ts+1] = (f('s', str) or str) 354 | posa = pos 355 | else 356 | pos = pos + 1 357 | end 358 | end 359 | end 360 | local exp = s:sub(posa) 361 | if #exp > 0 then ts[#ts+1] = (f('e', exp) or exp) end 362 | return table.concat(ts) 363 | end 364 | M.gsub = gsub 365 | 366 | 367 | return M 368 | -------------------------------------------------------------------------------- /qrtest.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | 4 | local function err( ... ) 5 | print(string.format(...)) 6 | end 7 | 8 | local failed = false 9 | local function assert_equal( a,b,func ) 10 | if a ~= b then 11 | err("Assertion failed: %s: %q is not equal to %q",func,tostring(a),tostring(b)) 12 | failed = true 13 | end 14 | end 15 | 16 | testing=true 17 | 18 | 19 | local qrcode = dofile("qrencode.lua") 20 | local tab 21 | local str = "HELLO WORLD" 22 | local almost_full_data = string.rep("1",70) 23 | local hello_world_version, hello_world_ec, hello_world_mode, hello_world_modebits, hello_world_lenbits 24 | local hello_world_arranged 25 | local hello_world_penalty 26 | local function new_matrix(size) 27 | local matrix = {} 28 | for i=1,size do 29 | matrix[i] = {} 30 | for j=1,size do 31 | matrix[i][j] = 0 32 | end 33 | end 34 | return matrix 35 | end 36 | assert_equal(qrcode.get_mode("0101"), 1,"get_encoding_byte 1") 37 | assert_equal(qrcode.get_mode(str), 2,"get_encoding_byte 2") 38 | assert_equal(qrcode.get_mode("0-9A-Z $%*./:+-"),2,"get_encoding_byte 3") 39 | assert_equal(qrcode.get_mode("foär"), 4,"get_encoding_byte 4") 40 | assert_equal(qrcode.get_length(str,1,2),"000001011","get_length") 41 | assert_equal(qrcode.binary(5,10),"0000000101","binary()") 42 | assert_equal(qrcode.binary(779,11),"01100001011","binary()") 43 | assert_equal(qrcode.add_pad_data(1,3,"0010101"),"00101010000000001110110000010001111011000001000111101100000100011110110000010001111011000001000111101100","pad_data") 44 | 45 | tab = qrcode.get_generator_polynominal_adjusted(13,25) 46 | assert_equal(tab[1],0,"get_generator_polynominal_adjusted 0") 47 | assert_equal(tab[24],74,"get_generator_polynominal_adjusted 24") 48 | assert_equal(tab[25],0,"get_generator_polynominal_adjusted 25") 49 | tab = qrcode.get_generator_polynominal_adjusted(13,24) 50 | assert_equal(tab[1],0,"get_generator_polynominal_adjusted 0") 51 | assert_equal(tab[23],74,"get_generator_polynominal_adjusted 23") 52 | assert_equal(tab[24],0,"get_generator_polynominal_adjusted 24") 53 | 54 | tab = qrcode.convert_bitstring_to_bytes("00100000010110110000101101111000110100010111001011011100010011010100001101000000111011000001000111101100") 55 | assert_equal(tab[1],32,"convert_bitstring_to_bytes") 56 | tab = qrcode.convert_bitstring_to_bytes("1111111100000001") 57 | assert_equal(tab[1],255,"convert_bitstring_to_bytes 2") 58 | assert_equal(tab[2],1,"convert_bitstring_to_bytes 3") 59 | assert_equal(qrcode.xor_lookup[141][43], 166,"xor_lookup") 60 | assert_equal(qrcode.xor_lookup[179][0], 179,"xor_lookup") 61 | 62 | -- local hello_world_msg_with_ec = "0010000001011011000010110111100011010001011100101101110001001101010000110100000011101100000100011110110010101000010010000001011001010010110110010011011010011100000000000010111000001111101101000111101000010000" 63 | 64 | assert_equal(qrcode.get_pixel_with_mask(0,21,21,1),-1,"get_pixel_with_mask 1") 65 | assert_equal(qrcode.get_pixel_with_mask(0,1,1,1),-1,"get_pixel_with_mask 2") 66 | local a,b,c,d,e = qrcode.get_version_eclevel_mode_bistringlength(str) 67 | assert_equal(a,1,"get_version_eclevel_mode_bistringlength 1") 68 | assert_equal(b,3,"get_version_eclevel_mode_bistringlength 2") 69 | assert_equal(c,"0010","get_version_eclevel_mode_bistringlength 3") 70 | assert_equal(d,2,"get_version_eclevel_mode_bistringlength 4") 71 | assert_equal(e,"000001011","get_version_eclevel_mode_bistringlength 5") 72 | 73 | assert_equal(qrcode.encode_string_numeric("01234567"),"000000110001010110011000011","encode string numeric") 74 | assert_equal(qrcode.encode_string_numeric("987654321"),"111101101110100011100101000001","encode string numeric multi groups") 75 | assert_equal(qrcode.encode_string_numeric("7"),"0111","encode string numeric single digit") 76 | assert_equal(qrcode.encode_string_numeric("42"),"0101010","encode string numeric two digits") 77 | assert_equal(qrcode.encode_string_ascii(str),"0110000101101111000110100010111001011011100010011010100001101","encode string ascii") 78 | assert_equal(qrcode.encode_string_ascii("HELLO"),"0110000101101111000110011000","encode string ascii odd length") 79 | assert_equal(qrcode.encode_string_ascii("AB"),"00111001101","encode string ascii even length") 80 | assert_equal(qrcode.encode_string_ascii("A"),"001010","encode string ascii single character") 81 | assert_equal(qrcode.encode_string_binary("Hi"),"0100100001101001","encode string binary") 82 | assert_equal(qrcode.encode_data("123",1),"0001111011","encode data numeric mode") 83 | assert_equal(qrcode.encode_data("HI",2),"01100001111","encode data ascii mode") 84 | assert_equal(qrcode.encode_data("A",4),"01000001","encode data binary mode") 85 | assert_equal(qrcode.add_pad_data(1,4,almost_full_data),almost_full_data .. "00","pad_data near capacity") 86 | assert_equal(qrcode.remainder[40],0,"get_remainder") 87 | assert_equal(qrcode.remainder[2],7,"get_remainder") 88 | 89 | local matrix = new_matrix(21) 90 | qrcode.fill_matrix_position(matrix,"1",5,5) 91 | assert_equal(matrix[5][5],2,"fill_matrix_position black") 92 | qrcode.fill_matrix_position(matrix,"0",5,6) 93 | assert_equal(matrix[5][6],-2,"fill_matrix_position white") 94 | 95 | matrix = new_matrix(21) 96 | qrcode.add_position_detection_patterns(matrix) 97 | assert_equal(matrix[1][1],2,"add_position_detection_patterns outer corner") 98 | assert_equal(matrix[4][2],-2,"add_position_detection_patterns inner white ring") 99 | assert_equal(matrix[3][3],2,"add_position_detection_patterns inner block") 100 | assert_equal(matrix[21][7],2,"add_position_detection_patterns top right edge") 101 | assert_equal(matrix[10][10],0,"add_position_detection_patterns untouched center") 102 | 103 | qrcode.add_timing_pattern(matrix) 104 | assert_equal(matrix[9][7],2,"add_timing_pattern vertical start") 105 | assert_equal(matrix[10][7],-2,"add_timing_pattern vertical gap") 106 | assert_equal(matrix[7][9],2,"add_timing_pattern horizontal start") 107 | assert_equal(matrix[7][10],-2,"add_timing_pattern horizontal gap") 108 | qrcode.add_typeinfo_to_matrix(matrix,1,0) 109 | assert_equal(matrix[9][21],2,"add_typeinfo_to_matrix bottom first bit") 110 | assert_equal(matrix[9][18],-2,"add_typeinfo_to_matrix bottom fourth bit") 111 | assert_equal(matrix[9][6],-2,"add_typeinfo_to_matrix bottom tenth bit") 112 | assert_equal(matrix[1][9],2,"add_typeinfo_to_matrix left first bit") 113 | assert_equal(matrix[4][9],-2,"add_typeinfo_to_matrix left fourth bit") 114 | assert_equal(matrix[21][9],-2,"add_typeinfo_to_matrix right last bit") 115 | 116 | local matrix_v2 = new_matrix(25) 117 | qrcode.add_position_detection_patterns(matrix_v2) 118 | qrcode.add_timing_pattern(matrix_v2) 119 | qrcode.add_alignment_pattern(matrix_v2) 120 | assert_equal(matrix_v2[19][19],2,"add_alignment_pattern center") 121 | assert_equal(matrix_v2[18][19],-2,"add_alignment_pattern inner ring vertical") 122 | assert_equal(matrix_v2[19][18],-2,"add_alignment_pattern inner ring horizontal") 123 | assert_equal(matrix_v2[17][19],2,"add_alignment_pattern outer ring vertical") 124 | assert_equal(matrix_v2[21][21],2,"add_alignment_pattern outer ring diagonal") 125 | assert_equal(matrix_v2[5][7],2,"add_alignment_pattern keep positioning pattern") 126 | 127 | local matrix_v7 = new_matrix(45) 128 | qrcode.add_version_information(matrix_v7,7) 129 | assert_equal(matrix_v7[1][37],2,"add_version_information bottom left ones") 130 | assert_equal(matrix_v7[5][35],2,"add_version_information bottom left center") 131 | assert_equal(matrix_v7[37][1],2,"add_version_information top right ones") 132 | assert_equal(matrix_v7[36][3],2,"add_version_information top right middle") 133 | assert_equal(matrix_v7[35][1],-2,"add_version_information top right zeros") 134 | 135 | local prepared_data = qrcode.prepare_matrix_with_mask(1,1,-1) 136 | qrcode.add_data_to_matrix(prepared_data,"10101010",-1) 137 | assert_equal(prepared_data[21][21],1,"add_data_to_matrix first bit") 138 | assert_equal(prepared_data[20][21],-1,"add_data_to_matrix second bit") 139 | assert_equal(prepared_data[21][20],1,"add_data_to_matrix third bit") 140 | assert_equal(prepared_data[20][20],-1,"add_data_to_matrix fourth bit") 141 | 142 | local prepared_masked = qrcode.prepare_matrix_with_mask(1,1,0) 143 | qrcode.add_data_to_matrix(prepared_masked,"10101010",0) 144 | assert_equal(qrcode.calculate_penalty(prepared_masked),567,"calculate_penalty sample") 145 | 146 | hello_world_version, hello_world_ec, hello_world_modebits, hello_world_mode, hello_world_lenbits = qrcode.get_version_eclevel_mode_bistringlength(str) 147 | hello_world_arranged = hello_world_modebits .. hello_world_lenbits .. qrcode.encode_data(str, hello_world_mode) 148 | hello_world_arranged = qrcode.add_pad_data(hello_world_version,hello_world_ec,hello_world_arranged) 149 | hello_world_arranged = qrcode.arrange_codewords_and_calculate_ec(hello_world_version,hello_world_ec,hello_world_arranged) 150 | hello_world_arranged = hello_world_arranged .. string.rep("0", qrcode.remainder[hello_world_version]) 151 | tab, hello_world_penalty = qrcode.get_matrix_and_penalty(hello_world_version,hello_world_ec,hello_world_arranged,0) 152 | assert_equal(#tab,21,"get_matrix_and_penalty size") 153 | assert_equal(hello_world_penalty,344,"get_matrix_and_penalty penalty") 154 | 155 | 156 | ------------------- 157 | -- Error correction 158 | ------------------- 159 | local data = {32, 234, 187, 136, 103, 116, 252, 228, 127, 141, 73, 236, 12, 206, 138, 7, 230, 101, 30, 91, 152, 80, 0, 236, 17, 236, 17, 236} 160 | local ec_expected = {73, 31, 138, 44, 37, 176, 170, 36, 254, 246, 191, 187, 13, 137, 84, 63} 161 | local ec = qrcode.calculate_error_correction(data,16) 162 | for i=1,#ec_expected do 163 | assert_equal(ec_expected[i],ec[i],string.format("calculate_error_correction %d",i)) 164 | end 165 | data = {32, 234, 187, 136, 103, 116, 252, 228, 127, 141, 73, 236, 12, 206, 138, 7, 230, 101, 30, 91, 152, 80, 0, 236, 17, 236, 17, 236, 17, 236, 17, 236, 17, 236} 166 | ec_expected = {66, 146, 126, 122, 79, 146, 2, 105, 180, 35} 167 | ec = qrcode.calculate_error_correction(data,10) 168 | for i=1,#ec_expected do 169 | assert_equal(ec_expected[i],ec[i],string.format("calculate_error_correction %d",i)) 170 | end 171 | data = {32, 83, 7, 120, 209, 114, 215, 60, 224} 172 | ec_expected = {123, 120, 222, 125, 116, 92, 144, 245, 58, 73, 104, 30, 108, 0, 30, 166, 152} 173 | ec = qrcode.calculate_error_correction(data,17) 174 | for i=1,#ec_expected do 175 | assert_equal(ec_expected[i],ec[i],string.format("calculate_error_correction %d",i)) 176 | end 177 | data = {32,83,7,120,209,114,215,60,224,236,17} 178 | ec_expected = {3, 67, 244, 57, 183, 14, 171, 101, 213, 52, 148, 3, 144, 148, 6, 155, 3, 252, 228, 100, 11, 56} 179 | ec = qrcode.calculate_error_correction(data,22) 180 | for i=1,#ec_expected do 181 | assert_equal(ec_expected[i],ec[i],string.format("calculate_error_correction %d",i)) 182 | end 183 | data = {236,17,236,17,236, 17,236, 17,236, 17,236} 184 | ec_expected = {171, 165, 230, 109, 241, 45, 198, 125, 213, 84, 88, 187, 89, 61, 220, 255, 150, 75, 113, 77, 147, 164} 185 | ec = qrcode.calculate_error_correction(data,22) 186 | for i=1,#ec_expected do 187 | assert_equal(ec_expected[i],ec[i],string.format("calculate_error_correction %d",i)) 188 | end 189 | data = {17,236, 17,236, 17,236,17,236, 17,236, 17,236} 190 | ec_expected = {23, 115, 68, 245, 125, 66, 203, 235, 85, 88, 174, 178, 229, 181, 118, 148, 44, 175, 213, 243, 27, 215} 191 | ec = qrcode.calculate_error_correction(data,22) 192 | for i=1,#ec_expected do 193 | assert_equal(ec_expected[i],ec[i],string.format("calculate_error_correction %d",i)) 194 | end 195 | data = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} 196 | ec_expected = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} 197 | ec = qrcode.calculate_error_correction(data,10) 198 | for i=1,#ec_expected do 199 | assert_equal(ec_expected[i],ec[i],string.format("calculate_error_correction %d",i)) 200 | end 201 | 202 | -- "HALLO WELT" in alphanumeric, code 5-H 203 | data = { 32,83,7,120,209,114,215,60,224,236,17,236,17,236,17,236, 17,236, 17,236, 17,236, 17, 236, 17,236, 17,236, 17,236, 17,236, 17,236, 17, 236, 17,236, 17,236, 17,236, 17,236, 17,236} 204 | local message_expected = {32, 236, 17, 17, 83, 17, 236, 236, 7, 236, 17, 17, 120, 17, 236, 236, 209, 236, 17, 17, 114, 17, 236, 236, 215, 236, 17, 17, 60, 17, 236, 236, 224, 236, 17, 17, 236, 17, 236, 236, 17, 236, 17, 17, 236, 236, 3, 171, 23, 23, 67, 165, 115, 115, 244, 230, 68, 68, 57, 109, 245, 245, 183, 241, 125, 125, 14, 45, 66, 66, 171, 198, 203, 203, 101, 125, 235, 235, 213, 213, 85, 85, 52, 84, 88, 88, 148, 88, 174, 174, 3, 187, 178, 178, 144, 89, 229, 229, 148, 61, 181, 181, 6, 220, 118, 118, 155, 255, 148, 148, 3, 150, 44, 44, 252, 75, 175, 175, 228, 113, 213, 213, 100, 77, 243, 243, 11, 147, 27, 27, 56, 164, 215, 215} 205 | local tmp = qrcode.arrange_codewords_and_calculate_ec(5,4,data) 206 | local message = qrcode.convert_bitstring_to_bytes(tmp) 207 | for i=1,#message do 208 | assert_equal(message_expected[i],message[i],string.format("arrange_codewords_and_calculate_ec %d",i)) 209 | end 210 | 211 | print("Tests end here") 212 | if failed then 213 | print("Some tests failed, see above") 214 | else 215 | print("Everything looks fine") 216 | end 217 | os.exit(failed and 1 or 0) 218 | -------------------------------------------------------------------------------- /locco/markdown.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | --[[ 4 | # markdown.lua -- version 0.40 5 | 6 | **Author:** Niklas Frykholm, 7 | **Date:** 31 May 2008 8 | 9 | See the following page for changes to the original file 10 | https://github.com/speedata/luamarkdown 11 | 12 | 13 | This is an implementation of the popular text markup language Markdown in pure Lua. 14 | Markdown can convert documents written in a simple and easy to read text format 15 | to well-formatted HTML. For a more thourough description of Markdown and the Markdown 16 | syntax, see . 17 | 18 | The original Markdown source is written in Perl and makes heavy use of advanced 19 | regular expression techniques (such as negative look-ahead, etc) which are not available 20 | in Lua's simple regex engine. Therefore this Lua port has been rewritten from the ground 21 | up. It is probably not completely bug free. If you notice any bugs, please report them to 22 | me. A unit test that exposes the error is helpful. 23 | 24 | ## Usage 25 | 26 | local markdown = require "markdown" 27 | markdown.markdown(source) 28 | 29 | ``markdown.lua`` returns a table with a single function named ``markdown(s)`` which applies the 30 | Markdown transformation to the specified string. 31 | 32 | ``markdown.lua`` can also be used directly from the command line: 33 | 34 | lua markdown.lua test.md 35 | 36 | Creates a file ``test.html`` with the converted content of ``test.md``. Run: 37 | 38 | lua markdown.lua -h 39 | 40 | For a description of the command-line options. 41 | 42 | ``markdown.lua`` uses the same license as Lua, the MIT license. 43 | 44 | ## License 45 | 46 | Copyright © 2008 Niklas Frykholm. 47 | 48 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 49 | software and associated documentation files (the "Software"), to deal in the Software 50 | without restriction, including without limitation the rights to use, copy, modify, merge, 51 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 52 | to whom the Software is furnished to do so, subject to the following conditions: 53 | 54 | The above copyright notice and this permission notice shall be included in all copies 55 | or substantial portions of the Software. 56 | 57 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 58 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 59 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 60 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 61 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 62 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 63 | THE SOFTWARE. 64 | 65 | ## Version history 66 | 67 | - **0.40** -- 17 Apr 2014 68 | - Compatibility for Lua 5.1 and 5.2 69 | - **0.32** -- 31 May 2008 70 | - Fix for links containing brackets 71 | - **0.31** -- 1 Mar 2008 72 | - Fix for link definitions followed by spaces 73 | - **0.30** -- 25 Feb 2008 74 | - Consistent behavior with Markdown when the same link reference is reused 75 | - **0.29** -- 24 Feb 2008 76 | - Fix for
 blocks with spaces in them
  77 | -   **0.28** -- 18 Feb 2008
  78 |     -   Fix for link encoding
  79 | -   **0.27** -- 14 Feb 2008
  80 |     -   Fix for link database links with ()
  81 | -   **0.26** -- 06 Feb 2008
  82 |     -   Fix for nested italic and bold markers
  83 | -   **0.25** -- 24 Jan 2008
  84 |     -   Fix for encoding of naked <
  85 | -   **0.24** -- 21 Jan 2008
  86 |     -   Fix for link behavior.
  87 | -   **0.23** -- 10 Jan 2008
  88 |     -   Fix for a regression bug in longer expressions in italic or bold.
  89 | -   **0.22** -- 27 Dec 2007
  90 |     -   Fix for crash when processing blocks with a percent sign in them.
  91 | -   **0.21** -- 27 Dec 2007
  92 |     -   Fix for combined strong and emphasis tags
  93 | -   **0.20** -- 13 Oct 2007
  94 |     -   Fix for < as well in image titles, now matches Dingus behavior
  95 | -   **0.19** -- 28 Sep 2007
  96 |     -   Fix for quotation marks " and ampersands & in link and image titles.
  97 | -   **0.18** -- 28 Jul 2007
  98 |     -   Does not crash on unmatched tags (behaves like standard markdown)
  99 | -   **0.17** -- 12 Apr 2007
 100 |     -   Fix for links with %20 in them.
 101 | -   **0.16** -- 12 Apr 2007
 102 |     -   Do not require arg global to exist.
 103 | -   **0.15** -- 28 Aug 2006
 104 |     -   Better handling of links with underscores in them.
 105 | -   **0.14** -- 22 Aug 2006
 106 |     -   Bug for *`foo()`*
 107 | -   **0.13** -- 12 Aug 2006
 108 |     -   Added -l option for including stylesheet inline in document.
 109 |     -   Fixed bug in -s flag.
 110 |     -   Fixed emphasis bug.
 111 | -   **0.12** -- 15 May 2006
 112 |     -   Fixed several bugs to comply with MarkdownTest 1.0 
 113 | -   **0.11** -- 12 May 2006
 114 |     -   Fixed bug for escaping `*` and `_` inside code spans.
 115 |     -   Added license terms.
 116 |     -   Changed join() to table.concat().
 117 | -   **0.10** -- 3 May 2006
 118 |     -   Initial public release.
 119 | 
 120 | // Niklas
 121 | ]]
 122 | 
 123 | 
 124 | local unpack = unpack or table.unpack
 125 | 
 126 | local span_transform, encode_backslash_escapes, block_transform, blocks_to_html, blocks_to_html
 127 | 
 128 | ----------------------------------------------------------------------
 129 | -- Utility functions
 130 | ----------------------------------------------------------------------
 131 | 
 132 | -- Returns the result of mapping the values in table t through the function f
 133 | local function map(t, f)
 134 |     local out = {}
 135 |     for k,v in pairs(t) do out[k] = f(v,k) end
 136 |     return out
 137 | end
 138 | 
 139 | -- The identity function, useful as a placeholder.
 140 | local function identity(text) return text end
 141 | 
 142 | -- Functional style if statement. (NOTE: no short circuit evaluation)
 143 | local function iff(t, a, b) if t then return a else return b end end
 144 | 
 145 | -- Splits the text into an array of separate lines.
 146 | local function split(text, sep)
 147 |     sep = sep or "\n"
 148 |     local lines = {}
 149 |     local pos = 1
 150 |     while true do
 151 |         local b,e = text:find(sep, pos)
 152 |         if not b then table.insert(lines, text:sub(pos)) break end
 153 |         table.insert(lines, text:sub(pos, b-1))
 154 |         pos = e + 1
 155 |     end
 156 |     return lines
 157 | end
 158 | 
 159 | -- Converts tabs to spaces
 160 | local function detab(text)
 161 |     local tab_width = 4
 162 |     local function rep(match)
 163 |         local spaces = -match:len()
 164 |         while spaces<1 do spaces = spaces + tab_width end
 165 |         return match .. string.rep(" ", spaces)
 166 |     end
 167 |     text = text:gsub("([^\n]-)\t", rep)
 168 |     return text
 169 | end
 170 | 
 171 | -- Applies string.find for every pattern in the list and returns the first match
 172 | local function find_first(s, patterns, index)
 173 |     local res = {}
 174 |     for _,p in ipairs(patterns) do
 175 |         local match = {s:find(p, index)}
 176 |         if #match>0 and (#res==0 or match[1] < res[1]) then res = match end
 177 |     end
 178 |     return unpack(res)
 179 | end
 180 | 
 181 | -- If a replacement array is specified, the range [start, stop] in the array is replaced
 182 | -- with the replacement array and the resulting array is returned. Without a replacement
 183 | -- array the section of the array between start and stop is returned.
 184 | local function splice(array, start, stop, replacement)
 185 |     if replacement then
 186 |         local n = stop - start + 1
 187 |         while n > 0 do
 188 |             table.remove(array, start)
 189 |             n = n - 1
 190 |         end
 191 |         for i,v in ipairs(replacement) do
 192 |             table.insert(array, start, v)
 193 |         end
 194 |         return array
 195 |     else
 196 |         local res = {}
 197 |         for i = start,stop do
 198 |             table.insert(res, array[i])
 199 |         end
 200 |         return res
 201 |     end
 202 | end
 203 | 
 204 | -- Outdents the text one step.
 205 | local function outdent(text)
 206 |     text = "\n" .. text
 207 |     text = text:gsub("\n  ? ? ?", "\n")
 208 |     text = text:sub(2)
 209 |     return text
 210 | end
 211 | 
 212 | -- Indents the text one step.
 213 | local function indent(text)
 214 |     text = text:gsub("\n", "\n    ")
 215 |     return text
 216 | end
 217 | 
 218 | -- Does a simple tokenization of html data. Returns the data as a list of tokens.
 219 | -- Each token is a table with a type field (which is either "tag" or "text") and
 220 | -- a text field (which contains the original token data).
 221 | local function tokenize_html(html)
 222 |     local tokens = {}
 223 |     local pos = 1
 224 |     while true do
 225 |         local start = find_first(html, {"", start)
 235 |         elseif html:match("^<%?", start) then
 236 |             _,stop = html:find("?>", start)
 237 |         else
 238 |             _,stop = html:find("%b<>", start)
 239 |         end
 240 |         if not stop then
 241 |             -- error("Could not match html tag " .. html:sub(start,start+30))
 242 |             table.insert(tokens, {type="text", text=html:sub(start, start)})
 243 |             pos = start + 1
 244 |         else
 245 |             table.insert(tokens, {type="tag", text=html:sub(start, stop)})
 246 |             pos = stop + 1
 247 |         end
 248 |     end
 249 |     return tokens
 250 | end
 251 | 
 252 | ----------------------------------------------------------------------
 253 | -- Hash
 254 | ----------------------------------------------------------------------
 255 | 
 256 | -- This is used to "hash" data into alphanumeric strings that are unique
 257 | -- in the document. (Note that this is not cryptographic hash, the hash
 258 | -- function is not one-way.) The hash procedure is used to protect parts
 259 | -- of the document from further processing.
 260 | 
 261 | local HASH = {
 262 |     -- Has the hash been inited.
 263 |     inited = false,
 264 | 
 265 |     -- The unique string prepended to all hash values. This is to ensure
 266 |     -- that hash values do not accidently coincide with an actual existing
 267 |     -- string in the document.
 268 |     identifier = "",
 269 | 
 270 |     -- Counter that counts up for each new hash instance.
 271 |     counter = 0,
 272 | 
 273 |     -- Hash table.
 274 |     table = {}
 275 | }
 276 | 
 277 | -- Inits hashing. Creates a hash_identifier that doesn't occur anywhere
 278 | -- in the text.
 279 | local function init_hash(text)
 280 |     HASH.inited = true
 281 |     HASH.identifier = ""
 282 |     HASH.counter = 0
 283 |     HASH.table = {}
 284 | 
 285 |     local s = "HASH"
 286 |     local counter = 0
 287 |     local id
 288 |     while true do
 289 |         id  = s .. counter
 290 |         if not text:find(id, 1, true) then break end
 291 |         counter = counter + 1
 292 |     end
 293 |     HASH.identifier = id
 294 | end
 295 | 
 296 | -- Returns the hashed value for s.
 297 | local function hash(s)
 298 |     assert(HASH.inited)
 299 |     if not HASH.table[s] then
 300 |         HASH.counter = HASH.counter + 1
 301 |         local id = HASH.identifier .. HASH.counter .. "X"
 302 |         HASH.table[s] = id
 303 |     end
 304 |     return HASH.table[s]
 305 | end
 306 | 
 307 | ----------------------------------------------------------------------
 308 | -- Protection
 309 | ----------------------------------------------------------------------
 310 | 
 311 | -- The protection module is used to "protect" parts of a document
 312 | -- so that they are not modified by subsequent processing steps.
 313 | -- Protected parts are saved in a table for later unprotection
 314 | 
 315 | -- Protection data
 316 | local PD = {
 317 |     -- Saved blocks that have been converted
 318 |     blocks = {},
 319 | 
 320 |     -- Block level tags that will be protected
 321 |     tags = {"p", "div", "h1", "h2", "h3", "h4", "h5", "h6", "blockquote",
 322 |     "pre", "table", "dl", "ol", "ul", "script", "noscript", "form", "fieldset",
 323 |     "iframe", "math", "ins", "del"}
 324 | }
 325 | 
 326 | -- Pattern for matching a block tag that begins and ends in the leftmost
 327 | -- column and may contain indented subtags, i.e.
 328 | -- 
329 | -- A nested block. 330 | --
331 | -- Nested data. 332 | --
333 | --
334 | local function block_pattern(tag) 335 | return "\n<" .. tag .. ".-\n[ \t]*\n" 336 | end 337 | 338 | -- Pattern for matching a block tag that begins and ends with a newline 339 | local function line_pattern(tag) 340 | return "\n<" .. tag .. ".-[ \t]*\n" 341 | end 342 | 343 | -- Protects the range of characters from start to stop in the text and 344 | -- returns the protected string. 345 | local function protect_range(text, start, stop) 346 | local s = text:sub(start, stop) 347 | local h = hash(s) 348 | PD.blocks[h] = s 349 | text = text:sub(1,start) .. h .. text:sub(stop) 350 | return text 351 | end 352 | 353 | -- Protect every part of the text that matches any of the patterns. The first 354 | -- matching pattern is protected first, etc. 355 | local function protect_matches(text, patterns) 356 | while true do 357 | local start, stop = find_first(text, patterns) 358 | if not start then break end 359 | text = protect_range(text, start, stop) 360 | end 361 | return text 362 | end 363 | 364 | -- Protects blocklevel tags in the specified text 365 | local function protect(text) 366 | -- First protect potentially nested block tags 367 | text = protect_matches(text, map(PD.tags, block_pattern)) 368 | -- Then protect block tags at the line level. 369 | text = protect_matches(text, map(PD.tags, line_pattern)) 370 | -- Protect
and comment tags 371 | text = protect_matches(text, {"\n]->[ \t]*\n"}) 372 | text = protect_matches(text, {"\n[ \t]*\n"}) 373 | return text 374 | end 375 | 376 | -- Returns true if the string s is a hash resulting from protection 377 | local function is_protected(s) 378 | return PD.blocks[s] 379 | end 380 | 381 | -- Unprotects the specified text by expanding all the nonces 382 | local function unprotect(text) 383 | for k,v in pairs(PD.blocks) do 384 | v = v:gsub("%%", "%%%%") 385 | text = text:gsub(k, v) 386 | end 387 | return text 388 | end 389 | 390 | 391 | ---------------------------------------------------------------------- 392 | -- Block transform 393 | ---------------------------------------------------------------------- 394 | 395 | -- The block transform functions transform the text on the block level. 396 | -- They work with the text as an array of lines rather than as individual 397 | -- characters. 398 | 399 | -- Returns true if the line is a ruler of (char) characters. 400 | -- The line must contain at least three char characters and contain only spaces and 401 | -- char characters. 402 | local function is_ruler_of(line, char) 403 | if not line:match("^[ %" .. char .. "]*$") then return false end 404 | if not line:match("%" .. char .. ".*%" .. char .. ".*%" .. char) then return false end 405 | return true 406 | end 407 | 408 | -- Identifies the block level formatting present in the line 409 | local function classify(line) 410 | local info = {line = line, text = line} 411 | 412 | if line:match("^ ") then 413 | info.type = "indented" 414 | info.outdented = line:sub(5) 415 | return info 416 | end 417 | 418 | for _,c in ipairs({'*', '-', '_', '='}) do 419 | if is_ruler_of(line, c) then 420 | info.type = "ruler" 421 | info.ruler_char = c 422 | return info 423 | end 424 | end 425 | 426 | if line == "" then 427 | info.type = "blank" 428 | return info 429 | end 430 | 431 | if line:match("^(#+)[ \t]*(.-)[ \t]*#*[ \t]*$") then 432 | local m1, m2 = line:match("^(#+)[ \t]*(.-)[ \t]*#*[ \t]*$") 433 | info.type = "header" 434 | info.level = m1:len() 435 | info.text = m2 436 | return info 437 | end 438 | 439 | if line:match("^ ? ? ?(%d+)%.[ \t]+(.+)") then 440 | local number, text = line:match("^ ? ? ?(%d+)%.[ \t]+(.+)") 441 | info.type = "list_item" 442 | info.list_type = "numeric" 443 | info.number = 0 + number 444 | info.text = text 445 | return info 446 | end 447 | 448 | if line:match("^ ? ? ?([%*%+%-])[ \t]+(.+)") then 449 | local bullet, text = line:match("^ ? ? ?([%*%+%-])[ \t]+(.+)") 450 | info.type = "list_item" 451 | info.list_type = "bullet" 452 | info.bullet = bullet 453 | info.text= text 454 | return info 455 | end 456 | 457 | if line:match("^>[ \t]?(.*)") then 458 | info.type = "blockquote" 459 | info.text = line:match("^>[ \t]?(.*)") 460 | return info 461 | end 462 | 463 | if is_protected(line) then 464 | info.type = "raw" 465 | info.html = unprotect(line) 466 | return info 467 | end 468 | 469 | info.type = "normal" 470 | return info 471 | end 472 | 473 | -- Find headers constisting of a normal line followed by a ruler and converts them to 474 | -- header entries. 475 | local function headers(array) 476 | local i = 1 477 | while i <= #array - 1 do 478 | if array[i].type == "normal" and array[i+1].type == "ruler" and 479 | (array[i+1].ruler_char == "-" or array[i+1].ruler_char == "=") then 480 | local info = {line = array[i].line} 481 | info.text = info.line 482 | info.type = "header" 483 | info.level = iff(array[i+1].ruler_char == "=", 1, 2) 484 | table.remove(array, i+1) 485 | array[i] = info 486 | end 487 | i = i + 1 488 | end 489 | return array 490 | end 491 | 492 | -- Find list blocks and convert them to protected data blocks 493 | local function lists(array, sublist) 494 | local function process_list(arr) 495 | local function any_blanks(arr) 496 | for i = 1, #arr do 497 | if arr[i].type == "blank" then return true end 498 | end 499 | return false 500 | end 501 | 502 | local function split_list_items(arr) 503 | local acc = {arr[1]} 504 | local res = {} 505 | for i=2,#arr do 506 | if arr[i].type == "list_item" then 507 | table.insert(res, acc) 508 | acc = {arr[i]} 509 | else 510 | table.insert(acc, arr[i]) 511 | end 512 | end 513 | table.insert(res, acc) 514 | return res 515 | end 516 | 517 | local function process_list_item(lines, block) 518 | while lines[#lines].type == "blank" do 519 | table.remove(lines) 520 | end 521 | 522 | local itemtext = lines[1].text 523 | for i=2,#lines do 524 | itemtext = itemtext .. "\n" .. outdent(lines[i].line) 525 | end 526 | if block then 527 | itemtext = block_transform(itemtext, true) 528 | if not itemtext:find("
") then itemtext = indent(itemtext) end
 529 |                 return "    
  • " .. itemtext .. "
  • " 530 | else 531 | local lines = split(itemtext) 532 | lines = map(lines, classify) 533 | lines = lists(lines, true) 534 | lines = blocks_to_html(lines, true) 535 | itemtext = table.concat(lines, "\n") 536 | if not itemtext:find("
    ") then itemtext = indent(itemtext) end
     537 |                 return "    
  • " .. itemtext .. "
  • " 538 | end 539 | end 540 | 541 | local block_list = any_blanks(arr) 542 | local items = split_list_items(arr) 543 | local out = "" 544 | for _, item in ipairs(items) do 545 | out = out .. process_list_item(item, block_list) .. "\n" 546 | end 547 | if arr[1].list_type == "numeric" then 548 | return "
      \n" .. out .. "
    " 549 | else 550 | return "
      \n" .. out .. "
    " 551 | end 552 | end 553 | 554 | -- Finds the range of lines composing the first list in the array. A list 555 | -- starts with (^ list_item) or (blank list_item) and ends with 556 | -- (blank* $) or (blank normal). 557 | -- 558 | -- A sublist can start with just (list_item) does not need a blank... 559 | local function find_list(array, sublist) 560 | local function find_list_start(array, sublist) 561 | if array[1].type == "list_item" then return 1 end 562 | if sublist then 563 | for i = 1,#array do 564 | if array[i].type == "list_item" then return i end 565 | end 566 | else 567 | for i = 1, #array-1 do 568 | if array[i].type == "blank" and array[i+1].type == "list_item" then 569 | return i+1 570 | end 571 | end 572 | end 573 | return nil 574 | end 575 | local function find_list_end(array, start) 576 | local pos = #array 577 | for i = start, #array-1 do 578 | if array[i].type == "blank" and array[i+1].type ~= "list_item" 579 | and array[i+1].type ~= "indented" and array[i+1].type ~= "blank" then 580 | pos = i-1 581 | break 582 | end 583 | end 584 | while pos > start and array[pos].type == "blank" do 585 | pos = pos - 1 586 | end 587 | return pos 588 | end 589 | 590 | local start = find_list_start(array, sublist) 591 | if not start then return nil end 592 | return start, find_list_end(array, start) 593 | end 594 | 595 | while true do 596 | local start, stop = find_list(array, sublist) 597 | if not start then break end 598 | local text = process_list(splice(array, start, stop)) 599 | local info = { 600 | line = text, 601 | type = "raw", 602 | html = text 603 | } 604 | array = splice(array, start, stop, {info}) 605 | end 606 | 607 | -- Convert any remaining list items to normal 608 | for _,line in ipairs(array) do 609 | if line.type == "list_item" then line.type = "normal" end 610 | end 611 | 612 | return array 613 | end 614 | 615 | -- Find and convert blockquote markers. 616 | local function blockquotes(lines) 617 | local function find_blockquote(lines) 618 | local start 619 | for i,line in ipairs(lines) do 620 | if line.type == "blockquote" then 621 | start = i 622 | break 623 | end 624 | end 625 | if not start then return nil end 626 | 627 | local stop = #lines 628 | for i = start+1, #lines do 629 | if lines[i].type == "blank" or lines[i].type == "blockquote" then 630 | elseif lines[i].type == "normal" then 631 | if lines[i-1].type == "blank" then stop = i-1 break end 632 | else 633 | stop = i-1 break 634 | end 635 | end 636 | while lines[stop].type == "blank" do stop = stop - 1 end 637 | return start, stop 638 | end 639 | 640 | local function process_blockquote(lines) 641 | local raw = lines[1].text 642 | for i = 2,#lines do 643 | raw = raw .. "\n" .. lines[i].text 644 | end 645 | local bt = block_transform(raw) 646 | if not bt:find("
    ") then bt = indent(bt) end
     647 |         return "
    \n " .. bt .. 648 | "\n
    " 649 | end 650 | 651 | while true do 652 | local start, stop = find_blockquote(lines) 653 | if not start then break end 654 | local text = process_blockquote(splice(lines, start, stop)) 655 | local info = { 656 | line = text, 657 | type = "raw", 658 | html = text 659 | } 660 | lines = splice(lines, start, stop, {info}) 661 | end 662 | return lines 663 | end 664 | 665 | -- Find and convert codeblocks. 666 | local function codeblocks(lines) 667 | local function find_codeblock(lines) 668 | local start 669 | for i,line in ipairs(lines) do 670 | if line.type == "indented" then start = i break end 671 | end 672 | if not start then return nil end 673 | 674 | local stop = #lines 675 | for i = start+1, #lines do 676 | if lines[i].type ~= "indented" and lines[i].type ~= "blank" then 677 | stop = i-1 678 | break 679 | end 680 | end 681 | while lines[stop].type == "blank" do stop = stop - 1 end 682 | return start, stop 683 | end 684 | 685 | local function process_codeblock(lines) 686 | local raw = detab(encode_code(outdent(lines[1].line))) 687 | for i = 2,#lines do 688 | raw = raw .. "\n" .. detab(encode_code(outdent(lines[i].line))) 689 | end 690 | return "
    " .. raw .. "\n
    " 691 | end 692 | 693 | while true do 694 | local start, stop = find_codeblock(lines) 695 | if not start then break end 696 | local text = process_codeblock(splice(lines, start, stop)) 697 | local info = { 698 | line = text, 699 | type = "raw", 700 | html = text 701 | } 702 | lines = splice(lines, start, stop, {info}) 703 | end 704 | return lines 705 | end 706 | 707 | -- Convert lines to html code 708 | function blocks_to_html(lines, no_paragraphs) 709 | local out = {} 710 | local i = 1 711 | while i <= #lines do 712 | local line = lines[i] 713 | if line.type == "ruler" then 714 | table.insert(out, "
    ") 715 | elseif line.type == "raw" then 716 | table.insert(out, line.html) 717 | elseif line.type == "normal" then 718 | local s = line.line 719 | 720 | while i+1 <= #lines and lines[i+1].type == "normal" do 721 | i = i + 1 722 | s = s .. "\n" .. lines[i].line 723 | end 724 | 725 | if no_paragraphs then 726 | table.insert(out, span_transform(s)) 727 | else 728 | table.insert(out, "

    " .. span_transform(s) .. "

    ") 729 | end 730 | elseif line.type == "header" then 731 | local s = "" .. span_transform(line.text) .. "" 732 | table.insert(out, s) 733 | else 734 | table.insert(out, line.line) 735 | end 736 | i = i + 1 737 | end 738 | return out 739 | end 740 | 741 | -- Perform all the block level transforms 742 | function block_transform(text, sublist) 743 | local lines = split(text) 744 | lines = map(lines, classify) 745 | lines = headers(lines) 746 | lines = lists(lines, sublist) 747 | lines = codeblocks(lines) 748 | lines = blockquotes(lines) 749 | lines = blocks_to_html(lines) 750 | local text = table.concat(lines, "\n") 751 | return text 752 | end 753 | 754 | -- Debug function for printing a line array to see the result 755 | -- of partial transforms. 756 | local function print_lines(lines) 757 | for i, line in ipairs(lines) do 758 | print(i, line.type, line.text or line.line) 759 | end 760 | end 761 | 762 | ---------------------------------------------------------------------- 763 | -- Span transform 764 | ---------------------------------------------------------------------- 765 | 766 | -- Functions for transforming the text at the span level. 767 | 768 | -- These characters may need to be escaped because they have a special 769 | -- meaning in markdown. 770 | escape_chars = "'\\`*_{}[]()>#+-.!'" 771 | escape_table = {} 772 | 773 | local function init_escape_table() 774 | escape_table = {} 775 | for i = 1,#escape_chars do 776 | local c = escape_chars:sub(i,i) 777 | escape_table[c] = hash(c) 778 | end 779 | end 780 | 781 | -- Adds a new escape to the escape table. 782 | local function add_escape(text) 783 | if not escape_table[text] then 784 | escape_table[text] = hash(text) 785 | end 786 | return escape_table[text] 787 | end 788 | 789 | -- Escape characters that should not be disturbed by markdown. 790 | local function escape_special_chars(text) 791 | local tokens = tokenize_html(text) 792 | 793 | local out = "" 794 | for _, token in ipairs(tokens) do 795 | local t = token.text 796 | if token.type == "tag" then 797 | -- In tags, encode * and _ so they don't conflict with their use in markdown. 798 | t = t:gsub("%*", escape_table["*"]) 799 | t = t:gsub("%_", escape_table["_"]) 800 | else 801 | t = encode_backslash_escapes(t) 802 | end 803 | out = out .. t 804 | end 805 | return out 806 | end 807 | 808 | -- Encode backspace-escaped characters in the markdown source. 809 | function encode_backslash_escapes(t) 810 | for i=1,escape_chars:len() do 811 | local c = escape_chars:sub(i,i) 812 | t = t:gsub("\\%" .. c, escape_table[c]) 813 | end 814 | return t 815 | end 816 | 817 | -- Unescape characters that have been encoded. 818 | local function unescape_special_chars(t) 819 | local tin = t 820 | for k,v in pairs(escape_table) do 821 | k = k:gsub("%%", "%%%%") 822 | t = t:gsub(v,k) 823 | end 824 | if t ~= tin then t = unescape_special_chars(t) end 825 | return t 826 | end 827 | 828 | -- Encode/escape certain characters inside Markdown code runs. 829 | -- The point is that in code, these characters are literals, 830 | -- and lose their special Markdown meanings. 831 | function encode_code(s) 832 | s = s:gsub("%&", "&") 833 | s = s:gsub("<", "<") 834 | s = s:gsub(">", ">") 835 | for k,v in pairs(escape_table) do 836 | s = s:gsub("%"..k, v) 837 | end 838 | return s 839 | end 840 | 841 | -- Handle backtick blocks. 842 | local function code_spans(s) 843 | s = s:gsub("\\\\", escape_table["\\"]) 844 | s = s:gsub("\\`", escape_table["`"]) 845 | 846 | local pos = 1 847 | while true do 848 | local start, stop = s:find("`+", pos) 849 | if not start then return s end 850 | local count = stop - start + 1 851 | -- Find a matching numbert of backticks 852 | local estart, estop = s:find(string.rep("`", count), stop+1) 853 | local brstart = s:find("\n", stop+1) 854 | if estart and (not brstart or estart < brstart) then 855 | local code = s:sub(stop+1, estart-1) 856 | code = code:gsub("^[ \t]+", "") 857 | code = code:gsub("[ \t]+$", "") 858 | code = code:gsub(escape_table["\\"], escape_table["\\"] .. escape_table["\\"]) 859 | code = code:gsub(escape_table["`"], escape_table["\\"] .. escape_table["`"]) 860 | code = "" .. encode_code(code) .. "" 861 | code = add_escape(code) 862 | s = s:sub(1, start-1) .. code .. s:sub(estop+1) 863 | pos = start + code:len() 864 | else 865 | pos = stop + 1 866 | end 867 | end 868 | return s 869 | end 870 | 871 | -- Encode alt text... enodes &, and ". 872 | local function encode_alt(s) 873 | if not s then return s end 874 | s = s:gsub('&', '&') 875 | s = s:gsub('"', '"') 876 | s = s:gsub('<', '<') 877 | return s 878 | end 879 | 880 | -- Handle image references 881 | local function images(text) 882 | local function reference_link(alt, id) 883 | alt = encode_alt(alt:match("%b[]"):sub(2,-2)) 884 | id = id:match("%[(.*)%]"):lower() 885 | if id == "" then id = text:lower() end 886 | link_database[id] = link_database[id] or {} 887 | if not link_database[id].url then return nil end 888 | local url = link_database[id].url or id 889 | url = encode_alt(url) 890 | local title = encode_alt(link_database[id].title) 891 | if title then title = " title=\"" .. title .. "\"" else title = "" end 892 | return add_escape ('' .. alt .. '") 893 | end 894 | 895 | local function inline_link(alt, link) 896 | alt = encode_alt(alt:match("%b[]"):sub(2,-2)) 897 | local url, title = link:match("%(?[ \t]*['\"](.+)['\"]") 898 | url = url or link:match("%(?%)") 899 | url = encode_alt(url) 900 | title = encode_alt(title) 901 | if title then 902 | return add_escape('' .. alt .. '') 903 | else 904 | return add_escape('' .. alt .. '') 905 | end 906 | end 907 | 908 | text = text:gsub("!(%b[])[ \t]*\n?[ \t]*(%b[])", reference_link) 909 | text = text:gsub("!(%b[])(%b())", inline_link) 910 | return text 911 | end 912 | 913 | -- Handle anchor references 914 | local function anchors(text) 915 | local function reference_link(text, id) 916 | text = text:match("%b[]"):sub(2,-2) 917 | id = id:match("%b[]"):sub(2,-2):lower() 918 | if id == "" then id = text:lower() end 919 | link_database[id] = link_database[id] or {} 920 | if not link_database[id].url then return nil end 921 | local url = link_database[id].url or id 922 | url = encode_alt(url) 923 | local title = encode_alt(link_database[id].title) 924 | if title then title = " title=\"" .. title .. "\"" else title = "" end 925 | return add_escape("") .. text .. add_escape("") 926 | end 927 | 928 | local function inline_link(text, link) 929 | text = text:match("%b[]"):sub(2,-2) 930 | local url, title = link:match("%(?[ \t]*['\"](.+)['\"]") 931 | title = encode_alt(title) 932 | url = url or link:match("%(?%)") or "" 933 | url = encode_alt(url) 934 | if title then 935 | return add_escape("") .. text .. "" 936 | else 937 | return add_escape("") .. text .. add_escape("") 938 | end 939 | end 940 | 941 | text = text:gsub("(%b[])[ \t]*\n?[ \t]*(%b[])", reference_link) 942 | text = text:gsub("(%b[])(%b())", inline_link) 943 | return text 944 | end 945 | 946 | -- Handle auto links, i.e. . 947 | local function auto_links(text) 948 | local function link(s) 949 | return add_escape("") .. s .. "" 950 | end 951 | -- Encode chars as a mix of dec and hex entitites to (perhaps) fool 952 | -- spambots. 953 | local function encode_email_address(s) 954 | -- Use a deterministic encoding to make unit testing possible. 955 | -- Code 45% hex, 45% dec, 10% plain. 956 | local hex = {code = function(c) return "&#x" .. string.format("%x", c:byte()) .. ";" end, count = 1, rate = 0.45} 957 | local dec = {code = function(c) return "&#" .. c:byte() .. ";" end, count = 0, rate = 0.45} 958 | local plain = {code = function(c) return c end, count = 0, rate = 0.1} 959 | local codes = {hex, dec, plain} 960 | local function swap(t,k1,k2) local temp = t[k2] t[k2] = t[k1] t[k1] = temp end 961 | 962 | local out = "" 963 | for i = 1,s:len() do 964 | for _,code in ipairs(codes) do code.count = code.count + code.rate end 965 | if codes[1].count < codes[2].count then swap(codes,1,2) end 966 | if codes[2].count < codes[3].count then swap(codes,2,3) end 967 | if codes[1].count < codes[2].count then swap(codes,1,2) end 968 | 969 | local code = codes[1] 970 | local c = s:sub(i,i) 971 | -- Force encoding of "@" to make email address more invisible. 972 | if c == "@" and code == plain then code = codes[2] end 973 | out = out .. code.code(c) 974 | code.count = code.count - 1 975 | end 976 | return out 977 | end 978 | local function mail(s) 979 | s = unescape_special_chars(s) 980 | local address = encode_email_address("mailto:" .. s) 981 | local text = encode_email_address(s) 982 | return add_escape("") .. text .. "" 983 | end 984 | -- links 985 | text = text:gsub("<(https?:[^'\">%s]+)>", link) 986 | text = text:gsub("<(ftp:[^'\">%s]+)>", link) 987 | 988 | -- mail 989 | text = text:gsub("%s]+)>", mail) 990 | text = text:gsub("<([-.%w]+%@[-.%w]+)>", mail) 991 | return text 992 | end 993 | 994 | -- Encode free standing amps (&) and angles (<)... note that this does not 995 | -- encode free >. 996 | local function amps_and_angles(s) 997 | -- encode amps not part of &..; expression 998 | local pos = 1 999 | while true do 1000 | local amp = s:find("&", pos) 1001 | if not amp then break end 1002 | local semi = s:find(";", amp+1) 1003 | local stop = s:find("[ \t\n&]", amp+1) 1004 | if not semi or (stop and stop < semi) or (semi - amp) > 15 then 1005 | s = s:sub(1,amp-1) .. "&" .. s:sub(amp+1) 1006 | pos = amp+1 1007 | else 1008 | pos = amp+1 1009 | end 1010 | end 1011 | 1012 | -- encode naked <'s 1013 | s = s:gsub("<([^a-zA-Z/?$!])", "<%1") 1014 | s = s:gsub("<$", "<") 1015 | 1016 | -- what about >, nothing done in the original markdown source to handle them 1017 | return s 1018 | end 1019 | 1020 | -- Handles emphasis markers (* and _) in the text. 1021 | local function emphasis(text) 1022 | for _, s in ipairs {"%*%*", "%_%_"} do 1023 | text = text:gsub(s .. "([^%s][%*%_]?)" .. s, "%1") 1024 | text = text:gsub(s .. "([^%s][^<>]-[^%s][%*%_]?)" .. s, "%1") 1025 | end 1026 | for _, s in ipairs {"%*", "%_"} do 1027 | text = text:gsub(s .. "([^%s_])" .. s, "%1") 1028 | text = text:gsub(s .. "([^%s_])" .. s, "%1") 1029 | text = text:gsub(s .. "([^%s_][^<>_]-[^%s_])" .. s, "%1") 1030 | text = text:gsub(s .. "([^<>_]-[^<>_]-[^<>_]-)" .. s, "%1") 1031 | end 1032 | return text 1033 | end 1034 | 1035 | -- Handles line break markers in the text. 1036 | local function line_breaks(text) 1037 | return text:gsub(" +\n", "
    \n") 1038 | end 1039 | 1040 | -- Perform all span level transforms. 1041 | function span_transform(text) 1042 | text = code_spans(text) 1043 | text = escape_special_chars(text) 1044 | text = images(text) 1045 | text = anchors(text) 1046 | text = auto_links(text) 1047 | text = amps_and_angles(text) 1048 | text = emphasis(text) 1049 | text = line_breaks(text) 1050 | return text 1051 | end 1052 | 1053 | ---------------------------------------------------------------------- 1054 | -- Markdown 1055 | ---------------------------------------------------------------------- 1056 | 1057 | -- Cleanup the text by normalizing some possible variations to make further 1058 | -- processing easier. 1059 | local function cleanup(text) 1060 | -- Standardize line endings 1061 | text = text:gsub("\r\n", "\n") -- DOS to UNIX 1062 | text = text:gsub("\r", "\n") -- Mac to UNIX 1063 | 1064 | -- Convert all tabs to spaces 1065 | text = detab(text) 1066 | 1067 | -- Strip lines with only spaces and tabs 1068 | while true do 1069 | local subs 1070 | text, subs = text:gsub("\n[ \t]+\n", "\n\n") 1071 | if subs == 0 then break end 1072 | end 1073 | 1074 | return "\n" .. text .. "\n" 1075 | end 1076 | 1077 | -- Strips link definitions from the text and stores the data in a lookup table. 1078 | local function strip_link_definitions(text) 1079 | local linkdb = {} 1080 | 1081 | local function link_def(id, url, title) 1082 | id = id:match("%[(.+)%]"):lower() 1083 | linkdb[id] = linkdb[id] or {} 1084 | linkdb[id].url = url or linkdb[id].url 1085 | linkdb[id].title = title or linkdb[id].title 1086 | return "" 1087 | end 1088 | 1089 | local def_no_title = "\n ? ? ?(%b[]):[ \t]*\n?[ \t]*]+)>?[ \t]*" 1090 | local def_title1 = def_no_title .. "[ \t]+\n?[ \t]*[\"'(]([^\n]+)[\"')][ \t]*" 1091 | local def_title2 = def_no_title .. "[ \t]*\n[ \t]*[\"'(]([^\n]+)[\"')][ \t]*" 1092 | local def_title3 = def_no_title .. "[ \t]*\n?[ \t]+[\"'(]([^\n]+)[\"')][ \t]*" 1093 | 1094 | text = text:gsub(def_title1, link_def) 1095 | text = text:gsub(def_title2, link_def) 1096 | text = text:gsub(def_title3, link_def) 1097 | text = text:gsub(def_no_title, link_def) 1098 | return text, linkdb 1099 | end 1100 | 1101 | link_database = {} 1102 | 1103 | -- Main markdown processing function 1104 | local function markdown(text) 1105 | init_hash(text) 1106 | init_escape_table() 1107 | 1108 | text = cleanup(text) 1109 | text = protect(text) 1110 | text, link_database = strip_link_definitions(text) 1111 | text = block_transform(text) 1112 | text = unescape_special_chars(text) 1113 | return text 1114 | end 1115 | 1116 | ---------------------------------------------------------------------- 1117 | -- End of module 1118 | ---------------------------------------------------------------------- 1119 | 1120 | -- Expose markdown function to the world 1121 | md = { markdown = markdown } 1122 | 1123 | -- Class for parsing command-line options 1124 | local OptionParser = {} 1125 | OptionParser.__index = OptionParser 1126 | 1127 | -- Creates a new option parser 1128 | function OptionParser:new() 1129 | local o = {short = {}, long = {}} 1130 | setmetatable(o, self) 1131 | return o 1132 | end 1133 | 1134 | -- Calls f() whenever a flag with specified short and long name is encountered 1135 | function OptionParser:flag(short, long, f) 1136 | local info = {type = "flag", f = f} 1137 | if short then self.short[short] = info end 1138 | if long then self.long[long] = info end 1139 | end 1140 | 1141 | -- Calls f(param) whenever a parameter flag with specified short and long name is encountered 1142 | function OptionParser:param(short, long, f) 1143 | local info = {type = "param", f = f} 1144 | if short then self.short[short] = info end 1145 | if long then self.long[long] = info end 1146 | end 1147 | 1148 | -- Calls f(v) for each non-flag argument 1149 | function OptionParser:arg(f) 1150 | self.arg = f 1151 | end 1152 | 1153 | -- Runs the option parser for the specified set of arguments. Returns true if all arguments 1154 | -- where successfully parsed and false otherwise. 1155 | function OptionParser:run(args) 1156 | local pos = 1 1157 | while pos <= #args do 1158 | local arg = args[pos] 1159 | if arg == "--" then 1160 | for i=pos+1,#args do 1161 | if self.arg then self.arg(args[i]) end 1162 | return true 1163 | end 1164 | end 1165 | if arg:match("^%-%-") then 1166 | local info = self.long[arg:sub(3)] 1167 | if not info then print("Unknown flag: " .. arg) return false end 1168 | if info.type == "flag" then 1169 | info.f() 1170 | pos = pos + 1 1171 | else 1172 | param = args[pos+1] 1173 | if not param then print("No parameter for flag: " .. arg) return false end 1174 | info.f(param) 1175 | pos = pos+2 1176 | end 1177 | elseif arg:match("^%-") then 1178 | for i=2,arg:len() do 1179 | local c = arg:sub(i,i) 1180 | local info = self.short[c] 1181 | if not info then print("Unknown flag: -" .. c) return false end 1182 | if info.type == "flag" then 1183 | info.f() 1184 | else 1185 | if i == arg:len() then 1186 | param = args[pos+1] 1187 | if not param then print("No parameter for flag: -" .. c) return false end 1188 | info.f(param) 1189 | pos = pos + 1 1190 | else 1191 | param = arg:sub(i+1) 1192 | info.f(param) 1193 | end 1194 | break 1195 | end 1196 | end 1197 | pos = pos + 1 1198 | else 1199 | if self.arg then self.arg(arg) end 1200 | pos = pos + 1 1201 | end 1202 | end 1203 | return true 1204 | end 1205 | 1206 | -- Handles the case when markdown is run from the command line 1207 | local function run_command_line(arg) 1208 | -- Generate output for input s given options 1209 | local function run(s, options) 1210 | s = markdown(s) 1211 | if not options.wrap_header then return s end 1212 | local header = "" 1213 | if options.header then 1214 | local f = io.open(options.header) or error("Could not open file: " .. options.header) 1215 | header = f:read("*a") 1216 | f:close() 1217 | else 1218 | header = [[ 1219 | 1220 | 1221 | 1222 | 1223 | TITLE 1224 | 1225 | 1226 | 1227 | ]] 1228 | local title = options.title or s:match("

    (.-)

    ") or s:match("

    (.-)

    ") or 1229 | s:match("

    (.-)

    ") or "Untitled" 1230 | header = header:gsub("TITLE", title) 1231 | if options.inline_style then 1232 | local style = "" 1233 | local f = io.open(options.stylesheet) 1234 | if f then 1235 | style = f:read("*a") f:close() 1236 | else 1237 | error("Could not include style sheet " .. options.stylesheet .. ": File not found") 1238 | end 1239 | header = header:gsub('', 1240 | "") 1241 | else 1242 | header = header:gsub("STYLESHEET", options.stylesheet) 1243 | end 1244 | header = header:gsub("CHARSET", options.charset) 1245 | end 1246 | local footer = "" 1247 | if options.footer then 1248 | local f = io.open(options.footer) or error("Could not open file: " .. options.footer) 1249 | footer = f:read("*a") 1250 | f:close() 1251 | end 1252 | return header .. s .. footer 1253 | end 1254 | 1255 | -- Generate output path name from input path name given options. 1256 | local function outpath(path, options) 1257 | if options.append then return path .. ".html" end 1258 | local m = path:match("^(.+%.html)[^/\\]+$") if m then return m end 1259 | m = path:match("^(.+%.)[^/\\]*$") if m and path ~= m .. "html" then return m .. "html" end 1260 | return path .. ".html" 1261 | end 1262 | 1263 | -- Default commandline options 1264 | local options = { 1265 | wrap_header = true, 1266 | header = nil, 1267 | footer = nil, 1268 | charset = "utf-8", 1269 | title = nil, 1270 | stylesheet = "default.css", 1271 | inline_style = false 1272 | } 1273 | local help = [[ 1274 | Usage: markdown.lua [OPTION] [FILE] 1275 | Runs the markdown text markup to HTML converter on each file specified on the 1276 | command line. If no files are specified, runs on standard input. 1277 | 1278 | No header: 1279 | -n, --no-wrap Don't wrap the output in ... tags. 1280 | Custom header: 1281 | -e, --header FILE Use content of FILE for header. 1282 | -f, --footer FILE Use content of FILE for footer. 1283 | Generated header: 1284 | -c, --charset SET Specifies charset (default utf-8). 1285 | -i, --title TITLE Specifies title (default from first

    tag). 1286 | -s, --style STYLE Specifies style sheet file (default default.css). 1287 | -l, --inline-style Include the style sheet file inline in the header. 1288 | Generated files: 1289 | -a, --append Append .html extension (instead of replacing). 1290 | Other options: 1291 | -h, --help Print this help text. 1292 | -t, --test Run the unit tests. 1293 | ]] 1294 | 1295 | local run_stdin = true 1296 | local op = OptionParser:new() 1297 | op:flag("n", "no-wrap", function () options.wrap_header = false end) 1298 | op:param("e", "header", function (x) options.header = x end) 1299 | op:param("f", "footer", function (x) options.footer = x end) 1300 | op:param("c", "charset", function (x) options.charset = x end) 1301 | op:param("i", "title", function(x) options.title = x end) 1302 | op:param("s", "style", function(x) options.stylesheet = x end) 1303 | op:flag("l", "inline-style", function(x) options.inline_style = true end) 1304 | op:flag("a", "append", function() options.append = true end) 1305 | op:flag("t", "test", function() 1306 | local n = arg[0]:gsub("markdown.lua", "markdown-tests.lua") 1307 | local f = io.open(n) 1308 | if f then 1309 | f:close() dofile(n) 1310 | else 1311 | error("Cannot find markdown-tests.lua") 1312 | end 1313 | run_stdin = false 1314 | end) 1315 | op:flag("h", "help", function() print(help) run_stdin = false end) 1316 | op:arg(function(path) 1317 | local file = io.open(path) or error("Could not open file: " .. path) 1318 | local s = file:read("*a") 1319 | file:close() 1320 | s = run(s, options) 1321 | file = io.open(outpath(path, options), "w") or error("Could not open output file: " .. outpath(path, options)) 1322 | file:write(s) 1323 | file:close() 1324 | run_stdin = false 1325 | end 1326 | ) 1327 | 1328 | if not op:run(arg) then 1329 | print(help) 1330 | run_stdin = false 1331 | end 1332 | 1333 | if run_stdin then 1334 | local s = io.read("*a") 1335 | s = run(s, options) 1336 | io.write(s) 1337 | end 1338 | end 1339 | 1340 | -- If we are being run from the command-line, act accordingly 1341 | if arg and arg[0]:find("markdown%.lua$") then 1342 | run_command_line(arg) 1343 | else 1344 | return md 1345 | end 1346 | -------------------------------------------------------------------------------- /qrencode.lua: -------------------------------------------------------------------------------- 1 | --- The qrcode library is licensed under the 3-clause BSD license (aka "new BSD") 2 | --- To get in contact with the author, mail to . 3 | --- 4 | --- Please report bugs on the [github project page](http://speedata.github.io/luaqrcode/). 5 | -- Copyright (c) 2012-2020, Patrick Gundlach and contributors, see https://github.com/speedata/luaqrcode 6 | -- All rights reserved. 7 | -- 8 | -- Redistribution and use in source and binary forms, with or without 9 | -- modification, are permitted provided that the following conditions are met: 10 | -- * Redistributions of source code must retain the above copyright 11 | -- notice, this list of conditions and the following disclaimer. 12 | -- * Redistributions in binary form must reproduce the above copyright 13 | -- notice, this list of conditions and the following disclaimer in the 14 | -- documentation and/or other materials provided with the distribution. 15 | -- * Neither the name of SPEEDATA nor the 16 | -- names of its contributors may be used to endorse or promote products 17 | -- derived from this software without specific prior written permission. 18 | -- 19 | -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | -- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | -- DISCLAIMED. IN NO EVENT SHALL SPEEDATA GMBH BE LIABLE FOR ANY 23 | -- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | -- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | -- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | -- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | -- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | -- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | 31 | --- Overall workflow 32 | --- ================ 33 | --- The steps to generate the qrcode, assuming we already have the codeword: 34 | --- 35 | --- 1. Determine version, ec level and mode (=encoding) for codeword 36 | --- 1. Encode data 37 | --- 1. Arrange data and calculate error correction code 38 | --- 1. Generate 8 matrices with different masks and calculate the penalty 39 | --- 1. Return qrcode with least penalty 40 | --- 41 | --- Each step is of course more or less complex and needs further description 42 | 43 | 44 | 45 | --- Helper functions 46 | --- ================ 47 | --- 48 | --- We start with some helper functions 49 | 50 | local max,min=math.max,math.min 51 | local floor,abs=math.floor,math.abs 52 | local byte,sub,rep=string.byte,string.sub,string.rep 53 | local gsub,match,format=string.gsub,string.match,string.format 54 | local concat = table.concat 55 | 56 | 57 | local xor_lookup = {} 58 | do 59 | -- Build a fast xor helper that stays compatible across Lua versions. 60 | -- We precompute a 256x256 lookup table once at load time using a portable 61 | -- arithmetic xor; the hot path is then a constant-time table lookup without 62 | -- requiring native bitwise operators or external bit libraries. 63 | -- Slow but portable xor used only while populating the lookup table if no native op exists. 64 | local function slow_xor(a,b) 65 | local result = 0 66 | local bitval = 1 67 | while a > 0 or b > 0 do 68 | if (a % 2) ~= (b % 2) then 69 | result = result + bitval 70 | end 71 | a = floor(a / 2) 72 | b = floor(b / 2) 73 | bitval = bitval * 2 74 | end 75 | return result 76 | end 77 | 78 | -- Always build a 256x256 table once at load time; avoids any reliance on bitwise operators. 79 | for i=0,255 do 80 | local row = {} 81 | for j=0,255 do 82 | row[j] = slow_xor(i,j) 83 | end 84 | xor_lookup[i] = row 85 | end 86 | end 87 | 88 | local decToHexTable={ 89 | ["0"]="0000",["1"]="0001",["2"]="0010",["3"]="0011", 90 | ["4"]="0100",["5"]="0101",["6"]="0110",["7"]="0111", 91 | ["8"]="1000",["9"]="1001",["a"]="1010",["b"]="1011", 92 | ["c"]="1100",["d"]="1101",["e"]="1110",["f"]="1111", 93 | } 94 | local function decToHex(d) return decToHexTable[d] end 95 | -- Return the binary representation of the number x with the width of `digits`. 96 | local function binary(x,digits) 97 | local s = format("%x",x) -- dec to hex 98 | s = gsub(s,"(.)",decToHex) -- hex to bin 99 | s = gsub(s,"^0+","") -- remove leading 0s 100 | return rep("0",digits - #s) .. s 101 | end 102 | 103 | -- A small helper function for add_typeinfo_to_matrix() and add_version_information() 104 | -- Add a 2 (black by default) / -2 (blank by default) to the matrix at position x,y 105 | -- depending on the bitstring (size 1!) where "0"=blank and "1"=black. 106 | local function fill_matrix_position(matrix,bitstr,x,y) 107 | matrix[x][y] = bitstr == "1" and 2 or -2 108 | end 109 | 110 | 111 | 112 | --- Step 1: Determine version, ec level and mode for codeword 113 | --- ========================================================= 114 | --- 115 | --- First we need to find out the version (= size) of the QR code. This depends on 116 | --- the input data (the mode to be used), the requested error correction level 117 | --- (normally we use the maximum level that fits into the minimal size). 118 | 119 | -- Return the mode for the given string `str`. 120 | -- See table 2 of the spec. We only support mode 1, 2 and 4. 121 | -- That is: numeric, alaphnumeric and binary. 122 | local function get_mode(str) 123 | if match(str,"^[0-9]+$") then 124 | return 1 125 | elseif match(str,"^[0-9A-Z $%%*./:+-]+$") then 126 | return 2 127 | else 128 | return 4 129 | end 130 | assert(false,"never reached") -- luacheck: ignore 131 | return nil 132 | end 133 | 134 | --- Capacity of QR codes 135 | --- -------------------- 136 | --- The capacity is calculated as follow: \\(\text{Number of data bits} = \text{number of codewords} * 8\\). 137 | --- The number of data bits is now reduced by 4 (the mode indicator) and the length string, 138 | --- that varies between 8 and 16, depending on the version and the mode (see method `get_length()`). The 139 | --- remaining capacity is multiplied by the amount of data per bit string (numeric: 3, alphanumeric: 2, other: 1) 140 | --- and divided by the length of the bit string (numeric: 10, alphanumeric: 11, binary: 8, kanji: 13). 141 | --- Then the floor function is applied to the result: 142 | --- $$\Big\lfloor \frac{( \text{#data bits} - 4 - \text{length string}) * \text{data per bit string}}{\text{length of the bit string}} \Big\rfloor$$ 143 | --- 144 | --- There is one problem remaining. The length string depends on the version, 145 | --- and the version depends on the length string. But we take this into account when calculating the 146 | --- the capacity, so this is not really a problem here. 147 | 148 | -- The capacity (number of codewords) of each version (1-40) for error correction levels 1-4 (LMQH). 149 | -- The higher the ec level, the lower the capacity of the version. Taken from spec, tables 7-11. 150 | local capacity = { 151 | { 19, 16, 13, 9},{ 34, 28, 22, 16},{ 55, 44, 34, 26},{ 80, 64, 48, 36}, 152 | { 108, 86, 62, 46},{ 136, 108, 76, 60},{ 156, 124, 88, 66},{ 194, 154, 110, 86}, 153 | { 232, 182, 132, 100},{ 274, 216, 154, 122},{ 324, 254, 180, 140},{ 370, 290, 206, 158}, 154 | { 428, 334, 244, 180},{ 461, 365, 261, 197},{ 523, 415, 295, 223},{ 589, 453, 325, 253}, 155 | { 647, 507, 367, 283},{ 721, 563, 397, 313},{ 795, 627, 445, 341},{ 861, 669, 485, 385}, 156 | { 932, 714, 512, 406},{1006, 782, 568, 442},{1094, 860, 614, 464},{1174, 914, 664, 514}, 157 | {1276, 1000, 718, 538},{1370, 1062, 754, 596},{1468, 1128, 808, 628},{1531, 1193, 871, 661}, 158 | {1631, 1267, 911, 701},{1735, 1373, 985, 745},{1843, 1455, 1033, 793},{1955, 1541, 1115, 845}, 159 | {2071, 1631, 1171, 901},{2191, 1725, 1231, 961},{2306, 1812, 1286, 986},{2434, 1914, 1354, 1054}, 160 | {2566, 1992, 1426, 1096},{2702, 2102, 1502, 1142},{2812, 2216, 1582, 1222},{2956, 2334, 1666, 1276}, 161 | } 162 | 163 | --- Return the smallest version for this codeword. If `requested_ec_level` is supplied, 164 | --- then the ec level (LMQH - 1,2,3,4) must be at least the requested level. 165 | -- mode = 1,2,4,8 166 | local function get_version_eclevel(len,mode,requested_ec_level) 167 | local local_mode = mode 168 | if mode == 4 then 169 | local_mode = 3 170 | elseif mode == 8 then 171 | local_mode = 4 172 | end 173 | assert( local_mode <= 4 ) 174 | 175 | local bits, digits, modebits, c 176 | local tab = { {10,9,8,8},{12,11,16,10},{14,13,16,12} } 177 | local minversion = 99 -- placeholder, must be replaced by a lower value 178 | local maxec_level = requested_ec_level or 1 179 | local minlv,maxlv = 1, 4 180 | if requested_ec_level and requested_ec_level >= 1 and requested_ec_level <= 4 then 181 | minlv = requested_ec_level 182 | maxlv = requested_ec_level 183 | end 184 | for ec_level=minlv,maxlv do 185 | for version=1,#capacity do 186 | bits = capacity[version][ec_level] * 8 187 | bits = bits - 4 -- the mode indicator 188 | if version < 10 then 189 | digits = tab[1][local_mode] 190 | elseif version < 27 then 191 | digits = tab[2][local_mode] 192 | elseif version <= 40 then 193 | digits = tab[3][local_mode] 194 | end 195 | modebits = bits - digits 196 | if local_mode == 1 then -- numeric 197 | c = floor(modebits * 3 / 10) 198 | elseif local_mode == 2 then -- alphanumeric 199 | c = floor(modebits * 2 / 11) 200 | elseif local_mode == 3 then -- binary 201 | c = floor(modebits * 1 / 8) 202 | else 203 | c = floor(modebits * 1 / 13) 204 | end 205 | if c >= len then 206 | if version <= minversion then 207 | minversion = version 208 | maxec_level = ec_level 209 | end 210 | break 211 | end 212 | end 213 | end 214 | assert(minversion<=40,"Data too long to encode in QR code") 215 | return minversion, maxec_level 216 | end 217 | 218 | -- Return a bit string of 0s and 1s that includes the length of the code string. 219 | -- The modes are numeric = 1, alphanumeric = 2, binary = 4, and japanese = 8 220 | local function get_length(str,version,mode) 221 | local i = mode 222 | if mode == 4 then 223 | i = 3 224 | elseif mode == 8 then 225 | i = 4 226 | end 227 | assert( i <= 4 ) 228 | local tab = { {10,9,8,8},{12,11,16,10},{14,13,16,12} } 229 | local digits 230 | if version < 10 then 231 | digits = tab[1][i] 232 | elseif version < 27 then 233 | digits = tab[2][i] 234 | elseif version <= 40 then 235 | digits = tab[3][i] 236 | else 237 | assert(false, "get_length, version > 40 not supported") 238 | end 239 | local len = binary(#str,digits) 240 | return len 241 | end 242 | 243 | --- If the `requested_ec_level` or the `mode` are provided, this will be used if possible. 244 | --- The mode depends on the characters used in the string `str`. It seems to be 245 | --- possible to split the QR code to handle multiple modes, but we don't do that. 246 | local function get_version_eclevel_mode_bistringlength(str,requested_ec_level,mode) 247 | local local_mode 248 | if mode then 249 | assert(false,"not implemented") 250 | -- check if the mode is OK for the string 251 | local_mode = mode 252 | else 253 | local_mode = get_mode(str) 254 | end 255 | local version, ec_level 256 | version, ec_level = get_version_eclevel(#str,local_mode,requested_ec_level) 257 | local length_string = get_length(str,version,local_mode) 258 | return version,ec_level,binary(local_mode,4),local_mode,length_string 259 | end 260 | 261 | 262 | 263 | --- Step 2: Encode data 264 | --- =================== 265 | --- 266 | --- There are several ways to encode the data. We currently support only numeric, alphanumeric and binary. 267 | --- We already chose the encoding (a.k.a. mode) in the first step, so we need to apply the mode to the 268 | --- codeword. 269 | --- 270 | --- **Numeric**: take three digits and encode them in 10 bits 271 | --- **Alphanumeric**: take two characters and encode them in 11 bits 272 | --- **Binary**: take one octet and encode it in 8 bits 273 | 274 | local asciitbl = { 275 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -- 0x01-0x0f 276 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -- 0x10-0x1f 277 | 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, -- 0x20-0x2f 278 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, -- 0x30-0x3f 279 | -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, -- 0x40-0x4f 280 | 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -- 0x50-0x5f 281 | } 282 | 283 | -- Return a binary representation of the numeric string `str`. This must contain only digits 0-9. 284 | local function encode_string_numeric(str) 285 | local encodebuffer = {} 286 | for i = 1, #str, 3 do 287 | local a = sub(str,i,i+2) 288 | -- #a is 1, 2, or 3, so bits are 4, 7, or 10 289 | encodebuffer[#encodebuffer+1]=binary(tonumber(a), #a * 3 + 1) 290 | end 291 | return table.concat(encodebuffer) 292 | end 293 | 294 | -- Return a binary representation of the alphanumeric string `str`. This must contain only 295 | -- digits 0-9, uppercase letters A-Z, space and the following chars: $%*./:+-. 296 | local function encode_string_ascii(str) 297 | local encodebuffer = {} 298 | local int 299 | local b1, b2 300 | for i = 1, #str, 2 do 301 | local a = sub(str,i,i+1) 302 | if #a == 2 then 303 | b1 = asciitbl[byte(sub(a,1,1))] 304 | b2 = asciitbl[byte(sub(a,2,2))] 305 | int = b1 * 45 + b2 306 | encodebuffer[#encodebuffer+1] = binary(int,11) 307 | else 308 | int = asciitbl[byte(a)] 309 | encodebuffer[#encodebuffer+1] = binary(int,6) 310 | end 311 | end 312 | return table.concat(encodebuffer) 313 | end 314 | 315 | -- Return a bitstring representing string str in binary mode. 316 | -- We don't handle UTF-8 in any special way because we assume the 317 | -- scanner recognizes UTF-8 and displays it correctly. 318 | local function encode_string_binary(str) 319 | local encodebuffer = {} 320 | for i = 1, #str do 321 | encodebuffer[i] = binary(byte(str,i),8) 322 | end 323 | return table.concat(encodebuffer) 324 | end 325 | 326 | -- Return a bitstring representing string str in the given mode. 327 | local function encode_data(str,mode) 328 | if mode == 1 then 329 | return encode_string_numeric(str) 330 | elseif mode == 2 then 331 | return encode_string_ascii(str) 332 | elseif mode == 4 then 333 | return encode_string_binary(str) 334 | else 335 | assert(false,"not implemented yet") 336 | end 337 | end 338 | 339 | -- Encoding the codeword is not enough. We need to make sure that 340 | -- the length of the binary string is equal to the number of codewords of the version. 341 | local function add_pad_data(version,ec_level,data) 342 | local cpty = capacity[version][ec_level] * 8 343 | local buffer = {data} 344 | local buffer_len = #data 345 | local count_to_pad = min(4,cpty - buffer_len) 346 | if count_to_pad > 0 then 347 | buffer[#buffer + 1] = rep("0",count_to_pad) 348 | buffer_len = buffer_len + count_to_pad 349 | end 350 | if buffer_len % 8 ~= 0 then 351 | local missing = 8 - buffer_len % 8 352 | buffer[#buffer + 1] = rep("0",missing) 353 | buffer_len = buffer_len + missing 354 | end 355 | -- add "11101100" and "00010001" until enough data 356 | local remaining_bytes = (cpty - buffer_len) / 8 -- decimal doesn't matter 357 | for i=1,remaining_bytes do 358 | buffer[#buffer + 1] = i % 2 == 1 and "11101100" or "00010001" 359 | end 360 | return concat(buffer) 361 | end 362 | 363 | 364 | 365 | --- Step 3: Organize data and calculate error correction code 366 | --- ========================================================= 367 | --- The data in the qrcode is not encoded linearly. For example code 5-H has four blocks, the first two blocks 368 | --- contain 11 codewords and 22 error correction codes each, the second block contain 12 codewords and 22 ec codes each. 369 | --- We just take the table from the spec and don't calculate the blocks ourself. The table `ecblocks` contains this info. 370 | --- 371 | --- During the phase of splitting the data into codewords, we do the calculation for error correction codes. This step involves 372 | --- polynomial division. Find a math book from school and follow the code here :) 373 | 374 | --- ### Reed Solomon error correction 375 | --- Now this is the slightly ugly part of the error correction. We start with log/antilog tables 376 | -- https://codyplanteen.com/assets/rs/gf256_log_antilog.pdf 377 | local alpha_int = { 378 | [0] = 1, 379 | 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 380 | 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 381 | 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, 70, 382 | 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, 95, 383 | 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 253, 384 | 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, 217, 385 | 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206, 129, 386 | 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, 133, 387 | 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, 168, 388 | 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, 230, 389 | 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 227, 390 | 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, 130, 391 | 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166, 81, 392 | 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, 18, 393 | 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, 44, 394 | 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 0, 0 395 | } 396 | 397 | local int_alpha = { 398 | [0] = 256, -- special value 399 | 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 400 | 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 401 | 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 29, 402 | 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6, 403 | 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 404 | 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 405 | 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, 202, 406 | 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 7, 407 | 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, 408 | 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 409 | 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 410 | 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 31, 411 | 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, 108, 412 | 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 413 | 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 414 | 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175 415 | } 416 | 417 | -- We only need the polynomial generators for block sizes 7, 10, 13, 15, 16, 17, 18, 20, 22, 24, 26, 28, and 30. Version 418 | -- 2 of the qr codes don't need larger ones (as opposed to version 1). The table has the format x^1*ɑ^21 + x^2*a^102 ... 419 | local generator_polynomial = { 420 | [7] = { 21, 102, 238, 149, 146, 229, 87, 0}, 421 | [10] = { 45, 32, 94, 64, 70, 118, 61, 46, 67, 251, 0 }, 422 | [13] = { 78, 140, 206, 218, 130, 104, 106, 100, 86, 100, 176, 152, 74, 0 }, 423 | [15] = {105, 99, 5, 124, 140, 237, 58, 58, 51, 37, 202, 91, 61, 183, 8, 0}, 424 | [16] = {120, 225, 194, 182, 169, 147, 191, 91, 3, 76, 161, 102, 109, 107, 104, 120, 0}, 425 | [17] = {136, 163, 243, 39, 150, 99, 24, 147, 214, 206, 123, 239, 43, 78, 206, 139, 43, 0}, 426 | [18] = {153, 96, 98, 5, 179, 252, 148, 152, 187, 79, 170, 118, 97, 184, 94, 158, 234, 215, 0}, 427 | [20] = {190, 188, 212, 212, 164, 156, 239, 83, 225, 221, 180, 202, 187, 26, 163, 61, 50, 79, 60, 17, 0}, 428 | [22] = {231, 165, 105, 160, 134, 219, 80, 98, 172, 8, 74, 200, 53, 221, 109, 14, 230, 93, 242, 247, 171, 210, 0}, 429 | [24] = { 21, 227, 96, 87, 232, 117, 0, 111, 218, 228, 226, 192, 152, 169, 180, 159, 126, 251, 117, 211, 48, 135, 121, 229, 0}, 430 | [26] = { 70, 218, 145, 153, 227, 48, 102, 13, 142, 245, 21, 161, 53, 165, 28, 111, 201, 145, 17, 118, 182, 103, 2, 158, 125, 173, 0}, 431 | [28] = {123, 9, 37, 242, 119, 212, 195, 42, 87, 245, 43, 21, 201, 232, 27, 205, 147, 195, 190, 110, 180, 108, 234, 224, 104, 200, 223, 168, 0}, 432 | [30] = {180, 192, 40, 238, 216, 251, 37, 156, 130, 224, 193, 226, 173, 42, 125, 222, 96, 239, 86, 110, 48, 50, 182, 179, 31, 216, 152, 145, 173, 41, 0}} 433 | 434 | 435 | -- Turn a binary string of length 8*x into a table size x of numbers. 436 | local function convert_bitstring_to_bytes(data) 437 | local msg = {} 438 | for i=1, #data / 8 do 439 | msg[i] = tonumber(sub(data,(i - 1) * 8 + 1,i * 8),2) 440 | end 441 | return msg 442 | end 443 | 444 | -- Return a table that has 0's in the first entries and then the alpha 445 | -- representation of the generator polynomial 446 | local function get_generator_polynomial_adjusted(num_ec_codewords,highest_exponent) 447 | local gp_alpha = {[0]=0} 448 | for i=0,highest_exponent - num_ec_codewords - 1 do 449 | gp_alpha[i] = 0 450 | end 451 | local gp = generator_polynomial[num_ec_codewords] 452 | for i=1,num_ec_codewords + 1 do 453 | gp_alpha[highest_exponent - num_ec_codewords + i - 1] = gp[i] 454 | end 455 | return gp_alpha 456 | end 457 | 458 | -- That's the heart of the error correction calculation. 459 | local function calculate_error_correction(data,num_ec_codewords) 460 | local mp 461 | if type(data)=="string" then 462 | mp = convert_bitstring_to_bytes(data) 463 | elseif type(data)=="table" then 464 | mp = data 465 | else 466 | assert(false,format("Unknown type for data: %s",type(data))) 467 | end 468 | local len_message = #mp 469 | 470 | local highest_exponent = len_message + num_ec_codewords - 1 471 | local gp_alpha 472 | local mp_int = {} 473 | -- create message shifted to left (highest exponent) 474 | for i=1,len_message do 475 | mp_int[highest_exponent - i + 1] = mp[i] 476 | end 477 | for i=1,highest_exponent - len_message do 478 | mp_int[i] = 0 479 | end 480 | mp_int[0] = 0 481 | 482 | while highest_exponent >= num_ec_codewords do 483 | gp_alpha = get_generator_polynomial_adjusted(num_ec_codewords,highest_exponent) 484 | 485 | -- Multiply generator polynomial by first coefficient of the above polynomial 486 | 487 | -- take the highest exponent from the message polynom (alpha) and add 488 | -- it to the generator polynom 489 | local exp = int_alpha[mp_int[highest_exponent]] 490 | for i=highest_exponent,highest_exponent - num_ec_codewords,-1 do 491 | if exp ~= 256 then 492 | gp_alpha[i] = (gp_alpha[i] + exp) % 255 493 | else 494 | gp_alpha[i] = 256 495 | end 496 | end 497 | for i=highest_exponent - num_ec_codewords - 1,0,-1 do 498 | gp_alpha[i] = 256 499 | end 500 | 501 | for i=highest_exponent,0,-1 do 502 | mp_int[i] = xor_lookup[alpha_int[gp_alpha[i]]][mp_int[i]] 503 | end 504 | -- remove leading 0's 505 | for i=highest_exponent,num_ec_codewords,-1 do 506 | if mp_int[i]==0 then 507 | highest_exponent=i-1 508 | else 509 | break 510 | end 511 | end 512 | 513 | if highest_exponent 0 then 584 | -- -- add 25 for each alignment pattern 585 | -- function_pattern_modules = function_pattern_modules + 25 * ( count_alignment_pattern^2 - 3 ) 586 | -- -- but subtract the timing pattern occupied by the alignment pattern on the top and left 587 | -- function_pattern_modules = function_pattern_modules - ( count_alignment_pattern - 2) * 10 588 | -- end 589 | -- size = size - function_pattern_modules 590 | -- if version > 6 then 591 | -- size = size - 67 592 | -- else 593 | -- size = size - 31 594 | -- end 595 | -- return math.floor(size/8),math.fmod(size,8) 596 | -- end 597 | 598 | --- Example: Version 5-H has four data and four error correction blocks. The table above lists 599 | --- `2, {33,11,11}, 2,{34,12,11}` for entry [5][4]. This means we take two blocks with 11 codewords 600 | --- and two blocks with 12 codewords, and two blocks with 33 - 11 = 22 ec codes and another 601 | --- two blocks with 34 - 12 = 22 ec codes. 602 | --- Block 1: D1 D2 D3 ... D11 603 | --- Block 2: D12 D13 D14 ... D22 604 | --- Block 3: D23 D24 D25 ... D33 D34 605 | --- Block 4: D35 D36 D37 ... D45 D46 606 | --- Then we place the data like this in the matrix: D1, D12, D23, D35, D2, D13, D24, D36 ... D45, D34, D46. The same goes 607 | --- with error correction codes. 608 | 609 | -- The given data can be a string of 0's and 1' (with #string mod 8 == 0). 610 | -- Alternatively the data can be a table of codewords. The number of codewords 611 | -- must match the capacity of the qr code. 612 | local function arrange_codewords_and_calculate_ec(version,ec_level,data) 613 | if type(data)=="table" then 614 | local tmp = {} 615 | for i=1,#data do 616 | tmp[i] = binary(data[i],8) 617 | end 618 | data = concat(tmp) 619 | end 620 | -- If the size of the data is not enough for the codeword, we add 0's and two special bytes until finished. 621 | local blocks = ecblocks[version][ec_level] 622 | local size_datablock_bytes, size_ecblock_bytes 623 | local datablocks = {} 624 | local final_ecblocks = {} 625 | local pos = 0 626 | for i=1,#blocks/2 do 627 | size_datablock_bytes = blocks[2*i][2] 628 | size_ecblock_bytes = blocks[2*i][1] - size_datablock_bytes 629 | for _=1,blocks[2*i - 1] do 630 | datablocks[#datablocks + 1] = sub(data, pos * 8 + 1,( pos + size_datablock_bytes)*8) 631 | local tmp_tab = calculate_error_correction(datablocks[#datablocks],size_ecblock_bytes) 632 | local tmp_str = {} 633 | for x=1,#tmp_tab do 634 | tmp_str[#tmp_str + 1] = binary(tmp_tab[x],8) 635 | end 636 | final_ecblocks[#final_ecblocks + 1] = concat(tmp_str) 637 | pos = pos + size_datablock_bytes 638 | end 639 | end 640 | 641 | -- Weave the data blocks. When there are multiple block sizes, the final data stream looks like: 642 | -- b1's 1st byte, b2's 1st byte, (b3's 1st byte, ...) 643 | -- b1's 2nd byte, b2's 2nd byte, (b3's 2nd byte, ...) 644 | -- b1's 3rd byte, ... 645 | local arranged_data = {} 646 | local maxBlockLen = 0 647 | for i = 1, #datablocks do maxBlockLen = max(maxBlockLen, #datablocks[i]) end 648 | for p = 1, maxBlockLen, 8 do 649 | for i = 1, #datablocks do 650 | arranged_data[#arranged_data + 1] = sub(datablocks[i], p, p + 7) 651 | end 652 | end 653 | 654 | -- Same for EC blocks 655 | maxBlockLen = 0 656 | for i = 1, #final_ecblocks do maxBlockLen = max(maxBlockLen, #final_ecblocks[i]) end 657 | for p = 1, maxBlockLen, 8 do 658 | for i = 1, #final_ecblocks do 659 | arranged_data[#arranged_data + 1] = sub(final_ecblocks[i], p, p + 7) 660 | end 661 | end 662 | return concat(arranged_data) 663 | end 664 | 665 | 666 | 667 | --- Step 4: Generate 8 matrices with different masks and calculate the penalty 668 | --- ========================================================================== 669 | --- 670 | --- Prepare matrix 671 | --- -------------- 672 | --- The first step is to prepare an _empty_ matrix for a given size/mask. The matrix has a 673 | --- few predefined areas that must be black or blank. We encode the matrix with a two 674 | --- dimensional field where the numbers determine which pixel is blank or not. 675 | --- 676 | --- The following code is used for our matrix: 677 | --- 0 = not in use yet, 678 | --- -2 = blank by mandatory pattern, 679 | --- 2 = black by mandatory pattern, 680 | --- -1 = blank by data, 681 | --- 1 = black by data 682 | --- 683 | --- To prepare the _empty_, we add positioning, alingment and timing patters. 684 | 685 | --- ### Positioning patterns ### 686 | local function add_position_detection_patterns(tab_x) 687 | local size = #tab_x 688 | -- allocate quite zone in the matrix area 689 | for i=1,8 do 690 | for j=1,8 do 691 | tab_x[i][j] = -2 692 | tab_x[size - 8 + i][j] = -2 693 | tab_x[i][size - 8 + j] = -2 694 | end 695 | end 696 | -- draw the detection pattern (outer) 697 | for i=1,7 do 698 | -- top left 699 | tab_x[1][i]=2 700 | tab_x[7][i]=2 701 | tab_x[i][1]=2 702 | tab_x[i][7]=2 703 | 704 | -- top right 705 | tab_x[size][i]=2 706 | tab_x[size - 6][i]=2 707 | tab_x[size - i + 1][1]=2 708 | tab_x[size - i + 1][7]=2 709 | 710 | -- bottom left 711 | tab_x[1][size - i + 1]=2 712 | tab_x[7][size - i + 1]=2 713 | tab_x[i][size - 6]=2 714 | tab_x[i][size]=2 715 | end 716 | -- draw the detection pattern (inner) 717 | for i=1,3 do 718 | for j=1,3 do 719 | -- top left 720 | tab_x[2+j][i+2]=2 721 | -- top right 722 | tab_x[size - j - 1][i+2]=2 723 | -- bottom left 724 | tab_x[2 + j][size - i - 1]=2 725 | end 726 | end 727 | end 728 | 729 | --- ### Timing patterns ### 730 | -- The timing patterns (two) are the dashed lines between two adjacent positioning patterns on row/column 7. 731 | local function add_timing_pattern(tab_x) 732 | local line,col=7,9 733 | for i=col,#tab_x-8 do 734 | tab_x[i][line] = i%2==0 and -2 or 2 735 | tab_x[line][i] = i%2==0 and -2 or 2 736 | end 737 | end 738 | 739 | --- ### Alignment patterns ### 740 | --- The alignment patterns must be added to the matrix for versions > 1. The amount and positions depend on the versions and are 741 | --- given by the spec. Beware: the patterns must not be placed where we have the positioning patterns 742 | --- (that is: top left, top right and bottom left.) 743 | 744 | -- For each version, where should we place the alignment patterns? See table E.1 of the spec 745 | local alignment_pattern = { 746 | {},{6,18},{6,22},{6,26},{6,30},{6,34}, -- 1-6 747 | {6,22,38},{6,24,42},{6,26,46},{6,28,50},{6,30,54},{6,32,58},{6,34,62}, -- 7-13 748 | {6,26,46,66},{6,26,48,70},{6,26,50,74},{6,30,54,78},{6,30,56,82},{6,30,58,86},{6,34,62,90}, -- 14-20 749 | {6,28,50,72,94},{6,26,50,74,98},{6,30,54,78,102},{6,28,54,80,106},{6,32,58,84,110},{6,30,58,86,114},{6,34,62,90,118}, -- 21-27 750 | {6,26,50,74,98 ,122},{6,30,54,78,102,126},{6,26,52,78,104,130},{6,30,56,82,108,134},{6,34,60,86,112,138},{6,30,58,86,114,142},{6,34,62,90,118,146}, -- 28-34 751 | {6,30,54,78,102,126,150}, {6,24,50,76,102,128,154},{6,28,54,80,106,132,158},{6,32,58,84,110,136,162},{6,26,54,82,110,138,166},{6,30,58,86,114,142,170} -- 35 - 40 752 | } 753 | 754 | --- The alignment pattern has size 5x5 and looks like this: 755 | --- XXXXX 756 | --- X X 757 | --- X X X 758 | --- X X 759 | --- XXXXX 760 | local function add_alignment_pattern(tab_x) 761 | local version = (#tab_x - 17) / 4 762 | local ap = alignment_pattern[version] 763 | local pos_x, pos_y 764 | for x=1,#ap do 765 | for y=1,#ap do 766 | -- we must not put an alignment pattern on top of the positioning pattern 767 | if not (x == 1 and y == 1 or x == #ap and y == 1 or x == 1 and y == #ap ) then 768 | pos_x,pos_y=ap[x]+1,ap[y]+1 769 | for dy=-2,2 do 770 | for dx=-2,2 do 771 | -- form the pattern with checking chebyshev distance instead of hardcoding 772 | tab_x[pos_x+dx][pos_y+dy]=max(abs(dx),abs(dy))%2==0 and 2 or -2 773 | end 774 | end 775 | end 776 | end 777 | end 778 | end 779 | 780 | --- ### Type information ### 781 | --- Let's not forget the type information that is in column 9 next to the left positioning patterns and on row 9 below 782 | --- the top positioning patterns. This type information is not fixed, it depends on the mask and the error correction. 783 | 784 | -- The first index is ec level (LMQH,1-4), the second is the mask (0-7). This bitstring of length 15 is to be used 785 | -- as mandatory pattern in the qrcode. Mask -1 is for debugging purpose only and is the 'noop' mask. 786 | local typeinfo = { 787 | { [-1]= "111111111111111", [0] = "111011111000100", "111001011110011", "111110110101010", "111100010011101", "110011000101111", "110001100011000", "110110001000001", "110100101110110" }, 788 | { [-1]= "111111111111111", [0] = "101010000010010", "101000100100101", "101111001111100", "101101101001011", "100010111111001", "100000011001110", "100111110010111", "100101010100000" }, 789 | { [-1]= "111111111111111", [0] = "011010101011111", "011000001101000", "011111100110001", "011101000000110", "010010010110100", "010000110000011", "010111011011010", "010101111101101" }, 790 | { [-1]= "111111111111111", [0] = "001011010001001", "001001110111110", "001110011100111", "001100111010000", "000011101100010", "000001001010101", "000110100001100", "000100000111011" } 791 | } 792 | 793 | -- The typeinfo is a mixture of mask and ec level information and is 794 | -- added twice to the qr code, one horizontal, one vertical. 795 | local function add_typeinfo_to_matrix(matrix,ec_level,mask) 796 | local ec_mask_type = typeinfo[ec_level][mask] 797 | 798 | local bit 799 | -- vertical from bottom to top 800 | for i=1,7 do 801 | bit = sub(ec_mask_type,i,i) 802 | fill_matrix_position(matrix,bit,9,#matrix - i + 1) 803 | end 804 | for i=8,9 do 805 | bit = sub(ec_mask_type,i,i) 806 | fill_matrix_position(matrix,bit,9,17-i) 807 | end 808 | for i=10,15 do 809 | bit = sub(ec_mask_type,i,i) 810 | fill_matrix_position(matrix,bit,9,16 - i) 811 | end 812 | -- horizontal, left to right 813 | for i=1,6 do 814 | bit = sub(ec_mask_type,i,i) 815 | fill_matrix_position(matrix,bit,i,9) 816 | end 817 | bit = sub(ec_mask_type,7,7) 818 | fill_matrix_position(matrix,bit,8,9) 819 | for i=8,15 do 820 | bit = sub(ec_mask_type,i,i) 821 | fill_matrix_position(matrix,bit,#matrix - 15 + i,9) 822 | end 823 | end 824 | 825 | -- Bits for version information 7-40 826 | -- The reversed strings from https://www.thonky.com/qr-code-tutorial/format-version-tables 827 | local version_information = { 828 | "001010010011111000", "001111011010000100", "100110010101100100", "110010110010010100", 829 | "011011111101110100", "010001101110001100", "111000100001101100", "101100000110011100", "000101001001111100", 830 | "000111101101000010", "101110100010100010", "111010000101010010", "010011001010110010", "011001011001001010", 831 | "110000010110101010", "100100110001011010", "001101111110111010", "001000110111000110", "100001111000100110", 832 | "110101011111010110", "011100010000110110", "010110000011001110", "111111001100101110", "101011101011011110", 833 | "000010100100111110", "101010111001000001", "000011110110100001", "010111010001010001", "111110011110110001", 834 | "110100001101001001", "011101000010101001", "001001100101011001", "100000101010111001", "100101100011000101", 835 | } 836 | 837 | -- Versions 7 and above need two bitfields with version information added to the code 838 | local function add_version_information(matrix,version) 839 | if version < 7 then return end 840 | local size = #matrix 841 | local bitstring = version_information[version - 6] 842 | local x,y, bit 843 | local start_x, start_y 844 | -- first top right 845 | start_x = size - 10 846 | start_y = 1 847 | for i=1,#bitstring do 848 | bit = sub(bitstring,i,i) 849 | x = start_x + (i - 1) % 3 850 | y = start_y + floor((i - 1) / 3) 851 | fill_matrix_position(matrix,bit,x,y) 852 | end 853 | 854 | -- now bottom left 855 | start_x = 1 856 | start_y = size - 10 857 | for i=1,#bitstring do 858 | bit = sub(bitstring,i,i) 859 | x = start_x + floor((i - 1) / 3) 860 | y = start_y + (i - 1) % 3 861 | fill_matrix_position(matrix,bit,x,y) 862 | end 863 | end 864 | 865 | --- Now it's time to use the methods above to create a prefilled matrix for the given mask 866 | local function prepare_matrix_with_mask(version,ec_level,mask) 867 | local size = version * 4 + 17 868 | local tab_x = {} 869 | 870 | for i=1,size do 871 | tab_x[i]={} 872 | for j=1,size do 873 | tab_x[i][j] = 0 874 | end 875 | end 876 | add_position_detection_patterns(tab_x) 877 | add_timing_pattern(tab_x) 878 | add_version_information(tab_x,version) 879 | 880 | -- black pixel above lower left position detection pattern 881 | tab_x[9][size - 7] = 2 882 | add_alignment_pattern(tab_x) 883 | add_typeinfo_to_matrix(tab_x,ec_level, mask) 884 | return tab_x 885 | end 886 | 887 | --- Finally we come to the place where we need to put the calculated data (remember step 3?) into the qr code. 888 | --- We do this for each mask. BTW speaking of mask, this is what we find in the spec: 889 | --- Mask Pattern Reference Condition 890 | --- 000 (y + x) mod 2 = 0 891 | --- 001 y mod 2 = 0 892 | --- 010 x mod 3 = 0 893 | --- 011 (y + x) mod 3 = 0 894 | --- 100 ((y div 2) + (x div 3)) mod 2 = 0 895 | --- 101 (y x) mod 2 + (y x) mod 3 = 0 896 | --- 110 ((y x) mod 2 + (y x) mod 3) mod 2 = 0 897 | --- 111 ((y x) mod 3 + (y+x) mod 2) mod 2 = 0 898 | 899 | -- Mask functions, i & j are 0-based, so input should be (x-1,y-1) 900 | -- true means 'invert this bit' 901 | local maskFunc={ 902 | [-1]=function(_,_) return false end, -- test purpose only, no mask applied 903 | [0]=function(x,y) return (y+x)%2==0 end, 904 | function(_,y) return y%2==0 end, 905 | function(x,_) return x%3==0 end, 906 | function(x,y) return (y+x)%3==0 end, 907 | function(x,y) return (y%4-1.5)*(x%6-2.5)>0 end, -- optimized for not using math.floor (too slow) or // operation (new Lua only) 908 | function(x,y) return (y*x)%2+(y*x)%3==0 end, 909 | function(x,y) return ((y*x)%3+y*x)%2==0 end, 910 | function(x,y) return ((y*x)%3+y+x)%2==0 end, 911 | } 912 | 913 | -- Receive 0 (blank) or 1 (black) from data, 914 | -- Return -1 (blank) or 1 (black) depending on the value, mask, and position. 915 | -- Parameter mask is 0-7 (-1 for 'no mask'). x and y are 1-based coordinates, 916 | -- 1,1 = upper left. value must be 0 or 1. 917 | local function get_pixel_with_mask(mask,x,y,dataBit) 918 | local invert = maskFunc[mask](x-1,y-1) 919 | return (dataBit==0)==invert and 1 or -1 920 | -- This^ == is used as boolean XNOR: 921 | -- data F T <- invert? 922 | -- 0 F -1 1 923 | -- 1 T 1 -1 924 | end 925 | 926 | -- Add the data string (0's and 1's) to the matrix for the given mask. 927 | local function add_data_to_matrix(matrix,data,mask) 928 | local size = #matrix 929 | -- Fill data into matrix 930 | local ptr=1 -- data pointer 931 | local x,y=size,size -- writing position, starts from bottom right 932 | local x_dir,y_dir=-1,-1 -- state of movement, notice that Y step once each two X steps 933 | while true do 934 | -- 0 means available data cell to write data 935 | if matrix[x][y]==0 then 936 | matrix[x][y] = get_pixel_with_mask(mask,x,y,byte(data,ptr)-48) -- '0' = 48, '1' = 49 937 | ptr = ptr + 1 938 | if ptr > #data or x < 0 then return matrix end -- all data written, finish 939 | end 940 | 941 | -- Move to next cell (won't write into unavailable cell so it's fine to move 1 step each time) 942 | -- switch left/right 943 | x = x + x_dir 944 | -- if just stepped right, it means current 2 bits were finished 945 | if x_dir == 1 then 946 | -- so we step up/down for next 2 bits 947 | y = y + y_dir 948 | 949 | -- when we went outside the matrix, move 2 cells left and turn back 950 | if not matrix[y] then -- square, so matrix[y] will be nil if y is out of range, no matter [x][y] or [y][x] 951 | x = x - 2 952 | if x == 7 then x = 6 end -- jump over timing pattern 953 | y = y_dir == -1 and 1 or size 954 | y_dir = -y_dir 955 | end 956 | end 957 | -- prepare next left/right 958 | x_dir = -x_dir 959 | end 960 | end 961 | 962 | --- The total penalty of the matrix is the sum of four steps. The following steps are taken into account: 963 | --- 964 | --- 1. Adjacent modules in row/column in same color 965 | --- 1. Block of modules in same color 966 | --- 1. 1:1:3:1:1 ratio (dark:light:dark:light:dark) pattern in row/column 967 | --- 1. Proportion of dark modules in entire symbol 968 | --- 969 | --- This all is done to avoid bad patterns in the code that prevent the scanner from 970 | --- reading the code. 971 | -- Return the penalty for the given matrix 972 | local function calculate_penalty(matrix) 973 | local penalty1, penalty2, penalty3 = 0,0,0 974 | local size = #matrix 975 | -- this is for penalty 4 976 | local number_of_dark_cells = 0 977 | 978 | -- 1: Adjacent modules in row/column in same color 979 | -- -------------------------------------------- 980 | -- No. of modules = (5+i) -> 3 + i 981 | local last_bit_blank -- < 0: blank, > 0: black 982 | local is_blank 983 | local number_of_consecutive_bits 984 | -- first: vertical 985 | for x=1,size do 986 | number_of_consecutive_bits = 0 987 | last_bit_blank = nil 988 | for y = 1,size do 989 | if matrix[x][y] > 0 then 990 | -- small optimization: this is for penalty 4 991 | number_of_dark_cells = number_of_dark_cells + 1 992 | is_blank = false 993 | else 994 | is_blank = true 995 | end 996 | if last_bit_blank == is_blank then 997 | number_of_consecutive_bits = number_of_consecutive_bits + 1 998 | else 999 | if number_of_consecutive_bits >= 5 then 1000 | penalty1 = penalty1 + number_of_consecutive_bits - 2 1001 | end 1002 | number_of_consecutive_bits = 1 1003 | end 1004 | last_bit_blank = is_blank 1005 | end 1006 | if number_of_consecutive_bits >= 5 then 1007 | penalty1 = penalty1 + number_of_consecutive_bits - 2 1008 | end 1009 | end 1010 | -- now horizontal 1011 | for y=1,size do 1012 | number_of_consecutive_bits = 0 1013 | last_bit_blank = nil 1014 | for x = 1,size do 1015 | is_blank = matrix[x][y] < 0 1016 | if last_bit_blank == is_blank then 1017 | number_of_consecutive_bits = number_of_consecutive_bits + 1 1018 | else 1019 | if number_of_consecutive_bits >= 5 then 1020 | penalty1 = penalty1 + number_of_consecutive_bits - 2 1021 | end 1022 | number_of_consecutive_bits = 1 1023 | end 1024 | last_bit_blank = is_blank 1025 | end 1026 | if number_of_consecutive_bits >= 5 then 1027 | penalty1 = penalty1 + number_of_consecutive_bits - 2 1028 | end 1029 | end 1030 | for x=1,size do 1031 | for y=1,size do 1032 | -- 2: Block of modules in same color 1033 | -- ----------------------------------- 1034 | -- Blocksize = m × n -> 3 × (m-1) × (n-1) 1035 | if (y < size - 1) and (x < size - 1) and ( 1036 | (matrix[x][y] < 0 and matrix[x+1][y] < 0 and matrix[x][y+1] < 0 and matrix[x+1][y+1] < 0) or 1037 | (matrix[x][y] > 0 and matrix[x+1][y] > 0 and matrix[x][y+1] > 0 and matrix[x+1][y+1] > 0) 1038 | ) then penalty2 = penalty2 + 3 end 1039 | 1040 | -- 3: 1:1:3:1:1 ratio (dark:light:dark:light:dark) pattern in row/column 1041 | -- ------------------------------------------------------------------ 1042 | -- Gives 40 points each 1043 | -- 1044 | -- I have no idea why we need the extra 0000 on left or right side. The spec doesn't mention it, 1045 | -- other sources do mention it. This is heavily inspired by zxing. 1046 | if (y + 6 < size and 1047 | matrix[x][y] > 0 and 1048 | matrix[x][y + 1] < 0 and 1049 | matrix[x][y + 2] > 0 and 1050 | matrix[x][y + 3] > 0 and 1051 | matrix[x][y + 4] > 0 and 1052 | matrix[x][y + 5] < 0 and 1053 | matrix[x][y + 6] > 0 and 1054 | ((y + 10 < size and 1055 | matrix[x][y + 7] < 0 and 1056 | matrix[x][y + 8] < 0 and 1057 | matrix[x][y + 9] < 0 and 1058 | matrix[x][y + 10] < 0) or 1059 | (y - 4 >= 1 and 1060 | matrix[x][y - 1] < 0 and 1061 | matrix[x][y - 2] < 0 and 1062 | matrix[x][y - 3] < 0 and 1063 | matrix[x][y - 4] < 0))) then penalty3 = penalty3 + 40 end 1064 | if (x + 6 <= size and 1065 | matrix[x][y] > 0 and 1066 | matrix[x + 1][y] < 0 and 1067 | matrix[x + 2][y] > 0 and 1068 | matrix[x + 3][y] > 0 and 1069 | matrix[x + 4][y] > 0 and 1070 | matrix[x + 5][y] < 0 and 1071 | matrix[x + 6][y] > 0 and 1072 | ((x + 10 <= size and 1073 | matrix[x + 7][y] < 0 and 1074 | matrix[x + 8][y] < 0 and 1075 | matrix[x + 9][y] < 0 and 1076 | matrix[x + 10][y] < 0) or 1077 | (x - 4 >= 1 and 1078 | matrix[x - 1][y] < 0 and 1079 | matrix[x - 2][y] < 0 and 1080 | matrix[x - 3][y] < 0 and 1081 | matrix[x - 4][y] < 0))) then penalty3 = penalty3 + 40 end 1082 | end 1083 | end 1084 | -- 4: Proportion of dark modules in entire symbol 1085 | -- ---------------------------------------------- 1086 | -- 50 ± (5 × k)% to 50 ± (5 × (k + 1))% -> 10 × k 1087 | local dark_ratio = number_of_dark_cells / (size * size) 1088 | local penalty4 = floor(abs(dark_ratio * 100 - 50)) * 2 1089 | return penalty1 + penalty2 + penalty3 + penalty4 1090 | end 1091 | 1092 | -- Create a matrix for the given parameters and calculate the penalty score. 1093 | -- Return both (matrix and penalty) 1094 | local function get_matrix_and_penalty(version,ec_level,data,mask) 1095 | local tab = prepare_matrix_with_mask(version,ec_level,mask) 1096 | add_data_to_matrix(tab,data,mask) 1097 | local penalty = calculate_penalty(tab) 1098 | return tab, penalty 1099 | end 1100 | 1101 | -- Return the matrix with the smallest penalty. To to this 1102 | -- we try out the matrix for all 8 masks and determine the 1103 | -- penalty (score) each. 1104 | local function get_matrix_with_lowest_penalty(version,ec_level,data) 1105 | local tab, penalty 1106 | local tab_min_penalty, min_penalty 1107 | 1108 | -- try masks 0-7 1109 | tab_min_penalty, min_penalty = get_matrix_and_penalty(version,ec_level,data,0) 1110 | for i=1,7 do 1111 | tab, penalty = get_matrix_and_penalty(version,ec_level,data,i) 1112 | if penalty < min_penalty then 1113 | tab_min_penalty = tab 1114 | min_penalty = penalty 1115 | end 1116 | end 1117 | return tab_min_penalty 1118 | end 1119 | 1120 | --- The main function. We connect everything together. Remember from above: 1121 | --- 1122 | --- 1. Determine version, ec level and mode (=encoding) for codeword 1123 | --- 1. Encode data 1124 | --- 1. Arrange data and calculate error correction code 1125 | --- 1. Generate 8 matrices with different masks and calculate the penalty 1126 | --- 1. Return qrcode with least penalty 1127 | 1128 | -- Return 1129 | -- on success: true, number matrix (only has ±1&±2. positive means black, ±2 means mandatory, in case if you didn't read comments above) 1130 | -- on failed: false, error message string 1131 | -- If ec_level or mode is given, use the ones for generating the qrcode. (mode option is not implemented yet, but it will be determined automatically) 1132 | local function qrcode(str,ec_level,mode_enc) 1133 | local arranged_data, version, data_raw, mode, len_bitstring 1134 | version, ec_level, data_raw, mode, len_bitstring = get_version_eclevel_mode_bistringlength(str,ec_level,mode_enc) 1135 | data_raw = data_raw .. len_bitstring 1136 | data_raw = data_raw .. encode_data(str,mode) 1137 | data_raw = add_pad_data(version,ec_level,data_raw) 1138 | arranged_data = arrange_codewords_and_calculate_ec(version,ec_level,data_raw) 1139 | if #arranged_data % 8 ~= 0 then 1140 | return false, format("Arranged data %% 8 != 0: data length = %d, mod 8 = %d",#arranged_data, #arranged_data % 8) 1141 | end 1142 | arranged_data = arranged_data .. rep("0",remainder[version]) 1143 | local tab = get_matrix_with_lowest_penalty(version,ec_level,arranged_data) 1144 | return true, tab 1145 | end 1146 | 1147 | if testing then 1148 | return { 1149 | encode_string_numeric = encode_string_numeric, 1150 | encode_string_ascii = encode_string_ascii, 1151 | encode_string_binary = encode_string_binary, 1152 | encode_data = encode_data, 1153 | add_position_detection_patterns = add_position_detection_patterns, 1154 | add_timing_pattern = add_timing_pattern, 1155 | add_alignment_pattern = add_alignment_pattern, 1156 | fill_matrix_position = fill_matrix_position, 1157 | add_typeinfo_to_matrix = add_typeinfo_to_matrix, 1158 | add_version_information = add_version_information, 1159 | prepare_matrix_with_mask = prepare_matrix_with_mask, 1160 | add_data_to_matrix = add_data_to_matrix, 1161 | qrcode = qrcode, 1162 | binary = binary, 1163 | get_mode = get_mode, 1164 | get_length = get_length, 1165 | add_pad_data = add_pad_data, 1166 | get_generator_polynominal_adjusted = get_generator_polynomial_adjusted, 1167 | get_pixel_with_mask = get_pixel_with_mask, 1168 | get_version_eclevel_mode_bistringlength = get_version_eclevel_mode_bistringlength, 1169 | remainder = remainder, 1170 | arrange_codewords_and_calculate_ec = arrange_codewords_and_calculate_ec, 1171 | calculate_error_correction = calculate_error_correction, 1172 | convert_bitstring_to_bytes = convert_bitstring_to_bytes, 1173 | xor_lookup = xor_lookup, 1174 | calculate_penalty = calculate_penalty, 1175 | get_matrix_and_penalty = get_matrix_and_penalty, 1176 | } 1177 | end 1178 | 1179 | return { 1180 | qrcode = qrcode 1181 | } 1182 | --------------------------------------------------------------------------------