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

Fix 1351 wrong nsstringdrawer truncating text #28

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

optimisme
Copy link

This fix solves two problems:

  • String drawing performance is very slow when truncating large texts
  • String drawing does not properly truncate NSLineBreakByTruncatingHead and NSLineBreakByTruncatingMiddle

This pull request is related with reported bug:

darlinghq/darling#1351

That can be closed after the merging

Copy link
Member

@bugaevc bugaevc left a comment

Choose a reason for hiding this comment

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

Cool; some comments below. In general, please watch out for memory leaks and for spacing. alignment.

Yes, perhaps my preferred objc style is slightly unconventional; it's closer to GNUstep's than to Apple's. Where Apple would write:

-(void)forwardInvocation:(NSInvocation*)anInvocation;

GNUstep would write:

- (void) forwardInvocation: (NSInvocation*)anInvocation;

I would write:

- (void) forwardInvocation: (NSInvocation*) anInvocation;

Hope this makes sense!

Comment on lines 255 to 259
[self _clipAndDrawInRect: rect
lineBreakMode: NSLineBreakByTruncatingTail];
} else {
[self _clipAndDrawInRect: rect
lineBreakMode: NSLineBreakByClipping];
Copy link
Member

Choose a reason for hiding this comment

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

Sorry in advance for nitpicking, but: code style!

Here in particular, the colons have to be vertically aligned:

Suggested change
[self _clipAndDrawInRect: rect
lineBreakMode: NSLineBreakByTruncatingTail];
} else {
[self _clipAndDrawInRect: rect
lineBreakMode: NSLineBreakByClipping];
[self _clipAndDrawInRect: rect
lineBreakMode: NSLineBreakByTruncatingTail];
} else {
[self _clipAndDrawInRect: rect
lineBreakMode: NSLineBreakByClipping];

[self _clipAndDrawInRect: rect truncatingTail: YES];
}

- (void)_clipAndDrawInRect:(NSRect)rect lineBreakMode:(NSLineBreakMode)lineBreakMode {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
- (void)_clipAndDrawInRect:(NSRect)rect lineBreakMode:(NSLineBreakMode)lineBreakMode {
- (void) _clipAndDrawInRect: (NSRect) rect lineBreakMode: (NSLineBreakMode) lineBreakMode {

or probably even

Suggested change
- (void)_clipAndDrawInRect:(NSRect)rect lineBreakMode:(NSLineBreakMode)lineBreakMode {
- (void) _clipAndDrawInRect: (NSRect) rect
lineBreakMode: (NSLineBreakMode) lineBreakMode
{

[[self attributedStringValue]
_clipAndDrawInRect: [self titleRectForBounds: cellFrame]
truncatingTail: (_lineBreakMode > NSLineBreakByClipping)];
lineBreakMode: _lineBreakMode];
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
lineBreakMode: _lineBreakMode];
lineBreakMode: _lineBreakMode];

Comment on lines 277 to 278
NSDictionary *attributes = [string attributesAtIndex:[string length] - 1 effectiveRange:NULL];
NSAttributedString *ellipsis = [[NSAttributedString alloc] initWithString:@"..." attributes:attributes];
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
NSDictionary *attributes = [string attributesAtIndex:[string length] - 1 effectiveRange:NULL];
NSAttributedString *ellipsis = [[NSAttributedString alloc] initWithString:@"..." attributes:attributes];
NSDictionary *attributes = [string attributesAtIndex: [string length] - 1
effectiveRange: NULL];
NSAttributedString *ellipsis = [[NSAttributedString alloc] initWithString: @"..."
attributes: attributes];

Also, you're leaking the ellipsis now, aren't you? It was autoreleased previously.

NSAttributedString *ellipsis = [[NSAttributedString alloc] initWithString:@"..." attributes:attributes];

NSInteger left = 0;
NSInteger right = [string length];
Copy link
Member

Choose a reason for hiding this comment

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

When writing binary search-like algorithms, I find it helpful to explicitly spell out the invariant. For instance:

// INVARIANT: The string truncated to 'left' characters always fits,
// and truncated to 'right' characters always does not fit.

Note that your invariant seems to be different -- or is it? What's yours, exactly?

NSMutableAttributedString *tmpString;

while (left < right) {
mid = (left + right) / 2;
Copy link
Member

Choose a reason for hiding this comment

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

So I wonder if we could do better than the regular binary search here, by taking advantage of the fact that we're not only getting a boolean (fits / doesn't fit), but the actual width measurements. We could estimate mid based on the measurements. It'd still be confined between left and right, and we'd still be converging towards the answer, but hopefully we can do it in fewer steps.

For instance if we know that at left = 10 the string takes up 100 px, and at right = 20 it takes up 200px, and our room for the string is 110 px wide, mid = 11 would be a better estimate than 15. We only have to ensure that it's strictly greater than left and less than right, otherwise we'll loop endlessly.

Copy link
Author

Choose a reason for hiding this comment

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

I tried to do it this way but it didn't work and was very slow.

switch (lineBreakMode) {
case NSLineBreakByTruncatingHead:
clippedTitle = [string attributedSubstringFromRange:NSMakeRange(mid, [string length] - mid)];
tmpString = [[[NSAttributedString alloc] initWithAttributedString:ellipsis] mutableCopy];
Copy link
Member

Choose a reason for hiding this comment

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

You're leaking the pre-mutableCopy string too. Are you perhaps used to programming with ARC?


case NSLineBreakByTruncatingMiddle:
clippedTitle = [string attributedSubstringFromRange:NSMakeRange(0, mid)];
NSAttributedString *clippedTail = [string attributedSubstringFromRange:NSMakeRange([string length] - mid, mid)];
Copy link
Member

Choose a reason for hiding this comment

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

So here's an issue: if mid is high enough, you're going to start duplicating parts of the string, e.g. if string = @"Hello world" and mid is 7, you'd get clippedTitle = @"Hello w", clippedTail = @"o world", tmpString = @"Hello w...o world".

This new string mostly likely wouldn't fit (considering the original didn't, since we're inside this if), but better prevent this from happening in the first place, i.e. start from right = [string length] / 2 for the NSLineBreakByTruncatingMiddle mode.

@optimisme
Copy link
Author

I have submitted a new version with some of the proposed changes. However, others, more complex, are beyond my knowledge."

@optimisme
Copy link
Author

I merged the new AppKit code with this branch, is there a problem to merge now?

@CKegel
Copy link

CKegel commented May 1, 2023

I have submitted a new version with some of the proposed changes. However, others, more complex, are beyond my knowledge."

What changes do you think are beyond your knowledge? If you elaborate a bit I'm sure myself or someone else may be able to help! Don't forget about the discord server, as the learning channel is a great place to gain knowledge.

@luzpaz
Copy link

luzpaz commented Jun 23, 2023

Any progress on this ?

@CKegel
Copy link

CKegel commented Jun 23, 2023

Any progress on this ?

Not that I have seen, the remaining issues brought up by the reviewer still need addressed, and the commits squashed. I'm happy to provide help in any way I can

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants