{"spec_id":"datamatrix-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\ndatamatrix-basic: Basic Data Matrix 2D Barcode\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 89/100 | Updated: 2026-05-20\n\"\"\"\n\nimport sys\n\n\nsys.path.pop(0)  # prevent this file from shadowing the installed bokeh package\n\nimport os\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, HoverTool, Label, Title\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\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\"\n\n# Data — ASCII-encode \"SERIAL:12345678\" for a Data Matrix ECC 200 barcode\ncontent = \"SERIAL:12345678\"\n\ncodewords = []\npos_idx = 0\nwhile pos_idx < len(content):\n    ch_val = ord(content[pos_idx])\n    if pos_idx + 1 < len(content) and content[pos_idx].isdigit() and content[pos_idx + 1].isdigit():\n        codewords.append(130 + int(content[pos_idx : pos_idx + 2]))\n        pos_idx += 2\n    elif 0 <= ch_val <= 127:\n        codewords.append(ch_val + 1)\n        pos_idx += 1\n    else:\n        codewords.append(235)\n        codewords.append(ch_val - 127)\n        pos_idx += 1\n\n# Determine symbol size (capacity, rows, cols) for ECC 200\nsymbol_sizes = [\n    (3, 10, 10),\n    (5, 12, 12),\n    (8, 14, 14),\n    (12, 16, 16),\n    (18, 18, 18),\n    (22, 20, 20),\n    (30, 22, 22),\n    (36, 24, 24),\n    (44, 26, 26),\n]\nrows, cols, capacity = 26, 26, 44\nfor cap, nr, nc in symbol_sizes:\n    if len(codewords) <= cap:\n        rows, cols, capacity = nr, nc, cap\n        break\n\n# Pad codewords to symbol capacity with randomised pad codewords\nwhile len(codewords) < capacity:\n    pad_pos = len(codewords) + 1\n    pad_cw = 129 + ((149 * pad_pos) % 253) + 1\n    if pad_cw > 254:\n        pad_cw -= 254\n    codewords.append(pad_cw)\n\n# Reed-Solomon ECC — GF(256) with primitive polynomial x^8+x^5+x^3+x^2+1 (=0x12D)\necc_sizes = {\n    (10, 10): 5,\n    (12, 12): 7,\n    (14, 14): 10,\n    (16, 16): 12,\n    (18, 18): 14,\n    (20, 20): 18,\n    (22, 22): 20,\n    (24, 24): 24,\n    (26, 26): 28,\n}\nn_ecc = ecc_sizes.get((rows, cols), 10)\n\ngf_prim = 0x12D\ngf_exp = [0] * 512\ngf_log = [0] * 256\ngf_x = 1\nfor gf_i in range(255):\n    gf_exp[gf_i] = gf_x\n    gf_log[gf_x] = gf_i\n    gf_x <<= 1\n    if gf_x >= 256:\n        gf_x ^= gf_prim\nfor gf_i in range(255, 512):\n    gf_exp[gf_i] = gf_exp[gf_i - 255]\n\n# Build RS generator polynomial: g(x) = prod(x + alpha^i) for i=0..n_ecc-1\nrs_gen = [1]\nfor ecc_i in range(n_ecc):\n    ei = gf_exp[ecc_i]\n    new_g = [0] * (len(rs_gen) + 1)\n    for g_j, gv in enumerate(rs_gen):\n        new_g[g_j] ^= gv\n        if gv != 0:\n            new_g[g_j + 1] ^= gf_exp[(gf_log[gv] + gf_log[ei]) % 255]\n    rs_gen = new_g\n\n# Polynomial long division to compute ECC codewords\nrs_rem = list(codewords) + [0] * n_ecc\nfor d_i in range(len(codewords)):\n    coef = rs_rem[d_i]\n    if coef != 0:\n        for enc_j in range(1, len(rs_gen)):\n            if rs_gen[enc_j] != 0:\n                rs_rem[d_i + enc_j] ^= gf_exp[(gf_log[rs_gen[enc_j]] + gf_log[coef]) % 255]\nall_codewords = codewords + rs_rem[len(codewords) :]\n\n# Construct Data Matrix grid (1=white/light module, 0=dark module)\nmatrix = np.ones((rows, cols), dtype=int)\nmatrix[:, 0] = 0  # L-shaped finder: solid left column\nmatrix[-1, :] = 0  # L-shaped finder: solid bottom row\nfor ti in range(cols):\n    matrix[0, ti] = ti % 2  # Alternating timing on top edge\nfor ti in range(rows):\n    matrix[ti, -1] = (ti + 1) % 2  # Alternating timing on right edge\n\n# Place data bits into inner region, row by row\nbit_idx = 0\nn_bits = len(all_codewords) * 8\nfor dr in range(1, rows - 1):\n    for dc in range(1, cols - 1):\n        if bit_idx < n_bits:\n            cw_pos = bit_idx // 8\n            bit_pos = 7 - (bit_idx % 8)\n            bit_val = (all_codewords[cw_pos] >> bit_pos) & 1\n            matrix[dr, dc] = 1 - bit_val  # dark module = 0\n            bit_idx += 1\n\n# Compute cell coordinates with quiet zone (2 modules on each side)\nquiet = 2\ntotal_w = cols + 2 * quiet\ntotal_h = rows + 2 * quiet\nblack_rows, black_cols = np.where(matrix == 0)\ncell_x = (black_cols + quiet + 0.5).astype(float)\ncell_y = (total_h - 1 - (black_rows + quiet) + 0.5).astype(float)\nmodule_types = [\n    \"L-finder\" if (c == 0 or r == rows - 1) else \"Timing\" if (r == 0 or c == cols - 1) else \"Data\"\n    for r, c in zip(black_rows.tolist(), black_cols.tolist(), strict=True)\n]\nsource = ColumnDataSource(\n    data={\n        \"x\": cell_x,\n        \"y\": cell_y,\n        \"module_row\": black_rows.astype(int),\n        \"module_col\": black_cols.astype(int),\n        \"module_type\": module_types,\n    }\n)\n\n# Plot — 2400×2400 square canvas suits the square Data Matrix barcode\np = figure(\n    width=2400,\n    height=2400,\n    title=\"datamatrix-basic · python · bokeh · anyplot.ai\",\n    x_range=(0, total_w),\n    y_range=(0, total_h),\n    toolbar_location=None,\n    min_border_top=120,\n    min_border_bottom=90,\n    min_border_left=90,\n    min_border_right=90,\n)\n\nOKABE_BLUE = \"#4467A3\"\nOKABE_ORANGE = \"#C475FD\"\nOKABE_GREEN = \"#009E73\"\n\n# Structural zone overlays — drawn first so barcode modules render on top\nbx = quiet  # barcode left edge in plot coords\nby = total_h - quiet - rows  # barcode bottom edge in plot coords\n# L-finder: solid left column + solid bottom row\np.rect(x=bx + 0.5, y=by + rows / 2, width=1, height=rows, fill_color=OKABE_BLUE, fill_alpha=0.15, line_color=None)\np.rect(x=bx + cols / 2, y=by + 0.5, width=cols, height=1, fill_color=OKABE_BLUE, fill_alpha=0.15, line_color=None)\n# Timing: alternating top row + right column\np.rect(\n    x=bx + cols / 2, y=by + rows - 0.5, width=cols, height=1, fill_color=OKABE_ORANGE, fill_alpha=0.15, line_color=None\n)\np.rect(\n    x=bx + cols - 0.5, y=by + rows / 2, width=1, height=rows, fill_color=OKABE_ORANGE, fill_alpha=0.15, line_color=None\n)\n# Data region: inner cells (rows 1..rows-2, cols 1..cols-2)\np.rect(\n    x=bx + cols / 2,\n    y=by + rows / 2,\n    width=cols - 2,\n    height=rows - 2,\n    fill_color=OKABE_GREEN,\n    fill_alpha=0.12,\n    line_color=None,\n)\n\nmodules_renderer = p.rect(x=\"x\", y=\"y\", width=0.95, height=0.95, source=source, fill_color=INK, line_color=None)\nhover = HoverTool(\n    renderers=[modules_renderer], tooltips=[(\"Type\", \"@module_type\"), (\"Row\", \"@module_row\"), (\"Col\", \"@module_col\")]\n)\np.add_tools(hover)\n\n# Theme-adaptive chrome\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = None\n\np.xaxis.visible = False\np.yaxis.visible = False\np.xgrid.visible = False\np.ygrid.visible = False\n\np.title.text_font_size = \"50pt\"\np.title.text_color = INK\np.title.align = \"center\"\np.title.text_font_style = \"normal\"\n\nsubtitle = Title(text=f'Content: \"{content}\"', text_font_size=\"34pt\", text_color=INK_SOFT, align=\"center\")\np.add_layout(subtitle, \"below\")\n\n# Zone labels in the quiet border areas, color-matched to their overlays\nfor lx, ly, ltxt, lcolor, langle in [\n    (1.0, total_h - quiet - rows / 2, \"L-finder\", OKABE_BLUE, np.pi / 2),\n    (quiet + cols / 2, total_h - quiet + 0.6, \"Timing\", OKABE_ORANGE, 0.0),\n    (total_w - 1.0, total_h - quiet - rows / 2, \"Data\", OKABE_GREEN, -np.pi / 2),\n]:\n    p.add_layout(\n        Label(\n            x=lx,\n            y=ly,\n            text=ltxt,\n            text_color=lcolor,\n            text_font_size=\"18pt\",\n            text_font_style=\"bold\",\n            text_align=\"center\",\n            text_baseline=\"middle\",\n            angle=langle,\n            background_fill_color=PAGE_BG,\n            background_fill_alpha=0.8,\n            border_line_color=lcolor,\n            border_line_width=2,\n        )\n    )\n\n# Save interactive HTML (required catalog artifact)\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome via Selenium\nW, H = 2400, 2400\n# Add height buffer to account for headless Chrome's viewport offset (~140px)\nWIN_H = H + 150\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{WIN_H}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, WIN_H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(2)\n# Match HTML page background to figure background so no contrast border appears\ndriver.execute_script(\n    \"document.body.style.backgroundColor = arguments[0];document.documentElement.style.backgroundColor = arguments[0];\",\n    PAGE_BG,\n)\ntime.sleep(1)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}