Re: How do I get X, Y pos and PangoLogAttr of each glyph in a rendered string?

On 04/19/11 16:27, Alex Kerr wrote:
> Hello,

Hi Alex,

> I'm using a really basic Cairo Pango prog as a test bed. It just displays a
> short string on a Cairo surface using a Pango layout, which works fine.
> For each glyph in the string, I now want to get it's X,Y pos (and width and
> height) on the rendered Cairo surface, and the PangoLogAttr data, e.g. so I
> could draw a box around each glyph, or whatever.
> After a lot of reading the API stuff, and googling for examples, I'm not quite
> sure how to tie the API functions together to achieve this. Anyone got any
> suggestions, ideas, or code please?

You definitely can do it with glyphs, but is that really what you want?
There's simpler API in pango to do that per cluster.  Note that you cannot map
a glyph to the original string (and hence PangoLogAttr).  You can only do that
per cluster.

Check PangoLayoutIter.

I'm also attaching Python code that draws boxes around clusters.

Hope that helps,

> Pseudocode for what I'm trying to do would be:
> 1. Render Pango text to Cairo surface (note: could be right-to-left, e.g.
> Arabic, as well as left-to-right) - Done this.
> 2. For each glyph (or ligature) in the line of text:
>     {
>     3. Get X, Y, Width and Height of rendered glyph (or ligature)
>     4. Get the PangoLogAttr structure for the same character
>     5. Move onto the next glyph (/ligature/character) in the line
>     }
> P.S. As a separate side note, I understand glyphs to be the separate
> components of a final rendered character, i.e. the main body and diacritic (if
> present) would be separate glyphs. Each glyph in turn could possibly be made
> up of more than one UTF-8 character. The rendering engine can potentially
> combine certain combinations of glyphs to form a single ligature. Glyphs or
> ligatures can also be referred to as characters (so PangoLogAttr applies to
> ligatures and glyphs?). Have I got this right!?
> Any help much appreciated :)
> Many thanks indeed!
> Alex
> _______________________________________________
> gtk-i18n-list mailing list
> gtk-i18n-list gnome org
# -*- coding:utf8 -*-
# I, Adam Olsen, am the original author of this work.  I hereby
# donate it into the public domain, and relinquish any rights I
# may have in it.
# I, Behdad Esfahbod, hereby disclaim any rights for my contributions
# to this code.

from __future__ import division

import sys
import cairo
import pygtk
import gtk
import gtk.gdk
import pango
import gobject

def generate_modes():
    for justify_desc, justify in [('', False), ('justified ', True)]:
        for align_desc, align in [('left', pango.ALIGN_LEFT),
                ('center', pango.ALIGN_CENTER), ('right', pango.ALIGN_RIGHT)]:
            for extent_desc, extentindex in [('logical', 1), ('ink', 0)]:
                for name in ['line', 'run', 'cluster', 'char']:
                    if name == 'char' and extent_desc == 'ink':
                    desc = '%s%s %s %s' % (justify_desc, align_desc, extent_desc, name)
                    yield extentindex, name, align, justify, desc

class ExtentDemo(gtk.Widget):
    def __init__(self, text="""Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor.\n\tسÙ?اÙ?Û? Ú?Ù? بÙ?Û? Ø®Ù?Ø´Ù? آشÙ?اÛ?Û?... بر Ø¢Ù? Ù?Ù?Ù?تÙ? دÛ?راÙ?â??داÙ? دÛ?راÙ?..."""):
        self.text = "foo"
        self.text = text
	self.all_modes = list(generate_modes())
	self.mode_num = 0;

	self.x_margin = 5
	self.y_margin = 5
	self.x_offset = 0
	self.y_offset = 25
	self.font_size = 24

    def do_realize(self):
        self.set_flags(self.flags() | gtk.REALIZED)

        self.window = gtk.gdk.Window(
            event_mask=self.get_events() | gtk.gdk.EXPOSURE_MASK)

        self.window.set_user_data(self), gtk.STATE_NORMAL)

    def do_unrealize(self):

    def do_size_request(self, requisition):

	width = 800

	layout = self.get_layout(self.get_pango_context())
	layout.set_width (pango.SCALE * (width     - (self.x_offset + 2 * self.x_margin)))
	height = layout.get_pixel_extents ()[1][3] + (self.y_offset + 2 * self.y_margin)

        requisition.width  = width
        requisition.height = height

    def do_expose_event(self, event):
        context = self.window.cairo_create()
        context.rectangle(event.area.x, event.area.y,
                          event.area.width, event.area.height)
        pangocontext = self.get_pango_context()

        self.draw(context, pangocontext)

        return False

    def get_layout (self, pangocontext):
        font = pango.FontDescription()
        font.set_size(self.font_size * pango.SCALE)

        layout = pango.Layout(pangocontext)

	return layout

    def draw(self, context, pangocontext):

    	context.set_source_rgb (1, 1, 1)
    	context.set_source_rgb (0, 0, 0)

	context.translate (self.x_margin, self.y_margin)

        extentindex, name, align, justify, desc = self.all_modes[self.mode_num]

        labellayout = pango.Layout(pangocontext)
        labellayout.set_text('%i: %s' % (self.mode_num + 1, desc))
        context.move_to(0, 0)

	context.translate (self.x_offset, self.y_offset)

	layout = self.get_layout (pangocontext)
        width  = self.allocation.width  - (self.x_offset + 2 * self.x_margin)
        layout.set_width(width * pango.SCALE)

        context.move_to(0, 0)


        context.set_source_rgba(1, 0, 0, 0.5)
    	context.set_line_width (2)
        x, y, width, height = layout.get_pixel_extents()[extentindex]
        context.rectangle(x-1, y-1, width+2, height+2)

        context.set_source_rgba(0, 1, 0, 0.7)
    	context.set_line_width (1)

        li = layout.get_iter()

        while True:
            extents = getattr(li, 'get_%s_extents' % name)()
            if name != 'char':
                extents = extents[extentindex]
            x, y, width, height = self._descale(extents)
            context.rectangle(x+.5, y+.5, width-1, height-1)

            if not getattr(li, 'next_%s' % name)():

    def cycle_mode_forward(self):
        self.mode_num += 1
        if self.mode_num >= len(self.all_modes):
            self.mode_num = 0

    def cycle_mode_backward(self):
        self.mode_num -= 1
        if self.mode_num < 0:
            self.mode_num = len(self.all_modes) - 1

    def key_press_event(self, widget, event):
        if event.string == ' ' or event.keyval == gtk.keysyms.Right:
        elif event.keyval == gtk.keysyms.BackSpace or event.keyval == gtk.keysyms.Left:
        elif event.string == 'q':

    def _descale(self, rect):
        return (i / pango.SCALE for i in rect)

    def run(self):
        window = gtk.Window()
        window.connect("destroy", gtk.main_quit)
        window.connect("key-press-event", self.key_press_event)



def main():
    if len (sys.argv) > 2:
        ed = ExtentDemo(sys.argv[2])
        ed = ExtentDemo()
    if len (sys.argv) > 1:
	mode = int(sys.argv[1])
	while mode > 1:
	    mode -= 1

if __name__ == "__main__":

[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]