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

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""" 

5 

6import importlib.resources 

7import pathlib 

8import re 

9import shutil 

10from typing import Optional 

11 

12import fitz 

13import markdown 

14import rendercv_tinytex 

15 

16from .. import data 

17from . import templater 

18 

19 

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. 

28 

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 

41 

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) 

48 

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 = "" 

57 

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 ) 

69 

70 

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. 

76 

77 Args: 

78 rendercv_data_model: The data model. 

79 output_directory: Path to the output directory. 

80 

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) 

87 

88 jinja2_environment = templater.setup_jinja2_environment() 

89 latex_file_object = templater.LaTeXFile( 

90 rendercv_data_model, 

91 jinja2_environment, 

92 ) 

93 

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) 

97 

98 return latex_file_path 

99 

100 

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. 

106 

107 Args: 

108 rendercv_data_model: The data model. 

109 output_directory: Path to the output directory. 

110 

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) 

117 

118 jinja2_environment = templater.setup_jinja2_environment() 

119 markdown_file_object = templater.MarkdownFile( 

120 rendercv_data_model, 

121 jinja2_environment, 

122 ) 

123 

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) 

127 

128 return markdown_file_path 

129 

130 

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. 

136 

137 Args: 

138 rendercv_data_model: The data model. 

139 output_directory: Path to the output directory. 

140 

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 ) 

148 

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 

156 

157 

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. 

162 

163 Args: 

164 latex_file_path: The path to the $\\LaTeX$ file. 

165 

166 Returns: 

167 The path to the rendered PDF file. 

168 """ 

169 return rendercv_tinytex.run_latex(latex_file_path, local_latex_command) 

170 

171 

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. 

174 

175 Args: 

176 pdf_file_path: The path to the PDF file. 

177 

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) 

185 

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) 

196 

197 return png_files 

198 

199 

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. 

203 

204 Args: 

205 markdown_file_path: The path to the Markdown file. 

206 

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) 

214 

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) 

218 

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 

222 

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) 

226 

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") 

230 

231 return html_file_path