LoginSignup
104
96

More than 3 years have passed since last update.

ルビを振る

Last updated at Posted at 2014-12-02

 やっはろー。iOS 8から文字列にルビを振ることができるようになりました。Core Text の CTRubyAnnotation (CTRubyAnnotationRef) を使います。

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,
])

例です。

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)

カフェインハイテンション

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

 ということで名詠式をやります。ルビの振りは青空文庫における表記を真似て |文字列《ルビ》 としています。

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 なのは、ルビの有無で行の高さが変わってつらい感じになるからです。どうして。

まさか、後罪の触媒を〈讃来歌〉無しで?

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

104
96
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
104
96