@@ -281,14 +281,38 @@ def _run_checked_subprocess(self, command, tex, *, cwd=None):
281
281
'Failed to process string with tex because {} could not be '
282
282
'found' .format (command [0 ]))from exc
283
283
except subprocess .CalledProcessError as exc :
284
- raise RuntimeError (
285
- '{prog} was not able to process the following string:\n '
286
- '{tex!r}\n \n '
287
- 'Here is the full report generated by {prog}:\n '
288
- '{exc}\n \n ' .format (
289
- prog = command [0 ],
290
- tex = tex .encode ('unicode_escape' ),
291
- exc = exc .output .decode ('utf-8' )))from exc
284
+ tex_log = exc .output .decode ('utf-8' )
285
+
286
+ # whether the exception was likely caused by an unescaped
287
+ # underscore. This cannot be easily determined unambiguously.
288
+ # By requiring `'$' not in tex` we err on the side of preventing
289
+ # false positives, because we only want to issue the customized
290
+ # error message if we are sure there's an unescaped underscore.
291
+ unescaped_underscore = (
292
+ '_' in tex and '$' not in tex and
293
+ re .match (r'.*Missing \$ inserted.*_\n' ,tex_log ,
294
+ flags = re .DOTALL )
295
+ )
296
+ if unescaped_underscore :
297
+ message = (
298
+ '{prog} was not able to process the following string:\n '
299
+ '{tex!r}\n \n '
300
+ 'This is likely caused by an unescaped underscore.\n '
301
+ 'You may escape the underscore or add '
302
+ 'r"\\ usepackage{{underscore}}"\n '
303
+ 'to the matplotlib rcParam text.latex.preamble' .format (
304
+ prog = command [0 ],
305
+ tex = tex .encode ('unicode_escape' )))
306
+ else :
307
+ message = (
308
+ '{prog} was not able to process the following string:\n '
309
+ '{tex!r}\n \n '
310
+ 'Here is the full report generated by {prog}:\n '
311
+ '{exc}\n \n ' .format (
312
+ prog = command [0 ],
313
+ tex = tex .encode ('unicode_escape' ),
314
+ exc = tex_log ))
315
+ raise RuntimeError (message )from exc
292
316
_log .debug (report )
293
317
return report
294
318