I feel your pain! There is no easy solution for rotating a label in Gtk4, unfortunately. For me, the Pango solution was easiest to implement, because in Holger's solution one would need to subclass the GtkLayoutManager to size the widget correctly, if I'm not mistaken.
So if you don't need an actual GtkLabel, and a plain widget with some vertical text will do, then you could use Pango to draw some text in a GtkDrawingArea, like in my example below. I used two passes of some custom drawing function, in which the first pass merely calculates the extent of the text. This allows setting the exact size of the drawing area, before doing the actual drawing in a second pass.

This is the full code, as some kind of "hello world" example
#include <gtk/gtk.h>
#include <string>
struct vertical_text_t {
std::string text;
int w = -1;
int h = -1;
};
static vertical_text_t vt{"Vertical text"};
void draw_vertical_text(GtkDrawingArea *drawing_area, cairo_t *cr, int w_widget, int /*h_widget*/, gpointer data) {
PangoContext *context = gtk_widget_create_pango_context(GTK_WIDGET(drawing_area));
PangoLayout *layout = pango_layout_new(context);
vertical_text_t *vt = (vertical_text_t*)data;
pango_layout_set_text(layout, vt->text.c_str(), -1);
PangoFontDescription *desc = pango_font_description_from_string("Sans 18"); // Same font and size as in our CSS file
pango_layout_set_font_description(layout, desc);
int w_text, h_text;
pango_layout_get_pixel_size(layout, &w_text, &h_text);
if (vt->w == -1) { // On a first pass, we'll only calculate the dimensions of the text (before rotation) and return
vt->w = w_text;
vt->h = h_text;
} else { // On a second pass, we'll actually do some drawing
cairo_rotate(cr, -G_PI/2); // Rotate the text 90 degrees counter clockwise
// Align the text to the upper-left corner
cairo_move_to(cr, -w_text, 0); // Vertical, horizontal (because the axes are swapped due to the rotation);
cairo_set_source_rgba(cr, 0, 0, 0, 1.0); // Black
pango_cairo_show_layout(cr, layout);
}
pango_font_description_free(desc);
g_object_unref(layout);
g_object_unref(context);
}
static void activate(GtkApplication *app, gpointer user_data) {
GtkWidget *window = gtk_application_window_new(app);
gtk_window_set_title(GTK_WINDOW(window), "Vertical text demo");
gtk_window_set_default_size(GTK_WINDOW(window), 300, 300);
GtkWidget *vertical_label = gtk_drawing_area_new();
draw_vertical_text(GTK_DRAWING_AREA(vertical_label), nullptr, 0, 0, &vt); // First pass; to calculate the size of the text; we only need to pass the drawing area and the vertical text struct
gtk_widget_set_size_request(vertical_label, vt.h, vt.w); // Adjust the size request based upon the results from the first pass
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(vertical_label), draw_vertical_text, &vt, nullptr); // Second pass will be triggered when the widget is drawn
gtk_widget_set_halign(vertical_label, GTK_ALIGN_START);
gtk_widget_set_valign(vertical_label, GTK_ALIGN_START);
gtk_window_set_child(GTK_WINDOW(window), vertical_label);
gtk_window_present(GTK_WINDOW(window));
}
int main(int argc, char** argv, char** env) {
GtkApplication *app;
app = gtk_application_new("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect(app, "activate", G_CALLBACK(activate), nullptr);
int status = g_application_run(G_APPLICATION(app), argc, argv);
g_object_unref(app);
return status;
}