+ (UIBezierPath *)attributedString2BezierPath:(NSAttributedString *)attributedString inBounds:(CGSize)bounds {
NSString *clearText = attributedString.string;
NSCharacterSet *ignoredCharsSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
if ([clearText stringByTrimmingCharactersInSet:ignoredCharsSet].length == 0) {
return [UIBezierPath bezierPath];
}
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attributedString);
CFRange textRange = CFRangeMake(0, attributedString.length);
CGSize frameSize = CTFramesetterSuggestFrameSizeWithConstraints(frameSetter, textRange, NULL, bounds, NULL);
CGPathRef framePath = [UIBezierPath bezierPathWithRect:CGRectMake(0.0, 0.0, frameSize.width, frameSize.height)].CGPath;
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, textRange, framePath, NULL);
CGMutablePathRef path = CGPathCreateMutable();
CFArrayRef lines = CTFrameGetLines(frame);
CGFloat linesShift = 0.0;
CFIndex lineCount = CFArrayGetCount(lines);
CGPoint origins[lineCount];
CTFrameGetLineOrigins(frame, CFRangeMake(0, lineCount), origins);
for (CFIndex index = 0; index < lineCount; index++) {
CTLineRef line = CFArrayGetValueAtIndex(lines, index);
CGPoint lineOrigin = origins[index];
CGFloat ascent, descent, leading;
CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
CFArrayRef lineRuns = CTLineGetGlyphRuns(line);
CGFloat effectiveDescent = 0.0;
CGFloat effectiveAscent = 0.0;
CFIndex lineRunCount = CFArrayGetCount(lineRuns);
for (CFIndex lineRunIndex = 0; lineRunIndex < lineRunCount; lineRunIndex++) {
CTRunRef lineRun = CFArrayGetValueAtIndex(lineRuns, lineRunIndex);
CFIndex glyphsCount = CTRunGetGlyphCount(lineRun);
if (glyphsCount == 0) {
continue;
}
CGFloat rt_ascent = 0.0;
CGFloat rt_descent = 0.0;
CGFloat rt_leading = 0.0;
CTRunGetTypographicBounds(lineRun, CFRangeMake(0, glyphsCount), &rt_ascent, &rt_descent, &rt_leading);
effectiveAscent = MAX(effectiveAscent, ABS(rt_ascent));
effectiveDescent = MAX(effectiveDescent, ABS(rt_descent));
}
for (CFIndex lineRunIndex = 0; lineRunIndex < lineRunCount; lineRunIndex++) {
CTRunRef lineRun = CFArrayGetValueAtIndex(lineRuns, lineRunIndex);
CFIndex glyphsCount = CTRunGetGlyphCount(lineRun);
if (glyphsCount == 0) {
continue;
}
NSDictionary *attributes = (__bridge NSDictionary *)CTRunGetAttributes(lineRun);
UIFont *font = attributes[NSFontAttributeName] ?: [UIFont systemFontOfSize:UIFont.systemFontSize];
const CGGlyph *glyphPtr = CTRunGetGlyphsPtr(lineRun);
const CGPoint *positionPtr = CTRunGetPositionsPtr(lineRun);
for (CFIndex glyphIndex = 0; glyphIndex < glyphsCount; glyphIndex++) {
CGGlyph glyph = glyphPtr[glyphIndex];
CGPoint gPosition = positionPtr[glyphIndex];
CGAffineTransform T = CGAffineTransformMakeScale(1, 1);
CTFontRef ctFont = (__bridge CTFontRef)(font);
CGPathRef glyphPath = CTFontCreatePathForGlyph(ctFont, glyph, &T);
if (glyphPath != NULL) {
CGRect pathBounds = CGPathGetBoundingBox(glyphPath);
CGAffineTransform pathOffset = CGAffineTransformMakeTranslation(-pathBounds.origin.x, -pathBounds.origin.y);
CGPathRef glyphPathRel = CGPathCreateCopyByTransformingPath(glyphPath, &pathOffset);
if (glyphPathRel == NULL) {
glyphPathRel = glyphPath;
}
CGPoint position = CGPointMake(lineOrigin.x + gPosition.x + pathBounds.origin.x, lineOrigin.y + gPosition.y + pathBounds.origin.y);
CGPoint offset = CGPointMake(position.x, position.y + (ascent - effectiveAscent) + linesShift);
CGAffineTransform PT = CGAffineTransformMakeTranslation(offset.x, offset.y);
CGPathAddPath(path, &PT, glyphPathRel);
if (glyphPathRel != glyphPath) {
CGPathRelease(glyphPathRel);
}
CFRelease(glyphPath);
}
}
}
linesShift += (ascent + descent) - (effectiveAscent + effectiveDescent);
}
CGAffineTransform matrix = CGAffineTransformMakeScale(1, -1);
matrix = CGAffineTransformTranslate(matrix, 0.0, -frameSize.height);
CGPathRef finalPath = CGPathCreateMutableCopyByTransformingPath(path, &matrix);
CFRelease(frameSetter);
CFRelease(frame);
CFRelease(path);
UIBezierPath *resultPath = [UIBezierPath bezierPathWithCGPath:finalPath];
CGPathRelease(finalPath);
return resultPath;
}