How to prevent endless g_object_get_qdata loop when subclassing PangoFontMap?

I’m trying to subclass PangoFontMap to read fonts in a custom format from a custom directory (the TXF format which cannot be read by FreeType2, but by OSG). I have the following code:

PangoFontOSG.hxx

#ifndef PANGO_FONT_OSG_HXX
#define PANGO_FONT_OSG_HXX

#include <map>

#include <pango/pango-font.h>

#include <osgText/Font>

#include "PangoFontMapOSG.hxx"

struct FontOSG {
	PangoFont parent_instance;
	PangoFontMap* fontmap;
};
struct FontOSGClass {
	PangoFontClass class_instance;
};

class PangoFontOSG {
	public:
		PangoFontOSG(PangoFontMapOSG* fontmap);
		~PangoFontOSG();
		static PangoFontOSG* getFont(PangoFont*);
		
		PangoFont* _font;
	private:
		static std::map<PangoFont*, PangoFontOSG*> _fontMap;
};

#endif // PANGO_FONT_MAP_OSG_HXX

PangoFontOSG.cxx

#include <glib-object.h>

#include "PangoFontMapOSG.hxx"
#include "PangoFontOSG.hxx"

G_DEFINE_TYPE(FontOSG, font_osg, PANGO_TYPE_FONT);
#define TYPE_FONT_OSG (font_osg_get_type())
#define FONT_OSG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_FONT_OSG, FontOSG))
#define IS_FONT_OSG(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), TYPE_FONT_OSG))

static GObjectClass* _pangoClass = 0;

void font_osg_init(FontOSG* priv) {
}

void font_osg_finalize(GObject* object) {
	// FontMap* priv = FONT_MAP(object);
	G_OBJECT_CLASS(_pangoClass)->finalize(object);
}

void font_osg_class_init(FontOSGClass* klass) {
	GObjectClass* object_class = G_OBJECT_CLASS(klass);
	object_class->finalize = font_osg_finalize;
	_pangoClass = static_cast<GObjectClass*>(g_type_class_peek_parent(klass));
}

std::map<PangoFont*, PangoFontOSG*> PangoFontOSG::_fontMap;

PangoFontOSG* PangoFontOSG::getFont(PangoFont* font) {
	if (_fontMap.count(font)) {
		return _fontMap[font];
	} else {
		return nullptr;
	}
}

PangoFontOSG::PangoFontOSG(PangoFontMapOSG* fontmap) {
	FontOSG* font = static_cast<FontOSG*>(g_object_new(TYPE_FONT_OSG, 0));
	font->fontmap = fontmap->_fontmap;
	_font = PANGO_FONT(font);
	_fontMap[_font] = this;
}

PangoFontOSG::~PangoFontOSG() {
	_fontMap.erase(_font);
}

PangoFontMapOSG.hxx

#ifndef PANGO_FONT_MAP_OSG_HXX
#define PANGO_FONT_MAP_OSG_HXX

#include <map>

#include <pango/pango-context.h>
#include <pango/pango-font.h>
#include <pango/pango-fontmap.h>

#include <osgText/Font>

struct FontMapOSG {
	PangoFontMap parent_instance;
};
struct FontMapOSGClass {
	PangoFontMapClass class_instance;
};

class PangoFontMapOSG {
	public:
		PangoFontMapOSG();
		~PangoFontMapOSG();
		static PangoFontMapOSG* getFontMap(PangoFontMap*);

		static void c_list_families(PangoFontMap* fontmap, PangoFontFamily*** families, int* n_families) {
			PangoFontMapOSG* fontmapOSG = getFontMap(fontmap);
			if (!fontmapOSG) {
				return;
			}
			fontmapOSG->list_families(families, n_families);
		};
		static PangoFont* c_load_font(PangoFontMap* fontmap, PangoContext* context, const PangoFontDescription* desc) {
			PangoFontMapOSG* fontmapOSG = getFontMap(fontmap);
			if (!fontmapOSG) {
				return nullptr;
			}
			return fontmapOSG->load_font(context, desc);
		};
		static PangoFontset* c_load_fontset(PangoFontMap* fontmap, PangoContext* context, const PangoFontDescription* desc, PangoLanguage* language) {
			PangoFontMapOSG* fontmapOSG = getFontMap(fontmap);
			if (!fontmapOSG) {
				return nullptr;
			}
			return fontmapOSG->load_fontset(context, desc, language);
		};

