Skip to content

Instantly share code, notes, and snippets.

@chrissimpkins
Forked from leonbreedt/FontPreview.swift
Created December 24, 2015 13:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chrissimpkins/ca285ed0618acb25dd5e to your computer and use it in GitHub Desktop.
Save chrissimpkins/ca285ed0618acb25dd5e to your computer and use it in GitHub Desktop.
Render font from command line on OS X without requiring font to be installed.
//
// FontPreview
// Copyright © 2015 Leon Breedt
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import AppKit
import WebKit
import Darwin
let kFontSampleBoxWidth = 400
let kFontSampleBoxSpacing = 5
let kDefaultFontSize: Float = 14.0
let kFontSampleText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
class TextRendererView : NSView {
let primaryFontFilePath: String
let fontSize: Float
let additionalFontFilePaths: [String]
var pdfData: NSData?
private var textFontsByFilePath: [String: CTFont]
private var fontsByFilePath: [String: NSFont]
init(frame: NSRect, primaryFontFilePath: String, fontSize: Float, additionalFontFilePaths: [String]) {
self.primaryFontFilePath = primaryFontFilePath
self.fontSize = fontSize
self.additionalFontFilePaths = additionalFontFilePaths
self.textFontsByFilePath = [:]
self.fontsByFilePath = [:]
self.pdfData = nil
super.init(frame: frame)
let allFontFilePaths = [primaryFontFilePath] + additionalFontFilePaths
self.textFontsByFilePath = allFontFilePaths.reduce([:]) { (var dict, path) in
dict[path] = loadFont(path)
return dict
}
self.fontsByFilePath = allFontFilePaths.reduce([:]) { (var dict, path) in
let font = textFontsByFilePath[path]!
dict[path] = font as NSFont
return dict
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) not supported")
}
var text: String? {
didSet {
setNeedsDisplayInRect(frame)
}
}
override func drawRect(dirtyRect: NSRect) {
let consumerData = NSMutableData()
let consumer = CGDataConsumerCreateWithCFData(consumerData)
var rect = CGRect(x: dirtyRect.origin.x,
y: dirtyRect.origin.y,
width: dirtyRect.width,
height: dirtyRect.height)
guard let context = CGPDFContextCreate(consumer, &rect, nil) else {
fatalError("failed to create PDF context")
}
CGContextSetTextMatrix(context, CGAffineTransformIdentity)
CGPDFContextBeginPage(context, nil)
var boxLeft = kFontSampleBoxSpacing
for filePath in [primaryFontFilePath] + additionalFontFilePaths {
guard let font = fontsByFilePath[filePath] else {
fatalError("failed to load font for \(filePath)")
}
Swift.print("drawing text with font: \(font.fontName)")
let attributedString = NSAttributedString(string: text ?? "", attributes: [NSFontAttributeName: font])
let framesetter = CTFramesetterCreateWithAttributedString(attributedString)
let path = CGPathCreateMutable()
let bounds = CGRect(x: boxLeft, y: 0, width: kFontSampleBoxWidth, height: 400)
CGPathAddRect(path, nil, bounds)
let frame = CTFramesetterCreateFrame(framesetter, CFRange(location: 0, length: 0), path, nil)
CTFrameDraw(frame, context)
boxLeft += kFontSampleBoxWidth + kFontSampleBoxSpacing
}
CGPDFContextEndPage(context)
CGPDFContextClose(context)
pdfData = consumerData
}
private func loadFont(filePath: String) -> CTFont {
Swift.print("loading font \(filePath) using Core Text")
guard let data = NSData(contentsOfFile: filePath) else {
fatalError("failed to load font from \(filePath)")
}
let provider = CGDataProviderCreateWithCFData(data)
guard let font = CGFontCreateWithDataProvider(provider) else {
fatalError("failed to create Core Graphics font")
}
var error: Unmanaged<CFError>?
if !CTFontManagerRegisterGraphicsFont(font, &error) {
fatalError("failed to register font: \(error)")
}
return CTFontCreateWithGraphicsFont(font, CGFloat(fontSize), nil, nil)
}
}
// Application
let args = Array(Process.arguments[1..<Process.arguments.count])
if args.count < 3 {
print("usage: FontPreview PDF-FILE-NAME FONT-SIZE FONT-FILE-PATH [COMPARE-FONT-FILE-PATH ...]")
exit(1)
}
let (pdfFilePath, fontSize, primaryFontFilePath) =
(args[0], Float(args[1]) ?? kDefaultFontSize, args[2])
let comparisonFontFilePaths: [String] =
args.count > 3
? Array(args[3..<args.count])
: []
let application = NSApplication.sharedApplication()
let rect = NSRect(x: -1024, y: -1024, width: 1024, height: 1024)
let window = NSWindow(contentRect: rect,
styleMask: NSBorderlessWindowMask,
backing: NSBackingStoreType.Buffered,
`defer`: true)
window.makeKeyWindow()
let totalWidth = kFontSampleBoxSpacing + kFontSampleBoxWidth +
(kFontSampleBoxWidth * comparisonFontFilePaths.count) +
(kFontSampleBoxSpacing * comparisonFontFilePaths.count) +
kFontSampleBoxSpacing
let rendererFrame = NSRect(x: 0, y: 0, width: totalWidth, height: 400)
let rendererView = TextRendererView(frame: rendererFrame,
primaryFontFilePath: primaryFontFilePath,
fontSize: fontSize,
additionalFontFilePaths: comparisonFontFilePaths)
rendererView.text = kFontSampleText
window.contentView!.addSubview(rendererView)
rendererView.drawRect(rendererFrame)
NSRunLoop.currentRunLoop().runUntilDate(NSDate(timeIntervalSinceNow: 0.1))
if let data = rendererView.pdfData {
data.writeToFile(pdfFilePath, atomically: true)
print("rendered \(data.length) byte(s) to PDF file \(pdfFilePath)")
} else {
fatalError("rendering did not occur")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment