2
* Copyright (C) 2007 Novell, Inc (http://www.novell.com)
4
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
5
* and associated documentation files (the "Software"), to deal in the Software without restriction,
6
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
7
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
8
* subject to the following conditions:
10
* The above copyright notice and this permission notice shall be included in all copies or substantial
11
* portions of the Software.
13
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
14
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
15
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
16
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
17
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
* Sebastien Pouliot <sebastien@ximian.com>
23
#include "gdiplus-private.h"
25
#ifdef USE_PANGO_RENDERING
27
#include "text-pango-private.h"
28
#include "graphics-private.h"
29
#include "graphics-cairo-private.h"
30
#include "brush-private.h"
31
#include "font-private.h"
35
* NOTE: all parameter's validations are done inside text.c
39
gdip_get_layout_attributes (PangoLayout *layout)
41
PangoAttrList *list = pango_layout_get_attributes (layout);
43
list = pango_attr_list_new ();
45
pango_attr_list_ref (list);
50
gdip_process_accelerators (gchar *text, int length, PangoAttrList *list)
53
for (i = 0; i < length; i++) {
54
if (*(text + i) == GDIP_WINDOWS_ACCELERATOR) {
55
/* don't show the prefix character */
56
*(text + i) = GDIP_PANGOHACK_ACCELERATOR;
57
/* if the next character is an accelerator then skip over it (&& == &) */
58
if ((i < length - 1) && (*(text + i + 1) == GDIP_WINDOWS_ACCELERATOR)) {
61
/* add an attribute on the next character */
62
PangoAttribute *attr = pango_attr_underline_new (PANGO_UNDERLINE_LOW);
63
attr->start_index = i + 1;
64
attr->end_index = i + 2;
65
pango_attr_list_insert (list, attr);
72
gdip_pango_setup_layout (cairo_t *ct, GDIPCONST WCHAR *stringUnicode, int length, GDIPCONST GpFont *font,
73
GDIPCONST RectF *rc, RectF *box, GDIPCONST GpStringFormat *format)
77
PangoContext *context;
78
PangoMatrix matrix = PANGO_MATRIX_INIT;
79
PangoRectangle logical;
80
PangoAttrList *list = NULL;
82
gchar *text = ucs2_to_utf8 (stringUnicode, length);
86
//g_warning ("layout >%s< (%d) [x %g, y %g, w %g, h %g] [font %s, %g points]", text, length, rc->X, rc->Y, rc->Width, rc->Height, font->face, font->emSize);
88
/* a NULL format is valid, it means get the generic default values (and free them later) */
90
GpStatus status = GdipStringFormatGetGenericDefault ((GpStringFormat **)&fmt);
96
fmt = (GpStringFormat *)format;
99
/* unless specified we don't consider the trailing spaces, unless there is just one space (#80680) */
100
if ((fmt->formatFlags & StringFormatFlagsMeasureTrailingSpaces) == 0) {
101
while ((length > 0) && (isspace (*(text + length - 1))))
107
layout = pango_cairo_create_layout (ct);
109
/* context is owned by Pango (i.e. not referenced counted) do not free */
110
context = pango_layout_get_context (layout);
112
pango_layout_set_font_description (layout, gdip_get_pango_font_description ((GpFont*) font));
114
if ((rc->Width <= 0.0) || (fmt->formatFlags & StringFormatFlagsNoWrap)) {
115
pango_layout_set_width (layout, -1);
117
/* minus one to deal with our AA offset */
118
int width = rc->Width - 1;
119
/* TODO incomplete (missing height adjustment) */
120
if ((fmt->formatFlags & StringFormatFlagsNoFitBlackBox) == 0)
122
pango_layout_set_width (layout, width * PANGO_SCALE);
125
if (fmt->formatFlags & StringFormatFlagsDirectionRightToLeft) {
126
/* with GDI+ the API not the renderer makes the direction decision */
127
pango_layout_set_auto_dir (layout, FALSE);
128
pango_context_set_base_dir (context, PANGO_DIRECTION_RTL);
129
pango_layout_context_changed (layout);
131
/* horizontal alignment */
132
switch (fmt->alignment) {
133
case StringAlignmentNear:
134
pango_layout_set_alignment (layout, PANGO_ALIGN_RIGHT);
136
case StringAlignmentCenter:
137
pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
139
case StringAlignmentFar:
140
pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT);
144
/* horizontal alignment */
145
switch (fmt->alignment) {
146
case StringAlignmentNear:
147
pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT);
149
case StringAlignmentCenter:
150
pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
152
case StringAlignmentFar:
153
pango_layout_set_alignment (layout, PANGO_ALIGN_RIGHT);
158
#ifdef PANGO_VERSION_CHECK
159
#if PANGO_VERSION_CHECK(1,16,0)
160
if (fmt->formatFlags & StringFormatFlagsDirectionVertical) {
161
/* only since Pango 1.16 */
162
pango_context_set_base_gravity (context, PANGO_GRAVITY_EAST);
163
pango_context_set_gravity_hint (context, PANGO_GRAVITY_HINT_STRONG);
164
pango_layout_context_changed (layout);
169
/* TODO - StringFormatFlagsDisplayFormatControl
170
scan and replace them ??? */
172
/* TODO - StringFormatFlagsLineLimit */
174
if ((rc->Width != 0) && (rc->Height != 0) && ((fmt->formatFlags & StringFormatFlagsNoClip) == 0)) {
175
//g_warning ("\tclip [%g %g %g %g]", rc->X, rc->Y, rc->Width, rc->Height);
176
/* We do not call cairo_reset_clip because we want to take previous clipping into account */
177
cairo_rectangle (ct, rc->X, rc->Y, rc->Width + 0.5, rc->Height + 0.5);
181
switch (fmt->trimming) {
182
case StringTrimmingNone:
183
pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
184
pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_NONE);
186
case StringTrimmingCharacter:
187
pango_layout_set_wrap (layout, PANGO_WRAP_CHAR);
188
pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_NONE);
190
case StringTrimmingWord:
191
pango_layout_set_wrap (layout, PANGO_WRAP_WORD);
192
pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_NONE);
194
case StringTrimmingEllipsisCharacter:
195
pango_layout_set_wrap (layout, PANGO_WRAP_CHAR);
196
pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END);
198
case StringTrimmingEllipsisWord:
199
pango_layout_set_wrap (layout, PANGO_WRAP_WORD);
200
pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END);
202
case StringTrimmingEllipsisPath:
203
pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
204
pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_MIDDLE);
208
/* some stuff can only be done by manipulating the attributes (but we can avoid this most of the time) */
209
if ((fmt->formatFlags & StringFormatFlagsNoFontFallback) || (font->style & (FontStyleUnderline | FontStyleStrikeout))) {
211
list = gdip_get_layout_attributes (layout);
213
/* StringFormatFlagsNoFontFallback */
214
if (fmt->formatFlags & StringFormatFlagsNoFontFallback) {
215
PangoAttribute *attr = pango_attr_fallback_new (FALSE);
216
attr->start_index = 0;
217
attr->end_index = length;
218
pango_attr_list_insert (list, attr);
221
if (font->style & FontStyleUnderline) {
222
PangoAttribute *attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
223
attr->start_index = 0;
224
attr->end_index = length;
225
pango_attr_list_insert (list, attr);
228
if (font->style & FontStyleStrikeout) {
229
PangoAttribute *attr = pango_attr_strikethrough_new (TRUE);
230
attr->start_index = 0;
231
attr->end_index = length;
232
pango_attr_list_insert (list, attr);
236
switch (fmt->hotkeyPrefix) {
237
case HotkeyPrefixHide:
238
/* we need to remove any accelerator from the string */
239
gdip_process_accelerators (text, length, NULL);
241
case HotkeyPrefixShow:
242
/* optimization: is seems that we never see the hotkey when using an underline font */
243
if (font->style & FontStyleUnderline) {
244
/* so don't bother drawing it (and simply add the '&' character) */
245
gdip_process_accelerators (text, length, NULL);
247
/* find accelerator and add attribute to the next character (unless it's the prefix too) */
249
list = gdip_get_layout_attributes (layout);
250
gdip_process_accelerators (text, length, list);
258
pango_layout_set_attributes (layout, list);
259
pango_attr_list_unref (list);
262
pango_layout_set_text (layout, text, length);
265
pango_layout_get_pixel_extents (layout, NULL, &logical);
266
//g_warning ("\tlogical\t[x %d, y %d, w %d, h %d]", logical.x, logical.y, logical.width, logical.height);
270
box->Height = logical.height;
271
/* add an extra pixel for our AA hack + 2 more if we don't draw on the box itself */
272
box->Width = logical.width + (fmt->formatFlags & StringFormatFlagsNoFitBlackBox) ? 1 : 3;
273
//g_warning ("\tbox\t[x %g, y %g, w %g, h %g]", box->X, box->Y, box->Width, box->Height);
275
/* vertical alignment*/
276
switch (fmt->lineAlignment) {
277
case StringAlignmentNear:
279
case StringAlignmentCenter:
280
box->Y += (rc->Height - logical.height) / 2;
282
case StringAlignmentFar:
283
box->Y += (rc->Height - logical.height);
286
//g_warning ("va-box\t[x %g, y %g, w %g, h %g]", box->X, box->Y, box->Width, box->Height);
288
pango_cairo_update_layout (ct, layout);
294
pango_DrawString (GpGraphics *graphics, GDIPCONST WCHAR *stringUnicode, int length, GDIPCONST GpFont *font, GDIPCONST RectF *rc,
295
GDIPCONST GpStringFormat *format, GpBrush *brush)
300
cairo_save (graphics->ct);
302
layout = gdip_pango_setup_layout (graphics->ct, stringUnicode, length, font, rc, &box, format);
304
cairo_restore (graphics->ct);
310
gdip_brush_setup (graphics, brush);
312
cairo_set_source_rgb (graphics->ct, 0., 0., 0.);
315
gdip_cairo_move_to (graphics, box.X, box.Y, FALSE, TRUE);
316
pango_cairo_show_layout (graphics->ct, layout);
318
g_object_unref (layout);
319
cairo_restore (graphics->ct);
324
pango_MeasureString (GpGraphics *graphics, GDIPCONST WCHAR *stringUnicode, int length, GDIPCONST GpFont *font, GDIPCONST RectF *rc,
325
GDIPCONST GpStringFormat *format, RectF *boundingBox, int *codepointsFitted, int *linesFilled)
329
cairo_save (graphics->ct);
331
layout = gdip_pango_setup_layout (graphics->ct, stringUnicode, length, font, rc, boundingBox, format);
333
cairo_restore (graphics->ct);
337
if (codepointsFitted) {
338
// TODO - dummy (total) value returned
339
*codepointsFitted = length;
343
*linesFilled = pango_layout_get_line_count (layout);
344
//g_warning ("linesFilled %d", *linesFilled);
346
//else g_warning ("linesFilled %d", pango_layout_get_line_count (layout));
348
g_object_unref (layout);
349
cairo_restore (graphics->ct);
354
pango_MeasureCharacterRanges (GpGraphics *graphics, GDIPCONST WCHAR *stringUnicode, int length, GDIPCONST GpFont *font,
355
GDIPCONST GpRectF *layoutRect, GDIPCONST GpStringFormat *format, int regionCount, GpRegion **regions)
358
GpStatus status = Ok;
362
cairo_save (graphics->ct);
364
layout = gdip_pango_setup_layout (graphics->ct, stringUnicode, length, font, layoutRect, &boundingBox, format);
366
cairo_restore (graphics->ct);
370
/* Create a region for every char range */
371
for (i = 0; i < format->charRangeCount; i++) {
373
CharacterRange range = format->charRanges [i];
375
GdipSetEmpty (regions [i]);
377
if (range.Length > 0)
380
start = range.First + range.Length;
382
end = start + range.Length;
384
/* sanity check on charRange. negative lengths are allowed */
385
if (range.First < 0) {
386
status = InvalidParameter;
390
if ((start < 0) || (end > length)) {
391
status = InvalidParameter;
395
/* calculate the regions */
396
for (j = start; j < end; j++) {
400
pango_layout_index_to_pos (layout, j, &box);
401
charRect.X = (float)box.x / PANGO_SCALE;
402
charRect.Y = (float)box.y / PANGO_SCALE;
403
charRect.Width = (float)box.width / PANGO_SCALE;
404
charRect.Height = (float)box.height / PANGO_SCALE;
405
//g_warning ("[%d] [%d : %d-%d] %c [x %g y %g w %g h %g]", i, j, start, end, (char)stringUnicode[j], charRect.X, charRect.Y, charRect.Width, charRect.Height);
406
status = GdipCombineRegionRect (regions [i], &charRect, CombineModeUnion);
415
cairo_restore (graphics->ct);