Skip to content

Commit

Permalink
refactoring to use nbbrowserpdf
Browse files Browse the repository at this point in the history
  • Loading branch information
bollwyvl committed Nov 28, 2015
1 parent 34dc5c5 commit bd79f8d
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 185 deletions.
2 changes: 0 additions & 2 deletions conda.recipe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ requirements:
- python
- notebook
- funcsigs
- pypdf2
- ghost.py

test:
imports:
Expand Down
20 changes: 16 additions & 4 deletions nbpresent/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,23 @@

def load_jupyter_server_extension(nbapp):
from nbconvert.exporters.export import exporter_map
from .exporters import (
PresentExporter,
PDFPresentExporter,
)
from .exporters.html import PresentExporter

nbapp.log.info("Enabling nbpresent HTML export")
exporter_map.update(
nbpresent=PresentExporter,
)

try:
from .exporters.pdf import PDFPresentExporter
except Exception as err:
nbapp.log.warn(
"nbbrowserpdf not available, PDF generation disabled: {}"
.format(err)
)
return

nbapp.log.info("Enabling nbpresent PDF export")
exporter_map.update(
nbpresent_pdf=PDFPresentExporter
)
2 changes: 0 additions & 2 deletions nbpresent/exporters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,3 @@
ASSETS,
APP_ROOT
)
from .html import PresentExporter
from .pdf import PDFPresentExporter
63 changes: 7 additions & 56 deletions nbpresent/exporters/pdf.py
Original file line number Diff line number Diff line change
@@ -1,60 +1,11 @@
import os
import shutil
import sys
from subprocess import check_call

from ipython_genutils.tempdir import TemporaryWorkingDirectory
import nbformat

from .html import PresentExporter

from nbbrowserpdf.exporters.pdf import BrowserPDFExporter

class PDFPresentExporter(PresentExporter):
def __init__(self, *args, **kwargs):
super(PDFPresentExporter, self).__init__(*args, **kwargs)

def from_notebook_node(self, nb, resources=None, **kw):
output, resources = super(PDFPresentExporter, self).from_notebook_node(
nb, resources=resources, **kw
)

with TemporaryWorkingDirectory() as td:
for path, res in resources.get("outputs", {}).items():
dest = os.path.join(td, os.path.basename(path))
shutil.copyfile(path, dest)

index_html = os.path.join(td, "index.html")

with open(index_html, "w+") as fp:
fp.write(output)

ipynb = "notebook.ipynb"

with open(os.path.join(td, ipynb), "w") as fp:
nbformat.write(nb, fp)

self.log.info("Building PDF...")
check_call([
sys.executable,
"-m", "nbpresent.exporters.pdf_capture",
td
])

pdf_file = "notebook.pdf"

if not os.path.isfile(pdf_file):
raise IOError("PDF creating failed")

self.log.info("PDF successfully created")

with open(pdf_file, 'rb') as f:
pdf_data = f.read()

# convert output extension to pdf
# the writer above required it to be tex
resources['output_extension'] = '.pdf'
# clear figure outputs, extracted by latex export,
# so we don't claim to be a multi-file export.
resources.pop('outputs', None)

return pdf_data, resources
class PDFPresentExporter(PresentExporter, BrowserPDFExporter):
def pdf_capture_args(self):
return [
"--capture-server-class",
"nbpresent.exporters.pdf_capture:SlideCaptureServer"
]
156 changes: 47 additions & 109 deletions nbpresent/exporters/pdf_capture.py
Original file line number Diff line number Diff line change
@@ -1,145 +1,83 @@
from concurrent import futures
import os
import logging
import time
import sys

from ghost import Ghost
from ghost.bindings import (
# QApplication,
# QImage,
QPainter,
QPrinter,
QtWebKit,
QtCore,
)


import tornado.web
from tornado.httpserver import HTTPServer

from tornado.ioloop import IOLoop
from tornado.concurrent import run_on_executor

import nbformat

from PyPDF2 import (
PdfFileReader,
PdfFileWriter,
PdfFileMerger,
)

from .base import DEFAULT_STATIC_FILES_PATH
from nbbrowserpdf.exporters.pdf_capture import CaptureServer

VIEWPORT = (1920, 1080)

class CaptureServer(HTTPServer):
executor = futures.ThreadPoolExecutor(max_workers=1)

def __init__(self, *args, **kwargs):
super(CaptureServer, self).__init__(*args, **kwargs)

