Handling text and fonts
Besides drawing graphics on a window, we often want to draw text. Text strings have two major properties: the characters to be drawn and the font with which they are drawn. In order to draw text, we need to first request the X server to load a font. We then assign a font to a Graphic Context, and finally, we draw the text in a window using the Graphic Context.
1. The Font structure
In order to support flexible fonts, a font type is defined, and guess what, it's an Id:
typedef uint32_t xcb_font_t;
A font id is passed to several functions that handle fonts selection and text drawing. We ask the X server to attribute an Id to our font with the function:
xcb_font_t xcb_generate_id (xcb_connection_t *c);
2. Opening a Font
To open a font, we use the following function:
xcb_void_cookie_t xcb_open_font (xcb_connection_t *c,
xcb_font_t fid,
uint16_t name_len,
const char *name);
The fid parameter is the font Id defined by xcb_generate_id() (see above). The name parameter is the name of the font you want to open. Use the command xlsfonts in a terminal to know which are the fonts available on your computer. The parameter name_len is the length of the name of the font (given by strlen()).
3. Assigning a Font to a Graphic Context
Once a font is opened, you have to create a Graphic Context that will contain the information about the color of the foreground and the background used when you draw a text in a Drawable. Here is an example of a Graphic Context that will allow us to draw an opened font with a black foreground and a white background:
/*
* c is the connection
* screen is the screen where the window is displayed
* window is the window in which we will draw the text
* font is the opened font
*/
uint32_t value_list[3];
xcb_gcontext_t gc;
uint32_t mask;
gc = xcb_generate_id (c);
mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
value_list[0] = screen->black_pixel;
value_list[1] = screen->white_pixel;
value_list[2] = font;
xcb_create_gc (c, gc, window, mask, value_list);
/* The font is not needed anymore, so we close it */
xcb_close_font (c, font);
4. Drawing text in a drawable
To draw a text in a drawable, we use the following function:
xcb_void_cookie_t xcb_image_text_8 (xcb_connection_t *c,
uint8_t string_len,
xcb_drawable_t drawable,
xcb_gcontext_t gc,
int16_t x,
int16_t y,
const char *string);
The string parameter is the text to draw. The location of the drawing is given by the parameters x and y. The base line of the text is exactly the parameter y.
5. Complete example
This example draw a text at 10 pixels (for the base line) of the bottom of a window. Pressing the Esc key exits the program.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <xcb/xcb.h>
#define WIDTH 300
#define HEIGHT 100
static xcb_gc_t getFontGC (xcb_connection_t *c,
xcb_screen_t *screen,
xcb_window_t window,
const char *font_name );
static void drawText (xcb_connection_t *c,
xcb_screen_t *screen,
xcb_window_t window,
int16_t x1,
int16_t y1,
const char *label );
static void
testCookie (xcb_void_cookie_t cookie,
xcb_connection_t *connection,
char *errMessage )
{
xcb_generic_error_t *error = xcb_request_check (connection, cookie);
if (error) {
fprintf (stderr, "ERROR: %s : %"PRIu8"\n", errMessage , error->error_code);
xcb_disconnect (connection);
exit (-1);
}
}
static void
drawText (xcb_connection_t *connection,
xcb_screen_t *screen,
xcb_window_t window,
int16_t x1,
int16_t y1,
const char *label )
{
/* get graphics context */
xcb_gcontext_t gc = getFontGC (connection, screen, window, "fixed");
/* draw the text */
xcb_void_cookie_t textCookie = xcb_image_text_8_checked (connection,
strlen (label),
window,
gc,
x1, y1,
label );
testCookie(textCookie, connection, "can't paste text");
/* free the gc */
xcb_void_cookie_t gcCookie = xcb_free_gc (connection, gc);
testCookie(gcCookie, connection, "can't free gc");
}
static xcb_gc_t
getFontGC (xcb_connection_t *connection,
xcb_screen_t *screen,
xcb_window_t window,
const char *font_name )
{
/* get font */
xcb_font_t font = xcb_generate_id (connection);
xcb_void_cookie_t fontCookie = xcb_open_font_checked (connection,
font,
strlen (font_name),
font_name );
testCookie(fontCookie, connection, "can't open font");
/* create graphics context */
xcb_gcontext_t gc = xcb_generate_id (connection);
uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
uint32_t value_list[3] = { screen->black_pixel,
screen->white_pixel,
font };
xcb_void_cookie_t gcCookie = xcb_create_gc_checked (connection,
gc,
window,
mask,
value_list );
testCookie(gcCookie, connection, "can't create gc");
/* close font */
fontCookie = xcb_close_font_checked (connection, font);
testCookie(fontCookie, connection, "can't close font");
return gc;
}
int
main ()
{
/* get the connection */
int screenNum;
xcb_connection_t *connection = xcb_connect (NULL, &screenNum);
if (!connection) {
fprintf (stderr, "ERROR: can't connect to an X server\n");
return -1;
}
/* get the current screen */
xcb_screen_iterator_t iter = xcb_setup_roots_iterator (xcb_get_setup (connection));
// we want the screen at index screenNum of the iterator
for (int i = 0; i < screenNum; ++i) {
xcb_screen_next (&iter);
}
xcb_screen_t *screen = iter.data;
if (!screen) {
fprintf (stderr, "ERROR: can't get the current screen\n");
xcb_disconnect (connection);
return -1;
}
/* create the window */
xcb_window_t window = xcb_generate_id (connection);
uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
uint32_t values[2];
values[0] = screen->white_pixel;
values[1] = XCB_EVENT_MASK_KEY_RELEASE |
XCB_EVENT_MASK_BUTTON_PRESS |
XCB_EVENT_MASK_EXPOSURE |
XCB_EVENT_MASK_POINTER_MOTION;
xcb_void_cookie_t windowCookie = xcb_create_window_checked (connection,
screen->root_depth,
window, screen->root,
20, 200,
WIDTH, HEIGHT,
0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
screen->root_visual,
mask, values);
testCookie(windowCookie, connection, "can't create window");
xcb_void_cookie_t mapCookie = xcb_map_window_checked (connection, window);
testCookie(mapCookie, connection, "can't map window");
xcb_flush(connection); // make sure window is drawn
/* event loop */
xcb_generic_event_t *event;
while (1) { ;
if ( (event = xcb_poll_for_event(connection)) ) {
switch (event->response_type & ~0x80) {
case XCB_EXPOSE: {
drawText (connection,
screen,
window,
10, HEIGHT - 10,
"Press ESC key to exit..." );
break;
}
case XCB_KEY_RELEASE: {
xcb_key_release_event_t *kr = (xcb_key_release_event_t *)event;
switch (kr->detail) {
/* ESC */
case 9: {
free (event);
xcb_disconnect (connection);
return 0;
}
}
free (event);
}
}
}
}
return 0;
}