Repository: jupyter/nbconvert
Pull request #1611 (2021-08-09 11:02:38)

Add qtpdf and qtpng exporters
Similar to the webpdf exporter, but using pyqtwebengine.

maartenbreddels: Nice work David, could you comment on the size of Qt vs Chromium, like what a minimal environment would take up in disk space?
davidbrochart: @maartenbreddels the PyQt5 package takes 357M on my machine and the pyppeteer Chromium download takes 300M.
bollwyvl: Excellent stuff!

I had played with this some on  [nbconvert-pdfqt](https://github.com/deathbeds/nbconvert-pdfqt) (hasn't been touched in a while). This uses JupyterLab as the renderer, and indeed, I would see that as eventually becoming a desirable feature. There were some features added (e.g. `--expose-app-in-browser`) that would make this even easier today.

> the PyQt5 package takes 357M on my machine and the pyppeteer Chromium download takes 300M

In some package managers (e.g. `conda`) the underlying package will be hardlinked between equivalent installations. And it _would_ be shared with other Qt apps (`matplotlib`, `spyder`) for a given environment. While this _seems_ like an equivalent feature to spraying stuff all over my `$HOME`, I'd much rather know these space restrictions at environment setup time.

But the big win, for my use case, is being able to specify/lock a single set of dependencies, with a single package manager, and get something that works without a "surprise" runtime download. 

It also means it's _possible_ to create full, platform-specific distributions (a la `miniforge`, etc) that contain everything needed to function on a "normal" computer: headless/docker usually missing some x11 stuff, but that list is getting shorter... and the chromium-based solutions _also_ share this burden. 

Even if QtWebEngine lags behind whatever frankenchromium comes out of some of these custom things, it's likely the Qt one is going to be packaged more robustly, and supported for longer.

Finally, `pyppeteer` still has some dangling issues as a hangover its multiple forks:

- turns off SSL verification
- seems to still have memory issues, and sometimes fails silently
martinRenou: I'd be curious to know if something similar to this: https://github.com/jupyter/nbconvert/pull/1718 would need to or could be done. Does PyQt allow passing a [`printBackground`](https://pyppeteer.github.io/pyppeteer/reference.html?highlight=pdf#pyppeteer.page.Page.pdf) option? Does it provide a way to [emulate the "screen" media](https://pyppeteer.github.io/pyppeteer/reference.html?highlight=pdf#pyppeteer.page.Page.emulateMedia)? 
martinRenou: > I'd be curious to know if something similar to this: https://github.com/jupyter/nbconvert/pull/1718 would need to or could be done. Does PyQt allow passing a [printBackground](https://pyppeteer.github.io/pyppeteer/reference.html?highlight=pdf#pyppeteer.page.Page.pdf) option? Does it provide a way to [emulate the "screen" media](https://pyppeteer.github.io/pyppeteer/reference.html?highlight=pdf#pyppeteer.page.Page.emulateMedia)?

Answering my own question. It looks like this PR works like a charm with the latest `--theme` fixes and `--embed-images` features without having to do anything :)
davidbrochart: I can see that installing `pyqtwebengine` with `pip` makes the `qtpdf` and `qtpng` exporters either hang or segfault. We have seen in the past that the wheels for OSX were broken but it seems to be the case on all platforms now.
When installing `pyqtwebengine` through `conda-forge`, everything works locally. But I couldn't use a conda environment in the CI, I think it is because of our `jupyterlab/maintainer-tools/.github/actions/base-setup@v1` action.
davidbrochart: Thanks @blink1073, let's try that.
davidbrochart: It doesn't seem to install in the conda environment, but in a system directory.
blink1073: Any steps that you want to run in the conda env after that need to have `shell: bash -l {0}`.
davidbrochart: Installing `pyqtwebengine` from conda-forge or pip [segfaults](https://github.com/jupyter/nbconvert/runs/7108283178?check_suite_focus=true) at run-time.
Maybe @cmaureir knows more?
cmaureir: > Installing `pyqtwebengine` from conda-forge or pip [segfaults](https://github.com/jupyter/nbconvert/runs/7108283178?check_suite_focus=true) at run-time. Maybe @cmaureir knows more?

not really, because I'm a bit unaware how things work on PyQt (because I work on PySide), but could it be possible to get a stacktrace of the segfault? I just see the core dump message.
davidbrochart: Thanks for the answer, I have a 324M core dumped, would it be useful?
davidbrochart: Here it is: https://filetransfer.io/data-package/3EiUgkUv#link
davidbrochart: Trigger CI.
SylvainCorlay: The windows failure looks legitimate.
davidbrochart: This is almost ready, but:
- I don't know what's going on in [generate-docs](https://github.com/jupyter/nbconvert/runs/7567002734?check_suite_focus=true).
- There seems to be two conflicting pre-commit hooks, both changing `share/templates/classic/static/style.css`.
- We changed the `tests` GitHub workflow to install the Qt stuff from conda-forge instead of PyPI, because we thought the wheels were corrupted, but we probably don't test on pypy because of that. I'll push a commit to restore the PyPI install, since the wheels were probably okay.
- We disable QtPng on Windows for now, because it produces an empty file.
davidbrochart: We got a [core dumped](https://github.com/jupyter/nbconvert/runs/7567434255?check_suite_focus=true) again, so maybe the wheels are wrong. I'll go back to the conda-forge install.
SylvainCorlay: The documentation generation failure seems to be related to nbsphinx - and is *probably* unrelated to this PR.

cc @blink1073 do you have a clue of what may be going on with this?
SylvainCorlay: The documentation generation failure seems to be related to nbsphinx - and is *probably* unrelated to this PR.

cc @blink1073 do you have a clue of what may be going on with this?
blink1073: The error has something to do with LaTeX, of which I know little.
davidbrochart: Looks like [MikTex](https://miktex.org) is temporarily [down](https://github.com/jupyter/nbconvert/runs/7584407812?check_suite_focus=true#step:11:438), and we have it in our [docs](https://github.com/jupyter/nbconvert/blob/86772713bdd29bf73d2e1f6eaa9fe20c5c25faf6/docs/source/install.rst), so the link-check workflow fails.
But the PDF generation worked :tada: 
SylvainCorlay: Force-pushed with a tiny fix to pass the pre-commit.

Patch:
diff --git a/nbconvert/exporters/__init__.py b/nbconvert/exporters/__init__.py
index 8a32b73c..9aa58d34 100644
--- a/nbconvert/exporters/__init__.py
+++ b/nbconvert/exporters/__init__.py
@@ -7,6 +7,8 @@ from .markdown import MarkdownExporter
 from .notebook import NotebookExporter
 from .pdf import PDFExporter
 from .python import PythonExporter
+from .qtpdf import QtPDFExporter
+from .qtpng import QtPNGExporter
 from .rst import RSTExporter
 from .script import ScriptExporter
 from .slides import SlidesExporter
diff --git a/nbconvert/exporters/qt_exporter.py b/nbconvert/exporters/qt_exporter.py
new file mode 100644
index 00000000..39fb9329
--- /dev/null
+++ b/nbconvert/exporters/qt_exporter.py
@@ -0,0 +1,59 @@
+import os
+import sys
+import tempfile
+
+from traitlets import default
+
+from .html import HTMLExporter
+
+
+class QtExporter(HTMLExporter):
+
+    paginate = None
+
+    @default("file_extension")
+    def _file_extension_default(self):
+        return ".html"
+
+    def _check_launch_reqs(self):
+        if sys.platform.startswith("win") and self.format == "png":
+            raise RuntimeError("Exporting to PNG using Qt is currently not supported on Windows.")
+        from .qt_screenshot import QT_INSTALLED
+
+        if not QT_INSTALLED:
+            raise RuntimeError(
+                f"PyQtWebEngine is not installed to support Qt {self.format.upper()} conversion. "
+                f"Please install `nbconvert[qt{self.format}]` to enable."
+            )
+        from .qt_screenshot import QtScreenshot
+
+        return QtScreenshot
+
+    def _run_pyqtwebengine(self, html):
+        ext = ".html"
+        temp_file = tempfile.NamedTemporaryFile(suffix=ext, delete=False)
+        filename = f"{temp_file.name[:-len(ext)]}.{self.format}"
+        with temp_file:
+            temp_file.write(html.encode("utf-8"))
+        try:
+            QtScreenshot = self._check_launch_reqs()
+            s = QtScreenshot()
+            s.capture(f"file://{temp_file.name}", filename, self.paginate)
+        finally:
+            # Ensure the file is deleted even if pyqtwebengine raises an exception
+            os.unlink(temp_file.name)
+        return s.data
+
+    def from_notebook_node(self, nb, resources=None, **kw):
+        self._check_launch_reqs()
+        html, resources = super().from_notebook_node(nb, resources=resources, **kw)
+
+        self.log.info(f"Building {self.format.upper()}")
+        data = self._run_pyqtwebengine(html)
+        self.log.info(f"{self.format.upper()} successfully created")
+
+        # convert output extension
+        # the writer above required it to be html
+        resources["output_extension"] = f".{self.format}"
+
+        return data, resources
diff --git a/nbconvert/exporters/qt_screenshot.py b/nbconvert/exporters/qt_screenshot.py
new file mode 100644
index 00000000..5e279808
--- /dev/null
+++ b/nbconvert/exporters/qt_screenshot.py
@@ -0,0 +1,77 @@
+import os
+
+try:
+    from PyQt5 import QtCore
+    from PyQt5.QtGui import QPageLayout, QPageSize
+    from PyQt5.QtWebEngineWidgets import QWebEngineSettings, QWebEngineView
+    from PyQt5.QtWidgets import QApplication
+
+    QT_INSTALLED = True
+except ModuleNotFoundError:
+    QT_INSTALLED = False
+
+
+if QT_INSTALLED:
+    APP = None
+    if not QApplication.instance():
+        APP = QApplication([])
+
+    class QtScreenshot(QWebEngineView):
+        def __init__(self):
+            super().__init__()
+            self.app = APP
+
+        def capture(self, url, output_file, paginate):
+            self.output_file = output_file
+            self.paginate = paginate
+            self.load(QtCore.QUrl(url))
+            self.loadFinished.connect(self.on_loaded)
+            # Create hidden view without scrollbars
+            self.setAttribute(QtCore.Qt.WA_DontShowOnScreen)
+            self.page().settings().setAttribute(QWebEngineSettings.ShowScrollBars, False)
+            self.data = b""
+            if output_file.endswith(".pdf"):
+                self.export = self.export_pdf
+
+                def cleanup(*args):
+                    self.app.quit()
+                    self.get_data()
+
+                self.page().pdfPrintingFinished.connect(cleanup)
+            elif output_file.endswith(".png"):
+                self.export = self.export_png
+            else:
+                raise RuntimeError(f"Export file extension not supported: {output_file}")
+            self.show()
+            self.app.exec()
+
+        def on_loaded(self):
+            self.size = self.page().contentsSize().toSize()
+            self.resize(self.size)
+            # Wait for resize
+            QtCore.QTimer.singleShot(1000, self.export)
+
+        def export_pdf(self):
+            if self.paginate:
+                page_size = QPageSize(QPageSize.A4)
+                page_layout = QPageLayout(page_size, QPageLayout.Portrait, QtCore.QMarginsF())
+            else:
+                factor = 0.75
+                page_size = QPageSize(
+                    QtCore.QSizeF(self.size.width() * factor, self.size.height() * factor),
+                    QPageSize.Point,
+                )
+                page_layout = QPageLayout(page_size, QPageLayout.Portrait, QtCore.QMarginsF())
+
+            self.page().printToPdf(self.output_file, pageLayout=page_layout)
+
+        def export_png(self):
+            self.grab().save(self.output_file, "PNG")
+            self.app.quit()
+            self.get_data()
+
+        def get_data(self):
+            if os.path.exists(self.output_file):
+                with open(self.output_file, "rb") as f:
+                    self.data = f.read()
+                os.unlink(self.output_file)
diff --git a/nbconvert/exporters/qtpdf.py b/nbconvert/exporters/qtpdf.py
new file mode 100644
index 00000000..738a9f70
--- /dev/null
+++ b/nbconvert/exporters/qtpdf.py
@@ -0,0 +1,30 @@
+"""Export to PDF via a headless browser"""
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+from traitlets import Bool
+
+from .qt_exporter import QtExporter
+
+
+class QtPDFExporter(QtExporter):
+    """Writer designed to write to PDF files.
+
+    This inherits from :class:`HTMLExporter`. It creates the HTML using the
+    template machinery, and then uses pyqtwebengine to create a pdf.
+    """
+
+    export_from_notebook = "PDF via HTML"
+    format = "pdf"
+
+    paginate = Bool(
+        True,
+        help="""
+        Split generated notebook into multiple pages.
+
+        If False, a PDF with one long page will be generated.
+
+        Set to True to match behavior of LaTeX based PDF generator
+        """,
+    ).tag(config=True)
diff --git a/nbconvert/exporters/qtpng.py b/nbconvert/exporters/qtpng.py
new file mode 100644
index 00000000..d506a432
--- /dev/null
+++ b/nbconvert/exporters/qtpng.py
@@ -0,0 +1,17 @@
+"""Export to PNG via a headless browser"""
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+from .qt_exporter import QtExporter
+
+
+class QtPNGExporter(QtExporter):
+    """Writer designed to write to PNG files.
+
+    This inherits from :class:`HTMLExporter`. It creates the HTML using the
+    template machinery, and then uses pyqtwebengine to create a png.
+    """
+
+    export_from_notebook = "PNG via HTML"
+    format = "png"
diff --git a/nbconvert/exporters/webpdf.py b/nbconvert/exporters/webpdf.py
index dfc99077..a9564857 100644
--- a/nbconvert/exporters/webpdf.py
+++ b/nbconvert/exporters/webpdf.py
@@ -38,8 +38,6 @@ class WebPDFExporter(HTMLExporter):
         """,
     ).tag(config=True)
 
-    output_mimetype = "text/html"
-
     @default("file_extension")
     def _file_extension_default(self):
         return ".html"
diff --git a/nbconvert/nbconvertapp.py b/nbconvert/nbconvertapp.py
index aa307338..e53de1dd 100755
--- a/nbconvert/nbconvertapp.py
+++ b/nbconvert/nbconvertapp.py
@@ -650,6 +650,7 @@ class DejavuApp(NbConvertApp):
         self.config.TemplateExporter.exclude_input_prompt = True
         self.config.ExecutePreprocessor.enabled = True
         self.config.WebPDFExporter.paginate = False
+        self.config.QtPDFExporter.paginate = False
 
         super().initialize(argv)
 
diff --git a/pyproject.toml b/pyproject.toml
index ce348263..f3013ce9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -45,6 +45,8 @@ html = "nbconvert.exporters:HTMLExporter"
 slides = "nbconvert.exporters:SlidesExporter"
 latex = "nbconvert.exporters:LatexExporter"
 pdf = "nbconvert.exporters:PDFExporter"
+qtpdf = "nbconvert.exporters:QtPDFExporter"
+qtpng = "nbconvert.exporters:QtPNGExporter"
 webpdf = "nbconvert.exporters:WebPDFExporter"
 markdown = "nbconvert.exporters:MarkdownExporter"
 python = "nbconvert.exporters:PythonExporter"
@@ -64,9 +66,11 @@ test = [
     "pyppeteer>=1,<1.1",
 ]
 serve = ["tornado>=6.1"]
+qtpdf = ["pyqtwebengine>=5.15"]
+qtpng = ["pyqtwebengine>=5.15"]
 webpdf = ["pyppeteer>=1,<1.1"]
 docs = [
-    "sphinx>=1.5.1",
+    "sphinx==5.0.2",
     "sphinx_rtd_theme",
     "nbsphinx>=0.2.12",
     "ipython",
@@ -80,8 +84,9 @@ all = [
     "pytest",
     "pytest-cov",
     "pytest-dependency",
+    "pyqtwebengine>=5.15",
     "pyppeteer>=1,<1.1",
-    "sphinx>=1.5.1",
+    "sphinx==5.0.2",
     "sphinx_rtd_theme",
     "tornado>=6.1",
 ]
diff --git a/share/templates/classic/static/style.css b/share/templates/classic/static/style.css
index 637af782..881c1a53 100644
--- a/share/templates/classic/static/style.css
+++ b/share/templates/classic/static/style.css
@@ -257,15 +257,24 @@ th {
   }
 }
 @font-face {
-  font-family: 'Glyphicons Halflings';
-  src: url('../components/bootstrap/fonts/glyphicons-halflings-regular.eot');
-  src: url('../components/bootstrap/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../components/bootstrap/fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../components/bootstrap/fonts/glyphicons-halflings-regular.woff') format('woff'), url('../components/bootstrap/fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../components/bootstrap/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
+  font-family: "Glyphicons Halflings";
+  src: url("../components/bootstrap/fonts/glyphicons-halflings-regular.eot");
+  src: url("../components/bootstrap/fonts/glyphicons-halflings-regular.eot?#iefix")
+      format("embedded-opentype"),
+    url("../components/bootstrap/fonts/glyphicons-halflings-regular.woff2")
+      format("woff2"),
+    url("../components/bootstrap/fonts/glyphicons-halflings-regular.woff")
+      format("woff"),
+    url("../components/bootstrap/fonts/glyphicons-halflings-regular.ttf")
+      format("truetype"),
+    url("../components/bootstrap/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular")
+      format("svg");
 }
 .glyphicon {
   position: relative;
   top: 1px;
   display: inline-block;
-  font-family: 'Glyphicons Halflings';
+  font-family: "Glyphicons Halflings";
   font-style: normal;
   font-weight: normal;
   line-height: 1;
@@ -1300,7 +1309,7 @@ small,
 mark,
 .mark {
   background-color: #fcf8e3;
-  padding: .2em;
+  padding: 0.2em;
 }
 .text-left {
   text-align: left;
@@ -1489,7 +1498,7 @@ blockquote .small {
 blockquote footer:before,
 blockquote small:before,
 blockquote .small:before {
-  content: '\2014 \00A0';
+  content: "\2014 \00A0";
 }
 .blockquote-reverse,
 blockquote.pull-right {
@@ -1505,7 +1514,7 @@ blockquote.pull-right footer:before,
 blockquote.pull-right small:before,
 .blockquote-reverse .small:before,
 blockquote.pull-right .small:before {
-  content: '';
+  content: "";
 }
 .blockquote-reverse footer:after,
 blockquote.pull-right footer:after,
@@ -1513,7 +1522,7 @@ blockquote.pull-right footer:after,
 blockquote.pull-right small:after,
 .blockquote-reverse .small:after,
 blockquote.pull-right .small:after {
-  content: '\00A0 \2014';
+  content: "\00A0 \2014";
 }
 address {
   margin-bottom: 18px;
@@ -1603,13 +1612,71 @@ pre code {
   margin-left: 0px;
   margin-right: 0px;
 }
-.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {
+.col-xs-1,
+.col-sm-1,
+.col-md-1,
+.col-lg-1,
+.col-xs-2,
+.col-sm-2,
+.col-md-2,
+.col-lg-2,
+.col-xs-3,
+.col-sm-3,
+.col-md-3,
+.col-lg-3,
+.col-xs-4,
+.col-sm-4,
+.col-md-4,
+.col-lg-4,
+.col-xs-5,
+.col-sm-5,
+.col-md-5,
+.col-lg-5,
+.col-xs-6,
+.col-sm-6,
+.col-md-6,
+.col-lg-6,
+.col-xs-7,
+.col-sm-7,
+.col-md-7,
+.col-lg-7,
+.col-xs-8,
+.col-sm-8,
+.col-md-8,
+.col-lg-8,
+.col-xs-9,
+.col-sm-9,
+.col-md-9,
+.col-lg-9,
+.col-xs-10,
+.col-sm-10,
+.col-md-10,
+.col-lg-10,
+.col-xs-11,
+.col-sm-11,
+.col-md-11,
+.col-lg-11,
+.col-xs-12,
+.col-sm-12,
+.col-md-12,
+.col-lg-12 {
   position: relative;
   min-height: 1px;
   padding-left: 0px;
   padding-right: 0px;
 }
-.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {
+.col-xs-1,
+.col-xs-2,
+.col-xs-3,
+.col-xs-4,
+.col-xs-5,
+.col-xs-6,
+.col-xs-7,
+.col-xs-8,
+.col-xs-9,
+.col-xs-10,
+.col-xs-11,
+.col-xs-12 {
   float: left;
 }
 .col-xs-12 {
@@ -1766,7 +1833,18 @@ pre code {
   margin-left: 0%;
 }
 @media (min-width: 768px) {
-  .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {
+  .col-sm-1,
+  .col-sm-2,
+  .col-sm-3,
+  .col-sm-4,
+  .col-sm-5,
+  .col-sm-6,
+  .col-sm-7,
+  .col-sm-8,
+  .col-sm-9,
+  .col-sm-10,
+  .col-sm-11,
+  .col-sm-12 {
     float: left;
   }
   .col-sm-12 {
@@ -1924,7 +2002,18 @@ pre code {
   }
 }
 @media (min-width: 992px) {
-  .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {
+  .col-md-1,
+  .col-md-2,
+  .col-md-3,
+  .col-md-4,
+  .col-md-5,
+  .col-md-6,
+  .col-md-7,
+  .col-md-8,
+  .col-md-9,
+  .col-md-10,
+  .col-md-11,
+  .col-md-12 {
     float: left;
   }
   .col-md-12 {
@@ -2082,7 +2171,18 @@ pre code {
   }
 }
 @media (min-width: 1200px) {
-  .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {
+  .col-lg-1,
+  .col-lg-2,
+  .col-lg-3,
+  .col-lg-4,
+  .col-lg-5,
+  .col-lg-6,
+  .col-lg-7,
+  .col-lg-8,
+  .col-lg-9,
+  .col-lg-10,
+  .col-lg-11,
+  .col-lg-12 {
     float: left;
   }
   .col-lg-12 {
@@ -2551,15 +2651,18 @@ output {
   border-radius: 2px;
   -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
   box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-  -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
-  -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
-  transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+  -webkit-transition: border-color ease-in-out 0.15s,
+    box-shadow ease-in-out 0.15s;
+  -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
+  transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
 }
 .form-control:focus {
   border-color: #66afe9;
   outline: 0;
-  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
-  box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
+    0 0 8px rgba(102, 175, 233, 0.6);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
+    0 0 8px rgba(102, 175, 233, 0.6);
 }
 .form-control::-moz-placeholder {
   color: #999;
@@ -3765,11 +3868,15 @@ tbody.collapse.in {
   border-radius: 0;
 }
 .btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,
-.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {
+.btn-group-vertical
+  > .btn-group:first-child:not(:last-child)
+  > .dropdown-toggle {
   border-bottom-right-radius: 0;
   border-bottom-left-radius: 0;
 }
-.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {
+.btn-group-vertical
+  > .btn-group:last-child:not(:first-child)
+  > .btn:first-child {
   border-top-right-radius: 0;
   border-top-left-radius: 0;
 }
@@ -4353,8 +4460,10 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
   padding: 10px 0px;
   border-top: 1px solid transparent;
   border-bottom: 1px solid transparent;
-  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
-  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+  -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1),
+    0 1px 0 rgba(255, 255, 255, 0.1);
+  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1),
+    0 1px 0 rgba(255, 255, 255, 0.1);
   margin-top: -1px;
   margin-bottom: -1px;
 }
@@ -4829,7 +4938,7 @@ fieldset[disabled] .navbar-inverse .btn-link:focus {
 }
 .label {
   display: inline;
-  padding: .2em .6em .3em;
+  padding: 0.2em 0.6em 0.3em;
   font-size: 75%;
   font-weight: bold;
   line-height: 1;
@@ -4837,7 +4946,7 @@ fieldset[disabled] .navbar-inverse .btn-link:focus {
   text-align: center;
   white-space: nowrap;
   vertical-align: baseline;
-  border-radius: .25em;
+  border-radius: 0.25em;
 }
 a.label:hover,
 a.label:focus {
@@ -5126,9 +5235,36 @@ a.thumbnail.active {
 }
 .progress-striped .progress-bar,
 .progress-bar-striped {
-  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -webkit-linear-gradient(
+    45deg,
+    rgba(255, 255, 255, 0.15) 25%,
+    transparent 25%,
+    transparent 50%,
+    rgba(255, 255, 255, 0.15) 50%,
+    rgba(255, 255, 255, 0.15) 75%,
+    transparent 75%,
+    transparent
+  );
+  background-image: -o-linear-gradient(
+    45deg,
+    rgba(255, 255, 255, 0.15) 25%,
+    transparent 25%,
+    transparent 50%,
+    rgba(255, 255, 255, 0.15) 50%,
+    rgba(255, 255, 255, 0.15) 75%,
+    transparent 75%,
+    transparent
+  );
+  background-image: linear-gradient(
+    45deg,
+    rgba(255, 255, 255, 0.15) 25%,
+    transparent 25%,
+    transparent 50%,
+    rgba(255, 255, 255, 0.15) 50%,
+    rgba(255, 255, 255, 0.15) 75%,
+    transparent 75%,
+    transparent
+  );
   background-size: 40px 40px;
 }
 .progress.active .progress-bar,
@@ -5141,33 +5277,141 @@ a.thumbnail.active {
   background-color: #5cb85c;
 }
 .progress-striped .progress-bar-success {
-  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -webkit-linear-gradient(
+    45deg,
+    rgba(255, 255, 255, 0.15) 25%,
+    transparent 25%,
+    transparent 50%,
+    rgba(255, 255, 255, 0.15) 50%,
+    rgba(255, 255, 255, 0.15) 75%,
+    transparent 75%,
+    transparent
+  );
+  background-image: -o-linear-gradient(
+    45deg,
+    rgba(255, 255, 255, 0.15) 25%,
+    transparent 25%,
+    transparent 50%,
+    rgba(255, 255, 255, 0.15) 50%,
+    rgba(255, 255, 255, 0.15) 75%,
+    transparent 75%,
+    transparent
+  );
+  background-image: linear-gradient(
+    45deg,
+    rgba(255, 255, 255, 0.15) 25%,
+    transparent 25%,
+    transparent 50%,
+    rgba(255, 255, 255, 0.15) 50%,
+    rgba(255, 255, 255, 0.15) 75%,
+    transparent 75%,
+    transparent
+  );
 }
 .progress-bar-info {
   background-color: #5bc0de;
 }
 .progress-striped .progress-bar-info {
-  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -webkit-linear-gradient(
+    45deg,
+    rgba(255, 255, 255, 0.15) 25%,
+    transparent 25%,
+    transparent 50%,
+    rgba(255, 255, 255, 0.15) 50%,
+    rgba(255, 255, 255, 0.15) 75%,
+    transparent 75%,
+    transparent
+  );
+  background-image: -o-linear-gradient(
+    45deg,
+    rgba(255, 255, 255, 0.15) 25%,
+    transparent 25%,
+    transparent 50%,
+    rgba(255, 255, 255, 0.15) 50%,
+    rgba(255, 255, 255, 0.15) 75%,
+    transparent 75%,
+    transparent
+  );
+  background-image: linear-gradient(
+    45deg,
+    rgba(255, 255, 255, 0.15) 25%,
+    transparent 25%,
+    transparent 50%,
+    rgba(255, 255, 255, 0.15) 50%,
+    rgba(255, 255, 255, 0.15) 75%,
+    transparent 75%,
+    transparent
+  );
 }
 .progress-bar-warning {
   background-color: #f0ad4e;
 }
 .progress-striped .progress-bar-warning {
-  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -webkit-linear-gradient(
+    45deg,
+    rgba(255, 255, 255, 0.15) 25%,
+    transparent 25%,
+    transparent 50%,
+    rgba(255, 255, 255, 0.15) 50%,
+    rgba(255, 255, 255, 0.15) 75%,
+    transparent 75%,
+    transparent
+  );
+  background-image: -o-linear-gradient(
+    45deg,
+    rgba(255, 255, 255, 0.15) 25%,
+    transparent 25%,
+    transparent 50%,
+    rgba(255, 255, 255, 0.15) 50%,
+    rgba(255, 255, 255, 0.15) 75%,
+    transparent 75%,
+    transparent
+  );
+  background-image: linear-gradient(
+    45deg,
+    rgba(255, 255, 255, 0.15) 25%,
+    transparent 25%,
+    transparent 50%,
+    rgba(255, 255, 255, 0.15) 50%,
+    rgba(255, 255, 255, 0.15) 75%,
+    transparent 75%,
+    transparent
+  );
 }
 .progress-bar-danger {
   background-color: #d9534f;
 }
 .progress-striped .progress-bar-danger {
-  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -webkit-linear-gradient(
+    45deg,
+    rgba(255, 255, 255, 0.15) 25%,
+    transparent 25%,
+    transparent 50%,
+    rgba(255, 255, 255, 0.15) 50%,
+    rgba(255, 255, 255, 0.15) 75%,
+    transparent 75%,
+    transparent
+  );
+  background-image: -o-linear-gradient(
+    45deg,
+    rgba(255, 255, 255, 0.15) 25%,
+    transparent 25%,
+    transparent 50%,
+    rgba(255, 255, 255, 0.15) 50%,
+    rgba(255, 255, 255, 0.15) 75%,
+    transparent 75%,
+    transparent
+  );
+  background-image: linear-gradient(
+    45deg,
+    rgba(255, 255, 255, 0.15) 25%,
+    transparent 25%,
+    transparent 50%,
+    rgba(255, 255, 255, 0.15) 50%,
+    rgba(255, 255, 255, 0.15) 75%,
+    transparent 75%,
+    transparent
+  );
 }
 .media {
   margin-top: 15px;
@@ -5473,7 +5717,10 @@ button.list-group-item-danger.active:focus {
   border-radius: 0;
 }
 .panel > .list-group:first-child .list-group-item:first-child,
-.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {
+.panel
+  > .panel-collapse
+  > .list-group:first-child
+  .list-group-item:first-child {
   border-top: 0;
   border-top-right-radius: 1px;
   border-top-left-radius: 1px;
@@ -5484,7 +5731,11 @@ button.list-group-item-danger.active:focus {
   border-bottom-right-radius: 1px;
   border-bottom-left-radius: 1px;
 }
-.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child {
+.panel
+  > .panel-heading
+  + .panel-collapse
+  > .list-group
+  .list-group-item:first-child {
   border-top-right-radius: 0;
   border-top-left-radius: 0;
 }
@@ -5511,30 +5762,78 @@ button.list-group-item-danger.active:focus {
   border-top-left-radius: 1px;
 }
 .panel > .table:first-child > thead:first-child > tr:first-child,
-.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,
+.panel
+  > .table-responsive:first-child
+  > .table:first-child
+  > thead:first-child
+  > tr:first-child,
 .panel > .table:first-child > tbody:first-child > tr:first-child,
-.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {
+.panel
+  > .table-responsive:first-child
+  > .table:first-child
+  > tbody:first-child
+  > tr:first-child {
   border-top-left-radius: 1px;
   border-top-right-radius: 1px;
 }
 .panel > .table:first-child > thead:first-child > tr:first-child td:first-child,
-.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,
+.panel
+  > .table-responsive:first-child
+  > .table:first-child
+  > thead:first-child
+  > tr:first-child
+  td:first-child,
 .panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,
-.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,
+.panel
+  > .table-responsive:first-child
+  > .table:first-child
+  > tbody:first-child
+  > tr:first-child
+  td:first-child,
 .panel > .table:first-child > thead:first-child > tr:first-child th:first-child,
-.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,
+.panel
+  > .table-responsive:first-child
+  > .table:first-child
+  > thead:first-child
+  > tr:first-child
+  th:first-child,
 .panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,
-.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {
+.panel
+  > .table-responsive:first-child
+  > .table:first-child
+  > tbody:first-child
+  > tr:first-child
+  th:first-child {
   border-top-left-radius: 1px;
 }
 .panel > .table:first-child > thead:first-child > tr:first-child td:last-child,
-.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,
+.panel
+  > .table-responsive:first-child
+  > .table:first-child
+  > thead:first-child
+  > tr:first-child
+  td:last-child,
 .panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,
-.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,
+.panel
+  > .table-responsive:first-child
+  > .table:first-child
+  > tbody:first-child
+  > tr:first-child
+  td:last-child,
 .panel > .table:first-child > thead:first-child > tr:first-child th:last-child,
-.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,
+.panel
+  > .table-responsive:first-child
+  > .table:first-child
+  > thead:first-child
+  > tr:first-child
+  th:last-child,
 .panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,
-.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {
+.panel
+  > .table-responsive:first-child
+  > .table:first-child
+  > tbody:first-child
+  > tr:first-child
+  th:last-child {
   border-top-right-radius: 1px;
 }
 .panel > .table:last-child,
@@ -5543,30 +5842,78 @@ button.list-group-item-danger.active:focus {
   border-bottom-left-radius: 1px;
 }
 .panel > .table:last-child > tbody:last-child > tr:last-child,
-.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,
+.panel
+  > .table-responsive:last-child
+  > .table:last-child
+  > tbody:last-child
+  > tr:last-child,
 .panel > .table:last-child > tfoot:last-child > tr:last-child,
-.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {
+.panel
+  > .table-responsive:last-child
+  > .table:last-child
+  > tfoot:last-child
+  > tr:last-child {
   border-bottom-left-radius: 1px;
   border-bottom-right-radius: 1px;
 }
 .panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,
-.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,
+.panel
+  > .table-responsive:last-child
+  > .table:last-child
+  > tbody:last-child
+  > tr:last-child
+  td:first-child,
 .panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,
-.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,
+.panel
+  > .table-responsive:last-child
+  > .table:last-child
+  > tfoot:last-child
+  > tr:last-child
+  td:first-child,
 .panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,
-.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,
+.panel
+  > .table-responsive:last-child
+  > .table:last-child
+  > tbody:last-child
+  > tr:last-child
+  th:first-child,
 .panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,
-.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {
+.panel
+  > .table-responsive:last-child
+  > .table:last-child
+  > tfoot:last-child
+  > tr:last-child
+  th:first-child {
   border-bottom-left-radius: 1px;
 }
 .panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,
-.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,
+.panel
+  > .table-responsive:last-child
+  > .table:last-child
+  > tbody:last-child
+  > tr:last-child
+  td:last-child,
 .panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,
-.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,
+.panel
+  > .table-responsive:last-child
+  > .table:last-child
+  > tfoot:last-child
+  > tr:last-child
+  td:last-child,
 .panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,
-.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,
+.panel
+  > .table-responsive:last-child
+  > .table:last-child
+  > tbody:last-child
+  > tr:last-child
+  th:last-child,
 .panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,
-.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {
+.panel
+  > .table-responsive:last-child
+  > .table:last-child
+  > tfoot:last-child
+  > tr:last-child
+  th:last-child {
   border-bottom-right-radius: 1px;
 }
 .panel > .panel-body + .table,
@@ -6304,18 +6651,42 @@ button.close {
   background-color: rgba(0, 0, 0, 0);
 }
 .carousel-control.left {
-  background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);
-  background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);
-  background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);
+  background-image: -webkit-linear-gradient(
+    left,
+    rgba(0, 0, 0, 0.5) 0%,
+    rgba(0, 0, 0, 0.0001) 100%
+  );
+  background-image: -o-linear-gradient(
+    left,
+    rgba(0, 0, 0, 0.5) 0%,
+    rgba(0, 0, 0, 0.0001) 100%
+  );
+  background-image: linear-gradient(
+    to right,
+    rgba(0, 0, 0, 0.5) 0%,
+    rgba(0, 0, 0, 0.0001) 100%
+  );
   background-repeat: repeat-x;
   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);
 }
 .carousel-control.right {
   left: auto;
   right: 0;
-  background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);
-  background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);
-  background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);
+  background-image: -webkit-linear-gradient(
+    left,
+    rgba(0, 0, 0, 0.0001) 0%,
+    rgba(0, 0, 0, 0.5) 100%
+  );
+  background-image: -o-linear-gradient(
+    left,
+    rgba(0, 0, 0, 0.0001) 0%,
+    rgba(0, 0, 0, 0.5) 100%
+  );
+  background-image: linear-gradient(
+    to right,
+    rgba(0, 0, 0, 0.0001) 0%,
+    rgba(0, 0, 0, 0.5) 100%
+  );
   background-repeat: repeat-x;
   filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);
 }
@@ -6355,10 +6726,10 @@ button.close {
   font-family: serif;
 }
 .carousel-control .icon-prev:before {
-  content: '\2039';
+  content: "\2039";
 }
 .carousel-control .icon-next:before {
-  content: '\203a';
+  content: "\203a";
 }
 .carousel-indicators {
   position: absolute;
@@ -6742,9 +7113,18 @@ button.close {
 /* FONT PATH
  * -------------------------- */
 @font-face {
-  font-family: 'FontAwesome';
-  src: url('../components/font-awesome/fonts/fontawesome-webfont.eot?v=4.7.0');
-  src: url('../components/font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../components/font-awesome/fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../components/font-awesome/fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../components/font-awesome/fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../components/font-awesome/fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');
+  font-family: "FontAwesome";
+  src: url("../components/font-awesome/fonts/fontawesome-webfont.eot?v=4.7.0");
+  src: url("../components/font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.7.0")
+      format("embedded-opentype"),
+    url("../components/font-awesome/fonts/fontawesome-webfont.woff2?v=4.7.0")
+      format("woff2"),
+    url("../components/font-awesome/fonts/fontawesome-webfont.woff?v=4.7.0")
+      format("woff"),
+    url("../components/font-awesome/fonts/fontawesome-webfont.ttf?v=4.7.0")
+      format("truetype"),
+    url("../components/font-awesome/fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular")
+      format("svg");
   font-weight: normal;
   font-style: normal;
 }
@@ -6797,9 +7177,9 @@ button.close {
   left: -1.85714286em;
 }
 .fa-border {
-  padding: .2em .25em .15em;
+  padding: 0.2em 0.25em 0.15em;
   border: solid 0.08em #eee;
-  border-radius: .1em;
+  border-radius: 0.1em;
 }
 .fa-pull-left {
   float: left;
@@ -6808,10 +7188,10 @@ button.close {
   float: right;
 }
 .fa.fa-pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .fa.fa-pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 /* Deprecated as of 4.4.0 */
 .pull-right {
@@ -6821,10 +7201,10 @@ button.close {
   float: left;
 }
 .fa.pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .fa.pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .fa-spin {
   -webkit-animation: fa-spin 2s infinite linear;
@@ -9117,15 +9497,15 @@ label {
 }
 /* Flexible box model classes */
 /* Taken from Alex Russell http://infrequently.org/2009/08/css-3-progress/ */
-/* This file is a compatability layer.  It allows the usage of flexible box 
+/* This file is a compatability layer.  It allows the usage of flexible box
 model layouts accross multiple browsers, including older browsers.  The newest,
 universal implementation of the flexible box model is used when available (see
-`Modern browsers` comments below).  Browsers that are known to implement this 
+`Modern browsers` comments below).  Browsers that are known to implement this
 new spec completely include:
 
     Firefox 28.0+
     Chrome 29.0+
-    Internet Explorer 11+ 
+    Internet Explorer 11+
     Opera 17.0+
 
 Browsers not listed, including Safari, are supported via the styling under the
@@ -9715,7 +10095,7 @@ ul.breadcrumb span {
 }
 .list_header {
   font-weight: bold;
-  background-color: #EEE;
+  background-color: #eee;
 }
 .list_placeholder {
   font-weight: bold;
@@ -9774,7 +10154,7 @@ ul.breadcrumb span {
 [dir="rtl"] .list_item > div input {
   margin-right: 0;
 }
-.new-file input[type=checkbox] {
+.new-file input[type="checkbox"] {
   visibility: hidden;
 }
 .item_name {
@@ -9836,7 +10216,7 @@ ul.breadcrumb span {
   height: 24px;
   line-height: 24px;
 }
-.list_item input:not([type=checkbox]) {
+.list_item input:not([type="checkbox"]) {
   padding-top: 3px;
   padding-bottom: 3px;
   height: 22px;
@@ -9872,7 +10252,7 @@ ul.breadcrumb span {
   min-width: 50px;
 }
 [dir="rtl"] #button-select-all.btn {
-  float: right ;
+  float: right;
 }
 #select-all {
   margin-left: 7px;
@@ -9901,16 +10281,16 @@ ul.breadcrumb span {
   content: "\f114";
 }
 .folder_icon:before.fa-pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .folder_icon:before.fa-pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .folder_icon:before.pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .folder_icon:before.pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .notebook_icon:before {
   display: inline-block;
@@ -9924,16 +10304,16 @@ ul.breadcrumb span {
   top: -1px;
 }
 .notebook_icon:before.fa-pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .notebook_icon:before.fa-pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .notebook_icon:before.pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .notebook_icon:before.pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .running_notebook_icon:before {
   display: inline-block;
@@ -9948,16 +10328,16 @@ ul.breadcrumb span {
   color: #5cb85c;
 }
 .running_notebook_icon:before.fa-pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .running_notebook_icon:before.fa-pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .running_notebook_icon:before.pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .running_notebook_icon:before.pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .file_icon:before {
   display: inline-block;
@@ -9971,16 +10351,16 @@ ul.breadcrumb span {
   top: -2px;
 }
 .file_icon:before.fa-pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .file_icon:before.fa-pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .file_icon:before.pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .file_icon:before.pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 #notebook_toolbar .pull-right {
   padding-top: 0px;
@@ -10015,7 +10395,7 @@ ul#new-menu {
   margin-bottom: 1em;
 }
 #running .panel-group .panel .panel-heading {
-  background-color: #EEE;
+  background-color: #eee;
   padding-top: 4px;
   padding-bottom: 4px;
   padding-left: 7px;
@@ -10097,16 +10477,16 @@ ul#new-menu {
   width: 20px;
 }
 .dirty-indicator.fa-pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .dirty-indicator.fa-pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .dirty-indicator.pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .dirty-indicator.pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .dirty-indicator-dirty {
   display: inline-block;
@@ -10118,16 +10498,16 @@ ul#new-menu {
   width: 20px;
 }
 .dirty-indicator-dirty.fa-pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .dirty-indicator-dirty.fa-pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .dirty-indicator-dirty.pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .dirty-indicator-dirty.pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .dirty-indicator-clean {
   display: inline-block;
@@ -10139,16 +10519,16 @@ ul#new-menu {
   width: 20px;
 }
 .dirty-indicator-clean.fa-pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .dirty-indicator-clean.fa-pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .dirty-indicator-clean.pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .dirty-indicator-clean.pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .dirty-indicator-clean:before {
   display: inline-block;
@@ -10160,16 +10540,16 @@ ul#new-menu {
   content: "\f00c";
 }
 .dirty-indicator-clean:before.fa-pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .dirty-indicator-clean:before.fa-pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .dirty-indicator-clean:before.pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .dirty-indicator-clean:before.pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 #filename {
   font-size: 16pt;
@@ -10186,7 +10566,7 @@ ul#new-menu {
 }
 @media not print {
   #texteditor-backdrop {
-    background-color: #EEE;
+    background-color: #eee;
   }
 }
 @media print {
@@ -10222,34 +10602,34 @@ ul#new-menu {
    http://www.xcolors.net/dl/baskerville-ivorylight and
    http://www.xcolors.net/dl/euphrasia */
 .ansi-black-fg {
-  color: #3E424D;
+  color: #3e424d;
 }
 .ansi-black-bg {
-  background-color: #3E424D;
+  background-color: #3e424d;
 }
 .ansi-black-intense-fg {
-  color: #282C36;
+  color: #282c36;
 }
 .ansi-black-intense-bg {
-  background-color: #282C36;
+  background-color: #282c36;
 }
 .ansi-red-fg {
-  color: #E75C58;
+  color: #e75c58;
 }
 .ansi-red-bg {
-  background-color: #E75C58;
+  background-color: #e75c58;
 }
 .ansi-red-intense-fg {
-  color: #B22B31;
+  color: #b22b31;
 }
 .ansi-red-intense-bg {
-  background-color: #B22B31;
+  background-color: #b22b31;
 }
 .ansi-green-fg {
-  color: #00A250;
+  color: #00a250;
 }
 .ansi-green-bg {
-  background-color: #00A250;
+  background-color: #00a250;
 }
 .ansi-green-intense-fg {
   color: #007427;
@@ -10258,67 +10638,67 @@ ul#new-menu {
   background-color: #007427;
 }
 .ansi-yellow-fg {
-  color: #DDB62B;
+  color: #ddb62b;
 }
 .ansi-yellow-bg {
-  background-color: #DDB62B;
+  background-color: #ddb62b;
 }
 .ansi-yellow-intense-fg {
-  color: #B27D12;
+  color: #b27d12;
 }
 .ansi-yellow-intense-bg {
-  background-color: #B27D12;
+  background-color: #b27d12;
 }
 .ansi-blue-fg {
-  color: #208FFB;
+  color: #208ffb;
 }
 .ansi-blue-bg {
-  background-color: #208FFB;
+  background-color: #208ffb;
 }
 .ansi-blue-intense-fg {
-  color: #0065CA;
+  color: #0065ca;
 }
 .ansi-blue-intense-bg {
-  background-color: #0065CA;
+  background-color: #0065ca;
 }
 .ansi-magenta-fg {
-  color: #D160C4;
+  color: #d160c4;
 }
 .ansi-magenta-bg {
-  background-color: #D160C4;
+  background-color: #d160c4;
 }
 .ansi-magenta-intense-fg {
-  color: #A03196;
+  color: #a03196;
 }
 .ansi-magenta-intense-bg {
-  background-color: #A03196;
+  background-color: #a03196;
 }
 .ansi-cyan-fg {
-  color: #60C6C8;
+  color: #60c6c8;
 }
 .ansi-cyan-bg {
-  background-color: #60C6C8;
+  background-color: #60c6c8;
 }
 .ansi-cyan-intense-fg {
-  color: #258F8F;
+  color: #258f8f;
 }
 .ansi-cyan-intense-bg {
-  background-color: #258F8F;
+  background-color: #258f8f;
 }
 .ansi-white-fg {
-  color: #C5C1B4;
+  color: #c5c1b4;
 }
 .ansi-white-bg {
-  background-color: #C5C1B4;
+  background-color: #c5c1b4;
 }
 .ansi-white-intense-fg {
-  color: #A1A6B2;
+  color: #a1a6b2;
 }
 .ansi-white-intense-bg {
-  background-color: #A1A6B2;
+  background-color: #a1a6b2;
 }
 .ansi-default-inverse-fg {
-  color: #FFFFFF;
+  color: #ffffff;
 }
 .ansi-default-inverse-bg {
   background-color: #000000;
@@ -10422,17 +10802,17 @@ div.cell:before {
   top: -1px;
   left: -1px;
   width: 5px;
-  height: calc(100% +  2px);
-  content: '';
+  height: calc(100% + 2px);
+  content: "";
   background: transparent;
 }
 div.cell.jupyter-soft-selected {
-  border-left-color: #E3F2FD;
+  border-left-color: #e3f2fd;
   border-left-width: 1px;
   padding-left: 5px;
-  border-right-color: #E3F2FD;
+  border-right-color: #e3f2fd;
   border-right-width: 1px;
-  background: #E3F2FD;
+  background: #e3f2fd;
 }
 @media print {
   div.cell.jupyter-soft-selected {
@@ -10450,9 +10830,9 @@ div.cell.selected.jupyter-soft-selected:before {
   top: -1px;
   left: -1px;
   width: 5px;
-  height: calc(100% +  2px);
-  content: '';
-  background: #42A5F5;
+  height: calc(100% + 2px);
+  content: "";
+  background: #42a5f5;
 }
 @media print {
   div.cell.selected,
@@ -10461,7 +10841,7 @@ div.cell.selected.jupyter-soft-selected:before {
   }
 }
 .edit_mode div.cell.selected {
-  border-color: #66BB6A;
+  border-color: #66bb6a;
 }
 .edit_mode div.cell.selected:before {
   position: absolute;
@@ -10469,9 +10849,9 @@ div.cell.selected.jupyter-soft-selected:before {
   top: -1px;
   left: -1px;
   width: 5px;
-  height: calc(100% +  2px);
-  content: '';
-  background: #66BB6A;
+  height: calc(100% + 2px);
+  content: "";
+  background: #66bb6a;
 }
 @media print {
   .edit_mode div.cell.selected {
@@ -10624,7 +11004,7 @@ div.input {
 }
 /* input_area and input_prompt must match in top border and margin for alignment */
 div.input_prompt {
-  color: #303F9F;
+  color: #303f9f;
   border-top: 1px solid transparent;
 }
 div.input_area > div.highlight {
@@ -10717,7 +11097,7 @@ Adapted from GitHub theme
   color: #333333;
 }
 .highlight-string {
-  color: #BA2121;
+  color: #ba2121;
 }
 .highlight-comment {
   color: #408080;
@@ -10727,7 +11107,7 @@ Adapted from GitHub theme
   color: #080;
 }
 .highlight-atom {
-  color: #88F;
+  color: #88f;
 }
 .highlight-keyword {
   color: #008000;
@@ -10740,11 +11120,11 @@ Adapted from GitHub theme
   color: #f00;
 }
 .highlight-operator {
-  color: #AA22FF;
+  color: #aa22ff;
   font-weight: bold;
 }
 .highlight-meta {
-  color: #AA22FF;
+  color: #aa22ff;
 }
 /* previously not defined, copying from default codemirror */
 .highlight-def {
@@ -10780,7 +11160,7 @@ Adapted from GitHub theme
   font-weight: bold;
 }
 .cm-s-ipython span.cm-atom {
-  color: #88F;
+  color: #88f;
 }
 .cm-s-ipython span.cm-number {
   color: #080;
@@ -10792,7 +11172,7 @@ Adapted from GitHub theme
   color: #000;
 }
 .cm-s-ipython span.cm-operator {
-  color: #AA22FF;
+  color: #aa22ff;
   font-weight: bold;
 }
 .cm-s-ipython span.cm-variable-2 {
@@ -10806,13 +11186,13 @@ Adapted from GitHub theme
   font-style: italic;
 }
 .cm-s-ipython span.cm-string {
-  color: #BA2121;
+  color: #ba2121;
 }
 .cm-s-ipython span.cm-string-2 {
   color: #f50;
 }
 .cm-s-ipython span.cm-meta {
-  color: #AA22FF;
+  color: #aa22ff;
 }
 .cm-s-ipython span.cm-qualifier {
   color: #555;
@@ -10909,7 +11289,7 @@ div.out_prompt_overlay:hover {
   background: rgba(240, 240, 240, 0.5);
 }
 div.output_prompt {
-  color: #D84315;
+  color: #d84315;
 }
 /* This class is the outer container of all output sections. */
 div.output_area {
@@ -11099,38 +11479,38 @@ div.output_unrecognized a:hover {
   font-size: 185.7%;
   margin: 1.08em 0 0 0;
   font-weight: bold;
-  line-height: 1.0;
+  line-height: 1;
 }
 .rendered_html h2 {
   font-size: 157.1%;
   margin: 1.27em 0 0 0;
   font-weight: bold;
-  line-height: 1.0;
+  line-height: 1;
 }
 .rendered_html h3 {
   font-size: 128.6%;
   margin: 1.55em 0 0 0;
   font-weight: bold;
-  line-height: 1.0;
+  line-height: 1;
 }
 .rendered_html h4 {
   font-size: 100%;
   margin: 2em 0 0 0;
   font-weight: bold;
-  line-height: 1.0;
+  line-height: 1;
 }
 .rendered_html h5 {
   font-size: 100%;
   margin: 2em 0 0 0;
   font-weight: bold;
-  line-height: 1.0;
+  line-height: 1;
   font-style: italic;
 }
 .rendered_html h6 {
   font-size: 100%;
   margin: 2em 0 0 0;
   font-weight: bold;
-  line-height: 1.0;
+  line-height: 1;
   font-style: italic;
 }
 .rendered_html h1:first-child {
@@ -11462,7 +11842,7 @@ p {
 }
 .end_space {
   min-height: 100px;
-  transition: height .2s ease;
+  transition: height 0.2s ease;
 }
 .notebook_app > #header {
   -webkit-box-shadow: 0px 0px 12px 1px rgba(87, 87, 87, 0.2);
@@ -11470,7 +11850,7 @@ p {
 }
 @media not print {
   .notebook_app {
-    background-color: #EEE;
+    background-color: #eee;
   }
 }
 kbd {
@@ -11507,9 +11887,9 @@ kbd {
 }
 /* CSS for the cell toolbar */
 .celltoolbar {
-  border: thin solid #CFCFCF;
+  border: thin solid #cfcfcf;
   border-bottom: none;
-  background: #EEE;
+  background: #eee;
   border-radius: 2px 2px 0px 0px;
   width: 100%;
   height: 29px;
@@ -11578,9 +11958,10 @@ kbd {
   border-radius: 2px;
   -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
   box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-  -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
-  -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
-  transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+  -webkit-transition: border-color ease-in-out 0.15s,
+    box-shadow ease-in-out 0.15s;
+  -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
+  transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
   height: 30px;
   padding: 5px 10px;
   font-size: 12px;
@@ -11595,8 +11976,10 @@ kbd {
 .celltoolbar select:focus {
   border-color: #66afe9;
   outline: 0;
-  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
-  box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
+    0 0 8px rgba(102, 175, 233, 0.6);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
+    0 0 8px rgba(102, 175, 233, 0.6);
 }
 .celltoolbar select::-moz-placeholder {
   color: #999;
@@ -11664,7 +12047,7 @@ select[multiple].celltoolbar select {
   width: 40px;
   height: 100%;
   /* Fade to background color of cell toolbar */
-  background: linear-gradient(to right, rgba(0, 0, 0, 0), #EEE);
+  background: linear-gradient(to right, rgba(0, 0, 0, 0), #eee);
 }
 .tags-input > * {
   margin-left: 4px;
@@ -11685,9 +12068,10 @@ select[multiple].celltoolbar select {
   border-radius: 2px;
   -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
   box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-  -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
-  -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
-  transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+  -webkit-transition: border-color ease-in-out 0.15s,
+    box-shadow ease-in-out 0.15s;
+  -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
+  transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
   height: 30px;
   padding: 5px 10px;
   font-size: 12px;
@@ -11706,8 +12090,10 @@ select[multiple].celltoolbar select {
 .tags-input button:focus {
   border-color: #66afe9;
   outline: 0;
-  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
-  box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
+    0 0 8px rgba(102, 175, 233, 0.6);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
+    0 0 8px rgba(102, 175, 233, 0.6);
 }
 .cell-tag::-moz-placeholder,
 .tags-input input::-moz-placeholder,
@@ -11778,7 +12164,7 @@ select[multiple].tags-input button {
   background-color: #fff;
   white-space: nowrap;
 }
-.tags-input input[type=text]:focus {
+.tags-input input[type="text"]:focus {
   outline: none;
   box-shadow: none;
   border-color: #ccc;
@@ -11936,16 +12322,16 @@ ul#help_menu li a i {
   margin-right: -10px;
 }
 .dropdown-submenu > a:after.fa-pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .dropdown-submenu > a:after.fa-pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .dropdown-submenu > a:after.pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .dropdown-submenu > a:after.pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 [dir="rtl"] .dropdown-submenu > a:after {
   float: left;
@@ -12054,16 +12440,16 @@ ul#help_menu li a i {
   content: "\f040";
 }
 .edit_mode .modal_indicator:before.fa-pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .edit_mode .modal_indicator:before.fa-pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .edit_mode .modal_indicator:before.pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .edit_mode .modal_indicator:before.pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .command_mode .modal_indicator:before {
   display: inline-block;
@@ -12072,19 +12458,19 @@ ul#help_menu li a i {
   text-rendering: auto;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
-  content: ' ';
+  content: " ";
 }
 .command_mode .modal_indicator:before.fa-pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .command_mode .modal_indicator:before.fa-pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .command_mode .modal_indicator:before.pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .command_mode .modal_indicator:before.pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .kernel_idle_icon:before {
   display: inline-block;
@@ -12096,16 +12482,16 @@ ul#help_menu li a i {
   content: "\f10c";
 }
 .kernel_idle_icon:before.fa-pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .kernel_idle_icon:before.fa-pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .kernel_idle_icon:before.pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .kernel_idle_icon:before.pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .kernel_busy_icon:before {
   display: inline-block;
@@ -12117,16 +12503,16 @@ ul#help_menu li a i {
   content: "\f111";
 }
 .kernel_busy_icon:before.fa-pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .kernel_busy_icon:before.fa-pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .kernel_busy_icon:before.pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .kernel_busy_icon:before.pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .kernel_dead_icon:before {
   display: inline-block;
@@ -12138,16 +12524,16 @@ ul#help_menu li a i {
   content: "\f1e2";
 }
 .kernel_dead_icon:before.fa-pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .kernel_dead_icon:before.fa-pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .kernel_dead_icon:before.pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .kernel_dead_icon:before.pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .kernel_disconnected_icon:before {
   display: inline-block;
@@ -12159,16 +12545,16 @@ ul#help_menu li a i {
   content: "\f127";
 }
 .kernel_disconnected_icon:before.fa-pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .kernel_disconnected_icon:before.fa-pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .kernel_disconnected_icon:before.pull-left {
-  margin-right: .3em;
+  margin-right: 0.3em;
 }
 .kernel_disconnected_icon:before.pull-right {
-  margin-left: .3em;
+  margin-left: 0.3em;
 }
 .notification_widget {
   color: #777;
@@ -12507,11 +12893,11 @@ div#pager .ui-resizable-handle {
   background: #f7f7f7;
   border-top: 1px solid #cfcfcf;
   border-bottom: 1px solid #cfcfcf;
-  /* This injects handle bars (a short, wide = symbol) for 
+  /* This injects handle bars (a short, wide = symbol) for
         the resize handle. */
 }
 div#pager .ui-resizable-handle::after {
-  content: '';
+  content: "";
   top: 2px;
   left: 50%;
   height: 3px;
@@ -12667,7 +13053,7 @@ span.autosave_status {
 .dropdown-menu > li > a.pulse,
 li.pulse > a.dropdown-toggle,
 li.pulse.open > a.dropdown-toggle {
-  background-color: #F37626;
+  background-color: #f37626;
   color: white;
 }
 /**
@@ -12825,7 +13211,7 @@ ul.typeahead-list > li > a {
   /* see https://github.com/jupyter/notebook/issues/559 */
   white-space: normal;
 }
-ul.typeahead-list  > li > a.pull-right {
+ul.typeahead-list > li > a.pull-right {
   float: left !important;
   float: left;
 }
@@ -12869,8 +13255,8 @@ ul.typeahead-list  > li > a.pull-right {
 }
 #find-and-replace #replace-preview .match,
 #find-and-replace #replace-preview .insert {
-  background-color: #BBDEFB;
-  border-color: #90CAF9;
+  background-color: #bbdefb;
+  border-color: #90caf9;
   border-style: solid;
   border-width: 1px;
   border-radius: 0px;
@@ -12882,13 +13268,13 @@ ul.typeahead-list  > li > a.pull-right {
   border-right: none;
 }
 #find-and-replace #replace-preview .replace .match {
-  background-color: #FFCDD2;
-  border-color: #EF9A9A;
+  background-color: #ffcdd2;
+  border-color: #ef9a9a;
   border-radius: 0px;
 }
 #find-and-replace #replace-preview .replace .insert {
-  background-color: #C8E6C9;
-  border-color: #A5D6A7;
+  background-color: #c8e6c9;
+  border-color: #a5d6a7;
   border-radius: 0px;
 }
 #find-and-replace #replace-preview {
@@ -12899,7 +13285,7 @@ ul.typeahead-list  > li > a.pull-right {
   padding: 5px 10px;
 }
 .terminal-app {
-  background: #EEE;
+  background: #eee;
 }
 .terminal-app #header {
   background: #fff;
@@ -12932,4 +13318,4 @@ ul.typeahead-list  > li > a.pull-right {
 .terminal-app #terminado-container {
   margin-top: 20px;
 }
-/*# sourceMappingURL=style.min.css.map */
\ No newline at end of file
+/*# sourceMappingURL=style.min.css.map */


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 4b728e40..5b0e1741 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -22,55 +22,84 @@ jobs:
           - os: "macos-latest"
             python-version: "3.9"
           - os: "ubuntu-latest"
-            python-version: "pypy-3.8"
+            python-version: "3.10"
       fail-fast: false
     steps:
       - name: Check out repository code
         uses: actions/checkout@v2
       - name: Run base setup actions
         uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
-
+      - uses: conda-incubator/setup-miniconda@v2
+        with:
+          mamba-version: "*"
+          channels: conda-forge
+      - name: Install conda-forge python
+        shell: bash -l {0}
+        run: |
+          mamba create -n nbconvert
+          conda activate nbconvert
+          mamba install python=${{ matrix.python-version }}
+      - name: Install conda-forge dependencies
+        shell: bash -l {0}
+        run: |
+          mamba create -n nbconvert
+          conda activate nbconvert
+          mamba install pip pyqtwebengine pandoc pyxdg
       - name: Install Linux dependencies
         if: startsWith(runner.os, 'Linux')
         run: |
           sudo apt-get update
           sudo apt-get install texlive-plain-generic inkscape texlive-xetex
-
-          # pandoc is not up to date in the ubuntu repos, so we install directly
-          wget https://github.com/jgm/pandoc/releases/download/2.17.1.1/pandoc-2.17.1.1-1-amd64.deb && sudo dpkg -i pandoc-2.17.1.1-1-amd64.deb
-
+          sudo apt-get install xvfb x11-utils libxkbcommon-x11-0
       - name: Install package dependencies
-        shell: bash
+        shell: bash -l {0}
         run: |
-          pip install codecov --user
-          pip install -e ".[execute,serve,test]" --user
-          python -m ipykernel.kernelspec --user
-          # many things installed in --users need this to be in $PATH
-          echo "/Users/runner/.local/bin" >> $GITHUB_PATH
+          conda activate nbconvert
+          pip install codecov
+          pip install -e ".[execute,serve,test]"
+          which python
+          python -m ipykernel.kernelspec --sys-prefix
 
       - name: List installed packages
+        shell: bash -l {0}
         run: |
+          conda activate nbconvert
           pip freeze
           pip check
 
       - name: Run tests with coverage
-        if: ${{ !startsWith(matrix.python-version, 'pypy') && !startsWith(runner.os, 'Windows')  }}
-        shell: bash
+        if: ${{ startsWith(runner.os, 'macos')  }}
+        shell: bash -l {0}
         run: |
+          conda activate nbconvert
           # See https://github.com/pyppeteer/pyppeteer/pull/321
-          pip install -U websockets --user
+          pip install -U websockets
           python -m pytest --cov nbconvert -vv
 
       - name: Run tests on pypy and Windows
-        if: ${{ startsWith(matrix.python-version, 'pypy') || startsWith(runner.os, 'Windows')  }}
-        shell: bash
+        if: ${{ startsWith(runner.os, 'linux') }}
+        shell: bash -l {0}
         run: |
+          conda activate nbconvert
           # See https://github.com/pyppeteer/pyppeteer/pull/321
-          pip install -U websockets --user
+          pip install -U websockets
+          #python -m pytest -vv
+          xvfb-run --auto-servernum `which coverage` run -m pytest -vv
+
+      - name: Run tests on pypy and Windows
+        if: ${{ startsWith(runner.os, 'Windows') }}
+        shell: bash -l {0}
+        run: |
+          conda activate nbconvert
+          # See https://github.com/pyppeteer/pyppeteer/pull/321
+          pip install -U websockets
           python -m pytest -vv
 
       - name: Code coverage
-        run: codecov
+        shell: bash -l {0}
+        run: |
+          conda activate nbconvert
+          codecov
 
   # Run "pre-commit run --all-files --hook-stage=manual"
   pre-commit:
diff --git a/nbconvert/exporters/tests/test_qtpdf.py b/nbconvert/exporters/tests/test_qtpdf.py
new file mode 100644
index 00000000..2dfa5db7
--- /dev/null
+++ b/nbconvert/exporters/tests/test_qtpdf.py
@@ -0,0 +1,24 @@
+"""Tests for the qtpdf preprocessor"""
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+import pytest
+
+from ..qt_screenshot import QT_INSTALLED
+from ..qtpdf import QtPDFExporter
+from .base import ExportersTestsBase
+
+
+@pytest.mark.skipif(not QT_INSTALLED, reason="PyQtWebEngine not installed")
+class TestQtPDFExporter(ExportersTestsBase):
+    """Contains test functions for qtpdf.py"""
+
+    exporter_class = QtPDFExporter
+
+    def test_export(self):
+        """
+        Can a TemplateExporter export something?
+        """
+        (output, resources) = QtPDFExporter().from_filename(self._get_notebook())
+        assert len(output) > 0
diff --git a/nbconvert/exporters/tests/test_qtpng.py b/nbconvert/exporters/tests/test_qtpng.py
new file mode 100644
index 00000000..511edaaa
--- /dev/null
+++ b/nbconvert/exporters/tests/test_qtpng.py
@@ -0,0 +1,31 @@
+"""Tests for the qtpng preprocessor"""
+
+# Copyright (c) IPython Development Team.
+# Distributed under the terms of the Modified BSD License.
+
+import os
+
+import pytest
+
+from ..qt_screenshot import QT_INSTALLED
+from ..qtpng import QtPNGExporter
+from .base import ExportersTestsBase
+
+
+@pytest.mark.skipif(not QT_INSTALLED, reason="PyQtWebEngine not installed")
+class TestQtPNGExporter(ExportersTestsBase):
+    """Contains test functions for qtpng.py"""
+
+    exporter_class = QtPNGExporter
+
+    def test_export(self):
+        """
+        Can a TemplateExporter export something?
+        """
+        if os.name == "nt":
+            # currently not supported
+            with pytest.raises(RuntimeError) as exc_info:
+                (output, resources) = QtPNGExporter().from_filename(self._get_notebook())
+        else:
+            (output, resources) = QtPNGExporter().from_filename(self._get_notebook())
+            assert len(output) > 0