diff --git a/Marker/Marker.xcodeproj/project.pbxproj b/Marker/Marker.xcodeproj/project.pbxproj index ef8579d..fca43a2 100644 --- a/Marker/Marker.xcodeproj/project.pbxproj +++ b/Marker/Marker.xcodeproj/project.pbxproj @@ -72,6 +72,21 @@ 27C4E66B1ED60C7700DDE387 /* UILabelExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27C4E6691ED60C7700DDE387 /* UILabelExtension.swift */; }; 480C7A5F1F15832A0094E4EA /* ParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271C85C81ED4E37700F8BBBB /* ParserTests.swift */; }; 480C7A601F15832A0094E4EA /* ParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271C85C81ED4E37700F8BBBB /* ParserTests.swift */; }; + 48F232751F1FB7B600E86D5D /* TextTransform+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48F232741F1FB7B600E86D5D /* TextTransform+Extensions.swift */; }; + 48F232761F1FB7B600E86D5D /* TextTransform+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48F232741F1FB7B600E86D5D /* TextTransform+Extensions.swift */; }; + 48F232771F1FB7B600E86D5D /* TextTransform+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48F232741F1FB7B600E86D5D /* TextTransform+Extensions.swift */; }; + 48F2327C1F1FBA4400E86D5D /* TextTransformEquatableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48F232781F1FBA2200E86D5D /* TextTransformEquatableTests.swift */; }; + 48F2327D1F1FBA4400E86D5D /* TextTransformEquatableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48F232781F1FBA2200E86D5D /* TextTransformEquatableTests.swift */; }; + 48F2327E1F1FBA4500E86D5D /* TextTransformEquatableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48F232781F1FBA2200E86D5D /* TextTransformEquatableTests.swift */; }; + 48F232801F1FBA7800E86D5D /* TextStyle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48F2327F1F1FBA7800E86D5D /* TextStyle+Extensions.swift */; }; + 48F232811F1FBA7800E86D5D /* TextStyle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48F2327F1F1FBA7800E86D5D /* TextStyle+Extensions.swift */; }; + 48F232821F1FBA7800E86D5D /* TextStyle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48F2327F1F1FBA7800E86D5D /* TextStyle+Extensions.swift */; }; + 48F232841F1FBD1300E86D5D /* TextStyleEquatableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48F232831F1FBD1300E86D5D /* TextStyleEquatableTests.swift */; }; + 48F232851F1FBD1300E86D5D /* TextStyleEquatableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48F232831F1FBD1300E86D5D /* TextStyleEquatableTests.swift */; }; + 48F232861F1FBD1300E86D5D /* TextStyleEquatableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48F232831F1FBD1300E86D5D /* TextStyleEquatableTests.swift */; }; + 48F232881F1FC2F900E86D5D /* TextStyleFactoryFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48F232871F1FC2F900E86D5D /* TextStyleFactoryFunctionTests.swift */; }; + 48F232891F1FC2F900E86D5D /* TextStyleFactoryFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48F232871F1FC2F900E86D5D /* TextStyleFactoryFunctionTests.swift */; }; + 48F2328A1F1FC2F900E86D5D /* TextStyleFactoryFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48F232871F1FC2F900E86D5D /* TextStyleFactoryFunctionTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -132,6 +147,11 @@ 27C4E6601ED6080B00DDE387 /* UITextViewExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UITextViewExtension.swift; path = TextView/UITextViewExtension.swift; sourceTree = ""; }; 27C4E6661ED6082800DDE387 /* NSTextViewExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NSTextViewExtension.swift; path = TextView/NSTextViewExtension.swift; sourceTree = ""; }; 27C4E6691ED60C7700DDE387 /* UILabelExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UILabelExtension.swift; path = Label/UILabelExtension.swift; sourceTree = ""; }; + 48F232741F1FB7B600E86D5D /* TextTransform+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TextTransform+Extensions.swift"; sourceTree = ""; }; + 48F232781F1FBA2200E86D5D /* TextTransformEquatableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextTransformEquatableTests.swift; sourceTree = ""; }; + 48F2327F1F1FBA7800E86D5D /* TextStyle+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TextStyle+Extensions.swift"; sourceTree = ""; }; + 48F232831F1FBD1300E86D5D /* TextStyleEquatableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextStyleEquatableTests.swift; sourceTree = ""; }; + 48F232871F1FC2F900E86D5D /* TextStyleFactoryFunctionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextStyleFactoryFunctionTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -216,7 +236,9 @@ 271C85AB1ED4E2B700F8BBBB /* Parser */, 271C85B31ED4E2B700F8BBBB /* String+Extensions.swift */, 271C85B41ED4E2B700F8BBBB /* TextStyle.swift */, + 48F2327F1F1FBA7800E86D5D /* TextStyle+Extensions.swift */, 271C85B51ED4E2B700F8BBBB /* TextTransform.swift */, + 48F232741F1FB7B600E86D5D /* TextTransform+Extensions.swift */, 27C4E61E1ED5EE3000DDE387 /* Utility */, ); path = Marker; @@ -226,6 +248,9 @@ isa = PBXGroup; children = ( 271C85C81ED4E37700F8BBBB /* ParserTests.swift */, + 48F232831F1FBD1300E86D5D /* TextStyleEquatableTests.swift */, + 48F232871F1FC2F900E86D5D /* TextStyleFactoryFunctionTests.swift */, + 48F232781F1FBA2200E86D5D /* TextTransformEquatableTests.swift */, 271C859A1ED4E2A000F8BBBB /* Info.plist */, 271C85C71ED4E37600F8BBBB /* MarkerTests-Bridging-Header.h */, ); @@ -574,6 +599,7 @@ buildActionMask = 2147483647; files = ( 271C85C51ED4E2B700F8BBBB /* TextStyle.swift in Sources */, + 48F232751F1FB7B600E86D5D /* TextTransform+Extensions.swift in Sources */, 271C85C01ED4E2B700F8BBBB /* MarkdownParser.swift in Sources */, 271C85BB1ED4E2B700F8BBBB /* Markup.swift in Sources */, 27C4E66A1ED60C7700DDE387 /* UILabelExtension.swift in Sources */, @@ -581,6 +607,7 @@ 271C85BD1ED4E2B700F8BBBB /* Element.swift in Sources */, 271C85BF1ED4E2B700F8BBBB /* MarkdownElement.swift in Sources */, 271C85C31ED4E2B700F8BBBB /* TagParser.swift in Sources */, + 48F232801F1FBA7800E86D5D /* TextStyle+Extensions.swift in Sources */, 27C4E64F1ED6064400DDE387 /* UIButtonExtension.swift in Sources */, 271C85C11ED4E2B700F8BBBB /* Symbol.swift in Sources */, 271C85BE1ED4E2B700F8BBBB /* ElementParser.swift in Sources */, @@ -599,6 +626,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 48F2327C1F1FBA4400E86D5D /* TextTransformEquatableTests.swift in Sources */, + 48F232881F1FC2F900E86D5D /* TextStyleFactoryFunctionTests.swift in Sources */, + 48F232841F1FBD1300E86D5D /* TextStyleEquatableTests.swift in Sources */, 271C85C91ED4E37700F8BBBB /* ParserTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -608,6 +638,7 @@ buildActionMask = 2147483647; files = ( 27C4E6091ED5ED6400DDE387 /* TextStyle.swift in Sources */, + 48F232761F1FB7B600E86D5D /* TextTransform+Extensions.swift in Sources */, 27C4E6041ED5ED6400DDE387 /* MarkdownParser.swift in Sources */, 27C4E5FF1ED5ED6400DDE387 /* Markup.swift in Sources */, 27C4E66B1ED60C7700DDE387 /* UILabelExtension.swift in Sources */, @@ -615,6 +646,7 @@ 27C4E6011ED5ED6400DDE387 /* Element.swift in Sources */, 27C4E6031ED5ED6400DDE387 /* MarkdownElement.swift in Sources */, 27C4E6071ED5ED6400DDE387 /* TagParser.swift in Sources */, + 48F232811F1FBA7800E86D5D /* TextStyle+Extensions.swift in Sources */, 27C4E6501ED6064400DDE387 /* UIButtonExtension.swift in Sources */, 27C4E6051ED5ED6400DDE387 /* Symbol.swift in Sources */, 27C4E6021ED5ED6400DDE387 /* ElementParser.swift in Sources */, @@ -633,6 +665,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 48F2327D1F1FBA4400E86D5D /* TextTransformEquatableTests.swift in Sources */, + 48F232891F1FC2F900E86D5D /* TextStyleFactoryFunctionTests.swift in Sources */, + 48F232851F1FBD1300E86D5D /* TextStyleEquatableTests.swift in Sources */, 480C7A601F15832A0094E4EA /* ParserTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -643,9 +678,11 @@ files = ( 27C4E61A1ED5ED6500DDE387 /* TextStyle.swift in Sources */, 27C4E64E1ED6064400DDE387 /* NSButtonExtension.swift in Sources */, + 48F232821F1FBA7800E86D5D /* TextStyle+Extensions.swift in Sources */, 27C4E6151ED5ED6500DDE387 /* MarkdownParser.swift in Sources */, 27C4E65A1ED6064400DDE387 /* NSTextFieldExtension.swift in Sources */, 27C4E6541ED6064400DDE387 /* Color.swift in Sources */, + 48F232771F1FB7B600E86D5D /* TextTransform+Extensions.swift in Sources */, 27C4E6101ED5ED6500DDE387 /* Markup.swift in Sources */, 27C4E6121ED5ED6500DDE387 /* Element.swift in Sources */, 27C4E6141ED5ED6500DDE387 /* MarkdownElement.swift in Sources */, @@ -666,6 +703,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 48F2327E1F1FBA4500E86D5D /* TextTransformEquatableTests.swift in Sources */, + 48F2328A1F1FC2F900E86D5D /* TextStyleFactoryFunctionTests.swift in Sources */, + 48F232861F1FBD1300E86D5D /* TextStyleEquatableTests.swift in Sources */, 480C7A5F1F15832A0094E4EA /* ParserTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Marker/Marker/Marker.swift b/Marker/Marker/Marker.swift index 4d43d65..6195706 100644 --- a/Marker/Marker/Marker.swift +++ b/Marker/Marker/Marker.swift @@ -65,6 +65,7 @@ public func parsedMarkdownString(from markdownText: String, elements.forEach { (element) in var font: Font? = nil var strikethroughStyle: NSUnderlineStyle? = nil + var underlineStyle: NSUnderlineStyle? = nil switch element { case .em(_): @@ -73,6 +74,8 @@ public func parsedMarkdownString(from markdownText: String, font = textStyle.strongFont case .strikethrough(_): strikethroughStyle = textStyle.strikethroughStyle + case .underline(_): + underlineStyle = textStyle.underlineStyle } if let font = font { @@ -88,6 +91,14 @@ public func parsedMarkdownString(from markdownText: String, range: parsedString.range(from: element.range)) } } + + if let underlineStyle = underlineStyle { + attributedString.addAttributes([NSUnderlineStyleAttributeName: underlineStyle.rawValue], range: parsedString.range(from: element.range)) + + if let underlineColor = textStyle.underlineColor { + attributedString.addAttributes([NSUnderlineColorAttributeName: underlineColor], range: parsedString.range(from: element.range)) + } + } } return attributedString diff --git a/Marker/Marker/Parser/MarkdownElement.swift b/Marker/Marker/Parser/MarkdownElement.swift index 9457ec9..61bc2f5 100644 --- a/Marker/Marker/Parser/MarkdownElement.swift +++ b/Marker/Marker/Parser/MarkdownElement.swift @@ -13,11 +13,13 @@ import Foundation /// - em: Emphasis element. /// - strong: Strong element. /// - strikethrough: Strikethrough element. +/// - underline: Underline element. internal enum MarkdownElement { case em(Range) case strong(Range) case strikethrough(Range) + case underline(Range) /// Range of characters that the elements apply to. var range: Range { @@ -28,6 +30,8 @@ internal enum MarkdownElement { return range case .strikethrough(let range): return range + case .underline(let range): + return range } } diff --git a/Marker/Marker/Parser/MarkdownParser.swift b/Marker/Marker/Parser/MarkdownParser.swift index e1aa33c..75aeca5 100644 --- a/Marker/Marker/Parser/MarkdownParser.swift +++ b/Marker/Marker/Parser/MarkdownParser.swift @@ -27,6 +27,7 @@ internal struct MarkdownParser { private static let underscoreStrongSymbol = Symbol(rawValue: "__") private static let asteriskStrongSymbol = Symbol(rawValue: "**") private static let tildeStrikethroughSymbol = Symbol(rawValue: "~~") + private static let equalUnderlineSymbol = Symbol(rawValue: "==") // MARK: - Static functions @@ -41,7 +42,8 @@ internal struct MarkdownParser { let asteriskEmSymbol = asteriskEmSymbol, let underscoreStrongSymbol = underscoreStrongSymbol, let asteriskStrongSymbol = asteriskStrongSymbol, - let tildeStrikethroughSymbol = tildeStrikethroughSymbol else { + let tildeStrikethroughSymbol = tildeStrikethroughSymbol, + let equalUnderlineSymbol = equalUnderlineSymbol else { return (string, []) } @@ -53,6 +55,8 @@ internal struct MarkdownParser { return .strong(element.range) case tildeStrikethroughSymbol: return .strikethrough(element.range) + case equalUnderlineSymbol: + return .underline(element.range) default: throw ParserError.invalidTagSymbol } @@ -63,7 +67,8 @@ internal struct MarkdownParser { asteriskEmSymbol, underscoreStrongSymbol, asteriskStrongSymbol, - tildeStrikethroughSymbol]) + tildeStrikethroughSymbol, + equalUnderlineSymbol]) return try (strippedString, elements.map(transformToMarkdownElement)) } diff --git a/Marker/Marker/TextStyle+Extensions.swift b/Marker/Marker/TextStyle+Extensions.swift new file mode 100644 index 0000000..f004d8b --- /dev/null +++ b/Marker/Marker/TextStyle+Extensions.swift @@ -0,0 +1,165 @@ +// +// TextStyle+Extensions.swift +// Marker +// +// Created by Harlan Kellaway on 7/7/17. +// Copyright © 2017 Prolific Interactive. All rights reserved. +// + +#if os(iOS) || os(tvOS) + import UIKit +#elseif os(macOS) || os(OSX) + import AppKit +#endif + +/// Adds factory functions producing new TextStyle from existing TextStyle. +public extension TextStyle { + + /// Creates new TextStyle from exisiting TextStyle, updating with provided values. + /// + /// - Parameters: + /// - newFont: New font. + /// - newEmFont: New emphasis font. + /// - newStrongFont: New strong font. + /// - newTextColor: New text color. + /// - newCharacterSpacing: New character spacing. + /// - newLineSpacing: New line spacing. + /// - newLineHeightMultiple: New line height multiple. + /// - newMinimumLineHeight: New minimum line height. + /// - newMaximumLineHeight: New maximum line height. + /// - newFirstLineHeadIndent: New first line head indent. + /// - newHeadIndent: New head indent. + /// - newParagraphSpacing: New paragraph spacing. + /// - newParagraphSpacingBefore: New paragraph spacing before. + /// - newTextAlignment: New text alignment. + /// - newLineBreakMode: New line break mode. + /// - newStrikethroughStyle: New strikethrough style. + /// - newStrikethroughColor: New strikethrough color. + /// - newUnderlineStyle: New underline style. + /// - newUnderlineColor: New underline color. + /// - newTextTransform: New text transform. + /// - Returns: New Text Style with updated value(s). + public func with(newFont: Font? = nil, + newEmFont: Font? = nil, + newStrongFont: Font? = nil, + newTextColor: Color? = nil, + newCharacterSpacing: CGFloat? = nil, + newLineSpacing: CGFloat? = nil, + newLineHeightMultiple: CGFloat? = nil, + newMinimumLineHeight: CGFloat? = nil, + newMaximumLineHeight: CGFloat? = nil, + newFirstLineHeadIndent: CGFloat? = nil, + newHeadIndent: CGFloat? = nil, + newParagraphSpacing: CGFloat? = nil, + newParagraphSpacingBefore: CGFloat? = nil, + newTextAlignment: NSTextAlignment? = nil, + newLineBreakMode: NSLineBreakMode? = nil, + newStrikethroughStyle: NSUnderlineStyle? = nil, + newStrikethroughColor: Color? = nil, + newUnderlineStyle: NSUnderlineStyle? = nil, + newUnderlineColor: Color? = nil, + newTextTransform: TextTransform? = nil) -> TextStyle { + let fontToUse = newFont ?? font + let emFontToUse = newEmFont ?? emFont + let strongFontToUse = newStrongFont ?? strongFont + let textColorToUse = newTextColor ?? textColor + let characterSpacingToUse = newCharacterSpacing ?? characterSpacing + let lineSpacingToUse = newLineSpacing ?? lineSpacing + let lineHeightMultipleToUse = newLineHeightMultiple ?? lineHeightMultiple + let minimumLineHeightToUse = newMinimumLineHeight ?? minimumLineHeight + let maximumLineHeightToUse = newMaximumLineHeight ?? maximumLineHeight + let firstLineHeadIndentToUse = newFirstLineHeadIndent ?? firstLineHeadIndent + let headIndentToUse = newHeadIndent ?? headIndent + let paragraphSpacingToUse = newParagraphSpacing ?? paragraphSpacing + let paragraphSpacingBeforeToUse = newParagraphSpacingBefore ?? paragraphSpacingBefore + let textAlignmentToUse = newTextAlignment ?? textAlignment + let lineBreakModeToUse = newLineBreakMode ?? lineBreakMode + let strikethroughStyleToUse = newStrikethroughStyle ?? strikethroughStyle + let strikethroughColorToUse = newStrikethroughColor ?? strikethroughColor + let underlineStyleToUse = newUnderlineStyle ?? underlineStyle + let underlineColorToUse = newUnderlineColor ?? underlineColor + let textTransformToUse = newTextTransform ?? textTransform + + return TextStyle( + font: fontToUse, + emFont: emFontToUse, + strongFont: strongFontToUse, + textColor: textColorToUse, + characterSpacing: characterSpacingToUse, + lineSpacing: lineSpacingToUse, + lineHeightMultiple: lineHeightMultipleToUse, + minimumLineHeight: minimumLineHeightToUse, + maximumLineHeight: maximumLineHeightToUse, + firstLineHeadIndent: firstLineHeadIndentToUse, + headIndent: headIndentToUse, + paragraphSpacing: paragraphSpacingToUse, + paragraphSpacingBefore: paragraphSpacingBeforeToUse, + textAlignment: textAlignmentToUse, + lineBreakMode: lineBreakModeToUse, + strikethroughStyle: strikethroughStyleToUse, + strikethroughColor: strikethroughColorToUse, + underlineStyle: underlineStyleToUse, + underlineColor: underlineColorToUse, + textTransform: textTransformToUse + ) + } + + /// Creates new TextStyle from existing TextStyle, with strikethrough. + /// + /// - Parameters: + /// - color: Strikethrough color. Defaults to textColor. + /// - style: Strikethrough style. Defaults to single line. + /// - Returns: New TextStyle with strikethrough. + public func strikethrough(color: Color? = nil, style: NSUnderlineStyle = .styleSingle) -> TextStyle { + return self.with( + newStrikethroughStyle: style, + newStrikethroughColor: color ?? textColor + ) + } + + /// Creates new TextStyle from existing TextStyle, with underline. + /// + /// - Parameter color: Underline color. Defaults to textColor. + /// - Parameter style: Underline style. Defaults to single line. + /// - Returns: New TextStyle with underline. + public func underlined(color: Color? = nil, style: NSUnderlineStyle = .styleSingle) -> TextStyle { + return self.with( + newUnderlineStyle: style, + newUnderlineColor: color ?? textColor + ) + } + +} + +// MARK: - Protocol conformance + +// MARK: Equatable + +extension TextStyle: Equatable { } + +public func ==(lhs: TextStyle, rhs: TextStyle) -> Bool { + guard lhs.font == rhs.font, + lhs.emFont == rhs.emFont, + lhs.strongFont == rhs.strongFont, + lhs.textColor == rhs.textColor, + lhs.characterSpacing == rhs.characterSpacing, + lhs.lineSpacing == rhs.lineSpacing, + lhs.lineHeightMultiple == rhs.lineHeightMultiple, + lhs.minimumLineHeight == rhs.minimumLineHeight, + lhs.maximumLineHeight == rhs.maximumLineHeight, + lhs.firstLineHeadIndent == rhs.firstLineHeadIndent, + lhs.headIndent == rhs.headIndent, + lhs.paragraphSpacing == rhs.paragraphSpacing, + lhs.paragraphSpacingBefore == rhs.paragraphSpacingBefore, + lhs.textAlignment == rhs.textAlignment, + lhs.lineBreakMode == rhs.lineBreakMode, + lhs.strikethroughStyle == rhs.strikethroughStyle, + lhs.strikethroughColor == rhs.strikethroughColor, + lhs.underlineStyle == rhs.underlineStyle, + lhs.underlineColor == rhs.underlineColor, + lhs.textTransform == rhs.textTransform else { + return false + } + + return true +} diff --git a/Marker/Marker/TextStyle.swift b/Marker/Marker/TextStyle.swift index 1314598..6c28ffc 100644 --- a/Marker/Marker/TextStyle.swift +++ b/Marker/Marker/TextStyle.swift @@ -70,6 +70,12 @@ public struct TextStyle { /// Stroke color for strikethough text. public var strikethroughColor: Color? + + /// Underline style for underlined text. + public var underlineStyle: NSUnderlineStyle? + + /// Stroke color for underlined text. + public var underlineColor: Color? /// Text transform. public var textTransform: TextTransform @@ -149,6 +155,8 @@ public struct TextStyle { - parameter lineBreakMode: Line break node. - parameter strikethroughStyle: Strikethrough style. - parameter strikethroughColor: Strikethrough color. + - parameter underlineStyle: Underline style. + - parameter underlineColor: Underline color. - parameter textTransform: Text transform option. - returns: An initialized text style object. @@ -170,6 +178,8 @@ public struct TextStyle { lineBreakMode: NSLineBreakMode? = nil, strikethroughStyle: NSUnderlineStyle? = nil, strikethroughColor: Color? = nil, + underlineStyle: NSUnderlineStyle? = nil, + underlineColor: Color? = nil, textTransform: TextTransform = .none) { self.font = font self.emFont = (emFont == nil) ? font : emFont! @@ -188,6 +198,8 @@ public struct TextStyle { self.lineBreakMode = lineBreakMode self.strikethroughStyle = strikethroughStyle self.strikethroughColor = strikethroughColor + self.underlineStyle = underlineStyle + self.underlineColor = underlineColor self.textTransform = textTransform } } diff --git a/Marker/Marker/TextTransform+Extensions.swift b/Marker/Marker/TextTransform+Extensions.swift new file mode 100644 index 0000000..5a9c54d --- /dev/null +++ b/Marker/Marker/TextTransform+Extensions.swift @@ -0,0 +1,38 @@ +// +// TextTransform+Extensions.swift +// Marker +// +// Created by Harlan Kellaway on 7/7/17. +// Copyright © 2017 Prolific Interactive. All rights reserved. +// + +import Foundation + +// MARK: - Protocol conformance + +// MARK: Equatable + +extension TextTransform: Equatable { } + +/// Returns a Boolean value indicating whether two TextTransform are equal. +/// NOTE: This function always returns false if one of the transforms are `custom` +/// since there is no good way to compare the associated functions. +/// +/// - Parameters: +/// - lhs: A TextTransform to compare. +/// - rhs: Another TextTransform to compare. +/// - Returns: true if the two transforms are equal. false otherwise. +public func ==(lhs: TextTransform, rhs: TextTransform) -> Bool { + switch (lhs, rhs) { + case (.none, .none): + return true + case (.lowercased, .lowercased): + return true + case (.uppercased, .uppercased): + return true + case (.capitalized, .capitalized): + return true + default: + return false + } +} diff --git a/Marker/MarkerTests/ParserTests.swift b/Marker/MarkerTests/ParserTests.swift index 66f44c5..24d3df1 100644 --- a/Marker/MarkerTests/ParserTests.swift +++ b/Marker/MarkerTests/ParserTests.swift @@ -1,3 +1,11 @@ +// +// ParserTests.swift +// Marker +// +// Created by Htin Linn on 7/7/17. +// Copyright © 2017 Prolific Interactive. All rights reserved. +// + import XCTest @testable import Marker @@ -43,7 +51,7 @@ class ParserTests: XCTestCase { } } - func testParseStrikeThroughElements() { + func testParseStrikethroughElements() { do { let (parsedString, parsedElements) = try MarkdownParser.parse("~~abc~~ def ~~ghi~~") @@ -59,24 +67,42 @@ class ParserTests: XCTestCase { } } + func testParseUnderlinedElements() { + do { + let (parsedString, parsedElements) = try MarkdownParser.parse("==abc== def ==ghi==") + + XCTAssert(parsedString == "abc def ghi") + XCTAssert(parsedElements.count == 2) + + parsedElements.forEach { XCTAssert($0.isUnderlineElement()) } + + XCTAssert(parsedElements[0].range == parsedString.range(of: "abc")) + XCTAssert(parsedElements[1].range == parsedString.range(of: "ghi")) + } catch { + XCTFail("Parsing failed.") + } + } + func testParseMixedElements() { do { - let (parsedString, parsedElements) = try MarkdownParser.parse("*abc* __def__ _ghi_ **jkl** ~~mno~~") + let (parsedString, parsedElements) = try MarkdownParser.parse("*abc* __def__ _ghi_ **jkl** ~~mno~~ ==pqr==") - XCTAssert(parsedString == "abc def ghi jkl mno") - XCTAssert(parsedElements.count == 5) + XCTAssert(parsedString == "abc def ghi jkl mno pqr") + XCTAssert(parsedElements.count == 6) XCTAssert(parsedElements[0].isEmElement()) XCTAssert(parsedElements[1].isStrongElement()) XCTAssert(parsedElements[2].isEmElement()) XCTAssert(parsedElements[3].isStrongElement()) XCTAssert(parsedElements[4].isStrikethroughElement()) + XCTAssert(parsedElements[5].isUnderlineElement()) XCTAssert(parsedElements[0].range == parsedString.range(of: "abc")) XCTAssert(parsedElements[1].range == parsedString.range(of: "def")) XCTAssert(parsedElements[2].range == parsedString.range(of: "ghi")) XCTAssert(parsedElements[3].range == parsedString.range(of: "jkl")) XCTAssert(parsedElements[4].range == parsedString.range(of: "mno")) + XCTAssert(parsedElements[5].range == parsedString.range(of: "pqr")) } catch { XCTFail("Parsing failed.") } @@ -115,22 +141,35 @@ class ParserTests: XCTestCase { } } + func testLiteralEqual() { + do { + let (parsedString, parsedElements) = try MarkdownParser.parse("two + two = four") + + XCTAssert(parsedString == "two + two = four") + XCTAssert(parsedElements.count == 0) + } catch { + XCTFail("Parsing failed.") + } + } + func testElementsInMiddleOfWords() { do { - let (parsedString, parsedElements) = try MarkdownParser.parse("the_quick_brown*fox*jumps__over__the**lazy**dog.") + let (parsedString, parsedElements) = try MarkdownParser.parse("the_quick_brown*fox*jumps__over__the**lazy**==dog==.") XCTAssert(parsedString == "thequickbrownfoxjumpsoverthelazydog.") - XCTAssert(parsedElements.count == 4) + XCTAssert(parsedElements.count == 5) XCTAssert(parsedElements[0].isEmElement()) XCTAssert(parsedElements[1].isEmElement()) XCTAssert(parsedElements[2].isStrongElement()) XCTAssert(parsedElements[3].isStrongElement()) + XCTAssert(parsedElements[4].isUnderlineElement()) XCTAssert(parsedElements[0].range == parsedString.range(of: "quick")) XCTAssert(parsedElements[1].range == parsedString.range(of: "fox")) XCTAssert(parsedElements[2].range == parsedString.range(of: "over")) XCTAssert(parsedElements[3].range == parsedString.range(of: "lazy")) + XCTAssert(parsedElements[4].range == parsedString.range(of: "dog")) } catch { XCTFail("Parsing failed.") } @@ -162,6 +201,12 @@ class ParserTests: XCTestCase { } catch { XCTAssert(error as! ElementParser.ParserError == ElementParser.ParserError.unclosedTags) } + + do { + let _ = try MarkdownParser.parse("Not ==correct.") + } catch { + XCTAssert(error as! ElementParser.ParserError == ElementParser.ParserError.unclosedTags) + } } } @@ -195,4 +240,13 @@ private extension MarkdownElement { } } + func isUnderlineElement() -> Bool { + switch self { + case .underline(_): + return true + default: + return false + } + } + } diff --git a/Marker/MarkerTests/TextStyleEquatableTests.swift b/Marker/MarkerTests/TextStyleEquatableTests.swift new file mode 100644 index 0000000..d8a0a1f --- /dev/null +++ b/Marker/MarkerTests/TextStyleEquatableTests.swift @@ -0,0 +1,123 @@ +// +// TextStyleEquatableTests.swift +// Marker +// +// Created by Harlan Kellaway on 7/7/17. +// Copyright © 2017 Prolific Interactive. All rights reserved. +// + +#if os(iOS) || os(tvOS) + import UIKit +#elseif os(macOS) || os(OSX) + import AppKit +#endif + +import XCTest +@testable import Marker + +class TextStyleEquatableTests: XCTestCase { + + var textStyle: TextStyle! + + override func setUp() { + super.setUp() + + textStyle = TextStyle(font: Font(name: "Helvetica", size: 10)!, + emFont: Font(name: "Helvetica-Oblique", size: 10)!, + strongFont: Font(name: "Helvetica-Bold", size: 10)!, + textColor: Color.red, + characterSpacing: 1, + lineSpacing: 2, + lineHeightMultiple: 3, + minimumLineHeight: 4, + maximumLineHeight: 5, + firstLineHeadIndent: 6, + headIndent: 7, + paragraphSpacing: 8, + paragraphSpacingBefore: 9, + textAlignment: .left, + lineBreakMode: .byWordWrapping, + strikethroughStyle: .styleSingle, + strikethroughColor: Color.red, + underlineStyle: .styleSingle, + underlineColor: Color.red, + textTransform: .lowercased) + } + + override func tearDown() { + textStyle = nil + + super.tearDown() + } + + func testTextStyle_isEqual_whenAllPropertiesTheSame() { + let sameTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(textStyle, sameTextStyle) + } + + func testTextStyle_isNotEqual_whenPropertiesAreDiffernt() { + let differentFont = textStyle.with(newFont: Font(name: textStyle.font.fontName, size: textStyle.font.pointSize + 10)!) + let differentEmFont = textStyle.with(newEmFont: Font(name: textStyle.emFont.fontName, size: textStyle.emFont.pointSize + 10)!) + let differentStrongFont = textStyle.with(newStrongFont: Font(name: textStyle.strongFont.fontName, size: textStyle.strongFont.pointSize + 10)!) + let differentTextColor = textStyle.with(newTextColor: Color.blue) + let differentCharacterSpacing = textStyle.with(newCharacterSpacing: textStyle.characterSpacing! + 10) + let differentLineSpacing = textStyle.with(newLineSpacing: textStyle.lineSpacing! + 10) + let differentLineHeightMultiple = textStyle.with(newLineHeightMultiple: textStyle.lineHeightMultiple! + 10) + let differentMinimumLineHeight = textStyle.with(newMinimumLineHeight: textStyle.minimumLineHeight! + 10) + let differentMaximumLineHeight = textStyle.with(newMaximumLineHeight: textStyle.maximumLineHeight! + 10) + let differentFirstLineHeadIndent = textStyle.with(newFirstLineHeadIndent: textStyle.firstLineHeadIndent! + 10) + let differentHeadIndent = textStyle.with(newHeadIndent: textStyle.headIndent! + 10) + let differentParagraphSpacing = textStyle.with(newParagraphSpacing: textStyle.paragraphSpacing! + 10) + let differentParagraphSpacingBefore = textStyle.with(newParagraphSpacingBefore: textStyle.paragraphSpacingBefore! + 10) + let differentTextAlignment = textStyle.with(newTextAlignment: .right) + let differentLineBreakMode = textStyle.with(newLineBreakMode: .byCharWrapping) + let differentStrikethroughStyle = textStyle.with(newStrikethroughStyle: .styleDouble) + let differentStrikethroughColor = textStyle.with(newStrikethroughColor: Color.blue) + let differentUnderlineStyle = textStyle.with(newUnderlineStyle: .styleDouble) + let differentUnderlineColor = textStyle.with(newUnderlineColor: Color.blue) + let differentTextTransform = textStyle.with(newTextTransform: .uppercased) + + + XCTAssertNotEqual(textStyle, differentFont) + XCTAssertNotEqual(textStyle, differentEmFont) + XCTAssertNotEqual(textStyle, differentStrongFont) + XCTAssertNotEqual(textStyle, differentTextColor) + XCTAssertNotEqual(textStyle, differentCharacterSpacing) + XCTAssertNotEqual(textStyle, differentLineSpacing) + XCTAssertNotEqual(textStyle, differentLineHeightMultiple) + XCTAssertNotEqual(textStyle, differentMinimumLineHeight) + XCTAssertNotEqual(textStyle, differentMaximumLineHeight) + XCTAssertNotEqual(textStyle, differentFirstLineHeadIndent) + XCTAssertNotEqual(textStyle, differentHeadIndent) + XCTAssertNotEqual(textStyle, differentParagraphSpacing) + XCTAssertNotEqual(textStyle, differentParagraphSpacingBefore) + XCTAssertNotEqual(textStyle, differentTextAlignment) + XCTAssertNotEqual(textStyle, differentLineBreakMode) + XCTAssertNotEqual(textStyle, differentStrikethroughStyle) + XCTAssertNotEqual(textStyle, differentStrikethroughColor) + XCTAssertNotEqual(textStyle, differentUnderlineStyle) + XCTAssertNotEqual(textStyle, differentUnderlineColor) + XCTAssertNotEqual(textStyle, differentTextTransform) + } + +} diff --git a/Marker/MarkerTests/TextStyleFactoryFunctionTests.swift b/Marker/MarkerTests/TextStyleFactoryFunctionTests.swift new file mode 100644 index 0000000..60b0afe --- /dev/null +++ b/Marker/MarkerTests/TextStyleFactoryFunctionTests.swift @@ -0,0 +1,701 @@ +// +// TextStyleFactoryFunctionTests.swift +// Marker +// +// Created by Harlan Kellaway on 7/7/17. +// Copyright © 2017 Prolific Interactive. All rights reserved. +// + +#if os(iOS) || os(tvOS) + import UIKit +#elseif os(macOS) || os(OSX) + import AppKit +#endif + +import XCTest +@testable import Marker + +class TextStyleFactoryFunctionTests: XCTestCase { + + var textStyle: TextStyle! + + override func setUp() { + super.setUp() + + textStyle = TextStyle(font: Font(name: "Helvetica", size: 10)!, + emFont: Font(name: "Helvetica-Oblique", size: 10)!, + strongFont: Font(name: "Helvetica-Bold", size: 10)!, + textColor: Color.red, + characterSpacing: 1, + lineSpacing: 2, + lineHeightMultiple: 3, + minimumLineHeight: 4, + maximumLineHeight: 5, + firstLineHeadIndent: 6, + headIndent: 7, + paragraphSpacing: 8, + paragraphSpacingBefore: 9, + textAlignment: .left, + lineBreakMode: .byWordWrapping, + textTransform: .lowercased) + } + + override func tearDown() { + textStyle = nil + + super.tearDown() + } + + func testTextStyleFactory_whenNewFont_newFontIsUsed() { + let newFont = Font(name: textStyle.font.fontName, size: textStyle.font.pointSize + 10)! + let newTextStyle = textStyle.with(newFont: newFont) + let expectedTextStyle = TextStyle(font: newFont, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenNewEmFont_newEmFontIsUsed() { + let newEmFont = Font(name: textStyle.emFont.fontName, size: textStyle.font.pointSize + 10)! + let newTextStyle = textStyle.with(newEmFont: newEmFont) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: newEmFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenNewStrongFont_newStrongFontIsUsed() { + let newStrongFont = Font(name: textStyle.strongFont.fontName, size: textStyle.font.pointSize + 10)! + let newTextStyle = textStyle.with(newStrongFont: newStrongFont) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: newStrongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenNewTextColor_newTextColorIsUsed() { + let newTextColor = Color.blue + let newTextStyle = textStyle.with(newTextColor: newTextColor) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: newTextColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenNewCharacterSpacing_newCharacterIsUsed() { + let newCharacterSpacing = textStyle.characterSpacing! + 10 + let newTextStyle = textStyle.with(newCharacterSpacing: newCharacterSpacing) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: newCharacterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenNewLineSpacing_newLineSpacingIsUsed() { + let newLineSpacing = textStyle.lineSpacing! + 10 + let newTextStyle = textStyle.with(newLineSpacing: newLineSpacing) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: newLineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenNewLineHeightMultiple_newLineHeightMultipleIsUsed() { + let newLineHeightMultiple = textStyle.lineHeightMultiple! + 10 + let newTextStyle = textStyle.with(newLineHeightMultiple: newLineHeightMultiple) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: newLineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenNewMinimumLineHeight_newMinimumLineHeightIsUsed() { + let newMinimumLineHeight = textStyle.minimumLineHeight! + 10 + let newTextStyle = textStyle.with(newMinimumLineHeight: newMinimumLineHeight) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: newMinimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenNewMaximumLineHeight_newMaximumLineHeightIsUsed() { + let newMaximumLineHeight = textStyle.maximumLineHeight! + 10 + let newTextStyle = textStyle.with(newMaximumLineHeight: newMaximumLineHeight) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: newMaximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenNewFirstLineHeadIndent_newFirstLineHeadIndentIsUsed() { + let newFirstLineHeadIndent = textStyle.firstLineHeadIndent! + 10 + let newTextStyle = textStyle.with(newFirstLineHeadIndent: newFirstLineHeadIndent) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: newFirstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenNewHeadIndent_newHeadIndentIsUsed() { + let newHeadIndent = textStyle.headIndent! + 10 + let newTextStyle = textStyle.with(newHeadIndent: newHeadIndent) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: newHeadIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenNewParagraphSpacing_newParagraphSpacingIsUsed() { + let newParagraphSpacing = textStyle.paragraphSpacing! + 10 + let newTextStyle = textStyle.with(newParagraphSpacing: newParagraphSpacing) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: newParagraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenNewParagraphSpacingBefore_newParagraphSpacingBeforeIsUsed() { + let newParagraphSpacingBefore = textStyle.paragraphSpacingBefore! + 10 + let newTextStyle = textStyle.with(newParagraphSpacingBefore: newParagraphSpacingBefore) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: newParagraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenNewTextAlignment_newTextAlignmentIsUsed() { + let newTextAlignment: NSTextAlignment = .right + let newTextStyle = textStyle.with(newTextAlignment: newTextAlignment) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: newTextAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenNewLineBreakMode_newLineBreakModeIsUsed() { + let newLineBreakMode: NSLineBreakMode = .byCharWrapping + let newTextStyle = textStyle.with(newLineBreakMode: newLineBreakMode) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: newLineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenNewStrikethroughStyle_newStrikethroughStyleIsUsed() { + let newStrikethroughStyle: NSUnderlineStyle = .styleDouble + let newTextStyle = textStyle.with(newStrikethroughStyle: newStrikethroughStyle) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: newStrikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenNewStrikethroughColor_newStrikethroughColorIsUsed() { + let newStrikethroughColor = Color.blue + let newTextStyle = textStyle.with(newStrikethroughColor: newStrikethroughColor) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: newStrikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenNewUnderlineStyle_newUnderlineStyleIsUsed() { + let newUnderlineStyle: NSUnderlineStyle = .styleDouble + let newTextStyle = textStyle.with(newUnderlineStyle: newUnderlineStyle) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: newUnderlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenNewUnderlineColor_newUnderlineColorIsUsed() { + let newUnderlineColor = Color.blue + let newTextStyle = textStyle.with(newUnderlineColor: newUnderlineColor) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: newUnderlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenNewTextTransform_newTextTransformIsUsed() { + let newTextTransform: TextTransform = .uppercased + let newTextStyle = textStyle.with(newTextTransform: newTextTransform) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: newTextTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenStrikethrough_strikethroughPropertiesAreSet() { + let strikethroughStyle: NSUnderlineStyle = .styleDouble + let strikethroughColor = Color.green + let newTextStyle = textStyle.strikethrough(color: strikethroughColor, style: strikethroughStyle) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: strikethroughStyle, + strikethroughColor: strikethroughColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenStrikethrough_defaultsToSingleLineOfSameColor() { + let expectedStyle: NSUnderlineStyle = .styleSingle + let expectedColor = textStyle.textColor + let newTextStyle = textStyle.strikethrough() + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: expectedStyle, + strikethroughColor: expectedColor, + underlineStyle: textStyle.underlineStyle, + underlineColor: textStyle.underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenUnderlined_underlinePropertiesAreSet() { + let underlineStyle: NSUnderlineStyle = .styleDouble + let underlineColor = Color.green + let newTextStyle = textStyle.underlined(color: underlineColor, style: underlineStyle) + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: underlineStyle, + underlineColor: underlineColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + + func testTextStyleFactory_whenUnderlined_defaultsToSingleLineOfSameColor() { + let expectedStyle: NSUnderlineStyle = .styleSingle + let expectedColor = textStyle.textColor + let newTextStyle = textStyle.underlined() + let expectedTextStyle = TextStyle(font: textStyle.font, + emFont: textStyle.emFont, + strongFont: textStyle.strongFont, + textColor: textStyle.textColor, + characterSpacing: textStyle.characterSpacing, + lineSpacing: textStyle.lineSpacing, + lineHeightMultiple: textStyle.lineHeightMultiple, + minimumLineHeight: textStyle.minimumLineHeight, + maximumLineHeight: textStyle.maximumLineHeight, + firstLineHeadIndent: textStyle.firstLineHeadIndent, + headIndent: textStyle.headIndent, + paragraphSpacing: textStyle.paragraphSpacing, + paragraphSpacingBefore: textStyle.paragraphSpacingBefore, + textAlignment: textStyle.textAlignment, + lineBreakMode: textStyle.lineBreakMode, + strikethroughStyle: textStyle.strikethroughStyle, + strikethroughColor: textStyle.strikethroughColor, + underlineStyle: expectedStyle, + underlineColor: expectedColor, + textTransform: textStyle.textTransform) + + XCTAssertEqual(newTextStyle, expectedTextStyle) + } + +} diff --git a/Marker/MarkerTests/TextTransformEquatableTests.swift b/Marker/MarkerTests/TextTransformEquatableTests.swift new file mode 100644 index 0000000..0abda60 --- /dev/null +++ b/Marker/MarkerTests/TextTransformEquatableTests.swift @@ -0,0 +1,32 @@ +// +// TextTransformEquatableTests.swift +// Marker +// +// Created by Harlan Kellaway on 7/7/17. +// Copyright © 2017 Prolific Interactive. All rights reserved. +// + +import XCTest +@testable import Marker + +class TextTransformEquatableTests: XCTestCase { + + func testTextStyle_isEqual_whenSameValue_andNotCustom() { + let none: TextTransform = .none + let lowercased: TextTransform = .lowercased + let uppercased: TextTransform = .uppercased + let capitalized: TextTransform = .capitalized + + XCTAssertEqual(none, none) + XCTAssertEqual(lowercased, lowercased) + XCTAssertEqual(uppercased, uppercased) + XCTAssertEqual(capitalized, capitalized) + } + + func testTextStyle_isNotEqual_whenCustom() { + let custom: TextTransform = .custom({ string in "\(string) \(string)" }) + + XCTAssertNotEqual(custom, custom) + } + +} diff --git a/README.md b/README.md index 863a5de..f4c9c81 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ Marker abstracts the most common attributed text properties into a data object c * Text alignment * Line break mode * Strikethrough style and color +* Underline style and color * Text transformation option `TextStyle` objects are a simple way of aggregating style information. For example: @@ -94,7 +95,11 @@ Marker also supports setting text with common Markdown tags: * Bold (`__` or `**`) * Italic (`_` or `*`) + +As well as convenient Markdown tags specific to Marker: + * Strikethrough (`~~`) +* Underline (`==`) To set Markdown text on these elements, use `setMarkdownText(_:using:)` (or `setMarkdownTitleText(_:using:)` for `UIButton`) function.