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

Trying to toggleStyle with emoticons selected breaks toggleStyle #149

Open
noox89 opened this issue Feb 26, 2024 · 7 comments
Open

Trying to toggleStyle with emoticons selected breaks toggleStyle #149

noox89 opened this issue Feb 26, 2024 · 7 comments
Labels
bug Something isn't working

Comments

@noox89
Copy link

noox89 commented Feb 26, 2024

RPReplay_Final1708962172.MP4

When selecting text which includes emoticons and applying context.toggleStyle(.bold) or context.toggleStyle(.italic) the spacing gets disrupted and toggleStyle stops working. To get back to normal, font needs to be changed.

@DominikBucher12
Copy link
Collaborator

I will take a look into this but if feels strange. Can you print attributes of the selected string, in order to distinguish, what is the issue (if its kerning or paragraph style or something else...) Thanks!

@DominikBucher12 DominikBucher12 added the bug Something isn't working label Feb 29, 2024
@noox89
Copy link
Author

noox89 commented Feb 29, 2024

Here is the print of the attributed string. The unexpected behaviour only happens with emoticon at the beginning of the line

NSColor = "<UIDynamicCatalogSystemColor: 0x132410540; name = labelColor>";
    NSFont = "<UICTFont: 0x131db6080> font-family: \".SFUI-Semibold\"; font-weight: bold; font-style: normal; font-size: 16.00pt";
    NSParagraphStyle = "Alignment Natural, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, 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 ''";
}😊{
    NSColor = "<UIDynamicCatalogSystemColor: 0x132410540; name = labelColor>";
    NSFont = "<UICTFont: 0x13b639800> font-family: \".AppleColorEmojiUI\"; font-weight: normal; font-style: normal; font-size: 16.00pt";
    NSOriginalFont = "<UICTFont: 0x131db6080> font-family: \".SFUI-Semibold\"; font-weight: bold; font-style: normal; font-size: 16.00pt";
    NSParagraphStyle = "Alignment Natural, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, 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 ''";
}this is a test{
    NSColor = "<UIDynamicCatalogSystemColor: 0x132410540; name = labelColor>";
    NSFont = "<UICTFont: 0x131db6080> font-family: \".SFUI-Semibold\"; font-weight: bold; font-style: normal; font-size: 16.00pt";
    NSParagraphStyle = "Alignment Natural, LineSpacing 0, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, 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 ''";
}

@DominikBucher12
Copy link
Collaborator

Hello @noox89 again,

I think I found the issue but I am not quite sure what to do with it right now.

You see, when apple renders Emojis, it uses font Apple Color Emoji which is special font with very nice symbolic traits which are probably not available in UIKit and we need to fix this in CoreText.

The issue is not just one function, altought I would be happy to fix it only in that way...

Given the font:

Snímek obrazovky 2024-02-29 v 23 54 31

The issue is in here:

func setRichTextStyle(
        _ style: RichTextStyle,
        to newValue: Bool
    ) {
        let value = newValue ? 1 : 0
        switch style {
        case .bold, .italic:
            let styles = richTextStyles
            guard styles.shouldAddOrRemove(style, newValue) else { return }
            >>>>>>>>>>>>  guard let font = richTextFont else { return } <<<<<<<<<<<<<<<
            guard let newFont = font.toggling(style) else { return }
            setRichTextFont(newFont)
        case .underlined:
            setRichTextAttribute(.underlineStyle, to: value)
        case .strikethrough:
            setRichTextAttribute(.strikethroughStyle, to: value)
        }
    }

which when we apply font style, we get richTextFont, our poor NSAttributes cannot get all the fonts at all ranges, so they return just the first font, which (unlucky for you) is Apple Color Emoji It sets this font to whole text together with bold and thats where the troubles begin. Because this font should be used for emojis only, it has some quirks like super long spaces etc and it causes the behaviour you are seeing.

I See the solution in some steps:

  1. Explicitly never allow font to be Apple Color Emoji in typing attributes or at any other text somehow
  2. When setting the bold style, set somehow just the style to the font and do not replace it, I think CoreText API is in need.
  3. ???
  4. PROFIT!

cc @danielsaidi looks like we have serious issue 😨

Final.bug.mov

BTW Thanks for reporting this❗️

@DominikBucher12
Copy link
Collaborator

Looks like my little PoC fixes the issue, but I need to add unit tests probably for this afterwards...

Simulator.Screen.Recording.-.iPhone.15.-.2024-03-01.at.01.00.02.mp4
 func setRichTextStyle(
        _ style: RichTextStyle,
        to newValue: Bool
    ) {
        let value = newValue ? 1 : 0
        switch style {
        case .bold, .italic:
            carymaryfuk😅()
//            let styles = richTextStyles
//            guard styles.shouldAddOrRemove(style, newValue) else { return }
//            guard let font = richTextFont else { return }
//            guard let newFont = font.toggling(style) else { return }
//            setRichTextFont(newFont)
        case .underlined:
            setRichTextAttribute(.underlineStyle, to: value)
        case .strikethrough:
            setRichTextAttribute(.strikethroughStyle, to: value)
        }
    }

    func carymaryfuk😅() {
        var updatedAttributes: [NSAttributedString.Key: Any] = [:]

        richText.enumerateAttributes(in: selectedRange, options: [.longestEffectiveRangeNotRequired]) { (attributes, range, _) in
            var updatedFont: UIFont?
            if let existingFont = attributes[.font] as? UIFont {
                print(existingFont)
                let newFontDescriptor = existingFont.fontDescriptor
                var traits = existingFont.fontDescriptor.symbolicTraits

                // Check and modify bold trait
                if existingFont.isBold {
                    traits.remove(.traitBold)
                } else {
                    traits.insert(.traitBold)
                }

                // Check and modify italic trait
                if existingFont.isItalic {
                    traits.remove(.traitItalic)
                } else {
                    traits.insert(.traitItalic)
                }

                if let newFontDescriptor = newFontDescriptor.withSymbolicTraits(traits) {
                    updatedFont = UIFont(descriptor: newFontDescriptor, size: existingFont.pointSize)
                }

                updatedAttributes[.font] = updatedFont

                richText.enumerateAttributes(in: range, options: []) { (attributes, range, _) in
                    if let font = attributes[.font] as? UIFont {
                        updatedAttributes[.font] = updatedFont ?? font
                    }
                }

                let updatedAttributedString = NSAttributedString(string: richText.attributedSubstring(from: range).string, attributes: updatedAttributes)
                (self as? RichTextView)?.textStorage.replaceCharacters(in: range, with: updatedAttributedString)
            }
        }
    }

  ..... 
// Little helpers

extension UIFont {
    var isBold: Bool {
        return fontDescriptor.symbolicTraits.contains(.traitBold)
    }

    var isItalic: Bool {
        return fontDescriptor.symbolicTraits.contains(.traitItalic)
    }
}

You can see I am enumerating the attributes and setting each coresponding font to just bold or italic (with some sample extensions)

I am going to sleep now, but I guess I can take a look tommorow :)

Thanks and good night :)

@noox89
Copy link
Author

noox89 commented Mar 1, 2024

Thanks for explanation and quick turnaround!:) Btw I like your new func -> čáry máry fuk 🧙‍♂️

Díky/Thanks🥳

@danielsaidi
Copy link
Owner

Omg, great find @noox89 and @DominikBucher12

@danielsaidi danielsaidi added this to the 1.0 milestone Mar 1, 2024
@danielsaidi danielsaidi removed this from the 1.0 milestone Mar 28, 2024
@danielsaidi
Copy link
Owner

Let's release 1.0 and look at this later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants