Class
The SVGPathStringTokenizer class breaks SVG strings into their command components.
class SVGPathStringTokenizer
This class is used to break an SVG string into its component commands prior to bring parsed into a CGPath
.
Essentially, it produces an array of tokens (i.e. commands and values) that may look like:
["M","0","1"...]
Used to skip or omit separator characters from the Token
array.
let separatorCharacterSet = CharacterSet(charactersIn: ...)
This removes spaces, tabs, new lines, carriage returns and commas.
Used to identify SVG-specific command characters.
let commandCharacterSet = CharacterSet(charactersIn: "mMLlHhVvzZCcSsQqTt")
Used to identify the characters necessary for reconstructing the first character of a value.
let numberStartCharacterSet = CharacterSet(charactersIn: "-+.0123456789")
Used to identify the characters necessary for reconstructing a value.
let numberCharacterSet = CharacterSet(charactersIn: ".0123456789eE")
The string to tokenize.
var string: String
The range of the string to parse.
var range: Range<String.UnicodeScalarView.Index>
Initialization requires a string.
init(string: String) {
self.string = string
range = string.unicodeScalars.startIndex..<string.unicodeScalars.endIndex
}
The function responsible for initializing tokenization and returning an array of Token
s.
func tokenize() -> [Token] {
var array = [Token]()
while let token = nextToken() {
array.append(token)
}
return array
}
This function attempts to grab the next token in the string
. It returns a command
, a value
, or nil
.
func nextToken() -> Token? {
skipSeparators()
guard let c = get() else {
return nil
}
if commandCharacterSet.isMember(c) {
return .command(c)
}
if numberStartCharacterSet.isMember(c) {
var numberString = String(c)
while let c = peek(), numberCharacterSet.isMember(c) {
numberString += String(c)
get()
}
if let value = Double(numberString) {
return .value(CGFloat(value))
}
}
return nil
}
Attempts to grab the next UnicodeScalar
element of string
. It keeps track of the current place in string
by updating range
.
func get() -> UnicodeScalar? {
guard range.lowerBound != range.upperBound else {
return nil
}
let c = string.unicodeScalars[range.lowerBound]
range = string.unicodeScalars.index(after: range.lowerBound)..<range.upperBound
return c
}
Looks at the next UnicodeScalar
in the string without updating range
.
func peek() -> UnicodeScalar? {
guard range.lowerBound != range.upperBound else {
return nil
}
return string.unicodeScalars[range.lowerBound]
}
Skips separator tokens.
func skipSeparators() {
while range.lowerBound != range.upperBound && separatorCharacterSet.isMember(string.unicodeScalars[range.lowerBound]) {
range = string.unicodeScalars.index(after: range.lowerBound)..<range.upperBound
}
}
result(s) found for “”.