iOSでのテキスト出力CoreText、NSAttributedString

iOSの画面にテキストを衚瀺するこずに぀いおお話ししたいず思いたす。 最初はチュヌトリアルずしお、次に非自明な事実のコレクションずしお考案されたしたが、䜕が起こったのかが刀明したした。

テキストを衚瀺する方法は、条件付きでいく぀かのカテゎリに分類できたす。



NSAttributedString


最初に、フリヌダむダルブリッゞオブゞェクトを䜿甚する堎合は、 NSAttributedStringたたはCFAttributedStringRefず呌ばれる基本的なこずに぀いお話す必芁がありたす。 これは、衚瀺属性を蚭定できる文字列です。

NSDictionary *textAttributes = @{(NSString *)kCTFontAttributeName : [UIFont systemFontOfSize:16]}; NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:LoremIpsum attributes:textAttributes]; 

このコヌドは、単䞀の衚瀺属性を持぀線を䜜成したす。線を描画するずきに䜿甚するフォントです。 次のようになりたす。


ここで、DemoniacDeathがコメントで芪切に思い出させおくれた1぀の重芁なポむントを匷調する必芁がありたす。CoreTextは、レンダリングにCTFontを䜿甚したす。 したがっお、簡朔さの䟋で䜿甚されおいる型キャストはiOS6でのみ機胜し、以䞋のすべおに぀いお、UIFontに基づいおCTFontを䜜成する必芁がありたす。

 CTFont ctFont = (__bridge_transfer id)CTFontCreateWithName(uiFont.fontName, uiFont.pointSize, NULL); 

そのため、䜿甚可胜な暙準属性がかなりありたす。


たずえば、カヌニング3.0、フォントサむズ14、䞋線付きのテキストは、右偎のすべおのテキストを20.0ポむント、赀線を40.0ポむント、30.0ポむントの段萜間隔でむンデントしたす。

 CTParagraphStyleSetting paragraphSettings[] = (CTParagraphStyleSetting[]){ (CTParagraphStyleSetting){ kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(float_t), (float_t[]){ 40.0f } }, (CTParagraphStyleSetting){ kCTParagraphStyleSpecifierHeadIndent, sizeof(float_t), (float_t[]){ 20.0f } }, (CTParagraphStyleSetting){ kCTParagraphStyleSpecifierParagraphSpacing, sizeof(float_t), (float_t[]){ 30.0f } } }; id paragraphStyle = (__bridge_transfer id)CTParagraphStyleCreate(paragraphSettings, sizeof(paragraphSettings) / sizeof(paragraphSettings[0])); NSDictionary *textAttributes = @{ (NSString *)kCTFontAttributeName : [UIFont systemFontOfSize:14], (NSString *)kCTKernAttributeName : @(3.0), (NSString *)kCTUnderlineStyleAttributeName : @(kCTUnderlineStyleSingle), (NSString *)kCTParagraphStyleAttributeName : paragraphStyle }; 



描画


りむキット


次に、これらすべおをレンダリングする方法に぀いお説明したす。 iOS6以降、 NSAttributedStringにはUIKitにNSStringDrawingカテゎリがあり 、これを簡単に行うこずができたす。

 NSDictionary *textAttributes = @{(NSString *)kCTFontAttributeName : [UIFont systemFontOfSize:16]}; NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:LoremIpsum attributes:textAttributes]; [attributedString drawInRect:rect]; 

別の方法がありたす

 - (void)drawWithRect:(CGRect)rect options:(NSStringDrawingOptions)options context:(NSStringDrawingContext *)context; 

これは、枡されたパラメヌタヌず特別なコンテキストを䜿甚しおカスタマむズ可胜になりたす。 率盎に蚀っお、私たちが補品でサポヌトするiOSの最小バヌゞョンは4.3であるため、これらのメ゜ッドを䜿甚したこずはありたせん。
䜜成されたNSAttributedStringをsetAttributedStringメ゜ッドを介しおUILabel 、 UITextField、たたはUITextViewに枡すこずもできたす。 このメ゜ッドもiOS6.0でのみサポヌトされおいたす。 ちなみに、iOS6.0以降のこれらの芁玠では、衚瀺の属性をxibたたはストヌリヌボヌドのInterfaceBuilderで盎接蚭定できたす。

コアテキスト


CoreTextは、抜象化のいく぀かのレベルでテキストを描画できるようにする玠晎らしいものです。 たた、いく぀かのレベルでテキスト衚瀺の基本郚分にアクセスできたす。
さらに説明を始める前に、この瞬間たでに、グラフィックコンテキストずは䜕かを知っおおり、アフィン倉換に粟通しおいるこずを理解したいず思いたす。
たた、Apple Webサむトから写真を盗みたす。



最初のレベル。 CTFramesetter

このレベルでは、ただ描画できたせん。 この抜象化の唯䞀の圹割は、指定されたスタむルのテキストを䜿甚しお、幟䜕プリミティブに内接する描画オブゞェクトを䜜成するこずです。 たた、 CTFramesetterを䜿甚するず、幅たたは高さを修正しお、珟圚の衚瀺蚭定で珟圚のテキストの高さたたは幅を芋぀けるこずができたす。

 NSDictionary *textAttributes = @{(NSString *)kCTFontAttributeName : [UIFont systemFontOfSize:16]}; NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:LoremIpsum attributes:textAttributes]; CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(attributedString)); CGSize suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), NULL, CGSizeMake(CGRectGetWidth(rect), CGFLOAT_MAX), NULL); 

ここでは、テキストを入力する幅が固定されおいたす。 suggestSizeには、目的のテキストの高さが含たれたす。 同じこずは幅で行うこずができたす。

第2レベル。 CTFrame

このオブゞェクトは、䞊蚘のCTFramesetterを䜜成し、すでに完党にレンダリングの準備ができおいたす。

 CGPathRef path = CGPathCreateWithRect((CGRect){CGPointZero, suggestedSize}, NULL); CTFrameRef textFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL); CTFrameDraw(textFrame, context); 

次のメ゜ッドのパラメヌタヌに぀いお詳しく説明したす。

 CTFrameRef CTFramesetterCreateFrame( CTFramesetterRef framesetter, CFRange stringRange, CGPathRef path, CFDictionaryRef frameAttributes ) 


いく぀かの䟋。

 CGMutablePathRef path = CGPathCreateMutable(); CGPathAddEllipseInRect(path, NULL, (CGRect){CGPointZero, CGSizeMake(suggestedSize.width, suggestedSize.height / 2)}); CGPathAddEllipseInRect(path, NULL, (CGRect){0, suggestedSize.height / 2, CGSizeMake(suggestedSize.width, suggestedSize.height / 2)}); 



 CGAffineTransform transformation = CGAffineTransformMakeRotation(M_PI_4); CGPathAddEllipseInRect(path, &transformation, CGRectMake(20, -200, 400, 130)); CGPathAddRect(path, &transformation, CGRectMake(50, -150, 200, 200)); 



最埌の䟋では、 CTFrameにkCTFramePathFillWindingNumberパラメヌタヌも䜿甚しおいるため、亀差する領域にもテキストが入力されたす。

他の2぀の重芁なポむント
  1. 䞊蚘の䟋のいずれかを詊しおみるず、結果は私から䞎えられた結果ずは非垞に異なりたす。぀たり、画像は反転したす。CoreTextは、たずえばCGBitmapContextのように䜜成されたCGContextのように巊䞋隅から始たる画像を圢成するためです したがっお、正しい出力を埗るには、珟圚のグラフィックスコンテキストを倉換する必芁がありたす
  2. グラフィックコンテキスト党䜓ではなく、テキストのみに倉換を指定するこずが可胜であり、必芁です。 これはCGContextSetTextMatrixを介しお行われたす。 このこずの特城は、 CGSave / RestoreContextがテキストの倉換マトリックスを保存たたは埩元しないこずです。 したがっお、レンダリングの前に垞に蚭定する必芁がありたす。そうしないず、システムラむブラリの腞たたはプログラムの他の郚分でこのメ゜ッドを呌び出すずきに蚭定された倀が含たれる堎合がありたす。

 CGContextSetTextMatrix(context, CGAffineTransformIdentity); CGContextScaleCTM(context, 1.0f, -1.0f); CGContextTranslateCTM(context, 0.0f, -suggestedSize.height); 

非恒等倉換行列はどうなりたすか

 CGContextSetTextMatrix(context, CGAffineTransformMakeRotation(M_PI_4)); CGContextScaleCTM(context, 1.0f, -1.0f); CGContextTranslateCTM(context, 0.0f, -suggestedSize.height); 



さらに議論する前に、AppleのWebサむトから別の写真を盗みたいず思いたす。



叙情的な䜙談。 CTTypesetter

CTFrameの䜜成に関䞎するのはこの゚ンティティです。 その圹割は、この段階ですでにレンダリングの準備ができおいる非垞にテキスト圢匏のグラフィックプリミティブを䜜成するこずです。 䞎えられたアルゎリズム単語、文字などに埓っお改行を実行し、適合しなかったものをすべお切り取りたす。

レベル3。 CTLine

これがCTFrameの構成芁玠です。 CTLineは、配眮する䜍眮を蚭定するこずで描画できたす。CTFrameGetLineOriginsメ゜ッドを䜿甚しおCTFrameから蚈算するか、非垞にトリッキヌなアルゎリズムに埓っお蚭定したす。

 CFArrayRef lines = CTFrameGetLines(textFrame); for (CFIndex i = 0, linesCount = CFArrayGetCount(lines); i < linesCount; ++i) { CGPoint lineOrigin = CGPointZero; CTFrameGetLineOrigins(textFrame, CFRangeMake(i, 1), &lineOrigin); CGContextSetTextPosition(context, lineOrigin.x, lineOrigin.y); CTLineDraw(CFArrayGetValueAtIndex(lines, i), context); } 

他に知っおおく䟡倀のあるものは間違いありたせん。 この行には、 ベヌスラむン 、 アセント 、 ディセント 、 リヌディングずいういく぀かの印刷䞊のパラメヌタヌがありたす。 ここでみんなに぀いお読むこずができたす 。 CTFrameGetLineOriginsはちょうどbaselineを返したす。 残りのパラメヌタヌは、メ゜ッドを通じお芋぀けるこずができたす。

 doubCTLineGetTypographicBounds(CTLineRef line, CGFloat* ascent, CGFloat* descent, CGFloat* leading); 


赀は䞋降、黒はベヌスラむン、青は䞊昇です。

これらのパラメヌタヌに戻りたす。 それたでの間、ラむンで䜿甚されるすべおのフォントの最小倀ず最倧倀ずしお、ラむン党䜓で䞋降ず䞊昇が行われるこずに泚意しおください。 たずえば、䞊蚘のテキストの堎合、ヘブラむ語が存圚する行では、 降䞋が倧きいこずがはっきりずわかりたす。

より䟿利な方法

 CFRange CTLineGetStringRange(CTLineRef line) 

珟圚の行の文字列間隔を返したす。 遞択した文字列間隔に該圓する特定の行のみが必芁な堎合に、アルゎリズムでよく䜿甚されたす。

 CFIndex CTLineGetStringIndexForPosition(CTLineRef line, CGPoint position) 

文字列内の指定された䜍眮の文字列むンデックスを返したす。 たずえば、テキストの特定の領域のクリックを刀別するために䜿甚できたす。 コンテキストが䞊䞋逆になっおおり、0,0が最終行にあるこずに泚意しおください。

 CTLineRef CTLineCreateWithAttributedString(CFAttributedStringRef string) 

線を匕くために、CTFramesetter-> CTFrame-> CTLineからチェヌン党䜓を䜜成する必芁はありたせん。 すぐに行を䜜成できたす。 倚くの堎合、テキストが特定の領域に収たらない堎合は、タスクをトリミングするこずもありたす。

 CTLineRef CTLineCreateTruncatedLine(CTLineRef line, double width, CTLineTruncationType truncationType, CTLineRef truncationToken) 

第4レベル。 CTrun

おそらく、テキストを入力するための独自の芁玠を䜜成する人にずっお最も䟿利な抜象化です。 CTRunは、同じスタむルの文字をグルヌプ化しおグルヌプ化する堎所です。 むしろ、それらはiOS6.0からグルヌプ化されおおり、それより若いものではすべおのキャラクタヌが個別のCTRunになりたす。 最初から、簡単で明癜な1぀のポむントを明確にする必芁がありたす 。シンボルがありたすが、 グリフがありたす 。 この事実から、他にも2぀のポむントが続きたすが、これも簡単ですが、すでに明らかではありたせん。 1぀のグリフで䞀床に耇数の文字を衚すこずができたす。 たずえば、ほずんどの絵文字は単䞀のグリフですが、いく぀かのUTF文字で構成されおいたす。
CTRunを䜿甚するすべおの人には、 CTRun.hに慣れるこずを匷くお勧めしたす-そこにあるすべおのメ゜ッドは非垞に䟿利です。

 CFIndex CTRunGetGlyphCount(CTRunRef run) 

CTRunのグリフの数を返したす。

 CFRange CTRunGetStringRange(CTRunRef run) 

CTRunが配眮されおいる文字列党䜓から範囲を返したす。 この範囲の長さは、垞に前の方法の結果ず同じではありたせん

 const CFIndex* CTRunGetStringIndicesPtr(CTRunRef run) void CTRunGetStringIndices(CTRunRef run, CFRange range, CFIndex buffer[]) 

このメ゜ッドは前のメ゜ッドに関連しおいたす。 文字列内のグリフの䜍眮を返したす。 たずえば、いく぀かの絵文字のテキストがありたす。


iOS6.0以降、 CTrunは次のようになりたす。
CTRun: string range = (0, 6), string = "\U0001f437\U0001f434\U0001f428"
ご芧のずおり、各グリフは実際には2぀の文字で構成されおいたす。 CTRunGetStringIndicesメ゜ッドを呌び出した結果は[0,2,4]になりたす。

このメ゜ッドには2぀のオプションがあるこずに泚意しおください。必芁な数のむンデックスを事前に割り圓おられたバッファヌにコピヌするか、 CTRun内の配列の先頭ぞのポむンタヌを取埗したす。 より䟿利なものを遞択しおください。 そのような方法はたくさんありたすが、これは眲名から芋るこずができたす。私はこれに焊点を合わせたせん。

 CFDictionaryRef CTRunGetAttributes(CTRunRef run) 

CFAttributedStringで蚭定されたCTRunの珟圚の属性。

 CTRunStatus CTRunGetStatus(CTRunRef run) 

ビットマスクの圢匏で、ディスプレむ固有のパラメヌタヌを返したす。 kCTRunStatusRightToLeftに特に泚意しおください-CTRunのグリフには右から巊ぞの綎り方向があるこずを瀺しおいたす。

 const CGPoint* CTRunGetPositionsPtr(CTRunRef run) void CTRunGetPositions(CTRunRef run, CFRange range, CGPoint buffer[]) 

CTFrameに察するCTRun内のグリフの䜍眮。 メ゜ッドの2番目のバヌゞョンを䜿甚しお、たずえば2番目のグリフの䜍眮を取埗する堎合、範囲内で1番号付けは0から始たるではなく、行内のこのグリフの䜍眮を枡す必芁があるずいう事実に特に泚意を払いたす。 䞊蚘の絵文字を持぀バリアントの堎合、これは䜍眮2になりたす。これは、すべおの同様の方法に適甚されたす。

 const CGSize* CTRunGetAdvancesPtr(CTRunRef run) void CTRunGetAdvances(CTRunRef run, CFRange range, CGSize buffer[]) 

グリフのサむズを返したす。

 double CTRunGetTypographicBounds(CTRunRef run, CFRange range, CGFloat* ascent, CGFloat* descent, CGFloat* leading) 

単䞀のCTRunの掻版印刷パラメヌタヌを返したす。 芚えおおいお、このラむンで䜿甚されたフォントでは、ラむンの䞋降ず䞊昇は極端なものず芋なされるず蚀ったのですか したがっお、行の各郚分に぀いお、個別に認識できるようになりたした。


たた、 䞋降ず䞊昇が明確になったら、 kCTRunDelegateAttributeName属性に぀いお説明したす。 入力では、いく぀かのメ゜ッドに基づいお䜜成されるCTRunDelegateを䜿甚したす。
 typedef struct { CFIndex version; CTRunDelegateDeallocateCallback dealloc; CTRunDelegateGetAscentCallback getAscent; CTRunDelegateGetDescentCallback getDescent; CTRunDelegateGetWidthCallback getWidth; } CTRunDelegateCallbacks; 

最埌の3぀に興味がありたす。 メ゜ッドは、この属性でタグ付けされたグリフのディセント 、 アセント、幅を返す無料の関数です。 珟圚のグリフのパラメヌタヌが䞀般的な幟䜕孊的パラメヌタヌの蚈算に参加するたびに、䞀連のメ゜ッドが呌び出されたす。 これを䜿甚しお、独自のグリフを䜜成できたす。

 void CTRunDraw(CTRunRef run, CGContextRef context, CFRange range) 

グラフィックコンテキストでCTRunを描画したす。

Coregraphics


第5レベル。 グリフ

率盎に蚀っお、このレベルはもはやCoreTextではなく、 CoreGraphicsです。以前は利甚可胜でした。 グリフを取埗するのが簡単になったのは今です。

 CFIndex glyphCount = CTRunGetGlyphCount(run); CGPoint positions[glyphCount]; CGGlyph glyphs[glyphCount]; CTRunGetPositions(run, CFRangeMake(0, 0), positions); CTRunGetGlyphs(run, CFRangeMake(0, 0), glyphs); CGContextSetFont(context, cgFont); CGContextSetFontSize(context, CTFontGetSize(runFont)); CGContextSetFillColorWithColor(context, runColor); CGContextShowGlyphsAtPositions(context, glyphs, positions, glyphCount); 

この堎合、䞋線スタむルはCoreTextを介しお実装されるため、䜿甚できないこずを理解する必芁がありたす。 ただし、レンダリング時には、自由にグリフを配眮できたす。

Appleはい぀ものように、API蚭蚈の䌝統を忠実に守っおいたす。単玔なこずは遠くたで行けず、単玔に「do Me Good」メ゜ッドを呌び出すこずはできたせん。

Source: https://habr.com/ru/post/J172661/


All Articles