Mixing CTRunDraw and drawAtPoint - python

I wrote a module that provides several methods to render text and create PDF files. Now I find that using Core Text along with higher level text rendering messes up the size of the Core Text layout. The later is mainly displayed too large.
Scenario:
Create a PDF context
Beginn a page
Render text via CTRunDraw (B) *
End a page
Close PDF context
Save PDF
*) This gets rendered properly, as expected. But only as long as I don’t draw any text using a NSAttributedString and drawAtPoint_ (A) before.
This means, both methods work perfectly on their own, but the core text drawing gets too large when they both come together.
Is there something to take care of when saving and restoring the graphics context? I tried almost anything. I can put some code here, but it’s quite a lot and in a complex setup, so if you can specify what you exactly want to see, please let me know.
(A) The high level text drawing:
class Text(object):
def __init__(self, text="", font="Monaco", fontSize=12, position=None ):
self.text = text
self.font = font
self.fontSize = fontSize
self.position = position
self.fontRGBColor = 0, 1, 1, 1
def drawText(self):
textAttributes = {}
textAttributes[NSFontAttributeName] = NSFont.fontWithName_size_( self.font, self.fontSize )
textAttributes[NSForegroundColorAttributeName] = NSColor.colorWithCalibratedRed_green_blue_alpha_( *self.fontRGBColor )
textAttributedString = NSAttributedString.alloc().initWithString_attributes_( self.text, textAttributes )
textAttributedString.drawAtPoint_((self.position[0], self.position[1]))
(B) The core text method:
class TextBox(object):
def __init__(self):
self.font = "Monaco"
self.fontSize = 12
self.text = "This is my Text"
self.fontColor = 0, 1, 1, 0, 0.75
self.pdfContext = NSGraphicsContext.currentContext().graphicsPort()
def drawTextBox(self):
CGContextSaveGState(self.pdfContext)
myBounds = (0, 0), (100, 100)
posX, posY = myBounds[0]
width, height = myBounds[1]
self.fontObject = NSFont.fontWithName_size_(self.font, self.fontSize)
self.fontAttributes = {}
self.fontAttributes[NSFontAttributeName] = self.fontObject
self.fontAttributes[NSForegroundColorAttributeName] = NSColor.colorWithDeviceCyan_magenta_yellow_black_alpha_(*self.fontColor)
TextBoxWithFeaturesAttributedString = NSMutableAttributedString.alloc().initWithString_attributes_(self.text, self.fontAttributes)
setter = CTFramesetterCreateWithAttributedString(TextBoxWithFeaturesAttributedString)
path = CGPathCreateMutable()
CGPathAddRect(path, None, CGRectMake(0, 0, width, height))
box = CTFramesetterCreateFrame(setter, (0, 0), path, None)
ctLines = CTFrameGetLines(box)
origins = CTFrameGetLineOrigins(box, (0, len(ctLines)), None)
for i, (originX, originY) in enumerate(origins):
ctLine = ctLines[i]
bounds = CTLineGetImageBounds(ctLine, self.pdfContext)
if bounds.size.width == 0:
continue
ctRuns = CTLineGetGlyphRuns(ctLine)
for ctRun in ctRuns:
CGContextSetTextPosition(self.pdfContext, posX + originX, posY + originY)
CTRunDraw(ctRun, self.pdfContext, (0, 0))
CGContextRestoreGState(self.pdfContext)
Many thanks and appreciations in advance.

Related

python - how to go to next image in slideshow using tkinter gui

i'm trying to make a photo slideshow program. it isn't working as i want to go to the next photo in the list using the self.counter variable, but the value of self.counter is going back to 0 as it forgets the fact that i changed the value. i didn't think i could use configure as then the buttons to go to the next image wouldn't work either.
i hope you can help :) much appreciated.
from tkinter import *
class SlideShowGUI:
def __init__(self, parent, counter = 0):
self.counter = counter
self.images_list = ["smiley.png", "carrot.png", "bunny.jpg"]
self.photo = PhotoImage(file = self.images_list[self.counter])
self.b1 = Button(parent, text = "", image = self.photo, bg = "white")
self.b1.grid(row = 0, column = 0)
back = Button(parent, width = 2, anchor = W, text = "<", relief = RIDGE, command = self.previous_image)
back.grid(row = 1, column = 0)
forward = Button(parent, width = 2, anchor = E, text = ">", relief = RIDGE, command = self.next_image)
forward.grid(row = 1, column = 1)
def previous_image(self):
self.counter -= 1
self.photo.configure(file = self.images_list[self.counter])
def next_image(self):
self.counter += 1
self.photo.configure(file = self.images_list[self.counter])
#main routine
if __name__ == "__main__":
root = Tk()
root.title("hello")
slides = SlideShowGUI(root)
root.mainloop()
sorry it will not work without getting images !
error message if i click next button two times
use this instead:
def previous_image(self):
self.counter -= 1
self.photo.configure(file = images_list[self.counter])
def next_image(self):
self.counter += 1
self.photo.configure(file = images_list[self.counter])
except You have to watch out for List Out Of Index errors
Also why are You using global images_list? There seems to be no point. If You wanted to reuse that list in the class You could have just named it self.images_list = [your, items].
The error You are getting: read this

How to 'split' a tkinter Frame with two different objects on each side

Thanks in advance for your time. I am writing a chess program using the tkinter module. I have put the board interface on the left side of the screen, while on the right side I would like to have the algebraic notation (e.g. 1. e4 e5 2. Nf3 Nc6) printed and updating as the players make their moves.
Since the text is updating, I tried to create a Label widget and a StringVar() object. But it is having some trouble : Basically, there is no right side to the screen, and the with each move the window flickers violently before showing the move in algebraic notation in the center of the right side of the window. Then, for each move, the same spasm occurs, the window expands a tiny bit, and eventually the string runs off the screen.
Ideally, every two moves, there would be a new line, but for now I would be content to just have the text 'wrap' once it hits the end of the frame.
I tried to create a Canvas widget and use the create_text() method to do it, but it was a bit unwieldy (though that seems to have been the best so far).
import tkinter as tk
master = tk.Tk()
class Chessboard(tk.Frame):
def __init__(self):
tk.Frame.__init__(self)
self.pack()
self.master.title('Chess')
self.mainframe = tk.Frame(master, width = 1600, height = 800)
self.mainframe.pack(side = 'top', anchor = 'n')
self.move_data = {'x': 0, 'y': 0, 'piece': None, 'to': None, 'from': None, 'last': None, 'color':'w', 'enem_color':'b'}
self.draw_board(master)
self.counter=2
self.en_passant = []
self.pgn_frame = tk.Canvas(self.mainframe, width = 500, height = 640, bd = 5, relief='ridge', cursor = 'trek')
self.pgn_frame.pack(side='right')
self.pgn_txt = ''
self.pgn_str = tk.StringVar()
self.pgn_label = tk.Label(self.pgn_frame, textvariable = self.pgn_str)
self.pgn_label.pack(fill='both', expand = True)
def draw_board(self, master):
self.board = tk.Canvas(self.mainframe, width = 680, height = 680, borderwidth = 20)
self.board.pack(fill='both', expand=True, side='left')
for r in range(7, -1, -1):
for c in range(8):
if c&1 ^ r&1:
fill = 'sienna2'
else:
fill = 'wheat1'
x0, y0 = c*80+24, r*80+24
x1, y1 = c*80+104, r*80+104
coords = (x0, y0, x1, y1)
center = ((x0+x1)/2, (y0+y1)/2)
sq = files[c] + ranks[-r-1]
self.board.create_rectangle(coords, fill=fill, width = 0, tags = ('square', sq))
Chessboard().mainloop()
I will note that the draw_board() function creates a canvas within the self.mainframe Frame and places it to the 'left'. Again, thank you for your time !
After searching the annals of SO, I stumbled across an ancient piece of wisdom from this question's first commenter :
"tkinter widgets are designed to shrink (or expand) to exactly fit their children." -Label within a Frame (Tkinter)
This little tidbit has put me on the right track to solving my problem : previously, I was trying to pack a Label widget into a Frame or Canvas widget, with the logic that the child should adapt to fit its parent. But, thanks to Mr. Oakley's insight, I have simplified the code to evict the superfluous widget and simply design my Label widget to meet the needs of the program. So, reformatted, it looks like
import tkinter as tk
master = tk.Tk()
class Chessboard(tk.Frame):
def __init__(self):
tk.Frame.__init__(self)
self.pack()
self.master.title('Chess')
self.mainframe = tk.Frame(master, width = 1600, height = 800)
self.mainframe.pack(side = 'top', anchor = 'n')
self.move_data = {'x': 0, 'y': 0, 'piece': None, 'to': None, 'from': None, 'last': None, 'color':'w', 'enem_color':'b'}
self.draw_board(master)
self.counter=2
self.en_passant = []
self.pgn_txt = ''
self.pgn_str = tk.StringVar()
self.pgn_frame = tk.Label(self.mainframe, width = 500, height = 640, bd = 5, relief='ridge', cursor = 'trek', textvariable = self.pgn_str)
self.pgn_frame.pack(side='right')
def draw_board(self, master):
self.board = tk.Canvas(self.mainframe, width = 680, height = 680, borderwidth = 20)
self.board.pack(fill='both', expand=True, side='left')
for r in range(7, -1, -1):
for c in range(8):
if c&1 ^ r&1:
fill = 'sienna2'
else:
fill = 'wheat1'
x0, y0 = c*80+24, r*80+24
x1, y1 = c*80+104, r*80+104
coords = (x0, y0, x1, y1)
center = ((x0+x1)/2, (y0+y1)/2)
self.board.create_rectangle(coords, fill=fill, width = 0, tags = ('square'))
Chessboard().mainloop()
It ain't pretty at the moment, but soon, it will be. In the actual program, the text updates without the aforementioned spasms, though will need to work a little on the formatting.

