Pango, CSS and Application
PangoFontDescription
PangoFontDescription is a C structure for a font. You can get font family, style, weight and size. You can also get a string that includes font attributes. For example, suppose that the PangoFontDescription has a font of “Noto Sans Mono”, “Bold”, “Italic” and 12 points of size. Then the string converted from the PangoFontDescription is “Noto Sans Mono Bold Italic 12”.
- Font family is “Noto Sans Mono”.
- Font style is “Italic”.
- Font weight is “Bold”, or 700.
- Font size is 12 pt.
The font in CSS is different from the string from PangoFontDescription.
font: bold italic 12pt "Noto Sans Mono"Noto Sans Mono Bold Italic 12
So, it may be easier to use each property, i.e. font-family, font-style, font-weight and font-size, to convert a PangoFontDescription data to CSS.
Refer to Pango document and W3C CSS Fonts Module Level 3 for further information.
Converter from PangoFontDescription to CSS
Two files pfd2css.h and pfd2css.c
include the converter from PangoFontDescription to CSS.
#pragma once
#include <pango/pango.h>
// Pango font description to CSS style string
// The returned string is owned by the caller. The caller should free it when it is no longer needed.
char*
pfd2css (PangoFontDescription *pango_font_desc);
// Each element (family, style, weight and size)
const char*
pfd2css_family (PangoFontDescription *pango_font_desc);
const char*
pfd2css_style (PangoFontDescription *pango_font_desc);
int
pfd2css_weight (PangoFontDescription *pango_font_desc);
// Returned string is owned by the caller. The caller should free it when it is no longer needed.
char *
pfd2css_size (PangoFontDescription *pango_font_desc);The five functions are public. The first function is a convenient function to set other four CSS properties at once.
#include <pango/pango.h>
#include "pfd2css.h"
// Pango font description to CSS style string
// The returned string is owned by caller. The caller should free it when it is no longer needed.
char *
pfd2css (PangoFontDescription *pango_font_desc) {
char *fontsize;
char *result;
fontsize = pfd2css_size (pango_font_desc);
result = g_strdup_printf ("font-family: \"%s\"; font-style: %s; font-weight: %d; font-size: %s;",
pfd2css_family (pango_font_desc), pfd2css_style (pango_font_desc),
pfd2css_weight (pango_font_desc), fontsize);
g_free (fontsize);
return result;
}
// Each element (family, style, weight and size)
const char *
pfd2css_family (PangoFontDescription *pango_font_desc) {
return pango_font_description_get_family (pango_font_desc);
}
const char *
pfd2css_style (PangoFontDescription *pango_font_desc) {
PangoStyle pango_style = pango_font_description_get_style (pango_font_desc);
switch (pango_style) {
case PANGO_STYLE_NORMAL:
return "normal";
case PANGO_STYLE_ITALIC:
return "italic";
case PANGO_STYLE_OBLIQUE:
return "oblique";
default:
return "normal";
}
}
int
pfd2css_weight (PangoFontDescription *pango_font_desc) {
PangoWeight pango_weight = pango_font_description_get_weight (pango_font_desc);
switch (pango_weight) {
case PANGO_WEIGHT_THIN:
return 100;
case PANGO_WEIGHT_ULTRALIGHT:
return 200;
case PANGO_WEIGHT_LIGHT:
return 300;
case PANGO_WEIGHT_SEMILIGHT:
return 350;
case PANGO_WEIGHT_BOOK:
return 380;
case PANGO_WEIGHT_NORMAL:
return 400; /* or "normal" */
case PANGO_WEIGHT_MEDIUM:
return 500;
case PANGO_WEIGHT_SEMIBOLD:
return 600;
case PANGO_WEIGHT_BOLD:
return 700; /* or "bold" */
case PANGO_WEIGHT_ULTRABOLD:
return 800;
case PANGO_WEIGHT_HEAVY:
return 900;
case PANGO_WEIGHT_ULTRAHEAVY:
return 900; /* 1000 is available since CSS Fonts level 4 but GTK currently supports level 3. */
default:
return 400; /* "normal" */
}
}
char *
pfd2css_size (PangoFontDescription *pango_font_desc) {
if (pango_font_description_get_size_is_absolute (pango_font_desc))
return g_strdup_printf ("%dpx", pango_font_description_get_size (pango_font_desc) / PANGO_SCALE);
else
return g_strdup_printf ("%dpt", pango_font_description_get_size (pango_font_desc) / PANGO_SCALE);
}- The function
pfd2css_familyreturns font family. - The function
pfd2css_stylereturns font style which is one of “normal”, “italic” or “oblique”. - The function
pfd2css_weightreturns font weight in integer. See the list below. - The function
pfd2css_sizereturns font size.- If the font description size is absolute, it returns the size in device units, which is pixel. Otherwise the unit is point.
- The function
pango_font_description_get_sizereturns the integer of the size but it is multiplied byPANGO_SCALE. So, you need to divide it byPANGO_SCALE. ThePANGO_SCALEis currently 1024, but this might be changed in the future. If the font size is 12pt, the size in pango is12*PANGO_SCALE=12*1024=12288.
- The function
pfd2cssreturns a string of the font. For example, if a font “Noto Sans Mono Bold Italic 12” is given, it returns “font-family: Noto Sans Mono; font-style: italic; font-weight: 700; font-size: 12pt;”.
The font weight number is one of:
- 100 - Thin
- 200 - Extra Light (Ultra Light)
- 300 - Light
- 400 - Normal
- 500 - Medium
- 600 - Semi Bold (Demi Bold)
- 700 - Bold
- 800 - Extra Bold (Ultra Bold)
- 900 - Black (Heavy)
Application Object
TfeApplication Class
TfeApplication class is a child of GtkApplication. It has some instance variables. The header file defines the type macro and a public function.
#pragma once
#include <gtk/gtk.h>
#define TFE_TYPE_APPLICATION tfe_application_get_type ()
G_DECLARE_FINAL_TYPE (TfeApplication, tfe_application, TFE, APPLICATION, GtkApplication)
TfeApplication *
tfe_application_new (const char *application_id, GApplicationFlags flag);The following code is extracted from
tfeapplication.c. It builds TfeApplication class
and instance.
#include <gtk/gtk.h>
#include "tfeapplication.h"
struct _TfeApplication {
GtkApplication parent;
TfeWindow *win;
GSettings *settings;
GtkCssProvider *provider;
};
G_DEFINE_FINAL_TYPE (TfeApplication, tfe_application, GTK_TYPE_APPLICATION)
static void
tfe_application_dispose (GObject *gobject) {
TfeApplication *app = TFE_APPLICATION (gobject);
g_clear_object (&app->settings);
g_clear_object (&app->provider);
G_OBJECT_CLASS (tfe_application_parent_class)->dispose (gobject);
}
static void
tfe_application_init (TfeApplication *app) {
app->settings = g_settings_new ("com.github.ToshioCP.tfe");
g_signal_connect (app->settings, "changed::font-desc", G_CALLBACK (changed_font_cb), app);
app->provider = gtk_css_provider_new ();
}
static void
tfe_application_class_init (TfeApplicationClass *class) {
G_OBJECT_CLASS (class)->dispose = tfe_application_dispose;
G_APPLICATION_CLASS (class)->startup = app_startup;
G_APPLICATION_CLASS (class)->activate = app_activate;
G_APPLICATION_CLASS (class)->open = app_open;
}
TfeApplication *
tfe_application_new (const char *application_id, GApplicationFlags flag) {
return TFE_APPLICATION (g_object_new (TFE_TYPE_APPLICATION, "application-id", application_id, "flags", flag, NULL));
}- The structure
_TfeApplicationis defined. It has four members. One is its parents and the others are kinds of instance variables. The members are usually initialized in the instance initialization function. And they are released in the disposal function or freed in the finalization function. The members are:- win: main window instance
- settings: GSettings instance.it is bound to “font-desc” item in the GSettings
- provider: a provider for the font of the textview.
- The macro
G_DEFINE_FINAL_TYPEdefinestfe_application_get_typefunction and some other useful things. - The function
tfe_application_class_initinitializes the TfeApplication class. It overrides four class methods. Three class methodsstartup,activateandopenpoint to the default signal handlers. The overriding replaces the default handlers. You can connect the handlers withg_signal_connectmacro but the result is different. The macro connects a user handler to the signal. The default handler still exists and no change is made to them. - The function
tfe_application_initinitializes an instance.- Creates a new GSettings instance and make
app->settingspoint to it. Then connects the handlerchanged_font_cbto the “changed::font-desc” signal. - Creates a new GtkCssProvider instance and make
app->providerpoint to it.
- Creates a new GSettings instance and make
- The function
tfe_application_disposereleases the GSettings and GtkCssProvider instances. Then, it calls the parent’s dispose handler. It is called “chaining up”. See GObject document.
Startup Signal Handlers
static void
app_startup (GApplication *application) {
TfeApplication *app = TFE_APPLICATION (application);
int i;
GtkCssProvider *provider = gtk_css_provider_new ();
GdkDisplay *display;
G_APPLICATION_CLASS (tfe_application_parent_class)->startup (application);
app->win = TFE_WINDOW (tfe_window_new (GTK_APPLICATION (app)));
gtk_css_provider_load_from_data (provider, "textview {padding: 10px;}", -1);
display = gdk_display_get_default ();
gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
g_object_unref (provider);
gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (app->provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
changed_font_cb (app->settings, "font-desc", app); // Sets the text view font to the font from the gsettings data base.
/* ----- accelerator ----- */
struct {
const char *action;
const char *accels[2];
} action_accels[] = {
{ "win.open", { "<Control>o", NULL } },
{ "win.save", { "<Control>s", NULL } },
{ "win.close", { "<Control>w", NULL } },
{ "win.new", { "<Control>n", NULL } },
{ "win.saveas", { "<Shift><Control>s", NULL } },
{ "win.close-all", { "<Control>q", NULL } },
};
for (i = 0; i < G_N_ELEMENTS(action_accels); i++)
gtk_application_set_accels_for_action(GTK_APPLICATION(app), action_accels[i].action, action_accels[i].accels);
}The function app_startup replace the default
signal handlers. It does five things.
- Calls the parent’s startup handler. It is called “chaining up”. The “startup” default handler runs before user handlers. So the call for the parent’s handler must be done at the beginning.
- Creates the main window. This application has only one top level window. In that case, it is a good way to create the window in the startup handler, which is called only once. Activate or open handlers can be called twice or more. Therefore, if you create a window in the activate or open handler, two or more windows can be created.
- Sets the default display CSS to “textview {padding: 10px;}”. It sets the GtkTextView, or TfeTextView, padding to 10px and makes the text easier to read. This CSS is fixed and never changed through the application life.
- Adds another CSS provider, which is pointed to by
app->provider, to the default display. This CSS depends on the GSettings “font-desc” value and it can be changed during the application life time. And callschanged_font_cbto update the font CSS setting. - Sets application accelerator with the function
gtk_application_set_accels_for_action. Accelerators are kinds of short cut key functions. For example,Ctrl+Ois an accelerator to activate “open” action. Accelerators are written in the arrayaction-accels[]. Its element is a structurestruct {const char *action; const char *accels[2];}. The memberactionis an action name. The memberaccelsis an array of two pointers. For example,{"win.open", { "<Control>o", NULL }}tells that the acceleratorCtrl+Ois connected to the “win.open” action. The second element ofaccelsis NULL which is the end mark. You can define more than one accelerator keys and the list must end with NULL (zero). If you want to do so, the array length needs to be three or more. For example,{"win.open", { "<Control>o", "<Alt>o", NULL }}means two acceleratorsCtrl+OandAlt+Ois connected to the “win.open” action. The parser recognizes “<control>o”, “<Shift><Alt>F2”, “<Ctrl>minus” and so on. If you want to use symbol key like “<Ctrl>-”, use “<Ctrl>minus” instead. Such relation between lower case and symbol (character code) is specified in gdkkeysyms.h in the GTK 4 source code.
Activate and Open Signal Handlers
Two functions app_activate and
app_open replace the default signal handlers.
static void
app_activate (GApplication *application) {
TfeApplication *app = TFE_APPLICATION (application);
tfe_window_notebook_page_new (app->win);
gtk_window_present (GTK_WINDOW (app->win));
}
static void
app_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
TfeApplication *app = TFE_APPLICATION (application);
tfe_window_notebook_page_new_with_files (app->win, files, n_files);
gtk_window_present (GTK_WINDOW (app->win));
}The original default handlers don’t do useful works and you don’t need to chain up to the parent’s default handlers. They just create notebook pages and show the top level window.
CSS Font Setting
static void
changed_font_cb (GSettings *settings, char *key, gpointer user_data) {
TfeApplication *app = TFE_APPLICATION (user_data);
char *font, *s, *css;
PangoFontDescription *pango_font_desc;
if (g_strcmp0(key, "font-desc") != 0)
return;
font = g_settings_get_string (app->settings, "font-desc");
pango_font_desc = pango_font_description_from_string (font);
g_free (font);
s = pfd2css (pango_font_desc); // converts Pango Font Description into CSS style string
pango_font_description_free (pango_font_desc);
css = g_strdup_printf ("textview {%s}", s);
gtk_css_provider_load_from_data (app->provider, css, -1);
g_free (s);
g_free (css);
}The function changed_font_cb is a handler for
“changed::font-desc” signal on the GSettings instance. The
signal name is “changed” and “font-desc” is a key name. This
signal is emitted when the value of the “font-desc” key is
changed. The value is bound to the “font-desc” property of the
GtkFontDialogButton instance. Therefore, the handler
changed_font_cb is called when the user selects and
updates a font through the font dialog.
A string is retrieved from the GSetting database and converts
it into a pango font description. And a CSS string is made by
the function pfd2css and
g_strdup_printf. Then the css provider is set to
the string. The provider has been inserted to the current
display in advance. So, the font is applied to the display.
Other Files
main.c
#include <gtk/gtk.h>
#include "tfeapplication.h"
#define APPLICATION_ID "com.github.ToshioCP.tfe"
int
main (int argc, char **argv) {
TfeApplication *app;
int stat;
app = tfe_application_new (APPLICATION_ID, G_APPLICATION_HANDLES_OPEN);
stat = g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}Resource XML file.
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/github/ToshioCP/tfe">
<file>tfewindow.ui</file>
<file>tfepref.ui</file>
<file>tfealert.ui</file>
<file>menu.ui</file>
</gresource>
</gresources>GSchema XML file
<?xml version="1.0" encoding="UTF-8"?>
<schemalist>
<schema path="/com/github/ToshioCP/tfe/" id="com.github.ToshioCP.tfe">
<key name="font-desc" type="s">
<default>'Monospace 12'</default>
<summary>Font</summary>
<description>A font to be used for textview.</description>
</key>
</schema>
</schemalist>Meson.build
project('tfe', 'c', license : 'GPL-3.0-or-later', meson_version:'>=1.0.1', version: '0.5')
gtkdep = dependency('gtk4')
gnome = import('gnome')
resources = gnome.compile_resources('resources','tfe.gresource.xml')
gnome.compile_schemas(depend_files: 'com.github.ToshioCP.tfe.gschema.xml')
sourcefiles = files('main.c', 'tfeapplication.c', 'tfewindow.c', 'tfepref.c', 'tfealert.c', 'pfd2css.c', '../tfetextview/tfetextview.c')
executable(meson.project_name(), sourcefiles, resources, dependencies: gtkdep, export_dynamic: true, install: true)
schema_dir = get_option('prefix') / get_option('datadir') / 'glib-2.0/schemas/'
install_data('com.github.ToshioCP.tfe.gschema.xml', install_dir: schema_dir)
gnome.post_install (glib_compile_schemas: true)- The function
projectdefines project and initialize meson. The first argument is the project name and the second is the language name. The other arguments are keyword arguments. - The function
dependencydefines the dependent library. Tfe depends on GTK4. This is used to createpkg-configoption in the command line of C compiler to include header files and link libraries. The returned objectgtkdepis used as an argument to theexecutablefunction later. - The function
importimports an extension module. The GNOME module has some convenient methods likegnome.compile_resourcesandgnome.compile_schemas. - The method
gnome.compile_resourcescompiles and creates resource files. The first argument is the resource name without extension and the second is the name of XML file. The returned value is an array['resources,c', 'resources.h']. - The function
gnome.compile_schemascompiles the schema files in the current directory. This just createsgschemas.compiledin the build directory. It is used to test the executable binary in the build directory. The function doesn’t install the schema file. - The function
filescreates a File Object. - The function
executabledefines the compilation elements such as target name, source files, dependencies and installation. The target name is “tfe”. The source files are elements of ‘sourcefiles’ and `resources’. It uses GTK4 libraries. It can be installed. - The last three lines are post install work. The variable
schema_diris the directory stored the schema file. If meson runs with--prefix=$HOME/.localargument, it is$HOME/.local/share/glib-2.9/schemas. The functioninstall_datacopies the file specified in the first argument into the directory specified in the second argument. The methodgnome.post_installrunsglib-compile-schemasand updatesgschemas_compiledfile.
Compilation and Installation.
If you want to install it to your local area, use
--prefix=$HOME/.local or
--prefix=$HOME option. If you want to install it to
the system area, no option is needed. It will be installed under
/user/local directory.
$ meson setup --prefix=$HOME/.local _build
$ ninja -C _build
$ ninja -C _build install
You need root privilege to install it to the system area..
$ meson setup _build
$ ninja -C _build
$ sudo ninja -C _build install
Source files are in /src/tfe6 directory.
We made a very small text editor. You can add features to this editor. When you add a new feature, be careful about the structure of the program. Maybe you need to divide a file into several files. It isn’t good to put many things into one file. And it is important to think about the relationship between source files and widget structures.
The source files are in the Gtk4 tutorial
GitHub repository. Download it and see
/src/tfe6 directory.