ルビを振る

　やっはろー。iOS 8から文字列にルビを振ることができるようになりました。Core Text の [CTRubyAnnotation](https://developer.apple.com/documentation/coretext/ctrubyannotation) (CTRubyAnnotationRef) を使います。

```swift
let ruby = "ルビ"
let string = "文字列"

let annotation = CTRubyAnnotationCreateWithAttributes(.auto, .auto, .before, ruby as CFString, [
    kCTRubyAnnotationSizeFactorAttributeName: 0.5,
] as CFDictionary)

let annotatedString = NSAttributedString(string: string, attributes: [
    kCTRubyAnnotationAttributeName as NSAttributedString.Key: annotation,
])
```

例です。

```swift
import UIKit
import PlaygroundSupport

extension NSAttributedString.Key {
    static let rubyAnnotation: NSAttributedString.Key = kCTRubyAnnotationAttributeName as NSAttributedString.Key
}

extension UIColor {
    static let foregroundColor: UIColor = UIColor(red: 0.788, green: 0.522, blue: 0.337, alpha: 1.0)
    static let backgroundColor: UIColor = UIColor(red: 0.984, green: 0.922, blue: 0.69, alpha: 1.0)
}

class View: UIView {
    lazy var attributedString: NSAttributedString = {
        let string = "桐間紗路"
        let ruby = "シャロ"
        
        let annotation = CTRubyAnnotationCreateWithAttributes(.auto, .auto, .before, ruby as CFString, [
            kCTForegroundColorAttributeName: UIColor.foregroundColor,
            kCTRubyAnnotationSizeFactorAttributeName: 0.5,
        ] as CFDictionary)

        let annotatedString = NSAttributedString(string: string, attributes: [
            .font: UIFont(name: "HiraMinProN-W6", size: 60.0)!,
            .foregroundColor: UIColor.foregroundColor,
            .rubyAnnotation: annotation,
        ])
        
        return annotatedString
    }()

    override func draw(_ rect: CGRect) {
        let size = attributedString.boundingRect(with: rect.size,
                                                 options: .usesDeviceMetrics,
                                                 context: nil)

        guard let context = UIGraphicsGetCurrentContext() else {
            return
        }

        context.setFillColor(UIColor.backgroundColor.cgColor)
        context.fill(rect)

        context.scaleBy(x: 1, y: -1)
        context.translateBy(x: (rect.width - size.width) / 2, y: -1 * (rect.height - size.height) / 2)

        let line = CTLineCreateWithAttributedString(attributedString)
        CTLineDraw(line, context)
    }
}


let frame = UIScreen.main.bounds.applying(CGAffineTransform(scaleX: 0.5, y: 0.5))
PlaygroundPage.current.liveView = View(frame: frame)
```

![カフェインハイテンション](https://qiita-image-store.s3.amazonaws.com/0/4250/e2fbf4ff-d020-cf4b-e0ea-bf68315b49f3.png)

楽しい！₍₍ (ง╹◡╹)ว ⁾⁾

　ということで名詠式をやります。ルビの振りは[青空文庫における表記](https://www.aozora.gr.jp/KOSAKU/MANUAL_2.html#ruby)を真似て `｜文字列《ルビ》` としています。
 
```swift
import UIKit
import PlaygroundSupport

extension NSAttributedString.Key {
    static let rubyAnnotation: NSAttributedString.Key = kCTRubyAnnotationAttributeName as NSAttributedString.Key
}

extension NSMutableAttributedString {
    func addAttributes(_ attrs: [NSAttributedString.Key: Any] = [:]) {
        addAttributes(attrs, range: NSRange(string.startIndex ..< string.endIndex, in: string))
    }
}

extension NSRegularExpression {
    func matches(in string: String, options: NSRegularExpression.MatchingOptions = []) -> [NSTextCheckingResult] {
        return matches(in: string, options: options, range: NSRange(string.startIndex ..< string.endIndex, in: string))
    }
}

class View: UIView {
    let fontSize: CGFloat = 16
    var lineHeight: CGFloat { fontSize * 1.8 }

    lazy var attributedString: NSAttributedString = {
        let text = """
        「まさか、｜後罪《クライム》の｜触媒《カタリスト》を〈｜讃来歌《オラトリオ》〉無しで？」
        教師たちの狼狽した声が次々と上がる。
        ……なんでだろう。何を驚いているんだろう。
        ただ普通に、この｜触媒《カタリスト》を使って｜名詠門《チャネル》を開かせただけなのに。
        そう言えば、何を｜詠《よ》ぼう。
        自分の一番好きな花でいいかな。
        どんな宝石より素敵な、わたしの大好きな緋色の花。
        ――『｜Keinez《赤》』――
        そして、少女の口ずさんだその後に――
        """

        let attributedString = NSMutableAttributedString(string: text)

        for result in try! NSRegularExpression(pattern: "｜(.+?)《(.+?)》").matches(in: text).reversed() {
            if let string = Range(result.range(at: 1), in: text).map({ String(text[$0]) }),
                let ruby = Range(result.range(at: 2), in: text).map({ String(text[$0]) }) {
                let annotation = CTRubyAnnotationCreateWithAttributes(.auto, .auto, .before, ruby as CFString, [
                    kCTRubyAnnotationSizeFactorAttributeName: 0.5,
                ] as CFDictionary)
                let annotatedString = NSAttributedString(string: string, attributes: [
                    .rubyAnnotation: annotation,
                ])

                attributedString.replaceCharacters(in: result.range, with: annotatedString)
            }
        }

        attributedString.addAttributes([
            .font: UIFont(name: "HiraMinProN-W3", size: fontSize)!,
            .verticalGlyphForm: true,
        ])

        return attributedString
    }()

    override func draw(_ rect: CGRect) {
        guard let context = UIGraphicsGetCurrentContext() else {
            return
        }

        context.setFillColor(UIColor.white.cgColor)
        context.fill(rect)

        context.rotate(by: .pi / 2)
        context.scaleBy(x: 1, y: -1)
        context.translateBy(x: 0, y: rect.width)

        let framesetter = CTFramesetterCreateWithAttributedString(attributedString)
        let frame = CTFramesetterCreateFrame(framesetter, CFRange(), CGPath(rect: rect, transform: nil), [
            kCTFrameProgressionAttributeName: CTFrameProgression.rightToLeft.rawValue,
        ] as CFDictionary)

        for line in CTFrameGetLines(frame) as! [CTLine] {
            context.translateBy(x: 0, y: -1 * lineHeight)
            CTLineDraw(line, context)
        }
    }
}

let frame = UIScreen.main.bounds.applying(CGAffineTransform(scaleX: 0.5, y: 0.5))
PlaygroundPage.current.liveView = View(frame: frame)
```

`CTFrameDraw` ではなく `CTLineDraw` なのは、ルビの有無で行の高さが変わってつらい感じになるからです。どうして。

![まさか、後罪の触媒を〈讃来歌〉無しで？](https://qiita-image-store.s3.amazonaws.com/0/4250/848f14cf-fe68-6817-302c-88cd01ffc870.png)

楽しい！₍₍ (ง╹◡╹)ว ⁾⁾

- [CTRubyAnnotation - Apple Developer Documentation](https://developer.apple.com/documentation/coretext/ctrubyannotation)
- [Core Text Programming Guide - Mac Developer Library](https://developer.apple.com/library/archive/documentation/StringsTextFonts/Conceptual/CoreText_Programming/)
- [iOS 8 - NSHipster](https://nshipster.com/ios8/)
- [iOS で文字を組む - Gunosy Tech Blog](https://tech.gunosy.io/entry/ios-typesetting)
- [工作員作業マニュアル - 青空文庫](https://www.aozora.gr.jp/KOSAKU/MANU_MOKU.html)
- [TVアニメ『ご注文はうさぎですか?』公式サイト](https://gochiusa.com/)
- [黄昏色の詠使い - Wikipedia](https://ja.wikipedia.org/wiki/%E9%BB%84%E6%98%8F%E8%89%B2%E3%81%AE%E8%A9%A0%E4%BD%BF%E3%81%84)