		PangoFont* load_font(PangoContext* context, const PangoFontDescription* desc);
		PangoFontset* load_fontset(PangoContext* context, const PangoFontDescription* desc, PangoLanguage* language);
		void list_families(PangoFontFamily*** families, int* n_families);
		
		PangoFontMap* _fontmap;
	private:
		static std::map<PangoFontMap*, PangoFontMapOSG*> _fontmapMap;
};

#endif // PANGO_FONT_MAP_OSG_HXX

PangoFontMapOSG.cxx

#include <set>

#include <glib-object.h>
#include <pango/pango-fontmap.h>
#include <pango/pango-fontset-simple.h>

#include <simgear/props/props.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/misc/ResourceManager.hxx>
#include <simgear/debug/ErrorReportingCallback.hxx>
#include "PangoFontMapOSG.hxx"
#include "PangoFontOSG.hxx"

G_DEFINE_TYPE(FontMapOSG, font_map_osg, PANGO_TYPE_FONT_MAP);
#define TYPE_FONT_MAP_OSG (font_map_osg_get_type())
#define FONT_MAP_OSG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_FONT_MAP_OSG, FontMapOSG))
#define IS_FONT_MAP_OSG(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), TYPE_FONT_MAP_OSG))

static GObjectClass* _pangoClass = 0;

void font_map_osg_init(FontMapOSG* priv) {
}

void font_map_osg_finalize(GObject* object) {
	G_OBJECT_CLASS(_pangoClass)->finalize(object);
}

void font_map_osg_class_init(FontMapOSGClass* klass) {
	GObjectClass* object_class = G_OBJECT_CLASS(klass);
	object_class->finalize = font_map_osg_finalize;
	PangoFontMapClass* font_map_class = PANGO_FONT_MAP_CLASS(klass);
	_pangoClass = static_cast<GObjectClass*>(g_type_class_peek_parent(klass));
	font_map_class->list_families = &PangoFontMapOSG::c_list_families;
	font_map_class->load_font = &PangoFontMapOSG::c_load_font;
	font_map_class->load_fontset = &PangoFontMapOSG::c_load_fontset;
	font_map_class->shape_engine_type = "PangoRendererCanvas";
}

std::map<PangoFontMap*, PangoFontMapOSG*> PangoFontMapOSG::_fontmapMap;

PangoFontMapOSG* PangoFontMapOSG::getFontMap(PangoFontMap* fontmap) {
	if (_fontmapMap.count(fontmap)) {
		return _fontmapMap[fontmap];
	} else {
		return nullptr;
	}
}

PangoFontMapOSG::PangoFontMapOSG() {
	_fontmap = PANGO_FONT_MAP(static_cast<FontMapOSG*>(g_object_new(TYPE_FONT_MAP_OSG, 0)));
	_fontmapMap[_fontmap] = this;
}

PangoFontMapOSG::~PangoFontMapOSG() {
	_fontmapMap.erase(_fontmap);
}

PangoFont* PangoFontMapOSG::load_font(PangoContext* context, const PangoFontDescription* desc) {
	SG_LOG(SG_GENERAL, SG_ALERT, "PangoFontMapOSG::load_font family=" << pango_font_description_get_family(desc));
	PangoFont* font = PangoFontOSG(this)._font;
	return font;
}

PangoFontset* PangoFontMapOSG::load_fontset(PangoContext* context, const PangoFontDescription* desc, PangoLanguage* language) {
	SG_LOG(SG_GENERAL, SG_ALERT, "PangoFontMapOSG::load_fontset family=" << pango_font_description_get_family(desc));
	PangoFontsetSimple* fontset = pango_fontset_simple_new(language);
	PangoFontDescription* tmp_desc = pango_font_description_copy_static(desc);

	const char* family = pango_font_description_get_family(desc);
	char** families = g_strsplit(family ? family : "", ",", -1);

	for (int i = 0; families[i]; i++) {
		char **aliases;
		int n_aliases;
		PangoFont *font;


		pango_font_description_set_family_static((PangoFontDescription*)desc, family);
		font = load_font(context, desc);
		if (font) {
			pango_fontset_simple_append(fontset, font);
		}
	}
	g_strfreev (families);

	return PANGO_FONTSET(fontset);
}

