Coverage for gws-app/gws/lib/htmlx/__init__.py: 97%

31 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-10-23 20:06 +0200

1"""HTML utilities. 

2 

3Escapes strings for HTML and renders HTML documents to PDF or PNG files with the 

4external ``wkhtmltopdf`` and ``wkhtmltoimage`` tools. The HTML is first written to 

5``<out_path>.html``, which is left in place, and the tool converts it to ``out_path``. 

6JavaScript is disabled and local files can be loaded. 

7 

8Example:: 

9 

10 html = f'<h1>{gws.lib.htmlx.escape(title)}</h1>' 

11 gws.lib.htmlx.render_to_pdf(html, '/tmp/out.pdf', page_size=(210, 297, gws.Uom.mm)) 

12""" 

13 

14import html 

15 

16import gws 

17import gws.lib.osx 

18import gws.lib.text 

19import gws.lib.uom 

20 

21 

22def escape(s: str, quote=True) -> str: 

23 """Escape a string for use in HTML. 

24 

25 Args: 

26 s: A string. 

27 quote: If ``True``, also escape double and single quotes. 

28 

29 Returns: 

30 The escaped string. 

31 """ 

32 return html.escape(s, quote=quote) 

33 

34 

35def render_to_pdf(html: str, out_path: str, page_size: gws.UomSize = None, page_margin: gws.UomExtent = None) -> str: 

36 """Render an HTML string to a PDF file with ``wkhtmltopdf``. 

37 

38 Args: 

39 html: HTML content. 

40 out_path: Path of the PDF file to create. 

41 page_size: Page size, converted to mm. A4 portrait by default. 

42 page_margin: Page margins (top, right, bottom, left), converted to mm. No margins by default. 

43 

44 Returns: 

45 The output path. 

46 

47 Raises: 

48 ``gws.lib.osx.Error``: If the command fails. 

49 """ 

50 mar = (0, 0, 0, 0, gws.Uom.mm) 

51 if page_margin: 

52 mar = gws.lib.uom.extent_to_mm(page_margin, gws.lib.uom.PDF_DPI) 

53 

54 # Page sizes need to be in mm. 

55 psz = (210, 297, gws.Uom.mm) 

56 if page_size: 

57 psz = gws.lib.uom.size_to_mm(page_size, gws.lib.uom.PDF_DPI) 

58 

59 gws.u.write_file(out_path + '.html', html) 

60 

61 cmd = [ 

62 'wkhtmltopdf', 

63 '--disable-javascript', 

64 '--disable-smart-shrinking', 

65 '--load-error-handling', 

66 'ignore', 

67 '--enable-local-file-access', 

68 '--dpi', 

69 gws.lib.text.to_int_str(gws.lib.uom.PDF_DPI), 

70 '--margin-top', 

71 gws.lib.text.to_int_str(mar[0]), 

72 '--margin-right', 

73 gws.lib.text.to_int_str(mar[1]), 

74 '--margin-bottom', 

75 gws.lib.text.to_int_str(mar[2]), 

76 '--margin-left', 

77 gws.lib.text.to_int_str(mar[3]), 

78 '--page-width', 

79 gws.lib.text.to_int_str(psz[0]), 

80 '--page-height', 

81 gws.lib.text.to_int_str(psz[1]), 

82 'page', 

83 out_path + '.html', 

84 out_path, 

85 ] 

86 

87 gws.lib.osx.run(cmd) 

88 return out_path 

89 

90 

91def render_to_png(html: str, out_path: str, page_size: gws.UomSize = None, page_margin: gws.UomExtent = None) -> str: 

92 """Render an HTML string to a PNG image with ``wkhtmltoimage``. 

93 

94 The image has a transparent background. 

95 

96 Args: 

97 html: HTML content. 

98 out_path: Path of the PNG file to create. 

99 page_size: Image size, converted to pixels at ``gws.lib.uom.PDF_DPI``. If set, the image is cropped to this size. 

100 page_margin: Margins (top, right, bottom, left), converted to pixels at ``gws.lib.uom.PDF_DPI`` 

101 and applied as a body style. 

102 

103 Returns: 

104 The output path. 

105 

106 Raises: 

107 ``gws.lib.osx.Error``: If the command fails. 

108 """ 

109 if page_margin: 

110 mar = gws.lib.uom.extent_to_px(page_margin, gws.lib.uom.PDF_DPI) 

111 html = f""" 

112 <body style="margin:{mar[0]}px {mar[1]}px {mar[2]}px {mar[3]}px"> 

113 {html} 

114 </body> 

115 """ 

116 

117 gws.u.write_file(out_path + '.html', html) 

118 

119 cmd = ['wkhtmltoimage'] 

120 

121 if page_size: 

122 # Page sizes need to be in pixels. 

123 psz = gws.lib.uom.size_to_px(page_size, gws.lib.uom.PDF_DPI) 

124 w, h, _ = psz 

125 cmd.extend( 

126 [ 

127 '--width', 

128 gws.lib.text.to_int_str(w), 

129 '--height', 

130 gws.lib.text.to_int_str(h), 

131 '--crop-w', 

132 gws.lib.text.to_int_str(w), 

133 '--crop-h', 

134 gws.lib.text.to_int_str(h), 

135 ] 

136 ) 

137 

138 cmd.extend( 

139 [ 

140 '--disable-javascript', 

141 '--disable-smart-width', 

142 '--transparent', 

143 '--enable-local-file-access', 

144 out_path + '.html', 

145 out_path, 

146 ] 

147 ) 

148 

149 gws.lib.osx.run(cmd) 

150 return out_path 

151