Coverage for graphica/core/caption_export.py: 100.0%

18 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-12 16:38 +0000

1"""論文用の LaTeX のコードとキャプションを作る。GUI には依存しない。""" 

2import re 

3 

4# 1文字ずつ1回だけ置き換える(順に replace すると、置き換えで生まれた \ まで二重にエスケープしてしまう) 

5_LATEX_SPECIAL_CHARS = { 

6 '\\': r'\textbackslash{}', 

7 '{': r'\{', 

8 '}': r'\}', 

9 '$': r'\$', 

10 '&': r'\&', 

11 '%': r'\%', 

12 '#': r'\#', 

13 '_': r'\_', 

14 '~': r'\textasciitilde{}', 

15 '^': r'\textasciicircum{}', 

16} 

17 

18DEFAULT_WIDTH = r'\linewidth' 

19 

20 

21def escape_latex(text: str) -> str: 

22 if not text: 

23 return '' 

24 return ''.join(_LATEX_SPECIAL_CHARS.get(ch, ch) for ch in text) 

25 

26 

27def sanitize_label(text: str, prefix: str = 'fig:') -> str: 

28 """タイトルなどから \\label{} に使える文字列を作る(英数字・-・_ だけ残す)。何も残らなければ既定の名前。""" 

29 cleaned = re.sub(r'[^A-Za-z0-9_-]+', '', (text or '').strip().replace(' ', '-')) 

30 # 日本語だけのタイトルなどで、ハイフンだけの意味の無いラベルにならないように 

31 if not re.search(r'[A-Za-z0-9]', cleaned): 

32 return f"{prefix}plot" 

33 return f"{prefix}{cleaned}" 

34 

35 

36def generate_latex_figure(image_filename: str, caption: str, label: str, width: str = DEFAULT_WIDTH) -> str: 

37 """figure 環境一式。image_filename が空なら 'figure.pdf'(後で差し替える前提)、label が空なら \\label 行を省く。""" 

38 lines = [ 

39 r'\begin{figure}[htbp]', 

40 r' \centering', 

41 f" \\includegraphics[width={width}]{{{image_filename or 'figure.pdf'}}}", 

42 f" \\caption{{{escape_latex(caption)}}}", 

43 ] 

44 if label: 

45 lines.append(f" \\label{{{label}}}") 

46 lines.append(r'\end{figure}') 

47 return '\n'.join(lines)