asciidoctor-pdf-mathjax 0.6.0 → 0.8.0
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.
- checksums.yaml +4 -4
- data/bin/render.js +122 -40
- data/lib/asciidoctor-pdf-mathjax.rb +36 -28
- metadata +18 -18
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7a4ff18612f669474f7731d46e59103e23488c5dcdde291a5aa6635249e28d88
|
|
4
|
+
data.tar.gz: 6fea7b703e6de28edfe0b9615e3132cabf704bdfdc1ca14b2750a1cb5b610b49
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ddf278d42765f9cd09394a73b62c6dd34d88059dae04265d5d15106a2834918d51464cab444ea1731e8747ca2fc9254815f41aac9bbfcc0ee84f829279ceefd8
|
|
7
|
+
data.tar.gz: 1c6066f9e7df67a6c1c9f9df9a584dd8b99161ed3f7fc8b37966db09a206ff0542275deb7b794e729b5a6390b64eb48f2aea3485e4c2e1bc3976ef44a6f39f33
|
data/bin/render.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const mj = require('mathjax-node');
|
|
4
|
-
|
|
5
3
|
// Two modes:
|
|
6
4
|
// render.js <latex> <format> <pixels_per_ex> <font>
|
|
7
5
|
// renders one formula, prints the SVG and exits.
|
|
@@ -11,48 +9,132 @@ const mj = require('mathjax-node');
|
|
|
11
9
|
// per formula, {"svg": "..."} or {"error": "..."}.
|
|
12
10
|
// Starting MathJax takes seconds; the server mode pays that once per font
|
|
13
11
|
// instead of once per formula.
|
|
12
|
+
//
|
|
13
|
+
// Font names are MathJax 4 names (mathjax-tex, mathjax-stix2, ...).
|
|
14
|
+
// The MathJax 2 names previously passed to mathjax-node are still accepted.
|
|
15
|
+
|
|
14
16
|
const server = process.argv[2] === '--server';
|
|
15
|
-
const
|
|
17
|
+
const fontArg = server ? process.argv[3] : process.argv[5];
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
const LEGACY_FONTS = {
|
|
20
|
+
'TeX': 'mathjax-tex',
|
|
21
|
+
'STIX-Web': 'mathjax-stix2',
|
|
22
|
+
'Asana-Math': 'mathjax-asana',
|
|
23
|
+
'Neo-Euler': 'mathjax-euler',
|
|
24
|
+
'Gyre-Pagella': 'mathjax-pagella',
|
|
25
|
+
'Gyre-Termes': 'mathjax-termes',
|
|
26
|
+
'Latin-Modern': 'mathjax-modern',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function resolveFont(name) {
|
|
30
|
+
const requested = (name && String(name).trim()) || 'TeX';
|
|
31
|
+
const font = LEGACY_FONTS[requested] || requested;
|
|
32
|
+
// Neo Euler ships as a font extension in MathJax 4, not a standalone font.
|
|
33
|
+
if (font === 'mathjax-euler') {
|
|
34
|
+
return { font: 'mathjax-tex', fontExtensions: ['mathjax-euler'] };
|
|
22
35
|
}
|
|
23
|
-
}
|
|
24
|
-
mj.start();
|
|
25
|
-
|
|
26
|
-
async function convertToSvg(latex, format, pixels_per_ex) {
|
|
27
|
-
const data = await mj.typeset({
|
|
28
|
-
ex: pixels_per_ex,
|
|
29
|
-
math: latex,
|
|
30
|
-
format: format,
|
|
31
|
-
svg: true,
|
|
32
|
-
});
|
|
33
|
-
return data.svg;
|
|
36
|
+
return { font: font, fontExtensions: [] };
|
|
34
37
|
}
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
function assertInstalled(packageName) {
|
|
40
|
+
try {
|
|
41
|
+
// Font packages export no main entry, so resolve a file they do export.
|
|
42
|
+
require.resolve(packageName + '/package.json');
|
|
43
|
+
} catch (err) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Cannot load ${packageName}. Install MathJax 4 with npm, for example: npm install ${packageName}@4`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const { font, fontExtensions } = resolveFont(fontArg);
|
|
51
|
+
|
|
52
|
+
let MathJax;
|
|
53
|
+
try {
|
|
54
|
+
assertInstalled('@mathjax/src');
|
|
55
|
+
assertInstalled(`@mathjax/${font}-font`);
|
|
56
|
+
fontExtensions.forEach((extension) => assertInstalled(`@mathjax/${extension}-font-extension`));
|
|
57
|
+
MathJax = require('@mathjax/src');
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error(err.message);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function convertToSvg(latex, format, pixelsPerEx) {
|
|
64
|
+
const display = format !== 'inline-TeX';
|
|
65
|
+
const ex = Number(pixelsPerEx) || 6;
|
|
66
|
+
return MathJax.tex2svgPromise(latex, {
|
|
67
|
+
display: display,
|
|
68
|
+
em: ex * 2,
|
|
69
|
+
ex: ex,
|
|
70
|
+
// Formulas are embedded at their natural width. A wide container keeps
|
|
71
|
+
// MathJax from inserting line breaks when a width is still consulted.
|
|
72
|
+
containerWidth: 100000,
|
|
73
|
+
}).then((node) => {
|
|
74
|
+
const adaptor = MathJax.startup.adaptor;
|
|
75
|
+
const svg = adaptor.tags(node, 'svg')[0];
|
|
76
|
+
if (!svg) {
|
|
77
|
+
throw new Error('MathJax produced no SVG');
|
|
78
|
+
}
|
|
79
|
+
const xml = adaptor.serializeXML(svg);
|
|
80
|
+
const mathError = xml.match(/data-mjx-error="([^"]*)"/);
|
|
81
|
+
if (mathError) {
|
|
82
|
+
throw new Error(mathError[1]);
|
|
83
|
+
}
|
|
84
|
+
return xml;
|
|
49
85
|
});
|
|
50
|
-
} else {
|
|
51
|
-
const latex = process.argv[2];
|
|
52
|
-
const format = process.argv[3];
|
|
53
|
-
const pixels_per_ex = parseInt(process.argv[4]);
|
|
54
|
-
|
|
55
|
-
convertToSvg(latex, format, pixels_per_ex).then(svg => {
|
|
56
|
-
console.log(svg);
|
|
57
|
-
}).catch(err => console.error(err));
|
|
58
86
|
}
|
|
87
|
+
|
|
88
|
+
MathJax.init({
|
|
89
|
+
loader: {
|
|
90
|
+
require: require,
|
|
91
|
+
load: ['input/tex', 'output/svg'],
|
|
92
|
+
// Font packages can report an older component version than MathJax itself.
|
|
93
|
+
versionWarnings: false,
|
|
94
|
+
},
|
|
95
|
+
output: {
|
|
96
|
+
font: font,
|
|
97
|
+
fontExtensions: fontExtensions,
|
|
98
|
+
displayOverflow: 'overflow',
|
|
99
|
+
linebreaks: { inline: false },
|
|
100
|
+
},
|
|
101
|
+
svg: {
|
|
102
|
+
fontCache: 'local',
|
|
103
|
+
},
|
|
104
|
+
startup: {
|
|
105
|
+
typeset: false,
|
|
106
|
+
},
|
|
107
|
+
}).then(() => {
|
|
108
|
+
if (server) {
|
|
109
|
+
let queue = Promise.resolve();
|
|
110
|
+
const input = require('readline').createInterface({ input: process.stdin });
|
|
111
|
+
input.on('line', (line) => {
|
|
112
|
+
queue = queue.then(async () => {
|
|
113
|
+
let answer;
|
|
114
|
+
try {
|
|
115
|
+
const request = JSON.parse(line);
|
|
116
|
+
answer = { svg: await convertToSvg(request.math, request.format, request.ex) };
|
|
117
|
+
} catch (err) {
|
|
118
|
+
answer = { error: String(err && err.message ? err.message : err) };
|
|
119
|
+
}
|
|
120
|
+
process.stdout.write(JSON.stringify(answer) + '\n');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
input.on('close', () => {
|
|
124
|
+
queue.then(() => {
|
|
125
|
+
if (MathJax.done) MathJax.done();
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
} else {
|
|
129
|
+
const latex = process.argv[2];
|
|
130
|
+
const format = process.argv[3];
|
|
131
|
+
const pixelsPerEx = parseInt(process.argv[4], 10);
|
|
132
|
+
return convertToSvg(latex, format, pixelsPerEx).then((svg) => {
|
|
133
|
+
process.stdout.write(svg + '\n');
|
|
134
|
+
if (MathJax.done) MathJax.done();
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}).catch((err) => {
|
|
138
|
+
console.error(err && err.message ? err.message : err);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
});
|
|
@@ -199,32 +199,33 @@ class AsciidoctorPDFExtensions < (Asciidoctor::Converter.for 'pdf')
|
|
|
199
199
|
svg_width = svg_width * scaling_factor
|
|
200
200
|
svg_height = svg_height * scaling_factor
|
|
201
201
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
202
|
+
# Points per viewBox unit. Held constant so the glyphs keep the size MathJax produced.
|
|
203
|
+
pt_per_unit = svg_height / svg_inner_height
|
|
204
|
+
orig_top = svg_inner_offset
|
|
205
|
+
orig_bottom = svg_inner_offset + svg_inner_height
|
|
206
|
+
|
|
207
|
+
# Line-sized window with the math baseline (y = 0) on the text baseline.
|
|
208
|
+
# Asciidoctor centers an inline image in the line when it is at most 1.5 line
|
|
209
|
+
# heights tall, and bottom-aligns it otherwise. A window exactly the line height
|
|
210
|
+
# crops any ink past the font ascender or descender (subscripts, denominators).
|
|
211
|
+
line_units = font.font_full_height / pt_per_unit
|
|
212
|
+
line_bottom = font.font_baseline / pt_per_unit
|
|
213
|
+
line_top = line_bottom - line_units
|
|
214
|
+
overflow_above = [line_top - orig_top, 0].max
|
|
215
|
+
overflow_below = [orig_bottom - line_bottom, 0].max
|
|
216
|
+
# Pad both sides equally so centering still leaves the baseline on the text baseline.
|
|
217
|
+
extra = [overflow_above, overflow_below].max
|
|
205
218
|
|
|
206
219
|
logger.debug "Original SVG height: #{svg_height.round(2)}, width: #{svg_width.round(2)}, inner height: #{svg_inner_height.round(2)}, inner offset: #{svg_inner_offset.round(2)}"
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
svg_inner_offset_new = svg_inner_relative_offset * svg_inner_height
|
|
213
|
-
svg_inner_height_padding = (svg_inner_offset - svg_inner_offset_new) * 0.25 # 25% padding to handle fractions
|
|
214
|
-
svg_inner_height_difference = 2 * svg_inner_height_padding
|
|
215
|
-
svg_inner_height_new = svg_inner_height + svg_inner_height_difference
|
|
216
|
-
svg_inner_height_relative_difference = svg_inner_height_new / svg_inner_height
|
|
217
|
-
|
|
218
|
-
logger.debug("svg_inner_offset = #{svg_inner_offset}, svg_inner_height = #{svg_inner_height}, svg_inner_offset_new = #{svg_inner_offset_new}, svg_inner_height_new = #{svg_inner_height_new}")
|
|
219
|
-
logger.debug("svg_inner_offset_diff = #{svg_inner_offset - svg_inner_offset_new}, svg_inner_offset_diff_relative = #{(svg_inner_offset - svg_inner_offset_new) / svg_inner_height}")
|
|
220
|
-
|
|
221
|
-
svg_height = svg_height * svg_inner_height_relative_difference
|
|
222
|
-
svg_inner_height = svg_inner_height_new
|
|
223
|
-
svg_inner_offset = svg_inner_offset_new - svg_inner_height_padding
|
|
220
|
+
fitted_height = (line_units + 2 * extra) * pt_per_unit
|
|
221
|
+
if fitted_height <= font.font_full_height * 1.5
|
|
222
|
+
svg_inner_offset = line_top - extra
|
|
223
|
+
svg_inner_height = line_units + 2 * extra
|
|
224
|
+
svg_height = fitted_height
|
|
224
225
|
else
|
|
225
|
-
|
|
226
|
-
svg_inner_height =
|
|
227
|
-
|
|
226
|
+
svg_inner_offset = orig_top
|
|
227
|
+
svg_inner_height = orig_bottom - orig_top
|
|
228
|
+
svg_height = svg_inner_height * pt_per_unit
|
|
228
229
|
end
|
|
229
230
|
|
|
230
231
|
view_box[1] = svg_inner_offset
|
|
@@ -280,12 +281,19 @@ class AsciidoctorPDFExtensions < (Asciidoctor::Converter.for 'pdf')
|
|
|
280
281
|
raise "No font context found for node #{node}"
|
|
281
282
|
end
|
|
282
283
|
|
|
283
|
-
#
|
|
284
|
-
|
|
285
|
-
|
|
284
|
+
# asciidoctor-pdf has already resolved the font catalog - against pdf-fontsdir, the theme
|
|
285
|
+
# directory and its bundled fonts - and registered the files with Prawn. Use that file, so a
|
|
286
|
+
# path relative to the theme directory is not looked up in the working directory.
|
|
287
|
+
font_file = node.document.converter.font_families.dig(font_family, font_style.to_sym) if node.document.converter.respond_to?(:font_families)
|
|
286
288
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
+
unless font_file && File.file?(font_file)
|
|
290
|
+
# noinspection RubyResolve
|
|
291
|
+
font_catalog = theme.font_catalog
|
|
292
|
+
font_file = font_catalog[font_family][font_style.to_s]
|
|
293
|
+
|
|
294
|
+
if font_file && !font_file.include?(File::SEPARATOR)
|
|
295
|
+
font_file = File.join(node.document.attributes['pdf-fontsdir'], font_file)
|
|
296
|
+
end
|
|
289
297
|
end
|
|
290
298
|
|
|
291
299
|
font = TTFunk::File.open(font_file)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: asciidoctor-pdf-mathjax
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Crown0815
|
|
@@ -98,63 +98,63 @@ dependencies:
|
|
|
98
98
|
- !ruby/object:Gem::Version
|
|
99
99
|
version: '3.0'
|
|
100
100
|
- !ruby/object:Gem::Dependency
|
|
101
|
-
name:
|
|
101
|
+
name: rake
|
|
102
102
|
requirement: !ruby/object:Gem::Requirement
|
|
103
103
|
requirements:
|
|
104
104
|
- - "~>"
|
|
105
105
|
- !ruby/object:Gem::Version
|
|
106
|
-
version: '
|
|
106
|
+
version: '13.0'
|
|
107
107
|
type: :development
|
|
108
108
|
prerelease: false
|
|
109
109
|
version_requirements: !ruby/object:Gem::Requirement
|
|
110
110
|
requirements:
|
|
111
111
|
- - "~>"
|
|
112
112
|
- !ruby/object:Gem::Version
|
|
113
|
-
version: '
|
|
113
|
+
version: '13.0'
|
|
114
114
|
- !ruby/object:Gem::Dependency
|
|
115
|
-
name:
|
|
115
|
+
name: minitest
|
|
116
116
|
requirement: !ruby/object:Gem::Requirement
|
|
117
117
|
requirements:
|
|
118
118
|
- - "~>"
|
|
119
119
|
- !ruby/object:Gem::Version
|
|
120
|
-
version: '
|
|
120
|
+
version: '5.0'
|
|
121
121
|
type: :development
|
|
122
122
|
prerelease: false
|
|
123
123
|
version_requirements: !ruby/object:Gem::Requirement
|
|
124
124
|
requirements:
|
|
125
125
|
- - "~>"
|
|
126
126
|
- !ruby/object:Gem::Version
|
|
127
|
-
version: '
|
|
127
|
+
version: '5.0'
|
|
128
128
|
- !ruby/object:Gem::Dependency
|
|
129
|
-
name:
|
|
129
|
+
name: logger
|
|
130
130
|
requirement: !ruby/object:Gem::Requirement
|
|
131
131
|
requirements:
|
|
132
132
|
- - "~>"
|
|
133
133
|
- !ruby/object:Gem::Version
|
|
134
|
-
version: '
|
|
134
|
+
version: '1.4'
|
|
135
135
|
type: :development
|
|
136
136
|
prerelease: false
|
|
137
137
|
version_requirements: !ruby/object:Gem::Requirement
|
|
138
138
|
requirements:
|
|
139
139
|
- - "~>"
|
|
140
140
|
- !ruby/object:Gem::Version
|
|
141
|
-
version: '
|
|
141
|
+
version: '1.4'
|
|
142
142
|
- !ruby/object:Gem::Dependency
|
|
143
|
-
name:
|
|
143
|
+
name: pdf-reader
|
|
144
144
|
requirement: !ruby/object:Gem::Requirement
|
|
145
145
|
requirements:
|
|
146
146
|
- - "~>"
|
|
147
147
|
- !ruby/object:Gem::Version
|
|
148
|
-
version: '
|
|
148
|
+
version: '2.0'
|
|
149
149
|
type: :development
|
|
150
150
|
prerelease: false
|
|
151
151
|
version_requirements: !ruby/object:Gem::Requirement
|
|
152
152
|
requirements:
|
|
153
153
|
- - "~>"
|
|
154
154
|
- !ruby/object:Gem::Version
|
|
155
|
-
version: '
|
|
156
|
-
description: Converts STEM blocks and inline macros to SVG images using MathJax
|
|
157
|
-
|
|
155
|
+
version: '2.0'
|
|
156
|
+
description: Converts STEM blocks and inline macros to SVG images using MathJax for
|
|
157
|
+
AsciiDoctor PDF.
|
|
158
158
|
executables:
|
|
159
159
|
- render.js
|
|
160
160
|
extensions: []
|
|
@@ -168,10 +168,10 @@ licenses:
|
|
|
168
168
|
metadata: {}
|
|
169
169
|
post_install_message: |
|
|
170
170
|
Thank you for installing asciidoctor-pdf-mathjax!
|
|
171
|
-
Note: This gem requires MathJax
|
|
171
|
+
Note: This gem requires MathJax 4 for full functionality (e.g., LaTeX rendering).
|
|
172
172
|
If you haven't installed it, run:
|
|
173
|
-
npm install -g mathjax-
|
|
174
|
-
See the README for details.
|
|
173
|
+
npm install -g @mathjax/src@4 @mathjax/mathjax-tex-font@4
|
|
174
|
+
See the README for details and for the other math fonts.
|
|
175
175
|
rdoc_options: []
|
|
176
176
|
require_paths:
|
|
177
177
|
- lib
|