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