Search code examples
pythonpdftextviewgtkreportlab

How to save textview content in gtk to pdf using python?


I want to save textview's buffer in pdf format. I can do it using reportlab if it's just a simple text. But, what if I want to save everything, from text with its tags and also images?

from gi.repository import Gtk, GdkPixbuf
import pango
from reportlab.pdfgen import canvas

class gui():
    def __init__(self):
        self.window = Gtk.Window()
        self.window.connect('delete-event', Gtk.main_quit)

        self.box = Gtk.Box()
        self.window.add(self.box)

        self.textview = Gtk.TextView()
        self.textbuffer = self.textview.get_buffer()

        self.tag_bold = self.textbuffer.create_tag("bold",
                                                    weight=pango.WEIGHT_BOLD)
        self.tag_italic = self.textbuffer.create_tag("italic",
                                                     style=pango.STYLE_ITALIC)

        pix = GdkPixbuf.Pixbuf.new_from_file_at_size('baby.jpg', 50, 50)

        tag = [self.tag_bold,
               self.tag_italic]

        self.textbuffer.insert_pixbuf(self.textbuffer.get_end_iter(), pix)
        for i in range(20):
            self.textbuffer.insert_with_tags(self.textbuffer.get_end_iter(),
                                                     'line%d\n' % (i+1),
                                                     tag[i % 2])

        self.box.pack_start(self.textview, True, True, 0)

        self.button = Gtk.Button(label='Start')
        self.button.connect('clicked', self.on_button_clicked)
        self.box.pack_start(self.button, True, True, 0)

        self.window.show_all()
        Gtk.main()

    def on_button_clicked(self, widget):
        canv = canvas.Canvas('tes.pdf')

        for i in range(self.textbuffer.get_line_count()):
            a = self.textbuffer.get_iter_at_line(i)
            b = self.textbuffer.get_iter_at_line(i+1).get_offset()
            c = self.textbuffer.get_iter_at_offset(b - 1)
            t = self.textbuffer.get_text(a, b, True)
            line = 750 - (15 * l)
            canv.drawString(40, line, t)

        canv.save()

if __name__ == '__main__':
    gui = gui()

EDIT: drahnr suggest to use cairo instead. Okay, I think it's a better idea since reportlab coordinate start from bottom left and cairo coordinate start from top left. Below is my code using cairo.

from gi.repository import Gtk, GdkPixbuf, Gdk
import pango
import cairo


class gui():
    def __init__(self):
        self.window = Gtk.Window()
        self.window.connect('delete-event', Gtk.main_quit)
        self.textview = Gtk.TextView()
        self.window.add(self.textview)

        self.initText()
        self.createPDF()

        self.window.show_all()
        Gtk.main()

    def initText(self):
        self.tag_bold = self.textview.get_buffer().create_tag("bold", weight=pango.WEIGHT_BOLD)
        self.tag_italic = self.textview.get_buffer().create_tag("italic", style=pango.STYLE_ITALIC)

        pix = GdkPixbuf.Pixbuf.new_from_file_at_size('baby.png', 50, 50)

        tag = [self.tag_bold, self.tag_italic]

        self.textview.get_buffer().insert_pixbuf(self.textview.get_buffer().get_end_iter(), pix)
        self.textview.get_buffer().insert(self.textview.get_buffer().get_end_iter(), '\n')
        for i in range(20):
            self.textview.get_buffer().insert_with_tags(self.textview.get_buffer().get_end_iter(), 'line%d' % (i+1), tag[i % 2])
            self.textview.get_buffer().insert(self.textview.get_buffer().get_end_iter(), '\n')

    def createPDF(self):
        line = 30
        row = 5
        pos = 0
        ps = cairo.PDFSurface('tes.pdf', 600, 770)
        cr = cairo.Context(ps)  

        while pos != self.textview.get_buffer().get_end_iter().get_offset():
            if self.textview.get_buffer().get_iter_at_offset(pos).has_tag(self.tag_bold):
                cr.select_font_face('Times', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
            elif self.textview.get_buffer().get_iter_at_offset(pos).has_tag(self.tag_italic):
                cr.select_font_face('Times', cairo.FONT_SLANT_ITALIC, cairo.FONT_WEIGHT_NORMAL)
            else:
                cr.select_font_face('Times', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)

            t = self.textview.get_buffer().get_slice(self.textview.get_buffer().get_iter_at_offset(pos), self.textview.get_buffer().get_iter_at_offset(pos+1), False)
            if t == '\n':
                line += 12
                row = 5
            elif t == unichr(0xFFFC):
                pix = self.textview.get_buffer().get_iter_at_offset(pos).get_pixbuf()
                Gdk.cairo_set_source_pixbuf(cr, pix, 8 * row, line)
                line += pix.get_width()
                cr.paint()
            else:
                cr.move_to(8 * row, line)
                cr.show_text(t)

            pos=pos+1
            row += 1

if __name__ == '__main__':
    gui()

literally it's still the same. I should hardcode it to draw everything. and drahnr suggest to use gtk_widget_draw to render it to cairo surface.


Solution

  • This is no python answer, but it might be applicable to your problem too:

    Rendering to a cairo_surface_t and use the pdf surface API - how you render your text is up to you, a simple way would be to render the textview via gtk_widget_draw completely.

    https://developer.gnome.org/gtk3/stable/GtkWidget.html#gtk-widget-draw. http://cairographics.org/manual/cairo-PDF-Surfaces.html

    Update #2:

    The below is incomplete and requires a widget that does the rendering for you and thus does not replace the gtk_widget_draw call. You need to create ctype extension or do the drawing manually.

    Source: http://www.stuartaxon.com/2010/02/03/using-cairo-to-generate-svg-in-django/

    Update #1:

    This function renders an image to a surface (PDF,SVG, whatever your compiled cairo library supports) and stores it to the file dest - for details refer to the manual of the specific function

    def draw_render_to_file(dest, Widget, Surface = PDFSurface, width = 100, height = 100)
      widget = Widget(Surface)
      surface = widget.Surface(dest, width, height)
      widget.draw(Context(surface), width, height)
      surface.finish()