↑クリックして拡大
↑クリックして拡大
↑クリックして拡大
↑クリックして拡大

頭痛が減ったので共有です!

rebuild.fmを応援しています!

HOME > CoreTextをイラストで理解してみる

CoreTextをイラストで理解してみる

CoreTextを過去2記事で紹介しましたが、まだ正しく理解できていませんので、 今回イラストをつかいつつ理解を深めていきたいと思います。

サンプル画像

上記がAppleのCoreText説明に記載されているイラスト図ですが正直よくわかりません。文章を三角や丸等の領域に表示したり、一行だけ表示したり、一文字だけ表示したり、 と様々な使い方ができるようです。

参考:iOSで文字を組む
参考:CoreTextを調べてみた
参考:Text Programming Guide for iOS
参考:About Core Text



以下の単語がまず理解できていないので一つずつ調べてみます

CTFramesetter

まずCTFramesetter。これがCoreTextのトップレベルに位置する型のようです。内部的に次の重要単語の CTTypesetterやCGPathを利用してCTFrameに出力するようで複数行にまたがる文章でも「一定の領域(丸や三角等)」に流し込んで 勝手にこのクラスが処理してくれる様です。自分で書いていてしっくりしませんので、一旦サンプルコードを記載してみます。

参考:Laying Out a Paragraph


import CoreText
import UIKit

class View: UIView {
    
    override func drawRect(rect: CGRect) {
        
        // 初期化
        let context:CGContextRef = UIGraphicsGetCurrentContext();
        
        CGContextTranslateCTM(context, 0, self.bounds.size.height);//これは必要
        CGContextScaleCTM(context, 1.0, -1.0);// 反転させる。(これをしないと文字が逆立ちします)
        CGContextSetTextMatrix(context, CGAffineTransformIdentity);// これはアフィン変換する前の状態にもどすようです。特別この前に処理していないのであれば必要ないのかも?
        
        // 描画したい領域を作成します。以下のは四角
        let path:CGMutablePathRef = CGPathCreateMutable();
        let bounds:CGRect = CGRectMake(10.0, 10.0, 300.0, 300.0);
        CGPathAddRect(path, nil, bounds );
        
        // 書き込む文字列を作成
        let tmpString:NSString = "こんにちはSwiftサラリーマンです。今回はCoreTextを学んでいるのですが難しい、Swiftのサンプルが少ないので苦戦中です";
        let textString:CFStringRef = tmpString as CFString;
        let textStringLen = tmpString.length;
        
        // ここで装飾するかを判断します。
        let attrString:CFMutableAttributedStringRef = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
        CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0),textString);
        
        // フォントの色を赤色にしたりする場合はattStringを変更していく。
        /*
        let rgbColorSpace:CGColorSpaceRef = CGColorSpaceCreateDeviceRGB();
        var components:[CGFloat] = [ 1.0, 0.0, 0.0, 0.8 ];
        let red:CGColorRef = CGColorCreate(rgbColorSpace, components);
        CFAttributedStringSetAttribute(attrString, CFRangeMake(0, textStringLen),kCTForegroundColorAttributeName, red);
        */
        
        // やっと出てきましたCTFramesetter。
        let framesetter :CTFramesetterRef = CTFramesetterCreateWithAttributedString(attrString);
        
        // CTFrameを作成します。framesetterを入れてやる必要があります。CTFramesetterとCTFrameは二つで一つでしょうか。
        let frame:CTFrameRef = CTFramesetterCreateFrame(framesetter,CFRangeMake(0, 0), path, nil);
        
        // 上記の内容を描画します。
        CTFrameDraw(frame, context);
        

    }
}

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

        self.view = View()
        self.view.backgroundColor = UIColor.whiteColor()
    }
}

サンプル画像

こんな感じに表示されます。この文字を利用して図を作ってみます。

CTFramesetterとCTFrameの関係は こんな感じ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

サンプル画像

さらに見えない内部的な関係は こんな感じ
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

サンプル画像

他にもCTRun等ありますが、ここまでです。順に理解を深めていきます。私もまだわかっていません

CTTypesetter

まずAppleのリファレンスをなんちゃって翻訳してみます


Line layout includes word wrapping, hyphenation, and line breaking in either vertical 
or horizontal rectangles. A typesetter object takes as input an attributed string 
and produces a line of typeset glyphs (composed into glyph runs) in a CTLine object. 
改行、ハイフン、行間の縦書き横書き両方の行レイアウト。Typesetterオブジェクトは装飾文字列のインプットと
Glyaphsの集合のラインを提供します。(GraphのRunsに)、CTLineのオブジェクトに対して。

The typesetter performs character-to-glyph encoding, glyph ordering, and positional operations, 
such as kerning, tracking, and baseline adjustments. If multiline layout is needed, 
it is performed by a framesetter object, which calls into the typesetter to generate 
the typeset lines to fill the frame.
Typsetterは文字を画像にするエンコーディング、グラフの順番、位置の調整(カーニング、tラッキングやベースライン)。
もし複数行のレイアウトが必要だった場合はFramesetterオブジェクトを利用します。それらはTypesetterを作成して
複数行にフレーム内に出力します。

A framesetter encapsulates a typesetter and provides a reference to it as a convenience, 
but a caller may also choose to create a freestanding typesetter.
FramesetterはTypesetterを内臓していて、それに対して参照します(簡単なものとして)
ただし、呼び手はおそらく柔軟なTypesetterを使うこともするでしょう。

これをみると、Framesetterの中にTypesetterがあって、柔軟に利用するにはTypesetterを利用 べきだよ、といっているのではと思われます。一行を表示するCTLineをレイアウトするのがCTTypesetterのようです。

サンプル画像

参考:CTTypesetter Reference

CTLine

上でCTLineも記載しましたがCTLineも同じくAppleのリファレンスを翻訳してみます。


The CTLine opaque type represents a line of text.
CTLineはテキストの文字列のことを表現します。

A CTLine object contains an array of glyph runs. 
Line objects are created by the typesetter during a framesetting operation 
and can draw themselves directly into a graphics context.
CTLineオブジェクトはGlaph Runsの配列を含みます。
LineオブジェクトはTypesetterによって作成されます(フレームセッティング処理中において)
そしてグラフィックコンテンツに直接描画します

サンプル画像

参考:CTLine Reference

このCTRunやGlyphがいまいち理解できていません。CTFramesetterとCTTypesetterとCTLineは明確になってきた気がします。以下CTLineのサンプルです。先ほどのCTFrameとほぼ同じで描画箇所をCTLineDrawしています。


import CoreText
import UIKit

class View: UIView {
    
    override func drawRect(rect: CGRect) {
        
        // 初期化
        let context:CGContextRef = UIGraphicsGetCurrentContext();
        
        CGContextTranslateCTM(context, 0, self.bounds.size.height);//これは必要
        CGContextScaleCTM(context, 1.0, -1.0);// 反転させる。(これをしないと文字が逆立ちします)
        CGContextSetTextMatrix(context, CGAffineTransformIdentity);// これはアフィン変換する前の状態にもどすようです。特別この前に処理していないのであれば必要ないのかも?
        
        // 描画したい領域を作成します。以下のは四角
        let path:CGMutablePathRef = CGPathCreateMutable();
        let bounds:CGRect = CGRectMake(10.0, 10.0, 300.0, 300.0);
        CGPathAddRect(path, nil, bounds );
        
        // 書き込む文字列を作成
        let tmpString:NSString = "こんにちはSwiftサラリーマンです。今回はCoreTextを学んでいるのですが難しい、Swiftのサンプルが少ないので苦戦中です";
        let textString:CFStringRef = tmpString as CFString;
        let textStringLen = tmpString.length;
        
        // ここで装飾するかを判断します。
        let attrString:CFMutableAttributedStringRef = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
        CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0),textString);
        
        //CTLine
        let line:CTLineRef = CTLineCreateWithAttributedString(attrString);
        CTLineDraw(line, context);
        
        
    }
}

class ViewController: UIViewController {
    override func loadView() {
        super.loadView()
        
        self.view = View()
        self.view.backgroundColor = UIColor.whiteColor()
    }
}
        

サンプル画像

CTRun

再度リファレンス確認です。


The CTRun opaque type represents a glyph run, 
which is a set of consecutive glyphs sharing the same attributes and direction.
CTRunはGlyph Runを表現します。
こちらは連続するGlyphの同じ文字装飾の方向が同じ要素を共有のセットです

The typesetter creates glyph runs as it produces lines 
from character strings, attributes, and font objects. 
That is, a line is constructed of one or more glyphs runs. 
Glyph runs can draw themselves into a graphic context, 
if desired, although most users have no need to interact directly with glyph runs.
TypesetterはGlpyh Runsをラインを作成する際に実施します。
キャラクターの文字列、装飾やフォントオブジェクト。

つまり、ラインはGlyph Runsの一つや複数の集合です。
Glyph Runsはそれらをグラフィックコンテンツに描画します。
要求されるのであれば、Glyph Runsに実行されていく必要が多くのユーザにはありませんが。

グラフィックのグラフ(Graphic=Graph)かと勘違いしていました。Glyph(=絵を使った標識)なんですね、、、間違っていました。さて、 CTTypesetterを要素に持つ最小の単位の一つ一つ?なのかな。どうやら基本的に自分で作成することはなく、NSAttributeStringを追加すると自動的に作られるようです。CTRunは一文字だけではなく、 CTRunは複数文字にも対応しているような記載です。わからないのでサンプルコードを見直してみます。

参考:Simple Text Label

CTRunは自動で作成されるとのことですので、確認。CTLineをdebugで出力すると。(文字に[I am a swift-salaryman. who are you?]と入れた状態で)

サンプル画像


Printing description of line:
<CTLine: 0x7fbb2bcf0e70>{run count = 1, string range = (0, 36), 
    width = 201.404, A/D/L = 9.24023/2.75977/0, glyph count = 36, runs = (
        <CTRun: 0x7fbb2bce6300>
        {string range = (0, 36), string = "I am a swift-salaryman. Who are you?", attributes = 
            <CFBasicHash 0x7fbb2bce61e0 [0x108b70180]>
            {type = mutable dict, count = 1, entries =>2 : 
                    <CFString 0x10905cc50 [0x108b70180]> {contents = "NSFont"} = 
                    <CTFont: 0x7fbb2bd2edd0>{name = Helvetica, size = 12.000000, matrix = 0x0, descriptor = 
                        <CTFontDescriptor: 0x7fbb2bd2f020>{attributes = 
                            <CFBasicHash 0x7fbb2bd2eee0 [0x108b70180]>{type = mutable dict, count = 1,entries =>2 : 
                                <CFString 0x109061550 [0x108b70180]>{contents = "NSFontNameAttribute"} = 
                                <CFString 0x109056e30 [0x108b70180]>{contents = "Helvetica"}
                            }
                        >}
                    }
                }
            }
    )
}

上記のような情報が、CTLineの中にCTRunの情報が既に含まれているようです。文字装飾情報やフォント情報等がCTLineに格納されていているのですね。 文字フォントが勝手にHelveticaになってます。

CGGlyph

こちらは文字情報の最小限の単位のようです。つまりCTLineはCGGlyphの集合体。上記のアウトプットの中にあるCTLineのプロパティ値のglyph count = 36の Glyphかと思われます。36個のGlyph=36文字。


Printing description of line:
<CTLine: 0x7fbb2bcf0e70>{run count = 1, string range = (0, 36), 
    width = 201.404, A/D/L = 9.24023/2.75977/0, glyph count = 36, runs = (
...

CTFont

こちらは上記のアウトプットの中のHelvetica情報のCTFontですね、ここを調整すればフォントサイズを変えたりフォント種類を変更できるようです。

CFAttributedString

CTFramesetterを作成する際やCTLineを初期化する際に、AttributedStringを引数でいれることができますので、 装飾したい時は調整できるようです。ここを調整することでルビをふったりもできます

CGPath

特定の形状のパスの中に表示する為の型です。上記CTFramesetterサンプルの以下描画箇所です。上記では このpathに四角形を指定していました。


        let frame:CTFrameRef = CTFramesetterCreateFrame(framesetter,CFRangeMake(0, 0), path, nil);
        

このPathを円に変更してみます。


import CoreText
import UIKit

class View: UIView {
    
    override func drawRect(rect: CGRect) {
        
        // 初期化
        let context:CGContextRef = UIGraphicsGetCurrentContext();
        
        CGContextTranslateCTM(context, 0, self.bounds.size.height);//これは必要
        CGContextScaleCTM(context, 1.0, -1.0);// 反転させる。(これをしないと文字が逆立ちします)
        CGContextSetTextMatrix(context, CGAffineTransformIdentity);// これはアフィン変換する前の状態にもどすようです。特別この前に処理していないのであれば必要ないのかも?
        
        // 描画したい領域を作成します。以下のは丸
        let path:CGMutablePathRef = CGPathCreateMutable();
        CGPathMoveToPoint(path, nil, 100, 100);
        CGPathAddLineToPoint(path, nil, 200, 100);
        CGPathAddArc(path, nil, 100, 100, 100, 0, CGFloat(360 * M_PI)/180, false);
        CGPathCloseSubpath(path);
        
        // 書き込む文字列を作成
        let tmpString:NSString = "こんにちはSwiftサラリーマンです。今回はCoreTextを学んでいるのですが難しい、Swiftのサンプルが少ないので苦戦中です。こんにちはSwiftサラリーマンです。今回はCoreTextを学んでいるのですが難しい、Swiftのサンプルが少ないので苦戦中です。こんにちはSwiftサラリーマンです。今回はCoreTextを学んでいるのですが難しい、Swiftのサンプルが少ないので苦戦中です";
        let textString:CFStringRef = tmpString as CFString;
        let textStringLen = tmpString.length;
        
        // ここで装飾するかを判断します。
        let attrString:CFMutableAttributedStringRef = CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
        CFAttributedStringReplaceString (attrString, CFRangeMake(0, 0),textString);
        
        // やっと出てきましたCTFramesetter。
        let framesetter :CTFramesetterRef = CTFramesetterCreateWithAttributedString(attrString);
        
        // CTFrameを作成します。framesetterを入れてやる必要があります。CTFramesetterとCTFrameは二つで一つでしょうか。
        let frame:CTFrameRef = CTFramesetterCreateFrame(framesetter,CFRangeMake(0, 0), path, nil);
        
        // 上記の内容を描画します。
        CTFrameDraw(frame, context);
        
        
    }
}

class ViewController: UIViewController {
    override func loadView() {
        super.loadView()
        
        self.view = View()
        self.view.backgroundColor = UIColor.whiteColor()
    }
}

サンプル画像

円が表示されました!

まとめ

なんだかバタバタと進めましたが、Coretextを少しだけは理解できた気がします。もっと複雑なこともできるようですので、 機会があれば記載していけたらと思います。

前の記事はこちらです!

↓こんな記事もありますよ!


2021-05-14 14:21:41

WatchOSのwatchconnectivityのFiletransferの落とし穴。と、避け方。

AppleWatch 実機だと成功するんだけど、シュミレーターだと失敗するという、、、 昔作成してた時は成功してたのになーと思って調べると、どうやら昔は成功してたみたい。watchOS6以降は...

2021-05-06 14:04:37

LINEのアニメーションスタンプ制作の落とし穴、、、失敗談

ゴールデンウィークにLINEスタンプを作成してみました。 作り切って申請も通したんですが、意図したアニメーションと違う、、、、 LINEクリエーターの画面だと、アニメーションのプレビュー...

2021-05-01 18:05:35

久しぶりのAdmobをobjective-cに実装。コンパイルエラーだらけ。バーミッション不具合でエミュレータにインスコできない。

忘れないようにメモ エミュレータにアプリをインストールする際にパーミッション系のエラーがでた時、また、iphone実機にインストールする際にも権限系のエラーが出る場合。 ターゲット→ex...
このエントリーをはてなブックマークに追加
右側のFacebookのLikeをクリック頂けると記事更新の際に通知されますので宜しければご利用下さい!