diff --git a/skia/viewer/build/premake5.lua b/skia/viewer/build/premake5.lua index acc68858..e564e088 100644 --- a/skia/viewer/build/premake5.lua +++ b/skia/viewer/build/premake5.lua @@ -19,17 +19,24 @@ includedirs {"../include", "../../../include", "../../renderer/include", "../../ "../../dependencies/skia", "../../dependencies/skia/include/core", "../../dependencies/skia/include/effects", "../../dependencies/skia/include/gpu", "../../dependencies/skia/include/config", "../../dependencies/imgui", "../../dependencies", - "../../dependencies/gl3w/build/include"} + "../../dependencies/gl3w/build/include", + "/Users/mike/other/freetype/include" +} -links {"Cocoa.framework", "IOKit.framework", "CoreVideo.framework", "rive", "skia", "rive_skia_renderer", "glfw3"} +links { + "Cocoa.framework", "IOKit.framework", "CoreVideo.framework", + "rive", "skia", "rive_skia_renderer", "glfw3", + "freetype", +} libdirs {"../../../build/%{cfg.system}/bin/%{cfg.buildcfg}", "../../dependencies/glfw_build/src", - "../../dependencies/skia/out/static", "../../renderer/build/%{cfg.system}/bin/%{cfg.buildcfg}"} + "../../dependencies/skia/out/static", "../../renderer/build/%{cfg.system}/bin/%{cfg.buildcfg}",} files {"../src/**.cpp", "../../dependencies/gl3w/build/src/gl3w.c", "../../dependencies/imgui/backends/imgui_impl_glfw.cpp", "../../dependencies/imgui/backends/imgui_impl_opengl3.cpp", "../../dependencies/imgui/imgui_widgets.cpp", "../../dependencies/imgui/imgui.cpp", "../../dependencies/imgui/imgui_tables.cpp", - "../../dependencies/imgui/imgui_draw.cpp"} + "../../dependencies/imgui/imgui_draw.cpp", + } buildoptions {"-Wall", "-fno-exceptions", "-fno-rtti", "-flto=full"} filter "configurations:debug" @@ -40,7 +47,7 @@ filter "configurations:release" defines {"RELEASE"} defines {"NDEBUG"} -optimize "On" +symbols "On" -- Clean Function -- newaction { diff --git a/skia/viewer/src/fonts.cpp b/skia/viewer/src/fonts.cpp new file mode 100644 index 00000000..d7be13a2 --- /dev/null +++ b/skia/viewer/src/fonts.cpp @@ -0,0 +1,200 @@ +#include "fonts.hpp" + +#include +#include + +template T pin(T v, T mn, T mx) { + return std::min(std::max(v, mn), mx); +} + +struct FTPathSinkCaller { +private: + PathSink* m_Sink; + // we have to manually close contours + bool m_ReadyToClose = false; + + static inline float dot6tofloat(int32_t x) { + return x * (1.0f/64); + } + + static int Move(const FT_Vector* pt, void* ctx) { + auto caller = (FTPathSinkCaller*)ctx; + if (caller->m_ReadyToClose) { + caller->m_Sink->close(); + caller->m_ReadyToClose = false; + } + caller->m_Sink->move(dot6tofloat(pt->x), -dot6tofloat(pt->y)); + return 0; + } + + static int Line(const FT_Vector* pt, void* ctx) { + auto caller = (FTPathSinkCaller*)ctx; + caller->m_Sink->line(dot6tofloat(pt->x), -dot6tofloat(pt->y)); + caller->m_ReadyToClose = true; + return 0; + } + + static int Quad(const FT_Vector* pt0, const FT_Vector* pt1, void* ctx) { + auto caller = (FTPathSinkCaller*)ctx; + caller->m_Sink->quad(dot6tofloat(pt0->x), -dot6tofloat(pt0->y), + dot6tofloat(pt1->x), -dot6tofloat(pt1->y)); + caller->m_ReadyToClose = true; + return 0; + } + + static int Cubic(const FT_Vector* pt0, const FT_Vector* pt1, const FT_Vector* pt2, void* ctx) { + auto caller = (FTPathSinkCaller*)ctx; + caller->m_Sink->cubic(dot6tofloat(pt0->x), -dot6tofloat(pt0->y), + dot6tofloat(pt1->x), -dot6tofloat(pt1->y), + dot6tofloat(pt2->x), -dot6tofloat(pt2->y)); + caller->m_ReadyToClose = true; + return 0; + } + +public: + FTPathSinkCaller(PathSink* sink) : m_Sink(sink) {} + + inline static constexpr const FT_Outline_Funcs Funcs{ + FTPathSinkCaller::Move, + FTPathSinkCaller::Line, + FTPathSinkCaller::Quad, + FTPathSinkCaller::Cubic, + 0, // shift + 0, // delta + }; +}; + +FTLib::FTLib() : m_Lib(nullptr) { + int err = FT_Init_FreeType(&m_Lib); + if (err) { + printf("FT_Init_FreeType returned %d\n", err); + return; + } + + FT_Add_Default_Modules(m_Lib); + FT_Set_Default_Properties(m_Lib); +} + +FTLib::~FTLib() { + if (m_Lib) { + FT_Done_Library(m_Lib); + } +} + +FTFace::~FTFace() { + if (m_Face) { + FT_Done_Face(m_Face); + } +} + +static std::string tagname(uint32_t x) { + std::string str("1234"); + str[0] = (x >> 24) & 0xFF; + str[1] = (x >> 16) & 0xFF; + str[2] = (x >> 8) & 0xFF; + str[3] = (x >> 0) & 0xFF; + return str; +} + +static float fixed2float(int32_t x) { + return x * (1.0f/65536); +} + +static int32_t float2fixed(float x) { + return (int)std::floor(x * 65536 + 0.5f); +} + +void FTFace::parseAxes(FT_Library lib) { + m_Axes.clear(); + + FT_MM_Var* var; + int err = FT_Get_MM_Var(m_Face, &var); + if (err) { + printf("failed getting variations %d\n", err); + return; + } + + assert(m_Face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS); + + printf("%d axes\n", var->num_axis); + for (unsigned i = 0; i < var->num_axis; ++i) { + m_Axes.push_back({ + (uint32_t)var->axis[i].tag, + fixed2float(var->axis[i].minimum), + fixed2float(var->axis[i].def), + fixed2float(var->axis[i].maximum), + }); + printf(" %s %g %g %g\n", + tagname(var->axis[i].tag).c_str(), + m_Axes[i].m_Min, m_Axes[i].m_Def, m_Axes[i].m_Max); + } + FT_Done_MM_Var(lib, var); +} + +bool FTFace::load(FT_Library lib, sk_sp data) { + int face_index = 0; // todo + int err = FT_New_Memory_Face(lib, (const FT_Byte*)data->data(), + data->size(), face_index, &m_Face); + if (err) { + printf("FT_New_Memory_Face returned %d\n", err); + return false; + } + m_Data = std::move(data); + + this->parseAxes(lib); + + return true; +} + +void FTFace::setCoords(const FTCoord user[], int count) { + std::vector ftc(m_Axes.size()); + for (size_t i = 0; i < m_Axes.size(); ++i) { + const auto& a = m_Axes[i]; + float value = a.m_Def; + for (int j = 0; j < count; ++j) { + if (a.m_Tag == user[j].m_Tag) { + value = user[j].m_Value; + } + } + ftc[i] = float2fixed(pin(value, a.m_Min, a.m_Max)); + } + + int err = FT_Set_Var_Design_Coordinates(m_Face, ftc.size(), ftc.data()); + if (err) { + printf("set design coords failed %d\n", err); + } +} + +int FTFace::upem() const { + assert(m_Face); + return m_Face->units_per_EM; +} + +bool FTFace::setSize(int size) { + assert(m_Face); + int err = FT_Set_Char_Size(m_Face, size * 64, 0, 72, 72); + if (err) { + printf("failed to set size %d\n", size); + return false; + } + return true; +} + +bool FTFace::getPath(uint16_t glyph, PathSink* sink) { + unsigned flags = FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP; + flags &= ~FT_LOAD_RENDER; + + int err = FT_Load_Glyph(m_Face, glyph, flags); + assert(err == 0); + + FTPathSinkCaller caller(sink); + err = FT_Outline_Decompose(&m_Face->glyph->outline, + &FTPathSinkCaller::Funcs, + &caller); + if (err) { + printf("failed calling decompose %d\n", err); + return false; + } + sink->close(); + return true; +} diff --git a/skia/viewer/src/fonts.hpp b/skia/viewer/src/fonts.hpp new file mode 100644 index 00000000..19253111 --- /dev/null +++ b/skia/viewer/src/fonts.hpp @@ -0,0 +1,110 @@ +#ifndef _RIVE_FONT_HPP_ +#define _RIVE_FONT_HPP_ + +#include "rive/rive_types.hpp" +#include "SkData.h" + +#include + +#include +#include FT_FREETYPE_H + +#include +#include +#include +#ifdef FT_COLOR_H // 2.10.0 +# include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct FT_LibraryRec_* FT_Library; +typedef struct FT_FaceRec_* FT_Face; +typedef struct FT_StreamRec_* FT_Stream; +typedef signed long FT_Pos; +typedef struct FT_BBox_ FT_BBox; + +struct PathSink { + virtual void move(float x, float y) = 0; + virtual void line(float x, float y) = 0; + virtual void quad(float x0, float y0, float x1, float y1) = 0; + virtual void cubic(float x0, float y0, float x1, float y1, float x2, float y2) = 0; + virtual void close() = 0; +}; + +struct FTLib { + FT_Library m_Lib; + + FTLib(); + ~FTLib(); + + operator bool() const { return m_Lib != nullptr; } +}; + +constexpr uint32_t tag(unsigned a, unsigned b, unsigned c, unsigned d) { + assert(a <= 255 && b <= 255 && c <= 255 && d <= 255); + return (a << 24) | (b << 16) | (c << 8) | d; +} + +struct FTAxis { + uint32_t m_Tag; + float m_Min, m_Def, m_Max; +}; + +struct FTCoord { + uint32_t m_Tag; + float m_Value; +}; + +struct FTFace { + sk_sp m_Data; + FT_Face m_Face; + std::vector m_Axes; + + FTFace() : m_Face(nullptr) {} + ~FTFace(); + + bool load(FT_Library lib, sk_sp data); + + int axisCount() const { return (int)m_Axes.size(); } + const FTAxis* axes() const { return m_Axes.data(); } + + operator bool() const { return m_Face != nullptr; } + + int upem() const; + bool setSize(int size); + void setCoords(const FTCoord[], int count); + + bool getPath(uint16_t glyph, PathSink* sink); + +private: + void parseAxes(FT_Library); +}; + +struct TestSink : public PathSink { + void move(float x, float y) override { + printf("Move %g %g\n", x, y); + } + void line(float x, float y) override { + printf("line %g %g\n", x, y); + } + void quad(float x0, float y0, float x1, float y1) override { + printf("quad %g %g %g %g\n", x0, y0, x1, y1); + } + void cubic(float x0, float y0, float x1, float y1, float x2, float y2) override { + printf("cube %g %g %g %g %g %g\n", x0, y0, x1, y1, x2, y2); + } + void close() override { + printf("close\n"); + } +}; + +#endif diff --git a/skia/viewer/src/main.cpp b/skia/viewer/src/main.cpp index a64d5697..ca7c8e5d 100644 --- a/skia/viewer/src/main.cpp +++ b/skia/viewer/src/main.cpp @@ -28,6 +28,8 @@ #include "imgui/backends/imgui_impl_glfw.h" #include "imgui/backends/imgui_impl_opengl3.h" +#include "fonts.hpp" + #include #include @@ -132,7 +134,69 @@ void glfwDropCallback(GLFWwindow* window, int count, const char** paths) { initAnimation(0); } +struct SkPathSink : public PathSink { + SkPath* m_Path; + SkPathSink(SkPath* path) : m_Path(path) {} + + void move(float x, float y) override { m_Path->moveTo(x, y); } + void line(float x, float y) override { m_Path->lineTo(x, y); } + void quad(float x0, float y0, float x1, float y1) override { + m_Path->quadTo(x0, y0, x1, y1); + } + void cubic(float x0, float y0, float x1, float y1, float x2, float y2) override { + m_Path->cubicTo(x0, y0, x1, y1, x2, y2); + } + void close() override { m_Path->close(); } +}; + +struct Bouncer { + float value; + float min, max; + float delta; + + Bouncer(float v, float mn, float mx, float durationInSeconds) + : value(v) + , min(mn) + , max(mx) + { + assert(mn < mx); + assert(mn <= value && value <= mx); + delta = (mx - mn) / durationInSeconds; + } + + float curr() const { return value; } + float advance(float secs) { + float nv = value + secs * delta; + if (nv > max) { + nv = max; + assert(delta > 0); + delta = -delta; + } else if (nv < min) { + nv = min; + assert(delta < 0); + delta = -delta; + } + value = nv; + return value; + } +}; + int main() { + FTLib lib; + + auto data = SkData::MakeFromFileName("/Users/mike/fonts/Skia.ttf"); + assert(data); + + FTFace face; + face.load(lib.m_Lib, data); + int glyph = FT_Get_Char_Index(face.m_Face, 'Q'); + face.setSize(600); + const FTAxis* ax = face.axes(); + Bouncer bouncers[2] = { + Bouncer(ax[0].m_Def, ax[0].m_Min, ax[0].m_Max, 4), + Bouncer(ax[1].m_Def, ax[1].m_Min, ax[1].m_Max, 3), + }; + if (!glfwInit()) { fprintf(stderr, "Failed to initialize glfw.\n"); return 1; @@ -235,6 +299,27 @@ int main() { paint.setColor(SK_ColorDKGRAY); canvas->drawPaint(paint); + { + SkPaint paint; + paint.setColor(0xFFFFFFFF); + SkAutoCanvasRestore acr(canvas, true); + canvas->translate(300, 700); + + FTCoord c[2]; + for (int i = 0; i < 2; ++i) { + c[i] = { + ax[i].m_Tag, + bouncers[i].advance(elapsed), + }; + } + face.setCoords(c, 2); + + SkPath path; + SkPathSink sink(&path); + face.getPath(glyph, &sink); + canvas->drawPath(path, paint); + } + if (artboard != nullptr) { if (animationInstance != nullptr) { animationInstance->advance(elapsed); @@ -373,4 +458,4 @@ int main() { glfwTerminate(); return 0; -} \ No newline at end of file +}