Skip to content

Commit 345f6fe

Browse files
committed
🔖 1.0.0
1 parent 2f962c3 commit 345f6fe

36 files changed

+1542
-54
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,8 @@ package-lock.json
1212
# generated on `python3 setup.py build`
1313
build/
1414

15+
# generated on `python3 -m build`
16+
dist/
17+
1518
# generated on `pyenv local 3.8.12`
1619
.python-version

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include ./README.adoc

README.adoc

Lines changed: 146 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,146 @@
1-
# adoc-math
1+
// Header
2+
# adoc-math
3+
:toc: macro
4+
5+
// Links
6+
:example: https://github.com/hacker-dom/adoc-math/raw/main/example/adoc-math-example.pdf[Example]
7+
:adoc: https://docs.asciidoctor.org/asciidoc/latest[AsciiDoc]
8+
:markdown: https://daringfireball.net/projects/markdown/[Markdown]
9+
:latex: https://www.latex-project.org[LaTeX]
10+
:adoctor: https://github.com/asciidoctor/asciidoctor[Asciidoctor]
11+
:adoctor-pdf: https://github.com/asciidoctor/asciidoctor-pdf[Asciidoctor-Pdf]
12+
:adoctorjs: https://github.com/asciidoctor/asciidoctor.js[Asciidoctor.js]
13+
:adoc-stem: https://docs.asciidoctor.org/asciidoc/latest/stem/[AsciiDoc STEM]
14+
:adoctor-pdf-stem: https://docs.asciidoctor.org/pdf-converter/latest/stem[Asciidoctor-Pdf STEM]
15+
:mathjax: https://github.com/mathjax/MathJax-src[MathJax]
16+
:katex: https://github.com/KaTeX/KaTeX[KaTeX]
17+
:adoc-math: https://github.com/hacker-dom/adoc-math[adoc-math]
18+
:adoctor-math: https://github.com/asciidoctor/asciidoctor-mathematical[asciidoctor-mathematical]
19+
:amath: http://asciimath.org[AsciiMath]
20+
21+
22+
toc::[]
23+
24+
## {example}
25+
26+
## Installation
27+
28+
adoc-math has zero depependencies! So it's fine to install it globallyfootnote:[Theoretically, the only time this could cause issues is if you have another package which has the name adoc-math (it obviously has to have a different PyPI name, because adoc-math is already taken 😛. But this is not very likely.. )] 😛
29+
30+
[source,bash]
31+
----
32+
pip3 install --user --upgrade adoc-math
33+
adoc-math-setup # will call `npm i -g mathjax@3` and `npm link`
34+
----
35+
36+
## Overview
37+
38+
### Background
39+
40+
I think of {adoc} as a markup syntax somewhere between {markdown} and {latex}. It originated with a https://github.com/asciidoc-py/asciidoc-py[Python implementation], but afaik that isn't actively developed, and the reference implementation is {adoctor} in Ruby.
41+
42+
{adoc} allows you to write a document and then output it in:
43+
44+
* html ({adoctor})
45+
* pdf ({adoctor-pdf})
46+
47+
and many other formats! There is even an {adoctorjs} version (an automated translation of the Ruby code to JavaScript).
48+
49+
### LaTeX
50+
Putting LaTeX equations in other places than a TeX document is not so easy. There are two main libraries for this:
51+
52+
* :mathjax:
53+
** It uses native browser fonts and a lot of Css to replicate {latex} in the browser.
54+
* :katex:
55+
** Similar to {mathjax}, built by Khan Academy.
56+
57+
### STEM
58+
STEM stands for Science, Technology, Engineering, Mathematics, basicaly {latex}. There are two sections in the {adoc} documentation on STEM:
59+
60+
* {adoc-stem}
61+
* {adoctor-pdf-stem}
62+
63+
TLDR:
64+
65+
* In {adoctor} (i.e. Html output), you can include math with `stem:[x+y]`. In the browser, {mathjax} is used to render the math, and frankly, it looks beautiful.
66+
* Since {mathjax} uses browser fonts and Css, it doesn't work in Pdfs. There is an official {adoctor-math} package that provides this support. However, it is extremely quirky, and the ouput doesn't look very good (see a comparison of {adoc-math} and {adoctor-math} in the {example})
67+
** Some more references:
68+
*** https://github.com/asciidoctor/asciidoctor-mathematical/issues/45
69+
70+
### Architecture
71+
72+
That's where `adoc-math` comes in! I decided for:
73+
74+
* a Python package that searches for naturally-looking latex cells (e.g. `$a+b$`), calls {mathjax} to create an svg, and replaces the cells with an image of the svg
75+
76+
I couldn't use {katex} because only {mathjax} has an Svg output (see https://github.com/KaTeX/KaTeX/issues/375).
77+
78+
Unfortunately, {mathjax} 3 doesn't come with a Node CLI package like https://github.com/mathjax/mathjax-node-cli/[MathJax 2]. So I implemented xref:./adoc_math/d_mathjax_wrapper.js[a wrapper] over the library.
79+
80+
### Usage
81+
82+
[cols="2*"]
83+
|===
84+
| Inline cells:
85+
a|
86+
----
87+
$x + y$ [...options]
88+
----
89+
90+
| Block cells:
91+
a|
92+
----
93+
$$ [...options]
94+
x + y
95+
$$
96+
----
97+
|===
98+
99+
For more examples, see the {example}.
100+
101+
102+
## FAQ
103+
104+
> Why isn't `adoc-math` written in Ruby?
105+
106+
I don't speak Ruby 😞 If you would like to translate this library to Ruby, or at least an AsciiDoc macro that can get replaced by an image, so we cant get rid of the extra metacompilation step, I'd be more than happy to help!
107+
108+
> What about Windows?
109+
110+
I tried to be conscious of non-Posix platforms, but haven't tested in on Windows. Any behavioral discrepancies would be considered valid issues.
111+
112+
> Can I reference a cell, or add a caption to a block cell?
113+
114+
Yes! Check out the {example}.
115+
116+
> It's annoying having to uncomment the source math to edit it.
117+
118+
You can use a `pre-post` pattern. `pre.adoc` will be your source code, and `post.adoc` will be the output of `adoc-math` / input to `asciidoctor(-pdf)?`. Run `cpy pre.adoc post.adoc` before every invocation to `adoc-math`.
119+
120+
> How come inline cells become part of the sentence when they are on a separate line?
121+
122+
In {adoc}, you need to separate two blocks with at least one _empty_ line. 🙂
123+
124+
> Does `adoc-math` work with an Html output?
125+
126+
This first version is geared towards Pdf output. Happy to add more powerful support for Html outputs in the future (e.g., just use the native `stem:[]` macro for Html, so we can use basic {mathjax} with browser fonts and Css (instead of svgs)).
127+
128+
> Can I use a different font?
129+
130+
{mathjax} currently http://docs.mathjax.org/en/v3.2-latest/output/fonts.html[doesn't provide support for multiple fonts].
131+
132+
> Can I make my math thinner/thicker?
133+
134+
The created svgs have a property called `stroke-width` that can adjust this. Unfortunately, it is currently set to 0, so it is not possible to make it thinner. In theory it should be possible to make it *thicker* by increasing that value. xref:./adoc_math/e_svg_transforming.py[svg_transforming.py] would be the place for that; or create an issue and I'll add it.
135+
136+
## Debugging
137+
138+
> I get a MODULE_NOT_FOUND error.
139+
140+
MathJax probably cannot be found. Try running `adoc-math-setup`.
141+
142+
> My AsciiMath fractions are too large!
143+
144+
It seems that {amath} interprets fractions in `displaystyle` rather than `textstyle` (`\dfrac{}{}` rather than `\tfrac{}{} or even `\frac{}{}`, see https://tex.stackexchange.com/a/135395/31626[StackExchange]).
145+
146+
I haven't found a good solution to this yet. If you have any ideas, please let me know! Note that if you have a singleton fraction (`$a/b$ amath`) you can scale it down with `$a/b$ amath, scale = 60%` (or just use `tex`).

adoc_math/__main__.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ def parse_args():
2020
)
2121

2222
p.add_argument(
23-
"target_files", help="List of files to run `adoc-math` on.", **list_of_files
23+
"target_files",
24+
help="List of files to run `adoc-math` on. Inline cells (lines such as `$x+y$ tex`) or block cells (lines such as [`$$ amath\n`, `x+y\n`, `$$\n`]) will be read for contents, parsed for options, passed into MathJax@3, and (optionally) the svg transformed. The svg will then be saved to the output directory, and the source file modified: the cell will be commented out and an image macro with a reference to the svg will be inserted. 🤟🚀",
25+
**list_of_files,
2426
)
2527

2628
p.add_argument(
@@ -35,17 +37,38 @@ def parse_args():
3537
**list_of_files,
3638
)
3739

38-
# p.add_argument("--default-alignment", help="Default alignment")
39-
4040
p.add_argument(
4141
"--default-lang",
42-
help="Default language. Can be TEX (LaTeX) or AMATH (AsciiMath).",
42+
help="Default language. Can be TEX (LaTeX) or AMATH (AsciiMath). Default: amath.",
43+
choices=("tex", "amath"),
44+
)
45+
46+
p.add_argument(
47+
"--default-scale",
48+
help="Default scale. This option scales your svgs width and height (in `ex` units). Note that Asciidoctor does not provide a good API for scaling svgs - you can specify a fixed width, but cannot scale by a factor. By default, the math is a little bit too big (a little larger than surrounding text) - at least on the default Asciidoc theme. As such, the default is 90%%. Default: 90%%.",
49+
)
50+
51+
p.add_argument(
52+
"--default-positioning",
53+
help='The raw svg, placed in the asciidoc document, usually gets rendered slightly above the line base (the baseline that characters which don\'t have lower parts (like "y" or "j" are above). Fortunately, Mathjax provides a "style" attribute in the svg (in the form of CSS), which is normally used by browsers to vertically align the math characters. I figured out a way to read this, process it, and achieve a similar effect in pdfs. The idea is that for simple symbols, like $x$, you usually want to position it. However, when you have a fraction ($\frac{a}{b}$), it usually looks better unpositioned. This option controls the default positioning for cells. Default: position.',
54+
choices=("position", "dont_position"),
55+
)
56+
57+
p.add_argument(
58+
"--default-vertical-align-offset",
59+
help="This option allows to specify an offset to the `vertical-align` style explained above. For example, capital letter (`P`) seem to get positioned nicely, but lowercase letters (`p`) are not; I found that `vertical_align_offset = -0.4ex` works well for singleton lowercase characters. Note that this option won't have any effect if `positioning` is set to `dont_position`. Default: 0.0ex.",
60+
)
61+
62+
p.add_argument(
63+
"--default-alignment",
64+
help="This option controls the default *horizontal* alignment of *block* cells. In particuar, it uses Asciidoc's image:...[align=(left|center|right)] API. Default: center.",
4365
)
4466

4567
p.add_argument(
4668
"--default-max-lines",
4769
help="To ensure that you don't forget to close a cell, there is an option to set the maximum number of lines that a cell can have. This option sets the default value, but it can be overridden: as `$$max_lines=10\nx\n$$`. Max lines option (either through CLI or through a cell attribute) has no effect on inline cells, since they span just one line. Default: 6.",
4870
)
71+
4972
p.add_argument(
5073
"--filename-snippet-length",
5174
type=int,
@@ -76,9 +99,9 @@ def main():
7699
args = parse_args()
77100

78101
# The idea here is that the arguments specified in `parse_args` will have value
79-
# None if they are not specified by the user. If we just use something like `i_impl.AdocMath(default_alignment=args.default_alignment`
102+
# None if they are not specified by the user. If we just use something like `i_impl.AdocMath(default_positioning=args.default_positioning`
80103
# etc (for all other arguments), then we'll lose the default values
81-
# as specified in AdocMath (e.g. default_alignment = Alignment.ALIGN)
104+
# as specified in AdocMath (e.g. default_positioning = Positioning.POSITION)
82105
# So instead, let's just filter out those that are None
83106
# (more specifically those that are falsy), and pass in the rest
84107
kwargs = {key: val for key, val in vars(args).items() if val}

adoc_math/_common/b_types.py

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
* this follows e.g. python ast's lineno
1414
* linenum is line number 1-indexed
1515
* this follows e.g. editors (e.g. vs code)"""
16+
Scale = NewType("Scale", int)
17+
VerticalAlignOffset = NewType("VerticalAlignOffset", float)
1618
MaxLines = NewType("MaxLines", int)
1719
LineInFile = NewType("LineIFile", str)
1820
Command = NewType("Command", str)
@@ -24,6 +26,9 @@
2426
SnippetPart = NewType("SnippetPart", str)
2527
DiffContent = NewType("DiffContent", str)
2628
LinesInserted = NewType("LinesInserted", int)
29+
SvgDocument = NewType("SvgDocument", xml.dom.minidom.Document)
30+
Svg = NewType("Svg", xml.dom.minidom.Element)
31+
Ex = NewType("Ex", float)
2732

2833

2934
class OutputMode(str, enum.Enum):
@@ -36,14 +41,23 @@ class Lang(str, enum.Enum):
3641
TEX = "TEX"
3742

3843

39-
# class Alignment(str, enum.Enum):
40-
# ALIGN = "ALIGN"
41-
# DONT_ALIGN = "DONT_ALIGN"
44+
class Positioning(str, enum.Enum):
45+
POSITION = "POSITION"
46+
DONT_POSITION = "DONT_POSITION"
47+
48+
49+
class Alignment(str, enum.Enum):
50+
LEFT = "LEFT"
51+
CENTER = "CENTER"
52+
RIGHT = "RIGHT"
4253

4354

4455
class Opts(NamedTuple):
4556
lang: Lang
46-
# alignment: Alignment
57+
scale: Scale
58+
positioning: Positioning
59+
vertical_align_offset: VerticalAlignOffset
60+
alignment: Alignment
4761
max_lines: MaxLines
4862

4963

@@ -63,3 +77,35 @@ class InlineCell(NamedTuple):
6377

6478

6579
Cell = Union[InlineCell, BlockCell]
80+
81+
82+
class SvgAttributesRaw(NamedTuple):
83+
style: str
84+
width: str
85+
height: str
86+
viewbox: str
87+
88+
89+
class ViewBox(NamedTuple):
90+
# https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox
91+
min_x: Ex
92+
min_y: Ex
93+
width: Ex
94+
height: Ex
95+
96+
def __str__(s):
97+
els = []
98+
for el in s:
99+
if el.is_integer():
100+
els.append(int(el))
101+
else:
102+
els.append(el)
103+
a, b, c, d = els
104+
return f"{a} {b} {c} {d}"
105+
106+
107+
class SvgAttributesParsed(NamedTuple):
108+
vertical_align: Ex
109+
width: Ex
110+
height: Ex
111+
viewbox: ViewBox

adoc_math/_common/c_constants.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
LOGGING_LEVEL = logging.DEBUG
44

55
# Note: whitespace before $$ is not allowed
6-
REGEX_BLOCK_OPENING_TAG_SRC = r"^\$\$(?P<opts>\S*)\s*$"
6+
REGEX_BLOCK_OPENING_TAG_SRC = r"^\$\$(?P<opts>.*?)\s*$"
77
REGEX_BLOCK_OPENING_TAG = re.compile(REGEX_BLOCK_OPENING_TAG_SRC)
88

99
# Note: whitespace before $$ is not allowed
1010
REGEX_BLOCK_CLOSING_TAG_SRC = r"^\$\$\s*$"
1111
REGEX_BLOCK_CLOSING_TAG = re.compile(REGEX_BLOCK_CLOSING_TAG_SRC)
1212

13-
REGEX_INLINE_SRC = r"^\$(?P<content>.*?)\$(?P<opts>\S*)\s*$"
13+
REGEX_INLINE_SRC = r"^\$(?P<content>.+?)\$(?P<opts>.*?)\s*$"
1414
REGEX_INLINE = re.compile(REGEX_INLINE_SRC)
1515

1616
REPO = "https://github.com/hacker-dom/asciidoc-mathjax"

adoc_math/_common/e_utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,19 @@ def change_cwd(path: Union[plib.Path, str]):
100100
yield
101101
finally:
102102
os.chdir(orig_cwd)
103+
104+
105+
def lshave(string: str, sub: str) -> str:
106+
if string.startswith(sub):
107+
return string[len(sub) :]
108+
else:
109+
return string
110+
111+
112+
def rshave(string, sub: str) -> str:
113+
if not isinstance(string, str):
114+
string = str(string)
115+
if string.endswith(sub):
116+
return string[: len(string) - len(sub)]
117+
else:
118+
return string

adoc_math/a_helpers.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ class Helpers:
99
# endregion
1010

1111
# region default cell options
12-
# default_alignment: Alignment
1312
default_lang: Lang
13+
default_scale: Scale
14+
default_positioning: Positioning
15+
default_vertical_align_offset: VerticalAlignOffset
16+
default_alignment: Alignment
1417
default_max_lines: MaxLines
1518
# endregion
1619

0 commit comments

Comments
 (0)