1#!/usr/local/bin/python3.8 2# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai 3 4 5__license__ = 'GPL v3' 6__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>' 7__docformat__ = 'restructuredtext en' 8 9import os 10 11from calibre.ebooks.pdf.render.common import Array, Name, Dictionary, String, UTF16String, current_log 12from polyglot.builtins import iteritems 13from polyglot.urllib import unquote, urlparse 14 15 16class Destination(Array): 17 18 def __init__(self, start_page, pos, get_pageref): 19 pnum = start_page + max(0, pos['column']) 20 q = pnum 21 while q > -1: 22 try: 23 pref = get_pageref(q) 24 break 25 except IndexError: 26 pos['left'] = pos['top'] = 0 27 q -= 1 28 if q != pnum: 29 current_log().warn('Could not find page {} for link destination, using page {} instead'.format(pnum, q)) 30 super().__init__([ 31 pref, Name('XYZ'), pos['left'], pos['top'], None 32 ]) 33 34 35class Links: 36 37 def __init__(self, pdf, mark_links, page_size): 38 self.anchors = {} 39 self.links = [] 40 self.start = {'top':page_size[1], 'column':0, 'left':0} 41 self.pdf = pdf 42 self.mark_links = mark_links 43 44 def add(self, base_path, start_page, links, anchors): 45 path = os.path.normcase(os.path.abspath(base_path)) 46 self.anchors[path] = a = {} 47 a[None] = Destination(start_page, self.start, self.pdf.get_pageref) 48 for anchor, pos in iteritems(anchors): 49 a[anchor] = Destination(start_page, pos, self.pdf.get_pageref) 50 for link in links: 51 href, page, rect = link 52 p, frag = href.partition('#')[0::2] 53 try: 54 pref = self.pdf.get_pageref(page).obj 55 except IndexError: 56 try: 57 pref = self.pdf.get_pageref(page-1).obj 58 except IndexError: 59 self.pdf.debug('Unable to find page for link: %r, ignoring it' % link) 60 continue 61 self.pdf.debug('The link %s points to non-existent page, moving it one page back' % href) 62 self.links.append(((path, p, frag or None), pref, Array(rect))) 63 64 def add_links(self): 65 for link in self.links: 66 path, href, frag = link[0] 67 page, rect = link[1:] 68 combined_path = os.path.normcase(os.path.abspath(os.path.join(os.path.dirname(path), *unquote(href).split('/')))) 69 is_local = not href or combined_path in self.anchors 70 annot = Dictionary({ 71 'Type':Name('Annot'), 'Subtype':Name('Link'), 72 'Rect':rect, 'Border':Array([0,0,0]), 73 }) 74 if self.mark_links: 75 annot.update({'Border':Array([16, 16, 1]), 'C':Array([1.0, 0, 76 0])}) 77 if is_local: 78 path = combined_path if href else path 79 try: 80 annot['Dest'] = self.anchors[path][frag] 81 except KeyError: 82 try: 83 annot['Dest'] = self.anchors[path][None] 84 except KeyError: 85 pass 86 else: 87 url = href + (('#'+frag) if frag else '') 88 try: 89 purl = urlparse(url) 90 except Exception: 91 self.pdf.debug('Ignoring unparsable URL: %r' % url) 92 continue 93 if purl.scheme and purl.scheme != 'file': 94 action = Dictionary({ 95 'Type':Name('Action'), 'S':Name('URI'), 96 }) 97 # Do not try to normalize/quote/unquote this URL as if it 98 # has a query part, it will get corrupted 99 action['URI'] = String(url) 100 annot['A'] = action 101 if 'A' in annot or 'Dest' in annot: 102 if 'Annots' not in page: 103 page['Annots'] = Array() 104 page['Annots'].append(self.pdf.objects.add(annot)) 105 else: 106 self.pdf.debug('Could not find destination for link: %s in file %s'% 107 (href, path)) 108 109 def add_outline(self, toc): 110 parent = Dictionary({'Type':Name('Outlines')}) 111 parentref = self.pdf.objects.add(parent) 112 self.process_children(toc, parentref, parent_is_root=True) 113 self.pdf.catalog.obj['Outlines'] = parentref 114 115 def process_children(self, toc, parentref, parent_is_root=False): 116 childrefs = [] 117 for child in toc: 118 childref = self.process_toc_item(child, parentref) 119 if childref is None: 120 continue 121 if childrefs: 122 childrefs[-1].obj['Next'] = childref 123 childref.obj['Prev'] = childrefs[-1] 124 childrefs.append(childref) 125 126 if len(child) > 0: 127 self.process_children(child, childref) 128 if childrefs: 129 parentref.obj['First'] = childrefs[0] 130 parentref.obj['Last'] = childrefs[-1] 131 if not parent_is_root: 132 parentref.obj['Count'] = -len(childrefs) 133 134 def process_toc_item(self, toc, parentref): 135 path = toc.abspath or None 136 frag = toc.fragment or None 137 if path is None: 138 return 139 path = os.path.normcase(os.path.abspath(path)) 140 if path not in self.anchors: 141 return None 142 a = self.anchors[path] 143 dest = a.get(frag, a[None]) 144 item = Dictionary({'Parent':parentref, 'Dest':dest, 145 'Title':UTF16String(toc.text or _('Unknown'))}) 146 return self.pdf.objects.add(item) 147