Blob Blame Raw

#include <iostream>

#include <fontconfig/fontconfig.h>

#include <freetype2/ft2build.h>
#include FT_FREETYPE_H
#include FT_TRUETYPE_TABLES_H
#include FT_TRUETYPE_IDS_H
#include FT_SFNT_NAMES_H

#include <glibmm.h>

#include <pango/pango.h>
#include <pango/pangoft2.h>
#include <pango/pangofc-fontmap.h>
#include <pangomm.h>

#include "layout.h"


// fake pango renderer - begin

G_BEGIN_DECLS

#define LAB_TYPE_FAKE_PANGO_RENDERER (lab_fake_pango_renderer_get_type ())

typedef struct _LabFakePangoRenderer LabFakePangoRenderer;
typedef struct _LabFakePangoRendererClass LabFakePangoRendererClass;

#define LAB_FAKE_PANGO_RENDERER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), LAB_TYPE_FAKE_PANGO_RENDERER, LabFakePangoRendererClass))
#define LAB_FAKE_IS_PANGO_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), LAB_TYPE_FAKE_PANGO_RENDERER))
#define LAB_FAKE_PANGO_RENDERER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), LAB_TYPE_FAKE_PANGO_RENDERER, LabFakePangoRendererClass))

PangoRenderer* lab_fake_pango_renderer_new(Layout::GlyphList *list);

G_END_DECLS

struct _LabFakePangoRenderer {
	PangoRenderer parent_instance;
	Layout::Line *line;
};

struct _LabFakePangoRendererClass {
  PangoRendererClass parent_class;
};

G_DEFINE_TYPE(LabFakePangoRenderer, lab_fake_pango_renderer, PANGO_TYPE_RENDERER)

static void
lab_fake_pango_renderer_init(LabFakePangoRenderer *renderer)
	{ renderer->line = nullptr; }

static void
lab_fake_pango_renderer_draw_glyph(
	PangoRenderer *renderer,
	PangoFont     *font,
	PangoGlyph     glyph,
	double         x,
	double         y );

static void
lab_fake_pango_renderer_class_init(LabFakePangoRendererClass *klass) {
	PangoRendererClass *renderer_class = PANGO_RENDERER_CLASS(klass);
	renderer_class->draw_glyph = lab_fake_pango_renderer_draw_glyph;
}

LabFakePangoRenderer*
lab_fake_pango_renderer_new()
	{ return (LabFakePangoRenderer*)g_object_new(LAB_TYPE_FAKE_PANGO_RENDERER, nullptr); }

// end - fake pango renderer



static void
lab_fake_pango_renderer_draw_glyph(
	PangoRenderer *renderer,
	PangoFont     *font,
	PangoGlyph     glyph,
	double         x,
	double         y )
{
	Layout::Line *line = ((LabFakePangoRenderer*)renderer)->line;
	assert(line);
	if (!line)
		return;

	const Real k = 1/64.0;

	assert(PANGO_IS_FC_FONT(font));
	FT_Face face = pango_fc_font_lock_face((PangoFcFont*)font);
	FT_Set_Transform(face, nullptr, nullptr);
	if (!FT_Load_Glyph(face, glyph, FT_LOAD_NO_HINTING)) {
		const FT_Glyph_Metrics &metrics = face->glyph->metrics;
		IntVector2 origin(metrics.vertBearingX - metrics.horiBearingX, 0);
		IntPair2 bounds(
			IntVector2( metrics.horiBearingX,
					   -metrics.horiBearingY ) + origin,
			IntVector2( metrics.horiBearingX + metrics.width, 
					   -metrics.horiBearingY + metrics.height ) + origin );
		
		Layout::RunList &runs = line->runs;
		if (runs.empty() || !runs.back().font || runs.back().font->gobj() != font) {
			runs.push_back(Layout::Run());
			runs.back().font = Glib::wrap(font, true);
		}
		Layout::Run &run = runs.back();
		run.glyphs.push_back(Layout::Glyph());
		Layout::Glyph &g = run.glyphs.back();

		g.glyph_index = glyph;
		g.origin = Vector2(origin.x, origin.y)*k;
		g.position = Vector2(x, y) - g.origin;
		g.bounds = Pair2( Vector2(bounds.p0.x, bounds.p0.y)*k,
						  Vector2(bounds.p1.x, bounds.p1.y)*k );
	}
	pango_fc_font_unlock_face((PangoFcFont*)font);
}

