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

Corrections to MeasureCharacterRanges, improvements to DrawString / MeasureString #137

Closed
wants to merge 4 commits into from

Conversation

akoeplinger
Copy link
Member

  • MeasureCharacterRanges now counts LF, CR, tab as characters for measuring a CharacterRanges. Also utilises layoutRect.
  • MeasureString now deals with all alignment, rather than DrawString. Doing so has meant that StringAlignment can now work correctly. Also handles boundingBox better.
  • DrawString has been changed to work with the changes to MeasureString.

Note: these are the extracted cairo changes from #28 + cleanup, let's see what CI says 😄

…easureString

MeasureCharacterRanges now counts LF, CR, tab as characters for measuring a CharacterRanges. Also utilises layoutRect.
MeasureString now deals with all alignment, rather than DrawString. Doing so has meant that StringAlignment can now work correctly. Also handles boundingBox better.
DrawString has been changed to work with the changes to MeasureString.
@akoeplinger
Copy link
Member Author

@hughbe extracted the left over pieces from #28, maybe it makes more sense in this cleaned up form :)

@akoeplinger akoeplinger mentioned this pull request Nov 24, 2017
@hughbe
Copy link
Contributor

hughbe commented Nov 24, 2017

Doesn't compile with MSVC:

13:54:07 d:\j\workspace\test-libgdiplus-pull-request-windows\src\text-cairo.c(1124): error C2057: expected constant expression [d:\j\workspace\test-libgdiplus-pull-request-windows\src\libgdiplus.vcxproj]
13:54:07 d:\j\workspace\test-libgdiplus-pull-request-windows\src\text-cairo.c(1124): error C2466: cannot allocate an array of constant size 0 [d:\j\workspace\test-libgdiplus-pull-request-windows\src\libgdiplus.vcxproj]
13:54:07 d:\j\workspace\test-libgdiplus-pull-request-windows\src\text-cairo.c(1124): error C2133: 'index_matching': unknown size [d:\j\workspace\test-libgdiplus-pull-request-windows\src\libgdiplus.vcxproj]

src/text-cairo.c Outdated
@@ -1099,6 +1121,7 @@ cairo_MeasureCharacterRanges (GpGraphics *graphics, GDIPCONST WCHAR *stringUnico
RectF charRect;
RectF rc_coords, *layoutRect = &rc_coords;
BOOL optimize_convert;
int index_matching[length];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a gnu extension - I think you need to change this to

int* index_matching = (int *) malloc (sizeof (int) * length));
if (!index_matching)
    return OutOfMemory;

Also should be freed in cleanup:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it's C99, but agreed with the allocation.

@hughbe
Copy link
Contributor

hughbe commented Nov 24, 2017

I don't really understand the code well enough. Also would be nice to get #5 merged or closed :D

Using array initializer with a variable is a gnu extensions and
doesn't work on MSVC.
@akoeplinger
Copy link
Member Author

Yeah merging #5 would be nice but we'd need to find out if it's correct first since I don't understand this code well enough either 😄

@hughbe
Copy link
Contributor

hughbe commented Dec 15, 2017

Is anything holding you back from merging this?

@akoeplinger
Copy link
Member Author

@hughbe I'd like to give it a test run with some WinForms apps to make sure it doesn't horribly break something but didn't get to it yet. Hopefully during Christmas break :)

@PreferLinux
Copy link
Contributor

I think I still understand most of it (the changes in particular), so if there's anything you'd like me to clarify I'll try to help.

boundingBox->Y += (rc->Width - boundingBox->Width) / 2;
} else {
boundingBox->X += (rc->Width - boundingBox->Width) / 2;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how this makes any sense. Why would you center the bounding box irrespective of the alignment?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't have noticed any impact on the bounding box directly, because only cairo_MeasureString (and therefore GdipMeasureString) even supply boundingBox to populate, and System.Drawing.Graphics.MeasureString() only uses the size component of that. I was probably trying to fix something but had the wrong place, and the code was never removed – what is here as the final result actually took several iterations to achieve.

The only other thing I can think of is something relating to counting how many characters were fitted. That doesn't make any sense though, because it'll instead mess that up rather badly due to what seems to me to be a bug in that code – it uses boundingBox / rc X + Width, not just Width. Before this change, that would have worked because the only System.Drawing.Graphics.MeasureString overload that returns that information provides a layoutRect (and therefore rc) with X and Y of 0.

So, I would suggest either changing it to take alignment into account and position the bounding box correctly and correcting the character-counting code (technically correct, although probably pointless), or reverting this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was solving similar issue in the Pango text rendering backend. Ideally we should write some tests for the correct behavior. I ended up returning the actual bounding box of the text with all the alignment taken into account.

The thing is that in the Pango case the line alignment (ie. vertical alignment for most cases) is calculated at the very end once the bounding box height is already known. Unless there is some major flaw in that approach I would prefer to structure the Cairo code in the same way since it makes the code simpler to reason about.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's probably the best way. MeasureString is complicated enough (and long enough) that anything that makes it easier to figure out is a good idea.

boundingBox->X += (rc->Height - boundingBox->Height) / 2;
} else {
boundingBox->Y += (rc->Height - boundingBox->Height) / 2;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

@filipnavara
Copy link
Contributor

filipnavara commented Mar 30, 2018

I went through the text-cairo code and it's almost universally broken whenever there are TABs, CRs, LFs in the string. It does some string preprocessing to strip these character and accounts for that in the layout, but it utterly fails to account for it when any measurements are reported back. That involves mainly the codepointsFitted metric and MeasureCharacterRanges. This patch tries hard to compensate for it in the context of MeasureCharacterRanges, but other places remain broken.

What would make much more sense though is NOT to strip the characters in the input and instead handle them in the layout pass. That way StringDetails indices will match the actual string that is passed in.

@PreferLinux
Copy link
Contributor

Yes, definitely. I was trying to make it work for me, which meant I went for the obvious route rather than changing a whole lot.

@filipnavara
Copy link
Contributor

filipnavara commented Mar 30, 2018

I'm not questioning your choices, just trying to find out how to proceed further. There's couple of separate changes in this patch:

  • Moving horizontal/vertical alignment adjustments from DrawString to MeasureString. This is reasonable change. The alignment affects the overall positioning of letters (as used by cairo_MeasureCharacterRanges and DrawString) and the final bounding rectangle (as used by cairo_MeasureString).
    A possible optimization is to move this into separate function that is used by cairo_DrawString and cairo_MeasureCharacterRanges since cairo_MeasureString doesn't need the recomputed coordinates.

  • The weird bounding box centring I commented on before. This intention is correct, but the implementation is not. The bounding box has to be re-positioned according to both horizontal and vertical alignment. This is currently not done and the X, Y coordinates are wrong. However, they are not used in System.Drawing, so the impact is limited.
    In a similar fashion to the above optimization we can apply the alignment to the bounding box only in cairo_MeasureString since cairo_DrawString and cairo_MeasureCharacterRanges don't use it.

  • Fixing of the indexes in cairo_MeasureCharacterRanges to account for the string clean up in MeasureString function.

The first two changes can be separated quite easily into individual patches. First one is trivial refactoring by moving code. Second one is merely copying code in PR #245 from Pango backend to the Cairo one. The last one I would propose to implement differently, but that will definitely require digging deeper into the code and refactoring it. A first step would be to have unit tests covering the problematic cases. I wanted to save some time on that by PR #262, but that will likely go nowhere. So in the interest of sharing my thoughts, here are the cases that need to be tested:

  • codepointsFitted should count LFs in cairo_MeasureString, most likely the same applies to CR and TAB
  • check how codepointsFitted accounts for '&' character in different modes of HotkeyPrefix.
  • cairo_MeasureString should set X, Y of bounding box accordingly to StringFormat->Alignment/LineAlignment and combinations of StringFormatFlagsDirectionVertical and StringFormatFlagsDirectionRightToLeft (not properly implement, btw)
  • cairo_MeasureCharacterRanges should correctly account for the CR, LF and TAB characters.
  • horizontal alignment in combination with StringFormatFlagsMeasureTrailingSpaces
  • how should invisible character be reported by cairo_MeasureCharacterRanges? (eg. LFs, trailing spaces)
  • should linesFilled count only full lines or also partial lines? does it depend on StringFormatFlagsLineLimit?

@akoeplinger
Copy link
Member Author

@filipnavara I'd definitely be in favor of splitting the no-brainer changes into separate PRs so we get some of it finally merged :)

@filipnavara
Copy link
Contributor

I will submit the PRs in next few days.

@filipnavara
Copy link
Contributor

I fixed up the coordinate part of this patch in PR #275. Tested with "notepad" in Mono repository and unit tests. That leaves the index_matching part.

@hughbe
Copy link
Contributor

hughbe commented Jul 26, 2018

Where are with here? Would be awesome to get this merged in :D

@akoeplinger
Copy link
Member Author

I won't have time to work on this so whoever wants to take over and drive this forward please do :)

@filipnavara
Copy link
Contributor

@hughbe Basically everything but the cairo_MeasureCharacterRanges function was already merged in one way or another. I would prefer if that part of patch was rewritten to use the charsRemoved approach from gdip_process_string in text-pango.c. Now it's doing twice the same processing, which is non-transparent and bound to break with any change. The maintenance cost of this code is already high, so anything that could converge towards the Pango code would be nice.

@migueldeicaza
Copy link
Contributor

This needs to be rebased.

@filipnavara
Copy link
Contributor

@migueldeicaza Thanks for taking care of the pull requests. This one needs more than rebasing, but currently no one is working on that. 2/3 of the patch are already rewritten and merged.

@akoeplinger
Copy link
Member Author

Talked with @filipnavara and at this point there's no value in the actual code change anymore. We'll open an issue instead.

@akoeplinger akoeplinger closed this Oct 3, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants