mirror of https://github.com/GNOME/gimp.git
645 lines
17 KiB
C
645 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 "libgimpmath/gimpmath.h"
|
|
|
|
#include "text-types.h"
|
|
|
|
#include "core/gimpimage.h"
|
|
#include "core/gimpunit.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 (Gimp *gimp,
|
|
gdouble value,
|
|
GimpUnit unit,
|
|
gdouble res);
|
|
static gint gimp_text_layout_point_size (Gimp *gimp,
|
|
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 (image->gimp,
|
|
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 (image->gimp,
|
|
text->box_width,
|
|
text->box_unit,
|
|
xres));
|
|
break;
|
|
}
|
|
|
|
pango_layout_set_indent (layout->layout,
|
|
gimp_text_layout_pixel_size (image->gimp,
|
|
text->indent,
|
|
text->unit,
|
|
xres));
|
|
pango_layout_set_spacing (layout->layout,
|
|
gimp_text_layout_pixel_size (image->gimp,
|
|
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 (image->gimp,
|
|
text->box_width,
|
|
text->box_unit,
|
|
xres));
|
|
layout->extents.height =
|
|
PANGO_PIXELS (gimp_text_layout_pixel_size (image->gimp,
|
|
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 (Gimp *gimp,
|
|
gdouble value,
|
|
GimpUnit unit,
|
|
gdouble res)
|
|
{
|
|
gdouble factor;
|
|
|
|
switch (unit)
|
|
{
|
|
case GIMP_UNIT_PIXEL:
|
|
return PANGO_SCALE * value;
|
|
|
|
default:
|
|
factor = _gimp_unit_get_factor (gimp, unit);
|
|
g_return_val_if_fail (factor > 0.0, 0);
|
|
|
|
return PANGO_SCALE * value * res / factor;
|
|
}
|
|
}
|
|
|
|
static gint
|
|
gimp_text_layout_point_size (Gimp *gimp,
|
|
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, GIMP_UNIT_POINT) / res);
|
|
|
|
default:
|
|
factor = _gimp_unit_get_factor (gimp, unit);
|
|
g_return_val_if_fail (factor > 0.0, 0);
|
|
|
|
return (PANGO_SCALE * value *
|
|
_gimp_unit_get_factor (gimp, GIMP_UNIT_POINT) / factor);
|
|
}
|
|
}
|