Ticket #3772: 0001-Port-to-Cairo.2.patch

File 0001-Port-to-Cairo.2.patch, 20.8 KB (added by humitos, 12 years ago)
  • balloongame.py

    From 15637e1a2b112ad5f67a498d837a97832b679887 Mon Sep 17 00:00:00 2001
    From: Manuel Kaufmann <humitos@gmail.com>
    Date: Wed, 25 Jul 2012 10:54:15 -0300
    Subject: [PATCH TypingTurtle 1/2] Port to Cairo
    
    This will ease the port to GTK+ 3
    
    Signed-off-by: Manuel Kaufmann <humitos@gmail.com>
    ---
     balloongame.py | 151 ++++++++++++++++++++++++++++------------------
     keyboard.py    | 187 +++++++++++++++++++++++++++------------------------------
     titlescene.py  |  44 ++++++++------
     3 files changed, 206 insertions(+), 176 deletions(-)
    
    diff --git a/balloongame.py b/balloongame.py
    index 56a6a34..7224893 100644
    a b  
    1414# You should have received a copy of the GNU General Public License
    1515# along with Typing Turtle.  If not, see <http://www.gnu.org/licenses/>.
    1616
     17import math
    1718import random, datetime
     19import pangocairo
     20
    1821from gettext import gettext as _
    1922
    2023import gobject, pygtk, gtk, pango
    class BalloonGame(gtk.VBox): 
    192195 
    193196        return True
    194197
    195     def draw_results(self, gc):
     198    def draw_results(self, cr):
    196199        # Draw background.
    197200        w = self.bounds.width - 400
    198201        h = self.bounds.height - 200
    199202        x = self.bounds.width/2 - w/2
    200203        y = self.bounds.height/2 - h/2
    201204
    202         gc.foreground = self.area.get_colormap().alloc_color(50000,50000,50000)
    203         self.area.window.draw_rectangle(gc, True, x, y, w, h)
    204         gc.foreground = self.area.get_colormap().alloc_color(0,0,0)
    205         self.area.window.draw_rectangle(gc, False, x, y, w, h)
     205        cr.set_source_rgb(0.762, 0.762, 0.762)
     206        cr.rectangle(x, y, w, h)
     207        cr.fill()
    206208
    207         # Draw text
    208         gc.foreground = self.area.get_colormap().alloc_color(0,0,0)
     209        cr.set_source_rgb(0, 0, 0)
     210        cr.rectangle(x, y, w, h)
     211        cr.stroke()
    209212
     213        # Draw text
    210214        title = _('You finished!') + '\n'
    211         layout = self.area.create_pango_layout(title)
    212         layout.set_font_description(pango.FontDescription('Serif Bold 16'))   
    213         size = layout.get_size()
    214         tx = x+w/2-(size[0]/pango.SCALE)/2
     215
     216        pango_cr = pangocairo.CairoContext(cr)
     217        pango_cr.set_source_rgb(0, 0, 0)
     218        pango_layout = cr.create_layout()
     219        pango_layout.set_font_description(pango.FontDescription('Serif Bold 16'))
     220        pango_layout.set_text(title)
     221        size = pango_layout.get_size()
     222        tx = x + (w / 2) - (size[0] / pango.SCALE) / 2
    215223        ty = y + 100
    216         self.area.window.draw_layout(gc, tx, ty, layout)
     224        pango_cr.move_to(tx, ty)
     225        pango_cr.show_layout(pango_layout)
     226        pango_cr.stroke()
    217227
    218228        report = ''
    219229        report += _('Your score was %(score)d.') % { 'score': self.score } + '\n'
    class BalloonGame(gtk.VBox): 
    222232        report += '\n'
    223233        report += _('Press the ENTER key to continue.')
    224234   
    225         layout = self.area.create_pango_layout(report)
    226         layout.set_font_description(pango.FontDescription('Times 12'))   
    227         size = layout.get_size()
    228         tx = x+w/2-(size[0]/pango.SCALE)/2
    229         ty = y + 200
    230         self.area.window.draw_layout(gc, tx, ty, layout)
     235        pango_cr = pangocairo.CairoContext(cr)
     236        pango_cr.set_source_rgb(0, 0, 0)
     237        pango_layout = cr.create_layout()
     238        pango_layout.set_font_description(pango.FontDescription('Times 12'))
     239        pango_layout.set_text(report)
     240        size = pango_layout.get_size()
     241        sx = x + w / 2 - (size[0] / pango.SCALE) / 2
     242        sy = y + 200
     243        pango_cr.move_to(sx, sy)
     244        pango_cr.show_layout(pango_layout)
     245        pango_cr.stroke()
     246
    231247
    232248    def finish_game(self):
    233249        self.finished = True
    class BalloonGame(gtk.VBox): 
    290306        h = int(b.size*1.5 + 10)
    291307        self.area.queue_draw_area(x, y, w, h)
    292308
    293     def draw_balloon(self, gc, b):
     309    def draw_balloon(self, cr, b):
    294310        x = int(b.x)
    295311        y = int(b.y)
    296        
     312
    297313        # Draw the string.
    298         gc.foreground = self.area.get_colormap().alloc_color(0,0,0)
    299         self.area.window.draw_line(gc,
    300             int(b.x), int(b.y+b.size/2),
    301             int(b.x), int(b.y+b.size))
    302        
     314        cr.set_source_rgb(0, 0, 0)
     315        cr.move_to(int(b.x), int(b.y + b.size / 2))
     316        cr.line_to(int(b.x), int(b.y + b.size))
     317        cr.stroke()
     318
    303319        # Draw the balloon.
    304         gc.foreground = self.area.get_colormap().alloc_color(b.color[0],b.color[1],b.color[2])
    305         self.area.window.draw_arc(gc, True, x-b.size/2, y-b.size/2, b.size, b.size, 0, 360*64)
    306    
    307         # Draw the text.
    308         gc.foreground = self.area.get_colormap().alloc_color(0,0,0)
    309         layout = self.area.create_pango_layout(b.word)
    310         layout.set_font_description(pango.FontDescription('Sans 12'))   
    311         size = layout.get_size()
    312         tx = x-(size[0]/pango.SCALE)/2
    313         ty = y-(size[1]/pango.SCALE)/2
    314         self.area.window.draw_layout(gc, tx, ty, layout)
    315    
     320        cr.save()
     321        cr.set_source_rgb(b.color[0], b.color[1], b.color[2])
     322        cr.arc(b.x, b.y, b.size / 2, 0, 2 * math.pi)
     323        cr.fill()
     324        cr.restore()
     325
     326        pango_cr = pangocairo.CairoContext(cr)
     327        pango_cr.set_source_rgb(0, 0, 0)
     328        pango_layout = cr.create_layout()
     329        pango_layout.set_font_description(pango.FontDescription('Sans 12'))
     330        pango_layout.set_text(unicode(b.word))
     331        size = pango_layout.get_size()
     332        x = x - (size[0] / pango.SCALE) / 2
     333        y = y - (size[1] / pango.SCALE) / 2
     334        pango_cr.move_to(x, y)
     335        pango_cr.show_layout(pango_layout)
     336        pango_cr.stroke()
     337
    316338    def add_score(self, num):
    317339        self.score += num
    318340        self.queue_draw_score()
    class BalloonGame(gtk.VBox): 
    325347        y = 20
    326348        self.queue_draw_area(x, y, x+size[0], y+size[1])
    327349
    328     def draw_score(self, gc):
    329         layout = self.area.create_pango_layout(_('SCORE: %d') % self.score)
    330         layout.set_font_description(pango.FontDescription('Times 14'))   
    331         size = layout.get_size()
    332         x = self.bounds.width-20-size[0]/pango.SCALE
     350    def draw_score(self, cr):
     351        pango_cr = pangocairo.CairoContext(cr)
     352        pango_cr.set_source_rgb(0, 0, 0)
     353        pango_layout = cr.create_layout()
     354        pango_layout.set_font_description(pango.FontDescription('Times 14'))
     355        pango_layout.set_text(_('SCORE: %d') % self.score)
     356        size = pango_layout.get_size()
     357        x = self.bounds.width - 20 - size[0] / pango.SCALE
    333358        y = 20
    334         self.area.window.draw_layout(gc, x, y, layout)
     359        pango_cr.move_to(x, y)
     360        pango_cr.show_layout(pango_layout)
     361        pango_cr.stroke()
    335362
    336     def draw_instructions(self, gc):
     363    def draw_instructions(self, cr):
    337364        # Draw instructions.
    338         gc.foreground = self.area.get_colormap().alloc_color(0,0,0)
    339 
    340         layout = self.area.create_pango_layout(_('Type the words to pop the balloons!'))
    341         layout.set_font_description(pango.FontDescription('Times 14'))   
    342         size = layout.get_size()
    343         x = (self.bounds.width - size[0]/pango.SCALE)/2
    344         y = self.bounds.height-20 - size[1]/pango.SCALE
    345         self.area.window.draw_layout(gc, x, y, layout)
     365        pango_cr = pangocairo.CairoContext(cr)
     366        pango_cr.set_source_rgb(0, 0, 0)
     367        pango_layout = cr.create_layout()
     368        pango_layout.set_font_description(pango.FontDescription('Times 14'))
     369        pango_layout.set_text(_('Type the words to pop the balloons!'))
     370        size = pango_layout.get_size()
     371        x = (self.bounds.width - size[0] / pango.SCALE) / 2
     372        y = self.bounds.height - 20 - size[1] / pango.SCALE
     373        pango_cr.move_to(x, y)
     374        pango_cr.show_layout(pango_layout)
     375        pango_cr.stroke()
    346376
    347377    def draw(self):
    348378        self.bounds = self.area.get_allocation()
    349379
    350         gc = self.area.window.new_gc()
    351        
     380        cr = self.area.window.cairo_create()
     381
    352382        # Draw background.
    353         gc.foreground = self.area.get_colormap().alloc_color(60000,60000,65535)
    354         self.area.window.draw_rectangle(gc, True, 0, 0, self.bounds.width, self.bounds.height)
     383        cr.set_source_rgb(0.915, 0.915, 1)
     384        cr.rectangle(0, 0, self.bounds.width, self.bounds.height)
     385        cr.fill()
    355386
    356387        # Draw the balloons.
    357388        for b in self.balloons:
    358             self.draw_balloon(gc, b)
     389            self.draw_balloon(cr, b)
    359390
    360391        if self.finished:
    361             self.draw_results(gc)
     392            self.draw_results(cr)
    362393
    363394        else:
    364             self.draw_instructions(gc)
     395            self.draw_instructions(cr)
    365396
    366             self.draw_score(gc)
     397            self.draw_score(cr)
    367398
    368399    def expose_cb(self, area, event):
    369400        self.draw()
  • keyboard.py

    diff --git a/keyboard.py b/keyboard.py
    index 35daeed..25e870e 100644
    a b  
    1616#!/usr/bin/env python
    1717# vi:sw=4 et
    1818
    19 import pygtk
    20 pygtk.require('2.0')
    2119import gtk
     20import cairo
     21import copy
    2222import rsvg
    2323import os, glob, re
    2424import pango
     25import pangocairo
     26import StringIO
    2527from port import json
    2628import subprocess
    2729from layouts.olpc import OLPC_LAYOUT
    class KeyboardImages: 
    130132            scale_width = int(scale_width * 1.1625)
    131133
    132134        for filename in glob.iglob('images/OLPC*.svg'):
    133             image = gtk.gdk.pixbuf_new_from_file_at_scale(filename, scale_width,
    134                                                           self.height, False)
     135            image = rsvg.Handle(file=filename)
    135136            name = os.path.basename(filename)
    136137            self.images[name] = image
    137138
    class KeyboardWidget(KeyboardData, gtk.DrawingArea): 
    383384            k['key-width'] = int(k['key-width'] * width_scale)
    384385            k['key-height'] = int(k['key-height'] * height_scale)
    385386
    386         self._make_all_key_images()
    387 
    388     def _make_key_images(self, key):
    389         key['key-images'] = {}
    390         for group in [0, 1]:
    391             for state in [0, gtk.gdk.SHIFT_MASK, gtk.gdk.MOD5_MASK, gtk.gdk.SHIFT_MASK|gtk.gdk.MOD5_MASK]:
    392                 key['key-images'][(state, group)] = self.get_key_image(key, state, group)
     387    def _draw_key(self, k, cr):
     388        bounds = self.get_allocation()
    393389
    394     def _make_all_key_images(self):
    395         for key in self.keys:
    396             self._make_key_images(key)
     390        # HACK: this is a hack used when the widget is not shown yet,
     391        # in that case bounds will be gtk.gdk.Rectangle(-1, -1, 1, 1)
     392        # and the key will be outside the canvas. This is used only
     393        # for the first key that appears below the instructions
     394        if bounds.x == -1:
     395            screen_x = screen_y = 0
     396        else:
     397            screen_x = int(bounds.width - self.image.width) / 2
     398            screen_y = int(bounds.height - self.image.height) / 2
    397399
    398     def _draw_key(self, k, draw, gc, for_pixmap, w=0, h=0):
    399         x1 = 0
    400         y1 = 0
    401         x2 = w
    402         y2 = h
     400        x1 = k['key-x'] + screen_x
     401        y1 = k['key-y'] + screen_y
     402        x2 = x1 + k['key-width']
     403        y2 = y1 + k['key-height']
    403404
    404         # Outline rounded box.
    405         gc.foreground = self.get_colormap().alloc_color(int(0.4*65536),int(0.7*65536),int(0.4*65536))
    406        
    407405        corner = 5
    408406        points = [
    409             (x1 + corner, y1), 
     407            (x1 + corner, y1),
    410408            (x2 - corner, y1),
    411409            (x2, y1 + corner),
    412410            (x2, y2 - corner),
    class KeyboardWidget(KeyboardData, gtk.DrawingArea): 
    414412            (x1 + corner, y2),
    415413            (x1, y2 - corner),
    416414            (x1, y1 + corner)
    417         ]
    418         draw.draw_polygon(gc, True, points)
    419        
    420         # Inner text.
    421         gc.foreground = self.get_colormap().alloc_color(int(1.0*65536),int(1.0*65536),int(1.0*65536))
     415            ]
     416
     417        cr.save()
     418        cr.new_path()
     419        cr.set_source_rgb(0.396, 0.698, 0.392)
     420        cr.set_line_width(2)
     421        cr.move_to(*points[0])
     422        for point in points:
     423            cr.line_to(*point)
     424        cr.line_to(*points[0])
     425        cr.close_path()
     426        cr.fill_preserve()
     427        cr.stroke()
     428        cr.restore()
    422429
    423430        text = ''
    424431        if k['key-label']:
    425432            text = k['key-label']
    426433        else:
    427             text = self.get_letter_for_key_state_group(k, self.active_state, self.active_group)
    428        
    429         try:
    430             layout = self.create_pango_layout(unicode(text))
    431             layout.set_font_description(pango.FontDescription('Monospace'))
    432             draw.draw_layout(gc, x1+8, y2-23, layout)
    433         except:
    434             pass
    435 
    436     def _expose_hands(self, gc):
     434            text = self.get_letter_for_key_state_group(
     435                k, self.active_state, self.active_group)
     436
     437        pango_context = pangocairo.CairoContext(cr)
     438        pango_context.set_source_rgb(0, 0, 0)
     439
     440        pango_layout = pango_context.create_layout()
     441        pango_layout.set_font_description(pango.FontDescription('Monospace'))
     442        pango_layout.set_text(unicode(text))
     443
     444        pango_context.move_to(x1 + 8, y2 - 23)
     445        pango_context.show_layout(pango_layout)
     446        cr.stroke()
     447
     448    def _expose_hands(self, cr):
    437449        lhand_image = self.image.images['OLPC_Lhand_HOMEROW.svg']
    438450        rhand_image = self.image.images['OLPC_Rhand_HOMEROW.svg']
    439451
    class KeyboardWidget(KeyboardData, gtk.DrawingArea): 
    459471
    460472                # TODO: Do something about ALTGR.
    461473
    462         bounds = self.get_allocation()
    463         screen_x = int(bounds.width-self.image.width)/2
    464         screen_y = int(bounds.height-self.image.height)/2
     474        # bounds = self.get_allocation()
     475        # screen_x = int(bounds.width-self.image.width)/2
     476        # screen_y = int(bounds.height-self.image.height)/2
     477
     478        # README: these values (cairo.Matrix) are taken seeing the image on the
     479        # screen, I think we should find a way to calculate them
     480        cr.save()
     481        matrix = cairo.Matrix(xx=0.3, yy=0.2, x0=10, y0=-20)
     482        cr.transform(matrix)
     483        lhand_image.render_cairo(cr)
    465484
    466         self.window.draw_pixbuf(gc, lhand_image, 0, 0, screen_x, screen_y + HAND_YOFFSET)
    467         self.window.draw_pixbuf(gc, rhand_image, 0, 0, screen_x, screen_y + HAND_YOFFSET)
     485        cr.restore()
     486        matrix = cairo.Matrix(xx=0.325, yy=0.2, x0=-5, y0=-20)
     487        cr.transform(matrix)
     488        rhand_image.render_cairo(cr)
    468489
    469490    def _expose_cb(self, area, event):
    470         gc = self.window.new_gc()
    471        
    472         bounds = self.get_allocation()
    473         screen_x = int(bounds.width-self.image.width)/2
    474         screen_y = int(bounds.height-self.image.height)/2
     491        cr = self.window.cairo_create()
    475492
    476493        # Draw the keys.
    477494        for k in self.keys:
    478             x1 = k['key-x'] + screen_x
    479             y1 = k['key-y'] + screen_y
    480             x2 = x1 + k['key-width']
    481             y2 = y1 + k['key-height']
    482 
    483             # Index cached key images by state and group.
    484             state = self.active_state & (gtk.gdk.SHIFT_MASK|gtk.gdk.MOD5_MASK)
    485             index = (state, self.active_group)
    486             image = k['key-images'].get(index)
    487 
    488             if image:
    489                 self.window.draw_image(gc, image, 0, 0, x1, y1, x2-x1, y2-y1)
    490        
     495            self._draw_key(k, cr)
     496
    491497        # Draw overlay images.
    492498        if self.draw_hands:
    493             self._expose_hands(gc)
    494        
     499            self._expose_hands(cr)
    495500        return True
    496501
    497502    def key_press_release_cb(self, widget, event):
    class KeyboardWidget(KeyboardData, gtk.DrawingArea): 
    512517            sig = self.format_key_sig(event.hardware_keycode, event.state, event.group)
    513518            if not self.letter_map.has_key(sig):
    514519                self.letter_map[sig] = event.string
    515                 self._make_key_images(key)
    516520                self.queue_draw()
    517521
    518522        return False
    519523
    520     def _keys_changed_cb(self, keymap):
    521         self._make_key_images()
    522 
    523524    def clear_hilite(self):
    524525        self.hilite_letter = None
    525526        self.queue_draw()
    class KeyboardWidget(KeyboardData, gtk.DrawingArea): 
    535536    def get_key_pixbuf(self, key, state=0, group=0, scale=1):
    536537        w = int(key['key-width'] * scale)
    537538        h = int(key['key-height'] * scale)
    538        
     539
    539540        old_state, old_group = self.active_state, self.active_group
    540541        self.active_state, self.active_group = state, group
    541        
    542         pixmap = gtk.gdk.Pixmap(self.root_window.window, w, h)
    543         gc = pixmap.new_gc()
    544        
    545         gc.foreground = self.get_colormap().alloc_color('#d0d0d0')
    546         pixmap.draw_rectangle(gc, True, 0, 0, w, h)
    547542
    548         self._draw_key(key, pixmap, gc, True, w, h)
    549        
    550         pb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, w, h)
    551         pb.get_from_drawable(pixmap, self.root_window.window.get_colormap(), 0, 0, 0, 0,w, h)
    552        
    553         self.active_state, self.active_group = old_state, old_group
     543        surface = cairo.ImageSurface(cairo.FORMAT_RGB24, w, h)
     544        cr = cairo.Context(surface)
     545        cr.set_source_rgb(1, 1, 1)
     546        cr.rectangle(0, 0, w, h)
     547        cr.fill()
    554548
    555         return pb
     549        # Duplicate the Key to be able to change its position values
     550        key = copy.deepcopy(key)
     551        key['key-x'] = 0
     552        key['key-y'] = 0
    556553
    557     def get_key_image(self, key, state=0, group=0, scale=1):
    558         w = int(key['key-width'] * scale)
    559         h = int(key['key-height'] * scale)
    560        
    561         old_state, old_group = self.active_state, self.active_group
    562         self.active_state, self.active_group = state, group
    563        
    564         pixmap = gtk.gdk.Pixmap(self.root_window.window, w, h)
    565         gc = pixmap.new_gc()
    566        
    567         gc.foreground = self.get_colormap().alloc_color('#d0d0d0')
    568         pixmap.draw_rectangle(gc, True, 0, 0, w, h)
     554        self._draw_key(key, cr)
     555
     556        # Convert cairo.Surface to Pixbuf
     557        pixbuf_data = StringIO.StringIO()
     558        surface.write_to_png(pixbuf_data)
     559        pxb_loader = gtk.gdk.PixbufLoader(image_type='png')
     560        pxb_loader.write(pixbuf_data.getvalue())
     561        temp_pix = pxb_loader.get_pixbuf()
     562        pxb_loader.close()
    569563
    570         self._draw_key(key, pixmap, gc, True, w, h)
    571        
    572         image = pixmap.get_image(0, 0, w, h)
    573        
    574564        self.active_state, self.active_group = old_state, old_group
    575565
    576         return image
    577    
     566        return temp_pix
  • titlescene.py

    diff --git a/titlescene.py b/titlescene.py
    index 7cc2d68..4dc6ae4 100644
    a b from gettext import gettext as _ 
    2020
    2121# Import PyGTK.
    2222import gobject, pygtk, gtk, pango
     23import pangocairo
     24
    2325
    2426class TitleScene(gtk.DrawingArea):
    2527    # Maximum portion of the screen the background can fill vertically.
    2628    BACKGROUND_HEIGHT_RATIO = 0.6
    2729
    2830    # Border from top right of screen to draw title at.
    29     TITLE_OFFSET = (20, 30)
     31    TITLE_OFFSET = (20, 50)
    3032
    3133    # Font used to display the title.
    3234    TITLE_FONT = 'Times 45'
    class TitleScene(gtk.DrawingArea): 
    5254
    5355    def expose_cb(self, area, event):
    5456        bounds = self.get_allocation()
    55        
    56         gc = self.get_style().fg_gc[gtk.STATE_NORMAL]
     57
     58        cr = self.window.cairo_create()
    5759
    5860        # Background picture.
    5961        x = (bounds.width - self.backgroundpixbuf.get_width())/2
    60         self.window.draw_pixbuf(
    61             gc, self.backgroundpixbuf, 0, 0,
    62             x, 0, self.backgroundpixbuf.get_width(), self.backgroundpixbuf.get_height())
    63         pc = self.create_pango_context()
    64        
    65         self.layout = self.create_pango_layout('')
    66         self.layout.set_font_description(pango.FontDescription(TitleScene.TITLE_FONT))
    67        
    68         self.layout.set_text(self.title_original)
    69         original_size = self.layout.get_size()
    70         self.x_text = (bounds.width-original_size[0]/pango.SCALE)-TitleScene.TITLE_OFFSET[0]
     62        cr.set_source_pixbuf(self.backgroundpixbuf, 0, 0)
     63        cr.rectangle(x, 0, self.backgroundpixbuf.get_width(),
     64                     self.backgroundpixbuf.get_height())
     65        cr.paint()
     66
     67        cr = pangocairo.CairoContext(cr)
     68        cr.set_source_rgb(0, 0, 0)
     69        self.pango_layout = cr.create_layout()
     70        self.pango_layout.set_font_description(
     71            pango.FontDescription(TitleScene.TITLE_FONT))
     72        self.pango_layout.set_text(unicode(self.title_original))
     73
     74        original_size = self.pango_layout.get_size()
     75        self.x_text = (bounds.width - original_size[0] / pango.SCALE) - \
     76            TitleScene.TITLE_OFFSET[0]
    7177        self.y_text = TitleScene.TITLE_OFFSET[1]
     78
    7279        gobject.timeout_add(50, self.timer_cb)
    7380
    7481    def draw_text(self):
    7582        # Animated Typing Turtle title.
    76         gc = self.get_style().fg_gc[gtk.STATE_NORMAL]
    77         self.layout.set_text(self.title_text)
    78         self.window.draw_layout(gc, self.x_text, self.y_text, self.layout)
     83        cr = self.window.cairo_create()
     84
     85        cr.move_to(self.x_text, self.y_text)
     86        self.pango_layout.set_text(unicode(self.title_text))
     87        cr.show_layout(self.pango_layout)
     88        cr.stroke()
    7989
    8090    def timer_cb(self):
    8191        if len(self.title_src) > 0: