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.
- rxiv_maker/__version__.py +1 -1
- rxiv_maker/cli/framework/workflow_commands.py +3 -1
- rxiv_maker/exporters/docx_citation_mapper.py +3 -84
- rxiv_maker/exporters/docx_content_processor.py +5 -23
- rxiv_maker/exporters/docx_exporter.py +14 -28
- rxiv_maker/exporters/docx_writer.py +201 -75
- rxiv_maker/processors/template_processor.py +10 -0
- rxiv_maker/templates/registry.py +52 -12
- rxiv_maker/tex/template.tex +2 -0
- rxiv_maker/utils/accent_character_map.py +150 -0
- rxiv_maker/utils/author_affiliation_processor.py +128 -0
- rxiv_maker/utils/citation_range_formatter.py +118 -0
- rxiv_maker/utils/comment_filter.py +46 -0
- rxiv_maker/utils/docx_helpers.py +4 -117
- rxiv_maker/utils/label_extractor.py +185 -0
- {rxiv_maker-1.17.0.dist-info → rxiv_maker-1.18.1.dist-info}/METADATA +1 -1
- {rxiv_maker-1.17.0.dist-info → rxiv_maker-1.18.1.dist-info}/RECORD +20 -15
- {rxiv_maker-1.17.0.dist-info → rxiv_maker-1.18.1.dist-info}/WHEEL +0 -0
- {rxiv_maker-1.17.0.dist-info → rxiv_maker-1.18.1.dist-info}/entry_points.txt +0 -0
- {rxiv_maker-1.17.0.dist-info → rxiv_maker-1.18.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
|
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=
|
|
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
|
-
#
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
|
238
|
-
for
|
|
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(
|
|
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.
|
|
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
|
|
484
|
+
self._apply_highlight(run, self.get_xref_color(xref_type))
|
|
375
485
|
if run_data.get("highlight_yellow"):
|
|
376
|
-
run
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
|
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
|
|
550
|
+
self._apply_highlight(run, self.get_xref_color(xref_type))
|
|
440
551
|
if run_data.get("highlight_yellow"):
|
|
441
|
-
run
|
|
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
|
|
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
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
|
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
|
|
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
|
-
#
|
|
581
|
-
|
|
582
|
-
|
|
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 >
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
786
|
+
self._apply_highlight(run, self.get_xref_color(xref_type))
|
|
662
787
|
if run_data.get("highlight_yellow"):
|
|
663
|
-
run
|
|
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
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
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(
|
|
680
|
-
run
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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:
|
rxiv_maker/templates/registry.py
CHANGED
|
@@ -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
|
-
##
|
|
193
|
+
## Data Availability
|
|
194
194
|
|
|
195
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
##
|
|
524
|
+
## Acknowledgements
|
|
489
525
|
|
|
490
|
-
|
|
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
|
-
##
|
|
528
|
+
## Funding
|
|
493
529
|
|
|
494
|
-
|
|
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
|
|