Tkinter Window/Widget Proportional Resize

I'm currently working on a Python GUI version of Reversi for a programming class. I've already programmed the game logic, and I'm currently trying to implement the GUI using Tkinter. I'm having some problems with resizing the game board (root window) and everything on it (canvases and shapes) proportionally. The game currently works, but everything that I've tried to get the board to resize correctly hasn't worked. The relevant code is as follows.
class OthelloApplication:
def __init__(self, game_state: othello.Othello):
self._game_state = game_state
self._game_state.new_game()
self._game_board = game_state.show_board()
self._root_window = tkinter.Tk()
for row in range(self._game_state.show_rows()):
for col in range(self._game_state.show_cols()):
canvas = tkinter.Canvas(self._root_window, width = 100, height = 100,
borderwidth = 0, highlightthickness = 1,
background = _BACKGROUND_COLOR, highlightbackground = 'black')
canvas.grid(row = row, column = col, padx = 0, pady = 0,
sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
self._root_window.rowconfigure(row, weight = 1)
self._root_window.columnconfigure(col, weight = 1)
self._turn_window = tkinter.Canvas(self._root_window, width = 200,
height = 100, highlightthickness = 0, background = 'white')
self._root_window.bind('<Button-1>', self._on_canvas_clicked)
self._root_window.bind('<Configure>', self.on_resize)
def draw_game_pieces(self) -> None:
for row in range(self._game_state.show_rows()):
for col in range(self._game_state.show_cols()):
if self._game_board[col][row] == ' ':
pass
else:
canvas = tkinter.Canvas(master = self._root_window, width = 100, height = 100,
borderwidth = 0, highlightthickness = 1,
background = _BACKGROUND_COLOR, highlightbackground = 'black')
canvas.grid(row = row, column = col, padx = 0, pady = 0,
sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
canvas.update()
canvas.create_oval(2, 2, canvas.winfo_width() - 2,
canvas.winfo_height() - 2, fill = self._which_color(col,
row), outline = self._which_color(col, row))
self._root_window.rowconfigure(row, weight = 1)
self._root_window.columnconfigure(col, weight = 1)
def display_turn(self) -> None:
if self._game_state.show_turn() == 'B':
turn = 'black'
else:
turn = 'white'
self._turn_window.grid(row = self._game_state.show_rows() // 2 - 1,
column = self._game_state.show_cols() + 1,
sticky = tkinter.N + tkinter.S + tkinter.E + tkinter.W)
self._turn_info = self._turn_window.create_text(10, 10,
font = 'Helvetica', anchor = 'nw')
self._turn_window.itemconfig(self._turn_info, text = 'Turn = ' + turn)
def on_resize(self, event: tkinter.Event) -> None:
self.draw_game_pieces()
def _which_color(self, col: int, row: int) -> str:
if self._game_board[col][row] == 'B':
return 'black'
elif self._game_board[col][row] == 'W':
return 'white'
def _on_canvas_clicked(self, event: tkinter.Event) -> (int):
print(event.widget.winfo_reqwidth(), event.widget.winfo_reqheight())
print(event.widget.winfo_width(), event.widget.winfo_height())
try:
grid_info = event.widget.grid_info()
move = (int(grid_info["row"]), int(grid_info["column"]))
self._game_state.player_move(move[1], move[0])
self.draw_game_pieces()
self.display_turn()
except AttributeError:
pass
except othello.InvalidMoveError:
print('Error: that wasn\'t a valid move.')
except othello.GameOverError:
print('The game is over.')
def start(self) -> None:
self.draw_game_pieces()
self.display_turn()
self._root_window.mainloop()
The draw_game_pieces() method draws the appropriately colored circle in the correct space on the board based on the size of the canvas on which it is being drawn.
My solution to my resize problem was binding on_resize() to the '<Configure>' event in the init method, but that causes the program to crash in a cycle of recursions. I'm new to tkinter and GUIs in general. Why is the on_resize() method being bound to '<Configure>' causing the program to crash?
Sorry about the messy code, I'm very much still working on it.
Thanks.
The crash due to recursion is probably because your on_resize method creates new widgets. Here's what's happening:
the widget gets a event which ...
calls on_resize which ...
creates a nested loop which...
creates a single canvas, which ...
causes a configuration change in the widget
you call update inside the nested loop which ...
causes the event to be processed, which ...
calls on_resize, which...
creates a nested loop which ...
creates a canvas which ...
causes a configuration change in the widget
you call update inside the nested loop which ...
causes the event to be processed, which ...
calls on_resize, which ...
...
As a rule of thumb you should never call update unless you are absolutely certain you need to, and even then you need to stop and think hard about it. A rule that should almost never, ever be broken is to call update in a function that is called in response to an event. Every time you call update it's almost as if you're calling mainloop again -- it creates a nested event loop which tries to process all pending events. If the processing of pending events causes new events to be generated, you'll end up with the recursive situation you now find yourself in.
The first fix is to remove the call to update. The second fix is probably to not create new widgets in the handling of the configure event. You really only need to create all of those widgets once. The redraw event only needs to redraw the ovals, because the canvas objects will resize themselves automatically.
My advice is to step back and take it slower. Rewrite your program to just draw the board without worrying about the ovals or game logic. Get the resize behavior of the board working the way you want, and then worry about redrawing all of the ovals.

block pixel shifting in Python/GTK

I want to make a sideways scrolling text box (a "ticker tape" display) using Python 2.6 and gtk+ (as per Centos 6.3).
I have made a timer driven routine that takes a text string and prints it repeatedly while incrementing the offset in the print window. That works but seems to be slightly more processor-intensive than I might like.
Rather than printing the string fully and repeatedly with an incrementing offset - is there a way to use block move acceleration in some way and benefit from the use of "blitting" hardware on most GPU's? I was wondering if the string can be printed to a pixel buffer of some type and then the relevant portion can be "blitted" to screen memory? Any comments or experience would be appreciated.
My target hardware is Intel 945GME based.
My best attempt (so far) at some code to do this follows. No, I am not
very experienced in these things and this is hardly "exemplary code" - but thought I
should share "wot I dun"
import gtk
import pango
import glib
import cairo
class Scroller(gtk.DrawingArea):
def __init__(self, TextString):
super(Scroller, self).__init__()
self.offset_x = 0
self.offset_y = 0
self.screen_x = 1000
self.screen_y = 0
# process the y=text string to make an image of it
self.p = self.create_pango_layout(TextString)
self.p.set_font_description(pango.FontDescription('Sans 36'))
attr = pango.AttrList()
attr.insert(pango.AttrForeground(60535, 60535, 0, 0, -1))
self.p.set_attributes(attr)
self.p.set_width(-1)
self.text_width, self.text_height = self.p.get_size()
self.text_width = self.text_width / pango.SCALE
self.text_height = self.text_height / pango.SCALE
#create a pixmap with the data of the pixbuf
self.pixelbuffer = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, self.text_width, self.text_height)
self.pixelbuffer.fill(0)
pixmap,_ = self.pixelbuffer.render_pixmap_and_mask()
self.pgc = pixmap.new_gc()
pixmap.draw_layout(self.pgc, 0, 0, self.p)
# now copy image back to pixelbuiffer
self.pixelbuffer.get_from_drawable(pixmap, pixmap.get_colormap(), 0, 0, 0, 0, -1, -1)
def initscreen(self):
print "screen initialised"
self.window.clear()
self.update_display()
def update_display(self):
offset = -1
# this scrolls the text sideways
self.screen_x = self.screen_x + offset
self.window.draw_pixbuf(self.pgc, self.pixelbuffer, self.offset_x, self.offset_y, self.screen_x, self.screen_y)
# and repeat until bored.
if self.screen_x == -self.text_width:
self.screen_x =1000
return True
# Main program
class PyApp(gtk.Window):
def __init__(self):
super(PyApp, self).__init__()
self.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(0, 0, 0))
self.connect("destroy", gtk.main_quit)
self.set_title("side scroller")
scroller_screen=Scroller('Breaking News - scrolling text works! More soon....Olympic Games in London are highly entertaining. No more please!')
self.add(scroller_screen)
self.set_size_request(1000, 100)
self.show_all()
scroller_screen.initscreen
# set a 20 millisec scroll refreash timer
glib.timeout_add(20, scroller_screen.update_display)
PyApp()
gtk.main()

