1

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.

user2435611
  • 1,093
  • 1
  • 12
  • 18
  • Take a look at http://www.joachim-breitner.de/blog/archives/494-Better-PDF-screenshots-with-gtk-3.html – user4815162342 Jul 02 '13 at 10:04
  • Sorry, can you please explain what he did? I don't understand at all. – user2435611 Jul 02 '13 at 11:35
  • I don't see how @user4815162342 link is relevant, and if it is I think it needs some more explanation. – Gordon Seidoh Worley Jul 02 '13 at 13:39
  • It sounds like what you want to do is actually print to pdf, which shouldn't require ReportLab. The point of ReportLab is to build PDFs progrmatically, but here you already have formatted text that you just want to get into a PDF, which is perfect for existing print to pdf tools. – Gordon Seidoh Worley Jul 02 '13 at 13:41
  • @GGordonWorleyIII The relevance of the link is that the author managed to draw an arbitrary widget (gucharmap in his case) to PDF using the cairo PDF backend. This is exactly what the OP is looking for. The pdfgen canvas is the wrong approach because it must duplicate the work the GTK does with fonts, colors, images, and other features of GtkTextView. Cairo is perfectly capable of producing PDFs, and GTK is capable of off-screen rendering, but it's not easy to combine the two. It was my impression that the linked author managed to do that in GTK 3 (and presumably that he published the code). – user4815162342 Jul 02 '13 at 14:36
  • @G Gordon Worley III and @user4815162342: yes, I already have formatted text and images in gtk textview and want to print it to pdf. – user2435611 Jul 02 '13 at 18:43
  • Related: http://stackoverflow.com/questions/4944441/how-to-draw-any-gtk-widget-on-top-of-cairo-surface – drahnr Jul 03 '13 at 17:13
  • Related: http://stackoverflow.com/questions/17154330/creating-multiple-pdfs-with-python-cairo – drahnr Jul 03 '13 at 17:13

1 Answers1

0

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()
drahnr
  • 6,782
  • 5
  • 48
  • 75
  • I still have not managed to do it. Do you have a good tutorial for Gtk.Widget.draw()? – user2435611 Jul 03 '13 at 14:21
  • @user2435611 you never call the gtk_widget_draw routine which is member of your widget (here: TextView). Also a common error is argument passing in python (I did not check your arguments, that is your task) – drahnr Jul 03 '13 at 16:04
  • there's no draw method from [textview](http://www.pygtk.org/docs/pygtk/class-gtktextview.html) and the weird thing is I don't find an equivalent of [gtk_widget_draw](https://developer.gnome.org/gtk3/stable/GtkWidget.html#gtk-widget-draw) from [pygtk](http://pygtk.org/docs/pygtk/class-gtkwidget.html) – user2435611 Jul 03 '13 at 16:44
  • I suggest you to go for a ctype binding - a python extension – drahnr Jul 03 '13 at 17:01
  • I still don't understand how to make it simple. So, I think I'll stick with my code above. It works actually, but I need iterate it for the whole text char by char. Thanks for help. – user2435611 Jul 03 '13 at 17:45
  • @user2435611 The documentation you link to is for pygtk, which you're not using — you're using `pygobject` which uses introspection to access GTK directly without a pygtk layer. `gtk_widget_draw` was introduced in GTK 3, so pygtk documentation (which applies to GTK 2) doesn't mention it. However, if you just try to access `Gtk.Widget.draw`, you'll see that it exists just fine. – user4815162342 Jul 05 '13 at 09:36