Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vertical text alignment and positioning fix #670

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/graphics-path.c
Original file line number Diff line number Diff line change
Expand Up @@ -1296,10 +1296,24 @@ GdipAddPathString (GpPath *path, GDIPCONST WCHAR *string, int length,
return status;
}

cairo_save(cr); // gdip_pango_setup_layout can transform the Cairo context (vertical text), so save it and restore later.

layout = gdip_pango_setup_layout (cr, string, length, font, layoutRect, &box, &box_offset, string_format, NULL);
cairo_move_to (cr, layoutRect->X + box_offset.X, layoutRect->Y + box_offset.Y);
if (string_format->formatFlags & StringFormatFlagsDirectionVertical) {
// The Cairo context is rotated 90 degrees, direction dependent on text direction, so the point to draw at must be rotated to match.
// To rotate 90 degrees, we swap X and Y, and invert one of them depending on which direction we're rotating.
if (string_format->formatFlags & StringFormatFlagsDirectionRightToLeft) {
cairo_move_to (cr, layoutRect->Y + box_offset.Y, -(layoutRect->X + box_offset.X));
} else {
cairo_move_to (cr, -(layoutRect->Y + box_offset.Y), layoutRect->X + box_offset.X);
}
} else {
cairo_move_to (cr, layoutRect->X + box_offset.X, layoutRect->Y + box_offset.Y);
}
pango_cairo_layout_path (cr, layout);
g_object_unref (layout);

cairo_restore(cr);

if (string_format != format)
GdipDeleteStringFormat (string_format);
Expand Down
51 changes: 44 additions & 7 deletions src/text-pango.c
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,22 @@ gdip_process_string (gchar *text, int length, int removeAccelerators, int trimSp
return res;
}

void
gdip_process_vertical_text_box_position(GDIPCONST RectF *rc, GDIPCONST PointF *box_offset, GDIPCONST GpStringFormat *format, RectF *box)
{
if (format->formatFlags & StringFormatFlagsDirectionVertical) {
if (format->formatFlags & StringFormatFlagsDirectionRightToLeft) {
box->X = -box->X - box->Width;
if (rc->Height > 0)
box->X += rc->Width;
} else {
box->Y = -box->Y - box->Height;
if (rc->Height > 0)
box->Y += rc->Height;
}
}
}

