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.
634 lines
17 KiB
C
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);
|
|
}
|
|
}
|