-
Notifications
You must be signed in to change notification settings - Fork 24.5k
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
Use CALayers to draw text #24387
Use CALayers to draw text #24387
Conversation
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:_contentFrame.origin]; | ||
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:_contentFrame.origin]; | ||
|
||
__block UIBezierPath *highlightPath = nil; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part I'm not 100% sure about, what is this _highlightLayer
for and how can I test it still renders properly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: Update RNTester screenshot tests |
OMG, that's so awesome and promising. Thank you! cc @rigdern |
@janicduplessis Do you know if this will have any effect on text accessibility for screen readers? |
@RubenSandwich I don't think so, it really only affects the drawing of the text. The accessibility part is handled by the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love it.
(See the comment in the code.)
_contentFrame = contentFrame; | ||
} | ||
|
||
- (void)drawLayer:(CALayer *)layer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am worried about two things:
- How can we deduplicate the code in this method and the similar method in the another file.
- Why we should manage another
_highlightLayer
inside this class? Why we cannot use the original class for that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Point 1: Not sure about this one, both methods are actually different. Here's how it works basically:
RCTTextView
has 2 different layer ivars, one is a regularCALayer
and the other one is aCATiledLayer
subclass. TheconfigureLayer
method checks the current frame and creates an instance of the right type of layer based on it. It also removes the other layer type if an instance already exists (could happen when updating text). This means we always have one layer instanciated and the other one nil.RCTTextRenderer
is aCALayerDelegate
which handles all the text drawing, this delegate is used by both theCALayer
andCATiledLayer
this way there is no duplication of the text drawing code.
Point 2: Yea I didn't really know what it was initially and just ported all the code inside the RCTTextRenderer
class but I agree it should still be managed by the RCTTextView
class. I can probably make it a CALayer
subclass RCTHighlightLayer
to handle the highlight drawing logic separately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Point 1. This code draws textStorage
on context
, right? If so, why can't we decouple that into a static function?
Point 2. Not sure I understand why we need a subclass for that. Why can't we keep the code which renders highlight boxes where it is now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shergin Oups I thought I replied to this -_-
- Right now text drawing is abstracted in the
RCTTextRenderer
class. It implementsCALayerDelegate
so it can be set on anyCALayer
. The goal of this abstraction is to avoid duplicating the text drawing code since it is used by the 2 differentCALayer
implementations
In this case I don't see the need to abstract text drawing further in a static function since the drawing code is already implemented only once. An alternative would be to create 2 different CALayer
subclasses and extract the drawing code in a static function like you mentioned instead of using the delegate.
The core of the code is the dance between the layer types in configureLayers
, not sure if we want to come up with a cleaner abstraction but since I doubt there will be more than 2 layer types it should get much more complex.
- Yea I didn't end up doing this, I moved it back to
RCTTextView
Nice work! This PR definitely fixes the missing text problems we're seeing when people (who are vision-impaired) scale the system fonts up in the accessibility menu of their phone. @janicduplessis, you're a humanitarian. For comparison:
(Ignore the black bar, that's a video scrolled mostly off-screen) |
@shergin Fixed the highlight layer and tested it works properly, even when using large amounts of text. |
I also had to update the screenshot tests but I can't really see any difference. Update: Used an image diff tool to find the differences (note that the differences are very small, only detected when fuzzing is 0, even at fuzzing 1 the tool doesn't detect the changes.): Those are the only 2 examples with differences, looks like some minor changes related to antialiasing or something. The difference isn't visible so I think this is fine. |
This looks good to me, great job figuring this out. I also noticed that it was based around the height, but didn’t occur to me that it was due to memory problems! |
218ab11
to
ab84933
Compare
@janicduplessis This is an extremely valuable and important improvement. However it also has to be perfect before we can land it. I do love this but I am still concerning about this part: I am sorry, I am probably missing something, but I also have to be sure. Text/TextInput are the most complex and used components a there is a huge maintenance (and perf) costs associated with it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Back to Janic's queue. See my last comment. ❤️
@janicduplessis this looks amazing, I will take a look at this as well. |
Awesome work @janicduplessis 🎉 Just wondering, is the 1024 tile size also appropriate on iPad and Apple TV? Or should it be based on the screen size so it’s always 1.5 times the max dimension? |
@jeanregisser Good point, 1.5 times the max dimensions sounds like a good heuristic. |
ab84933
to
aa3aa0e
Compare
@shergin Any change you can have another look at this? |
b415ac6
to
09405c0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sammy-SC has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.
This pull request was successfully merged by @janicduplessis in 690e85d. When will my fix make it into a release? | Upcoming Releases |
Summary: The current technique we use to draw text uses linear memory, which means that when text is too long the UIView layer is unable to draw it. This causes the issue described [here](#19453). On an iOS simulator the bug happens at around 500 lines which is quite annoying. It can also happen on a real device but requires a lot more text. To be more specific the amount of text doesn't actually matter, it is the size of the UIView that we use to draw the text. When we use `[drawRect:]` the view creates a bitmap to send to the gpu to render, if that bitmap is too big it cannot render. To fix this we can use `CATiledLayer` which will split drawing into smaller parts, that gets executed when the content is about to be visible. This drawing is also async which means the text can seem to appear during scroll. See https://developer.apple.com/documentation/quartzcore/calayer?language=objc. `CATiledLayer` also adds some overhead that we don't want when rendering small amount of text. To fix this we can use either a regular `CALayer` or a `CATiledLayer` depending on the size of the view containing the text. I picked 1024 as the threshold which is about 1 screen and a half, and is still smaller than the height needed for the bug to occur when using a regular `CALayer` on a iOS simulator. Also found this which addresses the problem in a similar manner and took some inspiration from the code linked there GitHawkApp/StyledTextKit#14 (comment) Fixes #19453 ## Changelog [iOS] [Fixed] - Use CALayers to draw text, fixes rendering for long text Pull Request resolved: #24387 Test Plan: - Added the example I was using to verify the fix to RNTester. - Made sure all other examples are still rendering properly. - Tested text selection Reviewed By: shergin Differential Revision: D15918277 Pulled By: sammy-SC fbshipit-source-id: c45409a8413e6e3ad272be39ba527a4e8d349e28
Summary: The current technique we use to draw text uses linear memory, which means that when text is too long the UIView layer is unable to draw it. This causes the issue described [here](facebook#19453). On an iOS simulator the bug happens at around 500 lines which is quite annoying. It can also happen on a real device but requires a lot more text. To be more specific the amount of text doesn't actually matter, it is the size of the UIView that we use to draw the text. When we use `[drawRect:]` the view creates a bitmap to send to the gpu to render, if that bitmap is too big it cannot render. To fix this we can use `CATiledLayer` which will split drawing into smaller parts, that gets executed when the content is about to be visible. This drawing is also async which means the text can seem to appear during scroll. See https://developer.apple.com/documentation/quartzcore/calayer?language=objc. `CATiledLayer` also adds some overhead that we don't want when rendering small amount of text. To fix this we can use either a regular `CALayer` or a `CATiledLayer` depending on the size of the view containing the text. I picked 1024 as the threshold which is about 1 screen and a half, and is still smaller than the height needed for the bug to occur when using a regular `CALayer` on a iOS simulator. Also found this which addresses the problem in a similar manner and took some inspiration from the code linked there GitHawkApp/StyledTextKit#14 (comment) Fixes facebook#19453 ## Changelog [iOS] [Fixed] - Use CALayers to draw text, fixes rendering for long text Pull Request resolved: facebook#24387 Test Plan: - Added the example I was using to verify the fix to RNTester. - Made sure all other examples are still rendering properly. - Tested text selection Reviewed By: shergin Differential Revision: D15918277 Pulled By: sammy-SC fbshipit-source-id: c45409a8413e6e3ad272be39ba527a4e8d349e28
Summary
The current technique we use to draw text uses linear memory, which means that when text is too long the UIView layer is unable to draw it. This causes the issue described here. On an iOS simulator the bug happens at around 500 lines which is quite annoying. It can also happen on a real device but requires a lot more text.
To be more specific the amount of text doesn't actually matter, it is the size of the UIView that we use to draw the text. When we use
[drawRect:]
the view creates a bitmap to send to the gpu to render, if that bitmap is too big it cannot render.To fix this we can use
CATiledLayer
which will split drawing into smaller parts, that gets executed when the content is about to be visible. This drawing is also async which means the text can seem to appear during scroll. See https://developer.apple.com/documentation/quartzcore/calayer?language=objc.CATiledLayer
also adds some overhead that we don't want when rendering small amount of text. To fix this we can use either a regularCALayer
or aCATiledLayer
depending on the size of the view containing the text. I picked 1024 as the threshold which is about 1 screen and a half, and is still smaller than the height needed for the bug to occur when using a regularCALayer
on a iOS simulator.Also found this which addresses the problem in a similar manner and took some inspiration from the code linked there GitHawkApp/StyledTextKit#14 (comment)
Fixes #19453
Changelog
[iOS] [Fixed] - Use CALayers to draw text, fixes rendering for long text
Test Plan