PangoLayout*
gdip_pango_setup_layout (cairo_t *cr, GDIPCONST WCHAR *stringUnicode, int length, GDIPCONST GpFont *font,
GDIPCONST RectF *rc, RectF *box, PointF *box_offset, GDIPCONST GpStringFormat *format, int **charsRemoved)
Expand Down Expand Up @@ -263,8 +279,12 @@ gdip_pango_setup_layout (cairo_t *cr, GDIPCONST WCHAR *stringUnicode, int length
if (fmt->formatFlags & StringFormatFlagsDirectionRightToLeft) {
pango_context_set_base_dir (context, PANGO_DIRECTION_WEAK_RTL);
pango_layout_context_changed (layout);
} /* else: pango default base dir is WEAK_LTR, which is what we want */

/* horizontal alignment */
/* horizontal alignment */
// When we have either (but not both) of RightToLeft and Vertical, Near == Right.
// With neither or both, Near == Left.
if (((fmt->formatFlags & StringFormatFlagsDirectionRightToLeft) != 0) != ((fmt->formatFlags & StringFormatFlagsDirectionVertical) != 0)) {
if (use_horizontal_layout) {
switch (fmt->alignment) {
case StringAlignmentNear:
Expand All @@ -279,9 +299,6 @@ gdip_pango_setup_layout (cairo_t *cr, GDIPCONST WCHAR *stringUnicode, int length
}
}
} else {
/* pango default base dir is WEAK_LTR, which is what we want */

/* horizontal alignment */
if (use_horizontal_layout) {
switch (fmt->alignment) {
case StringAlignmentNear:
Expand Down Expand Up @@ -481,16 +498,18 @@ gdip_pango_setup_layout (cairo_t *cr, GDIPCONST WCHAR *stringUnicode, int length
}

if (!use_horizontal_layout) {
// When we have either (but not both) of RightToLeft and Vertical, Near == Right.
// With neither or both, Near == Left.
switch (fmt->alignment) {
case StringAlignmentNear:
if (fmt->formatFlags & StringFormatFlagsDirectionRightToLeft)
if (((fmt->formatFlags & StringFormatFlagsDirectionRightToLeft) != 0) != ((fmt->formatFlags & StringFormatFlagsDirectionVertical) != 0))
box_offset->X += (FrameWidth - box->Width);
break;
case StringAlignmentCenter:
box_offset->X += (FrameWidth - box->Width) * 0.5;
break;
case StringAlignmentFar:
if (!(fmt->formatFlags & StringFormatFlagsDirectionRightToLeft))
if (!(((fmt->formatFlags & StringFormatFlagsDirectionRightToLeft) != 0) != ((fmt->formatFlags & StringFormatFlagsDirectionVertical) != 0)))
box_offset->X += (FrameWidth - box->Width);
break;
}
Expand All @@ -509,6 +528,13 @@ gdip_pango_setup_layout (cairo_t *cr, GDIPCONST WCHAR *stringUnicode, int length
tmp = box_offset->X;
box_offset->X = box_offset->Y;
box_offset->Y = tmp;

if (fmt->formatFlags & StringFormatFlagsDirectionRightToLeft) {
box_offset->X = -box_offset->X;
} else {
box_offset->Y = -box_offset->Y;
}
gdip_process_vertical_text_box_position (rc, box_offset, fmt, box);
}

box->X += rc->X;
Expand Down Expand Up @@ -552,7 +578,17 @@ pango_DrawString (GpGraphics *graphics, GDIPCONST WCHAR *stringUnicode, INT leng
cairo_clip (graphics->ct);
}

gdip_cairo_move_to (graphics, rc->X + box_offset.X, rc->Y + box_offset.Y, FALSE, TRUE);
if (format != NULL && format->formatFlags & StringFormatFlagsDirectionVertical) {
// The Cairo context is rotated, direction dependent on text direction, so the point to draw at must be rotated to match.
// To rotate 90 degrees, we swap X and Y, and invert one of them depending on which direction we're rotating.
if (format->formatFlags & StringFormatFlagsDirectionRightToLeft) {
gdip_cairo_move_to (graphics, rc->Y + box_offset.Y, -(rc->X + box_offset.X), FALSE, TRUE);
} else {
gdip_cairo_move_to (graphics, -(rc->Y + box_offset.Y), rc->X + box_offset.X, FALSE, TRUE);
}
} else {
gdip_cairo_move_to (graphics, rc->X + box_offset.X, rc->Y + box_offset.Y, FALSE, TRUE);
}
pango_cairo_show_layout (graphics->ct, layout);

g_object_unref (layout);
Expand Down Expand Up @@ -748,6 +784,7 @@ pango_MeasureCharacterRanges (GpGraphics *graphics, GDIPCONST WCHAR *stringUnico
charRect.Height = -charRect.Height;
charRect.Y -= charRect.Height;
}
gdip_process_vertical_text_box_position (layoutRect, &box_offset, format, &charRect);
charRect.X += box_offset.X + layoutRect->X;
charRect.Y += box_offset.Y + layoutRect->Y;
// 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);
Expand Down
15 changes: 15 additions & 0 deletions tests/testgraphicspath.c
Original file line number Diff line number Diff line change
Expand Up @@ -4018,6 +4018,21 @@ static void test_addPathString ()
assertSimilarFloat (rect1.Y + rect1.Height, rect2.Y + rect2.Height, 5.0);
GdipDeletePath (path);

// Check with vertical text
GdipSetStringFormatFlags (format, StringFormatFlagsDirectionVertical);
GdipCreatePath (FillModeAlternate, &path);
status = GdipAddPathString (path, longString, -1, family, 0, fontSize, &longLayoutRect, format);
assertEqualInt (status, Ok);
status = GdipGetPathWorldBounds (path, &rect1, NULL, NULL);
assertEqualInt (status, Ok);
status = GdipMeasureString (graphics, longString, -1, font, &longLayoutRect, format, &rect2, NULL, NULL);
assertEqualInt (status, Ok);
assertSimilarFloat (rect1.X, rect2.X, 10.0);
assertSimilarFloat (rect1.Y, rect2.Y, 10.0);
assertSimilarFloat (rect1.X + rect1.Width, rect2.X + rect2.Width, 5.0);
assertSimilarFloat (rect1.Y + rect1.Height, rect2.Y + rect2.Height, 5.0);
GdipDeletePath (path);

// Dispose the Graphics stuff
GdipDeleteGraphics (graphics);
GdipDeleteFont (font);
Expand Down
72 changes: 45 additions & 27 deletions tests/testtext.c
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ static void test_measure_string_alignment(void)
GpRectF rect, bounds;
GpRegion *region;
const WCHAR teststring1[] = { 'M', 0 };
INT i;
INT i, in_rect;
static const CharacterRange character_range = { 0, 1 };
static const struct test_data
{
Expand Down Expand Up @@ -251,7 +251,18 @@ static void test_measure_string_alignment(void)
{ StringFormatFlagsDirectionRightToLeft, StringAlignmentNear, StringAlignmentCenter, -1.0, 200, -0.5, 50, 0, 200, 0.5, 50 },
{ StringFormatFlagsDirectionRightToLeft, StringAlignmentFar, StringAlignmentFar, 0, 0, -1.0, 100, 1.0, 0, 0, 100 },
{ StringFormatFlagsDirectionRightToLeft, StringAlignmentCenter, StringAlignmentFar, -0.5, 100, -1.0, 100, 0.5, 100, 0, 100 },
{ StringFormatFlagsDirectionRightToLeft, StringAlignmentNear, StringAlignmentFar, -1.0, 200, -1.0, 100, 0, 200, 0, 100 }
{ StringFormatFlagsDirectionRightToLeft, StringAlignmentNear, StringAlignmentFar, -1.0, 200, -1.0, 100, 0, 200, 0, 100 },
#if defined USE_PANGO_RENDERING || defined(USE_WINDOWS_GDIPLUS)
{ StringFormatFlagsDirectionVertical | StringFormatFlagsDirectionRightToLeft, StringAlignmentNear, StringAlignmentFar, 0, 0, 0, 0, 1.0, 0, 1.0, 0 },
{ StringFormatFlagsDirectionVertical | StringFormatFlagsDirectionRightToLeft, StringAlignmentNear, StringAlignmentCenter, -0.5, 100, 0, 0, 0.5, 100, 1.0, 0 },
{ StringFormatFlagsDirectionVertical | StringFormatFlagsDirectionRightToLeft, StringAlignmentNear, StringAlignmentNear, -1.0, 200, 0, 0, 0, 200, 1.0, 0 },
{ StringFormatFlagsDirectionVertical | StringFormatFlagsDirectionRightToLeft, StringAlignmentCenter, StringAlignmentFar, 0, 0, -0.5, 50, 1.0, 0, 0.5, 50 },
{ StringFormatFlagsDirectionVertical | StringFormatFlagsDirectionRightToLeft, StringAlignmentCenter, StringAlignmentCenter, -0.5, 100, -0.5, 50, 0.5, 100, 0.5, 50 },
{ StringFormatFlagsDirectionVertical | StringFormatFlagsDirectionRightToLeft, StringAlignmentCenter, StringAlignmentNear, -1.0, 200, -0.5, 50, 0, 200, 0.5, 50 },
{ StringFormatFlagsDirectionVertical | StringFormatFlagsDirectionRightToLeft, StringAlignmentFar, StringAlignmentFar, 0, 0, -1.0, 100, 1.0, 0, 0, 100 },
{ StringFormatFlagsDirectionVertical | StringFormatFlagsDirectionRightToLeft, StringAlignmentFar, StringAlignmentCenter, -0.5, 100, -1.0, 100, 0.5, 100, 0, 100 },
{ StringFormatFlagsDirectionVertical | StringFormatFlagsDirectionRightToLeft, StringAlignmentFar, StringAlignmentNear, -1.0, 200, -1.0, 100, 0, 200, 0, 100 },
#endif
};


Expand All @@ -271,31 +282,38 @@ static void test_measure_string_alignment(void)

GdipSetStringFormatMeasurableCharacterRanges (format, 1, &character_range);

for (i = 0; i < sizeof(td) / sizeof(td[0]); i++) {
GdipSetStringFormatFlags (format, td[i].flags);
GdipSetStringFormatAlign (format, td[i].alignment);
GdipSetStringFormatLineAlign (format, td[i].line_alignment);

rect.X = 5.0;
rect.Y = 10.0;
rect.Width = 200.0;
rect.Height = 100.0;
set_rect_empty (&bounds);
status = GdipMeasureString (graphics, teststring1, 1, font, &rect, format, &bounds, NULL, NULL);
expect (Ok, status);
expectf_ (td[i].x_x0 + td[i].x_xx * bounds.Width + 5.0, bounds.X, 0.6);
expectf_ (td[i].y_y0 + td[i].y_yy * bounds.Height + 10.0, bounds.Y, 0.6);
expectf_ (td[i].right_x0 + td[i].right_xx * bounds.Width + 5.0, bounds.X + bounds.Width, 0.6);
expectf_ (td[i].bottom_y0 + td[i].bottom_yy * bounds.Height + 10.0, bounds.Y + bounds.Height, 0.6);

status = GdipMeasureCharacterRanges (graphics, teststring1, 1, font, &rect, format, 1, &region);
expect (Ok, status);
status = GdipGetRegionBounds (region, graphics, &bounds);
expect (Ok, status);
expectf_ (td[i].x_x0 + td[i].x_xx * bounds.Width + 5.0, bounds.X, 3.0);
expectf_ (td[i].y_y0 + td[i].y_yy * bounds.Height + 10.0, bounds.Y, 3.0);
expectf_ (td[i].right_x0 + td[i].right_xx * bounds.Width + 5.0, bounds.X + bounds.Width, 3.0);
expectf_ (td[i].bottom_y0 + td[i].bottom_yy * bounds.Height + 10.0, bounds.Y + bounds.Height, 3.0);
#if defined USE_PANGO_RENDERING || defined(USE_WINDOWS_GDIPLUS)
for (in_rect = 1; in_rect >= 0; in_rect--) {
#else
{
in_rect = 1;
#endif
for (i = 0; i < sizeof(td) / sizeof(td[0]); i++) {
GdipSetStringFormatFlags (format, td[i].flags);
GdipSetStringFormatAlign (format, td[i].alignment);
GdipSetStringFormatLineAlign (format, td[i].line_alignment);

rect.X = 5.0;
rect.Y = 10.0;
rect.Width = 200.0 * in_rect;
rect.Height = 100.0 * in_rect;
set_rect_empty (&bounds);
status = GdipMeasureString (graphics, teststring1, 1, font, &rect, format, &bounds, NULL, NULL);
expect (Ok, status);
expectf_ (td[i].x_x0 * in_rect + td[i].x_xx * bounds.Width + 5.0, bounds.X, 0.6);
expectf_ (td[i].y_y0 * in_rect + td[i].y_yy * bounds.Height + 10.0, bounds.Y, 0.6);
expectf_ (td[i].right_x0 * in_rect + td[i].right_xx * bounds.Width + 5.0, bounds.X + bounds.Width, 0.6);
expectf_ (td[i].bottom_y0 * in_rect + td[i].bottom_yy * bounds.Height + 10.0, bounds.Y + bounds.Height, 0.6);

status = GdipMeasureCharacterRanges (graphics, teststring1, 1, font, &rect, format, 1, &region);
expect (Ok, status);
status = GdipGetRegionBounds (region, graphics, &bounds);
expect (Ok, status);
expectf_ (td[i].x_x0 * in_rect + td[i].x_xx * bounds.Width + 5.0, bounds.X, 3.0);
expectf_ (td[i].y_y0 * in_rect + td[i].y_yy * bounds.Height + 10.0, bounds.Y, 3.0);
expectf_ (td[i].right_x0 * in_rect + td[i].right_xx * bounds.Width + 5.0, bounds.X + bounds.Width, 3.0);
expectf_ (td[i].bottom_y0 * in_rect + td[i].bottom_yy * bounds.Height + 10.0, bounds.Y + bounds.Height, 3.0);
}
}

GdipDeleteGraphics (graphics);
Expand Down