pdfmath 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pdfmath-0.1.0/LICENSE +21 -0
- pdfmath-0.1.0/NOTICE +20 -0
- pdfmath-0.1.0/PKG-INFO +425 -0
- pdfmath-0.1.0/README.md +389 -0
- pdfmath-0.1.0/pdfmath/__init__.py +0 -0
- pdfmath-0.1.0/pdfmath/asciimath/__init__.py +5 -0
- pdfmath-0.1.0/pdfmath/asciimath/serializer.py +330 -0
- pdfmath-0.1.0/pdfmath/cli/__init__.py +0 -0
- pdfmath-0.1.0/pdfmath/cli/main.py +752 -0
- pdfmath-0.1.0/pdfmath/corpus/__init__.py +0 -0
- pdfmath-0.1.0/pdfmath/corpus/arxiv.py +194 -0
- pdfmath-0.1.0/pdfmath/corpus/compile.py +114 -0
- pdfmath-0.1.0/pdfmath/corpus/generate.py +202 -0
- pdfmath-0.1.0/pdfmath/corpus/grammar.py +476 -0
- pdfmath-0.1.0/pdfmath/corpus/harness.py +318 -0
- pdfmath-0.1.0/pdfmath/corpus/milestone.py +83 -0
- pdfmath-0.1.0/pdfmath/corpus/shrink.py +126 -0
- pdfmath-0.1.0/pdfmath/corpus/strategies.py +71 -0
- pdfmath-0.1.0/pdfmath/debug/__init__.py +0 -0
- pdfmath-0.1.0/pdfmath/debug/explain.py +82 -0
- pdfmath-0.1.0/pdfmath/debug/svg.py +238 -0
- pdfmath-0.1.0/pdfmath/detection/__init__.py +35 -0
- pdfmath-0.1.0/pdfmath/detection/equations.py +344 -0
- pdfmath-0.1.0/pdfmath/detection/inline.py +410 -0
- pdfmath-0.1.0/pdfmath/eval/__init__.py +0 -0
- pdfmath-0.1.0/pdfmath/eval/arxiv_eval.py +172 -0
- pdfmath-0.1.0/pdfmath/eval/asciimath_roundtrip.py +160 -0
- pdfmath-0.1.0/pdfmath/eval/latexml.py +64 -0
- pdfmath-0.1.0/pdfmath/eval/mathml_compare.py +216 -0
- pdfmath-0.1.0/pdfmath/eval/omml_roundtrip.py +193 -0
- pdfmath-0.1.0/pdfmath/eval/roundtrip.py +203 -0
- pdfmath-0.1.0/pdfmath/extraction/__init__.py +0 -0
- pdfmath-0.1.0/pdfmath/extraction/model.py +227 -0
- pdfmath-0.1.0/pdfmath/extraction/pdfminer_backend.py +242 -0
- pdfmath-0.1.0/pdfmath/extraction/symbolscraper_adapter.py +118 -0
- pdfmath-0.1.0/pdfmath/fonts/__init__.py +0 -0
- pdfmath-0.1.0/pdfmath/fonts/data/__init__.py +0 -0
- pdfmath-0.1.0/pdfmath/fonts/data/extra_symbols.py +211 -0
- pdfmath-0.1.0/pdfmath/fonts/data/tex_encodings.py +1629 -0
- pdfmath-0.1.0/pdfmath/fonts/mathparams.py +180 -0
- pdfmath-0.1.0/pdfmath/fonts/metrics.py +129 -0
- pdfmath-0.1.0/pdfmath/fonts/normalize.py +191 -0
- pdfmath-0.1.0/pdfmath/fonts/symbols.py +410 -0
- pdfmath-0.1.0/pdfmath/fonts/tfm.py +251 -0
- pdfmath-0.1.0/pdfmath/geometry/__init__.py +0 -0
- pdfmath-0.1.0/pdfmath/geometry/bbox.py +77 -0
- pdfmath-0.1.0/pdfmath/geometry/index.py +169 -0
- pdfmath-0.1.0/pdfmath/latex/__init__.py +0 -0
- pdfmath-0.1.0/pdfmath/latex/data/__init__.py +0 -0
- pdfmath-0.1.0/pdfmath/latex/data/tex_commands.py +1034 -0
- pdfmath-0.1.0/pdfmath/latex/serializer.py +331 -0
- pdfmath-0.1.0/pdfmath/mathml/__init__.py +0 -0
- pdfmath-0.1.0/pdfmath/mathml/lgeval.py +184 -0
- pdfmath-0.1.0/pdfmath/mathml/serializer.py +250 -0
- pdfmath-0.1.0/pdfmath/omml/__init__.py +5 -0
- pdfmath-0.1.0/pdfmath/omml/serializer.py +345 -0
- pdfmath-0.1.0/pdfmath/parse/__init__.py +0 -0
- pdfmath-0.1.0/pdfmath/parse/accents.py +140 -0
- pdfmath-0.1.0/pdfmath/parse/atoms.py +82 -0
- pdfmath-0.1.0/pdfmath/parse/axis.py +79 -0
- pdfmath-0.1.0/pdfmath/parse/blocks.py +122 -0
- pdfmath-0.1.0/pdfmath/parse/context.py +193 -0
- pdfmath-0.1.0/pdfmath/parse/delimiters.py +192 -0
- pdfmath-0.1.0/pdfmath/parse/driver.py +425 -0
- pdfmath-0.1.0/pdfmath/parse/fractions.py +274 -0
- pdfmath-0.1.0/pdfmath/parse/matrices.py +247 -0
- pdfmath-0.1.0/pdfmath/parse/operators.py +190 -0
- pdfmath-0.1.0/pdfmath/parse/radicals.py +154 -0
- pdfmath-0.1.0/pdfmath/parse/rows.py +293 -0
- pdfmath-0.1.0/pdfmath/parse/scripts.py +415 -0
- pdfmath-0.1.0/pdfmath/parse/spacing.py +137 -0
- pdfmath-0.1.0/pdfmath/parse/style.py +75 -0
- pdfmath-0.1.0/pdfmath/parse/units.py +290 -0
- pdfmath-0.1.0/pdfmath/speech/__init__.py +7 -0
- pdfmath-0.1.0/pdfmath/speech/engine.py +192 -0
- pdfmath-0.1.0/pdfmath/speech/speak.js +35 -0
- pdfmath-0.1.0/pdfmath/tree/__init__.py +0 -0
- pdfmath-0.1.0/pdfmath/tree/nodes.py +374 -0
- pdfmath-0.1.0/pdfmath/units.py +38 -0
- pdfmath-0.1.0/pdfmath/wordperfect/__init__.py +5 -0
- pdfmath-0.1.0/pdfmath/wordperfect/serializer.py +301 -0
- pdfmath-0.1.0/pdfmath.egg-info/PKG-INFO +425 -0
- pdfmath-0.1.0/pdfmath.egg-info/SOURCES.txt +88 -0
- pdfmath-0.1.0/pdfmath.egg-info/dependency_links.txt +1 -0
- pdfmath-0.1.0/pdfmath.egg-info/entry_points.txt +2 -0
- pdfmath-0.1.0/pdfmath.egg-info/requires.txt +12 -0
- pdfmath-0.1.0/pdfmath.egg-info/top_level.txt +1 -0
- pdfmath-0.1.0/pyproject.toml +60 -0
- pdfmath-0.1.0/setup.cfg +4 -0
- pdfmath-0.1.0/tests/test_readme.py +126 -0
pdfmath-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Adam DePrince
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pdfmath-0.1.0/NOTICE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
pdfmath
|
|
2
|
+
Copyright 2026 Adam DePrince
|
|
3
|
+
|
|
4
|
+
Attribution for the data this project generates from other people's work. None
|
|
5
|
+
of it is code; it is all factual tables, and the generators are in tools/ so
|
|
6
|
+
they can be re-run.
|
|
7
|
+
|
|
8
|
+
* Glyph-name and encoding tables generated from the Adobe Font Metrics files
|
|
9
|
+
distributed with TeX Live for the Computer Modern and AMS fonts.
|
|
10
|
+
* Math-symbol classifications composed from LaTeXML's DefMath declarations
|
|
11
|
+
(LaTeXML is a work of the US National Institute of Standards and Technology
|
|
12
|
+
and is not subject to copyright in the United States) and from LaTeX's own
|
|
13
|
+
\DeclareMathSymbol tables.
|
|
14
|
+
* WordPerfect 5.1 equation-language vocabulary checked against the equation
|
|
15
|
+
parser in the wp51 project.
|
|
16
|
+
|
|
17
|
+
pdfmath speaks mathematics through MathJax's speech-rule-engine
|
|
18
|
+
(https://github.com/Speech-Rule-Engine/speech-rule-engine), which is licensed
|
|
19
|
+
under the Apache License 2.0 and is installed separately; it is invoked as a
|
|
20
|
+
subprocess and no part of it is included here.
|
pdfmath-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pdfmath
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A decompiler for TeX mathematics: born-digital PDF -> layout tree -> Presentation MathML
|
|
5
|
+
Author-email: Adam DePrince <adam.deprince@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/adamdeprince/pdfmath
|
|
8
|
+
Project-URL: Repository, https://github.com/adamdeprince/pdfmath
|
|
9
|
+
Project-URL: Issues, https://github.com/adamdeprince/pdfmath/issues
|
|
10
|
+
Keywords: tex,latex,pdf,mathml,mathematics,accessibility,decompiler,asciimath,omml,speech
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
20
|
+
Classifier: Topic :: Text Processing :: Markup :: LaTeX
|
|
21
|
+
Classifier: Topic :: Adaptive Technologies
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
License-File: NOTICE
|
|
26
|
+
Requires-Dist: pdfminer.six>=20231228
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
29
|
+
Requires-Dist: hypothesis>=6; extra == "dev"
|
|
30
|
+
Provides-Extra: oracles
|
|
31
|
+
Requires-Dist: py-asciimath>=0.3; extra == "oracles"
|
|
32
|
+
Requires-Dist: pypandoc_binary>=1.13; extra == "oracles"
|
|
33
|
+
Provides-Extra: pdf
|
|
34
|
+
Requires-Dist: pikepdf>=8; extra == "pdf"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# pdfmath
|
|
38
|
+
|
|
39
|
+
A decompiler for TeX mathematics. It reads a born-digital PDF and gives you back the
|
|
40
|
+
equations — as MathML, speech, AsciiMath, LaTeX, Word's OMML, or WordPerfect 5.1.
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
old TeX-generated PDF
|
|
44
|
+
↓ positioned glyphs and rules
|
|
45
|
+
↓ inferred TeX boxes and relationships
|
|
46
|
+
↓ reconstructed math layout tree
|
|
47
|
+
MathML · speech · AsciiMath · LaTeX · OMML · WP5.1
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Older mathematical papers are full of PDFs that show correct mathematics and record none
|
|
51
|
+
of it: no MathML, no tagging, often not even a usable ToUnicode map. The usual answer is
|
|
52
|
+
to rasterise the equation and ask a model to guess the LaTeX back. This project takes the
|
|
53
|
+
opposite view: **a TeX-produced PDF is not a picture of an equation, it is the compiled
|
|
54
|
+
output of a deterministic layout program, and the program's rules are published.** So we
|
|
55
|
+
do not recognise the equation from its appearance — we recover the structure implied by
|
|
56
|
+
the layout, and we can say in points how well it fits.
|
|
57
|
+
|
|
58
|
+
This README is a tutorial. For how it works inside, see [docs/architecture.md]; for how
|
|
59
|
+
well it works, [docs/evaluation.md].
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Install
|
|
64
|
+
|
|
65
|
+
**Required:** Python 3.10 or newer. That is all you need to decompile a PDF.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
git clone <this repo> && cd math
|
|
69
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
70
|
+
pip install -e ".[dev]"
|
|
71
|
+
pdfmath --help
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Optional, each unlocking one thing:**
|
|
75
|
+
|
|
76
|
+
| You want | Install | Needed for |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| exact font metrics, the sample document | a TeX distribution (MacTeX, TeX Live) | TFM metrics, `benchmark`, `roundtrip` |
|
|
79
|
+
| speech | Node, then `cd tools/sre && npm install` | `pdfmath speak` |
|
|
80
|
+
| the serialiser oracle tests | `pip install -e ".[oracles]"` | AsciiMath and OMML round-trip tests |
|
|
81
|
+
| scoring against a paper's own source | `brew install latexml` | `pdfmath arxiv` |
|
|
82
|
+
|
|
83
|
+
Without TeX, extraction and every output format still work — the metrics come from the
|
|
84
|
+
PDF's own `/Widths` instead of the TFM files, so the residuals stop being meaningful but
|
|
85
|
+
the structure is still recovered.
|
|
86
|
+
|
|
87
|
+
Check the install:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
python -m pytest tests/unit -q # fast, no TeX needed
|
|
91
|
+
python -m pytest -q # everything, needs TeX
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Tutorial
|
|
97
|
+
|
|
98
|
+
### 1. Build the sample
|
|
99
|
+
|
|
100
|
+
The repository ships a small document to work on. Build it once:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
pdflatex -output-directory=examples examples/sample.tex
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
It has three displayed equations and one inline formula. If you would rather use your
|
|
107
|
+
own paper, any born-digital PDF from pdfTeX will do — just substitute it below.
|
|
108
|
+
|
|
109
|
+
### 2. Decompile it
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
$ pdfmath extract examples/sample.pdf --asciimath
|
|
113
|
+
E = (x_i^2)/(sqrt(y))
|
|
114
|
+
sum_(k = 1)^n (1)/(k^2) = (pi^2)/(6) - epsilon_n.
|
|
115
|
+
A = ((a,b),(c,d)), quad det A = a d - b c.
|
|
116
|
+
sqrt(a^2 + b^2)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
No page number, no coordinates. Three of those are displayed equations, found by their
|
|
120
|
+
shape; the fourth is inside a sentence, found by a different method entirely — see below.
|
|
121
|
+
Pass `--no-inline` for displays only.
|
|
122
|
+
|
|
123
|
+
Ask for MathML instead and you get the real target format:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
$ pdfmath extract examples/sample.pdf --bbox 286,612,326,641 --mathml
|
|
127
|
+
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML">
|
|
128
|
+
<mrow>
|
|
129
|
+
<mi>E</mi>
|
|
130
|
+
<mo>=</mo>
|
|
131
|
+
<mfrac>
|
|
132
|
+
<msubsup>
|
|
133
|
+
<mi>x</mi>
|
|
134
|
+
<mi>i</mi>
|
|
135
|
+
<mn>2</mn>
|
|
136
|
+
</msubsup>
|
|
137
|
+
<msqrt><mi>y</mi></msqrt>
|
|
138
|
+
</mfrac>
|
|
139
|
+
</mrow>
|
|
140
|
+
</math>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 3. Read it aloud
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
$ pdfmath speak examples/sample.pdf
|
|
147
|
+
E equals the fraction with numerator x sub i squared and denominator the square root of y
|
|
148
|
+
the sum from k equals 1 to n of the fraction with numerator 1 and denominator k squared equals the fraction with numerator pi squared and denominator 6 minus epsilon sub n period
|
|
149
|
+
A equals the 2 by 2 matrix Row 1: a b Row 2: c d comma determinant A equals a d minus b c period
|
|
150
|
+
the square root of a squared plus b squared
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The last line is the formula from inside the sentence. That is ClearSpeak, which reads the way a person would say it. MathSpeak is unambiguous
|
|
154
|
+
and reversible instead — what you want when checking someone else's algebra:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
$ pdfmath speak examples/sample.pdf --rules mathspeak --verbosity brief --bbox 286,612,326,641
|
|
158
|
+
upper E equals StartFrac x Sub i Sup 2 Base Over StartRoot y EndRoot EndFrac
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`--ssml` emits SSML instead of plain text, so a synthesiser pauses in the right places.
|
|
162
|
+
|
|
163
|
+
Speech is a bridge to MathJax's [speech-rule-engine][sre] (Apache-2.0) rather than rules
|
|
164
|
+
of our own — MathSpeak and ClearSpeak are specified and user-tested, and an approximation
|
|
165
|
+
would be worse in ways that are hard to notice mid-paper. It needs Node:
|
|
166
|
+
`cd tools/sre && npm install`.
|
|
167
|
+
|
|
168
|
+
Two things are changed before speaking, both so you do not hear something that is not on
|
|
169
|
+
the page. A glyph we could not name is written `□` in MathML, which reads aloud as "white
|
|
170
|
+
square" — a real operator, and indistinguishable from one we meant; it is announced as
|
|
171
|
+
unrecognised instead. And the measured inter-atom spacing is dropped, because `mspace` is
|
|
172
|
+
read aloud as "empty", so a `\quad` before an equation number became a spoken word.
|
|
173
|
+
|
|
174
|
+
### 4. The other formats
|
|
175
|
+
|
|
176
|
+
| Format | Flag | What it is for |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| Presentation MathML | `--mathml` | the primary target; `--provenance` adds glyph ids |
|
|
179
|
+
| Speech | `speak`, `--speech` | ClearSpeak or MathSpeak text, or SSML |
|
|
180
|
+
| AsciiMath | `--asciimath` | linear, and stays readable magnified |
|
|
181
|
+
| LaTeX | `--latex` | the round-trip oracle, and useful on its own |
|
|
182
|
+
| Office MathML | `--omml` | what a `.docx` stores, so Word can *edit* it |
|
|
183
|
+
| WordPerfect 5.1 | `--wpeq` | the 1989 equation editor's command language |
|
|
184
|
+
| JSON tree | `--tree` | every node with provenance, residuals, confidence |
|
|
185
|
+
| LgEval label graph | `--lg` | comparison against MathSeer/CROHME tooling |
|
|
186
|
+
|
|
187
|
+
Ask for one and it prints bare; ask for several and you get JSON.
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
$ pdfmath extract examples/sample.pdf --latex
|
|
191
|
+
E = \frac{x_{i}^{2}}{\sqrt{y}}
|
|
192
|
+
\sum\limits_{k = 1}^{n} \frac{1}{k^{2}} = \frac{\pi^{2}}{6} - \varepsilon_{n} .
|
|
193
|
+
A = \left( \begin{matrix} a & b \\ c & d \end{matrix} \right) , \mskip 39.006mu \mathrm{det} \mskip 2.988mu A = a d - b c .
|
|
194
|
+
\sqrt{a^{2} + b^{2}}
|
|
195
|
+
|
|
196
|
+
$ pdfmath extract examples/sample.pdf --wpeq
|
|
197
|
+
E = {x sub i sup 2} over {sqrt {y}}
|
|
198
|
+
sum from {k = 1} to n {1} over {k sup 2} = {pi sup 2} over {6} - epsilon sub n .
|
|
199
|
+
A = left ( matrix {a & b # c & d} right ) , ~ det ` A = a d - b c .
|
|
200
|
+
sqrt {a sup 2 + b sup 2}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
The LaTeX writer aims to recompile to the identical page rather than to look idiomatic,
|
|
204
|
+
which is why the spacing is explicit; it reports whatever it could not reproduce instead
|
|
205
|
+
of guessing.
|
|
206
|
+
|
|
207
|
+
### 5. When an equation is missed
|
|
208
|
+
|
|
209
|
+
The sample's last sentence also names a vector `$\mathbf{v}$`, and nothing finds it —
|
|
210
|
+
`\mathbf` and bold prose are the same font, so the distinction is not on the page to be
|
|
211
|
+
found. Give it the box directly:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
$ pdfmath extract examples/sample.pdf --bbox 178,446,186,456 --style text --asciimath
|
|
215
|
+
v
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`--bbox` is `x0,y0,x1,y1` in TeX points from the bottom-left of the page (`--bbox-bp` for
|
|
219
|
+
PDF big points), and `--style` tells the parser which math style to expect, since that
|
|
220
|
+
changes every Appendix G prediction. This is also the workaround when detection misses a
|
|
221
|
+
display — numbered equations are one known case, because the tag pushed out by `\hfill`
|
|
222
|
+
widens the line past what the detector expects.
|
|
223
|
+
|
|
224
|
+
#### How inline detection works
|
|
225
|
+
|
|
226
|
+
An inline formula has no shape to find it by: it sits in the middle of a sentence, on the
|
|
227
|
+
same baseline, in the same paragraph. What it has instead is TeX's own bookkeeping.
|
|
228
|
+
|
|
229
|
+
* **The font.** TeX sets prose from the roman font and mathematics from cmmi, cmsy and
|
|
230
|
+
cmex. There is no way to type a cmmi glyph outside mathematics, so every one is a seed.
|
|
231
|
+
This finds more than variables: the comma in `$[0,1]$` comes from cmmi, and it is the
|
|
232
|
+
only non-roman character in that formula.
|
|
233
|
+
* **The gap, which calibrates itself.** Interword glue stretches so a line can be
|
|
234
|
+
justified; math glue does not. So the word space is a property of *this line*,
|
|
235
|
+
measurable from the line's own gaps — 6.4 mu on one line here, 6.1 on the next — and
|
|
236
|
+
every automatic math space is at most 5 mu. That is what separates `\log n` from two
|
|
237
|
+
words, with no threshold in points that would be wrong at another size.
|
|
238
|
+
* **The character class.** Roman characters do appear in formulas — digits, `+`, `=`,
|
|
239
|
+
parentheses, capital Greek — so a seed grows outward through those and stops at a roman
|
|
240
|
+
*letter*, which is prose. Operator names (`log`, `sin`, `max`) are the exception and are
|
|
241
|
+
recognised as a set.
|
|
242
|
+
|
|
243
|
+
What it cannot do is find a formula containing no math-font glyph at all: `$\mathbf{v}$`
|
|
244
|
+
is cmbx and so is bold prose; `$2$` is a roman digit and so is a page number. Those are
|
|
245
|
+
undecidable rather than hard — TeX threw the distinction away — and they are left alone.
|
|
246
|
+
|
|
247
|
+
**Which is why this is on by default.** A missed inline formula is not silence: its
|
|
248
|
+
characters are still there and are still read. And the formulas this method misses are
|
|
249
|
+
exactly the ones made entirely of ordinary characters, so reading them as characters is
|
|
250
|
+
already right — `$\mathbf{v}$` says "v", which is what it is. The asymmetry runs the
|
|
251
|
+
other way from most detection problems: finding one is a large gain, missing one costs
|
|
252
|
+
nothing, and only a false positive would do harm. `--no-inline` turns it off.
|
|
253
|
+
|
|
254
|
+
### 6. Ask why
|
|
255
|
+
|
|
256
|
+
Every inference records its evidence in the units of the rule that produced it:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
$ pdfmath explain examples/sample.pdf --page 1 --bbox 286,612,326,641
|
|
260
|
+
node 7 Fraction [fraction] confidence 0.9990
|
|
261
|
+
bbox 310.48,611.83 .. 325.37,639.57 (pt)
|
|
262
|
+
glyphs [26, 27, 28, 29, 30]
|
|
263
|
+
rules [0, 1]
|
|
264
|
+
children [4, 6]
|
|
265
|
+
evidence:
|
|
266
|
+
rule_width_pt 14.88660
|
|
267
|
+
rule_thickness_pt 0.43763
|
|
268
|
+
expected_default_rule_thickness_pt 0.43799
|
|
269
|
+
thickness_residual_pt -0.00035
|
|
270
|
+
n_above 3
|
|
271
|
+
n_below 3
|
|
272
|
+
style_fit display
|
|
273
|
+
residual_pt +0.00039
|
|
274
|
+
numerator_residual_pt +0.00018
|
|
275
|
+
denominator_residual_pt -0.00039
|
|
276
|
+
axis_height_pt 2.73750
|
|
277
|
+
consistent_with_rule_15 True
|
|
278
|
+
... (and the measured inter-atom spacing either
|
|
279
|
+
side, in mu, with the atom classes it implies)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
A residual of 0.0004 pt is the fraction bar landing exactly where Appendix G rule 15 says
|
|
283
|
+
it must. That is the difference between a decompiler and a recogniser: the number is a
|
|
284
|
+
measurement, not a score.
|
|
285
|
+
|
|
286
|
+
### 7. Triage a document
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
$ pdfmath survey examples/sample.pdf
|
|
290
|
+
examples/sample.pdf
|
|
291
|
+
equations 3
|
|
292
|
+
glyph recovery 100.000% (43/43)
|
|
293
|
+
Unknown nodes 0
|
|
294
|
+
inferences below 0.90 0
|
|
295
|
+
|
|
296
|
+
page 1 bbox [223.17, 484.9, 389.37, 511.18] confidence 0.9800 glyphs 20
|
|
297
|
+
page 1 bbox [263.06, 546.47, 349.47, 577.01] confidence 0.9900 glyphs 16
|
|
298
|
+
page 1 bbox [285.97, 611.83, 325.37, 639.57] confidence 0.9990 glyphs 7
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
`--floor 0.9` lists everything below a confidence you choose. A glyph nobody can explain
|
|
302
|
+
becomes an `Unknown` carrying its original geometry — nothing is silently dropped, and
|
|
303
|
+
there is a test for that.
|
|
304
|
+
|
|
305
|
+
### 8. See what the parser saw
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
pdfmath debug-svg examples/sample.pdf --page 1 --html -o page1.html
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Glyph boxes, baselines, extracted rules and the recovered tree, in one SVG with
|
|
312
|
+
toggleable layers. `pdfmath dump` gives the same thing as JSON, one record per glyph,
|
|
313
|
+
before any parsing happens.
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Checking the output against something else
|
|
318
|
+
|
|
319
|
+
The interesting question is not whether the tool is confident but whether it is right, so
|
|
320
|
+
every claim has a check that does not run our code.
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
pdfmath roundtrip examples/sample.pdf # recompile the LaTeX, compare pages glyph by glyph
|
|
324
|
+
pdfmath benchmark --suite all --n 200 # generate a corpus with known answers, score it
|
|
325
|
+
pdfmath arxiv math/0211159v1 # score against the paper's own source, via LaTeXML
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
`roundtrip` is the strongest of the three: it takes what we recovered, compiles it with
|
|
329
|
+
pdfTeX, and compares the resulting page against the original glyph by glyph. `exact`
|
|
330
|
+
means every glyph is the same character from the same font within 0.01 pt.
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
$ pdfmath roundtrip examples/sample.pdf
|
|
334
|
+
examples/sample.pdf
|
|
335
|
+
equations 3
|
|
336
|
+
exact 2 66.7%
|
|
337
|
+
shifted 1 33.3%
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
It then prints each equation that was not exact, with the LaTeX it recovered and the
|
|
341
|
+
glyphs that moved, so a `shifted` verdict points at the box width we got wrong rather
|
|
342
|
+
than just failing. The command exits non-zero unless every equation is exact, which makes
|
|
343
|
+
it usable as a CI gate.
|
|
344
|
+
|
|
345
|
+
The serialisers are checked the same way, each against a reader that is not ours: MathML
|
|
346
|
+
against LaTeXML, AsciiMath against `py-asciimath`, OMML against pandoc's `docx` reader.
|
|
347
|
+
WordPerfect has no automated check yet — its vocabulary is taken from the equation parser
|
|
348
|
+
in the `wp51` project, but that exposes no command for a bare equation string — so it
|
|
349
|
+
rests on goldens, and the handful of commands still unconfirmed are flagged in the
|
|
350
|
+
output.
|
|
351
|
+
|
|
352
|
+
Numbers and method: [docs/evaluation.md].
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## How it works
|
|
357
|
+
|
|
358
|
+
Four stages, one file each to start reading from.
|
|
359
|
+
|
|
360
|
+
1. **Extraction** (`extraction/pdfminer_backend.py`) intercepts pdfminer's renderer and
|
|
361
|
+
records every glyph's character code, font, size and text matrix, plus every rule
|
|
362
|
+
drawn as a filled rectangle. Not a bounding box from a page image — the numbers the
|
|
363
|
+
typesetter used.
|
|
364
|
+
2. **Font knowledge** (`fonts/tfm.py`) reads the TFM files TeX itself used, giving
|
|
365
|
+
per-character widths, heights, depths and italic corrections, and the σ and ξ
|
|
366
|
+
parameters Appendix G is written in terms of.
|
|
367
|
+
3. **Parsing** (`parse/`) runs recognisers that each invert one published rule — rule 15
|
|
368
|
+
for fractions, 18a–f for scripts, 11 for radicals, 13 for large operators — and report
|
|
369
|
+
a residual in points rather than a threshold verdict. Anchors are claimed outermost
|
|
370
|
+
first so a nested construct cannot steal its parent's.
|
|
371
|
+
4. **Serialisation** (`mathml/`, `speech/`, `asciimath/`, `latex/`, `omml/`,
|
|
372
|
+
`wordperfect/`) walks the finished tree. Every node carries provenance back to the
|
|
373
|
+
glyphs and rules it came from; that is not optional.
|
|
374
|
+
|
|
375
|
+
The design decisions, the inverted rules with their closed forms, the thresholds that
|
|
376
|
+
survive and why, and the known limitations with their causes are all in
|
|
377
|
+
[docs/architecture.md].
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
## Status and scope
|
|
382
|
+
|
|
383
|
+
Supported and measured: pdfTeX output, Computer Modern and Latin Modern, AMS symbol
|
|
384
|
+
fonts, displayed equations, display and text style, fractions, scripts, radicals,
|
|
385
|
+
delimiters (including built-up cmex assemblies), large operators with limits, matrices,
|
|
386
|
+
accents, over/underlines, and the inter-atom spacing table.
|
|
387
|
+
|
|
388
|
+
Inline formulas are found by default, from the fonts TeX switched to and the glue it
|
|
389
|
+
inserted rather than from any shape on the page; `--no-inline` restricts to displays.
|
|
390
|
+
|
|
391
|
+
Two-column layouts are fine: the text column is measured from the page's own prose, so a
|
|
392
|
+
narrower one changes nothing.
|
|
393
|
+
|
|
394
|
+
Not yet: documents whose mathematics is not in a TeX math font (a paper set in Times with
|
|
395
|
+
`mathptmx` is not detected at all, which is the edge of the stated scope rather than a
|
|
396
|
+
slope), numbered displays reliably detected, XeTeX/LuaTeX OpenType math, Type 3 fonts, scanned pages (out of scope by
|
|
397
|
+
design), `\overbrace`-style horizontal braces, and alignment recovery in `align` beyond a
|
|
398
|
+
table of rows. [docs/architecture.md] lists the known limitations with reasons.
|
|
399
|
+
|
|
400
|
+
## Where to read more
|
|
401
|
+
|
|
402
|
+
| Document | What is in it |
|
|
403
|
+
|---|---|
|
|
404
|
+
| [docs/architecture.md] | how it works: pipeline, module map, inverted rules, thresholds, limitations |
|
|
405
|
+
| [docs/evaluation.md] | how well it works: synthetic corpus, real papers, recompilation |
|
|
406
|
+
| [docs/prior-art.md] | the audit of SymbolScraper, MathFIRE and MaxTract, with reuse decisions |
|
|
407
|
+
| [docs/comparison.md] | the MathSeer hypothesis and the protocol for testing it |
|
|
408
|
+
|
|
409
|
+
## Licence
|
|
410
|
+
|
|
411
|
+
MIT, chosen over Apache 2.0 for GPLv2 compatibility. That matters here because the
|
|
412
|
+
screen readers this work is ultimately for are GPLv2 — NVDA among them — and MIT code can
|
|
413
|
+
be incorporated into them where Apache 2.0 code cannot. The cost is that MIT says nothing
|
|
414
|
+
about patents, which for an implementation of published 1980s typesetting rules is a
|
|
415
|
+
theoretical concern rather than a real one.
|
|
416
|
+
|
|
417
|
+
Dependencies are MIT/BSD/MPL. PyMuPDF is deliberately avoided because it is AGPL-3.0 and
|
|
418
|
+
would be viral for downstream users. `NOTICE` records the third-party tables the
|
|
419
|
+
generated data files are derived from.
|
|
420
|
+
|
|
421
|
+
[docs/architecture.md]: docs/architecture.md
|
|
422
|
+
[docs/evaluation.md]: docs/evaluation.md
|
|
423
|
+
[docs/prior-art.md]: docs/prior-art.md
|
|
424
|
+
[docs/comparison.md]: docs/comparison.md
|
|
425
|
+
[sre]: https://github.com/Speech-Rule-Engine/speech-rule-engine
|