Edited at
iOSDay 3

ルビを振る

More than 3 years have passed since last update.

 やっはろー。iOS 8 より Core Text に CTRubyAnnotationRef が追加され、文字列にルビを振れるようになりました、という話をします。

 Core Text の CTRubyAnnotationCreate を使って CTRubyAnnotationRef をつくります。アライメント・サイズ・位置の設定ができますが、普通に使う分には自動で良いと思います。

var text = [.passRetained(ruby) as Unmanaged<CFStringRef>?, .None, .None, .None]

let annotation = CTRubyAnnotationCreate(.Auto, .Auto, 0.5, &text)

あとは、kCTRubyAnnotationAttributeName とペアで NSAttributeString に渡し、Core Graphic で描画します。

import UIKit

class View: UIView {
override func drawRect(rect: CGRect) {
let string = "桐間紗路"
let ruby = "シャロ"

var text = [.passRetained(ruby) as Unmanaged<CFStringRef>?, .None, .None, .None]
let annotation = CTRubyAnnotationCreate(.Auto, .Auto, 0.5, &text)

let attributed = NSAttributedString(string: string, attributes: [
NSFontAttributeName: UIFont(name: "HiraMinProN-W6", size: 50.0)!,
NSForegroundColorAttributeName: UIColor(red: 0.788, green: 0.522, blue: 0.337, alpha: 1.0),
kCTRubyAnnotationAttributeName as String: annotation,
])

let size = attributed.boundingRectWithSize(
CGSizeMake(rect.width, rect.width),
options: .UsesLineFragmentOrigin,
context: nil)

let context = UIGraphicsGetCurrentContext()

CGContextSetRGBFillColor(context, 0.984, 0.922, 0.69, 1.0)
CGContextAddRect(context, rect)
CGContextFillPath(context)

CGContextTranslateCTM(context, (rect.width - size.width) / 2.0, 200.0)
CGContextScaleCTM(context, 1.0, -1.0)

let line = CTLineCreateWithAttributedString(attributed)
CTLineDraw(line, context!)
}
}

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

self.view = View()
}
}

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

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

 尺が余ったので名詠式やります。ルビの振りは、青空文庫における表記を真似て|文章《読み》としています。

import UIKit

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

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

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

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

var text = [.passRetained(ruby) as Unmanaged<CFStringRef>?, .None, .None, .None]
let annotation = CTRubyAnnotationCreate(.Auto, .Auto, 0.5, &text)

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

var height = 28.0
let settings = [
CTParagraphStyleSetting(
spec: .MinimumLineHeight,
valueSize: Int(sizeofValue(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()

CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 1.0)
CGContextAddRect(context, rect)
CGContextFillPath(context)

CGContextRotateCTM(context, CGFloat(M_PI_2))
CGContextTranslateCTM(context, 30.0, 35.0)
CGContextScaleCTM(context, 1.0, -1.0)

let framesetter = CTFramesetterCreateWithAttributedString(attributed)
let path = CGPathCreateWithRect(CGRectMake(0.0, 0.0, rect.height, rect.width), nil)
let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, nil)
CTFrameDraw(frame, context!)
}
}

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

self.view = View()
}
}

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

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