rxiv-maker 1.17.0__py3-none-any.whl → 1.18.1__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.
@@ -4,6 +4,7 @@ This module handles the actual generation of DOCX files using python-docx,
4
4
  writing structured content with formatting, citations, and references.
5
5
  """
6
6
 
7
+ import base64
7
8
  from pathlib import Path
8
9
  from typing import Any, Dict, Optional
9
10
 
@@ -11,11 +12,12 @@ from docx import Document
11
12
  from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_COLOR_INDEX
12
13
  from docx.oxml import OxmlElement
13
14
  from docx.oxml.ns import qn
14
- from docx.shared import Inches, Pt
15
+ from docx.shared import Pt, RGBColor
15
16
  from latex2mathml.converter import convert as latex_to_mathml
16
17
  from lxml import etree
17
18
 
18
19
  from ..core.logging_config import get_logger
20
+ from ..utils.author_affiliation_processor import AuthorAffiliationProcessor
19
21
  from ..utils.docx_helpers import convert_pdf_to_image
20
22
 
21
23
  logger = get_logger()
@@ -57,6 +59,8 @@ class DocxWriter:
57
59
  metadata: Optional[Dict[str, Any]] = None,
58
60
  table_map: Optional[Dict[str, int]] = None,
59
61
  figures_at_end: bool = False,
62
+ hide_highlighting: bool = False,
63
+ hide_comments: bool = False,
60
64
  ) -> Path:
61
65
  """Write DOCX file from structured content.
62
66
 
@@ -69,6 +73,8 @@ class DocxWriter:
69
73
  metadata: Document metadata (title, authors, affiliations)
70
74
  table_map: Mapping from table labels to numbers (for supplementary tables)
71
75
  figures_at_end: Place main figures at end before SI/bibliography
76
+ hide_highlighting: Disable colored highlighting on references and citations
77
+ hide_comments: Exclude all comments (block and inline) from output
72
78
 
73
79
  Returns:
74
80
  Path to created DOCX file
@@ -77,8 +83,13 @@ class DocxWriter:
77
83
  self.bibliography = bibliography
78
84
  self.include_footnotes = include_footnotes
79
85
  self.table_map = table_map or {}
86
+ self.hide_highlighting = hide_highlighting
87
+ self.hide_comments = hide_comments
80
88
  doc = Document()
81
89
 
90
+ # Set default font to Arial for entire document
91
+ self._set_default_font(doc, "Arial")
92
+
82
93
  # Add title and author information if metadata provided
83
94
  if metadata:
84
95
  self._add_title_page(doc, metadata)
@@ -123,14 +134,18 @@ class DocxWriter:
123
134
  # Add collected main figures at the end (before bibliography)
124
135
  if figures_at_end and collected_main_figures:
125
136
  doc.add_page_break()
126
- doc.add_heading("Figures", level=1)
137
+ heading = doc.add_heading("Figures", level=1)
138
+ for run in heading.runs:
139
+ run.font.color.rgb = RGBColor(0, 0, 0) # Ensure black text
127
140
  for section, fig_num in collected_main_figures:
128
141
  self._add_figure(doc, section, figure_number=fig_num, is_supplementary=False)
129
142
 
130
143
  # Add bibliography section at the end
131
144
  if include_footnotes and bibliography:
132
145
  doc.add_page_break()
133
- doc.add_heading("Bibliography", level=1)
146
+ heading = doc.add_heading("Bibliography", level=1)
147
+ for run in heading.runs:
148
+ run.font.color.rgb = RGBColor(0, 0, 0) # Ensure black text
134
149
 
135
150
  # Add numbered bibliography entries
136
151
  for num in sorted(bibliography.keys()):
@@ -144,12 +159,12 @@ class DocxWriter:
144
159
  # Add formatted bibliography text (without DOI - added separately below)
145
160
  para.add_run(bib_entry["formatted"])
146
161
 
