mathematical 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Build Status](https://travis-ci.org/gjtorikian/mathematical.svg?branch=master)](https://travis-ci.org/gjtorikian/mathematical)
|
5
|
+
[![Build Status](https://travis-ci.org/gjtorikian/mathematical.svg?branch=master)](https://travis-ci.org/gjtorikian/mathematical) [![Gem Version](https://badge.fury.io/rb/mathematical.svg)](http://badge.fury.io/rb/mathematical)
|
6
6
|
|
7
7
|
![Mathematical](https://i.imgur.com/JC7HT32.gif)
|
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
|