Python Forum
Universal(MACWINLIN) canvas pdf export
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Universal(MACWINLIN) canvas pdf export
#1
(I did a post earlier on this subject but I really want to find an answer on this)

My app is almost finished! The only problem that remains is exporting my musical score to pdf. I want to build a universal pdf export function that works on macOS, Windows, and Linux. My final executable should contain everything to export pdf so users don't have to install additional things.

I made a simple Tkinter canvas example in which we have to write the pdf export. How do other apps include the libraries needed to export pdf inside the executable? I searched so many times but all answers are using cmd-line apps like 'ps2pdf' or 'ps2pdfwr'. That's not what I want. Please can somebody share their knowledge? I want to know if it is possible in a self-contained way...

code example:
from tkinter import Tk, Canvas, Button
import random
from random import uniform

# root
root = Tk()
root.title('PianoScript')
scrwidth = root.winfo_screenwidth()
scrheight = root.winfo_screenheight()
root.geometry("%sx%s+%s+%s" % (int(scrwidth / 1.5), 
    int(scrheight / 1.25), 
    int(scrwidth / 6), 
    int(scrheight / 12)))
# canvas
canvas = Canvas(root, bg='white', relief='flat')
canvas.place(relwidth=1, relheight=1)
# buttons
button1 = Button(root, text='Generate PDF')
button1.pack()
button2 = Button(root, text='Generate Drawing')
button2.pack()

# Constants
MM = 3.5020519835841313
PAPER_HEIGHT = MM * 297  # a4 210x297 mm
PAPER_WIDTH = MM * 210

def generate_drawing():
    canvas.delete('all')
    canvas.create_rectangle(50,
        50,
        50+PAPER_WIDTH,
        50+PAPER_HEIGHT,
        outline='black',
        fill='white')
    def random_color():
        return ["#"+''.join([random.choice('ABCDEF0123456789') for i in range(6)])]
    for i in range(10):
        canvas.create_polygon(uniform(50,50+PAPER_WIDTH), 
            uniform(50,50+PAPER_HEIGHT), 
            uniform(50,50+PAPER_WIDTH), 
            uniform(50,50+PAPER_HEIGHT), 
            uniform(50,50+PAPER_WIDTH), 
            uniform(50,50+PAPER_HEIGHT), 
            uniform(50,50+PAPER_WIDTH), 
            uniform(50,50+PAPER_HEIGHT), 
            fill=random_color())


def generate_pdf():
    print('Generating PDF...')
    '''
    Here I want code that generates a pdf. We can 
    already use the canvas.postscript() function to
    export the drawing to postscript file.
    '''
    canvas.postscript(file='~/Desktop/randomdrawing.ps',
        x=50,
        y=50,
        width=PAPER_WIDTH,
        height=PAPER_HEIGHT,
        rotate=False)
    ''' solution here? '''


button1.configure(command=generate_pdf)
button2.configure(command=generate_drawing)
root.mainloop()
Reply
#2
perhaps you can find something useful here: https://pypi.org/search/?q=music+notation+pdf&o=
or here: https://www.musicxml.com/music-in-musicxml/
A long time ago, I wrote a music scale program with notation using lillypad see: https://github.com/Larz60p/MusicScales
It has a save to PDF function, but this is old code and rather complicated. I haven't updated in years.
Reply
#3
(Aug-11-2022, 09:11 AM)Larz60+ Wrote: perhaps you can find something useful here: https://pypi.org/search/?q=music+notation+pdf&o=
or here: https://www.musicxml.com/music-in-musicxml/
A long time ago, I wrote a music scale program with notation using lillypad see: https://github.com/Larz60p/MusicScales
It has a save to PDF function, but this is old code and rather complicated. I haven't updated in years.
This is not what I want to know. How can I include a pdf converting system inside my executable so the user doesn't have to install dependencies?
Reply
#4
I would try to draw in parallel a PIL image and the tkinter canvas like in this very old forum post. The pil image can be saved as pdf in modern versions of pil. You could define your own functions such as

def my_create_rectangle(...):
    my_canvas.create_rectangle(...)
    my_image.rectangle(...)

...
def generate_drawing():
    ...
    my_create_rectangle(...)

def generate_pdf(...)
    my_image.save(...) # save as pdf
Reply
#5
(Aug-12-2022, 11:26 AM)Gribouillis Wrote: I would try to draw in parallel a PIL image and the tkinter canvas like in this very old forum post. The pil image can be saved as pdf in modern versions of pil. You could define your own functions such as

def my_create_rectangle(...):
    my_canvas.create_rectangle(...)
    my_image.rectangle(...)

...
def generate_drawing():
    ...
    my_create_rectangle(...)

def generate_pdf(...)
    my_image.save(...) # save as pdf

A long time since this answer. Sorry I didn't react earlier. But I found after a long time a solution for this for mac windows and linux. Pil is not a real solution since My output needs to be a vector drawing for the best print quality.

