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

30 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-05 13:35 +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.uom 

19 

20 

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

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

23 

24 Args: 

25 s: A string. 

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

27 

28 Returns: 

29 The escaped string. 

30 """ 

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

32 

33 

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

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

36 

37 Args: 

38 html: HTML content. 

39 out_path: Path of the PDF file to create. 

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

41 page_margin: Page margins (top, right, bottom, left). The values are passed to 

42 ``wkhtmltopdf`` as millimetres, the unit is not converted. 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 = page_margin or (0, 0, 0, 0, gws.Uom.mm) 

51 

52 # Page sizes need to be in mm. 

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

54 if page_size: 

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

56 

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

58 

59 cmd = [ 

60 'wkhtmltopdf', 

61 '--disable-javascript', 

62 '--disable-smart-shrinking', 

63 '--load-error-handling', 

64 'ignore', 

65 '--enable-local-file-access', 

66 '--dpi', 

67 _int_str(gws.lib.uom.PDF_DPI), 

68 '--margin-top', 

69 _int_str(mar[0]), 

70 '--margin-right', 

71 _int_str(mar[1]), 

72 '--margin-bottom', 

73 _int_str(mar[2]), 

74 '--margin-left', 

75 _int_str(mar[3]), 

76 '--page-width', 

77 _int_str(psz[0]), 

78 '--page-height', 

79 _int_str(psz[1]), 

80 'page', 

81 out_path + '.html', 

82 out_path, 

83 ] 

84 

85 gws.lib.osx.run(cmd) 

86 return out_path 

87 

88 

89def render_to_png(html: str, out_path: str, page_size: gws.UomSize = None, page_margin: list[int] = None) -> str: 

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

91 

92 The image has a transparent background. 

93 

94 Args: 

95 html: HTML content. 

96 out_path: Path of the PNG file to create. 

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

98 page_margin: Margins in pixels (top, right, bottom, left), applied as a body style. 

99 

100 Returns: 

101 The output path. 

102 

103 Raises: 

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

105 """ 

106 if page_margin: 

107 mar = page_margin 

108 html = f""" 

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

110 {html} 

111 </body> 

112 """ 

113 

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

115 

116 cmd = ['wkhtmltoimage'] 

117 

118 if page_size: 

119 # Page sizes need to be in pixels. 

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

121 w, h, _ = psz 

122 cmd.extend( 

123 [ 

124 '--width', 

125 _int_str(w), 

126 '--height', 

127 _int_str(h), 

128 '--crop-w', 

129 _int_str(w), 

130 '--crop-h', 

131 _int_str(h), 

132 ] 

133 ) 

134 

135 cmd.extend( 

136 [ 

137 '--disable-javascript', 

138 '--disable-smart-width', 

139 '--transparent', 

140 '--enable-local-file-access', 

141 out_path + '.html', 

142 out_path, 

143 ] 

144 ) 

145 

146 gws.lib.osx.run(cmd) 

147 return out_path 

148 

149 

150def _int_str(x) -> str: 

151 return str(int(x)) 

152