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
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-23 20:06 +0200
1"""HTML utilities.
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.
8Example::
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"""
14import html
16import gws
17import gws.lib.osx
18import gws.lib.text
19import gws.lib.uom
22def escape(s: str, quote=True) -> str:
23 """Escape a string for use in HTML.
25 Args:
26 s: A string.
27 quote: If ``True``, also escape double and single quotes.
29 Returns:
30 The escaped string.
31 """
32 return html.escape(s, quote=quote)
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``.
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.
44 Returns:
45 The output path.
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)
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)
59 gws.u.write_file(out_path + '.html', html)
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 ]
87 gws.lib.osx.run(cmd)
88 return out_path
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``.
94 The image has a transparent background.
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.
103 Returns:
104 The output path.
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 """
117 gws.u.write_file(out_path + '.html', html)
119 cmd = ['wkhtmltoimage']
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 )
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 )
149 gws.lib.osx.run(cmd)
150 return out_path