170 lines
6.1 KiB
Swift
170 lines
6.1 KiB
Swift
//
|
|
// CheatTextView.swift
|
|
// Delta
|
|
//
|
|
// Created by Riley Testut on 5/22/16.
|
|
// Copyright © 2016 Riley Testut. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
import CoreText
|
|
|
|
import DeltaCore
|
|
|
|
import Roxas
|
|
|
|
private extension NSAttributedStringKey
|
|
{
|
|
static let cheatPrefix = NSAttributedStringKey("CheatPrefix")
|
|
}
|
|
|
|
class CheatTextView: UITextView
|
|
{
|
|
var cheatFormat: CheatFormat? {
|
|
didSet {
|
|
self.updateAttributedFormat()
|
|
}
|
|
}
|
|
|
|
@NSCopying fileprivate var attributedFormat: NSAttributedString?
|
|
|
|
required init?(coder aDecoder: NSCoder)
|
|
{
|
|
super.init(coder: aDecoder)
|
|
|
|
self.layoutManager.delegate = self
|
|
|
|
self.textContainer.widthTracksTextView = false
|
|
self.textContainer.heightTracksTextView = false
|
|
}
|
|
}
|
|
|
|
extension CheatTextView
|
|
{
|
|
override func layoutSubviews()
|
|
{
|
|
super.layoutSubviews()
|
|
|
|
if let format = self.cheatFormat, let font = self.font
|
|
{
|
|
let characterWidth = ("A" as NSString).size(withAttributes: [.font: font]).width
|
|
|
|
let width = characterWidth * CGFloat(format.format.count)
|
|
self.textContainer.size = CGSize(width: width, height: 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension CheatTextView
|
|
{
|
|
func updateAttributedFormat()
|
|
{
|
|
guard let format = self.cheatFormat?.format else
|
|
{
|
|
self.attributedFormat = nil
|
|
return
|
|
}
|
|
|
|
let attributedFormat = NSMutableAttributedString()
|
|
var prefixString: NSString? = nil
|
|
|
|
let scanner = Scanner(string: format)
|
|
scanner.charactersToBeSkipped = nil
|
|
|
|
while (!scanner.isAtEnd)
|
|
{
|
|
var string: NSString? = nil
|
|
scanner.scanCharacters(from: CharacterSet.alphanumerics, into: &string)
|
|
|
|
guard let scannedString = string, scannedString.length > 0 else { break }
|
|
|
|
let attributedString = NSMutableAttributedString(string: scannedString as String)
|
|
|
|
if let prefixString = prefixString, prefixString.length > 0
|
|
{
|
|
attributedString.addAttribute(.cheatPrefix, value: prefixString, range: NSRange(location: 0, length: 1))
|
|
}
|
|
|
|
attributedFormat.append(attributedString)
|
|
|
|
prefixString = nil
|
|
scanner.scanUpToCharacters(from: CharacterSet.alphanumerics, into: &prefixString)
|
|
}
|
|
|
|
self.attributedFormat = attributedFormat
|
|
|
|
rst_dispatch_sync_on_main_thread {
|
|
self.setNeedsLayout()
|
|
self.layoutIfNeeded()
|
|
|
|
let range = NSRange(location: 0, length: (self.text as NSString).length)
|
|
self.layoutManager.invalidateGlyphs(forCharacterRange: range, changeInLength: 0, actualCharacterRange: nil)
|
|
self.layoutManager.invalidateLayout(forCharacterRange: range, actualCharacterRange: nil)
|
|
self.layoutManager.ensureGlyphs(forCharacterRange: range)
|
|
self.layoutManager.ensureLayout(forCharacterRange: range)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension CheatTextView: NSLayoutManagerDelegate
|
|
{
|
|
func layoutManager(_ layoutManager: NSLayoutManager, shouldGenerateGlyphs glyphs: UnsafePointer<CGGlyph>, properties props: UnsafePointer<NSLayoutManager.GlyphProperty>, characterIndexes charIndexes: UnsafePointer<Int>, font aFont: UIFont, forGlyphRange glyphRange: NSRange) -> Int
|
|
{
|
|
// Returning 0 = let the layoutManager do the normal logic
|
|
guard let attributedFormat = self.attributedFormat else { return 0 }
|
|
|
|
let glyphCount = glyphRange.length
|
|
|
|
// Ensure the buffer is long enough to hold our additional glyphs
|
|
// If we're only modifying one character, glyphCount * 2 = 2, which is not large enough if we're inserting multiple separator characters
|
|
let bufferSize = max(attributedFormat.length + 1, glyphCount * 2)
|
|
|
|
// Allocate our replacement buffers
|
|
let glyphBuffer = UnsafeMutablePointer<CGGlyph>.allocate(capacity: bufferSize)
|
|
let propertyBuffer = UnsafeMutablePointer<NSLayoutManager.GlyphProperty>.allocate(capacity: bufferSize)
|
|
let characterBuffer = UnsafeMutablePointer<Int>.allocate(capacity: bufferSize)
|
|
|
|
var offset = 0
|
|
|
|
for i in 0 ..< glyphCount
|
|
{
|
|
// The index the actual character maps to in the cheat format
|
|
let characterIndex = charIndexes[i] % attributedFormat.length
|
|
|
|
if let prefix = attributedFormat.attributes(at: characterIndex, effectiveRange: nil)[.cheatPrefix] as? String
|
|
{
|
|
// If there is a prefix string, we insert the glyphs (and associated properties/character indexes) first
|
|
let prefixCount = prefix.count
|
|
|
|
for j in 0 ..< prefixCount
|
|
{
|
|
characterBuffer[i + offset + j] = charIndexes[i]
|
|
propertyBuffer[i + offset + j] = props[i]
|
|
|
|
// Prepend prefix character
|
|
var prefixCharacter = (prefix as NSString).character(at: 0)
|
|
CTFontGetGlyphsForCharacters(aFont as CTFont, &prefixCharacter, glyphBuffer + (i + offset + j), 1)
|
|
}
|
|
|
|
offset += prefixCount
|
|
}
|
|
|
|
// Copy over the information from the original buffers
|
|
characterBuffer[i + offset] = charIndexes[i]
|
|
propertyBuffer[i + offset] = props[i]
|
|
glyphBuffer[i + offset] = glyphs[i]
|
|
}
|
|
|
|
// Replace buffers with our own buffers, and ensure length takes into account any added glpyhs
|
|
layoutManager.setGlyphs(glyphBuffer, properties: propertyBuffer, characterIndexes: characterBuffer, font: aFont, forGlyphRange: NSRange(location: glyphRange.location, length: glyphCount + offset))
|
|
|
|
// Clean up memory
|
|
characterBuffer.deallocate(capacity: bufferSize)
|
|
propertyBuffer.deallocate(capacity: bufferSize)
|
|
glyphBuffer.deallocate(capacity: bufferSize)
|
|
|
|
// Return total number of glyphs
|
|
return glyphCount + offset
|
|
}
|
|
}
|