@@ -9,7 +9,7 @@ const { liteAdaptor } = require('mathjax-full/js/adaptors/liteAdaptor.js');
9
9
const { RegisterHTMLHandler } = require ( 'mathjax-full/js/handlers/html.js' ) ;
10
10
const { AllPackages } = require ( 'mathjax-full/js/input/tex/AllPackages.js' ) ;
11
11
const { decode } = require ( 'html-entities' ) ;
12
-
12
+ const { log } = require ( 'console' ) ;
13
13
14
14
const adaptor = liteAdaptor ( ) ;
15
15
RegisterHTMLHandler ( adaptor ) ;
@@ -24,7 +24,7 @@ const tex = new TeX({
24
24
} ) ;
25
25
26
26
const mathml = new MathML ( ) ;
27
- const asciimath = new AsciiMath ( ) ; // Enable AsciiMath input
27
+ const asciimath = new AsciiMath ( ) ;
28
28
29
29
const svg = new SVG ( {
30
30
fontCache : 'none' ,
@@ -39,9 +39,11 @@ function handleError(res) {
39
39
}
40
40
41
41
module . exports . generate = async ( configs , req , res , next ) => {
42
- let myForeground = req . query . fg ;
43
- let dpi = req . query . dpi ;
44
- let isSvg = req . query . svg ;
42
+ const query = configs . query || { } ;
43
+
44
+ let myForeground = query . fg ;
45
+ let dpi = query . dpi ;
46
+ let isSvg = query . svg === true || query . svg === '1' || query . svg === 'true' ;
45
47
46
48
let inputFormat = tex ;
47
49
@@ -68,7 +70,6 @@ module.exports.generate = async (configs, req, res, next) => {
68
70
}
69
71
70
72
function stripRequireCommands ( math ) {
71
- // Match \require{package} with optional spaces
72
73
return math . replace ( / \\ r e q u i r e \s * \{ [ ^ } ] * \} \s * / g, '' ) ;
73
74
}
74
75
@@ -79,35 +80,34 @@ module.exports.generate = async (configs, req, res, next) => {
79
80
if ( dpi < 75 ) dpi = 75 ;
80
81
if ( dpi > 2400 ) dpi = 2400 ;
81
82
82
- isSvg = ! ( ! isSvg || isSvg === '0' ) ;
83
-
84
83
try {
85
84
if ( ! configs ?. typeset ?. math ) {
86
- console . log ( 'No math provided' ) ;
87
85
return handleError ( res ) ;
88
86
}
89
- // Decode HTML entities in the math input
90
- const math = stripRequireCommands ( decode ( configs . typeset . math ) ) ;
87
+
88
+ let decodedMath = configs . typeset . math ;
89
+
90
+ try {
91
+ decodedMath = decodeURIComponent ( decodedMath ) ;
92
+ decodedMath = decodedMath . replace ( / & # 0 3 8 ; / g, '&' ) . replace ( / & # 3 8 ; / g, '&' ) ;
93
+ decodedMath = decode ( decodedMath ) ;
94
+ } catch ( decodeError ) {
95
+ return handleError ( res ) ;
96
+ }
91
97
98
+ const math = stripRequireCommands ( decodedMath ) ;
92
99
const isInline = ( math . startsWith ( '\\(' ) && math . endsWith ( '\\)' ) ) ||
93
100
( math . startsWith ( '$' ) && math . endsWith ( '$' ) && ! math . startsWith ( '$$' ) ) ;
94
-
95
101
const isBlock = ( math . startsWith ( '\\[' ) && math . endsWith ( '\\]' ) ) ||
96
102
( math . startsWith ( '$$' ) && math . endsWith ( '$$' ) ) ;
97
103
98
104
let cleanMath = math . trim ( ) ;
99
-
100
105
if ( isInline ) {
101
- if ( math . startsWith ( '\\(' ) && math . endsWith ( '\\)' ) ) {
102
- cleanMath = math . slice ( 2 , - 2 ) ;
103
- } else if ( math . startsWith ( '$' ) && math . endsWith ( '$' ) ) {
104
- cleanMath = math . slice ( 1 , - 1 ) ;
105
- }
106
+ cleanMath = math . slice ( math . startsWith ( '\\(' ) ? 2 : 1 , - 2 ) ;
106
107
} else if ( isBlock ) {
107
108
cleanMath = math . slice ( 2 , - 2 ) ;
108
109
}
109
110
110
- let svgContent ;
111
111
try {
112
112
const node = mathJaxDocument . convert ( cleanMath , {
113
113
display : ! isInline ,
@@ -118,7 +118,9 @@ module.exports.generate = async (configs, req, res, next) => {
118
118
scale : 1
119
119
} ) ;
120
120
121
- svgContent = adaptor . innerHTML ( node ) ;
121
+
122
+ let svgContent = adaptor . innerHTML ( node ) ;
123
+
122
124
123
125
if ( ! svgContent || ! svgContent . includes ( '<svg' ) || ! svgContent . includes ( '</svg>' ) ) {
124
126
return handleError ( res ) ;
@@ -130,25 +132,61 @@ module.exports.generate = async (configs, req, res, next) => {
130
132
) ;
131
133
132
134
if ( svgContent . includes ( 'merror' ) ) {
133
- console . error ( 'MathJax detected an error:' , svgContent ) ;
134
135
return handleError ( res ) ;
135
136
}
136
137
137
138
if ( isSvg ) {
138
139
res . set ( 'Content-Type' , 'image/svg+xml' ) ;
139
140
return res . send ( svgContent ) ;
140
141
} else {
141
- const sharp = require ( 'sharp' ) ;
142
- const png = await sharp ( Buffer . from ( svgContent ) , { density : dpi } ) . png ( ) . toBuffer ( ) ;
143
- res . set ( 'Content-Type' , 'image/png' ) ;
144
- return res . send ( png ) ;
142
+ try {
143
+ if ( ! svgContent . trim ( ) . startsWith ( '<svg' ) ) {
144
+ return handleError ( res ) ;
145
+ }
146
+
147
+ // Set Content-Type header early
148
+ res . set ( 'Content-Type' , 'image/png' ) ;
149
+
150
+ let fullSvgContent = svgContent ;
151
+
152
+ if ( ! fullSvgContent . includes ( 'xmlns="http://www.w3.org/2000/svg"' ) ) {
153
+ fullSvgContent = fullSvgContent . replace ( '<svg' , '<svg xmlns="http://www.w3.org/2000/svg"' ) ;
154
+ }
155
+
156
+ if ( ! fullSvgContent . startsWith ( '<?xml' ) ) {
157
+ fullSvgContent = '<?xml version="1.0" standalone="no"?>\n' + fullSvgContent ;
158
+ }
159
+
160
+
161
+ const sharp = require ( 'sharp' ) ;
162
+ const buffer = Buffer . from ( fullSvgContent ) ;
163
+ const image = sharp ( buffer , {
164
+ density : dpi > 300 ? 300 : dpi ,
165
+ limitInputPixels : 5000 * 5000
166
+ } ) ;
167
+
168
+ const png = await image
169
+ . resize ( 500 , 500 , {
170
+ fit : 'inside' ,
171
+ withoutEnlargement : true ,
172
+ background : { r : 255 , g : 255 , b : 255 , alpha : 0 }
173
+ } )
174
+ . png ( {
175
+ compressionLevel : 6 ,
176
+ adaptiveFiltering : false ,
177
+ force : true
178
+ } )
179
+ . toBuffer ( ) ;
180
+
181
+ return res . send ( png ) ;
182
+ } catch ( pngError ) {
183
+ return handleError ( res ) ;
184
+ }
145
185
}
146
186
} catch ( err ) {
147
- console . error ( 'MathJax processing error:' , err ) ;
148
187
return handleError ( res ) ;
149
188
}
150
189
} catch ( err ) {
151
- console . error ( 'General error:' , err ) ;
152
190
return handleError ( res ) ;
153
191
}
154
192
} ;
0 commit comments