mathematical 1.0.0 → 1.1.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/README.md +14 -14
- data/ext/mathematical/mathematical.c +20 -20
- data/lib/mathematical.rb +11 -11
- data/lib/mathematical/version.rb +1 -1
- data/test/mathematical/basic_test.rb +1 -1
- data/test/mathematical/fixtures_test.rb +3 -3
- data/test/mathematical/maliciousness_test.rb +25 -3
- data/test/mathematical/mathjax_test.rb +1 -1
- data/test/mathematical/mathml_test.rb +1 -1
- data/test/mathematical/multiples_test.rb +5 -5
- data/test/mathematical/png_test.rb +2 -2
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f7920616d037c8cdab3ade36bceda055362a6de9
|
4
|
+
data.tar.gz: a6d2688b1275203704cf01146a952ee7391c4d76
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 54a36429dd2a1bd98712ee5a194ef07396a8b76404894d9d3260ca0597b46896a86c97ab6f6664887c15c101dac1c49417880745c55c97ded3672e61390dbb2c
|
7
|
+
data.tar.gz: 51fdd320022dd0555be2a3141b7a2de65d5c7c076bd441565da36a61e3ba2a77f172e479e104367b268c3983cd362e170f7dda48a1624971d95bc0ff30b94890
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Quickly convert math equations into beautiful SVGs (or PNGs/MathML).
|
4
4
|
|
5
|
-
[](https://travis-ci.org/gjtorikian/mathematical)
|
5
|
+
[](https://travis-ci.org/gjtorikian/mathematical) [](http://badge.fury.io/rb/mathematical)
|
6
6
|
|
7
7
|

|
8
8
|
|
@@ -35,15 +35,18 @@ Mathematical.new.render(string_with_math)
|
|
35
35
|
The output will be a hash, with keys that depend on the format you want:
|
36
36
|
|
37
37
|
* If you asked for an SVG, you'll get:
|
38
|
-
*
|
39
|
-
*
|
40
|
-
*
|
38
|
+
* `:width`: the width of the resulting image
|
39
|
+
* `:height`: the height of the resulting image
|
40
|
+
* `:data`: the actual string of SVG
|
41
41
|
* If you asked for a PNG, you'll get:
|
42
|
-
*
|
43
|
-
*
|
44
|
-
*
|
42
|
+
* `:width`: the width of the resulting image
|
43
|
+
* `:height`: the height of the resulting image
|
44
|
+
* `:data`: the PNG data
|
45
45
|
* If you asked for MathML, you'll get:
|
46
|
-
*
|
46
|
+
* `:data`: the MathML data
|
47
|
+
* If you pass in invalid LaTeX, you'll get:
|
48
|
+
* `:data`: the original invalid LaTeX
|
49
|
+
* `:error`: the error class (with message)
|
47
50
|
|
48
51
|
**Note**: If you pass in invalid LaTeX, an error is not raised, but a message *is* printed to STDERR, and the original string is returned (not a hash).
|
49
52
|
|
@@ -60,10 +63,7 @@ inputs << '$c$'
|
|
60
63
|
Mathematical.new.render(inputs)
|
61
64
|
```
|
62
65
|
|
63
|
-
This returns an array of hashes, possessing the same keys as above.
|
64
|
-
|
65
|
-
**Note**: With an array, it is possible to receive elements that are not hashes. For example, given the following input:
|
66
|
-
|
66
|
+
This returns an array of hashes, possessing the same keys as above. For example:
|
67
67
|
```
|
68
68
|
array = ['$foof$', '$not__thisisnotreal$', '$poof$']
|
69
69
|
```
|
@@ -72,10 +72,10 @@ You will receive the following output:
|
|
72
72
|
|
73
73
|
```
|
74
74
|
Mathematical.new.render(array)
|
75
|
-
[ {:
|
75
|
+
[ {:data => "...", :width => ... }, { :data => '$not__thisisnotreal$', :error => "...", {:data => "...", :width => ... }]
|
76
76
|
```
|
77
77
|
|
78
|
-
That is, while the first and last elements are valid LaTeX math, the middle one is not, so the same string is returned. As with
|
78
|
+
That is, while the first and last elements are valid LaTeX math, the middle one is not, so the same string is returned. As with single strings, a message is also printed to STDERR.
|
79
79
|
|
80
80
|
### Options
|
81
81
|
|
@@ -76,10 +76,20 @@ void print_and_raise(VALUE error_type, const char* format, ...)
|
|
76
76
|
va_end(args);
|
77
77
|
}
|
78
78
|
|
79
|
+
static VALUE process_rescue(VALUE args, VALUE exception_object)
|
80
|
+
{
|
81
|
+
VALUE rescue_hash = rb_hash_new();
|
82
|
+
|
83
|
+
rb_hash_aset (rescue_hash, CSTR2SYM ("data"), args);
|
84
|
+
rb_hash_aset (rescue_hash, CSTR2SYM ("exception"), exception_object);
|
85
|
+
|
86
|
+
return rescue_hash;
|
87
|
+
}
|
88
|
+
|
79
89
|
VALUE process(VALUE self, unsigned long maxsize, const char *latex_code, unsigned long latex_size)
|
80
90
|
{
|
81
91
|
if (latex_size > maxsize) {
|
82
|
-
print_and_raise(rb_eMaxsizeError, "Size of latex string
|
92
|
+
print_and_raise(rb_eMaxsizeError, "Size of latex string is greater than the maxsize");
|
83
93
|
}
|
84
94
|
|
85
95
|
VALUE result_hash = rb_hash_new();
|
@@ -87,7 +97,7 @@ VALUE process(VALUE self, unsigned long maxsize, const char *latex_code, unsigne
|
|
87
97
|
|
88
98
|
// convert the LaTeX math to MathML
|
89
99
|
char * mathml = lsm_mtex_to_mathml(latex_code, latex_size, global_start);
|
90
|
-
if (mathml == NULL) { print_and_raise(rb_eParseError, "Failed to parse mtex
|
100
|
+
if (mathml == NULL) { print_and_raise(rb_eParseError, "Failed to parse mtex"); }
|
91
101
|
|
92
102
|
// basically, only update the next equation counter if the last math had a numbered equation
|
93
103
|
if (strstr(mathml, "<mlabeledtr>") != NULL) {
|
@@ -95,7 +105,7 @@ VALUE process(VALUE self, unsigned long maxsize, const char *latex_code, unsigne
|
|
95
105
|
}
|
96
106
|
|
97
107
|
if (format == FORMAT_MATHML) {
|
98
|
-
rb_hash_aset (result_hash,
|
108
|
+
rb_hash_aset (result_hash, CSTR2SYM ("data"), rb_str_new2(mathml));
|
99
109
|
mtex2MML_free_string(mathml);
|
100
110
|
return result_hash;
|
101
111
|
}
|
@@ -157,12 +167,12 @@ VALUE process(VALUE self, unsigned long maxsize, const char *latex_code, unsigne
|
|
157
167
|
switch (format) {
|
158
168
|
case FORMAT_SVG: {
|
159
169
|
if (rb_iv_get(self, "@svg") == Qnil) { print_and_raise(rb_eDocumentReadError, "Failed to read SVG contents"); }
|
160
|
-
rb_hash_aset (result_hash,
|
170
|
+
rb_hash_aset (result_hash, CSTR2SYM ("data"), rb_iv_get(self, "@svg"));
|
161
171
|
break;
|
162
172
|
}
|
163
173
|
case FORMAT_PNG: {
|
164
174
|
if (rb_iv_get(self, "@png") == Qnil) { print_and_raise(rb_eDocumentReadError, "Failed to read PNG contents"); }
|
165
|
-
rb_hash_aset (result_hash,
|
175
|
+
rb_hash_aset (result_hash, CSTR2SYM ("data"), rb_iv_get(self, "@png"));
|
166
176
|
break;
|
167
177
|
}
|
168
178
|
default: {
|
@@ -172,8 +182,8 @@ VALUE process(VALUE self, unsigned long maxsize, const char *latex_code, unsigne
|
|
172
182
|
}
|
173
183
|
}
|
174
184
|
|
175
|
-
rb_hash_aset (result_hash,
|
176
|
-
rb_hash_aset (result_hash,
|
185
|
+
rb_hash_aset (result_hash, CSTR2SYM ("width"), INT2FIX(width_pt));
|
186
|
+
rb_hash_aset (result_hash, CSTR2SYM ("height"), INT2FIX(height_pt));
|
177
187
|
|
178
188
|
// we need to clear out this key when attempting multiple calls. See http://git.io/i1hblQ
|
179
189
|
rb_iv_set(self, "@svg", Qnil);
|
@@ -189,11 +199,6 @@ static VALUE process_helper(VALUE data)
|
|
189
199
|
return process(args[0], NUM2ULONG(args[1]), StringValueCStr(args[2]), NUM2ULONG(args[3]));
|
190
200
|
}
|
191
201
|
|
192
|
-
static VALUE process_failed(void)
|
193
|
-
{
|
194
|
-
return Qnil;
|
195
|
-
}
|
196
|
-
|
197
202
|
static VALUE MATHEMATICAL_process(VALUE self, VALUE rb_Input)
|
198
203
|
{
|
199
204
|
unsigned long maxsize = (unsigned long) FIX2INT(rb_iv_get(self, "@maxsize"));
|
@@ -241,14 +246,9 @@ static VALUE MATHEMATICAL_process(VALUE self, VALUE rb_Input)
|
|
241
246
|
args[1] = ULONG2NUM(maxsize);
|
242
247
|
args[2] = math;
|
243
248
|
args[3] = ULONG2NUM(latex_size);
|
244
|
-
hash = rb_rescue(process_helper, args,
|
245
|
-
|
246
|
-
|
247
|
-
if (hash == Qnil) {
|
248
|
-
rb_ary_store(output, i, math);
|
249
|
-
} else {
|
250
|
-
rb_ary_store(output, i, hash);
|
251
|
-
}
|
249
|
+
hash = rb_rescue(process_helper, args, process_rescue, math);
|
250
|
+
|
251
|
+
rb_ary_store(output, i, hash);
|
252
252
|
}
|
253
253
|
break;
|
254
254
|
}
|
data/lib/mathematical.rb
CHANGED
@@ -34,13 +34,13 @@ class Mathematical
|
|
34
34
|
maths = validate_content(maths)
|
35
35
|
|
36
36
|
begin
|
37
|
-
|
38
|
-
fail RuntimeError if
|
37
|
+
result_data = @processer.process(maths)
|
38
|
+
fail RuntimeError if result_data.nil? || (!result_data.is_a?(Hash) && !result_data.is_a?(Array))
|
39
39
|
|
40
|
-
if
|
41
|
-
|
40
|
+
if result_data.is_a? Array
|
41
|
+
result_data.map { |d| format_data(d) }
|
42
42
|
else
|
43
|
-
format_data(
|
43
|
+
format_data(result_data)
|
44
44
|
end
|
45
45
|
rescue ParseError, DocumentCreationError, DocumentReadError => e
|
46
46
|
# an error in the C code, probably a bad TeX parse
|
@@ -96,19 +96,19 @@ class Mathematical
|
|
96
96
|
maths =~ /\A\${1,2}/
|
97
97
|
end
|
98
98
|
|
99
|
-
def format_data(
|
99
|
+
def format_data(result_hash)
|
100
100
|
# we passed in an array of math, and found an unprocessable element
|
101
|
-
return
|
101
|
+
return result_hash if result_hash[:exception]
|
102
102
|
|
103
103
|
case @config[:format]
|
104
104
|
when :svg
|
105
105
|
# remove starting <?xml...> tag
|
106
|
-
data
|
107
|
-
data
|
106
|
+
result_hash[:data] = result_hash[:data][XML_HEADER.length..-1]
|
107
|
+
result_hash[:data] = svg_to_base64(result_hash[:data]) if @config[:base64]
|
108
108
|
|
109
|
-
|
109
|
+
result_hash
|
110
110
|
when :png, :mathml # do nothing with these...for now?
|
111
|
-
|
111
|
+
result_hash
|
112
112
|
end
|
113
113
|
end
|
114
114
|
|
data/lib/mathematical/version.rb
CHANGED
@@ -9,7 +9,7 @@ class Mathematical::BasicTest < Test::Unit::TestCase
|
|
9
9
|
def test_multiple_calls
|
10
10
|
render = Mathematical.new
|
11
11
|
render.render('$\pi$')
|
12
|
-
output = render.render('$\pi$')[
|
12
|
+
output = render.render('$\pi$')[:data]
|
13
13
|
assert_equal 1, output.scan(/<svg/).size, 'should only contain one svg'
|
14
14
|
end
|
15
15
|
|
@@ -18,14 +18,14 @@ class Mathematical::FixturesTest < Test::Unit::TestCase
|
|
18
18
|
svg_content = Mathematical.new(:base64 => false).render(eq)
|
19
19
|
# remove \ and $, remove whitespace, keep alphanums, remove extraneous - and trailing -
|
20
20
|
filename = eq.gsub(/[\$\\]*/, '').gsub(/\s+/, '-').gsub(/[^a-zA-Z\d]/, '-').gsub(/-{2,}/, '-').gsub(/-$/, '')
|
21
|
-
File.open("samples/fixtures/#{filename}.svg", 'w') { |file| file.write svg_content[
|
21
|
+
File.open("samples/fixtures/#{filename}.svg", 'w') { |file| file.write svg_content[:data] }
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
25
|
actual = MathToItex(source).convert do |eq, type|
|
26
26
|
svg_content = Mathematical.new(:base64 => true).render(eq)
|
27
27
|
|
28
|
-
|
28
|
+
%(<img class="#{type}-math" data-math-type="#{type}-math" src="#{svg_content[:data]}"/>)
|
29
29
|
end.rstrip
|
30
30
|
|
31
31
|
expected_file = before.sub(/before/, 'after').sub(/text/, 'html')
|
@@ -34,7 +34,7 @@ class Mathematical::FixturesTest < Test::Unit::TestCase
|
|
34
34
|
|
35
35
|
expected = File.read(expected_file)
|
36
36
|
|
37
|
-
expected = (MathToItex(expected).convert {|string| Mathematical.new.render(string)}).rstrip
|
37
|
+
expected = (MathToItex(expected).convert { |string| Mathematical.new.render(string) }).rstrip
|
38
38
|
|
39
39
|
# Travis and OS X each render SVGs differently. For now, let's just be happy
|
40
40
|
# that something renders at all.
|
@@ -109,8 +109,30 @@ class Mathematical::MaliciousnessTest < Test::Unit::TestCase
|
|
109
109
|
assert_equal 3, output.length
|
110
110
|
assert_equal Hash, output.first.class
|
111
111
|
assert_equal Hash, output.last.class
|
112
|
-
|
113
|
-
|
114
|
-
|
112
|
+
|
113
|
+
assert_equal '$/this___istotallyfake$', output[1][:data]
|
114
|
+
assert_equal Mathematical::ParseError, output[1][:exception].class
|
115
|
+
assert_match 'Failed to parse mtex', output[1][:exception].message
|
116
|
+
|
117
|
+
# array errors also output to STDERR
|
118
|
+
assert_match /Failed to parse mtex/, err
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_it_passes_a_legible_error_for_maxsize
|
122
|
+
output = nil
|
123
|
+
render = Mathematical.new({:maxsize => 2})
|
124
|
+
|
125
|
+
_, err = capture_subprocess_io do
|
126
|
+
output = render.render(['$a \ne b$'])
|
127
|
+
end
|
128
|
+
|
129
|
+
assert_equal 1, output.length
|
130
|
+
|
131
|
+
assert_equal '$a \ne b$', output[0][:data]
|
132
|
+
assert_equal Mathematical::MaxsizeError, output[0][:exception].class
|
133
|
+
assert_match 'Size of latex string is greater than the maxsize', output[0][:exception].message
|
134
|
+
|
135
|
+
# array errors also output to STDERR
|
136
|
+
assert_match /Size of latex string is greater than the maxsize/, err
|
115
137
|
end
|
116
138
|
end
|
@@ -16,7 +16,7 @@ class Mathematical::MathJaxTest < Test::Unit::TestCase
|
|
16
16
|
assert_nothing_raised { data = render_svg.render(tex_contents) }
|
17
17
|
|
18
18
|
# assert the SVG actually rendered
|
19
|
-
doc = Nokogiri::HTML(data[
|
19
|
+
doc = Nokogiri::HTML(data[:data])
|
20
20
|
assert_empty doc.search(%(//svg[@width='0pt']))
|
21
21
|
assert_empty doc.search(%(//svg[@height='0pt']))
|
22
22
|
end
|
@@ -14,7 +14,7 @@ class Mathematical::MathMLTest < Test::Unit::TestCase
|
|
14
14
|
$$
|
15
15
|
'''
|
16
16
|
render = Mathematical.new({:format => :mathml})
|
17
|
-
mathml = render.render(string)[
|
17
|
+
mathml = render.render(string)[:data]
|
18
18
|
|
19
19
|
assert_match %r{<math xmlns='http://www.w3.org/1998/Math/MathML' display='block'><semantics><mrow><mrow><mo>\(}, mathml
|
20
20
|
end
|
@@ -16,7 +16,7 @@ $$
|
|
16
16
|
'''
|
17
17
|
|
18
18
|
output = @render.render([string])
|
19
|
-
svg = output.first[
|
19
|
+
svg = output.first[:data]
|
20
20
|
|
21
21
|
assert_equal 1, svg.scan(/svg\+xml;/).size, 'should only contain one svg'
|
22
22
|
assert_match 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My', svg
|
@@ -38,13 +38,13 @@ $$
|
|
38
38
|
output = @render.render(inputs)
|
39
39
|
assert_equal 1000, output.length
|
40
40
|
output.each do |data|
|
41
|
-
assert_match 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My', data[
|
41
|
+
assert_match 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My', data[:data]
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
45
|
def test_it_properly_accounts_for_equations
|
46
46
|
inputs = []
|
47
|
-
|
47
|
+
(1..2).each do |i|
|
48
48
|
string = """
|
49
49
|
$$
|
50
50
|
\\begin{equation}
|
@@ -60,8 +60,8 @@ $$
|
|
60
60
|
output = render.render(inputs)
|
61
61
|
assert_equal 3, output.length
|
62
62
|
output.each_with_index do |data_hash, i|
|
63
|
-
header = data_hash[
|
64
|
-
File.open("#{fixtures_dir}/png/numeric_test_#{i + 1}.png", 'w') { |f| f.write(data_hash[
|
63
|
+
header = data_hash[:data].unpack('H*').first.slice(0, 18)
|
64
|
+
File.open("#{fixtures_dir}/png/numeric_test_#{i + 1}.png", 'w') { |f| f.write(data_hash[:data])}
|
65
65
|
assert_equal header, '89504e470d0a1a0a00'
|
66
66
|
end
|
67
67
|
end
|
@@ -18,8 +18,8 @@ $$
|
|
18
18
|
'''
|
19
19
|
render = Mathematical.new({:format => :png})
|
20
20
|
data_hash = render.render(string)
|
21
|
-
header = data_hash[
|
22
|
-
File.open("#{fixtures_dir}/png/pmatrix.png", 'w') { |f| f.write(data_hash[
|
21
|
+
header = data_hash[:data].unpack('H*').first.slice(0, 18)
|
22
|
+
File.open("#{fixtures_dir}/png/pmatrix.png", 'w') { |f| f.write(data_hash[:data])}
|
23
23
|
assert_equal header, '89504e470d0a1a0a00'
|
24
24
|
end
|
25
25
|
end
|