void PangoFontMapOSG::list_families(PangoFontFamily*** families, int* n_families) {
	const SGPath fontmapXMLPath = simgear::ResourceManager::instance()->findPath("Fonts/fontmap.xml");
	if (fontmapXMLPath.isNull()) {
		simgear::reportFailure(
			simgear::LoadFailure::NotFound,
			simgear::ErrorCode::LoadingTexture,
			"PangoFontMapOSG couldn't find fontmap file 'Fonts/fontmap.xml'"
		);
		return;
	}

	SGPropertyNode* fontmapXML = new SGPropertyNode();
	readProperties(fontmapXMLPath, fontmapXML);
	if (!fontmapXML) {
		SG_LOG(SG_IO, SG_ALERT, "Failed loading fontmap from '" << fontmapXMLPath.utf8Str() << "'");
		return;
	}

	simgear::PropertyList fonts = fontmapXML->getChildren("font");
	std::set<std::string> familyNames;
	for (simgear::PropertyList::iterator it = fonts.begin(); it != fonts.end(); it++) {
		familyNames.insert((*it)->getStringValue("family", ""));
	}
	SG_LOG(SG_GENERAL, SG_ALERT, "families " << (*familyNames.begin()));
}

In the main function of my program PangoRenderer, I have:

			PangoContex* context = pango_context_new();
			PangoFontMapOSG fmap = PangoFontMapOSG();
			pango_context_set_font_map(_context, (PangoFontMap*)fmap._fontmap);

			PangoLayout* layout = pango_layout_new(_context);

When I then call layout->set_markup(…), I get the following console output repeating forever:

(process:1250236): GLib-GObject-CRITICAL **: 17:57:30.114: g_object_get_qdata: assertion 'G_IS_OBJECT (object)' failed

(process:1250236): GLib-GObject-CRITICAL **: 17:57:30.114: g_object_replace_qdata: assertion 'G_IS_OBJECT (object)' failed

The last function before g_object_get_qdata I could identify in gdb is pango_renderer_draw_layout.

The most important thing is, if I don’t override the load_fontset function of PangoFont, the console messages are not printed - instead, it says that All font fallbacks failed!!! even though I am returning a non-null font from load_font.

Now, the question: Where is the small mistake I made that causes either of the issues ? Am I missing a function that has to be implemented in FontOSG ?

PangoFont* font = PangoFontOSG(this)._font;
This is creating a PangoFontOSG object on the stack. The ‘this’ pointer to that object is then saved to the std::map. But after this line, the object is destroyed, and the pointer in the std::map becomes invalid.

I’ve sketched out how I would approach making a subclass of PangoFontMap where the implementation is done using C++ here: GitHub - talisein/pangomm-c-subclass-example

Rather than using a global variable, the pimpl idiom is used. The gobject construction and finalize calls new & delete. All the C code is in export “C”, since its technically not right to be passing static C++ method pointers to C.

Also take a look at the main.cpp; specifically the rest of your C++ program might be cleaner if it can just use the normal pangomm wrapping

    /* OsgFontMap is a PangoFontMap, so we can use the normal pangomm wrapping
       from the rest of the program.
       If OsgFontMap implements a new method you need to call, you can always osg_font_map_foo(font_map->gobj());
    */
    Glib::RefPtr<Pango::FontMap> font_map = Glib::wrap(PANGO_FONT_MAP(osg_font_map_new()));

Okay, but why do I get the qdata loop in the first place ? Because of the invalid pointer ? I’d rather expect a SEGFAULT from that.

As for PangoMM, I’m just adding some new functionality to an existing project, and the maintainers said they’d rather not have the extra MM wrappers dependency.

Also, could you add a renderer to your example ? Your example runs fine for me, but in my project the loop happens after pango_renderer_draw_layout gets called - since there’s no renderer in your example, this never happens.

I think qdata is used in the gobject runtime type information… I know the glibmm gobject uses it, but I’m less sure about where its used from the C lib. pango_renderer_draw_layout() probably does something like PANGO_RENDERER(renderer) which could peek at the qdata. Because the ‘this’ pointer you stored points into the stack, it is very plausible that it won’t cause a seg fault, its just pointing at completely different data that the qdata lookup code realizes is invalid.

I’ll add a renderer as another derived gobject. Unfortunately I don’t know much about pango so I don’t know enough to make a real example that prints a real jpg with a real font.

Hmm, well, when I used the ft2 font map instead of my own subclass everything was working fine - I was literally able to show markup within an OSG application. But I wanted to support my custom fonts, which is what I need the custom fontmap for.

Try running your original sample in GDB with the envvar G_DEBUG=fatal-criticals:

G_DEBUG=fatal-criticals gdb ./program

And type run to launch the program. It should stop just before the error is printed, after that type bt to get a backtrace.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.