Sign in

    moon-qrcode

    Pure-MoonBit QR Code Model 2 encoder with ASCII/SVG renderers, payload templates and a flat JS/wasm API | 纯 MoonBit 实现的 QR Code Model 2 编码器,支持 ASCII/SVG 渲染、载荷模板和扁平 JS/wasm API

    qrcode
    qr
    encoder
    iso18004
    svg
    Download zip
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    5 hours ago
    Downloads
    7

    #MoonQR

    CI

    English | 中文

    A pure-MoonBit QR Code encoder for ISO/IEC 18004 Model 2 symbols. The encoder produces the finished module matrix; its core, renderers and payload templates are dependency-free, and a thin CLI is included on top.

    • Versions 1–40, error-correction levels L / M / Q / H
    • Numeric, alphanumeric and UTF-8 byte segments with greedy auto-splitting
    • Automatic version selection and all eight data masks with Annex J scoring
    • Reed–Solomon over GF(28) (0x11D), BCH format/version information
    • Immutable QrCode matrix (size, get, modules, to_bytes)
    • ASCII, half-height and SVG renderers
    • Payload templates: URL, WiFi, tel, mailto, SMS, geo, vCard and text
    • Flat Uint8Array ABI for JS/wasm hosts; native CLI

    #Install

    moon add allenli178/moon-qrcode

    Requires MoonBit 0.1.20261020 or newer.

    #Quick start

    ///|
    test "README quick start" {
    let qr = @moon-qrcode.encode("HELLO WORLD", ec_level=@moon-qrcode.EcLevel::M)
    assert_eq(qr.version().value(), 1)
    assert_eq(qr.size(), 21)
    assert_true(qr.get(0, 0))
    let svg = @moon-qrcode.to_svg(qr, quiet_zone=4, module_size=4)
    assert_true(svg.contains("<svg"))
    }

    encode picks the smallest version in min_version..max_version that fits the selected EC level, then picks the mask with the lowest penalty.

    OptionDefaultMeaning
    text—payload string (UTF-8 in byte mode)
    ec_levelMerror-correction level
    min_version / max_version1 / 40version search range
    modeautoforce Numeric, Alphanumeric or Byte
    maskautoforce a mask M0–M7

    #Reading the matrix

    FunctionResult
    size()side length in modules (4 * version + 17)
    get(row, col)Bool for one module
    modules()row-major Array[Bool] copy
    to_bytes()row-major Array[Byte], 1 = dark
    version() / ec_level() / mask()metadata

    Errors are checked: DataTooLong carries the needed bits, capacity, version and EC level, and QrError::message() renders an actionable diagnostic.

    #Rendering

    ///|
    test "README rendering" {
    let qr = @moon-qrcode.encode("https://moonbitlang.com")
    let ascii = @moon-qrcode.to_ascii(qr, quiet_zone=2)
    let half = @moon-qrcode.to_ascii_half(qr)
    assert_true(ascii.length() > 0)
    assert_true(half.length() > 0)
    let custom = @moon-qrcode.to_svg(qr, dark="navy", light="#ffffff")
    assert_true(custom.contains("fill=\"navy\""))
    }

    • to_ascii(qr, quiet_zone?, scale?) uses double-width blocks; scale repeats each module.
    • to_ascii_half(qr, quiet_zone?) packs two module rows per terminal row.
    • to_svg(qr, quiet_zone?, module_size?, dark?, light?) returns a complete SVG document. Colors are validated and url(...) is rejected; invalid colors raise QrError::InvalidColor.

    #Payload templates

    ///|
    test "README templates" {
    let wifi = @moon-qrcode.Content::wifi("Cafe", "latte")
    assert_eq(wifi.to_text(), "WIFI:T:WPA;S:Cafe;P:latte;;")
    let card = @moon-qrcode.Content::vcard(
    "Ada Lovelace",
    email="ada@example.com",
    )
    let qr = @moon-qrcode.encode_content(card, ec_level=@moon-qrcode.EcLevel::H)
    assert_true(qr.size() >= 21)
    }

    Available constructors: Content::wifi(ssid, password, auth?), Content::mailto(address, subject?, body?), Content::sms(number, message?), Content::tel(number), Content::geo(lat, lng), Content::vcard(name, phone?, email?, org?), plus Text and Url. to_text() serializes a payload; encode_content() serializes and encodes it.

    #CLI

    # run from the module checkout moon run cmd/moonqr --target native -- "HELLO WORLD" -l H -o hello.svg # release binary: _build/native/release/build/cmd/moonqr/moonqr.exe moon build cmd/moonqr --target native --release

    USAGE: moonqr <payload> [options] -l, --level <L|M|Q|H> error correction level (default M) -v, --version <1..40> force an explicit version --min-version <n> smallest version considered --max-version <n> largest version considered -m, --mode <name> numeric | alphanumeric | byte (default auto) --mask <0..7> force a mask pattern -q, -qz, --quiet-zone <n> quiet zone width in modules (default 4) -s, --scale <n> ASCII scale factor (default 1) --module-size <n> SVG module size in pixels (default 4) -f, --format <name> ascii | half | svg (default: by output extension) -o, --output <path> write to a file instead of stdout --svg / --half format shorthands --dark / --light SVG colors

    Examples:

    moonqr "https://moonbitlang.com" --svg -o site.svg moonqr "0123456789" -v 2 -m numeric -s 2 moonqr "payload" --min-version 5 --max-version 10 -l Q -f half

    #JS / wasm flat API

    The js package exports a primitive-typed buffer ABI so hosts never pay for JSON or nested arrays. Build it with:

    moon build js --target js --release # _build/js/release/build/js/js.js + .d.ts moon build js --target wasm-gc --release # _build/wasm-gc/release/build/js/js.wasm

    import { encode_flat_buffer } from "./_build/js/release/build/js/js.js"; // ec_level: 0=L, 1=M, 2=Q, 3=H const result = encode_flat_buffer("HELLO WORLD", 1, 1, 40); if (result.$tag === 1) { const buf = result._0; // Uint8Array const size = (buf[0] << 8) | buf[1]; // side length in modules const version = buf[2]; const mask = buf[3]; const modules = buf.subarray(4); // size * size bytes, 1 = dark }

    Buffer layout: [size_hi, size_lo, version, mask, modules...]. The JS binding type-checks as Uint8Array; errors surface as Result ($tag === 0).

    Measured artifact sizes (release, this repository):

    ArtifactRawgzip
    wasm-gc encoder + flat API (js.wasm)20.6 KB8.6 KB
    JS ESM encoder + flat API (js.js)71.7 KB10.5 KB
    native release CLI (moonqr.exe)353 KB—

    #Correctness and testing

    moon test --target wasm-gc moon test --target wasm moon test --target js moon test --target native moon fmt --check && moon info # ZXing decode check (Node 18+) moon build cmd/moonqr --target native --release npm ci --prefix tools/zxing node tools/zxing/verify.js

    • 71 tests across core, render, content, js and the root facade.
    • Matrix digests are pinned from the Python qrcode reference implementation: 72 byte-mode vectors across versions/EC levels/masks, 32 alphanumeric and 8 numeric vectors (112 total), plus full-matrix snapshots.
    • GF(28), BCH divisibility/known values, RS parity vectors, capacity accounting, segment splitting, error paths and SVG/ASCII output are covered.
    • The same test suite passes on wasm, wasm-gc, JS and native targets, so output is bit-identical across the four backends.
    • Core coverage is above 90% (moon coverage analyze); remaining uncovered lines are defensive invariant checks and unreachable enum arms.
    • Twelve generated symbols (all EC levels, UTF-8, forced masks and versions, long payloads, payload templates) decode correctly with the ZXing reference implementation; the check runs in CI.

    Physical-camera tests on iOS/Android/WeChat are still manual; ZXing decoding and matrix-level cross-checking are the automated gates.

    #Scope

    Implemented: Model 2 versions 1–40, L/M/Q/H, numeric/alphanumeric/byte, greedy auto-segmentation, masks, renderers, templates, CLI, flat JS/wasm ABI.

    Deliberately excluded: decoding, PNG/JPEG/PDF export, logos/styling, Micro QR, Kanji mode, ECI and Structured Append. Formal moon prove contracts, a benchmark suite, physical-camera automation and a published npm wrapper are roadmap items; property tests stand in for proofs today.

    #Project layout

    core/ encoder: types, capacity, segments, bit buffer, RS, BCH, matrix, penalty render/ ASCII and SVG renderers content/ payload templates js/ flat Uint8Array ABI + ESM/wasm-gc link exports cmd/moonqr CLI tools/zxing ZXing decode verification harness (Node)

    core, render, content and js use only the MoonBit standard library. The CLI adds moonbitlang/x for filesystem and process support.

    #License

    Apache-2.0.

    Content

    Payload templates (URL, WiFi, vCard, tel, mailto, SMS, geo).

    载荷模板(URL、WiFi、vCard、电话、邮件、短信、地理坐标)。

    EcLevel

    Encoder facade: value types, capacity helpers and the single encode entry point.

    编码器门面:值类型、容量辅助函数和统一的 encode 入口。

    Mask

    Encoder facade: value types, capacity helpers and the single encode entry point.

    编码器门面:值类型、容量辅助函数和统一的 encode 入口。

    Mode

    Encoder facade: value types, capacity helpers and the single encode entry point.

    编码器门面:值类型、容量辅助函数和统一的 encode 入口。

    QrCode

    Encoder facade: value types, capacity helpers and the single encode entry point.

    编码器门面:值类型、容量辅助函数和统一的 encode 入口。

    QrError

    Encoder facade: value types, capacity helpers and the single encode entry point.

    编码器门面:值类型、容量辅助函数和统一的 encode 入口。

    Segment

    Encoder facade: value types, capacity helpers and the single encode entry point.

    编码器门面:值类型、容量辅助函数和统一的 encode 入口。

    Version

    Encoder facade: value types, capacity helpers and the single encode entry point.

    编码器门面:值类型、容量辅助函数和统一的 encode 入口。

    bits_needed

    fn bits_needed(segments : Array[
    Segment
    ], version : Int) -> Int

    Total bits needed for the segments at a given version, headers included.

    给定版本下所有段的总比特数,包含段头。

    block_count

    fn block_count(version : Int, ec :
    EcLevel
    ) -> Int

    char_count_bits

    fn char_count_bits(version : Int, mode :
    Mode
    ) -> Int

    character_capacity

    fn character_capacity(version : Int, ec :
    EcLevel
    , mode :
    Mode
    ) -> Int

    Maximum number of characters of the given mode that fit in a version/EC pair.

    给定版本/纠错等级下,指定模式可容纳的最大字符数。

    data_capacity_bits

    fn data_capacity_bits(version : Int, ec :
    EcLevel
    ) -> Int

    data_codewords

    fn data_codewords(version : Int, ec :
    EcLevel
    ) -> Int

    ecc_codewords

    fn ecc_codewords(version : Int, ec :
    EcLevel
    ) -> Int

    encode

    Encodes text as a QR Model 2 symbol.

    将 text 编码为 QR Model 2 符号。

    ec_level defaults to M, version range to 1..40, mode is chosen automatically, and the mask is selected by minimizing the Annex J penalty.

    ec_level 默认为 M,版本范围为 1..40,模式自动选择, 掩码通过最小化 Annex J 惩罚分选出。

    Example

    test "encode hello world" {
    let qr = @core.encode("HELLO WORLD") // V1-M
    assert_eq(qr.size(), 21)
    assert_eq(qr.version().value(), 1)
    }

    encode_content

    Convenience wrapper: render a template and encode it.

    便捷包装:渲染模板并编码。

    format_info

    BCH(15,5) format information, masked with 0x5412 as required by ISO 7.9.

    BCH(15,5) 格式信息,按 ISO 7.9 要求与 0x5412 异或掩码。

    gf_mul

    fn gf_mul(a : Byte, b : Byte) -> Byte

    Multiplication in GF(2^8).

    GF(2^8) 上的乘法。

    is_valid_color

    fn is_valid_color(color : String) -> Bool

    Accepts hex colors, named CSS colors, and rgb()/rgba()/hsl() style values. Rejects anything containing markup or characters that could escape an attribute.

    接受十六进制颜色、CSS 命名颜色以及 rgb()/rgba()/hsl() 格式。 拒绝包含标记或可能逃逸属性的字符。

    rs_encode

    fn rs_encode(msg : ArrayView[Byte], nsym : Int) -> Array[Byte]

    Reed-Solomon parity bytes for msg with nsym ECC codewords.

    对 msg 计算 nsym 个 ECC 校验字节的 Reed-Solomon 编码。

    split_segments

    Splits text into greedy class runs. Forcing mode produces a single segment and rejects characters outside that mode's repertoire.

    将文本拆分为贪心分类段。强制指定 mode 时生成单个段, 并拒绝该模式字符集之外的字符。

    to_ascii

    fn to_ascii(qr :
    QrCode
    , quiet_zone? : Int, scale? : Int) -> String

    Terminal rendering. Each module is two characters wide so the result looks roughly square in monospaced fonts. quiet_zone is measured in modules and scale repeats every module scale times.

    终端渲染。每个模块占两个字符宽度,在等宽字体下近似方形。 quiet_zone 以模块为单位,scale 控制每个模块的重复次数。

    to_ascii_half

    fn to_ascii_half(qr :
    QrCode
    , quiet_zone? : Int) -> String

    Half-height rendering: two module rows are packed into one text row using upper/lower block characters. Good for narrow terminals.

    半高渲染:使用上下半块字符将两行模块压缩到一行终端输出。 适合窄终端。

    to_svg

    fn to_svg(qr :
    QrCode
    , quiet_zone? : Int, module_size? : Int, dark? : String, light? : String) -> String raise
    QrError

    Renders the symbol as an SVG document. Modules are emitted as <rect> elements inside a <g> with the requested foreground color.

    将 QR 码渲染为 SVG 文档。模块以 <rect> 元素形式输出, 使用指定的前景色包裹在 <g> 中。

    total_codewords

    fn total_codewords(version : Int) -> Int

    Number of data + ECC modules available for codewords, divided by 8. Derived from ISO Table 1; remainder bits are not counted.

    可用于码字的数据 + ECC 模块数除以 8。 源自 ISO Table 1;不计余留位。

    version_info

    fn version_info(version : Int) -> Int?

    BCH(18,6) version information for versions 7..40; None below 7.

    BCH(18,6) 版本信息,适用于版本 7..40;低于 7 时返回 None。

    Source Files