From b7e7138b0ee24e74309855a3412fbca3de94b804 Mon Sep 17 00:00:00 2001 From: Karl Date: Tue, 8 Sep 2020 15:12:51 +1200 Subject: [PATCH 1/4] Add more alignment tests Add basic MeasureString alignment tests for combining Vertical and RightToLeft. Also run the alignment tests with a zero-size rectangle to check correct alignment in that situation. Add GraphicsPath test for Vertical strings. --- tests/testgraphicspath.c | 15 +++++++++ tests/testtext.c | 67 ++++++++++++++++++++++++---------------- 2 files changed, 55 insertions(+), 27 deletions(-) diff --git a/tests/testgraphicspath.c b/tests/testgraphicspath.c index 1111f4650..a9aaaa0e8 100644 --- a/tests/testgraphicspath.c +++ b/tests/testgraphicspath.c @@ -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); diff --git a/tests/testtext.c b/tests/testtext.c index 32853d3de..40ff3ea75 100644 --- a/tests/testtext.c +++ b/tests/testtext.c @@ -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 { @@ -251,7 +251,17 @@ 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 }, + + { 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 }, }; @@ -271,31 +281,34 @@ 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, ®ion); - 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); + for (in_rect = 1; in_rect >= 0; in_rect--) { + 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); + g_warning("in_rect %d, i %d\n", in_rect, i); + 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, ®ion); + 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); From 08c7d5de73a1fe5b5341c6c3f0218365f86c3fca Mon Sep 17 00:00:00 2001 From: Karl Date: Tue, 8 Sep 2020 15:15:49 +1200 Subject: [PATCH 2/4] Fix vertical text alignment and position. --- src/graphics-path.c | 16 +++++++++++++- src/text-pango.c | 51 ++++++++++++++++++++++++++++++++++++++------- tests/testtext.c | 1 - 3 files changed, 59 insertions(+), 9 deletions(-) diff --git a/src/graphics-path.c b/src/graphics-path.c index b9b7b12e7..c5b26e641 100644 --- a/src/graphics-path.c +++ b/src/graphics-path.c @@ -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); diff --git a/src/text-pango.c b/src/text-pango.c index 938a0d238..59284e14f 100644 --- a/src/text-pango.c +++ b/src/text-pango.c @@ -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) @@ -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: @@ -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: @@ -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; } @@ -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; @@ -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->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); @@ -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); diff --git a/tests/testtext.c b/tests/testtext.c index 40ff3ea75..497fafbaf 100644 --- a/tests/testtext.c +++ b/tests/testtext.c @@ -292,7 +292,6 @@ static void test_measure_string_alignment(void) rect.Width = 200.0 * in_rect; rect.Height = 100.0 * in_rect; set_rect_empty (&bounds); - g_warning("in_rect %d, i %d\n", in_rect, i); 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); From 5bc30af10d0d06a43039ccf2c6add34ed15efbde Mon Sep 17 00:00:00 2001 From: Karl <5079870+PreferLinux@users.noreply.github.com> Date: Tue, 8 Sep 2020 17:42:42 +1200 Subject: [PATCH 3/4] Fix segfault In pango_DrawString, the StringFormat can be null. I didn't handle that correctly. --- src/text-pango.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text-pango.c b/src/text-pango.c index 59284e14f..d6ccb3a33 100644 --- a/src/text-pango.c +++ b/src/text-pango.c @@ -578,7 +578,7 @@ pango_DrawString (GpGraphics *graphics, GDIPCONST WCHAR *stringUnicode, INT leng cairo_clip (graphics->ct); } - if (format->formatFlags & StringFormatFlagsDirectionVertical) { + 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) { From e8702d0049d8b88041bed95184bcfed77f9c6e8a Mon Sep 17 00:00:00 2001 From: Karl Date: Wed, 9 Sep 2020 11:45:15 +1200 Subject: [PATCH 4/4] Modify testtext to pass with Cairo text rendering --- tests/testtext.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/testtext.c b/tests/testtext.c index 497fafbaf..72865d7a9 100644 --- a/tests/testtext.c +++ b/tests/testtext.c @@ -252,7 +252,7 @@ static void test_measure_string_alignment(void) { 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 }, - +#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 }, @@ -262,6 +262,7 @@ static void test_measure_string_alignment(void) { 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 }; @@ -281,7 +282,12 @@ static void test_measure_string_alignment(void) GdipSetStringFormatMeasurableCharacterRanges (format, 1, &character_range); +#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);