> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sparklane.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Barcode anatomy

> Scan or paste a barcode to see its type, GS1 prefix, check digit, GS1 Application Identifiers and more, explained part by part.

export const AnatomyResult = ({r, onAnalyze}) => {
  const T = BARCODE_TOOLKIT, ui = T.ui;
  const link = slug => <a href={T.pageFor(slug)}>{T.NAMES[slug] || slug}</a>;
  const mark = ok => ok ? <span role="img" aria-label="Valid" style={ui.good}>✅</span> : <span role="img" aria-label="Invalid" style={ui.bad}>❌</span>;
  const BarcodeView = Barcode;
  const diagram = args => <BarcodeView {...args} />;
  const d = r.detected;
  const details = !d ? null : ['qr-code', 'micro-qr'].includes(d.slug) && d.version ? <>, version {d.version}, <a href="/reference/error-correction">error correction</a> level {d.ec}</> : d.slug === 'datamatrix' && d.version ? <>, {d.version.replace('x', '×')} modules</> : null;
  const header = <>
      {r.aim && r.aimInData && <p style={{
    fontSize: 14
  }}>Starts with the symbology identifier <code>{r.aim}</code>: {r.aimMeaning || 'an identifier this tool doesn’t know'}. It isn’t part of the data.</p>}
      {d && <p style={{
    fontSize: 14
  }}>
          Read from {T.withArticle(d.name).split(' ')[0]} {d.slug ? link(d.slug) : d.name}{details}.
          {d.aim && <> Its symbology identifier is <code>{d.aim}</code>{T.AIM[d.aim] ? ` (${T.AIM[d.aim]})` : ''}: scanners can send it in front of the data to say what kind of barcode it came from.</>}
        </p>}
    </>;
  if (r.kind === 'gtin') {
    const canDraw = r.valid && ['ean-13', 'upc-a', 'ean-8', 'upc-e', 'isbn', 'issn', 'itf-14'].includes(r.slug);
    return <div style={ui.panel}>
        {header}
        <h3 style={{
      marginTop: 0
    }}>{link(r.slug)} {mark(r.valid)}</h3>
        <p>
          {r.valid ? 'A valid GTIN (Global Trade Item Number).' : <>The check digit is wrong: for these digits it should be <strong>{r.expected}</strong>. Either a digit was misread or mistyped, or this isn’t a GTIN.</>}
        </p>
        {canDraw && diagram({
      view: 'anatomy',
      type: r.slug,
      value: r.digits
    })}
        <table>
          <tbody>
            {r.issuer && <tr><th scope="row">GS1 prefix {r.prefix}</th><td>{r.issuer}{r.issuer.endsWith('.') ? '' : '.'} A prefix shows which GS1 office issued the number, not where the product was made. <a href={T.PREFIX_SOURCE}>Prefix list</a></td></tr>}
            {r.indicator && <tr><th scope="row">Indicator digit {r.digits[0]}</th><td>{r.indicator}</td></tr>}
            {r.expanded && <tr><th scope="row">As a UPC-A</th><td style={ui.mono}>{r.expanded}</td></tr>}
            {r.isbn10 && <tr><th scope="row">As an ISBN-10</th><td style={ui.mono}>{r.isbn10}</td></tr>}
            {r.issn && <tr><th scope="row">ISSN</th><td style={ui.mono}>{r.issn}</td></tr>}
            <tr><th scope="row">As a 14-digit GTIN</th><td style={ui.mono}>{r.gtin14}</td></tr>
          </tbody>
        </table>
        {r.notes.map(n => <p key={n} style={{
      fontSize: 14
    }}>{n}</p>)}
        <h4>Check digit</h4>
        {diagram({
      view: 'check-digit',
      value: r.expanded || r.digits
    })}
        <p style={{
      fontSize: 14
    }}>
          Who does this product belong to? GS1’s <a href="https://www.gs1.org/services/verified-by-gs1">Verified by GS1</a> looks up the company behind a GTIN.
        </p>
      </div>;
  }
  if (r.kind === 'gs1' || r.kind === 'digital-link') {
    const hri = r.fields.map(f => `(${f.ai})${f.value}`).join('');
    return <div style={ui.panel}>
        {header}
        <h3 style={{
      marginTop: 0
    }}>{r.kind === 'digital-link' ? 'GS1 Digital Link' : 'GS1 data'}</h3>
        {r.kind === 'digital-link' ? <p>A web address that carries GS1 data. The domain, <code>{r.domain}</code>, is chosen by the brand. The GS1 data starts after it.</p> : <p>Application Identifiers and their values. Printed with brackets, it reads <code style={ui.mono}>{hri}</code>.</p>}
        {r.error && <p style={ui.bad}>{r.error}</p>}
        <table>
          <thead><tr><th>AI</th><th>Data title</th><th>Value</th><th>Valid</th></tr></thead>
          <tbody>
            {r.fields.map((f, i) => {
      const info = T.ai(f.ai), problems = T.checkField(f.ai, f.value), m = T.meaning(f.ai, f.value);
      return <tr key={i}>
                  <td style={ui.mono}>{f.ai}</td>
                  <td>{info ? info[0] : 'Unknown'}</td>
                  <td>
                    <span style={ui.mono}>{T.showControl(f.value)}</span>
                    {m && <div style={{
        ...ui.muted,
        fontSize: 13
      }}>{m}</div>}
                    {['01', '02'].includes(f.ai) && !problems.length && <div><button type="button" style={{
        ...ui.button,
        fontSize: 12,
        padding: '2px 8px',
        marginTop: 4
      }} onClick={() => onAnalyze(f.value)}>Look at this GTIN</button></div>}
                  </td>
                  <td>{mark(!problems.length)}{problems.map(p => <div key={p} style={{
        fontSize: 13
      }}>{p}</div>)}</td>
                </tr>;
    })}
          </tbody>
        </table>
        <p style={{
      fontSize: 14
    }}>See the <a href="/reference/gs1-application-identifiers">GS1 AI table</a> for every AI.</p>
      </div>;
  }
  if (r.kind === 'mrz') {
    return <div style={ui.panel}>
        {header}
        <h3 style={{
      marginTop: 0
    }}>{link('mrz')}: {r.format}</h3>
        {!r.fields && <p>This tool reads the fields of passport MRZs. For this format, see the <a href="/reference/documents/mrz">MRZ reference</a>.</p>}
        {r.fields && <table>
            <tbody>
              {r.fields.map(([name, value, ok]) => <tr key={name}><th scope="row">{name}</th><td style={ui.mono}>{value}</td><td>{ok === undefined ? '' : mark(ok)}</td></tr>)}
            </tbody>
          </table>}
      </div>;
  }
  if (r.kind === 'aamva') {
    return <div style={ui.panel}>
        {header}
        <h3 style={{
      marginTop: 0
    }}>Driver’s license data (AAMVA)</h3>
        <p>Data from the {link('pdf417')} barcode on a North American driver’s license or ID card{r.iin ? <>, from the issuer with IIN <code>{r.iin}</code></> : ''}. It never leaves this page. Use only <a href="/hardware/aamva-test-barcodes">test data</a>.</p>
        <table>
          <tbody>{r.fields.map(([id, name, v]) => <tr key={id}><td style={ui.mono}>{id}</td><th scope="row">{name}</th><td style={ui.mono}>{v}</td></tr>)}</tbody>
        </table>
      </div>;
  }
  if (r.kind === 'isbn10') {
    return <div style={ui.panel}>
        <h3 style={{
      marginTop: 0
    }}>ISBN-10 {mark(r.valid)}</h3>
        <p>An older 10-digit {link('isbn')}. Its barcode carries the 13-digit form.</p>
        <table><tbody><tr><th scope="row">As an ISBN-13</th><td style={ui.mono}>{r.isbn13}</td><td><button type="button" style={ui.button} onClick={() => onAnalyze(r.isbn13)}>Look at it</button></td></tr></tbody></table>
      </div>;
  }
  if (r.kind === 'issn') {
    return <div style={ui.panel}>
        <h3 style={{
      marginTop: 0
    }}>ISSN {mark(r.valid)}</h3>
        <p>An {link('issn')}. Its barcode carries it as a 13-digit EAN starting 977.</p>
        <table><tbody><tr><th scope="row">In the barcode</th><td style={ui.mono}>{r.ean}</td><td><button type="button" style={ui.button} onClick={() => onAnalyze(r.ean)}>Look at it</button></td></tr></tbody></table>
      </div>;
  }
  const text = r.text || '';
  const code39 = (/^[0-9A-Z\-. $/+%]+$/).test(text), ascii = (/^[\x20-\x7e]+$/).test(text);
  return <div style={ui.panel}>
      {header}
      <h3 style={{
    marginTop: 0
  }}>Text</h3>
      <p>{text.length} characters with no structure this tool recognizes: not a GTIN, GS1 data, an MRZ or license data. On its own, text doesn’t say which barcode type carried it.</p>
      <p style={{
    fontSize: 14
  }}>
        It could be carried by {code39 ? <>{link('code-39')}, {link('code-128')} or any 2D barcode</> : ascii ? <>{link('code-128')} or any 2D barcode</> : <>a 2D barcode such as {link('qr-code')}</>}.
        {r.kind === 'text' && (/^https?:/i).test(text) && ' It’s a web address but not a GS1 Digital Link, so it carries no product number.'}
      </p>
    </div>;
};

export const BarcodeAnatomyTool = () => {
  const T = BARCODE_TOOLKIT, ui = T.ui;
  const [input, setInput] = useState('');
  const [detected, setDetected] = useState(null);
  const [hintType, setHintType] = useState(null);
  const [scanning, setScanning] = useState(false);
  const [message, setMessage] = useState('');
  const video = useRef(null), stream = useRef(null), file = useRef(null);
  const canUseCamera = typeof navigator !== 'undefined' && !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
  const noReader = 'The barcode reader couldn’t be downloaded. Check your connection and try again, or paste the data instead.';
  useEffect(() => {
    const q = T.query();
    const type = q.get('type'), value = q.get('value');
    if (type) setHintType(type);
    if (value) setInput(value);
    return () => stop();
  }, []);
  const stop = () => {
    if (stream.current) stream.current.getTracks().forEach(t => t.stop());
    stream.current = null;
    setScanning(false);
  };
  const accept = found => {
    setInput(found.text);
    setDetected(found.detected);
    setMessage(`Read ${T.withArticle(found.detected.name)} barcode.`);
  };
  const scan = async () => {
    const ready = T.loadReader();
    try {
      stream.current = await navigator.mediaDevices.getUserMedia({
        video: {
          facingMode: 'environment'
        }
      });
    } catch {
      setMessage('The camera isn’t available. Check that this page is allowed to use it, or paste the data instead.');
      return;
    }
    setScanning(true);
    setMessage('Point the camera at a barcode.');
    await new Promise(r => setTimeout(r, 50));
    try {
      video.current.srcObject = stream.current;
      await video.current.play();
      await ready;
    } catch {
      if (stream.current) {
        stop();
        setMessage(noReader);
      }
      return;
    }
    const tick = async () => {
      const v = video.current;
      if (!stream.current || !v) return;
      const found = v.videoWidth ? await T.readPixels(T.pixelsOf(v, v.videoWidth, v.videoHeight, 1280)).catch(() => null) : null;
      if (found) {
        accept(found);
        stop();
        return;
      }
      setTimeout(tick, 150);
    };
    tick();
  };
  const fromPhoto = async e => {
    const f = e.target.files && e.target.files[0];
    e.target.value = '';
    if (!f) return;
    setMessage('Reading the photo…');
    try {
      await T.loadReader();
    } catch {
      setMessage(noReader);
      return;
    }
    try {
      const bitmap = await createImageBitmap(f);
      const found = await T.readPixels(T.pixelsOf(bitmap, bitmap.width, bitmap.height, 2000));
      if (found) accept(found); else setMessage('No barcode found in that photo. Try a sharper, closer one.');
    } catch {
      setMessage('That photo couldn’t be read.');
    }
  };
  const Result = AnatomyResult;
  const exampleType = hintType && T.EXAMPLES[hintType] ? hintType : 'ean-13';
  const result = T.analyze(input, detected, hintType);
  return <div className="not-prose barcode-tool">
      <div style={ui.panel}>
        <label htmlFor="anatomy-input" style={ui.label}>Barcode data</label>
        <textarea id="anatomy-input" rows={3} style={ui.input} value={input} placeholder={`Paste or type what a scan returned, such as ${T.EXAMPLES[exampleType].split('\n')[0]}`} onChange={e => {
    setInput(e.target.value);
    setDetected(null);
    setMessage('');
  }} />
        <div style={ui.row}>
          {canUseCamera && !scanning && <button type="button" style={ui.primary} onClick={scan}>Scan with camera</button>}
          {canUseCamera && scanning && <button type="button" style={ui.primary} onClick={stop}>Stop camera</button>}
          <button type="button" style={ui.button} onClick={() => file.current.click()}>Use a photo</button>
          <button type="button" style={ui.button} onClick={() => {
    setInput(T.EXAMPLES[exampleType]);
    setDetected(null);
    setMessage('');
  }}>
            Try {T.NAMES[exampleType] ? T.withArticle(T.NAMES[exampleType]) : 'an'} example
          </button>
          {input && <button type="button" style={ui.button} onClick={() => {
    setInput('');
    setDetected(null);
    setMessage('');
  }}>Clear</button>}
        </div>
        <input ref={file} type="file" accept="image/*" hidden onChange={fromPhoto} />
        {scanning && <video ref={video} playsInline muted style={{
    width: '100%',
    maxWidth: 420,
    marginTop: 12,
    borderRadius: 8
  }} />}
        <p role="status" style={{
    ...ui.muted,
    fontSize: 14,
    marginTop: message ? 10 : 0
  }}>{message}</p>
      </div>
      {result && <Result r={result} onAnalyze={v => {
    setInput(v);
    setDetected(null);
  }} />}
    </div>;
};

export const BARCODE_TOOLKIT = (() => {
  const PREFIXES = [[0, 19, 'GS1 US'], [20, 29, 'Restricted circulation within a region, such as items priced in store'], [30, 39, 'GS1 US'], [40, 49, 'Restricted circulation within a company'], [50, 59, 'GS1 US, reserved for future use'], [60, 139, 'GS1 US'], [200, 299, 'Restricted circulation within a region, such as items priced in store'], [300, 379, 'GS1 France'], [380, 380, 'GS1 Bulgaria'], [381, 381, 'GS1 Kosovo'], [383, 383, 'GS1 Slovenija'], [385, 385, 'GS1 Croatia'], [387, 387, 'GS1 BIH (Bosnia-Herzegovina)'], [389, 389, 'GS1 Montenegro'], [400, 440, 'GS1 Germany'], [450, 459, 'GS1 Japan'], [460, 469, 'GS1 Russia'], [470, 470, 'GS1 Kyrgyzstan'], [471, 471, 'GS1 Chinese Taipei'], [474, 474, 'GS1 Estonia'], [475, 475, 'GS1 Latvia'], [476, 476, 'GS1 Azerbaijan'], [477, 477, 'GS1 Lithuania'], [478, 478, 'GS1 Uzbekistan'], [479, 479, 'GS1 Sri Lanka'], [480, 480, 'GS1 Philippines'], [481, 481, 'GS1 Belarus'], [482, 482, 'GS1 Ukraine'], [483, 483, 'GS1 Turkmenistan'], [484, 484, 'GS1 Moldova'], [485, 485, 'GS1 Armenia'], [486, 486, 'GS1 Georgia'], [487, 487, 'GS1 Kazakstan'], [488, 488, 'GS1 Tajikistan'], [489, 489, 'GS1 Hong Kong, China'], [490, 499, 'GS1 Japan'], [500, 509, 'GS1 UK'], [520, 521, 'GS1 Association Greece'], [528, 528, 'GS1 Lebanon'], [529, 529, 'GS1 Cyprus'], [530, 530, 'GS1 Albania'], [531, 531, 'GS1 Macedonia'], [535, 535, 'GS1 Malta'], [539, 539, 'GS1 Ireland'], [540, 549, 'GS1 Belgium & Luxembourg'], [560, 560, 'GS1 Portugal'], [569, 569, 'GS1 Iceland'], [570, 579, 'GS1 Denmark'], [590, 590, 'GS1 Poland'], [594, 594, 'GS1 Romania'], [599, 599, 'GS1 Hungary'], [600, 601, 'GS1 South Africa'], [603, 603, 'GS1 Ghana'], [604, 604, 'GS1 Senegal'], [605, 605, 'GS1 Uganda'], [606, 606, 'GS1 Angola'], [607, 607, 'GS1 Oman'], [608, 608, 'GS1 Bahrain'], [609, 609, 'GS1 Mauritius'], [611, 611, 'GS1 Morocco'], [613, 613, 'GS1 Algeria'], [615, 615, 'GS1 Nigeria'], [616, 616, 'GS1 Kenya'], [617, 617, 'GS1 Cameroon'], [618, 618, 'GS1 Côte d’Ivoire'], [619, 619, 'GS1 Tunisia'], [620, 620, 'GS1 Tanzania'], [621, 621, 'GS1 Syria'], [622, 622, 'GS1 Egypt'], [624, 624, 'GS1 Libya'], [625, 625, 'GS1 Jordan'], [626, 626, 'GS1 Iran'], [627, 627, 'GS1 Kuwait'], [628, 628, 'GS1 Saudi Arabia'], [629, 629, 'GS1 Emirates'], [630, 630, 'GS1 Qatar'], [631, 631, 'GS1 Namibia'], [632, 632, 'GS1 Rwanda'], [640, 649, 'GS1 Finland'], [680, 681, 'GS1 China'], [690, 699, 'GS1 China'], [700, 709, 'GS1 Norway'], [729, 729, 'GS1 Israel'], [730, 739, 'GS1 Sweden'], [740, 740, 'GS1 Guatemala'], [741, 741, 'GS1 El Salvador'], [742, 742, 'GS1 Honduras'], [743, 743, 'GS1 Nicaragua'], [744, 744, 'GS1 Costa Rica'], [745, 745, 'GS1 Panama'], [746, 746, 'GS1 Republica Dominicana'], [750, 750, 'GS1 Mexico'], [754, 755, 'GS1 Canada'], [759, 759, 'GS1 Venezuela'], [760, 769, 'GS1 Switzerland'], [770, 771, 'GS1 Colombia'], [773, 773, 'GS1 Uruguay'], [775, 775, 'GS1 Peru'], [777, 777, 'GS1 Bolivia'], [778, 779, 'GS1 Argentina'], [780, 780, 'GS1 Chile'], [784, 784, 'GS1 Paraguay'], [786, 786, 'GS1 Ecuador'], [789, 790, 'GS1 Brasil'], [800, 839, 'GS1 Italy'], [840, 849, 'GS1 Spain'], [850, 850, 'GS1 Cuba'], [858, 858, 'GS1 Slovakia'], [859, 859, 'GS1 Czech'], [860, 860, 'GS1 Serbia'], [865, 865, 'GS1 Mongolia'], [867, 867, 'GS1 North Korea'], [868, 869, 'GS1 Türkiye'], [870, 879, 'GS1 Netherlands'], [880, 881, 'GS1 South Korea'], [883, 883, 'GS1 Myanmar'], [884, 884, 'GS1 Cambodia'], [885, 885, 'GS1 Thailand'], [887, 887, 'GS1 Laos'], [888, 888, 'GS1 Singapore'], [890, 890, 'GS1 India'], [893, 893, 'GS1 Vietnam'], [896, 896, 'GS1 Pakistan'], [899, 899, 'GS1 Indonesia'], [900, 919, 'GS1 Austria'], [930, 939, 'GS1 Australia'], [940, 949, 'GS1 New Zealand'], [950, 950, 'GS1 Global Office'], [951, 951, 'GS1 Global Office (EPC General Manager Numbers)'], [952, 952, 'Demonstrations and examples. Never a real product.'], [955, 955, 'GS1 Malaysia'], [958, 958, 'GS1 Macau, China'], [960, 969, 'GS1 Global Office (GTIN-8)'], [977, 977, 'A serial publication (ISSN)'], [978, 979, 'A book (ISBN)'], [980, 980, 'A refund receipt'], [981, 983, 'A coupon for a common currency area'], [990, 999, 'A coupon']];
  const PREFIX_SOURCE = 'https://www.gs1.org/standards/id-keys/company-prefix';
  const AIM = {
    ']E0': 'EAN-13, UPC-A or UPC-E',
    ']E4': 'EAN-8',
    ']C0': 'Code 128',
    ']C1': 'GS1-128',
    ']d1': 'Data Matrix',
    ']d2': 'GS1 DataMatrix',
    ']Q1': 'QR Code',
    ']Q3': 'GS1 QR Code',
    ']e0': 'GS1 DataBar',
    ']A0': 'Code 39',
    ']A1': 'Code 39, check character verified',
    ']G0': 'Code 93',
    ']F0': 'Codabar',
    ']I0': 'Interleaved 2 of 5',
    ']I1': 'Interleaved 2 of 5, check digit verified',
    ']L2': 'PDF417',
    ']z0': 'Aztec'
  };
  const GS1_AIM = [']C1', ']d2', ']Q3', ']e0'];
  const ZXING_URL = 'https://cdn.jsdelivr.net/npm/zxing-wasm@3.1.4/dist/es/reader/index.js';
  let reader = null;
  const loadReader = () => reader || (reader = new Function('url', 'return import(url)')(ZXING_URL).catch(e => {
    reader = null;
    throw e;
  }));
  const ZXING_FORMATS = {
    EAN13: 'ean-13',
    EAN8: 'ean-8',
    UPCA: 'upc-a',
    UPCE: 'upc-e',
    ISBN: 'isbn',
    Code128: 'code-128',
    Code39: 'code-39',
    Code39Std: 'code-39',
    Code39Ext: 'code-39',
    Code93: 'code-93',
    Codabar: 'codabar',
    ITF: 'interleaved-2-of-5',
    ITF14: 'itf-14',
    DataBar: 'gs1-databar',
    DataBarOmni: 'gs1-databar',
    DataBarStk: 'gs1-databar',
    DataBarStkOmni: 'gs1-databar',
    DataBarLtd: 'gs1-databar',
    DataBarExp: 'gs1-databar',
    DataBarExpStk: 'gs1-databar',
    QRCode: 'qr-code',
    QRCodeModel1: 'qr-code',
    QRCodeModel2: 'qr-code',
    MicroQRCode: 'micro-qr',
    DataMatrix: 'datamatrix',
    PDF417: 'pdf417',
    CompactPDF417: 'pdf417',
    MicroPDF417: 'micro-pdf417',
    Aztec: 'aztec',
    AztecCode: 'aztec',
    MaxiCode: 'maxicode'
  };
  const NAMES = {
    'ean-13': 'EAN-13',
    'upc-a': 'UPC-A',
    'ean-8': 'EAN-8',
    'upc-e': 'UPC-E',
    isbn: 'ISBN',
    issn: 'ISSN',
    'itf-14': 'ITF-14',
    'interleaved-2-of-5': 'Interleaved 2 of 5',
    'code-128': 'Code 128',
    'gs1-128': 'GS1-128',
    'code-39': 'Code 39',
    'code-93': 'Code 93',
    codabar: 'Codabar',
    'qr-code': 'QR Code',
    datamatrix: 'Data Matrix',
    pdf417: 'PDF417',
    aztec: 'Aztec',
    'gs1-databar': 'GS1 DataBar',
    mrz: 'MRZ',
    'micro-qr': 'Micro QR Code',
    'micro-pdf417': 'MicroPDF417',
    maxicode: 'MaxiCode'
  };
  const withArticle = name => `a${(/^([AEIO]|MRZ)/).test(name) ? 'n' : ''} ${name}`;
  const pageFor = slug => slug === 'mrz' ? '/reference/documents/mrz' : `/reference/barcode-types/${slug}`;
  const EXAMPLES = {
    'ean-13': '9521234567899',
    'upc-a': '061414100007',
    'ean-8': '95212340',
    'upc-e': '01234565',
    isbn: '9780306406157',
    issn: '9771234567003',
    'itf-14': '19521234567896',
    'gs1-128': '(01)09521234567899(17)271231(10)ABC123',
    datamatrix: '(01)09521234567899(17)271231(10)ABC123',
    'gs1-databar': '(01)09521234567899(3103)001250',
    'qr-code': 'https://id.gs1.org/01/09521234567899/10/ABC123',
    mrz: 'P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<\nL898902C36UTO7408122F1204159ZE184226B<<<<<10',
    pdf417: '@\n\x1e\rANSI 636000090002DL00410278ZV03190008DLDAQT64235789\nDCSSAMPLE\nDACMICHAEL\nDBB06061986\nDBA06062029\nDBC1\nDAJVA\nDAK000000000\n\r'
  };
  const mod10 = digits => {
    const sum = [...digits].reverse().reduce((a, d, i) => a + Number(d) * (i % 2 === 0 ? 3 : 1), 0);
    return (10 - sum % 10) % 10;
  };
  const validMod10 = v => (/^\d{2,}$/).test(v) && mod10(v.slice(0, -1)) === Number(v[v.length - 1]);
  const mod11 = (digits, top) => {
    const r = [...digits].reduce((a, d, i) => a + Number(d) * (top - i), 0) % 11;
    const c = (11 - r) % 11;
    return c === 10 ? 'X' : String(c);
  };
  const expandUpcE = v => {
    const [ns, a, b, c, d, e, f] = v.slice(0, 7);
    const body = f <= '2' ? a + b + f + '0000' + c + d + e : f === '3' ? a + b + c + '00000' + d + e : f === '4' ? a + b + c + d + '00000' + e : a + b + c + d + e + '0000' + f;
    return ns + body + v[7];
  };
  const compressUpcA = upcA => {
    const [ns, m1, m2, m3, m4, m5, , , p3, p4, p5, c] = upcA;
    const bodies = [m1 + m2 + p3 + p4 + p5 + m3, m1 + m2 + m3 + p4 + p5 + '3', m1 + m2 + m3 + m4 + p5 + '4', m1 + m2 + m3 + m4 + m5 + p5];
    return bodies.map(b => ns + b + c).find(e => expandUpcE(e) === upcA) || null;
  };
  const mrzCheck = s => String([...s].reduce((sum, c, i) => {
    const v = c === '<' ? 0 : (/\d/).test(c) ? Number(c) : c.charCodeAt(0) - 55;
    return sum + v * [7, 3, 1][i % 3];
  }, 0) % 10);
  const prefixOf = gtin13 => {
    const n = Number(gtin13.slice(0, 3));
    if (gtin13.startsWith('99')) return 'A coupon';
    const hit = PREFIXES.find(([a, b]) => n >= a && n <= b);
    return hit ? hit[2] : 'Not assigned. Reserved by GS1 for future use.';
  };
  const analyzeGtin = (digits, hint) => {
    const len = digits.length;
    const out = {
      kind: 'gtin',
      digits,
      notes: []
    };
    if (len === 8) {
      const upce = (/^[01]/).test(digits) && validMod10(expandUpcE(digits));
      const ean8 = validMod10(digits);
      const asUpcE = hint === 'upc-e' || hint !== 'ean-8' && upce && !ean8;
      if (asUpcE) {
        out.slug = 'upc-e';
        out.expanded = expandUpcE(digits);
        out.valid = upce;
        out.expected = mod10(out.expanded.slice(0, -1));
        out.gtin14 = out.expanded.padStart(14, '0');
      } else {
        out.slug = 'ean-8';
        out.valid = ean8;
        out.expected = mod10(digits.slice(0, -1));
        out.gtin14 = digits.padStart(14, '0');
        if (upce && ean8) out.notes.push('This could also be a UPC-E, which would mean a different product. The barcode type your scanner reports tells them apart.');
        if ((/^[02]/).test(digits)) out.notes.push('8-digit numbers starting with 0 or 2 are assigned by stores, not globally.');
      }
    } else {
      out.slug = len === 12 ? 'upc-a' : len === 13 ? 'ean-13' : 'itf-14';
      out.valid = validMod10(digits);
      out.expected = mod10(digits.slice(0, -1));
      out.gtin14 = digits.padStart(14, '0');
      const g13 = digits.padStart(13, '0').slice(-13);
      if (len === 14) {
        const ind = digits[0];
        out.indicator = ind === '0' ? 'A single item, padded to 14 digits' : ind === '9' ? 'A grouping whose quantity or weight varies' : `Packaging level ${ind}: a case or other grouping of the same item`;
      }
      const prefixDigits = len === 14 ? digits.slice(1, 4) : g13.slice(0, 3);
      out.prefix = prefixDigits;
      out.issuer = prefixOf(len === 14 ? digits.slice(1) : g13);
      if (len === 13 && (/^97[89]/).test(digits)) {
        out.slug = 'isbn';
        if (digits.startsWith('978')) out.isbn10 = digits.slice(3, 12) + mod11(digits.slice(3, 12), 10);
      }
      if (len === 13 && digits.startsWith('977')) {
        out.slug = 'issn';
        out.issn = `${digits.slice(3, 7)}-${digits.slice(7, 10)}${mod11(digits.slice(3, 10), 8)}`;
      }
    }
    return out;
  };
  const ai = code => typeof GS1_AIS !== 'undefined' ? GS1_AIS.ais[code] : undefined;
  const components = spec => spec.split(' ').map(c => {
    const optional = c.startsWith('[');
    const [type, ...linters] = c.replace(/[[\]]/g, '').split(',');
    const m = type.match(/^([NXYZ])(\.\.)?(\d+)$/);
    return {
      kind: m[1],
      variable: Boolean(m[2]),
      n: Number(m[3]),
      linters,
      optional
    };
  });
  const maxLength = spec => components(spec).reduce((a, c) => a + c.n, 0);
  const findAi = (s, i) => [2, 3, 4].map(n => s.slice(i, i + n)).find(code => ai(code));
  const parseElementString = s => {
    const fields = [];
    for (let i = 0; i < s.length; ) {
      if (s[i] === '\x1d') {
        i += 1;
        continue;
      }
      const code = findAi(s, i);
      if (!code) return {
        fields,
        error: `No Application Identifier starts at “${s.slice(i, i + 6)}”.`
      };
      const [, fixed, spec] = ai(code);
      i += code.length;
      const max = maxLength(spec);
      const gs = s.indexOf('\x1d', i);
      const end = fixed ? i + max : Math.min(gs === -1 ? s.length : gs, i + max);
      fields.push({
        ai: code,
        value: s.slice(i, end)
      });
      i = end;
    }
    return {
      fields
    };
  };
  const parseBracketed = s => {
    const fields = [...s.matchAll(/\((\d{2,4})\)([^(]*)/g)].map(m => ({
      ai: m[1],
      value: m[2]
    }));
    return fields.length && fields.map(f => `(${f.ai})${f.value}`).join('') === s ? {
      fields
    } : null;
  };
  const parseDigitalLink = s => {
    let url;
    try {
      url = new URL(s);
    } catch {
      return null;
    }
    const parts = url.pathname.split('/').filter(Boolean).map(decodeURIComponent);
    const start = parts.findIndex((p, i) => ai(p) && ai(p)[3] === 'key' && i + 1 < parts.length);
    if (start === -1) return null;
    const fields = [];
    for (let i = start; i + 1 < parts.length; i += 2) fields.push({
      ai: parts[i],
      value: parts[i + 1]
    });
    for (const [k, v] of url.searchParams) if ((/^\d{2,4}$/).test(k) && ai(k)) fields.push({
      ai: k,
      value: v
    });
    return {
      fields,
      domain: url.host,
      pathPrefix: parts.slice(0, start).join('/')
    };
  };
  const twoDigitYear = yy => {
    const now = new Date().getFullYear(), century = Math.floor(now / 100) * 100;
    const diff = Number(yy) - now % 100;
    return century + Number(yy) + (diff >= 51 ? -100 : diff <= -50 ? 100 : 0);
  };
  const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
  const readDate = v => {
    const [yy, mm, dd] = [v.slice(0, 2), Number(v.slice(2, 4)), Number(v.slice(4, 6))];
    const year = twoDigitYear(yy);
    if (mm < 1 || mm > 12) return null;
    const last = new Date(year, mm, 0).getDate();
    if (dd > last) return null;
    return dd === 0 ? `End of ${MONTHS[mm - 1]} ${year}` : `${MONTHS[mm - 1]} ${dd}, ${year}`;
  };
  const checkField = (code, value) => {
    const info = ai(code);
    if (!info) return ['Not a known Application Identifier.'];
    const problems = [];
    let rest = value;
    for (const c of components(info[2])) {
      const part = rest.slice(0, c.n);
      if (!part.length) {
        if (!c.optional) problems.push('Too short.');
        break;
      }
      if (!c.variable && part.length < c.n) problems.push(`Needs exactly ${c.n} characters here.`);
      if (c.kind === 'N' && !(/^\d+$/).test(part)) problems.push('Should be digits only.');
      if (c.linters.includes('csum') && part.length === c.n && !validMod10(part)) problems.push(`Wrong check digit: it should end in ${mod10(part.slice(0, -1))}.`);
      if (c.linters.some(l => l.startsWith('yymmd')) && part.length === 6 && !readDate(part)) problems.push('Not a valid date.');
      rest = rest.slice(part.length);
    }
    if (rest.length) problems.push('Too long.');
    return problems;
  };
  const meaning = (code, value) => {
    const info = ai(code);
    if (!info) return '';
    const comps = components(info[2]);
    if (comps[0].linters.some(l => l.startsWith('yymmd')) && value.length >= 6) return readDate(value.slice(0, 6)) || '';
    if ((/^3\d\d\d$/).test(code) && (/^\d+$/).test(value) && !(/^39[13]/).test(code)) {
      const decimals = Number(code[3]);
      const n = (Number(value) / 10 ** decimals).toFixed(decimals);
      const unit = (info[0].match(/\(([^)]+)\)/) || [])[1];
      return unit ? `${n} ${unit}` : n;
    }
    return '';
  };
  const parseMrz = lines => {
    const out = {
      kind: 'mrz',
      format: lines[0].length === 44 ? 'TD3 (passport)' : lines[0].length === 30 ? 'TD1 (ID card)' : 'TD2',
      lines
    };
    if (lines.length !== 2 || lines[0].length !== 44) return out;
    const [l1, l2] = lines;
    const name = l1.slice(5).replace(/<+$/, '').split('<<');
    const check = (field, digit) => mrzCheck(field) === digit;
    out.fields = [['Document type', l1.slice(0, 2).replace(/</g, '')], ['Issuing state', l1.slice(2, 5)], ['Surname', (name[0] || '').replace(/</g, ' ')], ['Given names', (name[1] || '').replace(/</g, ' ').trim()], ['Document number', l2.slice(0, 9).replace(/</g, ''), check(l2.slice(0, 9), l2[9])], ['Nationality', l2.slice(10, 13)], ['Date of birth', l2.slice(13, 19), check(l2.slice(13, 19), l2[19])], ['Sex', l2[20] === '<' ? 'Unspecified' : l2[20]], ['Expiry date', l2.slice(21, 27), check(l2.slice(21, 27), l2[27])], ['Optional data', l2.slice(28, 42).replace(/</g, ''), check(l2.slice(28, 42), l2[42])], ['Composite check', l2[43], check(l2.slice(0, 10) + l2.slice(13, 20) + l2.slice(21, 43), l2[43])]];
    return out;
  };
  const AAMVA = {
    DAQ: 'License number',
    DCS: 'Family name',
    DAC: 'First name',
    DAD: 'Middle name',
    DBB: 'Date of birth',
    DBA: 'Expiry date',
    DBD: 'Issue date',
    DBC: 'Sex',
    DAG: 'Street',
    DAI: 'City',
    DAJ: 'State',
    DAK: 'Postal code',
    DCF: 'Document discriminator',
    DCG: 'Country',
    DAU: 'Height',
    DAY: 'Eye color'
  };
  const parseAamva = s => {
    const iin = (s.match(/ANSI ?(\d{6})/) || [])[1];
    const fields = [...s.matchAll(/(?:^|[\n\r]|DL|ID)(D[A-Z]{2})([^\n\r]*)/g)].filter(([, id]) => AAMVA[id]).map(([, id, v]) => [id, AAMVA[id], v.trim()]);
    return {
      kind: 'aamva',
      iin,
      fields
    };
  };
  const fromScan = r => {
    let slug = ZXING_FORMATS[r.format] || null, text = r.text;
    const aim = r.symbologyIdentifier || null;
    if (slug === 'code-128' && aim === ']C1') slug = 'gs1-128';
    if (slug === 'interleaved-2-of-5' && (/^\d{14}$/).test(text) && validMod10(text)) slug = 'itf-14';
    if (['EAN13', 'UPCA'].includes(r.format) && (/^0\d{12}$/).test(text)) {
      slug = 'upc-a';
      text = text.slice(1);
    }
    if (slug === 'upc-e' && (/^0\d{12}$/).test(text)) text = compressUpcA(text.slice(1)) || text;
    return {
      text,
      detected: {
        slug,
        name: slug ? NAMES[slug] : r.format,
        aim,
        version: r.version || null,
        ec: r.ecLevel || null
      }
    };
  };
  const pixelsOf = (source, width, height, max) => {
    const k = Math.min(1, max / Math.max(width, height));
    const canvas = document.createElement('canvas');
    canvas.width = Math.round(width * k);
    canvas.height = Math.round(height * k);
    const ctx = canvas.getContext('2d', {
      willReadFrequently: true
    });
    ctx.drawImage(source, 0, 0, canvas.width, canvas.height);
    return ctx.getImageData(0, 0, canvas.width, canvas.height);
  };
  const readPixels = async pixels => {
    const zxing = await loadReader();
    const [r] = await zxing.readBarcodes(pixels, {
      formats: [],
      textMode: 'Plain',
      tryHarder: true,
      maxNumberOfSymbols: 1
    });
    return r && r.isValid ? fromScan(r) : null;
  };
  const analyze = (raw, detected, hint) => {
    let s = raw.replace(/[\r\n]+$/, '');
    let aim = null;
    const m = s.match(/^\][A-Za-z][0-9A-Za-z]/);
    if (m) {
      aim = m[0];
      s = s.slice(3);
    }
    const aimInData = !!aim;
    if (!aim && detected && detected.aim) aim = detected.aim;
    const trimmed = s.trim();
    const base = {
      aim,
      aimInData,
      aimMeaning: aim ? AIM[aim] || null : null,
      detected
    };
    if (!trimmed) return null;
    const lines = trimmed.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
    if (lines.length >= 2 && lines.every(l => (/^[A-Z0-9<]+$/).test(l) && l.length === lines[0].length) && [30, 36, 44].includes(lines[0].length)) {
      return {
        ...base,
        ...parseMrz(lines)
      };
    }
    if ((/^@/).test(s) && (/ANSI ?\d{6}/).test(s)) return {
      ...base,
      ...parseAamva(s)
    };
    if ((/^https?:\/\//i).test(trimmed)) {
      const dl = parseDigitalLink(trimmed);
      return dl ? {
        ...base,
        kind: 'digital-link',
        url: trimmed,
        ...dl
      } : {
        ...base,
        kind: 'text',
        text: trimmed
      };
    }
    const bracketed = parseBracketed(trimmed);
    if (bracketed) return {
      ...base,
      kind: 'gs1',
      ...bracketed
    };
    if ((/^\d{4}-\d{3}[\dX]$/i).test(trimmed)) {
      const seven = trimmed.replace('-', '').slice(0, 7);
      const ean = '977' + seven + '00';
      return {
        ...base,
        kind: 'issn',
        issn: trimmed.toUpperCase(),
        valid: mod11(seven, 8) === trimmed.slice(-1).toUpperCase(),
        ean: ean + mod10(ean)
      };
    }
    const digits = trimmed.replace(/[\s-]/g, '');
    const gs1Hint = aim && GS1_AIM.includes(aim) || s.includes('\x1d');
    if ((/^\d+$/).test(digits) && [8, 12, 13, 14].includes(digits.length) && !gs1Hint) {
      return {
        ...base,
        ...analyzeGtin(digits, detected && detected.slug || hint)
      };
    }
    if ((/^\d{9}[\dX]$/i).test(digits) && ((/[\s-]/).test(trimmed) || (/x$/i).test(digits))) {
      const isbn13 = '978' + digits.slice(0, 9);
      return {
        ...base,
        kind: 'isbn10',
        isbn10: digits.toUpperCase(),
        valid: mod11(digits.slice(0, 9), 10) === digits[9].toUpperCase(),
        isbn13: isbn13 + mod10(isbn13)
      };
    }
    if ((/^\d{4,}/).test(s) || gs1Hint) {
      const parsed = parseElementString(s);
      const keyOk = parsed.fields.some(f => ['00', '01', '02'].includes(f.ai) && validMod10(f.value));
      if (!parsed.error && parsed.fields.length && (gs1Hint || keyOk)) return {
        ...base,
        kind: 'gs1',
        ...parsed
      };
    }
    return {
      ...base,
      kind: 'text',
      text: s
    };
  };
  const orderFields = fields => [...fields].sort((a, b) => {
    const rank = f => f.ai === '01' || f.ai === '00' ? 0 : ai(f.ai) && ai(f.ai)[1] ? 1 : 2;
    return rank(a) - rank(b);
  });
  const toHri = fields => orderFields(fields).map(f => `(${f.ai})${f.value}`).join('');
  const toDigitalLink = (fields, domain) => {
    const key = fields.find(f => ai(f.ai) && ai(f.ai)[3] === 'key');
    if (!key) return null;
    const qualifiers = (ai(key.ai)[4] || '').split('|')[0].split(',').filter(Boolean);
    let path = `/${key.ai}/${encodeURIComponent(key.value)}`;
    for (const q of qualifiers) {
      const f = fields.find(x => x.ai === q);
      if (f) path += `/${q}/${encodeURIComponent(f.value)}`;
    }
    const rest = fields.filter(f => f !== key && !qualifiers.includes(f.ai));
    const query = rest.map(f => `${f.ai}=${encodeURIComponent(f.value)}`).join('&');
    return `${domain.replace(/\/+$/, '')}${path}${query ? `?${query}` : ''}`;
  };
  const ui = {
    panel: {
      border: '1px solid rgb(128 128 128 / 0.3)',
      borderRadius: 12,
      padding: 16,
      margin: '16px 0'
    },
    input: {
      width: '100%',
      padding: '8px 10px',
      borderRadius: 8,
      border: '1px solid rgb(128 128 128 / 0.45)',
      background: 'transparent',
      color: 'inherit',
      fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
      fontSize: 14
    },
    select: {
      padding: '8px 10px',
      borderRadius: 8,
      border: '1px solid rgb(128 128 128 / 0.45)',
      background: 'transparent',
      color: 'inherit',
      fontSize: 14
    },
    label: {
      display: 'block',
      fontWeight: 600,
      fontSize: 14,
      marginBottom: 6
    },
    row: {
      display: 'flex',
      flexWrap: 'wrap',
      gap: 8,
      marginTop: 10
    },
    button: {
      padding: '6px 12px',
      borderRadius: 8,
      border: '1px solid rgb(128 128 128 / 0.45)',
      background: 'transparent',
      color: 'inherit',
      fontSize: 14,
      cursor: 'pointer'
    },
    primary: {
      padding: '6px 12px',
      borderRadius: 8,
      border: '1px solid currentColor',
      background: 'transparent',
      color: 'inherit',
      fontSize: 14,
      fontWeight: 600,
      cursor: 'pointer'
    },
    muted: {
      opacity: 0.65
    },
    good: {
      color: 'rgb(22 163 74)'
    },
    bad: {
      color: 'rgb(220 38 38)'
    },
    mono: {
      fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace'
    }
  };
  const showControl = v => v.replace(/\x1d/g, '␝').replace(/\x1e/g, '␞').replace(/\r/g, '␍');
  const download = (container, name, kind) => {
    const svg = container && container.querySelector('svg');
    if (!svg) return;
    const copy = svg.cloneNode(true);
    copy.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
    copy.removeAttribute('style');
    const xml = new XMLSerializer().serializeToString(copy);
    const save = (blob, ext) => {
      const a = document.createElement('a');
      a.href = URL.createObjectURL(blob);
      a.download = `${name}.${ext}`;
      a.click();
      setTimeout(() => URL.revokeObjectURL(a.href), 1000);
    };
    if (kind === 'svg') return save(new Blob([xml], {
      type: 'image/svg+xml'
    }), 'svg');
    const vb = svg.viewBox.baseVal, scale = 6;
    const img = new Image();
    img.onload = () => {
      const c = document.createElement('canvas');
      c.width = vb.width * scale;
      c.height = vb.height * scale;
      const ctx = c.getContext('2d');
      ctx.fillStyle = '#ffffff';
      ctx.fillRect(0, 0, c.width, c.height);
      ctx.drawImage(img, 0, 0, c.width, c.height);
      c.toBlob(b => save(b, 'png'), 'image/png');
    };
    img.src = URL.createObjectURL(new Blob([xml], {
      type: 'image/svg+xml'
    }));
  };
  const query = () => typeof window === 'undefined' ? new URLSearchParams() : new URLSearchParams(window.location.search);
  return {
    PREFIX_SOURCE,
    AIM,
    NAMES,
    EXAMPLES,
    pageFor,
    withArticle,
    mod10,
    validMod10,
    expandUpcE,
    compressUpcA,
    loadReader,
    pixelsOf,
    readPixels,
    fromScan,
    analyze,
    checkField,
    meaning,
    ai,
    toHri,
    toDigitalLink,
    orderFields,
    ui,
    showControl,
    download,
    query
  };
})();

