rajdeep / proton

Purely native and extensible rich text editor for iOS and macOS Catalyst apps
Other
1.25k stars 81 forks source link

EditorView.attributedText problem #286

Open easiwriter opened 5 months ago

easiwriter commented 5 months ago

EditorView.attributedText before assignment is:

A
{
    NSColor = "<UIDynamicCatalogSystemColor: 0x60000179cd80; name = labelColor>";
    NSFont = "<UICTFont: 0x10a173dc0> font-family: \"Helvetica Neue\"; font-weight: bold; font-style: normal; font-size: 34.00pt";
    NSParagraphStyle = "Alignment Center, LineSpacing 1, ParagraphSpacing 0, ParagraphSpacingBefore 10, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 1, LineBreakMode WordWrapping, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection Natural, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (\n) ListIntentOrdinal 0 CodeBlockIntentLanguageHint ''";
    NSStrikethrough = 0;
    WriteStyle = "Write_.StyleIdentifier(name: Optional(\"largeTitle\"), id: Optional(\"DD7AE374-223F-4B4C-B0B1-2BE741DC1C00\"))";
}

The new value being assigned has an additional paragraph and is:

A{
    NSColor = "<UIDynamicCatalogSystemColor: 0x60000179cd80; name = labelColor>";
    NSFont = "<UICTFont: 0x10a173dc0> font-family: \"Helvetica Neue\"; font-weight: bold; font-style: normal; font-size: 34.00pt";
    NSParagraphStyle = "Alignment Center, LineSpacing 1, ParagraphSpacing 0, ParagraphSpacingBefore 10, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 1, LineBreakMode WordWrapping, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection Natural, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (\n) ListIntentOrdinal 0 CodeBlockIntentLanguageHint ''";
    NSStrikethrough = 0;
    WriteStyle = "Write_.StyleIdentifier(name: Optional(\"largeTitle\"), id: Optional(\"DD7AE374-223F-4B4C-B0B1-2BE741DC1C00\"))";
}
{
    NSColor = "<UIDynamicCatalogSystemColor: 0x60000179cd80; name = labelColor>";
    NSFont = "<UICTFont: 0x108a27ca0> font-family: \"Helvetica Neue\"; font-weight: normal; font-style: normal; font-size: 17.00pt";
    NSParagraphStyle = "Alignment Left, LineSpacing 1, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 1, LineBreakMode WordWrapping, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection Natural, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (\n) ListIntentOrdinal 0 CodeBlockIntentLanguageHint ''";
    NSStrikethrough = 0;
    WriteStyle = "Write_.StyleIdentifier(name: Optional(\"body\"), id: Optional(\"205B7720-ECA2-4820-9B00-CA98B30AB221\"))";
}

After assignment the value is:

A{
    NSColor = "<UIDynamicCatalogSystemColor: 0x60000179cd80; name = labelColor>";
    NSFont = "<UICTFont: 0x10a173dc0> font-family: \"Helvetica Neue\"; font-weight: bold; font-style: normal; font-size: 34.00pt";
    NSParagraphStyle = "Alignment Center, LineSpacing 1, ParagraphSpacing 0, ParagraphSpacingBefore 10, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 1, LineBreakMode WordWrapping, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection Natural, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (\n) ListIntentOrdinal 0 CodeBlockIntentLanguageHint ''";
    NSStrikethrough = 0;
    WriteStyle = "Write_.StyleIdentifier(name: Optional(\"largeTitle\"), id: Optional(\"DD7AE374-223F-4B4C-B0B1-2BE741DC1C00\"))";
}
{
    NSColor = "<UIDynamicCatalogSystemColor: 0x60000179cd80; name = labelColor>";
    NSFont = "<UICTFont: 0x108a27ca0> font-family: \"Helvetica Neue\"; font-weight: normal; font-style: normal; font-size: 17.00pt";
    NSParagraphStyle = "Alignment Center, LineSpacing 1, ParagraphSpacing 0, ParagraphSpacingBefore 10, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 1, LineBreakMode WordWrapping, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection Natural, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (\n) ListIntentOrdinal 0 CodeBlockIntentLanguageHint ''";
    NSStrikethrough = 0;
    WriteStyle = "Write_.StyleIdentifier(name: Optional(\"body\"), id: Optional(\"205B7720-ECA2-4820-9B00-CA98B30AB221\"))";
}

For some reason that I cannot work out the alignment in the second paragraph has been changed from left to centre. I'm sure it is obvious, but I can't see it.

easiwriter commented 4 months ago

I have hit a similar problem and I think this is all down to the setter in public var attributedText: NSAttributedString

I have the following code:

 extension EditorView {
    func setAttributedText(_ text: NSAttributedString) {
        let selection = selectedRange
        attributedText = text
        selectedRange = selection
    }
 }

When this is called with text (for example) set to the value:

O{
    NSColor = "<UIDynamicCatalogSystemColor: 0x600001794d80; name = labelColor>";
    NSFont = "<UICTFont: 0x10bf38840> font-family: \"Helvetica Neue\"; font-weight: bold; font-style: normal; font-size: 34.00pt";
    NSParagraphStyle = "Alignment Center, LineSpacing 1, ParagraphSpacing 0, ParagraphSpacingBefore 10, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 1, LineBreakMode WordWrapping, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection Natural, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (\n) ListIntentOrdinal 0 CodeBlockIntentLanguageHint ''";
    NSStrikethrough = 0;
    WriteStyle = "Write_.StyleIdentifier(name: Optional(\"largeTitle\"), id: Optional(\"DD7AE374-223F-4B4C-B0B1-2BE741DC1C00\"))";
}ne{
    NSColor = "<UIDynamicCatalogSystemColor: 0x600001794d80; name = labelColor>";
    NSFont = "<UICTFont: 0x10c04fd30> font-family: \"Helvetica Neue\"; font-weight: normal; font-style: normal; font-size: 17.00pt";
    NSParagraphStyle = "Alignment Left, LineSpacing 1, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 1, LineBreakMode WordWrapping, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection LeftToRight, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (\n) ListIntentOrdinal 0 CodeBlockIntentLanguageHint ''";
    NSStrikethrough = 0;
    NSUnderline = 0;
}

It calls the setter which contains the lines:

 // Clear text before setting new value to avoid issues with formatting/layout when
 // editor is hosted in a scrollable container and content is set multiple times.
 richTextView.attributedText = NSAttributedString()

And that I think is the source of my problem - this, as it says, clears the text. The moment it does this it invokes textViewDidChangeSelection, which in turn ends up back in my code responding to the selection change. This all happens before the setter assigns the new value in:

richTextView.attributedText = newValue

Unfortunately my code has operated on the incorrect attributed string. This, I'm afraid, is a bit of a show stopper.

rajdeep commented 4 months ago

@easiwriter, I am not sure if I am getting it fully. Just to share some context: this code, as comment states, is added to circumvent the issue of layout in UITextView. Please note that in case of simple text, this issue is not aggravated. However, when you have a large complex document with custom attributes and a lot of attachments, it has some inconsistent behaviour. In absence of this code, if there is some text with some formatting in the textview and then you reset some other text, in some cases, depending on the attributes it yields unexpected results - possibly trying to merge attributes at the same range. Furthermore, I observed that it also resulted in crashes in some specific cases as the layout calculations in that scenario get corrupted with old and new overlap.

Getting back to textViewDidChangeSelection invocation, if you notice when the text is cleared, the range is .zero and attributedText is also empty. Do you think you can have a check on your code to ignore or process based on this condition? Alternatively, if you help me understand what you are trying to do and the issue that this change causes, I may be able to provide additional info/callbacks that you can use so that you can ignore executing the code that you wish to prevent in such a case.

rajdeep commented 4 months ago

also, not sure if this is relevant for you in this case, but you should know that the text is set only when Editor is already in a Window or when it moves to a Window. Please see the details here.

easiwriter commented 4 months ago

