Note : all the code I am going to be posting under the GRAPHICS category is EXPERIMENTAL, and may required modifications if you plan to use it.
I plan on (over time) making this as close to the operation of the Xojo Canvas as I can.
There will also be posts in this same category for PDF and Picture methods both subclassed from this Canvas Object
/* ********************************* */
/* **** **** */
/* **** C A N V A S C L A S S **** */
/* **** Super : none **** */
/* **** **** */
/* ********************************* */
class Canvas {
private let sysFontName : String = "HelveticaNeue"
private let styleDASH : [CGFloat]=[4,4] // 11110000
private let styleDOT : [CGFloat]=[2,2] // 1100
private let styleNORMAL : [CGFloat]=[1] // 1
private let styleNONE : [CGFloat]=[]
//
private var zBold : Bool = false
private var zItalic : Bool = false
private var zUnderline : Bool = false
private let zAlign = NSMutableParagraphStyle()
private var zTextFont : String = ""
private var zTextSize : CGFloat = 13
private var zPenSize : CGFloat = 0.5
private var zPenStyle = lineStyle.solid
private var zForecolor : UIColor = UIColor.black
//
private var zFontInfo : UIFont = UIFont(name: "HelveticaNeue", size:13)!
//
private var zContext : CGContext?
private var zOFFSET : CGPoint = CGPoint.zero
private var zPosition : CGPoint = CGPoint.zero // Keep Track of "pen" position
//
// Create Graphics Object
//
init() {}
init(context: CGContext,offset:CGPoint=CGPoint.zero) {
zContext = context
zOFFSET = CGPoint(x: offset.x,y: offset.y)
textFont=""
textSize=(-1)
updateFont(1)
zAlign.alignment = .left
zContext?.setLineWidth(zPenSize)
}
var context : CGContext { get { return zContext! } }
var Offset : CGPoint {
get { return zOFFSET }
set { zOFFSET=newValue }
}
//
// Set/Get FontName
//
var textFont : String {
get { return zTextFont }
set {
zTextFont=newValue
if zTextFont.isEmpty || zTextFont.uppercased()=="SYSTEM" { zTextFont=sysFontName }
updateFont(2)
}
}
//
// Set/Get FontSize
//
var textSize : CGFloat {
get { return zTextSize }
set {
zTextSize=newValue
if zTextSize<=0 { zTextSize=13 }
updateFont(3)
}
}
//
// Set/Get Bold Trait
//
var bold : Bool {
set { zBold=newValue;updateFont(4) }
get { return zBold}
}
//
// Set/Get Italic Trait
//
var italic : Bool {
set { zItalic=newValue;updateFont(5)}
get { return zItalic}
}
//
// Set/Get Underline Trait
//
var underline : Bool {
set { zUnderline=newValue }
get { return zUnderline}
}
var textHeight : CGFloat { get { return zFontInfo.ascender-zFontInfo.descender } }
var textAscent : CGFloat { get { return zFontInfo.ascender } }
func stringWidth(_ text:String) -> CGFloat { return stringInfo(text).width }
func stringHeight(_ text:String) -> CGFloat { return stringInfo(text).height }
//
// Set/Get FontSize
//
var foreColor : UIColor {
get { return zForecolor }
set {
zForecolor=newValue
zContext?.setFillColor(zForecolor.cgColor)
zContext?.setStrokeColor(zForecolor.cgColor)
}
}
//
// Set/Get Text Alignment
//
var align : NSTextAlignment {
get { return zAlign.alignment }
set { zAlign.alignment = newValue }
}
//
// Set the PenSize [ie. Line Drawing Width]
//
var penSize : CGFloat {
get { return zPenSize }
set {
if zPenSize != newValue {
zPenSize=newValue
// zPenSize = max(0.25,newValue)
zContext?.setLineWidth(zPenSize)
}
}
}
//
// Set Pen Style [solid, dotted, dashed]
//
var penStyle :lineStyle {
get { return zPenStyle }
set {
if zPenStyle != newValue {
zPenStyle=newValue
switch zPenStyle {
case .none : zContext?.setLineDash(phase: 0,lengths: styleNONE)
case .solid: zContext?.setLineDash(phase:0,lengths: styleNORMAL)
case .dot : zContext?.setLineDash(phase:0,lengths: styleDOT)
case .dash : zContext?.setLineDash(phase:0,lengths: styleDASH)
}
}
}
}
//
// Track "pen" Position, this is required by some PDF routines
//
var position : CGPoint {
get { return zPosition }
set { zPosition = newValue }
}
//
// Insert an IMAGE into document
//
func drawPicture(_ img:UIImage,_ rect:CGRect) { img.draw(in: adjRect(rect)) }
//
// Insert Text into document
//
func drawString(_ text:String,_ rect:CGRect) {
let myString : NSString = text as NSString
//
let textAttributes = [
.font : zFontInfo,
.paragraphStyle : zAlign,
.foregroundColor : zForecolor
] as [NSAttributedString.Key : NSObject]
myString.draw(in: adjRect(rect), withAttributes: textAttributes)
// Bold and Italic are part of actual Font (if available), Underline we "fake"
if (zUnderline==true) {
let oldSize : CGFloat = penSize
let oldStyle : lineStyle = penStyle
var x1 : CGFloat=0
var x2 : CGFloat=0
let y : CGFloat=rect.minY+zFontInfo.ascender+1
let w=stringWidth(text)
penSize=0.5
switch zAlign.alignment {
case .left:
x1=rect.minX
x2=x1+w
case .center:
x1=rect.minX+(rect.width-w)/2
x2=x1+w
case .right:
x2=rect.maxX
x1=x2-w
default:
return
}
drawLine(x1,y,x2,y)
penSize = oldSize
penStyle = oldStyle
}
}
func drawString(_ text:String,_ x:CGFloat,_ y:CGFloat, width:CGFloat=0) {
let h:CGFloat=stringHeight(text)+1
var w:CGFloat=width
if w<=0 { w=stringWidth(text)+1 }
drawString(text,CGRect(x: x,y: y,width: w,height: h))
}
//
// Draw a line from (X1,Y1) -> (X2,Y2)
//
func drawLine(_ x1:CGFloat,_ y1:CGFloat,_ x2:CGFloat,_ y2:CGFloat) {
zContext?.move(to: CGPoint(x: x1+zOFFSET.x, y: y1+zOFFSET.y))
zContext?.addLine(to: CGPoint(x: x2+zOFFSET.x, y: y2+zOFFSET.y))
zContext?.strokePath()
position=CGPoint(x: x2,y: y2)
}
//
// OVAL
//
func drawOval(_ rect:CGRect) { zContext?.strokeEllipse(in: adjRect(rect)) }
func drawOval(_ x:CGFloat,_ y:CGFloat,_ width:CGFloat,_ height:CGFloat) { drawOval(CGRect(x: x,y: y,width: width,height: height)) }
func fillOval(_ rect:CGRect) { zContext?.fillEllipse(in: adjRect(rect))}
func fillOval(_ x:CGFloat,_ y:CGFloat,_ width:CGFloat,_ height:CGFloat) { fillOval(CGRect(x: x,y: y,width: width,height: height)) }
//
// Rectangle
//
func drawRect(_ rect:CGRect) { zContext?.stroke(adjRect(rect)) }
func drawRect(_ x:CGFloat,_ y:CGFloat,_ width:CGFloat,_ height:CGFloat) { drawRect(CGRect(x: x,y: y,width: width,height: height)) }
func fillRect(_ rect:CGRect) { zContext?.fill(adjRect(rect)) }
func fillRect(_ x:CGFloat,_ y:CGFloat,_ width:CGFloat,_ height:CGFloat) { fillRect(CGRect(x: x,y: y,width: width,height: height)) }
//
// Rounded Rectangle
//
func drawRoundrect(_ rect:CGRect,_ radius:CGFloat) { let path=UIBezierPath(roundedRect:rect,cornerRadius: radius); path.stroke() }
func drawRoundrect(_ x:CGFloat,_ y:CGFloat,_ width:CGFloat,_ height:CGFloat,_ radius:CGFloat) { drawRoundrect(CGRect(x: x,y: y,width: width,height: height),radius) }
func fillRoundrect(_ rect:CGRect,_ radius:CGFloat) { let path=UIBezierPath(roundedRect:rect,cornerRadius: radius); path.fill()}
func fillRoundrect(_ x:CGFloat,_ y:CGFloat,_ width:CGFloat,_ height:CGFloat,_ radius:CGFloat) { fillRoundrect(CGRect(x: x,y: y,width: width,height: height),radius) }
//
// Polygon
//
func drawPolygon(_ points:[CGPoint],fill:Bool=false) {
for i in(0...points.count-1) {
if(i == 0) {
zContext?.move(to: CGPoint(x: points[i].x, y: points[i].y))
} else {
zContext?.addLine(to: CGPoint(x: points[i].x, y: points[i].y))
}
}
zContext?.closePath()
if fill==true {
zContext?.fillPath()
} else {
zContext?.strokePath()
}
}
func fillPolygon(_ points:[CGPoint]) { drawPolygon(points,fill: true) }
/*--------------------------*/
/* PRIVATE CANVAS FUNCTIONS */
/*--------------------------*/
// create a UIFONT object based on Font/Size properties
private func updateFont(_ id:Int) {
func withTraits(_ traits:UIFontDescriptor.SymbolicTraits...) {
let descriptor = zFontInfo.fontDescriptor.withSymbolicTraits(UIFontDescriptor.SymbolicTraits(traits))
zFontInfo=UIFont(descriptor: descriptor!, size: 0)
}
zFontInfo=UIFont(name: zTextFont, size: zTextSize)!
if (zBold==true) || (zItalic==true) {
if (zBold==true) && (zItalic==false) { withTraits( .traitBold) }
if (zBold==false) && (zItalic==true) { withTraits( .traitItalic) }
if (zBold==true) && (zItalic==true) { withTraits( .traitItalic , .traitBold) }
}
}
// return String Metrics based on current font
private func stringInfo(_ text:String) -> CGSize {
let myString: NSString = text as NSString
return myString.size(withAttributes: [ .font: zFontInfo ])
}
private func adjRect(_ rect:CGRect) -> CGRect {
position=CGPoint(x: rect.maxX,y: rect.maxY) // DO NOT ADJUST POSITION
return rect.offsetBy(dx: zOFFSET.x, dy: zOFFSET.y)
}
}