239 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			239 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/env python
 | |
| # -*- coding: utf-8 -*-
 | |
| # vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
 | |
| 
 | |
| from __future__ import unicode_literals, division, absolute_import, print_function
 | |
| 
 | |
| from .compatibility_utils import unicode_str
 | |
| 
 | |
| from .unipath import pathof
 | |
| import os
 | |
| import imghdr
 | |
| 
 | |
| import struct
 | |
| # note:  struct pack, unpack, unpack_from all require bytestring format
 | |
| # data all the way up to at least python 2.7.5, python 3 okay with bytestring
 | |
| 
 | |
| USE_SVG_WRAPPER = True
 | |
| """ Set to True to use svg wrapper for default. """
 | |
| 
 | |
| FORCE_DEFAULT_TITLE = False
 | |
| """ Set to True to force to use the default title. """
 | |
| 
 | |
| COVER_PAGE_FINENAME = 'cover_page.xhtml'
 | |
| """ The name for the cover page. """
 | |
| 
 | |
| DEFAULT_TITLE = 'Cover'
 | |
| """ The default title for the cover page. """
 | |
| 
 | |
| MAX_WIDTH = 4096
 | |
| """ The max width for the svg cover page. """
 | |
| 
 | |
| MAX_HEIGHT = 4096
 | |
| """ The max height for the svg cover page. """
 | |
| 
 | |
| 
 | |
| def get_image_type(imgname, imgdata=None):
 | |
|     imgtype = unicode_str(imghdr.what(pathof(imgname), imgdata))
 | |
| 
 | |
|     # imghdr only checks for JFIF or Exif JPEG files. Apparently, there are some
 | |
|     # with only the magic JPEG bytes out there...
 | |
|     # ImageMagick handles those, so, do it too.
 | |
|     if imgtype is None:
 | |
|         if imgdata is None:
 | |
|             with open(pathof(imgname), 'rb') as f:
 | |
|                 imgdata = f.read()
 | |
|         if imgdata[0:2] == b'\xFF\xD8':
 | |
|             # Get last non-null bytes
 | |
|             last = len(imgdata)
 | |
|             while (imgdata[last-1:last] == b'\x00'):
 | |
|                 last-=1
 | |
|             # Be extra safe, check the trailing bytes, too.
 | |
|             if imgdata[last-2:last] == b'\xFF\xD9':
 | |
|                 imgtype = "jpeg"
 | |
|     return imgtype
 | |
| 
 | |
| 
 | |
| def get_image_size(imgname, imgdata=None):
 | |
|     '''Determine the image type of imgname (or imgdata) and return its size.
 | |
| 
 | |
|     Originally,
 | |
|     Determine the image type of fhandle and return its size.
 | |
|     from draco'''
 | |
|     if imgdata is None:
 | |
|         fhandle = open(pathof(imgname), 'rb')
 | |
|         head = fhandle.read(24)
 | |
|     else:
 | |
|         head = imgdata[0:24]
 | |
|     if len(head) != 24:
 | |
|         return
 | |
| 
 | |
|     imgtype = get_image_type(imgname, imgdata)
 | |
|     if imgtype == 'png':
 | |
|         check = struct.unpack(b'>i', head[4:8])[0]
 | |
|         if check != 0x0d0a1a0a:
 | |
|             return
 | |
|         width, height = struct.unpack(b'>ii', head[16:24])
 | |
|     elif imgtype == 'gif':
 | |
|         width, height = struct.unpack(b'<HH', head[6:10])
 | |
|     elif imgtype == 'jpeg' and imgdata is None:
 | |
|         try:
 | |
|             fhandle.seek(0)  # Read 0xff next
 | |
|             size = 2
 | |
|             ftype = 0
 | |
|             while not 0xc0 <= ftype <= 0xcf:
 | |
|                 fhandle.seek(size, 1)
 | |
|                 byte = fhandle.read(1)
 | |
|                 while ord(byte) == 0xff:
 | |
