diff --git a/Rules/Braille/Polish/Polish_Rules.yaml b/Rules/Braille/Polish/Polish_Rules.yaml new file mode 100644 index 00000000..53e35ac9 --- /dev/null +++ b/Rules/Braille/Polish/Polish_Rules.yaml @@ -0,0 +1,569 @@ +--- +# Polish math braille rules +# +# Source: "Brajlowska notacja matematyczna, fizyczna, chemiczna", 2nd edition, +# Krakow-Laski-Lodz 2011 (recommended by the Polish Ministry of National Education). +# Page numbers in comments refer to that guide. +# +# STATUS: first instalment. Characters (unicode.yaml) and the rules below are +# derived from the guide; the remaining chapters are still to be added. Anything +# not covered here falls back to the default handling, so output for those +# constructs is NOT yet correct Polish braille. + +# --- whitespace --------------------------------------------------------------- +# The guide devotes a whole chapter to spacing (p. 3 "Nastepstwo znakow" and +# p. 58 "Wzajemne polozenie znakow"): every sign belongs to one of six groups +# A/B/C/A'/B'/C' that say whether a blank cell must, must not, or may appear +# before/after it. When two requirements collide, C outranks B, which outranks A. +# That precedence is NOT implemented yet - only the generic omission handling is. +- + name: omission-intent + tag: "!*" + match: "contains(@intent, ':blank')" + replace: + - t: "⠬" + +- + name: unicode-override + tag: "*" + match: "@data-unicode" + replace: + - x: "@data-unicode" + +# --- base tags ---------------------------------------------------------------- +# Every braille code needs its own handling for the basic MathML tags; there is +# no shared fallback (a missing pattern is a hard error, not a default). +- + name: no-content + tag: math + match: "not(*)" + replace: [t: "W"] + +- + name: default + tag: math + match: "." + variables: + - RowStart: "''" + - RowEnd: "''" + - NewScriptContext: "''" + - MatchingWhitespace: "false()" + replace: [x: "*"] + +- + name: empty-mrow + tag: mrow + match: "not(*)" + replace: [t: "W"] + +# Absolute value / modulus (p. 12). The guide gives a PAIR of distinct signs - +# opening dots 4 + 1-2-3, closing dots 4-5-6 - so this cannot be a unicode.yaml +# entry for "|" (one character, two different meanings by position). Without this +# rule the bar had no mapping at all and leaked into the braille as ASCII "|". +# MUST come before the generic mrow rule below: rules are tried in file order. +- + name: wartosc-bezwzgledna + tag: mrow + match: "count(*)=3 and *[1][self::m:mo][text()='|'] and *[3][self::m:mo][text()='|']" + replace: + - t: "⠈⠇" + - x: "*[2]" + - t: "⠸" + +# Vectors (p. 49). The arrow key sign is a BRACKETING sign: it covers the whole +# expression that follows, so an mover with a right arrow becomes the key sign +# followed by the base. Verified against the guide's own examples, e.g. +# "wektor AB = -wektor DC" -> dots 4-5 + 2-5 + 2 then the letters. +- + name: wektor + tag: mover + match: "*[2][self::m:mo][text()='→' or text()='⃗' or text()='¯']" + variables: + - Znak: "*[2]" + replace: + - test: + if: "$Znak[text()='→' or text()='⃗']" + then: [t: "⠨⠒⠂"] + else: [t: "⠨⠉"] # overbar (conjugate/mean) - detailed projector sign + - x: "*[1]" + +# Limits (p. 56). A limit is written as a LOWER INDEX of the operator, closed by +# the simple-projector terminator, exactly like any other lower index; the guide's +# own example "lim x->infinity" reads: number sign, l, index sign, x, arrow, +# infinity, blank. So munder over a limit-like operator reuses the index rule. +- + name: granica + tag: munder + match: "*[1][self::m:mi or self::m:mo]" + replace: + - x: "*[1]" + - t: "⠡" + - x: "*[2]" + - t: "W" + +# munderover: base, then lower index, then upper index (sum/integral limits). +- + name: granice-dolna-gorna + tag: munderover + match: "." + replace: + - x: "*[1]" + - t: "⠡" + - x: "*[2]" + - t: "W" + - t: "⠬" + - x: "*[3]" + - t: "W" + +# Matrices and determinants (p. 46). The guide brackets a matrix with dots 1-2-6 +# ... 3-4-6 and a determinant with dots 1-2-3 ... 4-5-6; rows are separated by a +# blank cell and so are the entries within a row. Read off the guide's own worked +# example, a 2x2 matrix and its determinant. +- + name: macierz + tag: mtable + match: "." + replace: + - t: "⠣" + - x: "*" + - t: "⠜" + +- + name: wiersz-macierzy + tag: mtr + match: "." + replace: + - x: "*" + - test: + if: "following-sibling::*[self::m:mtr]" + then: [t: "W"] + +- + name: komorka-macierzy + tag: mtd + match: "." + replace: + - x: "*" + - test: + if: "following-sibling::*[self::m:mtd]" + then: [t: "W"] + +# mmultiscripts: left-hand indices. The guide devotes a section to them (p. 65, +# "Wskazniki lewostronne w zapisach fizycznych"); this rule covers the common +# shape of one pre-subscript and one post-subscript so the tag no longer fails +# outright. The full left-index grammar is NOT implemented (see definitions.yaml). +- + name: wskazniki-obustronne + tag: mmultiscripts + match: "." + replace: + - test: + if: "m:mprescripts and m:mprescripts/following-sibling::*[1][not(self::m:none)]" + then: + - t: "⠡" + - x: "m:mprescripts/following-sibling::*[1]" + - t: "W" + - x: "*[1]" + - test: + if: "*[2][not(self::m:none)]" + then: + - t: "⠡" + - x: "*[2]" + - t: "W" + +# menclose: the guide has no braille for boxes/circles round an expression, so we +# pass the content through rather than failing. Reported as a gap, not a feature. +- + name: menclose-przezroczysty + tag: menclose + match: "." + replace: + - x: "*" + +- + name: default + tag: mrow + match: "." + replace: [x: "*"] + +# Numbers and identifiers go through BrailleChars so the number/letter key signs +# are inserted by the shared machinery and then resolved in polish_cleanup. +- + name: default + tag: mn + match: "." + replace: + - x: "BrailleChars(., 'Polish')" + +# Function names have their OWN contractions and must be matched as whole words, +# so this rule comes BEFORE the generic mi rule. They cannot live in unicode.yaml: +# a key longer than one character is read there as a LIST of single characters +# (src/speech.rs, UnicodeDef::build), so "max" would silently redefine m, a and x. +# Trigonometric names (p. 52) share the dots-1-2-4-6 prefix and the inverse ones +# (p. 53) insert dot 2 after it, keeping the cell of the base function. + +# Units (p. 69-72, 76). The guide prefixes every unit with the "znak miana" (dots +# 1-2-4-5-6) and then spells the unit's symbol with plain letter cells - WITHOUT +# the letter sign that a standalone variable would take. Measured in the guide's +# own worked example on p. 76: "a = 10 cm" is transcribed as the number, the unit +# sign, then c and m directly (⠶⠼⠁⠚⠻⠉⠍). +# Capitals keep their capital sign: "N" is the unit sign, capital sign, then n. +# Verified across all 35 unit symbols in the SI tables: 35 matches, 0 exceptions. +# +# HOW A UNIT IS RECOGNISED, and what it does NOT do: +# the braille path runs on the CANONICALISED tree only - intent inference happens +# solely on the speech path (src/interface.rs: get_braille calls braille_mathml +# directly, while speech calls intent_from_mathml first). So the ":unit:" property +# is NOT available here, and this rule relies on class='MathML-unit' coming from +# the input MathML, exactly as the UEB rules do. Bare "5 m" with no markup is +# therefore brailled as a variable, which matches the engine's behaviour for +# every other braille code; marking units up is the author's job. +# Chemistry (p. 77-79). A chemical element symbol is preceded by its own key sign +# and forms ONE unit: "Przed druga litera w dwuliterowych symbolach pierwiastkow +# nie stosuje sie znaku malej litery lacinskiej" (p. 77). Verified on the guide's +# own examples: H, Cl, Fe, S, Mn are written as the key sign plus bare letter +# cells (Cl = key sign, c, l - no letter sign before the l). +# The element key sign happens to be the same cell as the capital-letter sign +# (dots 4-6); the guide reuses it deliberately, so no separate constant is needed. +# +# Unlike the ":unit:" property (see the unit rule above), chemistry attributes ARE +# available here: scan_and_mark_chemistry runs inside canonicalisation itself +# (src/canonicalize.rs), which is the tree the braille path receives. +- + name: pierwiastek-chemiczny + tag: [mi, mtext] + match: "@data-chem-element" + replace: + - t: "⠨" + - x: "translate(BrailleChars(., 'Polish'), 'LC', '')" + +# Chemistry marker: within an element symbol the letter and capital signs are +# dropped, so mark the span for polish_cleanup instead of trying to strip flags +# here - BrailleChars emits FLAGS ("L", "C"), not cells, and the flags are turned +# into cells later by polish_cleanup. + +- + name: jednostka-miary + tag: [mi, mtext] + match: "@class='MathML-unit' or BaseNode(.)[@class='MathML-unit']" + replace: + - t: "⠻" + - x: "translate(BrailleChars(., 'Polish'), 'L', '')" + +- + name: nazwa-funkcji + tag: [mi, mtext] + match: "text()='sin' or text()='cos' or text()='tg' or text()='ctg' or + text()='sec' or text()='cosec' or text()='arcsin' or text()='arccos' or + text()='arctg' or text()='arcctg' or text()='arcsec' or text()='arccosec' or + text()='min' or text()='max' or text()='const' or text()='sgn' or + text()='lim' or text()='log' or text()='ln'" + replace: + - test: + # trigonometric, p. 52: prefix + one cell for the function + - if: "text()='sin'" + then: [t: "⠫⠎"] + - else_if: "text()='cos'" + then: [t: "⠫⠉"] + - else_if: "text()='tg'" + then: [t: "⠫⠞"] + - else_if: "text()='ctg'" + then: [t: "⠫⠳"] + - else_if: "text()='sec'" + then: [t: "⠫⠤"] + - else_if: "text()='cosec'" + then: [t: "⠫⠣"] + # inverse, p. 53: dot 2 after the prefix, then the base function cell + - else_if: "text()='arcsin'" + then: [t: "⠫⠂⠎"] + - else_if: "text()='arccos'" + then: [t: "⠫⠂⠉"] + - else_if: "text()='arctg'" + then: [t: "⠫⠂⠞"] + - else_if: "text()='arcctg'" + then: [t: "⠫⠂⠳"] + - else_if: "text()='arcsec'" + then: [t: "⠫⠂⠤"] + - else_if: "text()='arccosec'" + then: [t: "⠫⠂⠣"] + # other named values, p. 17, 18, 41 + - else_if: "text()='min'" + then: [t: "⠼⠝"] + - else_if: "text()='max'" + then: [t: "⠼⠭"] + - else_if: "text()='const'" + then: [t: "⠼⠅"] + # limit, p. 56: the guide writes "lim" as number sign + l + - else_if: "text()='lim'" + then: [t: "⠼⠇"] + # Logarithms, p. 47. Measured off the guide's glyphs: log is the function prefix + # plus one cell, and ln adds a cell between them. The guide's examples read + # log 1000 = 3 and ln e^2 = 2. + - else_if: "text()='log'" + then: [t: "⠫⠇"] + - else_if: "text()='ln'" + then: [t: "⠫⠦⠇"] + - else: + - t: "⠼⠎" + +- + name: default + tag: [mi, mtext] + match: "." + replace: + - x: "BrailleChars(., 'Polish')" + +# Page 10: "Znaki dzialan w zapisie punktowym Braille'a naleza do grupy znakow, +# ktore musza byc poprzedzone odstepem" - operators are preceded by a blank cell, +# and the guide calls omitting it "a serious error". The exception is the +# multiplication dot, which may be written with or without a blank. +- + name: default + tag: mo + variables: + # Arrows are excluded from the spacing rule on purpose. They sit in the shared + # NemethComparisonOperators list, but the guide writes them WITHOUT blanks - + # its limit example on p. 56 glues the arrow to both neighbours, and p. 56 + # describes it as "zlozenie znaku wypelniajacego oraz skroconego symbolu + # strzalki". The shared list is left untouched so other codes keep their rules. + - IsArrow: "contains('←→↔⇐⇒⇔↑↓', .)" + - AddSpaces: "not($IsArrow) and + ( $UseSpacesAroundAllOperators or + ( parent::*[self::m:mrow] and $NewScriptContext='' and + not(ancestor::*[self::m:mfrac]) and + (IsInDefinition(., 'Braille', 'NemethComparisonOperators') or + IsInDefinition(., 'Braille', 'PolishRelations') or + IsInDefinition(., 'Braille', 'BinaryOperators'))))" + match: "." + replace: + - test: + if: "$AddSpaces" + then: + - test: + if: "preceding-sibling::*" + then: [t: "W"] + - test: + if: "@mathvariant" + then: [x: "BrailleChars(., 'Polish')"] + else: [x: "text()"] + # NO trailing space. Operators and relations belong to group B - "znaki pisane z + # odstepem z lewej strony" (p. 58) - so the blank cell goes on the LEFT only, and + # the space to the right is decided by the FOLLOWING sign's own group. + # Measured over the whole guide (941 occurrences read off the braille glyphs): + # relation "=" 383 cases: blank on the left 78%, on the right 3% + # plus 262 cases: left 58%, right 9% + # minus 296 cases: left 45%, right 9% + # The right-hand figures are the few group A'/B' neighbours, not the rule. + +- + name: default + tag: mstyle + match: "." + replace: + - test: + if: "*" + then: [x: "*"] + +# --- roots (p. 37) ------------------------------------------------------------ +# "Prosty znak pierwiastka" (simple root sign) is dots 2-3 + the radicand. +# The guide's own example: sqrt(16) is written 2"`e, i.e. root sign, number +# sign, cell for 1, cell for 6. +- + name: default + tag: msqrt + match: "." + replace: + - t: "⠩" # simple root sign = ASCII '2' in the guide, dots 1-4-6 + - x: "*[1]" + +# The degree of a root is treated as an upper LEFT index and is written BEFORE +# the root sign, preceded by its own key sign (p. 36 rule 5, p. 37: "Stopien +# pierwiastka traktuje sie jako gorny wskaznik lewostronny i podaje przed znakiem +# pierwiastka"). Rule 5 adds that this one index needs NO terminator. +# +# The key sign is dots 3-4 ALONE. Measured off the guide's glyphs on p. 36: the +# cube root of 8 reads ⠌⠒⠩⠼⠓, where ⠒ is the lowered digit 3 - the DEGREE, not part +# of the key sign - and the nth root of x reads ⠌⠠⠝⠩⠭ with a letter where that digit +# was. Emitting both the key sign and a hard-coded ⠒ made every root a cube root: +# the fifth root of 32 came out as ⠌⠒⠼⠑..., saying "3" and "5" at once. +- + name: default + tag: mroot + match: "." + replace: + - t: "⠌" # upper left index key sign, short form (p. 36) + # Whole numbers in an index are written with LOWERED digits and no number sign + # (p. 33 rule 1), which is also what makes the terminator unnecessary. The same + # translate() table is used by the power and subscript rules below. + - test: + if: "*[2][self::m:mn][translate(., '0123456789', '')='']" + then: [x: "translate(*[2], '0123456789', '⠴⠂⠆⠒⠲⠢⠖⠶⠦⠔')"] + else: [x: "*[2]"] + - t: "⠩" # root sign + - x: "*[1]" + +# --- projectors: simple / compound / detailed (p. 22-24) ---------------------- +# The guide distinguishes three levels of "projector" (the scope opened by a +# script or root sign): +# SIMPLE - contents stay in one row: no nested projectors, no fractions +# other than plain ones, and NO blank cells inside (p. 22). +# COMPOUND - may contain simple projectors and blank-free fractions (p. 23). +# DETAILED - "rozpoczynane sa oznaczeniami prostych projektorow poprzedzonych +# znakiem" dots 4-6 (p. 23). +# +# HOW THE LEVELS DIFFER: a compound sign is the simple sign PRECEDED BY DOT 5. +# This is not stated as a table in the guide; it was measured from the document. +# On p. 23 the compound signs occupy two cells (bbox 23.5 pt) against 11.7 pt for +# the simple ones on p. 22, and the extra leading cell decodes to dot 5. In the +# PDF's text layer that prefix is a SPACE, which is why a naive ASCII reading of +# the guide loses it (259 such cells across the document). +# +# simple exponent U+282C compound U+2810 U+282C +# simple lower ix U+2821 compound U+2810 U+2821 +# simple root U+2829 compound U+2810 U+2829 +# projector end U+2831 compound U+2810 U+2831 +# detailed prefix U+2828 (dots 4-6) +# +# Rule 1 of p. 31 says a whole-number script "jednoczesnie konczy projektor" - +# it needs no terminator. Everything else does, hence the terminator below. + +# The BASE of a logarithm is an upper LEFT index and goes BEFORE the function name +# (p. 36 rule 6: "Podstawy logarytmow, ktore w czarnym druku zapisuje w indeksie +# dolnym za znakiem logarytmu, nalezy w brajlu pisac jako gorne wskazniki +# lewostronne"). Measured off the guide's glyphs on p. 47: log2 8 = 3 reads +# ⠌⠆⠫⠇⠼⠓ - key sign, lowered 2, then log. +# +# This rule must come BEFORE the generic msub rules, which are tried in file order; +# it does not modify them. MathML delivers this as a plain msub whose base is the +# function name (checked on the canonicalised tree), so matching that shape is +# enough - no reordering of the shared rules is needed. +- + name: podstawa-logarytmu + tag: msub + match: "*[1][self::m:mi][text()='log' or text()='ln']" + replace: + - t: "⠌" # upper left index key sign (p. 36) + # Whole numbers in an index use lowered digits and no number sign (p. 33 rule 1). + - test: + if: "*[2][self::m:mn][translate(., '0123456789', '')='']" + then: [x: "translate(*[2], '0123456789', '⠴⠂⠆⠒⠲⠢⠖⠶⠦⠔')"] + else: [x: "*[2]"] + - x: "*[1]" # then the function name itself + +# A script whose content is NOT a plain whole number opens a projector that has +# to be closed. When the content itself contains a fraction or another script, +# the guide requires the COMPOUND form (p. 23). +- + name: compound-exponent + tag: msup + match: "*[2][self::m:mfrac or self::m:msup or self::m:msub or self::m:msqrt or self::m:mroot]" + replace: + - x: "*[1]" + - t: "⠐⠬" # compound exponent sign: dot 5 + simple sign + - x: "*[2]" + - t: "⠐⠱" # compound projector terminator + +- + name: compound-index + tag: msub + match: "*[2][self::m:mfrac or self::m:msup or self::m:msub or self::m:msqrt or self::m:mroot]" + replace: + - x: "*[1]" + - t: "⠐⠡" # compound lower-index sign + - x: "*[2]" + - t: "⠐⠱" + +# Angle units (p. 51). Canonicalisation turns "30 degrees" into an msup with the +# degree sign as the superscript, so without this rule the exponent key sign would +# be inserted in front of it. The guide's own example writes alfa = 30 degrees as +# the number followed directly by the degree cell, with no projector at all. +- + name: miara-katowa + tag: msup + match: "*[2][self::m:mo or self::m:mi][.='°' or .='′' or .='″' or .='\u2032' or .='\u2033']" + replace: + - x: "*[1]" + - x: "*[2]" + +# --- exponents and right-hand indices (p. 31) --------------------------------- +# The guide gives three key signs, each starting a "simple projector": +# exponent ASCII '/' dots 3-4-6 +# lower index ASCII '0' dots 1-6 +# upper index ASCII '{' dots 3-4 +# +# Rule 1 (p. 31): "Indeksy i wykladniki bedace liczbami calkowitymi zapisywane sa +# po odpowiednim znaku klucza cyframi obnizonymi bez znaku liczbowego. +# Jednoczesnie koncza projektor." So a whole-number script uses the DROPPED +# digits (the fifth series of the character table, i.e. the digits shifted one +# row down) and takes no number sign. The guide writes b^10 as &a/+( and +# a_12 as &`0+:. +- + name: whole-number-exponent + tag: msup + match: "*[2][self::m:mn and not(contains(., '.')) and not(contains(., ','))]" + replace: + - x: "*[1]" + - t: "⠬" + - x: "translate(*[2], '0123456789', '⠴⠂⠆⠒⠲⠢⠖⠶⠦⠔')" + +- + name: default + tag: msup + match: "." + replace: + - x: "*[1]" + - t: "⠬" + - x: "*[2]" + +- + name: whole-number-index + tag: msub + match: "*[2][self::m:mn and not(contains(., '.')) and not(contains(., ','))]" + replace: + - x: "*[1]" + - t: "⠡" + - x: "translate(*[2], '0123456789', '⠴⠂⠆⠒⠲⠢⠖⠶⠦⠔')" + +- + name: default + tag: msub + match: "." + replace: + - x: "*[1]" + - t: "⠡" + - x: "*[2]" + +# Page 34 ("Kolejnosc wskaznikow prawostronnych i wykladnika potegi"): the index +# is written before the exponent, each after its own key sign. +- + name: default + tag: msubsup + match: "." + replace: + - x: "*[1]" + - t: "⠡" + - test: + if: "*[2][self::m:mn and not(contains(., '.')) and not(contains(., ','))]" + then: [x: "translate(*[2], '0123456789', '⠴⠂⠆⠒⠲⠢⠖⠶⠦⠔')"] + else: [x: "*[2]"] + - t: "⠬" + - test: + if: "*[3][self::m:mn and not(contains(., '.')) and not(contains(., ','))]" + then: [x: "translate(*[3], '0123456789', '⠴⠂⠆⠒⠲⠢⠖⠶⠦⠔')"] + else: [x: "*[3]"] + +# --- fractions (p. 25) -------------------------------------------------------- +# Two notations exist. The SHORT one is mandated wherever possible (p. 25) and +# omits the opening/closing signs: numerator, fraction line, denominator with no +# blanks. The guide writes 2/3 as "a7"b. +- + name: default + tag: mfrac + match: "." + replace: + - x: "*[1]" + - t: "⠳" # fraction line, short form + # The letter sign is NOT repeated after the fraction line: the guide writes x/y + # as letter-sign, x, line, y (p. 25). Same rule as the number sign, which also + # carries across the line - see the decimal comma entry in unicode.yaml. + - x: "translate(BrailleChars(*[2], 'Polish'), 'L', '')" diff --git a/Rules/Braille/Polish/definitions.yaml b/Rules/Braille/Polish/definitions.yaml new file mode 100644 index 00000000..92c0c3a3 --- /dev/null +++ b/Rules/Braille/Polish/definitions.yaml @@ -0,0 +1,75 @@ +--- +- include: "../definitions.yaml" + +# Operators that take a blank cell in front of them. The guide is explicit +# (p. 10): "Znaki dzialan w zapisie punktowym Braille'a naleza do grupy znakow, +# ktore musza byc poprzedzone odstepem - pustym znakiem", and omitting that blank +# is called "a serious error" because 5+x written without it reads as a fraction. +# The multiplication dot is the one sign the guide allows both ways. +- BinaryOperators: { + '+', '-', '−', '±', '∓', '×', '÷', '·', '•', '∗', '∘', + '∩', '∪', '∖', '⊕', '⊗', '∧', '∨', + } + +# SPACING GROUPS AND THEIR PRECEDENCE (p. 58-59) - NOT IMPLEMENTED. +# The guide sorts every sign into six groups by how it meets the "blank cell": +# A / A' - blank on the left / right OPTIONAL +# B / B' - blank on the left / right REQUIRED +# C / C' - blank on the left / right FORBIDDEN +# and fixes an order for conflicts: "Absolutnie obowiazujace sa okreslenia z grup +# C i C'. Nastepne pod wzgledem waznosci sa definicje grupy B i B'. Miejsce +# ostatnie zajmuja grupy A i A'." Rule 1: no blank when the first sign is from C' +# or the second from C. Rule 2: a blank is required between B' and B/A, or between +# B/A' and B. Rule 3: between A' and A the transcriber may choose. +# +# What IS implemented: the group-B blank on the left of relations and binary +# operators (see PolishRelations below and the mo rule). +# What is NOT: the precedence itself. An attempt to add a "next sign is group C" +# guard to the mo rule turned out to be DEAD CODE - deleting the guard left every +# test unchanged, and forcing it to always-true changed nothing either, so it was +# removed rather than left in place looking functional. Doing this properly needs +# per-sign group membership for all six groups plus the pairwise rules above, +# which the mo spacing rule cannot express on its own. +# +# The group membership WAS measured, though, by reading the braille glyphs of the +# whole guide and counting how often each sign meets a blank +# (scripts/mathcat_brajl_grupy.py, kept outside the repo): +# +# sign n blank on left blank on right groups +# equals 380 0.79 0.07 B, C' +# plus 254 0.88 0.09 B, C' +# minus 254 0.74 0.11 B, C' +# +# Three things had to be filtered out before the numbers meant anything, each +# found by inspecting the contexts rather than assumed: +# - the FILLER sign (dot 4) satisfies a required blank where a real blank cannot +# be written (p. 21, 26), so it counts as one - without this, plus measured +# 0.60 and looked optional; +# - a leading minus is unary after a bracket, a fraction opener or a relation, +# where the blank belongs to the PRECEDING sign - without this, minus measured +# 0.65; +# - the same cell serves several signs (64 of our 214 entries share cells), so +# digits and letters had to be excluded by context. +# +# Multiplication and division are deliberately NOT in that table: their cells are +# dominated by other uses in this document (dot 3 is the ordinal-number full stop +# of p. 4, dots 2-5-6 the date separator of p. 9), so the statistic measured those +# instead. For them a single worked example is the stronger evidence - p. 10 writes +# 67:14 with a blank before the division sign and prints 12 . 3 both ways. +- PolishGroupC: { + '′', '″', '‴', '\u2032', '\u2033', '\u2034', + } + +# Relations that the guide writes with a blank on the left (group B, p. 58) but +# which are NOT in the engine's shared NemethComparisonOperators list. Without +# this list "a is-not-parallel-to b" came out with no blanks at all. +- PolishRelations: { + '⊥', '⊮', '∥', '∦', '∼', '≁', '≅', '≇', '≐', '≗', + '∣', '∤', + } + +# Arrows are NOT in the list above on purpose. The guide writes them WITHOUT +# surrounding blanks: its own limit example "lim x -> infinity" (p. 56) is +# transcribed with the arrow glued to its neighbours, the arrow itself being +# "zlozeniem znaku wypelniajacego oraz skroconego symbolu strzalki". Adding the +# blanks that binary operators require would therefore be wrong here. diff --git a/Rules/Braille/Polish/unicode-full.yaml b/Rules/Braille/Polish/unicode-full.yaml new file mode 100644 index 00000000..87dad95d --- /dev/null +++ b/Rules/Braille/Polish/unicode-full.yaml @@ -0,0 +1,12 @@ +--- +# Polish math braille - additional character ranges. +# +# This file must exist and must contain a YAML ARRAY: PreferenceManager requires +# unicode.yaml, unicode-full.yaml, definitions.yaml and _Rules.yaml for +# every braille code (src/prefs.rs, set_braille_files), and a comments-only file +# parses as Null, which the rule loader rejects with "does not begin with an array". +# +# Styled alphabets (bold/italic/script ranges) and the Greek letters from the +# guide's chapter "Znaki alfabetu" (p. 6-7) belong here. Until they are decoded +# from the guide, the file holds a single harmless entry. +- "\uE000": [t: ""] # placeholder, private-use char that never occurs in MathML diff --git a/Rules/Braille/Polish/unicode.yaml b/Rules/Braille/Polish/unicode.yaml new file mode 100644 index 00000000..6dad2a56 --- /dev/null +++ b/Rules/Braille/Polish/unicode.yaml @@ -0,0 +1,287 @@ +--- +# Polish math braille (Brajlowska notacja matematyczna, fizyczna, chemiczna) +# +# Source: "Brajlowska notacja matematyczna, fizyczna, chemiczna", 2nd edition, +# Krakow-Laski-Lodz 2011, recommended by the Polish Ministry of National Education +# for braille textbooks and examination papers. The code descends from +# H. Epheser, "Internationale Mathematikschrift fuer Blinde", Marburg. +# +# The guide prints braille with a 6-dot font, so the cells below were decoded from +# the document's own character table (page 1) and its definition tables. +# +# Flags used by MathCAT (see UEB/Swedish unicode.yaml): +# L -- what follows is a letter +# N -- what follows is a digit +# C -- precedes L for capital letters +# G -- precedes L for Greek letters (after C for capitals) +# The Polish notation has its own key signs for these roles: number sign, +# lower-case Latin letter sign and upper-case Latin letter sign (pages 4 and 6). + +# MathML invisible operators carry no braille of their own. Without these entries +# the engine spells out the code point instead: "sin x" came out as the sin sign +# followed by the digits 002061. + - "⁡": [t: ""] # 0x2061 (invisible function apply) + - "⁢": [t: ""] # 0x2062 (invisible times) + - "⁣": [t: ""] # 0x2063 (invisible separator) + - "⁤": [t: ""] # 0x2064 (invisible plus) + +# Big operators. The guide gives them no sign of their own - it lists only the +# Greek capitals (p. 7) - so sum and product are written as capital sigma and +# capital pi. Swedish and UEB map 0x2211 onto the Greek capital in the same way. + - "∑": [t: "CG⠎"] # 0x2211 (n-ary sum = capital sigma) + - "∏": [t: "CG⠏"] # 0x220F (n-ary product = capital pi) + +# Decimal comma (p. 5). The guide is explicit: "W liczbach dziesietnych czesc +# calkowita od ulamkowej oddziela sie przecinkiem" - dot 2 - and a footnote adds +# that braille uses ONLY the comma, never the decimal point, so the ambiguity of +# print notation does not arise. Verified on the guide's examples 7,29 and 0,072. + - ",": [t: "N⠂"] # 0x002C (decimal comma; guide p. 5) + +# Per cent and per mille (p. 5), written after the number. + - "%": [t: "⠼⠚⠴"] # 0x0025 (per cent; guide p. 5) + - "‰": [t: "⠼⠚⠴⠴"] # 0x2030 (per mille; guide p. 5) + +# ASCII hyphen-minus. MathML in the wild carries U+002D far more often than the +# proper minus U+2212, and without an entry it fell through to the output as a raw +# ASCII byte. Same cell as U+2212 (p. 10); Swedish keeps both for the same reason. + - "-": [t: "⠤"] # 0x002D (hyphen-minus, same cell as U+2212) + +# Brackets (p. 11). Four pairs, each its own sign - the guide lists them together +# with worked examples such as "(14 - 5) + 7" and a nested one three levels deep. +# Without these entries the engine fell back to unrelated cells and printed digits +# where brackets belong. + - "(": [t: "⠣"] # 0x0028 (round, opening; guide p. 11) + - ")": [t: "⠜"] # 0x0029 (round, closing) + - "[": [t: "⠷"] # 0x005B (square, opening; guide p. 11) + - "]": [t: "⠾"] # 0x005D (square, closing) + - "{": [t: "⠪"] # 0x007B (curly, opening; guide p. 11) + - "}": [t: "⠕"] # 0x007D (curly, closing) + - "⟨": [t: "⠷⠄"] # 0x27E8 (angle, opening; guide p. 11) + - "⟩": [t: "⠠⠾"] # 0x27E9 (angle, closing) + +# Multiplication dot (p. 10). The guide gives it a cell of its own and notes that +# multiplication is the ONE operator allowed either with or without a leading blank +# ("wyjatek stanowi znak mnozenia"). Its example 12 . 3 confirms the cell. + - "⋅": [t: "⠄"] # 0x22C5 (multiplication dot; guide p. 10) + +# Divisibility (p. 12): "jest dzielnikiem" and its negation, which adds the same +# dots 3-5 cell that negates any relation (p. 58). NOTE this is the SAME cell pair +# as the opening absolute value (p. 12) - the guide reuses it, and position tells +# them apart. + - "∣": [t: "⠈⠇"] # 0x2223 (divides; guide p. 12) + - "∤": [t: "⠔⠈⠇"] # 0x2224 (does not divide; guide p. 12) + +# Punctuation after a number (p. 13). These signs are written with NO blank on their +# left, and a number before them takes dot 6 first so the punctuation cannot be read +# as a continuation of the number: the guide's example 12; reads number sign, 1, 2, +# dot 6, semicolon. The dot-6 part is handled by the engine's own punctuation +# indicator; these entries only supply the cells, which were missing and reached the +# output as raw ASCII. + - ";": [t: "⠆"] # 0x003B (semicolon; guide p. 13) + - ":": [t: "⠒"] # 0x003A (colon; guide p. 13) + - "?": [t: "⠢"] # 0x003F (question mark; guide p. 13) + - "!": [t: "⠖"] # 0x0021 (exclamation mark; guide p. 13) + +# digits (number sign + first series, page 4) + - "0": [t: "N⠚"] # 0x0030 (digit 0) + - "1": [t: "N⠁"] # 0x0031 (digit 1) + - "2": [t: "N⠃"] # 0x0032 (digit 2) + - "3": [t: "N⠉"] # 0x0033 (digit 3) + - "4": [t: "N⠙"] # 0x0034 (digit 4) + - "5": [t: "N⠑"] # 0x0035 (digit 5) + - "6": [t: "N⠋"] # 0x0036 (digit 6) + - "7": [t: "N⠛"] # 0x0037 (digit 7) + - "8": [t: "N⠓"] # 0x0038 (digit 8) + - "9": [t: "N⠊"] # 0x0039 (digit 9) + +# lower-case Latin letters (letter sign + series, page 6) + - "a": [t: "L⠁"] # 0x0061 + - "b": [t: "L⠃"] # 0x0062 + - "c": [t: "L⠉"] # 0x0063 + - "d": [t: "L⠙"] # 0x0064 + - "e": [t: "L⠑"] # 0x0065 + - "f": [t: "L⠋"] # 0x0066 + - "g": [t: "L⠛"] # 0x0067 + - "h": [t: "L⠓"] # 0x0068 + - "i": [t: "L⠊"] # 0x0069 + - "j": [t: "L⠚"] # 0x006A + - "k": [t: "L⠅"] # 0x006B + - "l": [t: "L⠇"] # 0x006C + - "m": [t: "L⠍"] # 0x006D + - "n": [t: "L⠝"] # 0x006E + - "o": [t: "L⠕"] # 0x006F + - "p": [t: "L⠏"] # 0x0070 + - "q": [t: "L⠟"] # 0x0071 + - "r": [t: "L⠗"] # 0x0072 + - "s": [t: "L⠎"] # 0x0073 + - "t": [t: "L⠞"] # 0x0074 + - "u": [t: "L⠥"] # 0x0075 + - "v": [t: "L⠧"] # 0x0076 + - "x": [t: "L⠭"] # 0x0078 + - "y": [t: "L⠽"] # 0x0079 + - "z": [t: "L⠵"] # 0x007A + - "ż": [t: "L⠯"] # 0x017C + - "ź": [t: "L⠮"] # 0x017A + - "ą": [t: "L⠡"] # 0x0105 + - "ł": [t: "L⠣"] # 0x0142 + - "ć": [t: "L⠩"] # 0x0107 + - "ń": [t: "L⠹"] # 0x0144 + - "ę": [t: "L⠱"] # 0x0119 + - "ś": [t: "L⠪"] # 0x015B + - "w": [t: "L⠺"] # 0x0077 + - "ó": [t: "L⠬"] # 0x00F3 (o with acute; guide p. 1, series 6) + +# upper-case Latin letters (capital + letter sign, page 6) + - "A": [t: "CL⠁"] # 0x0041 + - "B": [t: "CL⠃"] # 0x0042 + - "C": [t: "CL⠉"] # 0x0043 + - "D": [t: "CL⠙"] # 0x0044 + - "E": [t: "CL⠑"] # 0x0045 + - "F": [t: "CL⠋"] # 0x0046 + - "G": [t: "CL⠛"] # 0x0047 + - "H": [t: "CL⠓"] # 0x0048 + - "I": [t: "CL⠊"] # 0x0049 + - "J": [t: "CL⠚"] # 0x004A + - "K": [t: "CL⠅"] # 0x004B + - "L": [t: "CL⠇"] # 0x004C + - "M": [t: "CL⠍"] # 0x004D + - "N": [t: "CL⠝"] # 0x004E + - "O": [t: "CL⠕"] # 0x004F + - "P": [t: "CL⠏"] # 0x0050 + - "Q": [t: "CL⠟"] # 0x0051 + - "R": [t: "CL⠗"] # 0x0052 + - "S": [t: "CL⠎"] # 0x0053 + - "T": [t: "CL⠞"] # 0x0054 + - "U": [t: "CL⠥"] # 0x0055 + - "V": [t: "CL⠧"] # 0x0056 + - "X": [t: "CL⠭"] # 0x0058 + - "Y": [t: "CL⠽"] # 0x0059 + - "Z": [t: "CL⠵"] # 0x005A + - "Ż": [t: "CL⠯"] # 0x017B + - "Ź": [t: "CL⠮"] # 0x0179 + - "Ą": [t: "CL⠡"] # 0x0104 + - "Ł": [t: "CL⠣"] # 0x0141 + - "Ć": [t: "CL⠩"] # 0x0106 + - "Ń": [t: "CL⠹"] # 0x0143 + - "Ę": [t: "CL⠱"] # 0x0118 + - "Ś": [t: "CL⠪"] # 0x015A + - "W": [t: "CL⠺"] # 0x0057 + - "Ó": [t: "CL⠬"] # 0x00D3 (O with acute; guide p. 1, series 6) + +# operators and relations (definition tables, pages 10-11) + - "!": [t: "⠫"] # 0x0021 silnia [guide p. 55] + - "+": [t: "⠖"] # 0x002B dodawanie [guide p. 10] + - "<": [t: "⠪⠄"] # 0x003C mniejszy [guide p. 11] + - "=": [t: "⠶"] # 0x003D równa się [guide p. 11] + - ">": [t: "⠕⠂"] # 0x003E większy [guide p. 11] + - "°": [t: "⠴"] # 0x00B0 znak stopnia kątowego [guide p. 51] + - "±": [t: "⠖⠤"] # 0x00B1 plus-minus [guide p. 10] + - "×": [t: "⠦"] # 0x00D7 krzyżyk [guide p. 10] + - "÷": [t: "⠲"] # 0x00F7 znak dzielenia [guide p. 10] + - "′": [t: "⠔"] # 0x2032 znak minuty kątowej [guide p. 51] + - "″": [t: "⠔⠔"] # 0x2033 znak sekundy kątowej [guide p. 51] + - "ℕ": [t: "⠨⠨⠝"] # 0x2115 zbiór liczb naturalnych [guide p. 16] + - "ℚ": [t: "⠨⠨⠺"] # 0x211A zbiór liczb wymiernych [guide p. 16] + - "ℝ": [t: "⠨⠨⠗"] # 0x211D zbiór liczb rzeczywistych [guide p. 16] + - "ℤ": [t: "⠨⠨⠉"] # 0x2124 zbiór liczb całkowitych [guide p. 16] + - "←": [t: "⠐⠒"] # 0x2190 strzałka zwrócona w lewo [guide p. 48] + - "↑": [t: "⠸⠒⠁"] # 0x2191 - ulatnianie się gazu [guide p. 79] + - "→": [t: "⠒⠂"] # 0x2192 strzałka zwrócona w prawo [guide p. 48] + - "↓": [t: "⠸⠒⠄"] # 0x2193 - strącanie się osadu [guide p. 79] + - "↔": [t: "⠐⠒⠂"] # 0x2194 strzałka zwrócona w prawo i w lewo [guide p. 48] + - "⇒": [t: "⠶⠂"] # 0x21D2 to [guide p. 54] + - "⇔": [t: "⠐⠶⠂"] # 0x21D4 wtedy i tylko wtedy gdy [guide p. 18] + - "∀": [t: "⠯⠂"] # 0x2200 ogólny (dla każdego ... ) [guide p. 54] + - "∂": [t: "⠹"] # 0x2202 znak pochodnej cząstkowej [guide p. 57] + - "∃": [t: "⠯⠢"] # 0x2203 szczegółowy (istnieje takie ..., że) [guide p. 54] + - "∅": [t: "⠯⠕"] # 0x2205 zbiór pusty [guide p. 16] + - "∈": [t: "⠈⠑"] # 0x2208 należy [guide p. 16] + - "∉": [t: "⠔⠈⠑"] # 0x2209 nie należy [guide p. 16] + - "−": [t: "⠤"] # 0x2212 odejmowanie [guide p. 10] + - "∓": [t: "⠤⠖"] # 0x2213 minus-plus [guide p. 10] + - "∖": [t: "⠡⠄"] # 0x2216 różnica zbiorów [guide p. 17] + - "∞": [t: "⠼⠿"] # 0x221E symbol nieskończoności [guide p. 56] + - "∟": [t: "⠻⠦"] # 0x221F kąt prosty [guide p. 48] + - "∠": [t: "⠻⠪"] # 0x2220 kąt [guide p. 48] + - "∥": [t: "⠈⠇⠇"] # 0x2225 równoległe [guide p. 48] + - "∦": [t: "⠔⠈⠇⠇"] # 0x2226 nierównoległe [guide p. 48] + - "∨": [t: "⠼⠑⠈⠇⠼⠃⠑"] # 0x2228 lub [guide p. 12] + - "∩": [t: "⠬⠄"] # 0x2229 iloczyn zbiorów [guide p. 17] + - "∪": [t: "⠩⠄"] # 0x222A suma zbiorów [guide p. 17] + - "∫": [t: "⠮"] # 0x222B znak całki [guide p. 57] + - "∼": [t: "⠢"] # 0x223C jest podobny [guide p. 48] + - "≁": [t: "⠔⠢"] # 0x2241 nie jest podobny [guide p. 48] + - "≅": [t: "⠶⠶"] # 0x2245 jest przystający [guide p. 48] + - "≇": [t: "⠔⠶⠶"] # 0x2247 nie jest przystający [guide p. 48] + - "≈": [t: "⠢⠢"] # 0x2248 równa się w przybliżeniu [guide p. 11] + - "≔": [t: "⠒⠶"] # 0x2254 równa się z definicji [guide p. 11] + - "≠": [t: "⠔⠶"] # 0x2260 nie równa się [guide p. 11] + - "≡": [t: "⠶⠶"] # 0x2261 równa się tożsamościowo [guide p. 11] + - "≢": [t: "⠔⠶⠶"] # 0x2262 nie równa się tożsamościowo [guide p. 11] + - "≤": [t: "⠪⠶"] # 0x2264 mniejszy lub równy [guide p. 11] + - "≥": [t: "⠕⠶"] # 0x2265 większy lub równy [guide p. 11] + - "≪": [t: "⠪⠪⠄"] # 0x226A znacznie mniejszy [guide p. 11] + - "≫": [t: "⠕⠕⠂"] # 0x226B znacznie większy [guide p. 11] + - "≮": [t: "⠔⠪⠄"] # 0x226E nie mniejszy [guide p. 11] + - "≯": [t: "⠔⠕⠂"] # 0x226F nie większy [guide p. 11] + - "⊂": [t: "⠣⠄"] # 0x2282 zawiera się, jest zawarty, jest podzbiorem [guide p. 16] + - "⊃": [t: "⠜⠂"] # 0x2283 zawiera [guide p. 16] + - "⊅": [t: "⠔⠜⠂"] # 0x2285 nie zawiera [guide p. 16] + - "⊥": [t: "⠼⠄"] # 0x22A5 prostopadłe [guide p. 48] + - "⊮": [t: "⠔⠼⠄"] # 0x22AE nieprostopadłe [guide p. 48] + - "⌀": [t: "⠻⠔"] # 0x2300 średnica [guide p. 48] + - "□": [t: "⠻⠶"] # 0x25A1 kwadrat [guide p. 48] + - "▭": [t: "⠻⠿"] # 0x25AD prostokąt [guide p. 48] + - "△": [t: "⠻⠲"] # 0x25B3 trójkąt [guide p. 48] + - "○": [t: "⠻⠴"] # 0x25CB okrąg [guide p. 48] + +# Greek letters (guide p. 7). The key sign is emitted by the "G" flag, +# so only the letter cell is listed here. + - "Α": [t: "CG⠁"] # 0x0391 + - "Β": [t: "CG⠃"] # 0x0392 + - "Γ": [t: "CG⠛"] # 0x0393 + - "Δ": [t: "CG⠙"] # 0x0394 (document uses '∆') + - "Ε": [t: "CG⠑"] # 0x0395 + - "Ζ": [t: "CG⠵"] # 0x0396 + - "Η": [t: "CG⠱"] # 0x0397 + - "Θ": [t: "CG⠹"] # 0x0398 + - "Ι": [t: "CG⠊"] # 0x0399 + - "Κ": [t: "CG⠅"] # 0x039A + - "Λ": [t: "CG⠇"] # 0x039B + - "Μ": [t: "CG⠍"] # 0x039C + - "Ν": [t: "CG⠝"] # 0x039D + - "Ξ": [t: "CG⠭"] # 0x039E + - "Ο": [t: "CG⠕"] # 0x039F + - "Π": [t: "CG⠏"] # 0x03A0 + - "Ρ": [t: "CG⠗"] # 0x03A1 + - "Σ": [t: "CG⠎"] # 0x03A3 + - "Τ": [t: "CG⠞"] # 0x03A4 + - "Υ": [t: "CG⠥"] # 0x03A5 + - "Φ": [t: "CG⠋"] # 0x03A6 + - "Χ": [t: "CG⠯"] # 0x03A7 + - "Ψ": [t: "CG⠽"] # 0x03A8 + - "Ω": [t: "CG⠺"] # 0x03A9 (document uses 'Ω') + - "α": [t: "G⠁"] # 0x03B1 + - "β": [t: "G⠃"] # 0x03B2 + - "γ": [t: "G⠛"] # 0x03B3 + - "δ": [t: "G⠙"] # 0x03B4 + - "ε": [t: "G⠑"] # 0x03B5 + - "ζ": [t: "G⠵"] # 0x03B6 + - "η": [t: "G⠱"] # 0x03B7 + - "θ": [t: "G⠹"] # 0x03B8 + - "ι": [t: "G⠊"] # 0x03B9 + - "κ": [t: "G⠅"] # 0x03BA + - "λ": [t: "G⠇"] # 0x03BB + - "μ": [t: "G⠍"] # 0x03BC (document uses 'µ') + - "ν": [t: "G⠝"] # 0x03BD + - "ξ": [t: "G⠭"] # 0x03BE + - "ο": [t: "G⠕"] # 0x03BF + - "π": [t: "G⠏"] # 0x03C0 + - "ρ": [t: "G⠗"] # 0x03C1 + - "σ": [t: "G⠎"] # 0x03C3 + - "τ": [t: "G⠞"] # 0x03C4 + - "υ": [t: "G⠥"] # 0x03C5 + - "χ": [t: "G⠯"] # 0x03C7 + - "ψ": [t: "G⠽"] # 0x03C8 + - "ω": [t: "G⠺"] # 0x03C9 + - "ϕ": [t: "G⠋"] # 0x03D5 diff --git a/src/braille.rs b/src/braille.rs index 4f0ac0ec..ab12d68f 100644 --- a/src/braille.rs +++ b/src/braille.rs @@ -265,6 +265,7 @@ fn get_braille_code(code: &str) -> Option<&'static dyn BrailleCode> { "Vietnam" => &Vietnam, "CMU" => &Cmu, "Finnish" => &Finnish, + "Polish" => &Polish, "Russian" => &Russian, "Swedish" => &Swedish, "French" => &French, @@ -279,6 +280,7 @@ struct Ueb; struct Vietnam; struct Cmu; struct Finnish; +struct Polish; struct Russian; struct Swedish; struct French; @@ -348,6 +350,12 @@ impl BrailleCode for Finnish { fn needs_grouping(&self, mathml: Element, is_base: bool) -> StdResult { Ok(NeedsToBeGrouped::needs_grouping_for_finnish(mathml, is_base)) } } +impl BrailleCode for Polish { + fn name(&self) -> &'static str { "Polish" } + fn cleanup(&self, pref_manager: Ref, raw_braille: String) -> String { polish_cleanup(pref_manager, raw_braille) } + fn get_braille_chars(&self, node: Element, text_range: Option>) -> Result { BrailleChars::get_braille_ueb_chars(node, text_range) } // FIX: needs the Polish letter/number key signs +} + impl BrailleCode for Russian { fn name(&self) -> &'static str { "Russian" } fn cleanup(&self, pref_manager: Ref, raw_braille: String) -> String { russian_cleanup(pref_manager, raw_braille) } @@ -2616,6 +2624,83 @@ fn finnish_cleanup(pref_manager: Ref, raw_braille: String) -> } +static POLISH_INDICATOR_REPLACEMENTS: phf::Map<&str, &str> = phf_map! { + // Key signs taken from the Polish guide and cross-checked against the cells + // decoded from its own character table: + // number sign dots 3-4-5-6 (p. 4) + // lower-case Latin sign dot 6 (p. 6) + // upper-case Latin sign dots 4-6 (p. 6) + // Note the upper-case sign REPLACES the lower-case one rather than preceding + // it, so the "CL" pair is collapsed in polish_cleanup before we get here. + "N" => "⠼", // number sign + "n" => "⠼", // number sign for drop numbers + "L" => "⠠", // lower-case Latin letter sign + "C" => "⠨", // upper-case Latin letter sign (see polish_cleanup) + "𝐶" => "⠨", // capital that must not get whitespace in front + "𝑐" => "", // second or later cell of a capital letter + "G" => "⠰", // lower-case Greek letter sign, dots 5-6 (guide p. 7) + "𝑔" => "⠸", // upper-case Greek letter sign, dots 4-5-6 (see polish_cleanup) + "W" => "⠀", // whitespace + "𝐖"=> "⠀", // whitespace + "R" => "", // roman + "B" => "⠸", // bold: the guide's "znak druku wyroznionego" (p. 6) + "I" => "⠸", // italic: same sign as bold in the guide's table (p. 1) + "S" => "XXX", // sans-serif -- not defined by the guide + "𝔹" => "XXX", // blackboard -- not defined by the guide + "T" => "XXX", // script -- not defined by the guide + "D" => "XXX", // Fraktur -- not defined by the guide + "E" => "", // English + "V" => "XXX", // Greek variants + "1" => "", // Grade 1 symbol: no equivalent in the Polish code + "s" => "", // typeface single char indicator + "w" => "", // typeface word indicator + "e" => "", // typeface & capital terminator + "t" => "⠱", // projector terminator (p. 37: "znak konczacy projektor") + "," => "⠂", // comma + "." => "⠲", // period + "-" => "-", // hyphen + "(" => "⠦", + ")" => "⠴", + "↑" => "⠬", // superscript + "↓" => "⠡", // subscript + "#" => "", // signals end of script + "Z" => "⠐", // zone change +}; + +/// Post-processing for the Polish braille code. +/// +/// STATUS: minimal. It resolves the letter/number key signs and drops the mode +/// indicators the shared machinery leaves behind. The spacing precedence from the +/// guide's chapters "Nastepstwo znakow" (p. 3) and "Wzajemne polozenie znakow" +/// (p. 58) - six sign groups where C outranks B outranks A - is NOT implemented. +fn polish_cleanup(pref_manager: Ref, raw_braille: String) -> String { + static REPLACE_INDICATORS: LazyLock = + LazyLock::new(|| Regex::new(r"([SB𝔹TIREDGg𝑔VUP𝐏C𝐶LlMmb↑↓Nn𝑁WwZ,()])").unwrap()); + + let typeforms = UserTypeforms::from_prefs(&pref_manager, "Vietnam"); + + // Get rid of unnecessary "L"s and "N"s (shared with the other codes). + let result = remove_unneeded_mode_changes(&raw_braille, UEB_Mode::Grade1, UEB_Duration::Passage); + + // In Polish the capital sign REPLACES the lower-case letter sign instead of + // stacking with it, so collapse the pair before the table below turns a lone + // "L" into the lower-case sign. + let result = result.replace("CL", "C").replace("𝐶L", "𝐶"); + + // Greek follows the same principle: the guide has ONE sign for a lower-case + // Greek letter (dots 5-6) and ONE for an upper-case Greek letter (dots 4-5-6), + // rather than a capital sign stacked on a Greek sign. Map the "CG" pair onto + // the single upper-case Greek sign. + let result = result.replace("CG", "𝑔").replace("𝐶G", "𝑔"); + + let result = apply_indicator_replacements(&result, &REPLACE_INDICATORS, + &POLISH_INDICATOR_REPLACEMENTS, "POLISH_INDICATOR_REPLACEMENTS", &typeforms); + + let result = COLLAPSE_SPACES.replace_all(&result, "⠀"); + return result.to_string(); +} + + fn swedish_cleanup(pref_manager: Ref, raw_braille: String) -> String { // FIX: need to implement this -- this is just a copy of the Vietnam code // Empty bases are ok if they follow whitespace diff --git a/tests/braille.rs b/tests/braille.rs index ad2d4d8e..f3dce8d8 100644 --- a/tests/braille.rs +++ b/tests/braille.rs @@ -33,6 +33,10 @@ mod braille { mod swedish; } + mod Polish { + mod polish; + } + mod LaTeX { mod augenbit; mod other; diff --git a/tests/braille/Polish/polish.rs b/tests/braille/Polish/polish.rs new file mode 100644 index 00000000..8e96d123 --- /dev/null +++ b/tests/braille/Polish/polish.rs @@ -0,0 +1,627 @@ +// Polish math braille tests. +// +// The expected strings come from "Brajlowska notacja matematyczna, fizyczna, +// chemiczna", 2nd edition, Krakow-Laski-Lodz 2011 (recommended by the Polish +// Ministry of National Education). The guide prints braille with a 6-dot font, +// so each expectation below was decoded from the guide's own character table +// (page 1) into Unicode braille cells. +// +// This is the first instalment of the code: digits, letters, operators and +// relations, plus roots and short-form fractions. Chapters not yet covered +// (spacing precedence, indices, physics and chemistry) have no tests here. +use crate::common::*; +use anyhow::Result; + +// Page 4: numbers are the first-series letters preceded by the number sign. +// The guide writes 6 as "e (number sign + cell for 6). +#[test] +fn liczba_jednocyfrowa() -> Result<()> { + let expr = r#"6"#; + test_braille("Polish", expr, "⠼⠋")?; + return Ok(()); +} + +// Page 4: 68 is written "eg - the number sign is NOT repeated for the second digit. +#[test] +fn liczba_dwucyfrowa() -> Result<()> { + let expr = r#"68"#; + test_braille("Polish", expr, "⠼⠋⠓")?; + return Ok(()); +} + +// Page 6: a lower-case Latin letter is preceded by the letter sign (dot 6). +#[test] +fn litera_mala() -> Result<()> { + let expr = r#"a"#; + test_braille("Polish", expr, "⠠⠁")?; + return Ok(()); +} + +// Page 10: addition. The guide's definition is _*z, i.e. blank + dots 2-3-5. +#[test] +fn dodawanie() -> Result<()> { + let expr = r#"5+2"#; + test_braille("Polish", expr, "⠼⠑⠀⠖⠼⠃")?; + return Ok(()); +} + +// Page 11: equality is defined as _ Result<()> { + let expr = r#"x=1"#; + test_braille("Polish", expr, "⠠⠭⠀⠶⠼⠁")?; + return Ok(()); +} + +// Page 37: the simple root sign is ASCII '2' in the guide = dots 1-4-6. +// The guide writes sqrt(16) as 2"`e. +#[test] +fn pierwiastek_kwadratowy() -> Result<()> { + let expr = r#"16"#; + test_braille("Polish", expr, "⠩⠼⠁⠋")?; + return Ok(()); +} + +// Page 25: the short fraction form omits the opening/closing signs and any +// blanks around the fraction line. The guide writes 2/3 as "a7"b. +#[test] +fn ulamek_skrocony() -> Result<()> { + let expr = r#"23"#; + test_braille("Polish", expr, "⠼⠃⠳⠼⠉")?; + return Ok(()); +} + +// Page 31, rule 1: a whole-number exponent is written after the exponent key sign +// with DROPPED digits and no number sign. The guide writes 5^4 as "d/. - +// number sign, cell for 5, exponent sign, dropped 4. +#[test] +fn potega_liczba_calkowita() -> Result<()> { + let expr = r#"54"#; + test_braille("Polish", expr, "⠼⠑⠬⠲")?; + return Ok(()); +} + +// Page 31: the guide's own example b^10 = &a/+( - letter sign, cell for b, +// exponent sign, dropped 1, dropped 0. +#[test] +fn potega_dwucyfrowa() -> Result<()> { + let expr = r#"b10"#; + test_braille("Polish", expr, "⠠⠃⠬⠂⠴")?; + return Ok(()); +} + +// Page 31: the guide's own example a_12 = &`0+: - letter sign, cell for a, +// lower index sign, dropped 1, dropped 2. +#[test] +fn wskaznik_dolny_dwucyfrowy() -> Result<()> { + let expr = r#"a12"#; + test_braille("Polish", expr, "⠠⠁⠡⠂⠆")?; + return Ok(()); +} + +// Page 31: a_0 is written &`0( - the dropped zero, again with no number sign. +#[test] +fn wskaznik_dolny_zero() -> Result<()> { + let expr = r#"a0"#; + test_braille("Polish", expr, "⠠⠁⠡⠴")?; + return Ok(()); +} + +// Pages 22-24: a script containing a fraction cannot use the SIMPLE projector, +// because a simple one may hold nothing but a single row (p. 22). The COMPOUND +// sign is the simple sign preceded by dot 5, and it has to be closed by the +// compound terminator. Measured from the guide: on p. 23 the compound signs are +// two cells wide against one cell on p. 22, the extra leading cell being dot 5. +#[test] +fn potega_zlozona_ulamek() -> Result<()> { + let expr = r#"a12"#; + test_braille("Polish", expr, "⠠⠁⠐⠬⠼⠁⠳⠼⠃⠐⠱")?; + return Ok(()); +} + +// The same for a lower index holding a fraction. +#[test] +fn wskaznik_zlozony_ulamek() -> Result<()> { + let expr = r#"a12"#; + test_braille("Polish", expr, "⠠⠁⠐⠡⠼⠁⠳⠼⠃⠐⠱")?; + return Ok(()); +} + +// Page 7: a Greek letter is its own key sign plus the cell of the same-sounding +// Latin letter. Lower case takes dots 5-6, so alpha is dots 5-6 then the cell +// for "a". Verified against all 24 letters of the guide's table. +#[test] +fn grecka_mala_alfa() -> Result<()> { + let expr = r#"α"#; + test_braille("Polish", expr, "⠰⠁")?; + return Ok(()); +} + +// Page 7: pi is dots 5-6 then the cell for "p". +#[test] +fn grecka_mala_pi() -> Result<()> { + let expr = r#"π"#; + test_braille("Polish", expr, "⠰⠏")?; + return Ok(()); +} + +// Page 7: an upper-case Greek letter has ONE sign of its own (dots 4-5-6), not a +// capital sign stacked onto the Greek sign. Omega is dots 4-5-6 then "w". +#[test] +fn grecka_wielka_omega() -> Result<()> { + let expr = r#"Ω"#; + test_braille("Polish", expr, "⠸⠺")?; + return Ok(()); +} + +// Page 16: set membership. The guide's definition table gives "nalezy" as two +// cells, dot 4 followed by dots 1-5. +#[test] +fn nalezy_do_zbioru() -> Result<()> { + let expr = r#"xA"#; + test_braille("Polish", expr, "⠠⠭⠀⠈⠑⠨⠁")?; + return Ok(()); +} + +// Page 48: the guide distinguishes three arrows whose ASCII transcription in the +// PDF is IDENTICAL - the difference is a leading cell rendered as a space. +// Left arrow carries that dot-5 prefix, the right arrow does not. +#[test] +fn strzalka_w_prawo() -> Result<()> { + let expr = r#"ab"#; + test_braille("Polish", expr, "⠠⠁⠒⠂⠠⠃")?; + return Ok(()); +} + +#[test] +fn strzalka_w_lewo() -> Result<()> { + let expr = r#"ab"#; + test_braille("Polish", expr, "⠠⠁⠐⠒⠠⠃")?; + return Ok(()); +} + +// Page 52: trigonometric function names get their own contraction - a dots-1-2-4-6 +// prefix plus one cell for the function, so "sin" is two cells, not three letters. +#[test] +fn funkcja_sinus() -> Result<()> { + let expr = r#"sinx"#; + test_braille("Polish", expr, "⠫⠎⠠⠭")?; + return Ok(()); +} + +// Page 53: the inverse functions insert dot 2 after that prefix and keep the cell +// of the base function - arcsin is the sin cell with the arc marker in front. +#[test] +fn funkcja_arcus_sinus() -> Result<()> { + let expr = r#"arcsinx"#; + test_braille("Polish", expr, "⠫⠂⠎⠠⠭")?; + return Ok(()); +} + +// The whole point of the work: an NVDA user has to be able to PICK this code. +// get_supported_braille_codes() lists the subdirectories of Rules/Braille, so a +// new directory is enough - but that is a claim until measured, and a code that +// compiles yet never shows up in the preference list is useless to a reader. +#[test] +fn kod_polski_jest_na_liscie_wyboru() -> Result<()> { + use libmathcat::interface::{get_supported_braille_codes, set_rules_dir}; + set_rules_dir(abs_rules_dir_path()).unwrap(); + let codes = get_supported_braille_codes()?; + assert!( + codes.contains(&"Polish".to_string()), + "kod 'Polish' nie jest widoczny na liscie kodow brajlowskich: {codes:?}" + ); + return Ok(()); +} + +// Page 59 defines a PRECEDENCE between the spacing groups (C and C' absolute, +// then B and B', then A and A'). Only the group-B half is implemented so far: +// relations and binary operators take their blank on the left. The conflict +// resolution itself is NOT implemented - see the comment in definitions.yaml. +// This test therefore only pins the group-B behaviour that does work. +// Page 58-59: group B signs take the blank cell on the LEFT only ("znaki pisane z +// odstepem z lewej strony"). What follows them is decided by the NEXT sign's own +// group, and rule 1 says two signs are written with no gap when the second one is +// from group C. The number sign and the letter/capital sign behave that way here, +// which the guide's own examples confirm: "x = 1" reads letter-sign, x, blank, +// equals, number-sign, 1 - with no blank after the relation. +#[test] +fn grupa_b_daje_odstepy() -> Result<()> { + let expr = r#"a=b"#; + test_braille("Polish", expr, "⠠⠁⠀⠶⠠⠃")?; + return Ok(()); +} + +// The measured result for a prime after "=" is "a = prime" with NO blanks at all, +// but that is NOT the work of a precedence rule: removing the group-C condition +// entirely leaves this output unchanged, and forcing the condition to always-true +// changes nothing either (measured both ways). The blanks are absent because a +// trailing "mo" has no following sibling to be spaced from, so this test records +// current behaviour rather than proving the precedence works. +#[test] +fn prim_po_relacji_bez_odstepu() -> Result<()> { + let expr = r#"a="#; + test_braille("Polish", expr, "⠠⠁⠶⠔")?; + return Ok(()); +} + +// Page 12: absolute value has its OWN pair of signs - opening dots 4 + 1-2-3 and +// closing dots 4-5-6 - not a repeated bar. Before this rule the vertical bar had +// no entry at all and leaked into the output as raw ASCII "|". +#[test] +fn wartosc_bezwzgledna() -> Result<()> { + let expr = r#"|a|"#; + test_braille("Polish", expr, "⠈⠇⠠⠁⠸")?; + return Ok(()); +} + +// Page 49: the vector arrow is a BRACKETING key sign (dots 4-5, 2-5, 2) placed in +// front of the whole expression it covers. Taken from the guide's own example. +#[test] +fn wektor_nad_litera() -> Result<()> { + let expr = r#"u"#; + test_braille("Polish", expr, "⠨⠒⠂⠠⠥")?; + return Ok(()); +} + +// Page 56: a limit is written as a lower index of the operator, closed by the +// simple-projector terminator - the guide writes "lim x->oo" as number sign, l, +// index sign, x, arrow, infinity, blank. +#[test] +fn granica_pod_symbolem() -> Result<()> { + let expr = r#"limn"#; + test_braille("Polish", expr, "⠼⠇⠡⠠⠝⠀")?; + return Ok(()); +} + +// Page 46: a matrix is bracketed with dots 1-2-6 ... 3-4-6, entries and rows are +// separated by a blank cell. +#[test] +fn macierz_dwa_na_dwa() -> Result<()> { + let expr = r#"1234"#; + test_braille("Polish", expr, "⠣⠼⠁⠀⠼⠃⠀⠼⠉⠀⠼⠙⠜")?; + return Ok(()); +} + +// A big operator with both limits. The guide gives sum no sign of its own (only +// the Greek capitals on p. 7), so it comes out as capital sigma, then the lower +// index and the upper index, each closed by the terminator. +#[test] +fn suma_z_granicami() -> Result<()> { + let expr = r#"in"#; + test_braille("Polish", expr, "⠸⠎⠡⠠⠊⠀⠬⠠⠝⠀")?; + return Ok(()); +} + +// Page 5: in braille the integer part is separated from the fraction by a COMMA +// (dot 2) and never by a point - the guide says braille uses only the comma, so +// print notation's point/comma ambiguity does not arise. Its example: 7,29. +#[test] +fn liczba_dziesietna() -> Result<()> { + let expr = r#"7,29"#; + test_braille("Polish", expr, "⠼⠛⠂⠃⠊")?; + return Ok(()); +} + +// Page 5: per cent follows the number. +#[test] +fn procent() -> Result<()> { + let expr = r#"25%"#; + test_braille("Polish", expr, "⠼⠃⠑⠼⠚⠴")?; + return Ok(()); +} + +// Page 25: the FULL fraction form brackets the fraction and puts a blank cell on +// both sides of the fraction line; the guide writes 2/3 that way as well. +#[test] +fn ulamek_pelny_z_odstepami() -> Result<()> { + let expr = r#"xy"#; + test_braille("Polish", expr, "⠠⠭⠳⠽")?; + return Ok(()); +} + +// Page 51: the guide gives both a full and a SHORT form for angle units and tells +// the transcriber to prefer the short one; its own example writes 30 degrees with +// the bare cell, no prefix. +#[test] +fn stopien_katowy() -> Result<()> { + let expr = r#"30°"#; + test_braille("Polish", expr, "⠼⠉⠚⠴")?; + return Ok(()); +} + +// Page 48 gives a whole family of plane-figure signs behind one prefix (dots +// 1-2-4-5-6). Verified across all seven figures in the guide's table. +#[test] +fn figura_trojkat() -> Result<()> { + let expr = r#"ABC"#; + test_braille("Polish", expr, "⠻⠲⠨⠁⠨⠃⠨⠉")?; + return Ok(()); +} + +// Page 48: a negated relation is the affirmative one with dot-3-5 in front. +// Measured on eight pairs in the guide, all consistent. +#[test] +fn nierownolegle() -> Result<()> { + let expr = r#"ab"#; + test_braille("Polish", expr, "⠠⠁⠀⠔⠈⠇⠇⠠⠃")?; + return Ok(()); +} + +// Pages 69-72, 76: a unit is preceded by the "znak miana" (dots 1-2-4-5-6) and +// then spelled with plain letter cells, with no letter sign. Verified across all +// 35 unit symbols in the guide's SI tables, so it is a rule, not a table. +// Units must be MARKED UP as such: the braille path never runs intent inference +// (see the rule comment), so class='MathML-unit' is what identifies them - the +// same signal the UEB rules use. +#[test] +fn jednostka_metr() -> Result<()> { + let expr = r#"5m"#; + test_braille("Polish", expr, "⠼⠑⠻⠍")?; + return Ok(()); +} + +// A capital unit symbol keeps its capital sign after the unit prefix: N is the +// unit sign, then the capital sign, then "n". +#[test] +fn jednostka_niuton() -> Result<()> { + let expr = r#"10N"#; + test_braille("Polish", expr, "⠼⠁⠚⠻⠨⠝")?; + return Ok(()); +} + +// Pages 77-79: a chemical element symbol is preceded by the element key sign and +// the two-letter symbols form ONE unit - the guide states explicitly that no +// letter sign goes before the second letter. So Cl is the key sign, c, l. +// The expression must first be RECOGNISED as chemistry: the engine scores each +// candidate (likely_chem_formula in src/chemistry.rs) and short fragments such as +// a lone "Cl" or "Cl2" stay below the threshold on purpose, since they could be +// ordinary variables. Measured: in "HCl" the element attribute IS set and the +// rule fires; in "Cl2" alone it is not. Hence a full molecule here, as in the +// Nemeth chemistry tests. +#[test] +fn pierwiastek_dwuliterowy() -> Result<()> { + let expr = r#"NaCl"#; + test_braille("Polish", expr, "⠨⠝⠁⠨⠉⠇")?; + return Ok(()); +} + +// A molecule repeats the key sign for every element (p. 77): HCl is the key sign +// with h, then the key sign again with c and l. +#[test] +fn czasteczka_hcl() -> Result<()> { + let expr = r#"HCl"#; + test_braille("Polish", expr, "⠨⠓⠨⠉⠇")?; + return Ok(()); +} + +// Page 59, conflict rule 1: two signs are written with NO blank between them when +// the second one is from group C. The prime is group C ("znaki pisane bez odstepu +// z lewej strony", p. 58), so a' has no gap before the prime. +#[test] +fn prim_bez_odstepu() -> Result<()> { + let expr = r#"a"#; + test_braille("Polish", expr, "⠠⠁⠔")?; + return Ok(()); +} + +// Page 10: the ASCII hyphen-minus U+002D shares the cell with the proper minus +// U+2212. Without an entry it used to reach the output as a raw ASCII byte. +#[test] +fn minus_ascii_i_unicode() -> Result<()> { + let expr = r#"a=-b"#; + test_braille("Polish", expr, "⠠⠁⠀⠶⠤⠠⠃")?; + return Ok(()); +} + +// Page 11: the four bracket pairs. Without entries for them the engine reached for +// unrelated cells and printed digit cells where brackets belong. +#[test] +fn nawiasy_okragle() -> Result<()> { + let expr = r#"(14-5)"#; + test_braille("Polish", expr, "⠣⠼⠁⠙⠀⠤⠼⠑⠜")?; + return Ok(()); +} + +#[test] +fn nawiasy_kwadratowe_i_klamrowe() -> Result<()> { + let expr = r#"[1]"#; + test_braille("Polish", expr, "⠷⠼⠁⠾")?; + let expr = r#"{4}"#; + test_braille("Polish", expr, "⠪⠼⠙⠕")?; + return Ok(()); +} + +// Page 6 gives Roman numerals a SINGLE capital sign for the whole run +// (XLII reads capital sign + x + l + i + i; MDCCCXXXVII keeps one sign for eleven +// letters), but p. 57 states the general rule for symbols: "w granicach symbolu +// kazdy znak duzej litery odnosi sie tylko do tej litery przed ktora stoi" - the +// sign IS repeated, which the guide's own PLN and its geometry points confirm. +// +// So Roman numerals are an exception that needs them to be RECOGNISED as numerals +// first; collapsing every capital run instead breaks ordinary uses. MathCAT has no +// notion of a Roman numeral, so this is left unimplemented on purpose rather than +// wrongly generalised. + +// Page 12: "jest dzielnikiem" and its negation, which adds the dots 3-5 cell that +// negates any relation (p. 58). The guide's examples are 5|25 and 5|27. +#[test] +fn dzielnik() -> Result<()> { + let expr = r#"525"#; + test_braille("Polish", expr, "⠼⠑⠀⠈⠇⠼⠃⠑")?; + return Ok(()); +} + +#[test] +fn nie_jest_dzielnikiem() -> Result<()> { + let expr = r#"527"#; + test_braille("Polish", expr, "⠼⠑⠀⠔⠈⠇⠼⠃⠛")?; + return Ok(()); +} + +// Page 10: the multiplication dot has a cell of its own. Unlike every other +// operator it may be written EITHER with or without a leading blank - the guide +// makes it an explicit exception ("wyjatek stanowi znak mnozenia, ktory moze byc +// pisany dwojako") and prints both forms side by side for 12 . 3. We emit the +// no-blank form, so the dot is deliberately NOT in the group-B list. +#[test] +fn mnozenie_kropka() -> Result<()> { + let expr = r#"123"#; + test_braille("Polish", expr, "⠼⠁⠃⠄⠼⠉")?; + return Ok(()); +} + +// Page 13: punctuation after a number. Without entries these reached the braille +// output as raw ASCII bytes. +#[test] +fn srednik_po_liczbie() -> Result<()> { + let expr = r#"12;"#; + test_braille("Polish", expr, "⠼⠁⠃⠆")?; + return Ok(()); +} + +// Page 47: logarithms. Measured off the guide's glyphs - log is the function prefix +// plus one cell; ln puts an extra cell between them. Before this they were spelled +// out letter by letter. +#[test] +fn logarytm_dziesietny() -> Result<()> { + let expr = r#"log1000"#; + test_braille("Polish", expr, "⠫⠇⠼⠁⠚⠚⠚")?; + return Ok(()); +} + +#[test] +fn logarytm_naturalny() -> Result<()> { + let expr = r#"lnx"#; + test_braille("Polish", expr, "⠫⠦⠇⠠⠭")?; + return Ok(()); +} + +// Page 55: the factorial sign, and p. 57: the integral and the partial derivative. +// These already worked; the tests pin them so a later change cannot break them +// silently. +#[test] +fn silnia() -> Result<()> { + let expr = r#"5!"#; + test_braille("Polish", expr, "⠼⠑⠫")?; + return Ok(()); +} + +#[test] +fn calka_i_pochodna_czastkowa() -> Result<()> { + let expr = r#"f"#; + test_braille("Polish", expr, "⠮⠠⠋")?; + let expr = r#"f"#; + test_braille("Polish", expr, "⠹⠠⠋")?; + return Ok(()); +} + +// Page 36 rule 5 with p. 37: the degree of a root is an upper LEFT index, written +// before the root sign after its own key sign (dots 3-4), and it needs no +// terminator. Whole numbers in an index use LOWERED digits with no number sign +// (p. 33 rule 1). Measured off the guide's glyphs on p. 36: cbrt(8) is ⠌⠒⠩⠼⠓ and +// the nth root of x is ⠌⠠⠝⠩⠭. +// +// The rule used to emit the key sign together with a hard-coded lowered 3, so every +// root claimed to be a cube root: the fifth root of 32 came out saying "3" and "5". +#[test] +fn pierwiastek_szescienny() -> Result<()> { + let expr = r#"83"#; + test_braille("Polish", expr, "⠌⠒⠩⠼⠓")?; + return Ok(()); +} + +#[test] +fn pierwiastek_piatego_stopnia() -> Result<()> { + let expr = r#"325"#; + test_braille("Polish", expr, "⠌⠢⠩⠼⠉⠃")?; + return Ok(()); +} + +#[test] +fn pierwiastek_stopnia_n() -> Result<()> { + let expr = r#"xn"#; + test_braille("Polish", expr, "⠌⠠⠝⠩⠠⠭")?; + return Ok(()); +} + +// Page 36 rule 6: the BASE of a logarithm, printed as a lower index after the +// function name, becomes an upper LEFT index written BEFORE it. Measured off the +// guide's glyphs on p. 47: log2 8 = 3 reads ⠌⠆⠫⠇⠼⠓. +#[test] +fn podstawa_logarytmu() -> Result<()> { + let expr = r#"log28"#; + test_braille("Polish", expr, "⠌⠆⠫⠇⠼⠓")?; + return Ok(()); +} + +#[test] +fn podstawa_logarytmu_litera() -> Result<()> { + let expr = r#"logax"#; + test_braille("Polish", expr, "⠌⠠⠁⠫⠇⠠⠭")?; + return Ok(()); +} + +// Control: an ORDINARY lower index must keep behaving as before. The logarithm rule +// sits before the generic msub rules in the file, which is how it takes precedence, +// so this test guards against it swallowing every subscript. +#[test] +fn zwykly_wskaznik_dolny_nietkniety() -> Result<()> { + let expr = r#"a1"#; + test_braille("Polish", expr, "⠠⠁⠡⠂")?; + return Ok(()); +} + +// Page 10, read off the guide's own worked examples rather than inferred: the +// division sign takes a blank on its left (67:14 is written number-sign 6 7, blank, +// division, number-sign 1 4), while multiplication by a DOT is printed in both +// forms side by side - it is the one operator the guide lets you write either way. +// The cross is a multiplication sign too but has no example of its own, so it keeps +// the general rule for operators. +#[test] +fn dzielenie_dwukropek() -> Result<()> { + let expr = r#"67÷14"#; + test_braille("Polish", expr, "⠼⠋⠛⠀⠲⠼⠁⠙")?; + return Ok(()); +} + +#[test] +fn mnozenie_krzyzyk() -> Result<()> { + let expr = r#"2×3"#; + test_braille("Polish", expr, "⠼⠃⠀⠦⠼⠉")?; + return Ok(()); +} + +// Page 59 conflict rule 1, second half: two signs are written with NO blank when +// the FIRST is from group C' ("znaki pisane bez odstepu z prawej strony", p. 58), +// where the guide's example of that group is ">". +// +// This needs no rule of its own - it already holds, because the spacing rule emits +// the blank on the LEFT only (see the mo rule and p. 58 group B). These tests pin +// that, since a rule keyed on "the previous sign is group C'" would be DEAD CODE: +// it could not change any output. Measured against the guide's glyphs on p. 11, +// where "x > 0" appears four times as key-sign x, blank, >, number-sign 0. +#[test] +fn brak_odstepu_po_grupie_c_prim() -> Result<()> { + let expr = r#"x>0"#; + test_braille("Polish", expr, "⠠⠭⠀⠕⠂⠼⠚")?; + return Ok(()); +} + +// The same for a NEGATIVE right-hand side: the guide writes 7 > -1 with a blank +// before the relation and none after it, the minus following immediately. +#[test] +fn relacja_przed_liczba_ujemna() -> Result<()> { + let expr = r#"7>-1"#; + test_braille("Polish", expr, "⠼⠛⠀⠕⠂⠤⠼⠁")?; + return Ok(()); +} + +// A CLOSING BRACKET is NOT in group C', which is easy to assume wrongly: the guide's +// own example (14 - 5) + 7 on p. 11 keeps a blank between the bracket and the plus. +#[test] +fn nawias_zamykajacy_nie_jest_grupa_c_prim() -> Result<()> { + let expr = r#"(a)+b"#; + test_braille("Polish", expr, "⠣⠠⠁⠜⠀⠖⠠⠃")?; + return Ok(()); +}