@@ -4,14 +4,20 @@ import MapboxDirections
44// Will automatically read localized Instructions.plist
55let OSRMTextInstructionsStrings = NSDictionary ( contentsOfFile: Bundle ( for: OSRMInstructionFormatter . self) . path ( forResource: " Instructions " , ofType: " plist " ) !) !
66
7- extension String {
8- public var sentenceCased : String {
9- return String ( characters. prefix ( 1 ) ) . uppercased ( ) + String( characters. dropFirst ( ) )
10- }
7+ protocol Tokenized {
8+ associatedtype T
119
1210 /**
1311 Replaces `{tokens}` in the receiver using the given closure.
1412 */
13+ func replacingTokens( using interpolator: ( ( TokenType ) -> T ) ) -> T
14+ }
15+
16+ extension String : Tokenized {
17+ public var sentenceCased : String {
18+ return String ( characters. prefix ( 1 ) ) . uppercased ( ) + String( characters. dropFirst ( ) )
19+ }
20+
1521 public func replacingTokens( using interpolator: ( ( TokenType ) -> String ) ) -> String {
1622 let scanner = Scanner ( string: self )
1723 scanner. charactersToBeSkipped = nil
@@ -52,6 +58,48 @@ extension String {
5258 }
5359}
5460
61+ extension NSAttributedString : Tokenized {
62+ public func replacingTokens( using interpolator: ( ( TokenType ) -> NSAttributedString ) ) -> NSAttributedString {
63+ let scanner = Scanner ( string: string)
64+ scanner. charactersToBeSkipped = nil
65+ let result = NSMutableAttributedString ( )
66+ while !scanner. isAtEnd {
67+ var buffer : NSString ?
68+
69+ if scanner. scanUpTo ( " { " , into: & buffer) {
70+ result. append ( NSAttributedString ( string: buffer! as String ) )
71+ }
72+ guard scanner. scanString ( " { " , into: nil ) else {
73+ continue
74+ }
75+
76+ var token : NSString ?
77+ guard scanner. scanUpTo ( " } " , into: & token) else {
78+ continue
79+ }
80+
81+ if scanner. scanString ( " } " , into: nil ) {
82+ if let tokenType = TokenType ( description: token! as String ) {
83+ result. append ( interpolator ( tokenType) )
84+ }
85+ } else {
86+ result. append ( NSAttributedString ( string: token! as String ) )
87+ }
88+ }
89+
90+ // remove excess spaces
91+ let wholeRange = NSRange ( location: 0 , length: result. mutableString. length)
92+ result. mutableString. replaceOccurrences ( of: " \\ s \\ s " , with: " " , options: . regularExpression, range: wholeRange)
93+
94+ // capitalize
95+ let meta = OSRMTextInstructionsStrings [ " meta " ] as! [ String : Any ]
96+ if meta [ " capitalizeFirstLetter " ] as? Bool ?? false {
97+ result. replaceCharacters ( in: NSRange ( location: 0 , length: 1 ) , with: String ( result. string. characters. first!) . uppercased ( ) )
98+ }
99+ return result as NSAttributedString
100+ }
101+ }
102+
55103public class OSRMInstructionFormatter : Formatter {
56104 let version : String
57105 let instructions : [ String : Any ]
@@ -179,14 +227,39 @@ public class OSRMInstructionFormatter: Formatter {
179227 /**
180228 Creates an instruction given a step and options.
181229
182- - parameter step:
230+ - parameter step: The step to format.
183231 - parameter legIndex: Current leg index the user is currently on.
184232 - parameter numberOfLegs: Total number of `RouteLeg` for the given `Route`.
185233 - parameter roadClasses: Option set representing the classes of road for the `RouteStep`.
186234 - parameter modifyValueByKey: Allows for mutating the instruction at given parts of the instruction.
187235 - returns: An instruction as a `String`.
188236 */
189237 public func string( for obj: Any ? , legIndex: Int ? , numberOfLegs: Int ? , roadClasses: RoadClasses ? = RoadClasses ( [ ] ) , modifyValueByKey: ( ( TokenType , String ) -> String ) ? ) -> String ? {
238+ guard let obj = obj else {
239+ return nil
240+ }
241+
242+ var modifyAttributedValueByKey : ( ( TokenType , NSAttributedString ) -> NSAttributedString ) ?
243+ if let modifyValueByKey = modifyValueByKey {
244+ modifyAttributedValueByKey = { ( key: TokenType , value: NSAttributedString ) -> NSAttributedString in
245+ return NSAttributedString ( string: modifyValueByKey ( key, value. string) )
246+ }
247+ }
248+ return attributedString ( for: obj, legIndex: legIndex, numberOfLegs: numberOfLegs, roadClasses: roadClasses, modifyValueByKey: modifyAttributedValueByKey) ? . string
249+ }
250+
251+ /**
252+ Creates an instruction as an attributed string given a step and options.
253+
254+ - parameter obj: The step to format.
255+ - parameter attrs: The default attributes to use for the returned attributed string.
256+ - parameter legIndex: Current leg index the user is currently on.
257+ - parameter numberOfLegs: Total number of `RouteLeg` for the given `Route`.
258+ - parameter roadClasses: Option set representing the classes of road for the `RouteStep`.
259+ - parameter modifyValueByKey: Allows for mutating the instruction at given parts of the instruction.
260+ - returns: An instruction as an `NSAttributedString`.
261+ */
262+ public func attributedString( for obj: Any , withDefaultAttributes attrs: [ String : Any ] ? = nil , legIndex: Int ? , numberOfLegs: Int ? , roadClasses: RoadClasses ? = RoadClasses ( [ ] ) , modifyValueByKey: ( ( TokenType , NSAttributedString ) -> NSAttributedString ) ? ) -> NSAttributedString ? {
190263 guard let step = obj as? RouteStep else {
191264 return nil
192265 }
@@ -208,14 +281,14 @@ public class OSRMInstructionFormatter: Formatter {
208281
209282 var instructionObject : InstructionsByToken
210283 var rotaryName = " "
211- var wayName : String
284+ var wayName : NSAttributedString
212285 switch type {
213286 case . takeRotary, . takeRoundabout:
214287 // Special instruction types have an intermediate level keyed to “default”.
215288 let instructionsByModifier = instructions [ type. description] as! [ String : InstructionsByModifier ]
216289 let defaultInstructions = instructionsByModifier [ " default " ] !
217290
218- wayName = step. exitNames? . first ?? " "
291+ wayName = NSAttributedString ( string : step. exitNames? . first ?? " " , attributes : attrs )
219292 if let _rotaryName = step. names? . first, let _ = step. exitIndex, let obj = defaultInstructions [ " name_exit " ] {
220293 instructionObject = obj
221294 rotaryName = _rotaryName
@@ -244,22 +317,42 @@ public class OSRMInstructionFormatter: Formatter {
244317 let isMotorway = roadClasses? . contains ( . motorway) ?? false
245318
246319 if let name = name, let ref = ref, name != ref, !isMotorway {
247- wayName = phrase ( named: . nameWithCode) . replacingTokens ( using: { ( tokenType) -> String in
320+ let attributedName = NSAttributedString ( string: name, attributes: attrs)
321+ let attributedRef = NSAttributedString ( string: ref, attributes: attrs)
322+ let phrase = NSAttributedString ( string: self . phrase ( named: . nameWithCode) , attributes: attrs)
323+ wayName = phrase. replacingTokens ( using: { ( tokenType) -> NSAttributedString in
248324 switch tokenType {
249325 case . wayName:
250- return modifyValueByKey ? ( . wayName, name ) ?? name
326+ return modifyValueByKey ? ( . wayName, attributedName ) ?? attributedName
251327 case . code:
252- return modifyValueByKey ? ( . code, ref ) ?? ref
328+ return modifyValueByKey ? ( . code, attributedRef ) ?? attributedRef
253329 default :
254330 fatalError ( " Unexpected token type \( tokenType) in name-and-ref phrase " )
255331 }
256332 } )
257333 } else if let ref = ref, isMotorway, let decimalRange = ref. rangeOfCharacter ( from: . decimalDigits) , !decimalRange. isEmpty {
258- wayName = modifyValueByKey != nil ? " \( modifyValueByKey!( . code, ref) ) " : ref
334+ let attributedRef = NSAttributedString ( string: ref, attributes: attrs)
335+ if let modifyValueByKey = modifyValueByKey {
336+ wayName = modifyValueByKey ( . code, attributedRef)
337+ } else {
338+ wayName = attributedRef
339+ }
259340 } else if name == nil , let ref = ref {
260- wayName = modifyValueByKey != nil ? " \( modifyValueByKey!( . code, ref) ) " : ref
341+ let attributedRef = NSAttributedString ( string: ref, attributes: attrs)
342+ if let modifyValueByKey = modifyValueByKey {
343+ wayName = modifyValueByKey ( . code, attributedRef)
344+ } else {
345+ wayName = attributedRef
346+ }
347+ } else if let name = name {
348+ let attributedName = NSAttributedString ( string: name, attributes: attrs)
349+ if let modifyValueByKey = modifyValueByKey {
350+ wayName = modifyValueByKey ( . wayName, attributedName)
351+ } else {
352+ wayName = attributedName
353+ }
261354 } else {
262- wayName = name != nil ? modifyValueByKey != nil ? " \( modifyValueByKey! ( . wayName , name! ) ) " : name! : " "
355+ wayName = NSAttributedString ( )
263356 }
264357 }
265358
@@ -292,7 +385,7 @@ public class OSRMInstructionFormatter: Formatter {
292385 instruction = obj
293386 } else if let _ = step. exitCodes? . first, let obj = instructionObject [ " exit " ] {
294387 instruction = obj
295- } else if !wayName. isEmpty, let obj = instructionObject [ " name " ] {
388+ } else if !wayName. string . isEmpty, let obj = instructionObject [ " name " ] {
296389 instruction = obj
297390 } else {
298391 instruction = instructionObject [ " default " ] !
@@ -315,11 +408,11 @@ public class OSRMInstructionFormatter: Formatter {
315408 if step. finalHeading != nil { bearing = Int ( step. finalHeading! as Double ) }
316409
317410 // Replace tokens
318- let result = instruction. replacingTokens { ( tokenType) -> String in
411+ let result = NSAttributedString ( string : instruction, attributes : attrs ) . replacingTokens { ( tokenType) -> NSAttributedString in
319412 var replacement : String
320413 switch tokenType {
321414 case . code: replacement = step. codes? . first ?? " "
322- case . wayName: replacement = wayName
415+ case . wayName: replacement = " " // ignored
323416 case . destination: replacement = destination
324417 case . exitCode: replacement = exitCode
325418 case . exitIndex: replacement = exitOrdinal
@@ -332,9 +425,10 @@ public class OSRMInstructionFormatter: Formatter {
332425 fatalError ( " Unexpected token type \( tokenType) in individual instruction " )
333426 }
334427 if tokenType == . wayName {
335- return replacement // already modified above
428+ return wayName // already modified above
336429 } else {
337- return modifyValueByKey ? ( tokenType, replacement) ?? replacement
430+ let attributedReplacement = NSAttributedString ( string: replacement, attributes: attrs)
431+ return modifyValueByKey ? ( tokenType, attributedReplacement) ?? attributedReplacement
338432 }
339433 }
340434
0 commit comments