I've added a flag which is set before assigning to attributedText such that any call backs to change selection are ignored. The code in the setter now gets to line 527 in the attributedText setter. At the line:

richTextView.attributedText = newValue

the newValue is correct.

A{
    NSColor = "<UIDynamicCatalogSystemColor: 0x600001784340; name = labelColor>";
    NSFont = "<UICTFont: 0x10ee37900> font-family: \"Helvetica Neue\"; font-weight: bold; font-style: normal; font-size: 34.00pt";
    NSParagraphStyle = "Alignment Center, LineSpacing 1, ParagraphSpacing 0, ParagraphSpacingBefore 10, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 1, LineBreakMode WordWrapping, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection Natural, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (\n) ListIntentOrdinal 0 CodeBlockIntentLanguageHint ''";
    NSStrikethrough = 0;
    WriteStyle = "Write_.StyleIdentifier(name: Optional(\"largeTitle\"), id: Optional(\"DAF9DAC4-5D4F-437A-B2B9-C8DCC6577DFC\"))";
}
{
    NSColor = "<UIDynamicCatalogSystemColor: 0x600001784340; name = labelColor>";
    NSFont = "<UICTFont: 0x11137a590> font-family: \"Helvetica Neue\"; font-weight: normal; font-style: normal; font-size: 17.00pt";
    NSParagraphStyle = "Alignment Left, LineSpacing 1, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 1, LineBreakMode WordWrapping, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection Natural, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (\n) ListIntentOrdinal 0 CodeBlockIntentLanguageHint ''";
    NSStrikethrough = 0;
    WriteStyle = "Write_.StyleIdentifier(name: Optional(\"body\"), id: Optional(\"51601A2C-0B6D-41B1-A05A-0219F541D050\"))";
}

The input text consists of the letter A followed by a newline. The app implements a style mechanism to control formatting. Instead of fiddling with formatting commands the user simply chooses a style from a menu. In the example above the letter A is formatted according to the attributes of LargeTitle. The style name is held as the custom attribute

WriteStyle = "Write_.StyleIdentifier(name: Optional(\"largeTitle\")

The second paragraph is set to the body style as shown by its style attribute.

This is where things go wrong. The newValue as I said is correct and the alignment of the second paragraph is Alignment Left. I had just changed this paragraph to use the body style.

However, the code below at RichTextView line 82 changes the alignment to Alignment Centre.

    override var attributedText: NSAttributedString! {
        willSet {
            // Remove all attachment subviews else we may run into PRTextStorage "in middle of editing" crash
            subviews.filter { $0 is AttachmentContentView }.forEach { $0.removeFromSuperview() }
        }
    }

Here is the text after this method is called

A{
    NSColor = "<UIDynamicCatalogSystemColor: 0x600001789840; name = labelColor>";
    NSFont = "<UICTFont: 0x113340ea0> font-family: \"Helvetica Neue\"; font-weight: bold; font-style: normal; font-size: 34.00pt";
    NSParagraphStyle = "Alignment Center, LineSpacing 1, ParagraphSpacing 0, ParagraphSpacingBefore 10, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 1, LineBreakMode WordWrapping, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection Natural, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (\n) ListIntentOrdinal 0 CodeBlockIntentLanguageHint ''";
    NSStrikethrough = 0;
    WriteStyle = "Write_.StyleIdentifier(name: Optional(\"largeTitle\"), id: Optional(\"DAF9DAC4-5D4F-437A-B2B9-C8DCC6577DFC\"))";
}
{
    NSColor = "<UIDynamicCatalogSystemColor: 0x600001789840; name = labelColor>";
    NSFont = "<UICTFont: 0x16d00c980> font-family: \"Helvetica Neue\"; font-weight: normal; font-style: normal; font-size: 17.00pt";
    NSParagraphStyle = "Alignment Center, LineSpacing 1, ParagraphSpacing 0, ParagraphSpacingBefore 10, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 1, LineBreakMode WordWrapping, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection Natural, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0 LineBreakStrategy 0 PresentationIntents (\n) ListIntentOrdinal 0 CodeBlockIntentLanguageHint ''";
    NSStrikethrough = 0;
    WriteStyle = "Write_.StyleIdentifier(name: Optional(\"body\"), id: Optional(\"51601A2C-0B6D-41B1-A05A-0219F541D050\"))";
}

