diff --git a/submodules/Display/Display/TextNode.swift b/submodules/Display/Display/TextNode.swift index b70b5400fa..a00c22c472 100644 --- a/submodules/Display/Display/TextNode.swift +++ b/submodules/Display/Display/TextNode.swift @@ -5,6 +5,14 @@ import CoreText private let defaultFont = UIFont.systemFont(ofSize: 15.0) +private final class TextNodeStrikethrough { + let frame: CGRect + + init(frame: CGRect) { + self.frame = frame + } +} + private final class TextNodeLine { let line: CTLine let frame: CGRect @@ -21,7 +29,7 @@ private final class TextNodeLine { } } -private final class TextNodeStrikethrough { +private final class TextNodeBlockQuote { let frame: CGRect init(frame: CGRect) { @@ -83,8 +91,9 @@ public final class TextNodeLayoutArguments { public let lineSpacing: CGFloat public let cutout: TextNodeCutout? public let insets: UIEdgeInsets + public let lineColor: UIColor? - public init(attributedString: NSAttributedString?, backgroundColor: UIColor? = nil, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment = .natural, lineSpacing: CGFloat = 0.12, cutout: TextNodeCutout? = nil, insets: UIEdgeInsets = UIEdgeInsets()) { + public init(attributedString: NSAttributedString?, backgroundColor: UIColor? = nil, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment = .natural, lineSpacing: CGFloat = 0.12, cutout: TextNodeCutout? = nil, insets: UIEdgeInsets = UIEdgeInsets(), lineColor: UIColor? = nil) { self.attributedString = attributedString self.backgroundColor = backgroundColor self.maximumNumberOfLines = maximumNumberOfLines @@ -94,6 +103,7 @@ public final class TextNodeLayoutArguments { self.lineSpacing = lineSpacing self.cutout = cutout self.insets = insets + self.lineColor = lineColor } } @@ -111,9 +121,11 @@ public final class TextNodeLayout: NSObject { public let truncated: Bool fileprivate let firstLineOffset: CGFloat fileprivate let lines: [TextNodeLine] + fileprivate let blockQuotes: [TextNodeBlockQuote] + fileprivate let lineColor: UIColor? public let hasRTL: Bool - fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], backgroundColor: UIColor?) { + fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], blockQuotes: [TextNodeBlockQuote], backgroundColor: UIColor?, lineColor: UIColor?) { self.attributedString = attributedString self.maximumNumberOfLines = maximumNumberOfLines self.truncationType = truncationType @@ -126,7 +138,9 @@ public final class TextNodeLayout: NSObject { self.truncated = truncated self.firstLineOffset = firstLineOffset self.lines = lines + self.blockQuotes = blockQuotes self.backgroundColor = backgroundColor + self.lineColor = lineColor var hasRTL = false for line in lines { if line.isRTL { @@ -561,7 +575,7 @@ public class TextNode: ASDisplayNode { } } - private class func calculateLayout(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets) -> TextNodeLayout { + private class func calculateLayout(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, lineColor: UIColor?) -> TextNodeLayout { if let attributedString = attributedString { let stringLength = attributedString.length @@ -582,11 +596,12 @@ public class TextNode: ASDisplayNode { let fontLineSpacing = floor(fontLineHeight * lineSpacingFactor) var lines: [TextNodeLine] = [] + var blockQuotes: [TextNodeBlockQuote] = [] var maybeTypesetter: CTTypesetter? maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString) if maybeTypesetter == nil { - return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], backgroundColor: backgroundColor) + return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor) } let typesetter = maybeTypesetter! @@ -682,11 +697,28 @@ public class TextNode: ASDisplayNode { truncated = true } + var headIndent: CGFloat = 0.0 + attributedString.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in + if let _ = attributes[NSAttributedStringKey.strikethroughStyle] { + let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil)) + let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil)) + let x = lowerX < upperX ? lowerX : upperX + strikethroughs.append(TextNodeStrikethrough(frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight))) + } else if let paragraphStyle = attributes[NSAttributedStringKey.paragraphStyle] as? NSParagraphStyle { + headIndent = paragraphStyle.headIndent + + } + } + let lineWidth = min(constrainedSize.width, ceil(CGFloat(CTLineGetTypographicBounds(coreTextLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(coreTextLine)))) - let lineFrame = CGRect(x: lineCutoutOffset, y: lineOriginY, width: lineWidth, height: fontLineHeight) + let lineFrame = CGRect(x: lineCutoutOffset + headIndent, y: lineOriginY, width: lineWidth, height: fontLineHeight) layoutSize.height += fontLineHeight + fontLineSpacing layoutSize.width = max(layoutSize.width, lineWidth + lineAdditionalWidth) + if headIndent > 0.0 { + blockQuotes.append(TextNodeBlockQuote(frame: lineFrame)) + } + var isRTL = false let glyphRuns = CTLineGetGlyphRuns(coreTextLine) as NSArray if glyphRuns.count != 0 { @@ -696,16 +728,7 @@ public class TextNode: ASDisplayNode { } } - attributedString.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in - if let _ = attributes[NSAttributedStringKey.strikethroughStyle] { - let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil)) - let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil)) - let x = lowerX < upperX ? lowerX : upperX - strikethroughs.append(TextNodeStrikethrough(frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight))) - } - } lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs)) - break } else { if lineCharacterCount > 0 { @@ -719,11 +742,27 @@ public class TextNode: ASDisplayNode { let coreTextLine = CTTypesetterCreateLineWithOffset(typesetter, lineRange, 100.0) lastLineCharacterIndex += lineCharacterCount + var headIndent: CGFloat = 0.0 + attributedString.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in + if let _ = attributes[NSAttributedStringKey.strikethroughStyle] { + let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil)) + let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil)) + let x = lowerX < upperX ? lowerX : upperX + strikethroughs.append(TextNodeStrikethrough(frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight))) + } else if let paragraphStyle = attributes[NSAttributedStringKey.paragraphStyle] as? NSParagraphStyle { + headIndent = paragraphStyle.headIndent + } + } + let lineWidth = ceil(CGFloat(CTLineGetTypographicBounds(coreTextLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(coreTextLine))) - let lineFrame = CGRect(x: lineCutoutOffset, y: lineOriginY, width: lineWidth, height: fontLineHeight) + let lineFrame = CGRect(x: lineCutoutOffset + headIndent, y: lineOriginY, width: lineWidth, height: fontLineHeight) layoutSize.height += fontLineHeight layoutSize.width = max(layoutSize.width, lineWidth + lineAdditionalWidth) + if headIndent > 0.0 { + blockQuotes.append(TextNodeBlockQuote(frame: lineFrame)) + } + var isRTL = false let glyphRuns = CTLineGetGlyphRuns(coreTextLine) as NSArray if glyphRuns.count != 0 { @@ -733,14 +772,6 @@ public class TextNode: ASDisplayNode { } } - attributedString.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in - if let _ = attributes[NSAttributedStringKey.strikethroughStyle] { - let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil)) - let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil)) - let x = lowerX < upperX ? lowerX : upperX - strikethroughs.append(TextNodeStrikethrough(frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight))) - } - } lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs)) } else { if !lines.isEmpty { @@ -762,9 +793,9 @@ public class TextNode: ASDisplayNode { } } - return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, backgroundColor: backgroundColor) + return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, blockQuotes: blockQuotes, backgroundColor: backgroundColor, lineColor: lineColor) } else { - return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], backgroundColor: backgroundColor) + return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], blockQuotes: [], backgroundColor: backgroundColor, lineColor: lineColor) } } @@ -799,11 +830,8 @@ public class TextNode: ASDisplayNode { let textMatrix = context.textMatrix let textPosition = context.textPosition - //CGContextSaveGState(context) - context.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0) - //let clipRect = CGContextGetClipBoundingBox(context) let alignment = layout.alignment let offset = CGPoint(x: layout.insets.left, y: layout.insets.top) @@ -832,7 +860,34 @@ public class TextNode: ASDisplayNode { } } - //CGContextRestoreGState(context) + var blockQuoteFrames: [CGRect] = [] + var currentBlockQuoteFrame: CGRect? + for blockQuote in layout.blockQuotes { + if let frame = currentBlockQuoteFrame { + if blockQuote.frame.minY - frame.maxY < 20.0 { + currentBlockQuoteFrame = frame.union(blockQuote.frame) + } else { + blockQuoteFrames.append(frame) + currentBlockQuoteFrame = frame + } + } else { + currentBlockQuoteFrame = blockQuote.frame + } + } + + if let frame = currentBlockQuoteFrame { + blockQuoteFrames.append(frame) + } + + for frame in blockQuoteFrames { + if let lineColor = layout.lineColor { + context.setFillColor(lineColor.cgColor) + } + let rect = UIBezierPath(roundedRect: CGRect(x: frame.minX - 9.0, y: frame.minY - 14.0, width: 2.0, height: frame.height), cornerRadius: 1.0) + context.addPath(rect.cgPath) + context.fillPath() + } + context.textMatrix = textMatrix context.textPosition = CGPoint(x: textPosition.x, y: textPosition.y) } @@ -872,11 +927,11 @@ public class TextNode: ASDisplayNode { if stringMatch { layout = existingLayout } else { - layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets) + layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor) updated = true } } else { - layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets) + layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets, lineColor: arguments.lineColor) updated = true } diff --git a/submodules/TelegramUI/TelegramUI/AuthorizationSequenceController.swift b/submodules/TelegramUI/TelegramUI/AuthorizationSequenceController.swift index 6856cc558c..d6e9fcf4b8 100644 --- a/submodules/TelegramUI/TelegramUI/AuthorizationSequenceController.swift +++ b/submodules/TelegramUI/TelegramUI/AuthorizationSequenceController.swift @@ -277,7 +277,7 @@ public final class AuthorizationSequenceController: NavigationController, MFMail } var dismissImpl: (() -> Void)? let alertTheme = AlertControllerTheme(presentationTheme: strongSelf.theme) - let attributedText = stringWithAppliedEntities(termsOfService.text, entities: termsOfService.entities, baseColor: alertTheme.primaryColor, linkColor: alertTheme.accentColor, baseFont: Font.regular(13.0), linkFont: Font.regular(13.0), boldFont: Font.semibold(13.0), italicFont: Font.italic(13.0), boldItalicFont: Font.semiboldItalic(13.0), fixedFont: Font.regular(13.0)) + let attributedText = stringWithAppliedEntities(termsOfService.text, entities: termsOfService.entities, baseColor: alertTheme.primaryColor, linkColor: alertTheme.accentColor, baseFont: Font.regular(13.0), linkFont: Font.regular(13.0), boldFont: Font.semibold(13.0), italicFont: Font.italic(13.0), boldItalicFont: Font.semiboldItalic(13.0), fixedFont: Font.regular(13.0), blockQuoteFont: Font.regular(13.0)) let contentNode = TextAlertContentNode(theme: alertTheme, title: NSAttributedString(string: strongSelf.strings.Login_TermsOfServiceHeader, font: Font.medium(17.0), textColor: alertTheme.primaryColor, paragraphAlignment: .center), text: attributedText, actions: [ TextAlertAction(type: .defaultAction, title: strongSelf.strings.Login_TermsOfServiceAgree, action: { dismissImpl?() diff --git a/submodules/TelegramUI/TelegramUI/ChatBotInfoItem.swift b/submodules/TelegramUI/TelegramUI/ChatBotInfoItem.swift index 2c42f66e9b..993f51b18a 100644 --- a/submodules/TelegramUI/TelegramUI/ChatBotInfoItem.swift +++ b/submodules/TelegramUI/TelegramUI/ChatBotInfoItem.swift @@ -152,7 +152,7 @@ final class ChatBotInfoItemNode: ListViewItemNode { updatedTextAndEntities = (item.text, generateTextEntities(item.text, enabledTypes: .all)) } - let attributedText = stringWithAppliedEntities(updatedTextAndEntities.0, entities: updatedTextAndEntities.1, baseColor: item.presentationData.theme.theme.chat.message.infoPrimaryTextColor, linkColor: item.presentationData.theme.theme.chat.message.infoLinkTextColor, baseFont: messageFont, linkFont: messageFont, boldFont: messageBoldFont, italicFont: messageItalicFont, boldItalicFont: messageBoldItalicFont, fixedFont: messageFixedFont) + let attributedText = stringWithAppliedEntities(updatedTextAndEntities.0, entities: updatedTextAndEntities.1, baseColor: item.presentationData.theme.theme.chat.message.infoPrimaryTextColor, linkColor: item.presentationData.theme.theme.chat.message.infoLinkTextColor, baseFont: messageFont, linkFont: messageFont, boldFont: messageBoldFont, italicFont: messageItalicFont, boldItalicFont: messageBoldItalicFont, fixedFont: messageFixedFont, blockQuoteFont: messageFont) let horizontalEdgeInset: CGFloat = 10.0 + params.leftInset let horizontalContentInset: CGFloat = 12.0 diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageActionItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageActionItemNode.swift index 3b7617e208..e07c4c5351 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageActionItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageActionItemNode.swift @@ -385,7 +385,7 @@ private func universalServiceMessageString(theme: ChatPresentationThemeData?, st } attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor) case let .customText(text, entities): - attributedString = stringWithAppliedEntities(text, entities: entities, baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, underlineLinks: false) + attributedString = stringWithAppliedEntities(text, entities: entities, baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, blockQuoteFont: titleFont, underlineLinks: false) case let .botDomainAccessGranted(domain): attributedString = NSAttributedString(string: strings.AuthSessions_Message(domain).0, font: titleFont, textColor: primaryTextColor) case let .botSentSecureValues(types): diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageAttachedContentNode.swift index f70cc6e086..b68200b160 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageAttachedContentNode.swift @@ -15,6 +15,7 @@ private let textBoldFont = Font.semibold(15.0) private let textItalicFont = Font.italic(15.0) private let textBoldItalicFont = Font.semiboldItalic(15.0) private let textFixedFont = Font.regular(15.0) +private let textBlockQuoteFont = Font.regular(15.0) private let buttonFont = Font.semibold(13.0) enum ChatMessageAttachedContentActionIcon { @@ -371,7 +372,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { string.append(NSAttributedString(string: "\n", font: textFont, textColor: messageTheme.primaryTextColor)) } if let entities = entities { - string.append(stringWithAppliedEntities(text, entities: entities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont)) + string.append(stringWithAppliedEntities(text, entities: entities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont, blockQuoteFont: textBlockQuoteFont)) } else { string.append(NSAttributedString(string: text + "\n", font: textFont, textColor: messageTheme.primaryTextColor)) } diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageTextBubbleContentNode.swift index 6e98e91484..e33571fb75 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageTextBubbleContentNode.swift @@ -230,7 +230,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { let forceStatusNewline = false if let entities = entities { - attributedText = stringWithAppliedEntities(rawText, entities: entities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseFont: textFont, linkFont: textFont, boldFont: item.presentationData.messageBoldFont, italicFont: item.presentationData.messageItalicFont, boldItalicFont: item.presentationData.messageBoldItalicFont, fixedFont: item.presentationData.messageFixedFont) + attributedText = stringWithAppliedEntities(rawText, entities: entities, baseColor: messageTheme.primaryTextColor, linkColor: messageTheme.linkTextColor, baseFont: textFont, linkFont: textFont, boldFont: item.presentationData.messageBoldFont, italicFont: item.presentationData.messageItalicFont, boldItalicFont: item.presentationData.messageBoldItalicFont, fixedFont: item.presentationData.messageFixedFont, blockQuoteFont: item.presentationData.messageBlockQuoteFont) } else { attributedText = NSAttributedString(string: rawText, font: textFont, textColor: messageTheme.primaryTextColor) } @@ -242,7 +242,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { let textInsets = UIEdgeInsets(top: 2.0, left: 0.0, bottom: 5.0, right: 0.0) - let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: cutout, insets: textInsets)) + let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: cutout, insets: textInsets, lineColor: messageTheme.accentControlColor)) var textFrame = CGRect(origin: CGPoint(x: -textInsets.left, y: -textInsets.top), size: textLayout.size) var textFrameWithoutInsets = CGRect(origin: CGPoint(x: textFrame.origin.x + textInsets.left, y: textFrame.origin.y + textInsets.top), size: CGSize(width: textFrame.width - textInsets.left - textInsets.right, height: textFrame.height - textInsets.top - textInsets.bottom)) diff --git a/submodules/TelegramUI/TelegramUI/ChatPresentationData.swift b/submodules/TelegramUI/TelegramUI/ChatPresentationData.swift index 9971681d4e..22b3e1a386 100644 --- a/submodules/TelegramUI/TelegramUI/ChatPresentationData.swift +++ b/submodules/TelegramUI/TelegramUI/ChatPresentationData.swift @@ -84,6 +84,7 @@ public final class ChatPresentationData { let messageItalicFont: UIFont let messageBoldItalicFont: UIFont let messageFixedFont: UIFont + let messageBlockQuoteFont: UIFont init(theme: ChatPresentationThemeData, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, disableAnimations: Bool, largeEmoji: Bool) { self.theme = theme @@ -103,5 +104,6 @@ public final class ChatPresentationData { self.messageItalicFont = UIFont.italicSystemFont(ofSize: baseFontSize) self.messageBoldItalicFont = Font.semiboldItalic(baseFontSize) self.messageFixedFont = UIFont(name: "Menlo-Regular", size: baseFontSize - 1.0) ?? UIFont.systemFont(ofSize: baseFontSize) + self.messageBlockQuoteFont = UIFont.systemFont(ofSize: baseFontSize - 1.0) } } diff --git a/submodules/TelegramUI/TelegramUI/ChatTextInputAttributes.swift b/submodules/TelegramUI/TelegramUI/ChatTextInputAttributes.swift index 032306111a..939a7918d6 100644 --- a/submodules/TelegramUI/TelegramUI/ChatTextInputAttributes.swift +++ b/submodules/TelegramUI/TelegramUI/ChatTextInputAttributes.swift @@ -39,6 +39,7 @@ struct ChatTextFontAttributes: OptionSet { static let bold = ChatTextFontAttributes(rawValue: 1 << 0) static let italic = ChatTextFontAttributes(rawValue: 1 << 1) static let monospace = ChatTextFontAttributes(rawValue: 1 << 2) + static let blockQuote = ChatTextFontAttributes(rawValue: 1 << 3) } func textAttributedStringForStateText(_ stateText: NSAttributedString, fontSize: CGFloat, textColor: UIColor, accentTextColor: UIColor) -> NSAttributedString { diff --git a/submodules/TelegramUI/TelegramUI/GalleryController.swift b/submodules/TelegramUI/TelegramUI/GalleryController.swift index e2c884eb88..6856c12d7a 100644 --- a/submodules/TelegramUI/TelegramUI/GalleryController.swift +++ b/submodules/TelegramUI/TelegramUI/GalleryController.swift @@ -122,7 +122,7 @@ private let boldItalicFont = Font.semiboldItalic(16.0) private let fixedFont = UIFont(name: "Menlo-Regular", size: 15.0) ?? textFont func galleryCaptionStringWithAppliedEntities(_ text: String, entities: [MessageTextEntity]) -> NSAttributedString { - return stringWithAppliedEntities(text, entities: entities, baseColor: .white, linkColor: UIColor(rgb: 0x5ac8fa), baseFont: textFont, linkFont: textFont, boldFont: boldFont, italicFont: italicFont, boldItalicFont: boldItalicFont, fixedFont: fixedFont, underlineLinks: false) + return stringWithAppliedEntities(text, entities: entities, baseColor: .white, linkColor: UIColor(rgb: 0x5ac8fa), baseFont: textFont, linkFont: textFont, boldFont: boldFont, italicFont: italicFont, boldItalicFont: boldItalicFont, fixedFont: fixedFont, blockQuoteFont: textFont, underlineLinks: false) } private func galleryMessageCaptionText(_ message: Message) -> String { diff --git a/submodules/TelegramUI/TelegramUI/ItemListAddressItem.swift b/submodules/TelegramUI/TelegramUI/ItemListAddressItem.swift index c05ce7c48f..c5eaa57a47 100644 --- a/submodules/TelegramUI/TelegramUI/ItemListAddressItem.swift +++ b/submodules/TelegramUI/TelegramUI/ItemListAddressItem.swift @@ -182,7 +182,7 @@ class ItemListAddressItemNode: ListViewItemNode { let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor: labelColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftOffset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let baseColor = item.theme.list.itemPrimaryTextColor - let string = stringWithAppliedEntities(item.text, entities: [], baseColor: baseColor, linkColor: item.theme.list.itemAccentColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont) + let string = stringWithAppliedEntities(item.text, entities: [], baseColor: baseColor, linkColor: item.theme.list.itemAccentColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont, blockQuoteFont: textFont) let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftOffset - leftInset - rightInset - 98.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let padding: CGFloat = !item.label.isEmpty ? 39.0 : 20.0 diff --git a/submodules/TelegramUI/TelegramUI/ItemListMultilineTextItem.swift b/submodules/TelegramUI/TelegramUI/ItemListMultilineTextItem.swift index 8a0dd83357..2b85440197 100644 --- a/submodules/TelegramUI/TelegramUI/ItemListMultilineTextItem.swift +++ b/submodules/TelegramUI/TelegramUI/ItemListMultilineTextItem.swift @@ -185,7 +185,7 @@ class ItemListMultilineTextItemNode: ListViewItemNode { } let entities = generateTextEntities(item.text, enabledTypes: item.enabledEntityTypes) - let string = stringWithAppliedEntities(item.text, entities: entities, baseColor: textColor, linkColor: item.theme.list.itemAccentColor, baseFont: titleFont, linkFont: titleFont, boldFont: titleBoldFont, italicFont: titleItalicFont, boldItalicFont: titleBoldItalicFont, fixedFont: titleFixedFont) + let string = stringWithAppliedEntities(item.text, entities: entities, baseColor: textColor, linkColor: item.theme.list.itemAccentColor, baseFont: titleFont, linkFont: titleFont, boldFont: titleBoldFont, italicFont: titleItalicFont, boldItalicFont: titleBoldItalicFont, fixedFont: titleFixedFont, blockQuoteFont: titleFont) let (titleLayout, titleApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) diff --git a/submodules/TelegramUI/TelegramUI/ItemListTextWithLabelItem.swift b/submodules/TelegramUI/TelegramUI/ItemListTextWithLabelItem.swift index 774113f7eb..c21de0574d 100644 --- a/submodules/TelegramUI/TelegramUI/ItemListTextWithLabelItem.swift +++ b/submodules/TelegramUI/TelegramUI/ItemListTextWithLabelItem.swift @@ -209,7 +209,7 @@ class ItemListTextWithLabelItemNode: ListViewItemNode { case .highlighted: baseColor = item.theme.list.itemHighlightedColor } - let string = stringWithAppliedEntities(item.text, entities: entities, baseColor: baseColor, linkColor: item.theme.list.itemAccentColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont) + let string = stringWithAppliedEntities(item.text, entities: entities, baseColor: baseColor, linkColor: item.theme.list.itemAccentColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont, blockQuoteFont: textFont) let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: item.multiline ? 0 : 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftOffset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let contentSize = CGSize(width: params.width, height: textLayout.size.height + 39.0) diff --git a/submodules/TelegramUI/TelegramUI/Pasteboard.swift b/submodules/TelegramUI/TelegramUI/Pasteboard.swift index 3f654f700d..7b10d9ea40 100644 --- a/submodules/TelegramUI/TelegramUI/Pasteboard.swift +++ b/submodules/TelegramUI/TelegramUI/Pasteboard.swift @@ -5,7 +5,7 @@ import TelegramCore import MobileCoreServices private func rtfStringWithAppliedEntities(_ text: String, entities: [MessageTextEntity]) -> String { - let test = stringWithAppliedEntities(text, entities: entities, baseColor: .black, linkColor: .black, baseFont: Font.regular(14.0), linkFont: Font.regular(14.0), boldFont: Font.semibold(14.0), italicFont: Font.italic(14.0), boldItalicFont: Font.semiboldItalic(14.0), fixedFont: Font.monospace(14.0), underlineLinks: false, external: true) + let test = stringWithAppliedEntities(text, entities: entities, baseColor: .black, linkColor: .black, baseFont: Font.regular(14.0), linkFont: Font.regular(14.0), boldFont: Font.semibold(14.0), italicFont: Font.italic(14.0), boldItalicFont: Font.semiboldItalic(14.0), fixedFont: Font.monospace(14.0), blockQuoteFont: Font.regular(14.0), underlineLinks: false, external: true) if let data = try? test.data(from: NSRange(location: 0, length: test.length), documentAttributes: [NSAttributedString.DocumentAttributeKey.documentType: NSAttributedString.DocumentType.rtf]) { if var rtf = String(data: data, encoding: .windowsCP1252) { diff --git a/submodules/TelegramUI/TelegramUI/StickerPackPreviewControllerNode.swift b/submodules/TelegramUI/TelegramUI/StickerPackPreviewControllerNode.swift index 03c62c1ed6..db7082b12e 100644 --- a/submodules/TelegramUI/TelegramUI/StickerPackPreviewControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/StickerPackPreviewControllerNode.swift @@ -371,7 +371,8 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol if self.currentItems.isEmpty && !updatedItems.isEmpty { let entities = generateTextEntities(info.title, enabledTypes: [.mention]) - self.contentTitleNode.attributedText = stringWithAppliedEntities(info.title, entities: entities, baseColor: self.presentationData.theme.actionSheet.primaryTextColor, linkColor: self.presentationData.theme.actionSheet.controlAccentColor, baseFont: Font.medium(20.0), linkFont: Font.medium(20.0), boldFont: Font.medium(20.0), italicFont: Font.medium(20.0), boldItalicFont: Font.medium(20.0), fixedFont: Font.medium(20.0)) + let font = Font.medium(20.0) + self.contentTitleNode.attributedText = stringWithAppliedEntities(info.title, entities: entities, baseColor: self.presentationData.theme.actionSheet.primaryTextColor, linkColor: self.presentationData.theme.actionSheet.controlAccentColor, baseFont: font, linkFont: font, boldFont: font, italicFont: font, boldItalicFont: font, fixedFont: font, blockQuoteFont: font) animateIn = true } transaction = StickerPackPreviewGridTransaction(previousList: self.currentItems, list: updatedItems, account: self.context.account, interaction: self.interaction) diff --git a/submodules/TelegramUI/TelegramUI/StringWithAppliedEntities.swift b/submodules/TelegramUI/TelegramUI/StringWithAppliedEntities.swift index fef36803ae..511b6534de 100644 --- a/submodules/TelegramUI/TelegramUI/StringWithAppliedEntities.swift +++ b/submodules/TelegramUI/TelegramUI/StringWithAppliedEntities.swift @@ -45,24 +45,25 @@ func chatInputStateStringWithAppliedEntities(_ text: String, entities: [MessageT return string } -func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], baseColor: UIColor, linkColor: UIColor, baseFont: UIFont, linkFont: UIFont, boldFont: UIFont, italicFont: UIFont, boldItalicFont: UIFont, fixedFont: UIFont, underlineLinks: Bool = true, external: Bool = false) -> NSAttributedString { +func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], baseColor: UIColor, linkColor: UIColor, baseFont: UIFont, linkFont: UIFont, boldFont: UIFont, italicFont: UIFont, boldItalicFont: UIFont, fixedFont: UIFont, blockQuoteFont: UIFont, underlineLinks: Bool = true, external: Bool = false) -> NSAttributedString { var nsString: NSString? let string = NSMutableAttributedString(string: text, attributes: [NSAttributedStringKey.font: baseFont, NSAttributedStringKey.foregroundColor: baseColor]) var skipEntity = false - let stringLength = string.length var underlineAllLinks = false if linkColor.isEqual(baseColor) { underlineAllLinks = true } var fontAttributes: [NSRange: ChatTextFontAttributes] = [:] + var rangeOffset: Int = 0 for i in 0 ..< entities.count { if skipEntity { skipEntity = false continue } + let stringLength = string.length let entity = entities[i] - var range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound) + var range = NSRange(location: entity.range.lowerBound + rangeOffset, length: entity.range.upperBound - entity.range.lowerBound) if nsString == nil { nsString = text as NSString } @@ -188,6 +189,25 @@ func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], ba string.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.BotCommand), value: nsString!.substring(with: range), range: range) case .Code, .Pre: string.addAttribute(NSAttributedStringKey.font, value: fixedFont, range: range) + case .BlockQuote: + if let fontAttribute = fontAttributes[range] { + fontAttributes[range] = fontAttribute.union(.blockQuote) + } else { + fontAttributes[range] = .blockQuote + } + + let paragraphBreak = "\n" + string.insert(NSAttributedString(string: paragraphBreak), at: range.lowerBound) + + let paragraphRange = NSRange(location: range.lowerBound + paragraphBreak.count, length: range.upperBound - range.lowerBound) + + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.headIndent = 10.0 + paragraphStyle.tabStops = [NSTextTab(textAlignment: .left, location: paragraphStyle.headIndent, options: [:])] + string.addAttribute(NSAttributedStringKey.paragraphStyle, value: paragraphStyle, range: paragraphRange) + + string.insert(NSAttributedString(string: paragraphBreak), at: paragraphRange.upperBound) + rangeOffset += paragraphBreak.count case let .Custom(type): if type == ApplicationSpecificEntityType.Timecode { string.addAttribute(NSAttributedStringKey.foregroundColor, value: linkColor, range: range) @@ -208,7 +228,9 @@ func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], ba for (range, fontAttributes) in fontAttributes { var font: UIFont? - if fontAttributes == [.bold, .italic] { + if fontAttributes.contains(.blockQuote) { + font = blockQuoteFont + } else if fontAttributes == [.bold, .italic] { font = boldItalicFont } else if fontAttributes == [.bold] { font = boldFont diff --git a/submodules/TelegramUI/TelegramUI/TermsOfServiceControllerNode.swift b/submodules/TelegramUI/TelegramUI/TermsOfServiceControllerNode.swift index 0d3c59e5fd..674458c03c 100644 --- a/submodules/TelegramUI/TelegramUI/TermsOfServiceControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/TermsOfServiceControllerNode.swift @@ -57,7 +57,7 @@ final class TermsOfServiceControllerNode: ViewControllerTracingNode { self.contentTextNode = ImmediateTextNode() self.contentTextNode.displaysAsynchronously = false self.contentTextNode.maximumNumberOfLines = 0 - self.contentTextNode.attributedText = stringWithAppliedEntities(text, entities: entities, baseColor: theme.primary, linkColor: theme.accent, baseFont: Font.regular(15.0), linkFont: Font.regular(15.0), boldFont: Font.semibold(15.0), italicFont: Font.italic(15.0), boldItalicFont: Font.semiboldItalic(15.0), fixedFont: Font.monospace(15.0)) + self.contentTextNode.attributedText = stringWithAppliedEntities(text, entities: entities, baseColor: theme.primary, linkColor: theme.accent, baseFont: Font.regular(15.0), linkFont: Font.regular(15.0), boldFont: Font.semibold(15.0), italicFont: Font.italic(15.0), boldItalicFont: Font.semiboldItalic(15.0), fixedFont: Font.monospace(15.0), blockQuoteFont: Font.regular(15.0)) self.toolbarNode = ASDisplayNode() self.toolbarSeparatorNode = ASDisplayNode() diff --git a/submodules/TelegramUI/TelegramUI/TextNode.swift b/submodules/TelegramUI/TelegramUI/TextNode.swift index 3a947ac672..de69f8ef66 100644 --- a/submodules/TelegramUI/TelegramUI/TextNode.swift +++ b/submodules/TelegramUI/TelegramUI/TextNode.swift @@ -41,4 +41,5 @@ struct TelegramTextAttributes { static let BotCommand = "TelegramBotCommand" static let Hashtag = "TelegramHashtag" static let Timecode = "TelegramTimecode" + static let BlockQuote = "TelegramBlockQuote" } diff --git a/submodules/TelegramUI/TelegramUI/UpdateInfoItem.swift b/submodules/TelegramUI/TelegramUI/UpdateInfoItem.swift index e5cf15cb24..aaab7a0921 100644 --- a/submodules/TelegramUI/TelegramUI/UpdateInfoItem.swift +++ b/submodules/TelegramUI/TelegramUI/UpdateInfoItem.swift @@ -208,7 +208,7 @@ class UpdateInfoItemNode: ListViewItemNode { let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 88.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let string = stringWithAppliedEntities(item.text, entities: item.entities, baseColor: textColor, linkColor: item.theme.list.itemAccentColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont) + let string = stringWithAppliedEntities(item.text, entities: item.entities, baseColor: textColor, linkColor: item.theme.list.itemAccentColor, baseFont: textFont, linkFont: textFont, boldFont: textBoldFont, italicFont: textItalicFont, boldItalicFont: textBoldItalicFont, fixedFont: textFixedFont, blockQuoteFont: textFont) let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 28.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let contentSize: CGSize