147
- # Add DOI as hyperlink with yellow highlighting if present
162
+ # Add DOI as hyperlink with yellow highlighting if present (unless hide_highlighting is enabled)
148
163
  if bib_entry.get("doi"):
149
164
  doi = bib_entry["doi"]
150
165
  doi_url = f"https://doi.org/{doi}" if not doi.startswith("http") else doi
151
166
  para.add_run("\nDOI: ")
152
- self._add_hyperlink(para, doi_url, doi_url, highlight=True)
167
+ self._add_hyperlink(para, doi_url, doi_url, highlight=not self.hide_highlighting)
153
168
 
154
169
  # Add spacing between entries
155
170
  para.paragraph_format.space_after = Pt(6)
@@ -158,6 +173,38 @@ class DocxWriter:
158
173
  doc.save(str(output_path))
159
174
  return output_path
160
175
 
176
+ def _set_default_font(self, doc: Document, font_name: str):
177
+ """Set the default font for the entire document.
178
+
179
+ Args:
180
+ doc: Document object
181
+ font_name: Font name to use (e.g., "Arial", "Times New Roman")
182
+ """
183
+ # Set font on Normal style (base style for most content)
184
+ style = doc.styles["Normal"]
185
+ font = style.font
186
+ font.name = font_name
187
+ font.size = Pt(10) # Default body font size
188
+
189
+ # Also set on heading styles to ensure consistency
190
+ for i in range(1, 10):
191
+ try:
192
+ heading_style = doc.styles[f"Heading {i}"]
193
+ heading_style.font.name = font_name
194
+ except KeyError:
195
+ # Heading style doesn't exist, skip
196
+ pass
197
+
198
+ def _apply_highlight(self, run, color: WD_COLOR_INDEX):
199
+ """Apply highlight color to a run, unless highlighting is disabled.
200
+
201
+ Args:
202
+ run: The run object to apply highlighting to
203
+ color: The WD_COLOR_INDEX color to apply
204
+ """
205
+ if not self.hide_highlighting:
206
+ run.font.highlight_color = color
207
+
161
208
  def _add_title_page(self, doc: Document, metadata: Dict[str, Any]):
162
209
  """Add title, author and affiliation information.
163
210
 
@@ -193,27 +240,16 @@ class DocxWriter:
193
240
  if not authors:
194
241
  return # Nothing more to add
195
242
 
196
- # Collect unique affiliations and build mapping
197
- all_affiliations = []
198
- affiliation_map = {} # Maps affiliation shortname to number
199
-
200
- # Get full affiliation details from metadata
201
- affiliation_details = {a.get("shortname"): a for a in metadata.get("affiliations", [])}
202
-
203
- for author in authors:
204
- author_affils = author.get("affiliations", [])
205
- for affil_shortname in author_affils:
206
- if affil_shortname not in affiliation_map:
207
- affiliation_map[affil_shortname] = len(affiliation_map) + 1
208
- # Look up full affiliation info
209
- affil_info = affiliation_details.get(affil_shortname, {})
210
- full_name = affil_info.get("full_name", affil_shortname)
211
- location = affil_info.get("location", "")
212
- # Format: "Full Name, Location" or just "Full Name" if no location
213
- affil_text = f"{full_name}, {location}" if location else full_name
214
- all_affiliations.append(affil_text)
215
-
216
- # Add authors with superscript affiliation numbers
243
+ # Process author and affiliation metadata using centralized processor
244
+ processor = AuthorAffiliationProcessor()
245
+ processed = processor.process(metadata)
246
+
247
+ affiliation_map = processed["affiliation_map"]
248
+ ordered_affiliations = processed["ordered_affiliations"]
249
+ cofirst_authors = processed["cofirst_authors"]
250
+ corresponding_authors = processed["corresponding_authors"]
251
+
252
+ # Add authors with superscript affiliation numbers and corresponding author markers
217
253
  if authors:
218
254
  author_para = doc.add_paragraph()
219
255
  for i, author in enumerate(authors):
@@ -231,25 +267,94 @@ class DocxWriter:
231
267
  sup_run = author_para.add_run(",".join(affil_nums))
232
268
  sup_run.font.superscript = True
233
269
 
270
+ # Add co-first author marker (dagger) if applicable
271
+ is_cofirst = author.get("co_first_author", False)
272
+ if is_cofirst:
273
+ cofirst_run = author_para.add_run("†")
274
+ cofirst_run.font.superscript = True
275
+
276
+ # Add corresponding author marker (asterisk) if applicable
277
+ is_corresponding = author.get("corresponding_author", False)
278
+ if is_corresponding:
279
+ corr_run = author_para.add_run("*")
280
+ corr_run.font.superscript = True
281
+
234
282
  author_para.paragraph_format.space_after = Pt(8)
235
283
 
236
284
  # Add affiliations
237
- if all_affiliations:
238
- for i, affil_text in enumerate(all_affiliations, start=1):
285
+ if ordered_affiliations:
286
+ for affil_num, _affil_shortname, affil_text in ordered_affiliations:
239
287
  affil_para = doc.add_paragraph()
240
288
 
241
289
  # Add superscript number
242
- num_run = affil_para.add_run(str(i))
290
+ num_run = affil_para.add_run(str(affil_num))
243
291
  num_run.font.superscript = True
292
+ num_run.font.size = Pt(8)
244
293
 
245
294
  # Add affiliation text
246
- affil_para.add_run(f" {affil_text}")
295
+ affil_run = affil_para.add_run(f" {affil_text}")
296
+ affil_run.font.size = Pt(8)
247
297
  affil_para.paragraph_format.space_after = Pt(4)
248
- affil_para.runs[1].font.size = Pt(10)
249
298
 
250
299
  # Extra space after last affiliation
251
300
  affil_para.paragraph_format.space_after = Pt(12)
252
301
 
302
+ # Add co-first author information if any (already extracted by processor)
303
+ if cofirst_authors:
304
+ cofirst_para = doc.add_paragraph()
305
+ cofirst_marker = cofirst_para.add_run("†")
306
+ cofirst_marker.font.superscript = True
307
+ cofirst_marker.font.size = Pt(8)
308
+
309
+ cofirst_label = cofirst_para.add_run(" These authors contributed equally: ")
310
+ cofirst_label.font.size = Pt(8)
311
+
312
+ for i, author in enumerate(cofirst_authors):
313
+ if i > 0:
314
+ sep_run = cofirst_para.add_run(", ")
315
+ sep_run.font.size = Pt(8)
316
+
317
+ name = author.get("name", "")
318
+ name_run = cofirst_para.add_run(name)
319
+ name_run.font.size = Pt(8)
320
+
321
+ cofirst_para.paragraph_format.space_after = Pt(12)
322
+
323
+ # Add corresponding author information if any (already extracted by processor)
324
+ if corresponding_authors:
325
+ corr_para = doc.add_paragraph()
326
+ corr_marker = corr_para.add_run("*")
327
+ corr_marker.font.superscript = True
328
+ corr_marker.font.size = Pt(8)
329
+
330
+ corr_label = corr_para.add_run(" Correspondence: ")
331
+ corr_label.font.size = Pt(8)
332
+
333
+ for i, author in enumerate(corresponding_authors):
334
+ if i > 0:
335
+ sep_run = corr_para.add_run("; ")
336
+ sep_run.font.size = Pt(8)
337
+
338
+ name = author.get("name", "")
339
+ email = author.get("email", "")
340
+
341
+ # Decode email if it's base64 encoded
342
+ if not email:
343
+ email64 = author.get("email64", "")
344
+ if email64:
345
+ try:
346
+ email = base64.b64decode(email64).decode("utf-8")
347
+ except Exception:
348
+ email = ""
349
+
350
+ if email:
351
+ info_run = corr_para.add_run(f"{name} ({email})")
352
+ else:
353
+ info_run = corr_para.add_run(name)
354
+ info_run.font.size = Pt(8)
355
+
356
+ corr_para.paragraph_format.space_after = Pt(12)
357
+
253
358
  def _add_section(
254
359
  self,
255
360
  doc: Document,
@@ -278,7 +383,8 @@ class DocxWriter:
278
383
  elif section_type == "code_block":
279
384
  self._add_code_block(doc, section)
280
385
  elif section_type == "comment":
281
- self._add_comment(doc, section)
386
+ if not self.hide_comments:
387
+ self._add_comment(doc, section)
282
388
  elif section_type == "figure":
283
389
  self._add_figure(doc, section)
284
390
  elif section_type == "table":
@@ -304,6 +410,7 @@ class DocxWriter:
304
410
  run = para.add_run(text)
305
411
  run.bold = True
306
412
  run.font.size = Pt(12)
413
+ run.font.color.rgb = RGBColor(0, 0, 0) # Ensure black text
307
414
 
308
415
  def _add_heading(self, doc: Document, section: Dict[str, Any]):
309
416
  """Add heading to document.
@@ -314,7 +421,10 @@ class DocxWriter:
314
421
  """
315
422
  level = section["level"]
316
423
  text = section["text"]
317
- doc.add_heading(text, level=level)
424
+ heading = doc.add_heading(text, level=level)
425
+ # Ensure heading text is black (not blue)
426
+ for run in heading.runs:
427
+ run.font.color.rgb = RGBColor(0, 0, 0) # Explicitly set to black
318
428
 
319
429
  def _add_paragraph(
320
430
  self,
@@ -371,9 +481,9 @@ class DocxWriter:
371
481
  if run_data.get("xref"):
372
482
  # Use color based on xref type (fig, sfig, stable, eq, etc.)
373
483
  xref_type = run_data.get("xref_type", "cite")
374
- run.font.highlight_color = self.get_xref_color(xref_type)
484
+ self._apply_highlight(run, self.get_xref_color(xref_type))
375
485
  if run_data.get("highlight_yellow"):
376
- run.font.highlight_color = WD_COLOR_INDEX.YELLOW
486
+ self._apply_highlight(run, WD_COLOR_INDEX.YELLOW)
377
487
 
378
488
  elif run_data["type"] == "hyperlink":
379
489
  # Add hyperlink with yellow highlighting
@@ -387,18 +497,19 @@ class DocxWriter:
387
497
  self._add_inline_equation(paragraph, latex_content)
388
498
 
389
499
  elif run_data["type"] == "inline_comment":
390
- # Add inline comment with gray highlighting
391
- comment_text = run_data["text"]
392
- run = paragraph.add_run(f"[Comment: {comment_text}]")
393
- run.font.highlight_color = WD_COLOR_INDEX.GRAY_25
394
- run.italic = True
395
- run.font.size = Pt(10)
500
+ # Add inline comment with gray highlighting (unless hide_comments is enabled)
501
+ if not self.hide_comments:
502
+ comment_text = run_data["text"]
503
+ run = paragraph.add_run(f"[Comment: {comment_text}]")
504
+ self._apply_highlight(run, WD_COLOR_INDEX.GRAY_25)
505
+ run.italic = True
506
+ run.font.size = Pt(10)
396
507
 
397
508
  elif run_data["type"] == "citation":
398
509
  cite_num = run_data["number"]
399
510
  # Add citation as [NN] inline with yellow highlighting
400
511
  run = paragraph.add_run(f"[{cite_num}]")
401
- run.font.highlight_color = WD_COLOR_INDEX.YELLOW
512
+ self._apply_highlight(run, WD_COLOR_INDEX.YELLOW)
402
513
  run.font.size = Pt(10)
403
514
 
404
515
  def _add_list(self, doc: Document, section: Dict[str, Any]):
@@ -436,12 +547,12 @@ class DocxWriter:
436
547
  if run_data.get("xref"):
437
548
  # Use color based on xref type
438
549
  xref_type = run_data.get("xref_type", "cite")
439
- run.font.highlight_color = self.get_xref_color(xref_type)
550
+ self._apply_highlight(run, self.get_xref_color(xref_type))
440
551
  if run_data.get("highlight_yellow"):
441
- run.font.highlight_color = WD_COLOR_INDEX.YELLOW
552
+ self._apply_highlight(run, WD_COLOR_INDEX.YELLOW)
442
553
  run.font.size = Pt(10)
443
554
  if run_data.get("highlight_yellow"):
444
- run.font.highlight_color = WD_COLOR_INDEX.YELLOW
555
+ self._apply_highlight(run, WD_COLOR_INDEX.YELLOW)
445
556
  elif run_data["type"] == "hyperlink":
446
557
  text = run_data.get("text", "")
447
558
  url = run_data.get("url", "")
@@ -451,18 +562,19 @@ class DocxWriter:
451
562
  latex_content = run_data.get("latex", "")
452
563
  self._add_inline_equation(paragraph, latex_content)
453
564
  elif run_data["type"] == "inline_comment":
454
- # Add inline comment with gray highlighting
455
- comment_text = run_data["text"]
456
- run = paragraph.add_run(f"[Comment: {comment_text}]")
457
- run.font.highlight_color = WD_COLOR_INDEX.GRAY_25
458
- run.italic = True
459
- run.font.size = Pt(10)
565
+ # Add inline comment with gray highlighting (unless hide_comments is enabled)
566
+ if not self.hide_comments:
567
+ comment_text = run_data["text"]
568
+ run = paragraph.add_run(f"[Comment: {comment_text}]")
569
+ self._apply_highlight(run, WD_COLOR_INDEX.GRAY_25)
570
+ run.italic = True
571
+ run.font.size = Pt(10)
460
572
  elif run_data["type"] == "citation":
461
573
  cite_num = run_data["number"]
462
574
  run = paragraph.add_run(f"[{cite_num}]")
463
575
  run.bold = True
464
576
  run.font.size = Pt(10)
465
- run.font.highlight_color = WD_COLOR_INDEX.YELLOW
577
+ self._apply_highlight(run, WD_COLOR_INDEX.YELLOW)
466
578
 
467
579
  def _add_code_block(self, doc: Document, section: Dict[str, Any]):
468
580
  """Add code block to document.
