# HG changeset patch # User Radek Brich # Date 1318278059 -7200 # Node ID f69a1f0382ce7136ce640485ca1f8c52085e63d5 # Parent b248ef500557396a0e14d80252ac3a0b4f72590d Partial DriverPygame.putch() implementation. Add patch for PyGame implementing font.render_glyph(). Add test for PyGame font module. Add special key definitions to DriverPygame. diff -r b248ef500557 -r f69a1f0382ce extra/pygame_font_render_glyph.patch --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extra/pygame_font_render_glyph.patch Mon Oct 10 22:20:59 2011 +0200 @@ -0,0 +1,109 @@ +# Add Font.render_glyph() method to PyGame. +# +# This is nicer than full string rendering, as Tuikit renders screen +# one character at the time and render_glyph is optimized for caching. +# See documentation for TTF_RenderGlyph_Shaded and TTF_GlyphMetrics. +# +# Radek Brich +diff -rupd pygame-1.9.1release/src/font.c pygame-1.9.1release-my/src/font.c +--- pygame-1.9.1release/src/font.c 2009-06-17 01:18:34.000000000 +0200 ++++ pygame-1.9.1release-my/src/font.c 2011-10-09 23:19:46.000000000 +0200 +@@ -371,6 +371,78 @@ font_render (PyObject* self, PyObject* a + } + + static PyObject* ++font_render_glyph (PyObject* self, PyObject* args) ++{ ++ TTF_Font* font = PyFont_AsFont (self); ++ int aa; ++ PyObject* text, *final; ++ PyObject* fg_rgba_obj, *bg_rgba_obj = NULL; ++ Uint8 rgba[4]; ++ SDL_Surface* surf; ++ SDL_Color foreg, backg; ++ ++ if (!PyArg_ParseTuple (args, "OiO|O", &text, &aa, &fg_rgba_obj, ++ &bg_rgba_obj)) ++ return NULL; ++ ++ if (!RGBAFromColorObj (fg_rgba_obj, rgba)) ++ return RAISE (PyExc_TypeError, "Invalid foreground RGBA argument"); ++ foreg.r = rgba[0]; ++ foreg.g = rgba[1]; ++ foreg.b = rgba[2]; ++ if (bg_rgba_obj) ++ { ++ if (!RGBAFromColorObj (bg_rgba_obj, rgba)) ++ return RAISE (PyExc_TypeError, "Invalid background RGBA argument"); ++ backg.r = rgba[0]; ++ backg.g = rgba[1]; ++ backg.b = rgba[2]; ++ backg.unused = 0; ++ } ++ else ++ { ++ backg.r = 0; ++ backg.g = 0; ++ backg.b = 0; ++ backg.unused = 0; ++ } ++ ++ if (PyUnicode_Check (text) && PyObject_Length (text) == 1) ++ { ++ Py_UNICODE *buf = PyUnicode_AsUnicode (text); ++ Uint16 ch = (Uint16) buf[0]; ++ ++ if (aa) ++ { ++ if (!bg_rgba_obj) ++ surf = TTF_RenderGlyph_Blended (font, ch, foreg); ++ else ++ surf = TTF_RenderGlyph_Shaded (font, ch, foreg, backg); ++ } ++ else ++ surf = TTF_RenderGlyph_Solid (font, ch, foreg); ++ } ++ else ++ return RAISE (PyExc_TypeError, "char must be string of exactly one unicode character"); ++ ++ if (!surf) ++ return RAISE (PyExc_SDLError, TTF_GetError()); ++ ++ if (!aa && bg_rgba_obj) /*turn off transparancy*/ ++ { ++ SDL_SetColorKey (surf, 0, 0); ++ surf->format->palette->colors[0].r = backg.r; ++ surf->format->palette->colors[0].g = backg.g; ++ surf->format->palette->colors[0].b = backg.b; ++ } ++ ++ final = PySurface_New (surf); ++ if (!final) ++ SDL_FreeSurface (surf); ++ return final; ++} ++ ++static PyObject* + font_size (PyObject* self, PyObject* args) + { + TTF_Font* font = PyFont_AsFont (self); +@@ -509,6 +581,7 @@ static PyMethodDef font_methods[] = + + { "metrics", font_metrics, METH_VARARGS, DOC_FONTMETRICS }, + { "render", font_render, METH_VARARGS, DOC_FONTRENDER }, ++ { "render_glyph", font_render_glyph, METH_VARARGS, DOC_FONTRENDERGLYPH }, + { "size", font_size, METH_VARARGS, DOC_FONTSIZE }, + + { NULL, NULL, 0, NULL } +diff -rupd pygame-1.9.1release/src/pygamedocs.h pygame-1.9.1release-my/src/pygamedocs.h +--- pygame-1.9.1release/src/pygamedocs.h 2009-06-19 08:20:25.000000000 +0200 ++++ pygame-1.9.1release-my/src/pygamedocs.h 2011-10-09 23:27:34.000000000 +0200 +@@ -299,6 +299,8 @@ + + #define DOC_FONTRENDER "Font.render(text, antialias, color, background=None): return Surface\ndraw text on a new Surface" + ++#define DOC_FONTRENDERGLYPH "Font.render_glyph(char, antialias, color, background=None): return Surface\ndraw glyph of char on a new Surface" ++ + #define DOC_FONTSIZE "Font.size(text): return (width, height)\ndetermine the amount of space needed to render text" + + #define DOC_FONTSETUNDERLINE "Font.set_underline(bool): return None\ncontrol if text is rendered with an underline" diff -r b248ef500557 -r f69a1f0382ce tests/pygame_font.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/pygame_font.py Mon Oct 10 22:20:59 2011 +0200 @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import pygame + +pygame.init() + +font = pygame.font.SysFont('dejavusansmono', 14) + +print('Size: %s %s' % font.size('Q')) +print('Linesize: %s' % font.get_linesize()) +print('Height: %s' % font.get_height()) +print('Ascent: %s' % font.get_ascent()) +print('Descent: %s' % font.get_descent()) + +chars = '┌─┐└─┘│' +metrics = font.metrics(chars) +print('metrics=(minx, maxx, miny, maxy, advance)') + +for c, m in zip(chars, metrics): + s = font.size(c) + surface = font.render(c, False, (255,255,255), (0,0,0)) + ss = surface.get_size() + ssg = None + if hasattr(font, 'render_glyph'): + surface_glyph = font.render_glyph(c, False, (255,255,255), (0,0,0)) + ssg = surface_glyph.get_size() + print('%s metrics=%s size=%s surface_size=%s glyph_size=%s' % (c, m, s, ss, ssg)) + diff -r b248ef500557 -r f69a1f0382ce tuikit/driver_curses.py --- a/tuikit/driver_curses.py Sun Oct 09 19:30:25 2011 +0200 +++ b/tuikit/driver_curses.py Mon Oct 10 22:20:59 2011 +0200 @@ -306,7 +306,7 @@ def process_mouse(self): try: - id, x, y, z, bstate = curses.getmouse() + _id, x, y, _z, bstate = curses.getmouse() except curses.error: return [] diff -r b248ef500557 -r f69a1f0382ce tuikit/driver_pygame.py --- a/tuikit/driver_pygame.py Sun Oct 09 19:30:25 2011 +0200 +++ b/tuikit/driver_pygame.py Mon Oct 10 22:20:59 2011 +0200 @@ -5,7 +5,90 @@ import logging from tuikit.driver import Driver -from tuikit.common import Size +from tuikit.common import Coords, Size + + +class CharCache: + + '''CharCache should implement character cache, as the name suggests. + + Currently it does not, characters ale rendered directly from TTF. + + ''' + + def __init__(self): + self.font = pygame.font.SysFont('dejavusansmono,liberationmono,freemono', 14) + # get advance of some random char (all should be same in monospace font) + advance = self.font.metrics('Q')[0][4] + height = self.font.get_height() + self.charsize = Size(advance, height) + self.ascent = self.font.get_ascent() + + # choose self.render() implementation + if hasattr(self.font, 'render_glyph'): + self.render = self.render_glyph + else: + self.render = self.render_noglyph + + def render_glyph(self, screen, x, y, c, fgcolor, bgcolor): + '''Render using render_glyph and metrics. + + This is the correct way, but the output seems same as of render_noglyph + and this implementation requires patching PyGame to work. + + This implements render() method. See render_noglyph for other implementation. + + ''' + # render character, get metrics + surface = self.font.render_glyph(c, True, fgcolor, bgcolor) + metrics = self.font.metrics(c)[0] + minx, maxx, miny, maxy, advance = metrics + height, ascent = self.charsize.h, self.ascent + + # clips origin and area of rendered character according to metrics + startx, starty = 0, 0 + if minx < 0: + startx = abs(minx) + minx = 0 + if maxy > ascent: + starty = maxy - ascent + maxy -= starty + if ascent - miny > height: + miny = ascent - height + if maxx > advance: + maxx = advance + + # draw background + dest = Coords(x * self.charsize.w, y * self.charsize.h) + screen.fill(bgcolor, pygame.Rect(dest.x, dest.y, self.charsize.w, self.charsize.h)) + + # draw character + dest.x += minx + dest.y += ascent - maxy + area = pygame.Rect(startx, starty, maxx - minx, maxy - miny) + screen.blit(surface, tuple(dest), area) + + def render_noglyph(self, screen, x, y, c, fgcolor, bgcolor): + '''Render character using normal text rendering. + + This implements render() method. See render_glyph for other implementation. + + ''' + # render character, get metrics + surface = self.font.render(c, True, fgcolor, bgcolor) + metrics = self.font.metrics(c)[0] + minx = metrics[0] + startx = 0 + if minx < 0: + startx = abs(minx) + + # draw background + dest = Coords(x * self.charsize.w, y * self.charsize.h) + screen.fill(bgcolor, pygame.Rect(dest.x, dest.y, self.charsize.w, self.charsize.h)) + + # draw character + area = pygame.Rect(startx, 0, self.charsize.w, self.charsize.h) + screen.blit(surface, tuple(dest), area) class DriverPygame(Driver): @@ -13,7 +96,35 @@ '''PyGame driver class.''' key_map = { - pygame.K_ESCAPE : 'escape', + pygame.K_ESCAPE : 'escape', + pygame.K_TAB : 'tab', + pygame.K_RETURN : 'enter', + pygame.K_BACKSPACE : 'backspace', + pygame.K_F1 : 'f1', + pygame.K_F2 : 'f2', + pygame.K_F3 : 'f3', + pygame.K_F4 : 'f4', + pygame.K_F5 : 'f5', + pygame.K_F6 : 'f6', + pygame.K_F7 : 'f7', + pygame.K_F8 : 'f8', + pygame.K_F9 : 'f9', + pygame.K_F10 : 'f10', + pygame.K_F11 : 'f11', + pygame.K_F12 : 'f12', + pygame.K_INSERT : 'insert', + pygame.K_DELETE : 'delete', + pygame.K_HOME : 'home', + pygame.K_END : 'end', + pygame.K_PAGEUP : 'pageup', + pygame.K_PAGEDOWN : 'pagedown', + pygame.K_UP : 'up', + pygame.K_DOWN : 'down', + pygame.K_LEFT : 'left', + pygame.K_RIGHT : 'right', + pygame.K_PRINT : 'print', + pygame.K_SCROLLOCK : 'scrollock', + pygame.K_PAUSE : 'pause', } color_map = { @@ -32,11 +143,16 @@ Driver.__init__(self) self.log = logging.getLogger('tuikit') self.screen = None - self.size.w, self.size.h = 80, 25 # screen size in characters - self.charsize = Size(8, 16) # character size in pixels + self.size.w, self.size.h = 120, 40 # screen size in characters + self.charcache = None + self.charsize = Size(16, 8) # character size in pixels + + self.colorprefix = [] # stack of color prefixes def start(self, mainfunc): pygame.init() + self.charcache = CharCache() + self.charsize = self.charcache.charsize mode = self.size.w * self.charsize.w, self.size.h * self.charsize.h self.screen = pygame.display.set_mode(mode) mainfunc() @@ -55,10 +171,10 @@ elif ev.type == pygame.MOUSEBUTTONDOWN: pass elif ev.type == pygame.KEYDOWN: - if ev.unicode: + if ev.key in self.key_map: + events.append(('keypress', self.key_map[ev.key], None)) + elif ev.unicode: events.append(('keypress', None, ev.unicode)) - elif ev.key in self.key_map: - events.append(('keypress', self.key_map[ev.key], None)) elif ev.type == pygame.VIDEORESIZE: #self.size.h, self.size.w = self.screen.getmaxyx() events.append(('resize',)) @@ -78,13 +194,9 @@ def putch(self, x, y, c): if not self.clipstack.test(x, y): return -# try: -# if isinstance(c, str) and len(c) == 1: -# self.screen.addstr(y, x, c) -# else: -# self.screen.addch(y, x, c) -# except curses.error: -# pass + fgcolor = (255,255,255) + bgcolor = (70,70,70) + self.charcache.render(self.screen, x, y, c, fgcolor, bgcolor) def commit(self): '''Commit changes to the screen.''' @@ -107,6 +219,12 @@ def popcolor(self): '''Remove color from top of stack and use new top color for following output.''' + def pushcolorprefix(self, name): + self.colorprefix.append(name) + + def popcolorprefix(self): + self.colorprefix.pop() + ## cursor ## diff -r b248ef500557 -r f69a1f0382ce tuikit/layout.py --- a/tuikit/layout.py Sun Oct 09 19:30:25 2011 +0200 +++ b/tuikit/layout.py Mon Oct 10 22:20:59 2011 +0200 @@ -104,7 +104,7 @@ for child in self._getchildren(): if coln == 0: row = [] - for i in range(self.numcols): + for _i in range(self.numcols): row.append({'widget': None, 'colspan': 0, 'rowspan': 0}) colspan = 1