Show More
@@ -507,6 +507,136 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||
|
507 | 507 | return |
|
508 | 508 | self._control.print_(printer) |
|
509 | 509 | |
|
510 | def export_html_inline(self, parent = None): | |
|
511 | """ Export the contents of the ConsoleWidget as HTML with inline PNGs. | |
|
512 | """ | |
|
513 | self.export_html(parent, inline = True) | |
|
514 | ||
|
515 | def export_html(self, parent = None, inline = False): | |
|
516 | """ Export the contents of the ConsoleWidget as HTML. | |
|
517 | ||
|
518 | Parameters: | |
|
519 | ----------- | |
|
520 | inline : bool, optional [default True] | |
|
521 | ||
|
522 | If True, include images as inline PNGs. Otherwise, | |
|
523 | include them as links to external PNG files, mimicking | |
|
524 | Firefox's "Web Page, complete" behavior. | |
|
525 | """ | |
|
526 | dialog = QtGui.QFileDialog(parent, 'Save HTML Document') | |
|
527 | dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) | |
|
528 | dialog.setDefaultSuffix('htm') | |
|
529 | dialog.setNameFilter('HTML document (*.htm)') | |
|
530 | if dialog.exec_(): | |
|
531 | filename = str(dialog.selectedFiles()[0]) | |
|
532 | if(inline): | |
|
533 | path = None | |
|
534 | else: | |
|
535 | offset = filename.rfind(".") | |
|
536 | if(offset > 0): | |
|
537 | path = filename[:offset]+"_files" | |
|
538 | else: | |
|
539 | path = filename+"_files" | |
|
540 | import os | |
|
541 | try: | |
|
542 | os.mkdir(path) | |
|
543 | except OSError: | |
|
544 | # TODO: check that this is an "already exists" error | |
|
545 | pass | |
|
546 | ||
|
547 | f = open(filename, 'w') | |
|
548 | try: | |
|
549 | # N.B. this is overly restrictive, but Qt's output is | |
|
550 | # predictable... | |
|
551 | img_re = re.compile(r'<img src="(?P<name>[\d]+)" />') | |
|
552 | html = self.fix_html_encoding( | |
|
553 | str(self._control.toHtml().toUtf8())) | |
|
554 | f.write(img_re.sub( | |
|
555 | lambda x: self.image_tag(x, path = path, format = "png"), | |
|
556 | html)) | |
|
557 | finally: | |
|
558 | f.close() | |
|
559 | return filename | |
|
560 | return None | |
|
561 | ||
|
562 | def export_xhtml(self, parent = None): | |
|
563 | """ Export the contents of the ConsoleWidget as XHTML with inline SVGs. | |
|
564 | """ | |
|
565 | dialog = QtGui.QFileDialog(parent, 'Save XHTML Document') | |
|
566 | dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) | |
|
567 | dialog.setDefaultSuffix('xml') | |
|
568 | dialog.setNameFilter('XHTML document (*.xml)') | |
|
569 | if dialog.exec_(): | |
|
570 | filename = str(dialog.selectedFiles()[0]) | |
|
571 | f = open(filename, 'w') | |
|
572 | try: | |
|
573 | # N.B. this is overly restrictive, but Qt's output is | |
|
574 | # predictable... | |
|
575 | img_re = re.compile(r'<img src="(?P<name>[\d]+)" />') | |
|
576 | html = str(self._control.toHtml().toUtf8()) | |
|
577 | # Hack to make xhtml header -- note that we are not doing | |
|
578 | # any check for valid xml | |
|
579 | offset = html.find("<html>") | |
|
580 | assert(offset > -1) | |
|
581 | html = ('<html xmlns="http://www.w3.org/1999/xhtml">\n'+ | |
|
582 | html[offset+6:]) | |
|
583 | # And now declare UTF-8 encoding | |
|
584 | html = self.fix_html_encoding(html) | |
|
585 | f.write(img_re.sub( | |
|
586 | lambda x: self.image_tag(x, path = None, format = "svg"), | |
|
587 | html)) | |
|
588 | finally: | |
|
589 | f.close() | |
|
590 | return filename | |
|
591 | return None | |
|
592 | ||
|
593 | def fix_html_encoding(self, html): | |
|
594 | """ Return html string, with a UTF-8 declaration added to <HEAD>. | |
|
595 | ||
|
596 | Assumes that html is Qt generated and has already been UTF-8 encoded | |
|
597 | and coerced to a python string. If the expected head element is | |
|
598 | not found, the given object is returned unmodified. | |
|
599 | ||
|
600 | This patching is needed for proper rendering of some characters | |
|
601 | (e.g., indented commands) when viewing exported HTML on a local | |
|
602 | system (i.e., without seeing an encoding declaration in an HTTP | |
|
603 | header). | |
|
604 | ||
|
605 | C.f. http://www.w3.org/International/O-charset for details. | |
|
606 | """ | |
|
607 | offset = html.find("<head>") | |
|
608 | if(offset > -1): | |
|
609 | html = (html[:offset+6]+ | |
|
610 | '\n<meta http-equiv="Content-Type" '+ | |
|
611 | 'content="text/html; charset=utf-8" />\n'+ | |
|
612 | html[offset+6:]) | |
|
613 | ||
|
614 | return html | |
|
615 | ||
|
616 | def image_tag(self, match, path = None, format = "png"): | |
|
617 | """ Return (X)HTML mark-up for the image-tag given by match. | |
|
618 | ||
|
619 | Parameters | |
|
620 | ---------- | |
|
621 | match : re.SRE_Match | |
|
622 | A match to an HTML image tag as exported by Qt, with | |
|
623 | match.group("Name") containing the matched image ID. | |
|
624 | ||
|
625 | path : string|None, optional [default None] | |
|
626 | If not None, specifies a path to which supporting files | |
|
627 | may be written (e.g., for linked images). | |
|
628 | If None, all images are to be included inline. | |
|
629 | ||
|
630 | format : "png"|"svg", optional [default "png"] | |
|
631 | Format for returned or referenced images. | |
|
632 | ||
|
633 | Subclasses supporting image display should override this | |
|
634 | method. | |
|
635 | """ | |
|
636 | ||
|
637 | # Default case -- not enough information to generate tag | |
|
638 | return "" | |
|
639 | ||
|
510 | 640 | def prompt_to_top(self): |
|
511 | 641 | """ Moves the prompt to the top of the viewport. |
|
512 | 642 | """ |
@@ -744,7 +874,15 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||
|
744 | 874 | menu.addSeparator() |
|
745 | 875 | print_action = menu.addAction('Print', self.print_) |
|
746 | 876 | print_action.setEnabled(True) |
|
747 | ||
|
877 | html_action = menu.addAction('Export HTML (external PNGs)', | |
|
878 | self.export_html) | |
|
879 | html_action.setEnabled(True) | |
|
880 | html_inline_action = menu.addAction('Export HTML (inline PNGs)', | |
|
881 | self.export_html_inline) | |
|
882 | html_inline_action.setEnabled(True) | |
|
883 | xhtml_action = menu.addAction('Export XHTML (inline SVGs)', | |
|
884 | self.export_xhtml) | |
|
885 | xhtml_action.setEnabled(True) | |
|
748 | 886 | return menu |
|
749 | 887 | |
|
750 | 888 | def _control_key_down(self, modifiers, include_command=True): |
@@ -25,6 +25,9 b' class RichIPythonWidget(IPythonWidget):' | |||
|
25 | 25 | """ |
|
26 | 26 | kw['kind'] = 'rich' |
|
27 | 27 | super(RichIPythonWidget, self).__init__(*args, **kw) |
|
28 | # Dictionary for resolving Qt names to images when | |
|
29 | # generating XHTML output | |
|
30 | self._name_to_svg = {} | |
|
28 | 31 | |
|
29 | 32 | #--------------------------------------------------------------------------- |
|
30 | 33 | # 'ConsoleWidget' protected interface |
@@ -68,6 +71,7 b' class RichIPythonWidget(IPythonWidget):' | |||
|
68 | 71 | self._append_plain_text('Received invalid plot data.') |
|
69 | 72 | else: |
|
70 | 73 | format = self._add_image(image) |
|
74 | self._name_to_svg[str(format.name())] = svg | |
|
71 | 75 | format.setProperty(self._svg_text_format_property, svg) |
|
72 | 76 | cursor = self._get_end_cursor() |
|
73 | 77 | cursor.insertBlock() |
@@ -121,3 +125,68 b' class RichIPythonWidget(IPythonWidget):' | |||
|
121 | 125 | filename = dialog.selectedFiles()[0] |
|
122 | 126 | image = self._get_image(name) |
|
123 | 127 | image.save(filename, format) |
|
128 | ||
|
129 | def image_tag(self, match, path = None, format = "png"): | |
|
130 | """ Return (X)HTML mark-up for the image-tag given by match. | |
|
131 | ||
|
132 | Parameters | |
|
133 | ---------- | |
|
134 | match : re.SRE_Match | |
|
135 | A match to an HTML image tag as exported by Qt, with | |
|
136 | match.group("Name") containing the matched image ID. | |
|
137 | ||
|
138 | path : string|None, optional [default None] | |
|
139 | If not None, specifies a path to which supporting files | |
|
140 | may be written (e.g., for linked images). | |
|
141 | If None, all images are to be included inline. | |
|
142 | ||
|
143 | format : "png"|"svg", optional [default "png"] | |
|
144 | Format for returned or referenced images. | |
|
145 | ||
|
146 | Subclasses supporting image display should override this | |
|
147 | method. | |
|
148 | """ | |
|
149 | ||
|
150 | if(format == "png"): | |
|
151 | try: | |
|
152 | image = self._get_image(match.group("name")) | |
|
153 | except KeyError: | |
|
154 | return "<b>Couldn't find image %s</b>" % match.group("name") | |
|
155 | ||
|
156 | if(path is not None): | |
|
157 | relpath = path[path.rfind("/")+1:] | |
|
158 | if(image.save("%s/qt_img%s.png" % (path,match.group("name")), | |
|
159 | "PNG")): | |
|
160 | return '<img src="%s/qt_img%s.png">' % (relpath, | |
|
161 | match.group("name")) | |
|
162 | else: | |
|
163 | return "<b>Couldn't save image!</b>" | |
|
164 | else: | |
|
165 | ba = QtCore.QByteArray() | |
|
166 | buffer_ = QtCore.QBuffer(ba) | |
|
167 | buffer_.open(QtCore.QIODevice.WriteOnly) | |
|
168 | image.save(buffer_, "PNG") | |
|
169 | buffer_.close() | |
|
170 | import re | |
|
171 | return '<img src="data:image/png;base64,\n%s\n" />' % ( | |
|
172 | re.sub(r'(.{60})',r'\1\n',str(ba.toBase64()))) | |
|
173 | ||
|
174 | elif(format == "svg"): | |
|
175 | try: | |
|
176 | svg = str(self._name_to_svg[match.group("name")]) | |
|
177 | except KeyError: | |
|
178 | return "<b>Couldn't find image %s</b>" % match.group("name") | |
|
179 | ||
|
180 | # Not currently checking path, because it's tricky to find a | |
|
181 | # cross-browser way to embed external SVG images (e.g., via | |
|
182 | # object or embed tags). | |
|
183 | ||
|
184 | # Chop stand-alone header from matplotlib SVG | |
|
185 | offset = svg.find("<svg") | |
|
186 | assert(offset > -1) | |
|
187 | ||
|
188 | return svg[offset:] | |
|
189 | ||
|
190 | else: | |
|
191 | return '<b>Unrecognized image format</b>' | |
|
192 |
General Comments 0
You need to be logged in to leave comments.
Login now