@@ -495,7 +607,7 @@ class DocxWriter:
495
607
 
496
608
  # Add comment text with light gray highlighting to distinguish from colored xrefs
497
609
  run = paragraph.add_run(f"[Comment: {comment_text}]")
498
- run.font.highlight_color = WD_COLOR_INDEX.GRAY_25
610
+ self._apply_highlight(run, WD_COLOR_INDEX.GRAY_25)
499
611
  run.italic = True
500
612
  run.font.size = Pt(10)
501
613
 
@@ -577,9 +689,22 @@ class DocxWriter:
577
689
  img_width, img_height = img.size
578
690
  aspect_ratio = img_width / img_height
579
691
 
580
- # Page dimensions with margins (Letter size: 8.5 x 11 inches, 1 inch margins)
581
- max_width = Inches(6.5) # 8.5 - 2*1
582
- max_height = Inches(9) # 11 - 2*1
692
+ # Calculate available width from document section settings
693
+ # Get the current section to read actual page dimensions and margins
694
+ section = doc.sections[-1] # Use the most recent section
695
+
696
+ # Page width minus left and right margins
697
+ available_width = section.page_width - section.left_margin - section.right_margin
698
+
699
+ # Page height minus top and bottom margins
700
+ available_height = section.page_height - section.top_margin - section.bottom_margin
701
+
702
+ # Convert available width to Inches for comparison
703
+ max_width = available_width
704
+ max_height = available_height
705
+
706
+ # Calculate aspect ratio thresholds
707
+ page_aspect_ratio = available_width / available_height
583
708
 
584
709
  # Add figure centered
585
710
  # Note: add_picture() creates a paragraph automatically, but we need to add it explicitly
@@ -588,7 +713,7 @@ class DocxWriter:
588
713
  fig_para.alignment = WD_ALIGN_PARAGRAPH.CENTER
589
714
 
590
715
  # Calculate optimal size maintaining aspect ratio
591
- if aspect_ratio > (6.5 / 9): # Wide image - constrain by width
716
+ if aspect_ratio > page_aspect_ratio: # Wide image - constrain by width
592
717
  run = fig_para.add_run()
593
718
  run.add_picture(img_source, width=max_width)
594
719
  else: # Tall image - constrain by height
@@ -625,11 +750,11 @@ class DocxWriter:
625
750
  else:
626
751
  run = caption_para.add_run(f"Fig. {figure_number}. ")
627
752
  run.bold = True
628
- run.font.size = Pt(7)
753
+ run.font.size = Pt(8)
629
754
  else:
630
755
  run = caption_para.add_run("Figure: ")
631
756
  run.bold = True
632
- run.font.size = Pt(7)
757
+ run.font.size = Pt(8)
633
758
 
634
759
  # Parse and add caption with inline formatting
635
760
  # Import the processor to parse inline formatting
@@ -642,7 +767,7 @@ class DocxWriter:
642
767
  if run_data["type"] == "text":
643
768
  text = run_data["text"]
644
769
  run = caption_para.add_run(text)
645
- run.font.size = Pt(7)
770
+ run.font.size = Pt(8)
646
771
 
647
772
  # Apply formatting
648
773
  if run_data.get("bold"):
@@ -658,26 +783,27 @@ class DocxWriter:
658
783
  if run_data.get("xref"):
659
784
  # Use color based on xref type
660
785
  xref_type = run_data.get("xref_type", "cite")
661
- run.font.highlight_color = self.get_xref_color(xref_type)
786
+ self._apply_highlight(run, self.get_xref_color(xref_type))
662
787
  if run_data.get("highlight_yellow"):
663
- run.font.highlight_color = WD_COLOR_INDEX.YELLOW
788
+ self._apply_highlight(run, WD_COLOR_INDEX.YELLOW)
664
789
  elif run_data["type"] == "inline_equation":
665
790
  # Add inline equation as Office Math
666
791
  latex_content = run_data.get("latex", "")
667
792
  self._add_inline_equation(caption_para, latex_content)
668
793
  elif run_data["type"] == "inline_comment":
669
- # Add inline comment with gray highlighting
670
- comment_text = run_data["text"]
671
- run = caption_para.add_run(f"[Comment: {comment_text}]")
672
- run.font.highlight_color = WD_COLOR_INDEX.GRAY_25
673
- run.italic = True
674
- run.font.size = Pt(7)
794
+ # Add inline comment with gray highlighting (unless hide_comments is enabled)
795
+ if not self.hide_comments:
796
+ comment_text = run_data["text"]
797
+ run = caption_para.add_run(f"[Comment: {comment_text}]")
798
+ self._apply_highlight(run, WD_COLOR_INDEX.GRAY_25)
799
+ run.italic = True
800
+ run.font.size = Pt(8)
675
801
  elif run_data["type"] == "citation":
676
802
  cite_num = run_data["number"]
677
803
  run = caption_para.add_run(f"[{cite_num}]")
678
804
  run.bold = True
679
- run.font.size = Pt(7)
680
- run.font.highlight_color = WD_COLOR_INDEX.YELLOW
805
+ run.font.size = Pt(8)
806
+ self._apply_highlight(run, WD_COLOR_INDEX.YELLOW)
681
807
 
682
808
  # Add spacing after figure (reduced from 12 to 6 for compactness)
683
809
  caption_para.paragraph_format.space_after = Pt(6)
@@ -752,7 +878,7 @@ class DocxWriter:
752
878
  if run_data.get("xref"):
753
879
  # Use color based on xref type
754
880
  xref_type = run_data.get("xref_type", "cite")
755
- run.font.highlight_color = self.get_xref_color(xref_type)
881
+ self._apply_highlight(run, self.get_xref_color(xref_type))
756
882
 
757
883
  # Add table caption if present
758
884
  caption = section.get("caption")
@@ -775,7 +901,7 @@ class DocxWriter:
775
901
  # Fallback if label not in map
776
902
  run = caption_para.add_run("Supp. Table: ")
777
903
  run.bold = True
778
- run.font.size = Pt(7)
904
+ run.font.size = Pt(8)
779
905
  elif label and label.startswith("table:"):
780
906
  # Extract label name for main tables
781
907
  label_name = label.split(":", 1)[1] if ":" in label else label
@@ -786,7 +912,7 @@ class DocxWriter:
786
912
  else:
787
913
  run = caption_para.add_run("Table: ")
788
914
  run.bold = True
789
- run.font.size = Pt(7)
915
+ run.font.size = Pt(8)
790
916
 
791
917
  # Parse and add caption with inline formatting
792
918
  caption_runs = processor._parse_inline_formatting(caption, {})
@@ -794,7 +920,7 @@ class DocxWriter:
794
920
  if run_data["type"] == "text":
795
921
  text = run_data["text"]
796
922
  run = caption_para.add_run(text)
797
- run.font.size = Pt(7)
923
+ run.font.size = Pt(8)
798
924
  if run_data.get("bold"):
799
925
  run.bold = True
800
926
  if run_data.get("italic"):
@@ -810,7 +936,7 @@ class DocxWriter:
810
936
  if run_data.get("xref"):
811
937
  # Use color based on xref type
812
938
  xref_type = run_data.get("xref_type", "cite")
813
- run.font.highlight_color = self.get_xref_color(xref_type)
939
+ self._apply_highlight(run, self.get_xref_color(xref_type))
814
940
 
815
941
  # Add spacing after table (reduced from 12 to 6 for compactness)
816
942
  caption_para.paragraph_format.space_after = Pt(6)
@@ -639,6 +639,16 @@ def process_template_replacements(template_content, yaml_metadata, article_md, o
639
639
  acknowledgements_block = ""
640
640
  template_content = template_content.replace("<PY-RPL:ACKNOWLEDGEMENTS-BLOCK>", acknowledgements_block)
641
641
 
642
+ # Funding
643
+ funding = content_sections.get("funding", "").strip()
644
+ if funding:
645
+ funding_block = f"""\\begin{{funding}}
646
+ {funding}
647
+ \\end{{funding}}"""
648
+ else:
649
+ funding_block = ""
650
+ template_content = template_content.replace("<PY-RPL:FUNDING-BLOCK>", funding_block)
651
+
642
652
  # Competing Interests
643
653
  competing_interests = content_sections.get("competing_interests", "").strip()
644
654
  if competing_interests:
@@ -190,9 +190,29 @@ Interpret your results in the context of existing literature and theory. Discuss
190
190
 
191
191
  Summarize your key findings and their significance. Reinforce the main contributions of your work and their broader impact on the field. Keep this section concise but impactful, leaving readers with a clear understanding of what your research has accomplished and why it matters.
192
192
 
193
- ## References
193
+ ## Data Availability
194
194
 
195
- Citations are automatically formatted from 03_REFERENCES.bib. Reference works using citation keys like [@smith2023] for single citations or [@smith2023; @johnson2022] for multiple citations.
195
+ Describe where the data supporting the findings of this study are available. Include repository names, accession numbers, DOIs, or URLs. If data are available upon request, state this clearly with contact information.
196
+
197
+ ## Code Availability
198
+
199
+ Provide information about the availability of code, software, or algorithms used in this study. Include repository URLs (e.g., GitHub), software package names with version numbers, and any licensing information.
200
+
201
+ ## Author Contributions
202
+
203
+ Describe the specific contributions of each author to the work. Use initials to identify authors and describe their roles (e.g., "A.B. and C.D. designed the study. A.B. performed experiments. C.D. analyzed data. All authors contributed to writing the manuscript.").
204
+
205
+ ## Acknowledgements
206
+
207
+ Acknowledge individuals, groups, or organizations that contributed to the work but do not meet authorship criteria. This may include technical assistance, discussions, or provision of materials.
208
+
209
+ ## Funding
210
+
211
+ Provide information about funding sources, grant numbers, and supporting organizations. If no external funding was received, state "This research received no external funding."
212
+
213
+ ## Competing Interests
214
+
215
+ The authors declare no competing interests.
196
216
  """
197
217
 
198
218
  def _get_default_supplementary_template(self) -> str:
@@ -353,10 +373,6 @@ Organize your content with additional sections as needed.
353
373
  ## Conclusions
354
374
 
355
375
  Summarize your conclusions.
356
-
357
- ## References
358
-
359
- Add citations using [@ref_key].
360
376
  """
361
377
 
362
378
  def _get_minimal_supplementary_template(self) -> str:
@@ -436,9 +452,29 @@ Suggest future research directions.
436
452
 
437
453
  Summarize key conclusions.
438
454
 
439
- ## References
455
+ ## Data Availability
456
+
457
+ Describe where the data supporting the findings of this study are available. Include repository names, accession numbers, DOIs, or URLs. If data are available upon request, state this clearly with contact information.
458
+
459
+ ## Code Availability
440
460
 
441
- [@ref]
461
+ Provide information about the availability of code, software, or algorithms used in this study. Include repository URLs (e.g., GitHub), software package names with version numbers, and any licensing information.
462
+
463
+ ## Author Contributions
464
+
465
+ Describe the specific contributions of each author to the work. Use initials to identify authors and describe their roles (e.g., "A.B. and C.D. designed the study. A.B. performed experiments. C.D. analyzed data. All authors contributed to writing the manuscript.").
466
+
467
+ ## Acknowledgements
468
+
469
+ Acknowledge individuals, groups, or organizations that contributed to the work but do not meet authorship criteria. This may include technical assistance, discussions, or provision of materials.
470
+
471
+ ## Funding
472
+
473
+ Provide information about funding sources, grant numbers, and supporting organizations. If no external funding was received, state "This research received no external funding."
474
+
475
+ ## Competing Interests
476
+
477
+ The authors declare no competing interests.
442
478
  """
443
479
 
444
480
  # Preprint template methods
@@ -485,13 +521,17 @@ Links to data repositories, code, and protocols for reproducibility.
485
521
 
486
522
  Detailed author contribution statements.
487
523
 
488
- ## Competing Interests
524
+ ## Acknowledgements
489
525
 
490
- Declaration of competing interests.
526
+ Acknowledge individuals, groups, or organizations that contributed to the work but do not meet authorship criteria. This may include technical assistance, discussions, or provision of materials.
491
527
 
492
- ## References
528
+ ## Funding
493
529
 
494
- [@ref]
530
+ Provide information about funding sources, grant numbers, and supporting organizations. If no external funding was received, state "This research received no external funding."
531
+
532
+ ## Competing Interests
533
+
534
+ Declaration of competing interests.
495
535
  """
496
536
 
497
537
 
@@ -48,6 +48,8 @@
48
48
 
49
49
  <PY-RPL:ACKNOWLEDGEMENTS-BLOCK>
50
50
 
51
+ <PY-RPL:FUNDING-BLOCK>
52
+
51
53
  <PY-RPL:COMPETING-INTERESTS-BLOCK>
52
54
 
53
55
  \begin{exauthor}