Need help working with self.MemoryDC in wxPython

I'm trying to make a custom text widget that is double buffered (In order to avoid flicker).
However, I'd like to be able to do a few things. Yet, I'm unsure of the exact methods I should use.
The first two are easy I simply want to change the background and foreground color.
So more or less I want to be able to change the text color for self.Text in self.Draw().
Snippet:
self.Text = mdc.DrawText(self.TextString, 10, 0)
As sell as the Background (fill) color for self.MemoryDC.
Next, does anyone know how I could center self.Text? Finally, how do I configure self.Text after it has been created?
The widget thus far:
class DynamicText (wx.Panel):
def __init__(self, par):
self.Par = par
wx.Panel.__init__(self, self.Par)
self.Time = Time(self, func=self.SetTime)
self.Dim = self.Par.GetClientSize()
self.SetSize(self.Dim)
self.Bind(wx.EVT_SIZE, self.Resize)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.Erase)
self.Bind(wx.EVT_PAINT, self.Paint)
def Set (self, text) :
self.TextString = text
def SetTime (self, time) :
self.Set(str(time))
self.Resize(None)
def Resize(self, event):
self.Width, self.Height = self.GetSize()
bitmap = wx.EmptyBitmap(self.Width, self.Height)
self.MemoryDC = wx.MemoryDC(bitmap)
''' Redraws **self.MemoryDC** '''
mdc = self.MemoryDC
''' Deletes everything from widget. '''
mdc.Clear()
fs = 11
font = wx.Font( fs, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
mdc.SetFont(font)
self.Draw()
self.Refresh()
def Draw (self) :
mdc = self.MemoryDC
self.Text = mdc.DrawText(self.TextString, 10, 0)
def Erase(self, event):
''' Does nothing, as to avoid flicker. '''
pass
def Paint(self, event):
pdc = wx.PaintDC(self)
w, h = self.MemoryDC.GetSize()
pdc.Blit(0, 0, w, h, self.MemoryDC, 0, 0)
I don't understand what you mean by configuring self.Text after it was created. If you want to change the text after you've drawn it - you can't. Once you've drawn it to the DC it's there, and the only way to change it would be to clear the DC and repaint it. In your case, it seems all you need to do when the text is updated is to call Resize() again, forcing a redraw. Note that DrawText() retruns nothing, so the value of your self.Text would be None. You definitely can't use that to refer to the drawn text. :D
As for the rest, here's an example of a Draw() method that centers the text and paints it blue:
def Draw(self) :
mdc = self.MemoryDC
dc_width, dc_height = mdc.GetSizeTuple()
text_width, text_height, descent, externalLeading = mdc.GetFullTextExtent(self.TextString)
x = (dc_width - text_width) / 2
y = (dc_height - text_height) / 2
mdc.SetTextForeground('Blue')
mdc.DrawText(self.TextString, x, y)

Categories