From 07333983ff99b22994a79c8a7cbf4fad0b80158f Mon Sep 17 00:00:00 2001 From: Cyrille Bagard Date: Sun, 1 Jun 2025 15:27:50 +0200 Subject: Build a static image for the About dialog box background. --- .gitignore | 5 ++ configure.ac | 2 + pixmaps/Makefile.am | 18 +---- pixmaps/chrysalide-full.png | Bin 28830 -> 0 bytes pixmaps/chrysalide_text.png | Bin 11432 -> 0 bytes pixmaps/revision.png | Bin 601 -> 0 bytes pixmaps/revision_0.png | Bin 760 -> 0 bytes pixmaps/revision_1.png | Bin 545 -> 0 bytes pixmaps/revision_2.png | Bin 727 -> 0 bytes pixmaps/revision_3.png | Bin 719 -> 0 bytes pixmaps/revision_4.png | Bin 691 -> 0 bytes pixmaps/revision_5.png | Bin 746 -> 0 bytes pixmaps/revision_6.png | Bin 775 -> 0 bytes pixmaps/revision_7.png | Bin 585 -> 0 bytes pixmaps/revision_8.png | Bin 802 -> 0 bytes pixmaps/revision_9.png | Bin 791 -> 0 bytes src/data/images/Makefile.am | 1 + src/data/images/gresource.xml | 3 + src/gui/dialogs/Makefile.am | 2 + src/gui/dialogs/about-int.h | 14 ---- src/gui/dialogs/about.c | 57 +++---------- src/gui/dialogs/about.css | 9 +++ src/gui/dialogs/about.ui | 80 +------------------ src/gui/dialogs/gresource.xml | 16 +--- src/gui/style.css | 6 +- tools/about/build.py | 120 ++++++++++++++++++++++++++++ tools/about/courier-10-pitch-bold_EQ97V.zip | Bin 0 -> 21114 bytes tools/about/courier10point.zip | Bin 0 -> 61784 bytes tools/about/gen.sh | 52 ++++++++++++ tools/about/logo.png | Bin 0 -> 633982 bytes tools/about/noise.png | Bin 0 -> 136944 bytes tools/about/ttf.py | 111 +++++++++++++++++++++++++ 32 files changed, 322 insertions(+), 174 deletions(-) delete mode 100644 pixmaps/chrysalide-full.png delete mode 100644 pixmaps/chrysalide_text.png delete mode 100644 pixmaps/revision.png delete mode 100644 pixmaps/revision_0.png delete mode 100644 pixmaps/revision_1.png delete mode 100644 pixmaps/revision_2.png delete mode 100644 pixmaps/revision_3.png delete mode 100644 pixmaps/revision_4.png delete mode 100644 pixmaps/revision_5.png delete mode 100644 pixmaps/revision_6.png delete mode 100644 pixmaps/revision_7.png delete mode 100644 pixmaps/revision_8.png delete mode 100644 pixmaps/revision_9.png create mode 100644 src/gui/dialogs/about.css create mode 100644 tools/about/build.py create mode 100644 tools/about/courier-10-pitch-bold_EQ97V.zip create mode 100644 tools/about/courier10point.zip create mode 100755 tools/about/gen.sh create mode 100644 tools/about/logo.png create mode 100644 tools/about/noise.png create mode 100644 tools/about/ttf.py diff --git a/.gitignore b/.gitignore index bb8db02..ae3f2cd 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,9 @@ tools/d2c/d2c tools/fuzzing/rost/fast-rost tools/yara2rost/yara2rost +# Images générées +src/data/images/about-bg.png + # Schemas src/schemas/gschemas.compiled src/schemas/*.valid @@ -84,6 +87,8 @@ src/schemas/*.valid # Misc plugins/python/androperms/androperms.db system/pkgconfig/chrysalide.pc +tools/about/bg.png +tools/about/cour10p_b.ttf tools/fuzzing/rost/rost.dict # Themes diff --git a/configure.ac b/configure.ac index 693e9f8..8429ae6 100644 --- a/configure.ac +++ b/configure.ac @@ -744,6 +744,8 @@ AC_CONFIG_FILES([stamp-h po/Makefile.in], [echo timestamp > stamp-h]) AC_CONFIG_COMMANDS([marshal], [echo -e "VOID:UINT64\nVOID:INT,UINT64,INT\nVOID:OBJECT,OBJECT\nVOID:ENUM,OBJECT\nVOID:ENUM,ENUM\nVOID:BOOLEAN,UINT64\nVOID:BOOLEAN,ULONG,ULONG\nVOID:INT,INT\nVOID:OBJECT,BOOLEAN\nVOID:ULONG,BOOLEAN\nVOID:DOUBLE,DOUBLE" > src/glibext/chrysamarshal.list]) +AC_CONFIG_COMMANDS([about], [./tools/about/gen.sh $version], [version=r$PACKAGE_VERSION]) + AC_CONFIG_FILES([Makefile doc/Makefile pixmaps/Makefile diff --git a/pixmaps/Makefile.am b/pixmaps/Makefile.am index d1dff31..da3de19 100644 --- a/pixmaps/Makefile.am +++ b/pixmaps/Makefile.am @@ -4,19 +4,6 @@ APP_ICONS = \ chrysalide-64.png \ chrysalide-128.png -REVISION_PIX = \ - revision_0.png \ - revision_1.png \ - revision_2.png \ - revision_3.png \ - revision_4.png \ - revision_5.png \ - revision_6.png \ - revision_7.png \ - revision_8.png \ - revision_9.png \ - revision.png - TOOLBAR_BUTTONS = \ tbutton_collapse.png \ tbutton_expand.png \ @@ -41,8 +28,6 @@ OTHER_ICONS = \ breakpoint_normal.png MISC = \ - chrysalide-full.png \ - chrysalide_text.png \ welcome.png CORE = \ @@ -57,13 +42,12 @@ EXTRA_DIST = \ openida_text.xcf \ before-after.png \ $(APP_ICONS) \ - $(REVISION_PIX) \ $(TOOLBAR_BUTTONS) \ $(LIST_ICONS) \ $(ERROR_ICONS) \ $(MISC) \ $(CORE) -pix_DATA = $(APP_ICONS) $(REVISION_PIX) $(TOOLBAR_BUTTONS) $(LIST_ICONS) $(ERROR_ICONS) $(OTHER_ICONS) $(MISC) +pix_DATA = $(APP_ICONS) $(TOOLBAR_BUTTONS) $(LIST_ICONS) $(ERROR_ICONS) $(OTHER_ICONS) $(MISC) pixdir = $(pixmapsdir) diff --git a/pixmaps/chrysalide-full.png b/pixmaps/chrysalide-full.png deleted file mode 100644 index a153353..0000000 Binary files a/pixmaps/chrysalide-full.png and /dev/null differ diff --git a/pixmaps/chrysalide_text.png b/pixmaps/chrysalide_text.png deleted file mode 100644 index b4d8c78..0000000 Binary files a/pixmaps/chrysalide_text.png and /dev/null differ diff --git a/pixmaps/revision.png b/pixmaps/revision.png deleted file mode 100644 index 330e108..0000000 Binary files a/pixmaps/revision.png and /dev/null differ diff --git a/pixmaps/revision_0.png b/pixmaps/revision_0.png deleted file mode 100644 index 0a468de..0000000 Binary files a/pixmaps/revision_0.png and /dev/null differ diff --git a/pixmaps/revision_1.png b/pixmaps/revision_1.png deleted file mode 100644 index 738a881..0000000 Binary files a/pixmaps/revision_1.png and /dev/null differ diff --git a/pixmaps/revision_2.png b/pixmaps/revision_2.png deleted file mode 100644 index b567b80..0000000 Binary files a/pixmaps/revision_2.png and /dev/null differ diff --git a/pixmaps/revision_3.png b/pixmaps/revision_3.png deleted file mode 100644 index 7f5e3fc..0000000 Binary files a/pixmaps/revision_3.png and /dev/null differ diff --git a/pixmaps/revision_4.png b/pixmaps/revision_4.png deleted file mode 100644 index a21ccbb..0000000 Binary files a/pixmaps/revision_4.png and /dev/null differ diff --git a/pixmaps/revision_5.png b/pixmaps/revision_5.png deleted file mode 100644 index a9130d9..0000000 Binary files a/pixmaps/revision_5.png and /dev/null differ diff --git a/pixmaps/revision_6.png b/pixmaps/revision_6.png deleted file mode 100644 index 291febd..0000000 Binary files a/pixmaps/revision_6.png and /dev/null differ diff --git a/pixmaps/revision_7.png b/pixmaps/revision_7.png deleted file mode 100644 index ccd4d4e..0000000 Binary files a/pixmaps/revision_7.png and /dev/null differ diff --git a/pixmaps/revision_8.png b/pixmaps/revision_8.png deleted file mode 100644 index 054c250..0000000 Binary files a/pixmaps/revision_8.png and /dev/null differ diff --git a/pixmaps/revision_9.png b/pixmaps/revision_9.png deleted file mode 100644 index 1123669..0000000 Binary files a/pixmaps/revision_9.png and /dev/null differ diff --git a/src/data/images/Makefile.am b/src/data/images/Makefile.am index 068e43f..d5ec84f 100644 --- a/src/data/images/Makefile.am +++ b/src/data/images/Makefile.am @@ -4,6 +4,7 @@ BUILT_SOURCES = resources.h resources.c noinst_LTLIBRARIES = libdataimages.la RES_FILES = \ + about-bg.png \ dock-station-left-symbolic.svg \ dock-station-right-symbolic.svg \ dock-station-bottom-symbolic.svg \ diff --git a/src/data/images/gresource.xml b/src/data/images/gresource.xml index 7659da3..25c8274 100644 --- a/src/data/images/gresource.xml +++ b/src/data/images/gresource.xml @@ -1,5 +1,8 @@ + + about-bg.png + dock-station-left-symbolic.svg dock-station-right-symbolic.svg diff --git a/src/gui/dialogs/Makefile.am b/src/gui/dialogs/Makefile.am index e910c96..3492f63 100644 --- a/src/gui/dialogs/Makefile.am +++ b/src/gui/dialogs/Makefile.am @@ -3,7 +3,9 @@ BUILT_SOURCES = resources.h resources.c noinst_LTLIBRARIES = libguidialogs.la + UI_FILES = \ + about.css \ about.ui \ preferences.ui # bookmark.ui \ diff --git a/src/gui/dialogs/about-int.h b/src/gui/dialogs/about-int.h index 616c73f..96a470e 100644 --- a/src/gui/dialogs/about-int.h +++ b/src/gui/dialogs/about-int.h @@ -37,20 +37,6 @@ struct _GtkAppAboutDialog { GtkWindow parent; /* A laisser en premier */ - union - { - struct - { - GtkPicture *revision_0; /* Numéro #0 */ - GtkPicture *revision_1; /* Numéro #1 */ - GtkPicture *revision_2; /* Numéro #2 */ - GtkPicture *revision_3; /* Numéro #3 */ - GtkPicture *revision_4; /* Numéro #4 */ - GtkPicture *revision_5; /* Numéro #5 */ - }; - GtkPicture *revisions[6]; /* Tous les numéros d'un coup */ - }; - }; /* Boîte "A propos de" dédiée à l'application (classe) */ diff --git a/src/gui/dialogs/about.c b/src/gui/dialogs/about.c index 956918b..1dca752 100644 --- a/src/gui/dialogs/about.c +++ b/src/gui/dialogs/about.c @@ -44,10 +44,10 @@ static void gtk_app_about_dialog_class_init(GtkAppAboutDialogClass *); static void gtk_app_about_dialog_init(GtkAppAboutDialog *); /* Supprime toutes les références externes. */ -static void gtk_app_about_dialog_dispose(GtkAppAboutDialog *); +static void gtk_app_about_dialog_dispose(GObject *); /* Procède à la libération totale de la mémoire. */ -static void gtk_app_about_dialog_finalize(GtkAppAboutDialog *); +static void gtk_app_about_dialog_finalize(GObject *); @@ -74,22 +74,17 @@ static void gtk_app_about_dialog_class_init(GtkAppAboutDialogClass *class) object = G_OBJECT_CLASS(class); - object->dispose = (GObjectFinalizeFunc/* ! */)gtk_app_about_dialog_dispose; - object->finalize = (GObjectFinalizeFunc)gtk_app_about_dialog_finalize; + object->dispose = gtk_app_about_dialog_dispose; + object->finalize = gtk_app_about_dialog_finalize; widget = GTK_WIDGET_CLASS(class); + gtk_widget_class_set_css_name(widget, "aboutdialog"); + gtk_widget_class_add_binding_action(widget, GDK_KEY_Escape, 0, "window.close", NULL); gtk_widget_class_set_template_from_resource(widget, "/re/chrysalide/framework/gui/dialogs/about.ui"); - gtk_widget_class_bind_template_child(widget, GtkAppAboutDialog, revision_0); - gtk_widget_class_bind_template_child(widget, GtkAppAboutDialog, revision_1); - gtk_widget_class_bind_template_child(widget, GtkAppAboutDialog, revision_2); - gtk_widget_class_bind_template_child(widget, GtkAppAboutDialog, revision_3); - gtk_widget_class_bind_template_child(widget, GtkAppAboutDialog, revision_4); - gtk_widget_class_bind_template_child(widget, GtkAppAboutDialog, revision_5); - } @@ -107,40 +102,14 @@ static void gtk_app_about_dialog_class_init(GtkAppAboutDialogClass *class) static void gtk_app_about_dialog_init(GtkAppAboutDialog *dialog) { - unsigned int revision; /* Numéro de révision */ - unsigned int max; /* Nbre. de boucles à effectuer*/ - unsigned int i; /* Boucle de parcours */ - unsigned int level; /* Unité la plus importante */ - char buffer[64]; /* Nom d'image à forger */ - gtk_widget_init_template(GTK_WIDGET(dialog)); - revision = REVISION; - max = log(revision) / log(10); - - assert(max <= 6); - - for (i = 0; i <= max; i++) - { - level = pow(10, max - i); - - snprintf(buffer, 64, "/org/chrysalide/gui/dialogs/about/revision_%u.png", revision / level); - - gtk_picture_set_resource(dialog->revisions[i], buffer); - - revision %= level; - - } - - for (; i < 6; i++) - gtk_widget_set_visible(GTK_WIDGET(dialog->revisions[i]), FALSE); - } /****************************************************************************** * * -* Paramètres : dialog = instance d'objet GLib à traiter. * +* Paramètres : object = instance d'objet GLib à traiter. * * * * Description : Supprime toutes les références externes. * * * @@ -150,18 +119,18 @@ static void gtk_app_about_dialog_init(GtkAppAboutDialog *dialog) * * ******************************************************************************/ -static void gtk_app_about_dialog_dispose(GtkAppAboutDialog *dialog) +static void gtk_app_about_dialog_dispose(GObject *object) { - gtk_widget_dispose_template(GTK_WIDGET(dialog), GTK_TYPE_APP_ABOUT_DIALOG); + gtk_widget_dispose_template(GTK_WIDGET(object), GTK_TYPE_APP_ABOUT_DIALOG); - G_OBJECT_CLASS(gtk_app_about_dialog_parent_class)->dispose(G_OBJECT(dialog)); + G_OBJECT_CLASS(gtk_app_about_dialog_parent_class)->dispose(object); } /****************************************************************************** * * -* Paramètres : dialog = instance d'objet GLib à traiter. * +* Paramètres : object = instance d'objet GLib à traiter. * * * * Description : Procède à la libération totale de la mémoire. * * * @@ -171,9 +140,9 @@ static void gtk_app_about_dialog_dispose(GtkAppAboutDialog *dialog) * * ******************************************************************************/ -static void gtk_app_about_dialog_finalize(GtkAppAboutDialog *dialog) +static void gtk_app_about_dialog_finalize(GObject *object) { - G_OBJECT_CLASS(gtk_app_about_dialog_parent_class)->finalize(G_OBJECT(dialog)); + G_OBJECT_CLASS(gtk_app_about_dialog_parent_class)->finalize(object); } diff --git a/src/gui/dialogs/about.css b/src/gui/dialogs/about.css new file mode 100644 index 0000000..af3fa97 --- /dev/null +++ b/src/gui/dialogs/about.css @@ -0,0 +1,9 @@ + +aboutdialog > box { + + background-color: black; + + background-image: url('resource:///re/chrysalide/framework/gui/dialogs/about/bg.png'); + background-repeat: no-repeat; + +} diff --git a/src/gui/dialogs/about.ui b/src/gui/dialogs/about.ui index 989e53b..7b519d2 100644 --- a/src/gui/dialogs/about.ui +++ b/src/gui/dialogs/about.ui @@ -8,91 +8,13 @@ true false - - vertical - - - - - - - - horizontal - center - 14 - - - 253 - resource:///org/chrysalide/gui/dialogs/about/chrysalide_text.png - - - - - - - - horizontal - 149 - - - 14 - resource:///org/chrysalide/gui/dialogs/about/revision.png - - - - - 14 - resource:///org/chrysalide/gui/dialogs/about/revision.png - - - - - 14 - resource:///org/chrysalide/gui/dialogs/about/revision.png - - - - - 14 - resource:///org/chrysalide/gui/dialogs/about/revision.png - - - - - 14 - resource:///org/chrysalide/gui/dialogs/about/revision.png - - - - - 14 - resource:///org/chrysalide/gui/dialogs/about/revision.png - - - - - 14 - resource:///org/chrysalide/gui/dialogs/about/revision.png - - - - - 22 + 368 10 <span fgcolor='white'>Copyright (C) 2008-2025 Cyrille Bagard</span> True diff --git a/src/gui/dialogs/gresource.xml b/src/gui/dialogs/gresource.xml index 169f440..966d9c8 100644 --- a/src/gui/dialogs/gresource.xml +++ b/src/gui/dialogs/gresource.xml @@ -1,22 +1,8 @@ + about.css about.ui preferences.ui - - ../../../pixmaps/chrysalide-full.png - ../../../pixmaps/chrysalide_text.png - ../../../pixmaps/revision.png - ../../../pixmaps/revision_0.png - ../../../pixmaps/revision_1.png - ../../../pixmaps/revision_2.png - ../../../pixmaps/revision_3.png - ../../../pixmaps/revision_4.png - ../../../pixmaps/revision_5.png - ../../../pixmaps/revision_6.png - ../../../pixmaps/revision_7.png - ../../../pixmaps/revision_8.png - ../../../pixmaps/revision_9.png - diff --git a/src/gui/style.css b/src/gui/style.css index e21d931..dce41fa 100644 --- a/src/gui/style.css +++ b/src/gui/style.css @@ -36,11 +36,7 @@ list.boxed-list, list.boxed-list > row:last-child { /* about.css */ -.black-bg { - - background-color: black; - -} +@import url('resource:///re/chrysalide/framework/gui/dialogs/about.css'); /* welcome.css */ diff --git a/tools/about/build.py b/tools/about/build.py new file mode 100644 index 0000000..2267492 --- /dev/null +++ b/tools/about/build.py @@ -0,0 +1,120 @@ + +# apt install libcairo2-dev pkg-config python3-dev +# pip install pycairo + +import cairo +import math +import sys + +from enum import IntEnum + +from ttf import create_cairo_font_face_for_file + + +WIDTH = 350 +HEIGHT = 430 + + +def deg2rad(degrees): + return degrees * (math.pi / 180) + + +class TextAlignment(IntEnum): + LEFT = 1 + CENTER = 2 + RIGHT = 3 + + +def draw_text(ctx, text, pos, fsize, noise, theta = 0.0, align = TextAlignment.CENTER): + """Dessine un texte en respectant des propriétées.""" + + ctx.save() + + ctx.set_font_size(fsize) + + fascent, fdescent, fheight, fxadvance, fyadvance = ctx.font_extents() + x_off, y_off, tw, th = ctx.text_extents(text)[:4] + + if align == TextAlignment.LEFT: + nx = 0 + elif align == TextAlignment.RIGHT: + nx = -tw + else: + nx = -tw / 2.0 + + ny = fheight / 2 + + ctx.translate(pos[0], pos[1]) + ctx.rotate(theta) + ctx.translate(nx, ny) + ctx.move_to(0,0) + + ctx.text_path(text) + ctx.set_source_surface(noise, 0, -fheight) + ctx.fill_preserve() + ctx.set_source_rgb(1.0, 1.0, 1.0) + ctx.set_line_width(1) + ctx.stroke() + + ctx.restore() + + +if __name__ == '__main__': + """Point d'entrée du script.""" + + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT) + + cr = cairo.Context(surface) + + face = create_cairo_font_face_for_file('cour10p_b.ttf') + cr.set_font_face(face) + + noise = cairo.ImageSurface.create_from_png('noise.png') + + # Type d'édition + + if len(sys.argv) > 2: + + draw_text(cr, sys.argv[2], [ 300, 25 ], 32, noise, + theta = deg2rad(270), align = TextAlignment.RIGHT) + + cr.set_source_rgb(0, 0, 0) + cr.paint_with_alpha(0.4) + + # Numéro de version + + draw_text(cr, sys.argv[1], [ 149, 10 + 259 + 14 + 42 + 2 ], 23, noise, align=TextAlignment.LEFT) + + cr.set_source_rgb(0, 0, 0) + cr.paint_with_alpha(0.5) + + # Logo + + logo = cairo.ImageSurface.create_from_png('logo.png') + + scale_y = 259 / logo.get_height() + + cr.save() + + cr.translate((WIDTH - (logo.get_width() * scale_y)) / 2, 12) + + cr.scale(scale_y, scale_y) + + cr.set_source_surface(logo, 0, 0) + cr.paint() + + cr.restore() + + # Titre + + draw_text(cr, 'Chrysalide', [ WIDTH / 2, 10 + 259 + 14 + 4 ], 42, noise) + + # cr.rectangle(265, 30, 10, 10) + # cr.set_source_rgb(1, 0, 0) + # cr.fill() + + # cr.rectangle(265, 255, 10, 10) + # cr.set_source_rgb(1, 0, 0) + # cr.fill() + + surface.write_to_png('bg.png') diff --git a/tools/about/courier-10-pitch-bold_EQ97V.zip b/tools/about/courier-10-pitch-bold_EQ97V.zip new file mode 100644 index 0000000..0585fe6 Binary files /dev/null and b/tools/about/courier-10-pitch-bold_EQ97V.zip differ diff --git a/tools/about/courier10point.zip b/tools/about/courier10point.zip new file mode 100644 index 0000000..82cd0de Binary files /dev/null and b/tools/about/courier10point.zip differ diff --git a/tools/about/gen.sh b/tools/about/gen.sh new file mode 100755 index 0000000..c22ec59 --- /dev/null +++ b/tools/about/gen.sh @@ -0,0 +1,52 @@ +#!/bin/bash + + +# Cf. https://stackoverflow.com/questions/59895/how-do-i-get-the-directory-where-a-bash-script-is-located-from-within-the-script/246128#246128 +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +pushd $SCRIPT_DIR > /dev/null + + +# Obtention de la police Courier-10-Pitch-Bold +# -------------------------------------------- + +# https://fontsgeek.com/fonts/Courier-10-Pitch-Bold +# https://fontsgeek.com/terms-and-conditions + +# => courier-10-pitch-bold_EQ97V.zip + + +# Courier 10 Pitch font missing after upgrade to 17.04? +# https://askubuntu.com/questions/914352/courier-10-pitch-font-missing-after-upgrade-to-17-04 + +# https://groups.google.com/g/trelby/c/CGkpcMBXW9U +# -> http://www.trelby.org/files/release/font/courier10point.zip +# --> https://web.archive.org/web/20140326080337/http://www.trelby.org/files/release/font/courier10point.zip + +# https://news.ycombinator.com/item?id=18802628 +# -> https://www.trelby.org/assets/courier10point.zip + +# => courier10point.zip + +if [ ! -f cour10p_b.ttf ]; then + + unzip -p courier10point.zip courier10point/cour10p_b.ttf > cour10p_b.ttf + +fi + + +# Construction de l'image de fond +#-------------------------------- + +if [ $# -lt 1 -o $# -gt 2 ]; then + + echo "Usage: $0 " + exit 123 + +fi + +python3 ./build.py $* + +cp bg.png ../../src/data/images/about-bg.png + +popd > /dev/null diff --git a/tools/about/logo.png b/tools/about/logo.png new file mode 100644 index 0000000..9bf9969 Binary files /dev/null and b/tools/about/logo.png differ diff --git a/tools/about/noise.png b/tools/about/noise.png new file mode 100644 index 0000000..b5d6b91 Binary files /dev/null and b/tools/about/noise.png differ diff --git a/tools/about/ttf.py b/tools/about/ttf.py new file mode 100644 index 0000000..eb6c027 --- /dev/null +++ b/tools/about/ttf.py @@ -0,0 +1,111 @@ + +# Source: https://www.cairographics.org/cookbook/freetypepython/ + +import ctypes as ct +import cairo + +_initialized = False +def create_cairo_font_face_for_file (filename, faceindex=0, loadoptions=0): + "given the name of a font file, and optional faceindex to pass to FT_New_Face" \ + " and loadoptions to pass to cairo_ft_font_face_create_for_ft_face, creates" \ + " a cairo.FontFace object that may be used to render text with that font." + global _initialized + global _freetype_so + global _cairo_so + global _ft_lib + global _ft_destroy_key + global _surface + + CAIRO_STATUS_SUCCESS = 0 + FT_Err_Ok = 0 + + if not _initialized: + # find shared objects + _freetype_so = ct.CDLL("libfreetype.so.6") + _cairo_so = ct.CDLL("libcairo.so.2") + _cairo_so.cairo_ft_font_face_create_for_ft_face.restype = ct.c_void_p + _cairo_so.cairo_ft_font_face_create_for_ft_face.argtypes = [ ct.c_void_p, ct.c_int ] + _cairo_so.cairo_font_face_get_user_data.restype = ct.c_void_p + _cairo_so.cairo_font_face_get_user_data.argtypes = (ct.c_void_p, ct.c_void_p) + _cairo_so.cairo_font_face_set_user_data.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p) + _cairo_so.cairo_set_font_face.argtypes = [ ct.c_void_p, ct.c_void_p ] + _cairo_so.cairo_font_face_status.argtypes = [ ct.c_void_p ] + _cairo_so.cairo_font_face_destroy.argtypes = (ct.c_void_p,) + _cairo_so.cairo_status.argtypes = [ ct.c_void_p ] + # initialize freetype + _ft_lib = ct.c_void_p() + status = _freetype_so.FT_Init_FreeType(ct.byref(_ft_lib)) + if status != FT_Err_Ok : + raise RuntimeError("Error %d initializing FreeType library." % status) + #end if + + class PycairoContext(ct.Structure): + _fields_ = \ + [ + ("PyObject_HEAD", ct.c_byte * object.__basicsize__), + ("ctx", ct.c_void_p), + ("base", ct.c_void_p), + ] + #end PycairoContext + + _surface = cairo.ImageSurface(cairo.FORMAT_A8, 0, 0) + _ft_destroy_key = ct.c_int() # dummy address + _initialized = True + #end if + + ft_face = ct.c_void_p() + cr_face = None + try : + # load FreeType face + status = _freetype_so.FT_New_Face(_ft_lib, filename.encode("utf-8"), faceindex, ct.byref(ft_face)) + if status != FT_Err_Ok : + raise RuntimeError("Error %d creating FreeType font face for %s" % (status, filename)) + #end if + + # create Cairo font face for freetype face + cr_face = _cairo_so.cairo_ft_font_face_create_for_ft_face(ft_face, loadoptions) + status = _cairo_so.cairo_font_face_status(cr_face) + if status != CAIRO_STATUS_SUCCESS : + raise RuntimeError("Error %d creating cairo font face for %s" % (status, filename)) + #end if + # Problem: Cairo doesn't know to call FT_Done_Face when its font_face object is + # destroyed, so we have to do that for it, by attaching a cleanup callback to + # the font_face. This only needs to be done once for each font face, while + # cairo_ft_font_face_create_for_ft_face will return the same font_face if called + # twice with the same FT Face. + # The following check for whether the cleanup has been attached or not is + # actually unnecessary in our situation, because each call to FT_New_Face + # will return a new FT Face, but we include it here to show how to handle the + # general case. + if _cairo_so.cairo_font_face_get_user_data(cr_face, ct.byref(_ft_destroy_key)) == None : + status = _cairo_so.cairo_font_face_set_user_data \ + ( + cr_face, + ct.byref(_ft_destroy_key), + ft_face, + _freetype_so.FT_Done_Face + ) + if status != CAIRO_STATUS_SUCCESS : + raise RuntimeError("Error %d doing user_data dance for %s" % (status, filename)) + #end if + ft_face = None # Cairo has stolen my reference + #end if + + # set Cairo font face into Cairo context + cairo_ctx = cairo.Context(_surface) + cairo_t = PycairoContext.from_address(id(cairo_ctx)).ctx + _cairo_so.cairo_set_font_face(cairo_t, cr_face) + status = _cairo_so.cairo_font_face_status(cairo_t) + if status != CAIRO_STATUS_SUCCESS : + raise RuntimeError("Error %d creating cairo font face for %s" % (status, filename)) + #end if + + finally : + _cairo_so.cairo_font_face_destroy(cr_face) + _freetype_so.FT_Done_Face(ft_face) + #end try + + # get back Cairo font face as a Python object + face = cairo_ctx.get_font_face() + return face +#end create_cairo_font_face_for_file -- cgit v0.11.2-87-g4458