A screen-reader-first mathematical accessibility toolkit for converting LaTeX, MathML, and plaintext/Unicode math into speech, Braille, and navigable ARIA structures.
Quick Start • Features • Installation • Usage • API Reference • Contributing
Accessible Math Reader (AMR) is a Python package and web application that makes mathematical notation accessible to visually impaired users. It parses LaTeX, MathML, and plaintext/Unicode math expressions and converts them into:
- Natural language speech with configurable verbosity (verbose, concise, superbrief)
- Braille notation in both Nemeth (US standard) and UEB (international standard)
- Audio files via Google Text-to-Speech (gTTS)
- ARIA-annotated HTML for keyboard-navigable, screen-reader-friendly exploration
AMR can be used as a Python library, a CLI tool, or through a Flask-based web interface.
🎓 Developed at FCRIT Vashi as a project in academic research in accessibility and inclusive technology.
| Category | Highlights |
|---|---|
| 📖 Multi-Format Input | Parse LaTeX, MathML, and plaintext/Unicode math expressions with auto-detection |
| 🔊 Speech Output | Natural language descriptions with 3 verbosity levels and SSML support |
| ⠿ Braille Support | Full Nemeth Braille Code and Unified English Braille (UEB) converters |
| ♿ ARIA Navigation | Keyboard-accessible, step-by-step expression exploration with 3 navigation modes |
| 📋 Multi-Format Clipboard | Copy formulas as LaTeX, accessible text, or Braille from the web UI |
| 🎨 Accessible Web UI | Dark/light themes, high-contrast mode, zoom controls, screen-reader optimized |
| 🔌 Plugin System | Extensible architecture for custom speech rules, Braille notations, and input formats |
| ⌨️ CLI Tool | Full-featured command-line interface with interactive and batch modes |
┌─────────────────────────────────────────────────────────────────────┐
│ Accessible Math Reader │
├──────────┬──────────┬───────────────────────────────┬───────────────┤
│ CLI │ Web UI │ Python API (MathReader) │ Plugins │
│ (amr) │ (Flask) │ │ (extensible) │
├──────────┴──────────┴───────────────────────────────┴───────────────┤
│ Core Pipeline │
│ ┌──────────┐ ┌──────────────┐ ┌────────────────────────────┐ │
│ │ Parser │──▸│ Semantic AST │──▸│ Renderers │ │
│ │ LaTeX │ │ (SemanticNode│ │ ┌─────────┬────────────┐ │ │
│ │ MathML │ │ NodeType) │ │ │ Speech │ Braille │ │ │
│ │ Plaintext│ | | | | | | | |
│ └──────────┘ └──────┬───────┘ │ │ Engine │ Nemeth/UEB │ │ │
│ │ │ └─────────┴────────────┘ │ │
│ ▼ │ ┌─────────┬────────────┐ │ │
│ ┌───────────┐ │ │ ARIA │ Simple │ │ │
│ │ Navigator │ │ │Renderer │ Text │ │ │
│ │ (keyboard │ │ └─────────┴────────────┘ │ │
│ │ explore) │ └────────────────────────────┘ │
│ └───────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ Configuration │
│ Config · SpeechConfig · BrailleConfig · A11yConfig │
└─────────────────────────────────────────────────────────────────────┘
accessible-math-reader/
├── accessible_math_reader/ # 📦 Installable Python package
│ ├── __init__.py # Public API exports
│ ├── reader.py # MathReader — high-level unified interface
│ ├── cli.py # CLI entry point (`amr` command)
│ ├── config.py # Configuration management (env, file, API)
│ ├── core/ # Core parsing & rendering engine
│ │ ├── parser.py # LaTeX / MathML / Plaintext → Semantic AST
│ │ ├── semantic.py # SemanticNode, NodeType, MathNavigator
│ │ ├── renderer.py # Base rendering infrastructure
│ │ ├── aria_navigator.py # ARIA-enhanced keyboard navigation
│ │ ├── aria_renderer.py # Accessible HTML generation
│ │ └── accessibility_contract.py # Accessibility validation contracts
│ ├── speech/ # Speech output subsystem
│ │ ├── engine.py # TTS engine abstraction (gTTS backend)
│ │ └── rules.py # Verbosity-based speech rules
│ ├── braille/ # Braille conversion subsystem
│ │ ├── nemeth.py # Nemeth Braille Code converter
│ │ └── ueb.py # Unified English Braille converter
│ └── plugins/ # Plugin system
│ └── base.py # Abstract plugin base classes
├── app.py # 🌐 Flask web application entry point
├── src/ # Legacy web-app helper modules
│ ├── latex_parser.py # Regex-based LaTeX parser (for Flask UI)
│ ├── braille_converter.py # Simple character-level Braille mapper
│ └── speech_converter.py # gTTS wrapper for web UI
├── templates/
│ └── index.html # 🖥️ Web UI template (dark/light, accessible)
├── static/
│ ├── css/style.css # Web UI styles
│ └── js/
│ ├── app.js # Frontend logic & keyboard shortcuts
│ └── clipboard.js # Multi-format clipboard support
├── docs/ # 📚 Documentation
│ ├── api.md # Full Python API reference
│ ├── input-formats.md # Supported LaTeX & MathML syntax
│ ├── accessibility.md # Screen reader, Braille & ARIA guide
│ ├── configuration.md # Configuration reference
│ └── examples.md # Code samples & use cases
├── output/ # Sample Braille output files (.brf)
├── pyproject.toml # Package metadata & build config
├── requirements.txt # Web-app-specific dependencies
└── LICENSE # MIT License
Install directly from the repository:
# Clone the repository
git clone https://github.com/AndyFerns/Accessible-Math-Reader.git
cd Accessible-Math-Reader
# Create and activate a virtual environment
python -m venv venv
# Windows
venv\Scripts\activate
# Linux / macOS
source venv/bin/activate
# Install the package in editable mode (with all extras)
pip install -e ".[dev,web]"This installs AMR as a package and registers the amr CLI command.
If you only need the Python API (no web UI, no dev tools):
pip install -e .Core dependencies are just gtts and lxml.
pip install accessible-math-readerAMR provides three interfaces: a Python API, a CLI tool, and a web UI.
from accessible_math_reader import MathReader
reader = MathReader()
# ── Speech ──────────────────────────────────────────
speech = reader.to_speech(r"\frac{a}{b}")
print(speech)
# → "start fraction a over b end fraction"
# ── Braille (Nemeth) ────────────────────────────────
braille = reader.to_braille(r"\frac{a}{b}", notation="nemeth")
print(braille)
# → "⠹⠁⠌⠃⠼"
# ── Braille (UEB) ──────────────────────────────────
ueb = reader.to_braille(r"\frac{a}{b}", notation="ueb")
# ── Audio file ──────────────────────────────────────
reader.to_audio(r"\frac{a}{b}", "output.mp3")
# ── SSML markup ─────────────────────────────────────
ssml = reader.to_ssml(r"\sqrt{x}")
# ── Semantic tree inspection ────────────────────────
structure = reader.get_structure(r"\frac{a+b}{c}")
# ── Plaintext / Unicode math ────────────────────────
speech = reader.to_speech("x² + y² = z²")
speech = reader.to_speech("(a+b)/(c-d)")
speech = reader.to_speech("sqrt(x) + π")from accessible_math_reader import MathReader, VerbosityLevel
reader = MathReader()
reader.set_verbosity(VerbosityLevel.VERBOSE)
reader.to_speech(r"\frac{a}{b}") # "start fraction a over b end fraction"
reader.set_verbosity(VerbosityLevel.CONCISE)
reader.to_speech(r"\frac{a}{b}") # "a over b"
reader.set_verbosity(VerbosityLevel.SUPERBRIEF)
reader.to_speech(r"\frac{a}{b}") # "fraction a b"from accessible_math_reader import MathReader, Config
from accessible_math_reader.config import SpeechConfig, BrailleConfig, SpeechStyle, BrailleNotation
config = Config(
speech=SpeechConfig(style=SpeechStyle.CONCISE, language="en", rate=0.9),
braille=BrailleConfig(notation=BrailleNotation.UEB),
)
reader = MathReader(config)nav = reader.get_navigator(r"\frac{a+b}{c}")
nav.enter() # Drill into the fraction
nav.next() # Move to the next sibling
nav.exit() # Go back up to the parent
path = nav.get_path() # Breadcrumb trail from rootAfter installing the package, the amr command is available system-wide.
# Speech output (default)
amr "\frac{a^2 + b^2}{c}"
# Plaintext / Unicode math
amr "x² + y² = z²"
amr "(a+b)/(c-d)"
amr "sqrt(x) + π"
# Braille output (Nemeth)
amr --braille "\frac{a}{b}"
# Braille output (UEB)
amr --braille --notation ueb "\sqrt{x}"
# Generate audio file
amr --audio output.mp3 "\frac{1}{2}"
# SSML output
amr --ssml "\frac{a}{b}"
# JSON output (speech + braille + structure)
amr --json "\frac{a}{b}"
# Show expression tree structure
amr --structure "\frac{a+b}{c}"
# Set verbosity
amr --verbosity concise "x^2 + y^2 = z^2"
# Read from file (one expression per line)
amr --input equations.txt
# Write output to file
amr --output results.txt "\frac{a}{b}"
# Interactive REPL mode
amr --interactive| Command | Action |
|---|---|
:quit / :exit |
Exit interactive mode |
:verbosity <level> |
Set verbosity (verbose, concise, superbrief) |
:braille |
Switch to Braille output |
:speech |
Switch to speech output |
:help |
Show all commands |
The web UI provides a visual, accessible interface built with Flask:
# Install web dependencies
pip install -e ".[web]"
# Start the development server
python app.py
# Open http://localhost:5000Web UI Features:
- LaTeX / MathML / plaintext/Unicode input with live conversion
- Tabbed output: Formula preview, Speech text, Braille, Accessible ARIA view
- Multi-format clipboard (copy as LaTeX, plain text, or Braille)
- Dark / Light / High-Contrast themes
- Full keyboard navigation (
Ctrl+Enterto convert,Alt+1–4to switch tabs) - Screen-reader optimized with ARIA live regions
AMR supports three configuration methods (highest priority first):
# Linux / macOS
export AMR_SPEECH_STYLE=concise # verbose | concise | superbrief
export AMR_BRAILLE_NOTATION=nemeth # nemeth | ueb
export AMR_SPEECH_LANGUAGE=en # Language code
export AMR_PLUGIN_DIRS=/path/to/plugins
# Windows PowerShell
$env:AMR_SPEECH_STYLE = "concise"
$env:AMR_BRAILLE_NOTATION = "ueb"{
"speech": {
"style": "verbose",
"language": "en",
"rate": 1.0,
"announce_structure": true
},
"braille": {
"notation": "nemeth",
"include_indicators": true
},
"accessibility": {
"step_by_step": true,
"announce_errors": true,
"highlight_current": true
}
}config = Config.from_file("amr-config.json")
reader = MathReader(config)config = Config()
config.speech.style = SpeechStyle.CONCISE
config.save("my-config.json")📖 Full configuration reference: docs/configuration.md
AMR's plugin architecture supports four extension points:
| Plugin Type | Base Class | Purpose |
|---|---|---|
| Speech Rules | SpeechRulesPlugin |
Custom verbalization rules for math constructs |
| Braille Notation | BrailleNotationPlugin |
Additional Braille systems (French, German, etc.) |
| Input Format | InputFormatPlugin |
New input parsers (AsciiMath, MathJSON, etc.) |
| Localization | BasePlugin |
Language/locale-specific adaptations |
from accessible_math_reader.plugins.base import SpeechRulesPlugin, PluginInfo, PluginType
class MyCustomRules(SpeechRulesPlugin):
@property
def info(self):
return PluginInfo(
name="my-rules",
version="1.0.0",
description="Custom speech rules",
author="Your Name",
plugin_type=PluginType.SPEECH_RULES,
)
def get_speech_rules(self):
return {"FRACTION": lambda node: "custom fraction rendering"}| Document | Description |
|---|---|
| API Reference | Full Python API with code examples |
| Input Formats | Supported LaTeX commands and MathML elements |
| Accessibility Guide | Screen reader, Braille display, and ARIA features |
| Configuration | All speech, Braille, and accessibility settings |
| Examples | Common use cases, batch processing, and web integration |
This project uses MkDocs with the Material theme for documentation hosting:
# Install documentation dependencies
pip install mkdocs mkdocs-material mkdocstrings[python]
# Serve docs locally (live reload)
mkdocs serve
# Build static site
mkdocs buildSee mkdocs.yml for the full configuration.
| Standard | Status |
|---|---|
| WCAG 2.2 AA | ✅ Full compliance for web interface |
| WAI-ARIA 1.2 | ✅ Proper roles, labels, live regions, roving tabindex |
| Screen Readers | ✅ Tested with NVDA, JAWS, VoiceOver, Narrator |
| Keyboard Navigation | ✅ Full keyboard access, visible focus indicators |
| Braille Displays | ✅ Unicode Braille output compatible with refreshable displays |
- Python 3.9+
- pip
git clone https://github.com/AndyFerns/Accessible-Math-Reader.git
cd Accessible-Math-Reader
python -m venv venv
venv\Scripts\activate # Windows
# source venv/bin/activate # Linux / macOS
pip install -e ".[dev,web]"# Run full test suite with coverage
pytest tests/ -v --cov=accessible_math_reader --cov-report=term-missing
# Run a specific test file
pytest tests/test_parser.py -v# Run ruff linter
ruff check accessible_math_reader/
# Auto-fix lint issues
ruff check accessible_math_reader/ --fixpython app.py
# Open http://localhost:5000We welcome contributions! Here's how to get started:
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature - Install dev dependencies:
pip install -e ".[dev]" - Make your changes and add tests
- Run the linter:
ruff check accessible_math_reader/ - Run the tests:
pytest tests/ -v - Commit with a descriptive message
- Push to your fork and open a Pull Request
- 🌍 Localization: Speech rules for other languages
- ⠿ Braille: Additional Braille notation systems (French, German, etc.)
- 🧪 Testing: More unit tests, especially for edge-case math expressions
- ♿ User Testing: Feedback from blind and low-vision users
- 📝 Documentation: Tutorials, guides, and examples
# Install build tool
pip install build
# Build source distribution and wheel
python -m build
# Output will be in dist/
# dist/accessible_math_reader-0.1.0.tar.gz
# dist/accessible_math_reader-0.1.0-py3-none-any.whl# Install twine
pip install twine
# Upload to TestPyPI (for testing)
twine upload --repository testpypi dist/*
# Upload to PyPI (production)
twine upload dist/*# From the wheel file
pip install dist/accessible_math_reader-0.1.0-py3-none-any.whl
# From TestPyPI
pip install --index-url https://test.pypi.org/simple/ accessible-math-reader
# From PyPI (once published)
pip install accessible-math-readerThis project is licensed under the MIT License — see the LICENSE file for details.
- gTTS — Google Text-to-Speech engine
- lxml — XML/MathML parsing
- Flask — Web framework
- WCAG — Accessibility guidelines
- Nemeth Braille Code — Mathematical Braille standard
- UEB — Unified English Braille
- FCRIT - Fr. Conceicao Rodrigues Institute of Technology
Built with ♿ accessibility as a first-class priority.