{"spec_id":"datamatrix-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\ndatamatrix-basic: Basic Data Matrix 2D Barcode\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 82/100 | Updated: 2026-05-20\n\"\"\"\n\nimport os\nimport sys\n\nimport cairosvg\nimport numpy as np\n\n\n# This file is named pygal.py, which shadows the installed package.\n# Temporarily remove the script directory from sys.path so the real package loads.\n_script_dir = os.path.dirname(os.path.abspath(__file__))\n_removed = [p for p in list(sys.path) if p in (\"\", \".\") or os.path.abspath(p) == _script_dir]\nfor _p in _removed:\n    sys.path.remove(_p)\n\ntry:\n    from pygal.graph.graph import Graph\n    from pygal.style import Style\nfinally:\n    for _p in _removed:\n        sys.path.insert(0, _p)\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nIMPRINT = (\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\")\n\n# Data Matrix ECC 200 — symbol sizes: (rows, cols, data_codewords, ec_codewords)\nSYMBOL_SIZES = [\n    (10, 10, 3, 5),\n    (12, 12, 5, 7),\n    (14, 14, 8, 10),\n    (16, 16, 12, 12),\n    (18, 18, 18, 14),\n    (20, 20, 22, 18),\n    (22, 22, 30, 20),\n    (24, 24, 36, 24),\n    (26, 26, 44, 28),\n]\n\n# Galois field GF(256) tables — polynomial 0x12D (Data Matrix standard)\nGF_EXP = [0] * 512\nGF_LOG = [0] * 256\n_x = 1\nfor _i in range(255):\n    GF_EXP[_i] = _x\n    GF_LOG[_x] = _i\n    _x <<= 1\n    if _x & 0x100:\n        _x ^= 0x12D\nfor _i in range(255, 512):\n    GF_EXP[_i] = GF_EXP[_i - 255]\n\n\ndef gf_mul(a, b):\n    if a == 0 or b == 0:\n        return 0\n    return GF_EXP[(GF_LOG[a % 256] + GF_LOG[b % 256]) % 255]\n\n\ndef rs_encode(data, num_ec):\n    data = [d % 256 for d in data]\n    g = [1]\n    for i in range(num_ec):\n        new_g = [0] * (len(g) + 1)\n        for j in range(len(g)):\n            new_g[j] ^= gf_mul(g[j], GF_EXP[i])\n            new_g[j + 1] ^= g[j]\n        g = new_g\n    encoded = list(data) + [0] * num_ec\n    for i in range(len(data)):\n        coef = encoded[i]\n        if coef != 0:\n            for j in range(len(g)):\n                encoded[i + j] ^= gf_mul(g[j], coef)\n    return encoded[len(data) :]\n\n\ndef generate_datamatrix(content):\n    # ASCII encoding: each char → ordinal + 1\n    codewords = [ord(c) + 1 for c in content if 0 <= ord(c) <= 127]\n\n    # Select smallest fitting symbol size\n    rows, cols, data_cap, ec_count = next((s for s in SYMBOL_SIZES if len(codewords) <= s[2]), SYMBOL_SIZES[-1])\n\n    # Pad codewords to fill data capacity\n    if len(codewords) < data_cap:\n        codewords.append(129)\n    while len(codewords) < data_cap:\n        pad = 130 + (((149 * (len(codewords) + 1)) % 253) + 1) % 254\n        codewords.append(pad)\n    codewords = codewords[:data_cap]\n\n    # Compute error correction and build full codeword stream\n    all_codewords = codewords + rs_encode(codewords, ec_count)\n\n    # Initialise matrix\n    matrix = np.zeros((rows, cols), dtype=int)\n\n    # L-shaped finder pattern: solid left column + solid bottom row\n    matrix[:, 0] = 1\n    matrix[rows - 1, :] = 1\n\n    # Alternating timing patterns: top row and right column\n    matrix[0, :] = np.arange(cols) % 2 == 0\n    matrix[:, cols - 1] = np.arange(rows) % 2 == 0\n\n    # Place data bits in the interior (column-major diagonal)\n    data_rows, data_cols = rows - 2, cols - 2\n    placed = np.zeros((data_rows, data_cols), dtype=bool)\n    bit_idx = 0\n    total_bits = len(all_codewords) * 8\n    for module_num in range(data_rows * data_cols):\n        if bit_idx >= total_bits:\n            break\n        r, c = module_num // data_cols, module_num % data_cols\n        if not placed[r, c]:\n            cw_idx, bit_pos = bit_idx // 8, 7 - (bit_idx % 8)\n            if cw_idx < len(all_codewords):\n                bit_value = (all_codewords[cw_idx] >> bit_pos) & 1\n                ar, ac = r + 1, c + 1\n                if 0 < ar < rows - 1 and 0 < ac < cols - 1:\n                    matrix[ar, ac] = bit_value\n            placed[r, c] = True\n            bit_idx += 1\n\n    return matrix\n\n\nclass DataMatrixChart(Graph):\n    def __init__(self, *args, **kwargs):\n        self.dm_data = kwargs.pop(\"dm_data\", \"ANYPLOT\")\n        self.module_color = kwargs.pop(\"module_color\", \"#1A1A17\")\n        self.cell_bg = kwargs.pop(\"cell_bg\", \"#FFFDF6\")\n        self.ink_color = kwargs.pop(\"ink_color\", \"#1A1A17\")\n        self.ink_soft_color = kwargs.pop(\"ink_soft_color\", \"#4A4A44\")\n        self.quiet_zone = kwargs.pop(\"quiet_zone\", 2)\n        super().__init__(*args, **kwargs)\n        self._dm_matrix = None\n\n    def _plot(self):\n        self._dm_matrix = generate_datamatrix(self.dm_data)\n        matrix_rows, matrix_cols = self._dm_matrix.shape\n        total_rows = matrix_rows + 2 * self.quiet_zone\n        total_cols = matrix_cols + 2 * self.quiet_zone\n\n        plot_width = self.view.width\n        plot_height = self.view.height\n        margin = 160\n        available_size = min(plot_width, plot_height) - 2 * margin\n        cell_size = available_size / max(total_rows, total_cols)\n\n        dm_width = total_cols * cell_size\n        dm_height = total_rows * cell_size\n        x_offset = self.view.x(0) + (plot_width - dm_width) / 2\n        # Center vertically within the plot area\n        y_offset = self.view.y(total_rows) + (plot_height - dm_height) / 2\n\n        plot_node = self.nodes[\"plot\"]\n        dm_group = self.svg.node(plot_node, class_=\"datamatrix\")\n\n        # Barcode area background (always light for reliable scan contrast)\n        bg_rect = self.svg.node(dm_group, \"rect\", x=x_offset, y=y_offset, width=dm_width, height=dm_height)\n        bg_rect.set(\"fill\", self.cell_bg)\n        bg_rect.set(\"stroke\", self.ink_soft_color)\n        bg_rect.set(\"stroke-width\", \"2\")\n\n        # Draw filled modules\n        for row in range(matrix_rows):\n            for col in range(matrix_cols):\n                if self._dm_matrix[row, col]:\n                    x = x_offset + (col + self.quiet_zone) * cell_size\n                    y = y_offset + (row + self.quiet_zone) * cell_size\n                    rect = self.svg.node(dm_group, \"rect\", x=x, y=y, width=cell_size, height=cell_size)\n                    rect.set(\"fill\", self.module_color)\n\n        # Labels below the barcode: encoded content, matrix spec, and context\n        label_y = y_offset + dm_height + 70\n        label_x = x_offset + dm_width / 2\n\n        content_node = self.svg.node(dm_group, \"text\", x=label_x, y=label_y)\n        content_node.set(\"text-anchor\", \"middle\")\n        content_node.set(\"fill\", self.ink_color)\n        content_node.set(\"style\", \"font-size:44px;font-weight:bold;font-family:sans-serif\")\n        content_node.text = f\"Encoded: {self.dm_data}\"\n\n        info_node = self.svg.node(dm_group, \"text\", x=label_x, y=label_y + 58)\n        info_node.set(\"text-anchor\", \"middle\")\n        info_node.set(\"fill\", self.ink_soft_color)\n        info_node.set(\"style\", \"font-size:36px;font-family:sans-serif\")\n        info_node.text = f\"Matrix: {matrix_rows} × {matrix_cols} | ECC 200 | 30% error recovery\"\n\n        # Storytelling annotation: explains real-world significance\n        story_node = self.svg.node(dm_group, \"text\", x=label_x, y=label_y + 110)\n        story_node.set(\"text-anchor\", \"middle\")\n        story_node.set(\"fill\", self.ink_soft_color)\n        story_node.set(\"style\", \"font-size:30px;font-style:italic;font-family:sans-serif\")\n        story_node.text = \"NDC codes identify drugs in the US pharmaceutical supply chain (FDA 21 CFR § 207)\"\n\n    def _compute(self):\n        if self._dm_matrix is None:\n            self._dm_matrix = generate_datamatrix(self.dm_data)\n        matrix_rows, matrix_cols = self._dm_matrix.shape\n        total_size = max(matrix_rows, matrix_cols) + 2 * self.quiet_zone\n        self._box.xmin = 0\n        self._box.xmax = total_size\n        self._box.ymin = 0\n        self._box.ymax = total_size\n\n\n# Style\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_SOFT,\n    colors=IMPRINT,\n    title_font_size=66,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n)\n\n# Data — pharmaceutical NDC (National Drug Code) for drug authentication\ndm_content = \"NDC:0069-0069-20\"\n\n# Plot\nchart = DataMatrixChart(\n    width=2400,\n    height=2400,\n    style=custom_style,\n    title=\"datamatrix-basic · python · pygal · anyplot.ai\",\n    dm_data=dm_content,\n    module_color=\"#1A1A17\",\n    cell_bg=\"#FFFDF6\",\n    ink_color=INK,\n    ink_soft_color=INK_SOFT,\n    quiet_zone=2,\n    show_legend=False,\n    margin=100,\n    margin_top=180,\n    margin_bottom=180,\n    show_x_labels=False,\n    show_y_labels=False,\n)\n\n# Required: pygal's Graph._draw() only calls _plot() when series data is present\nchart.add(\"\", [0])\n\n# Save\nchart.render_to_file(f\"plot-{THEME}.svg\")\ncairosvg.svg2png(url=f\"plot-{THEME}.svg\", write_to=f\"plot-{THEME}.png\", output_width=2400, output_height=2400)\n\nsvg_content = chart.render(is_unicode=True)\nhtml_content = f\"\"\"<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <title>datamatrix-basic - python - pygal - anyplot.ai</title>\n    <style>\n        body {{\n            margin: 0;\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            min-height: 100vh;\n            background: {PAGE_BG};\n        }}\n        .chart {{ max-width: 100%; height: auto; }}\n    </style>\n</head>\n<body>\n    <figure class=\"chart\">\n        {svg_content}\n    </figure>\n</body>\n</html>\n\"\"\"\n\nwith open(f\"plot-{THEME}.html\", \"w\", encoding=\"utf-8\") as f:\n    f.write(html_content)\n"}