How to create a letter spacing attribute with pycairo?
Asked Answered
O

1

40

I'm using Pango + Cairo (through GObject) to render text with python3.7, and would like to set the letter spacing by creating an attribute and attaching that attribute to my pango layout.

In the gnome documentation for pango, I can see that there should be a function called pango_attr_letter_spacing_new (since v1.6). However, if I run Pango.attr_letter_spacing_new, I get the error:

AttributeError: 'gi.repository.Pango' object has no attribute 'attr_letter_spacing_new'

This feels a bit strange, since I can use the pango_attr_type_get_name which should only have been available since v1.22.

I have a work-around by using markup with <span letter_spacing="1234"> but I would rather not go down this route.

Minimal "Working" Example

# pip install pycairo==1.18.0 pygobject==3.32.0

import cairo
import gi
gi.require_version('Pango', '1.0')
gi.require_version('PangoCairo', '1.0')
from gi.repository import Pango, PangoCairo

width, height = 328, 48

surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
context = cairo.Context(surface)
layout = PangoCairo.create_layout(context)

font_desc = Pango.font_description_from_string('Sans, 40px')
layout.set_font_description(font_desc)

# What I can do
layout.set_markup(f'<span letter_spacing="{1024 * 10}">Hello World</span>')

# What I would like to do
if False:
    letter_spacing_attr = Pango.attr_letter_spacing_new(1024 * 10)

    attr_list = Pango.AttrList()
    attr_list.insert(letter_spacing_attr)
    layout.set_attributes(attr_list)

    layout.set_text('Hello World')

PangoCairo.show_layout(context, layout)

with open('help-me.png', 'wb') as image_file:
    surface.write_to_png(image_file)

Manually creating a LetterSpacing attribute

I have been able to find the enum value Pango.AttrType.LETTER_SPACING, which allows me to do something like this:

c = Pango.AttrClass()
c.type = Pango.AttrType.LETTER_SPACING
a = Pango.Attribute()
a.init(c)

However, I haven't been able to find a way to set the value of it, and it makes me think it is the wrong way to approach things :|

Insert this into an Pango.AttrList, gave an error (not surprisingly) and made the python process segfault next time I did something with Pango:

** (process:17183): WARNING **: 12:00:56.985: (gi/pygi-struct-marshal.c:287):pygi_arg_struct_from_py_marshal: runtime check failed: (g_type_is_a (g_type, G_TYPE_VARIANT) || !is_pointer || transfer == GI_TRANSFER_NOTHING)

Other leads

.. that sadly have lead nowhere :(

  • pygtk listing a function pango.AttrLetterSpacing
    • Pango.AttrLetterSpacing => 'gi.repository.Pango' object has no attribute 'AttrLetterSpacing'
    • Pango.Attrbute.LetterSpacing => type object 'Attribute' has no attribute 'LetterSpacing'
  • the documentation for the pango packge for Vala (which seems to use GObject as well), also shows the attr_letter_spacing_new function -- this doesn't really help that much, but suggests that the function should be available through GObject, although I haven't tried.
Observe answered 5/4, 2019 at 10:22 Comment(8)
Looking at the pygobject documentation from this page it appears that the attr_letter_spacing_new function is unavailable without a python equivalent. This matches what was said in this question as something lost in the pygtk -> pygobject transition.Carcinoma
Some further research that might possibly lead to a solution. The pitivi video editor project uses PyGObject. They apparently work around the defective Pango.AttrList class as shown here.Carcinoma
@Carcinoma That's interesting, though they are pretty much reimplement it completely ("FIXME Fix Pango so we do NOT need that dirty reimplementation Pango.AttrList"). The <span> workaround suggested in the original question looks way cleaner (especially for the case when you need just this attribute and not all of them).Conciliar
@MichalČihař Absolutely, just wanted to present what was out there. It probably would be best to use the "span" workaround. .format() syntax could always be used if you want to make a function where those numbers are passed into the span.Carcinoma
There is a significant problem with using markup, which is that you need to ensure the input is escaped. In our case, the input comes directly from the user, so that's why I wanted an other method :)Observe
@Observe is this still an issue? I have a similar problem and are tentatively going down the markup route. This seems to work well (assuming data is escaped correctly). My current problem is that the Pango bindings leaks refcounts.Commentate
@RasmusWL, can you tell which version of Pango you have installed? I just tried your example (setting the if marked with What I would like to do to True), and it worked fine. I'm using 1.44.5. I've been reading about how GObject Introspection works, and I'm very curious about the problem you faced.Hagioscope
@RasmusWL, I'd appreciate if you could run this, too: strings /usr/lib/girepository-1.0/Pango-1.0.typelib | grep -i attr_letter_spacing_newHagioscope
H
2

Looks like this issue was already solved, using pycairo 1.20.0 and PyGObject 3.40.1:

import cairo
import gi
gi.require_version('Pango', '1.0')
gi.require_version('PangoCairo', '1.0')
from gi.repository import Pango, PangoCairo

width, height = 328, 48

surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
context = cairo.Context(surface)
layout = PangoCairo.create_layout(context)

font_desc = Pango.font_description_from_string('Sans, 40px')
layout.set_font_description(font_desc)

letter_spacing_attr = Pango.attr_letter_spacing_new(1024 * 10)

attr_list = Pango.AttrList()
attr_list.insert(letter_spacing_attr)
layout.set_attributes(attr_list)

layout.set_text('Hello World')

PangoCairo.show_layout(context, layout)

with open('help-me.png', 'wb') as image_file:
    surface.write_to_png(image_file)
Hadwin answered 15/6, 2021 at 23:54 Comment(1)
Confirmed to be working (pycairo 1.20.1, PyGObject 3.42.0)Observe

© 2022 - 2024 — McMap. All rights reserved.