Gimp/app/text/gimptextlayout.c
Michael Natterer 0908e53148 app: use the new unit conversion functions
and get rid of the brainfuck idea that app/ has to use _gimp_unit_foo()
functions, passing a gimp pointer. Instead, simply use the libgimpbase
API all over the place. Should we ever allow more than one gimp instance,
they will simply have to share one unit database.
2010-02-21 16:46:39 +01:00

634 lines
17 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* GimpText
* Copyright (C) 2002-2003 Sven Neumann <sven@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gegl.h>
#include <pango/pangocairo.h>
#include "libgimpbase/gimpbase.h"
#include "libgimpmath/gimpmath.h"
#include "text-types.h"
#include "core/gimpimage.h"
#include "gimptext.h"
#include "gimptextlayout.h"
struct _GimpTextLayout
{
GObject object;
GimpText *text;
gdouble xres;
gdouble yres;
PangoLayout *layout;
PangoRectangle extents;
};
static void gimp_text_layout_finalize (GObject *object);
static void gimp_text_layout_position (GimpTextLayout *layout);
static PangoContext * gimp_text_get_pango_context (GimpText *text,
gdouble xres,
gdouble yres);
static gint gimp_text_layout_pixel_size (gdouble value,
GimpUnit unit,
gdouble res);
static gint gimp_text_layout_point_size (gdouble value,
GimpUnit unit,
gdouble res);
G_DEFINE_TYPE (GimpTextLayout, gimp_text_layout, G_TYPE_OBJECT)
#define parent_class gimp_text_layout_parent_class
static void
gimp_text_layout_class_init (GimpTextLayoutClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gimp_text_layout_finalize;
}
static void
gimp_text_layout_init (GimpTextLayout *layout)
{
layout->text = NULL;
layout->layout = NULL;
}
static void
gimp_text_layout_finalize (GObject *object)
{
GimpTextLayout *layout = GIMP_TEXT_LAYOUT (object);
if (layout->text)
{
g_object_unref (layout->text);
layout->text = NULL;
}
if (layout->layout)
{
g_object_unref (layout->layout);
layout->layout = NULL;
}
G_OBJECT_CLASS (parent_class)->finalize (object);
}
GimpTextLayout *
gimp_text_layout_new (GimpText *text,
GimpImage *image)
{
GimpTextLayout *layout;
PangoContext *context;
PangoFontDescription *font_desc;
PangoAlignment alignment = PANGO_ALIGN_LEFT;
gdouble xres, yres;
gint size;
g_return_val_if_fail (GIMP_IS_TEXT (text), NULL);
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
font_desc = pango_font_description_from_string (text->font);
g_return_val_if_fail (font_desc != NULL, NULL);
gimp_image_get_resolution (image, &xres, &yres);
size = gimp_text_layout_point_size (text->font_size,
text->unit,
yres);
pango_font_description_set_size (font_desc, MAX (1, size));
context = gimp_text_get_pango_context (text, xres, yres);
layout = g_object_new (GIMP_TYPE_TEXT_LAYOUT, NULL);
layout->text = g_object_ref (text);
layout->layout = pango_layout_new (context);
layout->xres = xres;
layout->yres = yres;
pango_layout_set_wrap (layout->layout, PANGO_WRAP_WORD_CHAR);
g_object_unref (context);
pango_layout_set_font_description (layout->layout, font_desc);
pango_font_description_free (font_desc);
if (text->text)
pango_layout_set_text (layout->layout, text->text, -1);
else
pango_layout_set_text (layout->layout, NULL, 0);
switch (text->justify)
{
case GIMP_TEXT_JUSTIFY_LEFT:
alignment = PANGO_ALIGN_LEFT;
break;
case GIMP_TEXT_JUSTIFY_RIGHT:
alignment = PANGO_ALIGN_RIGHT;
break;
case GIMP_TEXT_JUSTIFY_CENTER:
alignment = PANGO_ALIGN_CENTER;
break;
case GIMP_TEXT_JUSTIFY_FILL:
alignment = PANGO_ALIGN_LEFT;
pango_layout_set_justify (layout->layout, TRUE);
break;
}
pango_layout_set_alignment (layout->layout, alignment);
switch (text->box_mode)
{
case GIMP_TEXT_BOX_DYNAMIC:
break;
case GIMP_TEXT_BOX_FIXED:
pango_layout_set_width (layout->layout,
gimp_text_layout_pixel_size (text->box_width,
text->box_unit,
xres));
break;
}
pango_layout_set_indent (layout->layout,
gimp_text_layout_pixel_size (text->indent,
text->unit,
xres));
pango_layout_set_spacing (layout->layout,
gimp_text_layout_pixel_size (text->line_spacing,
text->unit,
yres));
if (fabs (text->letter_spacing) > 0.1)
{
PangoAttrList *attrs = pango_attr_list_new ();
PangoAttribute *attr;
attr = pango_attr_letter_spacing_new (text->letter_spacing * PANGO_SCALE);
attr->start_index = 0;
attr->end_index = -1;
pango_attr_list_insert (attrs, attr);
pango_layout_set_attributes (layout->layout, attrs);
pango_attr_list_unref (attrs);
}
gimp_text_layout_position (layout);
switch (text->box_mode)
{
case GIMP_TEXT_BOX_DYNAMIC:
break;
case GIMP_TEXT_BOX_FIXED:
layout->extents.width =
PANGO_PIXELS (gimp_text_layout_pixel_size (text->box_width,
text->box_unit,
xres));
layout->extents.height =
PANGO_PIXELS (gimp_text_layout_pixel_size (text->box_height,
text->box_unit,
yres));
break;
}
return layout;
}
gboolean
gimp_text_layout_get_size (GimpTextLayout *layout,
gint *width,
gint *height)
{
g_return_val_if_fail (GIMP_IS_TEXT_LAYOUT (layout), FALSE);
if (width)
*width = layout->extents.width;
if (height)
*height = layout->extents.height;
return (layout->extents.width > 0 && layout->extents.height > 0);
}
void
gimp_text_layout_get_offsets (GimpTextLayout *layout,
gint *x,
gint *y)
{
g_return_if_fail (GIMP_IS_TEXT_LAYOUT (layout));
if (x)
*x = layout->extents.x;
if (y)
*y = layout->extents.y;
}
void
gimp_text_layout_get_resolution (GimpTextLayout *layout,
gdouble *xres,
gdouble *yres)
{
g_return_if_fail (GIMP_IS_TEXT_LAYOUT (layout));
if (xres)
*xres = layout->xres;
if (yres)
*yres = layout->yres;
}
GimpText *
gimp_text_layout_get_text (GimpTextLayout *layout)
{
g_return_val_if_fail (GIMP_IS_TEXT_LAYOUT (layout), NULL);
return layout->text;
}
PangoLayout *
gimp_text_layout_get_pango_layout (GimpTextLayout *layout)
{
g_return_val_if_fail (GIMP_IS_TEXT_LAYOUT (layout), NULL);
return layout->layout;
}
void
gimp_text_layout_get_transform (GimpTextLayout *layout,
cairo_matrix_t *matrix)
{
GimpText *text;
gdouble xres;
gdouble yres;
gdouble norm;
g_return_if_fail (GIMP_IS_TEXT_LAYOUT (layout));
g_return_if_fail (matrix != NULL);
text = gimp_text_layout_get_text (layout);
gimp_text_layout_get_resolution (layout, &xres, &yres);
norm = 1.0 / yres * xres;
matrix->xx = text->transformation.coeff[0][0] * norm;
matrix->xy = text->transformation.coeff[0][1] * 1.0;
matrix->yx = text->transformation.coeff[1][0] * norm;
matrix->yy = text->transformation.coeff[1][1] * 1.0;
matrix->x0 = 0;
matrix->y0 = 0;
}
void
gimp_text_layout_transform_rect (GimpTextLayout *layout,
PangoRectangle *rect)
{
cairo_matrix_t matrix;
gdouble x, y;
gdouble width, height;
g_return_if_fail (GIMP_IS_TEXT_LAYOUT (layout));
g_return_if_fail (rect != NULL);
x = rect->x;
y = rect->y;
width = rect->width;
height = rect->height;
gimp_text_layout_get_transform (layout, &matrix);
cairo_matrix_transform_point (&matrix, &x, &y);
cairo_matrix_transform_distance (&matrix, &width, &height);
rect->x = ROUND (x);
rect->y = ROUND (y);
rect->width = ROUND (width);
rect->height = ROUND (height);
}
void
gimp_text_layout_transform_point (GimpTextLayout *layout,
gdouble *x,
gdouble *y)
{
cairo_matrix_t matrix;
gdouble _x = 0.0;
gdouble _y = 0.0;
g_return_if_fail (GIMP_IS_TEXT_LAYOUT (layout));
if (x) _x = *x;
if (y) _y = *y;
gimp_text_layout_get_transform (layout, &matrix);
cairo_matrix_transform_point (&matrix, &_x, &_y);
if (x) *x = _x;
if (y) *y = _y;
}
void
gimp_text_layout_transform_distance (GimpTextLayout *layout,
gdouble *x,
gdouble *y)
{
cairo_matrix_t matrix;
gdouble _x = 0.0;
gdouble _y = 0.0;
g_return_if_fail (GIMP_IS_TEXT_LAYOUT (layout));
if (x) _x = *x;
if (y) _y = *y;
gimp_text_layout_get_transform (layout, &matrix);
cairo_matrix_transform_distance (&matrix, &_x, &_y);
if (x) *x = _x;
if (y) *y = _y;
}
void
gimp_text_layout_untransform_rect (GimpTextLayout *layout,
PangoRectangle *rect)
{
cairo_matrix_t matrix;
gdouble x, y;
gdouble width, height;
g_return_if_fail (GIMP_IS_TEXT_LAYOUT (layout));
g_return_if_fail (rect != NULL);
x = rect->x;
y = rect->y;
width = rect->width;
height = rect->height;
gimp_text_layout_get_transform (layout, &matrix);
if (cairo_matrix_invert (&matrix) == CAIRO_STATUS_SUCCESS)
{
cairo_matrix_transform_point (&matrix, &x, &y);
cairo_matrix_transform_distance (&matrix, &width, &height);
rect->x = ROUND (x);
rect->y = ROUND (y);
rect->width = ROUND (width);
rect->height = ROUND (height);
}
}
void
gimp_text_layout_untransform_point (GimpTextLayout *layout,
gdouble *x,
gdouble *y)
{
cairo_matrix_t matrix;
gdouble _x = 0.0;
gdouble _y = 0.0;
g_return_if_fail (GIMP_IS_TEXT_LAYOUT (layout));
if (x) _x = *x;
if (y) _y = *y;
gimp_text_layout_get_transform (layout, &matrix);
if (cairo_matrix_invert (&matrix) == CAIRO_STATUS_SUCCESS)
{
cairo_matrix_transform_point (&matrix, &_x, &_y);
if (x) *x = _x;
if (y) *y = _y;
}
}
void
gimp_text_layout_untransform_distance (GimpTextLayout *layout,
gdouble *x,
gdouble *y)
{
cairo_matrix_t matrix;
gdouble _x = 0.0;
gdouble _y = 0.0;
g_return_if_fail (GIMP_IS_TEXT_LAYOUT (layout));
if (x) _x = *x;
if (y) _y = *y;
gimp_text_layout_get_transform (layout, &matrix);
if (cairo_matrix_invert (&matrix) == CAIRO_STATUS_SUCCESS)
{
cairo_matrix_transform_distance (&matrix, &_x, &_y);
if (x) *x = _x;
if (y) *y = _y;
}
}
static void
gimp_text_layout_position (GimpTextLayout *layout)
{
PangoRectangle ink;
PangoRectangle logical;
gint x1, y1;
gint x2, y2;
layout->extents.x = 0;
layout->extents.x = 0;
layout->extents.width = 0;
layout->extents.height = 0;
pango_layout_get_pixel_extents (layout->layout, &ink, &logical);
ink.width = ceil ((gdouble) ink.width * layout->xres / layout->yres);
logical.width = ceil ((gdouble) logical.width * layout->xres / layout->yres);
#ifdef VERBOSE
g_print ("ink rect: %d x %d @ %d, %d\n",
ink.width, ink.height, ink.x, ink.y);
g_print ("logical rect: %d x %d @ %d, %d\n",
logical.width, logical.height, logical.x, logical.y);
#endif
if (ink.width < 1 || ink.height < 1)
return;
x1 = MIN (ink.x, logical.x);
y1 = MIN (ink.y, logical.y);
x2 = MAX (ink.x + ink.width, logical.x + logical.width);
y2 = MAX (ink.y + ink.height, logical.y + logical.height);
layout->extents.x = - x1;
layout->extents.y = - y1;
layout->extents.width = x2 - x1;
layout->extents.height = y2 - y1;
if (layout->text->border > 0)
{
gint border = layout->text->border;
layout->extents.x += border;
layout->extents.y += border;
layout->extents.width += 2 * border;
layout->extents.height += 2 * border;
}
#ifdef VERBOSE
g_print ("layout extents: %d x %d @ %d, %d\n",
layout->extents.width, layout->extents.height,
layout->extents.x, layout->extents.y);
#endif
}
static cairo_font_options_t *
gimp_text_get_font_options (GimpText *text)
{
cairo_font_options_t *options = cairo_font_options_create ();
cairo_font_options_set_antialias (options, (text->antialias ?
CAIRO_ANTIALIAS_DEFAULT :
CAIRO_ANTIALIAS_NONE));
switch (text->hint_style)
{
case GIMP_TEXT_HINT_STYLE_NONE:
cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_NONE);
break;
case GIMP_TEXT_HINT_STYLE_SLIGHT:
cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_SLIGHT);
break;
case GIMP_TEXT_HINT_STYLE_MEDIUM:
cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_MEDIUM);
break;
case GIMP_TEXT_HINT_STYLE_FULL:
cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_FULL);
break;
}
return options;
}
static PangoContext *
gimp_text_get_pango_context (GimpText *text,
gdouble xres,
gdouble yres)
{
PangoContext *context;
PangoCairoFontMap *fontmap;
cairo_font_options_t *options;
fontmap = PANGO_CAIRO_FONT_MAP (pango_cairo_font_map_new_for_font_type (CAIRO_FONT_TYPE_FT));
if (fontmap == NULL)
g_error ("You are using a Pango that has been built against a cairo that lacks the Freetype font backend");
pango_cairo_font_map_set_resolution (fontmap, yres);
context = pango_cairo_font_map_create_context (fontmap);
g_object_unref (fontmap);
options = gimp_text_get_font_options (text);
pango_cairo_context_set_font_options (context, options);
cairo_font_options_destroy (options);
if (text->language)
pango_context_set_language (context,
pango_language_from_string (text->language));
switch (text->base_dir)
{
case GIMP_TEXT_DIRECTION_LTR:
pango_context_set_base_dir (context, PANGO_DIRECTION_LTR);
break;
case GIMP_TEXT_DIRECTION_RTL:
pango_context_set_base_dir (context, PANGO_DIRECTION_RTL);
break;
}
return context;
}
static gint
gimp_text_layout_pixel_size (gdouble value,
GimpUnit unit,
gdouble res)
{
gdouble factor;
switch (unit)
{
case GIMP_UNIT_PIXEL:
return PANGO_SCALE * value;
default:
factor = gimp_unit_get_factor (unit);
g_return_val_if_fail (factor > 0.0, 0);
return PANGO_SCALE * value * res / factor;
}
}
static gint
gimp_text_layout_point_size (gdouble value,
GimpUnit unit,
gdouble res)
{
gdouble factor;
switch (unit)
{
case GIMP_UNIT_POINT:
return PANGO_SCALE * value;
case GIMP_UNIT_PIXEL:
g_return_val_if_fail (res > 0.0, 0);
return (PANGO_SCALE * value *
gimp_unit_get_factor (GIMP_UNIT_POINT) / res);
default:
factor = gimp_unit_get_factor (unit);
g_return_val_if_fail (factor > 0.0, 0);
return (PANGO_SCALE * value *
gimp_unit_get_factor (GIMP_UNIT_POINT) / factor);
}
}