富文本转路径
+ (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; }