"""
Importer for Quill Archives
"""
import tarfile
import struct
import os
from base import ImporterBase, QuillImporterError
[docs]class QuillPage(object):
# Purely arbitrary
# A line-width of 0.003 gives a good visual approximation to Quill's pen thickness of 5
pen_scale_factor = float(5.0/0.003)
def __init__(self, page_file=None, page_number=1):
self.page_number = page_number
fp = self.fp = page_file
self.version = struct.unpack(">i", fp.read(4))
if self.version != (6,):
raise QuillImporterError('wrong page version')
nbytes = struct.unpack(">h", fp.read(2))
self.uuid = fp.read(36)
self.tsversion = struct.unpack(">i", fp.read(4))
if self.tsversion != (1,):
raise QuillImporterError('wrong tag set version')
self.ntags = struct.unpack(">i", fp.read(4))
# If we have tags
self.tags = []
for x in xrange(self.ntags[0]):
self.tversion = struct.unpack(">i", fp.read(4))
if self.tversion != (1,):
raise QuillImporterError('wrong tag version')
nbytes = struct.unpack(">h", fp.read(2))
tag = {}
tag["tag"] = fp.read(nbytes[0]).decode("utf-8")
tag["autogenerated"] = struct.unpack(">?", fp.read(1))[0]
tag["ctime"] = struct.unpack(">q", fp.read(8))[0]
dummy = struct.unpack(">q", fp.read(8))
self.tags.append(tag)
foo = struct.unpack(">i", fp.read(4))
foo = struct.unpack(">i", fp.read(4))
self.paper_type = struct.unpack(">i", fp.read(4))
self.nimages = struct.unpack(">i", fp.read(4))
self.images = [ self.read_image() for i in xrange(self.nimages[0]) ]
dummy = struct.unpack(">i", fp.read(4))
if dummy != (0,):
raise QuillImporterError('out of sync')
self.read_only = struct.unpack(">?", fp.read(1))
self.aspect_ratio = struct.unpack(">f", fp.read(4))[0]
self.nstrokes = struct.unpack(">i", fp.read(4))
self.strokes = [ self.read_stroke() for i in xrange(self.nstrokes[0]) ]
self.nlines = struct.unpack(">i", fp.read(4))
# dummy = struct.unpack(">i", fp.read(4))
# self.ntext = struct.unpack(">i", fp.read(4))
[docs] def read_image(self):
fp = self.fp
version = struct.unpack(">i", fp.read(4))
if version != (1,):
raise QuillImporterError('wrong image version')
uuid_nbytes = struct.unpack(">h", fp.read(2))
uuid = fp.read(36)
top_left = struct.unpack(">f", fp.read(4))
top_right = struct.unpack(">f", fp.read(4))
bottom_left = struct.unpack(">f", fp.read(4))
bottom_right = struct.unpack(">f", fp.read(4))
constrain_aspect = struct.unpack(">?", fp.read(1))
from quill.image import Image
return Image(uuid,
top_left[0], top_right[0], bottom_left[0], bottom_right[0],
constrain_aspect[0])
[docs] def read_stroke(self):
fp = self.fp
version = struct.unpack(">i", fp.read(4))
if version != (2,):
raise QuillImporterError('wrong stroke version')
pen_color = struct.unpack(">i", fp.read(4))
red = (pen_color[0] >> 16) & 0xFF
green = (pen_color[0] >> 8) & 0xFF
blue = pen_color[0] & 0xFF
thickness = struct.unpack(">i", fp.read(4))
toolint = struct.unpack(">i", fp.read(4))
fountain_pen = (toolint == (0,))
N = struct.unpack(">i", fp.read(4))
points = []
for i in xrange(N[0]):
x = struct.unpack(">f", fp.read(4))
y = struct.unpack(">f", fp.read(4))
p = struct.unpack(">f", fp.read(4))
points.append((x[0], y[0], p[0]))
from quill.stroke import Stroke
return Stroke(fountain_pen, red, green, blue, points)
def _draw(self, cairo_context):
cr = cairo_context
cr.set_source_rgb(0, 0, 0)
cr.set_line_cap(cairo.LINE_CAP_ROUND)
cr.set_line_join(cairo.LINE_JOIN_ROUND)
# cr.translate(.010, .010)
cr.set_line_width(0.003)
cr.scale(self.height, self.height)
fp = self.fp
for stroke in xrange(self.nstrokes[0]):
sversion = struct.unpack(">i", fp.read(4))
pen_color = struct.unpack(">i", fp.read(4))
red = (pen_color[0] >> 16) & 0xFF
green = (pen_color[0] >> 8) & 0xFF
blue = pen_color[0] & 0xFF
cr.set_source_rgb(float(red/255.0), float(green/255.0), float(blue/255.0))
pen_thickness = struct.unpack(">i", fp.read(4))
pen_thickness_factor = float(pen_thickness[0])/self.pen_scale_factor
toolint = struct.unpack(">i", fp.read(4))
fountain_pen = (toolint == (0,))
N = struct.unpack(">i", fp.read(4))
points = []
for instance in xrange(N[0]):
x = struct.unpack(">f", fp.read(4))
y = struct.unpack(">f", fp.read(4))
p = struct.unpack(">f", fp.read(4))
points.append((x[0], y[0], p[0]))
if fountain_pen:
# width changes, each segment is its own stroke
for point in xrange(len(points) - 1):
cr.set_line_width(pen_thickness_factor * ((points[point][2] + points[point+1][2])/2))
cr.move_to(points[point][0], points[point][1])
cr.line_to(points[point + 1][0], points[point + 1][1])
cr.stroke()
else:
# constant width, join all segment into one stroke
cr.set_line_width(pen_thickness_factor)
for point in xrange(len(points) - 1):
cr.move_to(points[point][0], points[point][1])
cr.line_to(points[point + 1][0], points[point + 1][1])
cr.stroke()
self.nlines = struct.unpack(">i", fp.read(4))
dummy = struct.unpack(">i", fp.read(4))
self.ntext = struct.unpack(">i", fp.read(4))
[docs]class QuillIndex(object):
def __init__(self, index_file):
fp = index_file
self.version = struct.unpack(">i", fp.read(4))
if self.version != (4,):
raise QuillImporterError('wrong page version')
self.npages = struct.unpack(">i", fp.read(4))
self.page_uuids = []
for x in xrange(self.npages[0]):
nbytes = struct.unpack(">h", fp.read(2))
u = fp.read(36)
self.page_uuids.append(u)
self.currentPage = struct.unpack(">i", fp.read(4))
nbytes = struct.unpack(">h", fp.read(2))
self.title = fp.read(nbytes[0])
self.ctime = struct.unpack(">q", fp.read(8))
self.mtime = struct.unpack(">q", fp.read(8))
nbytes = struct.unpack(">h", fp.read(2))
self.uuid = fp.read(36)
def __repr__(self):
s = 'File version: '+str(self.version)+'\n'
s += 'Title: ' + self.title + '\n'
s += 'ctime: ' + str(self.ctime) + '\n'
s += 'mtime: ' + str(self.mtime) + '\n'
for i,p in enumerate(self.page_uuids):
s += 'page ' + str(i) + ': ' + p + '\n'
return s
[docs]class QuillImporter(ImporterBase):
def __init__(self, quill_filename):
self._filename = quill_filename
with tarfile.open(self._filename, "r") as t:
self._open_quill_archive(t)
def _open_quill_archive(self, t):
index_files = [ t.extractfile(f) for f in t.getmembers()
if f.name.endswith('index.quill_data') ]
if len(index_files) != 1:
raise QuillImporterError('Not a Quill file')
self._index = q = QuillIndex(index_files[0])
notebook_dir = os.path.split(index_files[0].name)[0]
self._page_filenames = [notebook_dir+'/page_'+page_uuid+'.quill_data'
for page_uuid in q.page_uuids ]
[docs] def uuid(self):
return self._index.uuid
[docs] def title(self):
return self._index.title
[docs] def mtime_millis(self):
return self._index.mtime[0]
[docs] def ctime_millis(self):
return self._index.ctime[0]
[docs] def n_pages(self):
return self._index.npages[0]
[docs] def get_page(self, n):
"""
Return the n-th page.
"""
page_filename = self._page_filenames[n]
with tarfile.open(self._filename, "r") as t:
page_file = t.extractfile(page_filename)
qp = QuillPage(page_file, n)
from quill.page import Page
return Page(n, qp.uuid, qp.aspect_ratio, qp.strokes, qp.images)