for all three systems I needed to use a external command line program to make a conversion between .ps and .pdf:
def exportPDF(event=''):    
    def is_tool(name):
        """Check whether `name` is on PATH and marked as executable."""
        return which(name) is not None
        print('exportPDF')

    if platform.system() == 'Linux':
        if is_tool('ps2pdfwr') == 0:
            messagebox.showinfo(title="Can't export PDF!",
                                message='PianoScript cannot export the PDF because function "ps2pdfwr" is not '
                                        'installed on your computer.')
            return

        f = filedialog.asksaveasfile(mode='w', parent=root, filetypes=[("pdf file", "*.pdf")], initialfile=Score['header']['title']['text'],
                                     initialdir='~/Desktop')
        if f:
            pslist = []
            if Score['properties']['engraver'] == 'pianoscript':
                numofpages = range(engrave_pianoscript('export',
                    renderpageno,
                    Score,
                    MM,
                    last_pianotick,
                    color_notation_editor,
                    color_editor_canvas,
                    pview,root,
                    BLACK))
                for rend in numofpages:
                    pview.postscript(file=f"/tmp/tmp{rend}.ps", 
                        x=10000,
                        y=rend * (Score['properties']['page-height'] * MM), 
                        width=Score['properties']['page-width'] * MM,
                        height=Score['properties']['page-height'] * MM, 
                        rotate=False,
                        fontmap='-*-Courier-Bold-R-Normal--*-120-*')
                    process = subprocess.Popen(
                        ["ps2pdfwr", "-sPAPERSIZE=a4", "-dFIXEDMEDIA", "-dEPSFitPage", "/tmp/tmp%s.ps" % rend,
                         "/tmp/tmp%s.pdf" % rend])
                    process.wait()
                    os.remove("/tmp/tmp%s.ps" % rend)
                    pslist.append("/tmp/tmp%s.pdf" % rend)
            else:
                numofpages = range(engrave_pianoscript_vertical('export',
                    renderpageno,
                    Score,
                    MM,
                    last_pianotick,
                    color_notation_editor,
                    color_editor_canvas,
                    pview,root,
                    BLACK))
                for rend in numofpages:
                    pview.postscript(file=f"/tmp/tmp{rend}.ps", 
                        x=rend * (Score['properties']['page-width'] * MM),
                        y=10000, 
                        width=Score['properties']['page-width'] * MM,
                        height=Score['properties']['page-height'] * MM, 
                        rotate=False,
                        fontmap='-*-Courier-Bold-R-Normal--*-120-*')
                    process = subprocess.Popen(
                        ["ps2pdfwr", "-sPAPERSIZE=a4", "-dFIXEDMEDIA", "-dEPSFitPage", "/tmp/tmp%s.ps" % rend,
                         "/tmp/tmp%s.pdf" % rend])
                    process.wait()
                    os.remove("/tmp/tmp%s.ps" % rend)
                    pslist.append("/tmp/tmp%s.pdf" % rend)
            cmd = 'pdfunite '
            for i in range(len(pslist)):
                cmd += pslist[i] + ' '
            cmd += '"%s"' % f.name
            process = subprocess.Popen(cmd, shell=True)
            process.wait()

    elif platform.system() == 'Windows':
        f = filedialog.asksaveasfile(mode='w', parent=root, filetypes=[("pdf Score", "*.pdf")], initialfile=Score['header']['title']['text'],
                                     initialdir='~/Desktop')
        if f:
            pslist = []
            if Score['properties']['engraver'] == 'pianoscript':
                print(f.name)
                counter = 0
                numofpages = range(engrave_pianoscript('export',
                        renderpageno,
                        Score,
                        MM,
                        last_pianotick,
                        color_notation_editor,
                        color_editor_canvas,
                        pview,root,
                        BLACK))
                for export in numofpages:
                    counter += 1
                    print('printing page ', counter)
                    pview.postscript(file=f"{f.name}{counter}.ps", 
                        colormode='gray', 
                        x=10000, 
                        y=export * (Score['properties']['page-height'] * MM),
                        width=(Score['properties']['page-width'] * MM), 
                        height=(Score['properties']['page-height'] * MM), 
                        rotate=False,
                        fontmap='-*-Courier-Bold-R-Normal--*-120-*')
                    pslist.append(str('"' + str(f.name) + str(counter) + '.ps' + '"'))
                try:
                    do_rend = subprocess.Popen(
                        f'''"C:/Program Files/gs/gs10.01.1/bin/gswin64c.exe" -dQUIET -dBATCH -dNOPAUSE -dFIXEDMEDIA -sPAPERSIZE=a4 -dEPSFitPage -sDEVICE=pdfwrite -sOutputFile="{f.name}.pdf" {' '.join(pslist)}''', shell=True)
                    do_rend.wait()
                    do_rend.terminate()    
                    for i in pslist:
                        os.remove(i.strip('"'))
                    f.close()
                    os.remove(f.name)
                except:
                    messagebox.showinfo(title="Can't export PDF!",
                                        message='''Be sure you have selected a valid path to "gswin64c.exe" in the gspath.json file that is located in the same folder as PianoScript program. You have to set the path+gswin64c.exe. example: "gspath":"C:/Program Files/gs/gs9.54.0/bin/gswin64c.exe". Then, restart PianoScript app.''')
            else:
                print(f.name)
                counter = 0
                numofpages = range(engrave_pianoscript_vertical('export',
                        renderpageno,
                        Score,
                        MM,
                        last_pianotick,
                        color_notation_editor,
                        color_editor_canvas,
                        pview,root,
                        BLACK))
                for export in numofpages:
                    counter += 1
                    print('printing page ', counter)
                    pview.postscript(file=f"{f.name}{counter}.ps", 
                        colormode='gray', 
                        x=export * (Score['properties']['page-width'] * MM), 
                        y=10000,
                        width=(Score['properties']['page-width'] * MM), 
                        height=(Score['properties']['page-height'] * MM),
                        rotate=False,
                        fontmap='-*-Courier-Bold-R-Normal--*-120-*')
                    pslist.append(str('"' + str(f.name) + str(counter) + '.ps' + '"'))
                try:
                    do_rend = subprocess.Popen(
                        f'''"C:/Program Files/gs/gs10.01.1/bin/gswin64c.exe" -dQUIET -dBATCH -dNOPAUSE -dFIXEDMEDIA -sPAPERSIZE=a4 -dEPSFitPage -sDEVICE=pdfwrite -sOutputFile="{f.name}.pdf" {' '.join(pslist)}''', shell=True)
                    do_rend.wait()
                    do_rend.terminate()    
                    for i in pslist:
                        os.remove(i.strip('"'))
                    f.close()
                    os.remove(f.name)
                except:
                    messagebox.showinfo(title="Can't export PDF!",
                                        message='''Be sure you have selected a valid path to "gswin64c.exe" in the gspath.json file that is located in the same folder as PianoScript program. You have to set the path+gswin64c.exe. example: "gspath":"C:/Program Files/gs/gs9.54.0/bin/gswin64c.exe". Then, restart PianoScript app.''')
    
    elif platform.system() == 'Darwin':
        f = filedialog.asksaveasfile(mode='w', parent=root, 
                                     filetypes=[("pdf Score", "*.pdf")], 
                                     initialfile=Score['header']['title']['text'],
                                     initialdir='~/Desktop')
        if f:
            pslist = []
            if Score['properties']['engraver'] == 'pianoscript':
                numofpages = range(engrave_pianoscript('export',
                    renderpageno,
                    Score,
                    MM,
                    last_pianotick,
                    color_notation_editor,
                    color_editor_canvas,
                    pview,root,
                    BLACK))
                for rend in numofpages:
                    pview.postscript(file=f"/tmp/tmp{rend}.ps", 
                        x=10000,
                        y=rend * (Score['properties']['page-height'] * MM), 
                        width=Score['properties']['page-width'] * MM,
                        height=Score['properties']['page-height'] * MM, 
                        rotate=False,
                        fontmap='-*-Courier-Bold-R-Normal--*-120-*')
                    process = subprocess.Popen(
                        ["ps2pdfwr", "-sPAPERSIZE=a4", "-dFIXEDMEDIA", "-dEPSFitPage", "/tmp/tmp%s.ps" % rend,
                         "/tmp/tmp%s.pdf" % rend])
                    process.wait()
                    os.remove("/tmp/tmp%s.ps" % rend)
                    pslist.append("/tmp/tmp%s.pdf" % rend)
            else:
                numofpages = range(engrave_pianoscript_vertical('export',
                    renderpageno,
                    Score,
                    MM,
                    last_pianotick,
                    color_notation_editor,
                    color_editor_canvas,
                    pview,root,
                    BLACK))
                for rend in numofpages:
                    pview.postscript(file=f"tmp{rend}.ps",
                        x=rend * (Score['properties']['page-width'] * MM),
                        y=10000, 
                        width=Score['properties']['page-width'] * MM,
                        height=Score['properties']['page-height'] * MM, 
                        rotate=False,
                        fontmap='-*-Courier-Bold-R-Normal--*-120-*')
                    process = subprocess.Popen(
                        f'''pstopdf tmp{rend}.ps''', shell=True)
                    process.wait()
                    os.remove(f"tmp{rend}.ps")
                    pslist.append(f"tmp{rend}.pdf")
            cmd = 'pdfunite '
            for i in range(len(pslist)):
                cmd += pslist[i] + ' '
            cmd += '"%s"' % f.name
            process = subprocess.Popen(cmd, shell=True)
            process.wait()
            # remove temporary pdf page files
            for rend in numofpages:
                os.remove(f"tmp{rend}.pdf")
(there seems to be a bug in the code coloring in this site, I see parts of the code in green like a string which is not in my text editor)
Gribouillis likes this post
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020