ricklon Claude Sonnet 4.6 commited on
Commit
b1169dd
·
1 Parent(s): 5862a49

Fix LaTeX display and add quality-of-life improvements

Browse files

- Fix display math mis-parsing: convert \[...\] to block $$\ncontent\n$$
form so arithmatex correctly identifies display math instead of treating
it as inline math surrounded by literal $ signs
- Fix MathJax typeset in Gradio shadow DOM: walk shadow roots to find
.math-preview instead of relying on document.querySelector which fails
inside Gradio's shadow DOM isolation; add 2s fallback for GPU cold-starts
- Add MathJax mathtools extension so \coloneqq / \eqqcolon render natively
without requiring text substitutions
- Text tab: convert \[...\] → $$...$$ and \(...\) → $...$ for readability
- Add Download Markdown button that appears after each extraction

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +46 -10
app.py CHANGED
@@ -110,14 +110,14 @@ def clean_output(text, include_images=False):
110
  else:
111
  text = re.sub(rf'(?m)^[^\n]*{re.escape(match[0])}[^\n]*\n?', '', text)
112
 
113
- text = text.replace('\\coloneqq', ':=').replace('\\eqqcolon', '=:')
114
-
115
  return text.strip()
116
 
117
  MATHJAX_HEAD = """
118
  <script>
119
  window.MathJax = {
 
120
  tex: {
 
121
  inlineMath: [['\\\\(', '\\\\)']],
122
  displayMath: [['\\\\[', '\\\\]']],
123
  processEscapes: true,
@@ -169,8 +169,16 @@ def to_math_html(text):
169
  # Pre-convert \[...\] and \(...\) to $$...$$ and $...$
170
  # Markdown strips backslashes before arithmatex can protect them,
171
  # so convert to $-delimiters first (arithmatex recognises those).
172
- text = re.sub(r'\\\[(.+?)\\\]', r'$$\1$$', text, flags=re.DOTALL)
173
- text = re.sub(r'\\\((.+?)\\\)', r'$\1$', text)
 
 
 
 
 
 
 
 
174
  html = md_lib.markdown(text, extensions=[
175
  'pymdownx.arithmatex',
176
  'tables',
@@ -354,6 +362,7 @@ with gr.Blocks(title="DeepSeek-OCR-2") as demo:
354
  gallery = gr.Gallery(show_label=False, columns=3, height=400)
355
  with gr.Tab("Raw Text", id="tab_raw"):
356
  raw_out = gr.Textbox(lines=20, buttons=["copy"], show_label=False)
 
357
 
358
  with gr.Accordion("Image Examples", open=True):
359
  gr.Examples(
@@ -406,21 +415,48 @@ with gr.Blocks(title="DeepSeek-OCR-2") as demo:
406
  elif image is not None:
407
  cleaned, markdown, raw, img_out, crops = process_image(image, task, custom_prompt)
408
  else:
409
- return "Error: Upload a file or image", "", "", None, []
410
- return cleaned, to_math_html(markdown), raw, img_out, crops
 
 
 
 
 
 
 
 
 
 
 
 
 
411
 
412
  submit_event = btn.click(run, [input_img, file_in, task, prompt, page_selector],
413
- [text_out, md_out, raw_out, img_out, gallery])
414
  submit_event.then(select_boxes, [task], [tabs])
415
  submit_event.then(fn=None, js="""() => {
416
  const tryTypeset = () => {
417
  if (!window.MathJax || !MathJax.typesetPromise) { setTimeout(tryTypeset, 100); return; }
418
- const el = document.querySelector('.math-preview');
419
- if (!el) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
420
  MathJax.typesetClear([el]);
421
  MathJax.typesetPromise([el]);
422
  };
423
- setTimeout(tryTypeset, 100);
 
424
  }""")
425
 
426
  if __name__ == "__main__":
 
110
  else:
111
  text = re.sub(rf'(?m)^[^\n]*{re.escape(match[0])}[^\n]*\n?', '', text)
112
 
 
 
113
  return text.strip()
114
 
115
  MATHJAX_HEAD = """
116
  <script>
117
  window.MathJax = {
118
+ loader: {load: ['[tex]/mathtools']},
119
  tex: {
120
+ packages: {'[+]': ['mathtools']},
121
  inlineMath: [['\\\\(', '\\\\)']],
122
  displayMath: [['\\\\[', '\\\\]']],
123
  processEscapes: true,
 
169
  # Pre-convert \[...\] and \(...\) to $$...$$ and $...$
170
  # Markdown strips backslashes before arithmatex can protect them,
171
  # so convert to $-delimiters first (arithmatex recognises those).
172
+ #
173
+ # IMPORTANT: the model outputs " \[ content \] " with surrounding spaces.
174
+ # Naively replacing to "$$ content $$" (space after $$) causes arithmatex
175
+ # to mis-parse as inline math surrounded by literal $ signs, which is why
176
+ # equations appeared as "$ ... $" instead of rendered display math.
177
+ # Fix: use the multi-line block form ($$\ncontent\n$$) and strip whitespace.
178
+ text = re.sub(r'\\\[(.+?)\\\]',
179
+ lambda m: f'\n\n$$\n{m.group(1).strip()}\n$$\n\n',
180
+ text, flags=re.DOTALL)
181
+ text = re.sub(r'\\\((.+?)\\\)', lambda m: f'${m.group(1).strip()}$', text)
182
  html = md_lib.markdown(text, extensions=[
183
  'pymdownx.arithmatex',
184
  'tables',
 
362
  gallery = gr.Gallery(show_label=False, columns=3, height=400)
363
  with gr.Tab("Raw Text", id="tab_raw"):
364
  raw_out = gr.Textbox(lines=20, buttons=["copy"], show_label=False)
365
+ download_btn = gr.DownloadButton("Download Markdown", visible=False, variant="secondary")
366
 
367
  with gr.Accordion("Image Examples", open=True):
368
  gr.Examples(
 
415
  elif image is not None:
416
  cleaned, markdown, raw, img_out, crops = process_image(image, task, custom_prompt)
417
  else:
418
+ return "Error: Upload a file or image", "", "", None, [], gr.DownloadButton(visible=False)
419
+
420
+ # Text tab: convert \[...\] → $$...$$ and \(...\) → $...$ for readability
421
+ text_display = re.sub(r'\\\[(.+?)\\\]',
422
+ lambda m: f'\n$$\n{m.group(1).strip()}\n$$\n',
423
+ cleaned, flags=re.DOTALL)
424
+ text_display = re.sub(r'\\\((.+?)\\\)', lambda m: f'${m.group(1).strip()}$', text_display)
425
+
426
+ # Download file: write cleaned markdown to a temp .md file
427
+ dl_tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.md', mode='w', encoding='utf-8')
428
+ dl_tmp.write(cleaned)
429
+ dl_tmp.close()
430
+
431
+ return (text_display, to_math_html(markdown), raw, img_out, crops,
432
+ gr.DownloadButton(value=dl_tmp.name, visible=True))
433
 
434
  submit_event = btn.click(run, [input_img, file_in, task, prompt, page_selector],
435
+ [text_out, md_out, raw_out, img_out, gallery, download_btn])
436
  submit_event.then(select_boxes, [task], [tabs])
437
  submit_event.then(fn=None, js="""() => {
438
  const tryTypeset = () => {
439
  if (!window.MathJax || !MathJax.typesetPromise) { setTimeout(tryTypeset, 100); return; }
440
+ // Gradio renders gr.HTML inside a shadow DOM, so document.querySelector
441
+ // won't find .math-preview. Walk all shadow roots to locate it.
442
+ const findInShadows = (root) => {
443
+ const el = root.querySelector('.math-preview');
444
+ if (el) return el;
445
+ for (const node of root.querySelectorAll('*')) {
446
+ if (node.shadowRoot) {
447
+ const found = findInShadows(node.shadowRoot);
448
+ if (found) return found;
449
+ }
450
+ }
451
+ return null;
452
+ };
453
+ const el = findInShadows(document);
454
+ if (!el) { MathJax.typesetPromise(); return; }
455
  MathJax.typesetClear([el]);
456
  MathJax.typesetPromise([el]);
457
  };
458
+ setTimeout(tryTypeset, 200); // primary: catches most cases
459
+ setTimeout(tryTypeset, 2000); // fallback: GPU cold-start can delay DOM update
460
  }""")
461
 
462
  if __name__ == "__main__":