##// END OF EJS Templates
remove some cast_unicode (#14562)
M Bussonnier -
r28943:a9633587 merge
parent child Browse files
Show More
@@ -1,1370 +1,1373
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Top-level display functions for displaying object in different formats."""
2 """Top-level display functions for displaying object in different formats."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7
7
8 from binascii import b2a_base64, hexlify
8 from binascii import b2a_base64, hexlify
9 import html
9 import html
10 import json
10 import json
11 import mimetypes
11 import mimetypes
12 import os
12 import os
13 import struct
13 import struct
14 import warnings
14 import warnings
15 from copy import deepcopy
15 from copy import deepcopy
16 from os.path import splitext
16 from os.path import splitext
17 from pathlib import Path, PurePath
17 from pathlib import Path, PurePath
18
18
19 from IPython.utils.py3compat import cast_unicode
19 from typing import Optional
20
20 from IPython.testing.skipdoctest import skip_doctest
21 from IPython.testing.skipdoctest import skip_doctest
21 from . import display_functions
22 from . import display_functions
22
23
23
24
24 __all__ = [
25 __all__ = [
25 "display_pretty",
26 "display_pretty",
26 "display_html",
27 "display_html",
27 "display_markdown",
28 "display_markdown",
28 "display_svg",
29 "display_svg",
29 "display_png",
30 "display_png",
30 "display_jpeg",
31 "display_jpeg",
31 "display_webp",
32 "display_webp",
32 "display_latex",
33 "display_latex",
33 "display_json",
34 "display_json",
34 "display_javascript",
35 "display_javascript",
35 "display_pdf",
36 "display_pdf",
36 "DisplayObject",
37 "DisplayObject",
37 "TextDisplayObject",
38 "TextDisplayObject",
38 "Pretty",
39 "Pretty",
39 "HTML",
40 "HTML",
40 "Markdown",
41 "Markdown",
41 "Math",
42 "Math",
42 "Latex",
43 "Latex",
43 "SVG",
44 "SVG",
44 "ProgressBar",
45 "ProgressBar",
45 "JSON",
46 "JSON",
46 "GeoJSON",
47 "GeoJSON",
47 "Javascript",
48 "Javascript",
48 "Image",
49 "Image",
49 "set_matplotlib_formats",
50 "set_matplotlib_formats",
50 "set_matplotlib_close",
51 "set_matplotlib_close",
51 "Video",
52 "Video",
52 ]
53 ]
53
54
54 _deprecated_names = ["display", "clear_output", "publish_display_data", "update_display", "DisplayHandle"]
55 _deprecated_names = ["display", "clear_output", "publish_display_data", "update_display", "DisplayHandle"]
55
56
56 __all__ = __all__ + _deprecated_names
57 __all__ = __all__ + _deprecated_names
57
58
58
59
59 # ----- warn to import from IPython.display -----
60 # ----- warn to import from IPython.display -----
60
61
61 from warnings import warn
62 from warnings import warn
62
63
63
64
64 def __getattr__(name):
65 def __getattr__(name):
65 if name in _deprecated_names:
66 if name in _deprecated_names:
66 warn(
67 warn(
67 f"Importing {name} from IPython.core.display is deprecated since IPython 7.14, please import from IPython.display",
68 f"Importing {name} from IPython.core.display is deprecated since IPython 7.14, please import from IPython.display",
68 DeprecationWarning,
69 DeprecationWarning,
69 stacklevel=2,
70 stacklevel=2,
70 )
71 )
71 return getattr(display_functions, name)
72 return getattr(display_functions, name)
72
73
73 if name in globals().keys():
74 if name in globals().keys():
74 return globals()[name]
75 return globals()[name]
75 else:
76 else:
76 raise AttributeError(f"module {__name__} has no attribute {name}")
77 raise AttributeError(f"module {__name__} has no attribute {name}")
77
78
78
79
79 #-----------------------------------------------------------------------------
80 #-----------------------------------------------------------------------------
80 # utility functions
81 # utility functions
81 #-----------------------------------------------------------------------------
82 #-----------------------------------------------------------------------------
82
83
83 def _safe_exists(path):
84 def _safe_exists(path):
84 """Check path, but don't let exceptions raise"""
85 """Check path, but don't let exceptions raise"""
85 try:
86 try:
86 return os.path.exists(path)
87 return os.path.exists(path)
87 except Exception:
88 except Exception:
88 return False
89 return False
89
90
90
91
91 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
92 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
92 """internal implementation of all display_foo methods
93 """internal implementation of all display_foo methods
93
94
94 Parameters
95 Parameters
95 ----------
96 ----------
96 mimetype : str
97 mimetype : str
97 The mimetype to be published (e.g. 'image/png')
98 The mimetype to be published (e.g. 'image/png')
98 *objs : object
99 *objs : object
99 The Python objects to display, or if raw=True raw text data to
100 The Python objects to display, or if raw=True raw text data to
100 display.
101 display.
101 raw : bool
102 raw : bool
102 Are the data objects raw data or Python objects that need to be
103 Are the data objects raw data or Python objects that need to be
103 formatted before display? [default: False]
104 formatted before display? [default: False]
104 metadata : dict (optional)
105 metadata : dict (optional)
105 Metadata to be associated with the specific mimetype output.
106 Metadata to be associated with the specific mimetype output.
106 """
107 """
107 if metadata:
108 if metadata:
108 metadata = {mimetype: metadata}
109 metadata = {mimetype: metadata}
109 if raw:
110 if raw:
110 # turn list of pngdata into list of { 'image/png': pngdata }
111 # turn list of pngdata into list of { 'image/png': pngdata }
111 objs = [ {mimetype: obj} for obj in objs ]
112 objs = [ {mimetype: obj} for obj in objs ]
112 display_functions.display(*objs, raw=raw, metadata=metadata, include=[mimetype])
113 display_functions.display(*objs, raw=raw, metadata=metadata, include=[mimetype])
113
114
114 #-----------------------------------------------------------------------------
115 #-----------------------------------------------------------------------------
115 # Main functions
116 # Main functions
116 #-----------------------------------------------------------------------------
117 #-----------------------------------------------------------------------------
117
118
118
119
119 def display_pretty(*objs, **kwargs):
120 def display_pretty(*objs, **kwargs):
120 """Display the pretty (default) representation of an object.
121 """Display the pretty (default) representation of an object.
121
122
122 Parameters
123 Parameters
123 ----------
124 ----------
124 *objs : object
125 *objs : object
125 The Python objects to display, or if raw=True raw text data to
126 The Python objects to display, or if raw=True raw text data to
126 display.
127 display.
127 raw : bool
128 raw : bool
128 Are the data objects raw data or Python objects that need to be
129 Are the data objects raw data or Python objects that need to be
129 formatted before display? [default: False]
130 formatted before display? [default: False]
130 metadata : dict (optional)
131 metadata : dict (optional)
131 Metadata to be associated with the specific mimetype output.
132 Metadata to be associated with the specific mimetype output.
132 """
133 """
133 _display_mimetype('text/plain', objs, **kwargs)
134 _display_mimetype('text/plain', objs, **kwargs)
134
135
135
136
136 def display_html(*objs, **kwargs):
137 def display_html(*objs, **kwargs):
137 """Display the HTML representation of an object.
138 """Display the HTML representation of an object.
138
139
139 Note: If raw=False and the object does not have a HTML
140 Note: If raw=False and the object does not have a HTML
140 representation, no HTML will be shown.
141 representation, no HTML will be shown.
141
142
142 Parameters
143 Parameters
143 ----------
144 ----------
144 *objs : object
145 *objs : object
145 The Python objects to display, or if raw=True raw HTML data to
146 The Python objects to display, or if raw=True raw HTML data to
146 display.
147 display.
147 raw : bool
148 raw : bool
148 Are the data objects raw data or Python objects that need to be
149 Are the data objects raw data or Python objects that need to be
149 formatted before display? [default: False]
150 formatted before display? [default: False]
150 metadata : dict (optional)
151 metadata : dict (optional)
151 Metadata to be associated with the specific mimetype output.
152 Metadata to be associated with the specific mimetype output.
152 """
153 """
153 _display_mimetype('text/html', objs, **kwargs)
154 _display_mimetype('text/html', objs, **kwargs)
154
155
155
156
156 def display_markdown(*objs, **kwargs):
157 def display_markdown(*objs, **kwargs):
157 """Displays the Markdown representation of an object.
158 """Displays the Markdown representation of an object.
158
159
159 Parameters
160 Parameters
160 ----------
161 ----------
161 *objs : object
162 *objs : object
162 The Python objects to display, or if raw=True raw markdown data to
163 The Python objects to display, or if raw=True raw markdown data to
163 display.
164 display.
164 raw : bool
165 raw : bool
165 Are the data objects raw data or Python objects that need to be
166 Are the data objects raw data or Python objects that need to be
166 formatted before display? [default: False]
167 formatted before display? [default: False]
167 metadata : dict (optional)
168 metadata : dict (optional)
168 Metadata to be associated with the specific mimetype output.
169 Metadata to be associated with the specific mimetype output.
169 """
170 """
170
171
171 _display_mimetype('text/markdown', objs, **kwargs)
172 _display_mimetype('text/markdown', objs, **kwargs)
172
173
173
174
174 def display_svg(*objs, **kwargs):
175 def display_svg(*objs, **kwargs):
175 """Display the SVG representation of an object.
176 """Display the SVG representation of an object.
176
177
177 Parameters
178 Parameters
178 ----------
179 ----------
179 *objs : object
180 *objs : object
180 The Python objects to display, or if raw=True raw svg data to
181 The Python objects to display, or if raw=True raw svg data to
181 display.
182 display.
182 raw : bool
183 raw : bool
183 Are the data objects raw data or Python objects that need to be
184 Are the data objects raw data or Python objects that need to be
184 formatted before display? [default: False]
185 formatted before display? [default: False]
185 metadata : dict (optional)
186 metadata : dict (optional)
186 Metadata to be associated with the specific mimetype output.
187 Metadata to be associated with the specific mimetype output.
187 """
188 """
188 _display_mimetype('image/svg+xml', objs, **kwargs)
189 _display_mimetype('image/svg+xml', objs, **kwargs)
189
190
190
191
191 def display_png(*objs, **kwargs):
192 def display_png(*objs, **kwargs):
192 """Display the PNG representation of an object.
193 """Display the PNG representation of an object.
193
194
194 Parameters
195 Parameters
195 ----------
196 ----------
196 *objs : object
197 *objs : object
197 The Python objects to display, or if raw=True raw png data to
198 The Python objects to display, or if raw=True raw png data to
198 display.
199 display.
199 raw : bool
200 raw : bool
200 Are the data objects raw data or Python objects that need to be
201 Are the data objects raw data or Python objects that need to be
201 formatted before display? [default: False]
202 formatted before display? [default: False]
202 metadata : dict (optional)
203 metadata : dict (optional)
203 Metadata to be associated with the specific mimetype output.
204 Metadata to be associated with the specific mimetype output.
204 """
205 """
205 _display_mimetype('image/png', objs, **kwargs)
206 _display_mimetype('image/png', objs, **kwargs)
206
207
207
208
208 def display_jpeg(*objs, **kwargs):
209 def display_jpeg(*objs, **kwargs):
209 """Display the JPEG representation of an object.
210 """Display the JPEG representation of an object.
210
211
211 Parameters
212 Parameters
212 ----------
213 ----------
213 *objs : object
214 *objs : object
214 The Python objects to display, or if raw=True raw JPEG data to
215 The Python objects to display, or if raw=True raw JPEG data to
215 display.
216 display.
216 raw : bool
217 raw : bool
217 Are the data objects raw data or Python objects that need to be
218 Are the data objects raw data or Python objects that need to be
218 formatted before display? [default: False]
219 formatted before display? [default: False]
219 metadata : dict (optional)
220 metadata : dict (optional)
220 Metadata to be associated with the specific mimetype output.
221 Metadata to be associated with the specific mimetype output.
221 """
222 """
222 _display_mimetype('image/jpeg', objs, **kwargs)
223 _display_mimetype('image/jpeg', objs, **kwargs)
223
224
224
225
225 def display_webp(*objs, **kwargs):
226 def display_webp(*objs, **kwargs):
226 """Display the WEBP representation of an object.
227 """Display the WEBP representation of an object.
227
228
228 Parameters
229 Parameters
229 ----------
230 ----------
230 *objs : object
231 *objs : object
231 The Python objects to display, or if raw=True raw JPEG data to
232 The Python objects to display, or if raw=True raw JPEG data to
232 display.
233 display.
233 raw : bool
234 raw : bool
234 Are the data objects raw data or Python objects that need to be
235 Are the data objects raw data or Python objects that need to be
235 formatted before display? [default: False]
236 formatted before display? [default: False]
236 metadata : dict (optional)
237 metadata : dict (optional)
237 Metadata to be associated with the specific mimetype output.
238 Metadata to be associated with the specific mimetype output.
238 """
239 """
239 _display_mimetype("image/webp", objs, **kwargs)
240 _display_mimetype("image/webp", objs, **kwargs)
240
241
241
242
242 def display_latex(*objs, **kwargs):
243 def display_latex(*objs, **kwargs):
243 """Display the LaTeX representation of an object.
244 """Display the LaTeX representation of an object.
244
245
245 Parameters
246 Parameters
246 ----------
247 ----------
247 *objs : object
248 *objs : object
248 The Python objects to display, or if raw=True raw latex data to
249 The Python objects to display, or if raw=True raw latex data to
249 display.
250 display.
250 raw : bool
251 raw : bool
251 Are the data objects raw data or Python objects that need to be
252 Are the data objects raw data or Python objects that need to be
252 formatted before display? [default: False]
253 formatted before display? [default: False]
253 metadata : dict (optional)
254 metadata : dict (optional)
254 Metadata to be associated with the specific mimetype output.
255 Metadata to be associated with the specific mimetype output.
255 """
256 """
256 _display_mimetype('text/latex', objs, **kwargs)
257 _display_mimetype('text/latex', objs, **kwargs)
257
258
258
259
259 def display_json(*objs, **kwargs):
260 def display_json(*objs, **kwargs):
260 """Display the JSON representation of an object.
261 """Display the JSON representation of an object.
261
262
262 Note that not many frontends support displaying JSON.
263 Note that not many frontends support displaying JSON.
263
264
264 Parameters
265 Parameters
265 ----------
266 ----------
266 *objs : object
267 *objs : object
267 The Python objects to display, or if raw=True raw json data to
268 The Python objects to display, or if raw=True raw json data to
268 display.
269 display.
269 raw : bool
270 raw : bool
270 Are the data objects raw data or Python objects that need to be
271 Are the data objects raw data or Python objects that need to be
271 formatted before display? [default: False]
272 formatted before display? [default: False]
272 metadata : dict (optional)
273 metadata : dict (optional)
273 Metadata to be associated with the specific mimetype output.
274 Metadata to be associated with the specific mimetype output.
274 """
275 """
275 _display_mimetype('application/json', objs, **kwargs)
276 _display_mimetype('application/json', objs, **kwargs)
276
277
277
278
278 def display_javascript(*objs, **kwargs):
279 def display_javascript(*objs, **kwargs):
279 """Display the Javascript representation of an object.
280 """Display the Javascript representation of an object.
280
281
281 Parameters
282 Parameters
282 ----------
283 ----------
283 *objs : object
284 *objs : object
284 The Python objects to display, or if raw=True raw javascript data to
285 The Python objects to display, or if raw=True raw javascript data to
285 display.
286 display.
286 raw : bool
287 raw : bool
287 Are the data objects raw data or Python objects that need to be
288 Are the data objects raw data or Python objects that need to be
288 formatted before display? [default: False]
289 formatted before display? [default: False]
289 metadata : dict (optional)
290 metadata : dict (optional)
290 Metadata to be associated with the specific mimetype output.
291 Metadata to be associated with the specific mimetype output.
291 """
292 """
292 _display_mimetype('application/javascript', objs, **kwargs)
293 _display_mimetype('application/javascript', objs, **kwargs)
293
294
294
295
295 def display_pdf(*objs, **kwargs):
296 def display_pdf(*objs, **kwargs):
296 """Display the PDF representation of an object.
297 """Display the PDF representation of an object.
297
298
298 Parameters
299 Parameters
299 ----------
300 ----------
300 *objs : object
301 *objs : object
301 The Python objects to display, or if raw=True raw javascript data to
302 The Python objects to display, or if raw=True raw javascript data to
302 display.
303 display.
303 raw : bool
304 raw : bool
304 Are the data objects raw data or Python objects that need to be
305 Are the data objects raw data or Python objects that need to be
305 formatted before display? [default: False]
306 formatted before display? [default: False]
306 metadata : dict (optional)
307 metadata : dict (optional)
307 Metadata to be associated with the specific mimetype output.
308 Metadata to be associated with the specific mimetype output.
308 """
309 """
309 _display_mimetype('application/pdf', objs, **kwargs)
310 _display_mimetype('application/pdf', objs, **kwargs)
310
311
311
312
312 #-----------------------------------------------------------------------------
313 #-----------------------------------------------------------------------------
313 # Smart classes
314 # Smart classes
314 #-----------------------------------------------------------------------------
315 #-----------------------------------------------------------------------------
315
316
316
317
317 class DisplayObject(object):
318 class DisplayObject(object):
318 """An object that wraps data to be displayed."""
319 """An object that wraps data to be displayed."""
319
320
320 _read_flags = 'r'
321 _read_flags = 'r'
321 _show_mem_addr = False
322 _show_mem_addr = False
322 metadata = None
323 metadata = None
323
324
324 def __init__(self, data=None, url=None, filename=None, metadata=None):
325 def __init__(self, data=None, url=None, filename=None, metadata=None):
325 """Create a display object given raw data.
326 """Create a display object given raw data.
326
327
327 When this object is returned by an expression or passed to the
328 When this object is returned by an expression or passed to the
328 display function, it will result in the data being displayed
329 display function, it will result in the data being displayed
329 in the frontend. The MIME type of the data should match the
330 in the frontend. The MIME type of the data should match the
330 subclasses used, so the Png subclass should be used for 'image/png'
331 subclasses used, so the Png subclass should be used for 'image/png'
331 data. If the data is a URL, the data will first be downloaded
332 data. If the data is a URL, the data will first be downloaded
332 and then displayed.
333 and then displayed.
333
334
334 Parameters
335 Parameters
335 ----------
336 ----------
336 data : unicode, str or bytes
337 data : unicode, str or bytes
337 The raw data or a URL or file to load the data from
338 The raw data or a URL or file to load the data from
338 url : unicode
339 url : unicode
339 A URL to download the data from.
340 A URL to download the data from.
340 filename : unicode
341 filename : unicode
341 Path to a local file to load the data from.
342 Path to a local file to load the data from.
342 metadata : dict
343 metadata : dict
343 Dict of metadata associated to be the object when displayed
344 Dict of metadata associated to be the object when displayed
344 """
345 """
345 if isinstance(data, (Path, PurePath)):
346 if isinstance(data, (Path, PurePath)):
346 data = str(data)
347 data = str(data)
347
348
348 if data is not None and isinstance(data, str):
349 if data is not None and isinstance(data, str):
349 if data.startswith('http') and url is None:
350 if data.startswith('http') and url is None:
350 url = data
351 url = data
351 filename = None
352 filename = None
352 data = None
353 data = None
353 elif _safe_exists(data) and filename is None:
354 elif _safe_exists(data) and filename is None:
354 url = None
355 url = None
355 filename = data
356 filename = data
356 data = None
357 data = None
357
358
358 self.url = url
359 self.url = url
359 self.filename = filename
360 self.filename = filename
360 # because of @data.setter methods in
361 # because of @data.setter methods in
361 # subclasses ensure url and filename are set
362 # subclasses ensure url and filename are set
362 # before assigning to self.data
363 # before assigning to self.data
363 self.data = data
364 self.data = data
364
365
365 if metadata is not None:
366 if metadata is not None:
366 self.metadata = metadata
367 self.metadata = metadata
367 elif self.metadata is None:
368 elif self.metadata is None:
368 self.metadata = {}
369 self.metadata = {}
369
370
370 self.reload()
371 self.reload()
371 self._check_data()
372 self._check_data()
372
373
373 def __repr__(self):
374 def __repr__(self):
374 if not self._show_mem_addr:
375 if not self._show_mem_addr:
375 cls = self.__class__
376 cls = self.__class__
376 r = "<%s.%s object>" % (cls.__module__, cls.__name__)
377 r = "<%s.%s object>" % (cls.__module__, cls.__name__)
377 else:
378 else:
378 r = super(DisplayObject, self).__repr__()
379 r = super(DisplayObject, self).__repr__()
379 return r
380 return r
380
381
381 def _check_data(self):
382 def _check_data(self):
382 """Override in subclasses if there's something to check."""
383 """Override in subclasses if there's something to check."""
383 pass
384 pass
384
385
385 def _data_and_metadata(self):
386 def _data_and_metadata(self):
386 """shortcut for returning metadata with shape information, if defined"""
387 """shortcut for returning metadata with shape information, if defined"""
387 if self.metadata:
388 if self.metadata:
388 return self.data, deepcopy(self.metadata)
389 return self.data, deepcopy(self.metadata)
389 else:
390 else:
390 return self.data
391 return self.data
391
392
392 def reload(self):
393 def reload(self):
393 """Reload the raw data from file or URL."""
394 """Reload the raw data from file or URL."""
394 if self.filename is not None:
395 if self.filename is not None:
395 encoding = None if "b" in self._read_flags else "utf-8"
396 encoding = None if "b" in self._read_flags else "utf-8"
396 with open(self.filename, self._read_flags, encoding=encoding) as f:
397 with open(self.filename, self._read_flags, encoding=encoding) as f:
397 self.data = f.read()
398 self.data = f.read()
398 elif self.url is not None:
399 elif self.url is not None:
399 # Deferred import
400 # Deferred import
400 from urllib.request import urlopen
401 from urllib.request import urlopen
401 response = urlopen(self.url)
402 response = urlopen(self.url)
402 data = response.read()
403 data = response.read()
403 # extract encoding from header, if there is one:
404 # extract encoding from header, if there is one:
404 encoding = None
405 encoding = None
405 if 'content-type' in response.headers:
406 if 'content-type' in response.headers:
406 for sub in response.headers['content-type'].split(';'):
407 for sub in response.headers['content-type'].split(';'):
407 sub = sub.strip()
408 sub = sub.strip()
408 if sub.startswith('charset'):
409 if sub.startswith('charset'):
409 encoding = sub.split('=')[-1].strip()
410 encoding = sub.split('=')[-1].strip()
410 break
411 break
411 if 'content-encoding' in response.headers:
412 if 'content-encoding' in response.headers:
412 # TODO: do deflate?
413 # TODO: do deflate?
413 if 'gzip' in response.headers['content-encoding']:
414 if 'gzip' in response.headers['content-encoding']:
414 import gzip
415 import gzip
415 from io import BytesIO
416 from io import BytesIO
416
417
417 # assume utf-8 if encoding is not specified
418 # assume utf-8 if encoding is not specified
418 with gzip.open(
419 with gzip.open(
419 BytesIO(data), "rt", encoding=encoding or "utf-8"
420 BytesIO(data), "rt", encoding=encoding or "utf-8"
420 ) as fp:
421 ) as fp:
421 encoding = None
422 encoding = None
422 data = fp.read()
423 data = fp.read()
423
424
424 # decode data, if an encoding was specified
425 # decode data, if an encoding was specified
425 # We only touch self.data once since
426 # We only touch self.data once since
426 # subclasses such as SVG have @data.setter methods
427 # subclasses such as SVG have @data.setter methods
427 # that transform self.data into ... well svg.
428 # that transform self.data into ... well svg.
428 if encoding:
429 if encoding:
429 self.data = data.decode(encoding, 'replace')
430 self.data = data.decode(encoding, 'replace')
430 else:
431 else:
431 self.data = data
432 self.data = data
432
433
433
434
434 class TextDisplayObject(DisplayObject):
435 class TextDisplayObject(DisplayObject):
435 """Create a text display object given raw data.
436 """Create a text display object given raw data.
436
437
437 Parameters
438 Parameters
438 ----------
439 ----------
439 data : str or unicode
440 data : str or unicode
440 The raw data or a URL or file to load the data from.
441 The raw data or a URL or file to load the data from.
441 url : unicode
442 url : unicode
442 A URL to download the data from.
443 A URL to download the data from.
443 filename : unicode
444 filename : unicode
444 Path to a local file to load the data from.
445 Path to a local file to load the data from.
445 metadata : dict
446 metadata : dict
446 Dict of metadata associated to be the object when displayed
447 Dict of metadata associated to be the object when displayed
447 """
448 """
448 def _check_data(self):
449 def _check_data(self):
449 if self.data is not None and not isinstance(self.data, str):
450 if self.data is not None and not isinstance(self.data, str):
450 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
451 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
451
452
452 class Pretty(TextDisplayObject):
453 class Pretty(TextDisplayObject):
453
454
454 def _repr_pretty_(self, pp, cycle):
455 def _repr_pretty_(self, pp, cycle):
455 return pp.text(self.data)
456 return pp.text(self.data)
456
457
457
458
458 class HTML(TextDisplayObject):
459 class HTML(TextDisplayObject):
459
460
460 def __init__(self, data=None, url=None, filename=None, metadata=None):
461 def __init__(self, data=None, url=None, filename=None, metadata=None):
461 def warn():
462 def warn():
462 if not data:
463 if not data:
463 return False
464 return False
464
465
465 #
466 #
466 # Avoid calling lower() on the entire data, because it could be a
467 # Avoid calling lower() on the entire data, because it could be a
467 # long string and we're only interested in its beginning and end.
468 # long string and we're only interested in its beginning and end.
468 #
469 #
469 prefix = data[:10].lower()
470 prefix = data[:10].lower()
470 suffix = data[-10:].lower()
471 suffix = data[-10:].lower()
471 return prefix.startswith("<iframe ") and suffix.endswith("</iframe>")
472 return prefix.startswith("<iframe ") and suffix.endswith("</iframe>")
472
473
473 if warn():
474 if warn():
474 warnings.warn("Consider using IPython.display.IFrame instead")
475 warnings.warn("Consider using IPython.display.IFrame instead")
475 super(HTML, self).__init__(data=data, url=url, filename=filename, metadata=metadata)
476 super(HTML, self).__init__(data=data, url=url, filename=filename, metadata=metadata)
476
477
477 def _repr_html_(self):
478 def _repr_html_(self):
478 return self._data_and_metadata()
479 return self._data_and_metadata()
479
480
480 def __html__(self):
481 def __html__(self):
481 """
482 """
482 This method exists to inform other HTML-using modules (e.g. Markupsafe,
483 This method exists to inform other HTML-using modules (e.g. Markupsafe,
483 htmltag, etc) that this object is HTML and does not need things like
484 htmltag, etc) that this object is HTML and does not need things like
484 special characters (<>&) escaped.
485 special characters (<>&) escaped.
485 """
486 """
486 return self._repr_html_()
487 return self._repr_html_()
487
488
488
489
489 class Markdown(TextDisplayObject):
490 class Markdown(TextDisplayObject):
490
491
491 def _repr_markdown_(self):
492 def _repr_markdown_(self):
492 return self._data_and_metadata()
493 return self._data_and_metadata()
493
494
494
495
495 class Math(TextDisplayObject):
496 class Math(TextDisplayObject):
496
497
497 def _repr_latex_(self):
498 def _repr_latex_(self):
498 s = r"$\displaystyle %s$" % self.data.strip('$')
499 s = r"$\displaystyle %s$" % self.data.strip('$')
499 if self.metadata:
500 if self.metadata:
500 return s, deepcopy(self.metadata)
501 return s, deepcopy(self.metadata)
501 else:
502 else:
502 return s
503 return s
503
504
504
505
505 class Latex(TextDisplayObject):
506 class Latex(TextDisplayObject):
506
507
507 def _repr_latex_(self):
508 def _repr_latex_(self):
508 return self._data_and_metadata()
509 return self._data_and_metadata()
509
510
510
511
511 class SVG(DisplayObject):
512 class SVG(DisplayObject):
512 """Embed an SVG into the display.
513 """Embed an SVG into the display.
513
514
514 Note if you just want to view a svg image via a URL use `:class:Image` with
515 Note if you just want to view a svg image via a URL use `:class:Image` with
515 a url=URL keyword argument.
516 a url=URL keyword argument.
516 """
517 """
517
518
518 _read_flags = 'rb'
519 _read_flags = 'rb'
519 # wrap data in a property, which extracts the <svg> tag, discarding
520 # wrap data in a property, which extracts the <svg> tag, discarding
520 # document headers
521 # document headers
521 _data = None
522 _data: Optional[str] = None
522
523
523 @property
524 @property
524 def data(self):
525 def data(self):
525 return self._data
526 return self._data
526
527
527 @data.setter
528 @data.setter
528 def data(self, svg):
529 def data(self, svg):
529 if svg is None:
530 if svg is None:
530 self._data = None
531 self._data = None
531 return
532 return
532 # parse into dom object
533 # parse into dom object
533 from xml.dom import minidom
534 from xml.dom import minidom
534 x = minidom.parseString(svg)
535 x = minidom.parseString(svg)
535 # get svg tag (should be 1)
536 # get svg tag (should be 1)
536 found_svg = x.getElementsByTagName('svg')
537 found_svg = x.getElementsByTagName('svg')
537 if found_svg:
538 if found_svg:
538 svg = found_svg[0].toxml()
539 svg = found_svg[0].toxml()
539 else:
540 else:
540 # fallback on the input, trust the user
541 # fallback on the input, trust the user
541 # but this is probably an error.
542 # but this is probably an error.
542 pass
543 pass
543 svg = cast_unicode(svg)
544 if isinstance(svg, bytes):
544 self._data = svg
545 self._data = svg.decode(errors="replace")
546 else:
547 self._data = svg
545
548
546 def _repr_svg_(self):
549 def _repr_svg_(self):
547 return self._data_and_metadata()
550 return self._data_and_metadata()
548
551
549 class ProgressBar(DisplayObject):
552 class ProgressBar(DisplayObject):
550 """Progressbar supports displaying a progressbar like element
553 """Progressbar supports displaying a progressbar like element
551 """
554 """
552 def __init__(self, total):
555 def __init__(self, total):
553 """Creates a new progressbar
556 """Creates a new progressbar
554
557
555 Parameters
558 Parameters
556 ----------
559 ----------
557 total : int
560 total : int
558 maximum size of the progressbar
561 maximum size of the progressbar
559 """
562 """
560 self.total = total
563 self.total = total
561 self._progress = 0
564 self._progress = 0
562 self.html_width = '60ex'
565 self.html_width = '60ex'
563 self.text_width = 60
566 self.text_width = 60
564 self._display_id = hexlify(os.urandom(8)).decode('ascii')
567 self._display_id = hexlify(os.urandom(8)).decode('ascii')
565
568
566 def __repr__(self):
569 def __repr__(self):
567 fraction = self.progress / self.total
570 fraction = self.progress / self.total
568 filled = '=' * int(fraction * self.text_width)
571 filled = '=' * int(fraction * self.text_width)
569 rest = ' ' * (self.text_width - len(filled))
572 rest = ' ' * (self.text_width - len(filled))
570 return '[{}{}] {}/{}'.format(
573 return '[{}{}] {}/{}'.format(
571 filled, rest,
574 filled, rest,
572 self.progress, self.total,
575 self.progress, self.total,
573 )
576 )
574
577
575 def _repr_html_(self):
578 def _repr_html_(self):
576 return "<progress style='width:{}' max='{}' value='{}'></progress>".format(
579 return "<progress style='width:{}' max='{}' value='{}'></progress>".format(
577 self.html_width, self.total, self.progress)
580 self.html_width, self.total, self.progress)
578
581
579 def display(self):
582 def display(self):
580 display_functions.display(self, display_id=self._display_id)
583 display_functions.display(self, display_id=self._display_id)
581
584
582 def update(self):
585 def update(self):
583 display_functions.display(self, display_id=self._display_id, update=True)
586 display_functions.display(self, display_id=self._display_id, update=True)
584
587
585 @property
588 @property
586 def progress(self):
589 def progress(self):
587 return self._progress
590 return self._progress
588
591
589 @progress.setter
592 @progress.setter
590 def progress(self, value):
593 def progress(self, value):
591 self._progress = value
594 self._progress = value
592 self.update()
595 self.update()
593
596
594 def __iter__(self):
597 def __iter__(self):
595 self.display()
598 self.display()
596 self._progress = -1 # First iteration is 0
599 self._progress = -1 # First iteration is 0
597 return self
600 return self
598
601
599 def __next__(self):
602 def __next__(self):
600 """Returns current value and increments display by one."""
603 """Returns current value and increments display by one."""
601 self.progress += 1
604 self.progress += 1
602 if self.progress < self.total:
605 if self.progress < self.total:
603 return self.progress
606 return self.progress
604 else:
607 else:
605 raise StopIteration()
608 raise StopIteration()
606
609
607 class JSON(DisplayObject):
610 class JSON(DisplayObject):
608 """JSON expects a JSON-able dict or list
611 """JSON expects a JSON-able dict or list
609
612
610 not an already-serialized JSON string.
613 not an already-serialized JSON string.
611
614
612 Scalar types (None, number, string) are not allowed, only dict or list containers.
615 Scalar types (None, number, string) are not allowed, only dict or list containers.
613 """
616 """
614 # wrap data in a property, which warns about passing already-serialized JSON
617 # wrap data in a property, which warns about passing already-serialized JSON
615 _data = None
618 _data = None
616 def __init__(self, data=None, url=None, filename=None, expanded=False, metadata=None, root='root', **kwargs):
619 def __init__(self, data=None, url=None, filename=None, expanded=False, metadata=None, root='root', **kwargs):
617 """Create a JSON display object given raw data.
620 """Create a JSON display object given raw data.
618
621
619 Parameters
622 Parameters
620 ----------
623 ----------
621 data : dict or list
624 data : dict or list
622 JSON data to display. Not an already-serialized JSON string.
625 JSON data to display. Not an already-serialized JSON string.
623 Scalar types (None, number, string) are not allowed, only dict
626 Scalar types (None, number, string) are not allowed, only dict
624 or list containers.
627 or list containers.
625 url : unicode
628 url : unicode
626 A URL to download the data from.
629 A URL to download the data from.
627 filename : unicode
630 filename : unicode
628 Path to a local file to load the data from.
631 Path to a local file to load the data from.
629 expanded : boolean
632 expanded : boolean
630 Metadata to control whether a JSON display component is expanded.
633 Metadata to control whether a JSON display component is expanded.
631 metadata : dict
634 metadata : dict
632 Specify extra metadata to attach to the json display object.
635 Specify extra metadata to attach to the json display object.
633 root : str
636 root : str
634 The name of the root element of the JSON tree
637 The name of the root element of the JSON tree
635 """
638 """
636 self.metadata = {
639 self.metadata = {
637 'expanded': expanded,
640 'expanded': expanded,
638 'root': root,
641 'root': root,
639 }
642 }
640 if metadata:
643 if metadata:
641 self.metadata.update(metadata)
644 self.metadata.update(metadata)
642 if kwargs:
645 if kwargs:
643 self.metadata.update(kwargs)
646 self.metadata.update(kwargs)
644 super(JSON, self).__init__(data=data, url=url, filename=filename)
647 super(JSON, self).__init__(data=data, url=url, filename=filename)
645
648
646 def _check_data(self):
649 def _check_data(self):
647 if self.data is not None and not isinstance(self.data, (dict, list)):
650 if self.data is not None and not isinstance(self.data, (dict, list)):
648 raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data))
651 raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data))
649
652
650 @property
653 @property
651 def data(self):
654 def data(self):
652 return self._data
655 return self._data
653
656
654 @data.setter
657 @data.setter
655 def data(self, data):
658 def data(self, data):
656 if isinstance(data, (Path, PurePath)):
659 if isinstance(data, (Path, PurePath)):
657 data = str(data)
660 data = str(data)
658
661
659 if isinstance(data, str):
662 if isinstance(data, str):
660 if self.filename is None and self.url is None:
663 if self.filename is None and self.url is None:
661 warnings.warn("JSON expects JSONable dict or list, not JSON strings")
664 warnings.warn("JSON expects JSONable dict or list, not JSON strings")
662 data = json.loads(data)
665 data = json.loads(data)
663 self._data = data
666 self._data = data
664
667
665 def _data_and_metadata(self):
668 def _data_and_metadata(self):
666 return self.data, self.metadata
669 return self.data, self.metadata
667
670
668 def _repr_json_(self):
671 def _repr_json_(self):
669 return self._data_and_metadata()
672 return self._data_and_metadata()
670
673
671
674
672 _css_t = """var link = document.createElement("link");
675 _css_t = """var link = document.createElement("link");
673 link.rel = "stylesheet";
676 link.rel = "stylesheet";
674 link.type = "text/css";
677 link.type = "text/css";
675 link.href = "%s";
678 link.href = "%s";
676 document.head.appendChild(link);
679 document.head.appendChild(link);
677 """
680 """
678
681
679 _lib_t1 = """new Promise(function(resolve, reject) {
682 _lib_t1 = """new Promise(function(resolve, reject) {
680 var script = document.createElement("script");
683 var script = document.createElement("script");
681 script.onload = resolve;
684 script.onload = resolve;
682 script.onerror = reject;
685 script.onerror = reject;
683 script.src = "%s";
686 script.src = "%s";
684 document.head.appendChild(script);
687 document.head.appendChild(script);
685 }).then(() => {
688 }).then(() => {
686 """
689 """
687
690
688 _lib_t2 = """
691 _lib_t2 = """
689 });"""
692 });"""
690
693
691 class GeoJSON(JSON):
694 class GeoJSON(JSON):
692 """GeoJSON expects JSON-able dict
695 """GeoJSON expects JSON-able dict
693
696
694 not an already-serialized JSON string.
697 not an already-serialized JSON string.
695
698
696 Scalar types (None, number, string) are not allowed, only dict containers.
699 Scalar types (None, number, string) are not allowed, only dict containers.
697 """
700 """
698
701
699 def __init__(self, *args, **kwargs):
702 def __init__(self, *args, **kwargs):
700 """Create a GeoJSON display object given raw data.
703 """Create a GeoJSON display object given raw data.
701
704
702 Parameters
705 Parameters
703 ----------
706 ----------
704 data : dict or list
707 data : dict or list
705 VegaLite data. Not an already-serialized JSON string.
708 VegaLite data. Not an already-serialized JSON string.
706 Scalar types (None, number, string) are not allowed, only dict
709 Scalar types (None, number, string) are not allowed, only dict
707 or list containers.
710 or list containers.
708 url_template : string
711 url_template : string
709 Leaflet TileLayer URL template: http://leafletjs.com/reference.html#url-template
712 Leaflet TileLayer URL template: http://leafletjs.com/reference.html#url-template
710 layer_options : dict
713 layer_options : dict
711 Leaflet TileLayer options: http://leafletjs.com/reference.html#tilelayer-options
714 Leaflet TileLayer options: http://leafletjs.com/reference.html#tilelayer-options
712 url : unicode
715 url : unicode
713 A URL to download the data from.
716 A URL to download the data from.
714 filename : unicode
717 filename : unicode
715 Path to a local file to load the data from.
718 Path to a local file to load the data from.
716 metadata : dict
719 metadata : dict
717 Specify extra metadata to attach to the json display object.
720 Specify extra metadata to attach to the json display object.
718
721
719 Examples
722 Examples
720 --------
723 --------
721 The following will display an interactive map of Mars with a point of
724 The following will display an interactive map of Mars with a point of
722 interest on frontend that do support GeoJSON display.
725 interest on frontend that do support GeoJSON display.
723
726
724 >>> from IPython.display import GeoJSON
727 >>> from IPython.display import GeoJSON
725
728
726 >>> GeoJSON(data={
729 >>> GeoJSON(data={
727 ... "type": "Feature",
730 ... "type": "Feature",
728 ... "geometry": {
731 ... "geometry": {
729 ... "type": "Point",
732 ... "type": "Point",
730 ... "coordinates": [-81.327, 296.038]
733 ... "coordinates": [-81.327, 296.038]
731 ... }
734 ... }
732 ... },
735 ... },
733 ... url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
736 ... url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
734 ... layer_options={
737 ... layer_options={
735 ... "basemap_id": "celestia_mars-shaded-16k_global",
738 ... "basemap_id": "celestia_mars-shaded-16k_global",
736 ... "attribution" : "Celestia/praesepe",
739 ... "attribution" : "Celestia/praesepe",
737 ... "minZoom" : 0,
740 ... "minZoom" : 0,
738 ... "maxZoom" : 18,
741 ... "maxZoom" : 18,
739 ... })
742 ... })
740 <IPython.core.display.GeoJSON object>
743 <IPython.core.display.GeoJSON object>
741
744
742 In the terminal IPython, you will only see the text representation of
745 In the terminal IPython, you will only see the text representation of
743 the GeoJSON object.
746 the GeoJSON object.
744
747
745 """
748 """
746
749
747 super(GeoJSON, self).__init__(*args, **kwargs)
750 super(GeoJSON, self).__init__(*args, **kwargs)
748
751
749
752
750 def _ipython_display_(self):
753 def _ipython_display_(self):
751 bundle = {
754 bundle = {
752 'application/geo+json': self.data,
755 'application/geo+json': self.data,
753 'text/plain': '<IPython.display.GeoJSON object>'
756 'text/plain': '<IPython.display.GeoJSON object>'
754 }
757 }
755 metadata = {
758 metadata = {
756 'application/geo+json': self.metadata
759 'application/geo+json': self.metadata
757 }
760 }
758 display_functions.display(bundle, metadata=metadata, raw=True)
761 display_functions.display(bundle, metadata=metadata, raw=True)
759
762
760 class Javascript(TextDisplayObject):
763 class Javascript(TextDisplayObject):
761
764
762 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
765 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
763 """Create a Javascript display object given raw data.
766 """Create a Javascript display object given raw data.
764
767
765 When this object is returned by an expression or passed to the
768 When this object is returned by an expression or passed to the
766 display function, it will result in the data being displayed
769 display function, it will result in the data being displayed
767 in the frontend. If the data is a URL, the data will first be
770 in the frontend. If the data is a URL, the data will first be
768 downloaded and then displayed.
771 downloaded and then displayed.
769
772
770 In the Notebook, the containing element will be available as `element`,
773 In the Notebook, the containing element will be available as `element`,
771 and jQuery will be available. Content appended to `element` will be
774 and jQuery will be available. Content appended to `element` will be
772 visible in the output area.
775 visible in the output area.
773
776
774 Parameters
777 Parameters
775 ----------
778 ----------
776 data : unicode, str or bytes
779 data : unicode, str or bytes
777 The Javascript source code or a URL to download it from.
780 The Javascript source code or a URL to download it from.
778 url : unicode
781 url : unicode
779 A URL to download the data from.
782 A URL to download the data from.
780 filename : unicode
783 filename : unicode
781 Path to a local file to load the data from.
784 Path to a local file to load the data from.
782 lib : list or str
785 lib : list or str
783 A sequence of Javascript library URLs to load asynchronously before
786 A sequence of Javascript library URLs to load asynchronously before
784 running the source code. The full URLs of the libraries should
787 running the source code. The full URLs of the libraries should
785 be given. A single Javascript library URL can also be given as a
788 be given. A single Javascript library URL can also be given as a
786 string.
789 string.
787 css : list or str
790 css : list or str
788 A sequence of css files to load before running the source code.
791 A sequence of css files to load before running the source code.
789 The full URLs of the css files should be given. A single css URL
792 The full URLs of the css files should be given. A single css URL
790 can also be given as a string.
793 can also be given as a string.
791 """
794 """
792 if isinstance(lib, str):
795 if isinstance(lib, str):
793 lib = [lib]
796 lib = [lib]
794 elif lib is None:
797 elif lib is None:
795 lib = []
798 lib = []
796 if isinstance(css, str):
799 if isinstance(css, str):
797 css = [css]
800 css = [css]
798 elif css is None:
801 elif css is None:
799 css = []
802 css = []
800 if not isinstance(lib, (list,tuple)):
803 if not isinstance(lib, (list,tuple)):
801 raise TypeError('expected sequence, got: %r' % lib)
804 raise TypeError('expected sequence, got: %r' % lib)
802 if not isinstance(css, (list,tuple)):
805 if not isinstance(css, (list,tuple)):
803 raise TypeError('expected sequence, got: %r' % css)
806 raise TypeError('expected sequence, got: %r' % css)
804 self.lib = lib
807 self.lib = lib
805 self.css = css
808 self.css = css
806 super(Javascript, self).__init__(data=data, url=url, filename=filename)
809 super(Javascript, self).__init__(data=data, url=url, filename=filename)
807
810
808 def _repr_javascript_(self):
811 def _repr_javascript_(self):
809 r = ''
812 r = ''
810 for c in self.css:
813 for c in self.css:
811 r += _css_t % c
814 r += _css_t % c
812 for l in self.lib:
815 for l in self.lib:
813 r += _lib_t1 % l
816 r += _lib_t1 % l
814 r += self.data
817 r += self.data
815 r += _lib_t2*len(self.lib)
818 r += _lib_t2*len(self.lib)
816 return r
819 return r
817
820
818
821
819 # constants for identifying png/jpeg/gif/webp data
822 # constants for identifying png/jpeg/gif/webp data
820 _PNG = b"\x89PNG\r\n\x1a\n"
823 _PNG = b"\x89PNG\r\n\x1a\n"
821 _JPEG = b"\xff\xd8"
824 _JPEG = b"\xff\xd8"
822 _GIF1 = b"GIF87a"
825 _GIF1 = b"GIF87a"
823 _GIF2 = b"GIF89a"
826 _GIF2 = b"GIF89a"
824 _WEBP = b"WEBP"
827 _WEBP = b"WEBP"
825
828
826
829
827 def _pngxy(data):
830 def _pngxy(data):
828 """read the (width, height) from a PNG header"""
831 """read the (width, height) from a PNG header"""
829 ihdr = data.index(b'IHDR')
832 ihdr = data.index(b'IHDR')
830 # next 8 bytes are width/height
833 # next 8 bytes are width/height
831 return struct.unpack('>ii', data[ihdr+4:ihdr+12])
834 return struct.unpack('>ii', data[ihdr+4:ihdr+12])
832
835
833
836
834 def _jpegxy(data):
837 def _jpegxy(data):
835 """read the (width, height) from a JPEG header"""
838 """read the (width, height) from a JPEG header"""
836 # adapted from http://www.64lines.com/jpeg-width-height
839 # adapted from http://www.64lines.com/jpeg-width-height
837
840
838 idx = 4
841 idx = 4
839 while True:
842 while True:
840 block_size = struct.unpack('>H', data[idx:idx+2])[0]
843 block_size = struct.unpack('>H', data[idx:idx+2])[0]
841 idx = idx + block_size
844 idx = idx + block_size
842 if data[idx:idx+2] == b'\xFF\xC0':
845 if data[idx:idx+2] == b'\xFF\xC0':
843 # found Start of Frame
846 # found Start of Frame
844 iSOF = idx
847 iSOF = idx
845 break
848 break
846 else:
849 else:
847 # read another block
850 # read another block
848 idx += 2
851 idx += 2
849
852
850 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
853 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
851 return w, h
854 return w, h
852
855
853
856
854 def _gifxy(data):
857 def _gifxy(data):
855 """read the (width, height) from a GIF header"""
858 """read the (width, height) from a GIF header"""
856 return struct.unpack('<HH', data[6:10])
859 return struct.unpack('<HH', data[6:10])
857
860
858
861
859 def _webpxy(data):
862 def _webpxy(data):
860 """read the (width, height) from a WEBP header"""
863 """read the (width, height) from a WEBP header"""
861 if data[12:16] == b"VP8 ":
864 if data[12:16] == b"VP8 ":
862 width, height = struct.unpack("<HH", data[24:30])
865 width, height = struct.unpack("<HH", data[24:30])
863 width = width & 0x3FFF
866 width = width & 0x3FFF
864 height = height & 0x3FFF
867 height = height & 0x3FFF
865 return (width, height)
868 return (width, height)
866 elif data[12:16] == b"VP8L":
869 elif data[12:16] == b"VP8L":
867 size_info = struct.unpack("<I", data[21:25])[0]
870 size_info = struct.unpack("<I", data[21:25])[0]
868 width = 1 + ((size_info & 0x3F) << 8) | (size_info >> 24)
871 width = 1 + ((size_info & 0x3F) << 8) | (size_info >> 24)
869 height = 1 + (
872 height = 1 + (
870 (((size_info >> 8) & 0xF) << 10)
873 (((size_info >> 8) & 0xF) << 10)
871 | (((size_info >> 14) & 0x3FC) << 2)
874 | (((size_info >> 14) & 0x3FC) << 2)
872 | ((size_info >> 22) & 0x3)
875 | ((size_info >> 22) & 0x3)
873 )
876 )
874 return (width, height)
877 return (width, height)
875 else:
878 else:
876 raise ValueError("Not a valid WEBP header")
879 raise ValueError("Not a valid WEBP header")
877
880
878
881
879 class Image(DisplayObject):
882 class Image(DisplayObject):
880
883
881 _read_flags = "rb"
884 _read_flags = "rb"
882 _FMT_JPEG = "jpeg"
885 _FMT_JPEG = "jpeg"
883 _FMT_PNG = "png"
886 _FMT_PNG = "png"
884 _FMT_GIF = "gif"
887 _FMT_GIF = "gif"
885 _FMT_WEBP = "webp"
888 _FMT_WEBP = "webp"
886 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF, _FMT_WEBP]
889 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF, _FMT_WEBP]
887 _MIMETYPES = {
890 _MIMETYPES = {
888 _FMT_PNG: "image/png",
891 _FMT_PNG: "image/png",
889 _FMT_JPEG: "image/jpeg",
892 _FMT_JPEG: "image/jpeg",
890 _FMT_GIF: "image/gif",
893 _FMT_GIF: "image/gif",
891 _FMT_WEBP: "image/webp",
894 _FMT_WEBP: "image/webp",
892 }
895 }
893
896
894 def __init__(
897 def __init__(
895 self,
898 self,
896 data=None,
899 data=None,
897 url=None,
900 url=None,
898 filename=None,
901 filename=None,
899 format=None,
902 format=None,
900 embed=None,
903 embed=None,
901 width=None,
904 width=None,
902 height=None,
905 height=None,
903 retina=False,
906 retina=False,
904 unconfined=False,
907 unconfined=False,
905 metadata=None,
908 metadata=None,
906 alt=None,
909 alt=None,
907 ):
910 ):
908 """Create a PNG/JPEG/GIF/WEBP image object given raw data.
911 """Create a PNG/JPEG/GIF/WEBP image object given raw data.
909
912
910 When this object is returned by an input cell or passed to the
913 When this object is returned by an input cell or passed to the
911 display function, it will result in the image being displayed
914 display function, it will result in the image being displayed
912 in the frontend.
915 in the frontend.
913
916
914 Parameters
917 Parameters
915 ----------
918 ----------
916 data : unicode, str or bytes
919 data : unicode, str or bytes
917 The raw image data or a URL or filename to load the data from.
920 The raw image data or a URL or filename to load the data from.
918 This always results in embedded image data.
921 This always results in embedded image data.
919
922
920 url : unicode
923 url : unicode
921 A URL to download the data from. If you specify `url=`,
924 A URL to download the data from. If you specify `url=`,
922 the image data will not be embedded unless you also specify `embed=True`.
925 the image data will not be embedded unless you also specify `embed=True`.
923
926
924 filename : unicode
927 filename : unicode
925 Path to a local file to load the data from.
928 Path to a local file to load the data from.
926 Images from a file are always embedded.
929 Images from a file are always embedded.
927
930
928 format : unicode
931 format : unicode
929 The format of the image data (png/jpeg/jpg/gif/webp). If a filename or URL is given
932 The format of the image data (png/jpeg/jpg/gif/webp). If a filename or URL is given
930 for format will be inferred from the filename extension.
933 for format will be inferred from the filename extension.
931
934
932 embed : bool
935 embed : bool
933 Should the image data be embedded using a data URI (True) or be
936 Should the image data be embedded using a data URI (True) or be
934 loaded using an <img> tag. Set this to True if you want the image
937 loaded using an <img> tag. Set this to True if you want the image
935 to be viewable later with no internet connection in the notebook.
938 to be viewable later with no internet connection in the notebook.
936
939
937 Default is `True`, unless the keyword argument `url` is set, then
940 Default is `True`, unless the keyword argument `url` is set, then
938 default value is `False`.
941 default value is `False`.
939
942
940 Note that QtConsole is not able to display images if `embed` is set to `False`
943 Note that QtConsole is not able to display images if `embed` is set to `False`
941
944
942 width : int
945 width : int
943 Width in pixels to which to constrain the image in html
946 Width in pixels to which to constrain the image in html
944
947
945 height : int
948 height : int
946 Height in pixels to which to constrain the image in html
949 Height in pixels to which to constrain the image in html
947
950
948 retina : bool
951 retina : bool
949 Automatically set the width and height to half of the measured
952 Automatically set the width and height to half of the measured
950 width and height.
953 width and height.
951 This only works for embedded images because it reads the width/height
954 This only works for embedded images because it reads the width/height
952 from image data.
955 from image data.
953 For non-embedded images, you can just set the desired display width
956 For non-embedded images, you can just set the desired display width
954 and height directly.
957 and height directly.
955
958
956 unconfined : bool
959 unconfined : bool
957 Set unconfined=True to disable max-width confinement of the image.
960 Set unconfined=True to disable max-width confinement of the image.
958
961
959 metadata : dict
962 metadata : dict
960 Specify extra metadata to attach to the image.
963 Specify extra metadata to attach to the image.
961
964
962 alt : unicode
965 alt : unicode
963 Alternative text for the image, for use by screen readers.
966 Alternative text for the image, for use by screen readers.
964
967
965 Examples
968 Examples
966 --------
969 --------
967 embedded image data, works in qtconsole and notebook
970 embedded image data, works in qtconsole and notebook
968 when passed positionally, the first arg can be any of raw image data,
971 when passed positionally, the first arg can be any of raw image data,
969 a URL, or a filename from which to load image data.
972 a URL, or a filename from which to load image data.
970 The result is always embedding image data for inline images.
973 The result is always embedding image data for inline images.
971
974
972 >>> Image('https://www.google.fr/images/srpr/logo3w.png') # doctest: +SKIP
975 >>> Image('https://www.google.fr/images/srpr/logo3w.png') # doctest: +SKIP
973 <IPython.core.display.Image object>
976 <IPython.core.display.Image object>
974
977
975 >>> Image('/path/to/image.jpg')
978 >>> Image('/path/to/image.jpg')
976 <IPython.core.display.Image object>
979 <IPython.core.display.Image object>
977
980
978 >>> Image(b'RAW_PNG_DATA...')
981 >>> Image(b'RAW_PNG_DATA...')
979 <IPython.core.display.Image object>
982 <IPython.core.display.Image object>
980
983
981 Specifying Image(url=...) does not embed the image data,
984 Specifying Image(url=...) does not embed the image data,
982 it only generates ``<img>`` tag with a link to the source.
985 it only generates ``<img>`` tag with a link to the source.
983 This will not work in the qtconsole or offline.
986 This will not work in the qtconsole or offline.
984
987
985 >>> Image(url='https://www.google.fr/images/srpr/logo3w.png')
988 >>> Image(url='https://www.google.fr/images/srpr/logo3w.png')
986 <IPython.core.display.Image object>
989 <IPython.core.display.Image object>
987
990
988 """
991 """
989 if isinstance(data, (Path, PurePath)):
992 if isinstance(data, (Path, PurePath)):
990 data = str(data)
993 data = str(data)
991
994
992 if filename is not None:
995 if filename is not None:
993 ext = self._find_ext(filename)
996 ext = self._find_ext(filename)
994 elif url is not None:
997 elif url is not None:
995 ext = self._find_ext(url)
998 ext = self._find_ext(url)
996 elif data is None:
999 elif data is None:
997 raise ValueError("No image data found. Expecting filename, url, or data.")
1000 raise ValueError("No image data found. Expecting filename, url, or data.")
998 elif isinstance(data, str) and (
1001 elif isinstance(data, str) and (
999 data.startswith('http') or _safe_exists(data)
1002 data.startswith('http') or _safe_exists(data)
1000 ):
1003 ):
1001 ext = self._find_ext(data)
1004 ext = self._find_ext(data)
1002 else:
1005 else:
1003 ext = None
1006 ext = None
1004
1007
1005 if format is None:
1008 if format is None:
1006 if ext is not None:
1009 if ext is not None:
1007 if ext == u'jpg' or ext == u'jpeg':
1010 if ext == u'jpg' or ext == u'jpeg':
1008 format = self._FMT_JPEG
1011 format = self._FMT_JPEG
1009 elif ext == u'png':
1012 elif ext == u'png':
1010 format = self._FMT_PNG
1013 format = self._FMT_PNG
1011 elif ext == u'gif':
1014 elif ext == u'gif':
1012 format = self._FMT_GIF
1015 format = self._FMT_GIF
1013 elif ext == "webp":
1016 elif ext == "webp":
1014 format = self._FMT_WEBP
1017 format = self._FMT_WEBP
1015 else:
1018 else:
1016 format = ext.lower()
1019 format = ext.lower()
1017 elif isinstance(data, bytes):
1020 elif isinstance(data, bytes):
1018 # infer image type from image data header,
1021 # infer image type from image data header,
1019 # only if format has not been specified.
1022 # only if format has not been specified.
1020 if data[:2] == _JPEG:
1023 if data[:2] == _JPEG:
1021 format = self._FMT_JPEG
1024 format = self._FMT_JPEG
1022 elif data[:8] == _PNG:
1025 elif data[:8] == _PNG:
1023 format = self._FMT_PNG
1026 format = self._FMT_PNG
1024 elif data[8:12] == _WEBP:
1027 elif data[8:12] == _WEBP:
1025 format = self._FMT_WEBP
1028 format = self._FMT_WEBP
1026 elif data[:6] == _GIF1 or data[:6] == _GIF2:
1029 elif data[:6] == _GIF1 or data[:6] == _GIF2:
1027 format = self._FMT_GIF
1030 format = self._FMT_GIF
1028
1031
1029 # failed to detect format, default png
1032 # failed to detect format, default png
1030 if format is None:
1033 if format is None:
1031 format = self._FMT_PNG
1034 format = self._FMT_PNG
1032
1035
1033 if format.lower() == 'jpg':
1036 if format.lower() == 'jpg':
1034 # jpg->jpeg
1037 # jpg->jpeg
1035 format = self._FMT_JPEG
1038 format = self._FMT_JPEG
1036
1039
1037 self.format = format.lower()
1040 self.format = format.lower()
1038 self.embed = embed if embed is not None else (url is None)
1041 self.embed = embed if embed is not None else (url is None)
1039
1042
1040 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
1043 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
1041 raise ValueError("Cannot embed the '%s' image format" % (self.format))
1044 raise ValueError("Cannot embed the '%s' image format" % (self.format))
1042 if self.embed:
1045 if self.embed:
1043 self._mimetype = self._MIMETYPES.get(self.format)
1046 self._mimetype = self._MIMETYPES.get(self.format)
1044
1047
1045 self.width = width
1048 self.width = width
1046 self.height = height
1049 self.height = height
1047 self.retina = retina
1050 self.retina = retina
1048 self.unconfined = unconfined
1051 self.unconfined = unconfined
1049 self.alt = alt
1052 self.alt = alt
1050 super(Image, self).__init__(data=data, url=url, filename=filename,
1053 super(Image, self).__init__(data=data, url=url, filename=filename,
1051 metadata=metadata)
1054 metadata=metadata)
1052
1055
1053 if self.width is None and self.metadata.get('width', {}):
1056 if self.width is None and self.metadata.get('width', {}):
1054 self.width = metadata['width']
1057 self.width = metadata['width']
1055
1058
1056 if self.height is None and self.metadata.get('height', {}):
1059 if self.height is None and self.metadata.get('height', {}):
1057 self.height = metadata['height']
1060 self.height = metadata['height']
1058
1061
1059 if self.alt is None and self.metadata.get("alt", {}):
1062 if self.alt is None and self.metadata.get("alt", {}):
1060 self.alt = metadata["alt"]
1063 self.alt = metadata["alt"]
1061
1064
1062 if retina:
1065 if retina:
1063 self._retina_shape()
1066 self._retina_shape()
1064
1067
1065
1068
1066 def _retina_shape(self):
1069 def _retina_shape(self):
1067 """load pixel-doubled width and height from image data"""
1070 """load pixel-doubled width and height from image data"""
1068 if not self.embed:
1071 if not self.embed:
1069 return
1072 return
1070 if self.format == self._FMT_PNG:
1073 if self.format == self._FMT_PNG:
1071 w, h = _pngxy(self.data)
1074 w, h = _pngxy(self.data)
1072 elif self.format == self._FMT_JPEG:
1075 elif self.format == self._FMT_JPEG:
1073 w, h = _jpegxy(self.data)
1076 w, h = _jpegxy(self.data)
1074 elif self.format == self._FMT_GIF:
1077 elif self.format == self._FMT_GIF:
1075 w, h = _gifxy(self.data)
1078 w, h = _gifxy(self.data)
1076 else:
1079 else:
1077 # retina only supports png
1080 # retina only supports png
1078 return
1081 return
1079 self.width = w // 2
1082 self.width = w // 2
1080 self.height = h // 2
1083 self.height = h // 2
1081
1084
1082 def reload(self):
1085 def reload(self):
1083 """Reload the raw data from file or URL."""
1086 """Reload the raw data from file or URL."""
1084 if self.embed:
1087 if self.embed:
1085 super(Image,self).reload()
1088 super(Image,self).reload()
1086 if self.retina:
1089 if self.retina:
1087 self._retina_shape()
1090 self._retina_shape()
1088
1091
1089 def _repr_html_(self):
1092 def _repr_html_(self):
1090 if not self.embed:
1093 if not self.embed:
1091 width = height = klass = alt = ""
1094 width = height = klass = alt = ""
1092 if self.width:
1095 if self.width:
1093 width = ' width="%d"' % self.width
1096 width = ' width="%d"' % self.width
1094 if self.height:
1097 if self.height:
1095 height = ' height="%d"' % self.height
1098 height = ' height="%d"' % self.height
1096 if self.unconfined:
1099 if self.unconfined:
1097 klass = ' class="unconfined"'
1100 klass = ' class="unconfined"'
1098 if self.alt:
1101 if self.alt:
1099 alt = ' alt="%s"' % html.escape(self.alt)
1102 alt = ' alt="%s"' % html.escape(self.alt)
1100 return '<img src="{url}"{width}{height}{klass}{alt}/>'.format(
1103 return '<img src="{url}"{width}{height}{klass}{alt}/>'.format(
1101 url=self.url,
1104 url=self.url,
1102 width=width,
1105 width=width,
1103 height=height,
1106 height=height,
1104 klass=klass,
1107 klass=klass,
1105 alt=alt,
1108 alt=alt,
1106 )
1109 )
1107
1110
1108 def _repr_mimebundle_(self, include=None, exclude=None):
1111 def _repr_mimebundle_(self, include=None, exclude=None):
1109 """Return the image as a mimebundle
1112 """Return the image as a mimebundle
1110
1113
1111 Any new mimetype support should be implemented here.
1114 Any new mimetype support should be implemented here.
1112 """
1115 """
1113 if self.embed:
1116 if self.embed:
1114 mimetype = self._mimetype
1117 mimetype = self._mimetype
1115 data, metadata = self._data_and_metadata(always_both=True)
1118 data, metadata = self._data_and_metadata(always_both=True)
1116 if metadata:
1119 if metadata:
1117 metadata = {mimetype: metadata}
1120 metadata = {mimetype: metadata}
1118 return {mimetype: data}, metadata
1121 return {mimetype: data}, metadata
1119 else:
1122 else:
1120 return {'text/html': self._repr_html_()}
1123 return {'text/html': self._repr_html_()}
1121
1124
1122 def _data_and_metadata(self, always_both=False):
1125 def _data_and_metadata(self, always_both=False):
1123 """shortcut for returning metadata with shape information, if defined"""
1126 """shortcut for returning metadata with shape information, if defined"""
1124 try:
1127 try:
1125 b64_data = b2a_base64(self.data, newline=False).decode("ascii")
1128 b64_data = b2a_base64(self.data, newline=False).decode("ascii")
1126 except TypeError as e:
1129 except TypeError as e:
1127 raise FileNotFoundError(
1130 raise FileNotFoundError(
1128 "No such file or directory: '%s'" % (self.data)) from e
1131 "No such file or directory: '%s'" % (self.data)) from e
1129 md = {}
1132 md = {}
1130 if self.metadata:
1133 if self.metadata:
1131 md.update(self.metadata)
1134 md.update(self.metadata)
1132 if self.width:
1135 if self.width:
1133 md['width'] = self.width
1136 md['width'] = self.width
1134 if self.height:
1137 if self.height:
1135 md['height'] = self.height
1138 md['height'] = self.height
1136 if self.unconfined:
1139 if self.unconfined:
1137 md['unconfined'] = self.unconfined
1140 md['unconfined'] = self.unconfined
1138 if self.alt:
1141 if self.alt:
1139 md["alt"] = self.alt
1142 md["alt"] = self.alt
1140 if md or always_both:
1143 if md or always_both:
1141 return b64_data, md
1144 return b64_data, md
1142 else:
1145 else:
1143 return b64_data
1146 return b64_data
1144
1147
1145 def _repr_png_(self):
1148 def _repr_png_(self):
1146 if self.embed and self.format == self._FMT_PNG:
1149 if self.embed and self.format == self._FMT_PNG:
1147 return self._data_and_metadata()
1150 return self._data_and_metadata()
1148
1151
1149 def _repr_jpeg_(self):
1152 def _repr_jpeg_(self):
1150 if self.embed and self.format == self._FMT_JPEG:
1153 if self.embed and self.format == self._FMT_JPEG:
1151 return self._data_and_metadata()
1154 return self._data_and_metadata()
1152
1155
1153 def _find_ext(self, s):
1156 def _find_ext(self, s):
1154 base, ext = splitext(s)
1157 base, ext = splitext(s)
1155
1158
1156 if not ext:
1159 if not ext:
1157 return base
1160 return base
1158
1161
1159 # `splitext` includes leading period, so we skip it
1162 # `splitext` includes leading period, so we skip it
1160 return ext[1:].lower()
1163 return ext[1:].lower()
1161
1164
1162
1165
1163 class Video(DisplayObject):
1166 class Video(DisplayObject):
1164
1167
1165 def __init__(self, data=None, url=None, filename=None, embed=False,
1168 def __init__(self, data=None, url=None, filename=None, embed=False,
1166 mimetype=None, width=None, height=None, html_attributes="controls"):
1169 mimetype=None, width=None, height=None, html_attributes="controls"):
1167 """Create a video object given raw data or an URL.
1170 """Create a video object given raw data or an URL.
1168
1171
1169 When this object is returned by an input cell or passed to the
1172 When this object is returned by an input cell or passed to the
1170 display function, it will result in the video being displayed
1173 display function, it will result in the video being displayed
1171 in the frontend.
1174 in the frontend.
1172
1175
1173 Parameters
1176 Parameters
1174 ----------
1177 ----------
1175 data : unicode, str or bytes
1178 data : unicode, str or bytes
1176 The raw video data or a URL or filename to load the data from.
1179 The raw video data or a URL or filename to load the data from.
1177 Raw data will require passing ``embed=True``.
1180 Raw data will require passing ``embed=True``.
1178
1181
1179 url : unicode
1182 url : unicode
1180 A URL for the video. If you specify ``url=``,
1183 A URL for the video. If you specify ``url=``,
1181 the image data will not be embedded.
1184 the image data will not be embedded.
1182
1185
1183 filename : unicode
1186 filename : unicode
1184 Path to a local file containing the video.
1187 Path to a local file containing the video.
1185 Will be interpreted as a local URL unless ``embed=True``.
1188 Will be interpreted as a local URL unless ``embed=True``.
1186
1189
1187 embed : bool
1190 embed : bool
1188 Should the video be embedded using a data URI (True) or be
1191 Should the video be embedded using a data URI (True) or be
1189 loaded using a <video> tag (False).
1192 loaded using a <video> tag (False).
1190
1193
1191 Since videos are large, embedding them should be avoided, if possible.
1194 Since videos are large, embedding them should be avoided, if possible.
1192 You must confirm embedding as your intention by passing ``embed=True``.
1195 You must confirm embedding as your intention by passing ``embed=True``.
1193
1196
1194 Local files can be displayed with URLs without embedding the content, via::
1197 Local files can be displayed with URLs without embedding the content, via::
1195
1198
1196 Video('./video.mp4')
1199 Video('./video.mp4')
1197
1200
1198 mimetype : unicode
1201 mimetype : unicode
1199 Specify the mimetype for embedded videos.
1202 Specify the mimetype for embedded videos.
1200 Default will be guessed from file extension, if available.
1203 Default will be guessed from file extension, if available.
1201
1204
1202 width : int
1205 width : int
1203 Width in pixels to which to constrain the video in HTML.
1206 Width in pixels to which to constrain the video in HTML.
1204 If not supplied, defaults to the width of the video.
1207 If not supplied, defaults to the width of the video.
1205
1208
1206 height : int
1209 height : int
1207 Height in pixels to which to constrain the video in html.
1210 Height in pixels to which to constrain the video in html.
1208 If not supplied, defaults to the height of the video.
1211 If not supplied, defaults to the height of the video.
1209
1212
1210 html_attributes : str
1213 html_attributes : str
1211 Attributes for the HTML ``<video>`` block.
1214 Attributes for the HTML ``<video>`` block.
1212 Default: ``"controls"`` to get video controls.
1215 Default: ``"controls"`` to get video controls.
1213 Other examples: ``"controls muted"`` for muted video with controls,
1216 Other examples: ``"controls muted"`` for muted video with controls,
1214 ``"loop autoplay"`` for looping autoplaying video without controls.
1217 ``"loop autoplay"`` for looping autoplaying video without controls.
1215
1218
1216 Examples
1219 Examples
1217 --------
1220 --------
1218 ::
1221 ::
1219
1222
1220 Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4')
1223 Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4')
1221 Video('path/to/video.mp4')
1224 Video('path/to/video.mp4')
1222 Video('path/to/video.mp4', embed=True)
1225 Video('path/to/video.mp4', embed=True)
1223 Video('path/to/video.mp4', embed=True, html_attributes="controls muted autoplay")
1226 Video('path/to/video.mp4', embed=True, html_attributes="controls muted autoplay")
1224 Video(b'raw-videodata', embed=True)
1227 Video(b'raw-videodata', embed=True)
1225 """
1228 """
1226 if isinstance(data, (Path, PurePath)):
1229 if isinstance(data, (Path, PurePath)):
1227 data = str(data)
1230 data = str(data)
1228
1231
1229 if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')):
1232 if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')):
1230 url = data
1233 url = data
1231 data = None
1234 data = None
1232 elif data is not None and os.path.exists(data):
1235 elif data is not None and os.path.exists(data):
1233 filename = data
1236 filename = data
1234 data = None
1237 data = None
1235
1238
1236 if data and not embed:
1239 if data and not embed:
1237 msg = ''.join([
1240 msg = ''.join([
1238 "To embed videos, you must pass embed=True ",
1241 "To embed videos, you must pass embed=True ",
1239 "(this may make your notebook files huge)\n",
1242 "(this may make your notebook files huge)\n",
1240 "Consider passing Video(url='...')",
1243 "Consider passing Video(url='...')",
1241 ])
1244 ])
1242 raise ValueError(msg)
1245 raise ValueError(msg)
1243
1246
1244 self.mimetype = mimetype
1247 self.mimetype = mimetype
1245 self.embed = embed
1248 self.embed = embed
1246 self.width = width
1249 self.width = width
1247 self.height = height
1250 self.height = height
1248 self.html_attributes = html_attributes
1251 self.html_attributes = html_attributes
1249 super(Video, self).__init__(data=data, url=url, filename=filename)
1252 super(Video, self).__init__(data=data, url=url, filename=filename)
1250
1253
1251 def _repr_html_(self):
1254 def _repr_html_(self):
1252 width = height = ''
1255 width = height = ''
1253 if self.width:
1256 if self.width:
1254 width = ' width="%d"' % self.width
1257 width = ' width="%d"' % self.width
1255 if self.height:
1258 if self.height:
1256 height = ' height="%d"' % self.height
1259 height = ' height="%d"' % self.height
1257
1260
1258 # External URLs and potentially local files are not embedded into the
1261 # External URLs and potentially local files are not embedded into the
1259 # notebook output.
1262 # notebook output.
1260 if not self.embed:
1263 if not self.embed:
1261 url = self.url if self.url is not None else self.filename
1264 url = self.url if self.url is not None else self.filename
1262 output = """<video src="{0}" {1} {2} {3}>
1265 output = """<video src="{0}" {1} {2} {3}>
1263 Your browser does not support the <code>video</code> element.
1266 Your browser does not support the <code>video</code> element.
1264 </video>""".format(url, self.html_attributes, width, height)
1267 </video>""".format(url, self.html_attributes, width, height)
1265 return output
1268 return output
1266
1269
1267 # Embedded videos are base64-encoded.
1270 # Embedded videos are base64-encoded.
1268 mimetype = self.mimetype
1271 mimetype = self.mimetype
1269 if self.filename is not None:
1272 if self.filename is not None:
1270 if not mimetype:
1273 if not mimetype:
1271 mimetype, _ = mimetypes.guess_type(self.filename)
1274 mimetype, _ = mimetypes.guess_type(self.filename)
1272
1275
1273 with open(self.filename, 'rb') as f:
1276 with open(self.filename, 'rb') as f:
1274 video = f.read()
1277 video = f.read()
1275 else:
1278 else:
1276 video = self.data
1279 video = self.data
1277 if isinstance(video, str):
1280 if isinstance(video, str):
1278 # unicode input is already b64-encoded
1281 # unicode input is already b64-encoded
1279 b64_video = video
1282 b64_video = video
1280 else:
1283 else:
1281 b64_video = b2a_base64(video, newline=False).decode("ascii").rstrip()
1284 b64_video = b2a_base64(video, newline=False).decode("ascii").rstrip()
1282
1285
1283 output = """<video {0} {1} {2}>
1286 output = """<video {0} {1} {2}>
1284 <source src="data:{3};base64,{4}" type="{3}">
1287 <source src="data:{3};base64,{4}" type="{3}">
1285 Your browser does not support the video tag.
1288 Your browser does not support the video tag.
1286 </video>""".format(self.html_attributes, width, height, mimetype, b64_video)
1289 </video>""".format(self.html_attributes, width, height, mimetype, b64_video)
1287 return output
1290 return output
1288
1291
1289 def reload(self):
1292 def reload(self):
1290 # TODO
1293 # TODO
1291 pass
1294 pass
1292
1295
1293
1296
1294 @skip_doctest
1297 @skip_doctest
1295 def set_matplotlib_formats(*formats, **kwargs):
1298 def set_matplotlib_formats(*formats, **kwargs):
1296 """
1299 """
1297 .. deprecated:: 7.23
1300 .. deprecated:: 7.23
1298
1301
1299 use `matplotlib_inline.backend_inline.set_matplotlib_formats()`
1302 use `matplotlib_inline.backend_inline.set_matplotlib_formats()`
1300
1303
1301 Select figure formats for the inline backend. Optionally pass quality for JPEG.
1304 Select figure formats for the inline backend. Optionally pass quality for JPEG.
1302
1305
1303 For example, this enables PNG and JPEG output with a JPEG quality of 90%::
1306 For example, this enables PNG and JPEG output with a JPEG quality of 90%::
1304
1307
1305 In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
1308 In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
1306
1309
1307 To set this in your config files use the following::
1310 To set this in your config files use the following::
1308
1311
1309 c.InlineBackend.figure_formats = {'png', 'jpeg'}
1312 c.InlineBackend.figure_formats = {'png', 'jpeg'}
1310 c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
1313 c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
1311
1314
1312 Parameters
1315 Parameters
1313 ----------
1316 ----------
1314 *formats : strs
1317 *formats : strs
1315 One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
1318 One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
1316 **kwargs
1319 **kwargs
1317 Keyword args will be relayed to ``figure.canvas.print_figure``.
1320 Keyword args will be relayed to ``figure.canvas.print_figure``.
1318 """
1321 """
1319 warnings.warn(
1322 warnings.warn(
1320 "`set_matplotlib_formats` is deprecated since IPython 7.23, directly "
1323 "`set_matplotlib_formats` is deprecated since IPython 7.23, directly "
1321 "use `matplotlib_inline.backend_inline.set_matplotlib_formats()`",
1324 "use `matplotlib_inline.backend_inline.set_matplotlib_formats()`",
1322 DeprecationWarning,
1325 DeprecationWarning,
1323 stacklevel=2,
1326 stacklevel=2,
1324 )
1327 )
1325
1328
1326 from matplotlib_inline.backend_inline import (
1329 from matplotlib_inline.backend_inline import (
1327 set_matplotlib_formats as set_matplotlib_formats_orig,
1330 set_matplotlib_formats as set_matplotlib_formats_orig,
1328 )
1331 )
1329
1332
1330 set_matplotlib_formats_orig(*formats, **kwargs)
1333 set_matplotlib_formats_orig(*formats, **kwargs)
1331
1334
1332 @skip_doctest
1335 @skip_doctest
1333 def set_matplotlib_close(close=True):
1336 def set_matplotlib_close(close=True):
1334 """
1337 """
1335 .. deprecated:: 7.23
1338 .. deprecated:: 7.23
1336
1339
1337 use `matplotlib_inline.backend_inline.set_matplotlib_close()`
1340 use `matplotlib_inline.backend_inline.set_matplotlib_close()`
1338
1341
1339 Set whether the inline backend closes all figures automatically or not.
1342 Set whether the inline backend closes all figures automatically or not.
1340
1343
1341 By default, the inline backend used in the IPython Notebook will close all
1344 By default, the inline backend used in the IPython Notebook will close all
1342 matplotlib figures automatically after each cell is run. This means that
1345 matplotlib figures automatically after each cell is run. This means that
1343 plots in different cells won't interfere. Sometimes, you may want to make
1346 plots in different cells won't interfere. Sometimes, you may want to make
1344 a plot in one cell and then refine it in later cells. This can be accomplished
1347 a plot in one cell and then refine it in later cells. This can be accomplished
1345 by::
1348 by::
1346
1349
1347 In [1]: set_matplotlib_close(False)
1350 In [1]: set_matplotlib_close(False)
1348
1351
1349 To set this in your config files use the following::
1352 To set this in your config files use the following::
1350
1353
1351 c.InlineBackend.close_figures = False
1354 c.InlineBackend.close_figures = False
1352
1355
1353 Parameters
1356 Parameters
1354 ----------
1357 ----------
1355 close : bool
1358 close : bool
1356 Should all matplotlib figures be automatically closed after each cell is
1359 Should all matplotlib figures be automatically closed after each cell is
1357 run?
1360 run?
1358 """
1361 """
1359 warnings.warn(
1362 warnings.warn(
1360 "`set_matplotlib_close` is deprecated since IPython 7.23, directly "
1363 "`set_matplotlib_close` is deprecated since IPython 7.23, directly "
1361 "use `matplotlib_inline.backend_inline.set_matplotlib_close()`",
1364 "use `matplotlib_inline.backend_inline.set_matplotlib_close()`",
1362 DeprecationWarning,
1365 DeprecationWarning,
1363 stacklevel=2,
1366 stacklevel=2,
1364 )
1367 )
1365
1368
1366 from matplotlib_inline.backend_inline import (
1369 from matplotlib_inline.backend_inline import (
1367 set_matplotlib_close as set_matplotlib_close_orig,
1370 set_matplotlib_close as set_matplotlib_close_orig,
1368 )
1371 )
1369
1372
1370 set_matplotlib_close_orig(close)
1373 set_matplotlib_close_orig(close)
@@ -1,257 +1,257
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tools for handling LaTeX."""
2 """Tools for handling LaTeX."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from io import BytesIO, open
7 from io import BytesIO, open
8 import os
8 import os
9 import tempfile
9 import tempfile
10 import shutil
10 import shutil
11 import subprocess
11 import subprocess
12 from base64 import encodebytes
12 from base64 import encodebytes
13 import textwrap
13 import textwrap
14
14
15 from pathlib import Path
15 from pathlib import Path
16
16
17 from IPython.utils.process import find_cmd, FindCmdError
17 from IPython.utils.process import find_cmd, FindCmdError
18 from traitlets.config import get_config
18 from traitlets.config import get_config
19 from traitlets.config.configurable import SingletonConfigurable
19 from traitlets.config.configurable import SingletonConfigurable
20 from traitlets import List, Bool, Unicode
20 from traitlets import List, Bool, Unicode
21 from IPython.utils.py3compat import cast_unicode
22
21
23
22
24 class LaTeXTool(SingletonConfigurable):
23 class LaTeXTool(SingletonConfigurable):
25 """An object to store configuration of the LaTeX tool."""
24 """An object to store configuration of the LaTeX tool."""
26 def _config_default(self):
25 def _config_default(self):
27 return get_config()
26 return get_config()
28
27
29 backends = List(
28 backends = List(
30 Unicode(), ["matplotlib", "dvipng"],
29 Unicode(), ["matplotlib", "dvipng"],
31 help="Preferred backend to draw LaTeX math equations. "
30 help="Preferred backend to draw LaTeX math equations. "
32 "Backends in the list are checked one by one and the first "
31 "Backends in the list are checked one by one and the first "
33 "usable one is used. Note that `matplotlib` backend "
32 "usable one is used. Note that `matplotlib` backend "
34 "is usable only for inline style equations. To draw "
33 "is usable only for inline style equations. To draw "
35 "display style equations, `dvipng` backend must be specified. ",
34 "display style equations, `dvipng` backend must be specified. ",
36 # It is a List instead of Enum, to make configuration more
35 # It is a List instead of Enum, to make configuration more
37 # flexible. For example, to use matplotlib mainly but dvipng
36 # flexible. For example, to use matplotlib mainly but dvipng
38 # for display style, the default ["matplotlib", "dvipng"] can
37 # for display style, the default ["matplotlib", "dvipng"] can
39 # be used. To NOT use dvipng so that other repr such as
38 # be used. To NOT use dvipng so that other repr such as
40 # unicode pretty printing is used, you can use ["matplotlib"].
39 # unicode pretty printing is used, you can use ["matplotlib"].
41 ).tag(config=True)
40 ).tag(config=True)
42
41
43 use_breqn = Bool(
42 use_breqn = Bool(
44 True,
43 True,
45 help="Use breqn.sty to automatically break long equations. "
44 help="Use breqn.sty to automatically break long equations. "
46 "This configuration takes effect only for dvipng backend.",
45 "This configuration takes effect only for dvipng backend.",
47 ).tag(config=True)
46 ).tag(config=True)
48
47
49 packages = List(
48 packages = List(
50 ['amsmath', 'amsthm', 'amssymb', 'bm'],
49 ['amsmath', 'amsthm', 'amssymb', 'bm'],
51 help="A list of packages to use for dvipng backend. "
50 help="A list of packages to use for dvipng backend. "
52 "'breqn' will be automatically appended when use_breqn=True.",
51 "'breqn' will be automatically appended when use_breqn=True.",
53 ).tag(config=True)
52 ).tag(config=True)
54
53
55 preamble = Unicode(
54 preamble = Unicode(
56 help="Additional preamble to use when generating LaTeX source "
55 help="Additional preamble to use when generating LaTeX source "
57 "for dvipng backend.",
56 "for dvipng backend.",
58 ).tag(config=True)
57 ).tag(config=True)
59
58
60
59
61 def latex_to_png(s, encode=False, backend=None, wrap=False, color='Black',
60 def latex_to_png(
62 scale=1.0):
61 s: str, encode=False, backend=None, wrap=False, color="Black", scale=1.0
62 ):
63 """Render a LaTeX string to PNG.
63 """Render a LaTeX string to PNG.
64
64
65 Parameters
65 Parameters
66 ----------
66 ----------
67 s : str
67 s : str
68 The raw string containing valid inline LaTeX.
68 The raw string containing valid inline LaTeX.
69 encode : bool, optional
69 encode : bool, optional
70 Should the PNG data base64 encoded to make it JSON'able.
70 Should the PNG data base64 encoded to make it JSON'able.
71 backend : {matplotlib, dvipng}
71 backend : {matplotlib, dvipng}
72 Backend for producing PNG data.
72 Backend for producing PNG data.
73 wrap : bool
73 wrap : bool
74 If true, Automatically wrap `s` as a LaTeX equation.
74 If true, Automatically wrap `s` as a LaTeX equation.
75 color : string
75 color : string
76 Foreground color name among dvipsnames, e.g. 'Maroon' or on hex RGB
76 Foreground color name among dvipsnames, e.g. 'Maroon' or on hex RGB
77 format, e.g. '#AA20FA'.
77 format, e.g. '#AA20FA'.
78 scale : float
78 scale : float
79 Scale factor for the resulting PNG.
79 Scale factor for the resulting PNG.
80 None is returned when the backend cannot be used.
80 None is returned when the backend cannot be used.
81
81
82 """
82 """
83 s = cast_unicode(s)
83 assert isinstance(s, str)
84 allowed_backends = LaTeXTool.instance().backends
84 allowed_backends = LaTeXTool.instance().backends
85 if backend is None:
85 if backend is None:
86 backend = allowed_backends[0]
86 backend = allowed_backends[0]
87 if backend not in allowed_backends:
87 if backend not in allowed_backends:
88 return None
88 return None
89 if backend == 'matplotlib':
89 if backend == 'matplotlib':
90 f = latex_to_png_mpl
90 f = latex_to_png_mpl
91 elif backend == 'dvipng':
91 elif backend == 'dvipng':
92 f = latex_to_png_dvipng
92 f = latex_to_png_dvipng
93 if color.startswith('#'):
93 if color.startswith('#'):
94 # Convert hex RGB color to LaTeX RGB color.
94 # Convert hex RGB color to LaTeX RGB color.
95 if len(color) == 7:
95 if len(color) == 7:
96 try:
96 try:
97 color = "RGB {}".format(" ".join([str(int(x, 16)) for x in
97 color = "RGB {}".format(" ".join([str(int(x, 16)) for x in
98 textwrap.wrap(color[1:], 2)]))
98 textwrap.wrap(color[1:], 2)]))
99 except ValueError as e:
99 except ValueError as e:
100 raise ValueError('Invalid color specification {}.'.format(color)) from e
100 raise ValueError('Invalid color specification {}.'.format(color)) from e
101 else:
101 else:
102 raise ValueError('Invalid color specification {}.'.format(color))
102 raise ValueError('Invalid color specification {}.'.format(color))
103 else:
103 else:
104 raise ValueError('No such backend {0}'.format(backend))
104 raise ValueError('No such backend {0}'.format(backend))
105 bin_data = f(s, wrap, color, scale)
105 bin_data = f(s, wrap, color, scale)
106 if encode and bin_data:
106 if encode and bin_data:
107 bin_data = encodebytes(bin_data)
107 bin_data = encodebytes(bin_data)
108 return bin_data
108 return bin_data
109
109
110
110
111 def latex_to_png_mpl(s, wrap, color='Black', scale=1.0):
111 def latex_to_png_mpl(s, wrap, color='Black', scale=1.0):
112 try:
112 try:
113 from matplotlib import figure, font_manager, mathtext
113 from matplotlib import figure, font_manager, mathtext
114 from matplotlib.backends import backend_agg
114 from matplotlib.backends import backend_agg
115 from pyparsing import ParseFatalException
115 from pyparsing import ParseFatalException
116 except ImportError:
116 except ImportError:
117 return None
117 return None
118
118
119 # mpl mathtext doesn't support display math, force inline
119 # mpl mathtext doesn't support display math, force inline
120 s = s.replace('$$', '$')
120 s = s.replace('$$', '$')
121 if wrap:
121 if wrap:
122 s = u'${0}$'.format(s)
122 s = u'${0}$'.format(s)
123
123
124 try:
124 try:
125 prop = font_manager.FontProperties(size=12)
125 prop = font_manager.FontProperties(size=12)
126 dpi = 120 * scale
126 dpi = 120 * scale
127 buffer = BytesIO()
127 buffer = BytesIO()
128
128
129 # Adapted from mathtext.math_to_image
129 # Adapted from mathtext.math_to_image
130 parser = mathtext.MathTextParser("path")
130 parser = mathtext.MathTextParser("path")
131 width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
131 width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
132 fig = figure.Figure(figsize=(width / 72, height / 72))
132 fig = figure.Figure(figsize=(width / 72, height / 72))
133 fig.text(0, depth / height, s, fontproperties=prop, color=color)
133 fig.text(0, depth / height, s, fontproperties=prop, color=color)
134 backend_agg.FigureCanvasAgg(fig)
134 backend_agg.FigureCanvasAgg(fig)
135 fig.savefig(buffer, dpi=dpi, format="png", transparent=True)
135 fig.savefig(buffer, dpi=dpi, format="png", transparent=True)
136 return buffer.getvalue()
136 return buffer.getvalue()
137 except (ValueError, RuntimeError, ParseFatalException):
137 except (ValueError, RuntimeError, ParseFatalException):
138 return None
138 return None
139
139
140
140
141 def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0):
141 def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0):
142 try:
142 try:
143 find_cmd('latex')
143 find_cmd('latex')
144 find_cmd('dvipng')
144 find_cmd('dvipng')
145 except FindCmdError:
145 except FindCmdError:
146 return None
146 return None
147
147
148 startupinfo = None
148 startupinfo = None
149 if os.name == "nt":
149 if os.name == "nt":
150 # prevent popup-windows
150 # prevent popup-windows
151 startupinfo = subprocess.STARTUPINFO()
151 startupinfo = subprocess.STARTUPINFO()
152 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
152 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
153
153
154 try:
154 try:
155 workdir = Path(tempfile.mkdtemp())
155 workdir = Path(tempfile.mkdtemp())
156 tmpfile = "tmp.tex"
156 tmpfile = "tmp.tex"
157 dvifile = "tmp.dvi"
157 dvifile = "tmp.dvi"
158 outfile = "tmp.png"
158 outfile = "tmp.png"
159
159
160 with workdir.joinpath(tmpfile).open("w", encoding="utf8") as f:
160 with workdir.joinpath(tmpfile).open("w", encoding="utf8") as f:
161 f.writelines(genelatex(s, wrap))
161 f.writelines(genelatex(s, wrap))
162
162
163 subprocess.check_call(
163 subprocess.check_call(
164 ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile],
164 ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile],
165 cwd=workdir,
165 cwd=workdir,
166 stdout=subprocess.DEVNULL,
166 stdout=subprocess.DEVNULL,
167 stderr=subprocess.DEVNULL,
167 stderr=subprocess.DEVNULL,
168 startupinfo=startupinfo,
168 startupinfo=startupinfo,
169 )
169 )
170
170
171 resolution = round(150 * scale)
171 resolution = round(150 * scale)
172 subprocess.check_call(
172 subprocess.check_call(
173 [
173 [
174 "dvipng",
174 "dvipng",
175 "-T",
175 "-T",
176 "tight",
176 "tight",
177 "-D",
177 "-D",
178 str(resolution),
178 str(resolution),
179 "-z",
179 "-z",
180 "9",
180 "9",
181 "-bg",
181 "-bg",
182 "Transparent",
182 "Transparent",
183 "-o",
183 "-o",
184 outfile,
184 outfile,
185 dvifile,
185 dvifile,
186 "-fg",
186 "-fg",
187 color,
187 color,
188 ],
188 ],
189 cwd=workdir,
189 cwd=workdir,
190 stdout=subprocess.DEVNULL,
190 stdout=subprocess.DEVNULL,
191 stderr=subprocess.DEVNULL,
191 stderr=subprocess.DEVNULL,
192 startupinfo=startupinfo,
192 startupinfo=startupinfo,
193 )
193 )
194
194
195 with workdir.joinpath(outfile).open("rb") as f:
195 with workdir.joinpath(outfile).open("rb") as f:
196 return f.read()
196 return f.read()
197 except subprocess.CalledProcessError:
197 except subprocess.CalledProcessError:
198 return None
198 return None
199 finally:
199 finally:
200 shutil.rmtree(workdir)
200 shutil.rmtree(workdir)
201
201
202
202
203 def kpsewhich(filename):
203 def kpsewhich(filename):
204 """Invoke kpsewhich command with an argument `filename`."""
204 """Invoke kpsewhich command with an argument `filename`."""
205 try:
205 try:
206 find_cmd("kpsewhich")
206 find_cmd("kpsewhich")
207 proc = subprocess.Popen(
207 proc = subprocess.Popen(
208 ["kpsewhich", filename],
208 ["kpsewhich", filename],
209 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
209 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
210 (stdout, stderr) = proc.communicate()
210 (stdout, stderr) = proc.communicate()
211 return stdout.strip().decode('utf8', 'replace')
211 return stdout.strip().decode('utf8', 'replace')
212 except FindCmdError:
212 except FindCmdError:
213 pass
213 pass
214
214
215
215
216 def genelatex(body, wrap):
216 def genelatex(body, wrap):
217 """Generate LaTeX document for dvipng backend."""
217 """Generate LaTeX document for dvipng backend."""
218 lt = LaTeXTool.instance()
218 lt = LaTeXTool.instance()
219 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
219 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
220 yield r'\documentclass{article}'
220 yield r'\documentclass{article}'
221 packages = lt.packages
221 packages = lt.packages
222 if breqn:
222 if breqn:
223 packages = packages + ['breqn']
223 packages = packages + ['breqn']
224 for pack in packages:
224 for pack in packages:
225 yield r'\usepackage{{{0}}}'.format(pack)
225 yield r'\usepackage{{{0}}}'.format(pack)
226 yield r'\pagestyle{empty}'
226 yield r'\pagestyle{empty}'
227 if lt.preamble:
227 if lt.preamble:
228 yield lt.preamble
228 yield lt.preamble
229 yield r'\begin{document}'
229 yield r'\begin{document}'
230 if breqn:
230 if breqn:
231 yield r'\begin{dmath*}'
231 yield r'\begin{dmath*}'
232 yield body
232 yield body
233 yield r'\end{dmath*}'
233 yield r'\end{dmath*}'
234 elif wrap:
234 elif wrap:
235 yield u'$${0}$$'.format(body)
235 yield u'$${0}$$'.format(body)
236 else:
236 else:
237 yield body
237 yield body
238 yield u'\\end{document}'
238 yield u'\\end{document}'
239
239
240
240
241 _data_uri_template_png = u"""<img src="data:image/png;base64,%s" alt=%s />"""
241 _data_uri_template_png = u"""<img src="data:image/png;base64,%s" alt=%s />"""
242
242
243 def latex_to_html(s, alt='image'):
243 def latex_to_html(s, alt='image'):
244 """Render LaTeX to HTML with embedded PNG data using data URIs.
244 """Render LaTeX to HTML with embedded PNG data using data URIs.
245
245
246 Parameters
246 Parameters
247 ----------
247 ----------
248 s : str
248 s : str
249 The raw string containing valid inline LateX.
249 The raw string containing valid inline LateX.
250 alt : str
250 alt : str
251 The alt text to use for the HTML.
251 The alt text to use for the HTML.
252 """
252 """
253 base64_data = latex_to_png(s, encode=True).decode('ascii')
253 base64_data = latex_to_png(s, encode=True).decode('ascii')
254 if base64_data:
254 if base64_data:
255 return _data_uri_template_png % (base64_data, alt)
255 return _data_uri_template_png % (base64_data, alt)
256
256
257
257
@@ -1,184 +1,184
1 """Windows-specific implementation of process utilities.
1 """Windows-specific implementation of process utilities.
2
2
3 This file is only meant to be imported by process.py, not by end-users.
3 This file is only meant to be imported by process.py, not by end-users.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2010-2011 The IPython Development Team
7 # Copyright (C) 2010-2011 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 # stdlib
17 # stdlib
18 import os
18 import os
19 import sys
19 import sys
20 import ctypes
20 import ctypes
21 import time
21 import time
22
22
23 from ctypes import c_int, POINTER
23 from ctypes import c_int, POINTER
24 from ctypes.wintypes import LPCWSTR, HLOCAL
24 from ctypes.wintypes import LPCWSTR, HLOCAL
25 from subprocess import STDOUT, TimeoutExpired
25 from subprocess import STDOUT, TimeoutExpired
26 from threading import Thread
26 from threading import Thread
27
27
28 # our own imports
28 # our own imports
29 from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split
29 from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split
30 from . import py3compat
30 from . import py3compat
31 from .encoding import DEFAULT_ENCODING
31 from .encoding import DEFAULT_ENCODING
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Function definitions
34 # Function definitions
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 class AvoidUNCPath(object):
37 class AvoidUNCPath(object):
38 """A context manager to protect command execution from UNC paths.
38 """A context manager to protect command execution from UNC paths.
39
39
40 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
40 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
41 This context manager temporarily changes directory to the 'C:' drive on
41 This context manager temporarily changes directory to the 'C:' drive on
42 entering, and restores the original working directory on exit.
42 entering, and restores the original working directory on exit.
43
43
44 The context manager returns the starting working directory *if* it made a
44 The context manager returns the starting working directory *if* it made a
45 change and None otherwise, so that users can apply the necessary adjustment
45 change and None otherwise, so that users can apply the necessary adjustment
46 to their system calls in the event of a change.
46 to their system calls in the event of a change.
47
47
48 Examples
48 Examples
49 --------
49 --------
50 ::
50 ::
51 cmd = 'dir'
51 cmd = 'dir'
52 with AvoidUNCPath() as path:
52 with AvoidUNCPath() as path:
53 if path is not None:
53 if path is not None:
54 cmd = '"pushd %s &&"%s' % (path, cmd)
54 cmd = '"pushd %s &&"%s' % (path, cmd)
55 os.system(cmd)
55 os.system(cmd)
56 """
56 """
57 def __enter__(self):
57 def __enter__(self):
58 self.path = os.getcwd()
58 self.path = os.getcwd()
59 self.is_unc_path = self.path.startswith(r"\\")
59 self.is_unc_path = self.path.startswith(r"\\")
60 if self.is_unc_path:
60 if self.is_unc_path:
61 # change to c drive (as cmd.exe cannot handle UNC addresses)
61 # change to c drive (as cmd.exe cannot handle UNC addresses)
62 os.chdir("C:")
62 os.chdir("C:")
63 return self.path
63 return self.path
64 else:
64 else:
65 # We return None to signal that there was no change in the working
65 # We return None to signal that there was no change in the working
66 # directory
66 # directory
67 return None
67 return None
68
68
69 def __exit__(self, exc_type, exc_value, traceback):
69 def __exit__(self, exc_type, exc_value, traceback):
70 if self.is_unc_path:
70 if self.is_unc_path:
71 os.chdir(self.path)
71 os.chdir(self.path)
72
72
73
73
74 def _system_body(p):
74 def _system_body(p):
75 """Callback for _system."""
75 """Callback for _system."""
76 enc = DEFAULT_ENCODING
76 enc = DEFAULT_ENCODING
77
77
78 def stdout_read():
78 def stdout_read():
79 for line in read_no_interrupt(p.stdout).splitlines():
79 for line in read_no_interrupt(p.stdout).splitlines():
80 line = line.decode(enc, 'replace')
80 line = line.decode(enc, 'replace')
81 print(line, file=sys.stdout)
81 print(line, file=sys.stdout)
82
82
83 def stderr_read():
83 def stderr_read():
84 for line in read_no_interrupt(p.stderr).splitlines():
84 for line in read_no_interrupt(p.stderr).splitlines():
85 line = line.decode(enc, 'replace')
85 line = line.decode(enc, 'replace')
86 print(line, file=sys.stderr)
86 print(line, file=sys.stderr)
87
87
88 Thread(target=stdout_read).start()
88 Thread(target=stdout_read).start()
89 Thread(target=stderr_read).start()
89 Thread(target=stderr_read).start()
90
90
91 # Wait to finish for returncode. Unfortunately, Python has a bug where
91 # Wait to finish for returncode. Unfortunately, Python has a bug where
92 # wait() isn't interruptible (https://bugs.python.org/issue28168) so poll in
92 # wait() isn't interruptible (https://bugs.python.org/issue28168) so poll in
93 # a loop instead of just doing `return p.wait()`.
93 # a loop instead of just doing `return p.wait()`.
94 while True:
94 while True:
95 result = p.poll()
95 result = p.poll()
96 if result is None:
96 if result is None:
97 time.sleep(0.01)
97 time.sleep(0.01)
98 else:
98 else:
99 return result
99 return result
100
100
101
101
102 def system(cmd):
102 def system(cmd):
103 """Win32 version of os.system() that works with network shares.
103 """Win32 version of os.system() that works with network shares.
104
104
105 Note that this implementation returns None, as meant for use in IPython.
105 Note that this implementation returns None, as meant for use in IPython.
106
106
107 Parameters
107 Parameters
108 ----------
108 ----------
109 cmd : str or list
109 cmd : str or list
110 A command to be executed in the system shell.
110 A command to be executed in the system shell.
111
111
112 Returns
112 Returns
113 -------
113 -------
114 int : child process' exit code.
114 int : child process' exit code.
115 """
115 """
116 # The controller provides interactivity with both
116 # The controller provides interactivity with both
117 # stdin and stdout
117 # stdin and stdout
118 #import _process_win32_controller
118 #import _process_win32_controller
119 #_process_win32_controller.system(cmd)
119 #_process_win32_controller.system(cmd)
120
120
121 with AvoidUNCPath() as path:
121 with AvoidUNCPath() as path:
122 if path is not None:
122 if path is not None:
123 cmd = '"pushd %s &&"%s' % (path, cmd)
123 cmd = '"pushd %s &&"%s' % (path, cmd)
124 return process_handler(cmd, _system_body)
124 return process_handler(cmd, _system_body)
125
125
126 def getoutput(cmd):
126 def getoutput(cmd):
127 """Return standard output of executing cmd in a shell.
127 """Return standard output of executing cmd in a shell.
128
128
129 Accepts the same arguments as os.system().
129 Accepts the same arguments as os.system().
130
130
131 Parameters
131 Parameters
132 ----------
132 ----------
133 cmd : str or list
133 cmd : str or list
134 A command to be executed in the system shell.
134 A command to be executed in the system shell.
135
135
136 Returns
136 Returns
137 -------
137 -------
138 stdout : str
138 stdout : str
139 """
139 """
140
140
141 with AvoidUNCPath() as path:
141 with AvoidUNCPath() as path:
142 if path is not None:
142 if path is not None:
143 cmd = '"pushd %s &&"%s' % (path, cmd)
143 cmd = '"pushd %s &&"%s' % (path, cmd)
144 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
144 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
145
145
146 if out is None:
146 if out is None:
147 out = b''
147 out = b''
148 return py3compat.decode(out)
148 return py3compat.decode(out)
149
149
150 try:
150 try:
151 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
151 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
152 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
152 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
153 CommandLineToArgvW.restype = POINTER(LPCWSTR)
153 CommandLineToArgvW.restype = POINTER(LPCWSTR)
154 LocalFree = ctypes.windll.kernel32.LocalFree
154 LocalFree = ctypes.windll.kernel32.LocalFree
155 LocalFree.res_type = HLOCAL
155 LocalFree.res_type = HLOCAL
156 LocalFree.arg_types = [HLOCAL]
156 LocalFree.arg_types = [HLOCAL]
157
157
158 def arg_split(commandline, posix=False, strict=True):
158 def arg_split(commandline, posix=False, strict=True):
159 """Split a command line's arguments in a shell-like manner.
159 """Split a command line's arguments in a shell-like manner.
160
160
161 This is a special version for windows that use a ctypes call to CommandLineToArgvW
161 This is a special version for windows that use a ctypes call to CommandLineToArgvW
162 to do the argv splitting. The posix parameter is ignored.
162 to do the argv splitting. The posix parameter is ignored.
163
163
164 If strict=False, process_common.arg_split(...strict=False) is used instead.
164 If strict=False, process_common.arg_split(...strict=False) is used instead.
165 """
165 """
166 #CommandLineToArgvW returns path to executable if called with empty string.
166 #CommandLineToArgvW returns path to executable if called with empty string.
167 if commandline.strip() == "":
167 if commandline.strip() == "":
168 return []
168 return []
169 if not strict:
169 if not strict:
170 # not really a cl-arg, fallback on _process_common
170 # not really a cl-arg, fallback on _process_common
171 return py_arg_split(commandline, posix=posix, strict=strict)
171 return py_arg_split(commandline, posix=posix, strict=strict)
172 argvn = c_int()
172 argvn = c_int()
173 result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn))
173 result_pointer = CommandLineToArgvW(commandline.lstrip(), ctypes.byref(argvn))
174 result_array_type = LPCWSTR * argvn.value
174 result_array_type = LPCWSTR * argvn.value
175 result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))]
175 result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))]
176 retval = LocalFree(result_pointer)
176 retval = LocalFree(result_pointer)
177 return result
177 return result
178 except AttributeError:
178 except AttributeError:
179 arg_split = py_arg_split
179 arg_split = py_arg_split
180
180
181 def check_pid(pid):
181 def check_pid(pid):
182 # OpenProcess returns 0 if no such process (of ours) exists
182 # OpenProcess returns 0 if no such process (of ours) exists
183 # positive int otherwise
183 # positive int otherwise
184 return bool(ctypes.windll.kernel32.OpenProcess(1,0,pid))
184 return bool(ctypes.windll.kernel32.OpenProcess(1,0,pid))
General Comments 0
You need to be logged in to leave comments. Login now