CTFontDrawGlyphs绘制的位置不正确

时间:2017-12-02 21:10:31

标签: swift fonts core-graphics quartz-2d

编辑:我用自包含版本

替换了原始代码存根

我使用Quartz2D将音乐符号绘制到CGContext。我在使用Swift 4的Playground中这样做。

音符由用于符头的单个CGGlyph和用于词干的线原语组成。字形取自音乐字体" bravura"。 符头字形的x位置正好位于其左边缘。阀杆必须与此原点完美对齐,因此其x位置必须是符头的x位置+阀杆的厚度/ 2。

我有一个函数,它将CGFloat作为x位置,并将字形和词干绘制到CGContext中。当x的值是像30.0这样的舍入值时,这非常适用。但是,当值为30.9的小数时,词干会不对齐:

picture of 1 good and 1 bad note

很明显,线条正确绘制,但字形忽略了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, &notePosition, 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"从...

https://www.smufl.org/fonts/

...但我重新编写了代码,因此默认使用Helvetica,这也适用于演示。

作为我原始代码中的一种解决方法,我在将x值传递给绘图函数之前实现了Foundation的.rounded()方法。这样可行,但我担心在小字体大小(整个代码编写以便所有内容都围绕字体大小缩放)时,不准确性会显而易见。

我遇到了CGStext的方法setShouldSubpixelPositionFonts和setAllowsFontSubpixelPositioning,但是将它们设置为true并没有做任何改变。

那么,是否有一种方法可以像绘制基元一样精确地绘制字体对象(必须有一个)?或者我是否担心将CGFloats四舍五入到Ints是不合理的?

1 个答案:

答案 0 :(得分:0)

经过无休止的挖掘,我找到了解决方案:

仅设置

是不够的
context.setShouldSubpixelPositionFonts(true)

您还必须设置

context.setShouldSubpixelQuantizeFonts(false)

因为后者似乎凌驾于前者之上。