export const GS1_AIS = {
  release: "2026-01-27",
  ais: {
    "10": ["BATCH/LOT", 0, "X..20", "attr", ""],
    "11": ["PROD DATE", 1, "N6,yymmd0", "attr", ""],
    "12": ["DUE DATE", 1, "N6,yymmd0", "attr", ""],
    "13": ["PACK DATE", 1, "N6,yymmd0", "attr", ""],
    "15": ["BEST BEFORE or BEST BY", 1, "N6,yymmd0", "attr", ""],
    "16": ["SELL BY", 1, "N6,yymmd0", "attr", ""],
    "17": ["USE BY or EXPIRY", 1, "N6,yymmd0", "attr", ""],
    "20": ["VARIANT", 1, "N2", "attr", ""],
    "21": ["SERIAL", 0, "X..20", "", ""],
    "22": ["CPV", 0, "X..20", "", ""],
    "30": ["VAR. COUNT", 0, "N..8", "attr", ""],
    "37": ["COUNT", 0, "N..8", "attr", ""],
    "90": ["INTERNAL", 0, "X..30", "attr", ""],
    "91": ["INTERNAL", 0, "X..90", "attr", ""],
    "92": ["INTERNAL", 0, "X..90", "attr", ""],
    "93": ["INTERNAL", 0, "X..90", "attr", ""],
    "94": ["INTERNAL", 0, "X..90", "attr", ""],
    "95": ["INTERNAL", 0, "X..90", "attr", ""],
    "96": ["INTERNAL", 0, "X..90", "attr", ""],
    "97": ["INTERNAL", 0, "X..90", "attr", ""],
    "98": ["INTERNAL", 0, "X..90", "attr", ""],
    "99": ["INTERNAL", 0, "X..90", "attr", ""],
    "235": ["TPX", 0, "X..28", "", ""],
    "240": ["ADDITIONAL ID", 0, "X..30", "attr", ""],
    "241": ["CUST. PART No.", 0, "X..30", "attr", ""],
    "242": ["MTO VARIANT", 0, "N..6", "attr", ""],
    "243": ["PCN", 0, "X..20", "attr", ""],
    "250": ["SECONDARY SERIAL", 0, "X..30", "attr", ""],
    "251": ["REF. TO SOURCE", 0, "X..30", "attr", ""],
    "253": ["GDTI", 0, "N13,csum,gcppos1 [X..17]", "key", ""],
    "254": ["GLN EXTENSION COMPONENT", 0, "X..20", "", ""],
    "255": ["GCN", 0, "N13,csum,gcppos1 [N..12]", "key", ""],
    "400": ["ORDER NUMBER", 0, "X..30", "attr", ""],
    "401": ["GINC", 0, "X..30,gcppos1", "key", ""],
    "402": ["GSIN", 0, "N17,csum,gcppos1", "key", ""],
    "403": ["ROUTE", 0, "X..30", "attr", ""],
    "410": ["SHIP TO LOC", 1, "N13,csum,gcppos1", "attr", ""],
    "411": ["BILL TO", 1, "N13,csum,gcppos1", "attr", ""],
    "412": ["PURCHASE FROM", 1, "N13,csum,gcppos1", "attr", ""],
    "413": ["SHIP FOR LOC", 1, "N13,csum,gcppos1", "attr", ""],
    "414": ["LOC No.", 1, "N13,csum,gcppos1", "key", "254|7040"],
    "415": ["PAY TO", 1, "N13,csum,gcppos1", "key", "8020"],
    "416": ["PROD/SERV LOC", 1, "N13,csum,gcppos1", "attr", ""],
    "417": ["PARTY", 1, "N13,csum,gcppos1", "key", "7040"],
    "420": ["SHIP TO POST", 0, "X..20", "attr", ""],
    "421": ["SHIP TO POST", 0, "N3,iso3166 X..9", "attr", ""],
    "422": ["ORIGIN", 0, "N3,iso3166", "attr", ""],
    "423": ["COUNTRY - INITIAL PROCESS", 0, "N3,iso3166 [N3],iso3166 [N3],iso3166 [N3],iso3166 [N3],iso3166", "attr", ""],
    "424": ["COUNTRY - PROCESS", 0, "N3,iso3166", "attr", ""],
    "425": ["COUNTRY - DISASSEMBLY", 0, "N3,iso3166 [N3],iso3166 [N3],iso3166 [N3],iso3166 [N3],iso3166", "attr", ""],
    "426": ["COUNTRY - FULL PROCESS", 0, "N3,iso3166", "attr", ""],
    "427": ["ORIGIN SUBDIVISION", 0, "X..3", "attr", ""],
    "710": ["NHRN PZN", 0, "X..20", "attr", ""],
    "711": ["NHRN CIP", 0, "X..20", "attr", ""],
    "712": ["NHRN CN", 0, "X..20", "attr", ""],
    "713": ["NHRN DRN", 0, "X..20", "attr", ""],
    "714": ["NHRN AIM", 0, "X..20", "attr", ""],
    "715": ["NHRN NDC", 0, "X..20", "attr", ""],
    "716": ["NHRN AIC", 0, "X..20", "attr", ""],
    "717": ["NHRN SRN", 0, "X..20", "attr", ""],
    "3100": ["NET WEIGHT (kg)", 1, "N6", "attr", ""],
    "3101": ["NET WEIGHT (kg)", 1, "N6", "attr", ""],
    "3102": ["NET WEIGHT (kg)", 1, "N6", "attr", ""],
    "3103": ["NET WEIGHT (kg)", 1, "N6", "attr", ""],
    "3104": ["NET WEIGHT (kg)", 1, "N6", "attr", ""],
    "3105": ["NET WEIGHT (kg)", 1, "N6", "attr", ""],
    "3110": ["LENGTH (m)", 1, "N6", "attr", ""],
    "3111": ["LENGTH (m)", 1, "N6", "attr", ""],
    "3112": ["LENGTH (m)", 1, "N6", "attr", ""],
    "3113": ["LENGTH (m)", 1, "N6", "attr", ""],
    "3114": ["LENGTH (m)", 1, "N6", "attr", ""],
    "3115": ["LENGTH (m)", 1, "N6", "attr", ""],
    "3120": ["WIDTH (m)", 1, "N6", "attr", ""],
    "3121": ["WIDTH (m)", 1, "N6", "attr", ""],
    "3122": ["WIDTH (m)", 1, "N6", "attr", ""],
    "3123": ["WIDTH (m)", 1, "N6", "attr", ""],
    "3124": ["WIDTH (m)", 1, "N6", "attr", ""],
    "3125": ["WIDTH (m)", 1, "N6", "attr", ""],
    "3130": ["HEIGHT (m)", 1, "N6", "attr", ""],
    "3131": ["HEIGHT (m)", 1, "N6", "attr", ""],
    "3132": ["HEIGHT (m)", 1, "N6", "attr", ""],
    "3133": ["HEIGHT (m)", 1, "N6", "attr", ""],
    "3134": ["HEIGHT (m)", 1, "N6", "attr", ""],
    "3135": ["HEIGHT (m)", 1, "N6", "attr", ""],
    "3140": ["AREA (m²)", 1, "N6", "attr", ""],
    "3141": ["AREA (m²)", 1, "N6", "attr", ""],
    "3142": ["AREA (m²)", 1, "N6", "attr", ""],
    "3143": ["AREA (m²)", 1, "N6", "attr", ""],
    "3144": ["AREA (m²)", 1, "N6", "attr", ""],
    "3145": ["AREA (m²)", 1, "N6", "attr", ""],
    "3150": ["NET VOLUME (l)", 1, "N6", "attr", ""],
    "3151": ["NET VOLUME (l)", 1, "N6", "attr", ""],
    "3152": ["NET VOLUME (l)", 1, "N6", "attr", ""],
    "3153": ["NET VOLUME (l)", 1, "N6", "attr", ""],
    "3154": ["NET VOLUME (l)", 1, "N6", "attr", ""],
    "3155": ["NET VOLUME (l)", 1, "N6", "attr", ""],
    "3160": ["NET VOLUME (m³)", 1, "N6", "attr", ""],
    "3161": ["NET VOLUME (m³)", 1, "N6", "attr", ""],
    "3162": ["NET VOLUME (m³)", 1, "N6", "attr", ""],
    "3163": ["NET VOLUME (m³)", 1, "N6", "attr", ""],
    "3164": ["NET VOLUME (m³)", 1, "N6", "attr", ""],
    "3165": ["NET VOLUME (m³)", 1, "N6", "attr", ""],
    "3200": ["NET WEIGHT (lb)", 1, "N6", "attr", ""],
    "3201": ["NET WEIGHT (lb)", 1, "N6", "attr", ""],
    "3202": ["NET WEIGHT (lb)", 1, "N6", "attr", ""],
    "3203": ["NET WEIGHT (lb)", 1, "N6", "attr", ""],
    "3204": ["NET WEIGHT (lb)", 1, "N6", "attr", ""],
    "3205": ["NET WEIGHT (lb)", 1, "N6", "attr", ""],
    "3210": ["LENGTH (in)", 1, "N6", "attr", ""],
    "3211": ["LENGTH (in)", 1, "N6", "attr", ""],
    "3212": ["LENGTH (in)", 1, "N6", "attr", ""],
    "3213": ["LENGTH (in)", 1, "N6", "attr", ""],
    "3214": ["LENGTH (in)", 1, "N6", "attr", ""],
    "3215": ["LENGTH (in)", 1, "N6", "attr", ""],
    "3220": ["LENGTH (ft)", 1, "N6", "attr", ""],
    "3221": ["LENGTH (ft)", 1, "N6", "attr", ""],
    "3222": ["LENGTH (ft)", 1, "N6", "attr", ""],
    "3223": ["LENGTH (ft)", 1, "N6", "attr", ""],
    "3224": ["LENGTH (ft)", 1, "N6", "attr", ""],
    "3225": ["LENGTH (ft)", 1, "N6", "attr", ""],
    "3230": ["LENGTH (yd)", 1, "N6", "attr", ""],
    "3231": ["LENGTH (yd)", 1, "N6", "attr", ""],
    "3232": ["LENGTH (yd)", 1, "N6", "attr", ""],
    "3233": ["LENGTH (yd)", 1, "N6", "attr", ""],
    "3234": ["LENGTH (yd)", 1, "N6", "attr", ""],
    "3235": ["LENGTH (yd)", 1, "N6", "attr", ""],
    "3240": ["WIDTH (in)", 1, "N6", "attr", ""],
    "3241": ["WIDTH (in)", 1, "N6", "attr", ""],
    "3242": ["WIDTH (in)", 1, "N6", "attr", ""],
    "3243": ["WIDTH (in)", 1, "N6", "attr", ""],
    "3244": ["WIDTH (in)", 1, "N6", "attr", ""],
    "3245": ["WIDTH (in)", 1, "N6", "attr", ""],
    "3250": ["WIDTH (ft)", 1, "N6", "attr", ""],
    "3251": ["WIDTH (ft)", 1, "N6", "attr", ""],
    "3252": ["WIDTH (ft)", 1, "N6", "attr", ""],
    "3253": ["WIDTH (ft)", 1, "N6", "attr", ""],
    "3254": ["WIDTH (ft)", 1, "N6", "attr", ""],
    "3255": ["WIDTH (ft)", 1, "N6", "attr", ""],
    "3260": ["WIDTH (yd)", 1, "N6", "attr", ""],
    "3261": ["WIDTH (yd)", 1, "N6", "attr", ""],
    "3262": ["WIDTH (yd)", 1, "N6", "attr", ""],
    "3263": ["WIDTH (yd)", 1, "N6", "attr", ""],
    "3264": ["WIDTH (yd)", 1, "N6", "attr", ""],
    "3265": ["WIDTH (yd)", 1, "N6", "attr", ""],
    "3270": ["HEIGHT (in)", 1, "N6", "attr", ""],
    "3271": ["HEIGHT (in)", 1, "N6", "attr", ""],
    "3272": ["HEIGHT (in)", 1, "N6", "attr", ""],
    "3273": ["HEIGHT (in)", 1, "N6", "attr", ""],
    "3274": ["HEIGHT (in)", 1, "N6", "attr", ""],
    "3275": ["HEIGHT (in)", 1, "N6", "attr", ""],
    "3280": ["HEIGHT (ft)", 1, "N6", "attr", ""],
    "3281": ["HEIGHT (ft)", 1, "N6", "attr", ""],
    "3282": ["HEIGHT (ft)", 1, "N6", "attr", ""],
    "3283": ["HEIGHT (ft)", 1, "N6", "attr", ""],
    "3284": ["HEIGHT (ft)", 1, "N6", "attr", ""],
    "3285": ["HEIGHT (ft)", 1, "N6", "attr", ""],
    "3290": ["HEIGHT (yd)", 1, "N6", "attr", ""],
    "3291": ["HEIGHT (yd)", 1, "N6", "attr", ""],
    "3292": ["HEIGHT (yd)", 1, "N6", "attr", ""],
    "3293": ["HEIGHT (yd)", 1, "N6", "attr", ""],
    "3294": ["HEIGHT (yd)", 1, "N6", "attr", ""],
    "3295": ["HEIGHT (yd)", 1, "N6", "attr", ""],
    "3300": ["GROSS WEIGHT (kg)", 1, "N6", "attr", ""],
    "3301": ["GROSS WEIGHT (kg)", 1, "N6", "attr", ""],
    "3302": ["GROSS WEIGHT (kg)", 1, "N6", "attr", ""],
    "3303": ["GROSS WEIGHT (kg)", 1, "N6", "attr", ""],
    "3304": ["GROSS WEIGHT (kg)", 1, "N6", "attr", ""],
    "3305": ["GROSS WEIGHT (kg)", 1, "N6", "attr", ""],
    "3310": ["LENGTH (m), log", 1, "N6", "attr", ""],
    "3311": ["LENGTH (m), log", 1, "N6", "attr", ""],
    "3312": ["LENGTH (m), log", 1, "N6", "attr", ""],
    "3313": ["LENGTH (m), log", 1, "N6", "attr", ""],
    "3314": ["LENGTH (m), log", 1, "N6", "attr", ""],
    "3315": ["LENGTH (m), log", 1, "N6", "attr", ""],
    "3320": ["WIDTH (m), log", 1, "N6", "attr", ""],
    "3321": ["WIDTH (m), log", 1, "N6", "attr", ""],
    "3322": ["WIDTH (m), log", 1, "N6", "attr", ""],
    "3323": ["WIDTH (m), log", 1, "N6", "attr", ""],
    "3324": ["WIDTH (m), log", 1, "N6", "attr", ""],
    "3325": ["WIDTH (m), log", 1, "N6", "attr", ""],
    "3330": ["HEIGHT (m), log", 1, "N6", "attr", ""],
    "3331": ["HEIGHT (m), log", 1, "N6", "attr", ""],
    "3332": ["HEIGHT (m), log", 1, "N6", "attr", ""],
    "3333": ["HEIGHT (m), log", 1, "N6", "attr", ""],
    "3334": ["HEIGHT (m), log", 1, "N6", "attr", ""],
    "3335": ["HEIGHT (m), log", 1, "N6", "attr", ""],
    "3340": ["AREA (m²), log", 1, "N6", "attr", ""],
    "3341": ["AREA (m²), log", 1, "N6", "attr", ""],
    "3342": ["AREA (m²), log", 1, "N6", "attr", ""],
    "3343": ["AREA (m²), log", 1, "N6", "attr", ""],
    "3344": ["AREA (m²), log", 1, "N6", "attr", ""],
    "3345": ["AREA (m²), log", 1, "N6", "attr", ""],
    "3350": ["VOLUME (l), log", 1, "N6", "attr", ""],
    "3351": ["VOLUME (l), log", 1, "N6", "attr", ""],
    "3352": ["VOLUME (l), log", 1, "N6", "attr", ""],
    "3353": ["VOLUME (l), log", 1, "N6", "attr", ""],
    "3354": ["VOLUME (l), log", 1, "N6", "attr", ""],
    "3355": ["VOLUME (l), log", 1, "N6", "attr", ""],
    "3360": ["VOLUME (m³), log", 1, "N6", "attr", ""],
    "3361": ["VOLUME (m³), log", 1, "N6", "attr", ""],
    "3362": ["VOLUME (m³), log", 1, "N6", "attr", ""],
    "3363": ["VOLUME (m³), log", 1, "N6", "attr", ""],
    "3364": ["VOLUME (m³), log", 1, "N6", "attr", ""],
    "3365": ["VOLUME (m³), log", 1, "N6", "attr", ""],
    "3370": ["KG PER m²", 1, "N6", "attr", ""],
    "3371": ["KG PER m²", 1, "N6", "attr", ""],
    "3372": ["KG PER m²", 1, "N6", "attr", ""],
    "3373": ["KG PER m²", 1, "N6", "attr", ""],
    "3374": ["KG PER m²", 1, "N6", "attr", ""],
    "3375": ["KG PER m²", 1, "N6", "attr", ""],
    "3400": ["GROSS WEIGHT (lb)", 1, "N6", "attr", ""],
    "3401": ["GROSS WEIGHT (lb)", 1, "N6", "attr", ""],
    "3402": ["GROSS WEIGHT (lb)", 1, "N6", "attr", ""],
    "3403": ["GROSS WEIGHT (lb)", 1, "N6", "attr", ""],
    "3404": ["GROSS WEIGHT (lb)", 1, "N6", "attr", ""],
    "3405": ["GROSS WEIGHT (lb)", 1, "N6", "attr", ""],
    "3410": ["LENGTH (in), log", 1, "N6", "attr", ""],
    "3411": ["LENGTH (in), log", 1, "N6", "attr", ""],
    "3412": ["LENGTH (in), log", 1, "N6", "attr", ""],
    "3413": ["LENGTH (in), log", 1, "N6", "attr", ""],
    "3414": ["LENGTH (in), log", 1, "N6", "attr", ""],
    "3415": ["LENGTH (in), log", 1, "N6", "attr", ""],
    "3420": ["LENGTH (ft), log", 1, "N6", "attr", ""],
    "3421": ["LENGTH (ft), log", 1, "N6", "attr", ""],
    "3422": ["LENGTH (ft), log", 1, "N6", "attr", ""],
    "3423": ["LENGTH (ft), log", 1, "N6", "attr", ""],
    "3424": ["LENGTH (ft), log", 1, "N6", "attr", ""],
    "3425": ["LENGTH (ft), log", 1, "N6", "attr", ""],
    "3430": ["LENGTH (yd), log", 1, "N6", "attr", ""],
    "3431": ["LENGTH (yd), log", 1, "N6", "attr", ""],
    "3432": ["LENGTH (yd), log", 1, "N6", "attr", ""],
    "3433": ["LENGTH (yd), log", 1, "N6", "attr", ""],
    "3434": ["LENGTH (yd), log", 1, "N6", "attr", ""],
    "3435": ["LENGTH (yd), log", 1, "N6", "attr", ""],
    "3440": ["WIDTH (in), log", 1, "N6", "attr", ""],
    "3441": ["WIDTH (in), log", 1, "N6", "attr", ""],
    "3442": ["WIDTH (in), log", 1, "N6", "attr", ""],
    "3443": ["WIDTH (in), log", 1, "N6", "attr", ""],
    "3444": ["WIDTH (in), log", 1, "N6", "attr", ""],
    "3445": ["WIDTH (in), log", 1, "N6", "attr", ""],
    "3450": ["WIDTH (ft), log", 1, "N6", "attr", ""],
    "3451": ["WIDTH (ft), log", 1, "N6", "attr", ""],
    "3452": ["WIDTH (ft), log", 1, "N6", "attr", ""],
    "3453": ["WIDTH (ft), log", 1, "N6", "attr", ""],
    "3454": ["WIDTH (ft), log", 1, "N6", "attr", ""],
    "3455": ["WIDTH (ft), log", 1, "N6", "attr", ""],
    "3460": ["WIDTH (yd), log", 1, "N6", "attr", ""],
    "3461": ["WIDTH (yd), log", 1, "N6", "attr", ""],
    "3462": ["WIDTH (yd), log", 1, "N6", "attr", ""],
    "3463": ["WIDTH (yd), log", 1, "N6", "attr", ""],
    "3464": ["WIDTH (yd), log", 1, "N6", "attr", ""],
    "3465": ["WIDTH (yd), log", 1, "N6", "attr", ""],
    "3470": ["HEIGHT (in), log", 1, "N6", "attr", ""],
    "3471": ["HEIGHT (in), log", 1, "N6", "attr", ""],
    "3472": ["HEIGHT (in), log", 1, "N6", "attr", ""],
    "3473": ["HEIGHT (in), log", 1, "N6", "attr", ""],
    "3474": ["HEIGHT (in), log", 1, "N6", "attr", ""],
    "3475": ["HEIGHT (in), log", 1, "N6", "attr", ""],
    "3480": ["HEIGHT (ft), log", 1, "N6", "attr", ""],
    "3481": ["HEIGHT (ft), log", 1, "N6", "attr", ""],
    "3482": ["HEIGHT (ft), log", 1, "N6", "attr", ""],
    "3483": ["HEIGHT (ft), log", 1, "N6", "attr", ""],
    "3484": ["HEIGHT (ft), log", 1, "N6", "attr", ""],
    "3485": ["HEIGHT (ft), log", 1, "N6", "attr", ""],
    "3490": ["HEIGHT (yd), log", 1, "N6", "attr", ""],
    "3491": ["HEIGHT (yd), log", 1, "N6", "attr", ""],
    "3492": ["HEIGHT (yd), log", 1, "N6", "attr", ""],
    "3493": ["HEIGHT (yd), log", 1, "N6", "attr", ""],
    "3494": ["HEIGHT (yd), log", 1, "N6", "attr", ""],
    "3495": ["HEIGHT (yd), log", 1, "N6", "attr", ""],
    "3500": ["AREA (in²)", 1, "N6", "attr", ""],
    "3501": ["AREA (in²)", 1, "N6", "attr", ""],
    "3502": ["AREA (in²)", 1, "N6", "attr", ""],
    "3503": ["AREA (in²)", 1, "N6", "attr", ""],
    "3504": ["AREA (in²)", 1, "N6", "attr", ""],
    "3505": ["AREA (in²)", 1, "N6", "attr", ""],
    "3510": ["AREA (ft²)", 1, "N6", "attr", ""],
    "3511": ["AREA (ft²)", 1, "N6", "attr", ""],
    "3512": ["AREA (ft²)", 1, "N6", "attr", ""],
    "3513": ["AREA (ft²)", 1, "N6", "attr", ""],
    "3514": ["AREA (ft²)", 1, "N6", "attr", ""],
    "3515": ["AREA (ft²)", 1, "N6", "attr", ""],
    "3520": ["AREA (yd²)", 1, "N6", "attr", ""],
    "3521": ["AREA (yd²)", 1, "N6", "attr", ""],
    "3522": ["AREA (yd²)", 1, "N6", "attr", ""],
    "3523": ["AREA (yd²)", 1, "N6", "attr", ""],
    "3524": ["AREA (yd²)", 1, "N6", "attr", ""],
    "3525": ["AREA (yd²)", 1, "N6", "attr", ""],
    "3530": ["AREA (in²), log", 1, "N6", "attr", ""],
    "3531": ["AREA (in²), log", 1, "N6", "attr", ""],
    "3532": ["AREA (in²), log", 1, "N6", "attr", ""],
    "3533": ["AREA (in²), log", 1, "N6", "attr", ""],
    "3534": ["AREA (in²), log", 1, "N6", "attr", ""],
    "3535": ["AREA (in²), log", 1, "N6", "attr", ""],
    "3540": ["AREA (ft²), log", 1, "N6", "attr", ""],
    "3541": ["AREA (ft²), log", 1, "N6", "attr", ""],
    "3542": ["AREA (ft²), log", 1, "N6", "attr", ""],
    "3543": ["AREA (ft²), log", 1, "N6", "attr", ""],
    "3544": ["AREA (ft²), log", 1, "N6", "attr", ""],
    "3545": ["AREA (ft²), log", 1, "N6", "attr", ""],
    "3550": ["AREA (yd²), log", 1, "N6", "attr", ""],
    "3551": ["AREA (yd²), log", 1, "N6", "attr", ""],
    "3552": ["AREA (yd²), log", 1, "N6", "attr", ""],
    "3553": ["AREA (yd²), log", 1, "N6", "attr", ""],
    "3554": ["AREA (yd²), log", 1, "N6", "attr", ""],
    "3555": ["AREA (yd²), log", 1, "N6", "attr", ""],
    "3560": ["NET WEIGHT (tr oz)", 1, "N6", "attr", ""],
    "3561": ["NET WEIGHT (tr oz)", 1, "N6", "attr", ""],
    "3562": ["NET WEIGHT (tr oz)", 1, "N6", "attr", ""],
    "3563": ["NET WEIGHT (tr oz)", 1, "N6", "attr", ""],
    "3564": ["NET WEIGHT (tr oz)", 1, "N6", "attr", ""],
    "3565": ["NET WEIGHT (tr oz)", 1, "N6", "attr", ""],
    "3570": ["NET VOLUME (oz)", 1, "N6", "attr", ""],
    "3571": ["NET VOLUME (oz)", 1, "N6", "attr", ""],
    "3572": ["NET VOLUME (oz)", 1, "N6", "attr", ""],
    "3573": ["NET VOLUME (oz)", 1, "N6", "attr", ""],
    "3574": ["NET VOLUME (oz)", 1, "N6", "attr", ""],
    "3575": ["NET VOLUME (oz)", 1, "N6", "attr", ""],
    "3600": ["NET VOLUME (qt (US))", 1, "N6", "attr", ""],
    "3601": ["NET VOLUME (qt (US))", 1, "N6", "attr", ""],
    "3602": ["NET VOLUME (qt (US))", 1, "N6", "attr", ""],
    "3603": ["NET VOLUME (qt (US))", 1, "N6", "attr", ""],
    "3604": ["NET VOLUME (qt (US))", 1, "N6", "attr", ""],
    "3605": ["NET VOLUME (qt (US))", 1, "N6", "attr", ""],
    "3610": ["NET VOLUME (gal.)", 1, "N6", "attr", ""],
    "3611": ["NET VOLUME (gal.)", 1, "N6", "attr", ""],
    "3612": ["NET VOLUME (gal.)", 1, "N6", "attr", ""],
    "3613": ["NET VOLUME (gal.)", 1, "N6", "attr", ""],
    "3614": ["NET VOLUME (gal.)", 1, "N6", "attr", ""],
    "3615": ["NET VOLUME (gal.)", 1, "N6", "attr", ""],
    "3620": ["VOLUME (qt (US)), log", 1, "N6", "attr", ""],
    "3621": ["VOLUME (qt (US)), log", 1, "N6", "attr", ""],
    "3622": ["VOLUME (qt (US)), log", 1, "N6", "attr", ""],
    "3623": ["VOLUME (qt (US)), log", 1, "N6", "attr", ""],
    "3624": ["VOLUME (qt (US)), log", 1, "N6", "attr", ""],
    "3625": ["VOLUME (qt (US)), log", 1, "N6", "attr", ""],
    "3630": ["VOLUME (gal (US)), log", 1, "N6", "attr", ""],
    "3631": ["VOLUME (gal (US)), log", 1, "N6", "attr", ""],
    "3632": ["VOLUME (gal (US)), log", 1, "N6", "attr", ""],
    "3633": ["VOLUME (gal (US)), log", 1, "N6", "attr", ""],
    "3634": ["VOLUME (gal (US)), log", 1, "N6", "attr", ""],
    "3635": ["VOLUME (gal (US)), log", 1, "N6", "attr", ""],
    "3640": ["NET VOLUME (in³)", 1, "N6", "attr", ""],
    "3641": ["NET VOLUME (in³)", 1, "N6", "attr", ""],
    "3642": ["NET VOLUME (in³)", 1, "N6", "attr", ""],
    "3643": ["NET VOLUME (in³)", 1, "N6", "attr", ""],
    "3644": ["NET VOLUME (in³)", 1, "N6", "attr", ""],
    "3645": ["NET VOLUME (in³)", 1, "N6", "attr", ""],
    "3650": ["NET VOLUME (ft³)", 1, "N6", "attr", ""],
    "3651": ["NET VOLUME (ft³)", 1, "N6", "attr", ""],
    "3652": ["NET VOLUME (ft³)", 1, "N6", "attr", ""],
    "3653": ["NET VOLUME (ft³)", 1, "N6", "attr", ""],
    "3654": ["NET VOLUME (ft³)", 1, "N6", "attr", ""],
    "3655": ["NET VOLUME (ft³)", 1, "N6", "attr", ""],
    "3660": ["NET VOLUME (yd³)", 1, "N6", "attr", ""],
    "3661": ["NET VOLUME (yd³)", 1, "N6", "attr", ""],
    "3662": ["NET VOLUME (yd³)", 1, "N6", "attr", ""],
    "3663": ["NET VOLUME (yd³)", 1, "N6", "attr", ""],
    "3664": ["NET VOLUME (yd³)", 1, "N6", "attr", ""],
    "3665": ["NET VOLUME (yd³)", 1, "N6", "attr", ""],
    "3670": ["VOLUME (in³), log", 1, "N6", "attr", ""],
    "3671": ["VOLUME (in³), log", 1, "N6", "attr", ""],
    "3672": ["VOLUME (in³), log", 1, "N6", "attr", ""],
    "3673": ["VOLUME (in³), log", 1, "N6", "attr", ""],
    "3674": ["VOLUME (in³), log", 1, "N6", "attr", ""],
    "3675": ["VOLUME (in³), log", 1, "N6", "attr", ""],
    "3680": ["VOLUME (ft³), log", 1, "N6", "attr", ""],
    "3681": ["VOLUME (ft³), log", 1, "N6", "attr", ""],
    "3682": ["VOLUME (ft³), log", 1, "N6", "attr", ""],
    "3683": ["VOLUME (ft³), log", 1, "N6", "attr", ""],
    "3684": ["VOLUME (ft³), log", 1, "N6", "attr", ""],
    "3685": ["VOLUME (ft³), log", 1, "N6", "attr", ""],
    "3690": ["VOLUME (yd³), log", 1, "N6", "attr", ""],
    "3691": ["VOLUME (yd³), log", 1, "N6", "attr", ""],
    "3692": ["VOLUME (yd³), log", 1, "N6", "attr", ""],
    "3693": ["VOLUME (yd³), log", 1, "N6", "attr", ""],
    "3694": ["VOLUME (yd³), log", 1, "N6", "attr", ""],
    "3695": ["VOLUME (yd³), log", 1, "N6", "attr", ""],
    "3900": ["AMOUNT", 0, "N..15", "attr", ""],
    "3901": ["AMOUNT", 0, "N..15", "attr", ""],
    "3902": ["AMOUNT", 0, "N..15", "attr", ""],
    "3903": ["AMOUNT", 0, "N..15", "attr", ""],
    "3904": ["AMOUNT", 0, "N..15", "attr", ""],
    "3905": ["AMOUNT", 0, "N..15", "attr", ""],
    "3906": ["AMOUNT", 0, "N..15", "attr", ""],
    "3907": ["AMOUNT", 0, "N..15", "attr", ""],
    "3908": ["AMOUNT", 0, "N..15", "attr", ""],
    "3909": ["AMOUNT", 0, "N..15", "attr", ""],
    "3910": ["AMOUNT", 0, "N3,iso4217 N..15", "attr", ""],
    "3911": ["AMOUNT", 0, "N3,iso4217 N..15", "attr", ""],
    "3912": ["AMOUNT", 0, "N3,iso4217 N..15", "attr", ""],
    "3913": ["AMOUNT", 0, "N3,iso4217 N..15", "attr", ""],
    "3914": ["AMOUNT", 0, "N3,iso4217 N..15", "attr", ""],
    "3915": ["AMOUNT", 0, "N3,iso4217 N..15", "attr", ""],
    "3916": ["AMOUNT", 0, "N3,iso4217 N..15", "attr", ""],
    "3917": ["AMOUNT", 0, "N3,iso4217 N..15", "attr", ""],
    "3918": ["AMOUNT", 0, "N3,iso4217 N..15", "attr", ""],
    "3919": ["AMOUNT", 0, "N3,iso4217 N..15", "attr", ""],
    "3920": ["PRICE", 0, "N..15", "attr", ""],
    "3921": ["PRICE", 0, "N..15", "attr", ""],
    "3922": ["PRICE", 0, "N..15", "attr", ""],
    "3923": ["PRICE", 0, "N..15", "attr", ""],
    "3924": ["PRICE", 0, "N..15", "attr", ""],
    "3925": ["PRICE", 0, "N..15", "attr", ""],
    "3926": ["PRICE", 0, "N..15", "attr", ""],
    "3927": ["PRICE", 0, "N..15", "attr", ""],
    "3928": ["PRICE", 0, "N..15", "attr", ""],
    "3929": ["PRICE", 0, "N..15", "attr", ""],
    "3930": ["PRICE", 0, "N3,iso4217 N..15", "attr", ""],
    "3931": ["PRICE", 0, "N3,iso4217 N..15", "attr", ""],
    "3932": ["PRICE", 0, "N3,iso4217 N..15", "attr", ""],
    "3933": ["PRICE", 0, "N3,iso4217 N..15", "attr", ""],
    "3934": ["PRICE", 0, "N3,iso4217 N..15", "attr", ""],
    "3935": ["PRICE", 0, "N3,iso4217 N..15", "attr", ""],
    "3936": ["PRICE", 0, "N3,iso4217 N..15", "attr", ""],
    "3937": ["PRICE", 0, "N3,iso4217 N..15", "attr", ""],
    "3938": ["PRICE", 0, "N3,iso4217 N..15", "attr", ""],
    "3939": ["PRICE", 0, "N3,iso4217 N..15", "attr", ""],
    "3940": ["PRCNT OFF", 0, "N4", "attr", ""],
    "3941": ["PRCNT OFF", 0, "N4", "attr", ""],
    "3942": ["PRCNT OFF", 0, "N4", "attr", ""],
    "3943": ["PRCNT OFF", 0, "N4", "attr", ""],
    "3950": ["PRICE/UoM", 0, "N6", "attr", ""],
    "3951": ["PRICE/UoM", 0, "N6", "attr", ""],
    "3952": ["PRICE/UoM", 0, "N6", "attr", ""],
    "3953": ["PRICE/UoM", 0, "N6", "attr", ""],
    "3954": ["PRICE/UoM", 0, "N6", "attr", ""],
    "3955": ["PRICE/UoM", 0, "N6", "attr", ""],
    "4300": ["SHIP TO COMP", 0, "X..35,pcenc", "attr", ""],
    "4301": ["SHIP TO NAME", 0, "X..35,pcenc", "attr", ""],
    "4302": ["SHIP TO ADD1", 0, "X..70,pcenc", "attr", ""],
    "4303": ["SHIP TO ADD2", 0, "X..70,pcenc", "attr", ""],
    "4304": ["SHIP TO SUB", 0, "X..70,pcenc", "attr", ""],
    "4305": ["SHIP TO LOC", 0, "X..70,pcenc", "attr", ""],
    "4306": ["SHIP TO REG", 0, "X..70,pcenc", "attr", ""],
    "4307": ["SHIP TO COUNTRY", 0, "X2,iso3166alpha2", "attr", ""],
    "4308": ["SHIP TO PHONE", 0, "X..30", "attr", ""],
    "4309": ["SHIP TO GEO", 0, "N10,latitude N10,longitude", "attr", ""],
    "4310": ["RTN TO COMP", 0, "X..35,pcenc", "attr", ""],
    "4311": ["RTN TO NAME", 0, "X..35,pcenc", "attr", ""],
    "4312": ["RTN TO ADD1", 0, "X..70,pcenc", "attr", ""],
    "4313": ["RTN TO ADD2", 0, "X..70,pcenc", "attr", ""],
    "4314": ["RTN TO SUB", 0, "X..70,pcenc", "attr", ""],
    "4315": ["RTN TO LOC", 0, "X..70,pcenc", "attr", ""],
    "4316": ["RTN TO REG", 0, "X..70,pcenc", "attr", ""],
    "4317": ["RTN TO COUNTRY", 0, "X2,iso3166alpha2", "attr", ""],
    "4318": ["RTN TO POST", 0, "X..20", "attr", ""],
    "4319": ["RTN TO PHONE", 0, "X..30", "attr", ""],
    "4320": ["SRV DESCRIPTION", 0, "X..35,pcenc", "attr", ""],
    "4321": ["DANGEROUS GOODS", 0, "N1,yesno", "attr", ""],
    "4322": ["AUTH TO LEAVE", 0, "N1,yesno", "attr", ""],
    "4323": ["SIG REQUIRED", 0, "N1,yesno", "attr", ""],
    "4324": ["NOT BEF DEL DT", 0, "N6,yymmd0 N4,hhmi", "attr", ""],
    "4325": ["NOT AFT DEL DT", 0, "N6,yymmd0 N4,hhmi", "attr", ""],
    "4326": ["REL DATE", 0, "N6,yymmdd", "attr", ""],
    "4330": ["MAX TEMP F.", 0, "N6 [X1],hyphen", "attr", ""],
    "4331": ["MAX TEMP C.", 0, "N6 [X1],hyphen", "attr", ""],
    "4332": ["MIN TEMP F.", 0, "N6 [X1],hyphen", "attr", ""],
    "4333": ["MIN TEMP C.", 0, "N6 [X1],hyphen", "attr", ""],
    "7001": ["NSN", 0, "N13", "attr", ""],
    "7002": ["MEAT CUT", 0, "X..30", "attr", ""],
    "7003": ["EXPIRY TIME", 0, "N6,yymmdd N4,hhmi", "attr", ""],
    "7004": ["ACTIVE POTENCY", 0, "N..4", "attr", ""],
    "7005": ["CATCH AREA", 0, "X..12", "attr", ""],
    "7006": ["FIRST FREEZE DATE", 0, "N6,yymmdd", "attr", ""],
    "7007": ["HARVEST DATE", 0, "N6,yymmdd [N6],yymmdd", "attr", ""],
    "7008": ["AQUATIC SPECIES", 0, "X..3", "attr", ""],
    "7009": ["FISHING GEAR TYPE", 0, "X..10", "attr", ""],
    "7010": ["PROD METHOD", 0, "X..2", "attr", ""],
    "7011": ["TEST BY DATE", 0, "N6,yymmdd [N4],hhmi", "attr", ""],
    "7020": ["REFURB LOT", 0, "X..20", "attr", ""],
    "7021": ["FUNC STAT", 0, "X..20", "attr", ""],
    "7022": ["REV STAT", 0, "X..20", "attr", ""],
    "7023": ["GIAI - ASSEMBLY", 0, "X..30,gcppos1", "attr", ""],
    "7030": ["PROCESSOR # 0", 0, "N3,iso3166999 X..27", "attr", ""],
    "7031": ["PROCESSOR # 1", 0, "N3,iso3166999 X..27", "attr", ""],
    "7032": ["PROCESSOR # 2", 0, "N3,iso3166999 X..27", "attr", ""],
    "7033": ["PROCESSOR # 3", 0, "N3,iso3166999 X..27", "attr", ""],
    "7034": ["PROCESSOR # 4", 0, "N3,iso3166999 X..27", "attr", ""],
    "7035": ["PROCESSOR # 5", 0, "N3,iso3166999 X..27", "attr", ""],
    "7036": ["PROCESSOR # 6", 0, "N3,iso3166999 X..27", "attr", ""],
    "7037": ["PROCESSOR # 7", 0, "N3,iso3166999 X..27", "attr", ""],
    "7038": ["PROCESSOR # 8", 0, "N3,iso3166999 X..27", "attr", ""],
    "7039": ["PROCESSOR # 9", 0, "N3,iso3166999 X..27", "attr", ""],
    "7040": ["UIC+EXT", 0, "N1 X1 X1 X1,importeridx", "", ""],
    "7041": ["UFRGT UNIT TYPE", 0, "X..4,packagetype", "", ""],
    "7230": ["CERT # 1", 0, "X2 X..28", "attr", ""],
    "7231": ["CERT # 2", 0, "X2 X..28", "attr", ""],
    "7232": ["CERT # 3", 0, "X2 X..28", "attr", ""],
    "7233": ["CERT # 4", 0, "X2 X..28", "attr", ""],
    "7234": ["CERT # 5", 0, "X2 X..28", "attr", ""],
    "7235": ["CERT # 6", 0, "X2 X..28", "attr", ""],
    "7236": ["CERT # 7", 0, "X2 X..28", "attr", ""],
    "7237": ["CERT # 8", 0, "X2 X..28", "attr", ""],
    "7238": ["CERT # 9", 0, "X2 X..28", "attr", ""],
    "7239": ["CERT # 10", 0, "X2 X..28", "attr", ""],
    "7240": ["PROTOCOL", 0, "X..20", "attr", ""],
    "7241": ["AIDC MEDIA TYPE", 0, "N2,mediatype", "attr", ""],
    "7242": ["VCN", 0, "X..25", "attr", ""],
    "7250": ["DOB", 0, "N8,yyyymmdd", "attr", ""],
    "7251": ["DOB TIME", 0, "N8,yyyymmdd N4,hhmi", "attr", ""],
    "7252": ["BIO SEX", 0, "N1,iso5218", "attr", ""],
    "7253": ["FAMILY NAME", 0, "X..40,pcenc", "attr", ""],
    "7254": ["GIVEN NAME", 0, "X..40,pcenc", "attr", ""],
    "7255": ["SUFFIX", 0, "X..10", "attr", ""],
    "7256": ["FULL NAME", 0, "X..90,pcenc", "attr", ""],
    "7257": ["PERSON ADDR", 0, "X..70,pcenc", "attr", ""],
    "7258": ["BIRTH SEQUENCE", 0, "X3,posinseqslash", "attr", ""],
    "7259": ["BABY", 0, "X..40,pcenc", "attr", ""],
    "8001": ["DIMENSIONS", 0, "N4,nonzero N5,nonzero N3,nonzero N1,winding N1", "attr", ""],
    "8002": ["CMT No.", 0, "X..20", "attr", ""],
    "8003": ["GRAI", 0, "N1,zero N13,csum,gcppos1 [X..16]", "key", ""],
    "8004": ["GIAI", 0, "X..30,gcppos1", "key", "7040"],
    "8005": ["PRICE PER UNIT", 0, "N6", "attr", ""],
    "8006": ["ITIP", 0, "N14,csum,gcppos2 N4,pieceoftotal", "key", "22,10,21"],
    "8007": ["IBAN", 0, "X..34,iban", "attr", ""],
    "8008": ["PROD TIME", 0, "N6,yymmdd N2,hh [N2],mi [N2],ss", "attr", ""],
    "8009": ["OPTSEN", 0, "X..50", "attr", ""],
    "8010": ["CPID", 0, "Y..30,gcppos1", "key", "8011"],
    "8011": ["CPID SERIAL", 0, "N..12,nozeroprefix", "", ""],
    "8012": ["VERSION", 0, "X..20", "attr", ""],
    "8013": ["GMN", 0, "X..25,csumalpha,gcppos1", "key", ""],
    "8014": ["MUDI", 0, "X..25,csumalpha,gcppos1,hasnondigit", "", ""],
    "8017": ["GSRN - PROVIDER", 0, "N18,csum,gcppos1", "key", "8019"],
    "8018": ["GSRN - RECIPIENT", 0, "N18,csum,gcppos1", "key", "8019"],
    "8019": ["SRIN", 0, "N..10", "", ""],
    "8020": ["REF No.", 0, "X..25", "", ""],
    "8026": ["ITIP CONTENT", 0, "N14,csum,gcppos2 N4,pieceoftotal", "attr", ""],
    "8030": ["DIGSIG", 0, "Z..90", "attr", ""],
    "8040": ["IMEI", 0, "N15", "", ""],
    "8041": ["IMEI2", 0, "N15", "", ""],
    "8042": ["ESIM", 0, "N32", "", ""],
    "8043": ["PSIM", 0, "N18 [N..2]", "", ""],
    "8110": ["", 0, "X..70,couponcode", "attr", ""],
    "8111": ["POINTS", 0, "N4", "attr", ""],
    "8112": ["", 0, "X..70,couponposoffer", "attr", ""],
    "8200": ["PRODUCT URL", 0, "X..70", "", ""],
    "00": ["SSCC", 1, "N18,csum,gcppos2", "key", ""],
    "01": ["GTIN", 1, "N14,csum,gcppos2", "key", "22,10,21|235"],
    "02": ["CONTENT", 1, "N14,csum,gcppos2", "attr", ""],
    "03": ["MTO GTIN", 1, "N14,csum,gcppos2", "", ""]
  }
};

export const Barcode = ({view, ...props}) => {
  const BARCODE_GROUPS = [{
    key: 'retail',
    name: 'Retail product',
    anchor: 'retail-product'
  }, {
    key: 'logistics',
    name: 'Logistics and shipping',
    anchor: 'logistics-and-shipping'
  }, {
    key: 'gs1',
    name: 'GS1',
    anchor: 'gs1'
  }, {
    key: 'linear',
    name: '1D (linear)',
    anchor: '1d-linear'
  }, {
    key: 'matrix',
    name: '2D (matrix)',
    anchor: '2d-matrix'
  }, {
    key: 'postal',
    name: 'Postal',
    anchor: 'postal'
  }, {
    key: 'id',
    name: 'ID and documents',
    anchor: 'id-and-documents'
  }];
  const BARCODE_TYPES = [{
    slug: 'upc-a',
    name: 'UPC-A',
    dimension: '1D',
    data: 'Digits 0–9',
    length: '12 digits',
    check: {
      label: 'GS1 mod-10',
      method: 'gs1-mod10'
    },
    groups: ['retail', 'linear'],
    standard: 'ISO/IEC 15420:2025, GS1 General Specifications',
    status: 'active',
    example: '061414100007',
    verified: true
  }, {
    slug: 'upc-e',
    name: 'UPC-E',
    dimension: '1D',
    data: 'Digits 0–9',
    length: '8 digits (expands to 12)',
    check: {
      label: 'GS1 mod-10, on the expanded UPC-A',
      method: 'upc-e'
    },
    groups: ['retail', 'linear'],
    standard: 'ISO/IEC 15420:2025, GS1 General Specifications',
    status: 'active',
    example: '01234565',
    verified: true
  }, {
    slug: 'ean-13',
    name: 'EAN-13',
    dimension: '1D',
    data: 'Digits 0–9',
    length: '13 digits',
    check: {
      label: 'GS1 mod-10',
      method: 'gs1-mod10'
    },
    groups: ['retail', 'linear'],
    standard: 'ISO/IEC 15420:2025, GS1 General Specifications',
    status: 'active',
    example: '9521234567899',
    verified: true
  }, {
    slug: 'ean-8',
    name: 'EAN-8',
    dimension: '1D',
    data: 'Digits 0–9',
    length: '8 digits',
    check: {
      label: 'GS1 mod-10',
      method: 'gs1-mod10'
    },
    groups: ['retail', 'linear'],
    standard: 'ISO/IEC 15420:2025, GS1 General Specifications',
    status: 'active',
    example: '95212340',
    verified: true
  }, {
    slug: 'isbn',
    name: 'ISBN',
    dimension: '1D',
    data: 'Digits 0–9',
    length: '13 digits, starting 978 or 979',
    check: {
      label: 'GS1 mod-10 (ISBN-10: mod-11)',
      method: 'gs1-mod10'
    },
    groups: ['retail', 'linear'],
    standard: 'ISO 2108, encoded as EAN-13',
    status: 'active',
    example: '9780306406157',
    verified: true
  }, {
    slug: 'issn',
    name: 'ISSN',
    dimension: '1D',
    data: 'Digits 0–9',
    length: '13 digits, starting 977, often with a 2-digit add-on',
    check: {
      label: 'GS1 mod-10 (the ISSN itself: mod-11)',
      method: 'gs1-mod10'
    },
    groups: ['retail', 'linear'],
    standard: 'ISO 3297, encoded as EAN-13',
    status: 'active',
    example: '9771234567003',
    verified: true
  }, {
    slug: 'gs1-databar',
    name: 'GS1 DataBar',
    dimension: '1D',
    data: 'A GTIN; Expanded adds GS1 data such as weight and expiry',
    length: '14 digits; Expanded up to 74 digits or 41 letters',
    check: {
      label: 'Built-in check, plus the GTIN check digit',
      method: null
    },
    groups: ['retail', 'gs1', 'linear'],
    standard: 'ISO/IEC 24724',
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'gs1-composite',
    name: 'GS1 Composite',
    dimension: '1D with a 2D component',
    data: 'GS1 Application Identifier element strings',
    length: 'Up to 56, 338 or 2,361 digits in the 2D part',
    check: {
      label: 'Each component has its own',
      method: null
    },
    groups: ['gs1'],
    standard: 'ISO/IEC 24723',
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'itf-14',
    name: 'ITF-14',
    dimension: '1D',
    data: 'Digits 0–9',
    length: '14 digits',
    check: {
      label: 'GS1 mod-10',
      method: 'gs1-mod10'
    },
    groups: ['logistics', 'linear'],
    standard: 'ISO/IEC 16390, GS1 General Specifications',
    status: 'active',
    example: '19521234567896',
    verified: true
  }, {
    slug: 'code-128',
    name: 'Code 128',
    dimension: '1D',
    data: 'All 128 ASCII characters',
    length: 'Variable',
    check: {
      label: 'Mod-103 check character',
      method: null
    },
    groups: ['linear'],
    standard: 'ISO/IEC 15417',
    status: 'active',
    example: null,
    verified: true
  }, {
    slug: 'gs1-128',
    name: 'GS1-128',
    dimension: '1D',
    data: 'GS1 Application Identifier element strings',
    length: 'Up to 48 characters',
    check: {
      label: 'Mod-103 check character, plus mod-10 inside GTINs and SSCCs',
      method: null
    },
    groups: ['logistics', 'gs1', 'linear'],
    standard: 'ISO/IEC 15417, GS1 General Specifications',
    status: 'active',
    example: null,
    verified: true
  }, {
    slug: 'code-39',
    name: 'Code 39',
    dimension: '1D',
    data: 'A–Z, 0–9 and - . space $ / + %',
    length: 'Variable',
    check: {
      label: 'Optional mod-43',
      method: null
    },
    groups: ['linear'],
    standard: 'ISO/IEC 16388',
    status: 'active',
    example: null,
    verified: true
  }, {
    slug: 'interleaved-2-of-5',
    name: 'Interleaved 2 of 5',
    dimension: '1D',
    data: 'Digits 0–9',
    length: 'Even number of digits',
    check: {
      label: 'Optional',
      method: null
    },
    groups: ['linear'],
    standard: 'ISO/IEC 16390',
    status: 'active',
    example: null,
    verified: true
  }, {
    slug: 'code-93',
    name: 'Code 93',
    dimension: '1D',
    data: 'A–Z, 0–9 and - . space $ / + %, plus shift pairs for the rest of ASCII',
    length: 'Variable',
    check: {
      label: 'Two check characters (C and K)',
      method: null
    },
    groups: ['linear'],
    standard: null,
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'codabar',
    name: 'Codabar',
    dimension: '1D',
    data: 'Digits 0–9 and - $ : / . +',
    length: 'Variable',
    check: {
      label: 'None required',
      method: null
    },
    groups: ['linear'],
    standard: null,
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'code-11',
    name: 'Code 11',
    dimension: '1D',
    data: 'Digits 0–9 and -',
    length: 'Variable',
    check: {
      label: 'One or two check characters',
      method: null
    },
    groups: ['linear'],
    standard: null,
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'msi-plessey',
    name: 'MSI Plessey',
    dimension: '1D',
    data: 'Digits 0–9',
    length: 'Variable',
    check: {
      label: 'Mod-10 or mod-11, sometimes two',
      method: null
    },
    groups: ['linear'],
    standard: null,
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'industrial-2-of-5',
    name: 'Industrial 2 of 5',
    dimension: '1D',
    data: 'Digits 0–9',
    length: 'Variable',
    check: {
      label: 'None required',
      method: null
    },
    groups: ['linear'],
    standard: null,
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'matrix-2-of-5',
    name: 'Matrix 2 of 5',
    dimension: '1D',
    data: 'Digits 0–9',
    length: 'Variable',
    check: {
      label: 'None required',
      method: null
    },
    groups: ['linear'],
    standard: null,
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'qr-code',
    name: 'QR Code',
    dimension: '2D',
    data: 'Any text or binary',
    length: 'Up to 7,089 digits or 2,953 bytes',
    check: {
      label: 'Reed–Solomon, levels L, M, Q and H',
      method: null
    },
    groups: ['retail', 'matrix', 'gs1'],
    groupWhen: {
      retail: 'with a GTIN'
    },
    standard: 'ISO/IEC 18004',
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'datamatrix',
    name: 'Data Matrix',
    dimension: '2D',
    data: 'Any text or binary',
    length: 'Up to 3,116 digits or 1,556 bytes',
    check: {
      label: 'Reed–Solomon, fixed by size',
      method: null
    },
    groups: ['retail', 'matrix', 'gs1'],
    groupWhen: {
      retail: 'with a GTIN'
    },
    standard: 'ISO/IEC 16022',
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'pdf417',
    name: 'PDF417',
    dimension: '2D (stacked)',
    data: 'Any text or binary',
    length: 'Up to about 2,710 digits or 1,108 bytes',
    check: {
      label: 'Reed–Solomon, levels 0 to 8',
      method: null
    },
    groups: ['matrix', 'id'],
    standard: 'ISO/IEC 15438',
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'aztec',
    name: 'Aztec',
    dimension: '2D',
    data: 'Any text or binary',
    length: 'Up to 3,832 digits or 1,914 bytes',
    check: {
      label: 'Reed–Solomon, 5% to 95%',
      method: null
    },
    groups: ['matrix'],
    standard: 'ISO/IEC 24778',
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'micro-qr',
    name: 'Micro QR Code',
    dimension: '2D',
    data: 'Any text or binary',
    length: 'Up to 35 digits or 15 bytes',
    check: {
      label: 'Reed–Solomon, levels L, M and Q',
      method: null
    },
    groups: ['matrix'],
    standard: 'ISO/IEC 18004',
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'micro-pdf417',
    name: 'MicroPDF417',
    dimension: '2D (stacked)',
    data: 'Any text or binary',
    length: null,
    check: {
      label: 'Reed–Solomon, fixed by size',
      method: null
    },
    groups: ['matrix'],
    standard: 'ISO/IEC 24728',
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'maxicode',
    name: 'MaxiCode',
    dimension: '2D',
    data: 'Any text; structured carrier messages',
    length: 'About 93 characters, in a fixed 1-inch square',
    check: {
      label: 'Reed–Solomon, set by the mode',
      method: null
    },
    groups: ['matrix', 'logistics'],
    standard: 'ISO/IEC 16023',
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'han-xin',
    name: 'Han Xin',
    dimension: '2D',
    data: 'Any text or binary, including Chinese characters',
    length: '23×23 to 189×189 modules',
    check: {
      label: 'Reed–Solomon, four levels',
      method: null
    },
    groups: ['matrix'],
    standard: 'ISO/IEC 20830',
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'grid-matrix',
    name: 'Grid Matrix',
    dimension: '2D',
    data: 'Any text or binary, including Chinese characters',
    length: '18×18 to 162×162 modules',
    check: {
      label: 'Five levels, 10% to 50%',
      method: null
    },
    groups: ['matrix'],
    standard: 'AIMD014',
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'usps-intelligent-mail',
    name: 'USPS Intelligent Mail',
    dimension: '4-state',
    data: 'Digits 0–9',
    length: 'A 20-digit tracking code and a routing code of 0, 5, 9 or 11 digits',
    check: {
      label: 'Built into the encoding',
      method: null
    },
    groups: ['postal'],
    standard: 'USPS-B-3200',
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'postnet',
    name: 'POSTNET',
    dimension: '2-state (bar height)',
    data: 'Digits 0–9',
    length: '5, 9 or 11 digits, plus a correction digit (32, 52 or 62 bars)',
    check: {
      label: 'Mod-10 correction digit',
      method: null
    },
    groups: ['postal'],
    standard: 'USPS Domestic Mail Manual (archived)',
    status: 'retired',
    example: null,
    verified: false
  }, {
    slug: 'planet',
    name: 'PLANET',
    dimension: '2-state (bar height)',
    data: 'Digits 0–9',
    length: '12 or 14 digits, including a check digit',
    check: {
      label: 'Check digit',
      method: null
    },
    groups: ['postal'],
    standard: 'USPS Domestic Mail Manual (archived)',
    status: 'retired',
    example: null,
    verified: false
  }, {
    slug: 'royal-mail-4-state',
    name: 'Royal Mail 4-State (RM4SCC)',
    dimension: '4-state',
    data: 'A–Z, 0–9',
    length: 'A postcode and delivery point suffix',
    check: {
      label: 'Checksum character',
      method: null
    },
    groups: ['postal'],
    standard: 'Royal Mail',
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'mailmark',
    name: 'Royal Mail Mailmark',
    dimension: '4-state and 2D',
    data: 'Uppercase letters, digits and space',
    length: '22 or 26 characters (4-state); 51, 70 or 90 (2D)',
    check: {
      label: 'At least 25% error correction',
      method: null
    },
    groups: ['postal'],
    standard: 'Royal Mail Mailmark barcode definition',
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'australia-post',
    name: 'Australia Post 4-State',
    dimension: '4-state',
    data: 'A delivery point identifier, plus optional customer information',
    length: '37, 52 or 67 bars',
    check: {
      label: 'Reed–Solomon',
      method: null
    },
    groups: ['postal'],
    standard: 'Australia Post Customer Barcoding Technical Specifications',
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'japan-post',
    name: 'Japan Post',
    dimension: '4-state',
    data: 'Digits, hyphens and letters',
    length: 'A 7-digit postal code and a 13-character address number',
    check: {
      label: 'Mod-19 check digit',
      method: null
    },
    groups: ['postal'],
    standard: 'Japan Post',
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'kix',
    name: 'KIX',
    dimension: '4-state',
    data: 'A–Z, 0–9',
    length: 'A postcode, house number and optional addition',
    check: {
      label: 'None',
      method: null
    },
    groups: ['postal'],
    standard: 'PostNL',
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'korea-post',
    name: 'Korea Post',
    dimension: '1D (bar position)',
    data: 'Digits 0–9',
    length: '6 digits plus a check digit',
    check: {
      label: 'Modified mod-10 check digit',
      method: null
    },
    groups: ['postal'],
    standard: null,
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'china-post-2-of-5',
    name: 'China Post 2 of 5',
    dimension: '1D',
    data: 'Digits 0–9',
    length: null,
    check: null,
    groups: ['postal'],
    standard: null,
    status: 'active',
    example: null,
    verified: false
  }, {
    slug: 'mrz',
    name: 'MRZ (machine-readable zone)',
    path: '/reference/documents/mrz',
    dimension: 'Printed text (OCR-B)',
    data: 'A–Z, 0–9 and <',
    length: '2 or 3 lines of 30, 36 or 44 characters',
    check: {
      label: 'Mod-10 with weights 7, 3, 1 on each field',
      method: 'mrz-td3'
    },
    groups: ['id'],
    standard: 'ICAO Doc 9303',
    status: 'active',
    example: 'L898902C36UTO7408122F1204159ZE184226B<<<<<10',
    verified: false
  }];
  const READERS = [{
    key: 'camera',
    name: 'Camera',
    detail: null,
    reads: ['upc-a', 'upc-e', 'ean-13', 'ean-8', 'isbn', 'issn', 'gs1-databar', 'itf-14', 'interleaved-2-of-5', 'code-128', 'code-39', 'code-93', 'codabar', 'msi-plessey', 'qr-code', 'micro-qr', 'datamatrix', 'pdf417', 'micro-pdf417', 'aztec', 'mailmark'],
    unknown: ['gs1-128', 'mrz'],
    notes: {
      'upc-a': 'as EAN-13 with a leading 0',
      isbn: 'as EAN-13',
      issn: 'as EAN-13, without add-ons',
      mailmark: 'the 2D form only, as Data Matrix'
    }
  }, {
    key: 'series-5',
    name: '5 series',
    detail: 'barcodes',
    reads: [],
    unknown: ['upc-a', 'upc-e', 'ean-13', 'ean-8', 'isbn', 'issn', 'gs1-databar', 'gs1-composite', 'itf-14', 'interleaved-2-of-5', 'code-128', 'gs1-128', 'code-39', 'code-93', 'codabar', 'code-11', 'msi-plessey', 'industrial-2-of-5', 'matrix-2-of-5', 'qr-code', 'micro-qr', 'datamatrix', 'pdf417', 'micro-pdf417', 'aztec', 'maxicode', 'han-xin', 'grid-matrix', 'usps-intelligent-mail', 'postnet', 'planet', 'royal-mail-4-state', 'mailmark', 'australia-post', 'japan-post', 'kix', 'korea-post', 'china-post-2-of-5'],
    notes: {}
  }, {
    key: 'series-7',
    name: '7 series',
    detail: 'barcodes + printed text',
    reads: ['upc-a', 'upc-e', 'ean-13', 'ean-8', 'isbn', 'issn', 'gs1-databar', 'gs1-composite', 'itf-14', 'interleaved-2-of-5', 'code-128', 'gs1-128', 'code-39', 'code-93', 'codabar', 'code-11', 'msi-plessey', 'industrial-2-of-5', 'matrix-2-of-5', 'qr-code', 'micro-qr', 'datamatrix', 'pdf417', 'micro-pdf417', 'aztec', 'maxicode', 'han-xin', 'grid-matrix', 'usps-intelligent-mail', 'postnet', 'planet', 'royal-mail-4-state', 'mailmark', 'australia-post', 'japan-post', 'kix', 'korea-post', 'china-post-2-of-5', 'mrz'],
    unknown: [],
    notes: {}
  }];
  const findType = slug => BARCODE_TYPES.find(t => t.slug === slug);
  const findGroup = key => BARCODE_GROUPS.find(g => g.key === key);
  const href = t => t.path || `/reference/barcode-types/${t.slug}`;
  const readerName = r => r.detail ? `${r.name} (${r.detail})` : r.name;
  const support = (r, slug) => {
    if (r.reads.includes(slug)) return {
      text: 'Yes',
      note: r.notes[slug]
    };
    if (r.unknown.includes(slug)) return {
      text: 'TBC'
    };
    return {
      text: 'No'
    };
  };
  const SupportCell = ({r, slug}) => {
    const {text, note} = support(r, slug);
    return <td style={text === 'TBC' ? muted : undefined}>
        {text}
        {note && <span style={muted}>, {note}</span>}
      </td>;
  };
  const muted = {
    opacity: 0.6
  };
  const nums = {
    fontVariantNumeric: 'tabular-nums'
  };
  const Facts = ({type}) => {
    const t = findType(type);
    if (!t) return null;
    const groups = t.groups.map((key, i) => {
      const g = findGroup(key);
      return <span key={key}>
          {i > 0 && ', '}
          <a href={`/reference/barcode-groups#${g.anchor}`}>{g.name}</a>
          {t.groupWhen && t.groupWhen[key] && ` (${t.groupWhen[key]})`}
        </span>;
    });
    const rows = [['Kind', t.dimension], ['Encodes', t.data], ['Length', t.length], ['Check', t.check && t.check.label], ['Groups', groups], ['Standard', t.standard], ['Status', t.status === 'retired' ? 'Retired' : 'In use']];
    return <table>
        <caption className="sr-only">{t.name} at a glance</caption>
        <tbody>
          {rows.map(([label, value]) => <tr key={label}>
              <th scope="row" className="font-medium">{label}</th>
              <td>{value == null ? <span style={muted}>TBC</span> : value}</td>
            </tr>)}
        </tbody>
      </table>;
  };
  const SupportMatrix = ({type, types}) => {
    if (type) {
      const t = findType(type);
      if (!t) return null;
      return <table>
            <thead>
              <tr>
                <th scope="col">Reader</th>
                <th scope="col">Reads {t.name}</th>
              </tr>
            </thead>
            <tbody>
              {READERS.map(r => <tr key={r.key}>
                  <th scope="row" className="font-medium">{readerName(r)}</th>
                  <SupportCell r={r} slug={t.slug} />
                </tr>)}
            </tbody>
          </table>;
    }
    const rows = types ? types.split(',').map(x => findType(x.trim())).filter(Boolean) : BARCODE_TYPES;
    return <div className="overflow-x-auto">
        <table>
          <thead>
            <tr>
              <th scope="col">Barcode type</th>
              {READERS.map(r => <th scope="col" key={r.key}>{readerName(r)}</th>)}
            </tr>
          </thead>
          <tbody>
            {rows.map(t => <tr key={t.slug}>
                <th scope="row" className="font-medium"><a href={href(t)}>{t.name}</a></th>
                {READERS.map(r => <SupportCell key={r.key} r={r} slug={t.slug} />)}
              </tr>)}
          </tbody>
        </table>
      </div>;
  };
  const GroupMatrix = () => <div className="overflow-x-auto">
      <table>
        <thead>
          <tr>
            <th scope="col">Barcode type</th>
            {BARCODE_GROUPS.map(g => <th scope="col" key={g.key}><a href={`#${g.anchor}`}>{g.name}</a></th>)}
          </tr>
        </thead>
        <tbody>
          {BARCODE_TYPES.map(t => <tr key={t.slug}>
              <th scope="row" className="font-medium">
                <a href={href(t)}>{t.name}</a>
              </th>
              {BARCODE_GROUPS.map(g => <td key={g.key} className="text-center">
                  {t.groups.includes(g.key) ? <>
                      <span aria-hidden="true">✓</span>
                      <span className="sr-only">Yes</span>
                      {t.groupWhen && t.groupWhen[g.key] && <span className="text-sm" style={muted}> {t.groupWhen[g.key]}</span>}
                    </> : <span className="sr-only">No</span>}
                </td>)}
            </tr>)}
        </tbody>
      </table>
    </div>;
  const CheckDigitSteps = ({value}) => {
    const digits = String(value).split('').map(Number);
    const data = digits.slice(0, -1);
    const given = digits[digits.length - 1];
    const weights = data.map((_, i) => (data.length - i) % 2 === 1 ? 3 : 1);
    const products = data.map((d, i) => d * weights[i]);
    const sum = products.reduce((a, b) => a + b, 0);
    const next = Math.ceil(sum / 10) * 10;
    const check = next - sum;
    const row = (label, cells, strong) => <tr>
        <th scope="row" className="font-medium whitespace-nowrap">{label}</th>
        {cells.map((c, i) => <td key={i} className={strong ? 'text-center font-semibold' : 'text-center'} style={nums}>{c}</td>)}
      </tr>;
    return <div>
        <div className="overflow-x-auto">
          <table>
            <caption className="sr-only">Check digit calculation for {value}</caption>
            <tbody>
              {row('Digit', data)}
              {row('× Weight', weights)}
              {row('= Product', products, true)}
            </tbody>
          </table>
        </div>
        <p>
          Sum of products: <strong style={nums}>{sum}</strong>. Round up to the next
          multiple of 10: <strong style={nums}>{next}</strong>. Check digit:{' '}
          <span style={nums}>{next} − {sum} = </span>
          <strong style={nums}>{check}</strong>
          {check === given ? `, which matches the last digit of ${value}.` : `, but ${value} ends in ${given}: this number is not valid.`}
        </p>
      </div>;
  };
  const EAN_L = ['0001101', '0011001', '0010011', '0111101', '0100011', '0110001', '0101111', '0111011', '0110111', '0001011'];
  const EAN_R = EAN_L.map(p => p.replace(/./g, c => c === '0' ? '1' : '0'));
  const EAN_G = EAN_R.map(p => p.split('').reverse().join(''));
  const EAN_PARITY = ['LLLLLL', 'LLGLGG', 'LLGGLG', 'LLGGGL', 'LGLLGG', 'LGGLLG', 'LGGGLL', 'LGLGLG', 'LGLGGL', 'LGGLGL'];
  const leftChar = k => 3 + 7 * k + 3.5;
  const rightChar = (k, halfWidth) => 3 + halfWidth + 5 + 7 * k + 3.5;
  const ANATOMY = {
    'ean-13': {
      length: 13,
      quiet: [11, 7],
      encode: d => {
        const parity = EAN_PARITY[d[0]];
        const left = d.slice(1, 7).map((n, i) => (parity[i] === 'L' ? EAN_L : EAN_G)[n]).join('');
        const right = d.slice(7).map(n => EAN_R[n]).join('');
        return '101' + left + '01010' + right + '101';
      },
      hri: i => i === 0 ? -6 : i <= 6 ? leftChar(i - 1) : rightChar(i - 7, 42),
      number: [[0, 3, 'GS1 prefix', 'Which national GS1 office issued the company prefix. Not the country the product was made in.'], [3, 12, 'Company prefix and item reference', 'The brand owner’s GS1 company prefix followed by its number for this item. Where one ends and the other begins depends on the prefix length, which you can’t tell from the digits alone.'], [12, 13, 'Check digit', 'Calculated from the other 12 digits with GS1 mod-10.']],
      leftNote: 'Six digits, each seven modules wide. Each digit is drawn in one of two patterns, and the combination of patterns encodes the first digit, which has no bars of its own.'
    },
    'upc-a': {
      length: 12,
      quiet: [9, 9],
      encode: d => '101' + d.slice(0, 6).map(n => EAN_L[n]).join('') + '01010' + d.slice(6).map(n => EAN_R[n]).join('') + '101',
      hri: i => i === 0 ? -5 : i <= 5 ? leftChar(i) : i <= 10 ? rightChar(i - 6, 42) : 95 + 5,
      extended: [[3, 10], [85, 92]],
      number: [[0, 11, 'Company prefix and item reference', 'The brand owner’s GS1 company prefix followed by its number for this item.'], [11, 12, 'Check digit', 'Calculated from the other 11 digits with GS1 mod-10.']],
      leftNote: 'Six digits, each seven modules wide.'
    },
    'ean-8': {
      length: 8,
      quiet: [7, 7],
      encode: d => '101' + d.slice(0, 4).map(n => EAN_L[n]).join('') + '01010' + d.slice(4).map(n => EAN_R[n]).join('') + '101',
      hri: i => i <= 3 ? leftChar(i) : rightChar(i - 4, 28),
      number: [[0, 3, 'GS1-8 prefix', 'Which national GS1 office issued the number.'], [3, 7, 'Item reference', 'Assigned by that GS1 office for this item.'], [7, 8, 'Check digit', 'Calculated from the other 7 digits with GS1 mod-10.']],
      leftNote: 'Four digits, each seven modules wide.'
    },
    'upc-e': {
      length: 8,
      quiet: [9, 7],
      encode: d => {
        const forZero = ['GGGLLL', 'GGLGLL', 'GGLLGL', 'GGLLLG', 'GLGGLL', 'GLLGGL', 'GLLLGG', 'GLGLGL', 'GLGLLG', 'GLLGLG'][d[7]];
        const parity = d[0] === 0 ? forZero : forZero.replace(/./g, c => c === 'G' ? 'L' : 'G');
        return '101' + d.slice(1, 7).map((n, i) => (parity[i] === 'L' ? EAN_L : EAN_G)[n]).join('') + '010101';
      },
      hri: i => i === 0 ? -5 : i <= 6 ? leftChar(i - 1) : 51 + 4,
      guards: () => [[0, 3], [45, 51]],
      structure: () => [['Start guard', 0, 3, 'Three modules (bar, space, bar) that tell the scanner where the symbol starts and how wide one module is.'], ['Six digits', 3, 45, 'Each digit is seven modules wide and drawn in one of two patterns. The combination of patterns encodes the number system and the check digit, which have no bars of their own.'], ['End guard', 45, 51, 'Six modules that mark the end of the symbol.']],
      number: [[0, 1, 'Number system', 'Always 0 or 1.'], [1, 7, 'Zero-suppressed digits', 'The company prefix and item reference with their zeros removed. The last of the six says where the zeros go back.'], [7, 8, 'Check digit', 'The check digit of the full 12-digit UPC-A.']]
    }
  };
  ANATOMY.isbn = {
    ...ANATOMY['ean-13'],
    number: [[0, 3, 'Prefix', '978 or 979, which mark the number as a book.'], [3, 12, 'Registration group, registrant and publication', 'The language area or country, the publisher, and the edition. Each part varies in length, which is what the hyphens in a printed ISBN show.'], [12, 13, 'Check digit', 'Calculated from the other 12 digits with GS1 mod-10.']]
  };
  ANATOMY.issn = {
    ...ANATOMY['ean-13'],
    number: [[0, 3, 'Prefix', '977, which marks the number as a serial publication.'], [3, 10, 'ISSN', 'The first seven digits of the ISSN. Its own check digit is left out.'], [10, 12, 'Variant', 'Usually 00. Publishers change it for a special issue or a price change.'], [12, 13, 'Check digit', 'Calculated from the other 12 digits with GS1 mod-10.']]
  };
  const Anatomy = ({type, value, plain}) => {
    const spec = ANATOMY[type];
    const digits = String(value).split('').map(Number);
    if (!spec || digits.length !== spec.length || digits.some(isNaN)) {
      return <p style={muted}>Can’t draw {type} for “{value}”.</p>;
    }
    const modules = spec.encode(digits);
    const n = modules.length;
    const half = (n - 11) / 2;
    const guards = spec.guards ? spec.guards() : [[0, 3], [3 + half, 8 + half], [n - 3, n]];
    const tall = [...guards, ...spec.extended || []];
    const isTall = m => tall.some(([a, b]) => m >= a && m < b);
    const M = 4;
    const [qL, qR] = spec.quiet;
    const x = m => (qL + m) * M;
    const width = (qL + n + qR) * M;
    const labelTop = plain ? 0 : 30, barTop = labelTop + 6, barH = 70, guardH = 76, hriY = barTop + guardH + 4, labelBottom = hriY + 6;
    const bottomBracket = labelBottom + 8, height = plain ? labelBottom : bottomBracket + 22;
    const bars = [];
    for (let m = 0; m < n; m++) {
      if (modules[m] !== '1') continue;
      let end = m;
      while (modules[end + 1] === '1') end++;
      bars.push(<rect key={m} x={x(m)} y={barTop} width={(end - m + 1) * M} height={isTall(m) ? guardH : barH} />);
      m = end;
    }
    const structure = spec.structure ? spec.structure() : [['Start guard', 0, 3, 'Three modules (bar, space, bar) that tell the scanner where the symbol starts and how wide one module is.'], ['Left half', 3, 3 + half, spec.leftNote], ['Center guard', 3 + half, 8 + half, 'Five modules that separate the two halves. Scanners decode each half on its own, then join them.'], ['Right half', 8 + half, n - 3, 'The remaining digits, each seven modules wide, always drawn from the same set of patterns.'], ['End guard', n - 3, n, 'Three modules that mark the end of the symbol.']];
    const numberSegs = spec.number.map(([a, b, name, meaning]) => [name, spec.hri(a) - 3.5, spec.hri(b - 1) + 3.5, meaning]);
    const legend = [...structure, ...numberSegs];
    const bracket = (key, x1, x2, y, up) => {
      const t = up ? y - 4 : y + 4;
      return <path key={key} d={`M${x1 + 1} ${t} V${y} H${x2 - 1} V${t}`} fill="none" stroke="currentColor" strokeWidth="1" />;
    };
    const marker = (key, label, cx, y) => <text key={key} x={cx} y={y} textAnchor="middle" fontSize="10" fontWeight="600" fill="currentColor">{label}</text>;
    const label = <>
        {}
        <rect x="0" y={labelTop} width={width} height={labelBottom - labelTop} fill="#ffffff" rx={plain ? 0 : 4} />
        <g fill="#111214">
          {bars}
          {digits.map((d, i) => <text key={i} x={x(spec.hri(i))} y={hriY} textAnchor="middle" fontSize="13" fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace">{d}</text>)}
        </g>
      </>;
    if (plain) {
      return <figure className="not-prose my-6">
          <svg viewBox={`0 0 ${width} ${height}`} role="img" aria-label={`${type.toUpperCase()} barcode for ${value}`} style={{
        width: '100%',
        maxWidth: 360,
        height: 'auto'
      }}>
            {label}
          </svg>
        </figure>;
    }
    return <figure className="not-prose my-6">
        <svg viewBox={`0 0 ${width} ${height}`} role="img" aria-labelledby={`anatomy-${type}-title`} style={{
      width: '100%',
      height: 'auto'
    }}>
          <title id={`anatomy-${type}-title`}>{`Annotated ${type.toUpperCase()} barcode for ${value}`}</title>
          {structure.map(([, a, b], i) => <g key={`s${i}`}>
              {bracket(`sb${i}`, x(a), x(b), 22, false)}
              {marker(`sm${i}`, i + 1, (x(a) + x(b)) / 2, 16)}
            </g>)}
          {label}
          {numberSegs.map(([, a, b], i) => <g key={`n${i}`}>
              {bracket(`nb${i}`, x(a), x(b), bottomBracket, true)}
              {marker(`nm${i}`, structure.length + i + 1, (x(a) + x(b)) / 2, bottomBracket + 14)}
            </g>)}
        </svg>
        <figcaption className="mt-4">
          <ol className="list-decimal pl-5 text-sm">
            {legend.map(([name, , , meaning]) => <li key={name}><strong>{name}.</strong> {meaning}</li>)}
          </ol>
        </figcaption>
      </figure>;
  };
  const C128 = [[2, 1, 2, 2, 2, 2], [2, 2, 2, 1, 2, 2], [2, 2, 2, 2, 2, 1], [1, 2, 1, 2, 2, 3], [1, 2, 1, 3, 2, 2], [1, 3, 1, 2, 2, 2], [1, 2, 2, 2, 1, 3], [1, 2, 2, 3, 1, 2], [1, 3, 2, 2, 1, 2], [2, 2, 1, 2, 1, 3], [2, 2, 1, 3, 1, 2], [2, 3, 1, 2, 1, 2], [1, 1, 2, 2, 3, 2], [1, 2, 2, 1, 3, 2], [1, 2, 2, 2, 3, 1], [1, 1, 3, 2, 2, 2], [1, 2, 3, 1, 2, 2], [1, 2, 3, 2, 2, 1], [2, 2, 3, 2, 1, 1], [2, 2, 1, 1, 3, 2], [2, 2, 1, 2, 3, 1], [2, 1, 3, 2, 1, 2], [2, 2, 3, 1, 1, 2], [3, 1, 2, 1, 3, 1], [3, 1, 1, 2, 2, 2], [3, 2, 1, 1, 2, 2], [3, 2, 1, 2, 2, 1], [3, 1, 2, 2, 1, 2], [3, 2, 2, 1, 1, 2], [3, 2, 2, 2, 1, 1], [2, 1, 2, 1, 2, 3], [2, 1, 2, 3, 2, 1], [2, 3, 2, 1, 2, 1], [1, 1, 1, 3, 2, 3], [1, 3, 1, 1, 2, 3], [1, 3, 1, 3, 2, 1], [1, 1, 2, 3, 1, 3], [1, 3, 2, 1, 1, 3], [1, 3, 2, 3, 1, 1], [2, 1, 1, 3, 1, 3], [2, 3, 1, 1, 1, 3], [2, 3, 1, 3, 1, 1], [1, 1, 2, 1, 3, 3], [1, 1, 2, 3, 3, 1], [1, 3, 2, 1, 3, 1], [1, 1, 3, 1, 2, 3], [1, 1, 3, 3, 2, 1], [1, 3, 3, 1, 2, 1], [3, 1, 3, 1, 2, 1], [2, 1, 1, 3, 3, 1], [2, 3, 1, 1, 3, 1], [2, 1, 3, 1, 1, 3], [2, 1, 3, 3, 1, 1], [2, 1, 3, 1, 3, 1], [3, 1, 1, 1, 2, 3], [3, 1, 1, 3, 2, 1], [3, 3, 1, 1, 2, 1], [3, 1, 2, 1, 1, 3], [3, 1, 2, 3, 1, 1], [3, 3, 2, 1, 1, 1], [3, 1, 4, 1, 1, 1], [2, 2, 1, 4, 1, 1], [4, 3, 1, 1, 1, 1], [1, 1, 1, 2, 2, 4], [1, 1, 1, 4, 2, 2], [1, 2, 1, 1, 2, 4], [1, 2, 1, 4, 2, 1], [1, 4, 1, 1, 2, 2], [1, 4, 1, 2, 2, 1], [1, 1, 2, 2, 1, 4], [1, 1, 2, 4, 1, 2], [1, 2, 2, 1, 1, 4], [1, 2, 2, 4, 1, 1], [1, 4, 2, 1, 1, 2], [1, 4, 2, 2, 1, 1], [2, 4, 1, 2, 1, 1], [2, 2, 1, 1, 1, 4], [4, 1, 3, 1, 1, 1], [2, 4, 1, 1, 1, 2], [1, 3, 4, 1, 1, 1], [1, 1, 1, 2, 4, 2], [1, 2, 1, 1, 4, 2], [1, 2, 1, 2, 4, 1], [1, 1, 4, 2, 1, 2], [1, 2, 4, 1, 1, 2], [1, 2, 4, 2, 1, 1], [4, 1, 1, 2, 1, 2], [4, 2, 1, 1, 1, 2], [4, 2, 1, 2, 1, 1], [2, 1, 2, 1, 4, 1], [2, 1, 4, 1, 2, 1], [4, 1, 2, 1, 2, 1], [1, 1, 1, 1, 4, 3], [1, 1, 1, 3, 4, 1], [1, 3, 1, 1, 4, 1], [1, 1, 4, 1, 1, 3], [1, 1, 4, 3, 1, 1], [4, 1, 1, 1, 1, 3], [4, 1, 1, 3, 1, 1], [1, 1, 3, 1, 4, 1], [1, 1, 4, 1, 3, 1], [3, 1, 1, 1, 4, 1], [4, 1, 1, 1, 3, 1], [2, 1, 1, 4, 1, 2], [2, 1, 1, 2, 1, 4], [2, 1, 1, 2, 3, 2], [2, 3, 3, 1, 1, 1, 2]];
  const C39_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%';
  const C39 = [0x034, 0x121, 0x061, 0x160, 0x031, 0x130, 0x070, 0x025, 0x124, 0x064, 0x109, 0x049, 0x148, 0x019, 0x118, 0x058, 0x00d, 0x10c, 0x04c, 0x01c, 0x103, 0x043, 0x142, 0x013, 0x112, 0x052, 0x007, 0x106, 0x046, 0x016, 0x181, 0x0c1, 0x1c0, 0x091, 0x190, 0x0d0, 0x085, 0x184, 0x0c4, 0x0a8, 0x0a2, 0x08a, 0x02a];
  const C39_STAR = 0x094;
  const I25 = ['NNWWN', 'WNNNW', 'NWNNW', 'WWNNN', 'NNWNW', 'WNWNN', 'NWWNN', 'NNNWW', 'WNNWN', 'NWNWN'];
  const C93 = [0x114, 0x148, 0x144, 0x142, 0x128, 0x124, 0x122, 0x150, 0x112, 0x10a, 0x1a8, 0x1a4, 0x1a2, 0x194, 0x192, 0x18a, 0x168, 0x164, 0x162, 0x134, 0x11a, 0x158, 0x14c, 0x146, 0x12c, 0x116, 0x1b4, 0x1b2, 0x1ac, 0x1a6, 0x196, 0x19a, 0x16c, 0x166, 0x136, 0x13a, 0x12e, 0x1d4, 0x1d2, 0x1ca, 0x16e, 0x176, 0x1ae, 0x126, 0x1da, 0x1d6, 0x132, 0x15e];
  const code93 = text => {
    const values = [...text].map(c => C39_CHARS.indexOf(c));
    const weighted = (vs, max) => vs.reduceRight((acc, v, i) => acc + v * ((vs.length - 1 - i) % max + 1), 0) % 47;
    const c = weighted(values, 20);
    const k = weighted([...values, c], 15);
    const modules = [47, ...values, c, k, 47].map(v => C93[v].toString(2).padStart(9, '0')).join('') + '1';
    const widths = [];
    for (let i = 0; i < modules.length; ) {
      let j = i;
      while (modules[j] === modules[i]) j++;
      widths.push(j - i);
      i = j;
    }
    const n = values.length;
    const name = v => C39_CHARS[v] || `value ${v}`;
    return {
      widths,
      hri: text,
      segments: [['Start character', 0, 9, 'Marks the start. Scanners don’t send it.'], ['Data', 9, 9 * (n + 1), 'Each character is 9 modules: three bars and three spaces, each 1 to 4 modules wide.'], ['Check character C', 9 * (n + 1), 9 * (n + 2), `Mod-47 over the data, with weights 1 to 20. Here, ${name(c)}.`], ['Check character K', 9 * (n + 2), 9 * (n + 3), `Mod-47 over the data and C, with weights 1 to 15. Here, ${name(k)}.`], ['Stop', 9 * (n + 3), 9 * (n + 4) + 1, 'The stop character, then a single-module bar that ends the symbol.']]
    };
  };
  const code39 = text => {
    const chars = [...text];
    const check = C39_CHARS[chars.reduce((sum, c) => sum + C39_CHARS.indexOf(c), 0) % 43];
    const element = bits => Array.from({
      length: 9
    }, (_, i) => bits >> 8 - i & 1 ? 3 : 1);
    const parts = [C39_STAR, ...chars.map(c => C39[C39_CHARS.indexOf(c)]), C39[C39_CHARS.indexOf(check)], C39_STAR];
    const widths = [];
    parts.forEach((bits, i) => {
      widths.push(...element(bits));
      if (i < parts.length - 1) widths.push(1);
    });
    const char = 16;
    const n = parts.length;
    return {
      widths,
      hri: text + check,
      segments: [['Start character', 0, 15, 'An asterisk. It marks the start, and scanners don’t send it.'], ['Data', char, char * (n - 2) - 1, 'Each character is nine elements, five bars and four spaces, three of them wide. A narrow gap separates characters.'], ['Check character', char * (n - 2), char * (n - 1) - 1, `Optional. The mod-43 check for this data is ${check}.`], ['Stop character', char * (n - 1), char * n - 1, 'Another asterisk, which marks the end.']]
    };
  };
  const code128Values = (text, gs1) => {
    const digitsAt = i => {
      let n = 0;
      while ((/\d/).test(text[i + n] || '')) n++;
      return n;
    };
    let set = digitsAt(0) >= 4 ? 'C' : 'B';
    const values = [set === 'C' ? 105 : 104];
    if (gs1) values.push(102);
    for (let i = 0; i < text.length; ) {
      if (text[i] === '\x1d') {
        values.push(102);
        i += 1;
        continue;
      }
      if (set === 'C') {
        if (digitsAt(i) >= 2) {
          values.push(Number(text.slice(i, i + 2)));
          i += 2;
        } else {
          values.push(100);
          set = 'B';
        }
      } else if (digitsAt(i) >= 4 && digitsAt(i) % 2 === 0) {
        values.push(99);
        set = 'C';
      } else {
        values.push(text.charCodeAt(i) - 32);
        i += 1;
      }
    }
    return values;
  };
  const code128 = (text, gs1) => {
    const data = gs1 ? gs1Text(text) : text;
    const values = code128Values(data, gs1);
    const check = values.reduce((sum, v, i) => sum + v * (i || 1), 0) % 103;
    const widths = [...values, check, 106].flatMap(v => C128[v]);
    const head = gs1 ? 22 : 11;
    const end = values.length * 11;
    const startSet = values[0] === 105 ? 'C' : 'B';
    const startNote = startSet === 'C' ? 'The first characters are digits, packed two to a character.' : 'The first characters are text. The start code also counts toward the check.';
    return {
      widths,
      hri: text,
      segments: [[`Start code ${startSet}`, 0, 11, startNote], ...gs1 ? [['FNC1', 11, 22, 'A special character that marks the barcode as GS1-128. Scanners don’t send it as data.']] : [], ['Data', head, end, gs1 ? 'Each character is 11 modules: three bars and three spaces. Digits are packed two to a character in code set C, then a switch to code set B handles the letters.' : 'Each character is 11 modules: three bars and three spaces, each 1 to 4 modules wide.'], ['Check character', end, end + 11, `Mod-103 check, value ${check}. Scanners verify it, then drop it.`], ['Stop', end + 11, end + 24, 'Thirteen modules, ending in a final bar.']]
    };
  };
  const itf = (digits, wide = 2.5) => {
    const widths = [1, 1, 1, 1];
    for (let i = 0; i < digits.length; i += 2) {
      const bars = I25[digits[i]], spaces = I25[digits[i + 1]];
      for (let k = 0; k < 5; k++) widths.push(bars[k] === 'W' ? wide : 1, spaces[k] === 'W' ? wide : 1);
    }
    widths.push(wide, 1, 1);
    const pair = 2 * (3 + 2 * wide);
    const end = 4 + pair * (digits.length / 2);
    return {
      widths,
      hri: digits,
      segments: [['Start', 0, 4, 'Four narrow elements: bar, space, bar, space.'], ['Digit pairs', 4, end, 'Each pair of digits shares five bars and five spaces. The first digit sets which bars are wide, the second which spaces are wide.'], ['Stop', end, end + wide + 2, 'A wide bar, a narrow space and a narrow bar.']]
    };
  };
  const GS1_AI_NAMES = {
    '00': ['SSCC', 'The serial shipping container code: an 18-digit number for one pallet or parcel.'],
    '01': ['GTIN', 'The product’s 14-digit GTIN.'],
    10: ['Batch or lot', 'Up to 20 characters. Variable length, so it comes last or is followed by a separator.'],
    11: ['Production date', 'YYMMDD.'],
    15: ['Best before date', 'YYMMDD.'],
    17: ['Expiry date', 'YYMMDD.'],
    21: ['Serial number', 'Up to 20 characters.'],
    37: ['Count', 'How many trade items the unit contains.']
  };
  const LINEAR = {
    'code-39': {
      quiet: 10,
      encode: v => code39(v)
    },
    'code-93': {
      quiet: 10,
      encode: v => code93(v)
    },
    'code-128': {
      quiet: 10,
      encode: v => code128(v, false)
    },
    'gs1-128': {
      quiet: 10,
      encode: v => code128(v, true),
      numbers: hri => [...hri.matchAll(/\((\d{2,4})\)[^(]*/g)].map(m => {
        const [name, meaning] = GS1_AI_NAMES[m[1]] || [`AI ${m[1]}`, ''];
        return [m.index, m.index + m[0].length, `(${m[1]}) ${name}`, meaning];
      })
    },
    'interleaved-2-of-5': {
      quiet: 10,
      encode: v => itf(v)
    },
    'itf-14': {
      quiet: 10,
      bearer: true,
      encode: v => itf(v),
      numbers: () => [[0, 1, 'Indicator digit', 'The packaging level. 1 to 8 mean a case or other grouping of the same item.'], [1, 13, 'Item number', 'Often the first 12 digits of the item’s own GTIN-13, but not always.'], [13, 14, 'Check digit', 'GS1 mod-10, calculated over all 14 digits.']]
    }
  };
  const Linear = ({type, value, plain}) => {
    const spec = LINEAR[type];
    if (!spec || !value) return <p style={muted}>Can’t draw {type} for “{value}”.</p>;
    const {widths, hri, segments} = spec.encode(String(value));
    const numbers = spec.numbers ? spec.numbers(hri) : [];
    const total = widths.reduce((a, b) => a + b, 0);
    const q = spec.quiet;
    const M = Math.max(2, Math.min(4, 520 / (total + 2 * q)));
    const x = m => (q + m) * M;
    const width = (total + 2 * q) * M;
    const bearer = spec.bearer ? 6 : 0;
    const labelTop = plain ? 0 : 30, barTop = labelTop + 6 + bearer, barH = 70;
    const hriY = barTop + barH + bearer + 16, labelBottom = hriY + 6;
    const bottomBracket = labelBottom + 8, height = plain ? labelBottom : bottomBracket + 22;
    const slot = total / hri.length;
    const charX = i => x((i + 0.5) * slot);
    const bars = [];
    let pos = 0;
    widths.forEach((w, i) => {
      if (i % 2 === 0) bars.push(<rect key={i} x={x(pos)} y={barTop} width={w * M} height={barH} />);
      pos += w;
    });
    const label = <>
        <rect x="0" y={labelTop} width={width} height={labelBottom - labelTop} fill="#ffffff" rx={plain ? 0 : 4} />
        <g fill="#111214">
          {spec.bearer && <rect x={x(-q / 2)} y={barTop - bearer} width={(total + q) * M} height={bearer - 1} />}
          {spec.bearer && <rect x={x(-q / 2)} y={barTop + barH + 1} width={(total + q) * M} height={bearer - 1} />}
          {bars}
          {[...hri].map((c, i) => <text key={i} x={charX(i)} y={hriY} textAnchor="middle" fontSize="12" fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace">{c}</text>)}
        </g>
      </>;
    const name = (findType(type) || ({
      name: type
    })).name;
    if (plain) {
      return <figure className="not-prose my-6">
          <svg viewBox={`0 0 ${width} ${height}`} role="img" aria-label={`${name} barcode for ${hri}`} style={{
        width: '100%',
        maxWidth: 480,
        height: 'auto'
      }}>
            {label}
          </svg>
        </figure>;
    }
    const bracket = (key, x1, x2, y, up) => {
      const t = up ? y - 4 : y + 4;
      return <path key={key} d={`M${x1 + 1} ${t} V${y} H${x2 - 1} V${t}`} fill="none" stroke="currentColor" strokeWidth="1" />;
    };
    const marker = (key, n, cx, y) => <text key={key} x={cx} y={y} textAnchor="middle" fontSize="10" fontWeight="600" fill="currentColor">{n}</text>;
    const numberSegs = numbers.map(([a, b, title, meaning]) => [title, charX(a) - slot * M / 2, charX(b - 1) + slot * M / 2, meaning]);
    return <figure className="not-prose my-6">
        <svg viewBox={`0 0 ${width} ${height}`} role="img" aria-labelledby={`anatomy-${type}-title`} style={{
      width: '100%',
      height: 'auto'
    }}>
          <title id={`anatomy-${type}-title`}>{`Annotated ${name} barcode for ${hri}`}</title>
          {segments.map(([, a, b], i) => <g key={`s${i}`}>
              {bracket(`sb${i}`, x(a), x(b), 22, false)}
              {marker(`sm${i}`, i + 1, (x(a) + x(b)) / 2, 16)}
            </g>)}
          {label}
          {numberSegs.map(([, a, b], i) => <g key={`n${i}`}>
              {bracket(`nb${i}`, a, b, bottomBracket, true)}
              {marker(`nm${i}`, segments.length + i + 1, (a + b) / 2, bottomBracket + 14)}
            </g>)}
        </svg>
        <figcaption className="mt-4">
          <ol className="list-decimal pl-5 text-sm">
            {[...segments, ...numberSegs].map(([title, , , meaning]) => <li key={title}><strong>{title}.</strong> {meaning}</li>)}
          </ol>
        </figcaption>
      </figure>;
  };
  const gf = poly => {
    const exp = new Array(512), log = new Array(256);
    let x = 1;
    for (let i = 0; i < 255; i++) {
      exp[i] = x;
      log[x] = i;
      x <<= 1;
      if (x & 0x100) x ^= poly;
    }
    for (let i = 255; i < 512; i++) exp[i] = exp[i - 255];
    const mul = (a, b) => a && b ? exp[log[a] + log[b]] : 0;
    const generator = (n, first) => {
      let g = [1];
      for (let i = 0; i < n; i++) {
        const next = new Array(g.length + 1).fill(0);
        g.forEach((c, j) => {
          next[j] ^= c;
          next[j + 1] ^= mul(c, exp[first + i]);
        });
        g = next;
      }
      return g.slice(1);
    };
    const remainder = (data, n, first) => {
      const g = generator(n, first);
      const rem = new Array(n).fill(0);
      for (const d of data) {
        const factor = d ^ rem.shift();
        rem.push(0);
        g.forEach((c, j) => {
          rem[j] ^= mul(c, factor);
        });
      }
      return rem;
    };
    return {
      remainder
    };
  };
  const QR_EC_PER_BLOCK = {
    L: [-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30],
    M: [-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28],
    Q: [-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30],
    H: [-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30]
  };
  const QR_BLOCKS = {
    L: [-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25],
    M: [-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49],
    Q: [-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68],
    H: [-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81]
  };
  const QR_FORMAT_EC = {
    L: 1,
    M: 0,
    Q: 3,
    H: 2
  };
  const qrRawModules = v => {
    let r = (16 * v + 128) * v + 64;
    if (v >= 2) {
      const n = Math.floor(v / 7) + 2;
      r -= (25 * n - 10) * n - 55;
      if (v >= 7) r -= 36;
    }
    return r;
  };
  const qrDataCodewords = (v, ec) => Math.floor(qrRawModules(v) / 8) - QR_EC_PER_BLOCK[ec][v] * QR_BLOCKS[ec][v];
  const qrAlignment = v => {
    if (v === 1) return [];
    const n = Math.floor(v / 7) + 2, size = v * 4 + 17;
    const step = v === 32 ? 26 : Math.ceil((v * 4 + 4) / (n * 2 - 2)) * 2;
    const out = [6];
    for (let pos = size - 7; out.length < n; pos -= step) out.splice(1, 0, pos);
    return out;
  };
  const qrMask = [(x, y) => (x + y) % 2 === 0, (x, y) => y % 2 === 0, x => x % 3 === 0, (x, y) => (x + y) % 3 === 0, (x, y) => (Math.floor(x / 3) + Math.floor(y / 2)) % 2 === 0, (x, y) => x * y % 2 + x * y % 3 === 0, (x, y) => (x * y % 2 + x * y % 3) % 2 === 0, (x, y) => ((x + y) % 2 + x * y % 3) % 2 === 0];
  const qrPenalty = m => {
    const n = m.length;
    let p = 0;
    const lines = [];
    for (let i = 0; i < n; i++) {
      lines.push(m[i]);
      lines.push(m.map(row => row[i]));
    }
    for (const line of lines) {
      for (let i = 0; i < n; ) {
        let j = i;
        while (j < n && line[j] === line[i]) j++;
        if (j - i >= 5) p += 3 + (j - i - 5);
        i = j;
      }
      const s = line.map(b => b ? 1 : 0).join('');
      for (const pat of ['10111010000', '00001011101']) for (let i = s.indexOf(pat); i !== -1; i = s.indexOf(pat, i + 1)) p += 40;
    }
    for (let y = 0; y < n - 1; y++) for (let x = 0; x < n - 1; x++) {
      const c = m[y][x];
      if (c === m[y][x + 1] && c === m[y + 1][x] && c === m[y + 1][x + 1]) p += 3;
    }
    const dark = m.flat().filter(Boolean).length;
    p += Math.floor(Math.abs(dark * 20 - n * n * 10) / (n * n)) * 10;
    return p;
  };
  const encodeQR = (text, ec = 'M') => {
    const bytes = [...new TextEncoder().encode(text)];
    let v = 1;
    const bitsNeeded = ver => 4 + (ver < 10 ? 8 : 16) + bytes.length * 8;
    while (v <= 40 && bitsNeeded(v) > qrDataCodewords(v, ec) * 8) v++;
    if (v > 40) throw new Error('Too much data for a QR Code');
    const capacity = qrDataCodewords(v, ec);
    const bits = [];
    const put = (val, len) => {
      for (let i = len - 1; i >= 0; i--) bits.push(val >> i & 1);
    };
    put(0b0100, 4);
    put(bytes.length, v < 10 ? 8 : 16);
    bytes.forEach(b => put(b, 8));
    put(0, Math.min(4, capacity * 8 - bits.length));
    while (bits.length % 8) bits.push(0);
    const data = [];
    for (let i = 0; i < bits.length; i += 8) data.push(parseInt(bits.slice(i, i + 8).join(''), 2));
    for (let pad = 0xec; data.length < capacity; pad ^= 0xec ^ 0x11) data.push(pad);
    const rs = gf(0x11d);
    const numBlocks = QR_BLOCKS[ec][v], ecLen = QR_EC_PER_BLOCK[ec][v];
    const raw = Math.floor(qrRawModules(v) / 8);
    const shortBlocks = numBlocks - raw % numBlocks, shortLen = Math.floor(raw / numBlocks);
    const blocks = [];
    for (let i = 0, k = 0; i < numBlocks; i++) {
      const len = shortLen - ecLen + (i < shortBlocks ? 0 : 1);
      const d = data.slice(k, k + len);
      k += len;
      blocks.push({
        d,
        e: rs.remainder(d, ecLen, 0)
      });
    }
    const codewords = [];
    for (let i = 0; i <= shortLen - ecLen; i++) blocks.forEach(b => {
      if (i < b.d.length) codewords.push(b.d[i]);
    });
    for (let i = 0; i < ecLen; i++) blocks.forEach(b => codewords.push(b.e[i]));
    const size = v * 4 + 17;
    const m = Array.from({
      length: size
    }, () => new Array(size).fill(false));
    const role = Array.from({
      length: size
    }, () => new Array(size).fill(null));
    const set = (x, y, dark, r) => {
      m[y][x] = dark;
      role[y][x] = r;
    };
    for (let i = 0; i < size; i++) {
      set(6, i, i % 2 === 0, 'timing');
      set(i, 6, i % 2 === 0, 'timing');
    }
    for (const [cx, cy] of [[3, 3], [size - 4, 3], [3, size - 4]]) {
      for (let dy = -4; dy <= 4; dy++) for (let dx = -4; dx <= 4; dx++) {
        const x = cx + dx, y = cy + dy;
        if (x < 0 || y < 0 || x >= size || y >= size) continue;
        const d = Math.max(Math.abs(dx), Math.abs(dy));
        set(x, y, d !== 2 && d !== 4, d === 4 ? 'separator' : 'finder');
      }
    }
    const align = qrAlignment(v);
    align.forEach((ax, i) => align.forEach((ay, j) => {
      if (i === 0 && j === 0 || i === 0 && j === align.length - 1 || i === align.length - 1 && j === 0) return;
      for (let dy = -2; dy <= 2; dy++) for (let dx = -2; dx <= 2; dx++) set(ax + dx, ay + dy, Math.max(Math.abs(dx), Math.abs(dy)) !== 1, 'alignment');
    }));
    const formatCells = [];
    for (let i = 0; i < 9; i++) {
      if (i !== 6) {
        formatCells.push([8, i], [i, 8]);
      }
    }
    for (let i = 0; i < 8; i++) formatCells.push([size - 1 - i, 8], [8, size - 1 - i]);
    formatCells.forEach(([x, y]) => set(x, y, false, 'format'));
    set(8, size - 8, true, 'format');
    if (v >= 7) {
      let rem = v;
      for (let i = 0; i < 12; i++) rem = rem << 1 ^ (rem >> 11) * 0x1f25;
      const vb = v << 12 | rem;
      for (let i = 0; i < 18; i++) {
        const bit = (vb >> i & 1) === 1, a = size - 11 + i % 3, b = Math.floor(i / 3);
        set(a, b, bit, 'version');
        set(b, a, bit, 'version');
      }
    }
    let i = 0;
    for (let right = size - 1; right >= 1; right -= 2) {
      if (right === 6) right = 5;
      for (let vert = 0; vert < size; vert++) for (let j = 0; j < 2; j++) {
        const x = right - j, upward = (right + 1 & 2) === 0, y = upward ? size - 1 - vert : vert;
        if (role[y][x]) continue;
        role[y][x] = i < codewords.length * 8 ? 'data' : 'remainder';
        m[y][x] = i < codewords.length * 8 ? (codewords[i >> 3] >> 7 - (i & 7) & 1) === 1 : false;
        i++;
      }
    }
    const formatBits = mask => {
      const d = QR_FORMAT_EC[ec] << 3 | mask;
      let rem = d;
      for (let k = 0; k < 10; k++) rem = rem << 1 ^ (rem >> 9) * 0x537;
      return (d << 10 | rem) ^ 0x5412;
    };
    const apply = mask => {
      const out = m.map((row, y) => row.map((dark, x) => (role[y][x] === 'data' || role[y][x] === 'remainder') && qrMask[mask](x, y) ? !dark : dark));
      const fb = formatBits(mask), bit = k => (fb >> k & 1) === 1;
      for (let k = 0; k <= 5; k++) out[k][8] = bit(k);
      out[7][8] = bit(6);
      out[8][8] = bit(7);
      out[8][7] = bit(8);
      for (let k = 9; k < 15; k++) out[8][14 - k] = bit(k);
      for (let k = 0; k < 8; k++) out[8][size - 1 - k] = bit(k);
      for (let k = 8; k < 15; k++) out[size - 15 + k][8] = bit(k);
      out[size - 8][8] = true;
      return out;
    };
    let best = null;
    for (let mask = 0; mask < 8; mask++) {
      const candidate = apply(mask), p = qrPenalty(candidate);
      if (!best || p < best.p) best = {
        p,
        mask,
        grid: candidate
      };
    }
    return {
      grid: best.grid,
      role,
      size,
      version: v,
      ec,
      mask: best.mask,
      quiet: 4,
      dataCodewords: capacity,
      ecCodewords: ecLen * numBlocks
    };
  };
  const DM_SIZES = [[10, 8, 3, 5], [12, 10, 5, 7], [14, 12, 8, 10], [16, 14, 12, 12], [18, 16, 18, 14], [20, 18, 22, 18], [22, 20, 30, 20], [24, 22, 36, 24], [26, 24, 44, 28], [32, 14, 62, 36], [36, 16, 86, 42], [40, 18, 114, 48], [44, 20, 144, 56], [48, 22, 174, 68]];
  const dmPlacement = (nrow, ncol) => {
    const a = new Array(nrow * ncol).fill(0);
    const module = (row, col, chr, bit) => {
      if (row < 0) {
        row += nrow;
        col += 4 - (nrow + 4) % 8;
      }
      if (col < 0) {
        col += ncol;
        row += 4 - (ncol + 4) % 8;
      }
      a[row * ncol + col] = 10 * chr + bit;
    };
    const utah = (row, col, chr) => {
      module(row - 2, col - 2, chr, 1);
      module(row - 2, col - 1, chr, 2);
      module(row - 1, col - 2, chr, 3);
      module(row - 1, col - 1, chr, 4);
      module(row - 1, col, chr, 5);
      module(row, col - 2, chr, 6);
      module(row, col - 1, chr, 7);
      module(row, col, chr, 8);
    };
    const corner = (chr, cells) => cells.forEach(([r, c], k) => module(r, c, chr, k + 1));
    let chr = 1, row = 4, col = 0;
    do {
      if (row === nrow && col === 0) corner(chr++, [[nrow - 1, 0], [nrow - 1, 1], [nrow - 1, 2], [0, ncol - 2], [0, ncol - 1], [1, ncol - 1], [2, ncol - 1], [3, ncol - 1]]);
      if (row === nrow - 2 && col === 0 && ncol % 4) corner(chr++, [[nrow - 3, 0], [nrow - 2, 0], [nrow - 1, 0], [0, ncol - 4], [0, ncol - 3], [0, ncol - 2], [0, ncol - 1], [1, ncol - 1]]);
      if (row === nrow - 2 && col === 0 && ncol % 8 === 4) corner(chr++, [[nrow - 3, 0], [nrow - 2, 0], [nrow - 1, 0], [0, ncol - 2], [0, ncol - 1], [1, ncol - 1], [2, ncol - 1], [3, ncol - 1]]);
      if (row === nrow + 4 && col === 2 && !(ncol % 8)) corner(chr++, [[nrow - 1, 0], [nrow - 1, ncol - 1], [0, ncol - 3], [0, ncol - 2], [0, ncol - 1], [1, ncol - 3], [1, ncol - 2], [1, ncol - 1]]);
      do {
        if (row < nrow && col >= 0 && !a[row * ncol + col]) utah(row, col, chr++);
        row -= 2;
        col += 2;
      } while (row >= 0 && col < ncol);
      row += 1;
      col += 3;
      do {
        if (row >= 0 && col < ncol && !a[row * ncol + col]) utah(row, col, chr++);
        row += 2;
        col -= 2;
      } while (row < nrow && col >= 0);
      row += 3;
      col += 1;
    } while (row < nrow || col < ncol);
    if (!a[nrow * ncol - 1]) {
      a[nrow * ncol - 1] = 1;
      a[nrow * ncol - ncol - 2] = 1;
    }
    return a;
  };
  const encodeDataMatrix = (text, gs1 = false) => {
    const cw = gs1 ? [232] : [];
    for (let i = 0; i < text.length; ) {
      const c = text.charCodeAt(i);
      if ((/\d\d/).test(text.slice(i, i + 2))) {
        cw.push(130 + Number(text.slice(i, i + 2)));
        i += 2;
      } else if (c === 0x1d && gs1) {
        cw.push(232);
        i += 1;
      } else if (c < 128) {
        cw.push(c + 1);
        i += 1;
      } else {
        cw.push(235, c - 128 + 1);
        i += 1;
      }
    }
    const spec = DM_SIZES.find(([, , d]) => d >= cw.length);
    if (!spec) throw new Error('Too much data for this Data Matrix encoder');
    const [size, region, dataLen, ecLen] = spec;
    const data = [...cw];
    if (data.length < dataLen) data.push(129);
    while (data.length < dataLen) {
      let pad = 129 + 149 * (data.length + 1) % 253 + 1;
      if (pad > 254) pad -= 254;
      data.push(pad);
    }
    const codewords = [...data, ...gf(0x12d).remainder(data, ecLen, 1)];
    const per = size / (region + 2);
    const n = region * per;
    const place = dmPlacement(n, n);
    const grid = Array.from({
      length: size
    }, () => new Array(size).fill(false));
    const role = Array.from({
      length: size
    }, () => new Array(size).fill(null));
    for (let by = 0; by < per; by++) for (let bx = 0; bx < per; bx++) {
      const y0 = by * (region + 2), x0 = bx * (region + 2);
      for (let k = 0; k < region + 2; k++) {
        grid[y0 + region + 1][x0 + k] = true;
        role[y0 + region + 1][x0 + k] = 'finder';
        grid[y0 + k][x0] = true;
        role[y0 + k][x0] = 'finder';
      }
      for (let k = 0; k < region + 2; k++) {
        if (!role[y0][x0 + k]) {
          grid[y0][x0 + k] = k % 2 === 0;
          role[y0][x0 + k] = 'timing';
        }
        if (!role[y0 + k][x0 + region + 1]) {
          grid[y0 + k][x0 + region + 1] = k % 2 === 1;
          role[y0 + k][x0 + region + 1] = 'timing';
        }
      }
    }
    for (let r = 0; r < n; r++) for (let c = 0; c < n; c++) {
      const y = Math.floor(r / region) * (region + 2) + 1 + r % region;
      const x = Math.floor(c / region) * (region + 2) + 1 + c % region;
      const v = place[r * n + c];
      const chr = Math.floor(v / 10), bit = v % 10;
      grid[y][x] = v === 1 ? true : chr > 0 ? (codewords[chr - 1] >> 8 - bit & 1) === 1 : false;
      role[y][x] = chr > dataLen ? 'ec' : 'data';
    }
    return {
      grid,
      role,
      size,
      quiet: 1,
      dataCodewords: dataLen,
      ecCodewords: ecLen,
      used: cw.length
    };
  };
  const GS1_FIXED = /^(0[0-3]|1[1-35-7]|20|3[1-6]\d\d|41[0-7])$/;
  const gs1Text = hri => {
    const fields = [...hri.matchAll(/\((\d{2,4})\)([^(]*)/g)];
    return fields.map(([, ai, v], i) => ai + v + (i < fields.length - 1 && !GS1_FIXED.test(ai) ? '\x1d' : '')).join('');
  };
  const MATRIX_ROLES = {
    'qr-code': [['finder', 'rgba(37, 99, 235, 0.45)', 'Finder patterns', 'Three squares that let a scanner find the symbol and work out its size and angle, from any direction.'], ['separator', null, null, null], ['timing', 'rgba(234, 88, 12, 0.55)', 'Timing patterns', 'Alternating dark and light modules between the finder patterns, which set the module grid.'], ['alignment', 'rgba(147, 51, 234, 0.5)', 'Alignment patterns', 'Smaller squares that help correct for a curved or skewed surface. Larger versions have more of them.'], ['format', 'rgba(22, 163, 74, 0.55)', 'Format information', 'The error correction level and the mask, stored twice so one copy can be damaged.'], ['version', 'rgba(219, 39, 119, 0.5)', 'Version information', 'The symbol’s size, stored twice. Only versions 7 and up have it.'], ['data', null, 'Data and error correction', 'Everything unshaded. Codewords run in two-module columns, zigzagging up and down from the bottom right.']],
    datamatrix: [['finder', 'rgba(37, 99, 235, 0.45)', 'Finder pattern', 'A solid L along the left and bottom edges, which shows a scanner where the symbol is and which way up it is.'], ['timing', 'rgba(234, 88, 12, 0.55)', 'Clock track', 'Alternating modules along the top and right edges, which set the module grid. Larger sizes repeat both inside the symbol.'], ['data', null, 'Data', 'Unshaded. The encoded data, eight modules to a codeword.'], ['ec', 'rgba(22, 163, 74, 0.4)', 'Error correction', 'Reed–Solomon codewords that let a scanner rebuild damaged data.']]
  };
  const Matrix2D = ({type, value, ec, plain, damage}) => {
    const sym = type === 'qr-code' ? encodeQR(String(value), ec || 'M') : encodeDataMatrix(gs1Text(String(value)) || String(value), String(value).startsWith('('));
    const {grid, role, size} = sym;
    const quiet = Math.max(sym.quiet, 2);
    const M = 8, full = (size + 2 * quiet) * M, at = i => (i + quiet) * M;
    const roles = MATRIX_ROLES[type];
    const cells = [];
    grid.forEach((row, y) => row.forEach((dark, x) => {
      if (dark) cells.push(<rect key={`${x}-${y}`} x={at(x)} y={at(y)} width={M} height={M} />);
    }));
    const h = ({
      light: 0.16,
      heavy: 0.3,
      severe: 0.36
    })[damage] || 0.16, w = 0.5;
    const scratch = damage ? <g fill="#ffffff">
        <rect x={at(size * 0.38)} y={at(size * 0.5)} width={size * w * M} height={size * h * M} transform={`rotate(-12 ${at(size * 0.67)} ${at(size * 0.58)})`} />
      </g> : null;
    const overlays = [];
    if (!plain && !damage) {
      roles.forEach(([r, color]) => {
        if (!color) return;
        grid.forEach((row, y) => row.forEach((_, x) => {
          if (role[y][x] === r) overlays.push(<rect key={`${r}-${x}-${y}`} x={at(x)} y={at(y)} width={M} height={M} fill={color} />);
        }));
      });
    }
    const legend = roles.filter(([, , title]) => title);
    const name = (findType(type) || ({
      name: type
    })).name;
    const label = `${name} for ${String(value)}`;
    const svg = <svg viewBox={`0 0 ${full} ${full}`} role="img" aria-label={damage ? `${label}, scratched, with error correction level ${ec}` : label} style={{
      width: '100%',
      maxWidth: plain || damage ? 240 : 360,
      height: 'auto'
    }}>
        <rect x="0" y="0" width={full} height={full} fill="#ffffff" rx={plain ? 0 : 6} />
        <g fill="#111214">{cells}</g>
        <g data-overlay="">{overlays}</g>
        {scratch}
      </svg>;
    if (plain || damage) return <figure className="not-prose my-6">{svg}</figure>;
    return <figure className="not-prose my-6">
        {svg}
        <figcaption className="mt-4">
          <ul className="pl-0 text-sm" style={{
      listStyle: 'none'
    }}>
            {legend.map(([r, color, title, meaning]) => <li key={r} style={{
      marginBottom: 6
    }}>
                <span aria-hidden="true" style={{
      display: 'inline-block',
      width: 12,
      height: 12,
      marginRight: 8,
      verticalAlign: 'middle',
      background: color || '#ffffff',
      border: '1px solid currentColor',
      opacity: color ? 1 : 0.5
    }} />
                <strong>{title}.</strong> {meaning}
              </li>)}
          </ul>
          <p className="text-sm" style={muted}>
            {type === 'qr-code' ? `Version ${sym.version} (${size}×${size} modules), error correction level ${sym.ec}, mask ${sym.mask}: ${sym.dataCodewords} data codewords and ${sym.ecCodewords} error correction codewords.` : `${size}×${size} modules: ${sym.dataCodewords} data codewords and ${sym.ecCodewords} error correction codewords.`}
          </p>
        </figcaption>
      </figure>;
  };
  const Damage = ({type = 'qr-code', value, levels = 'L,H', severity = 'light'}) => <div className="not-prose" style={{
    display: 'grid',
    gridTemplateColumns: 'repeat(auto-fit, minmax(110px, 1fr))',
    gap: 12
  }}>
      {levels.split(',').map(ec => {
    const {size} = encodeQR(String(value), ec.trim());
    return <div key={ec}>
            {Matrix2D({
      type,
      value,
      ec: ec.trim(),
      damage: severity
    })}
            <p className="text-sm" style={{
      textAlign: 'center',
      marginTop: -12
    }}>Level {ec.trim()} · {size}×{size}</p>
          </div>;
  })}
    </div>;
  const BarStates = ({kind = 'four-state', labels}) => {
    const names = labels ? labels.split(',').map(l => l.trim()) : kind === 'height' ? ['Tall', 'Short'] : ['Full', 'Ascender', 'Descender', 'Tracker'];
    const shapes = kind === 'height' ? [[0, 40], [24, 16]] : [[0, 40], [0, 26], [14, 26], [14, 12]];
    const gap = 92, width = names.length * gap;
    return <figure className="not-prose my-6">
        <svg viewBox={`0 0 ${width} 78`} role="img" aria-label={`Bar shapes: ${names.join(', ')}`} style={{
      width: '100%',
      maxWidth: width * 1.4,
      height: 'auto'
    }}>
          {kind !== 'height' && <rect x="0" y="14" width={width} height="12" fill="currentColor" opacity="0.08" />}
          {shapes.map(([top, h], i) => <g key={i}>
              <rect x={i * gap + gap / 2 - 5} y={top} width="10" height={h} fill="currentColor" />
              <text x={i * gap + gap / 2} y="72" textAnchor="middle" fontSize="12" fill="currentColor">{names[i]}</text>
            </g>)}
        </svg>
      </figure>;
  };
  const GroupMembers = ({group}) => {
    const members = BARCODE_TYPES.filter(t => t.groups.includes(group));
    if (!members.length) return <p style={muted}>No barcode types in “{group}”.</p>;
    return <ul>
        {members.map(t => <li key={t.slug}>
            <a href={href(t)}>{t.name}</a>
            {t.groupWhen && t.groupWhen[group] && `, ${t.groupWhen[group]}`}
          </li>)}
      </ul>;
  };
  const VIEWS = {
    facts: Facts,
    support: SupportMatrix,
    groups: GroupMatrix,
    'check-digit': CheckDigitSteps,
    anatomy: p => MATRIX_ROLES[p.type] ? Matrix2D(p) : LINEAR[p.type] ? Linear(p) : Anatomy(p),
    symbol: p => MATRIX_ROLES[p.type] ? Matrix2D({
      ...p,
      plain: true
    }) : LINEAR[p.type] ? Linear({
      ...p,
      plain: true
    }) : Anatomy({
      ...p,
      plain: true
    }),
    damage: Damage,
    'bar-states': BarStates,
    group: GroupMembers
  };
  const render = VIEWS[view];
  return render ? render(props) : <p style={muted}>Unknown barcode view “{view}”.</p>;
};

<BarcodeAnatomyTool />

## How to use it

Paste or type the data a scanner returned, or scan a barcode with your camera. The tool works out what kind of data it is and explains each part.

It recognizes:

* **Product numbers (GTINs)**: UPC-A, UPC-E, EAN-13, EAN-8, ISBN, ISSN and 14-digit case numbers. You get the GS1 prefix and who issued it, a check of the check digit, and the number's other forms.
* **GS1 data**: from GS1-128, GS1 DataMatrix, GS1 DataBar Expanded or GS1 QR Code, with or without brackets. Each Application Identifier is named and its value checked.
* **GS1 Digital Link** web addresses, read the same way.
* **Passport MRZs**, with every check digit verified.
* **Driver's license data** from the PDF417 barcode on North American licenses.

Anything else is shown as plain text, with the barcode types that could carry it.

Scanning works in any current browser, on phones and computers. When you scan or use a photo, the result also says which barcode type it was and, for a QR code, its version and error correction level.

## What the results mean

* **GS1 prefix**: the first three digits of a product number say which national GS1 office issued it, not where the product was made. See [EAN-13](/reference/barcode-types/ean-13#gs1-prefixes).
* **Check digit**: a wrong check digit means a digit was misread or mistyped. See [Check digits](/reference/check-digits).
* **Application Identifiers**: see the [GS1 AI table](/reference/gs1-application-identifiers).
* **A different number from the label**: see [Why doesn't my scan match the number printed under the barcode?](/reference/scan-vs-printed-number)

## Privacy

Everything happens in your browser. Nothing you paste, scan or photograph is sent anywhere, and camera frames never leave your device.

The first time you scan or use a photo, the page downloads the barcode reader, [ZXing](https://github.com/zxing-cpp/zxing-cpp), from the jsDelivr CDN. That download is about 450 KB and carries none of your data.

## Parse this in your app

ZipZapKit does the same work on every scan: it validates check digits, normalizes product numbers and parses GS1 data. See [GS1 Application Identifiers](/sdk/gs1/application-identifiers) and [Validation](/sdk/modifiers/validation).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.