Core Text で縦書き
Core Text でテキストを描画するには
CTFrameDraw(_:_:)
を呼べばいいのだけど、そのパラメータをどうつくるかという話。
FrameDraw 第2引数の CGContext
は UIGraphicsGetCurrentContext()
で得られる。
FrameDraw 第1引数の CTFrame
は
CTFramesetterCreateFrame(_:_:_:_:)
で作れる。
CreateFrame 第3引数
の CGPath
は UIBezierPath
から作れるけど、
CGRect
からなら CGPath(rect:transform:)
のほうが早い。
CreateFrame 第1引数の CTFramesetter
は
CTFramesetterCreateWithAttributedString(_:)
で作れる。
引数の CFAttributedString
は API ドキュメントによると
CFAttributedString is “toll-free bridged” with its Foundation counterpart, NSAttributedString.
とのことなので NSAttributedString
を使えば良いようである。
let astr = NSAttributedString(string: string) // アトリビュートなし
アトリビュートに縦書きを指定するには、
let astr = NSAttributedString(string: string, attributes: [.verticalGlyphForm: true])
CTFrame
を作るところだけまとめると、
func createFrame(string: String, rect: CGRect) -> CTFrame? { let astr = NSAttributedString(string: string, attributes: [.verticalGlyphForm: true]) let setter = CTFramesetterCreateWithAttributedString(astr) let path = CGPath(rect: rect, transform: nil) return CTFramesetterCreateFrame(setter, CFRange(), path, nil) }
描画はこうなる。
let frame = createFrame(string: string, rect: rect)! let context = UIGraphicsGetCurrentContext()! CTFrameDraw(frame, context)
赤い枠線は rect を描画したもの。
文字が裏返っている理由は、
Core Textは LLO(Lower Left Origin)の座標系で描くのだけど、
UIKit 由来の CGContext
は ULO (Upper Left Origin) の座標系だから。
LLOで描いたものがULOに正しく描画されるように、座標変換させる必要がある。
座標変換するときは、行儀よく保存・復帰させよう。
context.saveGState() defer { context.restoreGState() }
LLO→ULO座標変換はy軸を反転させるのがふつうなので
context.scaleBy(x: 1, y: -1) // 上下反転(LLO→ULO)
変換後の座標系での rect を点線で描いてある。
裏返るのは直ったけど、縦書きの向きにしたいので90度回転させる。
context.rotate(by: CGFloat.pi/2) // 90度回転 context.scaleBy(x: 1, y: -1) // 上下反転(LLO→ULO)
順序はこの通りでないといけない。
90度回転させると枠の縦横が入れ替わってしまうので、rect を LLO の座標系に逆変換してやらねばなるまい。
まず、一度 Affine 変換をつくってCTMに結合するやりかたに変更する。
let transform = CGAffineTransform(rotationAngle: CGFloat.pi/2).scaledBy(x: 1, y: -1) context.concatenate(transform)
逆変換したrectで描くと、正位置に出るはず。
let frame = createFrame(string: string, rect: rect.applying(transform.inverted()))!
さて調子に乗って NSString.draw(at:withAttributes:)
と混ぜると大変なことになる
これは NSString.draw(at:withAttributes:)
が CGContext.textMatrix
を変更するのだけど、元に戻してくれないため。
そして CTFrameDraw(_:_:)
が CGContext.textMatrix
に従って描画するため、だから。
しかたがないので自分で元に戻してやる。というかそもそもそういうもののようである。
context.textMatrix = CGAffineTransform.identity
まとめるとこんなかんじ。
func drawVText(context: CGContext, string: String, rect: CGRect) { context.saveGState() defer { context.restoreGState() } let transform = CGAffineTransform(rotationAngle: CGFloat.pi/2).scaledBy(x: 1, y: -1) context.concatenate(transform) let frame = createFrame(string: string, rect: rect.applying(transform.inverted()))! context.textMatrix = CGAffineTransform.identity CTFrameDraw(frame, context) } func createFrame(string: String, rect: CGRect) -> CTFrame? { let astr = NSAttributedString(string: string, attributes: [.verticalGlyphForm: true]) let setter = CTFramesetterCreateWithAttributedString(astr) let path = CGPath(rect: rect, transform: nil) return CTFramesetterCreateFrame(setter, CFRange(), path, nil) }
さてASCIIな文字を混ぜたりするとちょっと期待と異なるので、調整してやらねばならないのだけれども、 たぶん次回に続く