magic-pdf 0.5.4__py3-none-any.whl

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.
Files changed (121) hide show
  1. magic_pdf/__init__.py +0 -0
  2. magic_pdf/cli/__init__.py +0 -0
  3. magic_pdf/cli/magicpdf.py +294 -0
  4. magic_pdf/dict2md/__init__.py +0 -0
  5. magic_pdf/dict2md/mkcontent.py +397 -0
  6. magic_pdf/dict2md/ocr_mkcontent.py +356 -0
  7. magic_pdf/filter/__init__.py +0 -0
  8. magic_pdf/filter/pdf_classify_by_type.py +381 -0
  9. magic_pdf/filter/pdf_meta_scan.py +368 -0
  10. magic_pdf/layout/__init__.py +0 -0
  11. magic_pdf/layout/bbox_sort.py +681 -0
  12. magic_pdf/layout/layout_det_utils.py +182 -0
  13. magic_pdf/layout/layout_sort.py +732 -0
  14. magic_pdf/layout/layout_spiler_recog.py +101 -0
  15. magic_pdf/layout/mcol_sort.py +336 -0
  16. magic_pdf/libs/Constants.py +11 -0
  17. magic_pdf/libs/MakeContentConfig.py +10 -0
  18. magic_pdf/libs/ModelBlockTypeEnum.py +9 -0
  19. magic_pdf/libs/__init__.py +0 -0
  20. magic_pdf/libs/boxbase.py +408 -0
  21. magic_pdf/libs/calc_span_stats.py +239 -0
  22. magic_pdf/libs/commons.py +204 -0
  23. magic_pdf/libs/config_reader.py +63 -0
  24. magic_pdf/libs/convert_utils.py +5 -0
  25. magic_pdf/libs/coordinate_transform.py +9 -0
  26. magic_pdf/libs/detect_language_from_model.py +21 -0
  27. magic_pdf/libs/draw_bbox.py +227 -0
  28. magic_pdf/libs/drop_reason.py +27 -0
  29. magic_pdf/libs/drop_tag.py +19 -0
  30. magic_pdf/libs/hash_utils.py +15 -0
  31. magic_pdf/libs/json_compressor.py +27 -0
  32. magic_pdf/libs/language.py +31 -0
  33. magic_pdf/libs/markdown_utils.py +31 -0
  34. magic_pdf/libs/math.py +9 -0
  35. magic_pdf/libs/nlp_utils.py +203 -0
  36. magic_pdf/libs/ocr_content_type.py +21 -0
  37. magic_pdf/libs/path_utils.py +23 -0
  38. magic_pdf/libs/pdf_image_tools.py +33 -0
  39. magic_pdf/libs/safe_filename.py +11 -0
  40. magic_pdf/libs/textbase.py +33 -0
  41. magic_pdf/libs/version.py +1 -0
  42. magic_pdf/libs/vis_utils.py +308 -0
  43. magic_pdf/model/__init__.py +0 -0
  44. magic_pdf/model/doc_analyze_by_360layout.py +8 -0
  45. magic_pdf/model/doc_analyze_by_pp_structurev2.py +125 -0
  46. magic_pdf/model/magic_model.py +632 -0
  47. magic_pdf/para/__init__.py +0 -0
  48. magic_pdf/para/block_continuation_processor.py +562 -0
  49. magic_pdf/para/block_termination_processor.py +480 -0
  50. magic_pdf/para/commons.py +222 -0
  51. magic_pdf/para/denoise.py +246 -0
  52. magic_pdf/para/draw.py +121 -0
  53. magic_pdf/para/exceptions.py +198 -0
  54. magic_pdf/para/layout_match_processor.py +40 -0
  55. magic_pdf/para/para_pipeline.py +297 -0
  56. magic_pdf/para/para_split.py +644 -0
  57. magic_pdf/para/para_split_v2.py +772 -0
  58. magic_pdf/para/raw_processor.py +207 -0
  59. magic_pdf/para/stats.py +268 -0
  60. magic_pdf/para/title_processor.py +1014 -0
  61. magic_pdf/pdf_parse_by_ocr.py +219 -0
  62. magic_pdf/pdf_parse_by_ocr_v2.py +17 -0
  63. magic_pdf/pdf_parse_by_txt.py +410 -0
  64. magic_pdf/pdf_parse_by_txt_v2.py +56 -0
  65. magic_pdf/pdf_parse_for_train.py +685 -0
  66. magic_pdf/pdf_parse_union_core.py +241 -0
  67. magic_pdf/pipe/AbsPipe.py +112 -0
  68. magic_pdf/pipe/OCRPipe.py +28 -0
  69. magic_pdf/pipe/TXTPipe.py +29 -0
  70. magic_pdf/pipe/UNIPipe.py +83 -0
  71. magic_pdf/pipe/__init__.py +0 -0
  72. magic_pdf/post_proc/__init__.py +0 -0
  73. magic_pdf/post_proc/detect_para.py +3472 -0
  74. magic_pdf/post_proc/pdf_post_filter.py +67 -0
  75. magic_pdf/post_proc/remove_footnote.py +153 -0
  76. magic_pdf/pre_proc/__init__.py +0 -0
  77. magic_pdf/pre_proc/citationmarker_remove.py +157 -0
  78. magic_pdf/pre_proc/construct_page_dict.py +72 -0
  79. magic_pdf/pre_proc/cut_image.py +71 -0
  80. magic_pdf/pre_proc/detect_equation.py +134 -0
  81. magic_pdf/pre_proc/detect_footer_by_model.py +64 -0
  82. magic_pdf/pre_proc/detect_footer_header_by_statistics.py +284 -0
  83. magic_pdf/pre_proc/detect_footnote.py +170 -0
  84. magic_pdf/pre_proc/detect_header.py +64 -0
  85. magic_pdf/pre_proc/detect_images.py +647 -0
  86. magic_pdf/pre_proc/detect_page_number.py +64 -0
  87. magic_pdf/pre_proc/detect_tables.py +62 -0
  88. magic_pdf/pre_proc/equations_replace.py +559 -0
  89. magic_pdf/pre_proc/fix_image.py +244 -0
  90. magic_pdf/pre_proc/fix_table.py +270 -0
  91. magic_pdf/pre_proc/main_text_font.py +23 -0
  92. magic_pdf/pre_proc/ocr_detect_all_bboxes.py +115 -0
  93. magic_pdf/pre_proc/ocr_detect_layout.py +133 -0
  94. magic_pdf/pre_proc/ocr_dict_merge.py +336 -0
  95. magic_pdf/pre_proc/ocr_span_list_modify.py +258 -0
  96. magic_pdf/pre_proc/pdf_pre_filter.py +74 -0
  97. magic_pdf/pre_proc/post_layout_split.py +0 -0
  98. magic_pdf/pre_proc/remove_bbox_overlap.py +98 -0
  99. magic_pdf/pre_proc/remove_colored_strip_bbox.py +79 -0
  100. magic_pdf/pre_proc/remove_footer_header.py +117 -0
  101. magic_pdf/pre_proc/remove_rotate_bbox.py +188 -0
  102. magic_pdf/pre_proc/resolve_bbox_conflict.py +191 -0
  103. magic_pdf/pre_proc/solve_line_alien.py +29 -0
  104. magic_pdf/pre_proc/statistics.py +12 -0
  105. magic_pdf/rw/AbsReaderWriter.py +34 -0
  106. magic_pdf/rw/DiskReaderWriter.py +66 -0
  107. magic_pdf/rw/S3ReaderWriter.py +107 -0
  108. magic_pdf/rw/__init__.py +0 -0
  109. magic_pdf/spark/__init__.py +0 -0
  110. magic_pdf/spark/spark_api.py +51 -0
  111. magic_pdf/train_utils/__init__.py +0 -0
  112. magic_pdf/train_utils/convert_to_train_format.py +65 -0
  113. magic_pdf/train_utils/extract_caption.py +59 -0
  114. magic_pdf/train_utils/remove_footer_header.py +159 -0
  115. magic_pdf/train_utils/vis_utils.py +327 -0
  116. magic_pdf/user_api.py +136 -0
  117. magic_pdf-0.5.4.dist-info/LICENSE.md +661 -0
  118. magic_pdf-0.5.4.dist-info/METADATA +24 -0
  119. magic_pdf-0.5.4.dist-info/RECORD +121 -0
  120. magic_pdf-0.5.4.dist-info/WHEEL +5 -0
  121. magic_pdf-0.5.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,397 @@
1
+ import math
2
+ from loguru import logger
3
+
4
+ from magic_pdf.libs.boxbase import find_bottom_nearest_text_bbox, find_top_nearest_text_bbox
5
+ from magic_pdf.libs.commons import join_path
6
+ from magic_pdf.libs.ocr_content_type import ContentType
7
+
8
+ TYPE_INLINE_EQUATION = ContentType.InlineEquation
9
+ TYPE_INTERLINE_EQUATION = ContentType.InterlineEquation
10
+ UNI_FORMAT_TEXT_TYPE = ['text', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']
11
+
12
+
13
+ @DeprecationWarning
14
+ def mk_nlp_markdown_1(para_dict: dict):
15
+ """
16
+ 对排序后的bboxes拼接内容
17
+ """
18
+ content_lst = []
19
+ for _, page_info in para_dict.items():
20
+ para_blocks = page_info.get("para_blocks")
21
+ if not para_blocks:
22
+ continue
23
+
24
+ for block in para_blocks:
25
+ item = block["paras"]
26
+ for _, p in item.items():
27
+ para_text = p["para_text"]
28
+ is_title = p["is_para_title"]
29
+ title_level = p['para_title_level']
30
+ md_title_prefix = "#"*title_level
31
+ if is_title:
32
+ content_lst.append(f"{md_title_prefix} {para_text}")
33
+ else:
34
+ content_lst.append(para_text)
35
+
36
+ content_text = "\n\n".join(content_lst)
37
+
38
+ return content_text
39
+
40
+
41
+
42
+ # 找到目标字符串在段落中的索引
43
+ def __find_index(paragraph, target):
44
+ index = paragraph.find(target)
45
+ if index != -1:
46
+ return index
47
+ else:
48
+ return None
49
+
50
+
51
+ def __insert_string(paragraph, target, postion):
52
+ new_paragraph = paragraph[:postion] + target + paragraph[postion:]
53
+ return new_paragraph
54
+
55
+
56
+ def __insert_after(content, image_content, target):
57
+ """
58
+ 在content中找到target,将image_content插入到target后面
59
+ """
60
+ index = content.find(target)
61
+ if index != -1:
62
+ content = content[:index+len(target)] + "\n\n" + image_content + "\n\n" + content[index+len(target):]
63
+ else:
64
+ logger.error(f"Can't find the location of image {image_content} in the markdown file, search target is {target}")
65
+ return content
66
+
67
+ def __insert_before(content, image_content, target):
68
+ """
69
+ 在content中找到target,将image_content插入到target前面
70
+ """
71
+ index = content.find(target)
72
+ if index != -1:
73
+ content = content[:index] + "\n\n" + image_content + "\n\n" + content[index:]
74
+ else:
75
+ logger.error(f"Can't find the location of image {image_content} in the markdown file, search target is {target}")
76
+ return content
77
+
78
+
79
+ @DeprecationWarning
80
+ def mk_mm_markdown_1(para_dict: dict):
81
+ """拼装多模态markdown"""
82
+ content_lst = []
83
+ for _, page_info in para_dict.items():
84
+ page_lst = [] # 一个page内的段落列表
85
+ para_blocks = page_info.get("para_blocks")
86
+ pymu_raw_blocks = page_info.get("preproc_blocks")
87
+
88
+ all_page_images = []
89
+ all_page_images.extend(page_info.get("images",[]))
90
+ all_page_images.extend(page_info.get("image_backup", []) )
91
+ all_page_images.extend(page_info.get("tables",[]))
92
+ all_page_images.extend(page_info.get("table_backup",[]) )
93
+
94
+ if not para_blocks or not pymu_raw_blocks: # 只有图片的拼接的场景
95
+ for img in all_page_images:
96
+ page_lst.append(f"![]({img['image_path']})") # TODO 图片顺序
97
+ page_md = "\n\n".join(page_lst)
98
+
99
+ else:
100
+ for block in para_blocks:
101
+ item = block["paras"]
102
+ for _, p in item.items():
103
+ para_text = p["para_text"]
104
+ is_title = p["is_para_title"]
105
+ title_level = p['para_title_level']
106
+ md_title_prefix = "#"*title_level
107
+ if is_title:
108
+ page_lst.append(f"{md_title_prefix} {para_text}")
109
+ else:
110
+ page_lst.append(para_text)
111
+
112
+ """拼装成一个页面的文本"""
113
+ page_md = "\n\n".join(page_lst)
114
+ """插入图片"""
115
+ for img in all_page_images:
116
+ imgbox = img['bbox']
117
+ img_content = f"![]({img['image_path']})"
118
+ # 先看在哪个block内
119
+ for block in pymu_raw_blocks:
120
+ bbox = block['bbox']
121
+ if bbox[0]-1 <= imgbox[0] < bbox[2]+1 and bbox[1]-1 <= imgbox[1] < bbox[3]+1:# 确定在block内
122
+ for l in block['lines']:
123
+ line_box = l['bbox']
124
+ if line_box[0]-1 <= imgbox[0] < line_box[2]+1 and line_box[1]-1 <= imgbox[1] < line_box[3]+1: # 在line内的,插入line前面
125
+ line_txt = "".join([s['text'] for s in l['spans']])
126
+ page_md = __insert_before(page_md, img_content, line_txt)
127
+ break
128
+ break
129
+ else:# 在行与行之间
130
+ # 找到图片x0,y0与line的x0,y0最近的line
131
+ min_distance = 100000
132
+ min_line = None
133
+ for l in block['lines']:
134
+ line_box = l['bbox']
135
+ distance = math.sqrt((line_box[0] - imgbox[0])**2 + (line_box[1] - imgbox[1])**2)
136
+ if distance < min_distance:
137
+ min_distance = distance
138
+ min_line = l
139
+ if min_line:
140
+ line_txt = "".join([s['text'] for s in min_line['spans']])
141
+ img_h = imgbox[3] - imgbox[1]
142
+ if min_distance<img_h: # 文字在图片前面
143
+ page_md = __insert_after(page_md, img_content, line_txt)
144
+ else:
145
+ page_md = __insert_before(page_md, img_content, line_txt)
146
+ else:
147
+ logger.error(f"Can't find the location of image {img['image_path']} in the markdown file #1")
148
+ else:# 应当在两个block之间
149
+ # 找到上方最近的block,如果上方没有就找大下方最近的block
150
+ top_txt_block = find_top_nearest_text_bbox(pymu_raw_blocks, imgbox)
151
+ if top_txt_block:
152
+ line_txt = "".join([s['text'] for s in top_txt_block['lines'][-1]['spans']])
153
+ page_md = __insert_after(page_md, img_content, line_txt)
154
+ else:
155
+ bottom_txt_block = find_bottom_nearest_text_bbox(pymu_raw_blocks, imgbox)
156
+ if bottom_txt_block:
157
+ line_txt = "".join([s['text'] for s in bottom_txt_block['lines'][0]['spans']])
158
+ page_md = __insert_before(page_md, img_content, line_txt)
159
+ else:
160
+ logger.error(f"Can't find the location of image {img['image_path']} in the markdown file #2")
161
+
162
+ content_lst.append(page_md)
163
+
164
+ """拼装成全部页面的文本"""
165
+ content_text = "\n\n".join(content_lst)
166
+
167
+ return content_text
168
+
169
+
170
+ def __insert_after_para(text, type, element, content_list):
171
+ """
172
+ 在content_list中找到text,将image_path作为一个新的node插入到text后面
173
+ """
174
+ for i, c in enumerate(content_list):
175
+ content_type = c.get("type")
176
+ if content_type in UNI_FORMAT_TEXT_TYPE and text in c.get("text", ''):
177
+ if type == "image":
178
+ content_node = {
179
+ "type": "image",
180
+ "img_path": element.get("image_path"),
181
+ "img_alt": "",
182
+ "img_title": "",
183
+ "img_caption": "",
184
+ }
185
+ elif type == "table":
186
+ content_node = {
187
+ "type": "table",
188
+ "img_path": element.get("image_path"),
189
+ "table_latex": element.get("text"),
190
+ "table_title": "",
191
+ "table_caption": "",
192
+ "table_quality": element.get("quality"),
193
+ }
194
+ content_list.insert(i+1, content_node)
195
+ break
196
+ else:
197
+ logger.error(f"Can't find the location of image {element.get('image_path')} in the markdown file, search target is {text}")
198
+
199
+
200
+
201
+ def __insert_before_para(text, type, element, content_list):
202
+ """
203
+ 在content_list中找到text,将image_path作为一个新的node插入到text前面
204
+ """
205
+ for i, c in enumerate(content_list):
206
+ content_type = c.get("type")
207
+ if content_type in UNI_FORMAT_TEXT_TYPE and text in c.get("text", ''):
208
+ if type == "image":
209
+ content_node = {
210
+ "type": "image",
211
+ "img_path": element.get("image_path"),
212
+ "img_alt": "",
213
+ "img_title": "",
214
+ "img_caption": "",
215
+ }
216
+ elif type == "table":
217
+ content_node = {
218
+ "type": "table",
219
+ "img_path": element.get("image_path"),
220
+ "table_latex": element.get("text"),
221
+ "table_title": "",
222
+ "table_caption": "",
223
+ "table_quality": element.get("quality"),
224
+ }
225
+ content_list.insert(i, content_node)
226
+ break
227
+ else:
228
+ logger.error(f"Can't find the location of image {element.get('image_path')} in the markdown file, search target is {text}")
229
+
230
+
231
+ def mk_universal_format(pdf_info_list: list, img_buket_path):
232
+ """
233
+ 构造统一格式 https://aicarrier.feishu.cn/wiki/FqmMwcH69iIdCWkkyjvcDwNUnTY
234
+ """
235
+ content_lst = []
236
+ for page_info in pdf_info_list:
237
+ page_lst = [] # 一个page内的段落列表
238
+ para_blocks = page_info.get("para_blocks")
239
+ pymu_raw_blocks = page_info.get("preproc_blocks")
240
+
241
+ all_page_images = []
242
+ all_page_images.extend(page_info.get("images",[]))
243
+ all_page_images.extend(page_info.get("image_backup", []) )
244
+ # all_page_images.extend(page_info.get("tables",[]))
245
+ # all_page_images.extend(page_info.get("table_backup",[]) )
246
+ all_page_tables = []
247
+ all_page_tables.extend(page_info.get("tables", []))
248
+
249
+ if not para_blocks or not pymu_raw_blocks: # 只有图片的拼接的场景
250
+ for img in all_page_images:
251
+ content_node = {
252
+ "type": "image",
253
+ "img_path": join_path(img_buket_path, img['image_path']),
254
+ "img_alt":"",
255
+ "img_title":"",
256
+ "img_caption":""
257
+ }
258
+ page_lst.append(content_node) # TODO 图片顺序
259
+ for table in all_page_tables:
260
+ content_node = {
261
+ "type": "table",
262
+ "img_path": join_path(img_buket_path, table['image_path']),
263
+ "table_latex": table.get("text"),
264
+ "table_title": "",
265
+ "table_caption": "",
266
+ "table_quality": table.get("quality"),
267
+ }
268
+ page_lst.append(content_node) # TODO 图片顺序
269
+ else:
270
+ for block in para_blocks:
271
+ item = block["paras"]
272
+ for _, p in item.items():
273
+ font_type = p['para_font_type']# 对于文本来说,要么是普通文本,要么是个行间公式
274
+ if font_type == TYPE_INTERLINE_EQUATION:
275
+ content_node = {
276
+ "type": "equation",
277
+ "latex": p["para_text"]
278
+ }
279
+ page_lst.append(content_node)
280
+ else:
281
+ para_text = p["para_text"]
282
+ is_title = p["is_para_title"]
283
+ title_level = p['para_title_level']
284
+
285
+ if is_title:
286
+ content_node = {
287
+ "type": f"h{title_level}",
288
+ "text": para_text
289
+ }
290
+ page_lst.append(content_node)
291
+ else:
292
+ content_node = {
293
+ "type": "text",
294
+ "text": para_text
295
+ }
296
+ page_lst.append(content_node)
297
+
298
+ content_lst.extend(page_lst)
299
+
300
+ """插入图片"""
301
+ for img in all_page_images:
302
+ insert_img_or_table("image", img, pymu_raw_blocks, content_lst)
303
+
304
+ """插入表格"""
305
+ for table in all_page_tables:
306
+ insert_img_or_table("table", table, pymu_raw_blocks, content_lst)
307
+ # end for
308
+ return content_lst
309
+
310
+
311
+ def insert_img_or_table(type, element, pymu_raw_blocks, content_lst):
312
+ element_bbox = element['bbox']
313
+ # 先看在哪个block内
314
+ for block in pymu_raw_blocks:
315
+ bbox = block['bbox']
316
+ if bbox[0] - 1 <= element_bbox[0] < bbox[2] + 1 and bbox[1] - 1 <= element_bbox[1] < bbox[
317
+ 3] + 1: # 确定在这个大的block内,然后进入逐行比较距离
318
+ for l in block['lines']:
319
+ line_box = l['bbox']
320
+ if line_box[0] - 1 <= element_bbox[0] < line_box[2] + 1 and line_box[1] - 1 <= element_bbox[1] < line_box[
321
+ 3] + 1: # 在line内的,插入line前面
322
+ line_txt = "".join([s['text'] for s in l['spans']])
323
+ __insert_before_para(line_txt, type, element, content_lst)
324
+ break
325
+ break
326
+ else: # 在行与行之间
327
+ # 找到图片x0,y0与line的x0,y0最近的line
328
+ min_distance = 100000
329
+ min_line = None
330
+ for l in block['lines']:
331
+ line_box = l['bbox']
332
+ distance = math.sqrt((line_box[0] - element_bbox[0]) ** 2 + (line_box[1] - element_bbox[1]) ** 2)
333
+ if distance < min_distance:
334
+ min_distance = distance
335
+ min_line = l
336
+ if min_line:
337
+ line_txt = "".join([s['text'] for s in min_line['spans']])
338
+ img_h = element_bbox[3] - element_bbox[1]
339
+ if min_distance < img_h: # 文字在图片前面
340
+ __insert_after_para(line_txt, type, element, content_lst)
341
+ else:
342
+ __insert_before_para(line_txt, type, element, content_lst)
343
+ break
344
+ else:
345
+ logger.error(f"Can't find the location of image {element.get('image_path')} in the markdown file #1")
346
+ else: # 应当在两个block之间
347
+ # 找到上方最近的block,如果上方没有就找大下方最近的block
348
+ top_txt_block = find_top_nearest_text_bbox(pymu_raw_blocks, element_bbox)
349
+ if top_txt_block:
350
+ line_txt = "".join([s['text'] for s in top_txt_block['lines'][-1]['spans']])
351
+ __insert_after_para(line_txt, type, element, content_lst)
352
+ else:
353
+ bottom_txt_block = find_bottom_nearest_text_bbox(pymu_raw_blocks, element_bbox)
354
+ if bottom_txt_block:
355
+ line_txt = "".join([s['text'] for s in bottom_txt_block['lines'][0]['spans']])
356
+ __insert_before_para(line_txt, type, element, content_lst)
357
+ else: # TODO ,图片可能独占一列,这种情况上下是没有图片的
358
+ logger.error(f"Can't find the location of image {element.get('image_path')} in the markdown file #2")
359
+
360
+
361
+ def mk_mm_markdown(content_list):
362
+ """
363
+ 基于同一格式的内容列表,构造markdown,含图片
364
+ """
365
+ content_md = []
366
+ for c in content_list:
367
+ content_type = c.get("type")
368
+ if content_type == "text":
369
+ content_md.append(c.get("text"))
370
+ elif content_type == "equation":
371
+ content = c.get("latex")
372
+ if content.startswith("$$") and content.endswith("$$"):
373
+ content_md.append(content)
374
+ else:
375
+ content_md.append(f"\n$$\n{c.get('latex')}\n$$\n")
376
+ elif content_type in UNI_FORMAT_TEXT_TYPE:
377
+ content_md.append(f"{'#'*int(content_type[1])} {c.get('text')}")
378
+ elif content_type == "image":
379
+ content_md.append(f"![]({c.get('img_path')})")
380
+ return "\n\n".join(content_md)
381
+
382
+ def mk_nlp_markdown(content_list):
383
+ """
384
+ 基于同一格式的内容列表,构造markdown,不含图片
385
+ """
386
+ content_md = []
387
+ for c in content_list:
388
+ content_type = c.get("type")
389
+ if content_type == "text":
390
+ content_md.append(c.get("text"))
391
+ elif content_type == "equation":
392
+ content_md.append(f"$$\n{c.get('latex')}\n$$")
393
+ elif content_type == "table":
394
+ content_md.append(f"$$$\n{c.get('table_latex')}\n$$$")
395
+ elif content_type in UNI_FORMAT_TEXT_TYPE:
396
+ content_md.append(f"{'#'*int(content_type[1])} {c.get('text')}")
397
+ return "\n\n".join(content_md)