Sfoglia il codice sorgente

improved edit/label/font and panel shader

danaugrs 7 anni fa
parent
commit
3d9211ec5f

+ 1 - 1
gui/builder_panel.go

@@ -89,7 +89,7 @@ func buildLabel(b *Builder, am map[string]interface{}) (IPanel, error) {
 
 	var label *Label
 	if am[AttribIcon] != nil {
-		label = NewLabel(am[AttribIcon].(string), true)
+		label = NewIcon(am[AttribIcon].(string))
 	} else if am[AttribText] != nil {
 		label = NewLabel(am[AttribText].(string))
 	} else {

+ 1 - 1
gui/button.go

@@ -79,7 +79,7 @@ func NewButton(text string) *Button {
 // If there is currently a selected image, it is removed
 func (b *Button) SetIcon(icode string) {
 
-	ico := NewLabel(icode, true)
+	ico := NewIcon(icode)
 	if b.image != nil {
 		b.Panel.Remove(b.image)
 		b.image = nil

+ 1 - 1
gui/checkradio.go

@@ -90,7 +90,7 @@ func newCheckRadio(check bool, text string) *CheckRadio {
 	cb.Panel.Add(cb.Label)
 
 	// Creates icon label
-	cb.icon = NewLabel(" ", true)
+	cb.icon = NewIcon(" ")
 	cb.Panel.Add(cb.icon)
 
 	cb.recalc()

+ 2 - 2
gui/dropdown.go

@@ -52,8 +52,8 @@ func NewDropDown(width float32, item *ImageLabel) *DropDown {
 	dd.Panel.Add(dd.litem)
 
 	// Create icon
-	dd.icon = NewLabel(" ", true)
-	dd.icon.SetFontSize(StyleDefault().Font.Size() * 1.3)
+	dd.icon = NewIcon(" ")
+	dd.icon.SetFontSize(StyleDefault().Label.PointSize * 1.3)
 	dd.icon.SetText(string(icon.ArrowDropDown))
 	dd.Panel.Add(dd.icon)
 

+ 1 - 1
gui/edit.go

@@ -95,7 +95,7 @@ func (ed *Edit) Text() string {
 // SetFontSize sets label font size (overrides Label.SetFontSize)
 func (ed *Edit) SetFontSize(size float64) *Edit {
 
-	ed.Label.fontSize = size
+	ed.Label.SetFontSize(size)
 	ed.redraw(ed.focus)
 	return ed
 }

+ 1 - 1
gui/folder.go

@@ -56,7 +56,7 @@ func (f *Folder) Initialize(text string, width float32, contentPanel IPanel) {
 
 	// Create icon
 	f.icon.initialize("", StyleDefault().FontIcon)
-	f.icon.SetFontSize(f.label.FontSize() * 1.3)
+	f.icon.SetFontSize(StyleDefault().Label.PointSize * 1.3)
 	f.Panel.Add(&f.icon)
 
 	// Setup content panel

+ 1 - 1
gui/image_button.go

@@ -111,7 +111,7 @@ func (b *ImageButton) SetIcon(icode string) {
 	b.iconLabel = true
 	if b.label == nil {
 		// Create icon
-		b.label = NewLabel(icode, true)
+		b.label = NewIcon(icode)
 		b.Panel.Add(b.label)
 	} else {
 		b.label.SetText(icode)

+ 2 - 2
gui/imagelabel.go

@@ -72,8 +72,8 @@ func (il *ImageLabel) SetIcon(icon string) {
 		il.image = nil
 	}
 	if il.icon == nil {
-		il.icon = NewLabel(icon, true)
-		il.icon.SetFontSize(il.label.FontSize() * 1.4)
+		il.icon = NewIcon(icon)
+		il.icon.SetFontSize(StyleDefault().Label.PointSize * 1.4)
 		il.Panel.Add(il.icon)
 	}
 	il.icon.SetText(icon)

+ 108 - 92
gui/label.go

@@ -11,200 +11,216 @@ import (
 	"github.com/g3n/engine/texture"
 )
 
-// Label is a panel which contains a texture for rendering text.
+// Label is a panel which contains a texture with text.
 // The content size of the label panel is the exact size of the texture.
 type Label struct {
-	Panel       // Embedded panel
-	fontSize    float64
-	fontDPI     float64
-	lineSpacing float64
-	bgColor     math32.Color4
-	fgColor     math32.Color4
-	font        *text.Font
-	tex         *texture.Texture2D // Pointer to texture with drawed text
-	currentText string
-}
-
-// NewLabel creates and returns a label panel with the specified text
-// drawn using the current default text font.
-// If icon is true the text is drawn using the default icon font
-// TODO allow passing in any font
-func NewLabel(msg string, icon ...bool) *Label {
+	Panel                    // Embedded Panel
+	font  *text.Font         // TrueType font face
+	tex   *texture.Texture2D // Texture with text
+	style *LabelStyle        // The style of the panel and font attributes
+	text  string             // Text being displayed
+}
+
+// LabelStyle contains all the styling attributes of a Label.
+// It's essentially a BasicStyle combined with FontAttributes.
+type LabelStyle struct {
+	PanelStyle
+	text.FontAttributes
+	FgColor math32.Color4
+}
+
+// NewLabel creates and returns a label panel with
+// the specified text drawn using the default text font.
+func NewLabel(text string) *Label {
+	return NewLabelWithFont(text, StyleDefault().Font)
+}
+
+// NewLabel creates and returns a label panel with
+// the specified text drawn using the default icon font.
+func NewIcon(icon string) *Label {
+	return NewLabelWithFont(icon, StyleDefault().FontIcon)
+}
+
+// NewLabelWithFont creates and returns a label panel with
+// the specified text drawn using the specified font.
+func NewLabelWithFont(msg string, font *text.Font) *Label {
 
 	l := new(Label)
-	if len(icon) > 0 && icon[0] {
-		l.initialize(msg, StyleDefault().FontIcon)
-	} else {
-		l.initialize(msg, StyleDefault().Font)
-	}
+	l.initialize(msg, font)
 	return l
 }
 
 // initialize initializes this label and is normally used by other
-// gui types which contains a label.
+// components which contain a label.
 func (l *Label) initialize(msg string, font *text.Font) {
 
 	l.font = font
 	l.Panel.Initialize(0, 0)
 
-	// TODO: Remove this hack in an elegant way
+	// TODO: Remove this hack in an elegant way e.g. set the label style depending of if it's an icon or text label and have two defaults (one for icon labels one for text tabels)
 	if font != StyleDefault().FontIcon {
 		l.Panel.SetPaddings(2, 0, 2, 0)
 	}
 
-	l.fontSize = 14
-	l.fontDPI = 72
-	l.lineSpacing = 1.0
-	l.bgColor = math32.Color4{0, 0, 0, 0}
-	l.fgColor = math32.Color4{0, 0, 0, 1}
+	// Copy the style based on the default Label style
+	styleCopy := StyleDefault().Label
+	l.style = &styleCopy
+
 	l.SetText(msg)
 }
 
-// SetText draws the label text using the current font
-func (l *Label) SetText(msg string) {
+// SetText sets and draws the label text using the font.
+func (l *Label) SetText(text string) {
 
 	// Need at least a character to get dimensions
-	l.currentText = msg
-	if msg == "" {
-		msg = " "
+	l.text = text
+	if text == "" {
+		text = " "
 	}
 
 	// Set font properties
-	l.font.SetSize(l.fontSize)
-	l.font.SetDPI(l.fontDPI)
-	l.font.SetLineSpacing(l.lineSpacing)
-	l.font.SetBgColor4(&l.bgColor)
-	l.font.SetFgColor4(&l.fgColor)
-
-	// Measure text
-	width, height := l.font.MeasureText(msg)
-	// Create image canvas with the exact size of the texture
-	// and draw the text.
-	canvas := text.NewCanvas(width, height, &l.bgColor)
-	canvas.DrawText(0, 0, msg, l.font)
+	l.font.SetAttributes(&l.style.FontAttributes)
+	l.font.SetColor(&l.style.FgColor)
 
-	// Creates texture if if doesnt exist.
+	// Create an image with the text
+	textImage := l.font.DrawText(text)
+
+	// Create texture if it doesn't exist yet
 	if l.tex == nil {
-		l.tex = texture.NewTexture2DFromRGBA(canvas.RGBA)
+		l.tex = texture.NewTexture2DFromRGBA(textImage)
 		l.tex.SetMagFilter(gls.NEAREST)
 		l.tex.SetMinFilter(gls.NEAREST)
 		l.Panel.Material().AddTexture(l.tex)
 		// Otherwise update texture with new image
 	} else {
-		l.tex.SetFromRGBA(canvas.RGBA)
+		l.tex.SetFromRGBA(textImage)
 	}
 
-	// Updates label panel dimensions
-	l.Panel.SetContentSize(float32(width), float32(height))
+	// Update label panel dimensions
+	l.Panel.SetContentSize(float32(textImage.Rect.Dx()), float32(textImage.Rect.Dy()))
 }
 
-// Text returns the current label text
+// Text returns the label text.
 func (l *Label) Text() string {
 
-	return l.currentText
+	return l.text
 }
 
-// SetColor sets the color of the label text
-// The color alpha is set to 1.0
+// SetColor sets the text color.
+// Alpha is set to 1 (opaque).
 func (l *Label) SetColor(color *math32.Color) *Label {
 
-	l.fgColor.FromColor(color, 1.0)
-	l.SetText(l.currentText)
+	l.style.FgColor.FromColor(color, 1.0)
+	l.SetText(l.text)
 	return l
 }
 
-// SetColor4 sets the color4 of the label text
+// SetColor4 sets the text color.
 func (l *Label) SetColor4(color4 *math32.Color4) *Label {
 
-	l.fgColor = *color4
-	l.SetText(l.currentText)
+	l.style.FgColor = *color4
+	l.SetText(l.text)
 	return l
 }
 
-// Color returns the current color of the label text
+// Color returns the text color.
 func (l *Label) Color() math32.Color4 {
 
-	return l.fgColor
+	return l.style.FgColor
 }
 
-// SetBgColor sets the color of the label background
+// SetBgColor sets the background color.
 // The color alpha is set to 1.0
 func (l *Label) SetBgColor(color *math32.Color) *Label {
 
-	l.bgColor.FromColor(color, 1.0)
-	l.Panel.SetColor4(&l.bgColor)
-	l.SetText(l.currentText)
+	l.style.BgColor.FromColor(color, 1.0)
+	l.Panel.SetColor4(&l.style.BgColor)
+	l.SetText(l.text)
 	return l
 }
 
-// SetBgColor4 sets the color4 of the label background
+// SetBgColor4 sets the background color.
 func (l *Label) SetBgColor4(color *math32.Color4) *Label {
 
-	l.bgColor = *color
-	l.Panel.SetColor4(&l.bgColor)
-	l.SetText(l.currentText)
+	l.style.BgColor = *color
+	l.Panel.SetColor4(&l.style.BgColor)
+	l.SetText(l.text)
 	return l
 }
 
-// BgColor returns the current color the label background
+// BgColor returns returns the background color.
 func (l *Label) BgColor() math32.Color4 {
 
-	return l.bgColor
+	return l.style.BgColor
 }
 
-// SetFont sets this label text or icon font
+// SetFont sets the font.
 func (l *Label) SetFont(f *text.Font) {
 
 	l.font = f
-	l.SetText(l.currentText)
+	l.SetText(l.text)
+}
+
+// Font returns the font.
+func (l *Label) Font() *text.Font {
+
+	return l.font
 }
 
-// SetFontSize sets label font size
+// SetFontSize sets the point size of the font.
 func (l *Label) SetFontSize(size float64) *Label {
 
-	l.fontSize = size
-	l.SetText(l.currentText)
+	l.style.PointSize = size
+	l.SetText(l.text)
 	return l
 }
 
-// FontSize returns the current label font size
+// FontSize returns the point size of the font.
 func (l *Label) FontSize() float64 {
 
-	return l.fontSize
+	return l.style.PointSize
 }
 
-// SetFontDPI sets the font dots per inch
+// SetFontDPI sets the resolution of the font in dots per inch (DPI).
 func (l *Label) SetFontDPI(dpi float64) *Label {
 
-	l.fontDPI = dpi
-	l.SetText(l.currentText)
+	l.style.DPI = dpi
+	l.SetText(l.text)
 	return l
 }
 
+// FontDPI returns the resolution of the font in dots per inch (DPI).
+func (l *Label) FontDPI() float64 {
+
+	return l.style.DPI
+}
+
 // SetLineSpacing sets the spacing between lines.
-// The default value is 1.0
 func (l *Label) SetLineSpacing(spacing float64) *Label {
 
-	l.lineSpacing = spacing
-	l.SetText(l.currentText)
+	l.style.LineSpacing = spacing
+	l.SetText(l.text)
 	return l
 }
 
+// LineSpacing returns the spacing between lines.
+func (l *Label) LineSpacing() float64 {
+
+	return l.style.LineSpacing
+}
+
 // setTextCaret sets the label text and draws a caret at the
 // specified line and column.
 // It is normally used by the Edit widget.
 func (l *Label) setTextCaret(msg string, mx, width, line, col int) {
 
 	// Set font properties
-	l.font.SetSize(l.fontSize)
-	l.font.SetDPI(l.fontDPI)
-	l.font.SetLineSpacing(l.lineSpacing)
-	l.font.SetBgColor4(&l.bgColor)
-	l.font.SetFgColor4(&l.fgColor)
+	l.font.SetAttributes(&l.style.FontAttributes)
+	l.font.SetColor(&l.style.FgColor)
 
 	// Create canvas and draw text
 	_, height := l.font.MeasureText(msg)
-	canvas := text.NewCanvas(width, height, &l.bgColor)
+	canvas := text.NewCanvas(width, height, &l.style.BgColor)
 	canvas.DrawTextCaret(mx, 0, msg, l.font, line, col)
 
 	// Creates texture if if doesnt exist.
@@ -221,5 +237,5 @@ func (l *Label) setTextCaret(msg string, mx, width, line, col int) {
 
 	// Updates label panel dimensions
 	l.Panel.SetContentSize(float32(width), float32(height))
-	l.currentText = msg
+	l.text = msg
 }

+ 2 - 2
gui/menu.go

@@ -198,7 +198,7 @@ func (m *Menu) AddMenu(text string, subm *Menu) *MenuItem {
 	mi.submenu.autoOpen = true
 	mi.menu = m
 	if !m.bar {
-		mi.ricon = NewLabel(string(icon.PlayArrow), true)
+		mi.ricon = NewIcon(string(icon.PlayArrow))
 		mi.Panel.Add(mi.ricon)
 	}
 	mi.Panel.Add(mi.submenu)
@@ -594,7 +594,7 @@ func (mi *MenuItem) SetIcon(icon string) *MenuItem {
 		mi.licon = nil
 	}
 	// Sets the new icon
-	mi.licon = NewLabel(icon, true)
+	mi.licon = NewIcon(icon)
 	mi.Panel.Add(mi.licon)
 	mi.update()
 	return mi

+ 1 - 0
gui/style.go

@@ -12,6 +12,7 @@ import (
 type Style struct {
 	Font          *text.Font
 	FontIcon      *text.Font
+	Label         LabelStyle
 	Button        ButtonStyles
 	CheckRadio    CheckRadioStyles
 	Edit          EditStyles

+ 12 - 11
gui/style_light.go

@@ -25,11 +25,6 @@ func NewLightStyle() *Style {
 	if err != nil {
 		panic(err)
 	}
-	font.SetLineSpacing(1.0)
-	font.SetSize(14)
-	font.SetDPI(72)
-	font.SetFgColor4(math32.NewColor4("black"))
-	font.SetBgColor4(math32.NewColor4("black", 0))
 	s.Font = font
 
 	// Creates icon font
@@ -38,11 +33,6 @@ func NewLightStyle() *Style {
 	if err != nil {
 		panic(err)
 	}
-	fontIcon.SetLineSpacing(1.0)
-	fontIcon.SetSize(14)
-	fontIcon.SetDPI(72)
-	fontIcon.SetFgColor4(math32.NewColor4("black"))
-	fontIcon.SetBgColor4(math32.NewColor4("white", 0))
 	s.FontIcon = fontIcon
 
 	zeroBounds := RectBounds{0, 0, 0, 0}
@@ -62,6 +52,16 @@ func NewLightStyle() *Style {
 	fgColorSel := math32.Color4{0, 0, 0, 1}
 	fgColorDis := math32.Color4{0.4, 0.4, 0.4, 1}
 
+	// Label style
+	s.Label = LabelStyle{}
+	s.Label.FontAttributes = text.FontAttributes{}
+	s.Label.FontAttributes.PointSize = 14
+	s.Label.FontAttributes.DPI = 72
+	s.Label.FontAttributes.Hinting = text.HintingNone
+	s.Label.FontAttributes.LineSpacing = 1.0
+	s.Label.BgColor = math32.Color4{0,0,0,0}
+	s.Label.FgColor = math32.Color4{0,0,0,1}
+
 	// Button styles
 	s.Button = ButtonStyles{}
 	s.Button.Normal = ButtonStyle{}
@@ -74,7 +74,8 @@ func NewLightStyle() *Style {
 	s.Button.Over.BgColor = bgColorOver
 	s.Button.Focus = s.Button.Over
 	s.Button.Pressed = s.Button.Over
-	s.Button.Pressed.Border = twoBounds
+	s.Button.Pressed.Border = RectBounds{2, 2, 2, 2}
+	s.Button.Pressed.Padding = RectBounds{2, 2, 0, 4}
 	s.Button.Disabled = s.Button.Normal
 	s.Button.Disabled.BorderColor = borderColorDis
 	s.Button.Disabled.FgColor = fgColorDis

+ 3 - 3
gui/tabbar.go

@@ -78,7 +78,7 @@ func NewTabBar(width, height float32) *TabBar {
 	tb.Add(tb.list)
 
 	// Creates list icon button
-	tb.listButton = NewLabel(tb.styles.ListButtonIcon, true)
+	tb.listButton = NewIcon(tb.styles.ListButtonIcon)
 	tb.listButton.SetPaddingsFrom(&tb.styles.ListButtonPaddings)
 	tb.listButton.Subscribe(OnMouseDown, tb.onListButton)
 	tb.Add(tb.listButton)
@@ -409,7 +409,7 @@ func newTab(text string, tb *TabBar, styles *TabStyles) *Tab {
 	// Setup the header panel
 	tab.header.Initialize(0, 0)
 	tab.label = NewLabel(text)
-	tab.iconClose = NewLabel(styles.IconClose, true)
+	tab.iconClose = NewIcon(styles.IconClose)
 	tab.header.Add(tab.label)
 	tab.header.Add(tab.iconClose)
 	// Creates the bottom panel
@@ -493,7 +493,7 @@ func (tab *Tab) SetIcon(icon string) *Tab {
 	}
 	// Creates or updates icon
 	if tab.icon == nil {
-		tab.icon = NewLabel(icon, true)
+		tab.icon = NewIcon(icon)
 		tab.icon.SetPaddingsFrom(&tab.styles.IconPaddings)
 		tab.header.Add(tab.icon)
 	} else {

+ 1 - 1
gui/table.go

@@ -235,7 +235,7 @@ func NewTable(width, height float32, cols []TableColumn) (*Table, error) {
 		c.resize = cdesc.Resize
 		// Adds optional sort icon
 		if c.sort != TableSortNone {
-			c.ricon = NewLabel(string(tableSortedNoneIcon), true)
+			c.ricon = NewIcon(string(tableSortedNoneIcon))
 			c.Add(c.ricon)
 			c.ricon.Subscribe(OnMouseDown, func(evname string, ev interface{}) {
 				t.onRicon(evname, c)

+ 1 - 1
gui/tree.go

@@ -212,7 +212,7 @@ func newTreeNode(text string, tree *Tree, parNode *TreeNode) *TreeNode {
 
 	// Create node icon
 	n.icon.initialize("", StyleDefault().FontIcon)
-	n.icon.SetFontSize(n.label.FontSize() * 1.3)
+	n.icon.SetFontSize(StyleDefault().Label.PointSize * 1.3)
 	n.Panel.Add(&n.icon)
 
 	// Subscribe to events

+ 22 - 3
renderer/shaders/panel_fragment.glsl

@@ -72,18 +72,37 @@ void main() {
 
     // Check if fragment is inside content area
     if (checkRect(Content)) {
+
         // If no texture, the color will be the material color.
         vec4 color = ContentColor;
+
 		if (TextureValid) {
             // Adjust texture coordinates to fit texture inside the content area
             vec2 offset = vec2(-Content[0], -Content[1]);
             vec2 factor = vec2(1/Content[2], 1/Content[3]);
             vec2 texcoord = (FragTexcoord + offset) * factor;
             vec4 texColor = texture(MatTexture, texcoord * MatTexRepeat + MatTexOffset);
-            // Mix content color with texture color ???
-            //color = mix(color, texColor, texColor.a);
-            color = texColor;
+
+            // Mix content color with texture color.
+            // Note that doing a simple linear interpolation (e.g. using mix()) is not correct!
+            // The right formula can be found here: https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
+            // For a more in-depth discussion: http://apoorvaj.io/alpha-compositing-opengl-blending-and-premultiplied-alpha.html#toc4
+
+            // Pre-multiply the content color
+            vec4 contentPre = ContentColor;
+            contentPre.rgb *= contentPre.a;
+
+            // Pre-multiply the texture color
+            vec4 texPre = texColor;
+            texPre.rgb *= texPre.a;
+
+            // Combine colors the premultiplied final color
+            color = texPre + contentPre * (1 - texPre.a);
+
+            // Un-pre-multiply (pre-divide? :P)
+            color.rgb /= color.a;
 		}
+
         FragColor = color;
         return;
     }

+ 22 - 3
renderer/shaders/sources.go

@@ -331,18 +331,37 @@ void main() {
 
     // Check if fragment is inside content area
     if (checkRect(Content)) {
+
         // If no texture, the color will be the material color.
         vec4 color = ContentColor;
+
 		if (TextureValid) {
             // Adjust texture coordinates to fit texture inside the content area
             vec2 offset = vec2(-Content[0], -Content[1]);
             vec2 factor = vec2(1/Content[2], 1/Content[3]);
             vec2 texcoord = (FragTexcoord + offset) * factor;
             vec4 texColor = texture(MatTexture, texcoord * MatTexRepeat + MatTexOffset);
-            // Mix content color with texture color ???
-            //color = mix(color, texColor, texColor.a);
-            color = texColor;
+
+            // Mix content color with texture color.
+            // Note that doing a simple linear interpolation (e.g. using mix()) is not correct!
+            // The right formula can be found here: https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
+            // For a more in-depth discussion: http://apoorvaj.io/alpha-compositing-opengl-blending-and-premultiplied-alpha.html#toc4
+
+            // Pre-multiply the content color
+            vec4 contentPre = ContentColor;
+            contentPre.rgb *= contentPre.a;
+
+            // Pre-multiply the texture color
+            vec4 texPre = texColor;
+            texPre.rgb *= texPre.a;
+
+            // Combine colors the premultiplied final color
+            color = texPre + contentPre * (1 - texPre.a);
+
+            // Un-pre-multiply (pre-divide? :P)
+            color.rgb /= color.a;
 		}
+
         FragColor = color;
         return;
     }

+ 120 - 131
text/font.go

@@ -17,21 +17,25 @@ import (
 )
 
 // Font represents a TrueType font face.
+// Attributes must be set prior to drawing.
 type Font struct {
-	ttf         *truetype.Font
-	face        font.Face
-	fgColor     math32.Color4
-	bgColor     math32.Color4
-	fontSize    float64
-	fontDPI     float64
-	lineSpacing float64 // [0,1] relative to font height
-	fg          *image.Uniform
-	bg          *image.Uniform
-	hinting     font.Hinting
-	changed     bool
+	ttf     *truetype.Font // The TrueType font
+	face    font.Face      // The font face
+	attrib  FontAttributes // Internal attribute cache
+	fg      *image.Uniform // Text color cache
+	bg      *image.Uniform // Background color cache
+	changed bool           // Whether attributes have changed and the font face needs to be recreated
 }
 
-// Font hinting types.
+// FontAttributes contains tunable attributes of a font.
+type FontAttributes struct {
+	PointSize   float64      // Point size of the font
+	DPI         float64      // Resolution of the font in dots per inch
+	LineSpacing float64      // Spacing between lines (in terms of font height)
+	Hinting     font.Hinting // Font hinting
+}
+
+// Font Hinting types.
 const (
 	HintingNone     = font.HintingNone
 	HintingVertical = font.HintingVertical
@@ -39,17 +43,17 @@ const (
 )
 
 // NewFont creates and returns a new font object using the specified TrueType font file.
-func NewFont(fontfile string) (*Font, error) {
+func NewFont(ttfFile string) (*Font, error) {
 
 	// Reads font bytes
-	fontBytes, err := ioutil.ReadFile(fontfile)
+	fontBytes, err := ioutil.ReadFile(ttfFile)
 	if err != nil {
 		return nil, err
 	}
 	return NewFontFromData(fontBytes)
 }
 
-// NewFontFromData creates and returns a new font object from the specified data.
+// NewFontFromData creates and returns a new font object from the specified TTF data.
 func NewFontFromData(fontData []byte) (*Font, error) {
 
 	// Parses the font data
@@ -60,123 +64,93 @@ func NewFontFromData(fontData []byte) (*Font, error) {
 
 	f := new(Font)
 	f.ttf = ttf
-	f.fontSize = 12
-	f.fontDPI = 72
-	f.lineSpacing = 1.0
-	f.hinting = font.HintingNone
-	f.SetFgColor4(&math32.Color4{0, 0, 0, 1})
-	f.SetBgColor4(&math32.Color4{1, 1, 1, 0})
-	f.changed = false
-
-	// Creates font face
+
+	// Initialize with default values
+	f.attrib = FontAttributes{}
+	f.attrib.PointSize = 12
+	f.attrib.DPI = 72
+	f.attrib.LineSpacing = 1.0
+	f.attrib.Hinting = font.HintingNone
+	f.SetColor(&math32.Color4{0, 0, 0, 1})
+
+	// Create font face
 	f.face = truetype.NewFace(f.ttf, &truetype.Options{
-		Size:    f.fontSize,
-		DPI:     f.fontDPI,
-		Hinting: f.hinting,
+		Size:    f.attrib.PointSize,
+		DPI:     f.attrib.DPI,
+		Hinting: f.attrib.Hinting,
 	})
+
 	return f, nil
 }
 
-// SetSize sets the size of the font.
-func (f *Font) SetSize(size float64) {
+// SetPointSize sets the point size of the font.
+func (f *Font) SetPointSize(size float64) {
 
-	if size == f.fontSize {
+	if size == f.attrib.PointSize {
 		return
 	}
-	f.fontSize = size
+	f.attrib.PointSize = size
 	f.changed = true
 }
 
-// Size returns the current font size.
-func (f *Font) Size() float64 {
-
-	return f.fontSize
-}
-
-// SetDPI sets the current DPI of the font.
+// SetDPI sets the resolution of the font in dots per inches (DPI).
 func (f *Font) SetDPI(dpi float64) {
 
-	if dpi == f.fontDPI {
+	if dpi == f.attrib.DPI {
 		return
 	}
-	f.fontDPI = dpi
+	f.attrib.DPI = dpi
 	f.changed = true
 }
 
-// DPI returns the current font DPI.
-func (f *Font) DPI() float64 {
-
-	return f.fontDPI
-}
-
-// SetLineSpacing sets the spacing between lines.
+// SetLineSpacing sets the amount of spacing between lines (in terms of font height).
 func (f *Font) SetLineSpacing(spacing float64) {
 
-	f.lineSpacing = spacing
-}
-
-// LineSpacing returns the current spacing between lines.
-func (f *Font) LineSpacing() float64 {
-
-	return f.lineSpacing
+	if spacing == f.attrib.LineSpacing {
+		return
+	}
+	f.attrib.LineSpacing = spacing
+	f.changed = true
 }
 
 // SetHinting sets the hinting type.
 func (f *Font) SetHinting(hinting font.Hinting) {
 
-	if hinting == f.hinting {
+	if hinting == f.attrib.Hinting {
 		return
 	}
-	f.hinting = hinting
+	f.attrib.Hinting = hinting
 	f.changed = true
 }
 
-// Hinting returns the current hinting type.
-func (f *Font) Hinting() font.Hinting {
+// SetFgColor sets the text color.
+func (f *Font) SetFgColor(color *math32.Color4) {
 
-	return f.hinting
-}
-
-// SetFgColor sets the current foreground color of the font.
-// The alpha value is set to 1 (opaque).
-func (f *Font) SetFgColor(color *math32.Color) {
-
-	f.fgColor.FromColor(color, 1.0)
-	f.fg = image.NewUniform(Color4RGBA(&f.fgColor))
-}
-
-// SetFgColor4 sets the current foreground color of the font.
-func (f *Font) SetFgColor4(color *math32.Color4) {
-
-	f.fgColor = *color
 	f.fg = image.NewUniform(Color4RGBA(color))
 }
 
-// FgColor4 returns the current foreground color.
-func (f *Font) FgColor4() math32.Color4 {
-
-	return f.fgColor
-}
-
-// SetBgColor sets the current foreground color of the font.
-// The alpha value is set to 1 (opaque).
-func (f *Font) SetBgColor(color *math32.Color) {
+// SetBgColor sets the background color.
+func (f *Font) SetBgColor(color *math32.Color4) {
 
-	f.bgColor.FromColor(color, 1.0)
-	f.bg = image.NewUniform(Color4RGBA(&f.fgColor))
+	f.bg = image.NewUniform(Color4RGBA(color))
 }
 
-// SetBgColor4 sets the current background color of the font.
-func (f *Font) SetBgColor4(color *math32.Color4) {
+// SetColor sets the text color to the specified value and makes the background color transparent.
+// Note that for perfect transparency in the anti-aliased region it's important that the RGB components
+// of the text and background colors match. This method handles that for the user.
+func (f *Font) SetColor(fg *math32.Color4) {
 
-	f.bgColor = *color
-	f.bg = image.NewUniform(Color4RGBA(color))
+	f.fg = image.NewUniform(Color4RGBA(fg))
+	f.bg = image.NewUniform(Color4RGBA(&math32.Color4{fg.R, fg.G, fg.B, 0}))
 }
 
-// BgColor4 returns the current background color.
-func (f *Font) BgColor4() math32.Color4 {
+// SetAttributes sets the font attributes.
+func (f *Font) SetAttributes(fa *FontAttributes) {
 
-	return f.bgColor
+	f.SetPointSize(fa.PointSize)
+	f.SetDPI(fa.DPI)
+	f.SetLineSpacing(fa.LineSpacing)
+	f.SetHinting(fa.Hinting)
 }
 
 // updateFace updates the font face if parameters have changed.
@@ -184,17 +158,16 @@ func (f *Font) updateFace() {
 
 	if f.changed {
 		f.face = truetype.NewFace(f.ttf, &truetype.Options{
-			Size:    f.fontSize,
-			DPI:     f.fontDPI,
-			Hinting: f.hinting,
+			Size:    f.attrib.PointSize,
+			DPI:     f.attrib.DPI,
+			Hinting: f.attrib.Hinting,
 		})
 		f.changed = false
 	}
 }
 
-// MeasureText returns the minimum width and height in pixels
-// necessary for an image to contain the specified text.
-// The supplied text string can contain line break escape sequences (\n).
+// MeasureText returns the minimum width and height in pixels necessary for an image to contain
+// the specified text. The supplied text string can contain line break escape sequences (\n).
 func (f *Font) MeasureText(text string) (int, int) {
 
 	// Create font drawer
@@ -205,7 +178,7 @@ func (f *Font) MeasureText(text string) (int, int) {
 	var width, height int
 	metrics := f.face.Metrics()
 	lineHeight := (metrics.Ascent + metrics.Descent).Ceil()
-	lineGap := int((f.lineSpacing - float64(1)) * float64(lineHeight))
+	lineGap := int((f.attrib.LineSpacing - float64(1)) * float64(lineHeight))
 
 	lines := strings.Split(text, "\n")
 	for i, s := range lines {
@@ -222,14 +195,47 @@ func (f *Font) MeasureText(text string) (int, int) {
 	return width, height
 }
 
-// Metrics returns the font metrics
+// Metrics returns the font metrics.
 func (f *Font) Metrics() font.Metrics {
 
 	f.updateFace()
 	return f.face.Metrics()
 }
 
-// Canvas is an image to draw text
+// DrawText draws the specified text on a new, tightly fitting image, and returns a pointer to the image.
+func (f *Font) DrawText(text string) *image.RGBA {
+
+	width, height := f.MeasureText(text)
+	img := image.NewRGBA(image.Rect(0, 0, width, height))
+	draw.Draw(img, img.Bounds(), f.bg, image.ZP, draw.Src)
+	f.DrawTextOnImage(text, 0, 0, img)
+
+	return img
+}
+
+// DrawTextOnImage draws the specified text on the specified image at the specified coordinates.
+func (f *Font) DrawTextOnImage(text string, x, y int, dst *image.RGBA) {
+
+	f.updateFace()
+	d := &font.Drawer{Dst: dst, Src: f.fg, Face: f.face}
+
+	// Draw text
+	metrics := f.face.Metrics()
+	py := y + metrics.Ascent.Round()
+	lineHeight := (metrics.Ascent + metrics.Descent).Ceil()
+	lineGap := int((f.attrib.LineSpacing - float64(1)) * float64(lineHeight))
+	lines := strings.Split(text, "\n")
+	for i, s := range lines {
+		d.Dot = fixed.P(x, py)
+		d.DrawString(s)
+		py += lineHeight
+		if i > 1 {
+			py += lineGap
+		}
+	}
+}
+
+// Canvas is an image to draw on.
 type Canvas struct {
 	RGBA    *image.RGBA
 	bgColor *image.Uniform
@@ -255,30 +261,14 @@ func NewCanvas(width, height int, bgColor *math32.Color4) *Canvas {
 // The supplied text string can contain line break escape sequences (\n).
 func (c Canvas) DrawText(x, y int, text string, f *Font) {
 
-	// Creates drawer
-	f.updateFace()
-	d := &font.Drawer{Dst: c.RGBA, Src: f.fg, Face: f.face}
-
-	// Draw text
-	metrics := f.face.Metrics()
-	py := y + metrics.Ascent.Round()
-	lineHeight := (metrics.Ascent + metrics.Descent).Ceil()
-	lineGap := int((f.lineSpacing - float64(1)) * float64(lineHeight))
-	lines := strings.Split(text, "\n")
-	for i, s := range lines {
-		d.Dot = fixed.P(x, py)
-		d.DrawString(s)
-		py += lineHeight
-		if i > 1 {
-			py += lineGap
-		}
-	}
+	f.DrawTextOnImage(text, x, y, c.RGBA)
 }
 
 // DrawTextCaret draws text at the specified position (in pixels)
 // of this canvas, using the specified font, and also a caret at
 // the specified line and column.
 // The supplied text string can contain line break escape sequences (\n).
+// TODO Implement caret as a gui.Panel in gui.Edit
 func (c Canvas) DrawTextCaret(x, y int, text string, f *Font, line, col int) error {
 
 	// Creates drawer
@@ -289,7 +279,7 @@ func (c Canvas) DrawTextCaret(x, y int, text string, f *Font, line, col int) err
 	metrics := f.face.Metrics()
 	py := y + metrics.Ascent.Round()
 	lineHeight := (metrics.Ascent + metrics.Descent).Ceil()
-	lineGap := int((f.lineSpacing - float64(1)) * float64(lineHeight))
+	lineGap := int((f.attrib.LineSpacing - float64(1)) * float64(lineHeight))
 	lines := strings.Split(text, "\n")
 	for l, s := range lines {
 		d.Dot = fixed.P(x, py)
@@ -298,11 +288,9 @@ func (c Canvas) DrawTextCaret(x, y int, text string, f *Font, line, col int) err
 		if l == line && col <= StrCount(s) {
 			width, _ := f.MeasureText(StrPrefix(s, col))
 			// Draw caret vertical line
-			caretH := int(f.fontSize) + 2
-			//caretY := int(pt.Y>>6) - int(f.fontSize) + 2
-			caretY := int(d.Dot.Y>>6) - int(f.fontSize) + 2
-			fgCol := f.FgColor4()
-			color := Color4RGBA(&fgCol)
+			caretH := int(f.attrib.PointSize) + 2
+			caretY := int(d.Dot.Y>>6) - int(f.attrib.PointSize) + 2
+			color := Color4RGBA(&math32.Color4{0,0,0,1}) // Hardcoded to black
 			for j := caretY; j < caretY+caretH; j++ {
 				c.RGBA.Set(x+width, j, color)
 			}
@@ -313,7 +301,8 @@ func (c Canvas) DrawTextCaret(x, y int, text string, f *Font, line, col int) err
 		}
 	}
 
-	//	pt := freetype.Pt(font.marginX+x, font.marginY+y+int(font.ctx.PointToFixed(font.fontSize)>>6))
+	// TODO remove ?
+	//	pt := freetype.Pt(font.marginX+x, font.marginY+y+int(font.ctx.PointToFixed(font.attrib.PointSize)>>6))
 	//	for l, s := range lines {
 	//		// Draw string
 	//		_, err := font.ctx.DrawString(s, pt)
@@ -327,25 +316,25 @@ func (c Canvas) DrawTextCaret(x, y int, text string, f *Font, line, col int) err
 	//				return err
 	//			}
 	//			// Draw caret vertical line
-	//			caretH := int(font.fontSize) + 2
-	//			caretY := int(pt.Y>>6) - int(font.fontSize) + 2
+	//			caretH := int(font.PointSize) + 2
+	//			caretY := int(pt.Y>>6) - int(font.PointSize) + 2
 	//			color := Color4RGBA(&math32.Color4{0, 0, 0, 1})
 	//			for j := caretY; j < caretY+caretH; j++ {
 	//				c.RGBA.Set(x+width, j, color)
 	//			}
 	//		}
 	//		// Increment y coordinate
-	//		pt.Y += font.ctx.PointToFixed(font.fontSize * font.lineSpacing)
+	//		pt.Y += font.ctx.PointToFixed(font.PointSize * font.LineSpacing)
 	//	}
 	return nil
 }
 
-// Color4RGBA converts a math32.Color4 to Go's color.RGBA
+// Color4RGBA converts a math32.Color4 to Go's color.RGBA.
 func Color4RGBA(c *math32.Color4) color.RGBA {
 
 	red := uint8(c.R * 0xFF)
 	green := uint8(c.G * 0xFF)
 	blue := uint8(c.B * 0xFF)
-	al := uint8(c.A * 0xFF)
-	return color.RGBA{red, green, blue, al}
+	alpha := uint8(c.A * 0xFF)
+	return color.RGBA{red, green, blue, alpha}
 }