Coverage for rendercv/renderer/renderer.py: 100%
81 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-12-25 23:06 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-12-25 23:06 +0000
1"""
2The `rendercv.renderer.renderer` module contains the necessary functions for rendering
3$\\LaTeX$, PDF, Markdown, HTML, and PNG files from the `RenderCVDataModel` object.
4"""
6import importlib.resources
7import pathlib
8import re
9import shutil
10from typing import Optional
12import fitz
13import markdown
14import rendercv_tinytex
16from .. import data
17from . import templater
20def copy_theme_files_to_output_directory(
21 theme_name: str,
22 output_directory_path: pathlib.Path,
23):
24 """Copy the auxiliary files (all the files that don't end with `.j2.tex` and `.py`)
25 of the theme to the output directory. For example, a theme can have custom
26 fonts, and the $\\LaTeX$ needs it. If the theme is a custom theme, then it will be
27 copied from the current working directory.
29 Args:
30 theme_name: The name of the theme.
31 output_directory_path: Path to the output directory.
32 """
33 if theme_name in data.available_themes:
34 theme_directory_path = importlib.resources.files(
35 f"rendercv.themes.{theme_name}"
36 )
37 else:
38 # Then it means the theme is a custom theme. If theme_directory is not given
39 # as an argument, then look for the theme in the current working directory.
40 theme_directory_path = pathlib.Path.cwd() / theme_name
42 if not theme_directory_path.is_dir():
43 message = (
44 f"The theme {theme_name} doesn't exist in the available themes and"
45 " the current working directory!"
46 )
47 raise FileNotFoundError(message)
49 for theme_file in theme_directory_path.iterdir():
50 dont_copy_files_with_these_extensions = [".j2.tex", ".py"]
51 # theme_file.suffix returns the latest part of the file name after the last dot.
52 # But we need the latest part of the file name after the first dot:
53 try:
54 suffix = re.search(r"\..*", theme_file.name)[0] # type: ignore
55 except TypeError:
56 suffix = ""
58 if suffix not in dont_copy_files_with_these_extensions:
59 if theme_file.is_dir():
60 shutil.copytree(
61 str(theme_file),
62 output_directory_path / theme_file.name,
63 dirs_exist_ok=True,
64 )
65 else:
66 shutil.copyfile(
67 str(theme_file), output_directory_path / theme_file.name
68 )
71def create_a_latex_file(
72 rendercv_data_model: data.RenderCVDataModel, output_directory: pathlib.Path
73) -> pathlib.Path:
74 """Render the $\\LaTeX$ file with the given data model and write it to the output
75 directory.
77 Args:
78 rendercv_data_model: The data model.
79 output_directory: Path to the output directory.
81 Returns:
82 The path to the generated $\\LaTeX$ file.
83 """
84 # Create output directory if it doesn't exist:
85 if not output_directory.is_dir():
86 output_directory.mkdir(parents=True)
88 jinja2_environment = templater.setup_jinja2_environment()
89 latex_file_object = templater.LaTeXFile(
90 rendercv_data_model,
91 jinja2_environment,
92 )
94 latex_file_name = f"{str(rendercv_data_model.cv.name).replace(' ', '_')}_CV.tex"
95 latex_file_path = output_directory / latex_file_name
96 latex_file_object.create_file(latex_file_path)
98 return latex_file_path
101def create_a_markdown_file(
102 rendercv_data_model: data.RenderCVDataModel, output_directory: pathlib.Path
103) -> pathlib.Path:
104 """Render the Markdown file with the given data model and write it to the output
105 directory.
107 Args:
108 rendercv_data_model: The data model.
109 output_directory: Path to the output directory.
111 Returns:
112 The path to the rendered Markdown file.
113 """
114 # create output directory if it doesn't exist:
115 if not output_directory.is_dir():
116 output_directory.mkdir(parents=True)
118 jinja2_environment = templater.setup_jinja2_environment()
119 markdown_file_object = templater.MarkdownFile(
120 rendercv_data_model,
121 jinja2_environment,
122 )
124 markdown_file_name = f"{str(rendercv_data_model.cv.name).replace(' ', '_')}_CV.md"
125 markdown_file_path = output_directory / markdown_file_name
126 markdown_file_object.create_file(markdown_file_path)
128 return markdown_file_path
131def create_a_latex_file_and_copy_theme_files(
132 rendercv_data_model: data.RenderCVDataModel, output_directory: pathlib.Path
133) -> pathlib.Path:
134 """Render the $\\LaTeX$ file with the given data model in the output directory and
135 copy the auxiliary theme files to the output directory.
137 Args:
138 rendercv_data_model: The data model.
139 output_directory: Path to the output directory.
141 Returns:
142 The path to the rendered $\\LaTeX$ file.
143 """
144 latex_file_path = create_a_latex_file(rendercv_data_model, output_directory)
145 copy_theme_files_to_output_directory(
146 rendercv_data_model.design.theme, output_directory
147 )
149 # Copy the profile picture to the output directory, if it exists:
150 if rendercv_data_model.cv.photo:
151 shutil.copyfile(
152 rendercv_data_model.cv.photo,
153 output_directory / rendercv_data_model.cv.photo.name,
154 )
155 return latex_file_path
158def render_a_pdf_from_latex(
159 latex_file_path: pathlib.Path, local_latex_command: Optional[str] = None
160) -> pathlib.Path:
161 """Run TinyTeX with the given $\\LaTeX$ file to render the PDF.
163 Args:
164 latex_file_path: The path to the $\\LaTeX$ file.
166 Returns:
167 The path to the rendered PDF file.
168 """
169 return rendercv_tinytex.run_latex(latex_file_path, local_latex_command)
172def render_pngs_from_pdf(pdf_file_path: pathlib.Path) -> list[pathlib.Path]:
173 """Render a PNG file for each page of the given PDF file.
175 Args:
176 pdf_file_path: The path to the PDF file.
178 Returns:
179 The paths to the rendered PNG files.
180 """
181 # check if the file exists:
182 if not pdf_file_path.is_file():
183 message = f"The file {pdf_file_path} doesn't exist!"
184 raise FileNotFoundError(message)
186 # convert the PDF to PNG:
187 png_directory = pdf_file_path.parent
188 png_file_name = pdf_file_path.stem
189 png_files = []
190 pdf = fitz.open(pdf_file_path) # open the PDF file
191 for page in pdf: # iterate the pages
192 image = page.get_pixmap(dpi=300) # type: ignore
193 png_file_path = png_directory / f"{png_file_name}_{page.number + 1}.png" # type: ignore
194 image.save(png_file_path)
195 png_files.append(png_file_path)
197 return png_files
200def render_an_html_from_markdown(markdown_file_path: pathlib.Path) -> pathlib.Path:
201 """Render an HTML file from a Markdown file with the same name and in the same
202 directory. It uses `rendercv/themes/main.j2.html` as the Jinja2 template.
204 Args:
205 markdown_file_path: The path to the Markdown file.
207 Returns:
208 The path to the rendered HTML file.
209 """
210 # check if the file exists:
211 if not markdown_file_path.is_file():
212 message = f"The file {markdown_file_path} doesn't exist!"
213 raise FileNotFoundError(message)
215 # Convert the markdown file to HTML:
216 markdown_text = markdown_file_path.read_text(encoding="utf-8")
217 html_body = markdown.markdown(markdown_text)
219 # Get the title of the markdown content:
220 title = re.search(r"# (.*)\n", markdown_text)
221 title = title.group(1) if title else None
223 jinja2_environment = templater.setup_jinja2_environment()
224 html_template = jinja2_environment.get_template("main.j2.html")
225 html = html_template.render(html_body=html_body, title=title)
227 # Write html into a file:
228 html_file_path = markdown_file_path.parent / f"{markdown_file_path.stem}.html"
229 html_file_path.write_text(html, encoding="utf-8")
231 return html_file_path