如何在梯形(或圆形)中绘制文本?

时间:2022-10-30 08:41:52

Let's imagine that I have some text: "Some attributed text in trapezoid."

让我们想象一下我有一些文字:“有些文字是梯形的。”

I have NSAttributedString extension, which returns me UIImage with attributed text:

我有NSAttributedString扩展,它返回带有属性文本的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()
    }
}

But this function returns me text in one line, because of using boundingRect:

但是这个函数在一行中返回文本,因为使用了boundingRect:

------------------------------------
|Some attributed text in trapezoid.|
------------------------------------

If I would use custom rect for drawing text it won't help much...

如果我使用自定义矩形来绘制文本,那将无济于事......

UIGraphicsBeginImageContext(CGRect(x: 0, y: 0, width: 100, height: 30))
draw(at: CGPoint.zero)

...because of text will be in rectangle:

...因为文字将是矩形:

--------------
|Some attribu|
|ted text in |
|trapezoid.  |
--------------

What i need, is to draw text in a trapezoid with known corner positions (or in a circle with known radius). So each new line of text should start with a little offset, see example: 如何在梯形(或圆形)中绘制文本?

我需要的是在具有已知角位置(或具有已知半径的圆圈)的梯形中绘制文本。所以每个新的文本行都应该以一点偏移开始,参见示例:

So I want to see something like that:

所以我希望看到类似的东西:

---------------
\Some attribut/
 \ed text in /
  \trapezoid/
   ---------

How can I achieve this result?

我怎样才能达到这个效果?

2 个解决方案

#1


1  

You'll have to drop down to CoreText levels here. The good new is, you will be able to draw text in just about any shape you wish!

你必须在这里下载到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()
    }
}

And you can use it like so:

你可以像这样使用它:

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()!

The bad news is, this solution does not give you an ellipsis at the end. If you really want to have that, you may need to make some adjustments to the last line the framesetter gives you.

坏消息是,这个解决方案最后没有给你一个省略号。如果你真的想拥有它,你可能需要对framesetter给你的最后一行做一些调整。

#2


0  

With Christian Schnorr answer made it for my purposes:

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
    }
}

Using:

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

Note Degrees allowed from 0° to 90°

注意允许的度数从0°到90°

#1


1  

You'll have to drop down to CoreText levels here. The good new is, you will be able to draw text in just about any shape you wish!

你必须在这里下载到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()
    }
}

And you can use it like so:

你可以像这样使用它:

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()!

The bad news is, this solution does not give you an ellipsis at the end. If you really want to have that, you may need to make some adjustments to the last line the framesetter gives you.

坏消息是,这个解决方案最后没有给你一个省略号。如果你真的想拥有它,你可能需要对framesetter给你的最后一行做一些调整。

#2


0  

With Christian Schnorr answer made it for my purposes:

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
    }
}

Using:

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

Note Degrees allowed from 0° to 90°

注意允许的度数从0°到90°