{"spec_id":"barcode-ean13","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// barcode-ean13: EAN-13 Barcode\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 90/100 | Created: 2026-09-01\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- EAN-13 encoding tables (in-memory, deterministic) ----------------------\nconst L_CODE = [\"0001101\", \"0011001\", \"0010011\", \"0111101\", \"0100011\", \"0110001\", \"0101111\", \"0111011\", \"0110111\", \"0001011\"];\nconst G_CODE = [\"0100111\", \"0110011\", \"0011011\", \"0100001\", \"0011101\", \"0111001\", \"0000101\", \"0010001\", \"0001001\", \"0010111\"];\nconst R_CODE = [\"1110010\", \"1100110\", \"1101100\", \"1000010\", \"1011100\", \"1001110\", \"1010000\", \"1000100\", \"1001000\", \"1110100\"];\nconst PARITY = [\"LLLLLL\", \"LLGLGG\", \"LLGGLG\", \"LLGGGL\", \"LGLLGG\", \"LGGLLG\", \"LGGGLL\", \"LGLGLG\", \"LGLGGL\", \"LGGLGL\"];\n\nfunction checkDigit(digits12) {\n  let sum = 0;\n  for (let i = 0; i < 12; i++) {\n    sum += digits12[i] * (i % 2 === 0 ? 1 : 3);\n  }\n  return (10 - (sum % 10)) % 10;\n}\n\nfunction encodeModules(digits) {\n  const parity = PARITY[digits[0]];\n  let left = \"\";\n  for (let i = 0; i < 6; i++) {\n    const digit = digits[1 + i];\n    left += parity[i] === \"L\" ? L_CODE[digit] : G_CODE[digit];\n  }\n  let right = \"\";\n  for (let i = 0; i < 6; i++) {\n    right += R_CODE[digits[7 + i]];\n  }\n  return \"101\" + left + \"01010\" + right + \"101\";\n}\n\n// German retail product (EAN-13 country prefix 400-440) — 12-digit payload,\n// check digit auto-calculated per the spec's \"12 or 13 digit\" rule.\nconst payload = [4, 0, 0, 6, 3, 8, 1, 3, 3, 3, 9, 3];\nconst digits = payload.concat(checkDigit(payload));\nconst moduleBits = encodeModules(digits);\n\n// --- Module layout -----------------------------------------------------------\n// 9-module quiet zones (spec: \"at least 9 module widths\") + 95 barcode\n// modules (3 start guard + 42 left digits + 5 center guard + 42 right\n// digits + 3 end guard).\nconst QUIET_ZONE = 9;\nconst START_GUARD = 0;\nconst LEFT_DIGITS = 3;\nconst CENTER_GUARD = 45;\nconst RIGHT_DIGITS = 50;\nconst END_GUARD = 92;\nconst guardModules = new Set([\n  START_GUARD, START_GUARD + 1, START_GUARD + 2,\n  CENTER_GUARD, CENTER_GUARD + 1, CENTER_GUARD + 2, CENTER_GUARD + 3, CENTER_GUARD + 4,\n  END_GUARD, END_GUARD + 1, END_GUARD + 2,\n]);\n\n// Bars use theme-adaptive ink, not the Imprint brand green — a barcode's\n// bars are a printed mark (the \"Imprint\" metaphor itself), not a\n// categorical data series, and ink gives the highest contrast for scanning.\nconst labels = [];\nconst bars = [];\nconst colors = [];\n\nfor (let i = 0; i < QUIET_ZONE; i++) {\n  labels.push(`q${i}`);\n  bars.push([0, 1.3]);\n  colors.push(\"transparent\");\n}\nfor (let i = 0; i < moduleBits.length; i++) {\n  const isBar = moduleBits[i] === \"1\";\n  const isGuard = guardModules.has(i);\n  labels.push(`m${i}`);\n  bars.push(isBar && isGuard ? [0, 1.3] : [0.3, 1.3]);\n  colors.push(isBar ? t.ink : \"transparent\");\n}\nfor (let i = 0; i < QUIET_ZONE; i++) {\n  labels.push(`q${QUIET_ZONE + i}`);\n  bars.push([0, 1.3]);\n  colors.push(\"transparent\");\n}\n\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Chart ---------------------------------------------------------------\n// A floating (range) bar per module: guard bars span the full depth [0, 1.3],\n// data bars stop at 0.3 to leave the human-readable digit strip below them.\nconst title = \"German Retail Product · barcode-ean13 · javascript · chartjs · anyplot.ai\";\nconst titleFontSize = Math.round(22 * Math.min(1, 67 / title.length));\n\nnew Chart(canvas, {\n  type: \"bar\",\n  data: {\n    labels,\n    datasets: [\n      {\n        data: bars,\n        backgroundColor: colors,\n        borderWidth: 0,\n        categoryPercentage: 1,\n        barPercentage: 1,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: { padding: { top: 20, bottom: 64 } },\n    plugins: {\n      title: { display: true, text: title, color: t.ink, font: { size: titleFontSize, weight: \"500\" } },\n      legend: { display: false },\n      tooltip: { enabled: false },\n    },\n    scales: {\n      x: { display: false },\n      y: { display: false, min: 0, max: 1.3 },\n    },\n  },\n  plugins: [\n    {\n      id: \"ean13Digits\",\n      afterDraw(chart) {\n        const { ctx } = chart;\n        const x = chart.scales.x;\n        const y = chart.scales.y;\n        const moduleWidth = x.getPixelForValue(1) - x.getPixelForValue(0);\n        const yLabel = y.getPixelForValue(0.15);\n\n        // Faint quiet-zone frame: a subtle outline around the full symbol\n        // (bars + quiet zones), the way a printed barcode label is often\n        // bordered. Uses the low-contrast grid token so it reads as a\n        // hairline, never competing with the bars' scannable contrast.\n        ctx.save();\n        ctx.strokeStyle = t.grid;\n        ctx.lineWidth = 1;\n        ctx.strokeRect(\n          x.getPixelForValue(0),\n          y.getPixelForValue(1.3),\n          x.getPixelForValue(labels.length - 1) + moduleWidth - x.getPixelForValue(0),\n          y.getPixelForValue(0.3) - y.getPixelForValue(1.3)\n        );\n        ctx.restore();\n\n        ctx.save();\n        ctx.fillStyle = t.ink;\n        ctx.font = \"600 26px sans-serif\";\n        ctx.textAlign = \"center\";\n        ctx.textBaseline = \"middle\";\n\n        // First digit sits outside the guard, in the left quiet zone.\n        const xFirst = x.getPixelForValue(QUIET_ZONE + START_GUARD) - moduleWidth * 3;\n        ctx.fillText(String(digits[0]), xFirst, yLabel);\n\n        // Left group: 6 digits under the 42 left-side modules.\n        for (let i = 0; i < 6; i++) {\n          const idx = QUIET_ZONE + LEFT_DIGITS + i * 7;\n          const cx = x.getPixelForValue(idx) + moduleWidth * 3;\n          ctx.fillText(String(digits[1 + i]), cx, yLabel);\n        }\n\n        // Right group: 6 digits under the 42 right-side modules.\n        for (let i = 0; i < 6; i++) {\n          const idx = QUIET_ZONE + RIGHT_DIGITS + i * 7;\n          const cx = x.getPixelForValue(idx) + moduleWidth * 3;\n          ctx.fillText(String(digits[7 + i]), cx, yLabel);\n        }\n\n        ctx.restore();\n      },\n    },\n  ],\n});\n"}