diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7a6431a10faa4c74e2eda5d2101dbc57d2a8524d..18f5917437ddfec06e7054e91747fd66e8ef8757 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -26,6 +26,7 @@ if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
                 glad/0.1.35
                 glm/0.9.9.8
                 spdlog/1.10.0
+                freetype/2.11.1
             GENERATORS 
                 cmake_find_package
                 cmake_find_package_multi
diff --git a/demo/data/LiberationSans-Regular.ttf b/demo/data/LiberationSans-Regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..22bae91bf0dcb729520a0da12d834db056e5044a
Binary files /dev/null and b/demo/data/LiberationSans-Regular.ttf differ
diff --git a/demo/main.cpp b/demo/main.cpp
index 73ff92b915fb8ebb51a12aa17d33156b43ee7eca..f17d9fe2fffa7cfd8d9c1afb6b21312349590275 100644
--- a/demo/main.cpp
+++ b/demo/main.cpp
@@ -337,6 +337,8 @@ int main(int argc, const char* argv[]) {
     auto& locator = fggl::util::ServiceLocator::instance();
 	auto inputs = locator.supply<fggl::input::Input>(std::make_shared<fggl::input::Input>());
 	auto storage = locator.supply<fggl::data::Storage>(std::make_shared<fggl::data::Storage>());
+
+	locator.supply<fggl::gui::FontLibrary>(std::make_shared<fggl::gui::FontLibrary>());
 	locator.supply<fggl::ecs3::TypeRegistry>(std::make_shared<fggl::ecs3::TypeRegistry>());
 
     // Would be nice to not take args like this, it messes with lifetimes
diff --git a/fggl/CMakeLists.txt b/fggl/CMakeLists.txt
index f2db608663e1ecf4f1e4d64894cd07ebd110c750..fbcb32f26ccc9af780c6fd433bd2da038ac5517e 100644
--- a/fggl/CMakeLists.txt
+++ b/fggl/CMakeLists.txt
@@ -50,6 +50,7 @@ target_sources(${PROJECT_NAME}
 	gui/widget.cpp
 	gui/widgets.cpp
 	gui/containers.cpp
+	gui/fonts.cpp
 	math/triangulation.cpp
 	math/shapes.cpp
 )
@@ -58,6 +59,9 @@ target_sources(${PROJECT_NAME}
 find_package( spdlog )
 target_link_libraries(${PROJECT_NAME} PRIVATE spdlog::spdlog)
 
+find_package( freetype )
+target_link_libraries(${PROJECT_NAME} PUBLIC freetype )
+
 # Graphics backend
 add_subdirectory(gfx)
 
diff --git a/fggl/gfx/ogl/shader.cpp b/fggl/gfx/ogl/shader.cpp
index 63e69face5b108c876cac08643dc276414aa748c..e512f56bedba8435315de59e64bc1d71f5520a59 100644
--- a/fggl/gfx/ogl/shader.cpp
+++ b/fggl/gfx/ogl/shader.cpp
@@ -42,7 +42,7 @@ bool ShaderCache::compileShader(const std::string& fname, GLuint sid) {
 
 ShaderCache::ShaderCache(std::shared_ptr<fggl::data::Storage> storage) : m_storage(storage), m_shaders(), m_binary(true) {
 
-	if (GLAD_GL_ARB_get_program_binary) {
+	if ( !GLAD_GL_ARB_get_program_binary ) {
 		spdlog::warn("the graphics card doesn support shader caching, disabling");
 		m_binary = false;
 	}
diff --git a/fggl/gfx/ogl4/canvas.cpp b/fggl/gfx/ogl4/canvas.cpp
index 548dc152acf8f239f8d3c410d3ac858acf383b6a..3ea8893691144096fbe5447fa4b6a268472a7402 100644
--- a/fggl/gfx/ogl4/canvas.cpp
+++ b/fggl/gfx/ogl4/canvas.cpp
@@ -26,11 +26,22 @@
 
 #include <glm/gtc/type_ptr.hpp>
 #include "fggl/math/triangulation.hpp"
+#include "fggl/util/service.h"
+
+#include "fggl/gui/fonts.hpp"
 
 #define FGGL_OPENGL_CORRECTNESS
 
 namespace fggl::gfx::ogl4 {
 
+	static void make_box( gfx::Path2D& path, glm::vec2 topLeft, glm::vec2 bottomRight ) {
+		path.moveTo( { topLeft.x, topLeft.y } );
+		path.pathTo( { bottomRight.x, topLeft.y } );
+		path.pathTo( { bottomRight.x, bottomRight.y } );
+		path.pathTo( { topLeft.x, bottomRight.y } );
+		path.pathTo( { topLeft.x, topLeft.y } );
+	}
+
 	inline static void add_mesh_triangle(data::Mesh2D& mesh, const std::vector<data::Vertex2D>& verts) {
 		assert( verts.size() == 3);
 		for( const auto& vert : verts ) {
@@ -73,7 +84,7 @@ namespace fggl::gfx::ogl4 {
 				}
 			}
 
-			if (!verts.empty()) {
+			if (!verts.empty() && verts.size() > 3) {
 				math::fan_triangulation(verts, mesh);
 			}
 		}
@@ -105,7 +116,7 @@ namespace fggl::gfx::ogl4 {
 		glBindVertexArray(0);
 	}
 
-	void CanvasRenderer::render(GLuint shader, const gfx::Paint &paint) {
+	void CanvasRenderer::renderShapes (const gfx::Paint& paint, GLuint shader) {
 		data::Mesh2D mesh;
 		convert_to_mesh(paint, mesh);
 
@@ -131,4 +142,59 @@ namespace fggl::gfx::ogl4 {
 		glUseProgram( 0 );
 	}
 
+	void CanvasRenderer::renderText(const Paint& paint, GLuint shader) {
+
+		if ( paint.textCmds().empty() ) {
+			return;
+		}
+
+		auto fontFactory = util::ServiceLocator::instance().get<gui::FontLibrary>();
+		std::shared_ptr<gui::FontFace> face = fontFactory->getFont("LiberationSans-Regular.ttf");
+		if ( face == nullptr ){
+			return;
+		}
+
+		Paint textPaint;
+		for ( const auto& textCmd : paint.textCmds() ) {
+			auto label = textCmd.text;
+
+			gfx::Path2D textPath(textCmd.pos);
+			math::vec2 penPos(textCmd.pos);
+
+			// create a quad for this character
+			for (auto letter : label) {
+				auto& metrics = face->metrics(letter);
+
+				std::cerr << metrics.size.x << std::endl;
+
+				make_box(textPath, penPos, penPos + metrics.size);
+				penPos.x += (metrics.advance >> 6 );
+			}
+
+			textPaint.fill(textPath);
+		}
+
+		data::Mesh2D mesh;
+		convert_to_mesh(textPaint, mesh);
+
+		// render the text mesh
+		m_vao.bind();
+		m_vertexList.replace(mesh.vertexList.size(), mesh.vertexList.data());
+		m_indexList.replace(mesh.indexList.size(), mesh.indexList.data());
+
+		// draw
+		glUseProgram( shader );
+		auto projMat = glm::ortho(0.0f, 1920.0f, 1080.0f, 0.f);
+		glUniformMatrix4fv(glGetUniformLocation( shader, "projection"), 1, GL_FALSE,
+						   glm::value_ptr(projMat));
+
+		m_vao.drawElements(m_indexList, ogl::Primative::TRIANGLE, mesh.indexList.size());
+		glUseProgram( 0 );
+	}
+
+	void CanvasRenderer::render(GLuint shader, const gfx::Paint &paint) {
+		renderShapes(paint, shader);
+		renderText(paint, shader);
+	}
+
 }
diff --git a/fggl/gui/fonts.cpp b/fggl/gui/fonts.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..36a72bea3d6f488af4ded3c25913b8f673580fde
--- /dev/null
+++ b/fggl/gui/fonts.cpp
@@ -0,0 +1,62 @@
+/*
+ * ${license.title}
+ * Copyright (C) 2022 ${license.owner}
+ * ${license.mailto}
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "fggl/gui/fonts.hpp"
+
+namespace fggl::gui {
+
+	FontFace::FontFace(FT_Face face) : m_face(face) {
+	}
+
+	FontFace::~FontFace() {
+		FT_Done_Face(m_face);
+	}
+
+	FontLibrary::FontLibrary() {
+		FT_Init_FreeType(&m_context);
+	}
+
+	FontLibrary::~FontLibrary() {
+		// free all fonts
+		for (auto& face : m_cache ){
+			face.second = nullptr;
+		}
+
+		// shut the library down
+		FT_Done_FreeType(m_context);
+	}
+
+	GlyphMetrics &FontFace::populateMetrics(char letter) {
+		if (FT_Load_Char(m_face, letter, FT_LOAD_RENDER) ) {
+			// something bad happened
+			return m_metrics['?'];
+		}
+
+		GlyphMetrics metrics {
+			{ m_face->glyph->bitmap.width, m_face->glyph->bitmap.rows},
+			{ m_face->glyph->bitmap_left, m_face->glyph->bitmap_top },
+			m_face->glyph->advance.x
+		};
+
+		// return the new metrics data
+		auto it = m_metrics.emplace(letter, metrics);
+		return it.first->second;
+	}
+} // namespace fggl::fonts
\ No newline at end of file
diff --git a/fggl/gui/widgets.cpp b/fggl/gui/widgets.cpp
index c10d07d75d51b9c8ae25fe02bdaece0b4d547908..77d5c459dff780042d9494d7089e0d9c54b1b3ab 100644
--- a/fggl/gui/widgets.cpp
+++ b/fggl/gui/widgets.cpp
@@ -15,6 +15,8 @@ namespace fggl::gui {
 		gfx::Path2D path{ topLeft() };
 		draw_button(path, topLeft(), size(), m_hover, m_active);
 		paint.fill(path);
+
+		paint.text(m_value, topLeft());
 	}
 
 	void Button::activate() {
@@ -37,4 +39,12 @@ namespace fggl::gui {
 	void Button::addCallback(Callback cb) {
 		m_callbacks.push_back( cb );
 	}
+
+	void Button::label(const std::string &value) {
+		m_value = value;
+	}
+
+	std::string Button::label() const {
+		return m_value;
+	}
 } // namespace fggl::gui
\ No newline at end of file
diff --git a/fggl/math/triangulation.cpp b/fggl/math/triangulation.cpp
index 88d3d19c0c8d373dbc76a19fd8565d25003b0eb1..d235b8ef7130b0e9fd9ebe5530235a459d25fdd9 100644
--- a/fggl/math/triangulation.cpp
+++ b/fggl/math/triangulation.cpp
@@ -19,6 +19,7 @@
  */
 
 #include "fggl/math/triangulation.hpp"
+#include <iostream>
 
 namespace fggl::math {
 
diff --git a/fggl/scenes/menu.cpp b/fggl/scenes/menu.cpp
index 26b800aff6309723e12afa993ce86223f7cd945a..c1629e2932e0f0fdf84c867aa228aa53830c23fa 100644
--- a/fggl/scenes/menu.cpp
+++ b/fggl/scenes/menu.cpp
@@ -82,6 +82,8 @@ namespace fggl::scenes {
 
 		// build the button
 		auto btn = std::make_unique<gui::Button>(pos, btnSize);
+		btn->label(name);
+
 		btn->addCallback(cb);
 		m_canvas.add(std::move(btn));
     }
diff --git a/include/fggl/gfx/ogl4/canvas.hpp b/include/fggl/gfx/ogl4/canvas.hpp
index 6908867173e49ef6f1f36029309b880781b1d98d..57cf6a5e805f92c243073fa1e1da6ea59da625b4 100644
--- a/include/fggl/gfx/ogl4/canvas.hpp
+++ b/include/fggl/gfx/ogl4/canvas.hpp
@@ -8,6 +8,8 @@
 #include "fggl/gfx/paint.hpp"
 #include "fggl/gfx/ogl/types.hpp"
 
+#include "fggl/gui/fonts.hpp"
+
 namespace fggl::gfx::ogl4 {
 
 	class CanvasRenderer {
@@ -19,6 +21,10 @@ namespace fggl::gfx::ogl4 {
 			ogl::VertexArray m_vao;
 			ogl::ArrayBuffer m_vertexList;
 			ogl::ElementBuffer m_indexList;
+			gui::FontLibrary m_fonts;
+
+			void renderShapes(const Paint &paint, GLuint shader);
+			void renderText(const Paint&, GLuint shader);
 	};
 
 } // namespace fggl::gfx::ogl4
diff --git a/include/fggl/gfx/paint.hpp b/include/fggl/gfx/paint.hpp
index 01a63cf2daa3adea35189cab7b169e465863f57e..5afd9ec02970532333adb42bf6e77c4f1d43fac9 100644
--- a/include/fggl/gfx/paint.hpp
+++ b/include/fggl/gfx/paint.hpp
@@ -89,6 +89,10 @@ namespace fggl::gfx {
 				return m_cmds;
 			}
 
+			const std::vector<TextCmd>& textCmds() const {
+				return m_text;
+			}
+
 		private:
 			std::vector<PaintCmd> m_cmds;
 			std::vector<TextCmd> m_text;
diff --git a/include/fggl/gui/fonts.hpp b/include/fggl/gui/fonts.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..735e07d68e0e3b43ba5d6abdd3208055a89a9474
--- /dev/null
+++ b/include/fggl/gui/fonts.hpp
@@ -0,0 +1,106 @@
+/*
+ * ${license.title}
+ * Copyright (C) 2022 ${license.owner}
+ * ${license.mailto}
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+//
+// Created by webpigeon on 21/04/22.
+//
+
+#ifndef FGGL_GUI_FONTS_HPP
+#define FGGL_GUI_FONTS_HPP
+
+#include <memory>
+#include <map>
+#include <string>
+
+#include "fggl/math/types.hpp"
+#include "fggl/data/storage.hpp"
+#include "fggl/util/service.h"
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+
+namespace fggl::gui {
+
+	struct GlyphMetrics {
+		math::vec2 size;
+		math::vec2 bearing;
+		long advance;
+	};
+
+	class FontFace {
+		public:
+			FontFace(FT_Face face);
+			~FontFace();
+
+			inline GlyphMetrics& metrics(char letter){
+				auto itr = m_metrics.find(letter);
+				if ( itr == m_metrics.end() ) {
+					return populateMetrics(letter);
+				}
+				return itr->second;
+			}
+
+			math::vec2 stringSize(const std::string& text);
+			void texture(char letter, int& width, int& height, unsigned char* buff);
+
+		private:
+			FT_Face m_face;
+			std::map<char, GlyphMetrics> m_metrics;
+
+			GlyphMetrics& populateMetrics(char letter);
+	};
+
+	class FontLibrary {
+		public:
+			FontLibrary();
+			~FontLibrary();
+
+			inline std::shared_ptr<FontFace> getFont(const std::string& name) {
+				auto fontItr = m_cache.find(name);
+				if ( fontItr != m_cache.end() ) {
+					return fontItr->second;
+				}
+
+				// need to load the font...
+				auto storage = util::ServiceLocator::instance().get<data::Storage>();
+				auto path = storage->resolvePath(data::StorageType::Data, name);
+
+				FT_Face face;
+				if ( FT_New_Face(m_context, path.c_str(), 0, &face) ) {
+					return nullptr;
+				}
+				FT_Set_Pixel_Sizes(face, 0, 48);
+
+				// create the new font object
+				auto ptr = std::make_shared<FontFace>(face);
+				m_cache[name] = ptr;
+				return ptr;
+			}
+
+		private:
+			FT_Library m_context;
+			std::map<const std::string, std::shared_ptr<FontFace>> m_cache;
+	};
+
+
+
+} // nmespace fggl::gui
+
+#endif //FGGL_GUI_FONTS_HPP
diff --git a/include/fggl/gui/widgets.hpp b/include/fggl/gui/widgets.hpp
index 27a620723d7753ad6f78e6acb288eb193e668987..8f7ccaa59d314341315666be3044f3858f3e5b9f 100644
--- a/include/fggl/gui/widgets.hpp
+++ b/include/fggl/gui/widgets.hpp
@@ -19,9 +19,14 @@ namespace fggl::gui {
 			void onEnter() override;
 			void onExit() override;
 
-			void addCallback(Callback cb);
+			void label(const std::string& value);
+
+			[[nodiscard]]
+			std::string label() const;
+
+			void addCallback(Callback callback);
 		private:
-			const std::string m_value;
+			std::string m_value;
 			std::vector<Callback> m_callbacks;
 			bool m_hover;
 			bool m_active;
diff --git a/include/fggl/scenes/menu.hpp b/include/fggl/scenes/menu.hpp
index 8bb8c2be3a4d7c36e2cfa10ef26f368f8df4b202..b7ce39d7bea349ad187af294cc6378eab9b7959f 100644
--- a/include/fggl/scenes/menu.hpp
+++ b/include/fggl/scenes/menu.hpp
@@ -16,7 +16,7 @@ namespace fggl::scenes {
 
 	class BasicMenu : public AppState {
 		public:
-			BasicMenu(App &owner);
+			explicit BasicMenu(App &owner);
 
 			void update() override;
 			void render(gfx::Paint &paint) override;