From db304f2fc30a101db0555f1b7ef617ebc4a9a7b6 Mon Sep 17 00:00:00 2001 From: Ruslan Semov Date: Tue, 8 Sep 2026 12:17:36 +0300 Subject: [PATCH] Scale Rich Text message typography with the Text Size setting ChatMessageRichDataBubbleContentNode built its InstantPageTextCategories from nine hardcoded point sizes, so Rich Text / Extended Markdown messages did not follow Settings > Appearance > Text Size. An ordinary bubble uses ChatPresentationData.messageFont, i.e. Font.regular(fontSize.baseDisplaySize). The .regular step is 17pt, exactly the hardcoded paragraph size, so the two paths agree there and diverge at every other step. Scale the finished theme through the existing public helper InstantPageTheme.withUpdatedFontStyles by baseDisplaySize / 17.0 instead of adding a second set of constants. lineSpacingFactor: 1.0 keeps each category's own factor (the helper multiplies rather than replaces it) and forceSerif: false keeps the sans/serif split the theme already declares. Also scale the "show more" link, which was fixed at Font.regular(17.0), and add the base font size to the currentPageLayout cache key: that key held the bounding width and the theme object identity but no font size, so a Text Size change that did not also replace the PresentationTheme object would have reused a stale layout. At the .regular step the multiplier is 1, so rendering there is unchanged. --- .../ChatMessageRichDataBubbleContentNode.swift | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageRichDataBubbleContentNode/Sources/ChatMessageRichDataBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageRichDataBubbleContentNode/Sources/ChatMessageRichDataBubbleContentNode.swift index b16547937ef..22720c07012 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageRichDataBubbleContentNode/Sources/ChatMessageRichDataBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageRichDataBubbleContentNode/Sources/ChatMessageRichDataBubbleContentNode.swift @@ -50,6 +50,7 @@ public class ChatMessageRichDataBubbleContentNode: ChatMessageBubbleContentNode // this, the cached layout would shadow newly-arrived content during streaming. private var currentPageLayout: (boundingWidth: CGFloat, presentationThemeIdentity: ObjectIdentifier, + baseFontSize: CGFloat, expandedDetails: [Int: Bool], messageStableVersion: UInt32, pendingEditKey: ObjectIdentifier?, @@ -442,6 +443,11 @@ public class ChatMessageRichDataBubbleContentNode: ChatMessageBubbleContentNode let _ = codeBlockTitleColor let _ = codeBlockAccentColor + // Rich text is a message bubble like any other, so its typography follows + // Settings > Appearance > Text Size. The sizes below are authored against the + // `.regular` step (17.0pt), so scaling the finished theme by + // baseDisplaySize / 17.0 leaves that step identical and moves every other one. + let baseFontSize = item.presentationData.fontSize.baseDisplaySize let textCategories = InstantPageTextCategories( kicker: InstantPageTextAttributes(font: InstantPageFont(style: .sans, size: 15.0, lineSpacingFactor: 0.685), color: messageTheme.primaryTextColor), header: InstantPageTextAttributes(font: InstantPageFont(style: .serif, size: 19.0, lineSpacingFactor: 0.685), color: messageTheme.primaryTextColor), @@ -477,6 +483,10 @@ public class ChatMessageRichDataBubbleContentNode: ChatMessageBubbleContentNode secondaryControlColor: messageTheme.secondaryTextColor.mixedWith(mainColor.withMultipliedAlpha(0.2), alpha: 0.3), quoteAccentColor: mainColor ) + // withUpdatedFontStyles multiplies lineSpacingFactor rather than replacing it, + // so 1.0 keeps each category's own factor; forceSerif: false keeps the + // sans/serif split declared above. + .withUpdatedFontStyles(sizeMultiplier: baseFontSize / 17.0, lineSpacingFactor: 1.0, forceSerif: false) var hasDraft = false if item.message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) { @@ -530,6 +540,7 @@ public class ChatMessageRichDataBubbleContentNode: ChatMessageBubbleContentNode if let current = currentPageLayout, current.boundingWidth == suggestedBoundingWidth, current.presentationThemeIdentity == presentationThemeIdentity, + current.baseFontSize == baseFontSize, current.expandedDetails == currentExpandedDetails, current.showMoreExpanded == showMoreExpanded, current.messageStableVersion == currentMessageStableVersion, @@ -718,7 +729,7 @@ public class ChatMessageRichDataBubbleContentNode: ChatMessageBubbleContentNode var showMoreFramePageLocal: CGRect? if showMore, let pageLayout { let title = item.presentationData.strings.Chat_RichText_ShowMore - let attributedTitle = NSAttributedString(string: title, font: Font.regular(17.0), textColor: messageTheme.linkTextColor) + let attributedTitle = NSAttributedString(string: title, font: Font.regular(item.presentationData.fontSize.baseDisplaySize), textColor: messageTheme.linkTextColor) // The link only fits within the existing bubble width (it does not widen the // bubble the way the status node does); the short fixed string never needs more, // and `.end` truncation is a safe fallback for a pathologically narrow bubble. @@ -944,6 +955,7 @@ public class ChatMessageRichDataBubbleContentNode: ChatMessageBubbleContentNode self.currentPageLayout = ( suggestedBoundingWidth, ObjectIdentifier(item.presentationData.theme.theme), + item.presentationData.fontSize.baseDisplaySize, self.currentExpandedDetails, item.message.stableVersion, (item.attributes.updatingMedia?.richText).map({ ObjectIdentifier($0) }),