static void
lab_pango_fc_pattern_set_substitute(FcPattern *pattern, gpointer data) {
	const int &args = *(const int*)data;
	FcPatternDel(pattern, FC_HINTING);
	FcPatternDel(pattern, FC_ANTIALIAS);
	FcPatternAddBool(pattern, FC_HINTING, (args & 1) ? FcTrue : FcFalse);
	FcPatternAddBool(pattern, FC_ANTIALIAS, (args & 2) ? FcTrue : FcFalse);
}


static GQuark
layout_data_quark() {
	static GQuark quark = g_quark_from_static_string("lab_layout_data");
	return quark;
}

Glib::RefPtr<Pango::FontMap>
Layout::create_fontmap(bool hinting, bool antialiasing) {
	static int args[4] { 0, 1, 2, 3 }; // to avoid encoding of int into pointer
	int* data = args + (hinting ? 1 : 0) + (antialiasing ? 2 : 0);
	
	PangoFontMap *font_map = pango_ft2_font_map_new();
	pango_ft2_font_map_set_default_substitute(
		PANGO_FT2_FONT_MAP(font_map),
		lab_pango_fc_pattern_set_substitute,
		data,
		nullptr );
	g_object_set_qdata((GObject*)font_map, layout_data_quark(), data);
	
	return Glib::wrap(font_map);
}


Glib::RefPtr<Pango::Font>
Layout::load_font_file(
	const Glib::RefPtr<Pango::Context> &context,
	const std::string &filename,
	const Pango::FontDescription &font_desc )
{
	const std::string fullpath = Glib::path_is_absolute(filename)
							   ? filename : Glib::get_current_dir() + "/" + filename;

	Glib::RefPtr<Pango::Font> font;

	bool desc_ready = false;
	Pango::FontDescription desc = font_desc;
	
	FT_Library library;
	if (!FT_Init_FreeType(&library)) {
		FT_Face face;
		if (!FT_New_Face(library, fullpath.c_str(), 0, &face)) {
			desc.set_family(face->family_name);
			desc.set_style(
				  face->style_flags & FT_STYLE_FLAG_ITALIC
				? Pango::STYLE_ITALIC : Pango::STYLE_NORMAL );
			desc.set_weight(
				  face->style_flags & FT_STYLE_FLAG_BOLD
				? Pango::WEIGHT_BOLD : Pango::WEIGHT_NORMAL );
			desc_ready = true;
			FT_Done_Face(face);
		} else {
			std::cerr << "Layout::load_font_file: Cannot load font from file: " << fullpath << std::endl;
		}
		FT_Done_FreeType(library);
    } else {
		std::cerr << "Layout::load_font_file: Cannot init FreeType library" << std::endl;
	}
	
	if (desc_ready) {
		PangoFcFontMap *font_map = PANGO_FC_FONT_MAP(pango_context_get_font_map(context->gobj()));
		if (font_map) {
			FcConfig *config = pango_fc_font_map_get_config(font_map);
			if (config || FcConfigAppFontAddFile(config, (const FcChar8*)fullpath.c_str())) {
				pango_fc_font_map_config_changed(font_map);
				font = context->load_font(desc);
			} else {
				pango_fc_font_map_config_changed(font_map);
				std::cerr << "Layout::load_font_file: Cannot register font file in FontConfig: " << fullpath << std::endl;
			}
		} else {
			std::cerr << "Layout::load_font_file: Wrong context. FreeType/FontConfig context required" << std::endl;
		}
	}
	
	return font;
}



Layout::Layout(const Glib::RefPtr<Pango::Layout> &layout):
	hinting(),
	antialiasing()
{
	const int *data = (const int*)g_object_get_qdata(
		(GObject*)pango_context_get_font_map( pango_layout_get_context(layout->gobj()) ),
		layout_data_quark() );
	assert(data);
	if (!data)
		return;
	hinting = (*data) & 1;
	antialiasing = (*data) & 2;
	
	const Real k = 1/Real(PANGO_SCALE);
	
	PangoRectangle ink_rect, logical_rect;
	pango_layout_get_extents(layout->gobj(), &ink_rect, &logical_rect);
	bounds = Pair2( Vector2( ink_rect.x, ink_rect.y )*k,
					Vector2( ink_rect.x + ink_rect.width,
							 ink_rect.y + ink_rect.height )*k );
	logical_bounds = Pair2( Vector2( logical_rect.x, logical_rect.y )*k,
							Vector2( logical_rect.x + logical_rect.width,
									 logical_rect.y + logical_rect.height )*k );
	
	LabFakePangoRenderer *renderer = lab_fake_pango_renderer_new();
	pango_renderer_activate((PangoRenderer*)renderer);
	PangoLayoutIter *iter = pango_layout_get_iter(layout->gobj());
	do {
		lines.push_back(Line());
		Line &line = lines.back();
		
		pango_layout_iter_get_line_extents(iter, &ink_rect, &logical_rect);
		int baseline = pango_layout_iter_get_baseline(iter);
		
		line.position = Vector2(logical_rect.x, baseline)*k;
		line.bounds = Pair2(
			Vector2( ink_rect.x - logical_rect.x,
					 ink_rect.y - baseline)*k,
			Vector2( ink_rect.x - logical_rect.x + ink_rect.width,
					 ink_rect.y - baseline + ink_rect.height)*k );
		line.logical_bounds = Pair2(
			Vector2( 0, logical_rect.y )*k,
			Vector2( logical_rect.width,
					 logical_rect.y - baseline + logical_rect.height)*k );
		
		renderer->line = &line;
		PangoLayoutLine *l = pango_layout_iter_get_line_readonly(iter);
		pango_renderer_draw_layout_line((PangoRenderer*)renderer, l, 0, 0);
	} while(pango_layout_iter_next_line(iter));
	pango_layout_iter_free(iter);
	pango_renderer_deactivate((PangoRenderer*)renderer);
	g_object_unref(renderer);
}


TransformedLayout::TransformedLayout(
	const RefPtr<Layout> &layout,
	const Matrix2 &spacing_matrix,
	const Matrix2 &glyph_matrix
):
	layout(layout),
	spacing_matrix(spacing_matrix),
	glyph_matrix(glyph_matrix)
{
	if (!layout)
		return;
	
	const Pair2 &logical_bounds = layout->get_logical_bounds();
	const Layout::LineList &lines = layout->get_lines();
	line_bounds.reserve(lines.size());
	for(Layout::LineList::const_iterator li = lines.begin(); li != lines.end(); ++li) {
		Pair2 lb;
		for(Layout::RunList::const_iterator ri = li->runs.begin(); ri != li->runs.end(); ++ri) {
			for(Layout::GlyphList::const_iterator gi = ri->glyphs.begin(); gi != ri->glyphs.end(); ++gi) {
				const Vector2 position = spacing_matrix*gi->position;
				Pair2 gb = glyph_matrix.transform_bounds(gi->bounds);
				gb.p0 += position;
				gb.p1 += position;
				lb |= gb;
			}
		}
		line_bounds.push_back(lb);
		const Vector2 p0 = spacing_matrix*li->position;
		const Vector2 p1 = spacing_matrix*Vector2(0, li->position.y);
		const Vector2 p2 = spacing_matrix*Vector2(logical_bounds.p1.x - li->logical_bounds.p1.x, li->position.y);
		bounds |= Pair2(lb.p0 + p0, lb.p1 + p0);
		bounds |= Pair2(lb.p0 + p1, lb.p1 + p1);
		bounds |= Pair2(lb.p0 + p2, lb.p1 + p2);
	}
}