NSAttributedString(Core Text)の各文字(グリフ)ごとに正確なバウンディングボックスを計算する必要があります。同様の問題(コアテキストの選択など)を解決するために使用されるいくつかのコードをまとめると、結果は非常に良好ですが、適切に計算されているフレーム(赤)はわずかです。
ほとんどのフレームは、水平方向または垂直方向(ごくわずか)の位置がずれています。その原因は何ですか?どうすればこのコードを完成できますか?:
-(void)recalculate{
// get characters from NSString
NSUInteger len = [_attributedString.string length];
UniChar *characters = (UniChar *)malloc(sizeof(UniChar)*len);
CFStringGetCharacters((__bridge CFStringRef)_attributedString.string, CFRangeMake(0, [_attributedString.string length]), characters);
// allocate glyphs and bounding box arrays for holding the result
// assuming that each character is only one glyph, which is wrong
CGGlyph *glyphs = (CGGlyph *)malloc(sizeof(CGGlyph)*len);
CTFontGetGlyphsForCharacters(_font, characters, glyphs, len);
// get bounding boxes for glyphs
CTFontGetBoundingRectsForGlyphs(_font, kCTFontDefaultOrientation, glyphs, _characterFrames, len);
free(characters); free(glyphs);
// Measure how mush specec will be needed for this attributed string
// So we can find minimun frame needed
CFRange fitRange;
CGSize s = CTFramesetterSuggestFrameSizeWithConstraints(_framesetter, rangeAll, NULL, CGSizeMake(W, MAXFLOAT), &fitRange);
_frameRect = CGRectMake(0, 0, s.width, s.height);
CGPathRef framePath = CGPathCreateWithRect(_frameRect, NULL);
_ctFrame = CTFramesetterCreateFrame(_framesetter, rangeAll, framePath, NULL);
CGPathRelease(framePath);
// Get the lines in our frame
NSArray* lines = (NSArray*)CTFrameGetLines(_ctFrame);
_lineCount = [lines count];
// Allocate memory to hold line frames information:
if (_lineOrigins != NULL)free(_lineOrigins);
_lineOrigins = malloc(sizeof(CGPoint) * _lineCount);
if (_lineFrames != NULL)free(_lineFrames);
_lineFrames = malloc(sizeof(CGRect) * _lineCount);
// Get the Origin point of each of the lines
CTFrameGetLineOrigins(_ctFrame, CFRangeMake(0, 0), _lineOrigins);
// Solution borrowew from (but simplified):
// https://github.com/Twitter/twui/blob/master/lib/Support/CoreText%2BAdditions.m
// Loop throught the lines
for(CFIndex i = 0; i < _lineCount; ++i) {
CTLineRef line = (__bridge CTLineRef)[lines objectAtIndex:i];
CFRange lineRange = CTLineGetStringRange(line);
CFIndex lineStartIndex = lineRange.location;
CFIndex lineEndIndex = lineStartIndex + lineRange.length;
CGPoint lineOrigin = _lineOrigins[i];
CGFloat ascent, descent, leading;
CGFloat lineWidth = CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
// If we have more than 1 line, we want to find the real height of the line by measuring the distance between the current line and previous line. If it's only 1 line, then we'll guess the line's height.
BOOL useRealHeight = i < _lineCount - 1;
CGFloat neighborLineY = i > 0 ? _lineOrigins[i - 1].y : (_lineCount - 1 > i ? _lineOrigins[i + 1].y : 0.0f);
CGFloat lineHeight = ceil(useRealHeight ? abs(neighborLineY - lineOrigin.y) : ascent + descent + leading);
_lineFrames[i].Origin = lineOrigin;
_lineFrames[i].size = CGSizeMake(lineWidth, lineHeight);
for (int ic = lineStartIndex; ic < lineEndIndex; ic++) {
CGFloat startOffset = CTLineGetOffsetForStringIndex(line, ic, NULL);
_characterFrames[ic].Origin = CGPointMake(startOffset, lineOrigin.y);
}
}
}
#pragma mark - Rendering Text:
-(void)renderInContext:(CGContextRef)context contextSize:(CGSize)size{
CGContextSaveGState(context);
// Draw Core Text attributes string:
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, CGRectGetHeight(_frameRect));
CGContextScaleCTM(context, 1.0, -1.0);
CTFrameDraw(_ctFrame, context);
// Draw line and letter frames:
CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:0.0 green:0.0 blue:1.0 alpha:0.5].CGColor);
CGContextSetLineWidth(context, 1.0);
CGContextBeginPath(context);
CGContextAddRects(context, _lineFrames, _lineCount);
CGContextClosePath(context);
CGContextStrokePath(context);
CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.5].CGColor);
CGContextBeginPath(context);
CGContextAddRects(context, _characterFrames, _attributedString.string.length);
CGContextClosePath(context);
CGContextStrokePath(context);
CGContextRestoreGState(context);
}
あなたはあなたの質問で印象的な量の仕事をしました、そしてあなた自身でsoでした。発生していた問題は、各フレームの境界ボックスを配置する次のコード行に起因します。
_characterFrames[ic].Origin = CGPointMake(startOffset, lineOrigin.y);
それの問題は、あなたがoverridingであるということです。フレームがすでに持っていたオフセットは何でも。
その行をコメントアウトすると、すべてのフレームが同じ場所に配置されていることがわかります多かれ少なかれbutに配置されていないこともわかりますまったく同じ場所。左または右に配置されるものと、上または下に配置されるものがあります。つまり、グリフのフレームには独自の位置があります。
問題の解決策は、フレームをライン上の正しい位置に移動するときに、フレームの現在の位置を考慮することです。 xとyに別々に追加することでそれを行うことができます:
_characterFrames[ic].Origin.x += startOffset;
_characterFrames[ic].Origin.y += lineOrigin.y;
または長方形をオフセットすることによって:
_characterFrames[ic] = CGRectOffset(_characterFrames[ic],
startOffset, lineOrigin.y);
これで、バウンディングボックスは正しい位置になります。
そして、そこにある極端なフォントのいくつかで機能することがわかります