【问题标题】:swift text.draw method messes up CGContextswift text.draw 方法搞砸了 CGContext
【发布时间】:2018-10-29 13:57:23
【问题描述】:

我正在使用 CoreGraphics 在 CGContext 中与图元一起绘制单个字形。以下代码在 XCode 9.2 中的 swift playground 中运行当在 Playground 中启动时,一个带有两倍字母 A 的小矩形应出现在 Playground liveView 中的给定坐标处。

import Cocoa
import PlaygroundSupport

class MyView: NSView {

    init(inFrame: CGRect) {
        super.init(frame: inFrame)
    }

    required init?(coder decoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func draw(_ rect: CGRect) {

        // setup context properties

        let context: CGContext = NSGraphicsContext.current!.cgContext

        context.setStrokeColor(CGColor.black)
        context.setTextDrawingMode(.fill)
        context.setFillColor(CGColor(red: 0.99, green: 0.99, blue: 0.85, alpha: 1))
        context.beginPath()
        context.addRect(rect)
        context.fillPath()
        context.setFillColor(.black)

        // prepare variables and constants

        var font = CTFontCreateWithName("Helvetica" as CFString, 48, nil)
        var glyph = CTFontGetGlyphWithName(font, "A" as CFString)
        var glyph1Position = CGPoint(x: self.frame.minX, y: self.frame.maxY/2)
        var glyph2Position = CGPoint(x: self.frame.minX+150, y: self.frame.maxY/2)

        let text = "Hello"
        var textOrigin = NSPoint(x: self.frame.minX+50, y: self.frame.maxY/2)

        // draw one character

        CTFontDrawGlyphs(font, &glyph, &glyph1Position, 1, context)

        // ***   ***   when the next line is uncommented the bug appears   ***   ***

        // text.draw(at: textOrigin)

        CTFontDrawGlyphs(font, &glyph, &glyph2Position, 1, context)
    }
}

var frameRect = CGRect(x: 0, y: 0, width: 200, height: 100)

PlaygroundPage.current.liveView = MyView(inFrame: frameRect)

现在我想在相同的上下文中绘制常规文本。但是,当通过自己的绘图方法在两个字形的绘制之间绘制文本字符串时,当前的上下文似乎乱七八糟,第二个字形不会显示。在绘制两个单个字形之后绘制文本时,一切都很好。

所以很明显,绘制文本似乎对当前的 CGContext 有影响,但我无法弄清楚到底发生了什么。我在绘制字符串并在之后恢复之前尝试了 saveGstate() 方法,但没有成功。

我还尝试使用 CoreText 方法使用 CTFramesetterCreateWithAttributedString 创建属性字符串并使用 CTFramesetterCreateFrame 显示它,它也不起作用,这里在创建框架设置器后上下文混乱了。

我的实际操场更复杂,字形并没有完全消失,而是显示在错误的垂直位置,但基本问题 - 问题是一样的:

如何在不对背景进行任何其他更改的情况下将文本绘制到 currentContext 中?

【问题讨论】:

    标签: swift text core-graphics draw glyph


    【解决方案1】:

    您需要设置文本矩阵(应用于文本绘制的变换)。您应该始终在绘制文本之前设置它,因为它不是图形状态的一部分,并且可能会被其他绘制代码(例如 AppKit 的字符串绘制扩展)丢弃。

    在致电CTFontDrawGlyphs之前添加以下内容:

        context.textMatrix = .identity
    

    这应该在上下文的初始设置中调用,因为在调用 drawRect 之前没有保证文本矩阵是恒等的。然后,每当您调用修改文本矩阵的内容时,您都需要将其设置回您想要的内容(在这种情况下为身份,但如果您想以一种奇特的方式绘制,则可能是其他内容)。

    修改文本矩阵的内容并不总是很明显。 AppKit 绘图功能几乎总是这样做(尽管我不知道有任何文档表明这一点)。修改上下文的核心文本函数,如CTFrameDrawCTLineDraw,通常会用如下语句记录这一事实:

    此调用可以使上下文处于任何状态,并且不会在绘制操作后刷新它。

    同样CTFontDrawGlyphs 警告:

    如果在字体中指定了这些属性,则此函数会修改图形状态,包括字体、文本大小和文本矩阵。这些属性不会恢复。

    作为一项规则,我不鼓励混合文本绘图系统。使用 AppKit 或 Core Text,但不要混合使用。如果您选择一个并坚持下去,那么这通常不是问题(只要您在drawRect 的顶部初始化一次矩阵)。例如,如果您使用CTFontDrawGlyphs 完成所有绘图,则无需每次都重置矩阵;你会留在核心文本矩阵中,这很好(这就是为什么当你注释掉 draw(at:) 调用时它会起作用)。

    【讨论】:

    • 谢谢,罗伯。不幸的是,这无济于事。将这一行直接放在方法调用的前面和 draw 方法的开头都没有任何变化,第二个字形仍然不会出现。
    • 我拿走了你的游乐场,取消了第 44 行的注释,并在第 46 行添加了上面的代码,它在实时视图中绘制了“A Hello A”。这是 Xcode 10.1b3。你的配置是什么?
    • 嗨,Rob,是的,我现在发现了。但这不是和你的解释相矛盾吗?我最初在第 43 行插入了您的代码,并尝试在第 19 行插入它,正如您所写:始终在绘制文本之前设置它。现在它显然只在绘制文本后设置时才起作用。我想了解下面会发生什么。是不是每次画完一行文字都要设置这个参数?
    • 您的代码假定文本矩阵是单位矩阵(无旋转;无平移)。 drawRect 不保证文本矩阵是恒等的,但通常是这样。 AppKit 字符串绘制(如text.draw(at: textOrigin))函数会更改文本矩阵,并且不会恢复它。因此,如果您调用 AppKit 字符串绘制函数,则必须将文本矩阵设置为您想要的,在您的情况下是标识。
    • 好的,但这意味着在每次调用此类函数后,必须手动设置矩阵,对吗?我会将您的答案标记为正确答案,但也许您可以重写部分内容以反映这些 cmets 的结果。感谢您的帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-15
    • 2012-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多