commit e569afec1785aabb9d57196d7c48c0a71850354e Author: Artemis Tosini Date: Sun Sep 24 00:28:46 2023 +0000 First attempt at labels diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1e92b0e --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +result +.direnv + +*.pdf +addresses.csv +addresses.*.csv +/content +cache/ + +*.pyc +__pycache__/ + +options.json +next_serial.txt +mailer_id.txt diff --git a/common.typ b/common.typ new file mode 100644 index 0000000..33945af --- /dev/null +++ b/common.typ @@ -0,0 +1,54 @@ +#let address_content(width, height, card) = { + set par(leading: 0.5em) + let text_height = if card.imb == "" { + height + } else { + height - 1in/8 + } + place( + top + left, + dx: 0.5in, + block( + width: width - 0.5in, + height: text_height, + fill: luma(230), + align( + start + horizon, + text(font: ("OCR-B", "Noto Emoji"), size: 8pt, card.address) + ) + ) + ) + if card.imb != "" { + place( + top + left, + dy: height - 1in/8, + block( + width: 100%, + height: 1in/8, + fill: luma(220), + align( + top + center, + text(font: "USPSIMBCompact", size: 12pt, card.imb) + ) + ) + ) + } + if card.avatar != "" { + place( + top + left, + dx: 0.1in, + dy: 0.1in, + image(card.avatar, width: 0.3in) + ) + } +} + +#let address_block(width, height, card) = { + block( + width: width, + height: height, + breakable: false, + fill: luma(240), + address_content(width, height, card) + ) +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..a1128ea --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1695318763, + "narHash": "sha256-FHVPDRP2AfvsxAdc+AsgFJevMz5VBmnZglFUMlxBkcY=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "e12483116b3b51a185a33a272bf351e357ba9a99", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "utils": "utils" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..f6ee314 --- /dev/null +++ b/flake.nix @@ -0,0 +1,53 @@ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + utils.url = "github:numtide/flake-utils"; + }; + + outputs = { + self, + nixpkgs, + utils, + }: let + supportedSystems = ["x86_64-linux" "aarch64-linux"]; + in + utils.lib.eachSystem supportedSystems (system: let + pkgs = import nixpkgs {inherit system;}; + in rec { + formatter = pkgs.alejandra; + + packages.included-fonts = pkgs.runCommand "included-fonts" {} '' + mkdir -p $out/share/fonts/{truetype,opentype} + cp ${./fonts}/*.ttf $out/share/fonts/truetype + cp ${./fonts}/*.otf $out/share/fonts/opentype + ''; + + devShell = with pkgs; + mkShell { + packages = [ + typst + python3 + python3Packages.autopep8 + python3Packages.requests + ]; + + # We need CLDR main, not just the annotations + CLDR_ROOT = pkgs.cldr-annotations.overrideAttrs (final: prev: { + installPhase = '' + runHook preInstall + mkdir -p $out/share/unicode/cldr + mv common $out/share/unicode/cldr + runHook postInstall + ''; + }); + + TYPST_FONT_PATHS = with pkgs; symlinkJoin { + name = "typst-fonts"; + paths = [ + packages.included-fonts + noto-fonts-emoji + ]; + }; + }; + }); +} diff --git a/fonts/OCR-B-Regular.otf b/fonts/OCR-B-Regular.otf new file mode 100644 index 0000000..5fba217 Binary files /dev/null and b/fonts/OCR-B-Regular.otf differ diff --git a/fonts/USPSIMBCompact.ttf b/fonts/USPSIMBCompact.ttf new file mode 100644 index 0000000..da40626 Binary files /dev/null and b/fonts/USPSIMBCompact.ttf differ diff --git a/format.py b/format.py new file mode 100755 index 0000000..b7560c1 --- /dev/null +++ b/format.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +import argparse +import csv +import json +import os +import string +import typing +import xml.etree.ElementTree as ET +import requests +import imb + + +def iso_code(s: str) -> str: + if len(s) != 2: + raise ValueError("must be 2 characters long") + s = s.lower() + if not (s[0] in string.ascii_lowercase and s[1] in string.ascii_lowercase): + raise ValueError("must be ascii letters") + return s + + +def get_avatar(url: str) -> str: + if not os.path.exists("cache"): + os.mkdir("cache") + name = url.split("?")[0].split("/")[-1] + if os.path.exists("cache/" + name): + return "cache/" + name + result = requests.get(url) + if result.ok: + with open("cache/" + name, "wb") as outfile: + outfile.write(result.content) + return "cache/" + name + return "" + + +def get_country_name( + root: ET.ElementTree, destination: str, alt=None +) -> typing.Optional[str]: + elements = root.findall( + f"./localeDisplayNames/territories/territory[@type='{destination.upper()}']" + ) + normal = None + for element in elements: + if element.attrib.get("alt") == alt: + return element.text + elif element.attrib.get("alt") is None: + normal = element.text + return normal + + +parser = argparse.ArgumentParser( + prog="format", description="format postcards with latex" +) +parser.add_argument("template", help="template to use", + nargs="?", default="2card") +parser.add_argument( + "-o", "--origin", help="origin country code", default="us", type=iso_code +) +parser.add_argument( + "-l", + "--language", + help="language to use for countries ", + default="en", + type=iso_code, +) +parser.add_argument( + "-c", + "--count", + default=1, + type=int, + help="Number of sets of labels to print, default 1 (labels only)", +) +parser.add_argument( + "-s", + "--skip", + default=0, + type=int, + help="Number of labels to skip (label sheets only)", +) + +parser.add_argument( + "-a", + "--address-file", + default="addresses.csv", + type=str, + help="CSV file containing addresses", +) + +args = parser.parse_args() + +root = ET.parse( + f"{os.getenv('CLDR_ROOT')}/share/unicode/cldr/common/main/{args.language}.xml" +) + +csvfile = open(args.address_file) +rows = csv.DictReader(csvfile) + +current_serial = imb.get_first_serial() +mid = int(open("mailer_id.txt").read().strip()) + + +cards = [] +for row in rows: + if row["Address"] == "": + continue + + country = ( + [] + if row["Country"].lower() == args.origin + else [get_country_name(root, row["Country"]).upper()] + ) + + address = row["Address"].split("\n") + country + + if row.get("Avatar", "") != "": + avatar = get_avatar(row["Avatar"]) + else: + avatar = None + + cards += [ + { + "address": "\n".join(address), + "avatar": avatar, + "row": row, + "imb": "", + } + ] + +cards = cards * args.count + +serial = imb.get_first_serial() +if args.origin == "us": + for card in cards: + dpc = card["row"].get("DPC", "") + if dpc != "": + card["imb"] = imb.generate( + 0, 310, mid, serial, dpc.replace(" ", "").replace("-", "") + ) + serial += 1 +imb.write_current_serial(serial) + +with open("options.json", "w") as options: + json.dump( + fp=options, + obj={ + "args": args.__dict__, + "cards": cards, + }, + ) diff --git a/imb.py b/imb.py new file mode 100644 index 0000000..3e03eb3 --- /dev/null +++ b/imb.py @@ -0,0 +1,138 @@ +import datetime +import typing +import imb_table + + +def get_first_serial() -> int: + """ + Generate a 6-digit serial number for an intelligent mail barcode. + The first 2 digits are the last 2 digits of the julian date, and the next 4 + are serial and the next number to use is stored in a temporary file. + This will break if over 10000 serials are requested in a day + """ + # Last 2 digits of the ordinal day + date = datetime.datetime.utcnow().timetuple().tm_yday % 100 + try: + serial_file = open("next_serial.txt") + next_serial = int(serial_file.read().strip()) + if next_serial // 10000 == date: + first_idx = next_serial % 10000 + else: + first_idx = 0 + except (ValueError, IndexError, FileNotFoundError): + first_idx = 0 + + return date * 10000 + first_idx + + +def write_current_serial(current_serial: int): + serial_file = open("next_serial.txt", "w") + serial_file.write(format(current_serial, "06d")) + + +def _format_routing(routing: str) -> int: + if len(routing) == 0: + return 0 + elif len(routing) == 5: + return int(routing) + 1 + elif len(routing) == 9: + return int(routing) + 100000 + 1 + elif len(routing) == 11: + return int(routing) + 1000000000 + 100000 + 1 + else: + raise ValueError("Routing code must be 0, 5, 9, or 11 characters") + + +def _generate_crc(data_int: int) -> int: + """ + Do the weird USPS CRC11 which requires precisely 102 bits in + This is done by copying USPS code which is not very optimal + """ + data = data_int.to_bytes(13, "big") + poly = 0xF35 + fcs = 0x7FF + + current = data[0] << 5 + for _ in range(6): + if (fcs ^ current) & 0x400: + fcs = (fcs << 1) ^ poly + else: + fcs = fcs << 1 + fcs &= 0x7FF + current <<= 1 + + for current in data[1:]: + current <<= 3 + for _ in range(8): + if (fcs ^ current) & 0x400: + fcs = (fcs << 1) ^ poly + else: + fcs = fcs << 1 + fcs &= 0x7FF + current <<= 1 + + return fcs + + +def _generate_codewords(data: int, crc: int) -> typing.List[int]: + codewords = [] + codewords.append(data % 636) + data //= 636 + for i in range(8): + codewords.append(data % 1365) + data //= 1365 + codewords.append(data) + + codewords.reverse() + codewords[0] += ((crc >> 10) & 1) * 659 + codewords[9] *= 2 + return codewords + + +def _generate_characters(codewords: typing.List[int], crc: int) -> typing.List[int]: + characters = [] + for idx, codeword in enumerate(codewords): + xor = ((crc >> idx) & 1) * 0x1FFF + characters.append(imb_table.CHARACTER_TABLE[codeword] ^ xor) + + return characters + + +def _get_bit(characters: typing.List[int], character: int, bit: int) -> int: + return (characters[character] >> bit) & 1 + + +def _generate_bars(characters: typing.List[int]) -> str: + s = "" + for bar in range(65): + descender = _get_bit(characters, *imb_table.BAR_TABLE[bar * 2]) + ascender = _get_bit(characters, *imb_table.BAR_TABLE[bar * 2 + 1]) + s = s + "TADF"[descender * 2 + ascender] + return s + + +def from_payload(data: int) -> str: + crc = _generate_crc(data) + codewords = _generate_codewords(data, crc) + characters = _generate_characters(codewords, crc) + return _generate_bars(characters) + + +def _generate_payload(bi: int, stid: int, mailer: int, serial: int, raw_routing: str): + if mailer >= 1000000: + decimal_tracking = int(f"{stid:03d}{mailer:09d}{serial:06d}") + else: + # Why are you using this program? + decimal_tracking = int(f"{stid:03d}{mailer:06d}{serial:09d}") + + payload = _format_routing(raw_routing) + + # ... what the fuck usps + payload = payload * 10 + (bi // 10) + payload = payload * 5 + (bi % 10) + payload = payload * 10**18 + decimal_tracking + return payload + + +def generate(bi: int, stid: int, mailer: int, serial: int, raw_routing: str): + return from_payload(_generate_payload(bi, stid, mailer, serial, raw_routing)) diff --git a/imb_table.py b/imb_table.py new file mode 100644 index 0000000..26d4925 --- /dev/null +++ b/imb_table.py @@ -0,0 +1,1500 @@ +CHARACTER_TABLE = [ + 0x001F, + 0x1F00, + 0x002F, + 0x1E80, + 0x0037, + 0x1D80, + 0x003B, + 0x1B80, + 0x003D, + 0x1780, + 0x003E, + 0x0F80, + 0x004F, + 0x1E40, + 0x0057, + 0x1D40, + 0x005B, + 0x1B40, + 0x005D, + 0x1740, + 0x005E, + 0x0F40, + 0x0067, + 0x1CC0, + 0x006B, + 0x1AC0, + 0x006D, + 0x16C0, + 0x006E, + 0x0EC0, + 0x0073, + 0x19C0, + 0x0075, + 0x15C0, + 0x0076, + 0x0DC0, + 0x0079, + 0x13C0, + 0x007A, + 0x0BC0, + 0x007C, + 0x07C0, + 0x008F, + 0x1E20, + 0x0097, + 0x1D20, + 0x009B, + 0x1B20, + 0x009D, + 0x1720, + 0x009E, + 0x0F20, + 0x00A7, + 0x1CA0, + 0x00AB, + 0x1AA0, + 0x00AD, + 0x16A0, + 0x00AE, + 0x0EA0, + 0x00B3, + 0x19A0, + 0x00B5, + 0x15A0, + 0x00B6, + 0x0DA0, + 0x00B9, + 0x13A0, + 0x00BA, + 0x0BA0, + 0x00BC, + 0x07A0, + 0x00C7, + 0x1C60, + 0x00CB, + 0x1A60, + 0x00CD, + 0x1660, + 0x00CE, + 0x0E60, + 0x00D3, + 0x1960, + 0x00D5, + 0x1560, + 0x00D6, + 0x0D60, + 0x00D9, + 0x1360, + 0x00DA, + 0x0B60, + 0x00DC, + 0x0760, + 0x00E3, + 0x18E0, + 0x00E5, + 0x14E0, + 0x00E6, + 0x0CE0, + 0x00E9, + 0x12E0, + 0x00EA, + 0x0AE0, + 0x00EC, + 0x06E0, + 0x00F1, + 0x11E0, + 0x00F2, + 0x09E0, + 0x00F4, + 0x05E0, + 0x00F8, + 0x03E0, + 0x010F, + 0x1E10, + 0x0117, + 0x1D10, + 0x011B, + 0x1B10, + 0x011D, + 0x1710, + 0x011E, + 0x0F10, + 0x0127, + 0x1C90, + 0x012B, + 0x1A90, + 0x012D, + 0x1690, + 0x012E, + 0x0E90, + 0x0133, + 0x1990, + 0x0135, + 0x1590, + 0x0136, + 0x0D90, + 0x0139, + 0x1390, + 0x013A, + 0x0B90, + 0x013C, + 0x0790, + 0x0147, + 0x1C50, + 0x014B, + 0x1A50, + 0x014D, + 0x1650, + 0x014E, + 0x0E50, + 0x0153, + 0x1950, + 0x0155, + 0x1550, + 0x0156, + 0x0D50, + 0x0159, + 0x1350, + 0x015A, + 0x0B50, + 0x015C, + 0x0750, + 0x0163, + 0x18D0, + 0x0165, + 0x14D0, + 0x0166, + 0x0CD0, + 0x0169, + 0x12D0, + 0x016A, + 0x0AD0, + 0x016C, + 0x06D0, + 0x0171, + 0x11D0, + 0x0172, + 0x09D0, + 0x0174, + 0x05D0, + 0x0178, + 0x03D0, + 0x0187, + 0x1C30, + 0x018B, + 0x1A30, + 0x018D, + 0x1630, + 0x018E, + 0x0E30, + 0x0193, + 0x1930, + 0x0195, + 0x1530, + 0x0196, + 0x0D30, + 0x0199, + 0x1330, + 0x019A, + 0x0B30, + 0x019C, + 0x0730, + 0x01A3, + 0x18B0, + 0x01A5, + 0x14B0, + 0x01A6, + 0x0CB0, + 0x01A9, + 0x12B0, + 0x01AA, + 0x0AB0, + 0x01AC, + 0x06B0, + 0x01B1, + 0x11B0, + 0x01B2, + 0x09B0, + 0x01B4, + 0x05B0, + 0x01B8, + 0x03B0, + 0x01C3, + 0x1870, + 0x01C5, + 0x1470, + 0x01C6, + 0x0C70, + 0x01C9, + 0x1270, + 0x01CA, + 0x0A70, + 0x01CC, + 0x0670, + 0x01D1, + 0x1170, + 0x01D2, + 0x0970, + 0x01D4, + 0x0570, + 0x01D8, + 0x0370, + 0x01E1, + 0x10F0, + 0x01E2, + 0x08F0, + 0x01E4, + 0x04F0, + 0x01E8, + 0x02F0, + 0x020F, + 0x1E08, + 0x0217, + 0x1D08, + 0x021B, + 0x1B08, + 0x021D, + 0x1708, + 0x021E, + 0x0F08, + 0x0227, + 0x1C88, + 0x022B, + 0x1A88, + 0x022D, + 0x1688, + 0x022E, + 0x0E88, + 0x0233, + 0x1988, + 0x0235, + 0x1588, + 0x0236, + 0x0D88, + 0x0239, + 0x1388, + 0x023A, + 0x0B88, + 0x023C, + 0x0788, + 0x0247, + 0x1C48, + 0x024B, + 0x1A48, + 0x024D, + 0x1648, + 0x024E, + 0x0E48, + 0x0253, + 0x1948, + 0x0255, + 0x1548, + 0x0256, + 0x0D48, + 0x0259, + 0x1348, + 0x025A, + 0x0B48, + 0x025C, + 0x0748, + 0x0263, + 0x18C8, + 0x0265, + 0x14C8, + 0x0266, + 0x0CC8, + 0x0269, + 0x12C8, + 0x026A, + 0x0AC8, + 0x026C, + 0x06C8, + 0x0271, + 0x11C8, + 0x0272, + 0x09C8, + 0x0274, + 0x05C8, + 0x0278, + 0x03C8, + 0x0287, + 0x1C28, + 0x028B, + 0x1A28, + 0x028D, + 0x1628, + 0x028E, + 0x0E28, + 0x0293, + 0x1928, + 0x0295, + 0x1528, + 0x0296, + 0x0D28, + 0x0299, + 0x1328, + 0x029A, + 0x0B28, + 0x029C, + 0x0728, + 0x02A3, + 0x18A8, + 0x02A5, + 0x14A8, + 0x02A6, + 0x0CA8, + 0x02A9, + 0x12A8, + 0x02AA, + 0x0AA8, + 0x02AC, + 0x06A8, + 0x02B1, + 0x11A8, + 0x02B2, + 0x09A8, + 0x02B4, + 0x05A8, + 0x02B8, + 0x03A8, + 0x02C3, + 0x1868, + 0x02C5, + 0x1468, + 0x02C6, + 0x0C68, + 0x02C9, + 0x1268, + 0x02CA, + 0x0A68, + 0x02CC, + 0x0668, + 0x02D1, + 0x1168, + 0x02D2, + 0x0968, + 0x02D4, + 0x0568, + 0x02D8, + 0x0368, + 0x02E1, + 0x10E8, + 0x02E2, + 0x08E8, + 0x02E4, + 0x04E8, + 0x0307, + 0x1C18, + 0x030B, + 0x1A18, + 0x030D, + 0x1618, + 0x030E, + 0x0E18, + 0x0313, + 0x1918, + 0x0315, + 0x1518, + 0x0316, + 0x0D18, + 0x0319, + 0x1318, + 0x031A, + 0x0B18, + 0x031C, + 0x0718, + 0x0323, + 0x1898, + 0x0325, + 0x1498, + 0x0326, + 0x0C98, + 0x0329, + 0x1298, + 0x032A, + 0x0A98, + 0x032C, + 0x0698, + 0x0331, + 0x1198, + 0x0332, + 0x0998, + 0x0334, + 0x0598, + 0x0338, + 0x0398, + 0x0343, + 0x1858, + 0x0345, + 0x1458, + 0x0346, + 0x0C58, + 0x0349, + 0x1258, + 0x034A, + 0x0A58, + 0x034C, + 0x0658, + 0x0351, + 0x1158, + 0x0352, + 0x0958, + 0x0354, + 0x0558, + 0x0361, + 0x10D8, + 0x0362, + 0x08D8, + 0x0364, + 0x04D8, + 0x0383, + 0x1838, + 0x0385, + 0x1438, + 0x0386, + 0x0C38, + 0x0389, + 0x1238, + 0x038A, + 0x0A38, + 0x038C, + 0x0638, + 0x0391, + 0x1138, + 0x0392, + 0x0938, + 0x0394, + 0x0538, + 0x03A1, + 0x10B8, + 0x03A2, + 0x08B8, + 0x03A4, + 0x04B8, + 0x03C1, + 0x1078, + 0x03C2, + 0x0878, + 0x03C4, + 0x0478, + 0x040F, + 0x1E04, + 0x0417, + 0x1D04, + 0x041B, + 0x1B04, + 0x041D, + 0x1704, + 0x041E, + 0x0F04, + 0x0427, + 0x1C84, + 0x042B, + 0x1A84, + 0x042D, + 0x1684, + 0x042E, + 0x0E84, + 0x0433, + 0x1984, + 0x0435, + 0x1584, + 0x0436, + 0x0D84, + 0x0439, + 0x1384, + 0x043A, + 0x0B84, + 0x043C, + 0x0784, + 0x0447, + 0x1C44, + 0x044B, + 0x1A44, + 0x044D, + 0x1644, + 0x044E, + 0x0E44, + 0x0453, + 0x1944, + 0x0455, + 0x1544, + 0x0456, + 0x0D44, + 0x0459, + 0x1344, + 0x045A, + 0x0B44, + 0x045C, + 0x0744, + 0x0463, + 0x18C4, + 0x0465, + 0x14C4, + 0x0466, + 0x0CC4, + 0x0469, + 0x12C4, + 0x046A, + 0x0AC4, + 0x046C, + 0x06C4, + 0x0471, + 0x11C4, + 0x0472, + 0x09C4, + 0x0474, + 0x05C4, + 0x0487, + 0x1C24, + 0x048B, + 0x1A24, + 0x048D, + 0x1624, + 0x048E, + 0x0E24, + 0x0493, + 0x1924, + 0x0495, + 0x1524, + 0x0496, + 0x0D24, + 0x0499, + 0x1324, + 0x049A, + 0x0B24, + 0x049C, + 0x0724, + 0x04A3, + 0x18A4, + 0x04A5, + 0x14A4, + 0x04A6, + 0x0CA4, + 0x04A9, + 0x12A4, + 0x04AA, + 0x0AA4, + 0x04AC, + 0x06A4, + 0x04B1, + 0x11A4, + 0x04B2, + 0x09A4, + 0x04B4, + 0x05A4, + 0x04C3, + 0x1864, + 0x04C5, + 0x1464, + 0x04C6, + 0x0C64, + 0x04C9, + 0x1264, + 0x04CA, + 0x0A64, + 0x04CC, + 0x0664, + 0x04D1, + 0x1164, + 0x04D2, + 0x0964, + 0x04D4, + 0x0564, + 0x04E1, + 0x10E4, + 0x04E2, + 0x08E4, + 0x0507, + 0x1C14, + 0x050B, + 0x1A14, + 0x050D, + 0x1614, + 0x050E, + 0x0E14, + 0x0513, + 0x1914, + 0x0515, + 0x1514, + 0x0516, + 0x0D14, + 0x0519, + 0x1314, + 0x051A, + 0x0B14, + 0x051C, + 0x0714, + 0x0523, + 0x1894, + 0x0525, + 0x1494, + 0x0526, + 0x0C94, + 0x0529, + 0x1294, + 0x052A, + 0x0A94, + 0x052C, + 0x0694, + 0x0531, + 0x1194, + 0x0532, + 0x0994, + 0x0534, + 0x0594, + 0x0543, + 0x1854, + 0x0545, + 0x1454, + 0x0546, + 0x0C54, + 0x0549, + 0x1254, + 0x054A, + 0x0A54, + 0x054C, + 0x0654, + 0x0551, + 0x1154, + 0x0552, + 0x0954, + 0x0561, + 0x10D4, + 0x0562, + 0x08D4, + 0x0583, + 0x1834, + 0x0585, + 0x1434, + 0x0586, + 0x0C34, + 0x0589, + 0x1234, + 0x058A, + 0x0A34, + 0x058C, + 0x0634, + 0x0591, + 0x1134, + 0x0592, + 0x0934, + 0x05A1, + 0x10B4, + 0x05A2, + 0x08B4, + 0x05C1, + 0x1074, + 0x05C2, + 0x0874, + 0x0607, + 0x1C0C, + 0x060B, + 0x1A0C, + 0x060D, + 0x160C, + 0x060E, + 0x0E0C, + 0x0613, + 0x190C, + 0x0615, + 0x150C, + 0x0616, + 0x0D0C, + 0x0619, + 0x130C, + 0x061A, + 0x0B0C, + 0x061C, + 0x070C, + 0x0623, + 0x188C, + 0x0625, + 0x148C, + 0x0626, + 0x0C8C, + 0x0629, + 0x128C, + 0x062A, + 0x0A8C, + 0x062C, + 0x068C, + 0x0631, + 0x118C, + 0x0632, + 0x098C, + 0x0643, + 0x184C, + 0x0645, + 0x144C, + 0x0646, + 0x0C4C, + 0x0649, + 0x124C, + 0x064A, + 0x0A4C, + 0x0651, + 0x114C, + 0x0652, + 0x094C, + 0x0661, + 0x10CC, + 0x0662, + 0x08CC, + 0x0683, + 0x182C, + 0x0685, + 0x142C, + 0x0686, + 0x0C2C, + 0x0689, + 0x122C, + 0x068A, + 0x0A2C, + 0x0691, + 0x112C, + 0x0692, + 0x092C, + 0x06A1, + 0x10AC, + 0x06A2, + 0x08AC, + 0x06C1, + 0x106C, + 0x06C2, + 0x086C, + 0x0703, + 0x181C, + 0x0705, + 0x141C, + 0x0706, + 0x0C1C, + 0x0709, + 0x121C, + 0x070A, + 0x0A1C, + 0x0711, + 0x111C, + 0x0712, + 0x091C, + 0x0721, + 0x109C, + 0x0722, + 0x089C, + 0x0741, + 0x105C, + 0x0742, + 0x085C, + 0x0781, + 0x103C, + 0x0782, + 0x083C, + 0x080F, + 0x1E02, + 0x0817, + 0x1D02, + 0x081B, + 0x1B02, + 0x081D, + 0x1702, + 0x081E, + 0x0F02, + 0x0827, + 0x1C82, + 0x082B, + 0x1A82, + 0x082D, + 0x1682, + 0x082E, + 0x0E82, + 0x0833, + 0x1982, + 0x0835, + 0x1582, + 0x0836, + 0x0D82, + 0x0839, + 0x1382, + 0x083A, + 0x0B82, + 0x0847, + 0x1C42, + 0x084B, + 0x1A42, + 0x084D, + 0x1642, + 0x084E, + 0x0E42, + 0x0853, + 0x1942, + 0x0855, + 0x1542, + 0x0856, + 0x0D42, + 0x0859, + 0x1342, + 0x085A, + 0x0B42, + 0x0863, + 0x18C2, + 0x0865, + 0x14C2, + 0x0866, + 0x0CC2, + 0x0869, + 0x12C2, + 0x086A, + 0x0AC2, + 0x0871, + 0x11C2, + 0x0872, + 0x09C2, + 0x0887, + 0x1C22, + 0x088B, + 0x1A22, + 0x088D, + 0x1622, + 0x088E, + 0x0E22, + 0x0893, + 0x1922, + 0x0895, + 0x1522, + 0x0896, + 0x0D22, + 0x0899, + 0x1322, + 0x089A, + 0x0B22, + 0x08A3, + 0x18A2, + 0x08A5, + 0x14A2, + 0x08A6, + 0x0CA2, + 0x08A9, + 0x12A2, + 0x08AA, + 0x0AA2, + 0x08B1, + 0x11A2, + 0x08B2, + 0x09A2, + 0x08C3, + 0x1862, + 0x08C5, + 0x1462, + 0x08C6, + 0x0C62, + 0x08C9, + 0x1262, + 0x08CA, + 0x0A62, + 0x08D1, + 0x1162, + 0x08D2, + 0x0962, + 0x08E1, + 0x10E2, + 0x0907, + 0x1C12, + 0x090B, + 0x1A12, + 0x090D, + 0x1612, + 0x090E, + 0x0E12, + 0x0913, + 0x1912, + 0x0915, + 0x1512, + 0x0916, + 0x0D12, + 0x0919, + 0x1312, + 0x091A, + 0x0B12, + 0x0923, + 0x1892, + 0x0925, + 0x1492, + 0x0926, + 0x0C92, + 0x0929, + 0x1292, + 0x092A, + 0x0A92, + 0x0931, + 0x1192, + 0x0932, + 0x0992, + 0x0943, + 0x1852, + 0x0945, + 0x1452, + 0x0946, + 0x0C52, + 0x0949, + 0x1252, + 0x094A, + 0x0A52, + 0x0951, + 0x1152, + 0x0961, + 0x10D2, + 0x0983, + 0x1832, + 0x0985, + 0x1432, + 0x0986, + 0x0C32, + 0x0989, + 0x1232, + 0x098A, + 0x0A32, + 0x0991, + 0x1132, + 0x09A1, + 0x10B2, + 0x09C1, + 0x1072, + 0x0A07, + 0x1C0A, + 0x0A0B, + 0x1A0A, + 0x0A0D, + 0x160A, + 0x0A0E, + 0x0E0A, + 0x0A13, + 0x190A, + 0x0A15, + 0x150A, + 0x0A16, + 0x0D0A, + 0x0A19, + 0x130A, + 0x0A1A, + 0x0B0A, + 0x0A23, + 0x188A, + 0x0A25, + 0x148A, + 0x0A26, + 0x0C8A, + 0x0A29, + 0x128A, + 0x0A2A, + 0x0A8A, + 0x0A31, + 0x118A, + 0x0A43, + 0x184A, + 0x0A45, + 0x144A, + 0x0A46, + 0x0C4A, + 0x0A49, + 0x124A, + 0x0A51, + 0x114A, + 0x0A61, + 0x10CA, + 0x0A83, + 0x182A, + 0x0A85, + 0x142A, + 0x0A86, + 0x0C2A, + 0x0A89, + 0x122A, + 0x0A91, + 0x112A, + 0x0AA1, + 0x10AA, + 0x0AC1, + 0x106A, + 0x0B03, + 0x181A, + 0x0B05, + 0x141A, + 0x0B06, + 0x0C1A, + 0x0B09, + 0x121A, + 0x0B11, + 0x111A, + 0x0B21, + 0x109A, + 0x0B41, + 0x105A, + 0x0B81, + 0x103A, + 0x0C07, + 0x1C06, + 0x0C0B, + 0x1A06, + 0x0C0D, + 0x1606, + 0x0C0E, + 0x0E06, + 0x0C13, + 0x1906, + 0x0C15, + 0x1506, + 0x0C16, + 0x0D06, + 0x0C19, + 0x1306, + 0x0C23, + 0x1886, + 0x0C25, + 0x1486, + 0x0C26, + 0x0C86, + 0x0C29, + 0x1286, + 0x0C31, + 0x1186, + 0x0C43, + 0x1846, + 0x0C45, + 0x1446, + 0x0C49, + 0x1246, + 0x0C51, + 0x1146, + 0x0C61, + 0x10C6, + 0x0C83, + 0x1826, + 0x0C85, + 0x1426, + 0x0C89, + 0x1226, + 0x0C91, + 0x1126, + 0x0CA1, + 0x10A6, + 0x0CC1, + 0x1066, + 0x0D03, + 0x1816, + 0x0D05, + 0x1416, + 0x0D09, + 0x1216, + 0x0D11, + 0x1116, + 0x0D21, + 0x1096, + 0x0D41, + 0x1056, + 0x0D81, + 0x1036, + 0x0E03, + 0x180E, + 0x0E05, + 0x140E, + 0x0E09, + 0x120E, + 0x0E11, + 0x110E, + 0x0E21, + 0x108E, + 0x0E41, + 0x104E, + 0x0E81, + 0x102E, + 0x0F01, + 0x101E, + 0x100F, + 0x1E01, + 0x1017, + 0x1D01, + 0x101B, + 0x1B01, + 0x101D, + 0x1701, + 0x1027, + 0x1C81, + 0x102B, + 0x1A81, + 0x102D, + 0x1681, + 0x1033, + 0x1981, + 0x1035, + 0x1581, + 0x1039, + 0x1381, + 0x1047, + 0x1C41, + 0x104B, + 0x1A41, + 0x104D, + 0x1641, + 0x1053, + 0x1941, + 0x1055, + 0x1541, + 0x1059, + 0x1341, + 0x1063, + 0x18C1, + 0x1065, + 0x14C1, + 0x1069, + 0x12C1, + 0x1071, + 0x11C1, + 0x1087, + 0x1C21, + 0x108B, + 0x1A21, + 0x108D, + 0x1621, + 0x1093, + 0x1921, + 0x1095, + 0x1521, + 0x1099, + 0x1321, + 0x10A3, + 0x18A1, + 0x10A5, + 0x14A1, + 0x10A9, + 0x12A1, + 0x10B1, + 0x11A1, + 0x10C3, + 0x1861, + 0x10C5, + 0x1461, + 0x10C9, + 0x1261, + 0x10D1, + 0x1161, + 0x1107, + 0x1C11, + 0x110B, + 0x1A11, + 0x110D, + 0x1611, + 0x1113, + 0x1911, + 0x1115, + 0x1511, + 0x1119, + 0x1311, + 0x1123, + 0x1891, + 0x1125, + 0x1491, + 0x1129, + 0x1291, + 0x1131, + 0x1191, + 0x1143, + 0x1851, + 0x1145, + 0x1451, + 0x1149, + 0x1251, + 0x1183, + 0x1831, + 0x1185, + 0x1431, + 0x1189, + 0x1231, + 0x1207, + 0x1C09, + 0x120B, + 0x1A09, + 0x120D, + 0x1609, + 0x1213, + 0x1909, + 0x1215, + 0x1509, + 0x1219, + 0x1309, + 0x1223, + 0x1889, + 0x1225, + 0x1489, + 0x1229, + 0x1289, + 0x1243, + 0x1849, + 0x1245, + 0x1449, + 0x1283, + 0x1829, + 0x1285, + 0x1429, + 0x1303, + 0x1819, + 0x1305, + 0x1419, + 0x1407, + 0x1C05, + 0x140B, + 0x1A05, + 0x140D, + 0x1605, + 0x1413, + 0x1905, + 0x1415, + 0x1505, + 0x1423, + 0x1885, + 0x1425, + 0x1485, + 0x1443, + 0x1845, + 0x1483, + 0x1825, + 0x1503, + 0x1815, + 0x1603, + 0x180D, + 0x1807, + 0x1C03, + 0x180B, + 0x1A03, + 0x1813, + 0x1903, + 0x1823, + 0x1883, + 0x1843, + 0x1445, + 0x1249, + 0x1151, + 0x10E1, + 0x0C46, + 0x0A4A, + 0x0952, + 0x08E2, + 0x064C, + 0x0554, + 0x04E4, + 0x0358, + 0x02E8, + 0x01F0, + 0x0003, + 0x1800, + 0x0005, + 0x1400, + 0x0006, + 0x0C00, + 0x0009, + 0x1200, + 0x000A, + 0x0A00, + 0x000C, + 0x0600, + 0x0011, + 0x1100, + 0x0012, + 0x0900, + 0x0014, + 0x0500, + 0x0018, + 0x0300, + 0x0021, + 0x1080, + 0x0022, + 0x0880, + 0x0024, + 0x0480, + 0x0028, + 0x0280, + 0x0030, + 0x0180, + 0x0041, + 0x1040, + 0x0042, + 0x0840, + 0x0044, + 0x0440, + 0x0048, + 0x0240, + 0x0050, + 0x0140, + 0x0060, + 0x00C0, + 0x0081, + 0x1020, + 0x0082, + 0x0820, + 0x0084, + 0x0420, + 0x0088, + 0x0220, + 0x0090, + 0x0120, + 0x0101, + 0x1010, + 0x0102, + 0x0810, + 0x0104, + 0x0410, + 0x0108, + 0x0210, + 0x0201, + 0x1008, + 0x0202, + 0x0808, + 0x0204, + 0x0408, + 0x0401, + 0x1004, + 0x0402, + 0x0804, + 0x0801, + 0x1002, + 0x1001, + 0x0802, + 0x0404, + 0x0208, + 0x0110, + 0x00A0, +] + +BAR_TABLE = [ + (7, 2), + (4, 3), + (1, 10), + (0, 0), + (9, 12), + (2, 8), + (5, 5), + (6, 11), + (8, 9), + (3, 1), + (0, 1), + (5, 12), + (2, 5), + (1, 8), + (4, 4), + (9, 11), + (6, 3), + (8, 10), + (3, 9), + (7, 6), + (5, 11), + (1, 4), + (8, 5), + (2, 12), + (9, 10), + (0, 2), + (7, 1), + (6, 7), + (3, 6), + (4, 9), + (0, 3), + (8, 6), + (6, 4), + (2, 7), + (1, 1), + (9, 9), + (7, 10), + (5, 2), + (4, 0), + (3, 8), + (6, 2), + (0, 4), + (8, 11), + (1, 0), + (9, 8), + (3, 12), + (2, 6), + (7, 7), + (5, 1), + (4, 10), + (1, 12), + (6, 9), + (7, 3), + (8, 0), + (5, 8), + (9, 7), + (4, 6), + (2, 10), + (3, 4), + (0, 5), + (8, 4), + (5, 7), + (7, 11), + (1, 9), + (6, 0), + (9, 6), + (0, 6), + (4, 8), + (2, 1), + (3, 2), + (5, 9), + (8, 12), + (4, 11), + (6, 1), + (9, 5), + (7, 4), + (3, 3), + (1, 2), + (0, 7), + (2, 0), + (1, 3), + (4, 1), + (6, 10), + (3, 5), + (8, 7), + (9, 4), + (2, 11), + (5, 6), + (0, 8), + (7, 12), + (4, 2), + (8, 1), + (5, 10), + (3, 0), + (9, 3), + (0, 9), + (6, 5), + (2, 4), + (7, 8), + (1, 7), + (5, 0), + (4, 5), + (2, 3), + (0, 10), + (6, 12), + (9, 2), + (3, 11), + (1, 6), + (8, 8), + (7, 9), + (5, 4), + (0, 11), + (1, 5), + (2, 2), + (9, 1), + (4, 12), + (8, 3), + (6, 6), + (7, 0), + (3, 7), + (4, 7), + (7, 5), + (0, 12), + (1, 11), + (2, 9), + (9, 0), + (6, 8), + (5, 3), + (3, 10), + (8, 2), +] diff --git a/labels.typ b/labels.typ new file mode 100644 index 0000000..92168bf --- /dev/null +++ b/labels.typ @@ -0,0 +1,34 @@ +#{ + set page("us-letter", margin: 0em) + + import "common.typ" + + let options = json("options.json") + let cards = options.cards + let args = options.args + + let printer_offset = 1in/16 + + let label_position(idx) = { + let offset_idx = idx + args.skip + let col = calc.rem(offset_idx, 3) + let row = calc.rem(calc.floor(offset_idx / 3), 10) + let x_pos = 3in/16 + 2.75in * col + let y_pos = printer_offset + 1in/2 + 1in * row + (x_pos, y_pos) + } + + + for (idx, card) in cards.enumerate() { + if idx != 0 and calc.rem(idx + args.skip, 30) == 0 { + pagebreak() + } + let (dx, dy) = label_position(idx) + place( + top + left, + dx: dx, + dy: dy, + common.address_block(2in + 5in/8, 7in/8, card) + ) + } +}