##// END OF EJS Templates
unicode fixes in SVG...
MinRK -
Show More
@@ -1,689 +1,691 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Top-level display functions for displaying object in different formats.
3 3
4 4 Authors:
5 5
6 6 * Brian Granger
7 7 """
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2013 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-----------------------------------------------------------------------------
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Imports
18 18 #-----------------------------------------------------------------------------
19 19
20 20 from __future__ import print_function
21 21
22 22 import os
23 23 import struct
24 24
25 from IPython.utils.py3compat import string_types
25 from IPython.utils.py3compat import string_types, cast_bytes_py2, cast_unicode
26 26
27 27 from .displaypub import publish_display_data
28 28
29 29 #-----------------------------------------------------------------------------
30 30 # utility functions
31 31 #-----------------------------------------------------------------------------
32 32
33 33 def _safe_exists(path):
34 34 """Check path, but don't let exceptions raise"""
35 35 try:
36 36 return os.path.exists(path)
37 37 except Exception:
38 38 return False
39 39
40 40 def _merge(d1, d2):
41 41 """Like update, but merges sub-dicts instead of clobbering at the top level.
42 42
43 43 Updates d1 in-place
44 44 """
45 45
46 46 if not isinstance(d2, dict) or not isinstance(d1, dict):
47 47 return d2
48 48 for key, value in d2.items():
49 49 d1[key] = _merge(d1.get(key), value)
50 50 return d1
51 51
52 52 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
53 53 """internal implementation of all display_foo methods
54 54
55 55 Parameters
56 56 ----------
57 57 mimetype : str
58 58 The mimetype to be published (e.g. 'image/png')
59 59 objs : tuple of objects
60 60 The Python objects to display, or if raw=True raw text data to
61 61 display.
62 62 raw : bool
63 63 Are the data objects raw data or Python objects that need to be
64 64 formatted before display? [default: False]
65 65 metadata : dict (optional)
66 66 Metadata to be associated with the specific mimetype output.
67 67 """
68 68 if metadata:
69 69 metadata = {mimetype: metadata}
70 70 if raw:
71 71 # turn list of pngdata into list of { 'image/png': pngdata }
72 72 objs = [ {mimetype: obj} for obj in objs ]
73 73 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
74 74
75 75 #-----------------------------------------------------------------------------
76 76 # Main functions
77 77 #-----------------------------------------------------------------------------
78 78
79 79 def display(*objs, **kwargs):
80 80 """Display a Python object in all frontends.
81 81
82 82 By default all representations will be computed and sent to the frontends.
83 83 Frontends can decide which representation is used and how.
84 84
85 85 Parameters
86 86 ----------
87 87 objs : tuple of objects
88 88 The Python objects to display.
89 89 raw : bool, optional
90 90 Are the objects to be displayed already mimetype-keyed dicts of raw display data,
91 91 or Python objects that need to be formatted before display? [default: False]
92 92 include : list or tuple, optional
93 93 A list of format type strings (MIME types) to include in the
94 94 format data dict. If this is set *only* the format types included
95 95 in this list will be computed.
96 96 exclude : list or tuple, optional
97 97 A list of format type strings (MIME types) to exclude in the format
98 98 data dict. If this is set all format types will be computed,
99 99 except for those included in this argument.
100 100 metadata : dict, optional
101 101 A dictionary of metadata to associate with the output.
102 102 mime-type keys in this dictionary will be associated with the individual
103 103 representation formats, if they exist.
104 104 """
105 105 raw = kwargs.get('raw', False)
106 106 include = kwargs.get('include')
107 107 exclude = kwargs.get('exclude')
108 108 metadata = kwargs.get('metadata')
109 109
110 110 from IPython.core.interactiveshell import InteractiveShell
111 111
112 112 if raw:
113 113 for obj in objs:
114 114 publish_display_data('display', obj, metadata)
115 115 else:
116 116 format = InteractiveShell.instance().display_formatter.format
117 117 for obj in objs:
118 118 format_dict, md_dict = format(obj, include=include, exclude=exclude)
119 119 if metadata:
120 120 # kwarg-specified metadata gets precedence
121 121 _merge(md_dict, metadata)
122 122 publish_display_data('display', format_dict, md_dict)
123 123
124 124
125 125 def display_pretty(*objs, **kwargs):
126 126 """Display the pretty (default) representation of an object.
127 127
128 128 Parameters
129 129 ----------
130 130 objs : tuple of objects
131 131 The Python objects to display, or if raw=True raw text data to
132 132 display.
133 133 raw : bool
134 134 Are the data objects raw data or Python objects that need to be
135 135 formatted before display? [default: False]
136 136 metadata : dict (optional)
137 137 Metadata to be associated with the specific mimetype output.
138 138 """
139 139 _display_mimetype('text/plain', objs, **kwargs)
140 140
141 141
142 142 def display_html(*objs, **kwargs):
143 143 """Display the HTML representation of an object.
144 144
145 145 Parameters
146 146 ----------
147 147 objs : tuple of objects
148 148 The Python objects to display, or if raw=True raw HTML data to
149 149 display.
150 150 raw : bool
151 151 Are the data objects raw data or Python objects that need to be
152 152 formatted before display? [default: False]
153 153 metadata : dict (optional)
154 154 Metadata to be associated with the specific mimetype output.
155 155 """
156 156 _display_mimetype('text/html', objs, **kwargs)
157 157
158 158
159 159 def display_svg(*objs, **kwargs):
160 160 """Display the SVG representation of an object.
161 161
162 162 Parameters
163 163 ----------
164 164 objs : tuple of objects
165 165 The Python objects to display, or if raw=True raw svg data to
166 166 display.
167 167 raw : bool
168 168 Are the data objects raw data or Python objects that need to be
169 169 formatted before display? [default: False]
170 170 metadata : dict (optional)
171 171 Metadata to be associated with the specific mimetype output.
172 172 """
173 173 _display_mimetype('image/svg+xml', objs, **kwargs)
174 174
175 175
176 176 def display_png(*objs, **kwargs):
177 177 """Display the PNG representation of an object.
178 178
179 179 Parameters
180 180 ----------
181 181 objs : tuple of objects
182 182 The Python objects to display, or if raw=True raw png data to
183 183 display.
184 184 raw : bool
185 185 Are the data objects raw data or Python objects that need to be
186 186 formatted before display? [default: False]
187 187 metadata : dict (optional)
188 188 Metadata to be associated with the specific mimetype output.
189 189 """
190 190 _display_mimetype('image/png', objs, **kwargs)
191 191
192 192
193 193 def display_jpeg(*objs, **kwargs):
194 194 """Display the JPEG representation of an object.
195 195
196 196 Parameters
197 197 ----------
198 198 objs : tuple of objects
199 199 The Python objects to display, or if raw=True raw JPEG data to
200 200 display.
201 201 raw : bool
202 202 Are the data objects raw data or Python objects that need to be
203 203 formatted before display? [default: False]
204 204 metadata : dict (optional)
205 205 Metadata to be associated with the specific mimetype output.
206 206 """
207 207 _display_mimetype('image/jpeg', objs, **kwargs)
208 208
209 209
210 210 def display_latex(*objs, **kwargs):
211 211 """Display the LaTeX representation of an object.
212 212
213 213 Parameters
214 214 ----------
215 215 objs : tuple of objects
216 216 The Python objects to display, or if raw=True raw latex data to
217 217 display.
218 218 raw : bool
219 219 Are the data objects raw data or Python objects that need to be
220 220 formatted before display? [default: False]
221 221 metadata : dict (optional)
222 222 Metadata to be associated with the specific mimetype output.
223 223 """
224 224 _display_mimetype('text/latex', objs, **kwargs)
225 225
226 226
227 227 def display_json(*objs, **kwargs):
228 228 """Display the JSON representation of an object.
229 229
230 230 Note that not many frontends support displaying JSON.
231 231
232 232 Parameters
233 233 ----------
234 234 objs : tuple of objects
235 235 The Python objects to display, or if raw=True raw json data to
236 236 display.
237 237 raw : bool
238 238 Are the data objects raw data or Python objects that need to be
239 239 formatted before display? [default: False]
240 240 metadata : dict (optional)
241 241 Metadata to be associated with the specific mimetype output.
242 242 """
243 243 _display_mimetype('application/json', objs, **kwargs)
244 244
245 245
246 246 def display_javascript(*objs, **kwargs):
247 247 """Display the Javascript representation of an object.
248 248
249 249 Parameters
250 250 ----------
251 251 objs : tuple of objects
252 252 The Python objects to display, or if raw=True raw javascript data to
253 253 display.
254 254 raw : bool
255 255 Are the data objects raw data or Python objects that need to be
256 256 formatted before display? [default: False]
257 257 metadata : dict (optional)
258 258 Metadata to be associated with the specific mimetype output.
259 259 """
260 260 _display_mimetype('application/javascript', objs, **kwargs)
261 261
262 262 #-----------------------------------------------------------------------------
263 263 # Smart classes
264 264 #-----------------------------------------------------------------------------
265 265
266 266
267 267 class DisplayObject(object):
268 268 """An object that wraps data to be displayed."""
269 269
270 270 _read_flags = 'r'
271 271
272 272 def __init__(self, data=None, url=None, filename=None):
273 273 """Create a display object given raw data.
274 274
275 275 When this object is returned by an expression or passed to the
276 276 display function, it will result in the data being displayed
277 277 in the frontend. The MIME type of the data should match the
278 278 subclasses used, so the Png subclass should be used for 'image/png'
279 279 data. If the data is a URL, the data will first be downloaded
280 280 and then displayed. If
281 281
282 282 Parameters
283 283 ----------
284 284 data : unicode, str or bytes
285 285 The raw data or a URL or file to load the data from
286 286 url : unicode
287 287 A URL to download the data from.
288 288 filename : unicode
289 289 Path to a local file to load the data from.
290 290 """
291 291 if data is not None and isinstance(data, string_types):
292 292 if data.startswith('http') and url is None:
293 293 url = data
294 294 filename = None
295 295 data = None
296 296 elif _safe_exists(data) and filename is None:
297 297 url = None
298 298 filename = data
299 299 data = None
300 300
301 301 self.data = data
302 302 self.url = url
303 303 self.filename = None if filename is None else unicode(filename)
304 304
305 305 self.reload()
306 306
307 307 def reload(self):
308 308 """Reload the raw data from file or URL."""
309 309 if self.filename is not None:
310 310 with open(self.filename, self._read_flags) as f:
311 311 self.data = f.read()
312 312 elif self.url is not None:
313 313 try:
314 314 import urllib2
315 315 response = urllib2.urlopen(self.url)
316 316 self.data = response.read()
317 317 # extract encoding from header, if there is one:
318 318 encoding = None
319 319 for sub in response.headers['content-type'].split(';'):
320 320 sub = sub.strip()
321 321 if sub.startswith('charset'):
322 322 encoding = sub.split('=')[-1].strip()
323 323 break
324 324 # decode data, if an encoding was specified
325 325 if encoding:
326 326 self.data = self.data.decode(encoding, 'replace')
327 327 except:
328 328 self.data = None
329 329
330 330 class Pretty(DisplayObject):
331 331
332 332 def _repr_pretty_(self):
333 333 return self.data
334 334
335 335
336 336 class HTML(DisplayObject):
337 337
338 338 def _repr_html_(self):
339 339 return self.data
340 340
341 341 def __html__(self):
342 342 """
343 343 This method exists to inform other HTML-using modules (e.g. Markupsafe,
344 344 htmltag, etc) that this object is HTML and does not need things like
345 345 special characters (<>&) escaped.
346 346 """
347 347 return self._repr_html_()
348 348
349 349
350 350 class Math(DisplayObject):
351 351
352 352 def _repr_latex_(self):
353 353 s = self.data.strip('$')
354 354 return "$$%s$$" % s
355 355
356 356
357 357 class Latex(DisplayObject):
358 358
359 359 def _repr_latex_(self):
360 360 return self.data
361 361
362 362
363 363 class SVG(DisplayObject):
364 364
365 365 # wrap data in a property, which extracts the <svg> tag, discarding
366 366 # document headers
367 367 _data = None
368 368
369 369 @property
370 370 def data(self):
371 371 return self._data
372 372
373 373 @data.setter
374 374 def data(self, svg):
375 375 if svg is None:
376 376 self._data = None
377 377 return
378 378 # parse into dom object
379 379 from xml.dom import minidom
380 svg = cast_bytes_py2(svg)
380 381 x = minidom.parseString(svg)
381 382 # get svg tag (should be 1)
382 383 found_svg = x.getElementsByTagName('svg')
383 384 if found_svg:
384 385 svg = found_svg[0].toxml()
385 386 else:
386 387 # fallback on the input, trust the user
387 388 # but this is probably an error.
388 389 pass
390 svg = cast_unicode(svg)
389 391 self._data = svg
390 392
391 393 def _repr_svg_(self):
392 394 return self.data
393 395
394 396
395 397 class JSON(DisplayObject):
396 398
397 399 def _repr_json_(self):
398 400 return self.data
399 401
400 402 css_t = """$("head").append($("<link/>").attr({
401 403 rel: "stylesheet",
402 404 type: "text/css",
403 405 href: "%s"
404 406 }));
405 407 """
406 408
407 409 lib_t1 = """$.getScript("%s", function () {
408 410 """
409 411 lib_t2 = """});
410 412 """
411 413
412 414 class Javascript(DisplayObject):
413 415
414 416 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
415 417 """Create a Javascript display object given raw data.
416 418
417 419 When this object is returned by an expression or passed to the
418 420 display function, it will result in the data being displayed
419 421 in the frontend. If the data is a URL, the data will first be
420 422 downloaded and then displayed.
421 423
422 424 In the Notebook, the containing element will be available as `element`,
423 425 and jQuery will be available. The output area starts hidden, so if
424 426 the js appends content to `element` that should be visible, then
425 427 it must call `container.show()` to unhide the area.
426 428
427 429 Parameters
428 430 ----------
429 431 data : unicode, str or bytes
430 432 The Javascript source code or a URL to download it from.
431 433 url : unicode
432 434 A URL to download the data from.
433 435 filename : unicode
434 436 Path to a local file to load the data from.
435 437 lib : list or str
436 438 A sequence of Javascript library URLs to load asynchronously before
437 439 running the source code. The full URLs of the libraries should
438 440 be given. A single Javascript library URL can also be given as a
439 441 string.
440 442 css: : list or str
441 443 A sequence of css files to load before running the source code.
442 444 The full URLs of the css files should be given. A single css URL
443 445 can also be given as a string.
444 446 """
445 447 if isinstance(lib, basestring):
446 448 lib = [lib]
447 449 elif lib is None:
448 450 lib = []
449 451 if isinstance(css, basestring):
450 452 css = [css]
451 453 elif css is None:
452 454 css = []
453 455 if not isinstance(lib, (list,tuple)):
454 456 raise TypeError('expected sequence, got: %r' % lib)
455 457 if not isinstance(css, (list,tuple)):
456 458 raise TypeError('expected sequence, got: %r' % css)
457 459 self.lib = lib
458 460 self.css = css
459 461 super(Javascript, self).__init__(data=data, url=url, filename=filename)
460 462
461 463 def _repr_javascript_(self):
462 464 r = ''
463 465 for c in self.css:
464 466 r += css_t % c
465 467 for l in self.lib:
466 468 r += lib_t1 % l
467 469 r += self.data
468 470 r += lib_t2*len(self.lib)
469 471 return r
470 472
471 473 # constants for identifying png/jpeg data
472 474 _PNG = b'\x89PNG\r\n\x1a\n'
473 475 _JPEG = b'\xff\xd8'
474 476
475 477 def _pngxy(data):
476 478 """read the (width, height) from a PNG header"""
477 479 ihdr = data.index(b'IHDR')
478 480 # next 8 bytes are width/height
479 481 w4h4 = data[ihdr+4:ihdr+12]
480 482 return struct.unpack('>ii', w4h4)
481 483
482 484 def _jpegxy(data):
483 485 """read the (width, height) from a JPEG header"""
484 486 # adapted from http://www.64lines.com/jpeg-width-height
485 487
486 488 idx = 4
487 489 while True:
488 490 block_size = struct.unpack('>H', data[idx:idx+2])[0]
489 491 idx = idx + block_size
490 492 if data[idx:idx+2] == b'\xFF\xC0':
491 493 # found Start of Frame
492 494 iSOF = idx
493 495 break
494 496 else:
495 497 # read another block
496 498 idx += 2
497 499
498 500 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
499 501 return w, h
500 502
501 503 class Image(DisplayObject):
502 504
503 505 _read_flags = 'rb'
504 506 _FMT_JPEG = u'jpeg'
505 507 _FMT_PNG = u'png'
506 508 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
507 509
508 510 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None, width=None, height=None, retina=False):
509 511 """Create a display an PNG/JPEG image given raw data.
510 512
511 513 When this object is returned by an expression or passed to the
512 514 display function, it will result in the image being displayed
513 515 in the frontend.
514 516
515 517 Parameters
516 518 ----------
517 519 data : unicode, str or bytes
518 520 The raw image data or a URL or filename to load the data from.
519 521 This always results in embedded image data.
520 522 url : unicode
521 523 A URL to download the data from. If you specify `url=`,
522 524 the image data will not be embedded unless you also specify `embed=True`.
523 525 filename : unicode
524 526 Path to a local file to load the data from.
525 527 Images from a file are always embedded.
526 528 format : unicode
527 529 The format of the image data (png/jpeg/jpg). If a filename or URL is given
528 530 for format will be inferred from the filename extension.
529 531 embed : bool
530 532 Should the image data be embedded using a data URI (True) or be
531 533 loaded using an <img> tag. Set this to True if you want the image
532 534 to be viewable later with no internet connection in the notebook.
533 535
534 536 Default is `True`, unless the keyword argument `url` is set, then
535 537 default value is `False`.
536 538
537 539 Note that QtConsole is not able to display images if `embed` is set to `False`
538 540 width : int
539 541 Width to which to constrain the image in html
540 542 height : int
541 543 Height to which to constrain the image in html
542 544 retina : bool
543 545 Automatically set the width and height to half of the measured
544 546 width and height.
545 547 This only works for embedded images because it reads the width/height
546 548 from image data.
547 549 For non-embedded images, you can just set the desired display width
548 550 and height directly.
549 551
550 552 Examples
551 553 --------
552 554 # embedded image data, works in qtconsole and notebook
553 555 # when passed positionally, the first arg can be any of raw image data,
554 556 # a URL, or a filename from which to load image data.
555 557 # The result is always embedding image data for inline images.
556 558 Image('http://www.google.fr/images/srpr/logo3w.png')
557 559 Image('/path/to/image.jpg')
558 560 Image(b'RAW_PNG_DATA...')
559 561
560 562 # Specifying Image(url=...) does not embed the image data,
561 563 # it only generates `<img>` tag with a link to the source.
562 564 # This will not work in the qtconsole or offline.
563 565 Image(url='http://www.google.fr/images/srpr/logo3w.png')
564 566
565 567 """
566 568 if filename is not None:
567 569 ext = self._find_ext(filename)
568 570 elif url is not None:
569 571 ext = self._find_ext(url)
570 572 elif data is None:
571 573 raise ValueError("No image data found. Expecting filename, url, or data.")
572 574 elif isinstance(data, string_types) and (
573 575 data.startswith('http') or _safe_exists(data)
574 576 ):
575 577 ext = self._find_ext(data)
576 578 else:
577 579 ext = None
578 580
579 581 if ext is not None:
580 582 format = ext.lower()
581 583 if ext == u'jpg' or ext == u'jpeg':
582 584 format = self._FMT_JPEG
583 585 if ext == u'png':
584 586 format = self._FMT_PNG
585 587 elif isinstance(data, bytes) and format == 'png':
586 588 # infer image type from image data header,
587 589 # only if format might not have been specified.
588 590 if data[:2] == _JPEG:
589 591 format = 'jpeg'
590 592
591 593 self.format = unicode(format).lower()
592 594 self.embed = embed if embed is not None else (url is None)
593 595
594 596 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
595 597 raise ValueError("Cannot embed the '%s' image format" % (self.format))
596 598 self.width = width
597 599 self.height = height
598 600 self.retina = retina
599 601 super(Image, self).__init__(data=data, url=url, filename=filename)
600 602
601 603 if retina:
602 604 self._retina_shape()
603 605
604 606 def _retina_shape(self):
605 607 """load pixel-doubled width and height from image data"""
606 608 if not self.embed:
607 609 return
608 610 if self.format == 'png':
609 611 w, h = _pngxy(self.data)
610 612 elif self.format == 'jpeg':
611 613 w, h = _jpegxy(self.data)
612 614 else:
613 615 # retina only supports png
614 616 return
615 617 self.width = w // 2
616 618 self.height = h // 2
617 619
618 620 def reload(self):
619 621 """Reload the raw data from file or URL."""
620 622 if self.embed:
621 623 super(Image,self).reload()
622 624 if self.retina:
623 625 self._retina_shape()
624 626
625 627 def _repr_html_(self):
626 628 if not self.embed:
627 629 width = height = ''
628 630 if self.width:
629 631 width = ' width="%d"' % self.width
630 632 if self.height:
631 633 height = ' height="%d"' % self.height
632 634 return u'<img src="%s"%s%s/>' % (self.url, width, height)
633 635
634 636 def _data_and_metadata(self):
635 637 """shortcut for returning metadata with shape information, if defined"""
636 638 md = {}
637 639 if self.width:
638 640 md['width'] = self.width
639 641 if self.height:
640 642 md['height'] = self.height
641 643 if md:
642 644 return self.data, md
643 645 else:
644 646 return self.data
645 647
646 648 def _repr_png_(self):
647 649 if self.embed and self.format == u'png':
648 650 return self._data_and_metadata()
649 651
650 652 def _repr_jpeg_(self):
651 653 if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
652 654 return self._data_and_metadata()
653 655
654 656 def _find_ext(self, s):
655 657 return unicode(s.split('.')[-1].lower())
656 658
657 659
658 660 def clear_output(stdout=True, stderr=True, other=True):
659 661 """Clear the output of the current cell receiving output.
660 662
661 663 Optionally, each of stdout/stderr or other non-stream data (e.g. anything
662 664 produced by display()) can be excluded from the clear event.
663 665
664 666 By default, everything is cleared.
665 667
666 668 Parameters
667 669 ----------
668 670 stdout : bool [default: True]
669 671 Whether to clear stdout.
670 672 stderr : bool [default: True]
671 673 Whether to clear stderr.
672 674 other : bool [default: True]
673 675 Whether to clear everything else that is not stdout/stderr
674 676 (e.g. figures,images,HTML, any result of display()).
675 677 """
676 678 from IPython.core.interactiveshell import InteractiveShell
677 679 if InteractiveShell.initialized():
678 680 InteractiveShell.instance().display_pub.clear_output(
679 681 stdout=stdout, stderr=stderr, other=other,
680 682 )
681 683 else:
682 684 from IPython.utils import io
683 685 if stdout:
684 686 print('\033[2K\r', file=io.stdout, end='')
685 687 io.stdout.flush()
686 688 if stderr:
687 689 print('\033[2K\r', file=io.stderr, end='')
688 690 io.stderr.flush()
689 691
General Comments 0
You need to be logged in to leave comments. Login now