logo

const class

fwt::Pen

sys::Obj
  fwt::Pen
//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   11 Jul 08  Brian Frank  Creation
//

**
** Pen defines how a shape is stroked.
**
@simple
const class Pen
{

  **
  ** Width of the stroke, default is 1.
  **
  const Int width := 1

  **
  ** Defines how two ends of unjoined segements are stroked.
  ** Valid values are `capSquare`, `capButt`, and `capRound`.
  ** Default is capSquare.
  **
  const Int cap := capSquare

  ** Constant for `cap`
  static const Int capSquare := 0
  ** Constant for `cap`
  static const Int capButt  := 1
  ** Constant for `cap`
  static const Int capRound := 2

  **
  ** Defines how two path segments are joined at the endpoints.
  ** Valid values are `joinMiter`, `joinRound`, and `joinBevel`.
  ** Default is joinMiter.
  **
  const Int join := joinMiter

  ** Constant for `join`
  static const Int joinMiter := 0
  ** Constant for `join`
  static const Int joinBevel := 1
  ** Constant for `join`
  static const Int joinRound := 3

  **
  ** Dash pattern as on/off again lengths.  If null
  ** then shapes are stroked solid.
  **
  const Int[] dash

  **
  ** Default pen is width of 1 with capSquare and joinMiter.
  **
  static const Pen def := make

  **
  ** Parse a pen from string (see `toStr`).  If invalid
  ** and checked is true then throw ParseErr otherwise
  ** return null.
  **
  static Pen fromStr(Str str, Bool checked := true)
  {
    try
    {
      w := null
      c := capSquare
      j := joinMiter
      Int[] d := null

      b := str.index("[")
      if (b != null)
      {
        d = Int[,]
        str[b+1...str.index("]")].split(',').each |Str tok| { d.add(tok.toInt) }
        str = str[0...b].trim
      }

      str.split.each |Str s|
      {
        switch (s)
        {
          case "capSquare": c = capSquare
          case "capButt":   c = capButt
          case "capRound":  c = capRound
          case "joinMiter": j = joinMiter
          case "joinBevel": j = joinBevel
          case "joinRound": j = joinRound
          default:          w = s.toInt
        }
      }

      return Pen { width = w; cap = c; join = j; dash = d }
    }
    catch {}
    if (checked) throw ParseErr("Invalid Pen: $str")
    return null
  }

  **
  ** Hash the fields.
  **
  override Int hash()
  {
    h := width ^ (cap << 16) ^ (join << 20)
    if (dash != null) h ^= dash.hash << 32
    return h
  }

  **
  ** Equality is based on Pen's fields.
  **
  override Bool equals(Obj obj)
  {
    that := obj as Pen
    if (that == null) return false
    return this.width == that.width &&
           this.cap   == that.cap   &&
           this.join  == that.join  &&
           this.dash  == that.dash
  }

  **
  ** Return '"width cap join dash"' such as '"2 capButt joinBevel [1,1]"'.
  ** Omit cap, join, or dash if at defaults.
  **
  override Str toStr()
  {
    s := width.toStr
    switch (cap)
    {
      case capButt:  s += " capButt"
      case capRound: s += " capRound"
    }
    switch (join)
    {
      case joinBevel: s += " joinBevel"
      case joinRound: s += " joinRound"
    }
    if (dash != null) s += " [" + dash.join(",") + "]"
    return s
  }

}