UILabel+Markup.m 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2016-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. #import "UILabel+Markup.h"
  21. #import "TTTAttributedLabel.h"
  22. #import "UIFont+Traits.h"
  23. @implementation UILabel (Markup)
  24. typedef enum : int {
  25. StyleTypeBold,
  26. StyleTypeItalic,
  27. StyleTypeStrikethrough,
  28. } StyleType;
  29. - (void)applyMarkup {
  30. NSAttributedString *inputString;
  31. if (self.text) {
  32. inputString = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText];
  33. } else {
  34. inputString = [[NSMutableAttributedString alloc] initWithString:self.text];
  35. }
  36. NSAttributedString *string = [self applyMarkupFor:inputString];
  37. [self setAttributedText:string];
  38. }
  39. - (NSAttributedString *)applyMarkupFor:(NSAttributedString *)text {
  40. NSMutableAttributedString *mutableString = [[NSMutableAttributedString alloc] initWithAttributedString:text];
  41. if (text.length > 0) {
  42. NSRange range = NSMakeRange(0, 1);
  43. NSDictionary *attributes = [mutableString attributesAtIndex:0 effectiveRange:&range];
  44. self.font = attributes[NSFontAttributeName];
  45. }
  46. [self handleItalicTagsIn:mutableString];
  47. [self handleBoldTagsIn:mutableString];
  48. [self handleStrikethroughTagsIn:mutableString];
  49. return mutableString;
  50. }
  51. - (void)applyAttributes:(NSDictionary *)attributes on:(NSMutableAttributedString *)attributedString matching:(NSRegularExpression *)regex type:(StyleType)type {
  52. NSArray *matches = [regex matchesInString:attributedString.string
  53. options:0
  54. range:NSMakeRange(0, [attributedString.string length])];
  55. NSArray *sortedMatches = [matches sortedArrayWithOptions:0 usingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
  56. NSUInteger loc1 = ((NSTextCheckingResult*)obj1).range.location;
  57. NSUInteger loc2 = ((NSTextCheckingResult*)obj2).range.location;
  58. if (loc1 > loc2) {
  59. return NSOrderedAscending;
  60. } else if (loc1 < loc2) {
  61. return NSOrderedDescending;
  62. } else {
  63. return NSOrderedSame;
  64. }
  65. }];
  66. for (NSTextCheckingResult *match in sortedMatches) {
  67. NSRange matchRange = [match range];
  68. NSMutableArray *specialFormat = [NSMutableArray new];
  69. [attributedString enumerateAttributesInRange:matchRange options:NSAttributedStringEnumerationReverse usingBlock:
  70. ^(NSDictionary *attributes, NSRange range, BOOL *stop) {
  71. NSMutableDictionary *mutableAttributes = [NSMutableDictionary dictionaryWithDictionary:attributes];
  72. UIFont *currentFont = [mutableAttributes objectForKey: NSFontAttributeName];
  73. if (currentFont.isItalic) {
  74. [specialFormat addObject:[NSValue valueWithRange:range]];
  75. }
  76. }];
  77. [attributedString addAttributes:attributes range:matchRange];
  78. if (specialFormat.count > 0) {
  79. for (int i = 0; i < specialFormat.count; i++) {
  80. NSRange r = [specialFormat[i] rangeValue];
  81. NSDictionary *newAttributes = @{NSFontAttributeName: [self fontWithTraits:UIFontDescriptorTraitBold|UIFontDescriptorTraitItalic]};
  82. [attributedString addAttributes:newAttributes range:r];
  83. }
  84. }
  85. [attributedString deleteCharactersInRange:NSMakeRange(matchRange.location, 1)];
  86. [attributedString deleteCharactersInRange:NSMakeRange(matchRange.location + matchRange.length - 2, 1)];
  87. }
  88. }
  89. - (void)handleStrikethroughTagsIn:(NSMutableAttributedString *)attributedString {
  90. NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\B~[^\\r\\n]+?~\\B" options:0 error:nil];
  91. NSDictionary *attributes = @{NSBaselineOffsetAttributeName: @0, NSStrikethroughStyleAttributeName: [NSNumber numberWithInt:NSUnderlinePatternSolid | NSUnderlineStyleSingle], kTTTStrikeOutAttributeName: [NSNumber numberWithInt:NSUnderlinePatternSolid | NSUnderlineStyleSingle]};
  92. [self applyAttributes:attributes on:attributedString matching:regex type:StyleTypeStrikethrough];
  93. }
  94. - (void)handleBoldTagsIn:(NSMutableAttributedString *)attributedString {
  95. NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\B\\*[^\\r\\n]+?\\*\\B" options:0 error:nil];
  96. NSDictionary *attributes = @{NSFontAttributeName: [self fontWithTraits:UIFontDescriptorTraitBold]};
  97. [self applyAttributes:attributes on:attributedString matching:regex type:StyleTypeBold];
  98. }
  99. - (void)handleItalicTagsIn:(NSMutableAttributedString *)attributedString {
  100. NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\b_[^\\r\\n]+?_\\b" options:0 error:nil];
  101. NSDictionary *attributes = @{NSFontAttributeName: [self fontWithTraits:UIFontDescriptorTraitItalic]};
  102. [self applyAttributes:attributes on:attributedString matching:regex type:StyleTypeItalic];
  103. }
  104. - (UIFont*)fontWithTraits:(UIFontDescriptorSymbolicTraits)traits {
  105. UIFontDescriptor *fontDescriptor = self.font.fontDescriptor;
  106. UIFontDescriptor *traitedFontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:traits];
  107. return [UIFont fontWithDescriptor:traitedFontDescriptor size:self.font.pointSize];
  108. }
  109. @end