{
  "schema_version": "1.0.0",
  "last_updated": "2026-08",
  "verification_state": "unverified-draft",
  "note": "Shared by both editions. Unicode behaviour recorded here is specified in the standard and stable. Printer-specific values are vendor-dependent and carry todo_verify.",
  "editions": {
    "en": "Implementer-facing. Bidi algorithm, shaping, code pages, raster rendering. Path in doc_en.",
    "zh": "Shop-owner-facing. How to recognise a bad sample print and what to ask a vendor before paying. Written separately, not a translation. Path in doc_zh."
  },
  "source_types": {
    "official-authority": "Published or administered by a named body, including standards bodies where identified.",
    "public-regulation": "Derives from published law, decree or regulation.",
    "unverified": "Not attributable to either of the above as written. Treat as unknown."
  },
  "languages": [
    {
      "language": "Arabic",
      "iso_639_1": "ar",
      "iso_639_3": "ara",
      "doc_en": "en/languages/arabic.md",
      "doc_zh": "zh/languages/arabic.md",
      "script": {
        "iso_15924": "Arab",
        "name": "Arabic",
        "direction": "rtl",
        "requires_contextual_shaping": true,
        "positional_forms": [
          "isolated",
          "initial",
          "medial",
          "final"
        ],
        "mandatory_ligatures": [
          {
            "name": "lam-alef",
            "sequence": [
              "U+0644",
              "U+0627"
            ],
            "note": "Not stylistic. The unligated sequence is wrong.",
            "source": "official-authority"
          }
        ],
        "combining_marks_zero_width": {
          "value": true,
          "ranges": [
            "U+064B-U+0652"
          ],
          "note": "Counted by string length but occupy no printer column. Cause of column drift on real supplier data.",
          "source": "official-authority"
        },
        "mirrored_characters": {
          "value": true,
          "examples": [
            "parentheses",
            "brackets",
            "angle brackets"
          ],
          "note": "Mirror once. Doing it in both the renderer and application code cancels out.",
          "source": "official-authority"
        }
      },
      "bidirectional": {
        "algorithm": "Unicode UAX #9",
        "source": "official-authority",
        "base_direction_must_be_explicit_per_line": {
          "value": true,
          "note": "The first-strong default takes paragraph direction from the first strongly directional character, so an item name beginning with a Latin brand or SKU lays out mirrored relative to surrounding lines."
        },
        "each_line_is_its_own_paragraph": true,
        "numbers_form_ltr_runs_within_rtl_text": true,
        "known_failure_modes": [
          "numeric separators detach from the number and jump to the opposite end of the run",
          "signs and percent marks migrate to the wrong side",
          "a line's base direction flips when the name starts with Latin text",
          "padding computed on code point count instead of display width"
        ],
        "control_characters": [
          {
            "name": "RLM",
            "codepoint": "U+200F",
            "use": "invisible strong RTL; pins a neutral run"
          },
          {
            "name": "LRM",
            "codepoint": "U+200E",
            "use": "invisible strong LTR"
          },
          {
            "name": "LRI",
            "codepoint": "U+2066",
            "use": "left-to-right isolate"
          },
          {
            "name": "RLI",
            "codepoint": "U+2067",
            "use": "right-to-left isolate"
          },
          {
            "name": "FSI",
            "codepoint": "U+2068",
            "use": "first-strong isolate"
          },
          {
            "name": "PDI",
            "codepoint": "U+2069",
            "use": "terminates an isolate"
          }
        ],
        "preferred_technique": "isolates over directional marks",
        "storage_rule": "store logical order, render visual order, never store visual order"
      },
      "digits": {
        "forms": [
          {
            "name": "Western (ASCII)",
            "range": "U+0030-U+0039",
            "note": "Widely used on printed commercial documents across the Arab world."
          },
          {
            "name": "Arabic-Indic",
            "range": "U+0660-U+0669",
            "note": "Used in parts of the region."
          },
          {
            "name": "Eastern Arabic-Indic",
            "range": "U+06F0-U+06F9",
            "note": "Persian/Urdu context; distinct glyphs for some digits."
          }
        ],
        "market_expectation": {
          "value": null,
          "source": "unverified",
          "todo_verify": true
        },
        "rules": [
          "make the digit form a template setting, not a code branch",
          "convert at render time only, never in stored data",
          "never mix forms on one document",
          "switching digit form does not change ordering behaviour, only glyphs"
        ]
      },
      "printing": {
        "printers_implement_bidi": false,
        "printers_accept_utf8": false,
        "shaping_owner": {
          "value": null,
          "options": [
            "printer firmware with an Arabic ROM font",
            "application pre-shaping to presentation forms"
          ],
          "note": "Per-model fact, not discoverable at runtime. Wrong assumption prints disconnected isolated letters.",
          "source": "unverified",
          "todo_verify": true
        },
        "code_pages": [
          {
            "name": "CP864",
            "note": "DOS Arabic. Contains presentation forms, so text must be pre-shaped.",
            "esc_t_value": null,
            "todo_verify": true
          },
          {
            "name": "CP720",
            "note": "DOS Arabic, transparent ASMO. Different layout from CP864.",
            "esc_t_value": null,
            "todo_verify": true
          },
          {
            "name": "Windows-1256",
            "note": "Covers the base repertoire; expects the renderer to shape.",
            "esc_t_value": null,
            "todo_verify": true
          },
          {
            "name": "ISO-8859-6",
            "note": "Base Arabic, limited repertoire, uncommon in POS firmware.",
            "esc_t_value": null,
            "todo_verify": true
          }
        ],
        "code_page_numbers_omitted_deliberately": {
          "value": true,
          "reason": "Vendors renumber the code page table and clone firmware diverges from documented ordering while claiming compatibility. Read the value from the target printer's command reference and confirm by printing."
        },
        "single_byte_pages_insufficient_for_mixed_lines": {
          "value": true,
          "note": "Arabic plus a Latin brand name plus a currency symbol on one line exceeds any single-byte page."
        },
        "recommended_print_path": {
          "value": "raster",
          "commands": [
            "GS v 0",
            "ESC * (older firmware)"
          ],
          "solves": [
            "shaping",
            "bidi ordering",
            "ligatures",
            "code pages",
            "glyph coverage",
            "mixed-script lines"
          ],
          "costs": [
            "more data and slower printing",
            "printer font and size commands no longer apply",
            "resolution must match printer dot density"
          ]
        },
        "currency_glyph_coverage": {
          "value": null,
          "note": "ROM fonts lag Unicode by years. Where coverage is uncertain print the ISO code instead of the symbol.",
          "source": "unverified",
          "todo_verify": true
        }
      },
      "layout": {
        "amount_column_side": {
          "value": "conventionally mirrored from LTR layouts: description at the right edge, amount at the left",
          "note": "Consistency between header, item lines, totals block and tax breakdown matters more than the choice itself.",
          "source": "unverified"
        },
        "padding_must_be_outside_isolates": true,
        "measure_display_width_not_string_length": true,
        "normalise_before_measuring": true,
        "paper_width_guidance": [
          {
            "width_mm": 80,
            "columns_font_a": [
              48,
              42
            ],
            "two_column_layout_viable": true
          },
          {
            "width_mm": 58,
            "columns_font_a": 32,
            "two_column_layout_viable": false,
            "note": "Stack the name on its own line with the amount beneath it."
          }
        ],
        "line_breaking": {
          "hyphenation": false,
          "break_on": "whitespace only",
          "truncate_mid_word": false,
          "truncation_note": "Cutting a word changes the final letter's positional form, producing a different misspelled word rather than an obviously clipped string.",
          "do_not_orphan": [
            "ZWJ",
            "ZWNJ"
          ]
        }
      },
      "retail_regions": {
        "primary": [
          "Gulf states",
          "Levant",
          "Iraq",
          "Egypt",
          "Sudan",
          "Libya",
          "Maghreb"
        ],
        "qualifications": [
          {
            "region": "Maghreb",
            "note": "French is widely used in retail documentation in Morocco, Algeria and Tunisia, often alongside Arabic on the same document, making mixed-direction layout the normal case."
          },
          {
            "region": "Gulf",
            "note": "Arabic and English routinely appear on the same receipt. Some markets may have expectations about the language of tax figures.",
            "todo_verify": true
          }
        ],
        "legal_language_requirements": {
          "value": null,
          "source": "unverified",
          "todo_verify": true
        }
      },
      "deployment_pattern": {
        "independent_language_settings": [
          "till interface",
          "back office interface",
          "print template"
        ],
        "note": "Only the print template drives the rendering concerns in this file."
      },
      "acceptance_test_requires_native_reader": {
        "value": true,
        "note": "Disconnected letters, a flipped line direction and column drift all look plausible to a reviewer who does not read the script."
      },
      "open_verification_items": {
        "en": 6,
        "zh": 5
      }
    },
    {
      "language": "Hebrew",
      "iso_639_1": "he",
      "iso_639_3": "heb",
      "doc_en": "en/languages/hebrew.md",
      "doc_zh": "zh/languages/hebrew.md",
      "script": {
        "iso_15924": "Hebr",
        "name": "Hebrew",
        "direction": "rtl",
        "requires_contextual_shaping": false,
        "shaping_note": "Non-cursive. Letters do not join, there are no positional forms and no presentation-forms block is involved. This is the principal contrast with Arabic.",
        "positional_forms": [],
        "mandatory_ligatures": [],
        "final_forms": {
          "value": true,
          "mechanism": "separate codepoints chosen at spelling time, not glyph variants selected by a renderer",
          "pairs": [
            { "letter": "kaf", "regular": "U+05DB", "final": "U+05DA" },
            { "letter": "mem", "regular": "U+05DE", "final": "U+05DD" },
            { "letter": "nun", "regular": "U+05E0", "final": "U+05DF" },
            { "letter": "pe", "regular": "U+05E4", "final": "U+05E3" },
            { "letter": "tsadi", "regular": "U+05E6", "final": "U+05E5" }
          ],
          "note": "Because a final form can only occur at the end of a word, and the end of a word is at the left of the printed run, its position is a reliable reversal detector usable by someone who does not read Hebrew.",
          "affects_search": "A substring search using the regular form will not match the final form. Normalise search input, not stored data.",
          "source": "official-authority"
        },
        "combining_marks_zero_width": {
          "value": true,
          "ranges": [
            "U+05B0-U+05BC"
          ],
          "name": "nikud (vowel points) and cantillation marks",
          "note": "Absent from ordinary commercial text but arrive through copied supplier data, causing the same column drift as Arabic harakat.",
          "source": "official-authority"
        },
        "mirrored_characters": {
          "value": true,
          "examples": [
            "parentheses",
            "brackets",
            "angle brackets"
          ],
          "note": "Mirror once. Doing it in both the renderer and application code cancels out.",
          "source": "official-authority"
        }
      },
      "bidirectional": {
        "algorithm": "Unicode UAX #9",
        "source": "official-authority",
        "base_direction_must_be_explicit_per_line": {
          "value": true,
          "note": "Same requirement as Arabic. Israeli catalogues carry Latin brand names routinely, so a name-initial Latin run is an everyday case rather than an edge case."
        },
        "each_line_is_its_own_paragraph": true,
        "numbers_form_ltr_runs_within_rtl_text": true,
        "known_failure_modes": [
          "the whole string is reversed because visual and logical order were confused",
          "numeric separators detach from the number and jump to the opposite end of the run",
          "signs and percent marks migrate to the wrong side",
          "a line's base direction flips when the name starts with Latin text",
          "padding computed on code point count instead of display width"
        ],
        "preferred_technique": "isolates over directional marks",
        "storage_rule": "store logical order, render visual order, never store visual order"
      },
      "byte_order": {
        "is_the_distinctive_hebrew_problem": true,
        "note": "Hebrew legacy encodings disagree about whether bytes are in reading order or printing order. The two are not distinguishable from the bytes themselves — ISO-8859-8 and ISO-8859-8-I are byte-identical and differ only in declared order.",
        "failure_mode": "Text prints reversed rather than garbled. It still looks like Hebrew, so it survives review by a non-reader.",
        "source": "official-authority"
      },
      "digits": {
        "forms": [
          {
            "name": "Western (ASCII)",
            "range": "U+0030-U+0039",
            "note": "The form used on Hebrew commercial documents. There is no Hebrew equivalent of the Arabic-Indic digit choice."
          }
        ],
        "hebrew_letter_numerals": {
          "used_for_prices": false,
          "note": "Used for Hebrew-calendar dates and traditional enumeration. Treating them as a number format would be a mistake.",
          "source": "official-authority"
        },
        "rules": [
          "no per-market digit-form setting is required, unlike Arabic",
          "digits still form left-to-right runs inside right-to-left text"
        ]
      },
      "printing": {
        "printers_implement_bidi": false,
        "printers_accept_utf8": false,
        "shaping_owner": {
          "value": "not applicable",
          "note": "Hebrew is non-cursive, so the Arabic question of whether firmware or application shapes does not arise."
        },
        "printer_byte_order": {
          "value": null,
          "options": [
            "visual order, as CP862-derived firmware modes expect",
            "logical order"
          ],
          "note": "Per-model fact, not discoverable at runtime. The most important thing to establish about a Hebrew printer. Sending logical bytes to a visual-order mode prints words backwards; reordering in the application when the firmware also reorders is backwards for the same reason.",
          "source": "unverified",
          "todo_verify": true
        },
        "code_pages": [
          {
            "name": "Windows-1255",
            "order": "logical",
            "note": "Near-superset of ISO-8859-8. Carries the shekel sign at 0xA4 and supports vowel points. Usual modern choice.",
            "contains_shekel_sign": true,
            "esc_t_value": null,
            "todo_verify": true
          },
          {
            "name": "ISO-8859-8",
            "order": "visual",
            "note": "Position 0xA4 is the generic currency sign, not the shekel.",
            "contains_shekel_sign": false,
            "esc_t_value": null,
            "todo_verify": true
          },
          {
            "name": "ISO-8859-8-I",
            "order": "logical",
            "note": "Byte-identical to ISO-8859-8; the suffix declares logical order and nothing else.",
            "contains_shekel_sign": false,
            "esc_t_value": null,
            "todo_verify": true
          },
          {
            "name": "CP862",
            "order": "visual in practice",
            "note": "DOS Hebrew. Stored visually because DOS had no bidi support. Still present in thermal printer firmware.",
            "contains_shekel_sign": false,
            "esc_t_value": null,
            "todo_verify": true
          }
        ],
        "code_page_numbers_omitted_deliberately": {
          "value": true,
          "reason": "Same reason as Arabic: vendors renumber the code page table and clone firmware diverges from documented ordering while claiming compatibility."
        },
        "single_byte_pages_insufficient_for_mixed_lines": {
          "value": true,
          "note": "A Hebrew item name beside a Latin brand beside a currency symbol exceeds any single-byte page, and mixed script is the ordinary case in Israeli retail."
        },
        "recommended_print_path": {
          "value": "code page is viable; raster conditionally",
          "differs_from_arabic": true,
          "note": "Because Hebrew needs no shaping, single-byte code page printing genuinely covers the script. Use raster when the receipt mixes scripts, when the shekel sign must appear, or when the printer's byte order cannot be established.",
          "commands": [
            "GS v 0"
          ],
          "solves": [
            "code pages",
            "glyph coverage",
            "mixed-script lines",
            "visual versus logical order"
          ],
          "costs": [
            "more data and slower printing",
            "printer font and size commands no longer apply",
            "resolution must match printer dot density"
          ]
        },
        "currency": {
          "iso_4217": "ILS",
          "symbol": "₪",
          "codepoint": "U+20AA",
          "name": "new shekel",
          "glyph_coverage": {
            "value": null,
            "note": "Present in Windows-1255, absent from ISO-8859-8. ROM font coverage is a separate question from code page definition. Print the ISO code where coverage is uncertain.",
            "source": "unverified",
            "todo_verify": true
          },
          "symbol_position": {
            "value": null,
            "source": "unverified",
            "todo_verify": true
          }
        }
      },
      "layout": {
        "amount_column_side": {
          "value": "conventionally mirrored from LTR layouts: description at the right edge, amount at the left",
          "note": "Consistency between header, item lines, totals block and tax breakdown matters more than the choice itself.",
          "source": "unverified"
        },
        "padding_must_be_outside_isolates": true,
        "measure_display_width_not_string_length": true,
        "wide_characters": false,
        "width_rule": "one column per non-combining character; the only correction is for zero-width marks",
        "paper_width_guidance": [
          {
            "width_mm": 80,
            "columns_font_a": [
              48,
              42
            ],
            "two_column_layout_viable": true,
            "note": "More comfortable than Arabic and far more so than CJK; Hebrew commercial text is compact."
          },
          {
            "width_mm": 58,
            "columns_font_a": 32,
            "two_column_layout_viable": "for short names only",
            "note": "Names carrying an embedded Latin brand need the name stacked on its own line."
          }
        ],
        "line_breaking": {
          "hyphenation": false,
          "break_on": "whitespace only",
          "truncate_mid_word": true,
          "truncation_note": "Unlike Arabic, truncation does not change any glyph, because Hebrew letters have no positional forms. It is merely ugly."
        }
      },
      "retail_regions": {
        "primary": [
          "Israel"
        ],
        "qualifications": [
          {
            "region": "Israel",
            "note": "Not yet covered by a country file in this repository. Tax, invoicing and receipt-content requirements are undocumented here.",
            "todo_verify": true
          },
          {
            "region": "diaspora businesses",
            "note": "Hebrew usually appears as a secondary language on the document rather than the primary one, making mixed-script handling the whole job."
          }
        ],
        "legal_language_requirements": {
          "value": null,
          "source": "unverified",
          "todo_verify": true
        }
      },
      "deployment_pattern": {
        "independent_language_settings": [
          "till interface",
          "back office interface",
          "print template"
        ],
        "note": "Only the print template drives the rendering concerns in this file. Chinese-owned businesses in Israel typically pair a Chinese back office with a Hebrew till and Hebrew receipts, requiring two product name fields."
      },
      "acceptance_test_requires_native_reader": {
        "value": true,
        "note": "A reversed string still looks like Hebrew. The final-form position test is the one check a non-reader can perform reliably.",
        "non_reader_check_available": true
      },
      "open_verification_items": {
        "en": 6,
        "zh": 6
      }
    },
    {
      "language": "Chinese, Japanese and Korean",
      "group": "CJK",
      "iso_639_1": [
        "zh",
        "ja",
        "ko"
      ],
      "doc_en": "en/languages/cjk.md",
      "doc_zh": "zh/languages/cjk.md",
      "script": {
        "iso_15924": [
          "Hani",
          "Hira",
          "Kana",
          "Hang"
        ],
        "direction": "ltr",
        "requires_contextual_shaping": false
      },
      "width": {
        "standard": "Unicode UAX #11 East Asian Width",
        "source": "official-authority",
        "columns_per_character": 2,
        "halfwidth_katakana_columns": 1,
        "note": "The dominant failure: code measuring with a plain character count under-counts by a factor of two and overflows the line.",
        "ambiguous_width_characters": {
          "examples": [
            "°",
            "±",
            "←",
            "Greek letters"
          ],
          "note": "One column in a Western font, two in a CJK font. The printer decides; measure empirically.",
          "source": "unverified",
          "todo_verify": true
        }
      },
      "line_budget": [
        {
          "width_mm": 80,
          "columns": 48,
          "description_columns": 35,
          "cjk_characters": 17
        },
        {
          "width_mm": 58,
          "columns": 32,
          "description_columns": 19,
          "cjk_characters": 9,
          "note": "Name must go on its own line; this is the only workable layout, not a degraded one."
        }
      ],
      "line_breaking": {
        "breaks_without_whitespace": true,
        "kinsoku_rules": {
          "may_not_start_a_line": [
            "。",
            "，",
            "、",
            "）",
            "」",
            "》"
          ],
          "may_not_end_a_line": [
            "（",
            "「",
            "《"
          ],
          "source": "official-authority"
        }
      },
      "printing": {
        "printers_accept_utf8": false,
        "requires_multibyte_mode": true,
        "rom_font_present": {
          "value": null,
          "source": "unverified",
          "todo_verify": true,
          "note": "Printers sold into Western markets frequently have no CJK font and will print boxes regardless of what is sent. Most common cause of works-on-my-desk failures."
        },
        "encodings": {
          "Simplified Chinese": [
            "GB18030",
            "GBK",
            "GB2312"
          ],
          "Traditional Chinese": [
            "Big5"
          ],
          "Japanese": [
            "Shift_JIS"
          ],
          "Korean": [
            "EUC-KR"
          ]
        },
        "mode_command_values": {
          "value": null,
          "source": "unverified",
          "todo_verify": true
        },
        "glyph_coverage": {
          "value": null,
          "source": "unverified",
          "todo_verify": true,
          "note": "Rare surname characters, Traditional forms and the shop's own name are where coverage runs out."
        },
        "recommended_print_path": "raster"
      },
      "simplified_traditional": {
        "interchangeable": false,
        "note": "Conversion is not one-to-one; one simplified form can map to several traditional ones. Store two name fields rather than converting at runtime."
      },
      "deployment_relevance": "Chinese is the back-office language across most deployments in this repository, so this file applies to every country in it, not only where Chinese reaches the customer receipt. Customer-facing in Malaysia and on kitchen tickets in Chinese-owned restaurants.",
      "open_verification_items": {
        "en": 4,
        "zh": 4
      }
    },
    {
      "language": "Accented Latin scripts",
      "group": "Latin-accented",
      "covers": [
        "es",
        "pt",
        "fr",
        "de",
        "it",
        "tr",
        "nordic"
      ],
      "unaffected": {
        "languages": [
          "id",
          "ms"
        ],
        "note": "Latin script with no diacritics in normal commercial use; no code page work needed."
      },
      "doc_en": "en/languages/latin-accented.md",
      "doc_zh": "zh/languages/latin-accented.md",
      "script": {
        "iso_15924": "Latn",
        "direction": "ltr",
        "requires_contextual_shaping": false
      },
      "failure_mode": {
        "value": "silent",
        "note": "Output stays readable when wrong. Pina for Pina-with-tilde, Sao for Sao-with-tilde. Passes review by anyone testing with clean data, unlike Arabic or Thai which fail loudly."
      },
      "special_cases": [
        {
          "language": "es",
          "issue": "inverted punctuation",
          "chars": [
            "¿",
            "¡"
          ],
          "note": "Not decorative; outside ASCII and often the first thing to break."
        },
        {
          "language": "tr",
          "issue": "dotted and dotless i",
          "note": "i uppercases to I-with-dot, dotless-i uppercases to I. A naive toUpperCase produces a different word.",
          "severity": "correctness"
        },
        {
          "language": "de",
          "issue": "sharp s uppercase",
          "note": "Conventional fallback SS changes string length and therefore column width."
        }
      ],
      "printing": {
        "printers_accept_utf8": false,
        "default_code_page_is_usually": "CP437",
        "default_covers_accents": false,
        "note": "A printer whose code page was never set mangles every accented character. Most common single cause.",
        "candidate_code_pages": [
          "CP850",
          "CP858",
          "Windows-1252",
          "CP860",
          "ISO-8859-1",
          "ISO-8859-15",
          "Windows-1254"
        ],
        "code_page_values": {
          "value": null,
          "source": "unverified",
          "todo_verify": true
        },
        "euro_sign": {
          "absent_from": [
            "CP850",
            "ISO-8859-1"
          ],
          "note": "Select CP858, Windows-1252 or ISO-8859-15, or print EUR."
        }
      },
      "normalisation": {
        "required_form": "NFC",
        "apply_at": [
          "import",
          "before printing"
        ],
        "note": "Decomposed input loses the accent entirely through a single-byte code page, and inflates character counts so column maths disagrees with printed width.",
        "source": "official-authority"
      },
      "description_length_vs_english": "roughly a fifth longer for es and pt; longer for fr and de, which also produces unbreakable compounds",
      "open_verification_items": {
        "en": 3,
        "zh": 3
      }
    },
    {
      "language": "Thai",
      "iso_639_1": "th",
      "iso_639_3": "tha",
      "doc_en": "en/languages/thai.md",
      "doc_zh": "zh/languages/thai.md",
      "script": {
        "iso_15924": "Thai",
        "direction": "ltr",
        "has_case": false,
        "requires_contextual_shaping": false
      },
      "line_breaking": {
        "word_separating_spaces": false,
        "space_role": "separates phrases and sentences, roughly where English uses a comma or full stop",
        "requires_dictionary_algorithm": true,
        "standard": "UAX #14, which handles Thai by dictionary lookup rather than character properties alone",
        "note": "Wrapping on whitespace breaks inside words, producing a different string rather than a clipped one.",
        "raster_does_not_solve_this": true,
        "source": "official-authority"
      },
      "mark_stack": {
        "levels": [
          "base consonant",
          "above or below vowel",
          "tone mark",
          "thanthakhat"
        ],
        "columns_occupied": 1,
        "combining_marks_zero_width": true,
        "clipping_risk": "Tight leading clips upper tone marks. Legible to a non-reader, wrong to a Thai reader.",
        "sara_am": {
          "codepoint": "U+0E33",
          "note": "Single codepoint rendering as a mark plus a trailing tail; can split across a line break if decomposed."
        },
        "source": "official-authority"
      },
      "collation": {
        "codepoint_order_is_wrong": true,
        "leading_vowels": [
          "เ",
          "แ",
          "โ",
          "ใ",
          "ไ"
        ],
        "note": "These five vowels are written before the consonant they follow phonetically, so dictionary order files the word under the consonant. Affects product lists and search, not printing.",
        "recommended": "ICU th locale",
        "source": "official-authority"
      },
      "digits": {
        "thai_digits_range": "U+0E50-U+0E59",
        "western_usual_on_commercial_documents": true,
        "rule": "template setting, never mix forms on one document"
      },
      "calendar": {
        "buddhist_era_common": true,
        "offset_years": 543,
        "example": "2026 CE = 2569 BE",
        "rule": "store one canonical form, convert at render time",
        "todo_verify": true
      },
      "printing": {
        "printers_accept_utf8": false,
        "code_pages": [
          "TIS-620",
          "CP874"
        ],
        "code_page_values": {
          "value": null,
          "source": "unverified",
          "todo_verify": true
        },
        "recommended_print_path": "raster",
        "raster_caveat": "Resolves stacking, glyph coverage and code page, but NOT line breaking, which happens upstream.",
        "kitchen_printer_separate_test": {
          "value": true,
          "note": "Frequently a different and cheaper model than front of house."
        }
      },
      "acceptance_test_requires_native_reader": true,
      "open_verification_items": {
        "en": 4,
        "zh": 4
      }
    },
    {
      "language": "Vietnamese",
      "iso_639_1": "vi",
      "iso_639_3": "vie",
      "doc_en": "en/languages/vietnamese.md",
      "doc_zh": "zh/languages/vietnamese.md",
      "script": {
        "iso_15924": "Latn",
        "direction": "ltr",
        "note": "Latin script that behaves like a non-Latin one."
      },
      "diacritics": {
        "layers": [
          "base vowel",
          "quality diacritic (circumflex, breve, horn)",
          "tone mark"
        ],
        "both_can_stack": true,
        "letters_beyond_ascii": 134,
        "d_with_stroke": {
          "letter": "đ",
          "note": "A distinct letter, not a d to be normalised away."
        },
        "source": "official-authority"
      },
      "printing": {
        "printers_accept_utf8": false,
        "no_single_byte_page_covers_it_fully": {
          "value": true,
          "note": "Including CP1258, the page nominally designed for Vietnamese."
        },
        "encodings": [
          {
            "name": "Windows-1258",
            "problem": "Encodes some syllables as base letter plus combining tone mark, so a naive transcode drops or mangles tones even when the page is nominally correct."
          },
          {
            "name": "TCVN3 (ABC)",
            "problem": "Legacy; splits Vietnamese across two fonts, one lowercase one uppercase."
          },
          {
            "name": "VNI",
            "problem": "Legacy, a different split again."
          },
          {
            "name": "VISCII",
            "problem": "Legacy, rarely in printer ROM."
          }
        ],
        "code_page_selection_is_insufficient": {
          "value": true,
          "note": "Selecting CP1258 is not evidence Vietnamese will print correctly. Print and look."
        },
        "recommended_print_path": "raster",
        "line_breaking": "ordinary whitespace wrapping is correct; unlike Thai, no dictionary needed"
      },
      "supplier_data_risk": {
        "legacy_encodings_common": [
          "TCVN3",
          "VNI"
        ],
        "note": "Catalogues from long-established suppliers frequently arrive in legacy encodings. Import a small batch first and have a Vietnamese reader check it; correcting a corrupted catalogue after prices and stock are attached is expensive.",
        "normalise_to": "NFC",
        "source": "unverified",
        "todo_verify": true
      },
      "collation": {
        "codepoint_order_is_wrong": true,
        "rules": [
          "đ sorts after d",
          "quality-marked vowels sort after their base vowels"
        ],
        "recommended": "ICU vi locale",
        "source": "official-authority"
      },
      "currency_symbol": {
        "char": "₫",
        "codepoint": "U+20AB",
        "often_absent_from_rom": true,
        "fallback": "VND"
      },
      "description_length": "words are short but numerous, so names wrap across more lines than English; budget two-line item names at 58 mm",
      "acceptance_test_requires_native_reader": true,
      "open_verification_items": {
        "en": 3,
        "zh": 3
      }
    },
    {
      "language": "Cyrillic script",
      "group": "Cyrillic",
      "covers": [
        "ru",
        "uk",
        "be",
        "bg",
        "sr",
        "mk",
        "kk",
        "ky",
        "tg",
        "uz",
        "mn"
      ],
      "doc_en": "en/languages/cyrillic.md",
      "doc_zh": "zh/languages/cyrillic.md",
      "script": {
        "iso_15924": "Cyrl",
        "direction": "ltr",
        "has_case": true,
        "requires_contextual_shaping": false,
        "note": "Non-cursive, left to right, whitespace-separated, no stacking marks. The rendering concerns that dominate the Arabic, Thai and CJK files are all absent. The problems are in the data and in code page selection."
      },
      "homoglyphs": {
        "is_the_distinctive_cyrillic_problem": true,
        "note": "A group of Cyrillic letters is visually identical to Latin letters with different codepoints. Strings render and print correctly and compare unequal, so search returns nothing, imports create duplicate records and stock splits across them.",
        "source_of_entry": "keyboard layout switching during manual data entry; the Latin and Cyrillic forms share a physical key",
        "pairs_uppercase": [
          { "cyrillic": "U+0410", "latin": "U+0041" },
          { "cyrillic": "U+0412", "latin": "U+0042" },
          { "cyrillic": "U+0415", "latin": "U+0045" },
          { "cyrillic": "U+041A", "latin": "U+004B" },
          { "cyrillic": "U+041C", "latin": "U+004D" },
          { "cyrillic": "U+041D", "latin": "U+0048" },
          { "cyrillic": "U+041E", "latin": "U+004F" },
          { "cyrillic": "U+0420", "latin": "U+0050" },
          { "cyrillic": "U+0421", "latin": "U+0043" },
          { "cyrillic": "U+0422", "latin": "U+0054" },
          { "cyrillic": "U+0423", "latin": "U+0059" },
          { "cyrillic": "U+0425", "latin": "U+0058" },
          { "cyrillic": "U+0406", "latin": "U+0049" },
          { "cyrillic": "U+0408", "latin": "U+004A" },
          { "cyrillic": "U+0405", "latin": "U+0053" }
        ],
        "pairs_lowercase": [
          { "cyrillic": "U+0430", "latin": "U+0061" },
          { "cyrillic": "U+0435", "latin": "U+0065" },
          { "cyrillic": "U+043E", "latin": "U+006F" },
          { "cyrillic": "U+0440", "latin": "U+0070" },
          { "cyrillic": "U+0441", "latin": "U+0063" },
          { "cyrillic": "U+0443", "latin": "U+0079" },
          { "cyrillic": "U+0445", "latin": "U+0078" },
          { "cyrillic": "U+0456", "latin": "U+0069" },
          { "cyrillic": "U+0458", "latin": "U+006A" },
          { "cyrillic": "U+0455", "latin": "U+0073" }
        ],
        "detection_unit": "one whitespace-delimited token, not the whole line; a Cyrillic name beside a Latin brand is ordinary data",
        "rules": [
          "validate at entry and at import permanently, not once as a migration",
          "flag for a human, never auto-convert; a mixed-script record may already carry stock movements and sales history",
          "forbid Cyrillic letters, or letters entirely, in article codes and SKU fields"
        ],
        "source": "official-authority"
      },
      "normalisation": {
        "required_form": "NFC",
        "apply_at": [
          "import",
          "before printing"
        ],
        "decompositions": [
          { "composed": "U+0401", "decomposes_to": ["U+0415", "U+0308"], "letter": "Io" },
          { "composed": "U+0451", "decomposes_to": ["U+0435", "U+0308"], "letter": "io" },
          { "composed": "U+0419", "decomposes_to": ["U+0418", "U+0306"], "letter": "Short I" },
          { "composed": "U+0439", "decomposes_to": ["U+0438", "U+0306"], "letter": "short i" },
          { "composed": "U+040E", "decomposes_to": ["U+0423", "U+0306"], "letter": "Short U, Belarusian" },
          { "composed": "U+045E", "decomposes_to": ["U+0443", "U+0306"], "letter": "short u, Belarusian" },
          { "composed": "U+0407", "decomposes_to": ["U+0406", "U+0308"], "letter": "Yi, Ukrainian" },
          { "composed": "U+0457", "decomposes_to": ["U+0456", "U+0308"], "letter": "yi, Ukrainian" }
        ],
        "failure_mode": "silent",
        "note": "Through a single-byte code page the combining mark is dropped and the short i becomes plain i, leaving a different but still plausible word rather than a box or a question mark. Also inflates character counts and breaks equality against a composed copy.",
        "source": "official-authority"
      },
      "yo_folding": {
        "letters": ["U+0401", "U+0451"],
        "note": "Russian printed matter routinely substitutes e for io. Both spellings occur in real catalogues.",
        "rule": "fold in search input on both sides of the comparison; do not fold stored data",
        "legal_name_requirement": {
          "value": null,
          "source": "unverified",
          "todo_verify": true
        }
      },
      "collation": {
        "codepoint_order_is_wrong": true,
        "russian": {
          "note": "The alphabet places io immediately after e, but U+0401 sorts before U+0410 and U+0451 sorts after U+044F, so codepoint order files one at the very top of a product list and the other at the very bottom.",
          "source": "official-authority"
        },
        "ukrainian": "Ghe-with-upturn after Ghe, and Ie, Byelorussian-Ukrainian I and Yi inside the alphabet, while their codepoints U+0490, U+0404, U+0406 and U+0407 sit outside the contiguous run",
        "serbian": "Dje, Je, Lje, Nje, Tshe and Dzhe order inside the alphabet, while their codepoints U+0402 to U+040F all fall before U+0410",
        "koi8_byte_order": {
          "alphabetical": false,
          "note": "KOI8-R orders its letters in a pseudo-Latin transliteration sequence inherited from teletype practice, so sorting on stored 8-bit bytes from a KOI8 source is wrong in a third way, distinct from codepoint order."
        },
        "recommended": "ICU with the specific locale (ru, uk, sr, bg, kk, mn); a generic Cyrillic collation is not sufficient",
        "source": "official-authority"
      },
      "printing": {
        "printers_accept_utf8": false,
        "printers_implement_bidi": "not applicable",
        "no_default_code_page": {
          "value": true,
          "note": "Two live traditions, DOS and Windows, disagree across the whole range above 0x7F. Unlike a CP437 printer facing English, an unconfigured Cyrillic printer is not approximately right; it is either right or completely wrong, and which one is not discoverable at runtime."
        },
        "rom_font_present": {
          "value": null,
          "source": "unverified",
          "todo_verify": true
        },
        "code_page_family": {
          "value": null,
          "options": [
            "DOS tradition (CP866 and variants)",
            "Windows tradition (Windows-1251)"
          ],
          "note": "Per-model fact and the first question for a Cyrillic printer. Settled cheaply by one sample print.",
          "source": "unverified",
          "todo_verify": true
        },
        "code_pages": [
          {
            "name": "Windows-1251",
            "covers": "Russian, Ukrainian, Belarusian, Bulgarian, Serbian, Macedonian",
            "uppercase_range": "0xC0-0xDF",
            "lowercase_range": "0xE0-0xFF",
            "contains_ruble_sign": false,
            "esc_t_value": null,
            "todo_verify": true
          },
          {
            "name": "CP866",
            "covers": "Russian and Bulgarian fully; Ukrainian partially, carrying Ie and Yi but not Ghe-with-upturn or Byelorussian-Ukrainian I",
            "letter_ranges": ["0x80-0xAF", "0xE0-0xEF"],
            "box_drawing_range": "0xB0-0xDF",
            "note": "The box-drawing block is what makes its mojibake recognisable when Windows-1251 bytes are sent to it.",
            "esc_t_value": null,
            "todo_verify": true
          },
          {
            "name": "CP1125",
            "covers": "CP866 plus the missing Ukrainian letters",
            "esc_t_value": null,
            "todo_verify": true
          },
          {
            "name": "CP1131",
            "covers": "CP866 variant carrying Belarusian short u alongside Ukrainian letters",
            "esc_t_value": null,
            "todo_verify": true
          },
          {
            "name": "KOI8-R",
            "covers": "Russian and Bulgarian",
            "note": "Letters in pseudo-Latin order. Stripping the high bit yields a rough Latin transliteration, which is the design intent and a useful fingerprint.",
            "esc_t_value": null,
            "todo_verify": true
          },
          {
            "name": "KOI8-U",
            "covers": "KOI8-R plus Ukrainian",
            "esc_t_value": null,
            "todo_verify": true
          },
          {
            "name": "ISO-8859-5",
            "covers": "Slavic Cyrillic set",
            "note": "Rare in POS firmware; appears in data feeds.",
            "esc_t_value": null,
            "todo_verify": true
          },
          {
            "name": "Mac Cyrillic",
            "covers": "Slavic Cyrillic set",
            "note": "Rare; turns up in files from older Mac systems.",
            "esc_t_value": null,
            "todo_verify": true
          }
        ],
        "code_page_numbers_omitted_deliberately": {
          "value": true,
          "reason": "Same reason as Arabic and Hebrew: vendors renumber the code page table and clone firmware diverges from documented ordering while claiming compatibility."
        },
        "single_byte_pages_insufficient_beyond_slavic_set": {
          "value": true,
          "languages_not_covered": [
            "kk",
            "tt",
            "ba",
            "tg",
            "uz",
            "mn"
          ],
          "note": "Kazakhstan standardised an altered Windows-1251 variant (STRK1048) for this reason. For these markets the single-byte path is a dead end rather than a compromise.",
          "source": "official-authority"
        },
        "recommended_print_path": {
          "value": "code page is viable for the Slavic set once the family is confirmed; raster beyond it",
          "commands": [
            "GS v 0"
          ],
          "solves": [
            "code pages",
            "glyph coverage",
            "repertoire beyond the Slavic set",
            "ambiguous width"
          ],
          "costs": [
            "more data and slower printing",
            "printer font and size commands no longer apply",
            "resolution must match printer dot density"
          ]
        },
        "mojibake_signatures": [
          {
            "symptom": "box-drawing characters where capitals should be, wrong Cyrillic lowercase, stray numero, currency and square symbols",
            "cause": "Windows-1251 bytes into a CP866 printer",
            "recoverable_at_printer": true
          },
          {
            "symptom": "curly quotes, dashes, euro and per-mille where capitals should be",
            "cause": "CP866 bytes read as Windows-1251",
            "recoverable_at_printer": true
          },
          {
            "symptom": "two Latin-ish characters per intended letter",
            "cause": "UTF-8 bytes sent as single-byte",
            "recoverable_at_printer": true
          },
          {
            "symptom": "one question mark or blank per letter, ASCII unaffected",
            "cause": "transcoded to a page with no Cyrillic; the letters were destroyed at the encoder",
            "recoverable_at_printer": false
          },
          {
            "symptom": "one evenly spaced box per letter",
            "cause": "no Cyrillic ROM font; the code page is irrelevant",
            "recoverable_at_printer": false
          },
          {
            "symptom": "correct Russian but wrong or missing Ukrainian or Kazakh letters",
            "cause": "right code page family, wrong variant",
            "recoverable_at_printer": true
          }
        ],
        "fiscal_device_may_own_the_receipt": {
          "value": null,
          "note": "Several Cyrillic-script markets are understood to operate mandatory fiscal-device regimes in which the device, not the application, produces the receipt and therefore decides the code page, template and field order. Unresearched.",
          "source": "unverified",
          "todo_verify": true
        }
      },
      "currency": {
        "signs": [
          { "sign": "U+20BD", "iso_4217": "RUB", "note": "Added in Unicode 7.0 (2014), later than every code page listed above, so it is absent from all of them by construction rather than by chance." },
          { "sign": "U+20B8", "iso_4217": "KZT" },
          { "sign": "U+20B4", "iso_4217": "UAH" },
          { "sign": "U+20C0", "iso_4217": "KGS", "note": "Absent from Unicode 13.0; assume no ROM font carries it." },
          { "sign": "U+20AE", "iso_4217": "MNT" }
        ],
        "no_dedicated_sign": [
          { "iso_4217": "RSD", "written_as": "abbreviation or ISO code" }
        ],
        "traditional_abbreviations_still_common": {
          "value": true,
          "note": "Written abbreviations predate the signs, remain common in print, and are encodable in every code page above.",
          "source": "unverified",
          "todo_verify": true
        },
        "fallback": "print the ISO code where the sign cannot be rendered",
        "symbol_position": {
          "value": null,
          "source": "unverified",
          "todo_verify": true
        },
        "number_format": {
          "decimal_separator": ",",
          "group_separator": "space",
          "example": "1 234,56",
          "locales_checked": ["ru", "uk", "kk"],
          "source": "official-authority",
          "authority": "Unicode CLDR",
          "note": "The group separator is a space character, which is a line-breaking hazard as well as a formatting one. Use a no-break space and confirm it survives the print path."
        },
        "market_currency_changes": [
          {
            "market": "BG",
            "change": "euro adopted 2026-01-01 at 1.95583 BGN to 1 EUR; lev ceased to be legal tender 2026-02-01",
            "dual_display_period": "from 2025-08-08 to 2026-08-08 per secondary reporting, with one source giving 2026-06-30 instead",
            "source": "unverified",
            "todo_verify": true,
            "note": "Secondary reporting only, not confirmed against the Bulgarian authorities. There is no Bulgaria country file in this repository."
          }
        ]
      },
      "layout": {
        "measure_display_width_not_string_length": true,
        "wide_characters": false,
        "width_rule": "one column per character in a Western font context",
        "east_asian_width": {
          "standard": "Unicode UAX #11",
          "ambiguous_ranges": [
            "U+0401",
            "U+0410-U+044F",
            "U+0451"
          ],
          "narrow_or_neutral": "the rest of the Cyrillic block, including Ukrainian, Belarusian, Serbian, Kazakh and Mongolian letters",
          "note": "The Ambiguous set is exactly the 33-letter Russian alphabet. A printer with a CJK-derived ROM font may render those letters at two columns while the letters beside them stay at one, producing mixed widths inside a single word with an entirely correct encoding.",
          "source": "official-authority",
          "todo_verify": true
        },
        "paper_width_guidance": [
          {
            "width_mm": 80,
            "columns_font_a": 48,
            "description_columns": 35,
            "two_column_layout_viable": true
          },
          {
            "width_mm": 58,
            "columns_font_a": 32,
            "two_column_layout_viable": false,
            "note": "Cyrillic retail names run long with no compensating density, unlike CJK. Budget two lines for the name."
          }
        ],
        "line_breaking": {
          "hyphenation": false,
          "break_on": "whitespace only",
          "truncate_mid_word": true,
          "truncation_note": "Truncation changes no glyph, as in Hebrew; it is merely ugly."
        }
      },
      "digraphia": {
        "serbian": {
          "both_scripts_in_use": true,
          "note": "Cyrillic and Latin are both used for the same language and readers use both. Transliteration is close to letter-for-letter but not length-preserving: three Cyrillic letters map to Latin digraphs, so a round trip is not guaranteed.",
          "rule": "store what the customer is meant to see; do not convert silently at print time"
        },
        "localized_letterforms": {
          "value": true,
          "letters": ["be", "ghe", "de", "pe", "te"],
          "note": "Drawn differently in the Bulgarian and Serbian traditions from the Russian ones, handled in real fonts by language-tagged alternates. A thermal printer ROM font carries one set, normally the Russian shapes. Affects labels and signage rather than receipt legibility.",
          "source": "unverified",
          "todo_verify": true
        },
        "kazakhstan_latin_transition": {
          "value": null,
          "note": "Timetable has moved more than once. The POS implication does not depend on the date: both scripts will be present in real data for years, the same product will exist under two names, and search has to cross the scripts. Plan for two name fields rather than a conversion.",
          "source": "unverified",
          "todo_verify": true
        },
        "uzbekistan": "Latin official, Cyrillic still widely read",
        "mongolia": "Cyrillic with two letters outside Windows-1251"
      },
      "retail_regions": {
        "primary": [
          "Russia",
          "Belarus",
          "Ukraine",
          "Kazakhstan",
          "Kyrgyzstan",
          "Tajikistan",
          "Mongolia",
          "Serbia",
          "North Macedonia",
          "Montenegro",
          "Bulgaria",
          "Uzbekistan"
        ],
        "qualifications": [
          {
            "region": "all of the above",
            "note": "None is covered by a country file in this repository. Tax rates, fiscal device requirements, invoicing rules and mandatory receipt content are undocumented here.",
            "todo_verify": true
          },
          {
            "region": "Bulgaria",
            "note": "Changed currency in 2026; see currency.market_currency_changes.",
            "todo_verify": true
          }
        ],
        "legal_language_requirements": {
          "value": null,
          "source": "unverified",
          "todo_verify": true
        }
      },
      "deployment_pattern": {
        "independent_language_settings": [
          "till interface",
          "back office interface",
          "print template"
        ],
        "note": "Only the print template drives the code page and width concerns. The homoglyph, normalisation and collation concerns apply to the back office as well, and hardest there, because that is where product data is created. Chinese-owned businesses in these markets typically pair a Chinese back office with a local-language till and local-language receipts; a market in script transition may need three name fields."
      },
      "acceptance_test_requires_native_reader": {
        "value": true,
        "note": "A dropped breve leaves a different but still plausible word, and a mixed-script product name looks correct to everyone. Two of the tests in the file are data tests rather than print tests, and those are the ones a native reader looking at a sample receipt will pass without noticing."
      },
      "open_verification_items": {
        "en": 12,
        "zh": 8
      }
    },
    {
      "language": "Indic scripts",
      "group": "Brahmic",
      "covers": [
        "hi",
        "mr",
        "ne",
        "bn",
        "as",
        "ta",
        "te",
        "kn",
        "ml",
        "gu",
        "pa",
        "or",
        "si"
      ],
      "doc_en": "en/languages/indic.md",
      "doc_zh": "zh/languages/indic.md",
      "script": {
        "iso_15924": [
          "Deva",
          "Beng",
          "Taml",
          "Telu",
          "Knda",
          "Mlym",
          "Gujr",
          "Guru",
          "Orya",
          "Sinh"
        ],
        "direction": "ltr",
        "has_case": false,
        "whitespace_separated": true,
        "requires_contextual_shaping": true,
        "note": "Left to right, space separated, single width, caseless. The bidi, dictionary-wrapping and double-width problems of the Arabic, Thai and CJK files are all absent. The distinctive problem is that stored codepoint order and printed glyph order are different orders."
      },
      "reordering": {
        "is_the_distinctive_brahmic_problem": true,
        "note": "A prepended vowel sign is stored after the consonant it modifies and rendered before it. The reordering is performed by the shaping engine at render time and is not recorded in the stored string, so the start of a string does not predict the start of the printed line.",
        "verified_against": "Unicode Character Database 13.0.0 as distributed with CPython",
        "prepended_vowel_signs": [
          {
            "script": "Devanagari",
            "codepoint": "U+093F",
            "name": "DEVANAGARI VOWEL SIGN I",
            "general_category": "Mc",
            "combining_class": 0
          },
          {
            "script": "Bengali",
            "codepoint": "U+09BF",
            "name": "BENGALI VOWEL SIGN I",
            "general_category": "Mc",
            "combining_class": 0
          },
          {
            "script": "Tamil",
            "codepoint": "U+0BBF",
            "name": "TAMIL VOWEL SIGN I",
            "general_category": "Mc",
            "combining_class": 0
          },
          {
            "script": "Sinhala",
            "codepoint": "U+0DD9",
            "name": "SINHALA VOWEL SIGN KOMBUVA",
            "general_category": "Mc",
            "combining_class": 0
          }
        ],
        "two_part_vowels": {
          "note": "One logical vowel sign decomposes into two marks that render on opposite sides of the base consonant. No position in the visual line corresponds to the vowel.",
          "examples": [
            {
              "codepoint": "U+09CB",
              "name": "BENGALI VOWEL SIGN O",
              "decomposes_to": [
                "U+09C7",
                "U+09BE"
              ]
            },
            {
              "codepoint": "U+09CC",
              "name": "BENGALI VOWEL SIGN AU",
              "decomposes_to": [
                "U+09C7",
                "U+09D7"
              ]
            },
            {
              "codepoint": "U+0B4B",
              "name": "ORIYA VOWEL SIGN O",
              "decomposes_to": [
                "U+0B47",
                "U+0B3E"
              ]
            },
            {
              "codepoint": "U+0B4C",
              "name": "ORIYA VOWEL SIGN AU",
              "decomposes_to": [
                "U+0B47",
                "U+0B57"
              ]
            }
          ]
        }
      },
      "conjuncts": {
        "note": "Consonant plus virama plus consonant forms one fused glyph that has no byte value of its own and often resembles neither part. Glyph count is not codepoint count.",
        "verified_against": "Unicode Character Database 13.0.0 as distributed with CPython",
        "virama_combining_class": 9,
        "viramas": [
          { "script": "Devanagari", "codepoint": "U+094D", "name": "DEVANAGARI SIGN VIRAMA" },
          { "script": "Bengali", "codepoint": "U+09CD", "name": "BENGALI SIGN VIRAMA" },
          { "script": "Tamil", "codepoint": "U+0BCD", "name": "TAMIL SIGN VIRAMA" },
          { "script": "Telugu", "codepoint": "U+0C4D", "name": "TELUGU SIGN VIRAMA" },
          { "script": "Kannada", "codepoint": "U+0CCD", "name": "KANNADA SIGN VIRAMA" },
          { "script": "Malayalam", "codepoint": "U+0D4D", "name": "MALAYALAM SIGN VIRAMA" },
          { "script": "Gurmukhi", "codepoint": "U+0A4D", "name": "GURMUKHI SIGN VIRAMA" },
          { "script": "Gujarati", "codepoint": "U+0ACD", "name": "GUJARATI SIGN VIRAMA" },
          { "script": "Odia", "codepoint": "U+0B4D", "name": "ORIYA SIGN VIRAMA" },
          { "script": "Sinhala", "codepoint": "U+0DCA", "name": "SINHALA SIGN AL-LAKUNA" }
        ],
        "joiner_controls": {
          "zwnj": "U+200C",
          "zwj": "U+200D",
          "note": "Invisible characters that suppress or request conjunct formation. A sanitiser stripping non-printing characters removes them and silently changes the printed word.",
          "source": "unverified",
          "todo_verify": true
        }
      },
      "counting": {
        "note": "Bytes, codepoints, grapheme clusters and rendered width are four different numbers for the same text, all internally consistent and all disagreeing.",
        "truncation_unit": "grapheme cluster (UAX #29)",
        "measured_examples": [
          {
            "description": "Devanagari conjunct ksha",
            "codepoints": 3,
            "utf8_bytes": 9,
            "printed_glyphs": 1
          },
          {
            "description": "Devanagari cluster kshmee",
            "codepoints": 6,
            "utf8_bytes": 18,
            "printed_glyphs": 1
          },
          {
            "description": "Bengali syllable bo",
            "codepoints": 2,
            "utf8_bytes": 6,
            "printed_glyphs": "parts on both sides of the base"
          },
          { "description": "Tamil syllable ni", "codepoints": 2, "utf8_bytes": 6, "printed_glyphs": 2 }
        ],
        "truncating_on_codepoints_changes_the_word": true
      },
      "normalisation": {
        "form": "NFC",
        "nfc_lengthens_these_strings": true,
        "note": "The nukta letters are Unicode composition exclusions, so NFC decomposes them and does not recompose. Both spellings converge, which makes search work, but NFC adds codepoints rather than removing them. A column sized on the assumption that NFC never lengthens a string is an off-by-one.",
        "verified_against": "Unicode Character Database 13.0.0 as distributed with CPython",
        "composition_exclusions_checked": [
          {
            "precomposed": "U+0958",
            "name": "DEVANAGARI LETTER QA",
            "nfc_produces": [
              "U+0915",
              "U+093C"
            ]
          },
          {
            "precomposed": "U+0959",
            "name": "DEVANAGARI LETTER KHHA",
            "nfc_produces": [
              "U+0916",
              "U+093C"
            ]
          },
          {
            "precomposed": "U+095F",
            "name": "DEVANAGARI LETTER YYA",
            "nfc_produces": [
              "U+092F",
              "U+093C"
            ]
          },
          {
            "precomposed": "U+09DC",
            "name": "BENGALI LETTER RRA",
            "nfc_produces": [
              "U+09A1",
              "U+09BC"
            ]
          },
          {
            "precomposed": "U+09DF",
            "name": "BENGALI LETTER YYA",
            "nfc_produces": [
              "U+09AF",
              "U+09BC"
            ]
          },
          {
            "precomposed": "U+0A33",
            "name": "GURMUKHI LETTER LLA",
            "nfc_produces": [
              "U+0A32",
              "U+0A3C"
            ]
          },
          {
            "precomposed": "U+0A36",
            "name": "GURMUKHI LETTER SHA",
            "nfc_produces": [
              "U+0A38",
              "U+0A3C"
            ]
          },
          {
            "precomposed": "U+0B5C",
            "name": "ORIYA LETTER RRA",
            "nfc_produces": [
              "U+0B21",
              "U+0B3C"
            ]
          }
        ]
      },
      "printing": {
        "printers_accept_utf8": false,
        "code_page_route_available": {
          "value": false,
          "reasoning": "A code page maps a byte value to a ROM glyph, which can only express a script where each character is one glyph independent of its neighbours. Conjunct formation is context dependent and the fused glyph has no byte value, so the mechanism cannot reach it even given a complete ROM font.",
          "note": "This is an argument from the nature of a code page, not a survey of printers. Confirm against the target printer command reference.",
          "source": "unverified",
          "todo_verify": true
        },
        "raster_rendering_is_mandatory": true,
        "rom_font_present": { "value": null, "source": "unverified", "todo_verify": true },
        "raster_max_width_dots": { "value": null, "source": "unverified", "todo_verify": true },
        "shaping_engine_present_on_device": { "value": null, "source": "unverified", "todo_verify": true },
        "device_font_coverage": {
          "value": null,
          "note": "Total absence prints empty boxes and is visible. Partial coverage prints a correct-looking line with two wrong syllables and is not. Verify on the POS terminal, not the development machine.",
          "source": "unverified",
          "todo_verify": true
        }
      },
      "currency": {
        "signs": [
          { "codepoint": "U+20B9", "name": "INDIAN RUPEE SIGN" },
          { "codepoint": "U+20A8", "name": "RUPEE SIGN" },
          { "codepoint": "U+0BF9", "name": "TAMIL RUPEE SIGN" }
        ],
        "note": "Three distinct codepoints, not interchangeable. U+20B9 is absent from older fonts and from essentially every thermal printer ROM font. Several markets have a currency called rupee with different conventions; a shared name is not a shared symbol.",
        "printed_form_expected": { "value": null, "source": "unverified", "todo_verify": true }
      },
      "digit_grouping": {
        "indian_system_groups": "2-2-3",
        "western_system_groups": "3-3-3",
        "examples": [
          { "value": 100000, "western": "100,000", "indian": "1,00,000" },
          { "value": 1000000, "western": "1,000,000", "indian": "10,00,000" },
          { "value": 12345678, "western": "12,345,678", "indian": "1,23,45,678" }
        ],
        "note": "Recorded in CLDR for the relevant locales. Most formatting libraries default to 3-3-3 and produce a correct-looking, wrongly grouped figure. Grouping convention does not follow script, and a tax authority may specify a format that differs from shop-floor convention.",
        "applies_in_target_market": { "value": null, "source": "unverified", "todo_verify": true },
        "native_digits_in_supplier_data": { "value": null, "source": "unverified", "todo_verify": true }
      },
      "family_is_not_uniform": {
        "note": "Tamil forms far fewer conjuncts and is the most forgiving member; an implementation that passes Tamil can still fail Devanagari. Use Devanagari as the acceptance case.",
        "do_not_use_as_acceptance_test": "Tamil",
        "recommended_acceptance_test": "Devanagari"
      },
      "retail_regions": [
        "IN",
        "NP",
        "BD",
        "LK"
      ],
      "diaspora_regions_covered_elsewhere": [
        "MY",
        "ZA"
      ],
      "deployment_pattern": "Back office in one language, customer-facing receipt in another. Same pattern as the Arabic and Cyrillic files.",
      "tax_coverage": {
        "value": "none",
        "note": "This collection has no country file for India, Nepal, Bangladesh or Sri Lanka. Nothing has been checked against GST rules, invoice content rules or any e-invoicing mandate. The file covers text rendering only.",
        "source": "unverified",
        "todo_verify": true
      },
      "acceptance_test_requires_native_reader": true,
      "open_verification_items": { "en": 12, "zh": 9 }
    }
  ]
}