@run_on_executor
def capture(self):
# DO SOME MAGIC
ghost = Ghost(
log_level=logging.DEBUG
)
session = ghost.start(
class SlideCaptureServer(CaptureServer):
""" CaptureServer to generate multi-page PDF based on nbpresent metadata
"""
def init_session(self):
""" create a session with a some tweaked settings
"""
return self.ghost.start(
# display=True,
viewport_size=(1920, 1080),
# TODO: read this off config
viewport_size=VIEWPORT,
show_scrollbars=False,
)
merger = PdfFileMerger()
join = lambda *bits: os.path.join(self.static_path, *bits)

session.open("http://localhost:9999/index.html")
def page_ready(self):
""" Wait until nbpresent-css gets created
"""
self.session.wait_for_page_loaded()
self.session.wait_for_selector("#nbpresent-css")
time.sleep(1)

try:
session.wait_for_selector("#nbpresent-css")
time.sleep(1)
except Exception as err:
print(err)
def print_to_pdf(self, path):
""" Custom print based on metadata: generate one per slide
"""
merger = PdfFileMerger()

for i, slide in enumerate(self.notebook.metadata.nbpresent.slides):
print("\n\n\nprinting slide", i, slide)
filename = join("notebook-{0:04d}.pdf".format(i))
session.show()
screenshot(self.notebook, session, filename)
# science help you if you have 9999 slides
filename = self.in_static("notebook-{0:04d}.pdf".format(i))
# this is weird, but it seems to always need it
self.session.show()
self.screenshot(filename)
merger.append(PdfFileReader(filename, "rb"))
result, resources = session.evaluate(

# advance the slides
result, resources = self.session.evaluate(
"""
console.log(window.nbpresent);
console.log(window.nbpresent.mode.presenter.speaker.advance());
""")
time.sleep(1)

merger.write(join("notebook-unmeta.pdf"))

unmeta = PdfFileReader(join("notebook-unmeta.pdf"), "rb")

meta = PdfFileWriter()
meta.appendPagesFromReader(unmeta)

ipynb = "notebook.ipynb"
# always seem to get some weirdness... perhaps could inttegrate
# ioloop...
time.sleep(1)

with open(join(ipynb), "rb") as fp:
meta.addAttachment(ipynb, fp.read())
# all done!
merger.write(path)

with open(join("notebook.pdf"), "wb") as fp:
meta.write(fp)
def screenshot(self, filename):
""" do an individual slide screenshot
big thanks to https://gist.github.com/jmaupetit/4217925
"""

raise KeyboardInterrupt()
printer = QPrinter(mode=QPrinter.ScreenResolution)
printer.setOutputFormat(QPrinter.PdfFormat)
printer.setPaperSize(QtCore.QSizeF(*reversed(VIEWPORT)),
QPrinter.DevicePixel)
printer.setOrientation(QPrinter.Landscape)
printer.setOutputFileName(filename)
printer.setPageMargins(0, 0, 0, 0, QPrinter.DevicePixel)

def screenshot(nb, session, dest, as_print=False):
"""
big thanks to https://gist.github.com/jmaupetit/4217925
"""

printer = QPrinter(mode=QPrinter.ScreenResolution)
printer.setOutputFormat(QPrinter.PdfFormat)
printer.setPaperSize(QtCore.QSizeF(1080, 1920), QPrinter.DevicePixel)
printer.setOrientation(QPrinter.Landscape)
printer.setOutputFileName(dest)
printer.setPageMargins(0, 0, 0, 0, QPrinter.DevicePixel)

if as_print:
webview = QtWebKit.QWebView()
webview.setPage(session.page)
webview.print_(printer)
else:
painter = QPainter(printer)
painter.scale(1.45, 1.45)
session.main_frame.render(painter)
self.session.main_frame.render(painter)
painter.end()


def pdf_capture(static_path):
settings = {
"static_path": static_path
}

app = tornado.web.Application([
(r"/components/(.*)", tornado.web.StaticFileHandler, {
"path": os.path.join(DEFAULT_STATIC_FILES_PATH, "components")
}),
(r"/(.*)", tornado.web.StaticFileHandler, {
"path": settings['static_path']
}),
], **settings)

server = CaptureServer(app)
server.static_path = static_path

with open(os.path.join(static_path, "notebook.ipynb")) as fp:
server.notebook = nbformat.read(fp, 4)

ioloop = IOLoop()
ioloop.add_callback(server.capture)
server.listen(9999)

try:
ioloop.start()
except KeyboardInterrupt:
print("stopped")

if __name__ == "__main__":
pdf_capture(sys.argv[1])
50 changes: 40 additions & 10 deletions nbpresent/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@

import argparse
import os
from os.path import dirname, abspath, join, exists
from os.path import (
abspath,
dirname,
exists,
join,
)
from pprint import pprint
try:
from inspect import signature
except ImportError:
from funcsigs import signature

from jupyter_core.paths import jupyter_config_dir


def install(enable=False, **kwargs):
"""Install the nbpresent nbextension assets and optionally enables the
Expand All @@ -34,19 +42,41 @@ def install(enable=False, **kwargs):
if "prefix" in kwargs:
path = join(kwargs["prefix"], "etc", "jupyter")
if not exists(path):
print("Making directory", path)
os.makedirs(path)

cm = ConfigManager(config_dir=path)
print("Enabling for", cm.config_dir)
print("Enabling nbpresent server component...")
print("Enabling nbpresent server component in", cm.config_dir)
cfg = cm.get("jupyter_notebook_config")
print("Existing config...")
pprint(cfg)
server_extensions = (
cfg.setdefault("NotebookApp", {})
.setdefault("server_extensions", [])
)
if "nbpresent" not in server_extensions:
cfg["NotebookApp"]["server_extensions"] += ["nbpresent"]

cm.update("jupyter_notebook_config", cfg)
print("New config...")
pprint(cm.get("jupyter_notebook_config"))

cm = ConfigManager(config_dir=join(jupyter_config_dir(), "nbconfig"))
print(
"Enabling nbpresent nbextension at notebook launch in",
cm.config_dir
)

if not exists(cm.config_dir):
print("Making directory", cm.config_dir)
os.makedirs(cm.config_dir)


cm.update(
"jupyter_notebook_config", {
"notebook": {
"load_extensions": {"nbpresent/nbpresent.min": True}
"notebook", {
"load_extensions": {
"nbpresent/nbpresent.min": True
},
"NotebookApp": {
"server_extensions": ["nbpresent"]
}
}
)

Expand All @@ -60,7 +90,7 @@ def install(enable=False, **kwargs):
description="Installs nbpresent nbextension")
parser.add_argument(
"-e", "--enable",
help="Automatically load extension on notebook launch",
help="Automatically load server and nbextension on notebook launch",
action="store_true")

default_kwargs = dict(
Expand Down
2 changes: 1 addition & 1 deletion nbpresent/present/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import sys

from ..exporters import (
PresentExporter,
APP_ROOT
)
from ..exporters.html import PresentExporter


def main(nb):
Expand Down
Loading

0 comments on commit bd79f8d

Please sign in to comment.