c++gtkcairogtkmmpango

gtkmm : Drawing text with cairo


I want to draw a simple text with Cairo in an app using Gtkmm. I want to give the font style (it can be Pango::FontDescription or Pango::Context and so on ...) directly to draw text with Cairo when the Gtk::FontButton is clicked (in other words when the signal_font_set signal is issued). In the example below, I have a Gtk::HeaderBar that contains a Gtk::FontButton that sends a Glib::RefPtr<<Pango::Context>> to the drawing class when clicked. I want something like this:

MyWindow.cpp:

#include <iostream>
#include "MyWindow.h"

MyWindow::MyWindow() {
    
    set_default_size(1000, 1000);
    set_position(Gtk::WIN_POS_CENTER);

    header.set_show_close_button(true);
    header.pack_start(fontButton);;

    set_titlebar(header);

    fontButton.signal_font_set().connect([&] {
        drawingArea.select_font(fontButton.get_pango_context());
    });

    scrolledWindow.add(drawingArea);
    add(scrolledWindow);
    show_all();
}

MyDrawing.h:

#ifndef DRAWING_H
#define DRAWING_H

#include <gtkmm.h>
#include <cairo/cairo.h>

class MyDrawing : public Gtk::Layout
{

public:

     MyDrawing();
    ~MyDrawing() override;

    void select_font(Glib::RefPtr<Pango::Context> p_pangoContext);

private:
    bool draw_text(const Cairo::RefPtr<::Cairo::Context>& p_context);

    Glib::RefPtr<Pango::Context> m_pangoContext;
};

#endif // DRAWING_H

and MyDrawing.cpp:

#include <iostream>
#include <utility>
#include "MyDrawing.h"
#include <cairomm/context.h>
#include <cairomm/surface.h>


MyDrawing::MyDrawing() {

    this->signal_draw().connect(sigc::mem_fun(*this, &MyDrawing::draw_text));
}

MyDrawing::~MyDrawing() = default;

bool MyDrawing::draw_text(const Cairo::RefPtr<::Cairo::Context> &p_context) {

    auto layout = create_pango_layout("hello ");
    if(m_pangoContext) {
        layout->set_font_description(m_pangoContext->get_font_description());
    }
    else {
        Pango::FontDescription fontDescription;
        layout->set_font_description(fontDescription);
    }
    p_context->save();
    p_context->set_font_size(30);
    p_context->set_source_rgb(0.1, 0.1, 0.1);
    p_context->move_to(40, 40);
    layout->show_in_cairo_context(p_context);
    p_context->restore();
    return true;
}

void MyDrawing::select_font(Glib::RefPtr<Pango::Context> p_pangoContext) {
    this->m_pangoContext = std::move(p_pangoContext);
    queue_draw();
}

when I set up Pango::FontDescription manually like :

Pango::FontDescription fontDescription;
fontDescription.set_weight(Pango::WEIGHT_BOLD);
fontDescription.set_style(Pango::STYLE_ITALIC);
layout->set_font_description(fontDescription);

it works:

enter image description here


Solution

  • Here is a way that works: instead of using a Pango::Context, you can get the font name from Gtk::FontButton::get_font_name and then create a Pango::FontDescription from it. Here is a minimal example to show this:

    #include <iostream>
    #include <gtkmm.h>
    
    class MyDrawing : public Gtk::Layout
    {
    
    public:
    
        MyDrawing();
    
        void set_font(const std::string& p_selectedFont);
    
    private:
    
        bool draw_text(const Cairo::RefPtr<::Cairo::Context>& p_context);
    
        std::string                 m_selectedFont;
        Glib::RefPtr<Pango::Layout> m_pangoLayout;
        
    };
    
    MyDrawing::MyDrawing()
    {
        signal_draw().connect(sigc::mem_fun(*this, &MyDrawing::draw_text));
    
        m_pangoLayout = Pango::Layout::create(get_pango_context());
        m_pangoLayout->set_text(
            "This text's appearance should change\n"
            "when font description changes."
        );
    }
    
    void MyDrawing::set_font(const std::string& p_selectedFont)
    {
        // Record the font selected by the user (as a string... Ugh!):
        m_selectedFont = p_selectedFont;
        std::cout << "Selected font: " << m_selectedFont << std::endl;
    
        // Request a redraw:
        queue_draw();
    }
    
    bool MyDrawing::draw_text(const Cairo::RefPtr<::Cairo::Context>& p_context)
    {
        // Create a font description from what was selected by the user earlier,
        // and set it:
        const Pango::FontDescription description{m_selectedFont};
        m_pangoLayout->set_font_description(description);
    
        p_context->save();
        p_context->set_font_size(30);
        p_context->set_source_rgb(0.1, 0.1, 0.1);
        p_context->move_to(40, 40);
        m_pangoLayout->show_in_cairo_context(p_context);
        p_context->restore();
    
        return true;
    }
    
    class MyWindow : public Gtk::ApplicationWindow
    {
    
    public:
    
        MyWindow();
    
    private:
    
        Gtk::HeaderBar  m_headerBar;
        Gtk::FontButton m_fontButton;
        MyDrawing       m_drawingArea;
    
    };
    
    MyWindow::MyWindow()
    {
        m_headerBar.set_show_close_button(true);
        m_headerBar.pack_start(m_fontButton);;
    
        set_titlebar(m_headerBar);
    
        m_fontButton.signal_font_set().connect(
            [this]()
            {
                m_drawingArea.set_font(m_fontButton.get_font_name());
            }
        );
    
        add(m_drawingArea);
        show_all();
    }
    
    int main(int argc, char *argv[])
    {
        auto app = Gtk::Application::create(argc, argv, "org.gtkmm.examples.base");
      
        MyWindow window;
        window.show_all();
      
        return app->run(window);
    }
    

    I am not a Pango expert, so I don't know if there is a better way using your initial strategy. Furthermore, the string representing the font names seem to need to follow some convention. From the documentation of Pango::FontDescription::FontDescription(const Glib::ustring& font_name) (Gtkmm 3.24):

    font_name must have the form "[FAMILY-LIST] [STYLE-OPTIONS] [SIZE]", where FAMILY-LIST is a comma separated list of families optionally terminated by a comma, STYLE_OPTIONS is a whitespace separated list of words where each WORD describes one of style, variant, weight, or stretch, and SIZE is an decimal number (size in points). Any one of the options may be absent. If FAMILY-LIST is absent, then the family_name field of the resulting font description will be initialized to 0. If STYLE-OPTIONS is missing, then all style options will be set to the default values. If SIZE is missing, the size in the resulting font description will be set to 0.

    so you may have to do some testing of your own to make sure this works for all fonts. In my case, all 10-15 fonts I have tested seemed to work fine.