Skip to content

Commit 1654ac5

Browse files
committed
Make the option to use bold/shadow on headings formatters configurable for library users.
1 parent 562e972 commit 1654ac5

8 files changed

Lines changed: 137 additions & 105 deletions

File tree

Aztec.xcodeproj/project.pbxproj

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
599F25531D8BC9A1002871D6 /* TextStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 599F25311D8BC9A1002871D6 /* TextStorage.swift */; };
3737
599F25541D8BC9A1002871D6 /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 599F25321D8BC9A1002871D6 /* TextView.swift */; };
3838
74EEC71B1FAB6EC900B989E3 /* UIImage+Resize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74EEC71A1FAB6EC900B989E3 /* UIImage+Resize.swift */; };
39-
914CD9E72200B2D200DDD5C9 /* BoldFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 914CD9E62200B2D200DDD5C9 /* BoldFormatter.swift */; };
39+
914CD9E72200B2D200DDD5C9 /* BoldWithShadowForHeadingFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 914CD9E62200B2D200DDD5C9 /* BoldWithShadowForHeadingFormatter.swift */; };
4040
917E22132201D2A5008AC826 /* BoldFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 917E22122201D2A5008AC826 /* BoldFormatterTests.swift */; };
4141
B52220D31F86A05400D7E092 /* TextViewStubDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52220D21F86A05400D7E092 /* TextViewStubDelegate.swift */; };
4242
B524228D1F30C039002E7C6C /* HTMLDiv.swift in Sources */ = {isa = PBXBuildFile; fileRef = B524228C1F30C039002E7C6C /* HTMLDiv.swift */; };
@@ -247,6 +247,8 @@
247247
FFD3C1712344DB4E00AE8DA0 /* ForegroundColorCSSAttributeMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD3C1702344DB4E00AE8DA0 /* ForegroundColorCSSAttributeMatcher.swift */; };
248248
FFD3C1732344DCA900AE8DA0 /* ForegroundColorElementAttributeConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD3C1722344DCA900AE8DA0 /* ForegroundColorElementAttributeConverter.swift */; };
249249
FFD436981E3180A500A0E26F /* FontFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD436971E3180A500A0E26F /* FontFormatterTests.swift */; };
250+
FFFA53E523C6277700829A43 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFFA53E423C6277700829A43 /* Configuration.swift */; };
251+
FFFA53E723C628E700829A43 /* BoldFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFFA53E623C628E700829A43 /* BoldFormatter.swift */; };
250252
FFFEC7DB1EA7698900F4210F /* VideoAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFFEC7DA1EA7698900F4210F /* VideoAttachment.swift */; };
251253
/* End PBXBuildFile section */
252254

@@ -312,7 +314,7 @@
312314
59FEA06B1D8BDFA700D138DF /* InAttributeConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InAttributeConverterTests.swift; sourceTree = "<group>"; };
313315
59FEA06D1D8BDFA700D138DF /* InNodeConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InNodeConverterTests.swift; sourceTree = "<group>"; };
314316
74EEC71A1FAB6EC900B989E3 /* UIImage+Resize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Resize.swift"; sourceTree = "<group>"; };
315-
914CD9E62200B2D200DDD5C9 /* BoldFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoldFormatter.swift; sourceTree = "<group>"; };
317+
914CD9E62200B2D200DDD5C9 /* BoldWithShadowForHeadingFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoldWithShadowForHeadingFormatter.swift; sourceTree = "<group>"; };
316318
917E22122201D2A5008AC826 /* BoldFormatterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoldFormatterTests.swift; sourceTree = "<group>"; };
317319
B52220D21F86A05400D7E092 /* TextViewStubDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewStubDelegate.swift; sourceTree = "<group>"; };
318320
B524228C1F30C039002E7C6C /* HTMLDiv.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLDiv.swift; sourceTree = "<group>"; };
@@ -531,6 +533,8 @@
531533
FFD3C1702344DB4E00AE8DA0 /* ForegroundColorCSSAttributeMatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForegroundColorCSSAttributeMatcher.swift; sourceTree = "<group>"; };
532534
FFD3C1722344DCA900AE8DA0 /* ForegroundColorElementAttributeConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForegroundColorElementAttributeConverter.swift; sourceTree = "<group>"; };
533535
FFD436971E3180A500A0E26F /* FontFormatterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FontFormatterTests.swift; sourceTree = "<group>"; };
536+
FFFA53E423C6277700829A43 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
537+
FFFA53E623C628E700829A43 /* BoldFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoldFormatter.swift; sourceTree = "<group>"; };
534538
FFFEC7DA1EA7698900F4210F /* VideoAttachment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoAttachment.swift; sourceTree = "<group>"; };
535539
/* End PBXFileReference section */
536540

@@ -826,6 +830,7 @@
826830
F9982CF521877663001E606B /* TextViewPasteboardDelegate.swift */,
827831
FF7EAEC3234D253B007A26E0 /* FontProvider.swift */,
828832
FF1F56A023955EF600E93A30 /* ColorProvider.swift */,
833+
FFFA53E423C6277700829A43 /* Configuration.swift */,
829834
);
830835
path = TextKit;
831836
sourceTree = "<group>";
@@ -995,14 +1000,15 @@
9951000
isa = PBXGroup;
9961001
children = (
9971002
F12F58561EF20394008AE298 /* BlockquoteFormatter.swift */,
998-
914CD9E62200B2D200DDD5C9 /* BoldFormatter.swift */,
1003+
914CD9E62200B2D200DDD5C9 /* BoldWithShadowForHeadingFormatter.swift */,
9991004
F1BDDDE420603C18000714E1 /* FigcaptionFormatter.swift */,
10001005
F1BDDDE62060403B000714E1 /* FigureFormatter.swift */,
10011006
F12F58571EF20394008AE298 /* ColorFormatter.swift */,
10021007
F12F58591EF20394008AE298 /* HeaderFormatter.swift */,
10031008
B524228E1F30C098002E7C6C /* HTMLDivFormatter.swift */,
10041009
F12F585B1EF20394008AE298 /* HTMLParagraphFormatter.swift */,
10051010
F18986E41EF2043E0060EDBA /* ItalicFormatter.swift */,
1011+
FFFA53E623C628E700829A43 /* BoldFormatter.swift */,
10061012
F12F585D1EF20394008AE298 /* LinkFormatter.swift */,
10071013
F12F585E1EF20394008AE298 /* PreFormatter.swift */,
10081014
FF437B6520D7CB83000D9666 /* LiFormatter.swift */,
@@ -1561,7 +1567,7 @@
15611567
599F254B1D8BC9A1002871D6 /* String+RangeConversion.swift in Sources */,
15621568
FF1F56A123955EF600E93A30 /* ColorProvider.swift in Sources */,
15631569
599F25481D8BC9A1002871D6 /* Metrics.swift in Sources */,
1564-
914CD9E72200B2D200DDD5C9 /* BoldFormatter.swift in Sources */,
1570+
914CD9E72200B2D200DDD5C9 /* BoldWithShadowForHeadingFormatter.swift in Sources */,
15651571
F1BDDDE72060403B000714E1 /* FigureFormatter.swift in Sources */,
15661572
B59C9F9F1DF74BB80073B1D6 /* UIFont+Traits.swift in Sources */,
15671573
F10BE61A1EA7AE9D002E4625 /* NSAttributedString+ReplaceOcurrences.swift in Sources */,
@@ -1589,6 +1595,7 @@
15891595
F18A1EBA21C0828700F1AA9E /* PluginInputCustomizer.swift in Sources */,
15901596
F15BA60F21515C0F00424120 /* UnderlineStringAttributeConverter.swift in Sources */,
15911597
F18B81ED1EA560B700885F43 /* StringUTF16+RangeConversion.swift in Sources */,
1598+
FFFA53E723C628E700829A43 /* BoldFormatter.swift in Sources */,
15921599
F16A2AD520CC437E00BF3A0A /* LineAttachmentToElementConverter.swift in Sources */,
15931600
B574F4A41FB0CF3B0048F355 /* NSAttributedStringKey+Conversion.swift in Sources */,
15941601
F12F58631EF20394008AE298 /* AttributeFormatter.swift in Sources */,
@@ -1668,6 +1675,7 @@
16681675
F13E8AD820B71BAA007C9F8A /* PluginManager.swift in Sources */,
16691676
FF48CAC8234DD5D3007FFC79 /* NSMutableAttributedString+ReplaceAttributes.swift in Sources */,
16701677
F12F586E1EF20394008AE298 /* LinkFormatter.swift in Sources */,
1678+
FFFA53E523C6277700829A43 /* Configuration.swift in Sources */,
16711679
F1584796203C9B4C00EE05A1 /* NSMutableAttributedString+ParagraphProperty.swift in Sources */,
16721680
F15BA6092151501300424120 /* BoldStringAttributeConverter.swift in Sources */,
16731681
F1AFD65820B45E5700CB0279 /* CommentAttachmentRenderer.swift in Sources */,

Aztec/Classes/Converters/ElementsToAttributedString/Implementations/GenericElementConverter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class GenericElementConverter: ElementConverter {
1616
// MARK: - Built-in formatter instances
1717

1818
lazy var blockquoteFormatter = BlockquoteFormatter(increaseDepth: true)
19-
lazy var boldFormatter = BoldFormatter()
19+
lazy var boldFormatter = Configuration.defaultBoldFormatter
2020
lazy var divFormatter = HTMLDivFormatter()
2121
lazy var h1Formatter = HeaderFormatter(headerLevel: .h1)
2222
lazy var h2Formatter = HeaderFormatter(headerLevel: .h2)
Lines changed: 3 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,8 @@
11
import UIKit
22

3-
/// BoldFormatter uses 2 different mechanisms to apply the bold effect:
4-
/// 1. Headings: Applies shadow effect because headings are already bold by default
5-
/// 2. Other text: Applies bold font trait.
6-
class BoldFormatter: AttributeFormatter {
7-
8-
private enum Shadow {
9-
enum DefaultOffset {
10-
static let width: CGFloat = 0.65
11-
static let height: CGFloat = 0.0
12-
}
13-
14-
static let blurRadiusNoBlur: CGFloat = 0.0
15-
static let defaultColor: UIColor = .black
16-
17-
// Creates a no blur NSShadow instance with given offset values
18-
static func shadow(width: CGFloat = DefaultOffset.width,
19-
height: CGFloat = DefaultOffset.height,
20-
color: UIColor) -> NSShadow {
21-
let shadow = NSShadow()
22-
shadow.shadowBlurRadius = Shadow.blurRadiusNoBlur
23-
shadow.shadowOffset = CGSize(width: width, height: height)
24-
shadow.shadowColor = color
25-
return shadow
26-
}
27-
28-
// Calculate Shadow offset due to font size
29-
static func offset(with fontSize: CGFloat) -> CGFloat {
30-
if fontSize >= 22 {
31-
return 0.72
32-
} else if fontSize >= 20 && fontSize < 22 {
33-
return 0.68
34-
} else if fontSize >= 18 && fontSize < 20 {
35-
return 0.65
36-
} else if fontSize >= 16 && fontSize < 18 {
37-
return 0.62
38-
} else {
39-
return 0.6
40-
}
41-
}
42-
}
43-
44-
private let htmlRepresentationKey: NSAttributedString.Key = .boldHtmlRepresentation
45-
private let boldFontFormatter = FontFormatter(traits: .traitBold, htmlRepresentationKey: .boldHtmlRepresentation)
46-
47-
func apply(to attributes: [NSAttributedString.Key: Any], andStore representation: HTMLRepresentation?) -> [NSAttributedString.Key: Any] {
48-
guard attributes[.headingRepresentation] != nil else {
49-
return boldFontFormatter.apply(to: attributes, andStore: representation)
50-
}
51-
52-
var resultingAttributes = attributes
53-
54-
if let font = resultingAttributes[.font] as? UIFont {
55-
//Calculate letter spacing and shadow offset with respect to the font size
56-
let shadowOffsetWidth = Shadow.offset(with: font.pointSize)
57-
resultingAttributes[.shadow] = Shadow.shadow(width: shadowOffsetWidth, color: shadowColor(from: attributes))
58-
resultingAttributes[.kern] = shadowOffsetWidth
59-
} else {
60-
resultingAttributes[.shadow] = Shadow.shadow(color: shadowColor(from: attributes))
61-
resultingAttributes[.kern] = Shadow.DefaultOffset.width
62-
}
63-
64-
resultingAttributes[.boldHtmlRepresentation] = representation
65-
66-
return resultingAttributes
67-
}
68-
69-
func remove(from attributes: [NSAttributedString.Key: Any]) -> [NSAttributedString.Key: Any] {
70-
guard attributes[.headingRepresentation] != nil else {
71-
return boldFontFormatter.remove(from: attributes)
72-
}
73-
74-
var resultingAttributes = attributes
75-
76-
resultingAttributes.removeValue(forKey: .shadow)
77-
resultingAttributes.removeValue(forKey: .kern)
78-
resultingAttributes.removeValue(forKey: .boldHtmlRepresentation)
79-
80-
return resultingAttributes
81-
}
82-
83-
func present(in attributes: [NSAttributedString.Key: Any]) -> Bool {
84-
guard attributes[.headingRepresentation] != nil else {
85-
return boldFontFormatter.present(in: attributes)
86-
}
3+
class BoldFormatter: FontFormatter {
874

88-
return attributes[.shadow] != nil
89-
}
90-
91-
func applicationRange(for range: NSRange, in text: NSAttributedString) -> NSRange {
92-
return range
93-
}
94-
95-
private func shadowColor(from attributes: [NSAttributedString.Key: Any]) -> UIColor {
96-
if let color = attributes[.foregroundColor] as? UIColor {
97-
return color
98-
}
99-
return Shadow.defaultColor
5+
init() {
6+
super.init(traits: .traitBold, htmlRepresentationKey: .boldHtmlRepresentation)
1007
}
1018
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import UIKit
2+
3+
/// BoldFormatter uses 2 different mechanisms to apply the bold effect:
4+
/// 1. Headings: Applies shadow effect because headings are already bold by default
5+
/// 2. Other text: Applies bold font trait.
6+
class BoldWithShadowForHeadingFormatter: AttributeFormatter {
7+
8+
private enum Shadow {
9+
enum DefaultOffset {
10+
static let width: CGFloat = 0.65
11+
static let height: CGFloat = 0.0
12+
}
13+
14+
static let blurRadiusNoBlur: CGFloat = 0.0
15+
static let defaultColor: UIColor = .black
16+
17+
// Creates a no blur NSShadow instance with given offset values
18+
static func shadow(width: CGFloat = DefaultOffset.width,
19+
height: CGFloat = DefaultOffset.height,
20+
color: UIColor) -> NSShadow {
21+
let shadow = NSShadow()
22+
shadow.shadowBlurRadius = Shadow.blurRadiusNoBlur
23+
shadow.shadowOffset = CGSize(width: width, height: height)
24+
shadow.shadowColor = color
25+
return shadow
26+
}
27+
28+
// Calculate Shadow offset due to font size
29+
static func offset(with fontSize: CGFloat) -> CGFloat {
30+
if fontSize >= 22 {
31+
return 0.72
32+
} else if fontSize >= 20 && fontSize < 22 {
33+
return 0.68
34+
} else if fontSize >= 18 && fontSize < 20 {
35+
return 0.65
36+
} else if fontSize >= 16 && fontSize < 18 {
37+
return 0.62
38+
} else {
39+
return 0.6
40+
}
41+
}
42+
}
43+
44+
private let htmlRepresentationKey: NSAttributedString.Key = .boldHtmlRepresentation
45+
private let boldFontFormatter = FontFormatter(traits: .traitBold, htmlRepresentationKey: .boldHtmlRepresentation)
46+
47+
func apply(to attributes: [NSAttributedString.Key: Any], andStore representation: HTMLRepresentation?) -> [NSAttributedString.Key: Any] {
48+
guard attributes[.headingRepresentation] != nil else {
49+
return boldFontFormatter.apply(to: attributes, andStore: representation)
50+
}
51+
52+
var resultingAttributes = attributes
53+
54+
if let font = resultingAttributes[.font] as? UIFont {
55+
//Calculate letter spacing and shadow offset with respect to the font size
56+
let shadowOffsetWidth = Shadow.offset(with: font.pointSize)
57+
resultingAttributes[.shadow] = Shadow.shadow(width: shadowOffsetWidth, color: shadowColor(from: attributes))
58+
resultingAttributes[.kern] = shadowOffsetWidth
59+
} else {
60+
resultingAttributes[.shadow] = Shadow.shadow(color: shadowColor(from: attributes))
61+
resultingAttributes[.kern] = Shadow.DefaultOffset.width
62+
}
63+
64+
resultingAttributes[.boldHtmlRepresentation] = representation
65+
66+
return resultingAttributes
67+
}
68+
69+
func remove(from attributes: [NSAttributedString.Key: Any]) -> [NSAttributedString.Key: Any] {
70+
guard attributes[.headingRepresentation] != nil else {
71+
return boldFontFormatter.remove(from: attributes)
72+
}
73+
74+
var resultingAttributes = attributes
75+
76+
resultingAttributes.removeValue(forKey: .shadow)
77+
resultingAttributes.removeValue(forKey: .kern)
78+
resultingAttributes.removeValue(forKey: .boldHtmlRepresentation)
79+
80+
return resultingAttributes
81+
}
82+
83+
func present(in attributes: [NSAttributedString.Key: Any]) -> Bool {
84+
guard attributes[.headingRepresentation] != nil else {
85+
return boldFontFormatter.present(in: attributes)
86+
}
87+
88+
return attributes[.shadow] != nil
89+
}
90+
91+
func applicationRange(for range: NSRange, in text: NSAttributedString) -> NSRange {
92+
return range
93+
}
94+
95+
private func shadowColor(from attributes: [NSAttributedString.Key: Any]) -> UIColor {
96+
if let color = attributes[.foregroundColor] as? UIColor {
97+
return color
98+
}
99+
return Shadow.defaultColor
100+
}
101+
}

Aztec/Classes/Formatters/Implementations/HeaderFormatter.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ open class HeaderFormatter: ParagraphAttributeFormatter {
4242

4343
let newDescriptor = font.fontDescriptor.addingAttributes([.size: targetFontSize])
4444
var newFont = UIFont(descriptor: newDescriptor, size: targetFontSize)
45-
newFont = newFont.modifyTraits(.traitBold, enable: true)
45+
if Configuration.headersWithBoldTrait {
46+
newFont = newFont.modifyTraits(.traitBold, enable: true)
47+
}
4648

4749
resultingAttributes[.paragraphStyle] = newParagraphStyle
4850
resultingAttributes[.font] = newFont
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import Foundation
2+
3+
public final class Configuration {
4+
5+
public static var headersWithBoldTrait = false
6+
7+
static var defaultBoldFormatter: AttributeFormatter = {
8+
if headersWithBoldTrait {
9+
return BoldWithShadowForHeadingFormatter()
10+
} else {
11+
return BoldFormatter()
12+
}
13+
} ()
14+
}

Aztec/Classes/TextKit/TextView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -841,7 +841,7 @@ open class TextView: UITextView {
841841
// MARK: - Getting format identifiers
842842

843843
private static let formatterMap: [FormattingIdentifier: AttributeFormatter] = [
844-
.bold: BoldFormatter(),
844+
.bold: Configuration.defaultBoldFormatter,
845845
.italic: ItalicFormatter(),
846846
.underline: SpanUnderlineFormatter(),
847847
.strikethrough: StrikethroughFormatter(),
@@ -1011,7 +1011,7 @@ open class TextView: UITextView {
10111011
/// - Parameter range: The NSRange to edit.
10121012
///
10131013
open func toggleBold(range: NSRange) {
1014-
let formatter = BoldFormatter()
1014+
let formatter = Configuration.defaultBoldFormatter
10151015
toggle(formatter: formatter, atRange: range)
10161016
}
10171017

AztecTests/Formatters/BoldFormatterTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import XCTest
33

44
class BoldFormatterTests: XCTestCase {
55

6-
private let boldFormatter = BoldFormatter()
6+
private let boldFormatter = BoldWithShadowForHeadingFormatter()
77

88
func testApplyAttributesOnHeading() {
99
var attributes: [NSAttributedString.Key : Any] = [.font: UIFont.systemFont(ofSize: UIFont.systemFontSize)]

0 commit comments

Comments
 (0)