|                     byte = fhandle.read(1)
 | |
|                 ftype = ord(byte)
 | |
|                 size = struct.unpack(b'>H', fhandle.read(2))[0] - 2
 | |
|             # We are at a SOFn block
 | |
|             fhandle.seek(1, 1)  # Skip `precision' byte.
 | |
|             height, width = struct.unpack(b'>HH', fhandle.read(4))
 | |
|         except Exception:  # IGNORE:W0703
 | |
|             return
 | |
|     elif imgtype == 'jpeg' and imgdata is not None:
 | |
|         try:
 | |
|             pos = 0
 | |
|             size = 2
 | |
|             ftype = 0
 | |
|             while not 0xc0 <= ftype <= 0xcf:
 | |
|                 pos += size
 | |
|                 byte = imgdata[pos:pos+1]
 | |
|                 pos += 1
 | |
|                 while ord(byte) == 0xff:
 | |
|                     byte = imgdata[pos:pos+1]
 | |
|                     pos += 1
 | |
|                 ftype = ord(byte)
 | |
|                 size = struct.unpack(b'>H', imgdata[pos:pos+2])[0] - 2
 | |
|                 pos += 2
 | |
|             # We are at a SOFn block
 | |
|             pos += 1  # Skip `precision' byte.
 | |
|             height, width = struct.unpack(b'>HH', imgdata[pos:pos+4])
 | |
|             pos += 4
 | |
|         except Exception:  # IGNORE:W0703
 | |
|             return
 | |
|     else:
 | |
|         return
 | |
|     return width, height
 | |
| 
 | |
| # XXX experimental
 | |
| class CoverProcessor(object):
 | |
| 
 | |
|     """Create a cover page.
 | |
| 
 | |
|     """
 | |
|     def __init__(self, files, metadata, rscnames, imgname=None, imgdata=None):
 | |
|         self.files = files
 | |
|         self.metadata = metadata
 | |
|         self.rscnames = rscnames
 | |
|         self.cover_page = COVER_PAGE_FINENAME
 | |
|         self.use_svg = USE_SVG_WRAPPER  # Use svg wrapper.
 | |
|         self.lang = metadata.get('Language', ['en'])[0]
 | |
|         # This should ensure that if the methods to find the cover image's
 | |
|         # dimensions should fail for any reason, the SVG routine will not be used.
 | |
|         [self.width, self.height] = (-1,-1)
 | |
|         if FORCE_DEFAULT_TITLE:
 | |
|             self.title = DEFAULT_TITLE
 | |
|         else:
 | |
|             self.title = metadata.get('Title', [DEFAULT_TITLE])[0]
 | |
| 
 | |
|         self.cover_image = None
 | |
|         if imgname is not None:
 | |
|             self.cover_image = imgname
 | |
|         elif 'CoverOffset' in metadata:
 | |
|             imageNumber = int(metadata['CoverOffset'][0])
 | |
|             cover_image = self.rscnames[imageNumber]
 | |
|             if cover_image is not None:
 | |
|                 self.cover_image = cover_image
 | |
|             else:
 | |
|                 print('Warning: Cannot identify the cover image.')
 | |
|         if self.use_svg:
 | |
|             try:
 | |
|                 if imgdata is None:
 | |
|                     fname = os.path.join(files.imgdir, self.cover_image)
 | |
|                     [self.width, self.height] = get_image_size(fname)
 | |
|                 else:
 | |
|                     [self.width, self.height] = get_image_size(None, imgdata)
 | |
|             except:
 | |
|                 self.use_svg = False
 | |
|             width = self.width
 | |
|             height = self.height
 | |
|             if width < 0 or height < 0 or width > MAX_WIDTH or height > MAX_HEIGHT:
 | |
|                 self.use_svg = False
 | |
|         return
 | |
| 
 | |
|     def getImageName(self):
 | |
|         return self.cover_image
 | |
| 
 | |
|     def getXHTMLName(self):
 | |