I don't understand what is going on here because there is only one view, the one containing the text. The app only has a single EditorView. I can only assume that the code has somehow used the alignment of the first paragraph.

What is really don't understand is this is not new code. It was working OK before I took a rest for a few months, so I don't know if the changes to AttributedText (here and in the calling method) have been made during this period. If so this would account for the problems I'm now having.

rajdeep commented 4 months ago

the code you have referenced here:

    override var attributedText: NSAttributedString! {
        willSet {
            // Remove all attachment subviews else we may run into PRTextStorage "in middle of editing" crash
            subviews.filter { $0 is AttachmentContentView }.forEach { $0.removeFromSuperview() }
        }
    }

has noting to do with attributedText or alignments of paragraph. This is specifically removing subviews added to the Editor via Attachment. If I understand it correctly, you are not using any attachments so this code should be of no consequence to you.

Over the past few months, I have been making a lot of updates to Proton which are primarily geared towards fixing bugs and performance issues. There may have been a change or a fix to a bug that is now causing a bug in your code or I might have accidentally fixed one bug and introduced another. I think we can do the following here:

  1. If it helps, please have a look at the Release Notes here for the time you were not actively developing the app and see if there's something obvious that shows up.
  2. If possible, please create a failing unit test case with your scenario and the expected output. If I have a failing test case, it will be much easier for me to provide a fix or advise if there's something that you may need to do differently.

Also, there is no code at all in Proton anywhere that changes the alignment of Paragraph to center. The only code around alignment that is there is in regards to the default paragraphStyle that you can set on the Editor which is automatically applied if the editor has some content and then entire content is deleted using backspace/select and delete.

easiwriter commented 4 months ago

The code gets to line 527 in the setter where it executes richTextView.attributedText = newValue.

The newValue has left alignment set.

After executing this line the alignment is set to centre aligned, as shown in the debugger output.. Something must have reset it. That is why I’m confused because the code it executes, as you say, doesn’t change it. Maybe a callback has got in there?

I will look through the release note.

Best Keith

On 3 Mar 2024, at 08:06, Rajdeep Kwatra @.***> wrote:

the code you have referenced here:

override var attributedText: NSAttributedString! {
    willSet {
        // Remove all attachment subviews else we may run into PRTextStorage "in middle of editing" crash
        subviews.filter { $0 is AttachmentContentView }.forEach { $0.removeFromSuperview() }
    }
}

has noting to do with attributedText or alignments of paragraph. This is specifically removing subviews added to teh Editor via Attachment. If I understand it correctly, you are not using any attachments so this code should be of no consequence to you.

Over the past few months, I have been making a lot of updates to Proton which are primarily geared towards fixing bugs and performance issues. There may have been a change or a fix to a bug that is now i causing a bug in your code or I might have accidentally fixed one bug and introduced another. I think we can do the following here:

If it helps, please have a look at the Release Notes here https://github.com/rajdeep/proton/releases for the time you were not actively developing the app and see if there's something obvious that shows up. If possible, please create a failing unit test case with your scenario and the expected output. If I have a failing test case, it will be much easier for me to provide a fix or advise if there's something that you may need to do differently. Also, there is no code at all in Proton anywhere that changes the alignment of Paragraph to center. The only code around alignment that is there is in regards to the default paragraphStyle that you can set on the Editor which is automatically applied if the editor has some content and then entire content is deleted using backspace/select and delete.

— Reply to this email directly, view it on GitHub https://github.com/rajdeep/proton/issues/286#issuecomment-1975080364, or unsubscribe https://github.com/notifications/unsubscribe-auth/AEUXIA7WQPMQ5EO4OMPRWQTYWLKW5AVCNFSM6AAAAABD4FMPU2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTSNZVGA4DAMZWGQ. You are receiving this because you were mentioned.