CTFontDrawGlyphs 在不正确的位置绘制
CTFontDrawGlyphs draws at incorrect position
已编辑:我用独立版本替换了原始代码存根
我正在使用 Quartz2D 将音乐符号绘制到 CGContext。我在操场上使用 Swift 4.
音符由用于符头的单个 CGGlyph 和用于符干的基本线条组成。字形取自音乐字体 "bravura"。
符头字形的 x 位置恰好位于其左边缘。符杆必须与原点完美对齐,因此它的 x 位置必须是符头的 x 位置 + 符杆的厚度 / 2。
我有一个函数将 CGFloat 作为 x 位置并将字形和词干绘制到 CGContext 中。当 x 的值是像 30.0 这样的四舍五入值时,这非常有效。但是,当该值为 30.9 之类的小数时,词干会错位:
很明显线条绘制正确,但字形忽略了 CGFloat 的小数部分。当我将 x 值进一步提高到 31.0 时,两个部分再次同步。
这是简化的代码,可以在您的 XCode9 Playground 中运行:
import PlaygroundSupport
import Cocoa
let fontSize = CGFloat(64)
// uncomment the next paragraph if you have added the "Bravura" Font to the ressources folder
/*
let fontURL = Bundle.main.url(forResource: "Bravura", withExtension: "otf")
CTFontManagerRegisterFontsForURL(fontURL! as CFURL, CTFontManagerScope.process, nil)
var font = CTFontCreateWithName("Bravura" as CFString, fontSize, nil)
*/
// comment out the next line if you use Bravura
var font = CTFontCreateWithName("Helvetica" as CFString, fontSize, nil)
func drawAtXPosition(_ posX: CGFloat, inContext context: CGContext) {
// draw font glyph (notehead)
var glyph = CTFontGetGlyphWithName(font, "uniE0A3" as CFString)
var notePosition = CGPoint(x: posX, y: 100)
CTFontDrawGlyphs(font, &glyph, ¬ePosition, 1, context)
// draw line (note stem)
let stemThickness = CGFloat(fontSize/4*0.12)
let x: CGFloat = posX + stemThickness / 2
let y: CGFloat = 100 - (0.168 * fontSize / 4)
let stemBegin = CGPoint(x:x, y:y)
let stemEnd = CGPoint(x:x, y: y - (3.5 * fontSize / 4))
context.setLineWidth(stemThickness)
context.strokeLineSegments(between: [stemBegin, stemEnd])
}
class MyView: NSView {
override func draw(_ rect: CGRect) {
let context = NSGraphicsContext.current!.cgContext
context.setStrokeColor(CGColor.black)
context.setFillColor(CGColor.black)
drawAtXPosition(100, inContext: context) // this draws as expected
drawAtXPosition(200.99, inContext: context) // here the glyph is drawn at x=200, while the line is correctly drawn at 200.99
}
}
var myView = MyView(frame: CGRect(x: 0, y: 0, width: 500, height: 200))
let myViewController = NSViewController()
myViewController.view = myView
PlaygroundPage.current.liveView = myViewController
您可以从...下载音乐字体"Bravura"
...但我重写了代码,因此它默认使用 Helvetica,这也适用于演示。
作为我原始代码中的解决方法,我在将 x 值传递给绘图函数之前实现了 Foundation 的 .rounded() 方法。这行得通,但我担心在较小的字体大小下(编写整个代码以便所有内容都围绕字体大小缩放)不准确会很明显。
我遇到了 CGContext 的 setShouldSubpixelPositionFonts 和 setAllowsFontSubpixelPositioning 方法,但是将它们设置为 true 并没有做任何改变。
那么,有没有一种方法可以像图元一样精确地绘制字体对象(必须有一个)?或者我担心将 CGFloats 舍入为 Ints 是不合理的?
经过无休止的挖掘,我找到了解决方案:
设置不够
context.setShouldSubpixelPositionFonts(true)
你还得设置
context.setShouldSubpixelQuantizeFonts(false)
因为后者似乎覆盖了前者。
已编辑:我用独立版本替换了原始代码存根
我正在使用 Quartz2D 将音乐符号绘制到 CGContext。我在操场上使用 Swift 4.
音符由用于符头的单个 CGGlyph 和用于符干的基本线条组成。字形取自音乐字体 "bravura"。 符头字形的 x 位置恰好位于其左边缘。符杆必须与原点完美对齐,因此它的 x 位置必须是符头的 x 位置 + 符杆的厚度 / 2。
我有一个函数将 CGFloat 作为 x 位置并将字形和词干绘制到 CGContext 中。当 x 的值是像 30.0 这样的四舍五入值时,这非常有效。但是,当该值为 30.9 之类的小数时,词干会错位:
很明显线条绘制正确,但字形忽略了 CGFloat 的小数部分。当我将 x 值进一步提高到 31.0 时,两个部分再次同步。
这是简化的代码,可以在您的 XCode9 Playground 中运行:
import PlaygroundSupport
import Cocoa
let fontSize = CGFloat(64)
// uncomment the next paragraph if you have added the "Bravura" Font to the ressources folder
/*
let fontURL = Bundle.main.url(forResource: "Bravura", withExtension: "otf")
CTFontManagerRegisterFontsForURL(fontURL! as CFURL, CTFontManagerScope.process, nil)
var font = CTFontCreateWithName("Bravura" as CFString, fontSize, nil)
*/
// comment out the next line if you use Bravura
var font = CTFontCreateWithName("Helvetica" as CFString, fontSize, nil)
func drawAtXPosition(_ posX: CGFloat, inContext context: CGContext) {
// draw font glyph (notehead)
var glyph = CTFontGetGlyphWithName(font, "uniE0A3" as CFString)
var notePosition = CGPoint(x: posX, y: 100)
CTFontDrawGlyphs(font, &glyph, ¬ePosition, 1, context)
// draw line (note stem)
let stemThickness = CGFloat(fontSize/4*0.12)
let x: CGFloat = posX + stemThickness / 2
let y: CGFloat = 100 - (0.168 * fontSize / 4)
let stemBegin = CGPoint(x:x, y:y)
let stemEnd = CGPoint(x:x, y: y - (3.5 * fontSize / 4))
context.setLineWidth(stemThickness)
context.strokeLineSegments(between: [stemBegin, stemEnd])
}
class MyView: NSView {
override func draw(_ rect: CGRect) {
let context = NSGraphicsContext.current!.cgContext
context.setStrokeColor(CGColor.black)
context.setFillColor(CGColor.black)
drawAtXPosition(100, inContext: context) // this draws as expected
drawAtXPosition(200.99, inContext: context) // here the glyph is drawn at x=200, while the line is correctly drawn at 200.99
}
}
var myView = MyView(frame: CGRect(x: 0, y: 0, width: 500, height: 200))
let myViewController = NSViewController()
myViewController.view = myView
PlaygroundPage.current.liveView = myViewController
您可以从...下载音乐字体"Bravura"
...但我重写了代码,因此它默认使用 Helvetica,这也适用于演示。
作为我原始代码中的解决方法,我在将 x 值传递给绘图函数之前实现了 Foundation 的 .rounded() 方法。这行得通,但我担心在较小的字体大小下(编写整个代码以便所有内容都围绕字体大小缩放)不准确会很明显。
我遇到了 CGContext 的 setShouldSubpixelPositionFonts 和 setAllowsFontSubpixelPositioning 方法,但是将它们设置为 true 并没有做任何改变。
那么,有没有一种方法可以像图元一样精确地绘制字体对象(必须有一个)?或者我担心将 CGFloats 舍入为 Ints 是不合理的?
经过无休止的挖掘,我找到了解决方案:
设置不够
context.setShouldSubpixelPositionFonts(true)
你还得设置
context.setShouldSubpixelQuantizeFonts(false)
因为后者似乎覆盖了前者。