|         return self.cover_page
 | |
| 
 | |
|     def buildXHTML(self):
 | |
|         print('Building a cover page.')
 | |
|         files = self.files
 | |
|         cover_image = self.cover_image
 | |
|         title = self.title
 | |
|         lang = self.lang
 | |
| 
 | |
|         image_dir = os.path.normpath(os.path.relpath(files.k8images, files.k8text))
 | |
|         image_path = os.path.join(image_dir, cover_image).replace('\\', '/')
 | |
| 
 | |
|         if not self.use_svg:
 | |
|             data = ''
 | |
|             data += '<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html>'
 | |
|             data += '<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops"'
 | |
|             data += ' xml:lang="{:s}">\n'.format(lang)
 | |
|             data += '<head>\n<title>{:s}</title>\n'.format(title)
 | |
|             data += '<style type="text/css">\n'
 | |
|             data += 'body {\n  margin: 0;\n  padding: 0;\n  text-align: center;\n}\n'
 | |
|             data += 'div {\n  height: 100%;\n  width: 100%;\n  text-align: center;\n  page-break-inside: avoid;\n}\n'
 | |
|             data += 'img {\n  display: inline-block;\n  height: 100%;\n  margin: 0 auto;\n}\n'
 | |
|             data += '</style>\n</head>\n'
 | |
|             data += '<body><div>\n'
 | |
|             data += '  <img src="{:s}" alt=""/>\n'.format(image_path)
 | |
|             data += '</div></body>\n</html>'
 | |
|         else:
 | |
|             width = self.width
 | |
|             height = self.height
 | |
|             viewBox = "0 0 {0:d} {1:d}".format(width, height)
 | |
| 
 | |
|             data = ''
 | |
|             data += '<?xml version="1.0" encoding="utf-8"?><!DOCTYPE html>'
 | |
|             data += '<html xmlns="http://www.w3.org/1999/xhtml"'
 | |
|             data += ' xml:lang="{:s}">\n'.format(lang)
 | |
|             data += '<head>\n  <title>{:s}</title>\n'.format(title)
 | |
|             data += '<style type="text/css">\n'
 | |
|             data += 'svg {padding: 0pt; margin:0pt}\n'
 | |
|             data += 'body { text-align: center; padding:0pt; margin: 0pt; }\n'
 | |
|             data += '</style>\n</head>\n'
 | |
|             data += '<body>\n  <div>\n'
 | |
|             data += '    <svg xmlns="http://www.w3.org/2000/svg" height="100%" preserveAspectRatio="xMidYMid meet"'
 | |
|             data += ' version="1.1" viewBox="{0:s}" width="100%" xmlns:xlink="http://www.w3.org/1999/xlink">\n'.format(viewBox)
 | |
|             data += '      <image height="{0}" width="{1}" xlink:href="{2}"/>\n'.format(height, width, image_path)
 | |
|             data += '    </svg>\n'
 | |
|             data += '  </div>\n</body>\n</html>'
 | |
|         return data
 | |
| 
 | |
|     def writeXHTML(self):
 | |
|         files = self.files
 | |
|         cover_page = self.cover_page
 | |
| 
 | |
|         data = self.buildXHTML()
 | |
| 
 | |
|         outfile = os.path.join(files.k8text, cover_page)
 | |
|         if os.path.exists(pathof(outfile)):
 | |
|             print('Warning: {:s} already exists.'.format(cover_page))
 | |
|             os.remove(pathof(outfile))
 | |
|         with open(pathof(outfile), 'wb') as f:
 | |
|             f.write(data.encode('utf-8'))
 | |
|         return
 | |
| 
 | |
|     def guide_toxml(self):
 | |
|         files = self.files
 | |
|         text_dir = os.path.relpath(files.k8text, files.k8oebps)
 | |
|         data = '<reference type="cover" title="Cover" href="{:s}/{:s}" />\n'.format(
 | |
|                 text_dir, self.cover_page)
 | |
|         return data
 |