Asian Vertical Text

Hi everyone,

for my app that is a japanese dictionary, I need to display some text in vertical way. looking for in internet about this topic, I’ve found this answer.

in short word I need to subclass NSTextContainer, and I will have to deal with NSLayoutManager and NSTextContainer a lot.

but after had subclass the NSTextContainer class, what I have to do? how can I write some asian (Chinese or Japanese) text in vertical way?

UPDATE: finally I’ve found a solution but I’ve had to use Core Text and not Text Kit.

and this is the what I’ve done, but I’ve 2 problem:

1)I would like that the text use break line mode if the text is going out of the context. how can I do it?
2) I’m creating an uiview that show asian text (preferably japanese) with phonetic guide (in japanese: furigana). but when the app go in landscape, the view, because there are constrains fits to superview but the text is stretched. How can I solve the problem?

this is code:

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
        }
    }
}

enum textOrientation {
    case horizontal
    case vertical
    
}

class GQAsianTextView: UIView {
    var text:String = ""

    
    
    var orientation:textOrientation!
    
    // Only override draw() if you perform custom drawing.
    // An empty implementation adversely affects performance during animation.
    override func draw(_ rect: CGRect) {
        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(CTRubyAlignment.auto, CTRubyOverhang.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))
        
        
        
        switch orientation! {
        case .horizontal:
            attributed.addAttributes([
                NSFontAttributeName: UIFont(name: "DFKyoKaSho-W4-WIN-RKSJ-H", size: 20.0)!,
                NSVerticalGlyphFormAttributeName: false,
                kCTParagraphStyleAttributeName as String: style,
                ],
                                     range: NSMakeRange(0, attributed.length))
        case .vertical:
            attributed.addAttributes([
                NSFontAttributeName: UIFont(name: "DFKyoKaSho-W4-WIN-RKSJ-H", size: 20.0)!,
                NSVerticalGlyphFormAttributeName: true,
                kCTParagraphStyleAttributeName as String: style,
                ],
                                     range: NSMakeRange(0, attributed.length))
        }
        /*
        attributed.addAttributes([
            NSFontAttributeName: UIFont(name: "DFKyoKaSho-W4-WIN-RKSJ-H", size: 20.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!.setFillColor(red: 0.984, green: 0.922, blue: 0.69, alpha: 1.0)
        context!.addRect(rect)
        context?.fillPath()
        
        switch orientation! {
        case .horizontal:
            context!.translateBy(x: 30.0, y: 420.0)
            context!.scaleBy(x: 1.0, y: -1.0)
        case .vertical:
            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!)
    }
}

in MyViewController

let asianText = GQAsianTextView(frame: CGRect(x: 0, y: 64, width: 420, height: 400))
        asianText.text = [
            "「まさか、|後罪《クライム》の|触媒《カタリスト》を〈|讃来歌《オラトリオ》〉無しで?」",
            "|教師《きょうし》たちの狼狽した声が次々と上がる。",
            "……なんでだろう。何を驚いているんだろう。",
            "ただ普通に、この|触媒《カタリスト》を使って|名詠門《チャネル》を開かせただけなのに。",
            "そう言えば、何を|詠《よ》ぼう。",
            "自分の一番好きな花でいいかな。",
            "どんな宝石より素敵な、わたしの大好きな緋色の花。",
            "――『|Keinez《赤》』――",
            "そして、少女の口ずさんだその後に――",
            ]
            .joined(separator: "\n")
        
        asianText.orientation = textOrientation.vertical
        asianText.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(asianText)
        
        let viewDictionary = ["asianLabel":asianText]
        let asianText_POS_H = NSLayoutConstraint.constraints(withVisualFormat: "H:|-8-[asianLabel]-8-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
        let asianText_POS_V = NSLayoutConstraint.constraints(withVisualFormat: "V:|-64-[asianLabel]-8-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewDictionary)
        
        
        
        self.view.addConstraints(asianText_POS_H)
        self.view.addConstraints(asianText_POS_V)

These are the problems: stretched character and no breaking line mode

Can you help me?

thank you very much