如何绘制梯形(或圆形)的文字?
How to draw text in trapezoid (or circle)?
假设我有一些文本:“梯形中的一些属性文本。”
我有 NSAttributedString 扩展,returns 我的 UIImage 带有属性文本:
extension NSAttributedString {
func asImage() -> UIImage? {
defer {
UIGraphicsEndImageContext()
}
let size = boundingRect(with: CGSize.zero, options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil).size
UIGraphicsBeginImageContext(size)
draw(at: CGPoint.zero)
return UIGraphicsGetImageFromCurrentImageContext()
}
}
但是这个功能 returns 我在一行中输入文本,因为使用了 boundingRect
:
------------------------------------
|Some attributed text in trapezoid.|
------------------------------------
如果我使用自定义矩形来绘制文本,那也没什么用...
UIGraphicsBeginImageContext(CGRect(x: 0, y: 0, width: 100, height: 30))
draw(at: CGPoint.zero)
...因为文本将呈矩形:
--------------
|Some attribu|
|ted text in |
|trapezoid. |
--------------
我需要的是在角位置已知的梯形(或半径已知的圆)中绘制文本。所以每一行新的文本都应该以一个小的偏移量开始,参见示例:
所以我想看到类似的东西:
---------------
\Some attribut/
\ed text in /
\trapezoid/
---------
我怎样才能达到这个结果?
您必须在此处下降到 CoreText 级别。好消息是,您将能够以任何您想要的形状绘制文本!
extension NSAttributedString {
public func draw(in path: CGPath) {
let context = UIGraphicsGetCurrentContext()!
let transform = CGAffineTransform(scaleX: +1, y: -1)
let flippedPath = CGMutablePath()
flippedPath.addPath(path, transform: transform)
let range = CFRange(location: 0, length: 0)
let framesetter = CTFramesetterCreateWithAttributedString(self)
let frame = CTFramesetterCreateFrame(framesetter, range, flippedPath, nil)
context.saveGState()
// Debug: fill path.
context.setFillColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.5)
context.beginPath()
context.addPath(path)
context.fillPath()
context.concatenate(transform)
CTFrameDraw(frame, context)
context.restoreGState()
}
}
你可以这样使用它:
let string = NSAttributedString(string: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.")
let bounds = CGRect(x: 0, y: 0, width: 120, height: 120)
let path = CGMutablePath()
path.move(to: CGPoint(x: 10, y: 10))
path.addLine(to: CGPoint(x: 110, y: 10))
path.addLine(to: CGPoint(x: 90, y: 110))
path.addLine(to: CGPoint(x: 30, y: 110))
path.closeSubpath()
UIGraphicsBeginImageContextWithOptions(bounds.integral.size, true, 0)
defer { UIGraphicsEndImageContext() }
let context = UIGraphicsGetCurrentContext()!
context.setFillColor(UIColor.white.cgColor)
context.fill(.infinite)
string.draw(in: path)
let image = UIGraphicsGetImageFromCurrentImageContext()!
坏消息是,此解决方案末尾没有省略号。如果你真的想要那个,你可能需要对 framesetter 给你的最后一行做一些调整。
为了我的目的Christian Schnorr :
import PlaygroundSupport
import UIKit
extension NSAttributedString {
/// Draws attributed string in selected CGPath.
func draw(in path: CGPath) {
guard let context = UIGraphicsGetCurrentContext() else { return }
let transform = CGAffineTransform(scaleX: 1.0, y: -1.0)
let flippedPath = CGMutablePath()
flippedPath.addPath(path, transform: transform)
let range = CFRange(location: 0, length: 0)
let framesetter = CTFramesetterCreateWithAttributedString(self)
let frame = CTFramesetterCreateFrame(framesetter, range, flippedPath, nil)
context.saveGState()
context.concatenate(transform)
CTFrameDraw(frame, context)
context.restoreGState()
}
/// Renders attributed string.
///
/// - Parameters:
/// - size: A 'CGSize' for rendering string in trapezoid.
/// - degree: A `CGFloat`, representing trapezoid angles in degrees.
/// - Returns: An optional `UIImage` with rendered string.
func asTrapezoidImage(size: CGSize, degree: CGFloat) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, 0)
defer { UIGraphicsEndImageContext() }
draw(in: size.trapezoidPath(degree))
return UIGraphicsGetImageFromCurrentImageContext()
}
}
extension CGSize {
/// Converts CGSize into trapezoid CGPath.
///
/// - Parameter degree: A `CGFloat`, representing trapezoid angles in degrees.
/// - Returns: A `CGPath` with trapezoid.
func trapezoidPath(_ degree: CGFloat) -> CGPath {
var offset = height * tan(CGFloat.pi * degree / 180.0)
offset = max(0, min(width / 2.0, offset))
let path = CGMutablePath()
path.move(to: CGPoint.zero)
path.addLine(to: CGPoint(x: width, y: 0.0))
path.addLine(to: CGPoint(x: width - offset, y: height))
path.addLine(to: CGPoint(x: offset, y: height))
path.closeSubpath()
return path
}
}
使用:
let string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit."
let attributes: [NSAttributedStringKey: Any] = [
.foregroundColor: UIColor.blue,
.backgroundColor: UIColor.white,
.font: UIFont.systemFont(ofSize: 24.0)
]
let attrString = NSAttributedString(string: string, attributes: attributes)
let size = CGSize(width: 400.0, height: 120.0)
let image = attrString.asTrapezoidImage(size: size, degree: 12.0)
let imageView = UIImageView(image: image)
imageView.frame = CGRect(origin: CGPoint.zero, size: size)
PlaygroundPage.current.needsIndefiniteExecution = false
PlaygroundPage.current.liveView = imageView
注意 允许的角度从 0° 到 90°
假设我有一些文本:“梯形中的一些属性文本。”
我有 NSAttributedString 扩展,returns 我的 UIImage 带有属性文本:
extension NSAttributedString {
func asImage() -> UIImage? {
defer {
UIGraphicsEndImageContext()
}
let size = boundingRect(with: CGSize.zero, options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil).size
UIGraphicsBeginImageContext(size)
draw(at: CGPoint.zero)
return UIGraphicsGetImageFromCurrentImageContext()
}
}
但是这个功能 returns 我在一行中输入文本,因为使用了 boundingRect
:
------------------------------------
|Some attributed text in trapezoid.|
------------------------------------
如果我使用自定义矩形来绘制文本,那也没什么用...
UIGraphicsBeginImageContext(CGRect(x: 0, y: 0, width: 100, height: 30))
draw(at: CGPoint.zero)
...因为文本将呈矩形:
--------------
|Some attribu|
|ted text in |
|trapezoid. |
--------------
我需要的是在角位置已知的梯形(或半径已知的圆)中绘制文本。所以每一行新的文本都应该以一个小的偏移量开始,参见示例:
所以我想看到类似的东西:
---------------
\Some attribut/
\ed text in /
\trapezoid/
---------
我怎样才能达到这个结果?
您必须在此处下降到 CoreText 级别。好消息是,您将能够以任何您想要的形状绘制文本!
extension NSAttributedString {
public func draw(in path: CGPath) {
let context = UIGraphicsGetCurrentContext()!
let transform = CGAffineTransform(scaleX: +1, y: -1)
let flippedPath = CGMutablePath()
flippedPath.addPath(path, transform: transform)
let range = CFRange(location: 0, length: 0)
let framesetter = CTFramesetterCreateWithAttributedString(self)
let frame = CTFramesetterCreateFrame(framesetter, range, flippedPath, nil)
context.saveGState()
// Debug: fill path.
context.setFillColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.5)
context.beginPath()
context.addPath(path)
context.fillPath()
context.concatenate(transform)
CTFrameDraw(frame, context)
context.restoreGState()
}
}
你可以这样使用它:
let string = NSAttributedString(string: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.")
let bounds = CGRect(x: 0, y: 0, width: 120, height: 120)
let path = CGMutablePath()
path.move(to: CGPoint(x: 10, y: 10))
path.addLine(to: CGPoint(x: 110, y: 10))
path.addLine(to: CGPoint(x: 90, y: 110))
path.addLine(to: CGPoint(x: 30, y: 110))
path.closeSubpath()
UIGraphicsBeginImageContextWithOptions(bounds.integral.size, true, 0)
defer { UIGraphicsEndImageContext() }
let context = UIGraphicsGetCurrentContext()!
context.setFillColor(UIColor.white.cgColor)
context.fill(.infinite)
string.draw(in: path)
let image = UIGraphicsGetImageFromCurrentImageContext()!
坏消息是,此解决方案末尾没有省略号。如果你真的想要那个,你可能需要对 framesetter 给你的最后一行做一些调整。
为了我的目的Christian Schnorr
import PlaygroundSupport
import UIKit
extension NSAttributedString {
/// Draws attributed string in selected CGPath.
func draw(in path: CGPath) {
guard let context = UIGraphicsGetCurrentContext() else { return }
let transform = CGAffineTransform(scaleX: 1.0, y: -1.0)
let flippedPath = CGMutablePath()
flippedPath.addPath(path, transform: transform)
let range = CFRange(location: 0, length: 0)
let framesetter = CTFramesetterCreateWithAttributedString(self)
let frame = CTFramesetterCreateFrame(framesetter, range, flippedPath, nil)
context.saveGState()
context.concatenate(transform)
CTFrameDraw(frame, context)
context.restoreGState()
}
/// Renders attributed string.
///
/// - Parameters:
/// - size: A 'CGSize' for rendering string in trapezoid.
/// - degree: A `CGFloat`, representing trapezoid angles in degrees.
/// - Returns: An optional `UIImage` with rendered string.
func asTrapezoidImage(size: CGSize, degree: CGFloat) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, 0)
defer { UIGraphicsEndImageContext() }
draw(in: size.trapezoidPath(degree))
return UIGraphicsGetImageFromCurrentImageContext()
}
}
extension CGSize {
/// Converts CGSize into trapezoid CGPath.
///
/// - Parameter degree: A `CGFloat`, representing trapezoid angles in degrees.
/// - Returns: A `CGPath` with trapezoid.
func trapezoidPath(_ degree: CGFloat) -> CGPath {
var offset = height * tan(CGFloat.pi * degree / 180.0)
offset = max(0, min(width / 2.0, offset))
let path = CGMutablePath()
path.move(to: CGPoint.zero)
path.addLine(to: CGPoint(x: width, y: 0.0))
path.addLine(to: CGPoint(x: width - offset, y: height))
path.addLine(to: CGPoint(x: offset, y: height))
path.closeSubpath()
return path
}
}
使用:
let string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit."
let attributes: [NSAttributedStringKey: Any] = [
.foregroundColor: UIColor.blue,
.backgroundColor: UIColor.white,
.font: UIFont.systemFont(ofSize: 24.0)
]
let attrString = NSAttributedString(string: string, attributes: attributes)
let size = CGSize(width: 400.0, height: 120.0)
let image = attrString.asTrapezoidImage(size: size, degree: 12.0)
let imageView = UIImageView(image: image)
imageView.frame = CGRect(origin: CGPoint.zero, size: size)
PlaygroundPage.current.needsIndefiniteExecution = false
PlaygroundPage.current.liveView = imageView
注意 允许的角度从 0° 到 90°