Skip to content

Commit b3a9080

Browse files
Merge pull request #1262 from wordpress-mobile/issue/1185_headers_without_bold
Allow to use headers without bold/shadow traits
2 parents 562e972 + d4cd119 commit b3a9080

12 files changed

Lines changed: 185 additions & 117 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)

Aztec/Classes/Converters/StringAttributesToAttributes/Implementations/BoldStringAttributeConverter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ open class BoldStringAttributeConverter: StringAttributeConverter {
3232
// MARK: - Style Detection
3333

3434
func shouldEnableBoldElement(for attributes: [NSAttributedString.Key: Any]) -> Bool {
35-
if isHeading(for: attributes) {
35+
if isHeading(for: attributes) && Configuration.headersWithBoldTrait {
3636
// If this is a heading then shadow represents bold elements since
3737
// headings are bold by default
3838
return hasShadowTrait(for: attributes)

Aztec/Classes/Formatters/Base/ParagraphAttributeFormatter.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ extension ParagraphAttributeFormatter {
3737
let rangeToApply = applicationRange(for: range, in: text)
3838

3939
text.replaceOcurrences(of: String(.paragraphSeparator), with: String(.lineFeed), within: rangeToApply)
40-
41-
text.enumerateAttributes(in: rangeToApply, options: []) { (attributes, range, stop) in
42-
let currentAttributes = text.attributes(at: range.location, effectiveRange: nil)
40+
// We copy the string that gets update to avoid that removal of paragraph attributes, like NSParagraphStyle,
41+
// on a certain range affect all the paragraph and have side effects on the remaining removal
42+
let updatedText = text.mutableCopy() as! NSMutableAttributedString
43+
text.enumerateAttributes(in: rangeToApply, options: []) { (currentAttributes, range, stop) in
4344
let attributes = remove(from: currentAttributes)
44-
45-
text.setAttributes(attributes, range: range)
45+
updatedText.setAttributes(attributes, range: range)
4646
}
47-
47+
text.replaceCharacters(in: rangeToApply, with: updatedText.attributedSubstring(from: rangeToApply))
4848
return rangeToApply
4949
}
5050
}
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+
}

0 commit comments

Comments
 (0)