diff --git a/.github/workflows/automaticRelease.yaml b/.github/workflows/automaticRelease.yaml index d3c0c8d4..a9ace1de 100644 --- a/.github/workflows/automaticRelease.yaml +++ b/.github/workflows/automaticRelease.yaml @@ -1,12 +1,11 @@ name: automatic-release - on: push: branches: - main - schedule: - # * is a special character in YAML so you have to quote this string - - cron: '0 0 * * 6' + # schedule: + # # * is a special character in YAML so you have to quote this string + # - cron: '0 0 * * 6' jobs: l10n: @@ -23,34 +22,299 @@ jobs: git remote add l10n https://github.com/nvdaaddons/MathCAT git fetch l10n git reset l10n/stable addon/doc addon/locale - git commit -m "Update translations" + git commit -m "Update translations" --allow-empty git pull git push - pre-release: - name: Pre Release - needs: l10n + zip-rules: + name: zip up the rules directory + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Build Rust Library + run: | + cargo build --target x86_64-unknown-linux-gnu # doesn't need a release build since all that we want is the Rules dir + - name: create rules.zip + uses: thedoctor0/zip-release@0.7.5 + with: + type: 'zip' + filename: 'Rules.zip' + directory: 'addon/globalPlugins/MathCAT' + path: 'Rules' + - name: Upload Rules.zip + uses: actions/upload-artifact@v4 + with: + name: 'Rules.zip' + path: 'addon/globalPlugins/MathCAT/Rules.zip' + compression-level: 0 + retention-days: 1 + + rust-32: + name: Build 32 bit windows pyd file + runs-on: windows-latest # needs to run on windows because of bzip2 + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.11 + architecture: 'x86' + - name: Build Rust Library + run: | + cargo build --target i686-pc-windows-msvc --release + - name: Setup Example dir + run: | + cp target/i686-pc-windows-msvc/release/libmathcat_py.dll Example/libmathcat_py.pyd + mv addon/globalPlugins/MathCAT/Rules Example + - name: test the build + run: | + cd Example + python test.py + - name: create zip file for pyd file -- this allows the name to indicate arch, python version, etc., but unzipped it is correctly named + uses: thedoctor0/zip-release@0.7.5 + with: + type: 'zip' + filename: '../libmathcat_py-32-3.11-win.zip' + directory: 'Example' + path: 'libmathcat_py.pyd' + - name: Upload 32 bit pyd file + uses: actions/upload-artifact@v4 + with: + name: libmathcat_py-32-3.11-win.zip + path: libmathcat_py-32-3.11-win.zip + compression-level: 0 + retention-days: 1 + + rust-64: + name: Build 64 bit windows pyd file + runs-on: windows-latest # needs to run on windows because of bzip2 + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.13 + architecture: 'x64' + - name: Build Rust Library + run: | + cargo build --target x86_64-pc-windows-msvc --release + - name: Setup Example dir + run: | + cp target/x86_64-pc-windows-msvc/release/libmathcat_py.dll Example/libmathcat_py.pyd + mv addon/globalPlugins/MathCAT/Rules Example + - name: create zip file for pyd file -- this allows the name to indicate arch, python version, etc., but unzipped it is correctly named + uses: thedoctor0/zip-release@0.7.5 + with: + type: 'zip' + filename: '../libmathcat_py-64-3.13-win.zip' + directory: 'Example' + path: 'libmathcat_py.pyd' + - name: test the build + run: | + cd Example + python test.py + - name: Upload 64 bit pyd file + uses: actions/upload-artifact@v4 + with: + name: libmathcat_py-64-3.13-win.zip + path: libmathcat_py-64-3.13-win.zip + compression-level: 0 + retention-days: 1 + + linux-64: + name: Build linux pyd file (64-bit intel) + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.13 + architecture: 'x64' + - name: Build Rust Library + run: | + cargo build --target x86_64-unknown-linux-gnu --release + - name: Setup Example dir + run: | + # don't know why it is "liblib...". For consistancy with other versions, it is renamed. + cp target/x86_64-unknown-linux-gnu/release/liblibmathcat_py.so Example/libmathcat_py.so + cp -r addon/globalPlugins/MathCAT/Rules Example + - name: test build + run: | + cd Example + python test.py + - name: create zip file for pyd file -- this allows the name to indicate arch, python version, etc., but unzipped it is correctly named + uses: thedoctor0/zip-release@0.7.5 + with: + type: 'zip' + filename: '../libmathcat_py-64-3.13-linux.zip' + directory: 'Example' + path: 'libmathcat_py.so' + - name: Upload 64 bit pyd file + uses: actions/upload-artifact@v4 + with: + name: libmathcat_py-64-3.13-linux.zip + path: libmathcat_py-64-3.13-linux.zip + compression-level: 0 + retention-days: 1 + + build-64-bit-addon: + name: build-64-bit-addon + continue-on-error: false + needs: [l10n, zip-rules, rust-64] + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + # download the 64 build + - name: Download 64 bit build + uses: actions/download-artifact@v4 + with: + name: libmathcat_py-64-3.13-win.zip + # download the Rules dir (not part of checkout) + - name: Download 64 bit build + uses: actions/download-artifact@v4 + with: + name: Rules.zip + # put things where they belong + - name: Set up addons dir + run: | + unzip libmathcat_py-64-3.13-win.zip + mv libmathcat_py.pyd Rules.zip addon/globalPlugins/MathCAT + cd addon/globalPlugins/MathCAT + sed 's/^import wx\.xrc/# import wx.xrc/' --in-place MathCATgui.py # fix wx file + unzip Rules.zip + rm Rules.zip + # build the addon + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.13 # needed for scons + - name: Install scons dependencies + run: | + pip install scons markdown + sudo apt update + sudo apt install gettext + - name: Run scons to build .addon file + run: | + scons + - name: Rename addon for 64-bit + run: | + for f in *.nvda-addon; do + mv "$f" "${f%.nvda-addon}-64.nvda-addon" + done + - name: Upload the addon + uses: actions/upload-artifact@v4 + with: + name: addon-64 + path: "*.nvda-addon" + compression-level: 0 + retention-days: 1 + + build-32-bit-addon: + name: build-32-bit-addon + continue-on-error: false + needs: [l10n, zip-rules, rust-32] runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - - name: Set up Python 3.8 + # download the 32 build + - name: Download 32 bit build + uses: actions/download-artifact@v4 + with: + name: libmathcat_py-32-3.11-win.zip + # download the Rules dir (not part of checkout) + - name: Download 32 bit build + uses: actions/download-artifact@v4 + with: + name: Rules.zip + # put things where they belong + - name: Set up addons dir + run: | + unzip libmathcat_py-32-3.11-win.zip + mv libmathcat_py.pyd Rules.zip addon/globalPlugins/MathCAT + cd addon/globalPlugins/MathCAT + sed 's/^import wx\.xrc/# import wx.xrc/' --in-place MathCATgui.py # fix wx file + unzip Rules.zip + rm Rules.zip + # build the addon + - name: Set up Python uses: actions/setup-python@v4 with: - python-version: 3.8 - - name: Install dependencies + python-version: 3.11 # needed for scons + - name: Install scons dependencies run: | pip install scons markdown sudo apt update sudo apt install gettext - - name: Build - run: scons version=${{ github.sha }} - - uses: actions/checkout@v2 - - name: Set up Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true + - name: Update NVDA Version Constraints + run: | + # Adjust the version numbers for the old 32 bit NVDA version + sed -i 's/"addon_name": "MathCAT"/"addon_name": "MathCAT-for-NVDA2025"/' buildVars.py + sed -i 's/"addon_summary": _("MathCAT:/"addon_summary": _("MathCAT-for-NVDA2025:/' buildVars.py + sed -i 's/"addon_minimumNVDAVersion": "[^"]*"/"addon_minimumNVDAVersion": "2025.1"/' buildVars.py + sed -i 's/"addon_lastTestedNVDAVersion": "[^"]*"/"addon_lastTestedNVDAVersion": "2025.3"/' buildVars.py + cat buildVars.py + - name: Run scons to build .addon file + run: | + scons + - name: Rename addon for 32-bit + run: | + for f in *.nvda-addon; do + mv "$f" "${f%.nvda-addon}-32.nvda-addon" + done + - name: Upload the addon + uses: actions/upload-artifact@v4 + with: + name: addon-32 + path: "*.nvda-addon" + compression-level: 0 + retention-days: 1 + + pre-release: + name: Pre Release + continue-on-error: false + needs: [zip-rules, rust-32, rust-64, linux-64, build-32-bit-addon, build-64-bit-addon] + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + # download the previous build artifacts and put them in their proper places + - name: Download Rules.zip + uses: actions/download-artifact@v4 + with: + name: Rules.zip + - name: Download 32 bit build + uses: actions/download-artifact@v4 + with: + name: libmathcat_py-32-3.11-win.zip + - name: Download 64 bit build + uses: actions/download-artifact@v4 + with: + name: libmathcat_py-64-3.13-win.zip + - name: Download 64 bit linux build + uses: actions/download-artifact@v4 + with: + name: libmathcat_py-64-3.13-linux.zip + - name: Download 64-bit addon + uses: actions/download-artifact@v4 + with: + name: addon-64 + - name: Download 32-bit addon + uses: actions/download-artifact@v4 + with: + name: addon-32 + - name: Consolidate addon files + run: | + mv addon-64/*.nvda-addon . 2>/dev/null || true + mv addon-32/*.nvda-addon . 2>/dev/null || true + # put the files into the release - name: Automatic release uses: marvinpinto/action-automatic-releases@latest with: @@ -60,3 +324,7 @@ jobs: title: Development Build files: | *.nvda-addon + libmathcat_py-32-3.11-win.zip + libmathcat_py-64-3.13-win.zip + libmathcat_py-64-3.13-linux.zip + Rules.zip diff --git a/.github/workflows/checkTranslatorsComments.yml b/.github/workflows/checkTranslatorsComments.yaml similarity index 97% rename from .github/workflows/checkTranslatorsComments.yml rename to .github/workflows/checkTranslatorsComments.yaml index a8598c5c..8cbe7aac 100644 --- a/.github/workflows/checkTranslatorsComments.yml +++ b/.github/workflows/checkTranslatorsComments.yaml @@ -29,7 +29,7 @@ jobs: pip install scons markdown sudo apt update sudo apt install gettext - + - name: Generate the .pot file run: scons pot @@ -43,7 +43,7 @@ jobs: # checkPot.EXPECTED_MESSAGES_WITHOUT_COMMENTS = set() # res = checkPot.checkPot('$(ls *.pot)') # exit(res) - #shell: python + #shell: python run: | python -c "import checkPot;checkPot.EXPECTED_MESSAGES_WITHOUT_COMMENTS = set();exit(checkPot.checkPot('$(ls *.pot)'))" echo "nb_errors=$?" >> "$GITHUB_OUTPUT" @@ -58,4 +58,3 @@ jobs: echo "Translators comments: FAIL" exit 1; fi - \ No newline at end of file diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml deleted file mode 100644 index 4b984cd0..00000000 --- a/.github/workflows/lint.yaml +++ /dev/null @@ -1,15 +0,0 @@ -name: Lint - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - - lint: - uses: nvdaes/nvdaAddonWorkflows/.github/workflows/lint.yaml@main - \ No newline at end of file diff --git a/.github/workflows/manualRelease.yaml b/.github/workflows/manualRelease.yaml index 54dcf356..21952ea4 100644 --- a/.github/workflows/manualRelease.yaml +++ b/.github/workflows/manualRelease.yaml @@ -3,7 +3,7 @@ name: Manual release on: workflow_dispatch: inputs: - version: + version: description: 'Add-on version' required: true default: '0.0.0' @@ -20,7 +20,7 @@ jobs: buildAndUpload: continue-on-error: true runs-on: ubuntu-latest - + permissions: contents: write @@ -50,7 +50,7 @@ jobs: f.seek(0) f.write(text) f.truncate() - shell: python + shell: python - name: Build add-on run: scons - name: Push changes @@ -67,7 +67,7 @@ jobs: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.PASSPHRASE }} - if: ${{ inputs.signAddOn }} - name: Sign add-on + name: Sign add-on run: gpg --detach-sign *.nvda-addon - name: Calculate sha256 run: sha256sum *.nvda-addon >> sha256.txt @@ -82,4 +82,3 @@ jobs: artifacts: "*.nvda-addon,*.sig,publicKey.asc,sha256.txt" generateReleaseNotes: true prerelease: ${{ inputs.prerelease }} - diff --git a/.gitignore b/.gitignore index 0be8af1c..acb5dcd3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ addon/doc/*.css addon/doc/en/ +addon/globalPlugins/MathCAT/libmathcat_py.pyd +Cargo.lock +target/ *_docHandler.py *.html manifest.ini @@ -9,3 +12,4 @@ manifest.ini *.nvda-addon .sconsign.dblite /[0-9]*.[0-9]*.[0-9]*.json +.venv/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..d5c872f7 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,96 @@ + +# https://pre-commit.ci/ +# Configuration for Continuous Integration service +ci: + skip: [pyrightLocal] + autoupdate_schedule: monthly + autoupdate_commit_msg: "Pre-commit auto-update" + autofix_commit_msg: "Pre-commit auto-fix" + submodules: true + +default_language_version: + python: python3.11 + +repos: +- repo: https://github.com/pre-commit-ci/pre-commit-ci-config + rev: v1.6.1 + hooks: + - id: check-pre-commit-ci-config + +- repo: meta + hooks: + # ensures that exclude directives apply to any file in the repository. + - id: check-useless-excludes + # ensures that the configured hooks apply to at least one file in the repository. + - id: check-hooks-apply + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + # Prevents commits to certain branches + - id: no-commit-to-branch + args: ["--branch", "main"] + # Checks that large files have not been added. Default cut-off for "large" files is 500kb. + - id: check-added-large-files + # Checks python syntax + - id: check-ast + # Checks for filenames that will conflict on case insensitive filesystems (the majority of Windows filesystems, most of the time) + - id: check-case-conflict + # Checks for artifacts from resolving merge conflicts. + - id: check-merge-conflict + # Checks Python files for debug statements, such as python's breakpoint function, or those inserted by some IDEs. + - id: debug-statements + # Removes trailing whitespace. + - id: trailing-whitespace + types_or: [python, batch, markdown, toml, yaml, rust] + # Ensures all files end in 1 (and only 1) newline. + - id: end-of-file-fixer + types_or: [python, batch, markdown, toml, yaml, rust] + # Removes the UTF-8 BOM from files that have it. + # See https://github.com/nvaccess/nvda/blob/master/projectDocs/dev/codingStandards.md#encoding + - id: fix-byte-order-marker + types_or: [python, batch, markdown, toml, yaml, rust] + # Validates TOML files. + - id: check-toml + # Validates YAML files. + - id: check-yaml + # Validates XML files. + # Ensures that links to lines in files under version control point to a particular commit. + - id: check-vcs-permalinks + # Avoids using reserved Windows filenames. + - id: check-illegal-windows-names + +- repo: https://github.com/asottile/add-trailing-comma + rev: v3.1.0 + hooks: + # Ruff preserves indent/new-line formatting of function arguments, list items, and similar iterables, + # if a trailing comma is added. + # This adds a trailing comma to args/iterable items in case it was missed. + - id: add-trailing-comma + +- repo: https://github.com/astral-sh/ruff-pre-commit + # Matches Ruff version in pyproject. + rev: v0.8.1 + hooks: + - id: ruff + name: lint with ruff + args: [ --fix ] + - id: ruff-format + name: format with ruff + +- repo: https://github.com/RobertCraigie/pyright-python + rev: v1.1.394 + hooks: + - id: pyright + alias: pyrightLocal + name: Check types with pyright + +- repo: https://github.com/RobertCraigie/pyright-python + rev: v1.1.396 + hooks: + - id: pyright + alias: pyrightCI + name: Check types with pyright + # use nodejs version of pyright and install pyproject.toml for CI + additional_dependencies: [".", "pyright[nodejs]"] + stages: [manual] # Only run from CI manually diff --git a/.vs/MathCATForPython/v15/.suo b/.vs/MathCATForPython/v15/.suo new file mode 100644 index 00000000..fb297c10 Binary files /dev/null and b/.vs/MathCATForPython/v15/.suo differ diff --git a/.vscode/settings.json b/.vscode/settings.json index 7c80e60c..89f0f706 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,6 +17,7 @@ "bil-Malti", "Bislama", "bizaad", + "Bokmål", "Bosanski", "Català", "Česky", @@ -34,8 +35,11 @@ "Diné bizaad", "Dorerin", "Dorerin Naoero", + "eigh", "English", "Español", + "espeak", + "ESPEAK", "Esperanto", "Ɛʋɛ", "Faka", @@ -90,6 +94,7 @@ "Magyar", "Majel", "Malagasy", + "mathcat", "mathml", "Melayu", "Moldovenească", @@ -141,6 +146,7 @@ "Srpskohrvatski / Српскохрватски", "SSML", "staticline", + "subexprs", "Suomi", "surrogatepass", "Svenska", @@ -228,11 +234,19 @@ "中文", "日本語" ], - "python.linting.pylintEnabled": true, + "git.ignoreLimitWarning": true, + "python.analysis.diagnosticMode": "workspace", + "python.languageServer": "Pylance", + + "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python", + "python.linting.enabled": true, - "python.linting.mypyEnabled": false, + "python.linting.ruffEnabled": true, + "python.linting.ruffPath": "ruff", + + "python.linting.pylintEnabled": false, "python.linting.flake8Enabled": false, - "python.analysis.diagnosticSeverityOverrides": { - "reportMissingImports": "none" - }, -} \ No newline at end of file + "python.linting.mypyEnabled": false, + + "cmake.configureOnOpen": false, +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..db0d7d8f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,922 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "MathCatForPython" +version = "0.7.6-beta.1" +dependencies = [ + "mathcat", + "pyo3", + "zip", +] + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys", +] + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "bzip2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea8dcd42434048e4f7a304411d9273a411f647446c1234a65ce0554923f4cff" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "backtrace", + "version_check", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "libz-rs-sys", + "miniz_oxide", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "indoc" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "jiff" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c102670231191d07d37a35af3eb77f1f0dbf7a71be51a962dcd57ea607be7260" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cdde31a9d349f1b1f51a0b3714a5940ac022976f4b49485fc04be052b183b4c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" + +[[package]] +name = "libc" +version = "0.2.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "libz-rs-sys" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221" +dependencies = [ + "zlib-rs", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "mathcat" +version = "0.7.6-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6da2158ce7343f2f728b41780a1a52b6a2a701860259755638821e99f6fab9" +dependencies = [ + "bitflags", + "cfg-if", + "dirs", + "env_logger", + "error-chain", + "fastrand", + "lazy_static", + "log", + "phf", + "radix_fmt", + "regex", + "roman-numerals-rs", + "strum", + "strum_macros", + "sxd-document", + "sxd-xpath", + "unicode-script", + "yaml-rust", + "zip", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +dependencies = [ + "adler2", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "peresil" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f658886ed52e196e850cfbbfddab9eaa7f6d90dd0929e264c31e5cec07e09e57" + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a6df7eab65fc7bee654a421404947e10a0f7085b6951bf2ea395f4659fb0cf" +dependencies = [ + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f77d387774f6f6eec64a004eac0ed525aab7fa1966d94b42f743797b3e395afb" +dependencies = [ + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dd13844a4242793e02df3e2ec093f540d948299a6a77ea9ce7afd8623f542be" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf8f9f1108270b90d3676b8679586385430e5c0bb78bb5f043f95499c821a71" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a3b2274450ba5288bc9b8c1b69ff569d1d61189d4bff38f8d22e03d17f932b" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_fmt" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce082a9940a7ace2ad4a8b7d0b1eac6aa378895f18be598230c5f2284ac05426" + +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "roman-numerals-rs" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85cd47a33a4510b1424fe796498e174c6a9cf94e606460ef022a19f3e4ff85e" + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sxd-document" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d82f37be9faf1b10a82c4bd492b74f698e40082f0f40de38ab275f31d42078" +dependencies = [ + "peresil", + "typed-arena", +] + +[[package]] +name = "sxd-xpath" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36e39da5d30887b5690e29de4c5ebb8ddff64ebd9933f98a01daaa4fd11b36ea" +dependencies = [ + "peresil", + "quick-error", + "sxd-document", +] + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typed-arena" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-script" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" + +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "zip" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" +dependencies = [ + "arbitrary", + "bzip2", + "crc32fast", + "flate2", + "indexmap", + "memchr", + "zopfli", +] + +[[package]] +name = "zlib-rs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a" + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] diff --git a/Cargo.toml b/Cargo.toml index 5f489924..1a2d204b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,9 +6,11 @@ [package] name = "MathCatForPython" -version = "0.3.8" +version = "0.7.6-beta.9" authors = ["Neil Soiffer "] -edition = "2018" +edition = "2024" +resolver = "2" # allows different build dependency features + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -19,19 +21,19 @@ name = "libmathcat_py" crate-type = ["cdylib"] [dependencies.mathcat] -# version = "0.3.8" -# for testing MathCAT without having to publish a new version (change two occurences) -path = "../MathCAT/" +version = "=0.7.6-beta.9" +# for testing MathCAT without having to publish a new version (change two occurrences) +# path = "../MathCAT/" [dependencies.pyo3] -version = "0.15.1" +version = "0.27" features = ["extension-module", "abi3"] [build-dependencies] -zip = { version = "0.6.2", default-features = false, features = ["deflate"] } -# mathcat = "0.3.8" -# for testing MathCAT without having to publish a new version (change two occurences) -mathcat = {path = "../MathCAT/"} +zip = { version = "8.5", default-features = false, features = ["bzip2"] } +mathcat = {version = "=0.7.6-beta.9", features = ["include-zip"]} # for building, we want the zip files so we can include them separately +# mathcat = { path = "../MathCAT/", features = ["include-zip"]} # for building, we want the zip files so we can include them separately + [profile.release] diff --git a/Example/test.py b/Example/test.py index ec5f562a..4e9e6609 100644 --- a/Example/test.py +++ b/Example/test.py @@ -7,69 +7,112 @@ import os +import sys +import libmathcat_py as libmathcat # import shutil # if os.path.exists("libmathcat_py.pyd"): # os.remove("libmathcat_py.pyd") # shutil.copy("..\\target\\i686-pc-windows-msvc\\release\\libmathcat_py.dll", "libmathcat.pyd") -import libmathcat - -def SetMathCATPreferences(): - try: - libmathcat.SetRulesDir( - # this assumes the Rules dir is in the same dir a the library. Modify as needed - os.path.join( os.path.dirname(os.path.abspath(__file__)), "Rules") - ) - except Exception as e: - print("problem with finding the MathCAT rules") - - try: - libmathcat.SetPreference("TTS", "none") - libmathcat.SetPreference("Language", "en") # Also "id" and "vi" - libmathcat.SetPreference("SpeechStyle", "SimpleSpeak") # Also "ClearSpeak" - libmathcat.SetPreference("Verbosity", "Verbose") # also terse "Terse"/"Medium" - libmathcat.SetPreference("CapitalLetters_UseWord", "true") # if "true", X => "cap x" - except Exception as e: - print("problem with setting a preference") - -def SetMathMLForMathCAT(mathml: str): - try: - libmathcat.SetMathML(mathml) - except Exception as e: - print("problem with SetMathML") - -def GetSpeech(): - try: - return libmathcat.GetSpokenText() - except Exception as e: - return "problem with getting speech for MathML" - - - -SetMathCATPreferences() # you only need to this once -print("Using MathCAT version '{}'".format(libmathcat.GetVersion())) - -mathml = " 1 X " -SetMathMLForMathCAT(mathml) -print("MathML: {}\nSpeech: '{}'".format(mathml, GetSpeech())) - -mathml = "xy" -SetMathMLForMathCAT(mathml) -print("MathML: {}\nSpeech: '{}'".format(mathml, GetSpeech())) - -mathml = " x 3 " -SetMathMLForMathCAT(mathml) -print("MathML: {}\nSpeech: '{}'".format(mathml, GetSpeech())) - -mathml = " x T " -SetMathMLForMathCAT(mathml) -print("MathML: {}\nSpeech: '{}'".format(mathml, GetSpeech())) - -mathml = "!" -SetMathMLForMathCAT(mathml) -print("MathML: {}\nSpeech: '{}'".format(mathml, GetSpeech())) - -mathml = "(73)" -SetMathMLForMathCAT(mathml) -print("MathML: {}\nSpeech (no inference): '{}'".format(mathml, GetSpeech())) + +def setMathCATPreferences(): + try: + libmathcat.SetRulesDir( + # this assumes the Rules dir is in the same dir a the library. Modify as needed + os.path.join(os.path.dirname(os.path.abspath(__file__)), "Rules"), + ) + except Exception as e: + sys.exit(f"problem with finding the MathCAT rules: {e}") + + try: + libmathcat.SetPreference("TTS", "none") + libmathcat.SetPreference("Language", "en") # Also "id" and "vi" + libmathcat.SetPreference("SpeechStyle", "SimpleSpeak") # Also "ClearSpeak" + libmathcat.SetPreference("Verbosity", "Verbose") # also terse "Terse"/"Medium" + libmathcat.SetPreference("CapitalLetters_UseWord", "true") # if "true", X => "cap x" + libmathcat.SetPreference("BrailleCode", "Nemeth") + except Exception as e: + sys.exit(f"problem with setting a preference: {e}") + + +def setMathMLForMathCAT(mathml: str): + try: + libmathcat.SetMathML(mathml) + except Exception as e: + sys.exit(f"problem with setMathML: {e}") + + +def getSpeech(): + try: + return libmathcat.GetSpokenText() + except Exception as e: + sys.exit(f"problem with getting speech for MathML: {e}") + + +def getBraille(): + try: + return libmathcat.GetBraille("") + except Exception as e: + sys.exit(f"problem with getting braille for MathML: {e}") + + +def test(): + setMathCATPreferences() # you only need to this once + print("Using MathCAT version '{}'".format(libmathcat.GetVersion())) + + mathml = " 1 X " + setMathMLForMathCAT(mathml) + + languages = libmathcat.GetSupportedLanguages() + if "en" not in languages: + sys.exit(f"Supported languages does not include 'en': {languages}") + speech_styles = libmathcat.GetSupportedSpeechStyles("en") + if "SimpleSpeak" not in speech_styles or "ClearSpeak" not in speech_styles: + sys.exit(f"Supported speech styles does not include 'SimpleSpeak' and/or 'ClearSpeak': {speech_styles}") + braille_codes = libmathcat.GetSupportedBrailleCodes() + if "UEB" not in braille_codes or "Nemeth" not in braille_codes: + sys.exit(f"Supported languages does not include 'UEB' and/or 'Nemeth': {braille_codes}") + speech = getSpeech() + if speech != "1 over cap x": + sys.exit(f"MathML: {mathml}\nSpeech: '{speech}'") + braille = getBraille() + if braille != "⠹⠂⠌⠠⠭⠼": + sys.exit(f"MathML: {mathml}\nBraille: '{braille}'") + + mathml = "xy" + setMathMLForMathCAT(mathml) + speech = getSpeech() + if speech != "x cross product y": + sys.exit(f"MathML: {mathml}\nSpeech: '{speech}'") + + mathml = " x 3 " + setMathMLForMathCAT(mathml) + speech = getSpeech() + if speech != "x cubed": + sys.exit(f"MathML: {mathml}\nSpeech: '{speech}'") + + mathml = " x T " + setMathMLForMathCAT(mathml) + speech = getSpeech() + if speech != "x transpose": + sys.exit(f"MathML: {mathml}\nSpeech: '{speech}'") + + mathml = "!" + setMathMLForMathCAT(mathml) + speech = getSpeech() + if speech != "x factorial": + sys.exit(f"MathML: {mathml}\nSpeech: '{speech}'") + + mathml = "\ + (73)" + setMathMLForMathCAT(mathml) + speech = getSpeech() + if speech != "7 choose 3": + sys.exit(f"MathML: {mathml}\nSpeech: '{speech}'") + + print("Test was successful!") + + +test() +sys.exit(0) diff --git a/NVDA-addon/.pre-commit-config.yaml b/NVDA-addon/.pre-commit-config.yaml new file mode 100644 index 00000000..dd7a9d69 --- /dev/null +++ b/NVDA-addon/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: check-ast + - id: check-case-conflict + - id: check-yaml diff --git a/NVDA-addon/0.2.1.json b/NVDA-addon/0.2.1.json new file mode 100644 index 00000000..9c120f94 --- /dev/null +++ b/NVDA-addon/0.2.1.json @@ -0,0 +1,29 @@ +{ + "addonId": "MathCAT", + "displayName": "MathCAT: speech and braille from MathML", + "URL": "", + "description": "\n\t\tMathCAT is a replacement for MathPlayer which has been discontinued.\n\t\tIt provides speech and braille support, and also supports MathPlayer's three modes of navigation.\n\t\tThe speech quality is not quite as good as MathPlayer's speech yet,\n\t\tbut the braille support is much better and includes both Nemeth and UEB Technical.\n\t\tTranslations to languages other than English are in progress.\n\t\t", + "sha256": "5ce5852776e1ee2e94afb01898e88f9c1c403838601f282cf152c57413f34aee", + "homepage": "https://nsoiffer.github.io/MathCAT/", + "addonVersionName": "0.2.5", + "addonVersionNumber": { + "major": 0, + "minor": 2, + "patch": 5 + }, + "minNVDAVersion": { + "major": 2018, + "minor": 1, + "patch": 0 + }, + "lastTestedVersion": { + "major": 2023, + "minor": 1, + "patch": 0 + }, + "channel": "stable", + "publisher": "", + "sourceURL": "https://github.com/NSoiffer/MathCATForPython", + "license": "MIT and GPL 2", + "licenseURL": "https://raw.githubusercontent.com/NSoiffer/MathCAT/main/LICENSE" +} diff --git a/NVDA-addon/0.2.2.json b/NVDA-addon/0.2.2.json new file mode 100644 index 00000000..905aeea6 --- /dev/null +++ b/NVDA-addon/0.2.2.json @@ -0,0 +1,29 @@ +{ + "addonId": "MathCAT", + "displayName": "MathCAT: speech and braille from MathML", + "URL": "", + "description": "\n\t\tMathCAT is a replacement for MathPlayer which has been discontinued.\n\t\tIt provides speech and braille support, and also supports MathPlayer's three modes of navigation.\n\t\tThe speech quality is not quite as good as MathPlayer's speech yet,\n\t\tbut the braille support is much better and includes both Nemeth and UEB Technical.\n\t\tTranslations to languages other than English are in progress.\n\t\t", + "sha256": "017a2817b54de419eef2dcac125e67210941b87e65556ba83670fea69f59bb2c", + "homepage": "https://nsoiffer.github.io/MathCAT/", + "addonVersionName": "0.2.5", + "addonVersionNumber": { + "major": 0, + "minor": 2, + "patch": 5 + }, + "minNVDAVersion": { + "major": 2018, + "minor": 1, + "patch": 0 + }, + "lastTestedVersion": { + "major": 2023, + "minor": 1, + "patch": 0 + }, + "channel": "stable", + "publisher": "", + "sourceURL": "https://github.com/NSoiffer/MathCATForPython", + "license": "MIT and GPL 2", + "licenseURL": "https://raw.githubusercontent.com/NSoiffer/MathCAT/main/LICENSE" +} diff --git a/NVDA-addon/0.2.5.json b/NVDA-addon/0.2.5.json new file mode 100644 index 00000000..5ab2de4c --- /dev/null +++ b/NVDA-addon/0.2.5.json @@ -0,0 +1,29 @@ +{ + "addonId": "MathCAT", + "displayName": "MathCAT: speech and braille from MathML", + "URL": "", + "description": "\n\t\tMathCAT is a replacement for MathPlayer which has been discontinued.\n\t\tIt provides speech and braille support, and also supports MathPlayer's three modes of navigation.\n\t\tThe speech quality is not quite as good as MathPlayer's speech yet,\n\t\tbut the braille support is much better and includes both Nemeth and UEB Technical.\n\t\tTranslations to languages other than English are in progress.\n\t\t", + "sha256": "04b7c28990b83d0a1880bc4f93a2ead1c1d3bd9dd91da4e626e0e1b1d566c11c", + "homepage": "https://nsoiffer.github.io/MathCAT/", + "addonVersionName": "0.2.5", + "addonVersionNumber": { + "major": 0, + "minor": 2, + "patch": 5 + }, + "minNVDAVersion": { + "major": 2018, + "minor": 1, + "patch": 0 + }, + "lastTestedVersion": { + "major": 2023, + "minor": 1, + "patch": 0 + }, + "channel": "stable", + "publisher": "", + "sourceURL": "https://github.com/NSoiffer/MathCATForPython", + "license": "MIT and GPL 2", + "licenseURL": "https://raw.githubusercontent.com/NSoiffer/MathCAT/main/LICENSE" +} \ No newline at end of file diff --git a/NVDA-addon/0.2.6.json b/NVDA-addon/0.2.6.json new file mode 100644 index 00000000..c3af3e7a --- /dev/null +++ b/NVDA-addon/0.2.6.json @@ -0,0 +1,29 @@ +{ + "addonId": "MathCAT", + "displayName": "MathCAT: speech and braille from MathML", + "URL": "", + "description": "MathCAT is a replacement for MathPlayer which has been discontinued.\n\t\tIt provides speech and braille support, and also supports MathPlayer's three modes of navigation.\n\t\tThe speech quality is not quite as good as MathPlayer's speech yet,\n\t\tbut the braille support is much better and includes both Nemeth and UEB Technical.\n\t\tTranslations to languages other than English are in progress.", + "sha256": "b277935590bec4b6b78a99824c890f2a9114e69fdd6050c1ed1af828f1c2d8d4", + "homepage": "https://nsoiffer.github.io/MathCAT/", + "addonVersionName": "0.2.6", + "addonVersionNumber": { + "major": 0, + "minor": 2, + "patch": 6 + }, + "minNVDAVersion": { + "major": 2019, + "minor": 3, + "patch": 0 + }, + "lastTestedVersion": { + "major": 2023, + "minor": 1, + "patch": 0 + }, + "channel": "stable", + "publisher": "", + "sourceURL": "https://github.com/NSoiffer/MathCATForPython", + "license": "MIT and GPL 2", + "licenseURL": "https://raw.githubusercontent.com/NSoiffer/MathCAT/main/LICENSE" +} \ No newline at end of file diff --git a/NVDA-addon/0.3.0.json b/NVDA-addon/0.3.0.json new file mode 100644 index 00000000..3d148eb0 --- /dev/null +++ b/NVDA-addon/0.3.0.json @@ -0,0 +1,29 @@ +{ + "addonId": "MathCAT", + "displayName": "MathCAT: speech and braille from MathML", + "URL": "", + "description": "MathCAT is a replacement for MathPlayer which has been discontinued.\n\t\tIt provides speech and braille support, and also supports MathPlayer's three modes of navigation.\n\t\tThe speech quality is not quite as good as MathPlayer's speech yet,\n\t\tbut the braille support is much better and includes both Nemeth and UEB Technical.\n\t\tTranslations to Indonesian, Spanish, and Vietnamese exist and other translations are in progress.", + "sha256": "8be828b0b4ec48500e1dba517d467f63e7af121b6c0b805c2b31fae920e3fa32", + "homepage": "https://nsoiffer.github.io/MathCAT/", + "addonVersionName": "0.3.0", + "addonVersionNumber": { + "major": 0, + "minor": 3, + "patch": 0 + }, + "minNVDAVersion": { + "major": 2019, + "minor": 3, + "patch": 0 + }, + "lastTestedVersion": { + "major": 2023, + "minor": 1, + "patch": 0 + }, + "channel": "stable", + "publisher": "", + "sourceURL": "https://github.com/NSoiffer/MathCATForPython", + "license": "MIT and GPL 2", + "licenseURL": "https://raw.githubusercontent.com/NSoiffer/MathCAT/main/LICENSE" +} \ No newline at end of file diff --git a/NVDA-addon/0.3.1.json b/NVDA-addon/0.3.1.json new file mode 100644 index 00000000..60bfaf00 --- /dev/null +++ b/NVDA-addon/0.3.1.json @@ -0,0 +1,29 @@ +{ + "addonId": "MathCAT", + "displayName": "MathCAT: speech and braille from MathML", + "URL": "", + "description": "MathCAT is a replacement for MathPlayer which has been discontinued.\n\t\tIt provides speech and braille support, and also supports MathPlayer's three modes of navigation.\n\t\tThe speech quality is not quite as good as MathPlayer's speech yet,\n\t\tbut the braille support is much better and includes both Nemeth and UEB Technical.\n\t\tTranslations to Indonesian, Spanish, and Vietnamese exist and other translations are in progress.", + "sha256": "04747f219c34e9bd8716fa92d18f9e84ab71c09e0603960ecb84cb581f5428bd", + "homepage": "https://nsoiffer.github.io/MathCAT/", + "addonVersionName": "0.3.1", + "addonVersionNumber": { + "major": 0, + "minor": 3, + "patch": 1 + }, + "minNVDAVersion": { + "major": 2019, + "minor": 3, + "patch": 0 + }, + "lastTestedVersion": { + "major": 2023, + "minor": 1, + "patch": 0 + }, + "channel": "stable", + "publisher": "", + "sourceURL": "https://github.com/NSoiffer/MathCATForPython", + "license": "MIT and GPL 2", + "licenseURL": "https://raw.githubusercontent.com/NSoiffer/MathCAT/main/LICENSE" +} \ No newline at end of file diff --git a/NVDA-addon/0.3.2.json b/NVDA-addon/0.3.2.json new file mode 100644 index 00000000..cfe1d622 --- /dev/null +++ b/NVDA-addon/0.3.2.json @@ -0,0 +1,29 @@ +{ + "addonId": "MathCAT", + "displayName": "MathCAT: speech and braille from MathML", + "URL": "", + "description": "MathCAT is a replacement for MathPlayer which has been discontinued.\n\t\tIt provides speech and braille support, and also supports MathPlayer's three modes of navigation.\n\t\tThe speech quality is not quite as good as MathPlayer's speech yet,\n\t\tbut the braille support is much better and includes both Nemeth and UEB Technical.\n\t\tTranslations to Indonesian, Spanish, and Vietnamese exist and other translations are in progress.", + "sha256": "e4cc3984c1c29ea9cc0aee092ad1b1562fff3cf5a024b4e52c2470bcfca1b2cd", + "homepage": "https://nsoiffer.github.io/MathCAT/", + "addonVersionName": "0.3.2", + "addonVersionNumber": { + "major": 0, + "minor": 3, + "patch": 2 + }, + "minNVDAVersion": { + "major": 2019, + "minor": 3, + "patch": 0 + }, + "lastTestedVersion": { + "major": 2023, + "minor": 1, + "patch": 0 + }, + "channel": "stable", + "publisher": "", + "sourceURL": "https://github.com/NSoiffer/MathCATForPython", + "license": "MIT and GPL 2", + "licenseURL": "https://raw.githubusercontent.com/NSoiffer/MathCAT/main/LICENSE" +} \ No newline at end of file diff --git a/NVDA-addon/0.3.3.json b/NVDA-addon/0.3.3.json new file mode 100644 index 00000000..868f3f19 --- /dev/null +++ b/NVDA-addon/0.3.3.json @@ -0,0 +1,29 @@ +{ + "addonId": "MathCAT", + "displayName": "MathCAT: speech and braille from MathML", + "URL": "", + "description": "MathCAT is a replacement for MathPlayer which has been discontinued.\n\t\tIt provides speech and braille support, and also supports MathPlayer's three modes of navigation.\n\t\tThe speech quality is not quite as good as MathPlayer's speech yet,\n\t\tbut the braille support is much better and includes both Nemeth and UEB Technical.\n\t\tTranslations to Indonesian, Spanish, and Vietnamese exist and other translations are in progress.", + "sha256": "d48761ace19e2d6de3b2ef0e7e31c5d8a4ba275e364f9c32f72036e49b67ec08", + "homepage": "https://nsoiffer.github.io/MathCAT/", + "addonVersionName": "0.3.3", + "addonVersionNumber": { + "major": 0, + "minor": 3, + "patch": 3 + }, + "minNVDAVersion": { + "major": 2019, + "minor": 3, + "patch": 0 + }, + "lastTestedVersion": { + "major": 2023, + "minor": 1, + "patch": 0 + }, + "channel": "stable", + "publisher": "", + "sourceURL": "https://github.com/NSoiffer/MathCATForPython", + "license": "MIT and GPL 2", + "licenseURL": "https://raw.githubusercontent.com/NSoiffer/MathCAT/main/LICENSE" +} \ No newline at end of file diff --git a/NVDA-addon/MathCAT-0.2.0.json b/NVDA-addon/MathCAT-0.2.0.json new file mode 100644 index 00000000..6247af25 --- /dev/null +++ b/NVDA-addon/MathCAT-0.2.0.json @@ -0,0 +1,29 @@ +{ + "addonId": "MathCAT", + "displayName": "MathCAT: speech and braille from MathML", + "URL": "", + "description": "\n\t\tMathCAT is a replacement for MathPlayer which has been discontinued.\n\t\tIt provides speech and braille support, and also supports MathPlayer's three modes of navigation.\n\t\tThe speech quality is not quite as good as MathPlayer's speech yet,\n\t\tbut the braille support is much better and includes both Nemeth and UEB Technical.\n\t\tTranslations to languages other than English are in progress.\n\t\t", + "sha256": "86b80eebde3e30c2b9005a1b7e8c1adff4197eb172d93fde51196c813e6c7440", + "homepage": "https://nsoiffer.github.io/MathCAT/", + "addonVersionName": "0.2.5", + "addonVersionNumber": { + "major": 0, + "minor": 2, + "patch": 5 + }, + "minNVDAVersion": { + "major": 2018, + "minor": 1, + "patch": 0 + }, + "lastTestedVersion": { + "major": 2023, + "minor": 1, + "patch": 0 + }, + "channel": "stable", + "publisher": "", + "sourceURL": "https://github.com/NSoiffer/MathCATForPython", + "license": "MIT and GPL 2", + "licenseURL": "https://raw.githubusercontent.com/NSoiffer/MathCAT/main/LICENSE" +} diff --git a/NVDA-addon/README.md.bak b/NVDA-addon/README.md.bak new file mode 100644 index 00000000..e09b173e --- /dev/null +++ b/NVDA-addon/README.md.bak @@ -0,0 +1,28 @@ +# MathCAT + +* Author: Neil Soiffer +* NVDA compatibility: 2018.1 or later (untested in earlier versions) +* Download [stable version][1] + +MathCAT is designed to eventually replace MathPlayer because MathPlayer is no longer supported. MathCAT generates speech and braille from MathML. The speech for math produced by MathCAT is enhanced with prosody so that it sounds more natural. The speech can be navigated in three modes using the same commands as MathPlayer. In addition, the navigation node is indicated on a braille display. Both Nemeth and UEB technical are supported. + +MathCAT adds a settings menu to NVDA's preferences menu. In the settings menu, numerous options in MathCAT can be set to control the speech, navigation, and braille. + +For full user documentation, please see the [MathCAT User Documentation](https://nsoiffer.github.io/MathCAT/users.html). For information on the MathCAT project in general, see the main [MathCAT Documentation page](https://nsoiffer.github.io/MathCAT). + + +Who should use MathCAT: + +* Those who need high quality Nemeth braille (MathPlayer's Nemeth is based on liblouis' Nemeth generation which has a number of significant bugs that are technically difficult to fix). +* Those who need UEB technical braille +* Those who want to try out the latest technology and are willing to help by reporting bugs +* Those who use Eloquence as a voice + +Who should NOT use MathCAT: + +* Anyone who uses MathPlayer with a non-English language (translations exist for Indonesian and Vietnamese; translations will be coming in the future) +* Anyone who uses MathPlayer with a non-Nemeth/non-UEB braille output (contact me if you want to help out with a braille translation) +* Anyone who uses MathPlayer to read Chemical Formulas (that will hopefully show up in the next non-bug release) +* Anyone who prefers Access8Math to MathPlayer (for speech or other features) + +MathCAT's rules for speech are not yet as extensive as MathPlayer's rules -- that may be another reason to stick with MathPlayer. MathCAT is being used as a testbed for ideas for MathML 4 that allow authors to express their intent so that ambiguous notations can be spoken correctly and not guessed at. I have held off on adding too many rules since the architecture of MathCAT is centered around using and inferring author intent and these are not fully settled yet. \ No newline at end of file diff --git a/NVDA-addon/_template_addon_release.json b/NVDA-addon/_template_addon_release.json new file mode 100644 index 00000000..c6d3a5fe --- /dev/null +++ b/NVDA-addon/_template_addon_release.json @@ -0,0 +1,29 @@ +{ + "addonId": "easyAddonTech.XYZ", + "addonVersionNumber": { + "major": 21, + "minor": 6, + "patch": 0 + }, + "addonVersionName": "21.06", + "displayName": "My addon", + "publisher": "easyAddonTech", + "description": "Makes doing XYZ easier", + "homepage": "https://github.com/nvaccess/addon-datastore", + "minNVDAVersion": { + "major": 2019, + "minor": 3, + "patch": 0 + }, + "lastTestedVersion": { + "major": 2020, + "minor": 4, + "patch": 0 + }, + "channel": "beta", + "URL": "https://github.com/nvaccess/addon-datastore/releases/download/v0.1.0/myAddon.nvda-addon", + "sha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82", + "sourceURL": "https://github.com/nvaccess/addon-datastore/", + "license": "GPL v2", + "licenseURL": "https://github.com/nvaccess/addon-datastore/license.MD" +} diff --git a/NVDA-addon/addon/.github/workflows/build_addon.yml b/NVDA-addon/addon/.github/workflows/build_addon.yml new file mode 100644 index 00000000..9b97cd9f --- /dev/null +++ b/NVDA-addon/addon/.github/workflows/build_addon.yml @@ -0,0 +1,64 @@ +name: build addon + +on: + push: + tags: ["*"] + # To build on main/master branch, uncomment the following line: + # branches: [ main , master ] + + pull_request: + branches: [ main, master ] + + workflow_dispatch: + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - run: echo -e "pre-commit\nscons\nmarkdown">requirements.txt + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.9 + cache: 'pip' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip wheel + pip install -r requirements.txt + sudo apt-get update -y + sudo apt-get install -y gettext + + - name: Code checks + run: export SKIP=no-commit-to-branch; pre-commit run --all + + - name: building addon + run: scons + + - uses: actions/upload-artifact@v3 + with: + name: packaged_addon + path: ./*.nvda-addon + + upload_release: + runs-on: ubuntu-latest + if: ${{ startsWith(github.ref, 'refs/tags/') }} + needs: ["build"] + steps: + - uses: actions/checkout@v3 + - name: download releases files + uses: actions/download-artifact@v3 + - name: Display structure of downloaded files + run: ls -R + + - name: Release + uses: softprops/action-gh-release@v1 + with: + files: packaged_addon/*.nvda-addon + fail_on_unmatched_files: true + prerelease: ${{ contains(github.ref, '-') }} diff --git a/README.md b/NVDA-addon/addon/doc/en/readme.md similarity index 81% rename from README.md rename to NVDA-addon/addon/doc/en/readme.md index 3a856636..0cd0000e 100644 --- a/README.md +++ b/NVDA-addon/addon/doc/en/readme.md @@ -59,20 +59,3 @@ This release has a number of bug fixes in it. The major new features and bug fix There are lots of small tweaks to the speech and some bug fixes for both Nemeth and UEB. Note: there is now an option to get Vietnam's braille standard as braille output. This is still a work in progress and is too buggy to be used other than for testing. I expect the next MathCAT release will contain a reliable implementation. - -### Version 0.3.8 -Braille: -* Dialog has been internationalized for several languages (many thanks to the translators!) -* Initial implementation of CMU -- the braille code used in Spanish and Portuguese speaking countries -* Fix some UEB bugs and added some characters for UEB -* Significant improvements to Vietnamese braille - -Other fixes: -* Change relative rate dialog slider to have a maximum value of 100% (now only allows setting slower rates). Also, added step sizes so it is easier to raise/lower the rate significantly. -* Fix espeak bug that sometimes cut off speech when the relative rate was changed -* Improvements to Vietnamese speech -* Fixed bug with OneCore voices saying "a" -* Fixed some navigation bugs when `AutoZoomOut` is False (not the default) -* Fix updating around language changes and some other dialog changes so they take effect immediately upon clicking "Apply" or "OK". -* Several improvements for cleaning up poor MathML code - diff --git a/NVDA-addon/addon/doc/id/readme.md b/NVDA-addon/addon/doc/id/readme.md new file mode 100644 index 00000000..1b14b811 --- /dev/null +++ b/NVDA-addon/addon/doc/id/readme.md @@ -0,0 +1,27 @@ +# MathCAT + +- Penulis: Neil Soiffer +- Kompatibilitas NVDA: 2018.1 atau lebih baru (belum diuji di versi sebelumnya) +- Unduh [versi stabil][1] + +MathCAT dirancang untuk menggantikan MathPlayer karena MathPlayer tidak lagi didukung. MathCAT menghasilkan ucapan dan braille dari MathML. Ucapan untuk matematika yang dihasilkan oleh MathCAT ditingkatkan dengan prosodi sehingga terdengar lebih alami. Ucapan dapat dinavigasi dalam tiga mode menggunakan perintah yang sama seperti MathPlayer. Selain itu, simpul navigasi ditunjukkan pada tampilan braille. Mendukung braille Nemeth dan UEB. + +MathCAT menambahkan menu pengaturan ke menu preferensi NVDA. Di menu pengaturan, banyak opsi di MathCAT dapat diatur untuk mengontrol ucapan, navigasi, dan braille. + +Untuk dokumentasi pengguna secara lengkap, silakan lihat [Dokumentasi Pengguna MathCAT](https://nsoiffer.github.io/MathCAT/users.html). Untuk informasi tentang proyek MathCAT secara umum, lihat [Dokumentasi MathCAT](https://nsoiffer.github.io/MathCAT). + +Siapa yang boleh menggunakan MathCAT: + +- Mereka yang membutuhkan Nemeth braille (Nemeth MathPlayer didasarkan pada generasi Nemeth liblouis yang memiliki sejumlah bug signifikan yang secara teknis sulit untuk diperbaiki). +- Mereka yang membutuhkan braille UEB +- Mereka yang ingin mencoba teknologi terbaru dan bersedia membantu dengan melaporkan bug +- Mereka yang menggunakan Eloquence sebagai suara + +Siapa yang TIDAK boleh menggunakan MathCAT: + +- Siapa pun yang menggunakan MathPlayer dengan bahasa non-Inggris (terjemahan akan tersedia di masa mendatang) +- Siapa pun yang menggunakan MathPlayer dengan keluaran braille non-Nemeth/non-UEB (hubungi saya jika Anda ingin membantu dengan terjemahan braille) +- Siapa pun yang menggunakan MathPlayer untuk membaca Rumus Kimia (yang diharapkan akan muncul di rilis non-bug berikutnya) +- Siapa saja yang lebih memilih Access8Math daripada MathPlayer (untuk ucapan atau fitur lainnya) + +Aturan MathCAT untuk pengucapan belum seluas aturan MathPlayer -- itu mungkin alasan lain untuk tetap menggunakan MathPlayer. MathCAT digunakan sebagai testbed untuk ide-ide untuk MathML 4 yang memungkinkan penulis untuk mengekspresikan maksud mereka sehingga notasi ambigu dapat diucapkan dengan benar dan tidak ditebak. Saya telah menunda menambahkan terlalu banyak aturan karena arsitektur MathCAT berpusat di sekitar penggunaan dan menyimpulkan maksud penulis dan ini belum sepenuhnya diselesaikan. diff --git a/NVDA-addon/addon/doc/style.css b/NVDA-addon/addon/doc/style.css new file mode 100644 index 00000000..90f99d14 --- /dev/null +++ b/NVDA-addon/addon/doc/style.css @@ -0,0 +1,26 @@ +@charset "utf-8"; +body { +font-family : Verdana, Arial, Helvetica, Sans-serif; +line-height: 1.2em; +} +h1, h2 {text-align: center} +dt { +font-weight : bold; +float : left; +width: 10%; +clear: left +} +dd { +margin : 0 0 0.4em 0; +float : left; +width: 90%; +display: block; +} +p { clear : both; +} +a { text-decoration : underline; +} +:active { +text-decoration : none; +} +a:focus, a:hover {outline: solid} diff --git a/NVDA-addon/addon/globalPlugins/MathCAT/libmathcat.pyd b/NVDA-addon/addon/globalPlugins/MathCAT/libmathcat.pyd new file mode 100644 index 00000000..3b3ef982 Binary files /dev/null and b/NVDA-addon/addon/globalPlugins/MathCAT/libmathcat.pyd differ diff --git a/NVDA-addon/buildVars.py.bak b/NVDA-addon/buildVars.py.bak new file mode 100644 index 00000000..73ae47e6 --- /dev/null +++ b/NVDA-addon/buildVars.py.bak @@ -0,0 +1,79 @@ +# -*- coding: UTF-8 -*- + +# Build customizations +# Change this file instead of sconstruct or manifest files, whenever possible. + + +# Since some strings in `addon_info` are translatable, +# we need to include them in the .po files. +# Gettext recognizes only strings given as parameters to the `_` function. +# To avoid initializing translations in this module we simply roll our own "fake" `_` function +# which returns whatever is given to it as an argument. +def _(arg): + return arg + + +# Add-on information variables +addon_info = { + # add-on Name/identifier, internal for NVDA + "addon_name": "addonTemplate", + # Add-on summary, usually the user visible name of the addon. + # Translators: Summary for this add-on + # to be shown on installation and add-on information found in Add-ons Manager. + "addon_summary": _("Add-on user visible name"), + # Add-on description + # Translators: Long description to be shown for this add-on on add-on information from add-ons manager + "addon_description": _("""Description for the add-on. +It can span multiple lines."""), + # version + "addon_version": "x.y", + # Author(s) + "addon_author": "name ", + # URL for the add-on documentation support + "addon_url": None, + # URL for the add-on repository where the source code can be found + "addon_sourceURL": None, + # Documentation file name + "addon_docFileName": "readme.html", + # Minimum NVDA version supported (e.g. "2018.3.0", minor version is optional) + "addon_minimumNVDAVersion": None, + # Last NVDA version supported/tested (e.g. "2018.4.0", ideally more recent than minimum version) + "addon_lastTestedNVDAVersion": None, + # Add-on update channel (default is None, denoting stable releases, + # and for development releases, use "dev".) + # Do not change unless you know what you are doing! + "addon_updateChannel": None, + # Add-on license such as GPL 2 + "addon_license": None, + # URL for the license document the ad-on is licensed under + "addon_licenseURL": None, +} + +# Define the python files that are the sources of your add-on. +# You can either list every file (using ""/") as a path separator, +# or use glob expressions. +# For example to include all files with a ".py" extension from the "globalPlugins" dir of your add-on +# the list can be written as follows: +# pythonSources = ["addon/globalPlugins/*.py"] +# For more information on SCons Glob expressions please take a look at: +# https://scons.org/doc/production/HTML/scons-user/apd.html +pythonSources = [] + +# Files that contain strings for translation. Usually your python sources +i18nSources = pythonSources + ["buildVars.py"] + +# Files that will be ignored when building the nvda-addon file +# Paths are relative to the addon directory, not to the root directory of your addon sources. +excludedFiles = [] + +# Base language for the NVDA add-on +# If your add-on is written in a language other than english, modify this variable. +# For example, set baseLanguage to "es" if your add-on is primarily written in spanish. +baseLanguage = "en" + +# Markdown extensions for add-on documentation +# Most add-ons do not require additional Markdown extensions. +# If you need to add support for markup such as tables, fill out the below list. +# Extensions string must be of the form "markdown.extensions.extensionName" +# e.g. "markdown.extensions.tables" to add tables. +markdownExtensions = [] diff --git a/NVDA-addon/doc/id/readme.md b/NVDA-addon/doc/id/readme.md new file mode 100644 index 00000000..1b14b811 --- /dev/null +++ b/NVDA-addon/doc/id/readme.md @@ -0,0 +1,27 @@ +# MathCAT + +- Penulis: Neil Soiffer +- Kompatibilitas NVDA: 2018.1 atau lebih baru (belum diuji di versi sebelumnya) +- Unduh [versi stabil][1] + +MathCAT dirancang untuk menggantikan MathPlayer karena MathPlayer tidak lagi didukung. MathCAT menghasilkan ucapan dan braille dari MathML. Ucapan untuk matematika yang dihasilkan oleh MathCAT ditingkatkan dengan prosodi sehingga terdengar lebih alami. Ucapan dapat dinavigasi dalam tiga mode menggunakan perintah yang sama seperti MathPlayer. Selain itu, simpul navigasi ditunjukkan pada tampilan braille. Mendukung braille Nemeth dan UEB. + +MathCAT menambahkan menu pengaturan ke menu preferensi NVDA. Di menu pengaturan, banyak opsi di MathCAT dapat diatur untuk mengontrol ucapan, navigasi, dan braille. + +Untuk dokumentasi pengguna secara lengkap, silakan lihat [Dokumentasi Pengguna MathCAT](https://nsoiffer.github.io/MathCAT/users.html). Untuk informasi tentang proyek MathCAT secara umum, lihat [Dokumentasi MathCAT](https://nsoiffer.github.io/MathCAT). + +Siapa yang boleh menggunakan MathCAT: + +- Mereka yang membutuhkan Nemeth braille (Nemeth MathPlayer didasarkan pada generasi Nemeth liblouis yang memiliki sejumlah bug signifikan yang secara teknis sulit untuk diperbaiki). +- Mereka yang membutuhkan braille UEB +- Mereka yang ingin mencoba teknologi terbaru dan bersedia membantu dengan melaporkan bug +- Mereka yang menggunakan Eloquence sebagai suara + +Siapa yang TIDAK boleh menggunakan MathCAT: + +- Siapa pun yang menggunakan MathPlayer dengan bahasa non-Inggris (terjemahan akan tersedia di masa mendatang) +- Siapa pun yang menggunakan MathPlayer dengan keluaran braille non-Nemeth/non-UEB (hubungi saya jika Anda ingin membantu dengan terjemahan braille) +- Siapa pun yang menggunakan MathPlayer untuk membaca Rumus Kimia (yang diharapkan akan muncul di rilis non-bug berikutnya) +- Siapa saja yang lebih memilih Access8Math daripada MathPlayer (untuk ucapan atau fitur lainnya) + +Aturan MathCAT untuk pengucapan belum seluas aturan MathPlayer -- itu mungkin alasan lain untuk tetap menggunakan MathPlayer. MathCAT digunakan sebagai testbed untuk ide-ide untuk MathML 4 yang memungkinkan penulis untuk mengekspresikan maksud mereka sehingga notasi ambigu dapat diucapkan dengan benar dan tidak ditebak. Saya telah menunda menambahkan terlalu banyak aturan karena arsitektur MathCAT berpusat di sekitar penggunaan dan menyimpulkan maksud penulis dan ini belum sepenuhnya diselesaikan. diff --git a/NVDA-addon/doc/style.css b/NVDA-addon/doc/style.css new file mode 100644 index 00000000..aac17be3 --- /dev/null +++ b/NVDA-addon/doc/style.css @@ -0,0 +1,26 @@ +@charset "utf-8"; +body { +font-family : Verdana, Arial, Helvetica, Sans-serif; +line-height: 1.2em; +} +h1, h2 {text-align: center} +dt { +font-weight : bold; +float : left; +width: 10%; +clear: left +} +dd { +margin : 0 0 0.4em 0; +float : left; +width: 90%; +display: block; +} +p { clear : both; +} +a { text-decoration : underline; +} +:active { +text-decoration : none; +} +a:focus, a:hover {outline: solid} diff --git a/NVDA-addon/sconstruct.bak b/NVDA-addon/sconstruct.bak new file mode 100644 index 00000000..ab61654e --- /dev/null +++ b/NVDA-addon/sconstruct.bak @@ -0,0 +1,236 @@ +# NVDA add-on template SCONSTRUCT file +# Copyright (C) 2012-2021 Rui Batista, Noelia Martinez, Joseph Lee +# This file is covered by the GNU General Public License. +# See the file COPYING.txt for more details. + +import codecs +import gettext +import os +import os.path +import zipfile +import sys + +# While names imported below are available by default in every SConscript +# Linters aren't aware about them. +# To avoid Flake8 F821 warnings about them they are imported explicitly. +# When using other Scons functions please add them to the line below. +from SCons.Script import BoolVariable, Builder, Copy, Environment, Variables + +sys.dont_write_bytecode = True + +# Bytecode should not be written for build vars module to keep the repository root folder clean. +import buildVars # NOQA: E402 + + +def md2html(source, dest): + import markdown + # Use extensions if defined. + mdExtensions = buildVars.markdownExtensions + lang = os.path.basename(os.path.dirname(source)).replace('_', '-') + localeLang = os.path.basename(os.path.dirname(source)) + try: + _ = gettext.translation("nvda", localedir=os.path.join("addon", "locale"), languages=[localeLang]).gettext + summary = _(buildVars.addon_info["addon_summary"]) + except Exception: + summary = buildVars.addon_info["addon_summary"] + title = "{addonSummary} {addonVersion}".format( + addonSummary=summary, addonVersion=buildVars.addon_info["addon_version"] + ) + headerDic = { + "[[!meta title=\"": "# ", + "\"]]": " #", + } + with codecs.open(source, "r", "utf-8") as f: + mdText = f.read() + for k, v in headerDic.items(): + mdText = mdText.replace(k, v, 1) + htmlText = markdown.markdown(mdText, extensions=mdExtensions) + # Optimization: build resulting HTML text in one go instead of writing parts separately. + docText = "\n".join([ + "", + "" % lang, + "", + "" + "", + "", + "%s" % title, + "\n", + htmlText, + "\n" + ]) + with codecs.open(dest, "w", "utf-8") as f: + f.write(docText) + + +def mdTool(env): + mdAction = env.Action( + lambda target, source, env: md2html(source[0].path, target[0].path), + lambda target, source, env: 'Generating % s' % target[0], + ) + mdBuilder = env.Builder( + action=mdAction, + suffix='.html', + src_suffix='.md', + ) + env['BUILDERS']['markdown'] = mdBuilder + + +vars = Variables() +vars.Add("version", "The version of this build", buildVars.addon_info["addon_version"]) +vars.Add(BoolVariable("dev", "Whether this is a daily development version", False)) +vars.Add("channel", "Update channel for this build", buildVars.addon_info["addon_updateChannel"]) + +env = Environment(variables=vars, ENV=os.environ, tools=['gettexttool', mdTool]) +env.Append(**buildVars.addon_info) + +if env["dev"]: + import datetime + buildDate = datetime.datetime.now() + year, month, day = str(buildDate.year), str(buildDate.month), str(buildDate.day) + env["addon_version"] = "".join([year, month.zfill(2), day.zfill(2), "-dev"]) + env["channel"] = "dev" +elif env["version"] is not None: + env["addon_version"] = env["version"] +if "channel" in env and env["channel"] is not None: + env["addon_updateChannel"] = env["channel"] + +buildVars.addon_info["addon_version"] = env["addon_version"] +buildVars.addon_info["addon_updateChannel"] = env["addon_updateChannel"] + +addonFile = env.File("${addon_name}-${addon_version}.nvda-addon") + + +def addonGenerator(target, source, env, for_signature): + action = env.Action( + lambda target, source, env: createAddonBundleFromPath(source[0].abspath, target[0].abspath) and None, + lambda target, source, env: "Generating Addon %s" % target[0] + ) + return action + + +def manifestGenerator(target, source, env, for_signature): + action = env.Action( + lambda target, source, env: generateManifest(source[0].abspath, target[0].abspath) and None, + lambda target, source, env: "Generating manifest %s" % target[0] + ) + return action + + +def translatedManifestGenerator(target, source, env, for_signature): + dir = os.path.abspath(os.path.join(os.path.dirname(str(source[0])), "..")) + lang = os.path.basename(dir) + action = env.Action( + lambda target, source, env: generateTranslatedManifest(source[1].abspath, lang, target[0].abspath) and None, + lambda target, source, env: "Generating translated manifest %s" % target[0] + ) + return action + + +env['BUILDERS']['NVDAAddon'] = Builder(generator=addonGenerator) +env['BUILDERS']['NVDAManifest'] = Builder(generator=manifestGenerator) +env['BUILDERS']['NVDATranslatedManifest'] = Builder(generator=translatedManifestGenerator) + + +def createAddonHelp(dir): + docsDir = os.path.join(dir, "doc") + if os.path.isfile("style.css"): + cssPath = os.path.join(docsDir, "style.css") + cssTarget = env.Command(cssPath, "style.css", Copy("$TARGET", "$SOURCE")) + env.Depends(addon, cssTarget) + if os.path.isfile("readme.md"): + readmePath = os.path.join(docsDir, buildVars.baseLanguage, "readme.md") + readmeTarget = env.Command(readmePath, "readme.md", Copy("$TARGET", "$SOURCE")) + env.Depends(addon, readmeTarget) + + +def createAddonBundleFromPath(path, dest): + """ Creates a bundle from a directory that contains an addon manifest file.""" + basedir = os.path.abspath(path) + with zipfile.ZipFile(dest, 'w', zipfile.ZIP_DEFLATED) as z: + # FIXME: the include/exclude feature may or may not be useful. Also python files can be pre-compiled. + for dir, dirnames, filenames in os.walk(basedir): + relativePath = os.path.relpath(dir, basedir) + for filename in filenames: + pathInBundle = os.path.join(relativePath, filename) + absPath = os.path.join(dir, filename) + if pathInBundle not in buildVars.excludedFiles: + z.write(absPath, pathInBundle) + return dest + + +def generateManifest(source, dest): + addon_info = buildVars.addon_info + with codecs.open(source, "r", "utf-8") as f: + manifest_template = f.read() + manifest = manifest_template.format(**addon_info) + with codecs.open(dest, "w", "utf-8") as f: + f.write(manifest) + + +def generateTranslatedManifest(source, language, out): + _ = gettext.translation("nvda", localedir=os.path.join("addon", "locale"), languages=[language]).gettext + vars = {} + for var in ("addon_summary", "addon_description"): + vars[var] = _(buildVars.addon_info[var]) + with codecs.open(source, "r", "utf-8") as f: + manifest_template = f.read() + result = manifest_template.format(**vars) + with codecs.open(out, "w", "utf-8") as f: + f.write(result) + + +def expandGlobs(files): + return [f for pattern in files for f in env.Glob(pattern)] + + +addon = env.NVDAAddon(addonFile, env.Dir('addon')) + +langDirs = [f for f in env.Glob(os.path.join("addon", "locale", "*"))] + +# Allow all NVDA's gettext po files to be compiled in source/locale, and manifest files to be generated +for dir in langDirs: + poFile = dir.File(os.path.join("LC_MESSAGES", "nvda.po")) + moFile = env.gettextMoFile(poFile) + env.Depends(moFile, poFile) + translatedManifest = env.NVDATranslatedManifest( + dir.File("manifest.ini"), + [moFile, os.path.join("manifest-translated.ini.tpl")] + ) + env.Depends(translatedManifest, ["buildVars.py"]) + env.Depends(addon, [translatedManifest, moFile]) + +pythonFiles = expandGlobs(buildVars.pythonSources) +for file in pythonFiles: + env.Depends(addon, file) + +# Convert markdown files to html +# We need at least doc in English and should enable the Help button for the add-on in Add-ons Manager +createAddonHelp("addon") +for mdFile in env.Glob(os.path.join('addon', 'doc', '*', '*.md')): + htmlFile = env.markdown(mdFile) + env.Depends(htmlFile, mdFile) + env.Depends(addon, htmlFile) + +# Pot target +i18nFiles = expandGlobs(buildVars.i18nSources) +gettextvars = { + 'gettext_package_bugs_address': 'nvda-translations@groups.io', + 'gettext_package_name': buildVars.addon_info['addon_name'], + 'gettext_package_version': buildVars.addon_info['addon_version'] +} + +pot = env.gettextPotFile("${addon_name}.pot", i18nFiles, **gettextvars) +env.Alias('pot', pot) +env.Depends(pot, i18nFiles) +mergePot = env.gettextMergePotFile("${addon_name}-merge.pot", i18nFiles, **gettextvars) +env.Alias('mergePot', mergePot) +env.Depends(mergePot, i18nFiles) + +# Generate Manifest path +manifest = env.NVDAManifest(os.path.join("addon", "manifest.ini"), os.path.join("manifest.ini.tpl")) +# Ensure manifest is rebuilt if buildVars is updated. +env.Depends(manifest, "buildVars.py") + +env.Depends(addon, manifest) +env.Default(addon) +env.Clean(addon, ['.sconsign.dblite', 'addon/doc/' + buildVars.baseLanguage + '/']) diff --git a/addon/doc/da/readme.md b/addon/doc/da/readme.md index c6cc68c4..8417527a 100644 --- a/addon/doc/da/readme.md +++ b/addon/doc/da/readme.md @@ -56,30 +56,79 @@ forfatterens hensigt, og disse er ikke helt afklarede endnu. ## MathCAT Update Log -### Version 0.2 -* Masser af fejlrettelser -* Forbedringer af tale -* En præferenceindstilling til at kontrollere varigheden af pause (fungerer - med ændringer af relativ talehastighed for matematik) -* Støtte til at genkende keminotation og tale den korrekt -* Oversættelser til indonesisk og vietnamesisk +### Version 0.5.0 +* Added German LaTeX braille code. Unlike other braille codes, this + generates ASCII chars and uses the current braille output table to + translate the characters to braille. +* Added (expermental) ASCIIMath braille code. Like the LaTeX braille code, + this generates ASCII chars and uses the current braille output table to + translate the characters to braille. +* Added "CopyAs" preference that supports copying as MathML, LaTeX, or + ASCIIMath using cntl+C when focused on MathML (as before). The currently + focused node is copied. Note: this is only listed in the prefs.yaml file + and is not exposed (yet) in the MathCAT Preferences dialog. + +### Version 0.4.2 +* Fixed language switching when voice changes and MathCAT language is "Auto" +* Added more checks for $Impairments to improve reading when it is not set + for those who are blind +* Nemeth: fix for "~" when it isn't part of an mrow +* UEB: character additions, "~" spacing fix if prefix, xor fix, +* MathML cleanup for accented vowels (mainly for Vietnamese) +* Major rewrite of preference reading/updating code with big speedup -- + added `CheckRuleFiles` pref to control which files are checked for updates +* Added two new interface calls -- enables setting the navigaton location + from the braille cursor (not part of MathCAT addon yet) + +### Version 0.3.11 +* Upgraded to python 3.11 and verified working with NVDA 2024.1 +* Fix bugs in Vietnamese braille and also in Speech, mostly for chemistry. +* Fix broken braille when braille code and dependent language don't match + (specifically Vietnam braille and Vietnamese speech) +* Fix whitespace bug in HTML inside of tokens +* Improve roman numeral detection + +### Version 0.3.9 +* Added Traditional Chinese translation (thanks to Hon-Jang Yang) +* Fixed bug with navigating into the base of a scripted expression that has + parenthesis +* Significantly changed the way whitespace is handled. This mainly affects + braille output (spaces and "omission" detection). +* Improved recognition of chemistry +* UEB braille fixes that came up from adding chemistry examples +* UEB fixes for adding auxillary parenthesis in some cases +### Version 0.3.8 +Braille: -### Version 0.2.5 -* Flere forbedringer kemi -* Rettelser til Nemeth: +* Dialog has been internationalized for several languages (many thanks to + the translators!) +* Initial implementation of CMU -- the braille code used in Spanish and + Portuguese speaking countries +* Fix some UEB bugs and added some characters for UEB +* Significant improvements to Vietnamese braille - * Added "omission" rules - * Added some rules for English Language Indicators - * Added more cases where the Mulitpurpose indicator is needed - * Fixes related to Nemeth and punctuation +Other fixes: +* Change relative rate dialog slider to have a maximum value of 100% (now + only allows setting slower rates). Also, added step sizes so it is easier + to raise/lower the rate significantly. +* Fix eSpeak bug that sometimes cut off speech when the relative rate was + changed +* Improvements to Vietnamese speech +* Fixed bug with OneCore voices saying "a" +* Fixed some navigation bugs when `AutoZoomOut` is False (not the default) +* Fix updating around language changes and some other dialog changes so they + take effect immediately upon clicking "Apply" or "OK". +* Added an "Use Voice's Language" option so that out of the box, MathCAT + will speak in the right language (if there is a translation) +* Several improvements for cleaning up poor MathML code ### Version 0.3.3 Denne udgivelse indeholder en række fejlrettelser. De vigtigste nye funktioner og fejlrettelser er: -* Tilføjet spansk oversættelse (tak til Noelia Ruiz og María Allo Roldán) +* Added Spanish Translation (thanks to Noelia Ruiz and María Allo Roldán) * Ændret navigation, så den begynder at zoome i ét niveau * Tilføjet cntrl+alt+pil som en måde at navigere i tabelstrukturer på. Disse taster burde være mere mindeværdige, fordi de bruges til tabelnavigation i @@ -97,25 +146,22 @@ brailleoutput. Dette er stadig et igangværende arbejde og er for buggy til at blive brugt andet end til test. Jeg forventer, at den næste MathCAT-udgivelse vil indeholde en pålidelig implementering. -### Version 0.3.8 -Braille: - -* Dialog has been internationalized (many thanks to the translators!) -* Initial implementation of CMU -- braille code used in Spain and several - Portuguese speaking countries -* Significant improvements to Vietnamese braille -* Change relative rate dialog slider to have a maximum value of 100% (now - only allows setting slower rates). Also, added step sizes so it is easier - to raise/lower the rate significantly. -* Fix some UEB bugs and added some characters for UEB +### Version 0.2.5 +* Flere forbedringer kemi +* Rettelser til Nemeth: -Other fixes: + * Added "omission" rules + * Added some rules for English Language Indicators + * Added more cases where the Mulitpurpose indicator is needed + * Fixes related to Nemeth and punctuation -* Improvements to Vietnamese speech -* Fixed bug with OneCore voices saying "a" -* Fixed some navigation bugs when `AutoZoomOut` is False (not the default) -* Fix updating around language changes and some other dialog changes so they - take effect immediately upon clicking "Apply" or "OK". +### Version 0.2 +* Masser af fejlrettelser +* Forbedringer af tale +* En præferenceindstilling til at kontrollere varigheden af pause (fungerer + med ændringer af relativ talehastighed for matematik) +* Støtte til at genkende keminotation og tale den korrekt +* Oversættelser til indonesisk og vietnamesisk [[!tag dev stable]] diff --git a/addon/doc/de/readme.md b/addon/doc/de/readme.md index c68477a3..1c95edbd 100644 --- a/addon/doc/de/readme.md +++ b/addon/doc/de/readme.md @@ -5,13 +5,13 @@ Versionen) * [Stabile Version herunterladen][1] -MathCAT wurde entwickelt, um MathPlayer zu ersetzen, da MathPlayer nicht -mehr unterstützt wird. MathCAT generiert Sprache und Blindenschrift aus -MathML. Die von MathCAT erzeugte Sprache für Mathematik wird mit Prosodie -verbessert, damit sie natürlicher klingt. Die Sprache kann in drei Modi mit -denselben Befehlen wie MathPlayer bedient werden. Zusätzlich wird der -Navigationsbereich auf einer Braillezeile angezeigt. Sowohl Nemeth- als auch -UEB-Technik werden unterstützt. +MathCAT wurde entwickelt, um MathPlayer zu ersetzen, da der MathPlayer nicht +mehr unterstützt wird. MathCAT generiert Ausgaben über die Sprachausgabe und +in Braille aus MathML. Die von MathCAT erzeugte Sprache für Mathematik wird +durch Prosodie verbessert, so dass sie natürlicher klingt. Die Sprache kann +in drei Modi mit denselben Befehlen wie MathPlayer navigiert werden. Darüber +hinaus wird der Navigationsknoten auf einer Braillezeile angezeigt. Sowohl +Nemeth- als auch UEB-Technik werden unterstützt. MathCAT verfügt über eine Reihe von Konfigurationsoptionen, die die Sprachausgabe, Navigation und die Braille-Ausgabe steuern. Viele dieser @@ -36,19 +36,20 @@ Wer sollte MathCAT benutzen: (die Nemeth-Schrift von MathPlayer basiert auf der Nemeth-Schrift von liblouis, die eine Reihe schwerwiegender Fehler aufweist, die technisch schwer zu beheben sind). -* Diejenigen, die die technische Braille-Schrift UEB benötigen +* Diejenigen, die technische Braille-Schrift UEB, CMU + (Spanisch/Portugiesisch), deutsches LaTeX, ASCIIMath oder vietnamesische + Braille-Schrift benötigen * Diejenigen, die die neueste Technologie ausprobieren wollen und bereit sind, durch die Meldung von Fehlern zu helfen * Diejenigen, die eine Eloquence-Stimme benutzen Wer sollte MathCAT NICHT benutzen: -* Alle, die MathPlayer außer in englischer Sprache verwenden (Übersetzungen - für Indonesisch und Vietnamesisch sind vorhanden; weitere Übersetzungen - werden in Zukunft folgen) -* Alle, die den MathPlayer mit einer Braille-Ausgabe außer Nemeth und UEB - benutzt (kontaktiert mich, wenn ihr mit einer Braille-Übersetzung - aushelfen wollt) +* Alle, die den MathPlayer mit einer Sprache verwendet, die noch nicht von + MathCAT unterstützt wird (Übersetzungen gibt es für Chinesisch + (traditionell), Spanisch, Indonesisch und Vietnamesisch; Übersetzungen + werden in Zukunft folgen) und wer mit den unterstützten Sprachen (noch) + nicht vertraut ist. * Alle, die den Access8Math dem MathPlayer vorzieht (wegen der Sprache oder anderer Funktionen) @@ -63,31 +64,158 @@ geklärt sind. ## Änderungsprotokoll für MathCAT -### Version 0.2 -* Viele Fehlerkorrekturen -* Verbesserungen für die Sprachausgabe -* Eine Einstellung zur Steuerung der Dauer von Pausen (funktioniert mit - Änderungen der relativen Sprechgeschwindigkeit für Mathematik) -* Unterstützung bei der Erkennung der chemischen Notation und deren - korrekten Aussprache -* Übersetzungen ins Indonesische und Vietnamesische +### Version 0.6.3 +* Alle Sprach- und Braille-Regeldateien werden in ein Verzeichnis gepackt + und bei Bedarf entpackt. -### Version 0.2.5 -* Weitere Verbesserungen in Chemie -* Korrekturen für Nemeth: + * Dies spart derzeit ~5 MB, wenn Rules.zip entpackt wird, und wird noch + mehr sparen, wenn mehr Sprachen und Braille-Codes hinzugefügt werden. + * Dies ist eine Vorbereitung für die Integration von MathCAT in NVDA 2024.3 + +* Neue Einstellung `DecimalSeparator` hinzugefügt. + + * Der Standardwert ist `Auto`, andere Werte sind ".", "," und "Custom". Die + ersten drei Werte legen `DecimalSeparators` und `BlockSeparators` fest. + * Der Wert von `Auto` setzt diese Einstellungen auf der Grundlage des + Wertes der Voreinstellung "Sprache". Für einige Sprachen, wie + z.B. Spanisch, wird `,` in einigen Ländern und `.` in anderen + verwendet. In diesem Fall ist es am besten, die Sprache so einzustellen, + dass sie auch den Ländercode enthält (z. B. `es-es` oder `es-mx`), um + sicherzustellen, dass der richtige Wert verwendet wird. + +* Schwedisch wurde zu den unterstützten Sprachen hinzugefügt. +* Es wurden weitere Unicode-Zeichen hinzugefügt, um sowohl alle + Unicode-Zeichen, die als "Sm" gekennzeichnet sind, als auch diejenigen mit + einer mathematischen Klasse (mit Ausnahme der Klassen Alphabetic und + Glyph) in den Unicode-Standard aufzunehmen. +* Nachdem ich die Funktionsweise der Voreinstellungen in einer früheren + Version geändert hatte, hatte ich vergessen, `MathRate` und `PauseFactor` + in Zahlen und nicht in Strings zu ändern. +* Fehler in den Braille-Regeln behoben (verpasste Änderung von früher), wo + ein drittes Argument hätte angegeben werden müssen, um zu sagen, dass in + den _Braille_ `definitions.yaml`-Dateien und nicht in den Sprachdateien + nachgeschaut werden soll, wenn der Wert einer Definition gesucht wird. +* Die Verwendung der Datei `definitions.yaml` wurde bereinigt. +* Einige Fehler in der MathML-Bereinigung für "," Dezimaltrennzeichen wurden + behoben. +* Ich habe einen Fehler in der Braille-Hervorhebung gefunden, wenn nichts + hervorgehoben ist (vielleicht passiert das nie, weshalb ich es in der + Praxis nicht gesehen habe?) +* Der "Beschreibungs"-Modus wurde so korrigiert, dass er funktioniert - er + ist immer noch sehr minimal und wahrscheinlich noch nicht nützlich +* Minimale unterstützte Version neu festgelegt + +### Version 0.5.6 +* Ein Dialogfeld für Kopieren als... zum MathCAT-Dialog hinzugefügt (im + Bereich "Navigation"). +* Ein Fehler wurde behoben, bei dem die Sprache beim Wechsel des Sprachstils + auf Englisch zurückgesetzt wurde. +* Fehler bei der Navigation und in Braille behoben +* Einige Probleme mit den ASCIIMath-Abständen wurden behoben. +* Verbesserte Erkennung von chemischen Formeln +* MathCAT wurde auf die neue BANA-Nemeth-Chemie-Spezifikation aktualisiert + (immer noch nur einzeilig und Änderungen der Schriftart/Schriftart für + Sonderfälle werden nicht berücksichtigt) +* Behebung eines Absturzes, wenn Nicht-ASCII-Ziffern (z. B. fette Ziffern) + in Zahlen verwendet werden +* Keine kursiven Indikatoren in Braille-Codes verwenden, wenn die + mathematischen alphanumerischen kursiven Zeichen verwendet werden. +* Einige andere kleinere Fehlerbehebungen, die nicht von Benutzern gemeldet + wurden + +### Version 0.5.0 +* Deutscher LaTeX-Braille-Code hinzugefügt. Im Gegensatz zu anderen + Braille-Codes erzeugt dieser ASCII-Zeichen und verwendet die aktuelle + Braille-Ausgabetabelle, um die Zeichen in Braille zu übersetzen. +* Ein (experimenteller) ASCIIMath-Braille-Code wurde hinzugefügt. Wie der + LaTeX-Braille-Code erzeugt dieser ASCII-Zeichen und verwendet die aktuelle + Braille-Ausgabetabelle, um die Zeichen in Braille zu übersetzen. +* Neue Einstellung "Kopieren als", die das Kopieren als MathML, LaTeX oder + ASCIIMath mit cntl+C unterstützt, wenn der Fokus auf MathML liegt (wie + zuvor). Der aktuell fokussierte Knoten wird kopiert. Hinweis: Diese + Einstellung wird nur in der prefs.yaml-Datei aufgeführt und ist (noch) + nicht im Dialogfeld für die MathCAT-Einstellungen sichtbar. + +### Version 0.4.2 +* Sprachumschaltung behoben, wenn die Stimme wechselt und die + MathCAT-Sprache auf "Auto" eingestellt ist +* Weitere Überprüfungen für $Impairments hinzugefügt, um das Lesen zu + verbessern, wenn es für Blinde nicht gesetzt ist +* Nemeth: Korrektur für "~", wenn es nicht Teil eines Mrows ist +* UEB: Zeichen hinzugefügt, "~"-Abstand korrigiert, wenn Präfix, xor + korrigiert, +* MathML-Bereinigung für akzentuierte Vokale (hauptsächlich für + Vietnamesisch) +* Umfassende Überarbeitung des Codes zum Lesen und Aktualisieren von + Präferenzen mit großer Geschwindigkeitssteigerung -- Hinzufügen der + Präferenz `CheckRuleFiles`, um zu kontrollieren, welche Dateien auf + Aktualisierungen geprüft werden +* Zwei neue Interface-Aufrufe hinzugefügt -- ermöglicht das Setzen der + Navigationsposition des Braille-Cursors (noch nicht Teil von MathCAT) + +### Version 0.3.11 +* Aktualisiert auf Python 3.11 und Überprüfung, ob es mit NVDA 2024.1 + kompatibel ist +* Behebung von Fehlern in Braille in Vietnamesisch und auch in der + Sprachausgabe, vor allem für Chemie. +* Fehlerhafte Braille-Ausgaben korrigiert, wenn Braille-Code und abhängige + Sprache nicht übereinstimmen (insbesondere vietnamesische Braille-Schrift + und vietnamesische Sprache) +* Whitespace-Fehler in HTML innerhalb von Token behoben +* Verbesserung der Erkennung römischer Ziffern + + +### Version 0.3.9 +* Übersetzung für traditionelles Chinesisch hinzugefügt (Dank an Hon-Jang + Yang) +* Fehler beim Navigieren in die Basis eines geskripteten Ausdrucks mit + Klammern behoben +* Die Art und Weise, wie Leerzeichen behandelt werden, wurde erheblich + geändert. Dies betrifft vor allem die Braille-Ausgabe (Leerzeichen und + Erkennung von "Auslassungen"). +* Verbesserte Erkennung von chemischen Formeln +* UEB-Braille-Korrekturen, die sich aus dem Hinzufügen von Chemie-Beispielen + ergeben haben +* UEB-Korrekturen für das Hinzufügen von Hilfsklammern in einigen Fällen - * Added "omission" rules - * Added some rules for English Language Indicators - * Added more cases where the Mulitpurpose indicator is needed - * Fixes related to Nemeth and punctuation +### Version 0.3.8 + +Braille: + +* Das Dialogfeld wurde für mehrere Sprachen internationalisiert (vielen Dank + an die Übersetzer!) +* Erstmalige Einführung von CMU - dem Braille-Code, der in spanisch- und + portugiesischsprachigen Ländern verwendet wird +* Behebung einiger UEB-Fehler und Hinzufügen einiger UEB-Zeichen +* Signifikante Verbesserungen in Braille für Vietnamesisch + +Weitere Korrekturen: + +* Der Schieberegler des Dialogs "Relative Rate" hat nun einen Maximalwert + von 100 % (jetzt können nur noch langsamere Raten eingestellt + werden). Außerdem wurden Schrittgrößen hinzugefügt, damit es einfacher + ist, die Rate deutlich zu erhöhen/verringern. +* Fehler mit der eSpeak behoben, der manchmal die Sprache abschnitt, wenn + die relative Rate geändert wurde +* Verbesserungen der vietnamesischen Sprache +* Fehler bei den OneCore-Stimmen behoben, die "a" sagen +* Einige Navigationsfehler behoben, wenn "AutoZoomOut" auf "False" gesetzt + ist (nicht der Standard) +* Die Aktualisierung von Änderungen bei den Sprachen und einigen anderen + Änderungen bei Dialogfeldern wurde korrigiert, so dass sie sofort wirksam + werden, wenn Sie auf "Übernehmen" oder "OK" klicken. +* Die Option "Sprache der Stimme verwenden" wurde hinzugefügt, so dass + MathCAT von Anfang an in der richtigen Sprache spricht (wenn es eine + Übersetzung gibt) +* Mehrere Verbesserungen zur Bereinigung von schlechtem MathML-Code ### Version 0.3.3 Diese Version enthält eine Reihe von Fehlerkorrekturen. Die wichtigsten neuen Funktionen und Fehlerkorrekturen sind: -* Spanisch Übersetzung hinzugefügt (Dank an Noelia Ruiz und María Allo +* Spanische Übersetzung hinzugefügt (Dank an Noelia Ruiz und María Allo Roldán) * Die Navigation wurde so geändert, dass sie um eine Ebene vergrößert startet. @@ -108,25 +236,24 @@ als Braille-Ausgabe zu erhalten. Diese ist noch in Arbeit und fehleranfällig, um außer zum Testen verwendet zu werden. Die nächste MathCAT-Version wird eine zuverlässige Implementierung enthalten. -### Version 0.3.8 -Braille: +### Version 0.2.5 +* Weitere Verbesserungen in Chemie +* Korrekturen für Nemeth: + + * Regeln für "Auslassungen" hinzugefügt + * Einige Regeln für englische Sprachindikatoren hinzugefügt + * Weitere Fälle, in denen der Mehrzweck-Indikator benötigt wird, wurden + hinzugefügt + * Korrekturen im Zusammenhang mit Nemeth und Zeichensetzung -* Dialog has been internationalized (many thanks to the translators!) -* Initial implementation of CMU -- braille code used in Spain and several - Portuguese speaking countries -* Significant improvements to Vietnamese braille -* Change relative rate dialog slider to have a maximum value of 100% (now - only allows setting slower rates). Also, added step sizes so it is easier - to raise/lower the rate significantly. -* Fix some UEB bugs and added some characters for UEB - -Other fixes: - -* Improvements to Vietnamese speech -* Fixed bug with OneCore voices saying "a" -* Fixed some navigation bugs when `AutoZoomOut` is False (not the default) -* Fix updating around language changes and some other dialog changes so they - take effect immediately upon clicking "Apply" or "OK". +### Version 0.2 +* Viele Fehlerkorrekturen +* Verbesserungen für die Sprachausgabe +* Eine Einstellung zur Steuerung der Dauer von Pausen (funktioniert mit + Änderungen der relativen Sprechgeschwindigkeit für Mathematik) +* Unterstützung bei der Erkennung der chemischen Notation und deren + korrekten Aussprache +* Übersetzungen ins Indonesische und Vietnamesische [[!tag dev stable]] diff --git a/addon/doc/es/readme.md b/addon/doc/es/readme.md index 068bc03f..0fff3723 100644 --- a/addon/doc/es/readme.md +++ b/addon/doc/es/readme.md @@ -32,7 +32,8 @@ Quién debería usar MathCat: * Quien necesite Braille Nemeth de alta calidad (el Nemeth de MathPlayer se basa en la generación Nemeth de Liblouis, que tiene una importante cantidad de fallos técnicamente complicados de corregir). -* Quien necesite braille técnico UEB +* Aquellos que necesiten braille técnico UEB, CMU (español/portugués), LaTeX + alemán, ASCIIMath, o braille vietnamita * Quien quiera probar nuevas tecnologías y esté dispuesto a ayudar informando de fallos * Quien use Eloquence como voz @@ -40,10 +41,9 @@ Quién debería usar MathCat: Quién no debería usar MathCat: * Cualquiera que use Math Player en idiomas distintos al inglés (existen - traducciones a indonesio, vietnamita y español; las traducciones se irán - haciendo en el futuro). -* Cualquiera que use MathPlayer con una salida braille sin Nemeth o UEB - (contacta conmigo si quieres ayudar con una traducción braille) + traducciones a chino (tradicional), indonesio, vietnamita y español; las + traducciones se irán haciendo en el futuro) y no se sienten cómodos con la + voz en uno de los idiomas soportados. * Cualquiera que prefiera Access8Math a MathPlayer (por la voz u otras funciones) @@ -58,24 +58,145 @@ establecido. ## Registro de actualización de MathCat -### Versión 0.2 -* Muchos fallos corregidos -* Mejoras en la voz -* Una opción en las preferencias para controlar la duración de las pausas - (funciona con los cambios relativos de velocidad de la voz en matemáticas) -* Soporte para reconocer notación química y verbalizarla adecuadamente -* Traducciones al indonesio y al vietnamita +### Versión 0.6.3 + +* Todos los archivos de idioma y reglas braille se comprimen por carpeta y + se descomprimen a petición. + + * Se ahorran unos 5 mb cuando rules.zip está descomprimido, y ahorrará aún + más según se añadan más idiomas y códigos braille. + * Esto se hace como preparación para incluir MathCat en NVDA 2024.3 + +* Se añade una nueva preferencia de "separador decimal". + + * El valor por defecto es `Automático`, siendo otros valores ".", ",", y + "personalizado". Los primeros tres valores establecen `DecimalSeparators` + y `BlockSeparators`. + * `Automático` establece estas preferencias en función de la preferencia + `language`. En algunos idiomas, como el español, se usa ',' en algunos + países y '.' en otros. En este caso, es mejor configurar el idioma para + que incluya también el código del país (por ejemplo, `es-es` or `es-mx`) + para garantizar que se usa el valor correcto. + +* Se añade el sueco a los idiomas soportados. +* Se añaden más caracteres Unicode para incluir todos los caracteres Unicode + marcados como "Sm" y aquellos con una mathclass (excepto las clases + Alphabetic y Glyph) en el estándar Unicode. +* Tras cambiar el funcionamiento de las preferencias en una versión + anterior, olvidé cambiar `MathRate` y `PauseFactor` para que sean números + en vez de cadenas. +* Corregido un fallo en las reglas braille (un cambio anterior que se + perdió) por el que se debería haber dado un tercer argumento para decir + que se mire en los archivos `definitions.yaml` _Braille_ y no en los de + voz al buscar el valor de una definición. +* Se limpia el uso de `definitions.yaml`. +* Se corrigen algunos fallos en la limpieza de MathML para los separadores + decimales ",". +* Se ha encontrado un fallo en el resaltado braille cuando no hay nada + resaltado (tal vez nunca se dio, ¿por eso no lo vi en la práctica?) +* Corregido el modo "describir" para que funcione. Todavía está al mínimo y + probablemente aún no es útil +* Se corrige la versión mínima soportada + +### Versión 0.5.6 +* Se ha añadido Copiar como... al diálogo de MathCat (en el panel + "Navegación") +* Se corrige un fallo por el que el idioma volvía a ser inglés al cambiar + los estilos de habla. +* Se corrige un fallo con la navegación y el braille +* Se corrigen algunos problemas de espaciado de Asciimath. +* Mejorado el reconocimiento de química +* Se actualiza MathCat a la nueva especificación química BANA Nemeth + (todavía sólo una sola línea y no se manejan los casos especiales de + cambio de estilo/fuente) +* Se corrige un error fatal cuando se usan dígitos no ASCII en números (por + ejemplo, dígitos en negrita) +* No se usan indicadores de cursiva en los códigos braille cuando se usan + los caracteres de cursiva alfanuméricos de matemáticas +* Algunas pequeñas correcciones de las que no informaron los usuarios + +### Versión 0.5.0 +* Se añade código braille LaTeX en alemán. Al contrario que otros códigos + braille, este genera caracteres ASCII y usa la tabla de salida braille + actual para transcribirlos. +* Se añade código braille ASCIIMath (experimental). Al igual que el código + braille LaTeX, genera caracteres ASCII y usa la tabla braille de salida + actual para transcribirlos. +* Se añade la preferencia "CopyAs", que soporta copiar como MathML, LaTeX o + ASCIIMath usando control+c cuando el foco está sobre contenido MathML + (como antes). El nodo que tiene el foco se copia. Nota: esto sólo se lista + en el archivo prefs.yaml y no se expone en el diálogo de preferencias de + MathCat (todavía). + +### Versión 0.4.2 +* Se corrige el cambio de idioma cuando los cambios de voz y el idioma de + MathCat se configuran en "Automático" +* Se añaden más comprobaciones de impedimentos para mejorar la lectura + cuando no está configurada para personas ciegas +* Nemeth: corrección de "~" cuando no es parte de un mrow +* UEB: se añaden caracteres, corrección de espaciado en "~", prefijo if, + prefijo xor, +* Limpieza de MathML en vocales acentuadas (principalmente en vietnamita) +* Reescritura importante del código que lee y actualiza las preferencias con + mucha más celeridad. Se añade la preferencia `CheckRuleFiles` para + controlar para qué archivos se buscan actualizaciones +* Se añaden dos nuevas llamadas de interfaz que permiten configurar la + posición de navegación a partir del cursor braille (todavía no forman + parte del complemento MathCat) + +### Versión 0.3.11 +* Se actualiza a Python 3.11 y se comprueba el funcionamiento en NVDA 2024.1 +* Se corrigen fallos en el braille y la voz vietnamitas, en su mayoría de + química. +* Se corrige un fallo en braille cuando el código braille y el idioma + dependiente no coinciden (concretamente, braille vietnamita y voz + vietnamita) +* Se corrige un fallo con los espacios en blanco en HTML dentro de los + tokens +* Se mejora la detección de números romanos + + +### Versión 0.3.9 +* Se añade traducción al chino tradicional (gracias a Hon-Jang Yang) +* Se corrige un fallo con la navegación en la base de una expresión escrita + que tiene paréntesis +* Ha cambiado significativamente el manejo de los espacios en + blanco. Principalmente afecta a la salida braille (espacios y detección de + "omisión"). +* Mejorado el reconocimiento de química +* Correcciones en el braille UEB que aparecieron al añadir ejemplos de + química +* Correcciones en UEB al añadir paréntesis auxiliares en algunos casos -### Versión 0.2.5 -* Más mejoras en química -* Correcciones en Nemeth: +### Versión 0.3.8 - * Añadidas reglas de "omisión" - * Añadidas algunas reglas para los indicadores en inglés - * Añadidos más casos donde es necesario el indicador Mulitpurpose - * Correcciones relacionadas con Nemeth y la puntuación +Braille: +* Se ha internacionalizado el diálogo en varios idiomas (¡Muchas gracias a + los traductores!) +* Implementación inicial de CMU, código braille usado en los países de habla + hispana y portuguesa +* Se corrigen algunos fallos de UEB y se añaden algunos caracteres para UEB +* Mejoras importantes al braille vietnamita + +Otras correcciones: + +* Se cambia el deslizador del diálogo de velocidad relativa para que tenga + un valor máximo del 100% (ahora sólo permite configurar velocidades + menores). También se añaden pasos de tamaño para que sea más fácil + aumentar o disminuir la velocidad significativamente. +* Se corrige un problema con Espeak que hacía que la voz se entrecortara al + cambiar su velocidad relativa +* Mejoras en la voz vietnamita +* Corregido un fallo con las voces OneCore, que decían "a" +* Se corrigen algunos fallos de navegación cuando `AutoZoomOut` es falso (no + el que hay por defecto) +* Se corrigen algunos cambios en los diálogos y en el idioma para que tengan + efecto inmediato cuando se pulse "Aplicar" o "Aceptar". +* Se añade una opción "Usar idioma de la voz" para que MathCat hable en el + idioma correcto desde el principio (si hay traducción) +* Varias mejoras al limpiar código MathML pobre ### Versión 0.3.3 Esta versión contiene bastantes correcciones de fallos. Las principales @@ -102,27 +223,22 @@ salida braille. Este trabajo todavía está en progreso y tiene demasiados fallos como para usarlo para otra cosa que no sean pruebas. Espero que la próxima versión de MathCat contenga una implementación fiable. -### Versión 0.3.8 -Braille: - -* Se ha internacionalizado el diálogo (¡Muchas gracias a los traductores!) -* Implementación inicial de CMU, código braille usado en España y varios - países de habla portuguesa -* Mejoras importantes al braille vietnamita -* Se cambia el deslizador del diálogo de velocidad relativa para que tenga - un valor máximo del 100% (ahora sólo permite configurar velocidades - menores). También se añaden pasos de tamaño para que sea más fácil - aumentar o disminuir la velocidad significativamente. -* Se corrigen algunos fallos de UEB y se añaden algunos caracteres para UEB +### Versión 0.2.5 +* Más mejoras en química +* Correcciones en Nemeth: -Otras correcciones: + * Añadidas reglas de "omisión" + * Añadidas algunas reglas para los indicadores en inglés + * Añadidos más casos donde es necesario el indicador Multipropósito + * Correcciones relacionadas con Nemeth y la puntuación -* Mejoras en la voz vietnamita -* Corregido un fallo con las voces OneCore, que decían "a" -* Se corrigen algunos fallos de navegación cuando `AutoZoomOut` es falso (no - el que hay por defecto) -* Se corrigen algunos cambios en los diálogos y en el idioma para que tengan - efecto inmediato cuando se pulse "Aplicar" o "Aceptar". +### Versión 0.2 +* Muchos fallos corregidos +* Mejoras en la voz +* Una opción en las preferencias para controlar la duración de las pausas + (funciona con los cambios relativos de velocidad de la voz en matemáticas) +* Soporte para reconocer notación química y verbalizarla adecuadamente +* Traducciones al indonesio y al vietnamita [[!tag dev stable]] diff --git a/addon/doc/fi/readme.md b/addon/doc/fi/readme.md index d85939ac..6fdebfc5 100644 --- a/addon/doc/fi/readme.md +++ b/addon/doc/fi/readme.md @@ -4,12 +4,12 @@ * Yhteensopivuus: NVDA 2018.1 tai uudempi (ei testattu aiemmissa versioissa) * Lataa [vakaa versio][1] -MathCAT on suunniteltu korvaamaan lopulta MathPlayerin, koska sitä ei enää -tueta. MathCAT luo MathML:stä puhetta ja pistekirjoitusta. MathCATin -tuottamaa matemaattista puhetta on paranneltu prosodialla, jotta se -kuulostaa luonnollisemmalta. Puhetta voidaan navigoida kolmessa eri tilassa -samoilla komennoilla kuin MathPlayerissa. Navigointisolmu näkyy lisäksi -pistenäytöllä. Sekä Némethiä että UEB:tä tuetaan. +MathCAT is designed to eventually replace MathPlayer because MathPlayer is +no longer supported. MathCAT generates speech and braille from MathML. The +speech for math produced by MathCAT is enhanced with prosody so that it +sounds more natural. The speech can be navigated in three modes using the +same commands as MathPlayer. In addition, the navigation node is indicated +on a braille display. Both Nemeth and UEB technical are supported. MathCATissa on useita asetusvaihtoehtoja, jotka ohjaavat puhetta, navigointia ja pistekirjoitusta. Monia näistä voidaan määrittää MathCATin @@ -30,19 +30,18 @@ Kenen tulisi käyttää MathCATia: * Käyttäjien, jotka tarvitsevat korkealaatuista Nemeth-pistekirjoitusta (MathPlayerin Nemeth perustuu liblouisin Nemeth-sukupolveen, jossa on useita merkittäviä bugeja, joita on teknisesti vaikea korjata). -* Käyttäjien, jotka tarvitsevat teknistä UEB-pistekirjoitusta. +* Those who need UEB technical braille, CMU (Spanish/Portuguese), German + LaTeX, ASCIIMath, or Vietnamese braille * Käyttäjien, jotka haluavat kokeilla uusinta teknologiaa ja ovat valmiita - auttamaan ilmoittamalla bugeista. -* Käyttäjien, jotka käyttävät puhesyntetisaattorina Eloquencea. + auttamaan ilmoittamalla bugeista +* Käyttäjien, jotka käyttävät puhesyntetisaattorina Eloquencea Kenen EI tulisi käyttää MathCATia: -* Kaikkien, jotka käyttävät MathPlayeria muulla kuin englannin kielellä - (indonesian- ja vietnaminkieliset käännökset ovat käytettävissä, muita - käännöksiä on tulossa myöhemmin). -* Kaikkien, jotka käyttävät MathPlayeria muulla kuin - Nemeth/UEB-pistekirjoituksella (ota yhteyttä, jos haluat auttaa - pistekirjoituskäännöksen tekemisessä). +* Anyone who uses MathPlayer with a language that is not yet supported by + MathCAT (translations exist for Chinese (Traditional), Spanish, Indonesian + and Vietnamese; translations will be coming in the future) and are not + comfortable with speech in one of the supported languages. * Kaikkien, jotka pitävät enemmän Access8Mathista kuin MathPlayerista (puheen tai muiden ominaisuuksien vuoksi). @@ -56,25 +55,140 @@ päättelemiseen, eikä niitä ole vielä täysin ratkaistu. ## MathCATin päivitysloki -### Versio 0.2 -* Paljon bugikorjauksia -* Parannuksia puheeseen -* Asetus tauon keston säätämiseen (toimii matematiikkapuheen suhteellisen - nopeuden muutosten kanssa) -* Tuki kemiallisten merkintöjen tunnistamiselle ja niiden asianmukaiselle - puhumiselle -* Käännökset indonesiaksi ja vietnamiksi +### Version 0.6.3 + +* All the language and braille Rule files are zipped up per directory and + unzipped on demand. + + * This currently saves ~5mb when Rules.zip is unzipped, and will save even + more as more languages and braille codes are added. + * This is in preparation for MathCAT being built into NVDA 2024.3 + +* Added new preference `DecimalSeparator`. + + * The default value is `Auto`, with other values being ".", ",", and + "Custom". The first three values set `DecimalSeparators` and + `BlockSeparators`. + * `Auto` sets those preferences based on the value of the `Language` + pref. For some language such as Spanish, `,` is used in some countries + and `.` is used in others. In this case, it is best to set the language + to also include the country code (e.g, `es-es` or `es-mx`) to ensure the + right value is used. + +* Added Swedish to supported languages. +* Added more Unicode chars to include both all Unicode chars marked as "Sm" + and those with a mathclass (except Alphabetic and Glyph classes) in the + Unicode standard. +* After changing how prefs work in a previous version, I forgot to change + `MathRate` and `PauseFactor` to be numbers, not strings. +* Fixed bug in the braille Rules (missed change from earlier) where a third + argument should have been given to say to look in the _Braille_ + `definitions.yaml` files and not the speech ones when looking up the value + of a definition. +* Cleaned up use of `definitions.yaml`. +* Fixed some bugs in the MathML cleanup for "," decimal separators. +* Found a bug in braille highlighting when nothing is highlighted (maybe + never happens which is why I didn't see it in practice?) +* Fixed "Describe" mode so that it works -- it is still very minimal and + probably not useful yet +* Fixed minimum supported version + +### Version 0.5.6 +* Added Copy As... to the MathCAT dialog (in the "Navagation" pane). +* Fixed a bug where the language reverted to English when changing speech + styles. +* Fixed a bug with navigation and braille +* Fixed some Asciimath spacing problems. +* Improved chemistry recognition +* Updated MathCAT to new BANA Nemeth chemistry spec (still only single line + and special case style/font changes not handled) +* Fix a crash when non-ASCII digits (e.g., bold digits) are used in numbers +* Don't use italic indicators in braille codes when the math alphanumeric + italic chars are used +* Some other smaller bug fixes that weren't reported by users + +### Versio 0.5.0 +* Lisätty saksalainen LaTeX-pistekirjoitusmerkistö. Toisin kuin muut + pistekirjoitusmerkistöt, tämä tuottaa ASCII-merkkejä ja käyttää + senhetkistä pistetulostustaulukkoa merkkien kääntämiseen + pistekirjoitukseksi. +* Lisätty kokeellinen ASCIIMath-pistekirjoitusmerkistö. Kuten + LaTeX-merkistö, tämäkin tuottaa ASCII-merkkejä ja käyttää senhetkistä + pistetulostustaulukkoa merkkien kääntämiseen pistekirjoitukseksi. +* Lisätty "CopyAs"-asetus, joka tukee kopiointia MathML-, LaTeX- tai + ASCIIMath-muodossa Ctrl+C-näppäinkomentoa käyttäen kohdistuksen ollessa + MathML:ssä (kuten aiemmin). Aktiivisena oleva solmu kopioidaan. Huom: + Asetus on toistaiseksi muutettavissa vain prefs.yaml-tiedostossa eikä sitä + vielä näytetä MathCATin asetusvalintaikkunassa. + +### Versio 0.4.2 +* Korjattu kielen vaihtaminen kun puheääni vaihtuu ja MathCATin kielenä on + "Automaattinen" +* Lisätty tarkistuksia $Impairments-muuttujalle lukemisen parantamiseksi + silloin, kun se on määritetty muita kuin sokeita varten. +* Nemeth: Korjaus "~":lle, kun se ei ole osa mrow:ta +* UEB: Merkkejä lisätty, "~":n välilyönnin korjaus jos etuliitteenä, + xor-korjaus +* MathML:n siistiminen korostetuille vokaaleille (pääasiassa + vietnamilaisille). +* Suuri osa asetusten luku- ja päivityskoodista kirjoitettu uudestaan, mikä + nopeutti koodia huomattavasti – lisätty ``CheckRuleFiles``-asetus + säätelemään, mitkä tiedostot tarkistetaan päivitysten varalta +* Lisätty kaksi uutta rajapintakutsua: mahdollistaa navigointisijainnin + asettamisen pistekohdistimesta (ei vielä osa MathCAT-lisäosaa) + +### Versio 0.3.11 +* Päivitetty python versioksi 3.11 ja varmistettu toimivuus NVDA 2024.1:n + kanssa +* Korjattu pääasiassa kemiaan liittyviä bugeja vietnaminkielisessä + pistekirjoituksessa ja puheessa. +* Korjattu rikkoutuva pistekirjoitus, kun pistemerkistö ja siihen liittyvä + kieli eivät täsmää (erityisesti Vietnamilainen pistekirjoitus ja puhe) +* Korjattu tyhjän tilan bugi HTML:n sisällä olevissa merkeissä +* Paranneltu roomalaisten numeroiden tunnistusta + + +### Versio 0.3.9 +* Lisätty perinteisen kiinan käännös (kiitos Hon-Jang Yangille) +* Korjattu bugi navigoitaessa skriptatun lausekkeen sulkeita sisältävään + perusosaan +* Muutettu merkittävästi tapaa, jolla tyhjätilaa käsitellään. Tämä vaikuttaa + pääasiassa pistekirjoitustulosteeseen (välilyönnit ja "jättämisen" + havaitseminen). +* Kemiallisten merkintöjen tunnistusta paranneltu +* Tehty UEB-pistekirjoituksen korjauksia, joiden tarve ilmeni kemiallisten + kaavojen esimerkkejä lisättäessä +* Korjauksia UEB:lle apusulkeita lisättäessä joissakin tapauksissa -### Versio 0.2.5 -* Lisää parannuksia kemiallisiin merkintöihin -* Korjauksia Nemeth-merkintöihin: +### Versio 0.3.8 + +Pistekirjoitus: + +* Valintaikkuna on käännetty useille kielille (kiitokset kääntäjille!) +* Alustava toteutus Espanjassa ja useissa portugalinkielisissä maissa + käytettävälle CMU-pistekirjoitusmerkistölle +* Korjattu UEB:n bugeja ja lisätty merkkejä +* Merkittäviä parannuksia vietnamilaiseen pistekirjoitukseen - * Lisätty "jättösääntöjä" - * Lisätty sääntöjä englannin kielen ilmaisimille - * Lisätty enemmän tapauksia, joissa monitoimi-ilmaisinta tarvitaan - * Nemeth-merkintöihin ja välimerkkeihin liittyviä korjauksia +Muita korjauksia: +* Muutettu suhteellisen nopeuden liukusäädintä siten, että sen enimmäisarvo + on 100 % (sallii nyt vain hitaampien nopeuksien asettamisen). Lisätty myös + askelkokoja, jotta nopeuden merkittävä nostaminen/laskeminen olisi + helpompaa. +* Korjattu eSpeakin bugi, joka katkaisi toisinaan puheen, kun suhteellista + nopeutta muutettiin +* Parannuksia vietnaminkieliseen puheeseen +* Korjattu OneCore-äänien bugi, joka sai ne sanomaan "a" +* Korjattu navigoinnin bugeja, joita esiintyi kun automaattinen + loitontaminen ei ole käytössä (ei oletuksena) +* Korjattu kielen vaihtamisen ja muutaman muun valintaikkunan muutosten + päivittämistä siten, että muutokset tulevat voimaan heti Käytä- tai + OK-painiketta painettaessa. +* Lisätty "Käytä äänen kieltä" -asetus, jotta MathCAT puhuu oletusarvoisesti + oikealla kielellä (jos käännös on saatavilla) +* Useita parannuksia huonon MathML-koodin siistimiseksi ### Versio 0.3.3 Tähän versioon on tehty useita bugikorjauksia. Merkittävimpiä uusia @@ -109,28 +223,23 @@ keskeneräinen ja liian virhealtis käytettäväksi muuhun kuin testaamiseen. Seuraavassa MathCATin versiossa pitäisi jo olla luotettava toteutus. -### Versio 0.3.8 -Pistekirjoitus: - -* Valintaikkuna on käännetty useille kielille (kiitokset kääntäjille!) -* Alustava toteutus Espanjassa ja useissa portugalinkielisissä maissa - käytettävälle CMU-pistekirjoitusmerkistölle -* Merkittäviä parannuksia vietnamilaiseen pistekirjoitukseen -* Muutettu suhteellisen nopeuden liukusäädintä siten, että sen enimmäisarvo - on 100 % (sallii nyt vain hitaampien nopeuksien asettamisen). Lisätty myös - askelkokoja, jotta nopeuden merkittävä nostaminen/laskeminen olisi - helpompaa. -* Korjattu UEB:n bugeja ja lisätty merkkejä +### Versio 0.2.5 +* Lisää parannuksia kemiallisiin merkintöihin +* Korjauksia Nemeth-merkintöihin: -Muita korjauksia: + * Added "omission" rules + * Added some rules for English Language Indicators + * Added more cases where the Mulitpurpose indicator is needed + * Fixes related to Nemeth and punctuation -* Parannuksia vietnaminkieliseen puheeseen -* Korjattu OneCore-äänien bugi, joka sai ne sanomaan "a" -* Korjattu navigoinnin bugeja, joita esiintyi kun automaattinen - loitontaminen ei ole käytössä (ei oletuksena) -* Korjattu kielen vaihtamisen ja muutaman muun valintaikkunan muutosten - päivittämistä siten, että muutokset tulevat voimaan heti Käytä- tai - OK-painiketta painettaessa. +### Versio 0.2 +* Paljon bugikorjauksia +* Parannuksia puheeseen +* Asetus tauon keston säätämiseen (toimii matematiikkapuheen suhteellisen + nopeuden muutosten kanssa) +* Tuki kemiallisten merkintöjen tunnistamiselle ja niiden asianmukaiselle + puhumiselle +* Käännökset indonesiaksi ja vietnamiksi [[!tag dev stable]] diff --git a/addon/doc/fr/readme.md b/addon/doc/fr/readme.md index b891cf95..74ae189b 100644 --- a/addon/doc/fr/readme.md +++ b/addon/doc/fr/readme.md @@ -5,13 +5,13 @@ versions antérieures) * Télécharger [version stable][1] -MathCAT est conçu pour éventuellement remplacer MathPlayer, car ce dernier -n'est plus pris en charge. MathCAT génère la parole et le braille à partir -de MathML. La parole produite par MathCAT pour les mathématiques est -améliorée avec intonation afin qu'elle semble plus naturelle. Vous pouvez -naviguer dans la parole de trois façons en utilisant les mêmes commandes que -dans MathPlayer. De plus, le nœud de navigation est indiqué sur l'afficheur -braille. Nemeth et UEB technique sont pris en charge. +MathCAT est conçu pour finalement remplacer MathPlayer, car ce dernier n'est +plus pris en charge. MathCAT génère la parole et le braille à partir de +MathML. La parole produite par MathCAT pour les mathématiques est améliorée +avec intonation afin qu'elle semble plus naturelle. Vous pouvez naviguer +dans la parole de trois façons en utilisant les mêmes commandes que dans +MathPlayer. De plus, le nœud de navigation est indiqué sur l'afficheur +braille. les codes Nemeth et UEB technique sont pris en charge. MathCAT a un certain nombre d'options de configuration qui contrôlent la parole, la navigation et le braille. Beaucoup d'entre eux peuvent être @@ -35,19 +35,19 @@ Qui devrait utiliser MathCAT : * Ceux qui ont besoin de Nemeth Braille de haute qualité (Nemeth de MathPlayer est basé sur la génération Nemeth de liblouis qui a un certain nombre de bogues importants qui sont techniquement difficiles à corriger). -* Ceux qui ont besoin d'un braille technique UEB +* Ceux qui ont besoin du braille technique UEB, de CMU (espagnol/portugais), + du LaTeX allemand, d'ASCIIMath ou du braille vietnamien * Ceux qui veulent essayer les dernières technologies et sont prêts à aider en signalant des bogues * Ceux qui utilisent Eloquence comme voix Qui ne devrait pas utiliser MathCAT : -* Quiconque utilise MathPlayer avec une langue non anglaise (des traductions - existent pour les langues indonésiennes et vietnamiennes ; des traductions - arrivent dans le futur) -* Quiconque utilise MathPlayer avec une sortie en braille sans Nemeth / sans - UEB (contactez-moi si vous souhaitez vous aider avec une traduction en - braille) +* Toute personne utilisant MathPlayer avec une langue pas encore prise en + charge par Mathcat (des traductions existent pour le chinois + (traditionnel), l'espagnol, l'indonésien et le vietnamien; d'autres + traductions doivent arriver dans un futur proche) et qui ne se sent pas à + l'aise avec la parole dans les langues prises en charge. * Quiconque préfère Access8Math à MathPlayer (pour la parole ou d'autres fonctionnalités) @@ -62,25 +62,153 @@ pas encore complètement établi. ## Mise à jour du Journal de MathCAT -### Version 0.2 -* Beaucoup de correctifs de bogues -* Améliorations de la parole -* Un paramètre de préférence pour contrôler la durée de la pause (fonctionne - avec le changement du débit relatif de la parole pour les mathématiques) -* Prise en charge de la reconnaissance de la notation chimique et à la - verbaliser de manière appropriée -* Traductions vers indonésien et vietnamien +### Version 0.6.3 +* Tous les fichiers de règles de langue et de braille sont zippés par + répertoire et décompressés à la demande. -### Version 0.2.5 -* Plus d'améliorations chimique -* Correction pour Nemeth : + * Cela économise actuellement environ 5 Mo lorsque les règles.zip sont + décompressées et économisera encore plus lorsque plus de langues et de + codes braille seront ajoutés. + * Ceci est en préparation pour l'intégration de Mathcat dans NVDA 2024.3 + +* Ajout d'une nouvelle préférence `Decimalseparator`. + + * La valeur par défaut est `Auto`, les autres valeurs étant ".", "," et + "Custom". Les trois premières valeurs définissent `Decimalseparators` et + `Blockseparators`. + * `Auto` définit ces préférences en fonction de la valeur de pref + `Language`. Pour certaines langue comme l'espagnol, `,`,est utilisée dans + certains pays et `.` est utilisée dans d'autres. Dans ce cas, il est + préférable de définir la langue pour inclure également le code du pays + (par exemple, `es-es` ou `es-mx`) pour s'assurer que la bonne valeur est + utilisée. + +* Ajout du suédois aux langues prises en charge. +* Ajout de nouveaux caractères Unicode, incluant tous ceux marqués comme + "Sm" ainsi que ceux ayant une classe mathématique (à l'exception des + classes Alphabetic et Glyph) dans le standard Unicode. +* Après avoir changé le fonctionnement des préfes dans une version + précédente, j'ai oublié de modifier `Mathrate` et `Pausefactor` afin + qu'ils soient des nombres, pas des chaînes. +* Correction d'un bug dans les règles braille (modification oubliée + précédemment) où un troisième argument aurait dû être fourni pour indiquer + de rechercher dans les fichiers _Braille_ `definitions.yaml` et non dans + ceux de la parole lors de la recherche de la valeur d'une définition. +* Nettoyage de l'utilisation de `definitions.yaml`. +* Correction de bugs dans le nettoyage du MathML pour les séparateurs + décimaux ",". +* Correction d’un bug dans la mise en évidence braille lorsque rien n’est + mis en évidence (ce cas ne se produit peut-être jamais en pratique, ce qui + expliquerait pourquoi il n’avait pas été détecté). +* Correction du mode "Décrire" pour qu'il fonctionne -- il est encore très + minimaliste et probablement pas encore utile +* Correction de la version minimale prise en charge + +### Version 0.5.6 +* Ajout de Copier en tant que... au dialogue MathCAT (dans le volet + "Navigation"). +* Correction d'un bug où la langue retournait à l'anglais lors de la + modification des styles de parole. +* Correction d'un bug relatif à la navigation et au braille +* Correction de problèmes d'espacement Asciimath. +* Reconnaissance améliorée de la chimie +* Mise à jour de Mathcat pour les nouvelles spécifications de chimie Bana + Nemeth (toujours seulement pour les lignes simples, cas spéciaux de + changement de style ou de police non pris en charge) +* Correction d'un plantage lorsque des chiffres non ASCII (par exemple, des + chiffres en gras) sont utilisés dans les nombres +* Pas d'utilisation d'indicateurs italiques dans les codes braille lorsque + les caractères italiques alphanumériques mathématiques sont utilisés +* Quelques autres corrections de bogues plus petites qui n'ont pas été + rapportées par les utilisateurs + +### Version 0.5.0 +* Ajout du code braille LaTeX allemand. Contrairement à d'autres codes + braille, celui-ci génère des caractères ASCII et utilise la table de + sortie braille actuelle pour traduire les caractères en braille. +* Ajout du code braille ASCIIMath (expérimental). Comme le code braille + LaTeX, celui-ci génère des caractères ASCII et utilise la table de sortie + braille actuelle pour traduire les caractères en braille. +* Ajout de la préférence "CopyAs" qui prend en charge la copie au format + MathML, LaTeX ou ASCIIMath en utilisant contrôle+C lorsque le focus sur du + MathML (comme auparavant). Le nœud ayant le focus est copié. Remarque : + cette option n'est listée que dans le fichier prefs.yaml et n'est pas + (encore) exposé dans la boîte de dialogue Préférences MathCAT. + +### Version 0.4.2 +* Correctif pour le changement de langue lorsque la voix change et que la + langue MathCAT est "Auto" +* Ajout de plus de contrôles de $Impairments afin d'améliorer la lecture + lorsqu'elle n'est pas définie pour les personnes aveugles +* Nemeth : correction de "~" lorsqu'il ne fait pas partie d'un mrow +* UEB : ajouts de caractères, correction de l'espacement de "~" avec un + préfixe, correction de xor, +* Nettoyage MathML pour les voyelles accentuées (principalement pour le + vietnamien) +* Réécriture majeure du code de lecture/mise à jour des préférences avec une + grande accélération -- ajout de la préférence `CheckRuleFiles` pour + contrôler quels fichiers sont vérifiés pour les mises à jour +* Ajout de deux nouveaux appels d'interface -- permet de définir + l'emplacement de navigation à partir du curseur braille (ne fait pas + encore partie de l'extension MathCAT) + +### Version 0.3.11 +* Mise à niveau vers python 3.11 et vérification du fonctionnement avec NVDA + 2024.1 +* Correction de bugs dans le braille vietnamien et également dans la parole, + principalement pour la chimie. +* Correction du dysfonctionnement du braille lorsque le code braille et la + langue dépendante ne correspondent pas (en particulier le braille + vietnamien et la parole vietnamienne) +* Correction d'un bug d'espacement en HTML à l'intérieur des élements +* Amélioration de la détection des chiffres romains - * Règles "omission" ajoutées - * Ajout de quelques règles pour les indicateurs de langue Anglaise - * Ajout de plus de cas où l'indicateur polyvalent est nécessaire - * Correction liée à Nemeth et à la ponctuation +### Version 0.3.9 +* Ajout de la traduction en chinois traditionnel (remerciements à Hon-Jang + Yang) +* Correction d'un bug lors de la navigation dans la base d'une expression + scriptée comportant des parenthèses +* Modification significative de la façon dont les espaces sont gérés. Cela + affecte principalement la sortie braille (détection des espaces et des + "omissions"). +* Reconnaissance améliorée de la chimie +* Corrections du braille UEB résultant de l'ajout d'exemples de chimie +* Corrections UEB pour l'ajout de parenthèses auxiliaires dans certains cas + + +### Version 0.3.8 + +Braille : + +* Les dialogues ont été internationalisé pour plusieurs langues (un grand + merci aux traducteurs !) +* Implémentation initiale du CMU -- le code braille utilisé dans les pays + hispanophones et lusophones +* Correction de quelques bugs de l'UEB et ajout de quelques caractères pour + l'UEB +* Améliorations significatives du braille Vietnamien + +Autres correctifs : + +* Modifiez le curseur de la boîte de dialogue de débit relatif pour avoir + une valeur maximale de 100 % (permet désormais uniquement de définir des + débits plus lents). En outre, des tailles de pas ont été ajoutées pour + qu'il soit plus facile d'augmenter/diminuer le débit de manière + significative. +* Correction d'un bug d'eSpeak qui coupait parfois la parole lorsque le + débit relatif était modifié +* Améliorations de la parole en Vietnamien +* Correction d'un bug avec les voix OneCore disant "a" +* Correction de quelques bugs de navigation lorsque `AutoZoomOut` est False + (pas la valeur par défaut) +* Correction de la mise à jour autour des changements de langue et de + certains autres changements de boîte de dialogue afin qu'ils prennent + effet immédiatement après avoir cliqué sur "Appliquer" ou "OK". +* Ajout d'une option "Utiliser la langue de la voix" afin que MathCAT parle + immédiatement dans la bonne langue (s'il existe une traduction) +* Plusieurs améliorations de nettoyage de mauvais code MathML ### Version 0.3.3 Cette version contient un certain nombre de corrections de bogues. Les @@ -107,30 +235,23 @@ il y a trop de bogues pour être utilisé autrement que pour les tests. Je m'attends à ce que la prochaine version de MathCAT contienne une implémentation fiable. -### Version 0.3.8 -Braille : - -* Le dialogue s'est internationalisé (un grand merci aux traducteurs !) -* Mise en œuvre initiale de la CMU - code braille utilisé en Espagne et dans - plusieurs pays Lusophones -* Améliorations significatives du braille Vietnamien -* Modifiez le curseur de la boîte de dialogue de débit relatif pour avoir - une valeur maximale de 100 % (permet désormais uniquement de définir des - débits plus lents). En outre, des tailles de pas ont été ajoutées pour - qu'il soit plus facile d'augmenter/diminuer le débit de manière - significative. -* Correction de quelques bugs de l'UEB et ajout de quelques caractères pour - l'UEB +### Version 0.2.5 +* Plus d'améliorations chimique +* Correction pour Nemeth : -Autres correctifs : + * Ajout de règles "omission" + * Ajout de quelques règles pour les indicateurs de langue anglaise + * Ajout de plus de cas où l'indicateur polyvalent est nécessaire + * Correctifs liés au code Nemeth et à la ponctuation -* Améliorations de la parole en Vietnamien -* Correction d'un bug avec les voix OneCore disant "a" -* Correction de quelques bugs de navigation lorsque `AutoZoomOut` est False - (pas la valeur par défaut) -* Correction de la mise à jour autour des changements de langue et de - certains autres changements de boîte de dialogue afin qu'ils prennent - effet immédiatement après avoir cliqué sur "Appliquer" ou "OK". +### Version 0.2 +* Beaucoup de correctifs de bogues +* Améliorations de la parole +* Un paramètre de préférence pour contrôler la durée de la pause (fonctionne + avec le changement du débit relatif de la parole pour les mathématiques) +* Prise en charge de la reconnaissance de la notation chimique et à la + verbaliser de manière appropriée +* Traductions vers indonésien et vietnamien [[!tag dev stable]] diff --git a/addon/doc/ru/readme.md b/addon/doc/ru/readme.md new file mode 100644 index 00000000..b6e01753 --- /dev/null +++ b/addon/doc/ru/readme.md @@ -0,0 +1,246 @@ +# MathCAT # + +* Автор: Neil Soiffer +* Совместимость с NVDA: 2018.1 или выше (не проверялось в более ранних + версиях) +* Загрузить [стабильную версию][1] + +MathCat предназначен для замены в конечном итоге MathPlayer, поскольку +MathPlayer больше не поддерживается. MathCat генерирует речь и Брайль с +помощью MathML. Речь для math, созданная MathCat, дополнена просодией, чтобы +она звучала более естественно. Навигация по речи может осуществляться в трех +режимах с использованием тех же команд, что и в MathPlayer. Кроме того, +навигационный узел отображается на брайлевском дисплее. Поддерживаются +технические решения Nemeth и UEB. + +В MathCat есть несколько параметров конфигурации, которые управляют речью, +навигацией и Брайлем. Многие из них можно настроить в диалоге настроек +MathCat (находится в меню настроек NVDA). Дополнительную информацию об этих +настройках смотрите в [документации по +MathCat](https://nsoiffer.github.io/MathCAT/users.html). Документация +содержит ссылку на +[таблицу](https://nsoiffer.github.io/MathCAT/nav-commands.html), в которой +перечислены все навигационные команды в MathCat. + +Примечание: MathCat - это общая библиотека для генерации речи и шрифта +Брайля на основе MathML. Она используется в других проектах AT, помимо +NVDA. Информацию о проекте MathCat в целом смотрите на главной [странице +документации MathCat](https://nsoiffer.github.io/MathCAT). + + +Кто должен использовать MathCat: + +* Те, кому нужен высококачественный шрифт Брайля Nemeth (Nemeth от + MathPlayer основан на поколении Nemeth от liblouis, в котором есть ряд + существенных ошибок, которые технически трудно исправить). +* Те, кому нужен технический шрифт Брайля UEB, CMU + (испанский/португальский), немецкий LaTeX, AsciiMath или вьетнамский шрифт + Брайля +* Те, кто хочет опробовать новейшие технологии и готов помочь, сообщая об + ошибках +* Те, кто использует Eloquence в качестве голоса + +Кому НЕ следует использовать MathCat: + +* Любому, кто использует MathPlayer на языке, который еще не поддерживается + MathCat (существуют переводы для китайского (традиционного), испанского, + индонезийского и вьетнамского языков; переводы появятся в будущем), и кому + не нравится речь на одном из поддерживаемых языков. +* Любому, кто предпочитает Access8Math для MathPlayer (для речи или других + функций) + +Правила MathCat для речи еще не так обширны, как правила MathPlayer - это +может быть еще одной причиной придерживаться MathPlayer. MathCat +используется в качестве испытательного стенда для идей для MathML 4, которые +позволяют авторам выражать свои намерения так, чтобы двусмысленные +обозначения можно было произносить правильно, а не угадывать. Я воздержался +от добавления слишком большого количества правил, поскольку архитектура +MathCat сосредоточена на использовании и выводе намерений автора, и они еще +не полностью определены. + +## Журнал обновлений MathCat + +### Версия 0.6.3 + +* Все языковые файлы и файлы правил шрифта Брайля заархивированы по + каталогам и распаковываются по требованию. + + * В настоящее время это экономит ~5 Мб при распаковке Rules.zip и будет + экономить еще больше по мере добавления новых языков и кодов Брайля. + * Это делается в рамках подготовки к внедрению MathCat в NVDA 2024.3 + +* Добавлена новая настройка "DecimalSeparator`. + + * Значением по умолчанию является "Auto", другими значениями являются ".", + "," и "Custom". Первые три значения задают "DecimalSeparators" и + `BlockSeparators`. + * `Автоматически` устанавливает эти настройки на основе значения параметра + `Язык`. Для некоторых языков, таких как испанский, в одних странах + используется `,`, а в других - `.`. В этом случае лучше всего указать + язык, в котором также указан код страны (например, `es-es` или `es-mx`), + чтобы убедиться, что используется правильное значение. + +* Добавлен шведский язык в число поддерживаемых языков. +* Добавлено больше символов Unicode, чтобы включить в стандарт Unicode как + все символы Unicode, помеченные как "Sm", так и те, которые содержат + mathclass (за исключением классов Alphabetic и Glyph). +* После изменения способа работы префиксов в предыдущей версии я забыл + изменить "MathRate" и "PauseFactor" на числа, а не на строки. +* Исправлена ошибка в правилах Брайля (пропущенное изменение по сравнению с + предыдущими версиями), из-за которой при поиске значения определения + должен был быть указан третий аргумент, указывающий на необходимость + поиска в файлах _Braille_ `definitions.yaml`, а не в речевых файлах. +* Исправлено использование "definitions.yaml`. +* Исправлены некоторые ошибки в очистке MathML для десятичных разделителей + ",". +* Обнаружил ошибку в выделении шрифтом Брайля, когда ничего не выделяется + (возможно, этого никогда не происходит, поэтому я не видел этого на + практике?) +* Исправлен режим "Описания", чтобы он работал - он по-прежнему очень + минимален и, вероятно, пока бесполезен +* Исправлена минимальная поддерживаемая версия + +### Версия 0.5.6 +* Добавлено копировать как... в диалоговое окно MathCat (на панели + "Навигация"). +* Исправлена ошибка, из-за которой язык возвращался к английскому при смене + стиля речи. +* Исправлена ошибка с навигацией и Брайлем +* Исправлены некоторые проблемы с интервалом между символами Asciimath. +* Улучшено распознавание химического состава +* Обновлен MathCat до новой спецификации химии BANA Nemeth (по-прежнему не + обработаны изменения стиля/шрифта только в одной строке и в специальном + регистре) +* Исправлена ошибка, возникавшая при использовании в числах цифр, отличных + от ASCII (например, выделенных жирным шрифтом) +* Не использовать курсивные указатели в кодах Брайля, когда используются + математические буквенно-цифровые символы, выделенные курсивом +* Некоторые другие мелкие исправления ошибок, о которых пользователи не + сообщали + +### Версия 0.5.0 +* Добавлен немецкий шрифт Брайля LaTeX. В отличие от других шрифтов Брайля, + этот код генерирует символы ASCII и использует текущую таблицу вывода + шрифта Брайля для перевода символов в шрифт Брайля. +* Добавлен (экспериментально) код азбуки Брайля AsciiMath. Как и в коде + Брайлевского шрифта LaTeX, он генерирует символы ASCII и использует + текущую таблицу вывода Брайля для перевода символов в шрифт Брайля. +* Добавлена опция "CopyAs", которая поддерживает копирование в форматах + MathML, LaTeX или AsciiMath с использованием cntl +C при фокусировке на + MathML (как и раньше). Копируется текущий узел в фокусе. Примечание: это + указано только в настройках.файл yaml и не отображается (пока) в диалоге + настроек MathCat. + +### Версия 0.4.2 +* Исправлено переключение языка при изменении голоса и выборе языка MathCat + в качестве "Автоматического" +* Добавлены дополнительные проверки на наличие нарушений чтения, чтобы + улучшить чтение, когда оно не настроено для слепых +* Nemeth: исправлена ошибка с "~", когда она не является частью mrow +* UEB: добавление символов, исправление интервала "~" в префиксе if, + исправление xor, +* Очистка MathML для гласных с ударением (в основном для вьетнамцев) +* Кардинальная переработка предпочтительного кода чтения / обновления с + большим ускорением - добавлен параметр "CheckRuleFiles" для контроля того, + какие файлы проверяются на наличие обновлений +* Добавлены два новых вызова интерфейса - позволяет устанавливать + местоположение навигатора с помощью Брайлевского курсора (пока не является + частью дополнения MathCat) + +### Версия 0.3.11 +* Обновлен до python 3.11 и проверен на работоспособность с NVDA 2024.1 +* Исправлены ошибки во вьетнамском шрифте Брайля, а также в речи, в основном + по химии. +* Исправлены ошибки в наборе шрифта Брайля, когда код шрифта Брайля и + зависимый язык не совпадают (в частности, вьетнамский шрифт Брайля и + вьетнамская речь) +* Исправлена ошибка с пробелами в HTML внутри токенов +* Улучшено распознавание римских цифр + + +### Версия 0.3.9 +* Добавлен традиционный китайский перевод (спасибо Hon-Jang Yang) +* Исправлена ошибка с переходом к основанию скриптового выражения, + содержащего круглые скобки +* Существенно изменён способ обработки пробелов. В основном это влияет на + вывод по Брайлю (пробелы и обнаружение пропусков). +* Улучшено распознавание химии +* UEB исправляет ошибки, связанные с добавлением примеров по химии +* UEB исправляет ошибки при добавлении вспомогательных скобок в некоторых + случаях + + +### Версия 0.3.8 + +Брайль: + +* Диалог был интернационализирован для нескольких языков (большое спасибо + переводчикам!) +* Первоначальное внедрение CMU - кода Брайля, используемого в испаноязычных + и португалоязычных странах +* Исправлены некоторые ошибки в UEB и добавлены некоторые символы для UEB +* Значительные улучшения во вьетнамском шрифте Брайля + +Другие исправления: + +* Изменён ползунок диалога относительной скорости на максимальное значение + 100% (теперь можно устанавливать только более низкие скорости). Кроме + того, добавлены размеры шага, чтобы было проще значительно повышать / + понижать скорость. +* Исправлена ошибка eSpeak, из-за которой иногда прерывалась речь при + изменении относительной скорости +* Улучшения во вьетнамской речи +* Исправлена ошибка, из-за которой голоса OneCore произносили "a" +* Исправлены некоторые ошибки навигации, когда значение `AutoZoomOut` было + ложным (не по умолчанию) +* Исправлены изменения, связанные с изменением языка и некоторыми другими + изменениями в диалоге, чтобы они вступали в силу немедленно после нажатия + кнопки "Применить" или "ОК". +* Добавлена опция "Использовать язык озвучки", чтобы MathCat сразу говорил + на нужном языке (если есть перевод). +* Несколько улучшений для очистки некачественного кода MathML + +### Версия 0.3.3 +В этом выпуске исправлен ряд ошибок. Основными новыми функциями и +исправлениями ошибок являются: + +* Добавлен перевод на испанский (спасибо Noelia Ruiz и María Allo Roldán) +* Изменена навигация таким образом, что она начинает увеличиваться на один + уровень +* Добавлены клавиши cntrl+alt+стрелка для навигации по табличным + структурам. Эти клавиши должны быть более запоминающимися, поскольку они + используются для навигации по таблицам в NVDA. +* Устранена ошибка NVDA для голосовых сообщений eSpeak, из-за которой они + замедлялись, когда относительная математическая скорость была установлена + ниже скорости текстовой речи. +* Мы решили проблему с голосами OneCore, чтобы они произносили длинный звук + 'a'. + +There are lots of small tweaks to the speech and some bug fixes for both +Nemeth and UEB. + +Note: there is now an option to get Vietnam's braille standard as braille +output. This is still a work in progress and is too buggy to be used other +than for testing. I expect the next MathCAT release will contain a reliable +implementation. + +### Версия 0.2.5 +* Больше улучшений химии +* Fixes for Nemeth: + + * Добавлены правила "пропуска" + * Добавлены некоторые правила для индикаторов английского языка + * Добавлено больше случаев, когда требуется многоцелевой индикатор + * Исправления, связанные с Nemeth и пунктуацией + +### Версия 0.2 +* Множество исправлений ошибок +* Улучшения в речи +* Предпочтительная настройка для управления длительностью паузы (работает с + изменениями относительной скорости речи для математики) +* Поддержка распознавания химических знаков и правильного их произношения +* Переводы на индонезийский и вьетнамский языки + +[[!tag dev stable]] + +[1]: https://www.nvaccess.org/addonStore/legacy?file=mathcat diff --git a/addon/doc/tr/readme.md b/addon/doc/tr/readme.md index 83a94888..da8ecbd3 100644 --- a/addon/doc/tr/readme.md +++ b/addon/doc/tr/readme.md @@ -4,13 +4,13 @@ * NVDA uyumluluğu: 2018.1 veya sonrası (önceki sürümlerde denenmemiştir) * [Kararlı sürümü indir][1] -MathCAT, sonunda MathPlayer'ın yerini alacak şekilde tasarlanmıştır çünkü -MathPlayer artık desteklenmemektedir. MathCAT, MathML'den konuşma ve braille -oluşturur. MathCAT tarafından üretilen matematik konuşması, kulağa daha -doğal gelmesi için prozodi ile geliştirilmiştir. Konuşma, MathPlayer ile -aynı komutlar kullanılarak üç modda gezinilebilir. Ek olarak, navigasyon -düğümü bir braille ekranda gösterilir. Hem Nemeth hem de UEB teknik -desteklenir. +MathCAT, MathPlayer artık desteklenmediğinden, sonunda MathPlayer'ın yerini +alacak şekilde tasarlanmıştır. MathCAT, MathML'den konuşma ve braille +üretir. MathCAT tarafından matematik için üretilen konuşma, daha doğal +görünmesi için prozodi ile zenginleştirilmiştir. Konuşmada MathPlayer ile +aynı komutlar kullanılarak üç modda gezinilebilir. Ayrıca gezinme düğümü bir +braille ekranında gösterilir. Hem Nemeth hem de UEB teknik +desteklenmektedir. MathCAT konuşma, Gezinme ve braille'i kontrol eden bir dizi yapılandırma seçeneğine sahiptir. Bunların çoğu MathCAT ayarlar iletişim kutusunda @@ -32,18 +32,18 @@ MathCAT'ı kimler kullanmalı: * Yüksek kaliteli Nemeth braille'e ihtiyaç duyanlar (MathPlayer'ın Nemeth'i, teknik olarak düzeltilmesi zor olan bir dizi önemli hataya sahip olan liblouis'in Nemeth nesline dayanmaktadır). -* UEB teknik braille'e ihtiyaç duyanlar +* UEB teknik braille, CMU (İspanyolca/Portekizce), Almanca LaTeX, ASCIIMath + veya Vietnamca braille'e ihtiyaç duyanlar * En son teknolojiyi denemek isteyenler ve hataları bildirerek yardım etmeye istekli olanlar * Eloquence sesini kullananlar MathCAT'ı kimler KULLANMAMALIDIR: -* MathPlayer'ı İngilizce dışında bir dille kullanan herkes (Endonezce ve - Vietnamca için çeviriler mevcuttur; çeviriler gelecekte eklenecektir) -* MathPlayer'ı Nemeth olmayan/UEB olmayan braille çıktısıyla kullanan herkes - (bir braille çevirisine yardımcı olmak istiyorsanız benimle iletişime - geçin) +* MathPlayer'ı henüz MathCAT tarafından desteklenmeyen bir dille kullanan + (Çince (Geleneksel), İspanyolca, Endonezce ve Vietnamca için çeviriler + mevcuttur; çeviriler gelecekte gelecektir) ve desteklenen dillerden + birinde konuşma konusunda rahat olmayan herkes. * Access8Math'i MathPlayer'a tercih eden herkes (konuşma veya diğer özellikler için) @@ -57,30 +57,146 @@ tam olarak çözülmediğinden, çok fazla kural eklemeyi erteledim. ## MathCAT Güncelleme Günlüğü -### Sürüm 0.2 -* Çok sayıda hata düzeltmesi -* Konuşma iyileştirmeleri -* Duraklatma süresini kontrol etmek için bir tercih ayarı (matematik için - göreli konuşma hızındaki değişikliklerle çalışır) -* Kimya gösterimini tanıma ve uygun şekilde konuşma desteği -* Endonezce ve Vietnamca çeviriler +### Sürüm 0.6.3 + +* Tüm dil ve Braille Kural dosyaları dizin bazında sıkıştırılır ve talep + üzerine açılır. + + * Bu, şu anda Rules.zip dosyası açıldığında yaklaşık 5 MB tasarruf + sağlar. Daha fazla dil ve braille kodu eklendikçe daha da fazla tasarruf + sağlayacaktır. + * Bu, MathCAT'in NVDA 2024.3'e dahili olarak eklenmesine hazırlık + niteliğindedir + +* Yeni tercih 'Ondalık Ayırıcı' eklendi. + + * Varsayılan değer "Otomatik" olup diğer değerler ".", "," ve + "Özel"dir. İlk üç değer `Ondalık Ayırıcılar` ve `Blok Ayırıcılar`ı + ayarlar. + * 'Otomatik', bu tercihleri ​​'Dil' tercihinin değerine göre + ayarlar. İspanyolca gibi bazı diller için bazı ülkelerde `,`, bazılarında + ise `.` kullanılır. Bu durumda, doğru değerin kullanıldığından emin olmak + için dili ülke kodunu da içerecek şekilde ayarlamak (ör. "es-es" veya + "es-mx") en iyisidir. + +* Desteklenen dillere İsveççe eklendi. +* Unicode standardına hem "Sm" olarak işaretlenen tüm Unicode karakterlerini + hem de matematik sınıfına sahip olanları (Alfabetik ve Glif sınıfları + hariç) dahil etmek için daha fazla Unicode karakteri eklendi. +* Önceki bir sürümde tercihlerin çalışma şeklini değiştirdikten sonra, + 'MathRate' ve 'PauseFactor'ı dize olarak değil sayı olarak değiştirmeyi + unuttum. +* Bir tanımın değerini ararken konuşma dosyalarına değil, _Braille_ + `definitions.yaml` dosyalarına bakmak için üçüncü bir argümanın + verilmesinin gerektiği Braille Kurallarında (önceki değişikliklerde + kaçırılan) hata düzeltildi. +* 'definitions.yaml' kullanımı temizlendi. +* ``, ondalık ayırıcılar için MathML temizliğindeki bazı hatalar düzeltildi. +* Hiçbir şey vurgulanmadığında braille vurgulamada bir hata buldum (belki de + hiçbir zaman gerçekleşmez, bu yüzden bunu pratikte görmedim?) +* "Açıklama" modu çalışacak şekilde düzeltildi; hala çok az düzeyde ve + muhtemelen henüz kullanışlı değil +* Desteklenen minimum sürüm düzeltildi + +### Sürüm 0.5.6 +* MathCAT iletişim kutusuna ("Gezinme" bölmesinde) Farklı + Kopyala... eklendi. +* Konuşma stillerini değiştirirken dilin İngilizceye dönmesine neden olan + bir hata düzeltildi. +* Gezinme ve braille ile ilgili bir hata düzeltildi +* Bazı Asciimath aralık sorunları düzeltildi. +* Geliştirilmiş kimya tanıma +* MathCAT yeni BANA Nemeth kimya spesifikasyonuna güncellendi (hala yalnızca + tek satır ve özel durum stili/yazı tipi değişiklikleri işlenmedi) +* Sayılarda ASCII olmayan rakamlar (ör. kalın rakamlar) kullanıldığında + oluşan çökme düzeltildi +* Matematik alfanümerik italik karakterler kullanıldığında, braille + kodlarında italik göstergeler kullanmayın +* Kullanıcılar tarafından bildirilmeyen diğer bazı küçük hata düzeltmeleri + +### Sürüm 0.5.0 +* Almanca LaTeX braille kodu eklendi. Diğer braille kodlarından farklı + olarak bu, ASCII karakterleri oluşturur ve karakterleri braille'e çevirmek + için mevcut braille çıktı tablosunu kullanır. +* ASCIIMath braille kodu eklendi. (Deneysel) LaTeX braille kodu gibi, bu da + ASCII karakterleri oluşturur ve karakterleri braille'e çevirmek için + mevcut braille çıktı tablosunu kullanır. +* MathML'ye odaklanıldığında (daha önce olduğu gibi) CTRL+C kullanılarak + MathML, LaTeX veya ASCIIMath olarak kopyalamayı destekleyen "Farklı + Kopyala" tercihi eklendi. O anda odaklanılan düğüm kopyalanır. Not: Bu + yalnızca prefs.yaml dosyasında listelenir ve MathCAT Tercihleri ​​iletişim + kutusunda (henüz) gösterilmez. + +### Sürüm 0.4.2 +* Ses değiştiğinde ve MathCAT dili "Otomatik" olduğunda dil değişimi + düzeltildi +* Görme engelliler için ayarlanmadığında okumayı iyileştirmek amacıyla + $Impairments için daha fazla kontrol eklendi +* Nemeth: Bir mrowun parçası olmadığında "~" için düzeltme +* UEB: karakter eklemeleri, önek ise "~" boşluk düzeltmesi, xor düzeltmesi, +* Aksanlı ünlüler için MathML temizliği (özellikle Vietnamca için) +* Tercih okuma/güncelleme kodunun büyük bir hızla yeniden yazılması - hangi + dosyaların güncellemeler için kontrol edildiğini denetlemek için + "CheckRuleFiles" tercihi eklendi +* İki yeni arayüz çağrısı eklendi - gezinme konumunun braille imlecinden + ayarlanmasını sağlar (henüz MathCAT eklentisinin bir parçası değil) + +### Sürüm 0.3.11 +* Python 3.11'e yükseltildi ve NVDA 2024.1 ile çalıştığı doğrulandı +* Vietnamca braille alfabesindeki ve ayrıca Konuşmadaki, çoğunlukla kimyaya + yönelik hatalar düzeltildi. +* Braille kodu ve bağımlı dil eşleşmediğinde bozuk braille düzeltildi + (özellikle Vietnam braille ve Vietnamca konuşma) +* Belirteçlerin içindeki HTML'de bulunan boşluk hatası düzeltildi +* Roma rakamı algılaması geliştirildi + + +### Sürüm 0.3.9 +* Geleneksel Çince çevirisi eklendi (Hon-Jang Yang sayesinde) +* Parantez içeren kodlanmış bir ifadenin tabanına gitmeyle ilgili hata + düzeltildi +* Boşlukların işlenme şekli önemli ölçüde değiştirildi. Bu esas olarak + braille çıktısını etkiler (boşluklar ve "ihmal" tespiti). +* Kimyanın daha iyi tanınması +* Kimya örneklerinin eklenmesiyle ortaya çıkan UEB braille düzeltmeleri +* Bazı durumlarda yardımcı parantez eklemeye yönelik UEB düzeltmeleri -### Sürüm 0.2.5 -* Kimya alanında daha fazla iyileştirme -* Nemeth için Düzeltmeler: +### Sürüm 0.3.8 - * "İhmal" kuralları eklendi - * İngilizce Dil Göstergeleri için bazı kurallar eklendi - * Çok amaçlı göstergenin gerekli olduğu daha fazla durum eklendi - * Nemeth ve noktalama işaretleriyle ilgili düzeltmeler +Braille: +* Diyalog birçok dilde uluslararası hale getirildi (çevirmenlere çok + teşekkürler!) +* CMU'nun ilk uygulaması - İspanyolca ve Portekizce konuşulan ülkelerde + kullanılan braille kodu +* Bazı UEB hataları düzeltildi ve UEB için bazı karakterler eklendi +* Vietnamca braille alfabesinde önemli iyileştirmeler + +Diğer düzeltmeler: + +* Göreli hız iletişim kutusunun kaydırıcısı maksimum %100 değerine sahip + olacak şekilde değiştirildi (artık yalnızca daha yavaş hızların + ayarlanmasına izin veriyor). Ayrıca, hızı önemli ölçüde artırmak/düşürmek + daha kolay olacak şekilde adım boyutları eklendi. +* Göreceli hız değiştirildiğinde bazen konuşmayı kesen eSpeak hatası + düzeltildi +* Vietnamca konuşmada iyileştirmeler +* OneCore seslerinin "a" demesiyle ilgili hata düzeltildi +* 'Otomatik Yakınlaştırma' Yanlış olduğunda (varsayılan değil) bazı gezinme + hataları düzeltildi +* Dil değişiklikleri ve diğer bazı iletişim kutusu değişiklikleriyle ilgili + güncellemeler düzeltildi; böylece bunların "Uygula" veya "Tamam" + tıklandığında hemen etkili olması sağlandı. +* MathCAT'in kutudan çıkar çıkmaz doğru dilde konuşması için "Sesin Dilini + Kullan" seçeneği eklendi (bir çeviri varsa) +* Zayıf MathML kodunu temizlemeye yönelik çeşitli iyileştirmeler ### Sürüm 0.3.3 Bu sürümde bir dizi hata düzeltmesi var. Başlıca yeni özellikler ve hata düzeltmeleri şunlardır: -* İspanyolca Çeviri eklendi (Noelia Ruiz ve María Allo Roldán'a teşekkürler) +* İspanyolca Çeviri eklendi (Noelia Ruiz ve Maria Allo Roldan'a teşekkürler) * Gezinme bir seviye yakınlaştırılmış olarak başlayacak şekilde değiştirildi * Tablo yapılarında gezinmenin bir yolu olarak ctrl+alt+ok eklendi. Bu tuşlar, NVDA'da tablolarda gezinmek için kullanıldıkları için daha akılda @@ -99,28 +215,22 @@ seçeneği var. Bu hala devam eden bir çalışma ve test dışında kullanılamayacak kadar hatalı. Bir sonraki MathCAT sürümünün güvenilir bir uygulama içermesini bekliyorum. -### Sürüm 0.3.8 -Braille: - -* Diyalog uluslararası hale getirildi (çevirmenlere çok teşekkürler!) -* CMU'nun ilk uygulaması - İspanya'da ve Portekizce konuşulan birkaç ülkede - kullanılan braille kodu -* Vietnamca braille alfabesinde önemli iyileştirmeler -* Göreli hız iletişim kutusunun kaydırıcısı maksimum %100 değerine sahip - olacak şekilde değiştirildi (artık yalnızca daha yavaş hızların - ayarlanmasına izin veriyor). Ayrıca, hızı önemli ölçüde artırmak/düşürmek - daha kolay olacak şekilde adım boyutları eklendi. -* Bazı UEB hataları düzeltildi ve UEB için bazı karakterler eklendi +### Sürüm 0.2.5 +* Kimya alanında daha fazla iyileştirme +* Nemeth için Düzeltmeler: -Diğer düzeltmeler: + * "İhmal" kuralları eklendi + * İngilizce Dil Göstergeleri için bazı kurallar eklendi + * Çok amaçlı göstergenin gerekli olduğu daha fazla durum eklendi + * Nemeth ve noktalama işaretleriyle ilgili düzeltmeler -* Vietnamca konuşmada iyileştirmeler -* OneCore seslerinin "a" demesiyle ilgili hata düzeltildi -* 'Otomatik Yakınlaştırma' Yanlış olduğunda (varsayılan değil) bazı gezinme - hataları düzeltildi -* Dil değişiklikleri ve diğer bazı iletişim kutusu değişiklikleriyle ilgili - güncellemeler düzeltildi; böylece bunların "Uygula" veya "Tamam" - tıklandığında hemen etkili olması sağlandı. +### Sürüm 0.2 +* Çok sayıda hata düzeltmesi +* Konuşma iyileştirmeleri +* Duraklatma süresini kontrol etmek için bir tercih ayarı (matematik için + göreli konuşma hızındaki değişikliklerle çalışır) +* Kimya gösterimini tanıma ve uygun şekilde konuşma desteği +* Endonezce ve Vietnamca çeviriler [[!tag dev stable]] diff --git a/addon/doc/zh_CN/readme.md b/addon/doc/zh_CN/readme.md new file mode 100644 index 00000000..d535382d --- /dev/null +++ b/addon/doc/zh_CN/readme.md @@ -0,0 +1,184 @@ +# MathCAT # + +* 作者: 尼尔·索伊弗尔(Neil Soiffer) +* NVDA 兼容性: 2018.1 或更高版本(未在早期版本中测试) +* 下载 [稳定版][1] + +MathCAT is designed to eventually replace MathPlayer because MathPlayer is +no longer supported. MathCAT generates speech and braille from MathML. The +speech for math produced by MathCAT is enhanced with prosody so that it +sounds more natural. The speech can be navigated in three modes using the +same commands as MathPlayer. In addition, the navigation node is indicated +on a braille display. Both Nemeth and UEB technical are supported. + +MathCAT 有许多控制语音、导航和盲文的配置选项。其中许多可以在 MathCAT 设置对话框中设置 (可从 NVDA +首选项菜单中找到)。有关这些设置的更多信息,请参阅 [MathCAT +文档](https://nsoiffer.github.io/MathCAT/users.html)。该文档包含指向 [MathCAT +导航命令表](https://nsoiffer.github.io/MathCAT/nav-commands.html) 的链接。 + +注意: MathCAT 是一个用于从 MathML 生成语音和盲文的通用库。除了 NVDA 之外,其他 AT (辅助工具)项目也在使用。有关 +MathCAT 项目的常规信息,请参阅 [MathCAT 文档主页](https://nsoiffer.github.io/MathCAT)。 + + +谁应该使用 MathCAT: + +* 那些需要高质量 Nemeth 码盲文的人 (MathPlayer 的 Nemeth 码基于 liblouis 的 Nemeth + 码一代,有许多技术上难以修复的重大错误)。 +* Those who need UEB technical braille, CMU (Spanish/Portuguese), German + LaTeX, ASCIIMath, or Vietnamese braille +* 那些想要尝试最新技术并愿意通过报告 Bug 来提供帮助的人 +* 那些用 Eloquence 合成器的人 + +谁不应该使用 MathCAT: + +* Anyone who uses MathPlayer with a language that is not yet supported by + MathCAT (translations exist for Chinese (Traditional), Spanish, Indonesian + and Vietnamese; translations will be coming in the future) and are not + comfortable with speech in one of the supported languages. +* 任何喜欢 Access8Math 而不是 MathPlayer 的人 (需要使用语音或其他更多功能的人) + +MathCAT 的语音规则还没有 MathPlayer 的规则那么全面——这可能是坚持使用 MathPlayer 的另一个原因。MathCAT 被用作 +MathML 4 思想的测试平台,它允许作者表达他们的意图,以便能够正确地说出不明确的符号,而不是猜测。我没有添加太多规则,因为 MathCAT +的体系结构以使用和推断作者意图为中心,这些还没有完全解决。 + +## MathCAT 更新日志 + +### Version 0.6.3 + +* All the language and braille Rule files are zipped up per directory and + unzipped on demand. + + * This currently saves ~5mb when Rules.zip is unzipped, and will save even + more as more languages and braille codes are added. + * This is in preparation for MathCAT being built into NVDA 2024.3 + +* Added new preference `DecimalSeparator`. + + * The default value is `Auto`, with other values being ".", ",", and + "Custom". The first three values set `DecimalSeparators` and + `BlockSeparators`. + * `Auto` sets those preferences based on the value of the `Language` + pref. For some language such as Spanish, `,` is used in some countries + and `.` is used in others. In this case, it is best to set the language + to also include the country code (e.g, `es-es` or `es-mx`) to ensure the + right value is used. + +* Added Swedish to supported languages. +* Added more Unicode chars to include both all Unicode chars marked as "Sm" + and those with a mathclass (except Alphabetic and Glyph classes) in the + Unicode standard. +* After changing how prefs work in a previous version, I forgot to change + `MathRate` and `PauseFactor` to be numbers, not strings. +* Fixed bug in the braille Rules (missed change from earlier) where a third + argument should have been given to say to look in the _Braille_ + `definitions.yaml` files and not the speech ones when looking up the value + of a definition. +* Cleaned up use of `definitions.yaml`. +* Fixed some bugs in the MathML cleanup for "," decimal separators. +* Found a bug in braille highlighting when nothing is highlighted (maybe + never happens which is why I didn't see it in practice?) +* Fixed "Describe" mode so that it works -- it is still very minimal and + probably not useful yet +* Fixed minimum supported version + +### Version 0.5.6 +* Added Copy As... to the MathCAT dialog (in the "Navagation" pane). +* Fixed a bug where the language reverted to English when changing speech + styles. +* Fixed a bug with navigation and braille +* Fixed some Asciimath spacing problems. +* Improved chemistry recognition +* Updated MathCAT to new BANA Nemeth chemistry spec (still only single line + and special case style/font changes not handled) +* Fix a crash when non-ASCII digits (e.g., bold digits) are used in numbers +* Don't use italic indicators in braille codes when the math alphanumeric + italic chars are used +* Some other smaller bug fixes that weren't reported by users + +### 版本 0.5.0 +* 添加了德语 LaTeX 盲文代码。与其他盲文代码不同,这会生成 ASCII 字符,并使用当前盲文输出表将字符转换为盲文。 +* 增加了(实验) ASCII Math 盲文码。与 LaTeX 盲文代码一样,它生成 ASCII 字符,并使用当前盲文输出表将字符转换为盲文。 +* 添加了`CopyAs`首选项,支持在关注 MathML 时使用 ctrl+C 复制为 MathML、LaTeX 或 ASCII + Math(如前所述)。将复制当前关注的节点。注意:这仅列在 prefs.yaml 文件中,并且尚未在 “MathCAT首选项” 对话框中公开。 + +### 版本 0.4.2 +* 修复了语音变化且 MathCAT 语言为“自动”时的语言切换问题 +* 添加了更多 $Impairments 检查,以在未为盲人设置时提高阅读能力 +* Nemeth:修复了“~”不是 mrow 的一部分时的问题 +* UEB: character additions, "~" spacing fix if prefix, xor fix, +* 对重音元音的 MathML 清理(主要针对越南语) +* 首选项读取/更新代码的重大重写,速度大大加快--添加了`CheckRuleFiles`前缀以控制检查哪些文件进行更新 +* Added two new interface calls -- enables setting the navigaton location + from the braille cursor (not part of MathCAT addon yet) + +### 版本 0.3.11 +* 升级到 python 3.11,并验证可以与 NVDA 2024.1 一起工作 +* 修复越南语盲文和语音中的错误,主要用于化学。 +* 修复当盲文代码和相关语言不匹配时停止工作的问题(特别是越南盲文和越南语语音) +* 修复了 HTML 标记内部的空白错误 +* 改进了罗马数字检测 + + +### 版本 0.3.9 +* 增加了繁体中文翻译(感谢 Hon-Jang Yang) +* Fixed bug with navigating into the base of a scripted expression that has + parenthesis +* 显著改变了空白的处理方式。这主要影响盲文输出(空白和“省略”检测)。 +* 完善了对化学的识别 +* 添加化学示例后产生的UEB盲文修复 +* UEB 修复了在某些情况下添加辅助括号的问题 + + +### 版本 0.3.8 + +盲文: + +* 对话已经国际化(非常感谢翻译者们!) +* CMU 的初步实施——CMU 盲文码在西班牙和几个葡萄牙语国家使用 +* 修复了一些 UEB 错误,并为 UEB 添加了一些字符 +* 越南盲文的重大改进 + +其他修复: + +* 更改相对速率对话框滑块,使其最大值为 100% (现在只允许设置较慢的语速)。此外,增加了步长,因此更容易显著提高/降低语速。 +* 修复了当相对速率改变时有时会中断语音的 espeak 错误 +* 越南语语音的改进 +* 修正了 OneCore 语音说 “a” 的错误 +* 修复了`AutoZoomOut`为 False (非默认值) 时的一些导航错误 +* 修复了围绕语言更改和其他一些对话框更改的更新,以便它们在单击“应用”或“确认”后立即生效。 +* 添加了“使用语音的语言”选项,这样 MathCAT 就可以使用正确的语言(如果有翻译的话) +* 有关清理 MathML 码的几个改进 + +### 版本 0.3.3 +此版本修复了许多错误。主要的新功能和错误修复如下: + +* 添加了西班牙语翻译 (感谢 Noelia Ruiz 和 María Allo Roldán) +* 修改导航以便于在一个级别内开始放大 +* 添加了 ctrl+alt+箭头键用于导航表格结构。这些按键应该很容易记忆,因为它们与 NVDA 在使用的导航表格按键很类似。 +* 解决了 NVDA 的 eSpeak 语音的错误,当相对数学语速设置为低于文本语速时,该错误会导致语音减慢。 +* 解决了 OneCore 的语音问题,以便于它们能说出长 “a” 音。 + +语音有一些小的调整, Nemeth 码和 UEB 有一些错误修复。 + +注: 现在可以选择将越南的盲文标准作为盲文输出。这仍然是一项正在进行的工作,并且存在太多的 bug,不能用于测试之外的其他用途。我预计下一个 +MathCAT 版本将包含一个可靠的实现。 + +### 版本 0.2.5 +* 进一步化学改进 +* 对 Nemeth 码的修复: + + * Added "omission" rules + * Added some rules for English Language Indicators + * Added more cases where the Mulitpurpose indicator is needed + * Fixes related to Nemeth and punctuation + +### 版本 0.2 +* 大量错误修复 +* 改善语音 +* 一个首选项用于控制暂停持续时间 (适用于数学中相对语速的更改) +* 支持识别并是当说出化学符号 +* 印尼语和越南语翻译 + +[[!tag dev stable]] + +[1]: https://www.nvaccess.org/addonStore/legacy?file=mathcat diff --git a/addon/globalPlugins/MathCAT/MathCAT.py b/addon/globalPlugins/MathCAT/MathCAT.py index cf0f427b..62760fcc 100644 --- a/addon/globalPlugins/MathCAT/MathCAT.py +++ b/addon/globalPlugins/MathCAT/MathCAT.py @@ -9,493 +9,726 @@ # The plugin also requires the use of a small python dll: python3.dll # python3.dll has "Copyright © 2001-2022 Python Software Foundation; All Rights Reserved" - # Note: this code is a lot of cut/paste from other code and very likely could be substantially improved/cleaned. -import braille # we generate braille -import globalVars -from keyboardHandler import KeyboardInputGesture # navigation key strokes -from logHandler import log # logging -import mathPres # math plugin stuff -from os import path # set rule dir path -import re # regexp patter match -import speech # speech commands -import config # look up caps setting -import ui # copy message -from scriptHandler import script # copy MathML via ctrl-c -from synthDriverHandler import getSynth # speech engine param setting -import winUser # clipboard manipulation +import braille # we generate braille +import mathPres # math plugin stuff +import re # regexp patter match +import speech # speech commands +import config # look up caps setting +import ui # copy message +import winUser # clipboard manipulation import gettext -_ = gettext.gettext - import addonHandler -addonHandler.initTranslation() +import winKernel +import gui -from ctypes import windll # register clipboard formats -from typing import Any, Optional +from . import libmathcat_py as libmathcat +from typing import Type +from collections.abc import Generator, Callable +from keyboardHandler import KeyboardInputGesture # navigation key strokes +from logHandler import log # logging +from os import path # set rule dir path +from scriptHandler import script # copy MathML via ctrl-c +from synthDriverHandler import ( + getSynth, + SynthDriver, +) +from ctypes import windll # register clipboard formats from speech import getCurrentLanguage - -from . import libmathcat +from speech.types import SpeechSequence # speech/SSML processing borrowed from NVDA's mathPres/mathPlayer.py from speech.commands import ( - BeepCommand, - PitchCommand, - VolumeCommand, - RateCommand, - LangChangeCommand, - BreakCommand, - CharacterModeCommand, - PhonemeCommand, + BeepCommand, + PitchCommand, + VolumeCommand, + RateCommand, + LangChangeCommand, + BreakCommand, + CharacterModeCommand, + PhonemeCommand, + IndexCommand, + BaseProsodyCommand, + SpeechCommand, + SynthCommand, +) + +from textUtils import WCHAR_ENCODING +from ctypes import c_wchar, WinError, Array +from api import getClipData +from synthDrivers import _espeak + +_ = gettext.gettext + +addonHandler.initTranslation() + +RE_MATHML_SPEECH: re.Pattern = re.compile( + # Break. + r" ?" + # Pronunciation of characters. + r"|(?P[^<]+) ?" + # Specific pronunciation. + r"|(?P[^ <]+) ?" + # Prosody. + r"| ?" + r"|(?P) ?" + r"| ?" # hack for beeps + # Other tags, which we don't care about. + r"|<[^>]+> ?" + # Actual content. + r"|(?P[^<]+)", ) -RE_MATHML_SPEECH = re.compile( - # Break. - r" ?" - # Pronunciation of characters. - r"|(?P[^<]+) ?" - # Specific pronunciation. - r"|(?P[^ <]+) ?" - # Prosody. - r"| ?" - r"|(?P) ?" - r"| ?" # hack for beeps - # Other tags, which we don't care about. - r"|<[^>]+> ?" - # Actual content. - r"|(?P[^<]+)") - -PROSODY_COMMANDS = { - "pitch": PitchCommand, - "volume": VolumeCommand, - "rate": RateCommand, +PROSODY_COMMANDS: dict[str, BaseProsodyCommand] = { + "pitch": PitchCommand, + "volume": VolumeCommand, + "rate": RateCommand, } -RE_MATH_LANG = re.compile(r'''''') -def getLanguageToUse(mathMl:str) -> str: - """Get the language specified in a math tag if the language pref is Auto, else the language preference.""" - mathCATLanguageSetting = 'Auto' - try: - # ignore regional differences if the MathCAT language setting doesn't have it. - mathCATLanguageSetting = libmathcat.GetPreference("Language") - except Exception as e: - log.error(e) - - if mathCATLanguageSetting != 'Auto': - return mathCATLanguageSetting - - languageMatch = RE_MATH_LANG.search(mathMl) - language = languageMatch.group(2) if languageMatch else getCurrentLanguage() # seems to be current voice's language - language = language.lower().replace("_", "-") - return language - -def ConvertSSMLTextForNVDA(text:str, language:str="") -> list: - # MathCAT's default rate is 180 wpm. - # Assume that 0% is 80 wpm and 100% is 450 wpm and scale accordingly. - # log.info(f"\nSpeech str: '{text}'") - if language == "": # shouldn't happen - language = "en" # fallback to what was being used - mathCATLanguageSetting = "en" # fallback in case GetPreference fails for unknown reasons - try: - mathCATLanguageSetting = libmathcat.GetPreference("Language") - except Exception as e: - log.error(e) - - synth = getSynth() - _monkeyPatchESpeak() - wpm = synth._percentToParam(synth.rate, 80, 450) - try: - if synth.rateBoost: - wpm *= 3 # a guess based on espeak -- not sure what oneCore does - except AttributeError: - pass # SAPI voices don't have 'rateBoost' attr - - breakMulti = 180.0 / wpm - supported_commands = synth.supportedCommands - use_break = BreakCommand in supported_commands - use_pitch = PitchCommand in supported_commands - # use_rate = RateCommand in supported_commands - # use_volume = VolumeCommand in supported_commands - use_phoneme = PhonemeCommand in supported_commands - # as of 7/23, oneCore voices do not implement the CharacterModeCommand despite it being in supported_commands - use_character = CharacterModeCommand in supported_commands and synth.name != 'oneCore' - out = [] - if mathCATLanguageSetting != language: - try: - # log.info(f"Setting language to {language}") - libmathcat.SetPreference("Language", language) - out.append(LangChangeCommand(language)) - except Exception as e: - log.error(e) - language = mathCATLanguageSetting # didn't do the 'append' - - resetProsody = [] - for m in RE_MATHML_SPEECH.finditer(text): - if m.lastgroup == "break": - if use_break: - out.append(BreakCommand(time=int(int(m.group("break")) * breakMulti))) - elif m.lastgroup == "char": - ch = m.group("char") - if use_character: - out.extend((CharacterModeCommand(True), ch, CharacterModeCommand(False))) - else: - out.extend((" ", "eigh" if ch=="a" and language.startswith("en") else ch, " ")) - elif m.lastgroup == "beep": - out.append(BeepCommand(2000, 50)) - elif m.lastgroup == "pitch": - if use_pitch: - out.append(PitchCommand(multiplier=int(m.group(m.lastgroup)))) - resetProsody.append(PitchCommand) - elif m.lastgroup in PROSODY_COMMANDS: - command = PROSODY_COMMANDS[m.lastgroup] - if command in supported_commands: - out.append(command(multiplier=int(m.group(m.lastgroup)) / 100.0)) - resetProsody.append(command) - elif m.lastgroup == "prosodyReset": - # for command in resetProsody: # only supported commands were added, so no need to check - command = resetProsody.pop() - out.append(command(multiplier=1)) - elif m.lastgroup == "phonemeText": - if use_phoneme: - out.append(PhonemeCommand(m.group("ipa"), text=m.group("phonemeText"))) - else: - out.append(m.group("phonemeText")) - elif m.lastgroup == "content": - # MathCAT puts out spaces between words, the speak command seems to want to glom the strings together at times, - # so we need to add individual " "s to the output - out.extend((" ", m.group(0), " ")) - if mathCATLanguageSetting != language: - try: - libmathcat.SetPreference("Language", mathCATLanguageSetting) - out.append(LangChangeCommand(None)) - except Exception as e: - log.error(e) - # log.info(f"Speech commands: '{out}'") - return out +RE_MATH_LANG: re.Pattern = re.compile(r"""""") + +# try to get around espeak bug where voice slows down (for other voices, just a waste of time) +# we use a global that gets set at a time when the rate is probably good (SetMathML) +_synthesizerRate: int | None = None + + +def getLanguageToUse(mathMl: str = "") -> str: + """Get the language specified in a math tag if the language pref is Auto, else the language preference. + + :param mathMl: The MathML string to examine for language. Defaults to an empty string. + :returns: The language string to use. + """ + mathCATLanguageSetting: str = "Auto" + try: + # ignore regional differences if the MathCAT language setting doesn't have it. + mathCATLanguageSetting = libmathcat.GetPreference("Language") + except Exception as e: + log.exception(e) + + # log.info(f"getLanguageToUse: {mathCATLanguageSetting}") + if mathCATLanguageSetting != "Auto": + return mathCATLanguageSetting + + languageMatch: re.Match | None = RE_MATH_LANG.search(mathMl) + language: str = ( + languageMatch.group(2) if languageMatch else getCurrentLanguage() + ) # seems to be current voice's language + language = language.lower().replace("_", "-") + if language == "cmn": + language = "zh-cmn" + elif language == "yue": + language = "zh-yue" + return language + + +def convertSSMLTextForNVDA(text: str) -> list[str | SpeechCommand]: + """ + Change the SSML in the text into NVDA's command structure. + The environment is examined to determine whether a language switch is needed. + + :param text: The SSML text to convert. + :returns: A list of strings and SpeechCommand objects. + """ + # MathCAT's default rate is 180 wpm. + # Assume that 0% is 80 wpm and 100% is 450 wpm and scale accordingly. + # log.info(f"\nSpeech str: '{text}'") + + # find MathCAT's language setting and store it away (could be "Auto") + # if MathCAT's setting doesn't match NVDA's language setting, change the language that is used + mathCATLanguageSetting: str = "en" # set in case GetPreference fails + try: + mathCATLanguageSetting = libmathcat.GetPreference("Language") + except Exception as e: + log.exception(e) + language: str = getLanguageToUse() + nvdaLanguage: str = getCurrentLanguage().replace("_", "-") + # log.info(f"mathCATLanguageSetting={mathCATLanguageSetting}, lang={language}, NVDA={nvdaLanguage}") + + _monkeyPatchESpeak() + + synth: SynthDriver = getSynth() + # I tried the engines on a 180 word excerpt. The speeds do not change linearly and differ a bit between engines + # At "50" espeak finished in 46 sec, sapi in 75 sec, and one core in 70; at '100' one core was much slower than the others + wpm: int = max(10, 2 * getSynth()._get_rate()) + breakMulti: float = 180.0 / wpm + supportedCommands: set[Type["SynthCommand"]] = synth.supportedCommands + useBreak: bool = BreakCommand in supportedCommands + usePitch: bool = PitchCommand in supportedCommands + # use_rate = RateCommand in supported_commands + # use_volume = VolumeCommand in supported_commands + usePhoneme: bool = PhonemeCommand in supportedCommands + # as of 7/23, oneCore voices do not implement the CharacterModeCommand despite it being in supported_commands + useCharacter: bool = CharacterModeCommand in supportedCommands and synth.name != "oneCore" + out: list[str | SpeechCommand] = [] + if mathCATLanguageSetting != language: + # log.info(f"Setting language to {language}") + try: + libmathcat.SetPreference("Language", language) + except Exception as e: + log.exception(e) + language = mathCATLanguageSetting # didn't set the language + if language != nvdaLanguage: + out.append(LangChangeCommand(language)) + + resetProsody: list[Type["BaseProsodyCommand"]] = [] + # log.info(f"\ntext: {text}") + for m in RE_MATHML_SPEECH.finditer(text): + if m.lastgroup == "break": + if useBreak: + out.append(BreakCommand(time=int(int(m.group("break")) * breakMulti))) + elif m.lastgroup == "char": + ch: str = m.group("char") + if useCharacter: + out.extend((CharacterModeCommand(True), ch, CharacterModeCommand(False))) + else: + out.extend((" ", "eigh" if ch == "a" and language.startswith("en") else ch, " ")) + elif m.lastgroup == "beep": + out.append(BeepCommand(2000, 50)) + elif m.lastgroup == "pitch": + if usePitch: + out.append(PitchCommand(multiplier=int(m.group(m.lastgroup)))) + resetProsody.append(PitchCommand) + elif m.lastgroup in PROSODY_COMMANDS: + command: Type["BaseProsodyCommand"] = PROSODY_COMMANDS[m.lastgroup] + if command in supportedCommands: + out.append(command(multiplier=int(m.group(m.lastgroup)) / 100.0)) + resetProsody.append(command) + elif m.lastgroup == "prosodyReset": + # for command in resetProsody: # only supported commands were added, so no need to check + command: Type["BaseProsodyCommand"] = resetProsody.pop() + out.append(command(multiplier=1)) + elif m.lastgroup == "phonemeText": + if usePhoneme: + out.append(PhonemeCommand(m.group("ipa"), text=m.group("phonemeText"))) + else: + out.append(m.group("phonemeText")) + elif m.lastgroup == "content": + # MathCAT puts out spaces between words, the speak command seems to want to glom the strings together at times, + # so we need to add individual " "s to the output + out.extend((" ", m.group(0), " ")) + # there is a bug in MS Word that concats the math and the next character outside of math, so we add a space + out.append(" ") + + if mathCATLanguageSetting != language: + # restore the old value (probably "Auto") + try: + libmathcat.SetPreference("Language", mathCATLanguageSetting) + except Exception as e: + log.exception(e) + if language != nvdaLanguage: + out.append(LangChangeCommand(None)) + # log.info(f"Speech commands: '{out}'") + return out -class MathCATInteraction(mathPres.MathInteractionNVDAObject): - # Put MathML on the clipboard using the two formats below (defined by MathML spec) - # We use both formats because some apps may only use one or the other - # Note: filed https://github.com/nvaccess/nvda/issues/13240 to make this usable outside of MathCAT - CF_MathML = windll.user32.RegisterClipboardFormatW("MathML") - CF_MathML_Presentation = windll.user32.RegisterClipboardFormatW("MathML Presentation") - # log.info("2**** MathCAT registering data formats: CF_MathML %x, CF_MathML_Presentation %x" % (CF_MathML, CF_MathML_Presentation)) - - def __init__(self, provider=None, mathMl: Optional[str]=None): - super(MathCATInteraction, self).__init__(provider=provider, mathMl=mathMl) - self._language = getLanguageToUse(mathMl) - self.init_mathml = mathMl - - - def reportFocus(self): - super(MathCATInteraction, self).reportFocus() - try: - text = libmathcat.DoNavigateCommand("ZoomIn") - speech.speak(ConvertSSMLTextForNVDA(text, self._language)) - except Exception as e: - log.error(e) - speech.speakMessage(_("Error in starting navigation of math: see NVDA error log for details")) - - - def getBrailleRegions(self, review: bool = False): - # log.info("***MathCAT start getBrailleRegions") - yield braille.NVDAObjectRegion(self, appendText=" ") - region = braille.Region() - region.focusToHardLeft = True - # libmathcat.SetBrailleWidth(braille.handler.displaySize) - try: - region.rawText = libmathcat.GetBraille("") - except Exception as e: - log.error(e) - speech.speakMessage(_("Error in brailling math: see NVDA error log for details")) - region.rawText = "" - - # log.info("***MathCAT end getBrailleRegions ***") - yield region - - def getScript(self, gesture: KeyboardInputGesture): - # Pass most keys to MathCAT. Pretty ugly. - if isinstance(gesture, KeyboardInputGesture) and "NVDA" not in gesture.modifierNames and ( - gesture.mainKeyName in { - "leftArrow", "rightArrow", "upArrow", "downArrow", - "home", "end", - "space", "backspace", "enter", - "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", - } - # or len(gesture.mainKeyName) == 1 - ): - return self.script_navigate - return super().getScript(gesture) - - def script_navigate(self, gesture: KeyboardInputGesture): - # log.info("***MathCAT script_navigate") - try: - if gesture != None: # == None when initial focus -- handled in reportFocus() - modNames = gesture.modifierNames - text = libmathcat.DoNavigateKeyPress(gesture.vkCode, - "shift" in modNames, "control" in modNames, "alt" in modNames, False) - speech.speak(ConvertSSMLTextForNVDA(text, self._language)) - - # update the braille to reflect the nav position (might be excess code, but it works) - nav_node = libmathcat.GetNavigationMathMLId() - region = braille.Region() - region.rawText = libmathcat.GetBraille(nav_node[0]) - region.focusToHardLeft = True - region.update() - braille.handler.buffer.regions.append(region) - braille.handler.buffer.focus(region) - braille.handler.buffer.update() - braille.handler.update() - except Exception as e: - log.error(e) - speech.speakMessage(_("Error in navigating math: see NVDA error log for details")) - - - _startsWithMath = re.compile("\\s*?" # copy will fix up name spacing - elif self.init_mathml != '': - mathml = self.init_mathml - self._copyToClipAsMathML(mathml) - ui.message(_("copy")) - except Exception as e: - log.error(e) - speech.speakMessage(_("unable to copy math: see NVDA error log for details")) - - - # not a perfect match sequence, but should capture normal MathML - # not a perfect match sequence, but should capture normal MathML - _mathTagHasNameSpace = re.compile("") - _hasAddedId = re.compile(" id='[^'].+' data-id-added='true'") - _hasDataAttr = re.compile(" data-[^=]+='[^']*'") - def _wrapMathMLForClipBoard(self, text: str) -> str: - # cleanup the MathML a little - text = re.sub(self._hasAddedId, "", text) - mathml_with_ns = re.sub(self._hasDataAttr, "", text) - if not re.match(self._mathTagHasNameSpace, mathml_with_ns): - mathml_with_ns = mathml_with_ns.replace('math', "math xmlns='http://www.w3.org/1998/Math/MathML'", 1) - return mathml_with_ns - - def _copyToClipAsMathML(self, text: str, notify: Optional[bool] = False) -> bool: - """Copies the given text to the windows clipboard. - @returns: True if it succeeds, False otherwise. - @param text: the text which will be copied to the clipboard - @param notify: whether to emit a confirmation message - """ - # copied from api.py and modified to use CF_MathML_Presentation - if not isinstance(text, str) or len(text) == 0: - return False - from api import getClipData - import gui - - try: - with winUser.openClipboard(gui.mainFrame.Handle): - winUser.emptyClipboard() - text = self._wrapMathMLForClipBoard(text) - self._setClipboardData(self.CF_MathML, '' + text) - self._setClipboardData(self.CF_MathML_Presentation, '' + text) - self._setClipboardData(winUser.CF_UNICODETEXT, text) - got = getClipData() - except OSError: - if notify: - ui.reportTextCopiedToClipboard() # No argument reports a failure. - return False - if got == text: - if notify: - ui.reportTextCopiedToClipboard(text) - return True - if notify: - ui.reportTextCopiedToClipboard() # No argument reports a failure. - return False - - def _setClipboardData(self, format, data: str): - # Need to support MathML Presentation, so this copied from winUser.py and the first two lines are commented out - # For now only unicode is a supported format - # if format!=CF_UNICODETEXT: - # raise ValueError("Unsupported format") - from textUtils import WCHAR_ENCODING - from ctypes import c_wchar, WinError - import winKernel - text = data - bufLen = len(text.encode(WCHAR_ENCODING, errors="surrogatepass")) + 2 - # Allocate global memory - h=winKernel.HGLOBAL.alloc(winKernel.GMEM_MOVEABLE, bufLen) - # Acquire a lock to the global memory receiving a local memory address - with h.lock() as addr: - # Write the text into the allocated memory - buf=(c_wchar*bufLen).from_address(addr) - buf.value=text - # Set the clipboard data with the global memory - if not windll.user32.SetClipboardData(format,h): - raise WinError() - # NULL the global memory handle so that it is not freed at the end of scope as the clipboard now has it. - h.forget() -class MathCAT(mathPres.MathPresentationProvider): - def __init__(self): - # super(MathCAT, self).__init__(*args, **kwargs) - - try: - # IMPORTANT -- SetRulesDir must be the first call to libmathcat besides GetVersion() - rules_dir = path.join( path.dirname(path.abspath(__file__)), "Rules") - log.info(f"MathCAT {libmathcat.GetVersion()} installed. Using rules dir: {rules_dir}") - libmathcat.SetRulesDir(rules_dir) - libmathcat.SetPreference("TTS", "SSML") - except Exception as e: - log.error(e) - speech.speakMessage(_("MathCAT initialization failed: see NVDA error log for details")) - self._language = "" - - def getSpeechForMathMl(self, mathml: str): - try: - self._language = getLanguageToUse(mathml) - libmathcat.SetMathML(mathml) - except Exception as e: - log.error(e) - log.error(f"MathML is {mathml}") - speech.speakMessage(_("Illegal MathML found: see NVDA error log for details")) - libmathcat.SetMathML("") # set it to something - try: - synth = getSynth() - synthConfig = config.conf["speech"][synth.name] - supported_commands = synth.supportedCommands - # Set preferences for capital letters - libmathcat.SetPreference("CapitalLetters_Beep", "true" if synthConfig["beepForCapitals"] else "false") - libmathcat.SetPreference("CapitalLetters_UseWord", "true" if synthConfig["sayCapForCapitals"] else "false") - if PitchCommand in supported_commands: - libmathcat.SetPreference("CapitalLetters_Pitch", str(synthConfig["capPitchChange"])) - if self._add_sounds(): - return [BeepCommand(800,25)] + ConvertSSMLTextForNVDA(libmathcat.GetSpokenText(), self._language) + [BeepCommand(600,15)] - else: - return ConvertSSMLTextForNVDA(libmathcat.GetSpokenText(), self._language) - - except Exception as e: - log.error(e) - speech.speakMessage(_("Error in speaking math: see NVDA error log for details")) - return [""] - - def _add_sounds(self): - try: - return libmathcat.GetPreference("SpeechSound") != "None" - except: - return False - - def getBrailleForMathMl(self, mathml: str): - # log.info("***MathCAT getBrailleForMathMl") - try: - libmathcat.SetMathML(mathml) - except Exception as e: - log.error(e) - log.error(f"MathML is {mathml}") - speech.speakMessage(_("Illegal MathML found: see NVDA error log for details")) - libmathcat.SetMathML("") # set it to something - try: - return libmathcat.GetBraille("") - except Exception as e: - log.error(e) - speech.speakMessage(_("Error in brailling math: see NVDA error log for details")) - return "" - - - def interactWithMathMl(self, mathml: str): - MathCATInteraction(provider=self, mathMl=mathml).setFocus() - MathCATInteraction(provider=self, mathMl=mathml).script_navigate(None) - -CACHED_SYNTH = None -def _monkeyPatchESpeak(): - global CACHED_SYNTH - currentSynth = getSynth() - if currentSynth.name != "espeak" or CACHED_SYNTH == currentSynth: - return # already patched - - CACHED_SYNTH = currentSynth - currentSynth.speak = patched_speak.__get__(currentSynth, type(currentSynth)) +class MathCATInteraction(mathPres.MathInteractionNVDAObject): + """An NVDA object used to interact with MathML.""" + + # Put MathML or other formats on the clipboard. + # MathML is put on the clipboard using the two formats below (defined by MathML spec) + # We use both formats because some apps may only use one or the other + # Note: filed https://github.com/nvaccess/nvda/issues/13240 to make this usable outside of MathCAT + CF_MathML: int = windll.user32.RegisterClipboardFormatW("MathML") + CF_MathML_Presentation: int = windll.user32.RegisterClipboardFormatW( + "MathML Presentation", + ) + # log.info("2**** MathCAT registering data formats: + # CF_MathML %x, CF_MathML_Presentation %x" % (CF_MathML, CF_MathML_Presentation)) + + def __init__( + self, + provider: mathPres.MathPresentationProvider | None = None, + mathMl: str | None = None, + ): + """Initialize the MathCATInteraction object. + + :param provider: Optional presentation provider. + :param mathMl: Optional initial MathML string. + """ + super(MathCATInteraction, self).__init__(provider=provider, mathMl=mathMl) + if mathMl is None: + self.initMathML = "" + else: + self.initMathML = mathMl + + def reportFocus(self) -> None: + """Calls MathCAT's ZoomIn command and speaks the resulting text.""" + super(MathCATInteraction, self).reportFocus() + # try to get around espeak bug where voice slows down + if _synthesizerRate and getSynth().name == "espeak": + getSynth()._set_rate(_synthesizerRate) + try: + text: str = libmathcat.DoNavigateCommand("ZoomIn") + speech.speak(convertSSMLTextForNVDA(text)) + except Exception as e: + log.exception(e) + # Translators: this message directs users to look in the log file + speech.speakMessage(_("Error in starting navigation of math: see NVDA error log for details")) + finally: + # try to get around espeak bug where voice slows down + if _synthesizerRate and getSynth().name == "espeak": + # log.info(f'reportFocus: reset to {_synthesizer_rate}') + getSynth()._set_rate(_synthesizerRate) + + def getBrailleRegions( + self, + review: bool = False, + ) -> Generator[braille.Region, None, None]: + """Yields braille.Region objects for this MathCATInteraction object.""" + # log.info("***MathCAT start getBrailleRegions") + yield braille.NVDAObjectRegion(self, appendText=" ") + region: braille.Region = braille.Region() + region.focusToHardLeft = True + # libmathcat.SetBrailleWidth(braille.handler.displaySize) + try: + region.rawText = libmathcat.GetBraille("") + except Exception as e: + log.exception(e) + # Translators: this message directs users to look in the log file + speech.speakMessage(_("Error in brailling math: see NVDA error log for details")) + region.rawText = "" + + # log.info("***MathCAT end getBrailleRegions ***") + yield region + + def getScript( + self, + gesture: KeyboardInputGesture, + ) -> Callable[[KeyboardInputGesture], None] | None: + """ + Returns the script function bound to the given gesture. + + :param gesture: A KeyboardInputGesture sent to this object. + :returns: The script bound to that gesture. + """ + if ( + isinstance(gesture, KeyboardInputGesture) + and "NVDA" not in gesture.modifierNames + and gesture.mainKeyName + in { + "leftArrow", + "rightArrow", + "upArrow", + "downArrow", + "home", + "end", + "space", + "backspace", + "enter", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + } + # or len(gesture.mainKeyName) == 1 + ): + return self.script_navigate + else: + return super().getScript(gesture) + + def script_navigate(self, gesture: KeyboardInputGesture) -> None: + """Performs the specified navigation command. + + :param gesture: They keyboard command which specified the navigation command to perform. + """ + try: + # try to get around espeak bug where voice slows down + if _synthesizerRate and getSynth().name == "espeak": + getSynth()._set_rate(_synthesizerRate) + if gesture is not None: # == None when initial focus -- handled in reportFocus() + modNames: list[str] = gesture.modifierNames + text = libmathcat.DoNavigateKeyPress( + gesture.vkCode, + "shift" in modNames, + "control" in modNames, + "alt" in modNames, + False, + ) + # log.info(f"Navigate speech for {gesture.vkCode}/(s={'shift' in modNames}, c={'control' in modNames}): '{text}'") + speech.speak(convertSSMLTextForNVDA(text)) + except Exception as e: + log.exception(e) + # Translators: this message directs users to look in the log file + speech.speakMessage(_("Error in navigating math: see NVDA error log for details")) + finally: + # try to get around espeak bug where voice slows down + if _synthesizerRate and getSynth().name == "espeak": + # log.info(f'script_navigate: reset to {_synthesizer_rate}') + getSynth()._set_rate(_synthesizerRate) + + if not braille.handler.enabled: + return + + try: + # update the braille to reflect the nav position (might be excess code, but it works) + navNode: tuple[str, int] = libmathcat.GetNavigationMathMLId() + brailleChars = libmathcat.GetBraille(navNode[0]) + # log.info(f'braille display = {config.conf["braille"]["display"]}, braille_chars: {braille_chars}') + region: braille.Region = braille.Region() + region.rawText = brailleChars + region.focusToHardLeft = True + region.update() + braille.handler.buffer.regions.append(region) + braille.handler.buffer.focus(region) + braille.handler.buffer.update() + braille.handler.update() + except Exception as e: + log.exception(e) + # Translators: this message directs users to look in the log file + speech.speakMessage(_("Error in brailling math: see NVDA error log for details")) + + _startsWithMath: re.Pattern = re.compile("\\s*? None: + """Copies the raw data to the clipboard, either as MathML, ASCII math, or LaTeX, depending on user preferences. + + :param gesture: The gesture which activated this script. + """ + try: + copyAs: str = "mathml" # value used even if "CopyAs" pref is invalid + textToCopy: str = "" + try: + copyAs = libmathcat.GetPreference("CopyAs").lower() + except Exception as e: + log.exception(f"Not able to get 'CopyAs' preference: {e}") + if copyAs == "asciimath" or copyAs == "latex": + # save the old braille code, set the new one, get the braille, then reset the code + savedBrailleCode: str = libmathcat.GetPreference("BrailleCode") + libmathcat.SetPreference("BrailleCode", "LaTeX" if copyAs == "latex" else "ASCIIMath") + textToCopy = libmathcat.GetNavigationBraille() + libmathcat.SetPreference("BrailleCode", savedBrailleCode) + if copyAs == "asciimath": + copyAs = "ASCIIMath" # speaks better in at least some voices + else: + mathml: str = libmathcat.GetNavigationMathML()[0] + if not re.match(self._startsWithMath, mathml): + mathml = "\n" + mathml + "" # copy will fix up name spacing + elif self.initMathML != "": + mathml = self.initMathML + if copyAs == "speech": + # save the old MathML, set the navigation MathML as MathMl, get the speech, then reset the MathML + savedMathML: str = self.initMathML + savedTTS: str = libmathcat.GetPreference("TTS") + if savedMathML == "": # shouldn't happen + raise Exception("Internal error -- MathML not set for copy") + libmathcat.SetPreference("TTS", "None") + libmathcat.SetMathML(mathml) + # get the speech text and collapse the whitespace + textToCopy = " ".join(libmathcat.GetSpokenText().split()) + libmathcat.SetPreference("TTS", savedTTS) + libmathcat.SetMathML(savedMathML) + else: + textToCopy = self._wrapMathMLForClipBoard(mathml) + + self._copyToClipAsMathML(textToCopy, copyAs == "mathml") + # Translators: copy to clipboard + ui.message(_("copy as ") + copyAs) + except Exception as e: + log.exception(e) + # Translators: this message directs users to look in the log file + speech.speakMessage(_("unable to copy math: see NVDA error log for details")) + + # not a perfect match sequence, but should capture normal MathML + # not a perfect match sequence, but should capture normal MathML + _mathTagHasNameSpace: re.Pattern = re.compile("") + _hasAddedId: re.Pattern = re.compile(" id='[^'].+' data-id-added='true'") + _hasDataAttr: re.Pattern = re.compile(" data-[^=]+='[^']*'") + + def _wrapMathMLForClipBoard(self, text: str) -> str: + """Cleanup the MathML a little.""" + text = re.sub(self._hasAddedId, "", text) + mathMLWithNS: str = re.sub(self._hasDataAttr, "", text) + if not re.match(self._mathTagHasNameSpace, mathMLWithNS): + mathMLWithNS = mathMLWithNS.replace( + "math", + "math xmlns='http://www.w3.org/1998/Math/MathML'", + 1, + ) + return mathMLWithNS + + def _copyToClipAsMathML( + self, + text: str, + isMathML: bool, + notify: bool | None = False, + ) -> bool: + """Copies the given text to the windows clipboard. + + :param text: the text which will be copied to the clipboard. + :param notify: whether to emit a confirmation message. + :returns: True if it succeeds, False otherwise. + """ + # copied from api.py and modified to use CF_MathML_Presentation + if not isinstance(text, str) or len(text) == 0: + return False + + try: + with winUser.openClipboard(gui.mainFrame.Handle): + winUser.emptyClipboard() + if isMathML: + self._setClipboardData(self.CF_MathML, '' + text) + self._setClipboardData(self.CF_MathML_Presentation, '' + text) + self._setClipboardData(winUser.CF_UNICODETEXT, text) + got: str = getClipData() + except OSError: + if notify: + ui.reportTextCopiedToClipboard() # No argument reports a failure. + return False + if got == text: + if notify: + ui.reportTextCopiedToClipboard(text) + return True + if notify: + ui.reportTextCopiedToClipboard() # No argument reports a failure. + return False + + def _setClipboardData(self, format: int, data: str) -> None: + """Sets the clipboard data to the given data with the specified format. + + :param format: The format for the clipboard data. + This is an integer format code returned by windll.user32.RegisterClipboardFormatW. + :param data: The data to set on the clipboard. + """ + # Need to support MathML Presentation, so this copied from winUser.py and the first two lines are commented out + # For now only unicode is a supported format + # if format!=CF_UNICODETEXT: + # raise ValueError("Unsupported format") + text: str = data + bufLen: int = len(text.encode(WCHAR_ENCODING, errors="surrogatepass")) + 2 + # Allocate global memory + h: winKernel.HGLOBAL = winKernel.HGLOBAL.alloc(winKernel.GMEM_MOVEABLE, bufLen) + # Acquire a lock to the global memory receiving a local memory address + with h.lock() as addr: + # Write the text into the allocated memory + buf: Array[c_wchar] = (c_wchar * bufLen).from_address(addr) + buf.value = text + # Set the clipboard data with the global memory + if not windll.user32.SetClipboardData(format, h): + raise WinError() + # NULL the global memory handle so that it is not freed at the end of scope as the clipboard now has it. + h.forget() -from speech.types import SpeechSequence -from speech.commands import ( - IndexCommand, - CharacterModeCommand, - LangChangeCommand, - BreakCommand, - PitchCommand, - RateCommand, - VolumeCommand, - PhonemeCommand, -) -def patched_speak(self, speechSequence: SpeechSequence): # noqa: C901 - from synthDrivers import _espeak - # log.info(f"patched_speak input: {speechSequence}") - textList: List[str] = [] - langChanged = False - prosody: Dict[str, int] = {} - # We output malformed XML, as we might close an outer tag after opening an inner one; e.g. - # . - # However, eSpeak doesn't seem to mind. - for item in speechSequence: - if isinstance(item,str): - textList.append(self._processText(item)) - elif isinstance(item, IndexCommand): - textList.append(""%item.index) - elif isinstance(item, CharacterModeCommand): - textList.append("" if item.state else "") - elif isinstance(item, LangChangeCommand): - langChangeXML = self._handleLangChangeCommand(item, langChanged) - textList.append(langChangeXML) - langChanged = True - elif isinstance(item, BreakCommand): - textList.append(f'') - elif type(item) in self.PROSODY_ATTRS: - if prosody: - # Close previous prosody tag. - textList.append('') # hack added for cutoff speech (github.com/NSoiffer/MathCATForPython/issues/55) - textList.append("") - attr=self.PROSODY_ATTRS[type(item)] - if item.multiplier==1: - # Returning to normal. - try: - del prosody[attr] - except KeyError: - pass - else: - prosody[attr]=int(item.multiplier* 100) - if not prosody: - continue - textList.append("") - elif isinstance(item, PhonemeCommand): - # We can't use str.translate because we want to reject unknown characters. - try: - phonemes="".join([self.IPA_TO_ESPEAK[char] for char in item.ipa]) - # There needs to be a space after the phoneme command. - # Otherwise, eSpeak will announce a subsequent SSML tag instead of processing it. - textList.append(u"[[%s]] "%phonemes) - except KeyError: - log.debugWarning("Unknown character in IPA string: %s"%item.ipa) - if item.text: - textList.append(self._processText(item.text)) - else: - log.error("Unknown speech: %s"%item) - # Close any open tags. - if langChanged: - textList.append("") - if prosody: - textList.append("") - text=u"".join(textList) - # log.info(f"\ntext={text}") - # Added saving old rate and then resetting to that -- work around for https://github.com/nvaccess/nvda/issues/15221 - # I'm not clear why this works since _set_rate() is called before the speech is finished speaking - synth = getSynth() - oldRate = synth._get_rate() - _espeak.speak(text) - synth._set_rate(oldRate) +class MathCAT(mathPres.MathPresentationProvider): + def __init__(self): + """Initializes MathCAT, loading the rules specified in the rules directory.""" + # super(MathCAT, self).__init__(*args, **kwargs) + + try: + # IMPORTANT -- SetRulesDir must be the first call to libmathcat besides GetVersion() + rulesDir: str = path.join(path.dirname(path.abspath(__file__)), "Rules") + log.info(f"MathCAT {libmathcat.GetVersion()} installed. Using rules dir: {rulesDir}") + libmathcat.SetRulesDir(rulesDir) + libmathcat.SetPreference("TTS", "SSML") + except Exception as e: + log.exception(e) + # Translators: this message directs users to look in the log file + speech.speakMessage(_("MathCAT initialization failed: see NVDA error log for details")) + + def getSpeechForMathMl( + self, + mathml: str, + ) -> list[str | SpeechCommand]: + """Outputs a MathML string as speech. + + :param mathml: A MathML string. + :returns: A list of speech commands and strings representing the given MathML. + """ + global _synthesizerRate + synth: SynthDriver = getSynth() + synthConfig = config.conf["speech"][synth.name] + if synth.name == "espeak": + _synthesizerRate = synthConfig["rate"] + # log.info(f'_synthesizer_rate={_synthesizer_rate}, get_rate()={getSynth()._get_rate()}') + getSynth()._set_rate(_synthesizerRate) + # log.info(f'..............get_rate()={getSynth()._get_rate()}, name={synth.name}') + try: + # need to set Language before the MathML for DecimalSeparator canonicalization + language: str = getLanguageToUse(mathml) + # MathCAT should probably be extended to accept "extlang" tagging, but it uses lang-region tagging at the moment + libmathcat.SetPreference("Language", language) + libmathcat.SetMathML(mathml) + except Exception as e: + log.exception(e) + log.exception(f"MathML is {mathml}") + # Translators: this message directs users to look in the log file + speech.speakMessage(_("Illegal MathML found: see NVDA error log for details")) + libmathcat.SetMathML("") # set it to something + try: + say_fontstyle_change: bool = config.conf["documentFormatting"]["reportHighlight"] + libmathcat.SetPreference( + "IgnoreBold", + "false" if say_fontstyle_change else "true", + ) + supportedCommands: set[Type["SynthCommand"]] = synth.supportedCommands + # Set preferences for capital letters + libmathcat.SetPreference( + "CapitalLetters_Beep", + "true" if synthConfig["beepForCapitals"] else "false", + ) + libmathcat.SetPreference( + "CapitalLetters_UseWord", + "true" if synthConfig["sayCapForCapitals"] else "false", + ) + # log.info(f"Speech text: {libmathcat.GetSpokenText()}") + if PitchCommand in supportedCommands: + libmathcat.SetPreference("CapitalLetters_Pitch", str(synthConfig["capPitchChange"])) + if self._addSounds(): + return ( + [BeepCommand(800, 25)] + + convertSSMLTextForNVDA(libmathcat.GetSpokenText()) + + [BeepCommand(600, 15)] + ) + else: + return convertSSMLTextForNVDA(libmathcat.GetSpokenText()) + + except Exception as e: + log.exception(e) + # Translators: this message directs users to look in the log file + speech.speakMessage(_("Error in speaking math: see NVDA error log for details")) + return [""] + finally: + # try to get around espeak bug where voice slows down + if _synthesizerRate and getSynth().name == "espeak": + # log.info(f'getSpeechForMathMl: reset to {_synthesizer_rate}') + getSynth()._set_rate(_synthesizerRate) + + def _addSounds(self) -> bool: + """Queries the user preferences to determine whether or not sounds should be added. + + :returns: True if MathCAT's `SpeechSound` preference is set, and False otherwise. + """ + try: + return libmathcat.GetPreference("SpeechSound") != "None" + except Exception as e: + log.exception(f"MathCAT: An exception occurred in _add_sounds: {e}") + return False + + def getBrailleForMathMl(self, mathml: str) -> str: + """Gets the braille representation of a given MathML string by calling MathCAT's GetBraille function. + + :param mathml: A MathML string. + :returns: A braille string representing the input MathML. + """ + # log.info("***MathCAT getBrailleForMathMl") + try: + libmathcat.SetMathML(mathml) + except Exception as e: + log.exception(e) + log.exception(f"MathML is {mathml}") + # Translators: this message directs users to look in the log file + speech.speakMessage(_("Illegal MathML found: see NVDA error log for details")) + libmathcat.SetMathML("") # set it to something + try: + return libmathcat.GetBraille("") + except Exception as e: + log.exception(e) + # Translators: this message directs users to look in the log file + speech.speakMessage(_("Error in brailling math: see NVDA error log for details")) + return "" + + def interactWithMathMl(self, mathml: str) -> None: + """Interact with a MathML string, creating a MathCATInteraction object. + + :param mathml: The MathML representing the math to interact with. + """ + MathCATInteraction(provider=self, mathMl=mathml).setFocus() + MathCATInteraction(provider=self, mathMl=mathml).script_navigate(None) + + +CACHED_SYNTH: SynthDriver | None = None + + +def _monkeyPatchESpeak() -> None: + """Patches an eSpeak bug where the voice slows down.""" + global CACHED_SYNTH + currentSynth: SynthDriver = getSynth() + if currentSynth.name != "espeak" or CACHED_SYNTH == currentSynth: + return # already patched + + CACHED_SYNTH = currentSynth + currentSynth.speak = patchedSpeak.__get__(currentSynth, type(currentSynth)) + + +def patchedSpeak(self, speechSequence: SpeechSequence) -> None: # noqa: C901 + # log.info(f"\npatched_speak input: {speechSequence}") + textList: list[str] = [] + langChanged = False + prosody: dict[str, int] = {} + # We output malformed XML, as we might close an outer tag after opening an inner one; e.g. + # . + # However, eSpeak doesn't seem to mind. + for item in speechSequence: + if isinstance(item, str): + textList.append(self._processText(item)) + elif isinstance(item, IndexCommand): + textList.append('' % item.index) + elif isinstance(item, CharacterModeCommand): + textList.append('' if item.state else "") + elif isinstance(item, LangChangeCommand): + langChangeXML = self._handleLangChangeCommand(item, langChanged) + textList.append(langChangeXML) + langChanged = True + elif isinstance(item, BreakCommand): + textList.append(f'') + elif isinstance(item, RateCommand): + if item.multiplier == 1: + textList.append("") + else: + textList.append(f"") + elif type(item) in self.PROSODY_ATTRS: + if prosody: + # Close previous prosody tag. + textList.append('') # hack added for cutoff speech (issue #55) + textList.append("") + attr = self.PROSODY_ATTRS[type(item)] + if item.multiplier == 1: + # Returning to normal. + try: + del prosody[attr] + except KeyError: + pass + else: + prosody[attr] = int(item.multiplier * 100) + if not prosody: + continue + textList.append("") + elif isinstance(item, PhonemeCommand): + # We can't use str.translate because we want to reject unknown characters. + try: + phonemes: str = "".join([self.IPA_TO_ESPEAK[char] for char in item.ipa]) + # There needs to be a space after the phoneme command. + # Otherwise, eSpeak will announce a subsequent SSML tag instead of processing it. + textList.append("[[%s]] " % phonemes) + except KeyError: + log.debugWarning("Unknown character in IPA string: %s" % item.ipa) + if item.text: + textList.append(self._processText(item.text)) + else: + log.exception("Unknown speech: %s" % item) + # Close any open tags. + if langChanged: + textList.append("") + if prosody: + textList.append("") + text = "".join(textList) + # log.info(f"monkey-patched text={text}") + oldRate: int = getSynth()._get_rate() + _espeak.speak(text) + # try to get around espeak bug where voice slows down + getSynth()._set_rate(oldRate) diff --git a/addon/globalPlugins/MathCAT/MathCATPreferences.py b/addon/globalPlugins/MathCAT/MathCATPreferences.py index ce3204ac..0012ccb0 100644 --- a/addon/globalPlugins/MathCAT/MathCATPreferences.py +++ b/addon/globalPlugins/MathCAT/MathCATPreferences.py @@ -6,566 +6,1010 @@ from . import yaml import os import glob -import sys -import webbrowser +import webbrowser import gettext -_ = gettext.gettext - import addonHandler -addonHandler.initTranslation() +from logHandler import log # logging +from collections.abc import Callable +from .MathCAT import convertSSMLTextForNVDA +from speech import speak +from zipfile import ZipFile -from logHandler import log # logging -from typing import Any, Dict, Union +addonHandler.initTranslation() +_ = gettext.gettext # two constants to scale "PauseFactor" # these work out so that a slider that goes [0,14] has value ~100 at 7 and ~1000 at 14 -PAUSE_FACTOR_SCALE = 9.5 -PAUSE_FACTOR_LOG_BASE = 1.4 +PAUSE_FACTOR_SCALE: float = 9.5 +PAUSE_FACTOR_LOG_BASE: float = 1.4 # initialize the user preferences tuples -user_preferences: Dict[str, Dict[str, Union[int, str, bool]]] = {} -#Speech_Language is derived from the folder structure +userPreferences: dict[str, dict[str, int | str | bool]] = {} +# Speech_Language is derived from the folder structures +Speech_DecimalSeparator = ("Auto", ".", ",", "Custom") Speech_Impairment = ("LearningDisability", "Blindness", "LowVision") -#Speech_SpeechStyle is derived from the yaml files under the selected language +# Speech_SpeechStyle is derived from the yaml files under the selected language Speech_Verbosity = ("Terse", "Medium", "Verbose") -Speech_SubjectArea = ("General") +Speech_SubjectArea = "General" Speech_Chemistry = ("SpellOut", "Off") Navigation_NavMode = ("Enhanced", "Simple", "Character") -#Navigation_ResetNavMode is boolean -#Navigation_OverView is boolean +# Navigation_ResetNavMode is boolean +# Navigation_OverView is boolean Navigation_NavVerbosity = ("Terse", "Medium", "Verbose") -#Navigation_AutoZoomOut is boolean +# Navigation_AutoZoomOut is boolean +Navigation_CopyAs = ("MathML", "LaTeX", "ASCIIMath", "Speech") Braille_BrailleNavHighlight = ("Off", "FirstChar", "EndPoints", "All") -Braille_BrailleCode = ("Nemeth", "UEB", "Vietnam") + class UserInterface(MathCATgui.MathCATPreferencesDialog): - def __init__(self,parent): - #initialize parent class - MathCATgui.MathCATPreferencesDialog.__init__(self,parent) - - #load the logo into the dialog - full_path_to_logo = os.path.expanduser('~')+"\\AppData\\Roaming\\nvda\\addons\\mathCAT\\globalPlugins\\MathCAT\\logo.png" - if os.path.exists(full_path_to_logo): - self.m_bitmapLogo.SetBitmap(wx.Bitmap(full_path_to_logo)) - - # load in the system values followed by the user prefs (if any) - UserInterface.load_default_preferences() - UserInterface.load_user_preferences() - UserInterface.validate_user_preferences() - - if "MathCATPreferencesLastCategory" in user_preferences: - #set the categories selection to what we used on last run - self.m_listBoxPreferencesTopic.SetSelection(user_preferences["MathCATPreferencesLastCategory"]) - #show the appropriate dialogue page - self.m_simplebookPanelsCategories.SetSelection(self.m_listBoxPreferencesTopic.GetSelection()) - else: - #set the categories selection to the first item - self.m_listBoxPreferencesTopic.SetSelection(0) - user_preferences["MathCATPreferencesLastCategory"]="0" - #populate the languages - UserInterface.GetLanguages(self) - #set the ui items to match the preferences - UserInterface.set_ui_values(self) - - @staticmethod - def path_to_languages_folder(): - #the user preferences file is stored at: MathCAT\Rules\Languages - return os.path.expanduser('~')+"\\AppData\\Roaming\\nvda\\addons\\mathCAT\\globalPlugins\\MathCAT\\Rules\\Languages" - - @staticmethod - def LanguagesDict(): - languages = { - "aa": "Afar", - "ab": "Аҧсуа", - "af": "Afrikaans", - "ak": "Akana", - "an": "Aragonés", - "ar": "العربية", - "as": "অসমীয়া", - "av": "Авар", - "ay": "Aymar", - "az": "Azərbaycanca / آذربايجان", - "ba": "Башҡорт", - "be": "Беларуская", - "bg": "Български", - "bh": "भोजपुरी", - "bi": "Bislama", - "bm": "Bahamanian", - "bn": "বাংলা", - "bo": "བོད་ཡིག / Bod skad", - "bs": "Bosanski", - "ca": "Català", - "ce": "Нохчийн", - "ch": "Chamoru", - "co": "Corsu", - "cr": "Nehiyaw", - "cs": "Česky", - "cu": "словѣньскъ / slověnĭskŭ", - "cv": "Чăваш", - "cy": "Cymraeg", - "da": "Dansk", - "de": "Deutsch", - "dv": "ދިވެހިބަސް", - "dz": "རྫོང་ཁ", - "ee": "Ɛʋɛ", - "el": "Ελληνικά", - "en": "English", - "eo": "Esperanto", - "es": "Español", - "fa": "فارسی", - "fi": "Suomi", - "fj": "Na Vosa Vakaviti", - "fo": "Føroyskt", - "fr": "Français", - "fy": "Frysk", - "ga": "Gaeilge", - "gd": "Gàidhlig", - "gl": "Galego", - "gn": "Avañe'ẽ", - "gu": "ગુજરાતી", - "gv": "Gaelg", - "ha": "هَوُسَ", - "he": "עברית", - "hi": "हिन्दी", - "ho": "Hiri Motu", - "hr": "Hrvatski", - "ht": "Krèyol ayisyen", - "hu": "Magyar", - "hy": "Հայերեն", - "hz": "Otsiherero", - "ia": "Interlingua", - "id": "Bahasa Indonesia", - "ig": "Igbo", - "ii": "ꆇꉙ / 四川彝语", - "ik": "Iñupiak", - "io": "Ido", - "is": "Íslenska", - "iu": "ᐃᓄᒃᑎᑐᑦ", - "ja": "日本語", - "jv": "Basa Jawa", - "ka": "ქართული", - "kg": "KiKongo", - "ki": "Gĩkũyũ", - "kj": "Kuanyama", - "kk": "Қазақша", - "km": "ភាសាខ្មែរ", - "kn": "ಕನ್ನಡ", - "ko": "한국어", - "ks": "कॉशुर / کٲش", - "ku": "Kurdî", - "kv": "Коми", - "kw": "Kernewek", - "ky": "Kırgızca / Кыргызча", - "la": "Latina", - "lb": "Lëtzebuergesch", - "lg": "Luganda", - "li": "Limburgs", - "ln": "Lingála", - "lo": "ລາວ / Pha xa lao", - "lt": "Lietuvių", - "lv": "Latviešu", - "mg": "Malagasy", - "mh": "Kajin Majel / Ebon", - "mk": "Македонски", - "ml": "മലയാളം", - "mn": "Монгол", - "mo": "Moldovenească", - "ms": "Bahasa Melayu", - "mt": "bil-Malti", - "my": "Myanmasa", - "na": "Dorerin Naoero", - "ne": "नेपाली", - "ng": "Oshiwambo", - "nl": "Nederlands", - "nn": "Norsk (nynorsk)", - "nr": "isiNdebele", - "nv": "Diné bizaad", - "ny": "Chi-Chewa", - "oc": "Occitan", - "oj": "ᐊᓂᔑᓈᐯᒧᐎᓐ / Anishinaabemowin", - "om": "Oromoo", - "os": "Иронау", - "pa": "ਪੰਜਾਬੀ / پنجابی", - "pi": "Pāli / पाऴि", - "pl": "Polski", - "ps": "پښتو", - "pt": "Português", - "qu": "Runa Simi", - "rm": "Rumantsch", - "ro": "Română", - "ru": "Русский", - "rw": "Kinyarwandi", - "sa": "संस्कृतम्", - "sc": "Sardu", - "sd": "सिंधी / سنڌي", - "se": "Davvisámegiella", - "sg": "Sängö", - "sh": "Srpskohrvatski / Српскохрватски", - "si": "සිංහල", - "sk": "Slovenčina", - "sl": "Slovenščina", - "sm": "Gagana Samoa", - "sn": "chiShona", - "so": "Soomaaliga", - "sq": "Shqip", - "sr": "Српски", - "ss": "SiSwati", - "st": "Sesotho", - "su": "Basa Sunda", - "sv": "Svenska", - "sw": "Kiswahili", - "ta": "தமிழ்", - "tg": "Тоҷикӣ", - "th": "ไทย / Phasa Thai", - "ti": "ትግርኛ", - "tk": "Туркмен / تركمن", - "tl": "Tagalog", - "to": "Lea Faka-Tonga", - "tr": "Türkçe", - "ts": "Xitsonga", - "tt": "Tatarça", - "tw": "Twi", - "ty": "Reo Mā`ohi", - "ug": "Uyƣurqə / ئۇيغۇرچە", - "uk": "Українська", - "ur": "اردو", - "uz": "Ўзбек", - "ve": "Tshivenḓa", - "vi": "Tiếng Việt", - "vo": "Volapük", - "wa": "Walon", - "wo": "Wollof", - "xh": "isiXhosa", - "yi": "ייִדיש", - "yo": "Yorùbá", - "za": "Cuengh / Tôô / 壮语", - "zh": "中文", - "zu": "isiZulu" - } - return languages - - def GetLanguages(self): - # initialise the language list - languages_dict = UserInterface.LanguagesDict() - #clear the language names in the dialog - self.m_choiceLanguage.Clear() - self.m_choiceLanguage.Append(_("Use Voice's Language") + " (Auto)") - #populate the available language names in the dialog - for f in os.listdir(UserInterface.path_to_languages_folder()): - if os.path.isdir(UserInterface.path_to_languages_folder()+"\\"+f): - if languages_dict.get(f, 'missing') == 'missing': - self.m_choiceLanguage.Append(f + " (" + f + ")") - else: - self.m_choiceLanguage.Append(languages_dict[f] + " (" + f + ")") - - def GetLanguageCode(self): - lang_selection = self.m_choiceLanguage.GetStringSelection() - lang_code = lang_selection[lang_selection.find("(")+1 : lang_selection.find(")")] - return lang_code - - def GetSpeechStyles(self, this_SpeechStyle: str): - from speech import getCurrentLanguage - #clear the SpeechStyle choices - self.m_choiceSpeechStyle.Clear() - #get the currently selected language code - this_language_code = UserInterface.GetLanguageCode(self) - log.info(f"\nthis lang={this_language_code}, getCurrentLanguage = {getCurrentLanguage()}") - - if this_language_code == 'Auto': - # list the speech styles for the current voice rather than have none listed - this_language_code = getCurrentLanguage().lower().replace("_", "-") - # FIX: when dialog is aware of regional dialects, remove this next line that removes the dialect part - this_language_code = this_language_code.split('-')[0] # grab the first part - - this_path = os.path.expanduser('~')+"\\AppData\\Roaming\\nvda\\addons\\MathCAT\\globalPlugins\\MathCAT\\Rules\\Languages\\"+this_language_code+"\\*_Rules.yaml" - #populate the m_choiceSpeechStyle choices - for f in glob.glob(this_path): - fname = os.path.basename(f) - self.m_choiceSpeechStyle.Append((fname[:fname.find("_Rules.yaml")])) - try: - #set the SpeechStyle to the same as previous - self.m_choiceSpeechStyle.SetStringSelection(this_SpeechStyle) - except: - #that didn't work, choose the first in the list - self.m_choiceSpeechStyle.SetSelection(0) - - def set_ui_values(self): - #set the UI elements to the ones read from the preference file(s) - try: - self.m_choiceImpairment.SetSelection(Speech_Impairment.index(user_preferences["Speech"]["Impairment"])) - try: - lang_pref = user_preferences["Speech"]["Language"] - i = 0 - while "(" + lang_pref + ")" not in self.m_choiceLanguage.GetString(i): - i = i + 1 - if i == self.m_choiceLanguage.GetCount(): - break - if "(" + lang_pref + ")" in self.m_choiceLanguage.GetString(i): - self.m_choiceLanguage.SetSelection(i) - else: - self.m_choiceLanguage.SetSelection(0) - except: - #the language in the settings file is not in the folder structure, something went wrong, set to the first in the list - self.m_choiceLanguage.SetSelection(0) - try: - #now get the available SpeechStyles from the folder structure and set to the preference setting is possible - self.GetSpeechStyles(user_preferences["Speech"]["SpeechStyle"]) - except: - self.m_choiceSpeechStyle.Append("Error when setting SpeechStyle for " + self.m_choiceLanguage.GetStringSelection()) - #set the rest of the UI elements - self.m_choiceSpeechAmount.SetSelection(Speech_Verbosity.index(user_preferences["Speech"]["Verbosity"])) - self.m_sliderRelativeSpeed.SetValue(user_preferences["Speech"]["MathRate"]) - pause_factor = 0 if user_preferences["Speech"]["PauseFactor"]<=1 else round(math.log(user_preferences["Speech"]["PauseFactor"]/PAUSE_FACTOR_SCALE, PAUSE_FACTOR_LOG_BASE)) - self.m_sliderPauseFactor.SetValue(pause_factor) - self.m_checkBoxSpeechSound.SetValue(user_preferences["Speech"]["SpeechSound"] == "Beep") - self.m_choiceSpeechForChemical.SetSelection(Speech_Chemistry.index(user_preferences["Speech"]["Chemistry"])) - self.m_choiceNavigationMode.SetSelection(Navigation_NavMode.index(user_preferences["Navigation"]["NavMode"])) - self.m_checkBoxResetNavigationMode.SetValue(user_preferences["Navigation"]["ResetNavMode"]) - self.m_choiceSpeechAmountNavigation.SetSelection(Navigation_NavVerbosity.index(user_preferences["Navigation"]["NavVerbosity"])) - if user_preferences["Navigation"]["Overview"]: - self.m_choiceNavigationSpeech.SetSelection(1) - else: - self.m_choiceNavigationSpeech.SetSelection(0) - self.m_checkBoxResetNavigationSpeech.SetValue(user_preferences["Navigation"]["ResetOverview"]) - self.m_checkBoxAutomaticZoom.SetValue(user_preferences["Navigation"]["AutoZoomOut"]) - self.m_choiceBrailleHighlights.SetSelection(Braille_BrailleNavHighlight.index(user_preferences["Braille"]["BrailleNavHighlight"])) - self.m_choiceBrailleMathCode.SetSelection(Braille_BrailleCode.index(user_preferences["Braille"]["BrailleCode"])) - except KeyError as err: - print('Key not found') - - def get_ui_values(self): - global user_preferences - # read the values from the UI and update the user preferences dictionary - user_preferences["Speech"]["Impairment"] = Speech_Impairment[self.m_choiceImpairment.GetSelection()] - user_preferences["Speech"]["Language"] = self.GetLanguageCode() - user_preferences["Speech"]["SpeechStyle"] = self.m_choiceSpeechStyle.GetStringSelection() - user_preferences["Speech"]["Verbosity"] = Speech_Verbosity[self.m_choiceSpeechAmount.GetSelection()] - user_preferences["Speech"]["MathRate"] = self.m_sliderRelativeSpeed.GetValue() - pf_slider = self.m_sliderPauseFactor.GetValue() - pause_factor = 0 if pf_slider==0 else round(PAUSE_FACTOR_SCALE *math.pow(PAUSE_FACTOR_LOG_BASE, pf_slider)) # avoid log(0) - user_preferences["Speech"]["PauseFactor"] = pause_factor - if self.m_checkBoxSpeechSound.GetValue(): - user_preferences["Speech"]["SpeechSound"] = "Beep" - else: - user_preferences["Speech"]["SpeechSound"] = "None" - user_preferences["Speech"]["Chemistry"] = Speech_Chemistry[self.m_choiceSpeechForChemical.GetSelection()] - user_preferences["Navigation"]["NavMode"] = Navigation_NavMode[self.m_choiceNavigationMode.GetSelection()] - user_preferences["Navigation"]["ResetNavMode"] = self.m_checkBoxResetNavigationMode.GetValue() - user_preferences["Navigation"]["NavVerbosity"] = Navigation_NavVerbosity[self.m_choiceSpeechAmountNavigation.GetSelection()] - user_preferences["Navigation"]["Overview"] = self.m_choiceNavigationSpeech.GetSelection() != 0 - user_preferences["Navigation"]["ResetOverview"] = self.m_checkBoxResetNavigationSpeech.GetValue() - user_preferences["Navigation"]["AutoZoomOut"] = self.m_checkBoxAutomaticZoom.GetValue() - user_preferences["Braille"]["BrailleNavHighlight"] = Braille_BrailleNavHighlight[self.m_choiceBrailleHighlights.GetSelection()] - user_preferences["Braille"]["BrailleCode"] = Braille_BrailleCode[self.m_choiceBrailleMathCode.GetSelection()] - user_preferences["MathCATPreferencesLastCategory"] = self.m_listBoxPreferencesTopic.GetSelection() - - @staticmethod - def path_to_default_preferences(): - #the default preferences file is: C:\Users\AppData\Roaming\\nvda\\addons\\MathCAT\\globalPlugins\\MathCAT\\Rules\\prefs.yaml - return os.path.expanduser('~')+"\\AppData\\Roaming\\nvda\\addons\\MathCAT\\globalPlugins\\MathCAT\\Rules\\prefs.yaml" - - @staticmethod - def path_to_user_preferences_folder(): - #the user preferences file is stored at: C:\Users\AppData\Roaming\MathCAT\prefs.yaml - return os.path.expanduser('~')+"\\AppData\\Roaming\\MathCAT" - - @staticmethod - def path_to_user_preferences(): - #the user preferences file is stored at: C:\Users\AppData\Roaming\MathCAT\prefs.yaml - return UserInterface.path_to_user_preferences_folder() + "\\prefs.yaml" - - @staticmethod - def load_default_preferences(): - global user_preferences - #load default preferences into the user preferences data structure (overwrites existing) - if os.path.exists(UserInterface.path_to_default_preferences()): - with open(UserInterface.path_to_default_preferences(), encoding='utf-8') as f: - user_preferences = yaml.load(f, Loader=yaml.FullLoader) - - @staticmethod - def load_user_preferences(): - global user_preferences - #merge user file values into the user preferences data structure - if os.path.exists(UserInterface.path_to_user_preferences()): - with open(UserInterface.path_to_user_preferences(), encoding='utf-8') as f: - # merge with the default preferences, overwriting with the user's values - user_preferences.update(yaml.load(f, Loader=yaml.FullLoader)) - - @staticmethod - def validate(key1: str, key2: str, valid_values: list, default_value: Union[str, int, bool]): - global user_preferences - try: - if valid_values == None: - #any value is valid - if user_preferences[key1][key2] != "": - return - if (type(valid_values[0]) == int) and (type(valid_values[1]) == int): - #any value between lower and upper bounds is valid - if (user_preferences[key1][key2] >= valid_values[0]) and (user_preferences[key1][key2] <= valid_values[1]): - return - else: - #any value in the list is valid - if user_preferences[key1][key2] in valid_values: - return - except: - #the preferences entry does not exist - pass - if not key1 in user_preferences: - user_preferences[key1] = {key2: default_value} - else: - user_preferences[key1][key2] = default_value - - @staticmethod - def validate_user_preferences(): - #check each user preference value to ensure it is present and valid, set default value if not - # Speech: - #Impairment: Blindness # LearningDisability, LowVision, Blindness - UserInterface.validate("Speech", "Impairment", ["LearningDisability", "LowVision", "Blindness"], "Blindness") - # Language: en # any known language code and sub-code -- could be en-uk, etc - UserInterface.validate("Speech", "Language", None, "en") - # Verbosity: Medium # Terse, Medium, Verbose - UserInterface.validate("Speech", "Verbosity", ["Terse", "Medium", "Verbose"], "Medium") - # MathRate: 100 # Change from text speech rate (%) - UserInterface.validate("Speech", "MathRate", [0,200], 100) - # PauseFactor: 100 # TBC - UserInterface.validate("Speech", "PauseFactor", [0,1000], 100) - # SpeechSound: None # make a sound when starting/ending math speech -- None, Beep - UserInterface.validate("Speech", "SpeechSound", ["None", "Beep"], "None") - # SpeechStyle: ClearSpeak # Any known speech style (falls back to ClearSpeak) - UserInterface.validate("Speech", "SpeechStyle", None, "ClearSpeak") - # SubjectArea: General # FIX: still working on this - UserInterface.validate("Speech", "SubjectArea", None, "General") - # Chemistry: SpellOut # SpellOut (H 2 0), AsCompound (Water), Off (H sub 2 O) - UserInterface.validate("Speech", "Chemistry", ["SpellOut", "Off"], "SpellOut") - #Navigation: - # NavMode: Enhanced # Enhanced, Simple, Character - UserInterface.validate("Navigation", "NavMode", ["Enhanced", "Simple", "Character"], "Enhanced") - # ResetNavMode: false # remember previous value and use it - UserInterface.validate("Navigation", "ResetNavMode", [False, True], False) - # Overview: false # speak the expression or give a description/overview - UserInterface.validate("Navigation", "Overview", [False, True] ,False) - # ResetOverview: true # remember previous value and use it - UserInterface.validate("Navigation", "ResetOverview", [False, True], True) - # NavVerbosity: Medium # Terse, Medium, Full (words to say for nav command) - UserInterface.validate("Navigation", "NavVerbosity", ["Terse", "Medium", "Full"], "Medium") - # AutoZoomOut: true # Auto zoom out of 2D exprs (use shift-arrow to force zoom out if unchecked) - UserInterface.validate("Navigation", "AutoZoomOut", [False, True], True) - #Braille: - # BrailleNavHighlight: EndPoints # Highlight with dots 7 & 8 the current nav node -- values are Off, FirstChar, EndPoints, All - UserInterface.validate("Braille", "BrailleNavHighlight", ["Off", "FirstChar", "EndPoints", "All"], "EndPoints") - # BrailleCode: "Nemeth" # Any supported braille code (currently Nemeth, UEB, Vietnam) - UserInterface.validate("Braille", "BrailleCode", ["Nemeth", "UEB", "Vietnam"], "Nemeth") - - @staticmethod - def write_user_preferences(): - # Language is special because it is set elsewhere by SetPreference which overrides the user_prefs -- so set it here - from . import libmathcat - libmathcat.SetPreference("Language", user_preferences["Speech"]["Language"]) - if not os.path.exists(UserInterface.path_to_user_preferences_folder()): - #create a folder for the user preferences - os.mkdir(UserInterface.path_to_user_preferences_folder()) - with open(UserInterface.path_to_user_preferences(), 'w', encoding="utf-8") as f: - #write values to the user preferences file, NOT the default - yaml.dump(user_preferences, stream=f, allow_unicode=True) - - def OnRelativeSpeedChanged( self, event ): - from .MathCAT import ConvertSSMLTextForNVDA - from speech import speak - rate = self.m_sliderRelativeSpeed.GetValue() - text = _("the square root of x squared plus y squared").replace("XXX", str(rate), 1) - speak( ConvertSSMLTextForNVDA(text) ) - - def OnPauseFactorChanged( self, event ): - from .MathCAT import ConvertSSMLTextForNVDA - from speech import speak - rate = self.m_sliderRelativeSpeed.GetValue() - pf_slider = self.m_sliderPauseFactor.GetValue() - pause_factor = 0 if pf_slider==0 else round(PAUSE_FACTOR_SCALE *math.pow(PAUSE_FACTOR_LOG_BASE, pf_slider)) - text = _("the fraction with numerator x to the n -th power plus 1 and denominator x to the n -th power minus 1 end fraction ").format( - rate=rate, - pause_factor_128=128*pause_factor//100, - pause_factor_150=150*pause_factor//100, - pause_factor_300=300*pause_factor//100, - pause_factor_600=600*pause_factor//100) - speak( ConvertSSMLTextForNVDA(text) ) - - def OnClickOK(self,event): - UserInterface.get_ui_values(self) - UserInterface.write_user_preferences() - self.Destroy() - - def OnClickCancel(self,event): - self.Destroy() - - def OnClickApply(self,event): - UserInterface.get_ui_values(self) - UserInterface.write_user_preferences() - - def OnClickReset(self,event): - UserInterface.load_default_preferences() - UserInterface.validate_user_preferences() - UserInterface.set_ui_values(self) - - def OnClickHelp(self,event): - webbrowser.open('https://nsoiffer.github.io/MathCAT/users.html') - - def OnListBoxCategories(self,event): - #the category changed, now show the appropriate dialogue page - self.m_simplebookPanelsCategories.SetSelection(self.m_listBoxPreferencesTopic.GetSelection()) - - def OnLanguage(self,event): - #the language changed, get the SpeechStyles for the new language - UserInterface.GetSpeechStyles(self, self.m_choiceSpeechStyle.GetSelection()) - - def MathCATPreferencesDialogOnCharHook(self,event: wx.KeyEvent): - #designed choice is that Enter is the same as clicking OK, and Escape is the same as clicking Cancel - keyCode = event.GetKeyCode() - if keyCode == wx.WXK_ESCAPE: - UserInterface.OnClickCancel(self,event) - return - if keyCode == wx.WXK_RETURN: - UserInterface.OnClickOK(self,event) - if keyCode == wx.WXK_TAB: - if event.GetModifiers() == wx.MOD_CONTROL: - #cycle the category forward - new_category = self.m_listBoxPreferencesTopic.GetSelection() + 1 - if new_category == 3: - new_category = 0 - self.m_listBoxPreferencesTopic.SetSelection(new_category) - #update the ui to show the new page - UserInterface.OnListBoxCategories(self,event) - #set the focus into the category list box - self.m_listBoxPreferencesTopic.SetFocus() - #jump out so the tab key is not processed - return - if event.GetModifiers() == wx.MOD_CONTROL|wx.MOD_SHIFT: - #cycle the category back - new_category = self.m_listBoxPreferencesTopic.GetSelection() - 1 - if new_category == -1: - new_category = 2 - self.m_listBoxPreferencesTopic.SetSelection(new_category) - #update the ui to show the new page - UserInterface.OnListBoxCategories(self,event) - #update the ui to show the new page - self.m_listBoxPreferencesTopic.SetFocus() - #jump out so the tab key is not processed - return - if (event.GetModifiers() == wx.MOD_NONE) and (MathCATgui.MathCATPreferencesDialog.FindFocus() == self.m_listBoxPreferencesTopic): - if self.m_listBoxPreferencesTopic.GetSelection() == 0: - self.m_choiceImpairment.SetFocus() - elif self.m_listBoxPreferencesTopic.GetSelection() == 1: - self.m_choiceNavigationMode.SetFocus() - elif self.m_listBoxPreferencesTopic.GetSelection() == 2: - self.m_choiceBrailleMathCode.SetFocus() - return - if (event.GetModifiers() == wx.MOD_SHIFT) and (MathCATgui.MathCATPreferencesDialog.FindFocus() == self.m_buttonOK): - if self.m_listBoxPreferencesTopic.GetSelection() == 0: - self.m_choiceSpeechForChemical.SetFocus() - elif self.m_listBoxPreferencesTopic.GetSelection() == 1: - self.m_choiceSpeechAmountNavigation.SetFocus() - elif self.m_listBoxPreferencesTopic.GetSelection() == 2: - self.m_choiceBrailleHighlights.SetFocus() - return - #continue handling keyboard event - event.Skip() - + """UI class for the MathCAT Preferences Dialog. + + Initializes and manages user preferences, including language, speech, braille, + and navigation settings. Extends MathCATgui.MathCATPreferencesDialog. + """ + + def __init__(self, parent: wx.Window | None): + """Initialize the preferences dialog. + + Sets up the UI, loads preferences, applies defaults and saved settings, + and restores the previous UI state. + + :param parent: The parent window for the dialog. + """ + # initialize parent class + MathCATgui.MathCATPreferencesDialog.__init__(self, parent) + + # load the logo into the dialog + fullPathToLogo: str = os.path.join(os.path.dirname(os.path.abspath(__file__)), "logo.png") + if os.path.exists(fullPathToLogo): + self._bitmapLogo.SetBitmap(wx.Bitmap(fullPathToLogo)) + + # load in the system values followed by the user prefs (if any) + UserInterface.loadDefaultPreferences() + UserInterface.loadUserPreferences() + + # hack for "CopyAs" because its location in the prefs is not yet fixed + if "CopyAs" not in userPreferences["Navigation"]: + userPreferences["Navigation"]["CopyAs"] = ( + userPreferences["Other"]["CopyAs"] if "CopyAs" in userPreferences["Other"] else "MathML" + ) + UserInterface.validateUserPreferences() + + if "NVDAAddOn" in userPreferences: + # set the categories selection to what we used on last run + self._listBoxPreferencesTopic.SetSelection(userPreferences["NVDAAddOn"]["LastCategory"]) + # show the appropriate dialogue page + self._simplebookPanelsCategories.SetSelection(self._listBoxPreferencesTopic.GetSelection()) + else: + # set the categories selection to the first item + self._listBoxPreferencesTopic.SetSelection(0) + userPreferences["NVDAAddOn"] = {"LastCategory": "0"} + # populate the languages and braille codes + UserInterface.getLanguages(self) + UserInterface.getBrailleCodes(self) + # set the ui items to match the preferences + UserInterface.setUIValues(self) + + @staticmethod + def pathToLanguagesFolder() -> str: + r"""Returns the full path to the Languages rules folder. + + The language rules are stored in: + MathCAT\Rules\Languages, relative to the location of this file. + + :return: Absolute path to the Languages folder as a string. + """ + return os.path.join(os.path.dirname(os.path.abspath(__file__)), "Rules", "Languages") + + @staticmethod + def pathToBrailleFolder() -> str: + r"""Returns the full path to the Braille rules folder. + + The Braille rules are stored in: + MathCAT\Rules\Braille, relative to the location of this file. + + :return: Absolute path to the Braille folder as a string. + """ + return os.path.join(os.path.dirname(os.path.abspath(__file__)), "Rules", "Braille") + + @staticmethod + def languagesDict() -> dict[str, str]: + """Returns a dictionary mapping language codes to their corresponding language names. + + This dictionary includes standard language codes, as well as regional variants such as + 'en-GB', 'zh-HANT', and others. + + :return: A dictionary where the key is the language code (e.g., 'en', 'fr', 'zh-HANS') + and the value is the language name (e.g. 'English', 'Français', 'Chinese, Simplified'). + """ + languages = { + "aa": "Afar", + "ab": "Аҧсуа", + "af": "Afrikaans", + "ak": "Akana", + "an": "Aragonés", + "ar": "العربية", + "as": "অসমীয়া", + "av": "Авар", + "ay": "Aymar", + "az": "Azərbaycanca / آذربايجان", + "ba": "Башҡорт", + "be": "Беларуская", + "bg": "Български", + "bh": "भोजपुरी", + "bi": "Bislama", + "bm": "Bahamanian", + "bn": "বাংলা", + "bo": "བོད་ཡིག / Bod skad", + "bs": "Bosanski", + "ca": "Català", + "ce": "Нохчийн", + "ch": "Chamoru", + "co": "Corsu", + "cr": "Nehiyaw", + "cs": "Česky", + "cu": "словѣньскъ / slověnĭskŭ", + "cv": "Чăваш", + "cy": "Cymraeg", + "da": "Dansk", + "de": "Deutsch", + "dv": "ދިވެހިބަސް", + "dz": "རྫོང་ཁ", + "ee": "Ɛʋɛ", + "el": "Ελληνικά", + "en": "English", + "en-GB": "English, United Kingdom", + "en-US": "English, United States", + "eo": "Esperanto", + "es": "Español", + "fa": "فارسی", + "fi": "Suomi", + "fj": "Na Vosa Vakaviti", + "fo": "Føroyskt", + "fr": "Français", + "fy": "Frysk", + "ga": "Gaeilge", + "gd": "Gàidhlig", + "gl": "Galego", + "gn": "Avañe'ẽ", + "gu": "ગુજરાતી", + "gv": "Gaelg", + "ha": "هَوُسَ", + "he": "עברית", + "hi": "हिन्दी", + "ho": "Hiri Motu", + "hr": "Hrvatski", + "ht": "Krèyol ayisyen", + "hu": "Magyar", + "hy": "Հայերեն", + "hz": "Otsiherero", + "ia": "Interlingua", + "id": "Bahasa Indonesia", + "ig": "Igbo", + "ii": "ꆇꉙ / 四川彝语", + "ik": "Iñupiak", + "io": "Ido", + "is": "Íslenska", + "it": "Italiano", + "iu": "ᐃᓄᒃᑎᑐᑦ", + "ja": "日本語", + "jv": "Basa Jawa", + "ka": "ქართული", + "kg": "KiKongo", + "ki": "Gĩkũyũ", + "kj": "Kuanyama", + "kk": "Қазақша", + "km": "ភាសាខ្មែរ", + "kn": "ಕನ್ನಡ", + "ko": "한국어", + "ks": "कॉशुर / کٲش", + "ku": "Kurdî", + "kv": "Коми", + "kw": "Kernewek", + "ky": "Kırgızca / Кыргызча", + "la": "Latina", + "lb": "Lëtzebuergesch", + "lg": "Luganda", + "li": "Limburgs", + "ln": "Lingála", + "lo": "ລາວ / Pha xa lao", + "lt": "Lietuvių", + "lv": "Latviešu", + "mg": "Malagasy", + "mh": "Kajin Majel / Ebon", + "mk": "Македонски", + "ml": "മലയാളം", + "mn": "Монгол", + "mo": "Moldovenească", + "ms": "Bahasa Melayu", + "mt": "bil-Malti", + "my": "Myanmasa", + "na": "Dorerin Naoero", + "nb": "Norsk, bokmål", + "ne": "नेपाली", + "ng": "Oshiwambo", + "nl": "Nederlands", + "nn": "Norsk, nynorsk", + "nr": "isiNdebele", + "nv": "Diné bizaad", + "ny": "Chi-Chewa", + "oc": "Occitan", + "oj": "ᐊᓂᔑᓈᐯᒧᐎᓐ / Anishinaabemowin", + "om": "Oromoo", + "os": "Иронау", + "pa": "ਪੰਜਾਬੀ / پنجابی", + "pi": "Pāli / पाऴि", + "pl": "Polski", + "ps": "پښتو", + "pt": "Português", + "qu": "Runa Simi", + "rm": "Rumantsch", + "ro": "Română", + "ru": "Русский", + "rw": "Kinyarwandi", + "sa": "संस्कृतम्", + "sc": "Sardu", + "sd": "सिंधी / سنڌي", + "se": "Davvisámegiella", + "sg": "Sängö", + "sh": "Srpskohrvatski / Српскохрватски", + "si": "සිංහල", + "sk": "Slovenčina", + "sl": "Slovenščina", + "sm": "Gagana Samoa", + "sn": "chiShona", + "so": "Soomaaliga", + "sq": "Shqip", + "sr": "Српски", + "ss": "SiSwati", + "st": "Sesotho", + "su": "Basa Sunda", + "sv": "Svenska", + "sw": "Kiswahili", + "ta": "தமிழ்", + "tg": "Тоҷикӣ", + "th": "ไทย / Phasa Thai", + "ti": "ትግርኛ", + "tk": "Туркмен / تركمن", + "tl": "Tagalog", + "to": "Lea Faka-Tonga", + "tr": "Türkçe", + "ts": "Xitsonga", + "tt": "Tatarça", + "tw": "Twi", + "ty": "Reo Mā`ohi", + "ug": "Uyƣurqə / ئۇيغۇرچە", + "uk": "Українська", + "ur": "اردو", + "uz": "Ўзбек", + "ve": "Tshivenḓa", + "vi": "Tiếng Việt", + "vo": "Volapük", + "wa": "Walon", + "wo": "Wollof", + "xh": "isiXhosa", + "yi": "ייִדיש", + "yo": "Yorùbá", + "za": "Cuengh / Tôô / 壮语", + "zh": "中文", + "zh-HANS": "Chinese, Simplified", + "zh-HANT": "Chinese, Traditional", + "zh-TW": "Chinese, Traditional, Taiwan", + "zu": "isiZulu", + } + return languages + + def getRulesFiles( + self, + pathToDir: str, + processSubDirs: Callable[[str, str], list[str]] | None, + ) -> list[str]: + """ + Get the rule files from a directory, optionally processing subdirectories. + + Searches for files ending with '_Rules.yaml' in the specified directory. + If no rule files are found, attempts to find them inside a corresponding ZIP archive, + including checking any subdirectories inside the ZIP. + + :param pathToDir: Path to the directory to search for rule files. + :param processSubDirs: Optional callable to process subdirectories. It should take the subdirectory name + and the language code as arguments, returning a list of rule filenames found in that subdirectory. + :return: A list of rule file names found either directly in the directory or inside the ZIP archive. + """ + language: str = os.path.basename(pathToDir) + ruleFiles: list[str] = [ + os.path.basename(file) for file in glob.glob(os.path.join(pathToDir, "*_Rules.yaml")) + ] + for dir in os.listdir(pathToDir): + if os.path.isdir(os.path.join(pathToDir, dir)): + if processSubDirs: + ruleFiles.extend(processSubDirs(dir, language)) + + if len(ruleFiles) == 0: + # look in the .zip file for the style files, including regional subdirs -- it might not have been unzipped + try: + zip_file: ZipFile = ZipFile(f"{pathToDir}\\{language}.zip", "r") + for file in zip_file.namelist(): + if file.endswith("_Rules.yaml"): + ruleFiles.append(file) + elif zip_file.getinfo(file).is_dir() and processSubDirs: + ruleFiles.extend(processSubDirs(dir, language)) + except Exception as e: + log.debugWarning(f"MathCAT Dialog: didn't find zip file {zip_file}. Error: {e}") + + return ruleFiles + + def getLanguages(self) -> None: + """Populate the language choice dropdown with available languages and their regional variants. + + This method scans the language folders and adds entries for each language and its + regional dialects. Language folders use ISO 639-1 codes and regional variants use ISO 3166-1 alpha-2 codes. + + It also adds a special "Use Voice's Language (Auto)" option at the top. + """ + + def addRegionalLanguages(subDir: str, language: str) -> list[str]: + # the language variants are in folders named using ISO 3166-1 alpha-2 + # codes https://en.wikipedia.org/wiki/ISO_3166-2 + # check if there are language variants in the language folder + if subDir != "SharedRules": + languagesDict: dict[str, str] = UserInterface.languagesDict() + # add to the listbox the text for this language variant together with the code + regionalCode: str = language + "-" + subDir.upper() + if languagesDict.get(regionalCode, "missing") != "missing": + self._choiceLanguage.Append(f"{languagesDict[regionalCode]} ({language}-{subDir})") + elif languagesDict.get(language, "missing") != "missing": + self._choiceLanguage.Append(f"{languagesDict[language]} ({regionalCode})") + else: + self._choiceLanguage.Append(f"{language} ({regionalCode})") + return [os.path.basename(file) for file in glob.glob(os.path.join(subDir, "*_Rules.yaml"))] + return [] + + # initialise the language list + languagesDict: dict[str, str] = UserInterface.languagesDict() + # clear the language names in the dialog + self._choiceLanguage.Clear() + # Translators: menu item -- use the language of the voice chosen in the NVDA speech settings dialog + # "Auto" == "Automatic" -- other items in menu are "English (en)", etc., so this matches that style + self._choiceLanguage.Append(_("Use Voice's Language (Auto)")) + # populate the available language names in the dialog + # the implemented languages are in folders named using the relevant ISO 639-1 + # code https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes + languageDir: str = UserInterface.pathToLanguagesFolder() + for language in os.listdir(languageDir): + pathToLanguageDir: str = os.path.join(UserInterface.pathToLanguagesFolder(), language) + if os.path.isdir(pathToLanguageDir): + # only add this language if there is a xxx_Rules.yaml file + if len(self.getRulesFiles(pathToLanguageDir, addRegionalLanguages)) > 0: + # add to the listbox the text for this language together with the code + if languagesDict.get(language, "missing") != "missing": + self._choiceLanguage.Append(languagesDict[language] + " (" + language + ")") + else: + self._choiceLanguage.Append(language + " (" + language + ")") + + def getLanguageCode(self) -> str: + """Extract the language code from the selected language string in the UI. + + The selected language string is expected to contain the language code in parentheses, + for example: "English (en)". + + :return: The language code extracted from the selection. + """ + langSelection: str = self._choiceLanguage.GetStringSelection() + langCode: str = langSelection[langSelection.find("(") + 1 : langSelection.find(")")] + return langCode + + def getSpeechStyles(self, thisSpeechStyle: str) -> None: + """Get all the speech styles for the current language. + This sets the SpeechStyles dialog entry. + + :param thisSpeechStyle: The speech style to set or highlight in the dialog. + """ + from speech import getCurrentLanguage + + def getSpeechStyleFromDirectory(dir: str, lang: str) -> list[str]: + r"""Get the speech styles from any regional dialog, from the main language, dir and if there isn't from the zip file. + The 'lang', if it has a region dialect, is of the form 'en\uk' + The returned list is sorted alphabetically + + :param dir: The directory path to search for speech styles. + :param lang: Language code which may include a regional dialect (e.g., 'en\uk'). + :return: A list of speech styles sorted alphabetically. + """ + # start with the regional dialect, then add on any (unique) styles in the main dir + mainLang: str = lang.split("\\")[0] # does the right thing even if there is no regional directory + allStyleFiles: list[str] = [] + if lang.find("\\") >= 0: + allStyleFiles: list[str] = [ + os.path.basename(name) for name in glob.glob(dir + lang + "\\*_Rules.yaml") + ] + allStyleFiles.extend( + [os.path.basename(name) for name in glob.glob(dir + mainLang + "\\*_Rules.yaml")], + ) + allStyleFiles = list(set(allStyleFiles)) # make them unique + if len(allStyleFiles) == 0: + # look in the .zip file for the style files -- this will have regional variants, but also have that dir + try: + zipFilePath: str = dir + mainLang + "\\" + mainLang + ".zip" + zipFile: ZipFile = ZipFile(zipFilePath, "r") # file might not exist + allStyleFiles = [ + name.split("/")[-1] for name in zipFile.namelist() if name.endswith("_Rules.yaml") + ] + except Exception as e: + log.debugWarning(f"MathCAT Dialog: didn't find zip file {zipFile}. Error: {e}") + allStyleFiles.sort() + return allStyleFiles + + # clear the SpeechStyle choices + self._choiceSpeechStyle.Clear() + # get the currently selected language code + languageCode: str = UserInterface.getLanguageCode(self) + + if languageCode == "Auto": + # list the speech styles for the current voice rather than have none listed + languageCode = getCurrentLanguage().lower().replace("_", "-") + languageCode = languageCode.replace("-", "\\") + + languagePath = UserInterface.pathToLanguagesFolder() + "\\" + # log.info(f"languagePath={languagePath}") + # populate the m_choiceSpeechStyle choices + allStyleFiles = [ + # remove "_Rules.yaml" from the list + name[: name.find("_Rules.yaml")] + for name in getSpeechStyleFromDirectory(languagePath, languageCode) + ] + # There isn't a LiteralSpeak rules file since it has no language-specific rules. We add it at the end. + # Translators: at the moment, do NOT translate this string as some code specifically looks for this name. + allStyleFiles.append("LiteralSpeak") + for name in allStyleFiles: + self._choiceSpeechStyle.Append((name)) + try: + # set the SpeechStyle to the same as previous + self._choiceSpeechStyle.SetStringSelection( + thisSpeechStyle if thisSpeechStyle in allStyleFiles else allStyleFiles[0], + ) + except Exception as e: + log.exception( + f"MathCAT: An exception occurred in GetSpeechStyles evaluating set SetStringSelection: {e}", + ) + # that didn't work, choose the first in the list + self._choiceSpeechStyle.SetSelection(0) + + def getBrailleCodes(self) -> None: + """Initializes and populates the braille code choice control with available braille codes. + + Scans the braille codes folder for valid directories containing rules files, and adds them + to the braille code dropdown in the dialog. + """ + # initialise the braille code list + self._choiceBrailleMathCode.Clear() + # populate the available braille codes in the dialog + # the dir names are used, not the rule file names because the dir names have to be unique + pathToBrailleFolder: str = UserInterface.pathToBrailleFolder() + for brailleCode in os.listdir(pathToBrailleFolder): + pathToBrailleCode: str = os.path.join(pathToBrailleFolder, brailleCode) + if os.path.isdir(pathToBrailleCode): + if len(self.getRulesFiles(pathToBrailleCode, None)) > 0: + self._choiceBrailleMathCode.Append(brailleCode) + + def setUIValues(self) -> None: + """Sets the UI elements based on the values read from the user preferences. + + Attempts to match preference values to UI controls; falls back to defaults if values are invalid + or missing. + """ + try: + self._choiceImpairment.SetSelection( + Speech_Impairment.index(userPreferences["Speech"]["Impairment"]), + ) + try: + langPref: str = userPreferences["Speech"]["Language"] + self._choiceLanguage.SetSelection(0) + i: int = 1 # no need to test i == 0 + while i < self._choiceLanguage.GetCount(): + if f"({langPref})" in self._choiceLanguage.GetString(i): + self._choiceLanguage.SetSelection(i) + break + i += 1 + except Exception as e: + log.exception( + f"MathCAT: An exception occurred in setUIValues ('{userPreferences['Speech']['Language']}'): {e}", + ) + # the language in the settings file is not in the folder structure, something went wrong, + # set to the first in the list + self._choiceLanguage.SetSelection(0) + try: + # now get the available SpeechStyles from the folder structure and set to the preference setting is possible + self.getSpeechStyles(str(userPreferences["Speech"]["SpeechStyle"])) + except Exception as e: + log.exception(f"MathCAT: An exception occurred in set_ui_values (getting SpeechStyle): {e}") + self._choiceSpeechStyle.Append( + "Error when setting SpeechStyle for " + self._choiceLanguage.GetStringSelection(), + ) + # set the rest of the UI elements + self._choiceDecimalSeparator.SetSelection( + Speech_DecimalSeparator.index(userPreferences["Other"]["DecimalSeparator"]), + ) + self._choiceSpeechAmount.SetSelection( + Speech_Verbosity.index(userPreferences["Speech"]["Verbosity"]), + ) + self._sliderRelativeSpeed.SetValue(userPreferences["Speech"]["MathRate"]) + pause_factor = ( + 0 + if int(userPreferences["Speech"]["PauseFactor"]) <= 1 + else round( + math.log( + int(userPreferences["Speech"]["PauseFactor"]) / PAUSE_FACTOR_SCALE, + PAUSE_FACTOR_LOG_BASE, + ), + ) + ) + self._sliderPauseFactor.SetValue(pause_factor) + self._checkBoxSpeechSound.SetValue(userPreferences["Speech"]["SpeechSound"] == "Beep") + self._choiceSpeechForChemical.SetSelection( + Speech_Chemistry.index(userPreferences["Speech"]["Chemistry"]), + ) + + self._choiceNavigationMode.SetSelection( + Navigation_NavMode.index(userPreferences["Navigation"]["NavMode"]), + ) + self._checkBoxResetNavigationMode.SetValue(userPreferences["Navigation"]["ResetNavMode"]) + self._choiceSpeechAmountNavigation.SetSelection( + Navigation_NavVerbosity.index(userPreferences["Navigation"]["NavVerbosity"]), + ) + if userPreferences["Navigation"]["Overview"]: + self._choiceNavigationSpeech.SetSelection(1) + else: + self._choiceNavigationSpeech.SetSelection(0) + self._checkBoxResetNavigationSpeech.SetValue(userPreferences["Navigation"]["ResetOverview"]) + self._checkBoxAutomaticZoom.SetValue(userPreferences["Navigation"]["AutoZoomOut"]) + self._choiceCopyAs.SetSelection( + Navigation_CopyAs.index(userPreferences["Navigation"]["CopyAs"]), + ) + + self._choiceBrailleHighlights.SetSelection( + Braille_BrailleNavHighlight.index(userPreferences["Braille"]["BrailleNavHighlight"]), + ) + try: + braillePref: str = userPreferences["Braille"]["BrailleCode"] + i = 0 + while braillePref != self._choiceBrailleMathCode.GetString(i): + i = i + 1 + if i == self._choiceBrailleMathCode.GetCount(): + break + if braillePref == self._choiceBrailleMathCode.GetString(i): + self._choiceBrailleMathCode.SetSelection(i) + else: + self._choiceBrailleMathCode.SetSelection(0) + except Exception as e: + log.exception(f"MathCAT: An exception occurred while trying to set the Braille code: {e}") + # the braille code in the settings file is not in the folder structure, something went wrong, + # set to the first in the list + self._choiceBrailleMathCode.SetSelection(0) + except KeyError as err: + print("Key not found", err) + + def getUIValues(self) -> None: + """Reads the current values from the UI controls and updates the user preferences accordingly.""" + global userPreferences + # read the values from the UI and update the user preferences dictionary + userPreferences["Speech"]["Impairment"] = Speech_Impairment[self._choiceImpairment.GetSelection()] + userPreferences["Speech"]["Language"] = self.getLanguageCode() + userPreferences["Other"]["DecimalSeparator"] = Speech_DecimalSeparator[ + self._choiceDecimalSeparator.GetSelection() + ] + userPreferences["Speech"]["SpeechStyle"] = self._choiceSpeechStyle.GetStringSelection() + userPreferences["Speech"]["Verbosity"] = Speech_Verbosity[self._choiceSpeechAmount.GetSelection()] + userPreferences["Speech"]["MathRate"] = self._sliderRelativeSpeed.GetValue() + pfSlider: int = self._sliderPauseFactor.GetValue() + pauseFactor: int = ( + 0 if pfSlider == 0 else round(PAUSE_FACTOR_SCALE * math.pow(PAUSE_FACTOR_LOG_BASE, pfSlider)) + ) # avoid log(0) + userPreferences["Speech"]["PauseFactor"] = pauseFactor + if self._checkBoxSpeechSound.GetValue(): + userPreferences["Speech"]["SpeechSound"] = "Beep" + else: + userPreferences["Speech"]["SpeechSound"] = "None" + userPreferences["Speech"]["Chemistry"] = Speech_Chemistry[ + self._choiceSpeechForChemical.GetSelection() + ] + userPreferences["Navigation"]["NavMode"] = Navigation_NavMode[ + self._choiceNavigationMode.GetSelection() + ] + userPreferences["Navigation"]["ResetNavMode"] = self._checkBoxResetNavigationMode.GetValue() + userPreferences["Navigation"]["NavVerbosity"] = Navigation_NavVerbosity[ + self._choiceSpeechAmountNavigation.GetSelection() + ] + userPreferences["Navigation"]["Overview"] = self._choiceNavigationSpeech.GetSelection() != 0 + userPreferences["Navigation"]["ResetOverview"] = self._checkBoxResetNavigationSpeech.GetValue() + userPreferences["Navigation"]["AutoZoomOut"] = self._checkBoxAutomaticZoom.GetValue() + userPreferences["Navigation"]["CopyAs"] = Navigation_CopyAs[self._choiceCopyAs.GetSelection()] + + userPreferences["Braille"]["BrailleNavHighlight"] = Braille_BrailleNavHighlight[ + self._choiceBrailleHighlights.GetSelection() + ] + userPreferences["Braille"]["BrailleCode"] = self._choiceBrailleMathCode.GetStringSelection() + if "NVDAAddOn" not in userPreferences: + userPreferences["NVDAAddOn"] = {"LastCategory": "0"} + userPreferences["NVDAAddOn"]["LastCategory"] = self._listBoxPreferencesTopic.GetSelection() + + @staticmethod + def pathToDefaultPreferences() -> str: + """Returns the full path to the default preferences file.""" + return os.path.join(os.path.dirname(os.path.abspath(__file__)), "Rules", "prefs.yaml") + + @staticmethod + def pathToUserPreferencesFolder() -> str: + """Returns the path to the folder where user preferences are stored.""" + # the user preferences file is stored at: C:\Users\AppData\Roaming\MathCAT\prefs.yaml + return os.path.join(os.path.expandvars("%APPDATA%"), "MathCAT") + + @staticmethod + def pathToUserPreferences() -> str: + """Returns the full path to the user preferences file.""" + # the user preferences file is stored at: C:\Users\AppData\Roaming\MathCAT\prefs.yaml + return os.path.join(UserInterface.pathToUserPreferencesFolder(), "prefs.yaml") + + @staticmethod + def loadDefaultPreferences() -> None: + """Loads the default preferences, overwriting any existing user preferences.""" + global userPreferences + # load default preferences into the user preferences data structure (overwrites existing) + if os.path.exists(UserInterface.pathToDefaultPreferences()): + with open( + UserInterface.pathToDefaultPreferences(), + encoding="utf-8", + ) as f: + userPreferences = yaml.load(f, Loader=yaml.FullLoader) + + @staticmethod + def loadUserPreferences() -> None: + """Loads user preferences from a file and merges them into the current preferences. + + If the user preferences file exists, its values overwrite the defaults. + """ + global userPreferences + # merge user file values into the user preferences data structure + if os.path.exists(UserInterface.pathToUserPreferences()): + with open(UserInterface.pathToUserPreferences(), encoding="utf-8") as f: + # merge with the default preferences, overwriting with the user's values + userPreferences.update(yaml.load(f, Loader=yaml.FullLoader)) + + @staticmethod + def validate( + key1: str, + key2: str, + validValues: list[str | bool], + defaultValue: str | bool, + ) -> None: + """Validates that a preference value is in a list of valid options or non-empty if no list is given. + + If the value is missing or invalid, sets it to the default. + + :param key1: The first-level key in the preferences dictionary. + :param key2: The second-level key in the preferences dictionary. + :param validValues: A list of valid values; if empty, any non-empty value is valid. + :param defaultValue: The default value to set if validation fails. + """ + global userPreferences + try: + if validValues == []: + # any value is valid + if userPreferences[key1][key2] != "": + return + + else: + # any value in the list is valid + if userPreferences[key1][key2] in validValues: + return + except Exception as e: + log.exception(f"MathCAT: An exception occurred in validate: {e}") + # the preferences entry does not exist + if key1 not in userPreferences: + userPreferences[key1] = {key2: defaultValue} + else: + userPreferences[key1][key2] = defaultValue + + @staticmethod + def validateInt( + key1: str, + key2: str, + validValues: list[int], + defaultValue: int, + ) -> None: + """Validates that an integer preference is within a specified range. + + If the value is missing or out of bounds, sets it to the default. + + :param key1: The first-level key in the preferences dictionary. + :param key2: The second-level key in the preferences dictionary. + :param validValues: A list with two integers [min, max] representing valid bounds. + :param defaultValue: The default value to set if validation fails. + """ + global userPreferences + try: + # any value between lower and upper bounds is valid + if ( + int(userPreferences[key1][key2]) >= validValues[0] + and int(userPreferences[key1][key2]) <= validValues[1] + ): + return + except Exception as e: + log.exception(f"MathCAT: An exception occurred in validateInt: {e}") + # the preferences entry does not exist + if key1 not in userPreferences: + userPreferences[key1] = {key2: defaultValue} + else: + userPreferences[key1][key2] = defaultValue + + @staticmethod + def validateUserPreferences(): + """Validates all user preferences, ensuring each is present and valid. + + If a preference is missing or invalid, it is reset to its default value. + Validation covers speech, navigation, and braille settings. + """ + # Speech: + # Impairment: Blindness # LearningDisability, LowVision, Blindness + UserInterface.validate( + "Speech", + "Impairment", + ["LearningDisability", "LowVision", "Blindness"], + "Blindness", + ) + # Language: en # any known language code and sub-code -- could be en-uk, etc + UserInterface.validate("Speech", "Language", [], "en") + # Verbosity: Medium # Terse, Medium, Verbose + UserInterface.validate("Speech", "Verbosity", ["Terse", "Medium", "Verbose"], "Medium") + # MathRate: 100 # Change from text speech rate (%) + UserInterface.validateInt("Speech", "MathRate", [0, 200], 100) + # PauseFactor: 100 # TBC + UserInterface.validateInt("Speech", "PauseFactor", [0, 1000], 100) + # SpeechSound: None # make a sound when starting/ending math speech -- None, Beep + UserInterface.validate("Speech", "SpeechSound", ["None", "Beep"], "None") + # SpeechStyle: ClearSpeak # Any known speech style (falls back to ClearSpeak) + UserInterface.validate("Speech", "SpeechStyle", [], "ClearSpeak") + # SubjectArea: General # FIX: still working on this + UserInterface.validate("Speech", "SubjectArea", [], "General") + # Chemistry: SpellOut # SpellOut (H 2 0), AsCompound (Water), Off (H sub 2 O) + UserInterface.validate("Speech", "Chemistry", ["SpellOut", "Off"], "SpellOut") + # Navigation: + # NavMode: Enhanced # Enhanced, Simple, Character + UserInterface.validate("Navigation", "NavMode", ["Enhanced", "Simple", "Character"], "Enhanced") + # ResetNavMode: false # remember previous value and use it + UserInterface.validate("Navigation", "ResetNavMode", [False, True], False) + # Overview: false # speak the expression or give a description/overview + UserInterface.validate("Navigation", "Overview", [False, True], False) + # ResetOverview: true # remember previous value and use it + UserInterface.validate("Navigation", "ResetOverview", [False, True], True) + # NavVerbosity: Medium # Terse, Medium, Full (words to say for nav command) + UserInterface.validate("Navigation", "NavVerbosity", ["Terse", "Medium", "Full"], "Medium") + # AutoZoomOut: true # Auto zoom out of 2D exprs (use shift-arrow to force zoom out if unchecked) + UserInterface.validate("Navigation", "AutoZoomOut", [False, True], True) + # CopyAs: MathML # MathML, LaTeX, ASCIIMath, Speech + UserInterface.validate("Navigation", "CopyAs", ["MathML", "LaTeX", "ASCIIMath", "Speech"], "MathML") + # Braille: + # BrailleNavHighlight: EndPoints + # Highlight with dots 7 & 8 the current nav node -- values are Off, FirstChar, EndPoints, All + UserInterface.validate( + "Braille", + "BrailleNavHighlight", + ["Off", "FirstChar", "EndPoints", "All"], + "EndPoints", + ) + # BrailleCode: "Nemeth" # Any supported braille code (currently Nemeth, UEB, CMU, Vietnam) + UserInterface.validate("Braille", "BrailleCode", [], "Nemeth") + + @staticmethod + def writeUserPreferences() -> None: + """Writes the current user preferences to a file and updates special settings. + + Sets the language preference through the native library, ensures the preferences + folder exists, and saves the preferences to disk. + """ + # Language is special because it is set elsewhere by SetPreference which overrides the user_prefs -- so set it here + from . import libmathcat_py as libmathcat + + try: + libmathcat.SetPreference("Language", userPreferences["Speech"]["Language"]) + except Exception as e: + log.exception( + f'Error in trying to set MathCAT "Language" preference to "{userPreferences["Speech"]["Language"]}": {e}', + ) + if not os.path.exists(UserInterface.pathToUserPreferencesFolder()): + # create a folder for the user preferences + os.mkdir(UserInterface.pathToUserPreferencesFolder()) + with open(UserInterface.pathToUserPreferences(), "w", encoding="utf-8") as f: + # write values to the user preferences file, NOT the default + yaml.dump(userPreferences, stream=f, allow_unicode=True) + + def onRelativeSpeedChanged(self, event: wx.ScrollEvent) -> None: + """Handles changes to the relative speed slider and updates speech output. + + Adjusts the speech rate based on the slider value and speaks a test phrase + with the updated rate. + + :param event: The scroll event triggered by adjusting the relative speed slider. + """ + rate: int = self._sliderRelativeSpeed.GetValue() + # Translators: this is a test string that is spoken. Only translate "the square root of x squared plus y squared" + text: str = _("the square root of x squared plus y squared").replace( + "XXX", + str(rate), + 1, + ) + speak(convertSSMLTextForNVDA(text)) + + def onPauseFactorChanged(self, event: wx.ScrollEvent) -> None: + """Handles changes to the pause factor slider and updates speech output accordingly. + + Calculates the pause durations based on the slider value, constructs an SSML string + with adjusted prosody and breaks, and sends it for speech synthesis. + + :param event: The scroll event triggered by adjusting the pause factor slider. + """ + rate: int = self._sliderRelativeSpeed.GetValue() + pfSlider = self._sliderPauseFactor.GetValue() + pauseFactor = ( + 0 if pfSlider == 0 else round(PAUSE_FACTOR_SCALE * math.pow(PAUSE_FACTOR_LOG_BASE, pfSlider)) + ) + text: str = _( + # Translators: this is a test string that is spoken. Only translate "the fraction with numerator" + # and other parts NOT inside '<.../>', + "the fraction with numerator \ + x to the \ + n -th\ + power plus 1\ + and denominator \ + x to the \ + n -thpower\ + minus 1\ + end fraction ", + ).format( + rate=rate, + pause_factor_128=128 * pauseFactor // 100, + pause_factor_150=150 * pauseFactor // 100, + pause_factor_300=300 * pauseFactor // 100, + pause_factor_600=600 * pauseFactor // 100, + ) + speak(convertSSMLTextForNVDA(text)) + + def onClickOK(self, event: wx.CommandEvent) -> None: + """Saves current preferences and closes the dialog. + + Retrieves values from the UI, writes them to the preferences, and then closes the window. + + :param event: The event triggered by clicking the OK button. + """ + UserInterface.getUIValues(self) + UserInterface.writeUserPreferences() + self.Destroy() + + def onClickCancel(self, event: wx.CommandEvent) -> None: + """Closes the preferences dialog without saving changes. + + :param event: The event triggered by clicking the Cancel button. + """ + self.Destroy() + + def onClickApply(self, event: wx.CommandEvent) -> None: + """Applies the current UI settings to the user preferences. + + Retrieves values from the UI and writes them to the preferences configuration. + + :param event: The event triggered by clicking the Apply button. + """ + UserInterface.getUIValues(self) + UserInterface.writeUserPreferences() + + def onClickReset(self, event: wx.CommandEvent) -> None: + """Resets preferences to their default values. + + Loads the default preferences, validates them, and updates the UI accordingly. + + :param event: The event triggered by clicking the Reset button. + """ + UserInterface.loadDefaultPreferences() + UserInterface.validateUserPreferences() + UserInterface.setUIValues(self) + + def onClickHelp(self, event: wx.CommandEvent) -> None: + """Opens the MathCAT user guide in the default web browser. + + Triggered when the Help button is clicked. + + :param event: The event triggered by clicking the Help button. + """ + webbrowser.open("https://nsoiffer.github.io/MathCAT/users.html") + + def onListBoxCategories(self, event: wx.CommandEvent) -> None: + """Handles category selection changes in the preferences list box. + + Updates the displayed panel in the dialog to match the newly selected category. + + :param event: The event triggered by selecting a different category. + """ + self._simplebookPanelsCategories.SetSelection(self._listBoxPreferencesTopic.GetSelection()) + + def onLanguage(self, event: wx.CommandEvent) -> None: + """Handles the event when the user changes the selected language. + + Retrieves and updates the available speech styles for the newly selected language + in the preferences dialog. + + :param event: The event triggered by changing the language selection. + """ + UserInterface.getSpeechStyles(self, self._choiceSpeechStyle.GetStringSelection()) + + def mathCATPreferencesDialogOnCharHook(self, event: wx.KeyEvent) -> None: + """Handles character key events within the MathCAT Preferences dialog. + + This method interprets specific key presses to mimic button clicks or + navigate within the preferences dialog: + + - escape: Triggers the Cancel button functionality. + - enter: Triggers the OK button functionality. + - ctrl+tab: Cycles forward through the preference categories. + - ctrl+shift+tab: Cycles backward through the preference categories. + - tab: Moves focus to the first control in the currently selected category, + if the category list has focus. + - shift+tab: Moves focus to the second row of controls, + if the OK button has focus. + + If none of these keys are matched, the event is skipped to allow default processing. + + :param event: The keyboard event to handle. + """ + keyCode: int = event.GetKeyCode() + if keyCode == wx.WXK_ESCAPE: + UserInterface.onClickCancel(self, event) + return + if keyCode == wx.WXK_RETURN: + UserInterface.onClickOK(self, event) + if keyCode == wx.WXK_TAB: + if event.GetModifiers() == wx.MOD_CONTROL: + # cycle the category forward + newCategory: int = self._listBoxPreferencesTopic.GetSelection() + 1 + if newCategory == 3: + newCategory = 0 + self._listBoxPreferencesTopic.SetSelection(newCategory) + # update the ui to show the new page + UserInterface.onListBoxCategories(self, event) + # set the focus into the category list box + self._listBoxPreferencesTopic.SetFocus() + # jump out so the tab key is not processed + return + if event.GetModifiers() == wx.MOD_CONTROL | wx.MOD_SHIFT: + # cycle the category back + newCategory: int = self._listBoxPreferencesTopic.GetSelection() - 1 + if newCategory == -1: + newCategory = 2 + self._listBoxPreferencesTopic.SetSelection(newCategory) + # update the ui to show the new page + UserInterface.onListBoxCategories(self, event) + # update the ui to show the new page + self._listBoxPreferencesTopic.SetFocus() + # jump out so the tab key is not processed + return + if event.GetModifiers() == wx.MOD_NONE and ( + MathCATgui.MathCATPreferencesDialog.FindFocus() == self._listBoxPreferencesTopic + ): + if self._listBoxPreferencesTopic.GetSelection() == 0: + self._choiceImpairment.SetFocus() + elif self._listBoxPreferencesTopic.GetSelection() == 1: + self._choiceNavigationMode.SetFocus() + elif self._listBoxPreferencesTopic.GetSelection() == 2: + self._choiceBrailleMathCode.SetFocus() + return + if (event.GetModifiers() == wx.MOD_SHIFT) and ( + MathCATgui.MathCATPreferencesDialog.FindFocus() == self._buttonOK + ): + if self._listBoxPreferencesTopic.GetSelection() == 0: + self._choiceSpeechForChemical.SetFocus() + elif self._listBoxPreferencesTopic.GetSelection() == 1: + self._choiceSpeechAmountNavigation.SetFocus() + elif self._listBoxPreferencesTopic.GetSelection() == 2: + self._choiceBrailleHighlights.SetFocus() + return + # continue handling keyboard event + event.Skip() diff --git a/addon/globalPlugins/MathCAT/MathCATgui.py b/addon/globalPlugins/MathCAT/MathCATgui.py index 9554c5bc..fdf06b81 100644 --- a/addon/globalPlugins/MathCAT/MathCATgui.py +++ b/addon/globalPlugins/MathCAT/MathCATgui.py @@ -1,394 +1,883 @@ -# -*- coding: utf-8 -*- - -########################################################################### -## Python code generated with wxFormBuilder (version 3.10.1-0-g8feb16b3) -## http://www.wxformbuilder.org/ -## -## PLEASE DO *NOT* EDIT THIS FILE! -########################################################################### - import wx -# import wx.xrc +# import wx.xrc import gettext -_ = gettext.gettext - import addonHandler -addonHandler.initTranslation() + +_ = gettext.gettext +addonHandler.initTranslation() ########################################################################### -## Class MathCATPreferencesDialog +# Class MathCATPreferencesDialog ########################################################################### -class MathCATPreferencesDialog ( wx.Dialog ): - - def __init__( self, parent ): - wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = _("MathCAT Preferences"), pos = wx.DefaultPosition, size = wx.Size( -1,-1 ), style = wx.DEFAULT_DIALOG_STYLE ) - - self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) - - gbSizerMathCATPreferences = wx.GridBagSizer( 0, 0 ) - gbSizerMathCATPreferences.SetFlexibleDirection( wx.BOTH ) - gbSizerMathCATPreferences.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED ) - - self.m_panelCategories = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizerCategories = wx.BoxSizer( wx.VERTICAL ) - - self.m_staticTextCategories = wx.StaticText( self.m_panelCategories, wx.ID_ANY, _("Categories:"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticTextCategories.Wrap( -1 ) - - bSizerCategories.Add( self.m_staticTextCategories, 0, wx.ALL, 5 ) - - m_listBoxPreferencesTopicChoices = [ _("Speech"), _("Navigation"), _("Braille") ] - self.m_listBoxPreferencesTopic = wx.ListBox( self.m_panelCategories, wx.ID_ANY, wx.Point( -1,-1 ), wx.Size( -1,-1 ), m_listBoxPreferencesTopicChoices, wx.LB_NO_SB|wx.LB_SINGLE ) - bSizerCategories.Add( self.m_listBoxPreferencesTopic, 0, wx.ALL, 5 ) - - - bSizerCategories.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - - self.m_bitmapLogo = wx.StaticBitmap( self.m_panelCategories, wx.ID_ANY, wx.NullBitmap, wx.DefaultPosition, wx.Size( 126,85 ), 0 ) - bSizerCategories.Add( self.m_bitmapLogo, 0, wx.ALL, 5 ) - - - self.m_panelCategories.SetSizer( bSizerCategories ) - self.m_panelCategories.Layout() - bSizerCategories.Fit( self.m_panelCategories ) - gbSizerMathCATPreferences.Add( self.m_panelCategories, wx.GBPosition( 0, 0 ), wx.GBSpan( 1, 1 ), wx.EXPAND |wx.ALL, 5 ) - - self.m_simplebookPanelsCategories = wx.Simplebook( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_panelSpeech = wx.Panel( self.m_simplebookPanelsCategories, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.BORDER_SIMPLE|wx.TAB_TRAVERSAL ) - bSizerSpeech = wx.BoxSizer( wx.VERTICAL ) - - bSizerImpairment = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticTextImpairment = wx.StaticText( self.m_panelSpeech, wx.ID_ANY, _("Generate speech for:"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticTextImpairment.Wrap( -1 ) - - bSizerImpairment.Add( self.m_staticTextImpairment, 0, wx.ALL, 5 ) - - m_choiceImpairmentChoices = [ _("Learning disabilities"), _("Blindness"), _("Low vision") ] - self.m_choiceImpairment = wx.Choice( self.m_panelSpeech, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, m_choiceImpairmentChoices, 0 ) - self.m_choiceImpairment.SetSelection( 1 ) - bSizerImpairment.Add( self.m_choiceImpairment, 0, wx.ALL, 5 ) - - - bSizerSpeech.Add( bSizerImpairment, 1, wx.EXPAND, 5 ) - - bSizerLanguage = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticTextLanguage = wx.StaticText( self.m_panelSpeech, wx.ID_ANY, _("Language:"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticTextLanguage.Wrap( -1 ) - - bSizerLanguage.Add( self.m_staticTextLanguage, 0, wx.ALL, 5 ) - - m_choiceLanguageChoices = [ _("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") ] - self.m_choiceLanguage = wx.Choice( self.m_panelSpeech, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, m_choiceLanguageChoices, 0 ) - self.m_choiceLanguage.SetSelection( 0 ) - bSizerLanguage.Add( self.m_choiceLanguage, 0, wx.ALL, 5 ) - - - bSizerSpeech.Add( bSizerLanguage, 1, wx.EXPAND, 5 ) - - bSizerSpeechStyle = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticTextSpeechStyle = wx.StaticText( self.m_panelSpeech, wx.ID_ANY, _("Speech style:"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticTextSpeechStyle.Wrap( -1 ) - - bSizerSpeechStyle.Add( self.m_staticTextSpeechStyle, 0, wx.ALL, 5 ) - - m_choiceSpeechStyleChoices = [ _("xxxxxxxxxxxxxxxx") ] - self.m_choiceSpeechStyle = wx.Choice( self.m_panelSpeech, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, m_choiceSpeechStyleChoices, 0 ) - self.m_choiceSpeechStyle.SetSelection( 0 ) - bSizerSpeechStyle.Add( self.m_choiceSpeechStyle, 0, wx.ALL, 5 ) - - - bSizerSpeech.Add( bSizerSpeechStyle, 1, wx.EXPAND, 5 ) - - bSizer71 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticTextSpeechAmount = wx.StaticText( self.m_panelSpeech, wx.ID_ANY, _("Speech amount:"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticTextSpeechAmount.Wrap( -1 ) - - bSizer71.Add( self.m_staticTextSpeechAmount, 0, wx.ALL, 5 ) - - m_choiceSpeechAmountChoices = [ _("Terse"), _("Medium"), _("Verbose") ] - self.m_choiceSpeechAmount = wx.Choice( self.m_panelSpeech, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, m_choiceSpeechAmountChoices, 0 ) - self.m_choiceSpeechAmount.SetSelection( 0 ) - bSizer71.Add( self.m_choiceSpeechAmount, 0, wx.ALL, 5 ) - - - bSizerSpeech.Add( bSizer71, 1, wx.EXPAND, 5 ) - - bSizerRelativeSpeed = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticTextRelativeSpeed = wx.StaticText( self.m_panelSpeech, wx.ID_ANY, _("Relative speech rate:"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticTextRelativeSpeed.Wrap( -1 ) - - bSizerRelativeSpeed.Add( self.m_staticTextRelativeSpeed, 0, wx.ALL, 5 ) - - self.m_sliderRelativeSpeed = wx.Slider( self.m_panelSpeech, wx.ID_ANY, 100, 10, 100, wx.DefaultPosition, wx.DefaultSize, wx.SL_HORIZONTAL ) - self.m_sliderRelativeSpeed.SetLineSize(9) - bSizerRelativeSpeed.Add( self.m_sliderRelativeSpeed, 0, wx.ALL, 5 ) - - - bSizerSpeech.Add( bSizerRelativeSpeed, 1, wx.EXPAND, 5 ) - - bSizerPauseFactor = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticPauseFactor = wx.StaticText( self.m_panelSpeech, wx.ID_ANY, _("Pause factor:"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticPauseFactor.Wrap( -1 ) - - bSizerPauseFactor.Add( self.m_staticPauseFactor, 0, wx.ALL, 5 ) - - self.m_sliderPauseFactor = wx.Slider( self.m_panelSpeech, wx.ID_ANY, 7, 0, 14, wx.DefaultPosition, wx.DefaultSize, wx.SL_HORIZONTAL ) - bSizerPauseFactor.Add( self.m_sliderPauseFactor, 0, wx.ALL, 5 ) - - - bSizerSpeech.Add( bSizerPauseFactor, 1, wx.EXPAND, 5 ) - - bSizerSpeechSound = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_checkBoxSpeechSound = wx.CheckBox( self.m_panelSpeech, wx.ID_ANY, _("Make a sound when starting/ending math speech"), wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizerSpeechSound.Add( self.m_checkBoxSpeechSound, 0, wx.ALL, 5 ) - - - bSizerSpeech.Add( bSizerSpeechSound, 1, wx.EXPAND, 5 ) - - bSizerSubjectArea = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticTextSubjectArea = wx.StaticText( self.m_panelSpeech, wx.ID_ANY, _("Subject area to be used when it cannot be determined automatically:"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticTextSubjectArea.Wrap( -1 ) - - bSizerSubjectArea.Add( self.m_staticTextSubjectArea, 0, wx.ALL, 5 ) - - m_choiceSubjectAreaChoices = [ _("General") ] - self.m_choiceSubjectArea = wx.Choice( self.m_panelSpeech, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, m_choiceSubjectAreaChoices, 0 ) - self.m_choiceSubjectArea.SetSelection( 0 ) - bSizerSubjectArea.Add( self.m_choiceSubjectArea, 0, wx.ALL, 5 ) - - - bSizerSpeech.Add( bSizerSubjectArea, 1, wx.EXPAND, 5 ) - - bSizerSpeechForChemical = wx.BoxSizer( wx.HORIZONTAL ) - self.m_staticTextSpeechForChemical = wx.StaticText( self.m_panelSpeech, wx.ID_ANY, _("Speech for chemical formulas:"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticTextSpeechForChemical.Wrap( -1 ) - - bSizerSpeechForChemical.Add( self.m_staticTextSpeechForChemical, 0, wx.ALL, 5 ) - - m_choiceSpeechForChemicalChoices = [ _("Spell it out (H 2 O)"), _("Off (H sub 2 O)") ] - self.m_choiceSpeechForChemical = wx.Choice( self.m_panelSpeech, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, m_choiceSpeechForChemicalChoices, 0 ) - self.m_choiceSpeechForChemical.SetSelection( 0 ) - bSizerSpeechForChemical.Add( self.m_choiceSpeechForChemical, 0, wx.ALL, 5 ) - - - bSizerSpeech.Add( bSizerSpeechForChemical, 1, wx.EXPAND, 5 ) - - - self.m_panelSpeech.SetSizer( bSizerSpeech ) - self.m_panelSpeech.Layout() - bSizerSpeech.Fit( self.m_panelSpeech ) - self.m_simplebookPanelsCategories.AddPage( self.m_panelSpeech, _("a page"), False ) - self.m_panelNavigation = wx.Panel( self.m_simplebookPanelsCategories, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.BORDER_SIMPLE|wx.TAB_TRAVERSAL ) - bSizerNavigation = wx.BoxSizer( wx.VERTICAL ) - - sbSizerNavigationMode = wx.StaticBoxSizer( wx.StaticBox( self.m_panelNavigation, wx.ID_ANY, _("Navigation mode to use when beginning to navigate an equation:") ), wx.VERTICAL ) - - m_choiceNavigationModeChoices = [ _("Enhanced"), _("Simple"), _("Character") ] - self.m_choiceNavigationMode = wx.Choice( sbSizerNavigationMode.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, m_choiceNavigationModeChoices, 0 ) - self.m_choiceNavigationMode.SetSelection( 1 ) - sbSizerNavigationMode.Add( self.m_choiceNavigationMode, 0, wx.ALL, 5 ) - - self.m_checkBoxResetNavigationMode = wx.CheckBox( sbSizerNavigationMode.GetStaticBox(), wx.ID_ANY, _("Reset navigation mode on entry to an expression"), wx.DefaultPosition, wx.DefaultSize, 0 ) - sbSizerNavigationMode.Add( self.m_checkBoxResetNavigationMode, 0, wx.ALL, 5 ) - - - bSizerNavigation.Add( sbSizerNavigationMode, 1, wx.EXPAND, 5 ) - - sbSizerNavigationSpeech = wx.StaticBoxSizer( wx.StaticBox( self.m_panelNavigation, wx.ID_ANY, _("Navigation speech to use when beginning to navigate an equation:") ), wx.VERTICAL ) - - m_choiceNavigationSpeechChoices = [ _("Speak"), _("Describe/overview") ] - self.m_choiceNavigationSpeech = wx.Choice( sbSizerNavigationSpeech.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, m_choiceNavigationSpeechChoices, 0 ) - self.m_choiceNavigationSpeech.SetSelection( 1 ) - sbSizerNavigationSpeech.Add( self.m_choiceNavigationSpeech, 0, wx.ALL, 5 ) - - self.m_checkBoxResetNavigationSpeech = wx.CheckBox( sbSizerNavigationSpeech.GetStaticBox(), wx.ID_ANY, _("Reset navigation speech on entry to an expression"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_checkBoxResetNavigationSpeech.SetValue(True) - sbSizerNavigationSpeech.Add( self.m_checkBoxResetNavigationSpeech, 0, wx.ALL, 5 ) - - - bSizerNavigation.Add( sbSizerNavigationSpeech, 1, wx.EXPAND, 5 ) - - bSizerNavigationZoom = wx.BoxSizer( wx.VERTICAL ) - - self.m_checkBoxAutomaticZoom = wx.CheckBox( self.m_panelNavigation, wx.ID_ANY, _("Automatic zoom out of 2D notations"), wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizerNavigationZoom.Add( self.m_checkBoxAutomaticZoom, 0, wx.ALL, 5 ) - - bSizerSpeechAmountNavigation = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticTextSpeechAmountNavigation = wx.StaticText( self.m_panelNavigation, wx.ID_ANY, _("Speech amount for navigation:"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticTextSpeechAmountNavigation.Wrap( -1 ) - - bSizerSpeechAmountNavigation.Add( self.m_staticTextSpeechAmountNavigation, 0, wx.ALL, 5 ) - - m_choiceSpeechAmountNavigationChoices = [ _("Terse"), _("Medium"), _("Verbose") ] - self.m_choiceSpeechAmountNavigation = wx.Choice( self.m_panelNavigation, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, m_choiceSpeechAmountNavigationChoices, 0 ) - self.m_choiceSpeechAmountNavigation.SetSelection( 0 ) - bSizerSpeechAmountNavigation.Add( self.m_choiceSpeechAmountNavigation, 0, wx.ALL, 5 ) - - - bSizerNavigationZoom.Add( bSizerSpeechAmountNavigation, 1, wx.EXPAND, 5 ) - - - bSizerNavigation.Add( bSizerNavigationZoom, 1, wx.EXPAND, 5 ) - - - self.m_panelNavigation.SetSizer( bSizerNavigation ) - self.m_panelNavigation.Layout() - bSizerNavigation.Fit( self.m_panelNavigation ) - self.m_simplebookPanelsCategories.AddPage( self.m_panelNavigation, _("a page"), False ) - self.m_panelBraille = wx.Panel( self.m_simplebookPanelsCategories, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.BORDER_SIMPLE|wx.TAB_TRAVERSAL ) - bSizerBraille = wx.BoxSizer( wx.VERTICAL ) - - bSizerBrailleMathCode = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticTextBrailleMathCode = wx.StaticText( self.m_panelBraille, wx.ID_ANY, _("Braille math code for refreshable displays:"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticTextBrailleMathCode.Wrap( -1 ) - - bSizerBrailleMathCode.Add( self.m_staticTextBrailleMathCode, 0, wx.ALL, 5 ) - - m_choiceBrailleMathCodeChoices = [ _("Nemeth"), _("UEB"), _("Vietnam") ] - self.m_choiceBrailleMathCode = wx.Choice( self.m_panelBraille, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, m_choiceBrailleMathCodeChoices, 0 ) - self.m_choiceBrailleMathCode.SetSelection( 1 ) - bSizerBrailleMathCode.Add( self.m_choiceBrailleMathCode, 0, wx.ALL, 5 ) - - - bSizerBraille.Add( bSizerBrailleMathCode, 1, wx.EXPAND, 5 ) - - bSizerBrailleHighlights = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticTextBrailleHighlights = wx.StaticText( self.m_panelBraille, wx.ID_ANY, _("Highlight with dots 7 && 8 the current nav node:"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticTextBrailleHighlights.Wrap( -1 ) - - bSizerBrailleHighlights.Add( self.m_staticTextBrailleHighlights, 0, wx.ALL, 5 ) - - m_choiceBrailleHighlightsChoices = [ _("Off"), _("First character"), _("Endpoints"), _("All") ] - self.m_choiceBrailleHighlights = wx.Choice( self.m_panelBraille, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, m_choiceBrailleHighlightsChoices, 0 ) - self.m_choiceBrailleHighlights.SetSelection( 1 ) - bSizerBrailleHighlights.Add( self.m_choiceBrailleHighlights, 0, wx.ALL, 5 ) - - - bSizerBraille.Add( bSizerBrailleHighlights, 1, wx.EXPAND, 5 ) - - - bSizerBraille.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - - - bSizerBraille.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - - - bSizerBraille.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - - - bSizerBraille.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - - - bSizerBraille.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - - - self.m_panelBraille.SetSizer( bSizerBraille ) - self.m_panelBraille.Layout() - bSizerBraille.Fit( self.m_panelBraille ) - self.m_simplebookPanelsCategories.AddPage( self.m_panelBraille, _("a page"), False ) - - gbSizerMathCATPreferences.Add( self.m_simplebookPanelsCategories, wx.GBPosition( 0, 1 ), wx.GBSpan( 1, 1 ), wx.EXPAND |wx.ALL, 10 ) - - self.m_staticlineAboveButtons = wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ) - gbSizerMathCATPreferences.Add( self.m_staticlineAboveButtons, wx.GBPosition( 1, 0 ), wx.GBSpan( 1, 2 ), wx.EXPAND |wx.ALL, 5 ) - - self.m_panelButtons = wx.Panel( self, wx.ID_ANY, wx.Point( -1,-1 ), wx.DefaultSize, 0 ) - bSizerButtons = wx.BoxSizer( wx.HORIZONTAL ) - - - bSizerButtons.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - - self.m_buttonOK = wx.Button( self.m_panelButtons, wx.ID_ANY, _("OK"), wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizerButtons.Add( self.m_buttonOK, 0, wx.ALL, 5 ) - - self.m_buttonCancel = wx.Button( self.m_panelButtons, wx.ID_ANY, _("Cancel"), wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizerButtons.Add( self.m_buttonCancel, 0, wx.ALL, 5 ) - - self.m_buttonApply = wx.Button( self.m_panelButtons, wx.ID_ANY, _("Apply"), wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizerButtons.Add( self.m_buttonApply, 0, wx.ALL, 5 ) - - self.m_buttonReset = wx.Button( self.m_panelButtons, wx.ID_ANY, _("Reset to defaults"), wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizerButtons.Add( self.m_buttonReset, 0, wx.ALL, 5 ) - - self.m_buttonHelp = wx.Button( self.m_panelButtons, wx.ID_ANY, _("Help"), wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizerButtons.Add( self.m_buttonHelp, 0, wx.ALL, 5 ) - - - self.m_panelButtons.SetSizer( bSizerButtons ) - self.m_panelButtons.Layout() - bSizerButtons.Fit( self.m_panelButtons ) - gbSizerMathCATPreferences.Add( self.m_panelButtons, wx.GBPosition( 2, 1 ), wx.GBSpan( 1, 2 ), wx.EXPAND |wx.ALL, 5 ) - - - self.SetSizer( gbSizerMathCATPreferences ) +class MathCATPreferencesDialog(wx.Dialog): + """Main dialog window for configuring MathCAT preferences. + + This base class sets up the layout and controls. + """ + + def __init__(self, parent: wx.Window | None): + """Initialize the preferences dialog. + + :param parent: The parent window for this dialog. + """ + wx.Dialog.__init__( + self, + parent, + id=wx.ID_ANY, + # Translators: title for MathCAT preferences dialog + title=_("MathCAT Preferences"), + pos=wx.DefaultPosition, + size=wx.Size(-1, -1), + style=wx.DEFAULT_DIALOG_STYLE, + ) + + self.SetSizeHints(wx.DefaultSize, wx.DefaultSize) + + gbSizerMathCATPreferences: wx.GridBagSizer = wx.GridBagSizer(0, 0) + gbSizerMathCATPreferences.SetFlexibleDirection(wx.BOTH) + gbSizerMathCATPreferences.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED) + + self._panelCategories: wx.Panel = wx.Panel( + self, + wx.ID_ANY, + wx.DefaultPosition, + wx.DefaultSize, + wx.TAB_TRAVERSAL, + ) + bSizerCategories: wx.BoxSizer = wx.BoxSizer(wx.VERTICAL) + + self._staticTextCategories: wx.StaticText = wx.StaticText( + self._panelCategories, + wx.ID_ANY, + # Translators: A heading that labels three navigation pane tab names in the MathCAT dialog + _("Categories:"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + self._staticTextCategories.Wrap(-1) + + bSizerCategories.Add(self._staticTextCategories, 0, wx.ALL, 5) + + listBoxPreferencesTopicChoices: list[str] = [ + # Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" + _("Speech"), + # Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" + _("Navigation"), + # Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" + _("Braille"), + ] + self._listBoxPreferencesTopic: wx.ListBox = wx.ListBox( + self._panelCategories, + wx.ID_ANY, + wx.Point(-1, -1), + wx.Size(-1, -1), + listBoxPreferencesTopicChoices, + wx.LB_NO_SB | wx.LB_SINGLE, + ) + bSizerCategories.Add(self._listBoxPreferencesTopic, 0, wx.ALL, 5) + + bSizerCategories.Add((0, 0), 1, wx.EXPAND, 5) + + self._bitmapLogo: wx.StaticBitmap = wx.StaticBitmap( + self._panelCategories, + wx.ID_ANY, + wx.NullBitmap, + wx.DefaultPosition, + wx.Size(126, 85), + 0, + ) + bSizerCategories.Add(self._bitmapLogo, 0, wx.ALL, 5) + + self._panelCategories.SetSizer(bSizerCategories) + self._panelCategories.Layout() + bSizerCategories.Fit(self._panelCategories) + gbSizerMathCATPreferences.Add( + self._panelCategories, + wx.GBPosition(0, 0), + wx.GBSpan(1, 1), + wx.EXPAND | wx.ALL, + 5, + ) + + self._simplebookPanelsCategories: wx.Simplebook = wx.Simplebook( + self, + wx.ID_ANY, + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + self._panelSpeech: wx.Panel = wx.Panel( + self._simplebookPanelsCategories, + wx.ID_ANY, + wx.DefaultPosition, + wx.DefaultSize, + wx.BORDER_SIMPLE | wx.TAB_TRAVERSAL, + ) + bSizerSpeech: wx.BoxSizer = wx.BoxSizer(wx.VERTICAL) + + bSizerImpairment: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) + + self._staticTextImpairment: wx.StaticText = wx.StaticText( + self._panelSpeech, + wx.ID_ANY, + # Translators: this is the text label for whom to target the speech for (options are below) + _("Generate speech for:"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + self._staticTextImpairment.Wrap(-1) + + bSizerImpairment.Add(self._staticTextImpairment, 0, wx.ALL, 5) + + impairmentChoices: list[str] = [ + # Translators: these are the categories of impairments that MathCAT supports + # Translators: Learning disabilities includes dyslexia and ADHD + _("Learning disabilities"), + # Translators: target people who are blind + _("Blindness"), + # Translators: target people who have low vision + _("Low vision"), + ] + self._choiceImpairment: wx.Choice = wx.Choice( + self._panelSpeech, + wx.ID_ANY, + wx.DefaultPosition, + wx.DefaultSize, + impairmentChoices, + 0, + ) + self._choiceImpairment.SetSelection(1) + bSizerImpairment.Add(self._choiceImpairment, 0, wx.ALL, 5) + + bSizerSpeech.Add(bSizerImpairment, 1, wx.EXPAND, 5) + + bSizerLanguage: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) + + self._staticTextLanguage: wx.StaticText = wx.StaticText( + self._panelSpeech, + wx.ID_ANY, + # Translators: label for pull down allowing users to choose the speech language for math + _("Language:"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + self._staticTextLanguage.Wrap(-1) + + bSizerLanguage.Add(self._staticTextLanguage, 0, wx.ALL, 5) + + languageChoices: list[str] = ["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"] + self._choiceLanguage: wx.Choice = wx.Choice( + self._panelSpeech, + wx.ID_ANY, + wx.DefaultPosition, + wx.DefaultSize, + languageChoices, + 0, + ) + self._choiceLanguage.SetSelection(0) + bSizerLanguage.Add(self._choiceLanguage, 0, wx.ALL, 5) + + bSizerSpeech.Add(bSizerLanguage, 1, wx.EXPAND, 5) + + bSizerDecimalSeparator: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) + + self._staticTextDecimalSeparator: wx.StaticText = wx.StaticText( + self._panelSpeech, + wx.ID_ANY, + # Translators: label for pull down to specify what character to use in numbers as the decimal separator + _("Decimal separator for numbers:"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + self._staticTextDecimalSeparator.Wrap(-1) + + bSizerDecimalSeparator.Add(self._staticTextDecimalSeparator, 0, wx.ALL, 5) + + # Translators: options for decimal separator. + decimalSeparatorChoices: list[str] = [ + # Translators: options for decimal separator -- "Auto" = automatically pick the choice based on the language + _("Auto"), + # options for decimal separator -- use "." (and use ", " for block separators) + ("."), + # options for decimal separator -- use "," (and use ". " for block separators) + (","), + # Translators: options for decimal separator -- "Custom" = user sets it + # Currently there is no UI for how it is done yet, but eventually there will be a dialog that pops up to set it + _("Custom"), + ] + self._choiceDecimalSeparator: wx.Choice = wx.Choice( + self._panelSpeech, + wx.ID_ANY, + wx.DefaultPosition, + wx.DefaultSize, + decimalSeparatorChoices, + 0, + ) + self._choiceDecimalSeparator.SetSelection(0) + bSizerDecimalSeparator.Add(self._choiceDecimalSeparator, 0, wx.ALL, 5) + + bSizerSpeech.Add(bSizerDecimalSeparator, 1, wx.EXPAND, 5) + + bSizerSpeechStyle: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) + + self._staticTextSpeechStyle: wx.StaticText = wx.StaticText( + self._panelSpeech, + wx.ID_ANY, + # Translators: label for pull down allowing users to choose the "style" (version, rules) of speech for math + _("Speech style:"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + self._staticTextSpeechStyle.Wrap(-1) + + bSizerSpeechStyle.Add(self._staticTextSpeechStyle, 0, wx.ALL, 5) + + speechStyleChoices: list[str] = ["xxxxxxxxxxxxxxxx"] + self._choiceSpeechStyle: wx.Choice = wx.Choice( + self._panelSpeech, + wx.ID_ANY, + wx.DefaultPosition, + wx.DefaultSize, + speechStyleChoices, + 0, + ) + self._choiceSpeechStyle.SetSelection(0) + bSizerSpeechStyle.Add(self._choiceSpeechStyle, 0, wx.ALL, 5) + + bSizerSpeech.Add(bSizerSpeechStyle, 1, wx.EXPAND, 5) + + bSizerSpeechAmount: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) + + self._staticTextSpeechAmount: wx.StaticText = wx.StaticText( + self._panelSpeech, + wx.ID_ANY, + # Translators: label for pull down to specify how verbose/terse the speech should be + _("Speech verbosity:"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + self._staticTextSpeechAmount.Wrap(-1) + + bSizerSpeechAmount.Add(self._staticTextSpeechAmount, 0, wx.ALL, 5) + + # Translators: options for speech verbosity. + speechAmountChoices: list[str] = [ + # Translators: options for speech verbosity -- "terse" = use less words + _("Terse"), + # Translators: options for speech verbosity -- "medium" = try to be nether too terse nor too verbose words + _("Medium"), + # Translators: options for speech verbosity -- "verbose" = use more words + _("Verbose"), + ] + self._choiceSpeechAmount: wx.Choice = wx.Choice( + self._panelSpeech, + wx.ID_ANY, + wx.DefaultPosition, + wx.DefaultSize, + speechAmountChoices, + 0, + ) + self._choiceSpeechAmount.SetSelection(0) + bSizerSpeechAmount.Add(self._choiceSpeechAmount, 0, wx.ALL, 5) + + bSizerSpeech.Add(bSizerSpeechAmount, 1, wx.EXPAND, 5) + + bSizerRelativeSpeed: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) + + self._staticTextRelativeSpeed: wx.StaticText = wx.StaticText( + self._panelSpeech, + wx.ID_ANY, + # Translators: label for slider that specifies a percentage of the normal speech rate that should be used for math + _("Relative speech rate:"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + self._staticTextRelativeSpeed.Wrap(-1) + + bSizerRelativeSpeed.Add(self._staticTextRelativeSpeed, 0, wx.ALL, 5) + + self._sliderRelativeSpeed: wx.Slider = wx.Slider( + self._panelSpeech, + wx.ID_ANY, + 100, + 10, + 100, + wx.DefaultPosition, + wx.DefaultSize, + wx.SL_HORIZONTAL, + ) + self._sliderRelativeSpeed.SetLineSize(9) + bSizerRelativeSpeed.Add(self._sliderRelativeSpeed, 0, wx.ALL, 5) + + bSizerSpeech.Add(bSizerRelativeSpeed, 1, wx.EXPAND, 5) + + bSizerPauseFactor: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) + + self._staticPauseFactor: wx.StaticText = wx.StaticText( + self._panelSpeech, + wx.ID_ANY, + # Translators: label for slider that specifies relative factor to increase or decrease pauses in the math speech + _("Pause factor:"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + self._staticPauseFactor.Wrap(-1) + + bSizerPauseFactor.Add(self._staticPauseFactor, 0, wx.ALL, 5) + + self._sliderPauseFactor: wx.Slider = wx.Slider( + self._panelSpeech, + wx.ID_ANY, + 7, + 0, + 14, + wx.DefaultPosition, + wx.DefaultSize, + wx.SL_HORIZONTAL, + ) + bSizerPauseFactor.Add(self._sliderPauseFactor, 0, wx.ALL, 5) + + bSizerSpeech.Add(bSizerPauseFactor, 1, wx.EXPAND, 5) + + bSizerSpeechSound: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) + + self._checkBoxSpeechSound: wx.CheckBox = wx.CheckBox( + self._panelSpeech, + wx.ID_ANY, + # Translators: label for check box controling a beep sound + _("Make a sound when starting/ending math speech"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + bSizerSpeechSound.Add(self._checkBoxSpeechSound, 0, wx.ALL, 5) + + bSizerSpeech.Add(bSizerSpeechSound, 1, wx.EXPAND, 5) + + bSizerSubjectArea: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) + + self._staticTextSubjectArea: wx.StaticText = wx.StaticText( + self._panelSpeech, + wx.ID_ANY, + # Translators: label for pull down to specify a subject area (Geometry, Calculus, ...) + _("Subject area to be used when it cannot be determined automatically:"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + self._staticTextSubjectArea.Wrap(-1) + + bSizerSubjectArea.Add(self._staticTextSubjectArea, 0, wx.ALL, 5) + + # Translators: a generic (non-specific) math subject area + subjectAreaChoices: list[str] = [_("General")] + self._choiceSubjectArea: wx.Choice = wx.Choice( + self._panelSpeech, + wx.ID_ANY, + wx.DefaultPosition, + wx.DefaultSize, + subjectAreaChoices, + 0, + ) + self._choiceSubjectArea.SetSelection(0) + bSizerSubjectArea.Add(self._choiceSubjectArea, 0, wx.ALL, 5) + + bSizerSpeech.Add(bSizerSubjectArea, 1, wx.EXPAND, 5) + + bSizerSpeechForChemical: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) + + self._staticTextSpeechForChemical: wx.StaticText = wx.StaticText( + self._panelSpeech, + wx.ID_ANY, + # Translators: label for pull down to specify how verbose/terse the speech should be + _("Speech for chemical formulas:"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + self._staticTextSpeechForChemical.Wrap(-1) + + bSizerSpeechForChemical.Add(self._staticTextSpeechForChemical, 0, wx.ALL, 5) + + speechForChemicalChoices: list[str] = [ + # Translators: values for chemistry options with example speech in parenthesis + _("Spell it out (H 2 O)"), + # Translators: values for chemistry options with example speech in parenthesis (never interpret as chemistry) + _("Off (H sub 2 O)"), + ] + self._choiceSpeechForChemical = wx.Choice( + self._panelSpeech, + wx.ID_ANY, + wx.DefaultPosition, + wx.DefaultSize, + speechForChemicalChoices, + 0, + ) + self._choiceSpeechForChemical.SetSelection(0) + bSizerSpeechForChemical.Add(self._choiceSpeechForChemical, 0, wx.ALL, 5) + + bSizerSpeech.Add(bSizerSpeechForChemical, 1, wx.EXPAND, 5) + + self._panelSpeech.SetSizer(bSizerSpeech) + self._panelSpeech.Layout() + bSizerSpeech.Fit(self._panelSpeech) + self._simplebookPanelsCategories.AddPage(self._panelSpeech, "a page", False) + self._panelNavigation: wx.Panel = wx.Panel( + self._simplebookPanelsCategories, + wx.ID_ANY, + wx.DefaultPosition, + wx.DefaultSize, + wx.BORDER_SIMPLE | wx.TAB_TRAVERSAL, + ) + bSizerNavigation: wx.BoxSizer = wx.BoxSizer(wx.VERTICAL) + + sbSizerNavigationMode: wx.StaticBoxSizer = wx.StaticBoxSizer( + wx.StaticBox( + self._panelNavigation, + wx.ID_ANY, + # Translators: label for pull down to specify one of three modes use to navigate math expressions + _("Navigation mode to use when beginning to navigate an equation:"), + ), + wx.VERTICAL, + ) + + navigationModeChoices: list[str] = [ + # Translators: names of different modes of navigation. "Enhanced" mode understands math structure + _("Enhanced"), + # Translators: "Simple" walks by character expect for things like fractions, roots, and scripts + _("Simple"), + # Translators: "Character" moves around by character, automatically moving into fractions, etc + _("Character"), + ] + self._choiceNavigationMode: wx.Choice = wx.Choice( + sbSizerNavigationMode.GetStaticBox(), + wx.ID_ANY, + wx.DefaultPosition, + wx.DefaultSize, + navigationModeChoices, + 0, + ) + self._choiceNavigationMode.SetSelection(1) + sbSizerNavigationMode.Add(self._choiceNavigationMode, 0, wx.ALL, 5) + + self._checkBoxResetNavigationMode: wx.CheckBox = wx.CheckBox( + sbSizerNavigationMode.GetStaticBox(), + wx.ID_ANY, + # Translators: label for checkbox that controls whether any changes to the navigation mode should be preserved + _("Reset navigation mode on entry to an expression"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + sbSizerNavigationMode.Add(self._checkBoxResetNavigationMode, 0, wx.ALL, 5) + + bSizerNavigation.Add(sbSizerNavigationMode, 1, wx.EXPAND, 5) + + sbSizerNavigationSpeech: wx.StaticBoxSizer = wx.StaticBoxSizer( + wx.StaticBox( + self._panelNavigation, + wx.ID_ANY, + # Translators: label for pull down to specify whether the expression is spoken or described (an overview) + _("Navigation speech to use when beginning to navigate an equation:"), + ), + wx.VERTICAL, + ) + + # Translators: either "Speak" the expression or give a description (overview) of the expression + navigationSpeechChoices: list[str] = [ + # Translators: "Speak" the expression after moving to it + _("Speak"), + # Translators: "Describe" the expression after moving to it ("overview is a synonym") + _("Describe/overview"), + ] + self._choiceNavigationSpeech: wx.Choice = wx.Choice( + sbSizerNavigationSpeech.GetStaticBox(), + wx.ID_ANY, + wx.DefaultPosition, + wx.DefaultSize, + navigationSpeechChoices, + 0, + ) + self._choiceNavigationSpeech.SetSelection(1) + sbSizerNavigationSpeech.Add(self._choiceNavigationSpeech, 0, wx.ALL, 5) + + self._checkBoxResetNavigationSpeech: wx.CheckBox = wx.CheckBox( + sbSizerNavigationSpeech.GetStaticBox(), + wx.ID_ANY, + # Translators: label for checkbox that controls whether any changes to the speak vs overview reading should be ignored + _("Reset navigation speech on entry to an expression"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + self._checkBoxResetNavigationSpeech.SetValue(True) + sbSizerNavigationSpeech.Add(self._checkBoxResetNavigationSpeech, 0, wx.ALL, 5) + + bSizerNavigation.Add(sbSizerNavigationSpeech, 1, wx.EXPAND, 5) + + bSizerNavigationZoom: wx.BoxSizer = wx.BoxSizer(wx.VERTICAL) + + self._checkBoxAutomaticZoom: wx.CheckBox = wx.CheckBox( + self._panelNavigation, + wx.ID_ANY, + # Translators: label for checkbox that controls whether arrow keys move out of fractions, etc., + # or whether you have to manually back out of the fraction, etc. + _("Automatic zoom out of 2D notations"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + bSizerNavigationZoom.Add(self._checkBoxAutomaticZoom, 0, wx.ALL, 5) + + bSizerSpeechAmountNavigation: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) + + self._staticTextSpeechAmountNavigation: wx.StaticText = wx.StaticText( + self._panelNavigation, + wx.ID_ANY, + # Translators: label for pull down to specify whether you want a terse or verbose reading of navigation commands + _("Speech amount for navigation:"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + self._staticTextSpeechAmountNavigation.Wrap(-1) + + bSizerSpeechAmountNavigation.Add(self._staticTextSpeechAmountNavigation, 0, wx.ALL, 5) + + # Translators: options for navigation verbosity. + speechAmountNavigationChoices: list[str] = [ + # Translators: options for navigation verbosity -- "terse" = use less words + _("Terse"), + # Translators: options for navigation verbosity -- "medium" = try to be nether too terse nor too verbose words + _("Medium"), + # Translators: options for navigation verbosity -- "verbose" = use more words + _("Verbose"), + ] + self._choiceSpeechAmountNavigation: wx.Choice = wx.Choice( + self._panelNavigation, + wx.ID_ANY, + wx.DefaultPosition, + wx.DefaultSize, + speechAmountNavigationChoices, + 0, + ) + self._choiceSpeechAmountNavigation.SetSelection(0) + bSizerSpeechAmountNavigation.Add(self._choiceSpeechAmountNavigation, 0, wx.ALL, 5) + + bSizerNavigationZoom.Add(bSizerSpeechAmountNavigation, 1, wx.EXPAND, 5) + + bSizerNavigation.Add(bSizerNavigationZoom, 1, wx.EXPAND, 5) + + bSizerCopyAs: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) + + self._staticTextCopyMathAs: wx.StaticText = wx.StaticText( + self._panelNavigation, + wx.ID_ANY, + # Translators: label for pull down to specify how math will be copied to the clipboard + _("Copy math as:"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + self._staticTextCopyMathAs.Wrap(-1) + + bSizerCopyAs.Add(self._staticTextCopyMathAs, 0, wx.ALL, 5) + + # Translators: options for copy math as. + copyAsChoices: list[str] = [ + # Translators: options for Copy expression to clipboard as -- "MathML" + _("MathML"), + # Translators: options for Copy expression to clipboard as -- "LaTeX" + _("LaTeX"), + # Translators: options for Copy expression to clipboard as -- "ASCIIMath" + _("ASCIIMath"), + # Translators: options for Copy expression to clipboard as -- speech text + _("Speech"), + ] + self._choiceCopyAs: wx.Choice = wx.Choice( + self._panelNavigation, + wx.ID_ANY, + wx.DefaultPosition, + wx.DefaultSize, + copyAsChoices, + 0, + ) + self._choiceCopyAs.SetSelection(0) + bSizerCopyAs.Add(self._choiceCopyAs, 0, wx.ALL, 5) + + bSizerNavigation.Add(bSizerCopyAs, 1, wx.EXPAND, 5) + + self._panelNavigation.SetSizer(bSizerNavigation) + self._panelNavigation.Layout() + bSizerNavigation.Fit(self._panelNavigation) + self._simplebookPanelsCategories.AddPage( + self._panelNavigation, + "a page", + False, + ) + self._panelBraille: wx.Panel = wx.Panel( + self._simplebookPanelsCategories, + wx.ID_ANY, + wx.DefaultPosition, + wx.DefaultSize, + wx.BORDER_SIMPLE | wx.TAB_TRAVERSAL, + ) + bSizerBraille = wx.BoxSizer(wx.VERTICAL) + + bSizerBrailleMathCode: wx.BoxSizer = wx.BoxSizer(wx.HORIZONTAL) + + self._staticTextBrailleMathCode: wx.StaticText = wx.StaticText( + self._panelBraille, + wx.ID_ANY, + # Translators: label for pull down to specify which braille code to use + _("Braille math code for refreshable displays:"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + self._staticTextBrailleMathCode.Wrap(-1) + + bSizerBrailleMathCode.Add(self._staticTextBrailleMathCode, 0, wx.ALL, 5) + brailleMathCodeChoices: list[str] = ["xxxxxxxxxxx"] + self._choiceBrailleMathCode: wx.Choice = wx.Choice( + self._panelBraille, + wx.ID_ANY, + wx.DefaultPosition, + wx.DefaultSize, + brailleMathCodeChoices, + 0, + ) + self._choiceBrailleMathCode.SetSelection(1) + bSizerBrailleMathCode.Add(self._choiceBrailleMathCode, 0, wx.ALL, 5) + + bSizerBraille.Add(bSizerBrailleMathCode, 1, wx.EXPAND, 5) + + bSizerBrailleHighlights = wx.BoxSizer(wx.HORIZONTAL) + + self._staticTextBrailleHighlights: wx.StaticText = wx.StaticText( + self._panelBraille, + wx.ID_ANY, + # Translators: label for pull down to specify how braille dots should be modified when navigating/selecting subexprs + _("Highlight with dots 7 && 8 the current nav node:"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + self._staticTextBrailleHighlights.Wrap(-1) + + bSizerBrailleHighlights.Add(self._staticTextBrailleHighlights, 0, wx.ALL, 5) + + brailleHighlightsChoices: list[str] = [ + # Translators: options for using dots 7 and 8: + # Translators: "off" -- don't highlight + _("Off"), + # Translators: "First character" -- only the first character of the current navigation node uses dots 7 & 8 + _("First character"), + # Translators: "Endpoints" -- only the first and last character of the current navigation node uses dots 7 & 8 + _("Endpoints"), + # Translators: "All" -- all the characters for the current navigation node use dots 7 & 8 + _("All"), + ] + self._choiceBrailleHighlights: wx.Choice = wx.Choice( + self._panelBraille, + wx.ID_ANY, + wx.DefaultPosition, + wx.DefaultSize, + brailleHighlightsChoices, + 0, + ) + self._choiceBrailleHighlights.SetSelection(1) + bSizerBrailleHighlights.Add(self._choiceBrailleHighlights, 0, wx.ALL, 5) + + bSizerBraille.Add(bSizerBrailleHighlights, 1, wx.EXPAND, 5) + + bSizerBraille.Add((0, 0), 1, wx.EXPAND, 5) + + bSizerBraille.Add((0, 0), 1, wx.EXPAND, 5) + + bSizerBraille.Add((0, 0), 1, wx.EXPAND, 5) + + bSizerBraille.Add((0, 0), 1, wx.EXPAND, 5) + + bSizerBraille.Add((0, 0), 1, wx.EXPAND, 5) + + self._panelBraille.SetSizer(bSizerBraille) + self._panelBraille.Layout() + bSizerBraille.Fit(self._panelBraille) + self._simplebookPanelsCategories.AddPage(self._panelBraille, "a page", False) + + gbSizerMathCATPreferences.Add( + self._simplebookPanelsCategories, + wx.GBPosition(0, 1), + wx.GBSpan(1, 1), + wx.EXPAND | wx.ALL, + 10, + ) + + self._staticlineAboveButtons: wx.StaticLine = wx.StaticLine( + self, + wx.ID_ANY, + wx.DefaultPosition, + wx.DefaultSize, + wx.LI_HORIZONTAL, + ) + gbSizerMathCATPreferences.Add( + self._staticlineAboveButtons, + wx.GBPosition(1, 0), + wx.GBSpan(1, 2), + wx.EXPAND | wx.ALL, + 5, + ) + + self._panelButtons: wx.Panel = wx.Panel(self, wx.ID_ANY, wx.Point(-1, -1), wx.DefaultSize, 0) + bSizerButtons = wx.BoxSizer(wx.HORIZONTAL) + + bSizerButtons.Add((0, 0), 1, wx.EXPAND, 5) + + self._buttonOK: wx.Button = wx.Button( + self._panelButtons, + wx.ID_ANY, + # Translators: dialog "ok" button + _("OK"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + bSizerButtons.Add(self._buttonOK, 0, wx.ALL, 5) + + self._buttonCancel: wx.Button = wx.Button( + self._panelButtons, + wx.ID_ANY, + # Translators: dialog "cancel" button + _("Cancel"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + bSizerButtons.Add(self._buttonCancel, 0, wx.ALL, 5) + + self._buttonApply: wx.Button = wx.Button( + self._panelButtons, + wx.ID_ANY, + # Translators: dialog "apply" button + _("Apply"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + bSizerButtons.Add(self._buttonApply, 0, wx.ALL, 5) + + self._buttonReset: wx.Button = wx.Button( + self._panelButtons, + wx.ID_ANY, + # Translators: button to reset all the preferences to their default values + _("Reset to defaults"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + bSizerButtons.Add(self._buttonReset, 0, wx.ALL, 5) + + self._buttonHelp: wx.Button = wx.Button( + self._panelButtons, + wx.ID_ANY, + # Translators: button to bring up a help page + _("Help"), + wx.DefaultPosition, + wx.DefaultSize, + 0, + ) + bSizerButtons.Add(self._buttonHelp, 0, wx.ALL, 5) + + self._panelButtons.SetSizer(bSizerButtons) + self._panelButtons.Layout() + bSizerButtons.Fit(self._panelButtons) + gbSizerMathCATPreferences.Add( + self._panelButtons, + wx.GBPosition(2, 1), + wx.GBSpan(1, 2), + wx.EXPAND | wx.ALL, + 5, + ) + + self.SetSizer(gbSizerMathCATPreferences) self.Layout() - gbSizerMathCATPreferences.Fit( self ) + gbSizerMathCATPreferences.Fit(self) - self.Centre( wx.BOTH ) + self.Centre(wx.BOTH) # Connect Events - self.Bind( wx.EVT_CHAR_HOOK, self.MathCATPreferencesDialogOnCharHook ) - self.Bind( wx.EVT_KEY_UP, self.MathCATPreferencesDialogOnKeyUp ) - self.m_listBoxPreferencesTopic.Bind( wx.EVT_LISTBOX, self.OnListBoxCategories ) - self.m_choiceLanguage.Bind( wx.EVT_CHOICE, self.OnLanguage ) - self.m_sliderRelativeSpeed.Bind( wx.EVT_SCROLL_CHANGED, self.OnRelativeSpeedChanged ) - self.m_sliderPauseFactor.Bind( wx.EVT_SCROLL_CHANGED, self.OnPauseFactorChanged ) - self.m_buttonOK.Bind( wx.EVT_BUTTON, self.OnClickOK ) - self.m_buttonCancel.Bind( wx.EVT_BUTTON, self.OnClickCancel ) - self.m_buttonApply.Bind( wx.EVT_BUTTON, self.OnClickApply ) - self.m_buttonReset.Bind( wx.EVT_BUTTON, self.OnClickReset ) - self.m_buttonHelp.Bind( wx.EVT_BUTTON, self.OnClickHelp ) - - def __del__( self ): + self.Bind(wx.EVT_CHAR_HOOK, self.mathCATPreferencesDialogOnCharHook) + self.Bind(wx.EVT_KEY_UP, self.mathCATPreferencesDialogOnKeyUp) + self._listBoxPreferencesTopic.Bind(wx.EVT_LISTBOX, self.onListBoxCategories) + self._choiceLanguage.Bind(wx.EVT_CHOICE, self.onLanguage) + self._sliderRelativeSpeed.Bind( + wx.EVT_SCROLL_CHANGED, + self.onRelativeSpeedChanged, + ) + self._sliderPauseFactor.Bind(wx.EVT_SCROLL_CHANGED, self.onPauseFactorChanged) + self._buttonOK.Bind(wx.EVT_BUTTON, self.onClickOK) + self._buttonCancel.Bind(wx.EVT_BUTTON, self.onClickCancel) + self._buttonApply.Bind(wx.EVT_BUTTON, self.onClickApply) + self._buttonReset.Bind(wx.EVT_BUTTON, self.onClickReset) + self._buttonHelp.Bind(wx.EVT_BUTTON, self.onClickHelp) + + def __del__(self): + """Destructor placeholder; override if cleanup is needed.""" pass - # Virtual event handlers, override them in your derived class - def MathCATPreferencesDialogOnCharHook( self, event ): + def mathCATPreferencesDialogOnCharHook(self, event: wx.KeyEvent) -> None: + """Handle character input events; override in subclass as needed.""" event.Skip() - def MathCATPreferencesDialogOnKeyUp( self, event ): + def mathCATPreferencesDialogOnKeyUp(self, event: wx.KeyEvent) -> None: + """Handle key release events; override in subclass as needed.""" event.Skip() - def OnListBoxCategories( self, event ): + def onListBoxCategories(self, event: wx.CommandEvent) -> None: + """Handle selection events in the categories list box; override in subclass as needed.""" event.Skip() - def OnLanguage( self, event ): + def onLanguage(self, event: wx.CommandEvent) -> None: + """Handle language selection; override in subclass as needed.""" event.Skip() - def OnRelativeSpeedChanged( self, event ): + def onRelativeSpeedChanged(self, event: wx.ScrollEvent) -> None: + """Handle change in relative speed; override in subclass as needed.""" event.Skip() - def OnPauseFactorChanged( self, event ): + def onPauseFactorChanged(self, event: wx.ScrollEvent) -> None: + """Handle change in pause factor; override in subclass as needed.""" event.Skip() - def OnClickOK( self, event ): + def onClickOK(self, event: wx.CommandEvent) -> None: + """Handle OK button click; override in subclass as needed.""" event.Skip() - def OnClickCancel( self, event ): + def onClickCancel(self, event: wx.CommandEvent) -> None: + """Handle Cancel button click; override in subclass as needed.""" event.Skip() - def OnClickApply( self, event ): + def onClickApply(self, event: wx.CommandEvent) -> None: + """Handle Apply button click; override in subclass as needed.""" event.Skip() - def OnClickReset( self, event ): + def onClickReset(self, event: wx.CommandEvent) -> None: + """Handle Reset button click; override in subclass as needed.""" event.Skip() - def OnClickHelp( self, event ): + def onClickHelp(self, event: wx.CommandEvent) -> None: + """Handle Help button click; override in subclass as needed.""" event.Skip() - - diff --git a/addon/globalPlugins/MathCAT/__init__.py b/addon/globalPlugins/MathCAT/__init__.py index 633d96a0..4bc4da43 100644 --- a/addon/globalPlugins/MathCAT/__init__.py +++ b/addon/globalPlugins/MathCAT/__init__.py @@ -1,49 +1,71 @@ # -*- coding: UTF-8 -*- -# MathCAT add-on: generates speech, braille, and allows exploration of expressions written in MathML -# The goal of this add-on is to replicate/improve upon the functionality of MathPlayer which has been discontinued. -# Author: Neil Soiffer -# Copyright: this file is copyright GPL2 -# The code additionally makes use of the MathCAT library (written in Rust) which is covered by the MIT license -# and also (obviously) requires external speech engines and braille drivers. -# The plugin also requires the use of a small python dll: python3.dll -# python3.dll has "Copyright © 2001-2022 Python Software Foundation; All Rights Reserved" +""" +MathCAT add-on: generates speech, braille, and allows exploration of expressions written in MathML. +The goal of this add-on is to replicate/improve upon the functionality of MathPlayer which has been discontinued. +Author: Neil Soiffer +Copyright: this file is copyright GPL2 + The code additionally makes use of the MathCAT library (written in Rust) which is covered by the MIT license + and also (obviously) requires external speech engines and braille drivers. + The plugin also requires the use of a small python dll: python3.dll + python3.dll has "Copyright © 2001-2022 Python Software Foundation; All Rights Reserved +""" - -import globalPluginHandler # we are a global plugin +import globalPluginHandler # we are a global plugin import globalVars -from logHandler import log # logging -import mathPres # math plugin stuff -from gui import mainFrame +import mathPres # math plugin stuff import wx - import addonHandler -addonHandler.initTranslation() - +from gui import mainFrame from .MathCAT import MathCAT from .MathCATPreferences import UserInterface +# Import the _ function for translation +_ = wx.GetTranslation +addonHandler.initTranslation() mathPres.registerProvider(MathCAT(), speech=True, braille=True, interaction=True) + class GlobalPlugin(globalPluginHandler.GlobalPlugin): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - # MathCAT.__init__(self) - self.add_MathCAT_menu() - - def add_MathCAT_menu(self): - if not globalVars.appArgs.secure: - self.preferencesMenu = mainFrame.sysTrayIcon.preferencesMenu - self.settings = self.preferencesMenu.Append(wx.ID_ANY, _("&MathCAT Settings...")) - mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.on_settings, self.settings) - - def on_settings(self, evt): - mainFrame.popupSettingsDialog(UserInterface) - - def terminate(self): - try: - if not globalVars.appArgs.secure: - self.preferencesMenu.Remove(self.settings) - except (AttributeError, RuntimeError): - pass + """ + Global plugin for the MathCAT add-on. + """ + + def __init__(self, *args, **kwargs): + """ + Initialize the Global Plugin and add the MathCAT menu. + + :param args: Additional positional arguments. + :param kwargs: Additional keyword arguments. + """ + super().__init__(*args, **kwargs) + # MathCAT.__init__(self) + self.addMathCATMenu() + + def addMathCATMenu(self) -> None: + """ + Adds the MathCAT settings menu to the NVDA preferences. + """ + if not globalVars.appArgs.secure: + self.preferencesMenu = mainFrame.sysTrayIcon.preferencesMenu + # Translators: this show up in the NVDA preferences dialog. It opens the MathCAT preferences dialog + self.settings = self.preferencesMenu.Append(wx.ID_ANY, _("&MathCAT Settings...")) + mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.onSettings, self.settings) + + def onSettings(self, evt: wx.CommandEvent) -> None: + """ + Opens the MathCAT preferences dialog. + + :param evt: The event that triggered this action. + """ + mainFrame.popupSettingsDialog(UserInterface) + def terminate(self) -> None: + """ + Cleans up by removing the MathCAT menu item upon termination. + """ + try: + if not globalVars.appArgs.secure: + self.preferencesMenu.Remove(self.settings) + except (AttributeError, RuntimeError): + pass diff --git a/addon/globalPlugins/MathCAT/libmathcat_py.pyd b/addon/globalPlugins/MathCAT/libmathcat_py.pyd new file mode 100644 index 00000000..df703f63 Binary files /dev/null and b/addon/globalPlugins/MathCAT/libmathcat_py.pyd differ diff --git a/addon/globalPlugins/MathCAT/yaml/__init__.py b/addon/globalPlugins/MathCAT/yaml/__init__.py index 465041dc..87271d0a 100644 --- a/addon/globalPlugins/MathCAT/yaml/__init__.py +++ b/addon/globalPlugins/MathCAT/yaml/__init__.py @@ -8,7 +8,7 @@ from .loader import * from .dumper import * -__version__ = '6.0' +__version__ = '6.0.2' try: from .cyaml import * __with_libyaml__ = True @@ -154,9 +154,11 @@ def unsafe_load_all(stream): """ return load_all(stream, UnsafeLoader) -def emit(events, stream=None, Dumper=Dumper, - canonical=None, indent=None, width=None, - allow_unicode=None, line_break=None): +def emit( + events, stream=None, Dumper=Dumper, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, +): """ Emit YAML parsing events into a stream. If stream is None, return the produced string instead. @@ -165,8 +167,10 @@ def emit(events, stream=None, Dumper=Dumper, if stream is None: stream = io.StringIO() getvalue = stream.getvalue - dumper = Dumper(stream, canonical=canonical, indent=indent, width=width, - allow_unicode=allow_unicode, line_break=line_break) + dumper = Dumper( + stream, canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break, + ) try: for event in events: dumper.emit(event) @@ -175,11 +179,13 @@ def emit(events, stream=None, Dumper=Dumper, if getvalue: return getvalue() -def serialize_all(nodes, stream=None, Dumper=Dumper, - canonical=None, indent=None, width=None, - allow_unicode=None, line_break=None, - encoding=None, explicit_start=None, explicit_end=None, - version=None, tags=None): +def serialize_all( + nodes, stream=None, Dumper=Dumper, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, +): """ Serialize a sequence of representation trees into a YAML stream. If stream is None, return the produced string instead. @@ -191,10 +197,12 @@ def serialize_all(nodes, stream=None, Dumper=Dumper, else: stream = io.BytesIO() getvalue = stream.getvalue - dumper = Dumper(stream, canonical=canonical, indent=indent, width=width, - allow_unicode=allow_unicode, line_break=line_break, - encoding=encoding, version=version, tags=tags, - explicit_start=explicit_start, explicit_end=explicit_end) + dumper = Dumper( + stream, canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break, + encoding=encoding, version=version, tags=tags, + explicit_start=explicit_start, explicit_end=explicit_end, + ) try: dumper.open() for node in nodes: @@ -212,12 +220,14 @@ def serialize(node, stream=None, Dumper=Dumper, **kwds): """ return serialize_all([node], stream, Dumper=Dumper, **kwds) -def dump_all(documents, stream=None, Dumper=Dumper, - default_style=None, default_flow_style=False, - canonical=None, indent=None, width=None, - allow_unicode=None, line_break=None, - encoding=None, explicit_start=None, explicit_end=None, - version=None, tags=None, sort_keys=True): +def dump_all( + documents, stream=None, Dumper=Dumper, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True, +): """ Serialize a sequence of Python objects into a YAML stream. If stream is None, return the produced string instead. @@ -229,12 +239,14 @@ def dump_all(documents, stream=None, Dumper=Dumper, else: stream = io.BytesIO() getvalue = stream.getvalue - dumper = Dumper(stream, default_style=default_style, - default_flow_style=default_flow_style, - canonical=canonical, indent=indent, width=width, - allow_unicode=allow_unicode, line_break=line_break, - encoding=encoding, version=version, tags=tags, - explicit_start=explicit_start, explicit_end=explicit_end, sort_keys=sort_keys) + dumper = Dumper( + stream, default_style=default_style, + default_flow_style=default_flow_style, + canonical=canonical, indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break, + encoding=encoding, version=version, tags=tags, + explicit_start=explicit_start, explicit_end=explicit_end, sort_keys=sort_keys, + ) try: dumper.open() for data in documents: @@ -268,8 +280,10 @@ def safe_dump(data, stream=None, **kwds): """ return dump_all([data], stream, Dumper=SafeDumper, **kwds) -def add_implicit_resolver(tag, regexp, first=None, - Loader=None, Dumper=Dumper): +def add_implicit_resolver( + tag, regexp, first=None, + Loader=None, Dumper=Dumper, +): """ Add an implicit scalar detector. If an implicit scalar value matches the given regexp, @@ -385,6 +399,7 @@ def to_yaml(cls, dumper, data): """ Convert a Python object to a representation node. """ - return dumper.represent_yaml_object(cls.yaml_tag, data, cls, - flow_style=cls.yaml_flow_style) - + return dumper.represent_yaml_object( + cls.yaml_tag, data, cls, + flow_style=cls.yaml_flow_style, + ) diff --git a/addon/globalPlugins/MathCAT/yaml/_yaml.cp312-win32.pyd b/addon/globalPlugins/MathCAT/yaml/_yaml.cp312-win32.pyd new file mode 100644 index 00000000..d8e2cc57 Binary files /dev/null and b/addon/globalPlugins/MathCAT/yaml/_yaml.cp312-win32.pyd differ diff --git a/addon/globalPlugins/MathCAT/yaml/_yaml.cp37-win32.pyd b/addon/globalPlugins/MathCAT/yaml/_yaml.cp37-win32.pyd deleted file mode 100644 index a03984a1..00000000 Binary files a/addon/globalPlugins/MathCAT/yaml/_yaml.cp37-win32.pyd and /dev/null differ diff --git a/addon/globalPlugins/MathCAT/yaml/composer.py b/addon/globalPlugins/MathCAT/yaml/composer.py index 6d15cb40..1f389f41 100644 --- a/addon/globalPlugins/MathCAT/yaml/composer.py +++ b/addon/globalPlugins/MathCAT/yaml/composer.py @@ -38,9 +38,11 @@ def get_single_node(self): # Ensure that the stream contains no more documents. if not self.check_event(StreamEndEvent): event = self.get_event() - raise ComposerError("expected a single document in the stream", - document.start_mark, "but found another document", - event.start_mark) + raise ComposerError( + "expected a single document in the stream", + document.start_mark, "but found another document", + event.start_mark, + ) # Drop the STREAM-END event. self.get_event() @@ -65,16 +67,20 @@ def compose_node(self, parent, index): event = self.get_event() anchor = event.anchor if anchor not in self.anchors: - raise ComposerError(None, None, "found undefined alias %r" - % anchor, event.start_mark) + raise ComposerError( + None, None, "found undefined alias %r" + % anchor, event.start_mark, + ) return self.anchors[anchor] event = self.peek_event() anchor = event.anchor if anchor is not None: if anchor in self.anchors: - raise ComposerError("found duplicate anchor %r; first occurrence" - % anchor, self.anchors[anchor].start_mark, - "second occurrence", event.start_mark) + raise ComposerError( + "found duplicate anchor %r; first occurrence" + % anchor, self.anchors[anchor].start_mark, + "second occurrence", event.start_mark, + ) self.descend_resolver(parent, index) if self.check_event(ScalarEvent): node = self.compose_scalar_node(anchor) @@ -90,8 +96,10 @@ def compose_scalar_node(self, anchor): tag = event.tag if tag is None or tag == '!': tag = self.resolve(ScalarNode, event.value, event.implicit) - node = ScalarNode(tag, event.value, - event.start_mark, event.end_mark, style=event.style) + node = ScalarNode( + tag, event.value, + event.start_mark, event.end_mark, style=event.style, + ) if anchor is not None: self.anchors[anchor] = node return node @@ -101,9 +109,11 @@ def compose_sequence_node(self, anchor): tag = start_event.tag if tag is None or tag == '!': tag = self.resolve(SequenceNode, None, start_event.implicit) - node = SequenceNode(tag, [], - start_event.start_mark, None, - flow_style=start_event.flow_style) + node = SequenceNode( + tag, [], + start_event.start_mark, None, + flow_style=start_event.flow_style, + ) if anchor is not None: self.anchors[anchor] = node index = 0 @@ -119,9 +129,11 @@ def compose_mapping_node(self, anchor): tag = start_event.tag if tag is None or tag == '!': tag = self.resolve(MappingNode, None, start_event.implicit) - node = MappingNode(tag, [], - start_event.start_mark, None, - flow_style=start_event.flow_style) + node = MappingNode( + tag, [], + start_event.start_mark, None, + flow_style=start_event.flow_style, + ) if anchor is not None: self.anchors[anchor] = node while not self.check_event(MappingEndEvent): @@ -136,4 +148,3 @@ def compose_mapping_node(self, anchor): end_event = self.get_event() node.end_mark = end_event.end_mark return node - diff --git a/addon/globalPlugins/MathCAT/yaml/constructor.py b/addon/globalPlugins/MathCAT/yaml/constructor.py index 619acd30..5bb98137 100644 --- a/addon/globalPlugins/MathCAT/yaml/constructor.py +++ b/addon/globalPlugins/MathCAT/yaml/constructor.py @@ -5,7 +5,7 @@ 'FullConstructor', 'UnsafeConstructor', 'Constructor', - 'ConstructorError' + 'ConstructorError', ] from .error import * @@ -36,8 +36,10 @@ def check_state_key(self, key): object, to prevent user-controlled methods from being called during deserialization""" if self.get_state_keys_blacklist_regexp().match(key): - raise ConstructorError(None, None, - "blacklisted key '%s' in instance state found" % (key,), None) + raise ConstructorError( + None, None, + "blacklisted key '%s' in instance state found" % (key,), None, + ) def get_data(self): # Construct and return the next document. @@ -71,8 +73,10 @@ def construct_object(self, node, deep=False): old_deep = self.deep_construct self.deep_construct = True if node in self.recursive_objects: - raise ConstructorError(None, None, - "found unconstructable recursive node", node.start_mark) + raise ConstructorError( + None, None, + "found unconstructable recursive node", node.start_mark, + ) self.recursive_objects[node] = None constructor = None tag_suffix = None @@ -116,39 +120,51 @@ def construct_object(self, node, deep=False): def construct_scalar(self, node): if not isinstance(node, ScalarNode): - raise ConstructorError(None, None, - "expected a scalar node, but found %s" % node.id, - node.start_mark) + raise ConstructorError( + None, None, + "expected a scalar node, but found %s" % node.id, + node.start_mark, + ) return node.value def construct_sequence(self, node, deep=False): if not isinstance(node, SequenceNode): - raise ConstructorError(None, None, - "expected a sequence node, but found %s" % node.id, - node.start_mark) - return [self.construct_object(child, deep=deep) - for child in node.value] + raise ConstructorError( + None, None, + "expected a sequence node, but found %s" % node.id, + node.start_mark, + ) + return [ + self.construct_object(child, deep=deep) + for child in node.value + ] def construct_mapping(self, node, deep=False): if not isinstance(node, MappingNode): - raise ConstructorError(None, None, - "expected a mapping node, but found %s" % node.id, - node.start_mark) + raise ConstructorError( + None, None, + "expected a mapping node, but found %s" % node.id, + node.start_mark, + ) mapping = {} for key_node, value_node in node.value: key = self.construct_object(key_node, deep=deep) if not isinstance(key, collections.abc.Hashable): - raise ConstructorError("while constructing a mapping", node.start_mark, - "found unhashable key", key_node.start_mark) + raise ConstructorError( + "while constructing a mapping", node.start_mark, + "found unhashable key", key_node.start_mark, + ) value = self.construct_object(value_node, deep=deep) mapping[key] = value return mapping def construct_pairs(self, node, deep=False): if not isinstance(node, MappingNode): - raise ConstructorError(None, None, - "expected a mapping node, but found %s" % node.id, - node.start_mark) + raise ConstructorError( + None, None, + "expected a mapping node, but found %s" % node.id, + node.start_mark, + ) pairs = [] for key_node, value_node in node.value: key = self.construct_object(key_node, deep=deep) @@ -191,19 +207,23 @@ def flatten_mapping(self, node): submerge = [] for subnode in value_node.value: if not isinstance(subnode, MappingNode): - raise ConstructorError("while constructing a mapping", - node.start_mark, - "expected a mapping for merging, but found %s" - % subnode.id, subnode.start_mark) + raise ConstructorError( + "while constructing a mapping", + node.start_mark, + "expected a mapping for merging, but found %s" + % subnode.id, subnode.start_mark, + ) self.flatten_mapping(subnode) submerge.append(subnode.value) submerge.reverse() for value in submerge: merge.extend(value) else: - raise ConstructorError("while constructing a mapping", node.start_mark, - "expected a mapping or list of mappings for merging, but found %s" - % value_node.id, value_node.start_mark) + raise ConstructorError( + "while constructing a mapping", node.start_mark, + "expected a mapping or list of mappings for merging, but found %s" + % value_node.id, value_node.start_mark, + ) elif key_node.tag == 'tag:yaml.org,2002:value': key_node.tag = 'tag:yaml.org,2002:str' index += 1 @@ -295,17 +315,21 @@ def construct_yaml_binary(self, node): try: value = self.construct_scalar(node).encode('ascii') except UnicodeEncodeError as exc: - raise ConstructorError(None, None, - "failed to convert base64 data into ascii: %s" % exc, - node.start_mark) + raise ConstructorError( + None, None, + "failed to convert base64 data into ascii: %s" % exc, + node.start_mark, + ) try: if hasattr(base64, 'decodebytes'): return base64.decodebytes(value) else: return base64.decodestring(value) except binascii.Error as exc: - raise ConstructorError(None, None, - "failed to decode base64 data: %s" % exc, node.start_mark) + raise ConstructorError( + None, None, + "failed to decode base64 data: %s" % exc, node.start_mark, + ) timestamp_regexp = re.compile( r'''^(?P[0-9][0-9][0-9][0-9]) @@ -317,7 +341,8 @@ def construct_yaml_binary(self, node): :(?P[0-9][0-9]) (?:\.(?P[0-9]*))? (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) - (?::(?P[0-9][0-9]))?))?)?$''', re.X) + (?::(?P[0-9][0-9]))?))?)?$''', re.X, + ) def construct_yaml_timestamp(self, node): value = self.construct_scalar(node) @@ -347,8 +372,10 @@ def construct_yaml_timestamp(self, node): tzinfo = datetime.timezone(delta) elif values['tz']: tzinfo = datetime.timezone.utc - return datetime.datetime(year, month, day, hour, minute, second, fraction, - tzinfo=tzinfo) + return datetime.datetime( + year, month, day, hour, minute, second, fraction, + tzinfo=tzinfo, + ) def construct_yaml_omap(self, node): # Note: we do not check for duplicate keys, because it's too @@ -356,17 +383,23 @@ def construct_yaml_omap(self, node): omap = [] yield omap if not isinstance(node, SequenceNode): - raise ConstructorError("while constructing an ordered map", node.start_mark, - "expected a sequence, but found %s" % node.id, node.start_mark) + raise ConstructorError( + "while constructing an ordered map", node.start_mark, + "expected a sequence, but found %s" % node.id, node.start_mark, + ) for subnode in node.value: if not isinstance(subnode, MappingNode): - raise ConstructorError("while constructing an ordered map", node.start_mark, - "expected a mapping of length 1, but found %s" % subnode.id, - subnode.start_mark) + raise ConstructorError( + "while constructing an ordered map", node.start_mark, + "expected a mapping of length 1, but found %s" % subnode.id, + subnode.start_mark, + ) if len(subnode.value) != 1: - raise ConstructorError("while constructing an ordered map", node.start_mark, - "expected a single mapping item, but found %d items" % len(subnode.value), - subnode.start_mark) + raise ConstructorError( + "while constructing an ordered map", node.start_mark, + "expected a single mapping item, but found %d items" % len(subnode.value), + subnode.start_mark, + ) key_node, value_node = subnode.value[0] key = self.construct_object(key_node) value = self.construct_object(value_node) @@ -377,17 +410,23 @@ def construct_yaml_pairs(self, node): pairs = [] yield pairs if not isinstance(node, SequenceNode): - raise ConstructorError("while constructing pairs", node.start_mark, - "expected a sequence, but found %s" % node.id, node.start_mark) + raise ConstructorError( + "while constructing pairs", node.start_mark, + "expected a sequence, but found %s" % node.id, node.start_mark, + ) for subnode in node.value: if not isinstance(subnode, MappingNode): - raise ConstructorError("while constructing pairs", node.start_mark, - "expected a mapping of length 1, but found %s" % subnode.id, - subnode.start_mark) + raise ConstructorError( + "while constructing pairs", node.start_mark, + "expected a mapping of length 1, but found %s" % subnode.id, + subnode.start_mark, + ) if len(subnode.value) != 1: - raise ConstructorError("while constructing pairs", node.start_mark, - "expected a single mapping item, but found %d items" % len(subnode.value), - subnode.start_mark) + raise ConstructorError( + "while constructing pairs", node.start_mark, + "expected a single mapping item, but found %d items" % len(subnode.value), + subnode.start_mark, + ) key_node, value_node = subnode.value[0] key = self.construct_object(key_node) value = self.construct_object(value_node) @@ -424,60 +463,76 @@ def construct_yaml_object(self, node, cls): data.__dict__.update(state) def construct_undefined(self, node): - raise ConstructorError(None, None, - "could not determine a constructor for the tag %r" % node.tag, - node.start_mark) + raise ConstructorError( + None, None, + "could not determine a constructor for the tag %r" % node.tag, + node.start_mark, + ) SafeConstructor.add_constructor( 'tag:yaml.org,2002:null', - SafeConstructor.construct_yaml_null) + SafeConstructor.construct_yaml_null, +) SafeConstructor.add_constructor( 'tag:yaml.org,2002:bool', - SafeConstructor.construct_yaml_bool) + SafeConstructor.construct_yaml_bool, +) SafeConstructor.add_constructor( 'tag:yaml.org,2002:int', - SafeConstructor.construct_yaml_int) + SafeConstructor.construct_yaml_int, +) SafeConstructor.add_constructor( 'tag:yaml.org,2002:float', - SafeConstructor.construct_yaml_float) + SafeConstructor.construct_yaml_float, +) SafeConstructor.add_constructor( 'tag:yaml.org,2002:binary', - SafeConstructor.construct_yaml_binary) + SafeConstructor.construct_yaml_binary, +) SafeConstructor.add_constructor( 'tag:yaml.org,2002:timestamp', - SafeConstructor.construct_yaml_timestamp) + SafeConstructor.construct_yaml_timestamp, +) SafeConstructor.add_constructor( 'tag:yaml.org,2002:omap', - SafeConstructor.construct_yaml_omap) + SafeConstructor.construct_yaml_omap, +) SafeConstructor.add_constructor( 'tag:yaml.org,2002:pairs', - SafeConstructor.construct_yaml_pairs) + SafeConstructor.construct_yaml_pairs, +) SafeConstructor.add_constructor( 'tag:yaml.org,2002:set', - SafeConstructor.construct_yaml_set) + SafeConstructor.construct_yaml_set, +) SafeConstructor.add_constructor( 'tag:yaml.org,2002:str', - SafeConstructor.construct_yaml_str) + SafeConstructor.construct_yaml_str, +) SafeConstructor.add_constructor( 'tag:yaml.org,2002:seq', - SafeConstructor.construct_yaml_seq) + SafeConstructor.construct_yaml_seq, +) SafeConstructor.add_constructor( 'tag:yaml.org,2002:map', - SafeConstructor.construct_yaml_map) + SafeConstructor.construct_yaml_map, +) -SafeConstructor.add_constructor(None, - SafeConstructor.construct_undefined) +SafeConstructor.add_constructor( + None, + SafeConstructor.construct_undefined, +) class FullConstructor(SafeConstructor): # 'extend' is blacklisted because it is used by @@ -501,17 +556,21 @@ def construct_python_bytes(self, node): try: value = self.construct_scalar(node).encode('ascii') except UnicodeEncodeError as exc: - raise ConstructorError(None, None, - "failed to convert base64 data into ascii: %s" % exc, - node.start_mark) + raise ConstructorError( + None, None, + "failed to convert base64 data into ascii: %s" % exc, + node.start_mark, + ) try: if hasattr(base64, 'decodebytes'): return base64.decodebytes(value) else: return base64.decodestring(value) except binascii.Error as exc: - raise ConstructorError(None, None, - "failed to decode base64 data: %s" % exc, node.start_mark) + raise ConstructorError( + None, None, + "failed to decode base64 data: %s" % exc, node.start_mark, + ) def construct_python_long(self, node): return self.construct_yaml_int(node) @@ -524,23 +583,31 @@ def construct_python_tuple(self, node): def find_python_module(self, name, mark, unsafe=False): if not name: - raise ConstructorError("while constructing a Python module", mark, - "expected non-empty name appended to the tag", mark) + raise ConstructorError( + "while constructing a Python module", mark, + "expected non-empty name appended to the tag", mark, + ) if unsafe: try: __import__(name) except ImportError as exc: - raise ConstructorError("while constructing a Python module", mark, - "cannot find module %r (%s)" % (name, exc), mark) + raise ConstructorError( + "while constructing a Python module", mark, + "cannot find module %r (%s)" % (name, exc), mark, + ) if name not in sys.modules: - raise ConstructorError("while constructing a Python module", mark, - "module %r is not imported" % name, mark) + raise ConstructorError( + "while constructing a Python module", mark, + "module %r is not imported" % name, mark, + ) return sys.modules[name] def find_python_name(self, name, mark, unsafe=False): if not name: - raise ConstructorError("while constructing a Python object", mark, - "expected non-empty name appended to the tag", mark) + raise ConstructorError( + "while constructing a Python object", mark, + "expected non-empty name appended to the tag", mark, + ) if '.' in name: module_name, object_name = name.rsplit('.', 1) else: @@ -550,43 +617,57 @@ def find_python_name(self, name, mark, unsafe=False): try: __import__(module_name) except ImportError as exc: - raise ConstructorError("while constructing a Python object", mark, - "cannot find module %r (%s)" % (module_name, exc), mark) + raise ConstructorError( + "while constructing a Python object", mark, + "cannot find module %r (%s)" % (module_name, exc), mark, + ) if module_name not in sys.modules: - raise ConstructorError("while constructing a Python object", mark, - "module %r is not imported" % module_name, mark) + raise ConstructorError( + "while constructing a Python object", mark, + "module %r is not imported" % module_name, mark, + ) module = sys.modules[module_name] if not hasattr(module, object_name): - raise ConstructorError("while constructing a Python object", mark, - "cannot find %r in the module %r" - % (object_name, module.__name__), mark) + raise ConstructorError( + "while constructing a Python object", mark, + "cannot find %r in the module %r" + % (object_name, module.__name__), mark, + ) return getattr(module, object_name) def construct_python_name(self, suffix, node): value = self.construct_scalar(node) if value: - raise ConstructorError("while constructing a Python name", node.start_mark, - "expected the empty value, but found %r" % value, node.start_mark) + raise ConstructorError( + "while constructing a Python name", node.start_mark, + "expected the empty value, but found %r" % value, node.start_mark, + ) return self.find_python_name(suffix, node.start_mark) def construct_python_module(self, suffix, node): value = self.construct_scalar(node) if value: - raise ConstructorError("while constructing a Python module", node.start_mark, - "expected the empty value, but found %r" % value, node.start_mark) + raise ConstructorError( + "while constructing a Python module", node.start_mark, + "expected the empty value, but found %r" % value, node.start_mark, + ) return self.find_python_module(suffix, node.start_mark) - def make_python_instance(self, suffix, node, - args=None, kwds=None, newobj=False, unsafe=False): + def make_python_instance( + self, suffix, node, + args=None, kwds=None, newobj=False, unsafe=False, + ): if not args: args = [] if not kwds: kwds = {} cls = self.find_python_name(suffix, node.start_mark) if not (unsafe or isinstance(cls, type)): - raise ConstructorError("while constructing a Python instance", node.start_mark, - "expected a class, but found %r" % type(cls), - node.start_mark) + raise ConstructorError( + "while constructing a Python instance", node.start_mark, + "expected a class, but found %r" % type(cls), + node.start_mark, + ) if newobj and isinstance(cls, type): return cls.__new__(cls, *args, **kwds) else: @@ -660,55 +741,68 @@ def construct_python_object_new(self, suffix, node): FullConstructor.add_constructor( 'tag:yaml.org,2002:python/none', - FullConstructor.construct_yaml_null) + FullConstructor.construct_yaml_null, +) FullConstructor.add_constructor( 'tag:yaml.org,2002:python/bool', - FullConstructor.construct_yaml_bool) + FullConstructor.construct_yaml_bool, +) FullConstructor.add_constructor( 'tag:yaml.org,2002:python/str', - FullConstructor.construct_python_str) + FullConstructor.construct_python_str, +) FullConstructor.add_constructor( 'tag:yaml.org,2002:python/unicode', - FullConstructor.construct_python_unicode) + FullConstructor.construct_python_unicode, +) FullConstructor.add_constructor( 'tag:yaml.org,2002:python/bytes', - FullConstructor.construct_python_bytes) + FullConstructor.construct_python_bytes, +) FullConstructor.add_constructor( 'tag:yaml.org,2002:python/int', - FullConstructor.construct_yaml_int) + FullConstructor.construct_yaml_int, +) FullConstructor.add_constructor( 'tag:yaml.org,2002:python/long', - FullConstructor.construct_python_long) + FullConstructor.construct_python_long, +) FullConstructor.add_constructor( 'tag:yaml.org,2002:python/float', - FullConstructor.construct_yaml_float) + FullConstructor.construct_yaml_float, +) FullConstructor.add_constructor( 'tag:yaml.org,2002:python/complex', - FullConstructor.construct_python_complex) + FullConstructor.construct_python_complex, +) FullConstructor.add_constructor( 'tag:yaml.org,2002:python/list', - FullConstructor.construct_yaml_seq) + FullConstructor.construct_yaml_seq, +) FullConstructor.add_constructor( 'tag:yaml.org,2002:python/tuple', - FullConstructor.construct_python_tuple) + FullConstructor.construct_python_tuple, +) FullConstructor.add_constructor( 'tag:yaml.org,2002:python/dict', - FullConstructor.construct_yaml_map) + FullConstructor.construct_yaml_map, +) FullConstructor.add_multi_constructor( 'tag:yaml.org,2002:python/name:', - FullConstructor.construct_python_name) + FullConstructor.construct_python_name, +) class UnsafeConstructor(FullConstructor): @@ -720,27 +814,33 @@ def find_python_name(self, name, mark): def make_python_instance(self, suffix, node, args=None, kwds=None, newobj=False): return super(UnsafeConstructor, self).make_python_instance( - suffix, node, args, kwds, newobj, unsafe=True) + suffix, node, args, kwds, newobj, unsafe=True, + ) def set_python_instance_state(self, instance, state): return super(UnsafeConstructor, self).set_python_instance_state( - instance, state, unsafe=True) + instance, state, unsafe=True, + ) UnsafeConstructor.add_multi_constructor( 'tag:yaml.org,2002:python/module:', - UnsafeConstructor.construct_python_module) + UnsafeConstructor.construct_python_module, +) UnsafeConstructor.add_multi_constructor( 'tag:yaml.org,2002:python/object:', - UnsafeConstructor.construct_python_object) + UnsafeConstructor.construct_python_object, +) UnsafeConstructor.add_multi_constructor( 'tag:yaml.org,2002:python/object/new:', - UnsafeConstructor.construct_python_object_new) + UnsafeConstructor.construct_python_object_new, +) UnsafeConstructor.add_multi_constructor( 'tag:yaml.org,2002:python/object/apply:', - UnsafeConstructor.construct_python_object_apply) + UnsafeConstructor.construct_python_object_apply, +) # Constructor is same as UnsafeConstructor. Need to leave this in place in case # people have extended it directly. diff --git a/addon/globalPlugins/MathCAT/yaml/cyaml.py b/addon/globalPlugins/MathCAT/yaml/cyaml.py index 0c213458..946d60cc 100644 --- a/addon/globalPlugins/MathCAT/yaml/cyaml.py +++ b/addon/globalPlugins/MathCAT/yaml/cyaml.py @@ -1,7 +1,7 @@ __all__ = [ 'CBaseLoader', 'CSafeLoader', 'CFullLoader', 'CUnsafeLoader', 'CLoader', - 'CBaseDumper', 'CSafeDumper', 'CDumper' + 'CBaseDumper', 'CSafeDumper', 'CDumper', ] from yaml._yaml import CParser, CEmitter @@ -50,52 +50,69 @@ def __init__(self, stream): class CBaseDumper(CEmitter, BaseRepresenter, BaseResolver): - def __init__(self, stream, - default_style=None, default_flow_style=False, - canonical=None, indent=None, width=None, - allow_unicode=None, line_break=None, - encoding=None, explicit_start=None, explicit_end=None, - version=None, tags=None, sort_keys=True): - CEmitter.__init__(self, stream, canonical=canonical, - indent=indent, width=width, encoding=encoding, - allow_unicode=allow_unicode, line_break=line_break, - explicit_start=explicit_start, explicit_end=explicit_end, - version=version, tags=tags) - Representer.__init__(self, default_style=default_style, - default_flow_style=default_flow_style, sort_keys=sort_keys) + def __init__( + self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True, + ): + CEmitter.__init__( + self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags, + ) + Representer.__init__( + self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys, + ) Resolver.__init__(self) class CSafeDumper(CEmitter, SafeRepresenter, Resolver): - def __init__(self, stream, - default_style=None, default_flow_style=False, - canonical=None, indent=None, width=None, - allow_unicode=None, line_break=None, - encoding=None, explicit_start=None, explicit_end=None, - version=None, tags=None, sort_keys=True): - CEmitter.__init__(self, stream, canonical=canonical, - indent=indent, width=width, encoding=encoding, - allow_unicode=allow_unicode, line_break=line_break, - explicit_start=explicit_start, explicit_end=explicit_end, - version=version, tags=tags) - SafeRepresenter.__init__(self, default_style=default_style, - default_flow_style=default_flow_style, sort_keys=sort_keys) + def __init__( + self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True, + ): + CEmitter.__init__( + self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags, + ) + SafeRepresenter.__init__( + self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys, + ) Resolver.__init__(self) class CDumper(CEmitter, Serializer, Representer, Resolver): - def __init__(self, stream, - default_style=None, default_flow_style=False, - canonical=None, indent=None, width=None, - allow_unicode=None, line_break=None, - encoding=None, explicit_start=None, explicit_end=None, - version=None, tags=None, sort_keys=True): - CEmitter.__init__(self, stream, canonical=canonical, - indent=indent, width=width, encoding=encoding, - allow_unicode=allow_unicode, line_break=line_break, - explicit_start=explicit_start, explicit_end=explicit_end, - version=version, tags=tags) - Representer.__init__(self, default_style=default_style, - default_flow_style=default_flow_style, sort_keys=sort_keys) + def __init__( + self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True, + ): + CEmitter.__init__( + self, stream, canonical=canonical, + indent=indent, width=width, encoding=encoding, + allow_unicode=allow_unicode, line_break=line_break, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags, + ) + Representer.__init__( + self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys, + ) Resolver.__init__(self) - diff --git a/addon/globalPlugins/MathCAT/yaml/dumper.py b/addon/globalPlugins/MathCAT/yaml/dumper.py index 6aadba55..585f8f4f 100644 --- a/addon/globalPlugins/MathCAT/yaml/dumper.py +++ b/addon/globalPlugins/MathCAT/yaml/dumper.py @@ -8,55 +8,78 @@ class BaseDumper(Emitter, Serializer, BaseRepresenter, BaseResolver): - def __init__(self, stream, - default_style=None, default_flow_style=False, - canonical=None, indent=None, width=None, - allow_unicode=None, line_break=None, - encoding=None, explicit_start=None, explicit_end=None, - version=None, tags=None, sort_keys=True): - Emitter.__init__(self, stream, canonical=canonical, - indent=indent, width=width, - allow_unicode=allow_unicode, line_break=line_break) - Serializer.__init__(self, encoding=encoding, - explicit_start=explicit_start, explicit_end=explicit_end, - version=version, tags=tags) - Representer.__init__(self, default_style=default_style, - default_flow_style=default_flow_style, sort_keys=sort_keys) + def __init__( + self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True, + ): + Emitter.__init__( + self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break, + ) + Serializer.__init__( + self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags, + ) + Representer.__init__( + self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys, + ) Resolver.__init__(self) class SafeDumper(Emitter, Serializer, SafeRepresenter, Resolver): - def __init__(self, stream, - default_style=None, default_flow_style=False, - canonical=None, indent=None, width=None, - allow_unicode=None, line_break=None, - encoding=None, explicit_start=None, explicit_end=None, - version=None, tags=None, sort_keys=True): - Emitter.__init__(self, stream, canonical=canonical, - indent=indent, width=width, - allow_unicode=allow_unicode, line_break=line_break) - Serializer.__init__(self, encoding=encoding, - explicit_start=explicit_start, explicit_end=explicit_end, - version=version, tags=tags) - SafeRepresenter.__init__(self, default_style=default_style, - default_flow_style=default_flow_style, sort_keys=sort_keys) + def __init__( + self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True, + ): + Emitter.__init__( + self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break, + ) + Serializer.__init__( + self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags, + ) + SafeRepresenter.__init__( + self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys, + ) Resolver.__init__(self) class Dumper(Emitter, Serializer, Representer, Resolver): - def __init__(self, stream, - default_style=None, default_flow_style=False, - canonical=None, indent=None, width=None, - allow_unicode=None, line_break=None, - encoding=None, explicit_start=None, explicit_end=None, - version=None, tags=None, sort_keys=True): - Emitter.__init__(self, stream, canonical=canonical, - indent=indent, width=width, - allow_unicode=allow_unicode, line_break=line_break) - Serializer.__init__(self, encoding=encoding, - explicit_start=explicit_start, explicit_end=explicit_end, - version=version, tags=tags) - Representer.__init__(self, default_style=default_style, - default_flow_style=default_flow_style, sort_keys=sort_keys) + def __init__( + self, stream, + default_style=None, default_flow_style=False, + canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + encoding=None, explicit_start=None, explicit_end=None, + version=None, tags=None, sort_keys=True, + ): + Emitter.__init__( + self, stream, canonical=canonical, + indent=indent, width=width, + allow_unicode=allow_unicode, line_break=line_break, + ) + Serializer.__init__( + self, encoding=encoding, + explicit_start=explicit_start, explicit_end=explicit_end, + version=version, tags=tags, + ) + Representer.__init__( + self, default_style=default_style, + default_flow_style=default_flow_style, sort_keys=sort_keys, + ) Resolver.__init__(self) - diff --git a/addon/globalPlugins/MathCAT/yaml/emitter.py b/addon/globalPlugins/MathCAT/yaml/emitter.py index a664d011..a5b73657 100644 --- a/addon/globalPlugins/MathCAT/yaml/emitter.py +++ b/addon/globalPlugins/MathCAT/yaml/emitter.py @@ -15,10 +15,12 @@ class EmitterError(YAMLError): pass class ScalarAnalysis: - def __init__(self, scalar, empty, multiline, - allow_flow_plain, allow_block_plain, - allow_single_quoted, allow_double_quoted, - allow_block): + def __init__( + self, scalar, empty, multiline, + allow_flow_plain, allow_block_plain, + allow_single_quoted, allow_double_quoted, + allow_block, + ): self.scalar = scalar self.empty = empty self.multiline = multiline @@ -35,8 +37,10 @@ class Emitter: 'tag:yaml.org,2002:' : '!!', } - def __init__(self, stream, canonical=None, indent=None, width=None, - allow_unicode=None, line_break=None): + def __init__( + self, stream, canonical=None, indent=None, width=None, + allow_unicode=None, line_break=None, + ): # The stream should have the methods `write` and possibly `flush`. self.stream = stream @@ -164,8 +168,10 @@ def expect_stream_start(self): self.write_stream_start() self.state = self.expect_first_document_start else: - raise EmitterError("expected StreamStartEvent, but got %s" - % self.event) + raise EmitterError( + "expected StreamStartEvent, but got %s" + % self.event, + ) def expect_nothing(self): raise EmitterError("expected nothing, but got %s" % self.event) @@ -192,9 +198,11 @@ def expect_document_start(self, first=False): handle_text = self.prepare_tag_handle(handle) prefix_text = self.prepare_tag_prefix(prefix) self.write_tag_directive(handle_text, prefix_text) - implicit = (first and not self.event.explicit and not self.canonical - and not self.event.version and not self.event.tags - and not self.check_empty_document()) + implicit = ( + first and not self.event.explicit and not self.canonical + and not self.event.version and not self.event.tags + and not self.check_empty_document() + ) if not implicit: self.write_indent() self.write_indicator('---', True) @@ -208,8 +216,10 @@ def expect_document_start(self, first=False): self.write_stream_end() self.state = self.expect_nothing else: - raise EmitterError("expected DocumentStartEvent, but got %s" - % self.event) + raise EmitterError( + "expected DocumentStartEvent, but got %s" + % self.event, + ) def expect_document_end(self): if isinstance(self.event, DocumentEndEvent): @@ -220,8 +230,10 @@ def expect_document_end(self): self.flush_stream() self.state = self.expect_document_start else: - raise EmitterError("expected DocumentEndEvent, but got %s" - % self.event) + raise EmitterError( + "expected DocumentEndEvent, but got %s" + % self.event, + ) def expect_document_root(self): self.states.append(self.expect_document_end) @@ -229,8 +241,10 @@ def expect_document_root(self): # Node handlers. - def expect_node(self, root=False, sequence=False, mapping=False, - simple_key=False): + def expect_node( + self, root=False, sequence=False, mapping=False, + simple_key=False, + ): self.root_context = root self.sequence_context = sequence self.mapping_context = mapping @@ -420,19 +434,25 @@ def expect_block_mapping_value(self): # Checkers. def check_empty_sequence(self): - return (isinstance(self.event, SequenceStartEvent) and self.events - and isinstance(self.events[0], SequenceEndEvent)) + return ( + isinstance(self.event, SequenceStartEvent) and self.events + and isinstance(self.events[0], SequenceEndEvent) + ) def check_empty_mapping(self): - return (isinstance(self.event, MappingStartEvent) and self.events - and isinstance(self.events[0], MappingEndEvent)) + return ( + isinstance(self.event, MappingStartEvent) and self.events + and isinstance(self.events[0], MappingEndEvent) + ) def check_empty_document(self): if not isinstance(self.event, DocumentStartEvent) or not self.events: return False event = self.events[0] - return (isinstance(event, ScalarEvent) and event.anchor is None - and event.tag is None and event.implicit and event.value == '') + return ( + isinstance(event, ScalarEvent) and event.anchor is None + and event.tag is None and event.implicit and event.value == '' + ) def check_simple_key(self): length = 0 @@ -449,10 +469,16 @@ def check_simple_key(self): if self.analysis is None: self.analysis = self.analyze_scalar(self.event.value) length += len(self.analysis.scalar) - return (length < 128 and (isinstance(self.event, AliasEvent) - or (isinstance(self.event, ScalarEvent) - and not self.analysis.empty and not self.analysis.multiline) - or self.check_empty_sequence() or self.check_empty_mapping())) + return ( + length < 128 and ( + isinstance(self.event, AliasEvent) + or ( + isinstance(self.event, ScalarEvent) + and not self.analysis.empty and not self.analysis.multiline + ) + or self.check_empty_sequence() or self.check_empty_mapping() + ) + ) # Anchor, Tag, and Scalar processors. @@ -497,18 +523,28 @@ def choose_scalar_style(self): if self.event.style == '"' or self.canonical: return '"' if not self.event.style and self.event.implicit[0]: - if (not (self.simple_key_context and - (self.analysis.empty or self.analysis.multiline)) - and (self.flow_level and self.analysis.allow_flow_plain - or (not self.flow_level and self.analysis.allow_block_plain))): + if ( + not ( + self.simple_key_context and + (self.analysis.empty or self.analysis.multiline) + ) + and ( + self.flow_level and self.analysis.allow_flow_plain + or (not self.flow_level and self.analysis.allow_block_plain) + ) + ): return '' if self.event.style and self.event.style in '|>': - if (not self.flow_level and not self.simple_key_context - and self.analysis.allow_block): + if ( + not self.flow_level and not self.simple_key_context + and self.analysis.allow_block + ): return self.event.style if not self.event.style or self.event.style == '\'': - if (self.analysis.allow_single_quoted and - not (self.simple_key_context and self.analysis.multiline)): + if ( + self.analysis.allow_single_quoted and + not (self.simple_key_context and self.analysis.multiline) + ): return '\'' return '"' @@ -548,10 +584,14 @@ def prepare_tag_handle(self, handle): if handle[0] != '!' or handle[-1] != '!': raise EmitterError("tag handle must start and end with '!': %r" % handle) for ch in handle[1:-1]: - if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ - or ch in '-_'): - raise EmitterError("invalid character %r in the tag handle: %r" - % (ch, handle)) + if not ( + '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_' + ): + raise EmitterError( + "invalid character %r in the tag handle: %r" + % (ch, handle), + ) return handle def prepare_tag_prefix(self, prefix): @@ -617,20 +657,26 @@ def prepare_anchor(self, anchor): if not anchor: raise EmitterError("anchor must not be empty") for ch in anchor: - if not ('0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ - or ch in '-_'): - raise EmitterError("invalid character %r in the anchor: %r" - % (ch, anchor)) + if not ( + '0' <= ch <= '9' or 'A' <= ch <= 'Z' or 'a' <= ch <= 'z' \ + or ch in '-_' + ): + raise EmitterError( + "invalid character %r in the anchor: %r" + % (ch, anchor), + ) return anchor def analyze_scalar(self, scalar): # Empty scalar is a special case. if not scalar: - return ScalarAnalysis(scalar=scalar, empty=True, multiline=False, - allow_flow_plain=False, allow_block_plain=True, - allow_single_quoted=True, allow_double_quoted=True, - allow_block=False) + return ScalarAnalysis( + scalar=scalar, empty=True, multiline=False, + allow_flow_plain=False, allow_block_plain=True, + allow_single_quoted=True, allow_double_quoted=True, + allow_block=False, + ) # Indicators and special characters. block_indicators = False @@ -655,8 +701,10 @@ def analyze_scalar(self, scalar): preceded_by_whitespace = True # Last character or followed by a whitespace. - followed_by_whitespace = (len(scalar) == 1 or - scalar[1] in '\0 \t\r\n\x85\u2028\u2029') + followed_by_whitespace = ( + len(scalar) == 1 or + scalar[1] in '\0 \t\r\n\x85\u2028\u2029' + ) # The previous character is a space. previous_space = False @@ -697,9 +745,11 @@ def analyze_scalar(self, scalar): if ch in '\n\x85\u2028\u2029': line_breaks = True if not (ch == '\n' or '\x20' <= ch <= '\x7E'): - if (ch == '\x85' or '\xA0' <= ch <= '\uD7FF' - or '\uE000' <= ch <= '\uFFFD' - or '\U00010000' <= ch < '\U0010ffff') and ch != '\uFEFF': + if ( + ch == '\x85' or '\xA0' <= ch <= '\uD7FF' + or '\uE000' <= ch <= '\uFFFD' + or '\U00010000' <= ch < '\U0010ffff' + ) and ch != '\uFEFF': unicode_characters = True if not self.allow_unicode: special_characters = True @@ -732,8 +782,10 @@ def analyze_scalar(self, scalar): # Prepare for the next character. index += 1 preceded_by_whitespace = (ch in '\0 \t\r\n\x85\u2028\u2029') - followed_by_whitespace = (index+1 >= len(scalar) or - scalar[index+1] in '\0 \t\r\n\x85\u2028\u2029') + followed_by_whitespace = ( + index+1 >= len(scalar) or + scalar[index+1] in '\0 \t\r\n\x85\u2028\u2029' + ) # Let's decide what styles are allowed. allow_flow_plain = True @@ -743,8 +795,10 @@ def analyze_scalar(self, scalar): allow_block = True # Leading and trailing whitespaces are bad for plain scalars. - if (leading_space or leading_break - or trailing_space or trailing_break): + if ( + leading_space or leading_break + or trailing_space or trailing_break + ): allow_flow_plain = allow_block_plain = False # We do not permit trailing spaces for block scalars. @@ -775,13 +829,15 @@ def analyze_scalar(self, scalar): if block_indicators: allow_block_plain = False - return ScalarAnalysis(scalar=scalar, - empty=False, multiline=line_breaks, - allow_flow_plain=allow_flow_plain, - allow_block_plain=allow_block_plain, - allow_single_quoted=allow_single_quoted, - allow_double_quoted=allow_double_quoted, - allow_block=allow_block) + return ScalarAnalysis( + scalar=scalar, + empty=False, multiline=line_breaks, + allow_flow_plain=allow_flow_plain, + allow_block_plain=allow_block_plain, + allow_single_quoted=allow_single_quoted, + allow_double_quoted=allow_double_quoted, + allow_block=allow_block, + ) # Writers. @@ -797,8 +853,10 @@ def write_stream_start(self): def write_stream_end(self): self.flush_stream() - def write_indicator(self, indicator, need_whitespace, - whitespace=False, indention=False): + def write_indicator( + self, indicator, need_whitespace, + whitespace=False, indention=False, + ): if self.whitespace or not need_whitespace: data = indicator else: @@ -931,10 +989,16 @@ def write_double_quoted(self, text, split=True): if end < len(text): ch = text[end] if ch is None or ch in '"\\\x85\u2028\u2029\uFEFF' \ - or not ('\x20' <= ch <= '\x7E' - or (self.allow_unicode - and ('\xA0' <= ch <= '\uD7FF' - or '\uE000' <= ch <= '\uFFFD'))): + or not ( + '\x20' <= ch <= '\x7E' + or ( + self.allow_unicode + and ( + '\xA0' <= ch <= '\uD7FF' + or '\uE000' <= ch <= '\uFFFD' + ) + ) + ): if start < end: data = text[start:end] self.column += len(data) diff --git a/addon/globalPlugins/MathCAT/yaml/error.py b/addon/globalPlugins/MathCAT/yaml/error.py index b796b4dc..4e203a6e 100644 --- a/addon/globalPlugins/MathCAT/yaml/error.py +++ b/addon/globalPlugins/MathCAT/yaml/error.py @@ -47,8 +47,10 @@ class YAMLError(Exception): class MarkedYAMLError(YAMLError): - def __init__(self, context=None, context_mark=None, - problem=None, problem_mark=None, note=None): + def __init__( + self, context=None, context_mark=None, + problem=None, problem_mark=None, note=None, + ): self.context = context self.context_mark = context_mark self.problem = problem @@ -60,10 +62,12 @@ def __str__(self): if self.context is not None: lines.append(self.context) if self.context_mark is not None \ - and (self.problem is None or self.problem_mark is None - or self.context_mark.name != self.problem_mark.name - or self.context_mark.line != self.problem_mark.line - or self.context_mark.column != self.problem_mark.column): + and ( + self.problem is None or self.problem_mark is None + or self.context_mark.name != self.problem_mark.name + or self.context_mark.line != self.problem_mark.line + or self.context_mark.column != self.problem_mark.column + ): lines.append(str(self.context_mark)) if self.problem is not None: lines.append(self.problem) @@ -72,4 +76,3 @@ def __str__(self): if self.note is not None: lines.append(self.note) return '\n'.join(lines) - diff --git a/addon/globalPlugins/MathCAT/yaml/events.py b/addon/globalPlugins/MathCAT/yaml/events.py index f79ad389..ce211528 100644 --- a/addon/globalPlugins/MathCAT/yaml/events.py +++ b/addon/globalPlugins/MathCAT/yaml/events.py @@ -6,10 +6,14 @@ def __init__(self, start_mark=None, end_mark=None): self.start_mark = start_mark self.end_mark = end_mark def __repr__(self): - attributes = [key for key in ['anchor', 'tag', 'implicit', 'value'] - if hasattr(self, key)] - arguments = ', '.join(['%s=%r' % (key, getattr(self, key)) - for key in attributes]) + attributes = [ + key for key in ['anchor', 'tag', 'implicit', 'value'] + if hasattr(self, key) + ] + arguments = ', '.join([ + '%s=%r' % (key, getattr(self, key)) + for key in attributes + ]) return '%s(%s)' % (self.__class__.__name__, arguments) class NodeEvent(Event): @@ -19,8 +23,10 @@ def __init__(self, anchor, start_mark=None, end_mark=None): self.end_mark = end_mark class CollectionStartEvent(NodeEvent): - def __init__(self, anchor, tag, implicit, start_mark=None, end_mark=None, - flow_style=None): + def __init__( + self, anchor, tag, implicit, start_mark=None, end_mark=None, + flow_style=None, + ): self.anchor = anchor self.tag = tag self.implicit = implicit @@ -43,8 +49,10 @@ class StreamEndEvent(Event): pass class DocumentStartEvent(Event): - def __init__(self, start_mark=None, end_mark=None, - explicit=None, version=None, tags=None): + def __init__( + self, start_mark=None, end_mark=None, + explicit=None, version=None, tags=None, + ): self.start_mark = start_mark self.end_mark = end_mark self.explicit = explicit @@ -52,8 +60,10 @@ def __init__(self, start_mark=None, end_mark=None, self.tags = tags class DocumentEndEvent(Event): - def __init__(self, start_mark=None, end_mark=None, - explicit=None): + def __init__( + self, start_mark=None, end_mark=None, + explicit=None, + ): self.start_mark = start_mark self.end_mark = end_mark self.explicit = explicit @@ -62,8 +72,10 @@ class AliasEvent(NodeEvent): pass class ScalarEvent(NodeEvent): - def __init__(self, anchor, tag, implicit, value, - start_mark=None, end_mark=None, style=None): + def __init__( + self, anchor, tag, implicit, value, + start_mark=None, end_mark=None, style=None, + ): self.anchor = anchor self.tag = tag self.implicit = implicit @@ -83,4 +95,3 @@ class MappingStartEvent(CollectionStartEvent): class MappingEndEvent(CollectionEndEvent): pass - diff --git a/addon/globalPlugins/MathCAT/yaml/nodes.py b/addon/globalPlugins/MathCAT/yaml/nodes.py index c4f070c4..3ce5469c 100644 --- a/addon/globalPlugins/MathCAT/yaml/nodes.py +++ b/addon/globalPlugins/MathCAT/yaml/nodes.py @@ -24,8 +24,10 @@ def __repr__(self): class ScalarNode(Node): id = 'scalar' - def __init__(self, tag, value, - start_mark=None, end_mark=None, style=None): + def __init__( + self, tag, value, + start_mark=None, end_mark=None, style=None, + ): self.tag = tag self.value = value self.start_mark = start_mark @@ -33,8 +35,10 @@ def __init__(self, tag, value, self.style = style class CollectionNode(Node): - def __init__(self, tag, value, - start_mark=None, end_mark=None, flow_style=None): + def __init__( + self, tag, value, + start_mark=None, end_mark=None, flow_style=None, + ): self.tag = tag self.value = value self.start_mark = start_mark @@ -46,4 +50,3 @@ class SequenceNode(CollectionNode): class MappingNode(CollectionNode): id = 'mapping' - diff --git a/addon/globalPlugins/MathCAT/yaml/parser.py b/addon/globalPlugins/MathCAT/yaml/parser.py index 13a5995d..afd8fb3f 100644 --- a/addon/globalPlugins/MathCAT/yaml/parser.py +++ b/addon/globalPlugins/MathCAT/yaml/parser.py @@ -128,8 +128,10 @@ def parse_stream_start(self): # Parse the stream start. token = self.get_token() - event = StreamStartEvent(token.start_mark, token.end_mark, - encoding=token.encoding) + event = StreamStartEvent( + token.start_mark, token.end_mark, + encoding=token.encoding, + ) # Prepare the next state. self.state = self.parse_implicit_document_start @@ -139,13 +141,17 @@ def parse_stream_start(self): def parse_implicit_document_start(self): # Parse an implicit document. - if not self.check_token(DirectiveToken, DocumentStartToken, - StreamEndToken): + if not self.check_token( + DirectiveToken, DocumentStartToken, + StreamEndToken, + ): self.tag_handles = self.DEFAULT_TAGS token = self.peek_token() start_mark = end_mark = token.start_mark - event = DocumentStartEvent(start_mark, end_mark, - explicit=False) + event = DocumentStartEvent( + start_mark, end_mark, + explicit=False, + ) # Prepare the next state. self.states.append(self.parse_document_end) @@ -168,14 +174,18 @@ def parse_document_start(self): start_mark = token.start_mark version, tags = self.process_directives() if not self.check_token(DocumentStartToken): - raise ParserError(None, None, - "expected '', but found %r" - % self.peek_token().id, - self.peek_token().start_mark) + raise ParserError( + None, None, + "expected '', but found %r" + % self.peek_token().id, + self.peek_token().start_mark, + ) token = self.get_token() end_mark = token.end_mark - event = DocumentStartEvent(start_mark, end_mark, - explicit=True, version=version, tags=tags) + event = DocumentStartEvent( + start_mark, end_mark, + explicit=True, version=version, tags=tags, + ) self.states.append(self.parse_document_end) self.state = self.parse_document_content else: @@ -197,8 +207,10 @@ def parse_document_end(self): token = self.get_token() end_mark = token.end_mark explicit = True - event = DocumentEndEvent(start_mark, end_mark, - explicit=explicit) + event = DocumentEndEvent( + start_mark, end_mark, + explicit=explicit, + ) # Prepare the next state. self.state = self.parse_document_start @@ -206,8 +218,10 @@ def parse_document_end(self): return event def parse_document_content(self): - if self.check_token(DirectiveToken, - DocumentStartToken, DocumentEndToken, StreamEndToken): + if self.check_token( + DirectiveToken, + DocumentStartToken, DocumentEndToken, StreamEndToken, + ): event = self.process_empty_scalar(self.peek_token().start_mark) self.state = self.states.pop() return event @@ -221,20 +235,26 @@ def process_directives(self): token = self.get_token() if token.name == 'YAML': if self.yaml_version is not None: - raise ParserError(None, None, - "found duplicate YAML directive", token.start_mark) + raise ParserError( + None, None, + "found duplicate YAML directive", token.start_mark, + ) major, minor = token.value if major != 1: - raise ParserError(None, None, - "found incompatible YAML document (version 1.* is required)", - token.start_mark) + raise ParserError( + None, None, + "found incompatible YAML document (version 1.* is required)", + token.start_mark, + ) self.yaml_version = token.value elif token.name == 'TAG': handle, prefix = token.value if handle in self.tag_handles: - raise ParserError(None, None, - "duplicate tag handle %r" % handle, - token.start_mark) + raise ParserError( + None, None, + "duplicate tag handle %r" % handle, + token.start_mark, + ) self.tag_handles[handle] = prefix if self.tag_handles: value = self.yaml_version, self.tag_handles.copy() @@ -302,9 +322,11 @@ def parse_node(self, block=False, indentless_sequence=False): handle, suffix = tag if handle is not None: if handle not in self.tag_handles: - raise ParserError("while parsing a node", start_mark, - "found undefined tag handle %r" % handle, - tag_mark) + raise ParserError( + "while parsing a node", start_mark, + "found undefined tag handle %r" % handle, + tag_mark, + ) tag = self.tag_handles[handle]+suffix else: tag = suffix @@ -318,8 +340,10 @@ def parse_node(self, block=False, indentless_sequence=False): implicit = (tag is None or tag == '!') if indentless_sequence and self.check_token(BlockEntryToken): end_mark = self.peek_token().end_mark - event = SequenceStartEvent(anchor, tag, implicit, - start_mark, end_mark) + event = SequenceStartEvent( + anchor, tag, implicit, + start_mark, end_mark, + ) self.state = self.parse_indentless_sequence_entry else: if self.check_token(ScalarToken): @@ -331,34 +355,46 @@ def parse_node(self, block=False, indentless_sequence=False): implicit = (False, True) else: implicit = (False, False) - event = ScalarEvent(anchor, tag, implicit, token.value, - start_mark, end_mark, style=token.style) + event = ScalarEvent( + anchor, tag, implicit, token.value, + start_mark, end_mark, style=token.style, + ) self.state = self.states.pop() elif self.check_token(FlowSequenceStartToken): end_mark = self.peek_token().end_mark - event = SequenceStartEvent(anchor, tag, implicit, - start_mark, end_mark, flow_style=True) + event = SequenceStartEvent( + anchor, tag, implicit, + start_mark, end_mark, flow_style=True, + ) self.state = self.parse_flow_sequence_first_entry elif self.check_token(FlowMappingStartToken): end_mark = self.peek_token().end_mark - event = MappingStartEvent(anchor, tag, implicit, - start_mark, end_mark, flow_style=True) + event = MappingStartEvent( + anchor, tag, implicit, + start_mark, end_mark, flow_style=True, + ) self.state = self.parse_flow_mapping_first_key elif block and self.check_token(BlockSequenceStartToken): end_mark = self.peek_token().start_mark - event = SequenceStartEvent(anchor, tag, implicit, - start_mark, end_mark, flow_style=False) + event = SequenceStartEvent( + anchor, tag, implicit, + start_mark, end_mark, flow_style=False, + ) self.state = self.parse_block_sequence_first_entry elif block and self.check_token(BlockMappingStartToken): end_mark = self.peek_token().start_mark - event = MappingStartEvent(anchor, tag, implicit, - start_mark, end_mark, flow_style=False) + event = MappingStartEvent( + anchor, tag, implicit, + start_mark, end_mark, flow_style=False, + ) self.state = self.parse_block_mapping_first_key elif anchor is not None or tag is not None: # Empty scalars are allowed even if a tag or an anchor is # specified. - event = ScalarEvent(anchor, tag, (implicit, False), '', - start_mark, end_mark) + event = ScalarEvent( + anchor, tag, (implicit, False), '', + start_mark, end_mark, + ) self.state = self.states.pop() else: if block: @@ -366,9 +402,11 @@ def parse_node(self, block=False, indentless_sequence=False): else: node = 'flow' token = self.peek_token() - raise ParserError("while parsing a %s node" % node, start_mark, - "expected the node content, but found %r" % token.id, - token.start_mark) + raise ParserError( + "while parsing a %s node" % node, start_mark, + "expected the node content, but found %r" % token.id, + token.start_mark, + ) return event # block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END @@ -389,8 +427,10 @@ def parse_block_sequence_entry(self): return self.process_empty_scalar(token.end_mark) if not self.check_token(BlockEndToken): token = self.peek_token() - raise ParserError("while parsing a block collection", self.marks[-1], - "expected , but found %r" % token.id, token.start_mark) + raise ParserError( + "while parsing a block collection", self.marks[-1], + "expected , but found %r" % token.id, token.start_mark, + ) token = self.get_token() event = SequenceEndEvent(token.start_mark, token.end_mark) self.state = self.states.pop() @@ -402,8 +442,10 @@ def parse_block_sequence_entry(self): def parse_indentless_sequence_entry(self): if self.check_token(BlockEntryToken): token = self.get_token() - if not self.check_token(BlockEntryToken, - KeyToken, ValueToken, BlockEndToken): + if not self.check_token( + BlockEntryToken, + KeyToken, ValueToken, BlockEndToken, + ): self.states.append(self.parse_indentless_sequence_entry) return self.parse_block_node() else: @@ -435,8 +477,10 @@ def parse_block_mapping_key(self): return self.process_empty_scalar(token.end_mark) if not self.check_token(BlockEndToken): token = self.peek_token() - raise ParserError("while parsing a block mapping", self.marks[-1], - "expected , but found %r" % token.id, token.start_mark) + raise ParserError( + "while parsing a block mapping", self.marks[-1], + "expected , but found %r" % token.id, token.start_mark, + ) token = self.get_token() event = MappingEndEvent(token.start_mark, token.end_mark) self.state = self.states.pop() @@ -480,14 +524,18 @@ def parse_flow_sequence_entry(self, first=False): self.get_token() else: token = self.peek_token() - raise ParserError("while parsing a flow sequence", self.marks[-1], - "expected ',' or ']', but got %r" % token.id, token.start_mark) - + raise ParserError( + "while parsing a flow sequence", self.marks[-1], + "expected ',' or ']', but got %r" % token.id, token.start_mark, + ) + if self.check_token(KeyToken): token = self.peek_token() - event = MappingStartEvent(None, None, True, - token.start_mark, token.end_mark, - flow_style=True) + event = MappingStartEvent( + None, None, True, + token.start_mark, token.end_mark, + flow_style=True, + ) self.state = self.parse_flow_sequence_entry_mapping_key return event elif not self.check_token(FlowSequenceEndToken): @@ -501,8 +549,10 @@ def parse_flow_sequence_entry(self, first=False): def parse_flow_sequence_entry_mapping_key(self): token = self.get_token() - if not self.check_token(ValueToken, - FlowEntryToken, FlowSequenceEndToken): + if not self.check_token( + ValueToken, + FlowEntryToken, FlowSequenceEndToken, + ): self.states.append(self.parse_flow_sequence_entry_mapping_value) return self.parse_flow_node() else: @@ -546,12 +596,16 @@ def parse_flow_mapping_key(self, first=False): self.get_token() else: token = self.peek_token() - raise ParserError("while parsing a flow mapping", self.marks[-1], - "expected ',' or '}', but got %r" % token.id, token.start_mark) + raise ParserError( + "while parsing a flow mapping", self.marks[-1], + "expected ',' or '}', but got %r" % token.id, token.start_mark, + ) if self.check_token(KeyToken): token = self.get_token() - if not self.check_token(ValueToken, - FlowEntryToken, FlowMappingEndToken): + if not self.check_token( + ValueToken, + FlowEntryToken, FlowMappingEndToken, + ): self.states.append(self.parse_flow_mapping_value) return self.parse_flow_node() else: @@ -586,4 +640,3 @@ def parse_flow_mapping_empty_value(self): def process_empty_scalar(self, mark): return ScalarEvent(None, None, (True, False), '', mark, mark) - diff --git a/addon/globalPlugins/MathCAT/yaml/reader.py b/addon/globalPlugins/MathCAT/yaml/reader.py index 774b0219..5509369c 100644 --- a/addon/globalPlugins/MathCAT/yaml/reader.py +++ b/addon/globalPlugins/MathCAT/yaml/reader.py @@ -34,13 +34,17 @@ def __str__(self): if isinstance(self.character, bytes): return "'%s' codec can't decode byte #x%02x: %s\n" \ " in \"%s\", position %d" \ - % (self.encoding, ord(self.character), self.reason, - self.name, self.position) + % ( + self.encoding, ord(self.character), self.reason, + self.name, self.position, + ) else: return "unacceptable character #x%04x: %s\n" \ " in \"%s\", position %d" \ - % (self.character, self.reason, - self.name, self.position) + % ( + self.character, self.reason, + self.name, self.position, + ) class Reader(object): # Reader: @@ -113,11 +117,15 @@ def forward(self, length=1): def get_mark(self): if self.stream is None: - return Mark(self.name, self.index, self.line, self.column, - self.buffer, self.pointer) + return Mark( + self.name, self.index, self.line, self.column, + self.buffer, self.pointer, + ) else: - return Mark(self.name, self.index, self.line, self.column, - None, None) + return Mark( + self.name, self.index, self.line, self.column, + None, None, + ) def determine_encoding(self): while not self.eof and (self.raw_buffer is None or len(self.raw_buffer) < 2): @@ -140,8 +148,10 @@ def check_printable(self, data): if match: character = match.group() position = self.index+(len(self.buffer)-self.pointer)+match.start() - raise ReaderError(self.name, position, ord(character), - 'unicode', "special characters are not allowed") + raise ReaderError( + self.name, position, ord(character), + 'unicode', "special characters are not allowed", + ) def update(self, length): if self.raw_buffer is None: @@ -153,16 +163,20 @@ def update(self, length): self.update_raw() if self.raw_decode is not None: try: - data, converted = self.raw_decode(self.raw_buffer, - 'strict', self.eof) + data, converted = self.raw_decode( + self.raw_buffer, + 'strict', self.eof, + ) except UnicodeDecodeError as exc: character = self.raw_buffer[exc.start] if self.stream is not None: position = self.stream_pointer-len(self.raw_buffer)+exc.start else: position = exc.start - raise ReaderError(self.name, position, character, - exc.encoding, exc.reason) + raise ReaderError( + self.name, position, character, + exc.encoding, exc.reason, + ) else: data = self.raw_buffer converted = len(data) diff --git a/addon/globalPlugins/MathCAT/yaml/representer.py b/addon/globalPlugins/MathCAT/yaml/representer.py index 808ca06d..3f6b7dda 100644 --- a/addon/globalPlugins/MathCAT/yaml/representer.py +++ b/addon/globalPlugins/MathCAT/yaml/representer.py @@ -1,6 +1,8 @@ -__all__ = ['BaseRepresenter', 'SafeRepresenter', 'Representer', - 'RepresenterError'] +__all__ = [ + 'BaseRepresenter', 'SafeRepresenter', 'Representer', + 'RepresenterError', +] from .error import * from .nodes import * @@ -230,44 +232,70 @@ def represent_yaml_object(self, tag, data, cls, flow_style=None): def represent_undefined(self, data): raise RepresenterError("cannot represent an object", data) -SafeRepresenter.add_representer(type(None), - SafeRepresenter.represent_none) - -SafeRepresenter.add_representer(str, - SafeRepresenter.represent_str) - -SafeRepresenter.add_representer(bytes, - SafeRepresenter.represent_binary) - -SafeRepresenter.add_representer(bool, - SafeRepresenter.represent_bool) - -SafeRepresenter.add_representer(int, - SafeRepresenter.represent_int) - -SafeRepresenter.add_representer(float, - SafeRepresenter.represent_float) - -SafeRepresenter.add_representer(list, - SafeRepresenter.represent_list) - -SafeRepresenter.add_representer(tuple, - SafeRepresenter.represent_list) - -SafeRepresenter.add_representer(dict, - SafeRepresenter.represent_dict) - -SafeRepresenter.add_representer(set, - SafeRepresenter.represent_set) - -SafeRepresenter.add_representer(datetime.date, - SafeRepresenter.represent_date) - -SafeRepresenter.add_representer(datetime.datetime, - SafeRepresenter.represent_datetime) - -SafeRepresenter.add_representer(None, - SafeRepresenter.represent_undefined) +SafeRepresenter.add_representer( + type(None), + SafeRepresenter.represent_none, +) + +SafeRepresenter.add_representer( + str, + SafeRepresenter.represent_str, +) + +SafeRepresenter.add_representer( + bytes, + SafeRepresenter.represent_binary, +) + +SafeRepresenter.add_representer( + bool, + SafeRepresenter.represent_bool, +) + +SafeRepresenter.add_representer( + int, + SafeRepresenter.represent_int, +) + +SafeRepresenter.add_representer( + float, + SafeRepresenter.represent_float, +) + +SafeRepresenter.add_representer( + list, + SafeRepresenter.represent_list, +) + +SafeRepresenter.add_representer( + tuple, + SafeRepresenter.represent_list, +) + +SafeRepresenter.add_representer( + dict, + SafeRepresenter.represent_dict, +) + +SafeRepresenter.add_representer( + set, + SafeRepresenter.represent_set, +) + +SafeRepresenter.add_representer( + datetime.date, + SafeRepresenter.represent_date, +) + +SafeRepresenter.add_representer( + datetime.datetime, + SafeRepresenter.represent_datetime, +) + +SafeRepresenter.add_representer( + None, + SafeRepresenter.represent_undefined, +) class Representer(SafeRepresenter): @@ -291,7 +319,8 @@ def represent_name(self, data): def represent_module(self, data): return self.represent_scalar( - 'tag:yaml.org,2002:python/module:'+data.__name__, '') + 'tag:yaml.org,2002:python/module:'+data.__name__, '', + ) def represent_object(self, data): # We use __reduce__ API to save the data. data.__reduce__ returns @@ -340,7 +369,8 @@ def represent_object(self, data): if not args and not listitems and not dictitems \ and isinstance(state, dict) and newobj: return self.represent_mapping( - 'tag:yaml.org,2002:python/object:'+function_name, state) + 'tag:yaml.org,2002:python/object:'+function_name, state, + ) if not listitems and not dictitems \ and isinstance(state, dict) and not state: return self.represent_sequence(tag+function_name, args) @@ -363,27 +393,42 @@ def represent_ordered_dict(self, data): items = [[key, value] for key, value in data.items()] return self.represent_sequence(tag, [items]) -Representer.add_representer(complex, - Representer.represent_complex) - -Representer.add_representer(tuple, - Representer.represent_tuple) - -Representer.add_multi_representer(type, - Representer.represent_name) - -Representer.add_representer(collections.OrderedDict, - Representer.represent_ordered_dict) - -Representer.add_representer(types.FunctionType, - Representer.represent_name) - -Representer.add_representer(types.BuiltinFunctionType, - Representer.represent_name) - -Representer.add_representer(types.ModuleType, - Representer.represent_module) - -Representer.add_multi_representer(object, - Representer.represent_object) - +Representer.add_representer( + complex, + Representer.represent_complex, +) + +Representer.add_representer( + tuple, + Representer.represent_tuple, +) + +Representer.add_multi_representer( + type, + Representer.represent_name, +) + +Representer.add_representer( + collections.OrderedDict, + Representer.represent_ordered_dict, +) + +Representer.add_representer( + types.FunctionType, + Representer.represent_name, +) + +Representer.add_representer( + types.BuiltinFunctionType, + Representer.represent_name, +) + +Representer.add_representer( + types.ModuleType, + Representer.represent_module, +) + +Representer.add_multi_representer( + object, + Representer.represent_object, +) diff --git a/addon/globalPlugins/MathCAT/yaml/resolver.py b/addon/globalPlugins/MathCAT/yaml/resolver.py index 3522bdaa..4b255b71 100644 --- a/addon/globalPlugins/MathCAT/yaml/resolver.py +++ b/addon/globalPlugins/MathCAT/yaml/resolver.py @@ -96,8 +96,10 @@ def descend_resolver(self, current_node, current_index): if current_node: depth = len(self.resolver_prefix_paths) for path, kind in self.resolver_prefix_paths[-1]: - if self.check_resolver_prefix(depth, path, kind, - current_node, current_index): + if self.check_resolver_prefix( + depth, path, kind, + current_node, current_index, + ): if len(path) > depth: prefix_paths.append((path, kind)) else: @@ -117,8 +119,10 @@ def ascend_resolver(self): self.resolver_exact_paths.pop() self.resolver_prefix_paths.pop() - def check_resolver_prefix(self, depth, path, kind, - current_node, current_index): + def check_resolver_prefix( + self, depth, path, kind, + current_node, current_index, + ): node_check, index_check = path[depth-1] if isinstance(node_check, str): if current_node.tag != node_check: @@ -132,8 +136,10 @@ def check_resolver_prefix(self, depth, path, kind, and current_index is None: return if isinstance(index_check, str): - if not (isinstance(current_index, ScalarNode) - and index_check == current_index.value): + if not ( + isinstance(current_index, ScalarNode) + and index_check == current_index.value + ): return elif isinstance(index_check, int) and not isinstance(index_check, bool): if index_check != current_index: @@ -169,59 +175,76 @@ class Resolver(BaseResolver): Resolver.add_implicit_resolver( 'tag:yaml.org,2002:bool', - re.compile(r'''^(?:yes|Yes|YES|no|No|NO + re.compile( + r'''^(?:yes|Yes|YES|no|No|NO |true|True|TRUE|false|False|FALSE - |on|On|ON|off|Off|OFF)$''', re.X), - list('yYnNtTfFoO')) + |on|On|ON|off|Off|OFF)$''', re.X, + ), + list('yYnNtTfFoO'), +) Resolver.add_implicit_resolver( 'tag:yaml.org,2002:float', - re.compile(r'''^(?:[-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+][0-9]+)? + re.compile( + r'''^(?:[-+]?(?:[0-9][0-9_]*)\.[0-9_]*(?:[eE][-+][0-9]+)? |\.[0-9][0-9_]*(?:[eE][-+][0-9]+)? |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]* |[-+]?\.(?:inf|Inf|INF) - |\.(?:nan|NaN|NAN))$''', re.X), - list('-+0123456789.')) + |\.(?:nan|NaN|NAN))$''', re.X, + ), + list('-+0123456789.'), +) Resolver.add_implicit_resolver( 'tag:yaml.org,2002:int', - re.compile(r'''^(?:[-+]?0b[0-1_]+ + re.compile( + r'''^(?:[-+]?0b[0-1_]+ |[-+]?0[0-7_]+ |[-+]?(?:0|[1-9][0-9_]*) |[-+]?0x[0-9a-fA-F_]+ - |[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$''', re.X), - list('-+0123456789')) + |[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$''', re.X, + ), + list('-+0123456789'), +) Resolver.add_implicit_resolver( 'tag:yaml.org,2002:merge', re.compile(r'^(?:<<)$'), - ['<']) + ['<'], +) Resolver.add_implicit_resolver( 'tag:yaml.org,2002:null', - re.compile(r'''^(?: ~ + re.compile( + r'''^(?: ~ |null|Null|NULL - | )$''', re.X), - ['~', 'n', 'N', '']) + | )$''', re.X, + ), + ['~', 'n', 'N', ''], +) Resolver.add_implicit_resolver( 'tag:yaml.org,2002:timestamp', - re.compile(r'''^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] + re.compile( + r'''^(?:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] |[0-9][0-9][0-9][0-9] -[0-9][0-9]? -[0-9][0-9]? (?:[Tt]|[ \t]+)[0-9][0-9]? :[0-9][0-9] :[0-9][0-9] (?:\.[0-9]*)? - (?:[ \t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$''', re.X), - list('0123456789')) + (?:[ \t]*(?:Z|[-+][0-9][0-9]?(?::[0-9][0-9])?))?)$''', re.X, + ), + list('0123456789'), +) Resolver.add_implicit_resolver( 'tag:yaml.org,2002:value', re.compile(r'^(?:=)$'), - ['=']) + ['='], +) # The following resolver is only for documentation purposes. It cannot work # because plain scalars cannot start with '!', '&', or '*'. Resolver.add_implicit_resolver( 'tag:yaml.org,2002:yaml', re.compile(r'^(?:!|&|\*)$'), - list('!&*')) - + list('!&*'), +) diff --git a/addon/globalPlugins/MathCAT/yaml/scanner.py b/addon/globalPlugins/MathCAT/yaml/scanner.py index de925b07..39e2791a 100644 --- a/addon/globalPlugins/MathCAT/yaml/scanner.py +++ b/addon/globalPlugins/MathCAT/yaml/scanner.py @@ -255,9 +255,11 @@ def fetch_more_tokens(self): return self.fetch_plain() # No? It's an error. Let's produce a nice error message. - raise ScannerError("while scanning for the next token", None, - "found character %r that cannot start any token" % ch, - self.get_mark()) + raise ScannerError( + "while scanning for the next token", None, + "found character %r that cannot start any token" % ch, + self.get_mark(), + ) # Simple keys treatment. @@ -288,8 +290,10 @@ def stale_possible_simple_keys(self): if key.line != self.line \ or self.index-key.index > 1024: if key.required: - raise ScannerError("while scanning a simple key", key.mark, - "could not find expected ':'", self.get_mark()) + raise ScannerError( + "while scanning a simple key", key.mark, + "could not find expected ':'", self.get_mark(), + ) del self.possible_simple_keys[level] def save_possible_simple_key(self): @@ -305,18 +309,22 @@ def save_possible_simple_key(self): if self.allow_simple_key: self.remove_possible_simple_key() token_number = self.tokens_taken+len(self.tokens) - key = SimpleKey(token_number, required, - self.index, self.line, self.column, self.get_mark()) + key = SimpleKey( + token_number, required, + self.index, self.line, self.column, self.get_mark(), + ) self.possible_simple_keys[self.flow_level] = key def remove_possible_simple_key(self): # Remove the saved possible key position at the current flow level. if self.flow_level in self.possible_simple_keys: key = self.possible_simple_keys[self.flow_level] - + if key.required: - raise ScannerError("while scanning a simple key", key.mark, - "could not find expected ':'", self.get_mark()) + raise ScannerError( + "while scanning a simple key", key.mark, + "could not find expected ':'", self.get_mark(), + ) del self.possible_simple_keys[self.flow_level] @@ -362,11 +370,15 @@ def fetch_stream_start(self): # Read the token. mark = self.get_mark() - + # Add STREAM-START. - self.tokens.append(StreamStartToken(mark, mark, - encoding=self.encoding)) - + self.tokens.append( + StreamStartToken( + mark, mark, + encoding=self.encoding, + ), + ) + def fetch_stream_end(self): @@ -380,7 +392,7 @@ def fetch_stream_end(self): # Read the token. mark = self.get_mark() - + # Add STREAM-END. self.tokens.append(StreamEndToken(mark, mark)) @@ -388,7 +400,7 @@ def fetch_stream_end(self): self.done = True def fetch_directive(self): - + # Set the current indentation to -1. self.unwind_indent(-1) @@ -488,9 +500,11 @@ def fetch_block_entry(self): # Are we allowed to start a new entry? if not self.allow_simple_key: - raise ScannerError(None, None, - "sequence entries are not allowed here", - self.get_mark()) + raise ScannerError( + None, None, + "sequence entries are not allowed here", + self.get_mark(), + ) # We may need to add BLOCK-SEQUENCE-START. if self.add_indent(self.column): @@ -515,15 +529,17 @@ def fetch_block_entry(self): self.tokens.append(BlockEntryToken(start_mark, end_mark)) def fetch_key(self): - + # Block context needs additional checks. if not self.flow_level: # Are we allowed to start a key (not necessary a simple)? if not self.allow_simple_key: - raise ScannerError(None, None, - "mapping keys are not allowed here", - self.get_mark()) + raise ScannerError( + None, None, + "mapping keys are not allowed here", + self.get_mark(), + ) # We may need to add BLOCK-MAPPING-START. if self.add_indent(self.column): @@ -550,22 +566,26 @@ def fetch_value(self): # Add KEY. key = self.possible_simple_keys[self.flow_level] del self.possible_simple_keys[self.flow_level] - self.tokens.insert(key.token_number-self.tokens_taken, - KeyToken(key.mark, key.mark)) + self.tokens.insert( + key.token_number-self.tokens_taken, + KeyToken(key.mark, key.mark), + ) # If this key starts a new block mapping, we need to add # BLOCK-MAPPING-START. if not self.flow_level: if self.add_indent(key.column): - self.tokens.insert(key.token_number-self.tokens_taken, - BlockMappingStartToken(key.mark, key.mark)) + self.tokens.insert( + key.token_number-self.tokens_taken, + BlockMappingStartToken(key.mark, key.mark), + ) # There cannot be two simple keys one after another. self.allow_simple_key = False # It must be a part of a complex key. else: - + # Block context needs additional checks. # (Do we really need them? They will be caught by the parser # anyway.) @@ -574,9 +594,11 @@ def fetch_value(self): # We are allowed to start a complex value if and only if # we can start a simple key. if not self.allow_simple_key: - raise ScannerError(None, None, - "mapping values are not allowed here", - self.get_mark()) + raise ScannerError( + None, None, + "mapping values are not allowed here", + self.get_mark(), + ) # If this value starts a new block mapping, we need to add # BLOCK-MAPPING-START. It will be detected as an error later by @@ -744,8 +766,10 @@ def check_plain(self): # independent. ch = self.peek() return ch not in '\0 \t\r\n\x85\u2028\u2029-?:,[]{}#&*!|>\'\"%@`' \ - or (self.peek(1) not in '\0 \t\r\n\x85\u2028\u2029' - and (ch == '-' or (not self.flow_level and ch in '?:'))) + or ( + self.peek(1) not in '\0 \t\r\n\x85\u2028\u2029' + and (ch == '-' or (not self.flow_level and ch in '?:')) + ) # Scanners. @@ -812,16 +836,20 @@ def scan_directive_name(self, start_mark): length += 1 ch = self.peek(length) if not length: - raise ScannerError("while scanning a directive", start_mark, - "expected alphabetic or numeric character, but found %r" - % ch, self.get_mark()) + raise ScannerError( + "while scanning a directive", start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark(), + ) value = self.prefix(length) self.forward(length) ch = self.peek() if ch not in '\0 \r\n\x85\u2028\u2029': - raise ScannerError("while scanning a directive", start_mark, - "expected alphabetic or numeric character, but found %r" - % ch, self.get_mark()) + raise ScannerError( + "while scanning a directive", start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark(), + ) return value def scan_yaml_directive_value(self, start_mark): @@ -830,23 +858,29 @@ def scan_yaml_directive_value(self, start_mark): self.forward() major = self.scan_yaml_directive_number(start_mark) if self.peek() != '.': - raise ScannerError("while scanning a directive", start_mark, - "expected a digit or '.', but found %r" % self.peek(), - self.get_mark()) + raise ScannerError( + "while scanning a directive", start_mark, + "expected a digit or '.', but found %r" % self.peek(), + self.get_mark(), + ) self.forward() minor = self.scan_yaml_directive_number(start_mark) if self.peek() not in '\0 \r\n\x85\u2028\u2029': - raise ScannerError("while scanning a directive", start_mark, - "expected a digit or ' ', but found %r" % self.peek(), - self.get_mark()) + raise ScannerError( + "while scanning a directive", start_mark, + "expected a digit or ' ', but found %r" % self.peek(), + self.get_mark(), + ) return (major, minor) def scan_yaml_directive_number(self, start_mark): # See the specification for details. ch = self.peek() if not ('0' <= ch <= '9'): - raise ScannerError("while scanning a directive", start_mark, - "expected a digit, but found %r" % ch, self.get_mark()) + raise ScannerError( + "while scanning a directive", start_mark, + "expected a digit, but found %r" % ch, self.get_mark(), + ) length = 0 while '0' <= self.peek(length) <= '9': length += 1 @@ -869,8 +903,10 @@ def scan_tag_directive_handle(self, start_mark): value = self.scan_tag_handle('directive', start_mark) ch = self.peek() if ch != ' ': - raise ScannerError("while scanning a directive", start_mark, - "expected ' ', but found %r" % ch, self.get_mark()) + raise ScannerError( + "while scanning a directive", start_mark, + "expected ' ', but found %r" % ch, self.get_mark(), + ) return value def scan_tag_directive_prefix(self, start_mark): @@ -878,8 +914,10 @@ def scan_tag_directive_prefix(self, start_mark): value = self.scan_tag_uri('directive', start_mark) ch = self.peek() if ch not in '\0 \r\n\x85\u2028\u2029': - raise ScannerError("while scanning a directive", start_mark, - "expected ' ', but found %r" % ch, self.get_mark()) + raise ScannerError( + "while scanning a directive", start_mark, + "expected ' ', but found %r" % ch, self.get_mark(), + ) return value def scan_directive_ignored_line(self, start_mark): @@ -891,9 +929,11 @@ def scan_directive_ignored_line(self, start_mark): self.forward() ch = self.peek() if ch not in '\0\r\n\x85\u2028\u2029': - raise ScannerError("while scanning a directive", start_mark, - "expected a comment or a line break, but found %r" - % ch, self.get_mark()) + raise ScannerError( + "while scanning a directive", start_mark, + "expected a comment or a line break, but found %r" + % ch, self.get_mark(), + ) self.scan_line_break() def scan_anchor(self, TokenClass): @@ -919,16 +959,20 @@ def scan_anchor(self, TokenClass): length += 1 ch = self.peek(length) if not length: - raise ScannerError("while scanning an %s" % name, start_mark, - "expected alphabetic or numeric character, but found %r" - % ch, self.get_mark()) + raise ScannerError( + "while scanning an %s" % name, start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark(), + ) value = self.prefix(length) self.forward(length) ch = self.peek() if ch not in '\0 \t\r\n\x85\u2028\u2029?:,]}%@`': - raise ScannerError("while scanning an %s" % name, start_mark, - "expected alphabetic or numeric character, but found %r" - % ch, self.get_mark()) + raise ScannerError( + "while scanning an %s" % name, start_mark, + "expected alphabetic or numeric character, but found %r" + % ch, self.get_mark(), + ) end_mark = self.get_mark() return TokenClass(value, start_mark, end_mark) @@ -941,9 +985,11 @@ def scan_tag(self): self.forward(2) suffix = self.scan_tag_uri('tag', start_mark) if self.peek() != '>': - raise ScannerError("while parsing a tag", start_mark, - "expected '>', but found %r" % self.peek(), - self.get_mark()) + raise ScannerError( + "while parsing a tag", start_mark, + "expected '>', but found %r" % self.peek(), + self.get_mark(), + ) self.forward() elif ch in '\0 \t\r\n\x85\u2028\u2029': handle = None @@ -967,8 +1013,10 @@ def scan_tag(self): suffix = self.scan_tag_uri('tag', start_mark) ch = self.peek() if ch not in '\0 \r\n\x85\u2028\u2029': - raise ScannerError("while scanning a tag", start_mark, - "expected ' ', but found %r" % ch, self.get_mark()) + raise ScannerError( + "while scanning a tag", start_mark, + "expected ' ', but found %r" % ch, self.get_mark(), + ) value = (handle, suffix) end_mark = self.get_mark() return TagToken(value, start_mark, end_mark) @@ -1017,14 +1065,14 @@ def scan_block_scalar(self, style): # Unfortunately, folding rules are ambiguous. # # This is the folding according to the specification: - + if folded and line_break == '\n' \ and leading_non_space and self.peek() not in ' \t': if not breaks: chunks.append(' ') else: chunks.append(line_break) - + # This is Clark Evans's interpretation (also in the spec # examples): # @@ -1046,8 +1094,10 @@ def scan_block_scalar(self, style): chunks.extend(breaks) # We are done. - return ScalarToken(''.join(chunks), False, start_mark, end_mark, - style) + return ScalarToken( + ''.join(chunks), False, start_mark, end_mark, + style, + ) def scan_block_scalar_indicators(self, start_mark): # See the specification for details. @@ -1064,16 +1114,20 @@ def scan_block_scalar_indicators(self, start_mark): if ch in '0123456789': increment = int(ch) if increment == 0: - raise ScannerError("while scanning a block scalar", start_mark, - "expected indentation indicator in the range 1-9, but found 0", - self.get_mark()) + raise ScannerError( + "while scanning a block scalar", start_mark, + "expected indentation indicator in the range 1-9, but found 0", + self.get_mark(), + ) self.forward() elif ch in '0123456789': increment = int(ch) if increment == 0: - raise ScannerError("while scanning a block scalar", start_mark, - "expected indentation indicator in the range 1-9, but found 0", - self.get_mark()) + raise ScannerError( + "while scanning a block scalar", start_mark, + "expected indentation indicator in the range 1-9, but found 0", + self.get_mark(), + ) self.forward() ch = self.peek() if ch in '+-': @@ -1084,9 +1138,11 @@ def scan_block_scalar_indicators(self, start_mark): self.forward() ch = self.peek() if ch not in '\0 \r\n\x85\u2028\u2029': - raise ScannerError("while scanning a block scalar", start_mark, - "expected chomping or indentation indicators, but found %r" - % ch, self.get_mark()) + raise ScannerError( + "while scanning a block scalar", start_mark, + "expected chomping or indentation indicators, but found %r" + % ch, self.get_mark(), + ) return chomping, increment def scan_block_scalar_ignored_line(self, start_mark): @@ -1098,9 +1154,11 @@ def scan_block_scalar_ignored_line(self, start_mark): self.forward() ch = self.peek() if ch not in '\0\r\n\x85\u2028\u2029': - raise ScannerError("while scanning a block scalar", start_mark, - "expected a comment or a line break, but found %r" % ch, - self.get_mark()) + raise ScannerError( + "while scanning a block scalar", start_mark, + "expected a comment or a line break, but found %r" % ch, + self.get_mark(), + ) self.scan_line_break() def scan_block_scalar_indentation(self): @@ -1152,8 +1210,10 @@ def scan_flow_scalar(self, style): chunks.extend(self.scan_flow_scalar_non_spaces(double, start_mark)) self.forward() end_mark = self.get_mark() - return ScalarToken(''.join(chunks), False, start_mark, end_mark, - style) + return ScalarToken( + ''.join(chunks), False, start_mark, end_mark, + style, + ) ESCAPE_REPLACEMENTS = { '0': '\0', @@ -1210,9 +1270,11 @@ def scan_flow_scalar_non_spaces(self, double, start_mark): self.forward() for k in range(length): if self.peek(k) not in '0123456789ABCDEFabcdef': - raise ScannerError("while scanning a double-quoted scalar", start_mark, - "expected escape sequence of %d hexadecimal numbers, but found %r" % - (length, self.peek(k)), self.get_mark()) + raise ScannerError( + "while scanning a double-quoted scalar", start_mark, + "expected escape sequence of %d hexadecimal numbers, but found %r" % + (length, self.peek(k)), self.get_mark(), + ) code = int(self.prefix(length), 16) chunks.append(chr(code)) self.forward(length) @@ -1220,8 +1282,10 @@ def scan_flow_scalar_non_spaces(self, double, start_mark): self.scan_line_break() chunks.extend(self.scan_flow_scalar_breaks(double, start_mark)) else: - raise ScannerError("while scanning a double-quoted scalar", start_mark, - "found unknown escape character %r" % ch, self.get_mark()) + raise ScannerError( + "while scanning a double-quoted scalar", start_mark, + "found unknown escape character %r" % ch, self.get_mark(), + ) else: return chunks @@ -1235,8 +1299,10 @@ def scan_flow_scalar_spaces(self, double, start_mark): self.forward(length) ch = self.peek() if ch == '\0': - raise ScannerError("while scanning a quoted scalar", start_mark, - "found unexpected end of stream", self.get_mark()) + raise ScannerError( + "while scanning a quoted scalar", start_mark, + "found unexpected end of stream", self.get_mark(), + ) elif ch in '\r\n\x85\u2028\u2029': line_break = self.scan_line_break() breaks = self.scan_flow_scalar_breaks(double, start_mark) @@ -1258,8 +1324,10 @@ def scan_flow_scalar_breaks(self, double, start_mark): prefix = self.prefix(3) if (prefix == '---' or prefix == '...') \ and self.peek(3) in '\0 \t\r\n\x85\u2028\u2029': - raise ScannerError("while scanning a quoted scalar", start_mark, - "found unexpected document separator", self.get_mark()) + raise ScannerError( + "while scanning a quoted scalar", start_mark, + "found unexpected document separator", self.get_mark(), + ) while self.peek() in ' \t': self.forward() if self.peek() in '\r\n\x85\u2028\u2029': @@ -1289,9 +1357,11 @@ def scan_plain(self): while True: ch = self.peek(length) if ch in '\0 \t\r\n\x85\u2028\u2029' \ - or (ch == ':' and - self.peek(length+1) in '\0 \t\r\n\x85\u2028\u2029' - + (u',[]{}' if self.flow_level else u''))\ + or ( + ch == ':' and + self.peek(length+1) in '\0 \t\r\n\x85\u2028\u2029' + + (u',[]{}' if self.flow_level else u'') + )\ or (self.flow_level and ch in ',?[]{}'): break length += 1 @@ -1351,8 +1421,10 @@ def scan_tag_handle(self, name, start_mark): # tag handles. I have allowed it anyway. ch = self.peek() if ch != '!': - raise ScannerError("while scanning a %s" % name, start_mark, - "expected '!', but found %r" % ch, self.get_mark()) + raise ScannerError( + "while scanning a %s" % name, start_mark, + "expected '!', but found %r" % ch, self.get_mark(), + ) length = 1 ch = self.peek(length) if ch != ' ': @@ -1362,8 +1434,10 @@ def scan_tag_handle(self, name, start_mark): ch = self.peek(length) if ch != '!': self.forward(length) - raise ScannerError("while scanning a %s" % name, start_mark, - "expected '!', but found %r" % ch, self.get_mark()) + raise ScannerError( + "while scanning a %s" % name, start_mark, + "expected '!', but found %r" % ch, self.get_mark(), + ) length += 1 value = self.prefix(length) self.forward(length) @@ -1390,8 +1464,10 @@ def scan_tag_uri(self, name, start_mark): self.forward(length) length = 0 if not chunks: - raise ScannerError("while parsing a %s" % name, start_mark, - "expected URI, but found %r" % ch, self.get_mark()) + raise ScannerError( + "while parsing a %s" % name, start_mark, + "expected URI, but found %r" % ch, self.get_mark(), + ) return ''.join(chunks) def scan_uri_escapes(self, name, start_mark): @@ -1402,9 +1478,11 @@ def scan_uri_escapes(self, name, start_mark): self.forward() for k in range(2): if self.peek(k) not in '0123456789ABCDEFabcdef': - raise ScannerError("while scanning a %s" % name, start_mark, - "expected URI escape sequence of 2 hexadecimal numbers, but found %r" - % self.peek(k), self.get_mark()) + raise ScannerError( + "while scanning a %s" % name, start_mark, + "expected URI escape sequence of 2 hexadecimal numbers, but found %r" + % self.peek(k), self.get_mark(), + ) codes.append(int(self.prefix(2), 16)) self.forward(2) try: diff --git a/addon/globalPlugins/MathCAT/yaml/serializer.py b/addon/globalPlugins/MathCAT/yaml/serializer.py index fe911e67..1962fb16 100644 --- a/addon/globalPlugins/MathCAT/yaml/serializer.py +++ b/addon/globalPlugins/MathCAT/yaml/serializer.py @@ -12,8 +12,10 @@ class Serializer: ANCHOR_TEMPLATE = 'id%03d' - def __init__(self, encoding=None, - explicit_start=None, explicit_end=None, version=None, tags=None): + def __init__( + self, encoding=None, + explicit_start=None, explicit_end=None, version=None, tags=None, + ): self.use_encoding = encoding self.use_explicit_start = explicit_start self.use_explicit_end = explicit_end @@ -48,8 +50,12 @@ def serialize(self, node): raise SerializerError("serializer is not opened") elif self.closed: raise SerializerError("serializer is closed") - self.emit(DocumentStartEvent(explicit=self.use_explicit_start, - version=self.use_version, tags=self.use_tags)) + self.emit( + DocumentStartEvent( + explicit=self.use_explicit_start, + version=self.use_version, tags=self.use_tags, + ), + ) self.anchor_node(node) self.serialize_node(node, None, None) self.emit(DocumentEndEvent(explicit=self.use_explicit_end)) @@ -86,26 +92,41 @@ def serialize_node(self, node, parent, index): detected_tag = self.resolve(ScalarNode, node.value, (True, False)) default_tag = self.resolve(ScalarNode, node.value, (False, True)) implicit = (node.tag == detected_tag), (node.tag == default_tag) - self.emit(ScalarEvent(alias, node.tag, implicit, node.value, - style=node.style)) + self.emit( + ScalarEvent( + alias, node.tag, implicit, node.value, + style=node.style, + ), + ) elif isinstance(node, SequenceNode): - implicit = (node.tag - == self.resolve(SequenceNode, node.value, True)) - self.emit(SequenceStartEvent(alias, node.tag, implicit, - flow_style=node.flow_style)) + implicit = ( + node.tag + == self.resolve(SequenceNode, node.value, True) + ) + self.emit( + SequenceStartEvent( + alias, node.tag, implicit, + flow_style=node.flow_style, + ), + ) index = 0 for item in node.value: self.serialize_node(item, node, index) index += 1 self.emit(SequenceEndEvent()) elif isinstance(node, MappingNode): - implicit = (node.tag - == self.resolve(MappingNode, node.value, True)) - self.emit(MappingStartEvent(alias, node.tag, implicit, - flow_style=node.flow_style)) + implicit = ( + node.tag + == self.resolve(MappingNode, node.value, True) + ) + self.emit( + MappingStartEvent( + alias, node.tag, implicit, + flow_style=node.flow_style, + ), + ) for key, value in node.value: self.serialize_node(key, node, None) self.serialize_node(value, node, key) self.emit(MappingEndEvent()) self.ascend_resolver() - diff --git a/addon/globalPlugins/MathCAT/yaml/tokens.py b/addon/globalPlugins/MathCAT/yaml/tokens.py index 4d0b48a3..f3111adf 100644 --- a/addon/globalPlugins/MathCAT/yaml/tokens.py +++ b/addon/globalPlugins/MathCAT/yaml/tokens.py @@ -4,11 +4,15 @@ def __init__(self, start_mark, end_mark): self.start_mark = start_mark self.end_mark = end_mark def __repr__(self): - attributes = [key for key in self.__dict__ - if not key.endswith('_mark')] + attributes = [ + key for key in self.__dict__ + if not key.endswith('_mark') + ] attributes.sort() - arguments = ', '.join(['%s=%r' % (key, getattr(self, key)) - for key in attributes]) + arguments = ', '.join([ + '%s=%r' % (key, getattr(self, key)) + for key in attributes + ]) return '%s(%s)' % (self.__class__.__name__, arguments) #class BOMToken(Token): @@ -30,8 +34,10 @@ class DocumentEndToken(Token): class StreamStartToken(Token): id = '' - def __init__(self, start_mark=None, end_mark=None, - encoding=None): + def __init__( + self, start_mark=None, end_mark=None, + encoding=None, + ): self.start_mark = start_mark self.end_mark = end_mark self.encoding = encoding @@ -101,4 +107,3 @@ def __init__(self, value, plain, start_mark, end_mark, style=None): self.start_mark = start_mark self.end_mark = end_mark self.style = style - diff --git a/addon/locale/de/LC_MESSAGES/nvda.po b/addon/locale/de/LC_MESSAGES/nvda.po new file mode 100644 index 00000000..b2e380e0 --- /dev/null +++ b/addon/locale/de/LC_MESSAGES/nvda.po @@ -0,0 +1,372 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the MathCAT package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: MathCAT 0.3.3\n" +"Report-Msgid-Bugs-To: nvda-translations@groups.io\n" +"POT-Creation-Date: 2023-08-14 03:25+0000\n" +"PO-Revision-Date: 2024-07-14 06:30+0200\n" +"Last-Translator: René Linke \n" +"Language-Team: \n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.4.4\n" + +#. Translators: this message directs users to look in the log file +msgid "Error in starting navigation of math: see NVDA error log for details" +msgstr "" +"Fehler beim Starten der mathematischen Navigation: Schauen Sie für Details " +"bitte im Fehlerprotokoll von NVDA nach" + +#. Translators: this message directs users to look in the log file +msgid "Error in brailling math: see NVDA error log for details" +msgstr "" +"Fehler in der mathematischen Braille-Darstellung: Schauen Sie für Details " +"bitte im Fehlerprotokoll von NVDA nach" + +#. Translators: this message directs users to look in the log file +msgid "Error in navigating math: see NVDA error log for details" +msgstr "" +"Fehler in der mathematischen Navigation: Schauen Sie für Details bitte im " +"Fehlerprotokoll von NVDA nach" + +#. Translators: Message to be announced during Keyboard Help +msgid "Copy navigation focus to clipboard" +msgstr "Navigationsfokus in die Zwischenablage kopieren" + +#. Translators: Name of the section in "Input gestures" dialog. +msgid "Clipboard" +msgstr "Zwischenablage" + +#. Translators: copy to clipboard +msgid "copy as " +msgstr "Kopieren als " + +#. Translators: this message directs users to look in the log file +msgid "unable to copy math: see NVDA error log for details" +msgstr "" +"Es konnte die mathematische Formel nicht kopiert werden: Schauen Sie für " +"Details bitte im Fehlerprotokoll von NVDA nach" + +#. Translators: this message directs users to look in the log file +msgid "MathCAT initialization failed: see NVDA error log for details" +msgstr "" +"Die Initialisierung von MathCAT ist fehlgeschlagen: Schauen Sie für Details " +"bitte im Fehlerprotokoll von NVDA nach" + +#. Translators: this message directs users to look in the log file +msgid "Illegal MathML found: see NVDA error log for details" +msgstr "" +"Ungültiges MathML gefunden: Schauen Sie für Details bitte im Fehlerprotokoll " +"von NVDA nach" + +#. Translators: this message directs users to look in the log file +msgid "Error in speaking math: see NVDA error log for details" +msgstr "" +"Fehler beim Vorlesen der mathematischen Formel: Schauen Sie für Details " +"bitte im Fehlerprotokoll von NVDA nach" + +#. Translators: menu item -- use the language of the voice chosen in the NVDA speech settings dialog +#. "Auto" == "Automatic" -- other items in menu are "English (en)", etc., so this matches that style +msgid "Use Voice's Language (Auto)" +msgstr "Die Sprache der Stimme verwenden (Automatisch)" + +#. Translators: this is a test string that is spoken. Only translate "the square root of x squared plus y squared" +msgid "" +"the square root of x squared plus y squared" +msgstr "" +"Die Quadratwurzel aus x zum Quadrat plus y zum Quadrat" + +#. Translators: this is a test string that is spoken. Only translate "the fraction with numerator" +#. and other parts NOT inside '<.../>', +#, python-brace-format +msgid "" +"the fraction with numerator x to the n -th power plus 1 and " +"denominator x to the " +"n -" +"thpower minus 1 " +"end fraction " +msgstr "" +"Der Bruch mit Zähler x zum n ten " +"Teil Bruchstrich plus 1 " +" und Nenner x zum n ten TeilBruchstrich " +" minus 1 " +"Ende des Bruchs " + +#. Translators: title for MathCAT preferences dialog +msgid "MathCAT Preferences" +msgstr "MathCAT-Einstellungen" + +#. Translators: A heading that labels three navigation pane tab names in the MathCAT dialog +msgid "Categories:" +msgstr "Kategorien:" + +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" +msgid "Speech" +msgstr "Sprachausgabe" + +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" +msgid "Navigation" +msgstr "Navigation" + +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" +msgid "Braille" +msgstr "Braille" + +#. Translators: this is the text label for whom to target the speech for (options are below) +msgid "Generate speech for:" +msgstr "Vorlesen generieren für:" + +#. Translators: these are the categories of impairments that MathCAT supports +#. Translators: Learning disabilities includes dyslexia and ADHD +msgid "Learning disabilities" +msgstr "Lernbehinderte" + +#. Translators: target people who are blind +msgid "Blindness" +msgstr "Blinde" + +#. Translators: target people who have low vision +msgid "Low vision" +msgstr "Sehbehinderte" + +#. Translators: label for pull down allowing users to choose the speech language for math +msgid "Language:" +msgstr "Sprache:" + +#. Translators: label for pull down to specify what character to use in numbers as the decimal separator +msgid "Decimal separator for numbers:" +msgstr "Dezimaltrennzeichen für Zahlen:" + +#. Translators: options for decimal separator -- "Auto" = automatically pick the choice based on the language +msgid "Auto" +msgstr "Automatisch" + +#. Translators: options for decimal separator -- "Custom" = user sets it +#. Currently there is no UI for how it is done yet, but eventually there will be a dialog that pops up to set it +msgid "Custom" +msgstr "Benutzerdefiniert" + +#. Translators: label for pull down allowing users to choose the "style" (version, rules) of speech for math +msgid "Speech style:" +msgstr "Sprachausgabe:" + +#. Translators: label for pull down to specify how verbose/terse the speech should be +msgid "Speech verbosity:" +msgstr "Sprachausführlichkeit:" + +#. Translators: options for speech verbosity -- "terse" = use less words +#. Translators: options for navigation verbosity -- "terse" = use less words +msgid "Terse" +msgstr "Kurz und bündig" + +#. Translators: options for speech verbosity -- "medium" = try to be nether too terse nor too verbose words +#. Translators: options for navigation verbosity -- "medium" = try to be nether too terse nor too verbose words +msgid "Medium" +msgstr "Medium" + +#. Translators: options for speech verbosity -- "verbose" = use more words +#. Translators: options for navigation verbosity -- "verbose" = use more words +msgid "Verbose" +msgstr "Ausführlich" + +#. Translators: label for slider that specifies a percentage of the normal speech rate that should be used for math +msgid "Relative speech rate:" +msgstr "Relative Sprechgeschwindigkeit:" + +#. Translators: label for slider that specifies relative factor to increase or decrease pauses in the math speech +msgid "Pause factor:" +msgstr "Pausen-Faktor:" + +#. Translators: label for check box controling a beep sound +msgid "Make a sound when starting/ending math speech" +msgstr "" +"Einen Sound wiedergeben, sobald eine mathematische Formel beginnt oder endet" + +#. Translators: label for pull down to specify a subject area (Geometry, Calculus, ...) +msgid "Subject area to be used when it cannot be determined automatically:" +msgstr "" +"Zu verwendender Themenbereich, sofern dieser nicht automatisch bestimmt " +"werden kann:" + +#. Translators: a generic (non-specific) math subject area +msgid "General" +msgstr "Allgemein" + +#. Translators: label for pull down to specify how verbose/terse the speech should be +msgid "Speech for chemical formulas:" +msgstr "Chemische Formeln sprechen:" + +#. Translators: values for chemistry options with example speech in parenthesis +msgid "Spell it out (H 2 O)" +msgstr "Lautschrift (H sub 2 O)" + +#. Translators: values for chemistry options with example speech in parenthesis (never interpret as chemistry) +msgid "Off (H sub 2 O)" +msgstr "Aus (H sub 2 O)" + +#. Translators: label for pull down to specify one of three modes use to navigate math expressions +msgid "Navigation mode to use when beginning to navigate an equation:" +msgstr "Navigationsmodus für den Beginn der Navigation in einer Gleichung:" + +#. Translators: names of different modes of navigation. "Enhanced" mode understands math structure +msgid "Enhanced" +msgstr "Erweitert" + +#. Translators: "Simple" walks by character expect for things like fractions, roots, and scripts +msgid "Simple" +msgstr "Einfach" + +#. Translators: "Character" moves around by character, automatically moving into fractions, etc +msgid "Character" +msgstr "Zeichen" + +#. Translators: label for checkbox that controls whether any changes to the navigation mode should be preserved +msgid "Reset navigation mode on entry to an expression" +msgstr "Navigationsmodus bei Eingabe eines Ausdrucks zurücksetzen" + +#. Translators: label for pull down to specify whether the expression is spoken or described (an overview) +msgid "Navigation speech to use when beginning to navigate an equation:" +msgstr "" +"Sprache bei der Navigation für den Beginn der Navigation in einer Gleichung:" + +#. Translators: "Speak" the expression after moving to it +msgid "Speak" +msgstr "Vorlesen" + +#. Translators: "Describe" the expression after moving to it ("overview is a synonym") +msgid "Describe/overview" +msgstr "Beschreibung/Übersicht" + +#. Translators: label for checkbox that controls whether any changes to the speak vs overview reading should be ignored +msgid "Reset navigation speech on entry to an expression" +msgstr "Sprache bei der Navigation bei Eingabe eines Ausdrucks zurücksetzen" + +#. Translators: label for checkbox that controls whether arrow keys move out of fractions, etc., +#. or whether you have to manually back out of the fraction, etc. +msgid "Automatic zoom out of 2D notations" +msgstr "Automatisches Herauszoomen von 2D-Notationen" + +#. Translators: label for pull down to specify whether you want a terse or verbose reading of navigation commands +msgid "Speech amount for navigation:" +msgstr "Sprachausführlichkeit für die Navigation:" + +#. Translators: label for pull down to specify how math will be copied to the clipboard +msgid "Copy math as:" +msgstr "Mathematische Formel kopieren als:" + +#. Translators: options for Copy expression to clipboard as -- "MathML" +msgid "MathML" +msgstr "MathML" + +#. Translators: options for Copy expression to clipboard as -- "LaTeX" +msgid "LaTeX" +msgstr "LaTeX" + +#. Translators: options for Copy expression to clipboard as -- "ASCIIMath" +msgid "ASCIIMath" +msgstr "ASCIIMath" + +#. Translators: label for pull down to specify which braille code to use +msgid "Braille math code for refreshable displays:" +msgstr "Mathematischer Braille-Code für aktualisierbare Braillezeilen:" + +#. Translators: label for pull down to specify how braille dots should be modified when navigating/selecting subexprs +msgid "Highlight with dots 7 && 8 the current nav node:" +msgstr "Mit Punkten 7 &und 8 den aktuellen Navigationsknoten markieren:" + +#. Translators: options for using dots 7 and 8: +#. Translators: "off" -- don't highlight +msgid "Off" +msgstr "Aus" + +#. Translators: "First character" -- only the first character of the current navigation node uses dots 7 & 8 +msgid "First character" +msgstr "Erstes Zeichen" + +#. Translators: "Endpoints" -- only the first and last character of the current navigation node uses dots 7 & 8 +msgid "Endpoints" +msgstr "Endpunkte" + +#. Translators: "All" -- all the characters for the current navigation node use dots 7 & 8 +msgid "All" +msgstr "Alles" + +#. Translators: dialog "ok" button +msgid "OK" +msgstr "OK" + +#. Translators: dialog "cancel" button +msgid "Cancel" +msgstr "Abbrechen" + +#. Translators: dialog "apply" button +msgid "Apply" +msgstr "Übernehmen" + +#. Translators: button to reset all the preferences to their default values +msgid "Reset to defaults" +msgstr "Auf Standardwerte zurücksetzen" + +#. Translators: button to bring up a help page +msgid "Help" +msgstr "Hilfe" + +#. Translators: this show up in the NVDA preferences dialog. It opens the MathCAT preferences dialog +msgid "&MathCAT Settings..." +msgstr "&MathCAT-Einstellungen..." + +#. Add-on summary, usually the user visible name of the addon. +#. Translators: Summary for this add-on +#. to be shown on installation and add-on information found in Add-ons Manager. +msgid "MathCAT: speech and braille from MathML" +msgstr "MathCAT: Sprachausgabe und Braille aus MathML" + +#. Translators: Long description to be shown for this add-on on add-on information from add-ons manager +msgid "" +"MathCAT is a replacement for MathPlayer which has been discontinued.\n" +" It provides speech and braille support, and also supports " +"MathPlayer's three modes of navigation.\n" +" The speech quality is not quite as good as MathPlayer's speech yet,\n" +" but the braille support is much better and includes support for " +"Nemeth, UEB Technical, CMU (Spanish/Portuguese),\n" +" and Vietnamese braille code standards. Translations to Chinese " +"(Traditional), Indonesian, Spanish, and Vietnamese exist\n" +" and other translations are in progress." +msgstr "" +"MathCAT ist ein Ersatz für den MathPlayer, der nicht mehr weiterentwickelt " +"wird.\n" +" Es bietet Unterstützung via Sprachausgaben und Braille und es werden " +"auch die drei Navigationsmodi von MathPlayer unterstützt.\n" +" Die Sprachqualität ist noch nicht ganz so gut wie die vom " +"MathPlayer,\n" +" aber die Braille-Unterstützung ist viel besser und umfasst " +"Unterstützung für Nemeth, UEB Technical, CMU (Spanisch/Portugiesisch),\n" +" und vietnamesischen Braille-Code-Standards. Es gibt Übersetzungen " +"ins Chinesische (traditionell), Indonesische, Spanische und Vietnamesische.\n" +" und weitere Übersetzungen sind in Arbeit." diff --git a/addon/locale/es/LC_MESSAGES/nvda.po b/addon/locale/es/LC_MESSAGES/nvda.po index e3151e7e..ced7c6e8 100644 --- a/addon/locale/es/LC_MESSAGES/nvda.po +++ b/addon/locale/es/LC_MESSAGES/nvda.po @@ -8,252 +8,334 @@ msgstr "" "Project-Id-Version: MathCAT 0.3.3\n" "Report-Msgid-Bugs-To: nvda-translations@groups.io\n" "POT-Creation-Date: 2023-08-14 03:25+0000\n" -"PO-Revision-Date: 2023-08-14 13:47+0200\n" +"PO-Revision-Date: 2024-06-21 19:33+0200\n" "Last-Translator: José Manuel Delicado \n" "Language-Team: \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 3.3.2\n" +"X-Generator: Poedit 3.4.4\n" +#. Translators: this message directs users to look in the log file msgid "Error in starting navigation of math: see NVDA error log for details" msgstr "" "Error al iniciar la navegación matemática: consulta el registro de NVDA para " "más detalles" +#. Translators: this message directs users to look in the log file msgid "Error in brailling math: see NVDA error log for details" msgstr "" "Error al mostrar matemáticas en braille: consulta el registro de NVDA para " "más detalles" +#. Translators: this message directs users to look in the log file msgid "Error in navigating math: see NVDA error log for details" msgstr "" "Error al navegar por el contenido matemático: consulta el registro de NVDA " "para más detalles" -#. For translators: Message to be announced during Keyboard Help +#. Translators: Message to be announced during Keyboard Help msgid "Copy navigation focus to clipboard" msgstr "Copiar el foco de navegación al portapapeles" -#. For translators: Name of the section in "Input gestures" dialog. +#. Translators: Name of the section in "Input gestures" dialog. msgid "Clipboard" msgstr "Portapapeles" -msgid "copy" -msgstr "copiar" +#. Translators: copy to clipboard +msgid "copy as " +msgstr "copiar como " +#. Translators: this message directs users to look in the log file msgid "unable to copy math: see NVDA error log for details" msgstr "" "no se ha podido copiar el contenido matemático: consulta el registro de NVDA " "para más detalles" +#. Translators: this message directs users to look in the log file msgid "MathCAT initialization failed: see NVDA error log for details" msgstr "" "Ha fallado la inicialización de MathCat: consulta el registro de NVDA para " "más detalles" +#. Translators: this message directs users to look in the log file msgid "Illegal MathML found: see NVDA error log for details" msgstr "" "Se ha encontrado MathML no válido: consulta el registro de NVDA para más " "detalles" +#. Translators: this message directs users to look in the log file msgid "Error in speaking math: see NVDA error log for details" msgstr "" "Error al verbalizar el contenido matemático: consulta el registro de NVDA " "para más detalles" +#. Translators: menu item -- use the language of the voice chosen in the NVDA speech settings dialog +#. "Auto" == "Automatic" -- other items in menu are "English (en)", etc., so this matches that style +msgid "Use Voice's Language (Auto)" +msgstr "Utilizar idioma de la voz (automático)" + +#. Translators: this is a test string that is spoken. Only translate "the square root of x squared plus y squared" msgid "" "the square root of x squared plus y squared" msgstr "" "la raíz cuadrada de x al cuadrado más y al cuadrado" -#, fuzzy, python-brace-format +#. Translators: this is a test string that is spoken. Only translate "the fraction with numerator" +#. and other parts NOT inside '<.../>', +#, python-brace-format msgid "" "the fraction with numerator x to the n -th power plus 1 and " -"denominator x to the n -th power " -" minus 1 end fraction " -"" +"time='{pause_factor_300}ms'/> x to the n -th power plus 1 and " +"denominator x to the " +"n -" +"thpower minus 1 " +"end fraction " msgstr "" "la fracción con numerador x a la n -ésima potencia más 1 y denominador " -"x a la " +"time='{pause_factor_300}ms'/> x a la n -ésima potencia más 1 y " +"denominador x a la " "n -" -"ésima potencia menos 1 fin de fracción " +"ésimapotencia " +" menos 1 " +"fin de fracción " +#. Translators: title for MathCAT preferences dialog msgid "MathCAT Preferences" msgstr "Preferencias de MathCat" +#. Translators: A heading that labels three navigation pane tab names in the MathCAT dialog msgid "Categories:" msgstr "Categorías:" +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" msgid "Speech" msgstr "Voz" +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" msgid "Navigation" msgstr "Navegación" +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" msgid "Braille" msgstr "Braille" +#. Translators: this is the text label for whom to target the speech for (options are below) msgid "Generate speech for:" msgstr "Generar mensajes hablados para:" +#. Translators: these are the categories of impairments that MathCAT supports +#. Translators: Learning disabilities includes dyslexia and ADHD msgid "Learning disabilities" msgstr "Discapacidad cognitiva" +#. Translators: target people who are blind msgid "Blindness" msgstr "Ceguera" +#. Translators: target people who have low vision msgid "Low vision" msgstr "Baja visión" +#. Translators: label for pull down allowing users to choose the speech language for math msgid "Language:" msgstr "Idioma:" -msgid "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -msgstr "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +#. Translators: label for pull down to specify what character to use in numbers as the decimal separator +msgid "Decimal separator for numbers:" +msgstr "Separador decimal de números:" + +#. Translators: options for decimal separator -- "Auto" = automatically pick the choice based on the language +msgid "Auto" +msgstr "Automático" +#. Translators: options for decimal separator -- "Custom" = user sets it +#. Currently there is no UI for how it is done yet, but eventually there will be a dialog that pops up to set it +msgid "Custom" +msgstr "Personalizado" + +#. Translators: label for pull down allowing users to choose the "style" (version, rules) of speech for math msgid "Speech style:" msgstr "Estilo del habla:" -msgid "xxxxxxxxxxxxxxxx" -msgstr "xxxxxxxxxxxxxxxx" - -msgid "Speech amount:" -msgstr "Cantidad de habla:" +#. Translators: label for pull down to specify how verbose/terse the speech should be +msgid "Speech verbosity:" +msgstr "Verbosidad del habla:" +#. Translators: options for speech verbosity -- "terse" = use less words +#. Translators: options for navigation verbosity -- "terse" = use less words msgid "Terse" msgstr "Breve" +#. Translators: options for speech verbosity -- "medium" = try to be nether too terse nor too verbose words +#. Translators: options for navigation verbosity -- "medium" = try to be nether too terse nor too verbose words msgid "Medium" msgstr "Media" +#. Translators: options for speech verbosity -- "verbose" = use more words +#. Translators: options for navigation verbosity -- "verbose" = use more words msgid "Verbose" msgstr "Detallada" +#. Translators: label for slider that specifies a percentage of the normal speech rate that should be used for math msgid "Relative speech rate:" msgstr "Velocidad relativa de la voz:" +#. Translators: label for slider that specifies relative factor to increase or decrease pauses in the math speech msgid "Pause factor:" msgstr "Factor de pausa:" +#. Translators: label for check box controling a beep sound msgid "Make a sound when starting/ending math speech" msgstr "" "Reproducir un sonido al empezar y terminar de verbalizar contenido matemático" +#. Translators: label for pull down to specify a subject area (Geometry, Calculus, ...) msgid "Subject area to be used when it cannot be determined automatically:" msgstr "" "Área temática a utilizar cuando no se puede determinar automáticamente:" +#. Translators: a generic (non-specific) math subject area msgid "General" msgstr "General" +#. Translators: label for pull down to specify how verbose/terse the speech should be msgid "Speech for chemical formulas:" msgstr "Verbalización de fórmulas químicas:" +#. Translators: values for chemistry options with example speech in parenthesis msgid "Spell it out (H 2 O)" msgstr "Deletrear (H 2 O)" +#. Translators: values for chemistry options with example speech in parenthesis (never interpret as chemistry) msgid "Off (H sub 2 O)" msgstr "Desactivada (H sub 2 O)" -msgid "a page" -msgstr "una página" - +#. Translators: label for pull down to specify one of three modes use to navigate math expressions msgid "Navigation mode to use when beginning to navigate an equation:" msgstr "Modo de navegación a usar al empezar a navegar por una ecuación:" +#. Translators: names of different modes of navigation. "Enhanced" mode understands math structure msgid "Enhanced" msgstr "Mejorado" +#. Translators: "Simple" walks by character expect for things like fractions, roots, and scripts msgid "Simple" msgstr "Simple" +#. Translators: "Character" moves around by character, automatically moving into fractions, etc msgid "Character" msgstr "Carácter" +#. Translators: label for checkbox that controls whether any changes to the navigation mode should be preserved msgid "Reset navigation mode on entry to an expression" msgstr "Restablecer el modo de navegación al entrar en una expresión" +#. Translators: label for pull down to specify whether the expression is spoken or described (an overview) msgid "Navigation speech to use when beginning to navigate an equation:" msgstr "Modo de voz a usar cuando se empieza a navegar por una ecuación:" +#. Translators: "Speak" the expression after moving to it msgid "Speak" msgstr "Verbalizar" +#. Translators: "Describe" the expression after moving to it ("overview is a synonym") msgid "Describe/overview" msgstr "Describir/resumir" +#. Translators: label for checkbox that controls whether any changes to the speak vs overview reading should be ignored msgid "Reset navigation speech on entry to an expression" msgstr "" "Restablecer los ajustes de voz de navegación al entrar en una expresión" +#. Translators: label for checkbox that controls whether arrow keys move out of fractions, etc., +#. or whether you have to manually back out of the fraction, etc. msgid "Automatic zoom out of 2D notations" msgstr "Alejarse automáticamente en notaciones 2D" +#. Translators: label for pull down to specify whether you want a terse or verbose reading of navigation commands msgid "Speech amount for navigation:" msgstr "Cantidad de habla durante la navegación:" -msgid "Braille math code for refreshable displays:" -msgstr "Código matemático para pantallas braille:" +#. Translators: label for pull down to specify how math will be copied to the clipboard +msgid "Copy math as:" +msgstr "Copiar matemáticas como:" + +#. Translators: options for Copy expression to clipboard as -- "MathML" +msgid "MathML" +msgstr "MathML" -msgid "Nemeth" -msgstr "Nemeth" +#. Translators: options for Copy expression to clipboard as -- "LaTeX" +msgid "LaTeX" +msgstr "LaTeX" -msgid "UEB" -msgstr "UEB" +#. Translators: options for Copy expression to clipboard as -- "ASCIIMath" +msgid "ASCIIMath" +msgstr "ASCIIMath" -msgid "Vietnam" -msgstr "Vietnam" +#. Translators: label for pull down to specify which braille code to use +msgid "Braille math code for refreshable displays:" +msgstr "Código matemático para pantallas braille:" +#. Translators: label for pull down to specify how braille dots should be modified when navigating/selecting subexprs msgid "Highlight with dots 7 && 8 the current nav node:" msgstr "Resaltar con los puntos 7 y 8 el nodo actual de navegación:" +#. Translators: options for using dots 7 and 8: +#. Translators: "off" -- don't highlight msgid "Off" msgstr "Desactivado" +#. Translators: "First character" -- only the first character of the current navigation node uses dots 7 & 8 msgid "First character" msgstr "Primer carácter" +#. Translators: "Endpoints" -- only the first and last character of the current navigation node uses dots 7 & 8 msgid "Endpoints" msgstr "Extremos" +#. Translators: "All" -- all the characters for the current navigation node use dots 7 & 8 msgid "All" msgstr "Todo" +#. Translators: dialog "ok" button msgid "OK" msgstr "Aceptar" +#. Translators: dialog "cancel" button msgid "Cancel" msgstr "Cancelar" +#. Translators: dialog "apply" button msgid "Apply" msgstr "Aplicar" +#. Translators: button to reset all the preferences to their default values msgid "Reset to defaults" msgstr "Restaurar valores por defecto" +#. Translators: button to bring up a help page msgid "Help" msgstr "Ayuda" +#. Translators: this show up in the NVDA preferences dialog. It opens the MathCAT preferences dialog msgid "&MathCAT Settings..." msgstr "Opciones de &MathCat..." @@ -263,25 +345,24 @@ msgstr "Opciones de &MathCat..." msgid "MathCAT: speech and braille from MathML" msgstr "MathCat: voz y braille a partir de MathML" -#. Add-on description #. Translators: Long description to be shown for this add-on on add-on information from add-ons manager -#, fuzzy msgid "" "MathCAT is a replacement for MathPlayer which has been discontinued.\n" -"\t\tIt provides speech and braille support, and also supports MathPlayer's " -"three modes of navigation.\n" -"\t\tThe speech quality is not quite as good as MathPlayer's speech yet,\n" -"\t\tbut the braille support is much better and includes both Nemeth and UEB " -"Technical. It also includes support for CMU (Spanish/Portuguese) and " -"Vietnamese braille standards.\n" -"\t\tTranslations to Indonesian, Spanish, and Vietnamese exist and other " -"translations are in progress." +" It provides speech and braille support, and also supports " +"MathPlayer's three modes of navigation.\n" +" The speech quality is not quite as good as MathPlayer's speech yet,\n" +" but the braille support is much better and includes support for " +"Nemeth, UEB Technical, CMU (Spanish/Portuguese),\n" +" and Vietnamese braille code standards. Translations to Chinese " +"(Traditional), Indonesian, Spanish, and Vietnamese exist\n" +" and other translations are in progress." msgstr "" "MathCAT es un sustituto de MathPlayer, que ha sido descontinuado.\n" -"\t\tProporciona soporte de voz y braille, y también soporta los tres modos " -"de navegación de MathPlayer.\n" -"\t\tLa calidad de la voz aún no es tan buena como la de MathPlayer,\n" -"\t\tpero el soporte braille es mucho mejor e incluye tanto Nemeth como UEB " -"Technical.\n" -"\t\tExisten traducciones al indonesio, español y vietnamita, y hay otras " -"traducciones en progreso." +" Proporciona soporte de voz y braille, y también soporta los tres " +"modos de navegación de MathPlayer.\n" +" La calidad de la voz aún no es tan buena como la de MathPlayer,\n" +" pero el soporte braille es mucho mejor e incluye Nemeth, UEB " +"Técnico, CMU (español / portugués)\n" +" y braille vietnamita. Existen traducciones al chino (tradicional), " +"indonesio, español y vietnamita,\n" +" y hay otras traducciones en progreso." diff --git a/addon/locale/fi/LC_MESSAGES/nvda.po b/addon/locale/fi/LC_MESSAGES/nvda.po index 6b20103a..3c1345a4 100644 --- a/addon/locale/fi/LC_MESSAGES/nvda.po +++ b/addon/locale/fi/LC_MESSAGES/nvda.po @@ -8,248 +8,332 @@ msgstr "" "Project-Id-Version: MathCAT 0.3.3\n" "Report-Msgid-Bugs-To: nvda-translations@groups.io\n" "POT-Creation-Date: 2023-08-14 03:25+0000\n" -"PO-Revision-Date: 2023-08-16 21:24+0200\n" -"Last-Translator: Jani Kinnunen \n" +"PO-Revision-Date: 2024-06-11 09:50+0300\n" +"Last-Translator: Sami Määttä \n" "Language-Team: fi_FI \n" "Language: fi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 1.6.11\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.4.4\n" "X-Poedit-SourceCharset: UTF-8\n" +#. Translators: this message directs users to look in the log file msgid "Error in starting navigation of math: see NVDA error log for details" msgstr "" -"Virhe matemaattisen yhtälön navigoinnin aloittamisessa. Katso tarkemmat " -"tiedot NVDA:n virhelokista." +"Virhe matemaattisen yhtälön navigoinnin aloittamisessa: Katso tarkemmat " +"tiedot NVDA:n virhelokista" +#. Translators: this message directs users to look in the log file msgid "Error in brailling math: see NVDA error log for details" msgstr "" -"Virhe matemaattisen yhtälön näyttämisessä pistekirjoituksena. Katso " -"tarkemmat tiedot NVDA:n virhelokista." +"Virhe matemaattisen yhtälön näyttämisessä pistekirjoituksena: Katso " +"tarkemmat tiedot NVDA:n virhelokista" +#. Translators: this message directs users to look in the log file msgid "Error in navigating math: see NVDA error log for details" msgstr "" -"Virhe matemaattisen yhtälön navigoinnissa. Katso tarkemmat tiedot NVDA:n " -"virhelokista." +"Virhe matemaattisen yhtälön navigoinnissa: Katso tarkemmat tiedot NVDA:n " +"virhelokista" -#. For translators: Message to be announced during Keyboard Help +#. Translators: Message to be announced during Keyboard Help msgid "Copy navigation focus to clipboard" msgstr "Kopioi navigoinnin kohdistus leikepöydälle" -#. For translators: Name of the section in "Input gestures" dialog. +#. Translators: Name of the section in "Input gestures" dialog. msgid "Clipboard" msgstr "Leikepöytä" -msgid "copy" +#. Translators: copy to clipboard +#, fuzzy +msgid "copy as " msgstr "Kopioi" +#. Translators: this message directs users to look in the log file msgid "unable to copy math: see NVDA error log for details" msgstr "" -"Matemaattista yhtälöä ei voi kopioida. Katso tarkemmat tiedot NVDA:n " -"virhelokista." +"Matemaattista yhtälöä ei voi kopioida: Katso tarkemmat tiedot NVDA:n " +"virhelokista" +#. Translators: this message directs users to look in the log file msgid "MathCAT initialization failed: see NVDA error log for details" msgstr "" -"MathCATin alustus epäonnistui. Katso tarkemmat tiedot NVDA:n virhelokista." +"MathCATin alustus epäonnistui: Katso tarkemmat tiedot NVDA:n virhelokista" +#. Translators: this message directs users to look in the log file msgid "Illegal MathML found: see NVDA error log for details" msgstr "" -"Löytyi virheellinen MathML-merkintä. Katso tarkemmat tiedot NVDA:n " -"virhelokista." +"Löytyi virheellinen MathML-merkintä: Katso tarkemmat tiedot NVDA:n " +"virhelokista" +#. Translators: this message directs users to look in the log file msgid "Error in speaking math: see NVDA error log for details" msgstr "" -"Virhe puhuttaessa matemaattista yhtälöä. Katso tarkemmat tiedot NVDA:n " -"virhelokista." +"Virhe puhuttaessa matemaattista yhtälöä: Katso tarkemmat tiedot NVDA:n " +"virhelokista" + +#. Translators: menu item -- use the language of the voice chosen in the NVDA speech settings dialog +#. "Auto" == "Automatic" -- other items in menu are "English (en)", etc., so this matches that style +msgid "Use Voice's Language (Auto)" +msgstr "Käytä puheäänen kieltä (automaattinen)" +#. Translators: this is a test string that is spoken. Only translate "the square root of x squared plus y squared" msgid "" "the square root of x squared plus y squared" -msgstr "neliöjuuri x:n neliöstä plus y:n neliö" +msgstr "neliöjuuri x neliö plus y neliö" -#, fuzzy, python-brace-format +#. Translators: this is a test string that is spoken. Only translate "the fraction with numerator" +#. and other parts NOT inside '<.../>', +#, python-brace-format msgid "" "the fraction with numerator x to the n -th power plus 1 and " -"denominator x to the n -th power " -" minus 1 end fraction " -"" +"time='{pause_factor_300}ms'/> x to the n -th power plus 1 and " +"denominator x to the " +"n -" +"thpower minus 1 " +"end fraction " msgstr "" -"murtoluku, jonka osoittaja on x murtoluku jonka osoittaja on x n potenssiin plus 1 ja " +"nimittäjä x n potenssiin plus 1 ja nimittäjä " -"x n potenssiin miinus 1 murtoluvun loppu " +"phoneme>potenssiin miinus 1 " +"loppu murtoluku " +#. Translators: title for MathCAT preferences dialog msgid "MathCAT Preferences" msgstr "MathCATin asetukset" +#. Translators: A heading that labels three navigation pane tab names in the MathCAT dialog msgid "Categories:" msgstr "Kategoriat:" +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" msgid "Speech" msgstr "Puhe" +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" msgid "Navigation" msgstr "Navigointi" +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" msgid "Braille" msgstr "Pistekirjoitus" +#. Translators: this is the text label for whom to target the speech for (options are below) msgid "Generate speech for:" -msgstr "Puhu:" +msgstr "Vamma:" +#. Translators: these are the categories of impairments that MathCAT supports +#. Translators: Learning disabilities includes dyslexia and ADHD msgid "Learning disabilities" -msgstr "Oppimisvaikeudet" +msgstr "Oppimisvaikeus" +#. Translators: target people who are blind msgid "Blindness" msgstr "Sokeus" +#. Translators: target people who have low vision msgid "Low vision" -msgstr "Heikkonäköiset" +msgstr "Heikkonäköisyys" +#. Translators: label for pull down allowing users to choose the speech language for math msgid "Language:" msgstr "Kieli:" -msgid "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -msgstr "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +#. Translators: label for pull down to specify what character to use in numbers as the decimal separator +msgid "Decimal separator for numbers:" +msgstr "" -msgid "Speech style:" -msgstr "Puheen tyyli:" +#. Translators: options for decimal separator -- "Auto" = automatically pick the choice based on the language +msgid "Auto" +msgstr "" -msgid "xxxxxxxxxxxxxxxx" -msgstr "xxxxxxxxxxxxxxxx" +#. Translators: options for decimal separator -- "Custom" = user sets it +#. Currently there is no UI for how it is done yet, but eventually there will be a dialog that pops up to set it +msgid "Custom" +msgstr "" -msgid "Speech amount:" +#. Translators: label for pull down allowing users to choose the "style" (version, rules) of speech for math +msgid "Speech style:" +msgstr "Puhetyyli:" + +#. Translators: label for pull down to specify how verbose/terse the speech should be +msgid "Speech verbosity:" msgstr "Puheen määrä:" +#. Translators: options for speech verbosity -- "terse" = use less words +#. Translators: options for navigation verbosity -- "terse" = use less words msgid "Terse" msgstr "Suppea" +#. Translators: options for speech verbosity -- "medium" = try to be nether too terse nor too verbose words +#. Translators: options for navigation verbosity -- "medium" = try to be nether too terse nor too verbose words msgid "Medium" msgstr "Keskitaso" +#. Translators: options for speech verbosity -- "verbose" = use more words +#. Translators: options for navigation verbosity -- "verbose" = use more words msgid "Verbose" -msgstr "Yksityiskohtainen" +msgstr "Runsas" +#. Translators: label for slider that specifies a percentage of the normal speech rate that should be used for math msgid "Relative speech rate:" -msgstr "Suhteellisen puheen nopeus:" +msgstr "Puheen suhteellinen nopeus:" +#. Translators: label for slider that specifies relative factor to increase or decrease pauses in the math speech msgid "Pause factor:" msgstr "Taukokerroin:" +#. Translators: label for check box controling a beep sound msgid "Make a sound when starting/ending math speech" -msgstr "Toista ääni, kun matemaattisen yhtälön puhuminen aloitetaan/lopetetaan" +msgstr "" +"Toista äänimerkki, kun matemaattisen sisällön puhuminen aloitetaan/lopetetaan" +#. Translators: label for pull down to specify a subject area (Geometry, Calculus, ...) msgid "Subject area to be used when it cannot be determined automatically:" msgstr "Käytettävä aihealue, kun sitä ei voida määrittää automaattisesti:" +#. Translators: a generic (non-specific) math subject area msgid "General" msgstr "Yleiset" +#. Translators: label for pull down to specify how verbose/terse the speech should be msgid "Speech for chemical formulas:" msgstr "Kemiallisten kaavojen puhuminen:" +#. Translators: values for chemistry options with example speech in parenthesis msgid "Spell it out (H 2 O)" msgstr "Tavaaminen (H 2 O)" +#. Translators: values for chemistry options with example speech in parenthesis (never interpret as chemistry) msgid "Off (H sub 2 O)" msgstr "Pois käytöstä (H ala 2 O)" -msgid "a page" -msgstr "sivu" - +#. Translators: label for pull down to specify one of three modes use to navigate math expressions msgid "Navigation mode to use when beginning to navigate an equation:" -msgstr "Käytettävä navigointitila aloitettaessa yhtälössä navigointia:" +msgstr "Navigointitila, kun siirryt matemaattisen sisältöön:" +#. Translators: names of different modes of navigation. "Enhanced" mode understands math structure msgid "Enhanced" msgstr "Laajennettu" +#. Translators: "Simple" walks by character expect for things like fractions, roots, and scripts msgid "Simple" msgstr "Yksinkertainen" +#. Translators: "Character" moves around by character, automatically moving into fractions, etc msgid "Character" msgstr "Merkki" +#. Translators: label for checkbox that controls whether any changes to the navigation mode should be preserved msgid "Reset navigation mode on entry to an expression" msgstr "Nollaa navigointitila lausekkeeseen siirryttäessä" +#. Translators: label for pull down to specify whether the expression is spoken or described (an overview) msgid "Navigation speech to use when beginning to navigate an equation:" -msgstr "Käytettävä navigointipuhe aloitettaessa yhtälössä navigointia:" +msgstr "Navigointipuheen tyyli, kun aloitat lausekkeessa navigoimisen:" +#. Translators: "Speak" the expression after moving to it msgid "Speak" msgstr "Puhu" +#. Translators: "Describe" the expression after moving to it ("overview is a synonym") msgid "Describe/overview" msgstr "Kuvaile/anna yhteenveto" +#. Translators: label for checkbox that controls whether any changes to the speak vs overview reading should be ignored msgid "Reset navigation speech on entry to an expression" -msgstr "Nollaa navigointipuhe lausekkeeseen siirryttäessä" +msgstr "Nollaa navigointipuhe, kun siirryt uuteen lausekkeeseen" +#. Translators: label for checkbox that controls whether arrow keys move out of fractions, etc., +#. or whether you have to manually back out of the fraction, etc. msgid "Automatic zoom out of 2D notations" -msgstr "2D-merkintöjen automaattinen loitontaminen" +msgstr "Siirry automaattisesti uloimmalle tasolle 2D-merkintöjen jälkeen" +#. Translators: label for pull down to specify whether you want a terse or verbose reading of navigation commands msgid "Speech amount for navigation:" msgstr "Puheen määrä navigoitaessa:" -msgid "Braille math code for refreshable displays:" -msgstr "Pistekirjoituksen matematiikkakoodi:" +#. Translators: label for pull down to specify how math will be copied to the clipboard +msgid "Copy math as:" +msgstr "" -msgid "Nemeth" -msgstr "Nemeth" +#. Translators: options for Copy expression to clipboard as -- "MathML" +msgid "MathML" +msgstr "" -msgid "UEB" -msgstr "UEB" +#. Translators: options for Copy expression to clipboard as -- "LaTeX" +msgid "LaTeX" +msgstr "" + +#. Translators: options for Copy expression to clipboard as -- "ASCIIMath" +msgid "ASCIIMath" +msgstr "" -msgid "Vietnam" -msgstr "Vietnam" +#. Translators: label for pull down to specify which braille code to use +msgid "Braille math code for refreshable displays:" +msgstr "Matematiikan pistekirjoitusstandardi:" +#. Translators: label for pull down to specify how braille dots should be modified when navigating/selecting subexprs msgid "Highlight with dots 7 && 8 the current nav node:" -msgstr "Korosta nykyinen navigointisolmu pisteillä 7 ja 8:" +msgstr "Korosta nykyinen navigointikohta pisteillä 7 ja 8:" +#. Translators: options for using dots 7 and 8: +#. Translators: "off" -- don't highlight msgid "Off" msgstr "Pois käytöstä" +#. Translators: "First character" -- only the first character of the current navigation node uses dots 7 & 8 msgid "First character" msgstr "Ensimmäinen merkki" +#. Translators: "Endpoints" -- only the first and last character of the current navigation node uses dots 7 & 8 msgid "Endpoints" msgstr "Päätepisteet" +#. Translators: "All" -- all the characters for the current navigation node use dots 7 & 8 msgid "All" msgstr "Kaikki" +#. Translators: dialog "ok" button msgid "OK" msgstr "OK" +#. Translators: dialog "cancel" button msgid "Cancel" msgstr "Peruuta" +#. Translators: dialog "apply" button msgid "Apply" msgstr "Käytä" +#. Translators: button to reset all the preferences to their default values msgid "Reset to defaults" msgstr "Palauta oletukset" +#. Translators: button to bring up a help page msgid "Help" msgstr "Ohje" +#. Translators: this show up in the NVDA preferences dialog. It opens the MathCAT preferences dialog msgid "&MathCAT Settings..." msgstr "&MathCATin asetukset..." @@ -259,25 +343,46 @@ msgstr "&MathCATin asetukset..." msgid "MathCAT: speech and braille from MathML" msgstr "MathCAT: MathML:ää puheella ja pistekirjoituksella" -#. Add-on description #. Translators: Long description to be shown for this add-on on add-on information from add-ons manager -#, fuzzy msgid "" "MathCAT is a replacement for MathPlayer which has been discontinued.\n" -"\t\tIt provides speech and braille support, and also supports MathPlayer's " -"three modes of navigation.\n" -"\t\tThe speech quality is not quite as good as MathPlayer's speech yet,\n" -"\t\tbut the braille support is much better and includes both Nemeth and UEB " -"Technical. It also includes support for CMU (Spanish/Portuguese) and " -"Vietnamese braille standards.\n" -"\t\tTranslations to Indonesian, Spanish, and Vietnamese exist and other " -"translations are in progress." +" It provides speech and braille support, and also supports " +"MathPlayer's three modes of navigation.\n" +" The speech quality is not quite as good as MathPlayer's speech yet,\n" +" but the braille support is much better and includes support for " +"Nemeth, UEB Technical, CMU (Spanish/Portuguese),\n" +" and Vietnamese braille code standards. Translations to Chinese " +"(Traditional), Indonesian, Spanish, and Vietnamese exist\n" +" and other translations are in progress." msgstr "" "MathCAT on korvaaja MathPlayerille, jonka kehitys on lopetettu.\n" -"\t\tSe tarjoaa puhe- ja pistekirjoitustuen ja tukee myös MathPlayerin kolmea " -"navigointitilaa.\n" -"\t\tPuheen laatu ei ole vielä aivan yhtä hyvä kuin MathPlayerilla,\n" -"\t\tmutta pistekirjoituksen tuki on paljon parempi ja sisältää sekä Nemethin " -"että UEB Technicalin.\n" -"\t\tKäännökset indonesiaksi, espanjaksi ja vietnamiksi ovat käytettävissä, " -"ja muita käännöksiä on työn alla." +" Se tarjoaa puhe- ja pistekirjoitustuen sekä tukee myös MathPlayerin " +"kolmea navigointitilaa.\n" +" Puheen laatu ei vielä ole ihan yhtä hyvä kuin MathPlayerilla,\n" +" mutta pistekirjoituksen tuki on paljon parempi ja sisältää sekä " +"Nemethin että UEB Technicalin. Siinä on myös tuki\n" +" espanjalaiselle/portugalilaiselle CMU- ja vietnamilaisille " +"pistekirjoitussstandardeille. Käännökset kiinaksi (perinteinen),\n" +" indonesiaksi, espanjaksi ja vietnamiksi ovat käytettävissä, ja muita " +"käännöksiä on työn alla." + +#~ msgid "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +#~ msgstr "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + +#~ msgid "xxxxxxxxxxxxxxxx" +#~ msgstr "xxxxxxxxxxxxxxxx" + +#~ msgid "Speech amount:" +#~ msgstr "Puheen määrä:" + +#~ msgid "a page" +#~ msgstr "sivu" + +#~ msgid "Nemeth" +#~ msgstr "Nemeth" + +#~ msgid "UEB" +#~ msgstr "UEB" + +#~ msgid "Vietnam" +#~ msgstr "Vietnam" diff --git a/addon/locale/fr/LC_MESSAGES/nvda.po b/addon/locale/fr/LC_MESSAGES/nvda.po index 8a3641e0..15213fe0 100644 --- a/addon/locale/fr/LC_MESSAGES/nvda.po +++ b/addon/locale/fr/LC_MESSAGES/nvda.po @@ -8,258 +8,340 @@ msgstr "" "Project-Id-Version: MathCAT 0.3.3\n" "Report-Msgid-Bugs-To: nvda-translations@groups.io\n" "POT-Creation-Date: 2023-08-14 03:25+0000\n" -"PO-Revision-Date: 2023-08-15 05:56+0200\n" +"PO-Revision-Date: 2023-11-03 15:45+0100\n" "Last-Translator: Rémy Ruiz \n" "Language-Team: \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 1.8.8\n" +"X-Generator: Poedit 3.2.2\n" +#. Translators: this message directs users to look in the log file msgid "Error in starting navigation of math: see NVDA error log for details" msgstr "" "Erreur lors du démarrage de la navigation mathématique : voir le journal de " "NVDA pour plus de détails" +#. Translators: this message directs users to look in the log file msgid "Error in brailling math: see NVDA error log for details" msgstr "" "Erreur lors de l'affichage des mathématiques en braille : voir le journal de " "NVDA pour plus de détails" +#. Translators: this message directs users to look in the log file msgid "Error in navigating math: see NVDA error log for details" msgstr "" "Erreur lors de la navigation à travers du contenu mathématique : voir le " "journal de NVDA pour plus de détails" -#. For translators: Message to be announced during Keyboard Help +#. Translators: Message to be announced during Keyboard Help msgid "Copy navigation focus to clipboard" msgstr "Copier le focus de navigation au presse-papiers" -#. For translators: Name of the section in "Input gestures" dialog. +#. Translators: Name of the section in "Input gestures" dialog. msgid "Clipboard" msgstr "Presse-papiers" -msgid "copy" +#. Translators: copy to clipboard +#, fuzzy +msgid "copy as " msgstr "copier" +#. Translators: this message directs users to look in the log file msgid "unable to copy math: see NVDA error log for details" msgstr "" "impossible de copier le contenu mathématique : voir le journal de NVDA pour " "plus de détails" +#. Translators: this message directs users to look in the log file msgid "MathCAT initialization failed: see NVDA error log for details" msgstr "" "L'initialisation de MathCAT a échoué : voir le journal de NVDA pour plus de " "détails" +#. Translators: this message directs users to look in the log file msgid "Illegal MathML found: see NVDA error log for details" msgstr "" "MathML non valide trouvé : voir le journal de NVDA pour plus de détails" +#. Translators: this message directs users to look in the log file msgid "Error in speaking math: see NVDA error log for details" msgstr "" "Erreur lors de la verbalisation du contenu mathématique : voir le journal de " "NVDA pour plus de détails" +#. Translators: menu item -- use the language of the voice chosen in the NVDA speech settings dialog +#. "Auto" == "Automatic" -- other items in menu are "English (en)", etc., so this matches that style +msgid "Use Voice's Language (Auto)" +msgstr "" + +#. Translators: this is a test string that is spoken. Only translate "the square root of x squared plus y squared" msgid "" "the square root of x squared plus y squared" msgstr "" "la racine carrée de x au carré plus y au carré" +#. Translators: this is a test string that is spoken. Only translate "the fraction with numerator" +#. and other parts NOT inside '<.../>', #, fuzzy, python-brace-format msgid "" "the fraction with numerator x to the n -th power plus 1 and " -"denominator x to the n -th power " -" minus 1 end fraction " -"" +"time='{pause_factor_300}ms'/> x to the n -th power plus 1 and " +"denominator x to the " +"n -" +"thpower minus 1 " +"end fraction " msgstr "" "la fraction avec numérateur x à la n -ème puissance plus 1 et dénominateur x à la n -ème puissance " -" moins 1 fin de fraction " +"time='{pause_factor_300}ms'/> x à la n -ème " +"puissance " +"plus 1 et " +"dénominateur x à la n -ème puissance moins 1 fin de fraction " +"" +#. Translators: title for MathCAT preferences dialog msgid "MathCAT Preferences" msgstr "Préférences MathCAT" +#. Translators: A heading that labels three navigation pane tab names in the MathCAT dialog msgid "Categories:" msgstr "Catégories :" +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" msgid "Speech" msgstr "Parole" +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" msgid "Navigation" msgstr "Navigation" +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" msgid "Braille" msgstr "Braille" +#. Translators: this is the text label for whom to target the speech for (options are below) msgid "Generate speech for:" msgstr "Générer la parole pour :" +#. Translators: these are the categories of impairments that MathCAT supports +#. Translators: Learning disabilities includes dyslexia and ADHD msgid "Learning disabilities" msgstr "Difficultés d'apprentissage" +#. Translators: target people who are blind msgid "Blindness" msgstr "Cécité" +#. Translators: target people who have low vision msgid "Low vision" msgstr "Basse vision" +#. Translators: label for pull down allowing users to choose the speech language for math msgid "Language:" msgstr "Langue :" -msgid "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -msgstr "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +#. Translators: label for pull down to specify what character to use in numbers as the decimal separator +msgid "Decimal separator for numbers:" +msgstr "" + +#. Translators: options for decimal separator -- "Auto" = automatically pick the choice based on the language +msgid "Auto" +msgstr "" + +#. Translators: options for decimal separator -- "Custom" = user sets it +#. Currently there is no UI for how it is done yet, but eventually there will be a dialog that pops up to set it +msgid "Custom" +msgstr "" +#. Translators: label for pull down allowing users to choose the "style" (version, rules) of speech for math msgid "Speech style:" msgstr "Style de la parole :" -msgid "xxxxxxxxxxxxxxxx" -msgstr "xxxxxxxxxxxxxxxx" - -msgid "Speech amount:" -msgstr "Quantité de parole :" +#. Translators: label for pull down to specify how verbose/terse the speech should be +#, fuzzy +msgid "Speech verbosity:" +msgstr "Style de la parole :" +#. Translators: options for speech verbosity -- "terse" = use less words +#. Translators: options for navigation verbosity -- "terse" = use less words msgid "Terse" msgstr "Laconique" +#. Translators: options for speech verbosity -- "medium" = try to be nether too terse nor too verbose words +#. Translators: options for navigation verbosity -- "medium" = try to be nether too terse nor too verbose words msgid "Medium" msgstr "Moyen" +#. Translators: options for speech verbosity -- "verbose" = use more words +#. Translators: options for navigation verbosity -- "verbose" = use more words msgid "Verbose" msgstr "Verbeux" +#. Translators: label for slider that specifies a percentage of the normal speech rate that should be used for math msgid "Relative speech rate:" msgstr "Débit relatif de la parole :" +#. Translators: label for slider that specifies relative factor to increase or decrease pauses in the math speech msgid "Pause factor:" msgstr "Facteur de pause :" +#. Translators: label for check box controling a beep sound msgid "Make a sound when starting/ending math speech" msgstr "" "Émettre un son lors du début / fin de la verbalisation du contenu " "mathématique" +#. Translators: label for pull down to specify a subject area (Geometry, Calculus, ...) msgid "Subject area to be used when it cannot be determined automatically:" msgstr "" "Zone thématique à utiliser lorsque vous ne pouvez pas déterminer " "automatiquement :" +#. Translators: a generic (non-specific) math subject area msgid "General" msgstr "Général" +#. Translators: label for pull down to specify how verbose/terse the speech should be msgid "Speech for chemical formulas:" msgstr "Verbalisation des formules chimiques :" +#. Translators: values for chemistry options with example speech in parenthesis msgid "Spell it out (H 2 O)" msgstr "Épeler (H 2 O)" +#. Translators: values for chemistry options with example speech in parenthesis (never interpret as chemistry) msgid "Off (H sub 2 O)" msgstr "Désactivée (H sub 2 O)" -msgid "a page" -msgstr "une page" - +#. Translators: label for pull down to specify one of three modes use to navigate math expressions msgid "Navigation mode to use when beginning to navigate an equation:" msgstr "" "Mode de navigation à utiliser lorsque vous commencez à naviguer dans une " "équation :" +#. Translators: names of different modes of navigation. "Enhanced" mode understands math structure msgid "Enhanced" msgstr "Amélioré" +#. Translators: "Simple" walks by character expect for things like fractions, roots, and scripts msgid "Simple" msgstr "Simple" +#. Translators: "Character" moves around by character, automatically moving into fractions, etc msgid "Character" msgstr "Caractère" +#. Translators: label for checkbox that controls whether any changes to the navigation mode should be preserved msgid "Reset navigation mode on entry to an expression" msgstr "" "Réinitialiser le mode de navigation lors de l'entrée dans une expression" +#. Translators: label for pull down to specify whether the expression is spoken or described (an overview) msgid "Navigation speech to use when beginning to navigate an equation:" msgstr "" "Mode de parole à utiliser lorsque vous commencez à naviguer dans une " "équation :" +#. Translators: "Speak" the expression after moving to it msgid "Speak" msgstr "Verbaliser" +#. Translators: "Describe" the expression after moving to it ("overview is a synonym") msgid "Describe/overview" msgstr "Décrire / résumer" +#. Translators: label for checkbox that controls whether any changes to the speak vs overview reading should be ignored msgid "Reset navigation speech on entry to an expression" msgstr "" "Réinitialiser les paramètres de la parole de navigation lors de l'entrée " "dans une expression" +#. Translators: label for checkbox that controls whether arrow keys move out of fractions, etc., +#. or whether you have to manually back out of the fraction, etc. msgid "Automatic zoom out of 2D notations" msgstr "Zoomer en arrière automatiquement des notations 2D" +#. Translators: label for pull down to specify whether you want a terse or verbose reading of navigation commands msgid "Speech amount for navigation:" msgstr "Quantité de parole pendant la navigation :" -msgid "Braille math code for refreshable displays:" -msgstr "Code mathématique pour les afficheurs braille :" +#. Translators: label for pull down to specify how math will be copied to the clipboard +msgid "Copy math as:" +msgstr "" + +#. Translators: options for Copy expression to clipboard as -- "MathML" +msgid "MathML" +msgstr "" -msgid "Nemeth" -msgstr "Nemeth" +#. Translators: options for Copy expression to clipboard as -- "LaTeX" +msgid "LaTeX" +msgstr "" -msgid "UEB" -msgstr "UEB" +#. Translators: options for Copy expression to clipboard as -- "ASCIIMath" +msgid "ASCIIMath" +msgstr "" -msgid "Vietnam" -msgstr "Vietnam" +#. Translators: label for pull down to specify which braille code to use +msgid "Braille math code for refreshable displays:" +msgstr "Code mathématique pour les afficheurs braille :" +#. Translators: label for pull down to specify how braille dots should be modified when navigating/selecting subexprs msgid "Highlight with dots 7 && 8 the current nav node:" msgstr "Mise en évidence avec les points 7 et 8 Le nœud de navigation actuel :" +#. Translators: options for using dots 7 and 8: +#. Translators: "off" -- don't highlight msgid "Off" msgstr "Désactivé" +#. Translators: "First character" -- only the first character of the current navigation node uses dots 7 & 8 msgid "First character" msgstr "Premier caractère" +#. Translators: "Endpoints" -- only the first and last character of the current navigation node uses dots 7 & 8 msgid "Endpoints" msgstr "Extrêmes" +#. Translators: "All" -- all the characters for the current navigation node use dots 7 & 8 msgid "All" msgstr "Tous" +#. Translators: dialog "ok" button msgid "OK" msgstr "OK" +#. Translators: dialog "cancel" button msgid "Cancel" msgstr "Annuler" +#. Translators: dialog "apply" button msgid "Apply" msgstr "Appliquer" +#. Translators: button to reset all the preferences to their default values msgid "Reset to defaults" msgstr "Réinitialiser aux valeurs par défaut" +#. Translators: button to bring up a help page msgid "Help" msgstr "Aide" +#. Translators: this show up in the NVDA preferences dialog. It opens the MathCAT preferences dialog msgid "&MathCAT Settings..." msgstr "Paramètres &MathCAT..." @@ -269,19 +351,18 @@ msgstr "Paramètres &MathCAT..." msgid "MathCAT: speech and braille from MathML" msgstr "MathCAT : parole et braille à partir de MathML" -#. Add-on description #. Translators: Long description to be shown for this add-on on add-on information from add-ons manager #, fuzzy msgid "" "MathCAT is a replacement for MathPlayer which has been discontinued.\n" -"\t\tIt provides speech and braille support, and also supports MathPlayer's " -"three modes of navigation.\n" -"\t\tThe speech quality is not quite as good as MathPlayer's speech yet,\n" -"\t\tbut the braille support is much better and includes both Nemeth and UEB " -"Technical. It also includes support for CMU (Spanish/Portuguese) and " -"Vietnamese braille standards.\n" -"\t\tTranslations to Indonesian, Spanish, and Vietnamese exist and other " -"translations are in progress." +" It provides speech and braille support, and also supports " +"MathPlayer's three modes of navigation.\n" +" The speech quality is not quite as good as MathPlayer's speech yet,\n" +" but the braille support is much better and includes support for " +"Nemeth, UEB Technical, CMU (Spanish/Portuguese),\n" +" and Vietnamese braille code standards. Translations to Chinese " +"(Traditional), Indonesian, Spanish, and Vietnamese exist\n" +" and other translations are in progress." msgstr "" "MathCAT remplace MathPlayer qui a été abandonné.\n" "\t\tIl fournit un support de parole et de braille et prend également en " @@ -289,6 +370,28 @@ msgstr "" "\t\tLa qualité de la parole n'est pas encore aussi bonne que la parole de " "MathPlayer,\n" "\t\tmais le support en braille est bien meilleur et comprend à la fois " -"Nemeth et UEB technique.\n" +"Nemeth et UEB technique. Il inclut également un support des normes braille " +"CMU (espagnol/portugais) et vietnamien.\n" "\t\tLes traductions de l'Indonésien, de l'espagnol et du vietnamien existent " "et d'autres traductions sont en cours." + +#~ msgid "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +#~ msgstr "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + +#~ msgid "xxxxxxxxxxxxxxxx" +#~ msgstr "xxxxxxxxxxxxxxxx" + +#~ msgid "Speech amount:" +#~ msgstr "Quantité de parole :" + +#~ msgid "a page" +#~ msgstr "une page" + +#~ msgid "Nemeth" +#~ msgstr "Nemeth" + +#~ msgid "UEB" +#~ msgstr "UEB" + +#~ msgid "Vietnam" +#~ msgstr "Vietnam" diff --git a/addon/locale/pt_BR/LC_MESSAGES/nvda.po b/addon/locale/pt_BR/LC_MESSAGES/nvda.po new file mode 100644 index 00000000..ca89730f --- /dev/null +++ b/addon/locale/pt_BR/LC_MESSAGES/nvda.po @@ -0,0 +1,352 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2024-2025 NVDA Contributors. +# This file is distributed under the same license as the MathCAT package. +# Josevan Barbosa Fernandes , 2024-2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: MathCAT 0.3.3\n" +"Report-Msgid-Bugs-To: nvda-translations@groups.io\n" +"POT-Creation-Date: 2023-08-14 03:25+0000\n" +"PO-Revision-Date: 2025-05-10 17:54-0300\n" +"Last-Translator: Josevan Barbosa Fernandes \n" +"Language-Team: NVDA Brazilian Portuguese translation team (Equipe de " +"tradução do NVDA para Português Brasileiro) \n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.6\n" + +#. Translators: this message directs users to look in the log file +msgid "Error in starting navigation of math: see NVDA error log for details" +msgstr "" +"Erro ao iniciar a navegação de matemática: consulte o registro de erros do " +"NVDA para obter detalhes" + +#. Translators: this message directs users to look in the log file +msgid "Error in brailling math: see NVDA error log for details" +msgstr "" +"Erro no brailling math: consulte o registro de erros do NVDA para obter " +"detalhes" + +#. Translators: this message directs users to look in the log file +msgid "Error in navigating math: see NVDA error log for details" +msgstr "" +"Erro na navegação de matemática: consulte o registro de erros do NVDA para " +"obter detalhes" + +#. Translators: Message to be announced during Keyboard Help +msgid "Copy navigation focus to clipboard" +msgstr "Copie o foco de navegação para a área de transferência" + +#. Translators: Name of the section in "Input gestures" dialog. +msgid "Clipboard" +msgstr "Área de transferência" + +#. Translators: copy to clipboard +msgid "copy as " +msgstr "compiar como " + +#. Translators: this message directs users to look in the log file +msgid "unable to copy math: see NVDA error log for details" +msgstr "" +"incapaz de copiar matemática : consulte o registro de erros do NVDA para " +"obter detalhes" + +#. Translators: this message directs users to look in the log file +msgid "MathCAT initialization failed: see NVDA error log for details" +msgstr "" +"Falha na inicialização do MathCAT: consulte o registro de erros do NVDA para " +"obter detalhes" + +#. Translators: this message directs users to look in the log file +msgid "Illegal MathML found: see NVDA error log for details" +msgstr "" +"MathML ilegal encontrado: consulte o registro de erros do NVDA para obter " +"detalhes" + +#. Translators: this message directs users to look in the log file +msgid "Error in speaking math: see NVDA error log for details" +msgstr "" +"Erro ao falar matemática: consulte o registro de erros do NVDA para obter " +"detalhes" + +#. Translators: menu item -- use the language of the voice chosen in the NVDA speech settings dialog +#. "Auto" == "Automatic" -- other items in menu are "English (en)", etc., so this matches that style +msgid "Use Voice's Language (Auto)" +msgstr "Usar o idioma da voz (automático)" + +#. Translators: this is a test string that is spoken. Only translate "the square root of x squared plus y squared" +msgid "" +"the square root of x squared plus y squared" +msgstr "" + +#. Translators: this is a test string that is spoken. Only translate "the fraction with numerator" +#. and other parts NOT inside '<.../>', +#, python-brace-format +msgid "" +"the fraction with numerator x to the n -th power plus 1 and " +"denominator x to the " +"n -" +"thpower minus 1 " +"end fraction " +msgstr "" + +#. Translators: title for MathCAT preferences dialog +msgid "MathCAT Preferences" +msgstr "Preferências do MathCAT" + +#. Translators: A heading that labels three navigation pane tab names in the MathCAT dialog +msgid "Categories:" +msgstr "Categorias:" + +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" +msgid "Speech" +msgstr "Fala" + +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" +msgid "Navigation" +msgstr "Navegação" + +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" +msgid "Braille" +msgstr "Braille" + +#. Translators: this is the text label for whom to target the speech for (options are below) +msgid "Generate speech for:" +msgstr "Gerar fala para:" + +#. Translators: these are the categories of impairments that MathCAT supports +#. Translators: Learning disabilities includes dyslexia and ADHD +msgid "Learning disabilities" +msgstr "Dificuldades de aprendizagem" + +#. Translators: target people who are blind +msgid "Blindness" +msgstr "Cegueira" + +#. Translators: target people who have low vision +msgid "Low vision" +msgstr "Baixa visão" + +#. Translators: label for pull down allowing users to choose the speech language for math +msgid "Language:" +msgstr "Idioma:" + +#. Translators: label for pull down to specify what character to use in numbers as the decimal separator +msgid "Decimal separator for numbers:" +msgstr "" + +#. Translators: options for decimal separator -- "Auto" = automatically pick the choice based on the language +msgid "Auto" +msgstr "" + +#. Translators: options for decimal separator -- "Custom" = user sets it +#. Currently there is no UI for how it is done yet, but eventually there will be a dialog that pops up to set it +msgid "Custom" +msgstr "Personalizado" + +#. Translators: label for pull down allowing users to choose the "style" (version, rules) of speech for math +msgid "Speech style:" +msgstr "Estilo de fala:" + +#. Translators: label for pull down to specify how verbose/terse the speech should be +msgid "Speech verbosity:" +msgstr "Verbosidade de fala:" + +#. Translators: options for speech verbosity -- "terse" = use less words +#. Translators: options for navigation verbosity -- "terse" = use less words +msgid "Terse" +msgstr "Fraco" + +#. Translators: options for speech verbosity -- "medium" = try to be nether too terse nor too verbose words +#. Translators: options for navigation verbosity -- "medium" = try to be nether too terse nor too verbose words +msgid "Medium" +msgstr "Médio" + +#. Translators: options for speech verbosity -- "verbose" = use more words +#. Translators: options for navigation verbosity -- "verbose" = use more words +msgid "Verbose" +msgstr "Verboso" + +#. Translators: label for slider that specifies a percentage of the normal speech rate that should be used for math +msgid "Relative speech rate:" +msgstr "Taxa de fala relativa:" + +#. Translators: label for slider that specifies relative factor to increase or decrease pauses in the math speech +msgid "Pause factor:" +msgstr "Fator de pausa:" + +#. Translators: label for check box controling a beep sound +msgid "Make a sound when starting/ending math speech" +msgstr "Fazer um som ao iniciar/terminar a fala em matemática" + +#. Translators: label for pull down to specify a subject area (Geometry, Calculus, ...) +msgid "Subject area to be used when it cannot be determined automatically:" +msgstr "" +"Área de assunto a ser usada quando não puder ser determinada automaticamente:" + +#. Translators: a generic (non-specific) math subject area +msgid "General" +msgstr "Geral" + +#. Translators: label for pull down to specify how verbose/terse the speech should be +msgid "Speech for chemical formulas:" +msgstr "Fala para fórmulas químicas:" + +#. Translators: values for chemistry options with example speech in parenthesis +msgid "Spell it out (H 2 O)" +msgstr "Soletre (H 2 O)" + +#. Translators: values for chemistry options with example speech in parenthesis (never interpret as chemistry) +msgid "Off (H sub 2 O)" +msgstr "" + +#. Translators: label for pull down to specify one of three modes use to navigate math expressions +msgid "Navigation mode to use when beginning to navigate an equation:" +msgstr "Modo de navegação a ser usado ao começar a navegar em uma equação:" + +#. Translators: names of different modes of navigation. "Enhanced" mode understands math structure +msgid "Enhanced" +msgstr "Aprimorado" + +#. Translators: "Simple" walks by character expect for things like fractions, roots, and scripts +msgid "Simple" +msgstr "Simples" + +#. Translators: "Character" moves around by character, automatically moving into fractions, etc +msgid "Character" +msgstr "Caractere" + +#. Translators: label for checkbox that controls whether any changes to the navigation mode should be preserved +msgid "Reset navigation mode on entry to an expression" +msgstr "Redefinir o modo de navegação ao entrar em uma expressão" + +#. Translators: label for pull down to specify whether the expression is spoken or described (an overview) +msgid "Navigation speech to use when beginning to navigate an equation:" +msgstr "Fala navegação a ser usado ao começar a navegar em uma equação:" + +#. Translators: "Speak" the expression after moving to it +msgid "Speak" +msgstr "Fala" + +#. Translators: "Describe" the expression after moving to it ("overview is a synonym") +msgid "Describe/overview" +msgstr "Descrição/visão geral" + +#. Translators: label for checkbox that controls whether any changes to the speak vs overview reading should be ignored +msgid "Reset navigation speech on entry to an expression" +msgstr "Redefinir a fala de navegação ao entrar em uma expressão" + +#. Translators: label for checkbox that controls whether arrow keys move out of fractions, etc., +#. or whether you have to manually back out of the fraction, etc. +msgid "Automatic zoom out of 2D notations" +msgstr "Redução automática do zoom de notações 2D" + +#. Translators: label for pull down to specify whether you want a terse or verbose reading of navigation commands +msgid "Speech amount for navigation:" +msgstr "Quantidade de fala para navegação:" + +#. Translators: label for pull down to specify how math will be copied to the clipboard +msgid "Copy math as:" +msgstr "Copiar matemática como:" + +#. Translators: options for Copy expression to clipboard as -- "MathML" +msgid "MathML" +msgstr "" + +#. Translators: options for Copy expression to clipboard as -- "LaTeX" +msgid "LaTeX" +msgstr "" + +#. Translators: options for Copy expression to clipboard as -- "ASCIIMath" +msgid "ASCIIMath" +msgstr "" + +#. Translators: label for pull down to specify which braille code to use +msgid "Braille math code for refreshable displays:" +msgstr "Código matemático em Braille para telas atualizáveis:" + +#. Translators: label for pull down to specify how braille dots should be modified when navigating/selecting subexprs +msgid "Highlight with dots 7 && 8 the current nav node:" +msgstr "" + +#. Translators: options for using dots 7 and 8: +#. Translators: "off" -- don't highlight +msgid "Off" +msgstr "Desligado" + +#. Translators: "First character" -- only the first character of the current navigation node uses dots 7 & 8 +msgid "First character" +msgstr "Primeiro caractere" + +#. Translators: "Endpoints" -- only the first and last character of the current navigation node uses dots 7 & 8 +msgid "Endpoints" +msgstr "Pontos finais" + +#. Translators: "All" -- all the characters for the current navigation node use dots 7 & 8 +msgid "All" +msgstr "Todo" + +#. Translators: dialog "ok" button +msgid "OK" +msgstr "Ok" + +#. Translators: dialog "cancel" button +msgid "Cancel" +msgstr "Cancelar" + +#. Translators: dialog "apply" button +msgid "Apply" +msgstr "Aplicar" + +#. Translators: button to reset all the preferences to their default values +msgid "Reset to defaults" +msgstr "Redefinir para os padrões" + +#. Translators: button to bring up a help page +msgid "Help" +msgstr "Ajuda" + +#. Translators: this show up in the NVDA preferences dialog. It opens the MathCAT preferences dialog +msgid "&MathCAT Settings..." +msgstr "Configurações do &MathCAT..." + +#. Add-on summary, usually the user visible name of the addon. +#. Translators: Summary for this add-on +#. to be shown on installation and add-on information found in Add-ons Manager. +msgid "MathCAT: speech and braille from MathML" +msgstr "" + +#. Translators: Long description to be shown for this add-on on add-on information from add-ons manager +msgid "" +"MathCAT is a replacement for MathPlayer which has been discontinued.\n" +" It provides speech and braille support, and also supports " +"MathPlayer's three modes of navigation.\n" +" The speech quality is not quite as good as MathPlayer's speech yet,\n" +" but the braille support is much better and includes support for " +"Nemeth, UEB Technical, CMU (Spanish/Portuguese),\n" +" and Vietnamese braille code standards. Translations to Chinese " +"(Traditional), Indonesian, Spanish, and Vietnamese exist\n" +" and other translations are in progress." +msgstr "" +"O MathCAT é um substituto para o MathPlayer, que foi descontinuado.\n" +" Ele oferece suporte a fala e braile e também suporta os três modos " +"de navegação do MathPlayer.\n" +" A qualidade da fala ainda não é tão boa quanto a do MathPlayer,\n" +" mas o suporte a braile é muito melhor e inclui suporte aos padrões de " +"código braile Nemeth, UEB Technical, CMU (espanhol/português),\n" +" e vietnamita. Existem traduções para chinês (tradicional), indonésio, " +"espanhol e vietnamita\n" +" e outras traduções estão em andamento." diff --git a/addon/locale/pt_PT/LC_MESSAGES/nvda.po b/addon/locale/pt_PT/LC_MESSAGES/nvda.po index 4e4df2d5..c77457ae 100644 --- a/addon/locale/pt_PT/LC_MESSAGES/nvda.po +++ b/addon/locale/pt_PT/LC_MESSAGES/nvda.po @@ -8,244 +8,324 @@ msgstr "" "Project-Id-Version: MathCAT 0.3.3\n" "Report-Msgid-Bugs-To: nvda-translations@groups.io\n" "POT-Creation-Date: 2023-08-14 03:25+0000\n" -"PO-Revision-Date: 2023-08-15 10:27+0100\n" -"Last-Translator: \n" +"PO-Revision-Date: 2024-07-04 12:13+0100\n" +"Last-Translator: Ângelo Abrantes \n" "Language-Team: \n" "Language: pt_PT\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 3.3.2\n" +"X-Generator: Poedit 3.4.4\n" +#. Translators: this message directs users to look in the log file msgid "Error in starting navigation of math: see NVDA error log for details" msgstr "" "Erro ao iniciar a navegação de matemática: ver registo de erros NVDA para " "mais detalhes" +#. Translators: this message directs users to look in the log file msgid "Error in brailling math: see NVDA error log for details" msgstr "" "Erro de braile na matemática: ver registo de erros do NVDA para mais detalhes" +#. Translators: this message directs users to look in the log file msgid "Error in navigating math: see NVDA error log for details" msgstr "" "Erro na navegação matemática: ver registo de erros do NVDA para mais detalhes" -#. For translators: Message to be announced during Keyboard Help +#. Translators: Message to be announced during Keyboard Help msgid "Copy navigation focus to clipboard" msgstr "Copiar o foco de navegação para a área de transferência" -#. For translators: Name of the section in "Input gestures" dialog. +#. Translators: Name of the section in "Input gestures" dialog. msgid "Clipboard" msgstr "Área de transferência" -msgid "copy" -msgstr "copiar" +#. Translators: copy to clipboard +msgid "copy as " +msgstr "copiar como" +#. Translators: this message directs users to look in the log file msgid "unable to copy math: see NVDA error log for details" -msgstr "" -"Erro de braile na matemática: ver registo de erros do NVDA para mais detalhes" +msgstr "unable to copy math: ver registo de erros NVDA para mais detalhes" +#. Translators: this message directs users to look in the log file msgid "MathCAT initialization failed: see NVDA error log for details" msgstr "" "Falha na inicialização do MathCAT: ver registo de erros do NVDA para mais " "detalhes" +#. Translators: this message directs users to look in the log file msgid "Illegal MathML found: see NVDA error log for details" msgstr "MathML ilegal encontrado: ver registo de erros NVDA para mais detalhes" +#. Translators: this message directs users to look in the log file msgid "Error in speaking math: see NVDA error log for details" msgstr "" "Erro ao falar matemática: ver registo de erros do NVDA para mais detalhes" +#. Translators: menu item -- use the language of the voice chosen in the NVDA speech settings dialog +#. "Auto" == "Automatic" -- other items in menu are "English (en)", etc., so this matches that style +msgid "Use Voice's Language (Auto)" +msgstr "Utilizar o idioma da voz (automático)" + +#. Translators: this is a test string that is spoken. Only translate "the square root of x squared plus y squared" msgid "" "the square root of x squared plus y squared" msgstr "" "a raiz quadrada de x ao quadrado mais y ao quadrado" -#, fuzzy, python-brace-format +#. Translators: this is a test string that is spoken. Only translate "the fraction with numerator" +#. and other parts NOT inside '<.../>', +#, python-brace-format msgid "" "the fraction with numerator x to the n -th power plus 1 and " -"denominator x to the n -th power " -" minus 1 end fraction " -"" +"time='{pause_factor_300}ms'/> x to the n -th power plus 1 and " +"denominator x to the " +"n -" +"thpower minus 1 " +"end fraction " msgstr "" "a fração com numerador x elevado à x elevado à enésima potência mais 1 e " -"denominador x elevado à enésima potência " -" menos 1 fim fração " - +"time='{pause_factor_128}ms'/> mais 1 e denominador " +" x " +"elevado à enésima potência menos 1 end fraction " + +#. Translators: title for MathCAT preferences dialog msgid "MathCAT Preferences" msgstr "Configurações do MathCAT" +#. Translators: A heading that labels three navigation pane tab names in the MathCAT dialog msgid "Categories:" msgstr "Categorias:" +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" msgid "Speech" msgstr "Discurso" +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" msgid "Navigation" msgstr "Navegação" +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" msgid "Braille" msgstr "Braille" +#. Translators: this is the text label for whom to target the speech for (options are below) msgid "Generate speech for:" msgstr "Gerar discurso para:" +#. Translators: these are the categories of impairments that MathCAT supports +#. Translators: Learning disabilities includes dyslexia and ADHD msgid "Learning disabilities" msgstr "Dificuldades de aprendizagem" +#. Translators: target people who are blind msgid "Blindness" msgstr "Cegueira" +#. Translators: target people who have low vision msgid "Low vision" msgstr "Baixa visão" +#. Translators: label for pull down allowing users to choose the speech language for math msgid "Language:" msgstr "Idioma:" -msgid "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -msgstr "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +#. Translators: label for pull down to specify what character to use in numbers as the decimal separator +msgid "Decimal separator for numbers:" +msgstr "Separador decimal para números:" + +#. Translators: options for decimal separator -- "Auto" = automatically pick the choice based on the language +msgid "Auto" +msgstr "Auto" +#. Translators: options for decimal separator -- "Custom" = user sets it +#. Currently there is no UI for how it is done yet, but eventually there will be a dialog that pops up to set it +msgid "Custom" +msgstr "Personalizado" + +#. Translators: label for pull down allowing users to choose the "style" (version, rules) of speech for math msgid "Speech style:" msgstr "Estilo de discurso:" -msgid "xxxxxxxxxxxxxxxx" -msgstr "xxxxxxxxxxxxxxxxxxxx" - -msgid "Speech amount:" -msgstr "Quantidade de discurso:" +#. Translators: label for pull down to specify how verbose/terse the speech should be +msgid "Speech verbosity:" +msgstr "Verbosidade do discurso:" +#. Translators: options for speech verbosity -- "terse" = use less words +#. Translators: options for navigation verbosity -- "terse" = use less words msgid "Terse" msgstr "Seco" +#. Translators: options for speech verbosity -- "medium" = try to be nether too terse nor too verbose words +#. Translators: options for navigation verbosity -- "medium" = try to be nether too terse nor too verbose words msgid "Medium" msgstr "Média" +#. Translators: options for speech verbosity -- "verbose" = use more words +#. Translators: options for navigation verbosity -- "verbose" = use more words msgid "Verbose" msgstr "Verbosidade" +#. Translators: label for slider that specifies a percentage of the normal speech rate that should be used for math msgid "Relative speech rate:" msgstr "Velocidade relativa do discurso:" +#. Translators: label for slider that specifies relative factor to increase or decrease pauses in the math speech msgid "Pause factor:" msgstr "Fator de pausa:" +#. Translators: label for check box controling a beep sound msgid "Make a sound when starting/ending math speech" msgstr "Fazer um som ao iniciar/terminar o discurso matemático" +#. Translators: label for pull down to specify a subject area (Geometry, Calculus, ...) msgid "Subject area to be used when it cannot be determined automatically:" msgstr "" "Área temática a utilizar quando não puder ser determinada automaticamente:" +#. Translators: a generic (non-specific) math subject area msgid "General" msgstr "Geral" +#. Translators: label for pull down to specify how verbose/terse the speech should be msgid "Speech for chemical formulas:" msgstr "Discurso para fórmulas químicas:" +#. Translators: values for chemistry options with example speech in parenthesis msgid "Spell it out (H 2 O)" msgstr "Soletra (H 2 O)" +#. Translators: values for chemistry options with example speech in parenthesis (never interpret as chemistry) msgid "Off (H sub 2 O)" msgstr "Desligado (H sub 2 O)" -msgid "a page" -msgstr "uma página" - +#. Translators: label for pull down to specify one of three modes use to navigate math expressions msgid "Navigation mode to use when beginning to navigate an equation:" msgstr "Modo de navegação a utilizar quando se começa a navegar numa equação:" +#. Translators: names of different modes of navigation. "Enhanced" mode understands math structure msgid "Enhanced" msgstr "Melhorado" +#. Translators: "Simple" walks by character expect for things like fractions, roots, and scripts msgid "Simple" msgstr "Simples" +#. Translators: "Character" moves around by character, automatically moving into fractions, etc msgid "Character" msgstr "Carater" +#. Translators: label for checkbox that controls whether any changes to the navigation mode should be preserved msgid "Reset navigation mode on entry to an expression" msgstr "Repor o modo de navegação à entrada de uma expressão" +#. Translators: label for pull down to specify whether the expression is spoken or described (an overview) msgid "Navigation speech to use when beginning to navigate an equation:" msgstr "" "Discurso de navegação a utilizar quando se começa a navegar numa equação:" +#. Translators: "Speak" the expression after moving to it msgid "Speak" msgstr "Falar" +#. Translators: "Describe" the expression after moving to it ("overview is a synonym") msgid "Describe/overview" msgstr "Descrição/visão geral" +#. Translators: label for checkbox that controls whether any changes to the speak vs overview reading should be ignored msgid "Reset navigation speech on entry to an expression" msgstr "Repor o discurso de navegação à entrada de uma expressão" +#. Translators: label for checkbox that controls whether arrow keys move out of fractions, etc., +#. or whether you have to manually back out of the fraction, etc. msgid "Automatic zoom out of 2D notations" msgstr "Redução automática do zoom das notações 2D" +#. Translators: label for pull down to specify whether you want a terse or verbose reading of navigation commands msgid "Speech amount for navigation:" msgstr "Quantidade de voz para navegação:" -msgid "Braille math code for refreshable displays:" -msgstr "Código matemático Braille para ecrãs actualizáveis:" +#. Translators: label for pull down to specify how math will be copied to the clipboard +msgid "Copy math as:" +msgstr "Copiar matemática como:" -msgid "Nemeth" -msgstr "Nemeth" +#. Translators: options for Copy expression to clipboard as -- "MathML" +msgid "MathML" +msgstr "MathML" -msgid "UEB" -msgstr "UEB" +#. Translators: options for Copy expression to clipboard as -- "LaTeX" +msgid "LaTeX" +msgstr "LaTeX" -msgid "Vietnam" -msgstr "Vietname" +#. Translators: options for Copy expression to clipboard as -- "ASCIIMath" +msgid "ASCIIMath" +msgstr "ASCIIMath" +#. Translators: label for pull down to specify which braille code to use +msgid "Braille math code for refreshable displays:" +msgstr "Código matemático Braille para ecrãs actualizáveis:" + +#. Translators: label for pull down to specify how braille dots should be modified when navigating/selecting subexprs msgid "Highlight with dots 7 && 8 the current nav node:" msgstr "Realce com os pontos 7 && 8 o nó de navegação atual:" +#. Translators: options for using dots 7 and 8: +#. Translators: "off" -- don't highlight msgid "Off" msgstr "Desligado" +#. Translators: "First character" -- only the first character of the current navigation node uses dots 7 & 8 msgid "First character" msgstr "Primeiro carácter" +#. Translators: "Endpoints" -- only the first and last character of the current navigation node uses dots 7 & 8 msgid "Endpoints" msgstr "Pontos finais" +#. Translators: "All" -- all the characters for the current navigation node use dots 7 & 8 msgid "All" msgstr "Todos" +#. Translators: dialog "ok" button msgid "OK" msgstr "OK" +#. Translators: dialog "cancel" button msgid "Cancel" msgstr "Cancelar" +#. Translators: dialog "apply" button msgid "Apply" msgstr "Aplicar" +#. Translators: button to reset all the preferences to their default values msgid "Reset to defaults" msgstr "Restaurar para valores por padrão" +#. Translators: button to bring up a help page msgid "Help" msgstr "Ajuda" +#. Translators: this show up in the NVDA preferences dialog. It opens the MathCAT preferences dialog msgid "&MathCAT Settings..." msgstr "Configurações do &MathCAT..." @@ -255,19 +335,17 @@ msgstr "Configurações do &MathCAT..." msgid "MathCAT: speech and braille from MathML" msgstr "MathCAT: fala e braille a partir de MathML" -#. Add-on description #. Translators: Long description to be shown for this add-on on add-on information from add-ons manager -#, fuzzy msgid "" "MathCAT is a replacement for MathPlayer which has been discontinued.\n" -"\t\tIt provides speech and braille support, and also supports MathPlayer's " -"three modes of navigation.\n" -"\t\tThe speech quality is not quite as good as MathPlayer's speech yet,\n" -"\t\tbut the braille support is much better and includes both Nemeth and UEB " -"Technical. It also includes support for CMU (Spanish/Portuguese) and " -"Vietnamese braille standards.\n" -"\t\tTranslations to Indonesian, Spanish, and Vietnamese exist and other " -"translations are in progress." +" It provides speech and braille support, and also supports " +"MathPlayer's three modes of navigation.\n" +" The speech quality is not quite as good as MathPlayer's speech yet,\n" +" but the braille support is much better and includes support for " +"Nemeth, UEB Technical, CMU (Spanish/Portuguese),\n" +" and Vietnamese braille code standards. Translations to Chinese " +"(Traditional), Indonesian, Spanish, and Vietnamese exist\n" +" and other translations are in progress." msgstr "" "O MathCAT é um substituto do MathPlayer que foi descontinuado.\n" "\t\tFornece suporte de voz e braille, e também suporta os três modos de " @@ -277,3 +355,24 @@ msgstr "" "Technical.\n" "\t\tExistem traduções para indonésio, espanhol e vietnamita e outras " "traduções estão em curso." + +#~ msgid "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +#~ msgstr "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + +#~ msgid "xxxxxxxxxxxxxxxx" +#~ msgstr "xxxxxxxxxxxxxxxxxxxx" + +#~ msgid "Speech amount:" +#~ msgstr "Quantidade de discurso:" + +#~ msgid "a page" +#~ msgstr "uma página" + +#~ msgid "Nemeth" +#~ msgstr "Nemeth" + +#~ msgid "UEB" +#~ msgstr "UEB" + +#~ msgid "Vietnam" +#~ msgstr "Vietname" diff --git a/addon/locale/ru/LC_MESSAGES/nvda.po b/addon/locale/ru/LC_MESSAGES/nvda.po new file mode 100644 index 00000000..dae34db5 --- /dev/null +++ b/addon/locale/ru/LC_MESSAGES/nvda.po @@ -0,0 +1,374 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the MathCAT package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: MathCAT 0.6.6\n" +"Report-Msgid-Bugs-To: nvda-translations@groups.io\n" +"POT-Creation-Date: 2024-12-19 20:17+0700\n" +"PO-Revision-Date: 2024-12-30 02:34+0500\n" +"Last-Translator: Ruslan Kolodyazhni \n" +"Language-Team: Translators \n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.5\n" +"X-Poedit-Basepath: ../../../..\n" +"X-Poedit-SearchPath-0: addon/globalPlugins/MathCAT\n" +"X-Poedit-SearchPath-1: buildVars.py\n" +"X-Poedit-SearchPath-2: Example\n" + +#. Translators: this message directs users to look in the log file +msgid "Error in starting navigation of math: see NVDA error log for details" +msgstr "" +"Ошибка при запуске навигации по математическому выражению: подробности " +"смотрите в журнале ошибок NVDA" + +#. Translators: this message directs users to look in the log file +msgid "Error in brailling math: see NVDA error log for details" +msgstr "" +"Ошибка отображения математического выражения по брайлю: подробности " +"смотрите в журнале ошибок NVDA" + +#. Translators: this message directs users to look in the log file +msgid "Error in navigating math: see NVDA error log for details" +msgstr "" +"Ошибка навигации по математическому выражению: подробности смотрите в " +"журнале ошибок NVDA" + +#. Translators: Message to be announced during Keyboard Help +msgid "Copy navigation focus to clipboard" +msgstr "Скопировать элемент в фокусе в буфер обмена" + +#. Translators: Name of the section in "Input gestures" dialog. +msgid "Clipboard" +msgstr "Буфер обмена" + +#. Translators: copy to clipboard +msgid "copy as " +msgstr "копировать как " + +#. Translators: this message directs users to look in the log file +msgid "unable to copy math: see NVDA error log for details" +msgstr "" +"не удалось скопировать математическое выражение: подробности смотрите в " +"журнале ошибок NVDA" + +#. Translators: this message directs users to look in the log file +msgid "MathCAT initialization failed: see NVDA error log for details" +msgstr "" +"Ошибка инициализации MathCat: подробности смотрите в журнале ошибок NVDA" + +#. Translators: this message directs users to look in the log file +msgid "Illegal MathML found: see NVDA error log for details" +msgstr "" +"Обнаружен недопустимый элемент MathML: подробности смотрите в журнале ошибок " +"NVDA" + +#. Translators: this message directs users to look in the log file +msgid "Error in speaking math: see NVDA error log for details" +msgstr "" +"Ошибка произнесения математического выражения: подробности смотрите в " +"журнале ошибок NVDA" + +#. Translators: menu item -- use the language of the voice chosen in the NVDA speech settings dialog +#. "Auto" == "Automatic" -- other items in menu are "English (en)", etc., so this matches that style +msgid "Use Voice's Language (Auto)" +msgstr "Использовать язык голоса (автоматически)" + +#. Translators: this is a test string that is spoken. Only translate "the square root of x squared plus y squared" +msgid "" +"the square root of x squared plus y squared" +msgstr "" +"квадратный корень из x в квадрате плюс y в квадрате" + +#. Translators: this is a test string that is spoken. Only translate "the fraction with numerator" +#. and other parts NOT inside '<.../>', +#, python-brace-format +msgid "" +"the fraction with numerator x to the n -th power plus 1 and " +"denominator x to the " +"n -" +"thpower minus 1 " +"end fraction " +msgstr "" +"дробь с числителем x to the " +"n -" +"th в степени " +" плюс 1 " +" и знаменателем x to the n -thв степени " +" минус 1 конец дроби " + +#. Translators: title for MathCAT preferences dialog +msgid "MathCAT Preferences" +msgstr "Настройки MathCat" + +#. Translators: A heading that labels three navigation pane tab names in the MathCAT dialog +msgid "Categories:" +msgstr "Категории:" + +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" +msgid "Speech" +msgstr "Речь" + +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" +msgid "Navigation" +msgstr "Навигация" + +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" +msgid "Braille" +msgstr "Брайль" + +#. Translators: this is the text label for whom to target the speech for (options are below) +msgid "Generate speech for:" +msgstr "" +"Выберете тип речевых сообщений, соответствующий ограничениям здоровья " +"пользователя:" + +#. Translators: these are the categories of impairments that MathCAT supports +#. Translators: Learning disabilities includes dyslexia and ADHD +msgid "Learning disabilities" +msgstr "Нарушения обучаемости" + +#. Translators: target people who are blind +msgid "Blindness" +msgstr "Слепота" + +#. Translators: target people who have low vision +msgid "Low vision" +msgstr "Остаточное зрение" + +#. Translators: label for pull down allowing users to choose the speech language for math +msgid "Language:" +msgstr "Язык:" + +#. Translators: label for pull down to specify what character to use in numbers as the decimal separator +msgid "Decimal separator for numbers:" +msgstr "Десятичный разделитель чисел:" + +#. Translators: options for decimal separator -- "Auto" = automatically pick the choice based on the language +msgid "Auto" +msgstr "Автоматически" + +#. Translators: options for decimal separator -- "Custom" = user sets it +#. Currently there is no UI for how it is done yet, but eventually there will be a dialog that pops up to set it +msgid "Custom" +msgstr "Пользовательский" + +#. Translators: label for pull down allowing users to choose the "style" (version, rules) of speech for math +msgid "Speech style:" +msgstr "Стиль речи:" + +#. Translators: label for pull down to specify how verbose/terse the speech should be +msgid "Speech verbosity:" +msgstr "Объём речи:" + +#. Translators: options for speech verbosity -- "terse" = use less words +#. Translators: options for navigation verbosity -- "terse" = use less words +msgid "Terse" +msgstr "Краткий" + +#. Translators: options for speech verbosity -- "medium" = try to be nether too terse nor too verbose words +#. Translators: options for navigation verbosity -- "medium" = try to be nether too terse nor too verbose words +msgid "Medium" +msgstr "Средний" + +#. Translators: options for speech verbosity -- "verbose" = use more words +#. Translators: options for navigation verbosity -- "verbose" = use more words +msgid "Verbose" +msgstr "Подробный" + +#. Translators: label for slider that specifies a percentage of the normal speech rate that should be used for math +msgid "Relative speech rate:" +msgstr "Относительная скорость речи:" + +#. Translators: label for slider that specifies relative factor to increase or decrease pauses in the math speech +msgid "Pause factor:" +msgstr "Коэффициент паузы:" + +#. Translators: label for check box controling a beep sound +msgid "Make a sound when starting/ending math speech" +msgstr "" +"Воспроизводить звук при начале/завершении проговаривания математического " +"выражения" + +#. Translators: label for pull down to specify a subject area (Geometry, Calculus, ...) +msgid "Subject area to be used when it cannot be determined automatically:" +msgstr "" +"Предметная область, используемая в тех случаях, когда она не может быть " +"определена автоматически:" + +#. Translators: a generic (non-specific) math subject area +msgid "General" +msgstr "Общее" + +#. Translators: label for pull down to specify how verbose/terse the speech should be +msgid "Speech for chemical formulas:" +msgstr "Проговаривание химических формул:" + +#. Translators: values for chemistry options with example speech in parenthesis +msgid "Spell it out (H 2 O)" +msgstr "Произносить по буквам (H 2 O)" + +#. Translators: values for chemistry options with example speech in parenthesis (never interpret as chemistry) +msgid "Off (H sub 2 O)" +msgstr "Выключено (H sub 2 O)" + +#. Translators: label for pull down to specify one of three modes use to navigate math expressions +msgid "Navigation mode to use when beginning to navigate an equation:" +msgstr "Режим навигации, используемый при перемещении по выражению:" + +#. Translators: names of different modes of navigation. "Enhanced" mode understands math structure +msgid "Enhanced" +msgstr "Улучшенный" + +#. Translators: "Simple" walks by character expect for things like fractions, roots, and scripts +msgid "Simple" +msgstr "Простой" + +#. Translators: "Character" moves around by character, automatically moving into fractions, etc +msgid "Character" +msgstr "Символ" + +#. Translators: label for checkbox that controls whether any changes to the navigation mode should be preserved +msgid "Reset navigation mode on entry to an expression" +msgstr "Сбрасывать режим навигации при переходе к выражению" + +#. Translators: label for pull down to specify whether the expression is spoken or described (an overview) +msgid "Navigation speech to use when beginning to navigate an equation:" +msgstr "Режим речи, используемый при перемещении по выражению:" + +#. Translators: "Speak" the expression after moving to it +msgid "Speak" +msgstr "Проговаривание" + +#. Translators: "Describe" the expression after moving to it ("overview is a synonym") +msgid "Describe/overview" +msgstr "Описание/обзор" + +#. Translators: label for checkbox that controls whether any changes to the speak vs overview reading should be ignored +msgid "Reset navigation speech on entry to an expression" +msgstr "Сбрасывать настройки режима речи при переходе к выражению" + +#. Translators: label for checkbox that controls whether arrow keys move out of fractions, etc., +#. or whether you have to manually back out of the fraction, etc. +msgid "Automatic zoom out of 2D notations" +msgstr "Автоматическое уменьшение масштаба Двумерных обозначений" + +#. Translators: label for pull down to specify whether you want a terse or verbose reading of navigation commands +msgid "Speech amount for navigation:" +msgstr "Объём речи при навигации:" + +#. Translators: label for pull down to specify how math will be copied to the clipboard +msgid "Copy math as:" +msgstr "Копировать математику как:" + +#. Translators: options for Copy expression to clipboard as -- "MathML" +msgid "MathML" +msgstr "MathML" + +#. Translators: options for Copy expression to clipboard as -- "LaTeX" +msgid "LaTeX" +msgstr "LaTeX" + +#. Translators: options for Copy expression to clipboard as -- "ASCIIMath" +msgid "ASCIIMath" +msgstr "ASCIIMath" + +#. Translators: label for pull down to specify which braille code to use +msgid "Braille math code for refreshable displays:" +msgstr "Математический код Брайля для обновляемых дисплеев:" + +#. Translators: label for pull down to specify how braille dots should be modified when navigating/selecting subexprs +msgid "Highlight with dots 7 && 8 the current nav node:" +msgstr "Выделять текущий навигационный узел точками 7 && 8:" + +#. Translators: options for using dots 7 and 8: +#. Translators: "off" -- don't highlight +msgid "Off" +msgstr "Выключено" + +#. Translators: "First character" -- only the first character of the current navigation node uses dots 7 & 8 +msgid "First character" +msgstr "Первый символ" + +#. Translators: "Endpoints" -- only the first and last character of the current navigation node uses dots 7 & 8 +msgid "Endpoints" +msgstr "Конечные точки" + +#. Translators: "All" -- all the characters for the current navigation node use dots 7 & 8 +msgid "All" +msgstr "Всё" + +#. Translators: dialog "ok" button +msgid "OK" +msgstr "ОК" + +#. Translators: dialog "cancel" button +msgid "Cancel" +msgstr "Отменить" + +#. Translators: dialog "apply" button +msgid "Apply" +msgstr "Применить" + +#. Translators: button to reset all the preferences to their default values +msgid "Reset to defaults" +msgstr "Сбросить к настройкам по умолчанию" + +#. Translators: button to bring up a help page +msgid "Help" +msgstr "Справка" + +#. Translators: this show up in the NVDA preferences dialog. It opens the MathCAT preferences dialog +msgid "&MathCAT Settings..." +msgstr "Настройки &MathCat..." + +#. Add-on summary, usually the user visible name of the addon. +#. Translators: Summary for this add-on +#. to be shown on installation and add-on information found in Add-ons Manager. +msgid "MathCAT: speech and braille from MathML" +msgstr "MathCat: речь и Брайль для MathML" + +#. Translators: Long description to be shown for this add-on on add-on information from add-ons manager +msgid "" +"MathCAT is a replacement for MathPlayer which has been discontinued.\n" +" It provides speech and braille support, and also supports " +"MathPlayer's three modes of navigation.\n" +" The speech quality is not quite as good as MathPlayer's speech yet,\n" +" but the braille support is much better and includes support for " +"Nemeth, UEB Technical, CMU (Spanish/Portuguese),\n" +" and Vietnamese braille code standards. Translations to Chinese " +"(Traditional), Indonesian, Spanish, and Vietnamese exist\n" +" and other translations are in progress." +msgstr "" +"MathCAT - это замена MathPlayer, поддержка которого была прекращена.\n" +"\t\tЭто дополнение обеспечивает поддержку речи и шрифта Брайля, а также " +"поддерживает три режима навигации MathPlayer.\n" +"\t\tКачество речи пока не такое хорошее, как у MathPlayer,\n" +"\t\tно поддержка шрифта Брайля намного лучше и включает в себя Nemeth и UEB " +"Technical. Также поддерживаются стандарты Брайля CMU (испанский/" +"португальский) и вьетнамский.\n" +"\t\tСуществуют переводы на индонезийский, испанский и вьетнамский языки, " +"другие переводы находятся в процессе разработки." diff --git a/addon/locale/tr/LC_MESSAGES/nvda.po b/addon/locale/tr/LC_MESSAGES/nvda.po index b0a81c8a..ee00a14f 100644 --- a/addon/locale/tr/LC_MESSAGES/nvda.po +++ b/addon/locale/tr/LC_MESSAGES/nvda.po @@ -5,239 +5,321 @@ # msgid "" msgstr "" -"Project-Id-Version: MathCAT 0.3.3\n" +"Project-Id-Version: MathCAT\n" "Report-Msgid-Bugs-To: nvda-translations@groups.io\n" "POT-Creation-Date: 2023-08-14 03:25+0000\n" -"PO-Revision-Date: 2023-08-14 21:37+0300\n" +"PO-Revision-Date: \n" "Last-Translator: Umut KORKMAZ \n" -"Language-Team: \n" -"Language: tr\n" +"Language-Team: Umut KORKMAZ \n" +"Language: tr_TR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 3.3.2\n" +"X-Generator: Poedit 3.4.4\n" +#. Translators: this message directs users to look in the log file msgid "Error in starting navigation of math: see NVDA error log for details" msgstr "" "Matematik gezintisi başlatılırken hata oluştu: ayrıntılar için NVDA hata " "günlüğüne bakın" +#. Translators: this message directs users to look in the log file msgid "Error in brailling math: see NVDA error log for details" msgstr "Braille matematikte hata: ayrıntılar için NVDA hata günlüğüne bakın" +#. Translators: this message directs users to look in the log file msgid "Error in navigating math: see NVDA error log for details" msgstr "Matematikte gezinme hatası: ayrıntılar için NVDA hata günlüğüne bakın" -#. For translators: Message to be announced during Keyboard Help +#. Translators: Message to be announced during Keyboard Help msgid "Copy navigation focus to clipboard" msgstr "Gezinme odağını panoya kopyala" -#. For translators: Name of the section in "Input gestures" dialog. +#. Translators: Name of the section in "Input gestures" dialog. msgid "Clipboard" msgstr "Pano" -msgid "copy" -msgstr "kopyala" +#. Translators: copy to clipboard +msgid "copy as " +msgstr "şu şekilde kopyala " +#. Translators: this message directs users to look in the log file msgid "unable to copy math: see NVDA error log for details" msgstr "matematik kopyalanamıyor: ayrıntılar için NVDA hata günlüğüne bakın" +#. Translators: this message directs users to look in the log file msgid "MathCAT initialization failed: see NVDA error log for details" msgstr "" "MathCAT başlatma başarısız oldu: ayrıntılar için NVDA hata günlüğüne bakın" +#. Translators: this message directs users to look in the log file msgid "Illegal MathML found: see NVDA error log for details" msgstr "Geçersiz MathML bulundu: ayrıntılar için NVDA hata günlüğüne bakın" +#. Translators: this message directs users to look in the log file msgid "Error in speaking math: see NVDA error log for details" msgstr "Matematik konuşma hatası: ayrıntılar için NVDA hata günlüğüne bakın" +#. Translators: menu item -- use the language of the voice chosen in the NVDA speech settings dialog +#. "Auto" == "Automatic" -- other items in menu are "English (en)", etc., so this matches that style +msgid "Use Voice's Language (Auto)" +msgstr "Sesin Dilini Kullan (Otomatik)" + +#. Translators: this is a test string that is spoken. Only translate "the square root of x squared plus y squared" msgid "" "the square root of x squared plus y squared" msgstr "x kare artı y karenin karekökü" -#, fuzzy, python-brace-format +#. Translators: this is a test string that is spoken. Only translate "the fraction with numerator" +#. and other parts NOT inside '<.../>', +#, python-brace-format msgid "" "the fraction with numerator x to the n -th power plus 1 and " -"denominator x to the n -th power " -" minus 1 end fraction " -"" +"time='{pause_factor_300}ms'/> x to the n -th power plus 1 and " +"denominator x to the " +"n -" +"thpower minus 1 " +"end fraction " msgstr "" "the fraction with numerator x to the n -th power plus 1 and denominator x to the n -th power minus 1 end fraction " - +"time='{pause_factor_300}ms'/> x to the n -th power plus 1 and " +"denominator x to the " +"n -" +"thpower minus 1 " +"end fraction " + +#. Translators: title for MathCAT preferences dialog msgid "MathCAT Preferences" msgstr "MathCAT Tercihleri" +#. Translators: A heading that labels three navigation pane tab names in the MathCAT dialog msgid "Categories:" msgstr "Kategoriler:" +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" msgid "Speech" msgstr "Konuşma" +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" msgid "Navigation" msgstr "Gezinme" +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" msgid "Braille" msgstr "Braille" +#. Translators: this is the text label for whom to target the speech for (options are below) msgid "Generate speech for:" msgstr "Aşağıdakiler için konuş:" +#. Translators: these are the categories of impairments that MathCAT supports +#. Translators: Learning disabilities includes dyslexia and ADHD msgid "Learning disabilities" msgstr "Öğrenme güçlüğü çekenler" +#. Translators: target people who are blind msgid "Blindness" msgstr "Körler" +#. Translators: target people who have low vision msgid "Low vision" msgstr "Az görenler" +#. Translators: label for pull down allowing users to choose the speech language for math msgid "Language:" msgstr "Dil:" -msgid "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -msgstr "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +#. Translators: label for pull down to specify what character to use in numbers as the decimal separator +msgid "Decimal separator for numbers:" +msgstr "Sayılar için ondalık ayırıcı:" + +#. Translators: options for decimal separator -- "Auto" = automatically pick the choice based on the language +msgid "Auto" +msgstr "Oto" +#. Translators: options for decimal separator -- "Custom" = user sets it +#. Currently there is no UI for how it is done yet, but eventually there will be a dialog that pops up to set it +msgid "Custom" +msgstr "Özelleştir" + +#. Translators: label for pull down allowing users to choose the "style" (version, rules) of speech for math msgid "Speech style:" msgstr "Konuşma stili:" -msgid "xxxxxxxxxxxxxxxx" -msgstr "xxxxxxxxxxxxxxxx" - -msgid "Speech amount:" -msgstr "Konuşma Düzeği:" +#. Translators: label for pull down to specify how verbose/terse the speech should be +msgid "Speech verbosity:" +msgstr "Konuşma ayrıntısı:" +#. Translators: options for speech verbosity -- "terse" = use less words +#. Translators: options for navigation verbosity -- "terse" = use less words msgid "Terse" msgstr "Kısa" +#. Translators: options for speech verbosity -- "medium" = try to be nether too terse nor too verbose words +#. Translators: options for navigation verbosity -- "medium" = try to be nether too terse nor too verbose words msgid "Medium" msgstr "Orta" +#. Translators: options for speech verbosity -- "verbose" = use more words +#. Translators: options for navigation verbosity -- "verbose" = use more words msgid "Verbose" msgstr "Ayrıntılı" +#. Translators: label for slider that specifies a percentage of the normal speech rate that should be used for math msgid "Relative speech rate:" msgstr "Oransal konuşma hızı:" +#. Translators: label for slider that specifies relative factor to increase or decrease pauses in the math speech msgid "Pause factor:" msgstr "Duraklatma faktörü:" +#. Translators: label for check box controling a beep sound msgid "Make a sound when starting/ending math speech" msgstr "Matematik konuşmasını başlatırken/bitirirken ses çal" +#. Translators: label for pull down to specify a subject area (Geometry, Calculus, ...) msgid "Subject area to be used when it cannot be determined automatically:" msgstr "Otomatik olarak belirlenemediği durumlarda kullanılacak konu alanı:" +#. Translators: a generic (non-specific) math subject area msgid "General" msgstr "Genel" +#. Translators: label for pull down to specify how verbose/terse the speech should be msgid "Speech for chemical formulas:" msgstr "Kimyasal formüller için konuşma:" +#. Translators: values for chemistry options with example speech in parenthesis msgid "Spell it out (H 2 O)" msgstr "Hecele (H 2 O)" +#. Translators: values for chemistry options with example speech in parenthesis (never interpret as chemistry) msgid "Off (H sub 2 O)" msgstr "Kapalı (H sub 2 O)" -msgid "a page" -msgstr "bir sayfa" - +#. Translators: label for pull down to specify one of three modes use to navigate math expressions msgid "Navigation mode to use when beginning to navigate an equation:" msgstr "Bir denklemde dolaşmaya başlarken kullanılacak gezinme modu:" +#. Translators: names of different modes of navigation. "Enhanced" mode understands math structure msgid "Enhanced" msgstr "Gelişmiş" +#. Translators: "Simple" walks by character expect for things like fractions, roots, and scripts msgid "Simple" msgstr "Basit" +#. Translators: "Character" moves around by character, automatically moving into fractions, etc msgid "Character" msgstr "Karakter" +#. Translators: label for checkbox that controls whether any changes to the navigation mode should be preserved msgid "Reset navigation mode on entry to an expression" msgstr "Bir ifadeye girişte gezinme modunu sıfırla" +#. Translators: label for pull down to specify whether the expression is spoken or described (an overview) msgid "Navigation speech to use when beginning to navigate an equation:" msgstr "Bir denklemde gezinmeye başlarken kullanılacak gezinme konuşması:" +#. Translators: "Speak" the expression after moving to it msgid "Speak" msgstr "Konuşma" +#. Translators: "Describe" the expression after moving to it ("overview is a synonym") msgid "Describe/overview" msgstr "Açıklama/genel bakış" +#. Translators: label for checkbox that controls whether any changes to the speak vs overview reading should be ignored msgid "Reset navigation speech on entry to an expression" msgstr "Bir ifadeye girişte gezinme konuşmasını sıfırla" +#. Translators: label for checkbox that controls whether arrow keys move out of fractions, etc., +#. or whether you have to manually back out of the fraction, etc. msgid "Automatic zoom out of 2D notations" msgstr "2B gösterimlerde otomatik uzaklaştırma" +#. Translators: label for pull down to specify whether you want a terse or verbose reading of navigation commands msgid "Speech amount for navigation:" msgstr "Gezinme için konuşma düzeyi:" -msgid "Braille math code for refreshable displays:" -msgstr "Yenilenebilir ekranlar için Braille matematik kodu:" +#. Translators: label for pull down to specify how math will be copied to the clipboard +msgid "Copy math as:" +msgstr "Matematiği şu şekilde kopyala:" + +#. Translators: options for Copy expression to clipboard as -- "MathML" +msgid "MathML" +msgstr "MathML" -msgid "Nemeth" -msgstr "Nemet" +#. Translators: options for Copy expression to clipboard as -- "LaTeX" +msgid "LaTeX" +msgstr "LaTeX" -msgid "UEB" -msgstr "UEB" +#. Translators: options for Copy expression to clipboard as -- "ASCIIMath" +msgid "ASCIIMath" +msgstr "ASCIIMath" -msgid "Vietnam" -msgstr "Vietnam" +#. Translators: label for pull down to specify which braille code to use +msgid "Braille math code for refreshable displays:" +msgstr "Yenilenebilir ekranlar için Braille matematik kodu:" +#. Translators: label for pull down to specify how braille dots should be modified when navigating/selecting subexprs msgid "Highlight with dots 7 && 8 the current nav node:" msgstr "Geçerli gezinme noktasını 7 ve 8 noktalarıyla &vurgulayın:" +#. Translators: options for using dots 7 and 8: +#. Translators: "off" -- don't highlight msgid "Off" msgstr "Kapalı" +#. Translators: "First character" -- only the first character of the current navigation node uses dots 7 & 8 msgid "First character" msgstr "İlk karakter" +#. Translators: "Endpoints" -- only the first and last character of the current navigation node uses dots 7 & 8 msgid "Endpoints" msgstr "Uç noktalar" +#. Translators: "All" -- all the characters for the current navigation node use dots 7 & 8 msgid "All" msgstr "Tümü" +#. Translators: dialog "ok" button msgid "OK" msgstr "Tamam" +#. Translators: dialog "cancel" button msgid "Cancel" msgstr "İptal" +#. Translators: dialog "apply" button msgid "Apply" msgstr "Uygula" +#. Translators: button to reset all the preferences to their default values msgid "Reset to defaults" msgstr "Varsayılanlara sıfırla" +#. Translators: button to bring up a help page msgid "Help" msgstr "Yardım" +#. Translators: this show up in the NVDA preferences dialog. It opens the MathCAT preferences dialog msgid "&MathCAT Settings..." msgstr "&MathCAT Ayarları..." @@ -247,25 +329,46 @@ msgstr "&MathCAT Ayarları..." msgid "MathCAT: speech and braille from MathML" msgstr "MathCAT: MathML'den konuşma ve braille" -#. Add-on description #. Translators: Long description to be shown for this add-on on add-on information from add-ons manager -#, fuzzy msgid "" "MathCAT is a replacement for MathPlayer which has been discontinued.\n" -"\t\tIt provides speech and braille support, and also supports MathPlayer's " -"three modes of navigation.\n" -"\t\tThe speech quality is not quite as good as MathPlayer's speech yet,\n" -"\t\tbut the braille support is much better and includes both Nemeth and UEB " -"Technical. It also includes support for CMU (Spanish/Portuguese) and " -"Vietnamese braille standards.\n" -"\t\tTranslations to Indonesian, Spanish, and Vietnamese exist and other " -"translations are in progress." +" It provides speech and braille support, and also supports " +"MathPlayer's three modes of navigation.\n" +" The speech quality is not quite as good as MathPlayer's speech yet,\n" +" but the braille support is much better and includes support for " +"Nemeth, UEB Technical, CMU (Spanish/Portuguese),\n" +" and Vietnamese braille code standards. Translations to Chinese " +"(Traditional), Indonesian, Spanish, and Vietnamese exist\n" +" and other translations are in progress." msgstr "" -"MathCAT, kullanımdan kaldırılan MathPlayer'ın yerini almıştır.\n" -"\t\tKonuşma ve braille desteği sağlar ve ayrıca MathPlayer'ın üç gezinme " -"modunu destekler.\n" -"\t\tKonuşma kalitesi henüz MathPlayer'ın konuşması kadar iyi değil,\n" -"\t\tancak braille desteği çok daha iyi ve hem Nemeth'i hem de UEB Teknik'i " -"içeriyor.\n" -"\t\tEndonezce, İspanyolca ve Vietnamca çeviriler mevcuttur ve diğer " -"çeviriler devam etmektedir." +"MathCAT, geliştirilmesi durdurulan MathPlayer'ın yerine geçen bir " +"yazılımdır.\n" +" Konuşma ve braille desteği sağlar ve ayrıca MathPlayer'ın üç gezinme " +"modunu da destekler.\n" +" Konuşma kalitesi henüz MathPlayer'ın konuşması kadar iyi değil,\n" +" ancak braille desteği çok daha iyidir ve Nemeth, UEB Teknik, CMU " +"(İspanyolca/Portekizce) desteğini içerir.\n" +" ve Vietnam braille kodu standartları. Çince (Geleneksel), Endonezce, " +"İspanyolca ve Vietnamcaya çeviriler mevcuttur\n" +" diğer çeviriler devam etmektedir." + +#~ msgid "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +#~ msgstr "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + +#~ msgid "xxxxxxxxxxxxxxxx" +#~ msgstr "xxxxxxxxxxxxxxxx" + +#~ msgid "Speech amount:" +#~ msgstr "Konuşma Düzeği:" + +#~ msgid "a page" +#~ msgstr "bir sayfa" + +#~ msgid "Nemeth" +#~ msgstr "Nemet" + +#~ msgid "UEB" +#~ msgstr "UEB" + +#~ msgid "Vietnam" +#~ msgstr "Vietnam" diff --git a/addon/locale/zh_CN/LC_MESSAGES/nvda.po b/addon/locale/zh_CN/LC_MESSAGES/nvda.po new file mode 100644 index 00000000..02da2c48 --- /dev/null +++ b/addon/locale/zh_CN/LC_MESSAGES/nvda.po @@ -0,0 +1,350 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the 'MathCAT' package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: 'MathCAT' '0.4.2'\n" +"Report-Msgid-Bugs-To: 'nvda-translations@groups.io'\n" +"POT-Creation-Date: 2024-03-23 08:29+0800\n" +"PO-Revision-Date: 2024-03-23 10:34+0800\n" +"Last-Translator: Zhang Yunxi \n" +"Language-Team: NVDA Simplified Chinese team\n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.4.2\n" + +#. Translators: this message directs users to look in the log file +msgid "Error in starting navigation of math: see NVDA error log for details" +msgstr "启动数学导航时出错: 有关详细信息,请查找 NVDA 错误日志" + +#. Translators: this message directs users to look in the log file +msgid "Error in brailling math: see NVDA error log for details" +msgstr "处理数学盲文时出错: 有关详细信息,请查看 NVDA 错误日志" + +#. Translators: this message directs users to look in the log file +msgid "Error in navigating math: see NVDA error log for details" +msgstr "在与数学导航时出错: 有关详细信息,请查看 NVDA 错误日志" + +#. Translators: Message to be announced during Keyboard Help +msgid "Copy navigation focus to clipboard" +msgstr "复制导航焦点到剪贴板" + +#. Translators: Name of the section in "Input gestures" dialog. +msgid "Clipboard" +msgstr "剪贴板" + +#. Translators: copy to clipboard +#, fuzzy +msgid "copy as " +msgstr "复制" + +#. Translators: this message directs users to look in the log file +msgid "unable to copy math: see NVDA error log for details" +msgstr "无法复制 MathML: 有关详细信息,请查看 NVDA 错误日志" + +#. Translators: this message directs users to look in the log file +msgid "MathCAT initialization failed: see NVDA error log for details" +msgstr "MathCat 初始化失败: 有关详细信息,请查看 NVDA 错误日志" + +#. Translators: this message directs users to look in the log file +msgid "Illegal MathML found: see NVDA error log for details" +msgstr "发现非法 MathML: 有关详细信息,请查看 NVDA 错误日志" + +#. Translators: this message directs users to look in the log file +msgid "Error in speaking math: see NVDA error log for details" +msgstr "朗读数学时出错: 有关详细信息,请查看 NVDA 错误日志" + +#. Translators: menu item -- use the language of the voice chosen in the NVDA speech settings dialog +#. "Auto" == "Automatic" -- other items in menu are "English (en)", etc., so this matches that style +msgid "Use Voice's Language (Auto)" +msgstr "使用语音的语言 (自动)" + +#. Translators: this is a test string that is spoken. Only translate "the square root of x squared plus y squared" +msgid "" +"the square root of x squared plus y squared" +msgstr "" +"the square root of x squared plus y squared" + +#. Translators: this is a test string that is spoken. Only translate "the fraction with numerator" +#. and other parts NOT inside '<.../>', +#, fuzzy, python-brace-format +msgid "" +"the fraction with numerator x to the n -th power plus 1 and " +"denominator x to the " +"n -" +"thpower minus 1 " +"end fraction " +msgstr "" +"the fraction with numerator x to the n -th power plus 1 and " +"denominator x to the " +"n -" +"thpower minus 1 " +"end fraction " + +#. Translators: title for MathCAT preferences dialog +msgid "MathCAT Preferences" +msgstr "MathCat 首选项" + +#. Translators: A heading that labels three navigation pane tab names in the MathCAT dialog +msgid "Categories:" +msgstr "分类:" + +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" +msgid "Speech" +msgstr "语音" + +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" +msgid "Navigation" +msgstr "导航" + +#. Translators: these are navigation pane headings for the MathCAT preferences dialog under the title "Categories" +msgid "Braille" +msgstr "盲文" + +#. Translators: this is the text label for whom to target the speech for (options are below) +msgid "Generate speech for:" +msgstr "为谁朗读:" + +#. Translators: these are the categories of impairments that MathCAT supports +#. Translators: Learning disabilities includes dyslexia and ADHD +msgid "Learning disabilities" +msgstr "学习障碍" + +#. Translators: target people who are blind +msgid "Blindness" +msgstr "盲人" + +#. Translators: target people who have low vision +msgid "Low vision" +msgstr "低视力" + +#. Translators: label for pull down allowing users to choose the speech language for math +msgid "Language:" +msgstr "语言:" + +#. Translators: label for pull down to specify what character to use in numbers as the decimal separator +msgid "Decimal separator for numbers:" +msgstr "" + +#. Translators: options for decimal separator -- "Auto" = automatically pick the choice based on the language +msgid "Auto" +msgstr "" + +#. Translators: options for decimal separator -- "Custom" = user sets it +#. Currently there is no UI for how it is done yet, but eventually there will be a dialog that pops up to set it +msgid "Custom" +msgstr "" + +#. Translators: label for pull down allowing users to choose the "style" (version, rules) of speech for math +msgid "Speech style:" +msgstr "语音风格:" + +#. Translators: label for pull down to specify how verbose/terse the speech should be +msgid "Speech verbosity:" +msgstr "语音详细度:" + +#. Translators: options for speech verbosity -- "terse" = use less words +#. Translators: options for navigation verbosity -- "terse" = use less words +msgid "Terse" +msgstr "简要" + +#. Translators: options for speech verbosity -- "medium" = try to be nether too terse nor too verbose words +#. Translators: options for navigation verbosity -- "medium" = try to be nether too terse nor too verbose words +msgid "Medium" +msgstr "适中" + +#. Translators: options for speech verbosity -- "verbose" = use more words +#. Translators: options for navigation verbosity -- "verbose" = use more words +msgid "Verbose" +msgstr "详细" + +#. Translators: label for slider that specifies a percentage of the normal speech rate that should be used for math +msgid "Relative speech rate:" +msgstr "相对语速:" + +#. Translators: label for slider that specifies relative factor to increase or decrease pauses in the math speech +msgid "Pause factor:" +msgstr "停顿时常:" + +#. Translators: label for check box controling a beep sound +msgid "Make a sound when starting/ending math speech" +msgstr "语音首尾音效提示" + +#. Translators: label for pull down to specify a subject area (Geometry, Calculus, ...) +msgid "Subject area to be used when it cannot be determined automatically:" +msgstr "无法自动确定时要使用的主题区域:" + +#. Translators: a generic (non-specific) math subject area +msgid "General" +msgstr "常规" + +#. Translators: label for pull down to specify how verbose/terse the speech should be +msgid "Speech for chemical formulas:" +msgstr "朗读化学式:" + +#. Translators: values for chemistry options with example speech in parenthesis +msgid "Spell it out (H 2 O)" +msgstr "拼写 (H2O)" + +#. Translators: values for chemistry options with example speech in parenthesis (never interpret as chemistry) +msgid "Off (H sub 2 O)" +msgstr "关闭 (H 下标2 O)" + +#. Translators: label for pull down to specify one of three modes use to navigate math expressions +msgid "Navigation mode to use when beginning to navigate an equation:" +msgstr "公式导航模式:" + +#. Translators: names of different modes of navigation. "Enhanced" mode understands math structure +msgid "Enhanced" +msgstr "增强" + +#. Translators: "Simple" walks by character expect for things like fractions, roots, and scripts +msgid "Simple" +msgstr "简单" + +#. Translators: "Character" moves around by character, automatically moving into fractions, etc +msgid "Character" +msgstr "字符" + +#. Translators: label for checkbox that controls whether any changes to the navigation mode should be preserved +msgid "Reset navigation mode on entry to an expression" +msgstr "进入表达式时重置导航模式" + +#. Translators: label for pull down to specify whether the expression is spoken or described (an overview) +msgid "Navigation speech to use when beginning to navigate an equation:" +msgstr "在导航公式时使用的表达方式:" + +#. Translators: "Speak" the expression after moving to it +msgid "Speak" +msgstr "说出" + +#. Translators: "Describe" the expression after moving to it ("overview is a synonym") +msgid "Describe/overview" +msgstr "描述/概述" + +#. Translators: label for checkbox that controls whether any changes to the speak vs overview reading should be ignored +msgid "Reset navigation speech on entry to an expression" +msgstr "在进入表达式时重置导航表达方式" + +#. Translators: label for checkbox that controls whether arrow keys move out of fractions, etc., +#. or whether you have to manually back out of the fraction, etc. +msgid "Automatic zoom out of 2D notations" +msgstr "自动跳出 2 维结构" + +#. Translators: label for pull down to specify whether you want a terse or verbose reading of navigation commands +msgid "Speech amount for navigation:" +msgstr "导航时语音详细度:" + +#. Translators: label for pull down to specify how math will be copied to the clipboard +msgid "Copy math as:" +msgstr "" + +#. Translators: options for Copy expression to clipboard as -- "MathML" +msgid "MathML" +msgstr "" + +#. Translators: options for Copy expression to clipboard as -- "LaTeX" +msgid "LaTeX" +msgstr "" + +#. Translators: options for Copy expression to clipboard as -- "ASCIIMath" +msgid "ASCIIMath" +msgstr "" + +#. Translators: label for pull down to specify which braille code to use +msgid "Braille math code for refreshable displays:" +msgstr "为可刷新盲文显示器提供的数学码:" + +#. Translators: label for pull down to specify how braille dots should be modified when navigating/selecting subexprs +msgid "Highlight with dots 7 && 8 the current nav node:" +msgstr "用 7 && 8 点高亮当前导航点:" + +#. Translators: options for using dots 7 and 8: +#. Translators: "off" -- don't highlight +msgid "Off" +msgstr "关闭" + +#. Translators: "First character" -- only the first character of the current navigation node uses dots 7 & 8 +msgid "First character" +msgstr "首字符" + +#. Translators: "Endpoints" -- only the first and last character of the current navigation node uses dots 7 & 8 +msgid "Endpoints" +msgstr "仅首尾" + +#. Translators: "All" -- all the characters for the current navigation node use dots 7 & 8 +msgid "All" +msgstr "全部" + +#. Translators: dialog "ok" button +msgid "OK" +msgstr "确认" + +#. Translators: dialog "cancel" button +msgid "Cancel" +msgstr "取消" + +#. Translators: dialog "apply" button +msgid "Apply" +msgstr "应用" + +#. Translators: button to reset all the preferences to their default values +msgid "Reset to defaults" +msgstr "重置为默认" + +#. Translators: button to bring up a help page +msgid "Help" +msgstr "帮助" + +#. Translators: this show up in the NVDA preferences dialog. It opens the MathCAT preferences dialog +msgid "&MathCAT Settings..." +msgstr "MathCAT 设置 (&M)..." + +#. Add-on summary, usually the user visible name of the addon. +#. Translators: Summary for this add-on +#. to be shown on installation and add-on information found in Add-ons Manager. +msgid "MathCAT: speech and braille from MathML" +msgstr "MathCAT: 从 MathML 产生语音和盲文" + +#. Translators: Long description to be shown for this add-on on add-on information from add-ons manager +msgid "" +"MathCAT is a replacement for MathPlayer which has been discontinued.\n" +" It provides speech and braille support, and also supports " +"MathPlayer's three modes of navigation.\n" +" The speech quality is not quite as good as MathPlayer's speech yet,\n" +" but the braille support is much better and includes support for " +"Nemeth, UEB Technical, CMU (Spanish/Portuguese),\n" +" and Vietnamese braille code standards. Translations to Chinese " +"(Traditional), Indonesian, Spanish, and Vietnamese exist\n" +" and other translations are in progress." +msgstr "" +"MathCAT 是已停产的 MathPlayer 的替代品。\n" +" 它提供语音和盲文支持,还支持 MathPlayer 的三种导航模式。\n" +" 语音质量相比 MathPlayer 还没有那么好,,\n" +" 但盲文支持要好得多,包括对 Nemeth、UEB Technical、CMU (西班牙语/葡萄" +"牙语)和越南盲文代码标准的支持。\n" +" 已经翻译为中文(繁体)、印尼语、西班牙语和越南语,\n" +" 其他翻译正在进行中。" diff --git a/build-nvda-addon.sh b/build-nvda-addon.sh index a8a09ec0..c5c7c4fc 100644 --- a/build-nvda-addon.sh +++ b/build-nvda-addon.sh @@ -1,13 +1,19 @@ -#!/bin/csh -# RUN THIS FILE to build the NVDA addon - -rm -rf addon/globalPlugins/MathCAT/Rules -# NVDA is currently uses 32 bit python 3.7 -# We need to tell PYO3 that -set PYO3_PYTHON_64=c:/Users/neils/AppData/Local/Programs/Python/Python39 -set PYO3_PYTHON_32=c:/Users/neils/AppData/Local/Programs/Python/Python37-32 -env PYO3_PYTHON=$PYO3_PYTHON_32/python.exe cargo build --target i686-pc-windows-msvc --release -cp target/i686-pc-windows-msvc/release/libmathcat_py.dll addon/globalPlugins/MathCAT/libmathcat.pyd +#!/bin/bash +(set -o igncr) 2>/dev/null && set -o igncr; # comment is needed +PYO3_PYTHON_64=C:/Software/Python/Python313 +PYO3_PYTHON_32=C:/Software/Python/Python311-32 +STFLAGS='-C linker=lld' +PYO3_PYTHON=$YO3_PYTHON_32/python.exe STFLAGS="$STFLAGS" cargo build --target i686-pc-windows-msvc --release + + +cp target/i686-pc-windows-msvc/release/libmathcat_py.dll addon/globalPlugins/MathCAT/libmathcat_py.pyd + +# for testing +cp target/i686-pc-windows-msvc/release/libmathcat_py.dll Example/libmathcat_py.pyd +cp -r addon/globalPlugins/MathCAT/Rules Example sed 's/^import wx\.xrc/# import wx.xrc/' --in-place "addon/globalPlugins/MathCAT/MathCATgui.py" -rm MathCAT-*.nvda-addon -$PYO3_PYTHON_32/Scripts/scons +rm -f MathCAT-*.nvda-addon + +# use "pip install SCons" to add scons to the python env +echo ${PYO3_PYTHON_32} +${PYO3_PYTHON_32}/Scripts/scons diff --git a/build.rs b/build.rs index ef4d99c0..ac4f5757 100644 --- a/build.rs +++ b/build.rs @@ -7,16 +7,10 @@ use std::path::PathBuf; use zip::ZipArchive; fn main() { - let archive = libmathcat::ZIPPED_RULE_FILES; + let archive = libmathcat::shim_filesystem::ZIPPED_RULE_FILES; let archive = std::io::Cursor::new(archive); let location = PathBuf::from("addon/globalPlugins/MathCAT"); let mut zip_archive = ZipArchive::new(archive).unwrap(); zip_archive.extract(&location).expect("Zip extraction failed"); - - // the test dir 'zz' doesn't need to be part of the addon - let mut zz_dir = location.clone(); - zz_dir.push("Rules/Languages/zz"); - std::fs::remove_dir_all(&zz_dir) - .expect(&format!("Failed to remove directory {}", zz_dir.to_str().unwrap())); -} \ No newline at end of file +} diff --git a/build64-nvda-addon.sh b/build64-nvda-addon.sh new file mode 100644 index 00000000..56bc30e7 --- /dev/null +++ b/build64-nvda-addon.sh @@ -0,0 +1,22 @@ +#!/bin/bash +(set -o igncr) 2>/dev/null && set -o igncr; # comment is needed +PYO3_PYTHON_64=C:/Users/neils/AppData/Local/Python/pythoncore-3.13-64 +PYO3_PYTHON_32=C:/Software/Python/Python311-32 +export PYO3_PYTHON_64 PYO3_PYTHON_32 +STFLAGS='-C linker=lld' +echo "before cargo build" +PYO3_PYTHON=${PYO3_PYTHON_64}/python.exe STFLAGS="$STFLAGS" cargo build --target x86_64-pc-windows-msvc --release +echo "after cargo build" + +echo "before cp target/x86_64-pc-windows-msvc/release/libmathcat_py.dll addon/globalPlugins/MathCAT/libmathcat_py.pyd" + +cp target/x86_64-pc-windows-msvc/release/libmathcat_py.dll addon/globalPlugins/MathCAT/libmathcat_py.pyd + +# for testing +cp target/x86_64-pc-windows-msvc/release/libmathcat_py.dll Example/libmathcat_py.pyd +cp -r addon/globalPlugins/MathCAT/Rules Example +sed 's/^import wx\.xrc/# import wx.xrc/' --in-place "addon/globalPlugins/MathCAT/MathCATgui.py" +rm -f MathCAT-*.nvda-addon + +# use "pip install SCons" to add scons to the python env +${PYO3_PYTHON_64}/Scripts/scons diff --git a/buildVars.py b/buildVars.py index 3a5a45e3..36fd01d8 100644 --- a/buildVars.py +++ b/buildVars.py @@ -10,47 +10,51 @@ # To avoid initializing translations in this module we simply roll our own "fake" `_` function # which returns whatever is given to it as an argument. def _(arg): - return arg + return arg # Add-on information variables addon_info = { - # add-on Name/identifier, internal for NVDA - "addon_name": "MathCAT", - # Add-on summary, usually the user visible name of the addon. - # Translators: Summary for this add-on - # to be shown on installation and add-on information found in Add-ons Manager. - "addon_summary": _("MathCAT: speech and braille from MathML"), - # Add-on description - # Translators: Long description to be shown for this add-on on add-on information from add-ons manager - "addon_description": _("""MathCAT is a replacement for MathPlayer which has been discontinued. - It provides speech and braille support, and also supports MathPlayer's three modes of navigation. - The speech quality is not quite as good as MathPlayer's speech yet, - but the braille support is much better and includes both Nemeth and UEB Technical. - It also includes support for CMU (Spanish/Portuguese) and Vietnamese braille code standards. - Translations to Indonesian, Spanish, and Vietnamese exist and other translations are in progress."""), - # version - "addon_version": "0.3.8", - # Author(s) - "addon_author": "Neil Soiffer ", - # URL for the add-on documentation support - "addon_url": "https://nsoiffer.github.io/MathCAT/", - # URL for the add-on repository where the source code can be found - "addon_sourceURL": "https://github.com/NSoiffer/MathCATForPython", - # Documentation file name - "addon_docFileName": "readme.html", - # Minimum NVDA version supported (e.g. "2018.3.0", minor version is optional) - "addon_minimumNVDAVersion": "2019.3", - # Last NVDA version supported/tested (e.g. "2018.4.0", ideally more recent than minimum version) - "addon_lastTestedNVDAVersion": "2023.3", - # Add-on update channel (default is None, denoting stable releases, - # and for development releases, use "dev".) - # Do not change unless you know what you are doing! - "addon_updateChannel": None, - # Add-on license such as GPL 2 - "addon_license": "MIT and GPL 2", - # URL for the license document the ad-on is licensed under - "addon_licenseURL": "https://raw.githubusercontent.com/NSoiffer/MathCAT/main/LICENSE", + # add-on Name/identifier, internal for NVDA + "addon_name": "MathCAT", + # Add-on summary, usually the user visible name of the addon. + # Translators: Summary for this add-on + # to be shown on installation and add-on information found in Add-ons Manager. + "addon_summary": _("MathCAT: speech and braille from MathML"), + # Add-on description + "addon_description": _( + # Translators: Long description to be shown for this add-on on add-on information from add-ons manager + """MathCAT is a replacement for MathPlayer which has been discontinued. + It provides speech and braille support, and also supports MathPlayer's three modes of navigation. + The supported languages are: Chinese (Traditional), English, Finnish, French, + German, Greek, Hungarian, Indonesian, Norwegian, Polish, Russian, Spanish, Swedish, and Vietnamese. + The supported braille codes are: Nemeth, UEB Technical, CMU (Spanish/Portuguese), + Russian, Swedish, and Vietnamese braille code standards. + Also supported are the German/Austrian braille code standards for ASCIIMath and LaTeX math markup. + Other language and braille code translations are in progress.""", + ), + # version + "addon_version": "0.7.6-beta.9", + # Author(s) + "addon_author": "Neil Soiffer ", + # URL for the add-on documentation support + "addon_url": "https://nsoiffer.github.io/MathCAT/", + # URL for the add-on repository where the source code can be found + "addon_sourceURL": "https://github.com/NSoiffer/MathCATForPython", + # Documentation file name + "addon_docFileName": "readme.html", + # Minimum NVDA version supported (e.g. "2018.3.0", minor version is optional) + "addon_minimumNVDAVersion": "2026.1", + # Last NVDA version supported/tested (e.g. "2018.4.0", ideally more recent than minimum version) + "addon_lastTestedNVDAVersion": "2026.1", + # Add-on update channel (default is None, denoting stable releases, + # and for development releases, use "dev".) + # Do not change unless you know what you are doing! + "addon_updateChannel": "dev", + # Add-on license such as GPL 2 + "addon_license": "MIT and GPL 2", + # URL for the license document the ad-on is licensed under + "addon_licenseURL": "https://raw.githubusercontent.com/NSoiffer/MathCAT/main/LICENSE", } # Define the python files that are the sources of your add-on. diff --git a/flake8.ini b/flake8.ini index 82539256..a9dc16f7 100644 --- a/flake8.ini +++ b/flake8.ini @@ -25,7 +25,7 @@ show-source = True # Options max-complexity = 15 -max-line-length = 110 +max-line-length = 130 # Final bracket should match indentation of the start of the line of the opening bracket hang-closing = False @@ -41,3 +41,4 @@ builtins = # inform flake8 about functions we consider built-in. exclude = # don't bother looking in the following subdirectories / files. .git, __pycache__, + addon\globalPlugins\MathCAT\yaml\* diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..f83a0734 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,211 @@ +[build-system] +requires = ["setuptools~=72.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "MathCATForPython" +dynamic = ["version"] +description = "Math Capable Assistive Technology (MathCAT) Python bindings and addon for NVDA" +maintainers = [ + {name = "Neil Soiffer", email = "soiffer@alum.nit.edu"}, +] +requires-python = ">=3.11,<3.12" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: End Users/Desktop", + "License :: OSI Approved :: GNU General Public License v2", + "Operating System :: Microsoft :: Windows", + "Programming Language :: Python :: 3", + "Topic :: Accessibility", +] +readme = "readme.md" +license = {file = "LICENSE"} +dependencies = [ + "wxPython==4.2.2", +] + +[project.urls] +Repository = "http://github.com/NSoiffer/MathCATForPython" + +[tool.ruff] +line-length = 110 + +builtins = [ + # translation lookup + "_", + # translation lookup + "ngettext", + # translation lookup + "pgettext", + # translation lookup + "npgettext", +] + +include = [ + "*.py", + "sconstruct", +] + +exclude = [ + ".git", + "__pycache__", + ".venv", + "./addon/globalPlugins/MathCAT/yaml/*", +] + +[tool.ruff.format] +indent-style = "tab" +line-ending = "lf" + +[tool.ruff.lint.mccabe] +max-complexity = 15 + +[tool.ruff.lint] +ignore = [ + # indentation contains tabs + "W191", +] +logger-objects = ["logHandler.log"] + +[tool.ruff.lint.per-file-ignores] +# sconscripts contains many inbuilt functions not recognised by the lint, +# so ignore F821. +"sconstruct" = ["F821"] + +[tool.pyright] +venvPath = ".venv" +venv = "." +pythonPlatform = "Windows" +typeCheckingMode = "strict" + +include = [ + "**/*.py", +] + +exclude = [ + "sconstruct", + ".git", + "__pycache__", + ".venv", + # When excluding concrete paths relative to a directory, + # not matching multiple folders by name e.g. `__pycache__`, + # paths are relative to the configuration file. +] + +# Tell pyright where to load python code from +extraPaths = [ + "./addon", + "../nvda/source", +] + +# General config +analyzeUnannotatedFunctions = true +deprecateTypingAliases = true + +# Stricter typing +strictParameterNoneValue = true +strictListInference = true +strictDictionaryInference = true +strictSetInference = true + +# Compliant rules +reportAssertAlwaysTrue = true +reportAssertTypeFailure = true +reportDuplicateImport = true +reportIncompleteStub = true +reportInconsistentOverload = true +reportInconsistentConstructor = true +reportInvalidStringEscapeSequence = true +reportInvalidStubStatement = true +reportInvalidTypeVarUse = true +reportMatchNotExhaustive = true +reportMissingModuleSource = true +reportMissingImports = false # temporarily setting to false for MathCAT integration +reportNoOverloadImplementation = true +reportOptionalContextManager = true +reportOverlappingOverload = true +reportPrivateImportUsage = true +reportPropertyTypeMismatch = true +reportSelfClsParameterName = true +reportShadowedImports = true +reportTypeCommentUsage = true +reportTypedDictNotRequiredAccess = true +reportUndefinedVariable = true +reportUnusedExpression = true +reportUnboundVariable = true +reportUnhashable = true +reportUnnecessaryCast = true +reportUnnecessaryContains = true +reportUnnecessaryTypeIgnoreComment = true +reportUntypedClassDecorator = true +reportUntypedFunctionDecorator = true +reportUnusedClass = true +reportUnusedCoroutine = true +reportUnusedExcept = true + +# Should switch to true when possible +reportDeprecated = false + +# Can be enabled by generating type stubs for modules via pyright CLI +reportMissingTypeStubs = false + +reportUnsupportedDunderAll = false +reportAbstractUsage = false +reportUntypedBaseClass = false +reportOptionalIterable = false +reportCallInDefaultInitializer = false +reportInvalidTypeArguments = false +reportUntypedNamedTuple = false +reportRedeclaration = false +reportOptionalCall = false +reportConstantRedefinition = false +reportWildcardImportFromLibrary = false +reportIncompatibleVariableOverride = false +reportInvalidTypeForm = false +reportGeneralTypeIssues = false +reportOptionalOperand = false +reportUnnecessaryComparison = false +reportFunctionMemberAccess = false +reportUnnecessaryIsInstance = false +reportUnusedFunction = false +reportImportCycles = false +reportUnusedImport = false +reportUnusedVariable = false +reportOperatorIssue = false +reportAssignmentType = false +reportReturnType = false +reportPossiblyUnboundVariable = false +reportMissingSuperCall = false +reportUninitializedInstanceVariable = false +reportUnknownLambdaType = false +reportMissingTypeArgument = false +reportImplicitStringConcatenation = false +reportIncompatibleMethodOverride = false +reportPrivateUsage = false +reportUnusedCallResult = false +reportOptionalSubscript = false +reportCallIssue = false +reportOptionalMemberAccess = false +reportImplicitOverride = false +reportIndexIssue = false +reportAttributeAccessIssue = false +reportArgumentType = false +reportUnknownParameterType = false +reportMissingParameterType = false +reportUnknownVariableType = false +reportUnknownArgumentType = false +reportUnknownMemberType = false + +[dependency-groups] +dev = [ + "SCons==4.8.1", + "setuptools~=72.0", +] +lint = [ + "ruff==0.8.1", + "pre-commit==4.0.1", + "pyright==1.1.396", +] + +[tool.setuptools.dynamic] +version = {attr = "buildVersion.version_detailed"} diff --git a/readme.md b/readme.md new file mode 100644 index 00000000..5192e37d --- /dev/null +++ b/readme.md @@ -0,0 +1,351 @@ +# MathCAT + +* Author: Neil Soiffer +* NVDA compatibility: 2025.1 or later (switching to a more modern Python makes it incompatible with earlier versions of NVDA) for 32 bit NVDA versions; 2026.1 for 64 bit NVDA versions. +* Download [stable version][1] + +MathCAT replaces MathPlayer because MathPlayer is no longer supported. MathCAT generates speech and braille from MathML. The speech for math produced by MathCAT is enhanced with prosody so that it sounds more natural. The speech can be navigated in three modes using the same commands as MathPlayer. In addition, the navigation node is indicated on a braille display. + +The supported languages are: Chinese (Traditional), English, Finnish, French, +German, Greek, Hungarian, Indonesian, Norwegian, Polish, Russian, Spanish, Swedish, and Vietnamese. + +The supported braille codes are: Nemeth, UEB Technical, CMU (Spanish/Portuguese), +Russian, Swedish, and Vietnamese braille code standards. +Also supported are the German/Austrian braille code standards for ASCIIMath and LaTeX math markup. + +Other language and braille code translations are in progress. + +MathCAT has a number of configuration options that control speech, navigation, and braille. +Many of these can be set in the MathCAT settings dialog (found NVDA Preferences menu). +For more information on these settings, see the [MathCAT documentation](https://nsoiffer.github.io/MathCAT/users.html). +The documentation includes a link to [a table listing all of the navigation commands in MathCAT](https://nsoiffer.github.io/MathCAT/nav-commands.html). + +Note: MathCAT is a general library for generating speech and braille from MathML. It is used by other AT projects besides NVDA. For information on the MathCAT project in general, see the main [MathCAT Documentation page](https://nsoiffer.github.io/MathCAT). + +Who should use MathCAT: + +* Those who use one of the supported languages or braille codes. +* Those who need high quality Nemeth braille (MathPlayer's Nemeth is based on liblouis' Nemeth generation which has a number of significant bugs that are technically difficult to fix). +* Those who use Eloquence as a voice +* Those who are reading PDF or HTML documents whose source is LaTeX that has turned on tagging. Those documents make use of a new MathML feature for expressing author intents, which can improve speech. + +Who should NOT use MathCAT: + +* Anyone who prefers Access8Math (for speech or other features) + +## MathCAT Update Log + +### Version v0.7.6-beta.8 + +## User-facing highlights + +| Area | Since 0.7.5 | +| ---- | ----------- | +| Languages | New: Greek, French, Hungarian, Polish, Russian; Improvements German, Norwegian, Traditional Chinease | +| Braille | New: Russian; Nemeth 2022-ish fixes; unicode ranges; BrailleCode refactor | +| Content | Augmented matrices; chemistry; currency/mtext cleanup; SSML fix | +| Quality | Fuzzing; better errors; dual `no-unsafe` CI track | + +## Details + +### Languages & speech + +* **Polish (`pl`)** — new +* **French (`fr`)** — new +* **Greek (`el`)** — new +* **Hungarian (`hu`)** — new +* **Russian (`ru`)** — new +* **Norwegian (`nb`)** — speech + navigation enhancements +* **German (`de`)** — digit rules, units, other speech fixes +* **Traditional Chinese (`zn-tw`)** — navigation / related rule updates +* **English** — core concept names (#381); principal Log vs log; identity / **augmented matrices**; black circled Latin letters; circled-number support; unit-definition cleanups +* **Intent** — inference tweaks + +### Braille + +* **Russian** — new +* **Nemeth** — 2022-oriented updates (ellipses, multipurpose between scripts, currency, typeform prefs cleanup, bugfixes) +* Unicode tables — **collapse to ranges** + range fixups +* Refactor — **`BrailleCode` trait + registry** +* Table-related braille/rules work +* Remove incomplete **ASCIIMath-fi** packaging that broke builds + +### Canonicalization, chemistry, robustness + +* Chemistry — mmultiscripts/scripts merge, atomic-number scoring, chem-element intent/heuristics, chem test fixes +* Bad mmultiscripts / empty bases / `data-split` cleanup +* Currency symbols split out of `mtext` / `mi` / `mn`; less aggressive `?` fill-in +* Empty `` / degree edge cases; mtext-as-number → `mn` where appropriate +* Stronger panic/error reporting (tests + containers) +* **SSML** — stopped incorrectly escaping SSML (#585) + +### Internals + +* Rust **edition 2024**; `lazy_static` → **`LazyLock`**; API cleanups (`AsRef`, etc.) +* **anyhow** instead of error-chain +* Optional **`no-unsafe`** via `sxd-document-no-unsafe` / `sxd-xpath-no-unsafe` (default = classic mode) +* CI — build/test/clippy for default **and** `no-unsafe`; fuzz both configs; CI on all branches; Python tooling CI; coverage; pre-release fixes +* **cargo-fuzz** harness, dictionary, corpus cache, regression helpers +* **audit-translations** and related Python/uv tooling +* AGENTS.md, CODEOWNERS, CLI / `mathml2text` path +* Dependency bumps; BrailleDocs publish exclude + +### Docs + +* README / product-page copy; AT notes (JAWS, Orca, Kurzweil, etc.) +* User guides / translators’ guide work; example HTML for translator testing + +### Version 0.7.5 + +#### Bug Fixes and Enhancements + +* Fixed bug in MathCAT dialog that prevented proper selection of Norwegian +* Fixed bug in German translation for division involving units (=> "pro") +* If MathML is directly embedded inside of a MathML leaf element, it will be spoken well. +* Improved Chemistry so that if the intent property `:chemical-formula` is used, it is inherited and overrides heuristics to determine if something is a chemical formula. For example, "A=B" will now speak "=" as "double bond" If some parent is marked with `intent=':chemical-formula'`. +* For ASCIIMath, added translations for chars with umlauts and also for ß (goes to "ss" because there is no defined ASCIIMath encoding for it) +* Added literal speech for"×", "‼", and "/" to English. + +#### API Additions + +* Added calls for `GetSupportedLanguages`, `GetSupportedSpeechStyles`, and `GetSupportedBrailleCodes`. + +### Version 0.7.2 + +* Added German translation. There is still more work to do on this, but I'm told it is usable. +* Added Norwegian translation +* Improved reading of "neuter" units +* Changed some character wording ("if and only if", "implies", "triangle") +* Fix problems with the zip files and regional variants. This should allow en-gb and zh-tw to be available. +* Fixed bugs in navigating in character mode and simple mode. +* Changed the names of some characters to be more semantic (e.g., "long double left right arrow" is not "if and only if"). +* Add some "literal" (not semantic) names for characters for LiteralSpeak and navigation. +* Fixed some bugs dealing with "intent" +* Fix a bug with generating id's that could cause a crash once every 36^4 times +* Add another heuristic to prevent something from being a potential function (when the potential function name appears within the argument) +* Fixed reading of a degree symbol followed by "F" or "C". +* Corrected the rule for what is allowed for "intent" +* Improved the inference rules for units (supports "mi" if it is marked as "normal") +* Fixed a navigation bug with log, ln, and lg +* Improved error messages -* these should aid in reporting problems in speech and navigation +* Improved speech for fractions that involve units ("meters *per* second") +* Many improvements to the recognition of Chemistry +* Fixed a Nemeth bug where a script end and baseline indicator were emitted when neither should have been present. +* Added varepsilon character to UEB +* Fixed off-by-one error when computing what to highlight in braille. +* Add definitions for "ⅆ", "ⅇ", "ⅈ" to braille codes + +### Version 0.6.10 + +* Update manifest to indicate compatibility with 2025.1, no external changes +* Because of internal NVDA changes to some speech engines, you may need to tweak MathCAT's "PauseFactor" setting +* Internal improvements to type hints and doc strings thanks to @seanbudd and @RyanMcCleary + +### Version 0.6.9 + +* Update manifest to indicate compatibility with 2025.1 +* Fix unhandled exception when opening up a new user preference file +* Strip '_' and '-' from unmatched intent names (and literals). For example "my-function" should be "my function". +* Add rule for "x check" (inverted hat) +* Add rule for repeating decimals for 'en'. This only handles the line over the repeating part, not other notations. + +### Version 0.6.8 + +Lots of changes because it has been a while since the last official release. + +#### Speech + +* Added "LiteralSpeak" style that does not infer what the meaning of the math and therefore, how that meaning spoken. +* Added Swedish to supported languages. +* Added Finnish to supported languages. +* For Vietnamese, added optional pitch change and beep for capital letters +* MathCAT will switch the voice when reading math if a different language from the current voice was set in the preference dialog. +* Added a en-UK variant with some British ways to speak bracketing chars. +* Added English rules for div, grad, and curl (calculus) +* Added English rule for $P(A|B)$ so that | is spoken as "given" +* Added more cases where invisible times is spoken (before roots) +* In terse mode, integer subscripts are spoken as "x 1" instead of "x sub 1". +* Added ability for authors to insert pauses (English only at the moment) +* Added a pause before row/equation/line labels +* Changed the speech for ≈ from "congruent to" to "approximately equal to" +* Added inference for cross-product and dot-product +* Added inference for div, grad, and curl +* Added special speech for zero, identity, and diagonal matrices in English +* Be more restrictive when inferring a table +* Changed speech for the general cases of `mover` and `munder` from "modified x with y above it" to "quantity x with y above it" +* Improved rule for {} so that it isn't always spoken as "set of ...". It could just be bracketing chars. +* Tweaked the speech for ∈ inside of a set so that the word "is" is dropped when part of a set -* "the set of all x is an element of ..." sounds poor. +* Improved rule for chemistry recognition for atomic numbers. +* Update to speech hint property names in the proposed MathML Core property list +* Add speech for coordinates ("the point at 1 comma 2") +* Added pauses for a "," +* Added an experimental `:pause-long`, `:pause-medium`, `:pause-short` for intent +* Added an 'xlong' pause +* Increased the meaning of short/medium/long pauses from 150ms/300ms/600ms to 200ms/400ms/800ms. As always, these are scaled to the speech rate. +* In MathML 4, `mlabedtr` is deprecated. A workaround is to use the intent property `:equation-label` on an `mtd` and this is now supported +* Added speech for units (e.g., "km", "in") -* won't work for single letter units such as "m" and "s" unless marked as a unit +* Terse mode now says "of" for functions except for trig/log functions. It was a little too terse before. + +#### Navigation + +* Added "Speech" to copy menu when navigating so that you can copy out the text used to speak the focus point in the expression being explored. +* Substantial rewrite of the navigation rules so that follow the inferred meaning. For example, if MathCAT says "absolute value of x" and you "zoom in", then you move to the "x", not to a vertical bar. As another example, if MathCAT determines that a table consists of rows of equations, navigation won't concatenate the columns so that the table acts like there is only one column. +* "Speak Overview" didn't do anything (fixed). Overviews remain under-developed. + +#### Braille + +* Added support for Finnish version of AsciiMath braille. +* Added support for Swedish braille. +* Added support for Vietnamese accents position for Vietnamese braille vowel "rhymes". +* Added preferences so that Nemeth users can remap typeforms (e.g, map BlackBoard Bold to a different character). +* Changed Blackboard typeform indicator to reuse italic indicator instead of reusing the script indicator. By changing the Nemeth typeform values in NVDA's addon subdirectory `addons\MathCAT\globalPlugins\MathCAT\Rulesprefs.yaml or adding it to `%AppData%\MathCAT\prefs.yaml`, you can restore the old mapping. + +#### Other + +* All the language and braille Rule files are zipped up per directory and unzipped on demand. + * This currently saves ~5mb when Rules.zip is unzipped, and will save even more as more languages and braille codes are added. + * If you know certain languages or braille code will definitely be used (e.g., it is the default), then the files in those directories can be manually unzipped to save a few tens of milliseconds the first time the language/braille code by that user. +* Added new preference DecimalSeparator. + * The default value is Auto, with other values being ".", ",", and "Custom". The first three values set DecimalSeparators and BlockSeparators. + * Auto sets those preferences based on the value of the Language pref. For some language such as Spanish, , is used in some countries and . is used in others. In this case, it is best to set the language to also include the country code (e.g, es-es or es-mx) to ensure the right value is used. +* Added more Unicode chars to include both all Unicode chars marked as "Sm" and those with a mathclass (except Alphabetic and Glyph classes) in the Unicode standard. +* Add support for some (upcoming) new Unicode characters (equilibrium arrows and others) used in Chemistry into UEB and Nemeth +* Fixed a bug with double-struck numbers for Nemeth. +* Several fixes for recognizing chemistry. + +#### Fixes + +* Fixed bug with espeak where it would slow down +* Forgot to implement relative slowdown when navigating -* fixed +* Fixed sans-serif indicator for Nemeth braille. +* Fixed a bug where empty cells in a table that is piecewise, m:system-of-equations or lines were spoken. +* Fixed bug where open/closed intervals were inferred when brackets/parens were nested (can't be an interval). +* Fixed a bug in UEB where passage mode should have been used for capitals. +* Fixed a crash with UEB in certain conditions with runs of capital letters. +* Fixed bug in Navigation of tables (previously reported "Error in Navigation"). +* Fixed bug moving to previous/next column in tables when at a table row level. +* Fixed bug when trying to correct bad MathML representation of chemistry inside of the base of a script. +* Fixed Vietnamese braille for "/". +* In the dialog code, the file location and %APPDATA% are now used to find the Rules and prefs files. +* After changing how prefs work in a previous version, I forgot to change MathRate and PauseFactor to be numbers, not strings. +* Fixed bug in the braille Rules (missed change from earlier) where a third argument should have been given to say to look in the Braille definitions.yaml files and not the speech ones when looking up the value of a definition. +* Cleaned up use of definitions.yaml. +* Fixed some bugs in the MathML cleanup for "," decimal separators. +* Found a bug in braille highlighting when nothing is highlighted (maybe never happens which is why I didn't see it in practice?) +* Fixed "Describe" mode so that it works -* it is still very minimal and probably not useful yet +* Add space after math speech to work around a MS Word bug that concatinates the next character in the text onto the math. + +### Version 0.5.6 + +* Added Copy As... to the MathCAT dialog (in the "Navagation" pane). +* Fixed a bug where the language reverted to English when changing speech styles. +* Fixed a bug with navigation and braille +* Fixed some Asciimath spacing problems. +* Improved chemistry recognition +* Updated MathCAT to new BANA Nemeth chemistry spec (still only single line and special case style/font changes not handled) +* Fix a crash when non-ASCII digits (e.g., bold digits) are used in numbers +* Don't use italic indicators in braille codes when the math alphanumeric italic chars are used +* Some other smaller bug fixes that weren't reported by users + +### Version 0.5.0 + +* Added German LaTeX braille code. Unlike other braille codes, this generates ASCII chars and uses the current braille output table to translate the characters to braille. +* Added (expermental) ASCIIMath braille code. Like the LaTeX braille code, this generates ASCII chars and uses the current braille output table to translate the characters to braille. +* Added "CopyAs" preference that supports copying as MathML, LaTeX, or ASCIIMath using cntl+C when focused on MathML (as before). The currently focused node is copied. Note: this is only listed in the prefs.yaml file and is not exposed (yet) in the MathCAT Preferences dialog. + +### Version 0.4.2 + +* Fixed language switching when voice changes and MathCAT language is "Auto" +* Added more checks for $Impairments to improve reading when it is not set for those who are blind +* Nemeth: fix for "~" when it isn't part of an mrow +* UEB: character additions, "~" spacing fix if prefix, xor fix, +* MathML cleanup for accented vowels (mainly for Vietnamese) +* Major rewrite of preference reading/updating code with big speedup -* added `CheckRuleFiles` pref to control which files are checked for updates +* Added two new interface calls -* enables setting the navigaton location from the braille cursor (not part of MathCAT addon yet) + +### Version 0.3.11 + +* Upgraded to python 3.11 and verified working with NVDA 2024.1 +* Fix bugs in Vietnamese braille and also in Speech, mostly for chemistry. +* Fix broken braille when braille code and dependent language don't match (specifically Vietnam braille and Vietnamese speech) +* Fix whitespace bug in HTML inside of tokens +* Improve roman numeral detection + +### Version 0.3.9 + +* Added Traditional Chinese translation (thanks to Hon-Jang Yang) +* Fixed bug with navigating into the base of a scripted expression that has parenthesis +* Significantly changed the way whitespace is handled. This mainly affects braille output (spaces and "omission" detection). +* Improved recognition of chemistry +* UEB braille fixes that came up from adding chemistry examples +* UEB fixes for adding auxillary parenthesis in some cases + +### Version 0.3.8 + +Braille: + +* Dialog has been internationalized for several languages (many thanks to the translators!) +* Initial implementation of CMU -* the braille code used in Spanish and Portuguese speaking countries +* Fix some UEB bugs and added some characters for UEB +* Significant improvements to Vietnamese braille + +Other fixes: + +* Change relative rate dialog slider to have a maximum value of 100% (now only allows setting slower rates). Also, added step sizes so it is easier to raise/lower the rate significantly. +* Fix eSpeak bug that sometimes cut off speech when the relative rate was changed +* Improvements to Vietnamese speech +* Fixed bug with OneCore voices saying "a" +* Fixed some navigation bugs when `AutoZoomOut` is False (not the default) +* Fix updating around language changes and some other dialog changes so they take effect immediately upon clicking "Apply" or "OK". +* Added an "Use Voice's Language" option so that out of the box, MathCAT will speak in the right language (if there is a translation) +* Several improvements for cleaning up poor MathML code + +### Version 0.3.3 + +This release has a number of bug fixes in it. The major new features and bug fixes are: + +* Added Spanish Translation (thanks to Noelia Ruiz and María Allo Roldán) +* Modified navigation so that it starts zoomed in one level +* Added cntrl+alt+arrow as a way to navigate tabular structures. These keys should be more memorable because they are used for table navigation in NVDA. +* Worked around NVDA bug for eSpeak voices that caused them to slow down when the relative MathRate was set to be slower than the text speech rate. +* Worked around a OneCore voice problem so that they will speak the long 'a' sound. + +There are lots of small tweaks to the speech and some bug fixes for both Nemeth and UEB. + +Note: there is now an option to get Vietnam's braille standard as braille output. This is still a work in progress and is too buggy to be used other than for testing. I expect the next MathCAT release will contain a reliable implementation. + +### Version 0.2.5 + +* More improvements chemistry +* Fixes for Nemeth: +* * Added "omission" rules +* * Added some rules for English Language Indicators +* * Added more cases where the Mulitpurpose indicator is needed +* * Fixes related to Nemeth and punctuation + +### Version 0.2 + +* Lots of bug fixes +* Improvements to speech +* A preference setting to control the duration of pausing (works with changes to relative speech rate for math) +* Support to recognize chemistry notation and speak it appropriately +* Translations to Indonesian and Vietnamese + +## Development Environment + +How to set up your developer environment: + +1. Install Python 3.11 (32-bit). +1. Set up your virtual environment. + * `python -m venv .venv` +1. Install python dependencies to `.venv`. + * Activate the virtual environment + `.venv\Scripts\activate` + * Install lint dependencies + `pip install ruff==0.8.1 pre-commit==4.0.1 pyright==1.1.396` +1. Import NVDA code. + * NVDA source code needs to be discoverable to get type hints, namespace resolution, code completion, and other IDE hints. + * The relative path `../nvda/source` is included in the pyright configuration in `pyproject.toml`. + * The [NVDA repository](https://github.com/nvaccess/nvda) should be cloned into the same parent directory. + i.e. as a sibling to this repository. + Alternatively, update `../nvda/source` in `pyproject.toml` to another location where the NVDA repository has been cloned. diff --git a/sconstruct b/sconstruct index 8aa914b5..979b6ffd 100644 --- a/sconstruct +++ b/sconstruct @@ -9,6 +9,7 @@ import os import os.path import zipfile import sys +import markdown # While names imported below are available by default in every SConscript # Linters aren't aware about them. @@ -16,98 +17,112 @@ import sys # When using other Scons functions please add them to the line below. from SCons.Script import BoolVariable, Builder, Copy, Environment, Variables -sys.dont_write_bytecode = True - # Bytecode should not be written for build vars module to keep the repository root folder clean. import buildVars # NOQA: E402 +sys.dont_write_bytecode = True + def md2html(source, dest): - import markdown - # Use extensions if defined. - mdExtensions = buildVars.markdownExtensions - lang = os.path.basename(os.path.dirname(source)).replace('_', '-') - localeLang = os.path.basename(os.path.dirname(source)) - try: - _ = gettext.translation("nvda", localedir=os.path.join("addon", "locale"), languages=[localeLang]).gettext - summary = _(buildVars.addon_info["addon_summary"]) - except Exception: - summary = buildVars.addon_info["addon_summary"] - title = "{addonSummary} {addonVersion}".format( - addonSummary=summary, addonVersion=buildVars.addon_info["addon_version"] - ) - headerDic = { - "[[!meta title=\"": "# ", - "\"]]": " #", - } - with codecs.open(source, "r", "utf-8") as f: - mdText = f.read() - for k, v in headerDic.items(): - mdText = mdText.replace(k, v, 1) - htmlText = markdown.markdown(mdText, extensions=mdExtensions) - # Optimization: build resulting HTML text in one go instead of writing parts separately. - docText = "\n".join([ - "", - "" % lang, - "", - "" - "", - "", - "%s" % title, - "\n", - htmlText, - "\n" - ]) - with codecs.open(dest, "w", "utf-8") as f: - f.write(docText) + + # Use extensions if defined. + mdExtensions = buildVars.markdownExtensions + lang = os.path.basename(os.path.dirname(source)).replace("_", "-") + localeLang = os.path.basename(os.path.dirname(source)) + try: + _ = gettext.translation( + "nvda", localedir=os.path.join("addon", "locale"), languages=[localeLang] + ).gettext + summary = _(buildVars.addon_info["addon_summary"]) + except Exception: + summary = buildVars.addon_info["addon_summary"] + title = "{addonSummary} {addonVersion}".format( + addonSummary=summary, addonVersion=buildVars.addon_info["addon_version"] + ) + headerDic = { + '[[!meta title="': "# ", + '"]]': " #", + } + with codecs.open(source, "r", "utf-8") as f: + mdText = f.read() + for k, v in headerDic.items(): + mdText = mdText.replace(k, v, 1) + htmlText = markdown.markdown(mdText, extensions=mdExtensions) + # Optimization: build resulting HTML text in one go instead of writing parts separately. + docText = "\n".join( + [ + "", + '' % lang, + "", + '' + '', + '', + "%s" % title, + "\n", + htmlText, + "\n", + ] + ) + with codecs.open(dest, "w", "utf-8") as f: + f.write(docText) def mdTool(env): - mdAction = env.Action( - lambda target, source, env: md2html(source[0].path, target[0].path), - lambda target, source, env: 'Generating % s' % target[0], - ) - mdBuilder = env.Builder( - action=mdAction, - suffix='.html', - src_suffix='.md', - ) - env['BUILDERS']['markdown'] = mdBuilder + mdAction = env.Action( + lambda target, source, env: md2html(source[0].path, target[0].path), + lambda target, source, env: "Generating % s" % target[0], + ) + mdBuilder = env.Builder( + action=mdAction, + suffix=".html", + src_suffix=".md", + ) + env["BUILDERS"]["markdown"] = mdBuilder def validateVersionNumber(key, val, env): - # Used to make sure version major.minor.patch are integers to comply with NV Access add-on store. - # Ignore all this if version number is not specified, in which case json generator will validate this info. - if val == "0.0.0": - return - versionNumber = val.split(".") - if len(versionNumber) < 3: - raise ValueError("versionNumber must have three parts (major.minor.patch)") - if not all([part.isnumeric() for part in versionNumber]): - raise ValueError("versionNumber (major.minor.patch) must be integers") + # Used to make sure version major.minor.patch are integers to comply with NV Access add-on store. + # Ignore all this if version number is not specified, in which case json generator will validate this info. + if val == "0.0.0": + return + versionNumber = val.split(".") + if len(versionNumber) < 3: + raise ValueError("versionNumber must have three parts (major.minor.patch)") + if not all([part.isnumeric() for part in versionNumber]): + raise ValueError("versionNumber (major.minor.patch) must be integers") vars = Variables() vars.Add("version", "The version of this build", buildVars.addon_info["addon_version"]) -vars.Add("versionNumber", "Version number of the form major.minor.patch", "0.0.0", validateVersionNumber) +vars.Add( + "versionNumber", + "Version number of the form major.minor.patch", + "0.0.0", + validateVersionNumber, +) vars.Add(BoolVariable("dev", "Whether this is a daily development version", False)) -vars.Add("channel", "Update channel for this build", buildVars.addon_info["addon_updateChannel"]) +vars.Add( + "channel", + "Update channel for this build", + buildVars.addon_info["addon_updateChannel"], +) -env = Environment(variables=vars, ENV=os.environ, tools=['gettexttool', mdTool]) +env = Environment(variables=vars, ENV=os.environ, tools=["gettexttool", mdTool]) env.Append(**buildVars.addon_info) if env["dev"]: - import datetime - buildDate = datetime.datetime.now() - year, month, day = str(buildDate.year), str(buildDate.month), str(buildDate.day) - versionTimestamp = "".join([year, month.zfill(2), day.zfill(2)]) - env["addon_version"] = f"{versionTimestamp}-dev" - env["versionNumber"] = f"{versionTimestamp}.0.0" - env["channel"] = "dev" + import datetime + + buildDate = datetime.datetime.now() + year, month, day = str(buildDate.year), str(buildDate.month), str(buildDate.day) + versionTimestamp = "".join([year, month.zfill(2), day.zfill(2)]) + env["addon_version"] = f"{versionTimestamp}-dev" + env["versionNumber"] = f"{versionTimestamp}.0.0" + env["channel"] = "dev" elif env["version"] is not None: - env["addon_version"] = env["version"] + env["addon_version"] = env["version"] if "channel" in env and env["channel"] is not None: - env["addon_updateChannel"] = env["channel"] + env["addon_updateChannel"] = env["channel"] buildVars.addon_info["addon_version"] = env["addon_version"] buildVars.addon_info["addon_updateChannel"] = env["addon_updateChannel"] @@ -116,212 +131,227 @@ addonFile = env.File("${addon_name}-${addon_version}.nvda-addon") def addonGenerator(target, source, env, for_signature): - action = env.Action( - lambda target, source, env: createAddonBundleFromPath(source[0].abspath, target[0].abspath) and None, - lambda target, source, env: "Generating Addon %s" % target[0] - ) - return action + action = env.Action( + lambda target, source, env: createAddonBundleFromPath( + source[0].abspath, target[0].abspath + ) + and None, + lambda target, source, env: "Generating Addon %s" % target[0], + ) + return action def manifestGenerator(target, source, env, for_signature): - action = env.Action( - lambda target, source, env: generateManifest(source[0].abspath, target[0].abspath) and None, - lambda target, source, env: "Generating manifest %s" % target[0] - ) - return action + action = env.Action( + lambda target, source, env: generateManifest( + source[0].abspath, target[0].abspath + ) + and None, + lambda target, source, env: "Generating manifest %s" % target[0], + ) + return action def translatedManifestGenerator(target, source, env, for_signature): - dir = os.path.abspath(os.path.join(os.path.dirname(str(source[0])), "..")) - lang = os.path.basename(dir) - action = env.Action( - lambda target, source, env: generateTranslatedManifest(source[1].abspath, lang, target[0].abspath) and None, - lambda target, source, env: "Generating translated manifest %s" % target[0] - ) - return action + dir = os.path.abspath(os.path.join(os.path.dirname(str(source[0])), "..")) + lang = os.path.basename(dir) + action = env.Action( + lambda target, source, env: generateTranslatedManifest( + source[1].abspath, lang, target[0].abspath + ) + and None, + lambda target, source, env: "Generating translated manifest %s" % target[0], + ) + return action -env['BUILDERS']['NVDAAddon'] = Builder(generator=addonGenerator) -env['BUILDERS']['NVDAManifest'] = Builder(generator=manifestGenerator) -env['BUILDERS']['NVDATranslatedManifest'] = Builder(generator=translatedManifestGenerator) +env["BUILDERS"]["NVDAAddon"] = Builder(generator=addonGenerator) +env["BUILDERS"]["NVDAManifest"] = Builder(generator=manifestGenerator) +env["BUILDERS"]["NVDATranslatedManifest"] = Builder( + generator=translatedManifestGenerator +) def createAddonHelp(dir): - docsDir = os.path.join(dir, "doc") - if os.path.isfile("style.css"): - cssPath = os.path.join(docsDir, "style.css") - cssTarget = env.Command(cssPath, "style.css", Copy("$TARGET", "$SOURCE")) - env.Depends(addon, cssTarget) - if os.path.isfile("readme.md"): - readmePath = os.path.join(docsDir, buildVars.baseLanguage, "readme.md") - readmeTarget = env.Command(readmePath, "readme.md", Copy("$TARGET", "$SOURCE")) - env.Depends(addon, readmeTarget) + docsDir = os.path.join(dir, "doc") + if os.path.isfile("style.css"): + cssPath = os.path.join(docsDir, "style.css") + cssTarget = env.Command(cssPath, "style.css", Copy("$TARGET", "$SOURCE")) + env.Depends(addon, cssTarget) + if os.path.isfile("readme.md"): + readmePath = os.path.join(docsDir, buildVars.baseLanguage, "readme.md") + readmeTarget = env.Command(readmePath, "readme.md", Copy("$TARGET", "$SOURCE")) + env.Depends(addon, readmeTarget) def createAddonBundleFromPath(path, dest): - """ Creates a bundle from a directory that contains an addon manifest file.""" - basedir = os.path.abspath(path) - with zipfile.ZipFile(dest, 'w', zipfile.ZIP_DEFLATED) as z: - # FIXME: the include/exclude feature may or may not be useful. Also python files can be pre-compiled. - for dir, dirnames, filenames in os.walk(basedir): - relativePath = os.path.relpath(dir, basedir) - for filename in filenames: - pathInBundle = os.path.join(relativePath, filename) - absPath = os.path.join(dir, filename) - if pathInBundle not in buildVars.excludedFiles: - z.write(absPath, pathInBundle) - createAddonStoreJson(dest) - return dest + """Creates a bundle from a directory that contains an addon manifest file.""" + basedir = os.path.abspath(path) + with zipfile.ZipFile(dest, "w", zipfile.ZIP_DEFLATED) as z: + # FIXME: the include/exclude feature may or may not be useful. Also python files can be pre-compiled. + for dir, dirnames, filenames in os.walk(basedir): + relativePath = os.path.relpath(dir, basedir) + for filename in filenames: + pathInBundle = os.path.join(relativePath, filename) + absPath = os.path.join(dir, filename) + if pathInBundle not in buildVars.excludedFiles: + z.write(absPath, pathInBundle) + createAddonStoreJson(dest) + return dest def createAddonStoreJson(bundle): - """Creates add-on store JSON file from an add-on package and manifest data.""" - import json - import hashlib - # Set different json file names and version number properties based on version number parsing results. - if env["versionNumber"] == "0.0.0": - env["versionNumber"] = buildVars.addon_info["addon_version"] - versionNumberParsed = env["versionNumber"].split(".") - if all([part.isnumeric() for part in versionNumberParsed]): - if len(versionNumberParsed) == 1: - versionNumberParsed += ["0", "0"] - elif len(versionNumberParsed) == 2: - versionNumberParsed.append("0") - else: - versionNumberParsed = [] - if len(versionNumberParsed): - major, minor, patch = [int(part) for part in versionNumberParsed] - jsonFilename = f'{major}.{minor}.{patch}.json' - else: - jsonFilename = f'{buildVars.addon_info["addon_version"]}.json' - major, minor, patch = 0, 0, 0 - print('Generating % s' % jsonFilename) - sha256 = hashlib.sha256() - with open(bundle, "rb") as f: - for byte_block in iter(lambda: f.read(65536), b""): - sha256.update(byte_block) - hashValue = sha256.hexdigest() - try: - minimumNVDAVersion = buildVars.addon_info["addon_minimumNVDAVersion"].split(".") - except AttributeError: - minimumNVDAVersion = [0, 0, 0] - minMajor, minMinor = minimumNVDAVersion[:2] - minPatch = minimumNVDAVersion[-1] if len(minimumNVDAVersion) == 3 else "0" - try: - lastTestedNVDAVersion = buildVars.addon_info["addon_lastTestedNVDAVersion"].split(".") - except AttributeError: - lastTestedNVDAVersion = [0, 0, 0] - lastTestedMajor, lastTestedMinor = lastTestedNVDAVersion[:2] - lastTestedPatch = lastTestedNVDAVersion[-1] if len(lastTestedNVDAVersion) == 3 else "0" - channel = buildVars.addon_info["addon_updateChannel"] - if channel is None: - channel = "stable" - addonStoreEntry = { - "addonId": buildVars.addon_info["addon_name"], - "displayName": buildVars.addon_info["addon_summary"], - "URL": "", - "description": buildVars.addon_info["addon_description"], - "sha256": hashValue, - "homepage": buildVars.addon_info["addon_url"], - "addonVersionName": buildVars.addon_info["addon_version"], - "addonVersionNumber": { - "major": major, - "minor": minor, - "patch": patch - }, - "minNVDAVersion": { - "major": int(minMajor), - "minor": int(minMinor), - "patch": int(minPatch) - }, - "lastTestedVersion": { - "major": int(lastTestedMajor), - "minor": int(lastTestedMinor), - "patch": int(lastTestedPatch) - }, - "channel": channel, - "publisher": "", - "sourceURL": buildVars.addon_info["addon_sourceURL"], - "license": buildVars.addon_info["addon_license"], - "licenseURL": buildVars.addon_info["addon_licenseURL"], - } - with open(jsonFilename, "w") as addonStoreJson: - json.dump(addonStoreEntry, addonStoreJson, indent="\t") + """Creates add-on store JSON file from an add-on package and manifest data.""" + import json + import hashlib + + # Set different json file names and version number properties based on version number parsing results. + if env["versionNumber"] == "0.0.0": + env["versionNumber"] = buildVars.addon_info["addon_version"] + versionNumberParsed = env["versionNumber"].split(".") + if all([part.isnumeric() for part in versionNumberParsed]): + if len(versionNumberParsed) == 1: + versionNumberParsed += ["0", "0"] + elif len(versionNumberParsed) == 2: + versionNumberParsed.append("0") + else: + versionNumberParsed = [] + if len(versionNumberParsed): + major, minor, patch = [int(part) for part in versionNumberParsed] + jsonFilename = f"{major}.{minor}.{patch}.json" + else: + jsonFilename = f'{buildVars.addon_info["addon_version"]}.json' + major, minor, patch = 0, 0, 0 + print("Generating % s" % jsonFilename) + sha256 = hashlib.sha256() + with open(bundle, "rb") as f: + for byte_block in iter(lambda: f.read(65536), b""): + sha256.update(byte_block) + hashValue = sha256.hexdigest() + try: + minimumNVDAVersion = buildVars.addon_info["addon_minimumNVDAVersion"].split(".") + except AttributeError: + minimumNVDAVersion = [0, 0, 0] + minMajor, minMinor = minimumNVDAVersion[:2] + minPatch = minimumNVDAVersion[-1] if len(minimumNVDAVersion) == 3 else "0" + try: + lastTestedNVDAVersion = buildVars.addon_info[ + "addon_lastTestedNVDAVersion" + ].split(".") + except AttributeError: + lastTestedNVDAVersion = [0, 0, 0] + lastTestedMajor, lastTestedMinor = lastTestedNVDAVersion[:2] + lastTestedPatch = ( + lastTestedNVDAVersion[-1] if len(lastTestedNVDAVersion) == 3 else "0" + ) + channel = buildVars.addon_info["addon_updateChannel"] + if channel is None: + channel = "stable" + addonStoreEntry = { + "addonId": buildVars.addon_info["addon_name"], + "displayName": buildVars.addon_info["addon_summary"], + "URL": "", + "description": buildVars.addon_info["addon_description"], + "sha256": hashValue, + "homepage": buildVars.addon_info["addon_url"], + "addonVersionName": buildVars.addon_info["addon_version"], + "addonVersionNumber": {"major": major, "minor": minor, "patch": patch}, + "minNVDAVersion": { + "major": int(minMajor), + "minor": int(minMinor), + "patch": int(minPatch), + }, + "lastTestedVersion": { + "major": int(lastTestedMajor), + "minor": int(lastTestedMinor), + "patch": int(lastTestedPatch), + }, + "channel": channel, + "publisher": "", + "sourceURL": buildVars.addon_info["addon_sourceURL"], + "license": buildVars.addon_info["addon_license"], + "licenseURL": buildVars.addon_info["addon_licenseURL"], + } + with open(jsonFilename, "w") as addonStoreJson: + json.dump(addonStoreEntry, addonStoreJson, indent="\t") def generateManifest(source, dest): - addon_info = buildVars.addon_info - with codecs.open(source, "r", "utf-8") as f: - manifest_template = f.read() - manifest = manifest_template.format(**addon_info) - with codecs.open(dest, "w", "utf-8") as f: - f.write(manifest) + addon_info = buildVars.addon_info + with codecs.open(source, "r", "utf-8") as f: + manifest_template = f.read() + manifest = manifest_template.format(**addon_info) + with codecs.open(dest, "w", "utf-8") as f: + f.write(manifest) def generateTranslatedManifest(source, language, out): - _ = gettext.translation("nvda", localedir=os.path.join("addon", "locale"), languages=[language]).gettext - vars = {} - for var in ("addon_summary", "addon_description"): - vars[var] = _(buildVars.addon_info[var]) - with codecs.open(source, "r", "utf-8") as f: - manifest_template = f.read() - result = manifest_template.format(**vars) - with codecs.open(out, "w", "utf-8") as f: - f.write(result) + _ = gettext.translation( + "nvda", localedir=os.path.join("addon", "locale"), languages=[language] + ).gettext + vars = {} + for var in ("addon_summary", "addon_description"): + vars[var] = _(buildVars.addon_info[var]) + with codecs.open(source, "r", "utf-8") as f: + manifest_template = f.read() + result = manifest_template.format(**vars) + with codecs.open(out, "w", "utf-8") as f: + f.write(result) def expandGlobs(files): - return [f for pattern in files for f in env.Glob(pattern)] + return [f for pattern in files for f in env.Glob(pattern)] -addon = env.NVDAAddon(addonFile, env.Dir('addon')) +addon = env.NVDAAddon(addonFile, env.Dir("addon")) langDirs = [f for f in env.Glob(os.path.join("addon", "locale", "*"))] # Allow all NVDA's gettext po files to be compiled in source/locale, and manifest files to be generated for dir in langDirs: - poFile = dir.File(os.path.join("LC_MESSAGES", "nvda.po")) - moFile = env.gettextMoFile(poFile) - env.Depends(moFile, poFile) - translatedManifest = env.NVDATranslatedManifest( - dir.File("manifest.ini"), - [moFile, os.path.join("manifest-translated.ini.tpl")] - ) - env.Depends(translatedManifest, ["buildVars.py"]) - env.Depends(addon, [translatedManifest, moFile]) + poFile = dir.File(os.path.join("LC_MESSAGES", "nvda.po")) + moFile = env.gettextMoFile(poFile) + env.Depends(moFile, poFile) + translatedManifest = env.NVDATranslatedManifest( + dir.File("manifest.ini"), [moFile, os.path.join("manifest-translated.ini.tpl")] + ) + env.Depends(translatedManifest, ["buildVars.py"]) + env.Depends(addon, [translatedManifest, moFile]) pythonFiles = expandGlobs(buildVars.pythonSources) for file in pythonFiles: - env.Depends(addon, file) + env.Depends(addon, file) # Convert markdown files to html # We need at least doc in English and should enable the Help button for the add-on in Add-ons Manager createAddonHelp("addon") -for mdFile in env.Glob(os.path.join('addon', 'doc', '*', '*.md')): - htmlFile = env.markdown(mdFile) - env.Depends(htmlFile, mdFile) - env.Depends(addon, htmlFile) +for mdFile in env.Glob(os.path.join("addon", "doc", "*", "*.md")): + htmlFile = env.markdown(mdFile) + env.Depends(htmlFile, mdFile) + env.Depends(addon, htmlFile) # Pot target i18nFiles = expandGlobs(buildVars.i18nSources) gettextvars = { - 'gettext_package_bugs_address': 'nvda-translations@groups.io', - 'gettext_package_name': buildVars.addon_info['addon_name'], - 'gettext_package_version': buildVars.addon_info['addon_version'] + "gettext_package_bugs_address": "nvda-translations@groups.io", + "gettext_package_name": buildVars.addon_info["addon_name"], + "gettext_package_version": buildVars.addon_info["addon_version"], } pot = env.gettextPotFile("${addon_name}.pot", i18nFiles, **gettextvars) -env.Alias('pot', pot) +env.Alias("pot", pot) env.Depends(pot, i18nFiles) mergePot = env.gettextMergePotFile("${addon_name}-merge.pot", i18nFiles, **gettextvars) -env.Alias('mergePot', mergePot) +env.Alias("mergePot", mergePot) env.Depends(mergePot, i18nFiles) # Generate Manifest path -manifest = env.NVDAManifest(os.path.join("addon", "manifest.ini"), os.path.join("manifest.ini.tpl")) +manifest = env.NVDAManifest( + os.path.join("addon", "manifest.ini"), os.path.join("manifest.ini.tpl") +) # Ensure manifest is rebuilt if buildVars is updated. env.Depends(manifest, "buildVars.py") env.Depends(addon, manifest) env.Default(addon) -env.Clean(addon, ['.sconsign.dblite', 'addon/doc/' + buildVars.baseLanguage + '/']) +env.Clean(addon, [".sconsign.dblite", "addon/doc/" + buildVars.baseLanguage + "/"]) diff --git a/site_scons/site_tools/gettexttool/__init__.py b/site_scons/site_tools/gettexttool/__init__.py index db9a0096..3e8af277 100644 --- a/site_scons/site_tools/gettexttool/__init__.py +++ b/site_scons/site_tools/gettexttool/__init__.py @@ -1,4 +1,4 @@ -""" This tool allows generation of gettext .mo compiled files, pot files from source code files +"""This tool allows generation of gettext .mo compiled files, pot files from source code files and pot files for merging. Three new builders are added into the constructed environment: @@ -15,6 +15,7 @@ """ + from SCons.Action import Action @@ -36,20 +37,24 @@ def generate(env): env.SetDefault(gettext_package_name="") env.SetDefault(gettext_package_version="") - env['BUILDERS']['gettextMoFile'] = env.Builder( + env["BUILDERS"]["gettextMoFile"] = env.Builder( action=Action("msgfmt -o $TARGET $SOURCE", "Compiling translation $SOURCE"), suffix=".mo", - src_suffix=".po" + src_suffix=".po", ) - env['BUILDERS']['gettextPotFile'] = env.Builder( - action=Action("xgettext " + XGETTEXT_COMMON_ARGS, "Generating pot file $TARGET"), - suffix=".pot") + env["BUILDERS"]["gettextPotFile"] = env.Builder( + action=Action( + "xgettext " + XGETTEXT_COMMON_ARGS, + "Generating pot file $TARGET", + ), + suffix=".pot", + ) - env['BUILDERS']['gettextMergePotFile'] = env.Builder( + env["BUILDERS"]["gettextMergePotFile"] = env.Builder( action=Action( "xgettext " + "--omit-header --no-location " + XGETTEXT_COMMON_ARGS, - "Generating pot file $TARGET" + "Generating pot file $TARGET", ), - suffix=".pot" + suffix=".pot", ) diff --git a/src/lib.rs b/src/lib.rs index 8854114b..c96412f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ //! 1. whatever preferences the AT needs to set, it is done with calls to [`SetPreference`]. //! 2. the MathML is sent over via [`SetMathML`]. //! 3. AT calls to get the speech [`GetSpokenText`] and calls [`GetBraille`] to get the (Unicode) braille. -//! +//! //! Navigation can be done via calls to either: //! * [`DoNavigateKeyPress`] (takes key events as input) //! * [`DoNavigateCommand`] (takes the commands the key events internally map to) @@ -14,7 +14,7 @@ //! * [`GetNavigationMathML`] -- returns a string representing the MathML for the selected node //! Note: a second integer is returned. This is the offset in characters for a leaf node. //! This is needed when navigating by character for multi-symbol leaf nodes such as "sin" and "1234" -//! +//! //! It is also possible to find out what preferences are currently set by calling [`GetPreference`] //! //! AT can pass key strokes to allow a user to navigate the MathML by calling [`DoNavigateKeyPress`]; the speech is returned. @@ -64,6 +64,24 @@ pub fn GetVersion(_py: Python) -> PyResult { return Ok( get_version() ); } +#[pyfunction] +/// Returns a list of all supported languages (["en", "es", ...]) +pub fn GetSupportedLanguages(_py: Python) -> PyResult> { // type in Python is list[str] + return convert_error(get_supported_languages()); +} + +#[pyfunction] +/// Returns a list of all supported speech styles given a language (["ClearSpeak", "SimpleSpeak", ...]) +pub fn GetSupportedSpeechStyles(_py: Python, lang: String) -> PyResult> { // type in Python is list[str] + return convert_error(get_supported_speech_styles(lang)); +} + +#[pyfunction] +/// Returns a list of all supported braille codes (["UEB", "Nemeth", ...]) +pub fn GetSupportedBrailleCodes(_py: Python) -> PyResult> { // type in Python is list[str] + return convert_error(get_supported_braille_codes()); +} + #[pyfunction] /// Get the spoken text of the MathML that was set. /// The speech takes into account any AT or user preferences. @@ -95,15 +113,23 @@ pub fn GetPreference(_py: Python, name: String) -> PyResult { } #[pyfunction] -#[allow(unused_variables)] /// Get the braille associated with the MathML node with a given id (MathML set by `SetMathML`]). /// An empty string can be used to return the braille associated with the entire expression. -/// +/// /// The braille returned depends upon the preference for braille output. pub fn GetBraille(_py: Python, nav_node_id: String) -> PyResult { return convert_error( get_braille(nav_node_id) ); } +#[pyfunction] +/// Get the braille associated with the MathML node with a given id (MathML set by `SetMathML`]). +/// An empty string can be used to return the braille associated with the entire expression. +/// +/// The braille returned depends upon the preference for braille output. +pub fn GetNavigationBraille(_py: Python) -> PyResult { + return convert_error( get_navigation_braille() ); +} + #[pyfunction] /// Given a key code along with the modifier keys, the current node is moved accordingly (or value reported in some cases). /// @@ -116,17 +142,17 @@ pub fn DoNavigateKeyPress(_py: Python, key: usize, shift_key: bool, control_key: /// Given a command, the current node is moved accordingly (or value reported in some cases). /// /// The spoken text for the new current node is returned. -/// +/// /// The list of legal commands are: -/// "MovePrevious", "MoveNext", "MoveStart", "MoveEnd", "MoveLineStart", "MoveLineEnd", -/// "MoveCellPrevious", "MoveCellNext", "MoveCellUp", "MoveCellDown", "MoveColumnStart", "MoveColumnEnd", -/// "ZoomIn", "ZoomOut", "ZoomOutAll", "ZoomInAll", -/// "MoveLastLocation", -/// "ReadPrevious", "ReadNext", "ReadCurrent", "ReadCellCurrent", "ReadStart", "ReadEnd", "ReadLineStart", "ReadLineEnd", -/// "DescribePrevious", "DescribeNext", "DescribeCurrent", -/// "WhereAmI", "WhereAmIAll", -/// "ToggleZoomLockUp", "ToggleZoomLockDown", "ToggleSpeakMode", -/// "Exit", +/// "MovePrevious", "MoveNext", "MoveStart", "MoveEnd", "MoveLineStart", "MoveLineEnd", +/// "MoveCellPrevious", "MoveCellNext", "MoveCellUp", "MoveCellDown", "MoveColumnStart", "MoveColumnEnd", +/// "ZoomIn", "ZoomOut", "ZoomOutAll", "ZoomInAll", +/// "MoveLastLocation", +/// "ReadPrevious", "ReadNext", "ReadCurrent", "ReadCellCurrent", "ReadStart", "ReadEnd", "ReadLineStart", "ReadLineEnd", +/// "DescribePrevious", "DescribeNext", "DescribeCurrent", +/// "WhereAmI", "WhereAmIAll", +/// "ToggleZoomLockUp", "ToggleZoomLockDown", "ToggleSpeakMode", +/// "Exit", /// "MoveTo0","MoveTo1","MoveTo2","MoveTo3","MoveTo4","MoveTo5","MoveTo6","MoveTo7","MoveTo8","MoveTo9", /// "Read0","Read1","Read2","Read3","Read4","Read5","Read6","Read7","Read8","Read9", /// "Describe0","Describe1","Describe2","Describe3","Describe4","Describe5","Describe6","Describe7","Describe8","Describe9", @@ -148,14 +174,18 @@ pub fn GetNavigationMathML(_py: Python) -> PyResult<(String, usize)> { } #[pymodule] -fn libmathcat(_py: Python, m: &PyModule) -> PyResult<()> { +fn libmathcat_py(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(SetRulesDir, m)?)?; m.add_function(wrap_pyfunction!(SetMathML, m)?)?; m.add_function(wrap_pyfunction!(GetVersion, m)?)?; + m.add_function(wrap_pyfunction!(GetSupportedLanguages, m)?)?; + m.add_function(wrap_pyfunction!(GetSupportedSpeechStyles, m)?)?; + m.add_function(wrap_pyfunction!(GetSupportedBrailleCodes, m)?)?; m.add_function(wrap_pyfunction!(GetSpokenText, m)?)?; m.add_function(wrap_pyfunction!(SetPreference, m)?)?; m.add_function(wrap_pyfunction!(GetPreference, m)?)?; m.add_function(wrap_pyfunction!(GetBraille, m)?)?; + m.add_function(wrap_pyfunction!(GetNavigationBraille, m)?)?; m.add_function(wrap_pyfunction!(DoNavigateKeyPress, m)?)?; m.add_function(wrap_pyfunction!(DoNavigateCommand, m)?)?; m.add_function(wrap_pyfunction!(GetNavigationMathMLId, m)?)?; @@ -171,7 +201,7 @@ mod py_tests { #[test] fn test_setting() { // this isn't a real test - pyo3::prepare_freethreaded_python(); + Python::initialize(); let mathml_str = "(451,231)"; match convert_error( libmathcat::interface::set_mathml(mathml_str.to_string()) ) { Ok(_mathml_with_ids) => println!("MathML is set w/o error"), @@ -183,4 +213,4 @@ mod py_tests { Err(e) => panic!("Error remains {}", e.to_string()), } } -} \ No newline at end of file +} diff --git a/tasks.json b/tasks.json new file mode 100644 index 00000000..93112892 --- /dev/null +++ b/tasks.json @@ -0,0 +1,37 @@ +// tasks.json +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "flake8-whole-project", + "type": "shell", + "command": "flake8 .", + "presentation": { + "echo": true, + "reveal": "never", + "focus": false, + "panel": "shared", + "showReuseMessage": false, + "clear": true, + "revealProblems": "never" + }, + "problemMatcher": { + "owner": "flake8", + "source": "flake8-whole-project", + "fileLocation": ["relative", "${workspaceFolder}"], + "pattern": { + "regexp": "^(.+):(\\d+):(\\d+): ((\\w+)\\d+) (.+)$", + "file": 1, + "line": 2, + "column": 3, + "code": 4, + "severity": 5, + "message": 6 + } + } + } + ] + } + \ No newline at end of file diff --git a/test.py b/test.py deleted file mode 100644 index 3f81a5be..00000000 --- a/test.py +++ /dev/null @@ -1,570 +0,0 @@ -"""create and manipulate C data types in Python""" - -import os as _os, sys as _sys -import types as _types - -__version__ = "1.1.0" - -from _ctypes import Union, Structure, Array -from _ctypes import _Pointer -from _ctypes import CFuncPtr as _CFuncPtr -from _ctypes import __version__ as _ctypes_version -from _ctypes import RTLD_LOCAL, RTLD_GLOBAL -from _ctypes import ArgumentError - -from struct import calcsize as _calcsize - -if __version__ != _ctypes_version: - raise Exception("Version number mismatch", __version__, _ctypes_version) - -if _os.name == "nt": - from _ctypes import FormatError - -DEFAULT_MODE = RTLD_LOCAL -if _os.name == "posix" and _sys.platform == "darwin": - # On OS X 10.3, we use RTLD_GLOBAL as default mode - # because RTLD_LOCAL does not work at least on some - # libraries. OS X 10.3 is Darwin 7, so we check for - # that. - - if int(_os.uname().release.split('.')[0]) < 8: - DEFAULT_MODE = RTLD_GLOBAL - -from _ctypes import FUNCFLAG_CDECL as _FUNCFLAG_CDECL, \ - FUNCFLAG_PYTHONAPI as _FUNCFLAG_PYTHONAPI, \ - FUNCFLAG_USE_ERRNO as _FUNCFLAG_USE_ERRNO, \ - FUNCFLAG_USE_LASTERROR as _FUNCFLAG_USE_LASTERROR - -# WINOLEAPI -> HRESULT -# WINOLEAPI_(type) -# -# STDMETHODCALLTYPE -# -# STDMETHOD(name) -# STDMETHOD_(type, name) -# -# STDAPICALLTYPE - -def create_string_buffer(init, size=None): - """create_string_buffer(aBytes) -> character array - create_string_buffer(anInteger) -> character array - create_string_buffer(aBytes, anInteger) -> character array - """ - if isinstance(init, bytes): - if size is None: - size = len(init)+1 - _sys.audit("ctypes.create_string_buffer", init, size) - buftype = c_char * size - buf = buftype() - buf.value = init - return buf - elif isinstance(init, int): - _sys.audit("ctypes.create_string_buffer", None, init) - buftype = c_char * init - buf = buftype() - return buf - raise TypeError(init) - -def c_buffer(init, size=None): -## "deprecated, use create_string_buffer instead" -## import warnings -## warnings.warn("c_buffer is deprecated, use create_string_buffer instead", -## DeprecationWarning, stacklevel=2) - return create_string_buffer(init, size) - -_c_functype_cache = {} -def CFUNCTYPE(restype, *argtypes, **kw): - """CFUNCTYPE(restype, *argtypes, - use_errno=False, use_last_error=False) -> function prototype. - - restype: the result type - argtypes: a sequence specifying the argument types - - The function prototype can be called in different ways to create a - callable object: - - prototype(integer address) -> foreign function - prototype(callable) -> create and return a C callable function from callable - prototype(integer index, method name[, paramflags]) -> foreign function calling a COM method - prototype((ordinal number, dll object)[, paramflags]) -> foreign function exported by ordinal - prototype((function name, dll object)[, paramflags]) -> foreign function exported by name - """ - flags = _FUNCFLAG_CDECL - if kw.pop("use_errno", False): - flags |= _FUNCFLAG_USE_ERRNO - if kw.pop("use_last_error", False): - flags |= _FUNCFLAG_USE_LASTERROR - if kw: - raise ValueError("unexpected keyword argument(s) %s" % kw.keys()) - try: - return _c_functype_cache[(restype, argtypes, flags)] - except KeyError: - class CFunctionType(_CFuncPtr): - _argtypes_ = argtypes - _restype_ = restype - _flags_ = flags - _c_functype_cache[(restype, argtypes, flags)] = CFunctionType - return CFunctionType - -if _os.name == "nt": - from _ctypes import LoadLibrary as _dlopen - from _ctypes import FUNCFLAG_STDCALL as _FUNCFLAG_STDCALL - - _win_functype_cache = {} - def WINFUNCTYPE(restype, *argtypes, **kw): - # docstring set later (very similar to CFUNCTYPE.__doc__) - flags = _FUNCFLAG_STDCALL - if kw.pop("use_errno", False): - flags |= _FUNCFLAG_USE_ERRNO - if kw.pop("use_last_error", False): - flags |= _FUNCFLAG_USE_LASTERROR - if kw: - raise ValueError("unexpected keyword argument(s) %s" % kw.keys()) - try: - return _win_functype_cache[(restype, argtypes, flags)] - except KeyError: - class WinFunctionType(_CFuncPtr): - _argtypes_ = argtypes - _restype_ = restype - _flags_ = flags - _win_functype_cache[(restype, argtypes, flags)] = WinFunctionType - return WinFunctionType - if WINFUNCTYPE.__doc__: - WINFUNCTYPE.__doc__ = CFUNCTYPE.__doc__.replace("CFUNCTYPE", "WINFUNCTYPE") - -elif _os.name == "posix": - from _ctypes import dlopen as _dlopen - -from _ctypes import sizeof, byref, addressof, alignment, resize -from _ctypes import get_errno, set_errno -from _ctypes import _SimpleCData - -def _check_size(typ, typecode=None): - # Check if sizeof(ctypes_type) against struct.calcsize. This - # should protect somewhat against a misconfigured libffi. - from struct import calcsize - if typecode is None: - # Most _type_ codes are the same as used in struct - typecode = typ._type_ - actual, required = sizeof(typ), calcsize(typecode) - if actual != required: - raise SystemError("sizeof(%s) wrong: %d instead of %d" % \ - (typ, actual, required)) - -class py_object(_SimpleCData): - _type_ = "O" - def __repr__(self): - try: - return super().__repr__() - except ValueError: - return "%s()" % type(self).__name__ -_check_size(py_object, "P") - -class c_short(_SimpleCData): - _type_ = "h" -_check_size(c_short) - -class c_ushort(_SimpleCData): - _type_ = "H" -_check_size(c_ushort) - -class c_long(_SimpleCData): - _type_ = "l" -_check_size(c_long) - -class c_ulong(_SimpleCData): - _type_ = "L" -_check_size(c_ulong) - -if _calcsize("i") == _calcsize("l"): - # if int and long have the same size, make c_int an alias for c_long - c_int = c_long - c_uint = c_ulong -else: - class c_int(_SimpleCData): - _type_ = "i" - _check_size(c_int) - - class c_uint(_SimpleCData): - _type_ = "I" - _check_size(c_uint) - -class c_float(_SimpleCData): - _type_ = "f" -_check_size(c_float) - -class c_double(_SimpleCData): - _type_ = "d" -_check_size(c_double) - -class c_longdouble(_SimpleCData): - _type_ = "g" -if sizeof(c_longdouble) == sizeof(c_double): - c_longdouble = c_double - -if _calcsize("l") == _calcsize("q"): - # if long and long long have the same size, make c_longlong an alias for c_long - c_longlong = c_long - c_ulonglong = c_ulong -else: - class c_longlong(_SimpleCData): - _type_ = "q" - _check_size(c_longlong) - - class c_ulonglong(_SimpleCData): - _type_ = "Q" - ## def from_param(cls, val): - ## return ('d', float(val), val) - ## from_param = classmethod(from_param) - _check_size(c_ulonglong) - -class c_ubyte(_SimpleCData): - _type_ = "B" -c_ubyte.__ctype_le__ = c_ubyte.__ctype_be__ = c_ubyte -# backward compatibility: -##c_uchar = c_ubyte -_check_size(c_ubyte) - -class c_byte(_SimpleCData): - _type_ = "b" -c_byte.__ctype_le__ = c_byte.__ctype_be__ = c_byte -_check_size(c_byte) - -class c_char(_SimpleCData): - _type_ = "c" -c_char.__ctype_le__ = c_char.__ctype_be__ = c_char -_check_size(c_char) - -class c_char_p(_SimpleCData): - _type_ = "z" - def __repr__(self): - return "%s(%s)" % (self.__class__.__name__, c_void_p.from_buffer(self).value) -_check_size(c_char_p, "P") - -class c_void_p(_SimpleCData): - _type_ = "P" -c_voidp = c_void_p # backwards compatibility (to a bug) -_check_size(c_void_p) - -class c_bool(_SimpleCData): - _type_ = "?" - -from _ctypes import POINTER, pointer, _pointer_type_cache - -class c_wchar_p(_SimpleCData): - _type_ = "Z" - def __repr__(self): - return "%s(%s)" % (self.__class__.__name__, c_void_p.from_buffer(self).value) - -class c_wchar(_SimpleCData): - _type_ = "u" - -def _reset_cache(): - _pointer_type_cache.clear() - _c_functype_cache.clear() - if _os.name == "nt": - _win_functype_cache.clear() - # _SimpleCData.c_wchar_p_from_param - POINTER(c_wchar).from_param = c_wchar_p.from_param - # _SimpleCData.c_char_p_from_param - POINTER(c_char).from_param = c_char_p.from_param - _pointer_type_cache[None] = c_void_p - -def create_unicode_buffer(init, size=None): - """create_unicode_buffer(aString) -> character array - create_unicode_buffer(anInteger) -> character array - create_unicode_buffer(aString, anInteger) -> character array - """ - if isinstance(init, str): - if size is None: - if sizeof(c_wchar) == 2: - # UTF-16 requires a surrogate pair (2 wchar_t) for non-BMP - # characters (outside [U+0000; U+FFFF] range). +1 for trailing - # NUL character. - size = sum(2 if ord(c) > 0xFFFF else 1 for c in init) + 1 - else: - # 32-bit wchar_t (1 wchar_t per Unicode character). +1 for - # trailing NUL character. - size = len(init) + 1 - _sys.audit("ctypes.create_unicode_buffer", init, size) - buftype = c_wchar * size - buf = buftype() - buf.value = init - return buf - elif isinstance(init, int): - _sys.audit("ctypes.create_unicode_buffer", None, init) - buftype = c_wchar * init - buf = buftype() - return buf - raise TypeError(init) - - -# XXX Deprecated -def SetPointerType(pointer, cls): - if _pointer_type_cache.get(cls, None) is not None: - raise RuntimeError("This type already exists in the cache") - if id(pointer) not in _pointer_type_cache: - raise RuntimeError("What's this???") - pointer.set_type(cls) - _pointer_type_cache[cls] = pointer - del _pointer_type_cache[id(pointer)] - -# XXX Deprecated -def ARRAY(typ, len): - return typ * len - -################################################################ - - -class CDLL(object): - """An instance of this class represents a loaded dll/shared - library, exporting functions using the standard C calling - convention (named 'cdecl' on Windows). - - The exported functions can be accessed as attributes, or by - indexing with the function name. Examples: - - .qsort -> callable object - ['qsort'] -> callable object - - Calling the functions releases the Python GIL during the call and - reacquires it afterwards. - """ - _func_flags_ = _FUNCFLAG_CDECL - _func_restype_ = c_int - # default values for repr - _name = '' - _handle = 0 - _FuncPtr = None - - def __init__(self, name, mode=DEFAULT_MODE, handle=None, - use_errno=False, - use_last_error=False, - winmode=None): - self._name = name - flags = self._func_flags_ - if use_errno: - flags |= _FUNCFLAG_USE_ERRNO - if use_last_error: - flags |= _FUNCFLAG_USE_LASTERROR - if _sys.platform.startswith("aix"): - """When the name contains ".a(" and ends with ")", - e.g., "libFOO.a(libFOO.so)" - this is taken to be an - archive(member) syntax for dlopen(), and the mode is adjusted. - Otherwise, name is presented to dlopen() as a file argument. - """ - if name and name.endswith(")") and ".a(" in name: - mode |= ( _os.RTLD_MEMBER | _os.RTLD_NOW ) - if _os.name == "nt": - if winmode is not None: - mode = winmode - else: - import nt - mode = 0x00001000 - if '/' in name or '\\' in name: - self._name = nt._getfullpathname(self._name) - mode |= nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR - - class _FuncPtr(_CFuncPtr): - _flags_ = flags - _restype_ = self._func_restype_ - self._FuncPtr = _FuncPtr - - if handle is None: - self._handle = _dlopen(self._name, mode) - else: - self._handle = handle - - def __repr__(self): - return "<%s '%s', handle %x at %#x>" % \ - (self.__class__.__name__, self._name, - (self._handle & (_sys.maxsize*2 + 1)), - id(self) & (_sys.maxsize*2 + 1)) - - def __getattr__(self, name): - if name.startswith('__') and name.endswith('__'): - raise AttributeError(name) - func = self.__getitem__(name) - setattr(self, name, func) - return func - - def __getitem__(self, name_or_ordinal): - func = self._FuncPtr((name_or_ordinal, self)) - if not isinstance(name_or_ordinal, int): - func.__name__ = name_or_ordinal - return func - -class PyDLL(CDLL): - """This class represents the Python library itself. It allows - accessing Python API functions. The GIL is not released, and - Python exceptions are handled correctly. - """ - _func_flags_ = _FUNCFLAG_CDECL | _FUNCFLAG_PYTHONAPI - -if _os.name == "nt": - - class WinDLL(CDLL): - """This class represents a dll exporting functions using the - Windows stdcall calling convention. - """ - _func_flags_ = _FUNCFLAG_STDCALL - - # XXX Hm, what about HRESULT as normal parameter? - # Mustn't it derive from c_long then? - from _ctypes import _check_HRESULT, _SimpleCData - class HRESULT(_SimpleCData): - _type_ = "l" - # _check_retval_ is called with the function's result when it - # is used as restype. It checks for the FAILED bit, and - # raises an OSError if it is set. - # - # The _check_retval_ method is implemented in C, so that the - # method definition itself is not included in the traceback - # when it raises an error - that is what we want (and Python - # doesn't have a way to raise an exception in the caller's - # frame). - _check_retval_ = _check_HRESULT - - class OleDLL(CDLL): - """This class represents a dll exporting functions using the - Windows stdcall calling convention, and returning HRESULT. - HRESULT error values are automatically raised as OSError - exceptions. - """ - _func_flags_ = _FUNCFLAG_STDCALL - _func_restype_ = HRESULT - -class LibraryLoader(object): - def __init__(self, dlltype): - self._dlltype = dlltype - - def __getattr__(self, name): - if name[0] == '_': - raise AttributeError(name) - dll = self._dlltype(name) - setattr(self, name, dll) - return dll - - def __getitem__(self, name): - return getattr(self, name) - - def LoadLibrary(self, name): - return self._dlltype(name) - - # __class_getitem__ = classmethod(_types.GenericAlias) - -cdll = LibraryLoader(CDLL) -pydll = LibraryLoader(PyDLL) - -if _os.name == "nt": - pythonapi = PyDLL("python dll", None, _sys.dllhandle) -elif _sys.platform == "cygwin": - pythonapi = PyDLL("libpython%d.%d.dll" % _sys.version_info[:2]) -else: - pythonapi = PyDLL(None) - - -if _os.name == "nt": - windll = LibraryLoader(WinDLL) - oledll = LibraryLoader(OleDLL) - - GetLastError = windll.kernel32.GetLastError - from _ctypes import get_last_error, set_last_error - - def WinError(code=None, descr=None): - if code is None: - code = GetLastError() - if descr is None: - descr = FormatError(code).strip() - return OSError(None, descr, None, code) - -if sizeof(c_uint) == sizeof(c_void_p): - c_size_t = c_uint - c_ssize_t = c_int -elif sizeof(c_ulong) == sizeof(c_void_p): - c_size_t = c_ulong - c_ssize_t = c_long -elif sizeof(c_ulonglong) == sizeof(c_void_p): - c_size_t = c_ulonglong - c_ssize_t = c_longlong - -# functions - -from _ctypes import _memmove_addr, _memset_addr, _string_at_addr, _cast_addr - -## void *memmove(void *, const void *, size_t); -memmove = CFUNCTYPE(c_void_p, c_void_p, c_void_p, c_size_t)(_memmove_addr) - -## void *memset(void *, int, size_t) -memset = CFUNCTYPE(c_void_p, c_void_p, c_int, c_size_t)(_memset_addr) - -def PYFUNCTYPE(restype, *argtypes): - class CFunctionType(_CFuncPtr): - _argtypes_ = argtypes - _restype_ = restype - _flags_ = _FUNCFLAG_CDECL | _FUNCFLAG_PYTHONAPI - return CFunctionType - -_cast = PYFUNCTYPE(py_object, c_void_p, py_object, py_object)(_cast_addr) -def cast(obj, typ): - return _cast(obj, obj, typ) - -_string_at = PYFUNCTYPE(py_object, c_void_p, c_int)(_string_at_addr) -def string_at(ptr, size=-1): - """string_at(addr[, size]) -> string - - Return the string at addr.""" - return _string_at(ptr, size) - -try: - from _ctypes import _wstring_at_addr -except ImportError: - pass -else: - _wstring_at = PYFUNCTYPE(py_object, c_void_p, c_int)(_wstring_at_addr) - def wstring_at(ptr, size=-1): - """wstring_at(addr[, size]) -> string - - Return the string at addr.""" - return _wstring_at(ptr, size) - - -if _os.name == "nt": # COM stuff - def DllGetClassObject(rclsid, riid, ppv): - try: - ccom = __import__("comtypes.server.inprocserver", globals(), locals(), ['*']) - except ImportError: - return -2147221231 # CLASS_E_CLASSNOTAVAILABLE - else: - return ccom.DllGetClassObject(rclsid, riid, ppv) - - def DllCanUnloadNow(): - try: - ccom = __import__("comtypes.server.inprocserver", globals(), locals(), ['*']) - except ImportError: - return 0 # S_OK - return ccom.DllCanUnloadNow() - -from ctypes._endian import BigEndianStructure, LittleEndianStructure - -# Fill in specifically-sized types -c_int8 = c_byte -c_uint8 = c_ubyte -for kind in [c_short, c_int, c_long, c_longlong]: - if sizeof(kind) == 2: c_int16 = kind - elif sizeof(kind) == 4: c_int32 = kind - elif sizeof(kind) == 8: c_int64 = kind -for kind in [c_ushort, c_uint, c_ulong, c_ulonglong]: - if sizeof(kind) == 2: c_uint16 = kind - elif sizeof(kind) == 4: c_uint32 = kind - elif sizeof(kind) == 8: c_uint64 = kind -del(kind) - -_reset_cache() - -print( "type of SetClipboardData: %s" % str(windll.user32.SetClipboardData)) -print( "type of RegisterClipboardFormat: %s" % str(windll.user32.RegisterClipboardFormatA)) -CF_MathML = windll.user32.RegisterClipboardFormatW("MathML") -CF_MathML_Presentation = windll.user32.RegisterClipboardFormatW("MathML Presentation") -print("MathCAT registering data formats: CF_MathML %x, CF_MathML_Presentation %x" % - (CF_MathML, CF_MathML_Presentation)) diff --git a/v0.7.2-rc.3.json b/v0.7.2-rc.3.json new file mode 100644 index 00000000..6bc9502e --- /dev/null +++ b/v0.7.2-rc.3.json @@ -0,0 +1,29 @@ +{ + "addonId": "MathCAT", + "displayName": "MathCAT: speech and braille from MathML", + "URL": "", + "description": "MathCAT is a replacement for MathPlayer which has been discontinued.\n It provides speech and braille support, and also supports MathPlayer's three modes of navigation.\n The speech quality is not quite as good as MathPlayer's speech yet,\n but the braille support is much better and includes support for Nemeth, UEB Technical, CMU (Spanish/Portuguese),\n and Vietnamese braille code standards. Translations to Chinese (Traditional), Indonesian, Spanish, and Vietnamese exist\n and other translations are in progress.", + "sha256": "4639ac555798a60ce021ab7b31657caaa9e16ad4c0b234f71c10dc20589f83a1", + "homepage": "https://nsoiffer.github.io/MathCAT/", + "addonVersionName": "v0.7.2-rc.3", + "addonVersionNumber": { + "major": 0, + "minor": 0, + "patch": 0 + }, + "minNVDAVersion": { + "major": 2025, + "minor": 1, + "patch": 0 + }, + "lastTestedVersion": { + "major": 2025, + "minor": 3, + "patch": 0 + }, + "channel": "dev", + "publisher": "", + "sourceURL": "https://github.com/NSoiffer/MathCATForPython", + "license": "MIT and GPL 2", + "licenseURL": "https://raw.githubusercontent.com/NSoiffer/MathCAT/main/LICENSE" +} \ No newline at end of file diff --git a/v0.7.2-rc.4.json b/v0.7.2-rc.4.json new file mode 100644 index 00000000..a1ffe6f6 --- /dev/null +++ b/v0.7.2-rc.4.json @@ -0,0 +1,29 @@ +{ + "addonId": "MathCAT", + "displayName": "MathCAT: speech and braille from MathML", + "URL": "", + "description": "MathCAT is a replacement for MathPlayer which has been discontinued.\n It provides speech and braille support, and also supports MathPlayer's three modes of navigation.\n The speech quality is not quite as good as MathPlayer's speech yet,\n but the braille support is much better and includes support for Nemeth, UEB Technical, CMU (Spanish/Portuguese),\n and Vietnamese braille code standards. Translations to Chinese (Traditional), Indonesian, Spanish, and Vietnamese exist\n and other translations are in progress.", + "sha256": "9f26aacf95923e9710a4e09dbc509328edb312a3940326f23079e39f4bc0a22b", + "homepage": "https://nsoiffer.github.io/MathCAT/", + "addonVersionName": "v0.7.2-rc.4", + "addonVersionNumber": { + "major": 0, + "minor": 0, + "patch": 0 + }, + "minNVDAVersion": { + "major": 2025, + "minor": 1, + "patch": 0 + }, + "lastTestedVersion": { + "major": 2025, + "minor": 3, + "patch": 0 + }, + "channel": "dev", + "publisher": "", + "sourceURL": "https://github.com/NSoiffer/MathCATForPython", + "license": "MIT and GPL 2", + "licenseURL": "https://raw.githubusercontent.com/NSoiffer/MathCAT/main/LICENSE" +} \ No newline at end of file diff --git a/v0.7.4-beta1.json b/v0.7.4-beta1.json new file mode 100644 index 00000000..6c050ab5 --- /dev/null +++ b/v0.7.4-beta1.json @@ -0,0 +1,29 @@ +{ + "addonId": "MathCAT", + "displayName": "MathCAT: speech and braille from MathML", + "URL": "", + "description": "MathCAT is a replacement for MathPlayer which has been discontinued.\n It provides speech and braille support, and also supports MathPlayer's three modes of navigation.\n The speech quality is not quite as good as MathPlayer's speech yet,\n but the braille support is much better and includes support for Nemeth, UEB Technical, CMU (Spanish/Portuguese),\n and Vietnamese braille code standards. Translations to Chinese (Traditional), Indonesian, Spanish, and Vietnamese exist\n and other translations are in progress.", + "sha256": "8b16a3e386630f57ff5a8b50c95ca6fa5858f78e5212b1d1acd341e89c5e7625", + "homepage": "https://nsoiffer.github.io/MathCAT/", + "addonVersionName": "v0.7.4-beta1", + "addonVersionNumber": { + "major": 0, + "minor": 0, + "patch": 0 + }, + "minNVDAVersion": { + "major": 2025, + "minor": 1, + "patch": 0 + }, + "lastTestedVersion": { + "major": 2025, + "minor": 3, + "patch": 0 + }, + "channel": "dev", + "publisher": "", + "sourceURL": "https://github.com/NSoiffer/MathCATForPython", + "license": "MIT and GPL 2", + "licenseURL": "https://raw.githubusercontent.com/NSoiffer/MathCAT/main/LICENSE" +} \ No newline at end of file