Skip to content

Instantly share code, notes, and snippets.

@KentarouKanno
Last active March 10, 2018 19:07
Show Gist options
  • Save KentarouKanno/2f404e94523afe16f967dbd5ab65c01b to your computer and use it in GitHub Desktop.
Save KentarouKanno/2f404e94523afe16f967dbd5ab65c01b to your computer and use it in GitHub Desktop.
CoreText

CoreText

ルビを振る

★ 縦書き文章にルビをふる

import UIKit

extension String {
    func find(pattern: String) -> NSTextCheckingResult? {
        do {
            let re = try NSRegularExpression(pattern: pattern, options: [])
            return re.firstMatch(
                in: self,
                options: [],
                range: NSMakeRange(0, self.utf16.count))
        } catch {
            return nil
        }
    }

    func replace(pattern: String, template: String) -> String {
        do {
            let re = try NSRegularExpression(pattern: pattern, options: [])
            return re.stringByReplacingMatches(
                in: self,
                options: [],
                range: NSMakeRange(0, self.utf16.count),
                withTemplate: template)
        } catch {
            return self
        }
    }
}

class View: UIView {

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

        let attributed =
            text
                .replace(pattern: "(|.+?《.+?》)", template: ",$1,")
                .components(separatedBy: ",")
                .map { x -> NSAttributedString in
                    if let pair = x.find(pattern: "|(.+?)《(.+?)》") {
                        let string = (x as NSString).substring(with: pair.rangeAt(1))
                        let ruby = (x as NSString).substring(with: pair.rangeAt(2))

                        var text: [Unmanaged<CFString>?] = [Unmanaged<CFString>.passRetained(ruby as CFString) as Unmanaged<CFString>, .none, .none, .none]

                        let annotation = CTRubyAnnotationCreate(.auto, .auto, 0.5, &text[0]!)

                        return NSAttributedString(
                            string: string,
                            attributes: [kCTRubyAnnotationAttributeName as String: annotation])
                    } else {
                        return NSAttributedString(string: x, attributes: nil)
                    }
                }
                .reduce(NSMutableAttributedString()) { $0.append($1); return $0 }

        var height = 28.0
        let settings = [
            CTParagraphStyleSetting(
                spec: .minimumLineHeight,
                valueSize: Int(MemoryLayout.size(ofValue: height)),
                value: &height)
        ]
        let style = CTParagraphStyleCreate(settings, Int(settings.count))

        attributed.addAttributes([
            NSFontAttributeName: UIFont(name: "HiraMinProN-W3", size: 14.0)!,
            NSVerticalGlyphFormAttributeName: true,
            kCTParagraphStyleAttributeName as String: style,
            ],
                                 range: NSMakeRange(0, attributed.length))

        let context = UIGraphicsGetCurrentContext()

        context!.setFillColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
        context!.addRect(rect)
        context?.fillPath()

        context!.rotate(by: CGFloat(M_PI_2))
        context!.translateBy(x: 30.0, y: 35.0)
        context!.scaleBy(x: 1.0, y: -1.0)

        let framesetter = CTFramesetterCreateWithAttributedString(attributed)
        let path = CGPath(rect: CGRect(x: 0.0, y: 0.0, width: rect.height, height: rect.width), transform: nil)
        let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, nil)
        CTFrameDraw(frame, context!)
    }
}

class ViewController: UIViewController {
    override func loadView() {
        super.loadView()

        self.view = View()
    }
}

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment