/* * Copyright (C) 2006 Sergey V. Udaltsov * Copyright (C) 2010 Daiki Ueno * Copyright (C) 2010 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include "eek-keyboard.h" #include "eek-key.h" #include "eek-drawing.h" #include "eek-keysym.h" void eek_draw_text_on_layout (PangoLayout *layout, const gchar *text) { /* pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER); */ pango_layout_set_text (layout, text, -1); } struct _GetFontSizeCallbackData { PangoLayout *layout; EekKeysymCategory category; gint minimum_font_size; gint font_size; }; typedef struct _GetFontSizeCallbackData GetFontSizeCallbackData; static gint get_font_size (const gchar *text, EekBounds *bounds, PangoLayout *layout) { gdouble scale_x, scale_y; const PangoFontDescription *base_font_desc; PangoFontDescription *font_desc; PangoRectangle logical_rect = { 0, }; gint font_size; layout = pango_layout_copy (layout); base_font_desc = pango_layout_get_font_description (layout); font_desc = pango_font_description_copy (base_font_desc); font_size = eek_bounds_long_side (bounds) * PANGO_SCALE; pango_font_description_set_size (font_desc, font_size); pango_layout_set_font_description (layout, font_desc); pango_font_description_free (font_desc); eek_draw_text_on_layout (layout, text); pango_layout_get_extents (layout, NULL, &logical_rect); scale_x = scale_y = 1.0; if (logical_rect.width > bounds->width * PANGO_SCALE) scale_x = bounds->width * PANGO_SCALE / logical_rect.width; if (logical_rect.height > bounds->height * PANGO_SCALE) scale_y = bounds->height * PANGO_SCALE / logical_rect.height; g_object_unref (layout); return font_size * (scale_x < scale_y ? scale_x : scale_y); } static void egf_key_callback (EekElement *element, gpointer user_data) { EekKey *key = EEK_KEY(element); GetFontSizeCallbackData *data = user_data; guint *keysyms; gint num_groups, num_levels, i; gdouble font_size; eek_key_get_keysyms (key, &keysyms, &num_groups, &num_levels); for (i = 0; i < num_groups * num_levels; i++) { guint keysym = keysyms[i]; EekBounds bounds; const gchar *label; if (keysym == EEK_INVALID_KEYSYM || eek_keysym_get_category (keysym) != data->category) continue; eek_element_get_bounds (EEK_ELEMENT(key), &bounds); label = eek_keysym_to_string (keysym); font_size = get_font_size (label, &bounds, data->layout); if (font_size < data->font_size && font_size >= data->minimum_font_size) data->font_size = font_size; } } static void egf_section_callback (EekElement *element, gpointer user_data) { eek_container_foreach_child (EEK_CONTAINER(element), egf_key_callback, user_data); } static PangoFontDescription * get_font_for_category (EekKeyboard *keyboard, EekKeysymCategory category, PangoLayout *layout, gdouble minimum_font_size, gdouble maximum_font_size) { GetFontSizeCallbackData data; PangoFontDescription *font_desc; const PangoFontDescription *base_font_desc; data.layout = layout; data.category = category; data.minimum_font_size = minimum_font_size; data.font_size = maximum_font_size; eek_container_foreach_child (EEK_CONTAINER(keyboard), egf_section_callback, &data); base_font_desc = pango_layout_get_font_description (layout); font_desc = pango_font_description_copy (base_font_desc); pango_font_description_set_size (font_desc, data.font_size); return font_desc; } void eek_get_fonts (EekKeyboard *keyboard, PangoLayout *layout, PangoFontDescription **fonts) { EekBounds bounds; PangoFontDescription *font_desc; gint font_size; /* font for EEK_KEYSYM_CATEGORY_LETTER */ eek_element_get_bounds (EEK_ELEMENT(keyboard), &bounds); font_desc = get_font_for_category (keyboard, EEK_KEYSYM_CATEGORY_LETTER, layout, 0, eek_bounds_long_side (&bounds) * PANGO_SCALE); font_size = pango_font_description_get_size (font_desc); fonts[EEK_KEYSYM_CATEGORY_LETTER] = font_desc; /* font for EEK_KEYSYM_CATEGORY_FUNCTION */ font_desc = get_font_for_category (keyboard, EEK_KEYSYM_CATEGORY_FUNCTION, layout, font_size * 0.3, font_size); fonts[EEK_KEYSYM_CATEGORY_FUNCTION] = font_desc; /* font for EEK_KEYSYM_CATEGORY_KEYNAME */ font_desc = get_font_for_category (keyboard, EEK_KEYSYM_CATEGORY_KEYNAME, layout, font_size * 0.3, font_size); fonts[EEK_KEYSYM_CATEGORY_KEYNAME] = font_desc; } /* * The functions below are borrowed from * libgnomekbd/gkbd-keyboard-drawing.c. * Copyright (C) 2006 Sergey V. Udaltsov * * length(), point_line_distance(), normal_form(), inverse(), multiply(), * intersect(), rounded_corner(), draw_rounded_polygon() */ static gdouble length (gdouble x, gdouble y) { return sqrt (x * x + y * y); } static gdouble point_line_distance (gdouble ax, gdouble ay, gdouble nx, gdouble ny) { return ax * nx + ay * ny; } static void normal_form (gdouble ax, gdouble ay, gdouble bx, gdouble by, gdouble * nx, gdouble * ny, gdouble * d) { gdouble l; *nx = by - ay; *ny = ax - bx; l = length (*nx, *ny); *nx /= l; *ny /= l; *d = point_line_distance (ax, ay, *nx, *ny); } static void inverse (gdouble a, gdouble b, gdouble c, gdouble d, gdouble * e, gdouble * f, gdouble * g, gdouble * h) { gdouble det; det = a * d - b * c; *e = d / det; *f = -b / det; *g = -c / det; *h = a / det; } static void multiply (gdouble a, gdouble b, gdouble c, gdouble d, gdouble e, gdouble f, gdouble * x, gdouble * y) { *x = a * e + b * f; *y = c * e + d * f; } static void intersect (gdouble n1x, gdouble n1y, gdouble d1, gdouble n2x, gdouble n2y, gdouble d2, gdouble * x, gdouble * y) { gdouble e, f, g, h; inverse (n1x, n1y, n2x, n2y, &e, &f, &g, &h); multiply (e, f, g, h, d1, d2, x, y); } /* draw an angle from the current point to b and then to c, * with a rounded corner of the given radius. */ static void rounded_corner (cairo_t * cr, gdouble bx, gdouble by, gdouble cx, gdouble cy, gdouble radius) { gdouble ax, ay; gdouble n1x, n1y, d1; gdouble n2x, n2y, d2; gdouble pd1, pd2; gdouble ix, iy; gdouble dist1, dist2; gdouble nx, ny, d; gdouble a1x, a1y, c1x, c1y; gdouble phi1, phi2; cairo_get_current_point (cr, &ax, &ay); #ifdef KBDRAW_DEBUG printf (" current point: (%f, %f), radius %f:\n", ax, ay, radius); #endif /* make sure radius is not too large */ dist1 = length (bx - ax, by - ay); dist2 = length (cx - bx, cy - by); radius = MIN (radius, MIN (dist1, dist2)); /* construct normal forms of the lines */ normal_form (ax, ay, bx, by, &n1x, &n1y, &d1); normal_form (bx, by, cx, cy, &n2x, &n2y, &d2); /* find which side of the line a,b the point c is on */ if (point_line_distance (cx, cy, n1x, n1y) < d1) pd1 = d1 - radius; else pd1 = d1 + radius; /* find which side of the line b,c the point a is on */ if (point_line_distance (ax, ay, n2x, n2y) < d2) pd2 = d2 - radius; else pd2 = d2 + radius; /* intersect the parallels to find the center of the arc */ intersect (n1x, n1y, pd1, n2x, n2y, pd2, &ix, &iy); nx = (bx - ax) / dist1; ny = (by - ay) / dist1; d = point_line_distance (ix, iy, nx, ny); /* a1 is the point on the line a-b where the arc starts */ intersect (n1x, n1y, d1, nx, ny, d, &a1x, &a1y); nx = (cx - bx) / dist2; ny = (cy - by) / dist2; d = point_line_distance (ix, iy, nx, ny); /* c1 is the point on the line b-c where the arc ends */ intersect (n2x, n2y, d2, nx, ny, d, &c1x, &c1y); /* determine the first angle */ if (a1x - ix == 0) phi1 = (a1y - iy > 0) ? M_PI_2 : 3 * M_PI_2; else if (a1x - ix > 0) phi1 = atan ((a1y - iy) / (a1x - ix)); else phi1 = M_PI + atan ((a1y - iy) / (a1x - ix)); /* determine the second angle */ if (c1x - ix == 0) phi2 = (c1y - iy > 0) ? M_PI_2 : 3 * M_PI_2; else if (c1x - ix > 0) phi2 = atan ((c1y - iy) / (c1x - ix)); else phi2 = M_PI + atan ((c1y - iy) / (c1x - ix)); /* compute the difference between phi2 and phi1 mod 2pi */ d = phi2 - phi1; while (d < 0) d += 2 * M_PI; while (d > 2 * M_PI) d -= 2 * M_PI; #ifdef KBDRAW_DEBUG printf (" line 1 to: (%f, %f):\n", a1x, a1y); #endif if (!(isnan (a1x) || isnan (a1y))) cairo_line_to (cr, a1x, a1y); /* pick the short arc from phi1 to phi2 */ if (d < M_PI) cairo_arc (cr, ix, iy, radius, phi1, phi2); else cairo_arc_negative (cr, ix, iy, radius, phi1, phi2); #ifdef KBDRAW_DEBUG printf (" line 2 to: (%f, %f):\n", cx, cy); #endif cairo_line_to (cr, cx, cy); } void eek_draw_rounded_polygon (cairo_t *cr, gboolean filled, gdouble radius, EekPoint *points, gint num_points) { gint i, j; cairo_move_to (cr, (gdouble) (points[num_points - 1].x + points[0].x) / 2, (gdouble) (points[num_points - 1].y + points[0].y) / 2); #ifdef KBDRAW_DEBUG printf (" rounded polygon of radius %f:\n", radius); #endif for (i = 0; i < num_points; i++) { j = (i + 1) % num_points; rounded_corner (cr, (gdouble) points[i].x, (gdouble) points[i].y, (gdouble) (points[i].x + points[j].x) / 2, (gdouble) (points[i].y + points[j].y) / 2, radius); #ifdef KBDRAW_DEBUG printf (" corner (%d, %d) -> (%d, %d):\n", points[i].x, points[i].y, points[j].x, points[j].y); #endif }; cairo_close_path (cr); if (filled) cairo_fill (cr); else cairo_stroke (cr); }