def __init__(self, page_size): super().__init__({ 'Type': Name('Pages'), 'MediaBox': Array([0, 0, page_size[0], page_size[1]]), 'Kids': Array(), 'Count': 0, })
def pdf_serialize(self, stream, font_name): if not self.text: return stream.write_line('BT ') serialize(Name(font_name), stream) stream.write(' %g Tf '%self.size) stream.write(' '.join(map(type(u''), self.transform)) + ' Tm ') if self.horizontal_scale != self.default_horizontal_scale: stream.write('%g Tz '%self.horizontal_scale) if self.word_spacing != self.default_word_spacing: stream.write('%g Tw '%self.word_spacing) if self.char_space != self.default_char_space: stream.write('%g Tc '%self.char_space) stream.write_line() if self.glyph_adjust is self.default_glyph_adjust: serialize(String(self.text), stream) stream.write(' Tj ') else: chars = Array() frac, widths = self.glyph_adjust for c, width in izip(self.text, widths): chars.append(String(c)) chars.append(int(width * frac)) serialize(chars, stream) stream.write(' TJ ') stream.write_line('ET')
def add_extra_keys(self, d): d['Type'] = Name('Pattern') d['PatternType'] = 1 d['PaintType'] = self.paint_type d['TilingType'] = 1 d['BBox'] = Array([0, 0, self.w, self.h]) d['XStep'] = self.w d['YStep'] = self.h d['Matrix'] = Array(self.matrix) d['Resources'] = self.resources
def apply_stroke(self, state, pdf_system, painter): # TODO: Support miter limit by using QPainterPathStroker pen = state.stroke self.pending_state.do_stroke = True pdf = self.pdf # Width w = pen.widthF() if pen.isCosmetic(): t = painter.transform() try: w /= sqrt(t.m11()**2 + t.m22()**2) except ZeroDivisionError: pass pdf.serialize(w) pdf.current_page.write(' w ') # Line cap cap = { Qt.FlatCap: 0, Qt.RoundCap: 1, Qt.SquareCap: 2 }.get(pen.capStyle(), 0) pdf.current_page.write('%d J ' % cap) # Line join join = { Qt.MiterJoin: 0, Qt.RoundJoin: 1, Qt.BevelJoin: 2 }.get(pen.joinStyle(), 0) pdf.current_page.write('%d j ' % join) # Dash pattern if pen.style() == Qt.CustomDashLine: pdf.serialize(Array(pen.dashPattern())) pdf.current_page.write(' %d d ' % pen.dashOffset()) else: ps = { Qt.DashLine: [3], Qt.DotLine: [1, 2], Qt.DashDotLine: [3, 2, 1, 2], Qt.DashDotDotLine: [3, 2, 1, 2, 1, 2] }.get(pen.style(), []) pdf.serialize(Array(ps)) pdf.current_page.write(' 0 d ') # Stroke fill color, opacity, pattern, self.pending_state.do_stroke = self.convert_brush( pen.brush(), state.brush_origin, state.opacity, pdf_system, painter.transform()) self.pdf.apply_stroke(color, pattern, opacity) if pen.style() == Qt.NoPen: self.pending_state.do_stroke = False
def add_links(self): for link in self.links: path, href, frag = link[0] page, rect = link[1:] combined_path = os.path.normcase( os.path.abspath( os.path.join(os.path.dirname(path), *unquote(href).split('/')))) is_local = not href or combined_path in self.anchors annot = Dictionary({ 'Type': Name('Annot'), 'Subtype': Name('Link'), 'Rect': rect, 'Border': Array([0, 0, 0]), }) if self.mark_links: annot.update({ 'Border': Array([16, 16, 1]), 'C': Array([1.0, 0, 0]) }) if is_local: path = combined_path if href else path try: annot['Dest'] = self.anchors[path][frag] except KeyError: try: annot['Dest'] = self.anchors[path][None] except KeyError: pass else: url = href + (('#' + frag) if frag else '') try: purl = urlparse(url) except Exception: self.pdf.debug('Ignoring unparseable URL: %r' % url) continue if purl.scheme and purl.scheme != 'file': action = Dictionary({ 'Type': Name('Action'), 'S': Name('URI'), }) # Do not try to normalize/quote/unquote this URL as if it # has a query part, it will get corrupted action['URI'] = String(url) annot['A'] = action if 'A' in annot or 'Dest' in annot: if 'Annots' not in page: page['Annots'] = Array() page['Annots'].append(self.pdf.objects.add(annot)) else: self.pdf.debug( 'Could not find destination for link: %s in file %s' % (href, path))
def __init__(self, brush, matrix, pdf, pixel_page_width, pixel_page_height): self.matrix = (matrix.m11(), matrix.m12(), matrix.m21(), matrix.m22(), matrix.dx(), matrix.dy()) gradient = sip.cast(brush.gradient(), QLinearGradient) start, stop, stops = self.spread_gradient(gradient, pixel_page_width, pixel_page_height, matrix) # TODO: Handle colors with different opacities self.const_opacity = stops[0].color[-1] funcs = Array() bounds = Array() encode = Array() for i, current_stop in enumerate(stops): if i < len(stops) - 1: next_stop = stops[i+1] func = Dictionary({ 'FunctionType': 2, 'Domain': Array([0, 1]), 'C0': Array(current_stop.color[:3]), 'C1': Array(next_stop.color[:3]), 'N': 1, }) funcs.append(func) encode.extend((0, 1)) if i+1 < len(stops) - 1: bounds.append(next_stop.t) func = Dictionary({ 'FunctionType': 3, 'Domain': Array([stops[0].t, stops[-1].t]), 'Functions': funcs, 'Bounds': bounds, 'Encode': encode, }) shader = Dictionary({ 'ShadingType': 2, 'ColorSpace': Name('DeviceRGB'), 'AntiAlias': True, 'Coords': Array([start.x(), start.y(), stop.x(), stop.y()]), 'Function': func, 'Extend': Array([True, True]), }) Dictionary.__init__(self, { 'Type': Name('Pattern'), 'PatternType': 2, 'Shading': shader, 'Matrix': Array(self.matrix), }) self.cache_key = (self.__class__.__name__, self.matrix, tuple(shader['Coords']), stops)
def add_links(self): for link in self.links: path, href, frag = link[0] page, rect = link[1:] combined_path = os.path.abspath( os.path.join(os.path.dirname(path), *unquote(href).split('/'))) is_local = not href or combined_path in self.anchors annot = Dictionary({ 'Type': Name('Annot'), 'Subtype': Name('Link'), 'Rect': rect, 'Border': Array([0, 0, 0]), }) if self.mark_links: annot.update({ 'Border': Array([16, 16, 1]), 'C': Array([1.0, 0, 0]) }) if is_local: path = combined_path if href else path try: annot['Dest'] = self.anchors[path][frag] except KeyError: try: annot['Dest'] = self.anchors[path][None] except KeyError: pass else: url = href + (('#' + frag) if frag else '') purl = urlparse(url) if purl.scheme and purl.scheme != 'file': action = Dictionary({ 'Type': Name('Action'), 'S': Name('URI'), }) parts = (x.encode('utf-8') if isinstance(x, type(u'')) else x for x in purl) url = urlunparse(map(quote, map(unquote, parts))).decode('ascii') action['URI'] = String(url) annot['A'] = action if 'A' in annot or 'Dest' in annot: if 'Annots' not in page: page['Annots'] = Array() page['Annots'].append(self.pdf.objects.add(annot)) else: self.pdf.debug( 'Could not find destination for link: %s in file %s' % (href, path))
def add_resources(self): r = Dictionary() if self.opacities: extgs = Dictionary() for opref, name in iteritems(self.opacities): extgs[name] = opref r['ExtGState'] = extgs if self.fonts: fonts = Dictionary() for ref, name in iteritems(self.fonts): fonts[name] = ref r['Font'] = fonts if self.xobjects: xobjects = Dictionary() for ref, name in iteritems(self.xobjects): xobjects[name] = ref r['XObject'] = xobjects if self.patterns: r['ColorSpace'] = Dictionary( {'PCSp': Array([Name('Pattern'), Name('DeviceRGB')])}) patterns = Dictionary() for ref, name in iteritems(self.patterns): patterns[name] = ref r['Pattern'] = patterns if r: self.page_dict['Resources'] = r
def __init__(self, metrics, num, objects, compress): self.metrics, self.compress = metrics, compress self.is_otf = self.metrics.is_otf self.subset_tag = str( re.sub('.', lambda m: codepoint_to_chr(int(m.group())+ord('A')), oct(num).replace('o', '') )).rjust(6, 'A') self.font_stream = FontStream(metrics.is_otf, compress=compress) try: psname = metrics.postscript_name except Exception: psname = uuid4() self.font_descriptor = Dictionary({ 'Type': Name('FontDescriptor'), 'FontName': Name('%s+%s'%(self.subset_tag, psname)), 'Flags': 0b100, # Symbolic font 'FontBBox': Array(metrics.pdf_bbox), 'ItalicAngle': metrics.post.italic_angle, 'Ascent': metrics.pdf_ascent, 'Descent': metrics.pdf_descent, 'CapHeight': metrics.pdf_capheight, 'AvgWidth': metrics.pdf_avg_width, 'StemV': metrics.pdf_stemv, }) self.descendant_font = Dictionary({ 'Type':Name('Font'), 'Subtype':Name('CIDFontType' + ('0' if metrics.is_otf else '2')), 'BaseFont': self.font_descriptor['FontName'], 'FontDescriptor':objects.add(self.font_descriptor), 'CIDSystemInfo':Dictionary({ 'Registry':String('Adobe'), 'Ordering':String('Identity'), 'Supplement':0, }), }) if not self.is_otf: self.descendant_font['CIDToGIDMap'] = Name('Identity') self.font_dict = Dictionary({ 'Type':Name('Font'), 'Subtype':Name('Type0'), 'Encoding':Name('Identity-H'), 'BaseFont':self.descendant_font['BaseFont'], 'DescendantFonts':Array([objects.add(self.descendant_font)]), }) self.used_glyphs = set()
def write_widths(self, objects): glyphs = sorted(self.used_glyphs|{0}) widths = {g:self.metrics.pdf_scale(w) for g, w in izip(glyphs, self.metrics.glyph_widths(glyphs))} counter = Counter() for g, w in widths.iteritems(): counter[w] += 1 most_common = counter.most_common(1)[0][0] self.descendant_font['DW'] = most_common widths = {g:w for g, w in widths.iteritems() if w != most_common} groups = Array() for k, g in groupby(enumerate(widths.iterkeys()), lambda (i,x):i-x): group = list(map(itemgetter(1), g)) gwidths = [widths[g] for g in group] if len(set(gwidths)) == 1 and len(group) > 1: w = (min(group), max(group), gwidths[0]) else: w = (min(group), Array(gwidths)) groups.extend(w)
def write_widths(self, objects): glyphs = sorted(self.used_glyphs|{0}) widths = {g:self.metrics.pdf_scale(w) for g, w in zip(glyphs, self.metrics.glyph_widths(glyphs))} counter = Counter() for g, w in iteritems(widths): counter[w] += 1 most_common = counter.most_common(1)[0][0] self.descendant_font['DW'] = most_common widths = {g:w for g, w in iteritems(widths) if w != most_common} groups = Array() for k, g in groupby(enumerate(widths), lambda i_x:i_x[0]-i_x[1]): group = list(map(itemgetter(1), g)) gwidths = [widths[g] for g in group] if len(set(gwidths)) == 1 and len(group) > 1: w = (min(group), max(group), gwidths[0]) else: w = (min(group), Array(gwidths)) groups.extend(w) self.descendant_font['W'] = objects.add(groups)
def add(self, base_path, start_page, links, anchors): path = os.path.normcase(os.path.abspath(base_path)) self.anchors[path] = a = {} a[None] = Destination(start_page, self.start, self.pdf.get_pageref) for anchor, pos in anchors.iteritems(): a[anchor] = Destination(start_page, pos, self.pdf.get_pageref) for link in links: href, page, rect = link p, frag = href.partition('#')[0::2] link = ((path, p, frag or None), self.pdf.get_pageref(page).obj, Array(rect)) self.links.append(link)
def add_extra_keys(self, d): d['Type'] = Name('XObject') d['Subtype'] = Name('Image') d['Width'] = self.width d['Height'] = self.height if self.depth == 1: d['ImageMask'] = True d['Decode'] = Array([1, 0]) else: d['BitsPerComponent'] = 8 d['ColorSpace'] = Name('Device' + ('RGB' if self.depth == 32 else 'Gray')) if self.mask is not None: d['Mask'] = self.mask if self.soft_mask is not None: d['SMask'] = self.soft_mask
def end(self): if self.current_page.getvalue(): self.end_page() self.font_manager.embed_fonts(self.debug) inforef = self.objects.add(self.info) self.links.add_links() self.objects.pdf_serialize(self.stream) self.write_line() startxref = self.objects.write_xref(self.stream) file_id = String(as_unicode(self.stream.hashobj.hexdigest())) self.write_line('trailer') trailer = Dictionary({'Root':self.catalog, 'Size':len(self.objects)+1, 'ID':Array([file_id, file_id]), 'Info':inforef}) serialize(trailer, self.stream) self.write_line('startxref') self.write_line('%d'%startxref) self.stream.write('%%EOF')
def add(self, base_path, start_page, links, anchors): path = os.path.normcase(os.path.abspath(base_path)) self.anchors[path] = a = {} a[None] = Destination(start_page, self.start, self.pdf.get_pageref) for anchor, pos in anchors.iteritems(): a[anchor] = Destination(start_page, pos, self.pdf.get_pageref) for link in links: href, page, rect = link p, frag = href.partition('#')[0::2] try: pref = self.pdf.get_pageref(page).obj except IndexError: try: pref = self.pdf.get_pageref(page-1).obj except IndexError: self.pdf.debug('Unable to find page for link: %r, ignoring it' % link) continue self.pdf.debug('The link %s points to non-existent page, moving it one page back' % href) self.links.append(((path, p, frag or None), pref, Array(rect)))
def __init__(self, brush, matrix, pdf, pixel_page_width, pixel_page_height): self.matrix = (matrix.m11(), matrix.m12(), matrix.m21(), matrix.m22(), matrix.dx(), matrix.dy()) gradient = sip.cast(brush.gradient(), QLinearGradient) start, stop, stops = self.spread_gradient(gradient, pixel_page_width, pixel_page_height, matrix) # TODO: Handle colors with different opacities self.const_opacity = stops[0].color[-1] funcs = Array() bounds = Array() encode = Array() for i, current_stop in enumerate(stops): if i < len(stops) - 1: next_stop = stops[i + 1] func = Dictionary({ 'FunctionType': 2, 'Domain': Array([0, 1]), 'C0': Array(current_stop.color[:3]), 'C1': Array(next_stop.color[:3]), 'N': 1, }) funcs.append(func) encode.extend((0, 1)) if i + 1 < len(stops) - 1: bounds.append(next_stop.t) func = Dictionary({ 'FunctionType': 3, 'Domain': Array([stops[0].t, stops[-1].t]), 'Functions': funcs, 'Bounds': bounds, 'Encode': encode, }) shader = Dictionary({ 'ShadingType': 2, 'ColorSpace': Name('DeviceRGB'), 'AntiAlias': True, 'Coords': Array([start.x(), start.y(), stop.x(), stop.y()]), 'Function': func, 'Extend': Array([True, True]), }) Dictionary.__init__( self, { 'Type': Name('Pattern'), 'PatternType': 2, 'Shading': shader, 'Matrix': Array(self.matrix), }) self.cache_key = (self.__class__.__name__, self.matrix, tuple(shader['Coords']), stops)