├── .gitignore ├── README.md ├── build.zig ├── dependencies ├── gl │ ├── KHR │ │ └── khrplatform.h │ ├── glad.c │ └── glad │ │ └── glad.h └── stb_truetype-1.24 │ ├── stb_truetype.h │ └── stb_truetype_impl.c ├── run.bat ├── run.sh ├── src ├── app.zig ├── c.zig ├── constants.zig ├── embeds │ ├── fonts │ │ ├── Goudy │ │ │ ├── Barry Schwartz License.txt │ │ │ └── goudy_bookletter_1911.otf │ │ ├── JetBrainsMono │ │ │ ├── LICENSE │ │ │ └── ttf │ │ │ │ ├── JetBrainsMono-Bold-Italic.ttf │ │ │ │ ├── JetBrainsMono-Bold.ttf │ │ │ │ ├── JetBrainsMono-ExtraBold-Italic.ttf │ │ │ │ ├── JetBrainsMono-ExtraBold.ttf │ │ │ │ ├── JetBrainsMono-ExtraLight-Italic.ttf │ │ │ │ ├── JetBrainsMono-ExtraLight.ttf │ │ │ │ ├── JetBrainsMono-Italic.ttf │ │ │ │ ├── JetBrainsMono-Light-Italic.ttf │ │ │ │ ├── JetBrainsMono-Light.ttf │ │ │ │ ├── JetBrainsMono-Medium-Italic.ttf │ │ │ │ ├── JetBrainsMono-Medium.ttf │ │ │ │ ├── JetBrainsMono-Regular.ttf │ │ │ │ ├── JetBrainsMono-SemiLight-Italic.ttf │ │ │ │ ├── JetBrainsMono-SemiLight.ttf │ │ │ │ ├── No ligatures │ │ │ │ ├── JetBrainsMonoNL-Bold-Italic.ttf │ │ │ │ ├── JetBrainsMonoNL-Bold.ttf │ │ │ │ ├── JetBrainsMonoNL-ExtraBold-Italic.ttf │ │ │ │ ├── JetBrainsMonoNL-ExtraBold.ttf │ │ │ │ ├── JetBrainsMonoNL-ExtraLight-Italic.ttf │ │ │ │ ├── JetBrainsMonoNL-ExtraLight.ttf │ │ │ │ ├── JetBrainsMonoNL-Italic.ttf │ │ │ │ ├── JetBrainsMonoNL-Light-Italic.ttf │ │ │ │ ├── JetBrainsMonoNL-Light.ttf │ │ │ │ ├── JetBrainsMonoNL-Medium-Italic.ttf │ │ │ │ ├── JetBrainsMonoNL-Medium.ttf │ │ │ │ ├── JetBrainsMonoNL-Regular.ttf │ │ │ │ ├── JetBrainsMonoNL-SemiLight-Italic.ttf │ │ │ │ └── JetBrainsMonoNL-SemiLight.ttf │ │ │ │ └── Variable │ │ │ │ ├── JetBrainsMono-Variable-Italic.ttf │ │ │ │ └── JetBrainsMono-Variable.ttf │ │ ├── Leander │ │ │ ├── Leander.ttf │ │ │ └── Tension Type Font License.txt │ │ ├── text_atlas.png │ │ └── text_info.json │ └── shaders │ │ ├── fragment_texalpha.glsl │ │ ├── vertex.glsl │ │ ├── web_fragment_texalpha.glsl │ │ └── web_vertex.glsl ├── glyphee.zig ├── helpers.zig ├── main.zig ├── platform.zig ├── renderer.zig └── web.zig ├── web ├── data │ └── test.txt ├── index.html └── interface.js └── web_build.bat /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *~ 3 | *.*~ 4 | SDL2.dll 5 | zig-cache/ 6 | zig-out/ 7 | *.wasm 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sdl_base 2 | 3 | this is a base starter for any sdl based project. 4 | 5 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(b: *std.build.Builder) void { 4 | // Standard target options allows the person running `zig build` to choose 5 | // what target to build for. Here we do not override the defaults, which 6 | // means any target is allowed, and the default is native. Other options 7 | // for restricting supported target set are available. 8 | 9 | // Standard release options allow the person running `zig build` to select 10 | // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. 11 | const mode = b.standardReleaseOptions(); 12 | const windows_build = if (b.option(bool, "windows", "Build for windows")) |w| w else true; 13 | 14 | const web_build = false; 15 | var options = b.addOptions(); 16 | options.addOption(bool, "web_build", web_build); 17 | 18 | if (!web_build) { 19 | const target = b.standardTargetOptions(.{}); 20 | const exe = b.addExecutable("typeroo", "src/main.zig"); 21 | exe.setTarget(target); 22 | exe.addOptions("build_options", options); 23 | exe.setBuildMode(mode); 24 | exe.addSystemIncludePath("src"); 25 | if (!web_build) { 26 | exe.addSystemIncludePath("dependencies/gl"); 27 | exe.addSystemIncludePath("dependencies/stb_truetype-1.24"); 28 | exe.addCSourceFile("dependencies/gl/glad.c", &[_][]const u8{"-std=c99"}); 29 | exe.addCSourceFile("dependencies/stb_truetype-1.24/stb_truetype_impl.c", &[_][]const u8{"-std=c99"}); 30 | } 31 | if (windows_build) { 32 | exe.addSystemIncludePath("C:/SDL2/include"); 33 | exe.addLibraryPath("C:/SDL2/lib/x64"); 34 | exe.addLibraryPath("C:/Program Files (x86)/Windows Kits/10/Lib/10.0.18362.0/um/x64"); 35 | b.installBinFile("C:/SDL2/lib/x64/SDL2.dll", "SDL2.dll"); 36 | } 37 | exe.linkSystemLibrary("sdl2"); 38 | if (windows_build) { 39 | exe.linkSystemLibrary("OpenGL32"); 40 | } else { 41 | exe.linkSystemLibrary("OpenGL"); 42 | } 43 | exe.linkLibC(); 44 | exe.install(); 45 | const run_cmd = exe.run(); 46 | run_cmd.step.dependOn(b.getInstallStep()); 47 | if (b.args) |args| { 48 | run_cmd.addArgs(args); 49 | } 50 | const run_step = b.step("run", "Run the app"); 51 | run_step.dependOn(&run_cmd.step); 52 | } else { 53 | const target = std.zig.CrossTarget.parse(.{ .arch_os_abi = "wasm32-freestanding" }) catch unreachable; 54 | const exe = b.addSharedLibrary("typeroo", "src/main.zig", .unversioned); 55 | exe.setTarget(target); 56 | exe.addOptions("build_options", options); 57 | exe.setBuildMode(mode); 58 | exe.addSystemIncludePath("src"); 59 | exe.install(); 60 | // TODO (12 May 2022 sam): This runs before the build. Figure it out. 61 | // b.updateFile("zig-out/lib/typeroo.wasm", "web/typeroo.wasm") catch unreachable; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /dependencies/gl/KHR/khrplatform.h: -------------------------------------------------------------------------------- 1 | #ifndef __khrplatform_h_ 2 | #define __khrplatform_h_ 3 | 4 | /* 5 | ** Copyright (c) 2008-2018 The Khronos Group Inc. 6 | ** 7 | ** Permission is hereby granted, free of charge, to any person obtaining a 8 | ** copy of this software and/or associated documentation files (the 9 | ** "Materials"), to deal in the Materials without restriction, including 10 | ** without limitation the rights to use, copy, modify, merge, publish, 11 | ** distribute, sublicense, and/or sell copies of the Materials, and to 12 | ** permit persons to whom the Materials are furnished to do so, subject to 13 | ** the following conditions: 14 | ** 15 | ** The above copyright notice and this permission notice shall be included 16 | ** in all copies or substantial portions of the Materials. 17 | ** 18 | ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. 25 | */ 26 | 27 | /* Khronos platform-specific types and definitions. 28 | * 29 | * The master copy of khrplatform.h is maintained in the Khronos EGL 30 | * Registry repository at https://github.com/KhronosGroup/EGL-Registry 31 | * The last semantic modification to khrplatform.h was at commit ID: 32 | * 67a3e0864c2d75ea5287b9f3d2eb74a745936692 33 | * 34 | * Adopters may modify this file to suit their platform. Adopters are 35 | * encouraged to submit platform specific modifications to the Khronos 36 | * group so that they can be included in future versions of this file. 37 | * Please submit changes by filing pull requests or issues on 38 | * the EGL Registry repository linked above. 39 | * 40 | * 41 | * See the Implementer's Guidelines for information about where this file 42 | * should be located on your system and for more details of its use: 43 | * http://www.khronos.org/registry/implementers_guide.pdf 44 | * 45 | * This file should be included as 46 | * #include 47 | * by Khronos client API header files that use its types and defines. 48 | * 49 | * The types in khrplatform.h should only be used to define API-specific types. 50 | * 51 | * Types defined in khrplatform.h: 52 | * khronos_int8_t signed 8 bit 53 | * khronos_uint8_t unsigned 8 bit 54 | * khronos_int16_t signed 16 bit 55 | * khronos_uint16_t unsigned 16 bit 56 | * khronos_int32_t signed 32 bit 57 | * khronos_uint32_t unsigned 32 bit 58 | * khronos_int64_t signed 64 bit 59 | * khronos_uint64_t unsigned 64 bit 60 | * khronos_intptr_t signed same number of bits as a pointer 61 | * khronos_uintptr_t unsigned same number of bits as a pointer 62 | * khronos_ssize_t signed size 63 | * khronos_usize_t unsigned size 64 | * khronos_float_t signed 32 bit floating point 65 | * khronos_time_ns_t unsigned 64 bit time in nanoseconds 66 | * khronos_utime_nanoseconds_t unsigned time interval or absolute time in 67 | * nanoseconds 68 | * khronos_stime_nanoseconds_t signed time interval in nanoseconds 69 | * khronos_boolean_enum_t enumerated boolean type. This should 70 | * only be used as a base type when a client API's boolean type is 71 | * an enum. Client APIs which use an integer or other type for 72 | * booleans cannot use this as the base type for their boolean. 73 | * 74 | * Tokens defined in khrplatform.h: 75 | * 76 | * KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values. 77 | * 78 | * KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0. 79 | * KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0. 80 | * 81 | * Calling convention macros defined in this file: 82 | * KHRONOS_APICALL 83 | * KHRONOS_APIENTRY 84 | * KHRONOS_APIATTRIBUTES 85 | * 86 | * These may be used in function prototypes as: 87 | * 88 | * KHRONOS_APICALL void KHRONOS_APIENTRY funcname( 89 | * int arg1, 90 | * int arg2) KHRONOS_APIATTRIBUTES; 91 | */ 92 | 93 | #if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC) 94 | # define KHRONOS_STATIC 1 95 | #endif 96 | 97 | /*------------------------------------------------------------------------- 98 | * Definition of KHRONOS_APICALL 99 | *------------------------------------------------------------------------- 100 | * This precedes the return type of the function in the function prototype. 101 | */ 102 | #if defined(KHRONOS_STATIC) 103 | /* If the preprocessor constant KHRONOS_STATIC is defined, make the 104 | * header compatible with static linking. */ 105 | # define KHRONOS_APICALL 106 | #elif defined(_WIN32) 107 | # define KHRONOS_APICALL __declspec(dllimport) 108 | #elif defined (__SYMBIAN32__) 109 | # define KHRONOS_APICALL IMPORT_C 110 | #elif defined(__ANDROID__) 111 | # define KHRONOS_APICALL __attribute__((visibility("default"))) 112 | #else 113 | # define KHRONOS_APICALL 114 | #endif 115 | 116 | /*------------------------------------------------------------------------- 117 | * Definition of KHRONOS_APIENTRY 118 | *------------------------------------------------------------------------- 119 | * This follows the return type of the function and precedes the function 120 | * name in the function prototype. 121 | */ 122 | #if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__) 123 | /* Win32 but not WinCE */ 124 | # define KHRONOS_APIENTRY __stdcall 125 | #else 126 | # define KHRONOS_APIENTRY 127 | #endif 128 | 129 | /*------------------------------------------------------------------------- 130 | * Definition of KHRONOS_APIATTRIBUTES 131 | *------------------------------------------------------------------------- 132 | * This follows the closing parenthesis of the function prototype arguments. 133 | */ 134 | #if defined (__ARMCC_2__) 135 | #define KHRONOS_APIATTRIBUTES __softfp 136 | #else 137 | #define KHRONOS_APIATTRIBUTES 138 | #endif 139 | 140 | /*------------------------------------------------------------------------- 141 | * basic type definitions 142 | *-----------------------------------------------------------------------*/ 143 | #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__) 144 | 145 | 146 | /* 147 | * Using 148 | */ 149 | #include 150 | typedef int32_t khronos_int32_t; 151 | typedef uint32_t khronos_uint32_t; 152 | typedef int64_t khronos_int64_t; 153 | typedef uint64_t khronos_uint64_t; 154 | #define KHRONOS_SUPPORT_INT64 1 155 | #define KHRONOS_SUPPORT_FLOAT 1 156 | 157 | #elif defined(__VMS ) || defined(__sgi) 158 | 159 | /* 160 | * Using 161 | */ 162 | #include 163 | typedef int32_t khronos_int32_t; 164 | typedef uint32_t khronos_uint32_t; 165 | typedef int64_t khronos_int64_t; 166 | typedef uint64_t khronos_uint64_t; 167 | #define KHRONOS_SUPPORT_INT64 1 168 | #define KHRONOS_SUPPORT_FLOAT 1 169 | 170 | #elif defined(_WIN32) && !defined(__SCITECH_SNAP__) 171 | 172 | /* 173 | * Win32 174 | */ 175 | typedef __int32 khronos_int32_t; 176 | typedef unsigned __int32 khronos_uint32_t; 177 | typedef __int64 khronos_int64_t; 178 | typedef unsigned __int64 khronos_uint64_t; 179 | #define KHRONOS_SUPPORT_INT64 1 180 | #define KHRONOS_SUPPORT_FLOAT 1 181 | 182 | #elif defined(__sun__) || defined(__digital__) 183 | 184 | /* 185 | * Sun or Digital 186 | */ 187 | typedef int khronos_int32_t; 188 | typedef unsigned int khronos_uint32_t; 189 | #if defined(__arch64__) || defined(_LP64) 190 | typedef long int khronos_int64_t; 191 | typedef unsigned long int khronos_uint64_t; 192 | #else 193 | typedef long long int khronos_int64_t; 194 | typedef unsigned long long int khronos_uint64_t; 195 | #endif /* __arch64__ */ 196 | #define KHRONOS_SUPPORT_INT64 1 197 | #define KHRONOS_SUPPORT_FLOAT 1 198 | 199 | #elif 0 200 | 201 | /* 202 | * Hypothetical platform with no float or int64 support 203 | */ 204 | typedef int khronos_int32_t; 205 | typedef unsigned int khronos_uint32_t; 206 | #define KHRONOS_SUPPORT_INT64 0 207 | #define KHRONOS_SUPPORT_FLOAT 0 208 | 209 | #else 210 | 211 | /* 212 | * Generic fallback 213 | */ 214 | #include 215 | typedef int32_t khronos_int32_t; 216 | typedef uint32_t khronos_uint32_t; 217 | typedef int64_t khronos_int64_t; 218 | typedef uint64_t khronos_uint64_t; 219 | #define KHRONOS_SUPPORT_INT64 1 220 | #define KHRONOS_SUPPORT_FLOAT 1 221 | 222 | #endif 223 | 224 | 225 | /* 226 | * Types that are (so far) the same on all platforms 227 | */ 228 | typedef signed char khronos_int8_t; 229 | typedef unsigned char khronos_uint8_t; 230 | typedef signed short int khronos_int16_t; 231 | typedef unsigned short int khronos_uint16_t; 232 | 233 | /* 234 | * Types that differ between LLP64 and LP64 architectures - in LLP64, 235 | * pointers are 64 bits, but 'long' is still 32 bits. Win64 appears 236 | * to be the only LLP64 architecture in current use. 237 | */ 238 | #ifdef _WIN64 239 | typedef signed long long int khronos_intptr_t; 240 | typedef unsigned long long int khronos_uintptr_t; 241 | typedef signed long long int khronos_ssize_t; 242 | typedef unsigned long long int khronos_usize_t; 243 | #else 244 | typedef signed long int khronos_intptr_t; 245 | typedef unsigned long int khronos_uintptr_t; 246 | typedef signed long int khronos_ssize_t; 247 | typedef unsigned long int khronos_usize_t; 248 | #endif 249 | 250 | #if KHRONOS_SUPPORT_FLOAT 251 | /* 252 | * Float type 253 | */ 254 | typedef float khronos_float_t; 255 | #endif 256 | 257 | #if KHRONOS_SUPPORT_INT64 258 | /* Time types 259 | * 260 | * These types can be used to represent a time interval in nanoseconds or 261 | * an absolute Unadjusted System Time. Unadjusted System Time is the number 262 | * of nanoseconds since some arbitrary system event (e.g. since the last 263 | * time the system booted). The Unadjusted System Time is an unsigned 264 | * 64 bit value that wraps back to 0 every 584 years. Time intervals 265 | * may be either signed or unsigned. 266 | */ 267 | typedef khronos_uint64_t khronos_utime_nanoseconds_t; 268 | typedef khronos_int64_t khronos_stime_nanoseconds_t; 269 | #endif 270 | 271 | /* 272 | * Dummy value used to pad enum types to 32 bits. 273 | */ 274 | #ifndef KHRONOS_MAX_ENUM 275 | #define KHRONOS_MAX_ENUM 0x7FFFFFFF 276 | #endif 277 | 278 | /* 279 | * Enumerated boolean type 280 | * 281 | * Values other than zero should be considered to be true. Therefore 282 | * comparisons should not be made against KHRONOS_TRUE. 283 | */ 284 | typedef enum { 285 | KHRONOS_FALSE = 0, 286 | KHRONOS_TRUE = 1, 287 | KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM 288 | } khronos_boolean_enum_t; 289 | 290 | #endif /* __khrplatform_h_ */ 291 | -------------------------------------------------------------------------------- /dependencies/stb_truetype-1.24/stb_truetype_impl.c: -------------------------------------------------------------------------------- 1 | #define STB_TRUETYPE_IMPLEMENTATION 2 | #include "stb_truetype.h" 3 | -------------------------------------------------------------------------------- /run.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set executable=zig-out\bin\typeroo.exe 3 | if exist %executable% (del %executable%) 4 | call timecmd zig build 5 | if exist %executable% (call %executable%) 6 | goto :done 7 | 8 | :done 9 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | time zig build -Dwindows=false & zig build run -Dwindows=false 2 | -------------------------------------------------------------------------------- /src/app.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const c = @import("platform.zig"); 3 | const constants = @import("constants.zig"); 4 | 5 | const glyph_lib = @import("glyphee.zig"); 6 | const TypeSetter = glyph_lib.TypeSetter; 7 | 8 | const helpers = @import("helpers.zig"); 9 | const Vector2 = helpers.Vector2; 10 | const Camera = helpers.Camera; 11 | const SingleInput = helpers.SingleInput; 12 | const MouseState = helpers.MouseState; 13 | const EditableText = helpers.EditableText; 14 | const TYPING_BUFFER_SIZE = 16; 15 | 16 | const InputKey = enum { 17 | shift, 18 | tab, 19 | enter, 20 | space, 21 | escape, 22 | ctrl, 23 | }; 24 | const INPUT_KEYS_COUNT = @typeInfo(InputKey).Enum.fields.len; 25 | const InputMap = struct { 26 | key: c.SDL_Keycode, 27 | input: InputKey, 28 | }; 29 | 30 | const INPUT_MAPPING = [_]InputMap{ 31 | .{ .key = c.SDLK_LSHIFT, .input = .shift }, 32 | .{ .key = c.SDLK_LCTRL, .input = .ctrl }, 33 | .{ .key = c.SDLK_TAB, .input = .tab }, 34 | .{ .key = c.SDLK_RETURN, .input = .enter }, 35 | .{ .key = c.SDLK_SPACE, .input = .space }, 36 | .{ .key = c.SDLK_ESCAPE, .input = .escape }, 37 | }; 38 | 39 | pub const InputState = struct { 40 | const Self = @This(); 41 | keys: [INPUT_KEYS_COUNT]SingleInput = [_]SingleInput{.{}} ** INPUT_KEYS_COUNT, 42 | mouse: MouseState = MouseState{}, 43 | typed: [TYPING_BUFFER_SIZE]u8 = [_]u8{0} ** TYPING_BUFFER_SIZE, 44 | num_typed: usize = 0, 45 | 46 | pub fn get_key(self: *Self, key: InputKey) *SingleInput { 47 | return &self.keys[@enumToInt(key)]; 48 | } 49 | 50 | pub fn type_key(self: *Self, k: u8) void { 51 | if (self.num_typed >= TYPING_BUFFER_SIZE) { 52 | helpers.debug_print("Typing buffer already filled.\n", .{}); 53 | return; 54 | } 55 | self.typed[self.num_typed] = k; 56 | self.num_typed += 1; 57 | } 58 | 59 | pub fn reset(self: *Self) void { 60 | for (self.keys) |*key| key.reset(); 61 | self.mouse.reset_mouse(); 62 | self.num_typed = 0; 63 | } 64 | }; 65 | 66 | pub const App = struct { 67 | const Self = @This(); 68 | typesetter: TypeSetter = undefined, 69 | camera: Camera = .{}, 70 | allocator: std.mem.Allocator, 71 | arena: std.mem.Allocator, 72 | ticks: u32 = 0, 73 | quit: bool = false, 74 | position: Vector2 = .{}, 75 | inputs: InputState = .{}, 76 | load_data: []u8 = undefined, 77 | 78 | pub fn new(allocator: std.mem.Allocator, arena: std.mem.Allocator) Self { 79 | return Self{ 80 | .allocator = allocator, 81 | .arena = arena, 82 | }; 83 | } 84 | 85 | pub fn init(self: *Self) !void { 86 | try self.typesetter.init(&self.camera, self.allocator); 87 | } 88 | 89 | pub fn deinit(self: *Self) void { 90 | self.typesetter.deinit(); 91 | } 92 | 93 | pub fn handle_inputs(self: *Self, event: c.SDL_Event) void { 94 | if (event.@"type" == c.SDL_KEYDOWN and event.key.keysym.sym == c.SDLK_END) 95 | self.quit = true; 96 | self.inputs.mouse.handle_input(event, self.ticks, &self.camera); 97 | if (event.@"type" == c.SDL_KEYDOWN) { 98 | for (INPUT_MAPPING) |map| { 99 | if (event.key.keysym.sym == map.key) self.inputs.get_key(map.input).set_down(self.ticks); 100 | } 101 | } else if (event.@"type" == c.SDL_KEYUP) { 102 | for (INPUT_MAPPING) |map| { 103 | if (event.key.keysym.sym == map.key) self.inputs.get_key(map.input).set_release(); 104 | } 105 | } 106 | } 107 | 108 | pub fn update(self: *Self, ticks: u32, arena: std.mem.Allocator) void { 109 | self.ticks = ticks; 110 | self.arena = arena; 111 | if (self.inputs.get_key(.space).is_down) { 112 | const xpos = (@sin(@intToFloat(f32, self.ticks) / 2000.0) * 0.5 + 0.5) * constants.DEFAULT_WINDOW_WIDTH; 113 | const ypos = (@sin(@intToFloat(f32, self.ticks) / 1145.0) * 0.5 + 0.5) * constants.DEFAULT_WINDOW_HEIGHT; 114 | self.typesetter.draw_text_world_centered_font_color(.{ .x = xpos, .y = ypos }, "SDL here hi!", .debug, .{ .x = 1, .y = 1, .z = 1, .w = 1 }); 115 | } else { 116 | const xpos = 0.5 * constants.DEFAULT_WINDOW_WIDTH; 117 | const ypos = 0.5 * constants.DEFAULT_WINDOW_HEIGHT; 118 | self.typesetter.draw_text_world_centered_font_color(.{ .x = xpos, .y = ypos }, "Press and hold space", .debug, .{ .x = 1, .y = 1, .z = 1, .w = 1 }); 119 | } 120 | if (self.inputs.mouse.l_button.is_clicked) { 121 | self.position = self.inputs.mouse.current_pos; 122 | } 123 | } 124 | 125 | pub fn end_frame(self: *Self) void { 126 | self.inputs.reset(); 127 | } 128 | }; 129 | -------------------------------------------------------------------------------- /src/c.zig: -------------------------------------------------------------------------------- 1 | pub usingnamespace @cImport({ 2 | @cInclude("glad/glad.h"); 3 | @cInclude("SDL.h"); 4 | @cInclude("stb_truetype.h"); 5 | }); 6 | 7 | pub const milliTimestamp = @import("std").time.milliTimestamp; 8 | -------------------------------------------------------------------------------- /src/constants.zig: -------------------------------------------------------------------------------- 1 | const build_options = @import("build_options"); 2 | 3 | pub const DEFAULT_WINDOW_WIDTH: f32 = 1920; 4 | pub const DEFAULT_WINDOW_HEIGHT: f32 = 1080; 5 | pub const DEFAULT_USER_WINDOW_SCALE: f32 = 1.0 / 1.2; 6 | 7 | pub const WEB_BUILD = build_options.web_build; 8 | -------------------------------------------------------------------------------- /src/embeds/fonts/Goudy/Barry Schwartz License.txt: -------------------------------------------------------------------------------- 1 | This font has been released into the public domain by its author, Barry Schwartz. This applies worldwide. 2 | 3 | In some countries this may not be legally possible; if so: 4 | 5 | Barry Schwartz grants anyone the right to use this work for any purpose, without any conditions, unless such conditions are required by law. -------------------------------------------------------------------------------- /src/embeds/fonts/Goudy/goudy_bookletter_1911.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/Goudy/goudy_bookletter_1911.otf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Bold-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Bold-Italic.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Bold.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-ExtraBold-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-ExtraBold-Italic.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-ExtraBold.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-ExtraLight-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-ExtraLight-Italic.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-ExtraLight.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Italic.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Light-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Light-Italic.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Light.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Medium-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Medium-Italic.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Medium.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Regular.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-SemiLight-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-SemiLight-Italic.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-SemiLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-SemiLight.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Bold-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Bold-Italic.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Bold.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-ExtraBold-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-ExtraBold-Italic.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-ExtraBold.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-ExtraLight-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-ExtraLight-Italic.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-ExtraLight.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Italic.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Light-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Light-Italic.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Light.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Medium-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Medium-Italic.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Medium.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Regular.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-SemiLight-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-SemiLight-Italic.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-SemiLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-SemiLight.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/Variable/JetBrainsMono-Variable-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/Variable/JetBrainsMono-Variable-Italic.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/JetBrainsMono/ttf/Variable/JetBrainsMono-Variable.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/JetBrainsMono/ttf/Variable/JetBrainsMono-Variable.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/Leander/Leander.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/Leander/Leander.ttf -------------------------------------------------------------------------------- /src/embeds/fonts/Leander/Tension Type Font License.txt: -------------------------------------------------------------------------------- 1 | By downloading and/or installing a Tension Type Free Font you agree to this licence: 2 | 3 | This Tension Type Free Font is free to use in any and all of your personal and commercial work. 4 | 5 | A donation is much appreciated, but not necessary (donations may be done through PayPal to: mtension@gmail.com). No donation is too small. 6 | 7 | You may install and use an unlimited number of copies of a Tension Type Free Font. 8 | 9 | Reproduction and Distribution. You may reproduce and distribute an unlimited number of copies of a Tension Type Free Font; provided that each copy shall be a true and complete copy, including all copyright and trademark notices (if applicable), and shall be accompanied by a copy of this text file. Copies of the Font may not be distributed for profit either on a standalone basis or included as part of your own product unless by prior permission of Tension Type. 10 | 11 | You may not rename, edit or create any derivative works from a Tension Type Free Font, other than subsetting when embedding them in documents unless you have permission from Tension Type. 12 | 13 | Embedding a Tension Type Free Font in a PDF document and web pages is allowed. 14 | 15 | Michael Tension and Tension Type are not responsible for any damage resulting from the use of this Tension Type Free Font. 16 | 17 | Any questions, or if you wish to share your designs, please contact Michael Tension: mtension@gmail.com 18 | 19 | Thanks a ton, 20 | Michael Tension -------------------------------------------------------------------------------- /src/embeds/fonts/text_atlas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samhattangady/zig_sdl_base/ac11ca394d52c4da9ff1d452705377b338b4990e/src/embeds/fonts/text_atlas.png -------------------------------------------------------------------------------- /src/embeds/fonts/text_info.json: -------------------------------------------------------------------------------- 1 | { 2 | "glyph_data": [ 3 | { 4 | "x0": 1, 5 | "y0": 1, 6 | "x1": 1, 7 | "y1": 1, 8 | "xoff": 0.0e+00, 9 | "yoff": 0.0e+00, 10 | "xadvance": 1.1612903594970703e+01 11 | }, 12 | { 13 | "x0": 2, 14 | "y0": 1, 15 | "x1": 6, 16 | "y1": 17, 17 | "xoff": 4.0e+00, 18 | "yoff": -1.5e+01, 19 | "xadvance": 1.1612903594970703e+01 20 | }, 21 | { 22 | "x0": 7, 23 | "y0": 1, 24 | "x1": 13, 25 | "y1": 8, 26 | "xoff": 3.0e+00, 27 | "yoff": -1.5e+01, 28 | "xadvance": 1.1612903594970703e+01 29 | }, 30 | { 31 | "x0": 14, 32 | "y0": 1, 33 | "x1": 25, 34 | "y1": 16, 35 | "xoff": 0.0e+00, 36 | "yoff": -1.5e+01, 37 | "xadvance": 1.1612903594970703e+01 38 | }, 39 | { 40 | "x0": 26, 41 | "y0": 1, 42 | "x1": 36, 43 | "y1": 21, 44 | "xoff": 1.0e+00, 45 | "yoff": -1.7e+01, 46 | "xadvance": 1.1612903594970703e+01 47 | }, 48 | { 49 | "x0": 37, 50 | "y0": 1, 51 | "x1": 49, 52 | "y1": 17, 53 | "xoff": 0.0e+00, 54 | "yoff": -1.5e+01, 55 | "xadvance": 1.1612903594970703e+01 56 | }, 57 | { 58 | "x0": 50, 59 | "y0": 1, 60 | "x1": 60, 61 | "y1": 17, 62 | "xoff": 1.0e+00, 63 | "yoff": -1.5e+01, 64 | "xadvance": 1.1612903594970703e+01 65 | }, 66 | { 67 | "x0": 61, 68 | "y0": 1, 69 | "x1": 63, 70 | "y1": 8, 71 | "xoff": 5.0e+00, 72 | "yoff": -1.5e+01, 73 | "xadvance": 1.1612903594970703e+01 74 | }, 75 | { 76 | "x0": 64, 77 | "y0": 1, 78 | "x1": 71, 79 | "y1": 21, 80 | "xoff": 3.0e+00, 81 | "yoff": -1.7e+01, 82 | "xadvance": 1.1612903594970703e+01 83 | }, 84 | { 85 | "x0": 72, 86 | "y0": 1, 87 | "x1": 78, 88 | "y1": 21, 89 | "xoff": 2.0e+00, 90 | "yoff": -1.7e+01, 91 | "xadvance": 1.1612903594970703e+01 92 | }, 93 | { 94 | "x0": 79, 95 | "y0": 1, 96 | "x1": 90, 97 | "y1": 12, 98 | "xoff": 0.0e+00, 99 | "yoff": -1.2e+01, 100 | "xadvance": 1.1612903594970703e+01 101 | }, 102 | { 103 | "x0": 91, 104 | "y0": 1, 105 | "x1": 101, 106 | "y1": 11, 107 | "xoff": 1.0e+00, 108 | "yoff": -1.1e+01, 109 | "xadvance": 1.1612903594970703e+01 110 | }, 111 | { 112 | "x0": 102, 113 | "y0": 1, 114 | "x1": 107, 115 | "y1": 8, 116 | "xoff": 3.0e+00, 117 | "yoff": -3.0e+00, 118 | "xadvance": 1.1612903594970703e+01 119 | }, 120 | { 121 | "x0": 108, 122 | "y0": 1, 123 | "x1": 117, 124 | "y1": 3, 125 | "xoff": 1.0e+00, 126 | "yoff": -7.0e+00, 127 | "xadvance": 1.1612903594970703e+01 128 | }, 129 | { 130 | "x0": 118, 131 | "y0": 1, 132 | "x1": 122, 133 | "y1": 5, 134 | "xoff": 4.0e+00, 135 | "yoff": -3.0e+00, 136 | "xadvance": 1.1612903594970703e+01 137 | }, 138 | { 139 | "x0": 123, 140 | "y0": 1, 141 | "x1": 132, 142 | "y1": 21, 143 | "xoff": 1.0e+00, 144 | "yoff": -1.7e+01, 145 | "xadvance": 1.1612903594970703e+01 146 | }, 147 | { 148 | "x0": 133, 149 | "y0": 1, 150 | "x1": 142, 151 | "y1": 17, 152 | "xoff": 1.0e+00, 153 | "yoff": -1.5e+01, 154 | "xadvance": 1.1612903594970703e+01 155 | }, 156 | { 157 | "x0": 143, 158 | "y0": 1, 159 | "x1": 153, 160 | "y1": 16, 161 | "xoff": 1.0e+00, 162 | "yoff": -1.5e+01, 163 | "xadvance": 1.1612903594970703e+01 164 | }, 165 | { 166 | "x0": 154, 167 | "y0": 1, 168 | "x1": 164, 169 | "y1": 16, 170 | "xoff": 1.0e+00, 171 | "yoff": -1.5e+01, 172 | "xadvance": 1.1612903594970703e+01 173 | }, 174 | { 175 | "x0": 165, 176 | "y0": 1, 177 | "x1": 174, 178 | "y1": 17, 179 | "xoff": 1.0e+00, 180 | "yoff": -1.5e+01, 181 | "xadvance": 1.1612903594970703e+01 182 | }, 183 | { 184 | "x0": 175, 185 | "y0": 1, 186 | "x1": 184, 187 | "y1": 16, 188 | "xoff": 1.0e+00, 189 | "yoff": -1.5e+01, 190 | "xadvance": 1.1612903594970703e+01 191 | }, 192 | { 193 | "x0": 185, 194 | "y0": 1, 195 | "x1": 194, 196 | "y1": 17, 197 | "xoff": 1.0e+00, 198 | "yoff": -1.5e+01, 199 | "xadvance": 1.1612903594970703e+01 200 | }, 201 | { 202 | "x0": 195, 203 | "y0": 1, 204 | "x1": 205, 205 | "y1": 17, 206 | "xoff": 1.0e+00, 207 | "yoff": -1.5e+01, 208 | "xadvance": 1.1612903594970703e+01 209 | }, 210 | { 211 | "x0": 206, 212 | "y0": 1, 213 | "x1": 216, 214 | "y1": 16, 215 | "xoff": 1.0e+00, 216 | "yoff": -1.5e+01, 217 | "xadvance": 1.1612903594970703e+01 218 | }, 219 | { 220 | "x0": 217, 221 | "y0": 1, 222 | "x1": 227, 223 | "y1": 17, 224 | "xoff": 1.0e+00, 225 | "yoff": -1.5e+01, 226 | "xadvance": 1.1612903594970703e+01 227 | }, 228 | { 229 | "x0": 228, 230 | "y0": 1, 231 | "x1": 238, 232 | "y1": 16, 233 | "xoff": 1.0e+00, 234 | "yoff": -1.5e+01, 235 | "xadvance": 1.1612903594970703e+01 236 | }, 237 | { 238 | "x0": 239, 239 | "y0": 1, 240 | "x1": 243, 241 | "y1": 13, 242 | "xoff": 4.0e+00, 243 | "yoff": -1.1e+01, 244 | "xadvance": 1.1612903594970703e+01 245 | }, 246 | { 247 | "x0": 244, 248 | "y0": 1, 249 | "x1": 249, 250 | "y1": 16, 251 | "xoff": 3.0e+00, 252 | "yoff": -1.1e+01, 253 | "xadvance": 1.1612903594970703e+01 254 | }, 255 | { 256 | "x0": 250, 257 | "y0": 1, 258 | "x1": 259, 259 | "y1": 12, 260 | "xoff": 1.0e+00, 261 | "yoff": -1.2e+01, 262 | "xadvance": 1.1612903594970703e+01 263 | }, 264 | { 265 | "x0": 260, 266 | "y0": 1, 267 | "x1": 269, 268 | "y1": 8, 269 | "xoff": 1.0e+00, 270 | "yoff": -1.0e+01, 271 | "xadvance": 1.1612903594970703e+01 272 | }, 273 | { 274 | "x0": 270, 275 | "y0": 1, 276 | "x1": 279, 277 | "y1": 12, 278 | "xoff": 1.0e+00, 279 | "yoff": -1.2e+01, 280 | "xadvance": 1.1612903594970703e+01 281 | }, 282 | { 283 | "x0": 280, 284 | "y0": 1, 285 | "x1": 287, 286 | "y1": 17, 287 | "xoff": 3.0e+00, 288 | "yoff": -1.5e+01, 289 | "xadvance": 1.1612903594970703e+01 290 | }, 291 | { 292 | "x0": 288, 293 | "y0": 1, 294 | "x1": 298, 295 | "y1": 20, 296 | "xoff": 1.0e+00, 297 | "yoff": -1.5e+01, 298 | "xadvance": 1.1612903594970703e+01 299 | }, 300 | { 301 | "x0": 299, 302 | "y0": 1, 303 | "x1": 309, 304 | "y1": 16, 305 | "xoff": 1.0e+00, 306 | "yoff": -1.5e+01, 307 | "xadvance": 1.1612903594970703e+01 308 | }, 309 | { 310 | "x0": 310, 311 | "y0": 1, 312 | "x1": 320, 313 | "y1": 16, 314 | "xoff": 1.0e+00, 315 | "yoff": -1.5e+01, 316 | "xadvance": 1.1612903594970703e+01 317 | }, 318 | { 319 | "x0": 321, 320 | "y0": 1, 321 | "x1": 330, 322 | "y1": 17, 323 | "xoff": 1.0e+00, 324 | "yoff": -1.5e+01, 325 | "xadvance": 1.1612903594970703e+01 326 | }, 327 | { 328 | "x0": 331, 329 | "y0": 1, 330 | "x1": 340, 331 | "y1": 16, 332 | "xoff": 1.0e+00, 333 | "yoff": -1.5e+01, 334 | "xadvance": 1.1612903594970703e+01 335 | }, 336 | { 337 | "x0": 341, 338 | "y0": 1, 339 | "x1": 349, 340 | "y1": 16, 341 | "xoff": 2.0e+00, 342 | "yoff": -1.5e+01, 343 | "xadvance": 1.1612903594970703e+01 344 | }, 345 | { 346 | "x0": 350, 347 | "y0": 1, 348 | "x1": 359, 349 | "y1": 16, 350 | "xoff": 2.0e+00, 351 | "yoff": -1.5e+01, 352 | "xadvance": 1.1612903594970703e+01 353 | }, 354 | { 355 | "x0": 360, 356 | "y0": 1, 357 | "x1": 369, 358 | "y1": 17, 359 | "xoff": 1.0e+00, 360 | "yoff": -1.5e+01, 361 | "xadvance": 1.1612903594970703e+01 362 | }, 363 | { 364 | "x0": 370, 365 | "y0": 1, 366 | "x1": 379, 367 | "y1": 16, 368 | "xoff": 1.0e+00, 369 | "yoff": -1.5e+01, 370 | "xadvance": 1.1612903594970703e+01 371 | }, 372 | { 373 | "x0": 380, 374 | "y0": 1, 375 | "x1": 388, 376 | "y1": 16, 377 | "xoff": 2.0e+00, 378 | "yoff": -1.5e+01, 379 | "xadvance": 1.1612903594970703e+01 380 | }, 381 | { 382 | "x0": 389, 383 | "y0": 1, 384 | "x1": 398, 385 | "y1": 17, 386 | "xoff": 1.0e+00, 387 | "yoff": -1.5e+01, 388 | "xadvance": 1.1612903594970703e+01 389 | }, 390 | { 391 | "x0": 399, 392 | "y0": 1, 393 | "x1": 409, 394 | "y1": 16, 395 | "xoff": 1.0e+00, 396 | "yoff": -1.5e+01, 397 | "xadvance": 1.1612903594970703e+01 398 | }, 399 | { 400 | "x0": 410, 401 | "y0": 1, 402 | "x1": 419, 403 | "y1": 16, 404 | "xoff": 2.0e+00, 405 | "yoff": -1.5e+01, 406 | "xadvance": 1.1612903594970703e+01 407 | }, 408 | { 409 | "x0": 420, 410 | "y0": 1, 411 | "x1": 429, 412 | "y1": 16, 413 | "xoff": 1.0e+00, 414 | "yoff": -1.5e+01, 415 | "xadvance": 1.1612903594970703e+01 416 | }, 417 | { 418 | "x0": 430, 419 | "y0": 1, 420 | "x1": 439, 421 | "y1": 16, 422 | "xoff": 1.0e+00, 423 | "yoff": -1.5e+01, 424 | "xadvance": 1.1612903594970703e+01 425 | }, 426 | { 427 | "x0": 440, 428 | "y0": 1, 429 | "x1": 449, 430 | "y1": 17, 431 | "xoff": 1.0e+00, 432 | "yoff": -1.5e+01, 433 | "xadvance": 1.1612903594970703e+01 434 | }, 435 | { 436 | "x0": 450, 437 | "y0": 1, 438 | "x1": 460, 439 | "y1": 16, 440 | "xoff": 1.0e+00, 441 | "yoff": -1.5e+01, 442 | "xadvance": 1.1612903594970703e+01 443 | }, 444 | { 445 | "x0": 461, 446 | "y0": 1, 447 | "x1": 470, 448 | "y1": 20, 449 | "xoff": 1.0e+00, 450 | "yoff": -1.5e+01, 451 | "xadvance": 1.1612903594970703e+01 452 | }, 453 | { 454 | "x0": 471, 455 | "y0": 1, 456 | "x1": 481, 457 | "y1": 16, 458 | "xoff": 1.0e+00, 459 | "yoff": -1.5e+01, 460 | "xadvance": 1.1612903594970703e+01 461 | }, 462 | { 463 | "x0": 482, 464 | "y0": 1, 465 | "x1": 492, 466 | "y1": 17, 467 | "xoff": 1.0e+00, 468 | "yoff": -1.5e+01, 469 | "xadvance": 1.1612903594970703e+01 470 | }, 471 | { 472 | "x0": 493, 473 | "y0": 1, 474 | "x1": 503, 475 | "y1": 16, 476 | "xoff": 1.0e+00, 477 | "yoff": -1.5e+01, 478 | "xadvance": 1.1612903594970703e+01 479 | }, 480 | { 481 | "x0": 1, 482 | "y0": 22, 483 | "x1": 10, 484 | "y1": 38, 485 | "xoff": 1.0e+00, 486 | "yoff": -1.5e+01, 487 | "xadvance": 1.1612903594970703e+01 488 | }, 489 | { 490 | "x0": 11, 491 | "y0": 22, 492 | "x1": 21, 493 | "y1": 37, 494 | "xoff": 1.0e+00, 495 | "yoff": -1.5e+01, 496 | "xadvance": 1.1612903594970703e+01 497 | }, 498 | { 499 | "x0": 22, 500 | "y0": 22, 501 | "x1": 33, 502 | "y1": 37, 503 | "xoff": 0.0e+00, 504 | "yoff": -1.5e+01, 505 | "xadvance": 1.1612903594970703e+01 506 | }, 507 | { 508 | "x0": 34, 509 | "y0": 22, 510 | "x1": 45, 511 | "y1": 37, 512 | "xoff": 0.0e+00, 513 | "yoff": -1.5e+01, 514 | "xadvance": 1.1612903594970703e+01 515 | }, 516 | { 517 | "x0": 46, 518 | "y0": 22, 519 | "x1": 57, 520 | "y1": 37, 521 | "xoff": 0.0e+00, 522 | "yoff": -1.5e+01, 523 | "xadvance": 1.1612903594970703e+01 524 | }, 525 | { 526 | "x0": 58, 527 | "y0": 22, 528 | "x1": 67, 529 | "y1": 37, 530 | "xoff": 1.0e+00, 531 | "yoff": -1.5e+01, 532 | "xadvance": 1.1612903594970703e+01 533 | }, 534 | { 535 | "x0": 68, 536 | "y0": 22, 537 | "x1": 73, 538 | "y1": 42, 539 | "xoff": 4.0e+00, 540 | "yoff": -1.7e+01, 541 | "xadvance": 1.1612903594970703e+01 542 | }, 543 | { 544 | "x0": 74, 545 | "y0": 22, 546 | "x1": 83, 547 | "y1": 42, 548 | "xoff": 1.0e+00, 549 | "yoff": -1.7e+01, 550 | "xadvance": 1.1612903594970703e+01 551 | }, 552 | { 553 | "x0": 84, 554 | "y0": 22, 555 | "x1": 90, 556 | "y1": 42, 557 | "xoff": 2.0e+00, 558 | "yoff": -1.7e+01, 559 | "xadvance": 1.1612903594970703e+01 560 | }, 561 | { 562 | "x0": 91, 563 | "y0": 22, 564 | "x1": 100, 565 | "y1": 31, 566 | "xoff": 1.0e+00, 567 | "yoff": -1.5e+01, 568 | "xadvance": 1.1612903594970703e+01 569 | }, 570 | { 571 | "x0": 101, 572 | "y0": 22, 573 | "x1": 111, 574 | "y1": 24, 575 | "xoff": 1.0e+00, 576 | "yoff": 0.0e+00, 577 | "xadvance": 1.1612903594970703e+01 578 | }, 579 | { 580 | "x0": 112, 581 | "y0": 22, 582 | "x1": 117, 583 | "y1": 26, 584 | "xoff": 3.0e+00, 585 | "yoff": -1.6e+01, 586 | "xadvance": 1.1612903594970703e+01 587 | }, 588 | { 589 | "x0": 118, 590 | "y0": 22, 591 | "x1": 127, 592 | "y1": 34, 593 | "xoff": 1.0e+00, 594 | "yoff": -1.1e+01, 595 | "xadvance": 1.1612903594970703e+01 596 | }, 597 | { 598 | "x0": 128, 599 | "y0": 22, 600 | "x1": 137, 601 | "y1": 38, 602 | "xoff": 1.0e+00, 603 | "yoff": -1.5e+01, 604 | "xadvance": 1.1612903594970703e+01 605 | }, 606 | { 607 | "x0": 138, 608 | "y0": 22, 609 | "x1": 147, 610 | "y1": 34, 611 | "xoff": 1.0e+00, 612 | "yoff": -1.1e+01, 613 | "xadvance": 1.1612903594970703e+01 614 | }, 615 | { 616 | "x0": 148, 617 | "y0": 22, 618 | "x1": 157, 619 | "y1": 38, 620 | "xoff": 1.0e+00, 621 | "yoff": -1.5e+01, 622 | "xadvance": 1.1612903594970703e+01 623 | }, 624 | { 625 | "x0": 158, 626 | "y0": 22, 627 | "x1": 167, 628 | "y1": 34, 629 | "xoff": 1.0e+00, 630 | "yoff": -1.1e+01, 631 | "xadvance": 1.1612903594970703e+01 632 | }, 633 | { 634 | "x0": 168, 635 | "y0": 22, 636 | "x1": 178, 637 | "y1": 37, 638 | "xoff": 1.0e+00, 639 | "yoff": -1.5e+01, 640 | "xadvance": 1.1612903594970703e+01 641 | }, 642 | { 643 | "x0": 179, 644 | "y0": 22, 645 | "x1": 188, 646 | "y1": 37, 647 | "xoff": 1.0e+00, 648 | "yoff": -1.1e+01, 649 | "xadvance": 1.1612903594970703e+01 650 | }, 651 | { 652 | "x0": 189, 653 | "y0": 22, 654 | "x1": 198, 655 | "y1": 37, 656 | "xoff": 1.0e+00, 657 | "yoff": -1.5e+01, 658 | "xadvance": 1.1612903594970703e+01 659 | }, 660 | { 661 | "x0": 199, 662 | "y0": 22, 663 | "x1": 209, 664 | "y1": 37, 665 | "xoff": 1.0e+00, 666 | "yoff": -1.5e+01, 667 | "xadvance": 1.1612903594970703e+01 668 | }, 669 | { 670 | "x0": 210, 671 | "y0": 22, 672 | "x1": 218, 673 | "y1": 41, 674 | "xoff": 1.0e+00, 675 | "yoff": -1.5e+01, 676 | "xadvance": 1.1612903594970703e+01 677 | }, 678 | { 679 | "x0": 219, 680 | "y0": 22, 681 | "x1": 228, 682 | "y1": 37, 683 | "xoff": 2.0e+00, 684 | "yoff": -1.5e+01, 685 | "xadvance": 1.1612903594970703e+01 686 | }, 687 | { 688 | "x0": 229, 689 | "y0": 22, 690 | "x1": 240, 691 | "y1": 37, 692 | "xoff": 0.0e+00, 693 | "yoff": -1.5e+01, 694 | "xadvance": 1.1612903594970703e+01 695 | }, 696 | { 697 | "x0": 241, 698 | "y0": 22, 699 | "x1": 251, 700 | "y1": 33, 701 | "xoff": 1.0e+00, 702 | "yoff": -1.1e+01, 703 | "xadvance": 1.1612903594970703e+01 704 | }, 705 | { 706 | "x0": 252, 707 | "y0": 22, 708 | "x1": 261, 709 | "y1": 33, 710 | "xoff": 1.0e+00, 711 | "yoff": -1.1e+01, 712 | "xadvance": 1.1612903594970703e+01 713 | }, 714 | { 715 | "x0": 262, 716 | "y0": 22, 717 | "x1": 271, 718 | "y1": 34, 719 | "xoff": 1.0e+00, 720 | "yoff": -1.1e+01, 721 | "xadvance": 1.1612903594970703e+01 722 | }, 723 | { 724 | "x0": 272, 725 | "y0": 22, 726 | "x1": 281, 727 | "y1": 37, 728 | "xoff": 1.0e+00, 729 | "yoff": -1.1e+01, 730 | "xadvance": 1.1612903594970703e+01 731 | }, 732 | { 733 | "x0": 282, 734 | "y0": 22, 735 | "x1": 291, 736 | "y1": 37, 737 | "xoff": 1.0e+00, 738 | "yoff": -1.1e+01, 739 | "xadvance": 1.1612903594970703e+01 740 | }, 741 | { 742 | "x0": 292, 743 | "y0": 22, 744 | "x1": 301, 745 | "y1": 33, 746 | "xoff": 2.0e+00, 747 | "yoff": -1.1e+01, 748 | "xadvance": 1.1612903594970703e+01 749 | }, 750 | { 751 | "x0": 302, 752 | "y0": 22, 753 | "x1": 311, 754 | "y1": 34, 755 | "xoff": 1.0e+00, 756 | "yoff": -1.1e+01, 757 | "xadvance": 1.1612903594970703e+01 758 | }, 759 | { 760 | "x0": 312, 761 | "y0": 22, 762 | "x1": 321, 763 | "y1": 36, 764 | "xoff": 1.0e+00, 765 | "yoff": -1.4e+01, 766 | "xadvance": 1.1612903594970703e+01 767 | }, 768 | { 769 | "x0": 322, 770 | "y0": 22, 771 | "x1": 331, 772 | "y1": 34, 773 | "xoff": 1.0e+00, 774 | "yoff": -1.1e+01, 775 | "xadvance": 1.1612903594970703e+01 776 | }, 777 | { 778 | "x0": 332, 779 | "y0": 22, 780 | "x1": 342, 781 | "y1": 33, 782 | "xoff": 1.0e+00, 783 | "yoff": -1.1e+01, 784 | "xadvance": 1.1612903594970703e+01 785 | }, 786 | { 787 | "x0": 343, 788 | "y0": 22, 789 | "x1": 354, 790 | "y1": 33, 791 | "xoff": 0.0e+00, 792 | "yoff": -1.1e+01, 793 | "xadvance": 1.1612903594970703e+01 794 | }, 795 | { 796 | "x0": 355, 797 | "y0": 22, 798 | "x1": 365, 799 | "y1": 33, 800 | "xoff": 1.0e+00, 801 | "yoff": -1.1e+01, 802 | "xadvance": 1.1612903594970703e+01 803 | }, 804 | { 805 | "x0": 366, 806 | "y0": 22, 807 | "x1": 376, 808 | "y1": 37, 809 | "xoff": 1.0e+00, 810 | "yoff": -1.1e+01, 811 | "xadvance": 1.1612903594970703e+01 812 | }, 813 | { 814 | "x0": 377, 815 | "y0": 22, 816 | "x1": 386, 817 | "y1": 33, 818 | "xoff": 1.0e+00, 819 | "yoff": -1.1e+01, 820 | "xadvance": 1.1612903594970703e+01 821 | }, 822 | { 823 | "x0": 387, 824 | "y0": 22, 825 | "x1": 396, 826 | "y1": 42, 827 | "xoff": 1.0e+00, 828 | "yoff": -1.7e+01, 829 | "xadvance": 1.1612903594970703e+01 830 | }, 831 | { 832 | "x0": 397, 833 | "y0": 22, 834 | "x1": 399, 835 | "y1": 42, 836 | "xoff": 5.0e+00, 837 | "yoff": -1.7e+01, 838 | "xadvance": 1.1612903594970703e+01 839 | }, 840 | { 841 | "x0": 400, 842 | "y0": 22, 843 | "x1": 409, 844 | "y1": 42, 845 | "xoff": 1.0e+00, 846 | "yoff": -1.7e+01, 847 | "xadvance": 1.1612903594970703e+01 848 | }, 849 | { 850 | "x0": 410, 851 | "y0": 22, 852 | "x1": 420, 853 | "y1": 27, 854 | "xoff": 1.0e+00, 855 | "yoff": -9.0e+00, 856 | "xadvance": 1.1612903594970703e+01 857 | }, 858 | { 859 | "x0": 421, 860 | "y0": 22, 861 | "x1": 430, 862 | "y1": 36, 863 | "xoff": 1.0e+00, 864 | "yoff": -1.4e+01, 865 | "xadvance": 1.1612903594970703e+01 866 | }, 867 | { 868 | "x0": 1, 869 | "y0": 1, 870 | "x1": 1, 871 | "y1": 1, 872 | "xoff": 0.0e+00, 873 | "yoff": 0.0e+00, 874 | "xadvance": 1.0776000022888184e+01 875 | }, 876 | { 877 | "x0": 2, 878 | "y0": 1, 879 | "x1": 8, 880 | "y1": 21, 881 | "xoff": 1.0e+00, 882 | "yoff": -1.8e+01, 883 | "xadvance": 9.312000274658203e+00 884 | }, 885 | { 886 | "x0": 9, 887 | "y0": 1, 888 | "x1": 9, 889 | "y1": 1, 890 | "xoff": 0.0e+00, 891 | "yoff": 0.0e+00, 892 | "xadvance": 1.2e+01 893 | }, 894 | { 895 | "x0": 10, 896 | "y0": 1, 897 | "x1": 23, 898 | "y1": 17, 899 | "xoff": 1.0e+00, 900 | "yoff": -1.5e+01, 901 | "xadvance": 1.4808000564575195e+01 902 | }, 903 | { 904 | "x0": 24, 905 | "y0": 1, 906 | "x1": 38, 907 | "y1": 25, 908 | "xoff": 1.0e+00, 909 | "yoff": -2.1e+01, 910 | "xadvance": 1.4496000289916992e+01 911 | }, 912 | { 913 | "x0": 39, 914 | "y0": 1, 915 | "x1": 39, 916 | "y1": 1, 917 | "xoff": 0.0e+00, 918 | "yoff": 0.0e+00, 919 | "xadvance": 1.2e+01 920 | }, 921 | { 922 | "x0": 40, 923 | "y0": 1, 924 | "x1": 40, 925 | "y1": 1, 926 | "xoff": 0.0e+00, 927 | "yoff": 0.0e+00, 928 | "xadvance": 1.2e+01 929 | }, 930 | { 931 | "x0": 41, 932 | "y0": 1, 933 | "x1": 41, 934 | "y1": 1, 935 | "xoff": 0.0e+00, 936 | "yoff": 0.0e+00, 937 | "xadvance": 1.2e+01 938 | }, 939 | { 940 | "x0": 42, 941 | "y0": 1, 942 | "x1": 51, 943 | "y1": 24, 944 | "xoff": 1.0e+00, 945 | "yoff": -1.8e+01, 946 | "xadvance": 1.0583999633789062e+01 947 | }, 948 | { 949 | "x0": 52, 950 | "y0": 1, 951 | "x1": 62, 952 | "y1": 24, 953 | "xoff": -2.0e+00, 954 | "yoff": -1.7e+01, 955 | "xadvance": 9.095999717712402e+00 956 | }, 957 | { 958 | "x0": 63, 959 | "y0": 1, 960 | "x1": 63, 961 | "y1": 1, 962 | "xoff": 0.0e+00, 963 | "yoff": 0.0e+00, 964 | "xadvance": 1.2e+01 965 | }, 966 | { 967 | "x0": 64, 968 | "y0": 1, 969 | "x1": 64, 970 | "y1": 1, 971 | "xoff": 0.0e+00, 972 | "yoff": 0.0e+00, 973 | "xadvance": 1.2e+01 974 | }, 975 | { 976 | "x0": 65, 977 | "y0": 1, 978 | "x1": 71, 979 | "y1": 11, 980 | "xoff": 1.0e+00, 981 | "yoff": -4.0e+00, 982 | "xadvance": 8.880000114440918e+00 983 | }, 984 | { 985 | "x0": 72, 986 | "y0": 1, 987 | "x1": 80, 988 | "y1": 8, 989 | "xoff": 2.0e+00, 990 | "yoff": -1.0e+01, 991 | "xadvance": 1.2576000213623047e+01 992 | }, 993 | { 994 | "x0": 81, 995 | "y0": 1, 996 | "x1": 87, 997 | "y1": 6, 998 | "xoff": 1.0e+00, 999 | "yoff": -3.0e+00, 1000 | "xadvance": 8.32800006866455e+00 1001 | }, 1002 | { 1003 | "x0": 88, 1004 | "y0": 1, 1005 | "x1": 96, 1006 | "y1": 19, 1007 | "xoff": 1.0e+00, 1008 | "yoff": -1.7e+01, 1009 | "xadvance": 1.0008000373840332e+01 1010 | }, 1011 | { 1012 | "x0": 97, 1013 | "y0": 1, 1014 | "x1": 109, 1015 | "y1": 14, 1016 | "xoff": 1.0e+00, 1017 | "yoff": -1.2e+01, 1018 | "xadvance": 1.3848000526428223e+01 1019 | }, 1020 | { 1021 | "x0": 110, 1022 | "y0": 1, 1023 | "x1": 115, 1024 | "y1": 15, 1025 | "xoff": 3.0e+00, 1026 | "yoff": -1.3e+01, 1027 | "xadvance": 1.0727999687194824e+01 1028 | }, 1029 | { 1030 | "x0": 116, 1031 | "y0": 1, 1032 | "x1": 126, 1033 | "y1": 15, 1034 | "xoff": 1.0e+00, 1035 | "yoff": -1.3e+01, 1036 | "xadvance": 1.2552000045776367e+01 1037 | }, 1038 | { 1039 | "x0": 127, 1040 | "y0": 1, 1041 | "x1": 137, 1042 | "y1": 20, 1043 | "xoff": 0.0e+00, 1044 | "yoff": -1.3e+01, 1045 | "xadvance": 1.0751999855041504e+01 1046 | }, 1047 | { 1048 | "x0": 138, 1049 | "y0": 1, 1050 | "x1": 152, 1051 | "y1": 19, 1052 | "xoff": 0.0e+00, 1053 | "yoff": -1.3e+01, 1054 | "xadvance": 1.4664000511169434e+01 1055 | }, 1056 | { 1057 | "x0": 153, 1058 | "y0": 1, 1059 | "x1": 162, 1060 | "y1": 21, 1061 | "xoff": 1.0e+00, 1062 | "yoff": -1.4e+01, 1063 | "xadvance": 1.0800000190734863e+01 1064 | }, 1065 | { 1066 | "x0": 163, 1067 | "y0": 1, 1068 | "x1": 174, 1069 | "y1": 20, 1070 | "xoff": 1.0e+00, 1071 | "yoff": -1.8e+01, 1072 | "xadvance": 1.3104000091552734e+01 1073 | }, 1074 | { 1075 | "x0": 175, 1076 | "y0": 1, 1077 | "x1": 186, 1078 | "y1": 19, 1079 | "xoff": 1.0e+00, 1080 | "yoff": -1.3e+01, 1081 | "xadvance": 1.3199999809265137e+01 1082 | }, 1083 | { 1084 | "x0": 187, 1085 | "y0": 1, 1086 | "x1": 197, 1087 | "y1": 20, 1088 | "xoff": 1.0e+00, 1089 | "yoff": -1.8e+01, 1090 | "xadvance": 1.1904000282287598e+01 1091 | }, 1092 | { 1093 | "x0": 198, 1094 | "y0": 1, 1095 | "x1": 210, 1096 | "y1": 20, 1097 | "xoff": 0.0e+00, 1098 | "yoff": -1.3e+01, 1099 | "xadvance": 1.2359999656677246e+01 1100 | }, 1101 | { 1102 | "x0": 211, 1103 | "y0": 1, 1104 | "x1": 217, 1105 | "y1": 15, 1106 | "xoff": 2.0e+00, 1107 | "yoff": -1.3e+01, 1108 | "xadvance": 1.0248000144958496e+01 1109 | }, 1110 | { 1111 | "x0": 218, 1112 | "y0": 1, 1113 | "x1": 224, 1114 | "y1": 18, 1115 | "xoff": 2.0e+00, 1116 | "yoff": -1.1e+01, 1117 | "xadvance": 1.0416000366210938e+01 1118 | }, 1119 | { 1120 | "x0": 225, 1121 | "y0": 1, 1122 | "x1": 225, 1123 | "y1": 1, 1124 | "xoff": 0.0e+00, 1125 | "yoff": 0.0e+00, 1126 | "xadvance": 1.2e+01 1127 | }, 1128 | { 1129 | "x0": 226, 1130 | "y0": 1, 1131 | "x1": 226, 1132 | "y1": 1, 1133 | "xoff": 0.0e+00, 1134 | "yoff": 0.0e+00, 1135 | "xadvance": 1.2e+01 1136 | }, 1137 | { 1138 | "x0": 227, 1139 | "y0": 1, 1140 | "x1": 227, 1141 | "y1": 1, 1142 | "xoff": 0.0e+00, 1143 | "yoff": 0.0e+00, 1144 | "xadvance": 1.2e+01 1145 | }, 1146 | { 1147 | "x0": 228, 1148 | "y0": 1, 1149 | "x1": 238, 1150 | "y1": 22, 1151 | "xoff": 2.0e+00, 1152 | "yoff": -1.9e+01, 1153 | "xadvance": 1.3704000473022461e+01 1154 | }, 1155 | { 1156 | "x0": 239, 1157 | "y0": 1, 1158 | "x1": 257, 1159 | "y1": 19, 1160 | "xoff": 0.0e+00, 1161 | "yoff": -1.6e+01, 1162 | "xadvance": 1.843199920654297e+01 1163 | }, 1164 | { 1165 | "x0": 258, 1166 | "y0": 1, 1167 | "x1": 276, 1168 | "y1": 20, 1169 | "xoff": 0.0e+00, 1170 | "yoff": -1.8e+01, 1171 | "xadvance": 1.8191999435424805e+01 1172 | }, 1173 | { 1174 | "x0": 277, 1175 | "y0": 1, 1176 | "x1": 293, 1177 | "y1": 21, 1178 | "xoff": 0.0e+00, 1179 | "yoff": -1.9e+01, 1180 | "xadvance": 1.6007999420166016e+01 1181 | }, 1182 | { 1183 | "x0": 294, 1184 | "y0": 1, 1185 | "x1": 312, 1186 | "y1": 21, 1187 | "xoff": 0.0e+00, 1188 | "yoff": -1.9e+01, 1189 | "xadvance": 1.8144001007080078e+01 1190 | }, 1191 | { 1192 | "x0": 313, 1193 | "y0": 1, 1194 | "x1": 332, 1195 | "y1": 20, 1196 | "xoff": 0.0e+00, 1197 | "yoff": -1.8e+01, 1198 | "xadvance": 1.9607999801635742e+01 1199 | }, 1200 | { 1201 | "x0": 333, 1202 | "y0": 1, 1203 | "x1": 349, 1204 | "y1": 21, 1205 | "xoff": 0.0e+00, 1206 | "yoff": -1.9e+01, 1207 | "xadvance": 1.6416000366210938e+01 1208 | }, 1209 | { 1210 | "x0": 350, 1211 | "y0": 1, 1212 | "x1": 365, 1213 | "y1": 20, 1214 | "xoff": 0.0e+00, 1215 | "yoff": -1.8e+01, 1216 | "xadvance": 1.564799976348877e+01 1217 | }, 1218 | { 1219 | "x0": 366, 1220 | "y0": 1, 1221 | "x1": 385, 1222 | "y1": 21, 1223 | "xoff": 0.0e+00, 1224 | "yoff": -1.9e+01, 1225 | "xadvance": 1.931999969482422e+01 1226 | }, 1227 | { 1228 | "x0": 386, 1229 | "y0": 1, 1230 | "x1": 407, 1231 | "y1": 20, 1232 | "xoff": 0.0e+00, 1233 | "yoff": -1.8e+01, 1234 | "xadvance": 2.1384000778198242e+01 1235 | }, 1236 | { 1237 | "x0": 408, 1238 | "y0": 1, 1239 | "x1": 416, 1240 | "y1": 20, 1241 | "xoff": 1.0e+00, 1242 | "yoff": -1.8e+01, 1243 | "xadvance": 1.0272000312805176e+01 1244 | }, 1245 | { 1246 | "x0": 417, 1247 | "y0": 1, 1248 | "x1": 427, 1249 | "y1": 27, 1250 | "xoff": 0.0e+00, 1251 | "yoff": -1.8e+01, 1252 | "xadvance": 1.0440000534057617e+01 1253 | }, 1254 | { 1255 | "x0": 428, 1256 | "y0": 1, 1257 | "x1": 445, 1258 | "y1": 20, 1259 | "xoff": 0.0e+00, 1260 | "yoff": -1.8e+01, 1261 | "xadvance": 1.797599983215332e+01 1262 | }, 1263 | { 1264 | "x0": 446, 1265 | "y0": 1, 1266 | "x1": 463, 1267 | "y1": 20, 1268 | "xoff": 0.0e+00, 1269 | "yoff": -1.8e+01, 1270 | "xadvance": 1.718400001525879e+01 1271 | }, 1272 | { 1273 | "x0": 464, 1274 | "y0": 1, 1275 | "x1": 487, 1276 | "y1": 21, 1277 | "xoff": 0.0e+00, 1278 | "yoff": -1.9e+01, 1279 | "xadvance": 2.2920000076293945e+01 1280 | }, 1281 | { 1282 | "x0": 488, 1283 | "y0": 1, 1284 | "x1": 509, 1285 | "y1": 20, 1286 | "xoff": 0.0e+00, 1287 | "yoff": -1.8e+01, 1288 | "xadvance": 2.1144001007080078e+01 1289 | }, 1290 | { 1291 | "x0": 1, 1292 | "y0": 28, 1293 | "x1": 22, 1294 | "y1": 49, 1295 | "xoff": 0.0e+00, 1296 | "yoff": -2.0e+01, 1297 | "xadvance": 2.0880001068115234e+01 1298 | }, 1299 | { 1300 | "x0": 23, 1301 | "y0": 28, 1302 | "x1": 37, 1303 | "y1": 48, 1304 | "xoff": 0.0e+00, 1305 | "yoff": -1.9e+01, 1306 | "xadvance": 1.4904000282287598e+01 1307 | }, 1308 | { 1309 | "x0": 38, 1310 | "y0": 28, 1311 | "x1": 73, 1312 | "y1": 53, 1313 | "xoff": 0.0e+00, 1314 | "yoff": -1.9e+01, 1315 | "xadvance": 2.11200008392334e+01 1316 | }, 1317 | { 1318 | "x0": 74, 1319 | "y0": 28, 1320 | "x1": 92, 1321 | "y1": 48, 1322 | "xoff": 0.0e+00, 1323 | "yoff": -1.9e+01, 1324 | "xadvance": 1.886400032043457e+01 1325 | }, 1326 | { 1327 | "x0": 93, 1328 | "y0": 28, 1329 | "x1": 107, 1330 | "y1": 48, 1331 | "xoff": 0.0e+00, 1332 | "yoff": -1.9e+01, 1333 | "xadvance": 1.4496000289916992e+01 1334 | }, 1335 | { 1336 | "x0": 108, 1337 | "y0": 28, 1338 | "x1": 126, 1339 | "y1": 48, 1340 | "xoff": 1.0e+00, 1341 | "yoff": -1.9e+01, 1342 | "xadvance": 1.927199935913086e+01 1343 | }, 1344 | { 1345 | "x0": 127, 1346 | "y0": 28, 1347 | "x1": 146, 1348 | "y1": 47, 1349 | "xoff": 0.0e+00, 1350 | "yoff": -1.8e+01, 1351 | "xadvance": 1.8719999313354492e+01 1352 | }, 1353 | { 1354 | "x0": 147, 1355 | "y0": 28, 1356 | "x1": 168, 1357 | "y1": 47, 1358 | "xoff": 0.0e+00, 1359 | "yoff": -1.8e+01, 1360 | "xadvance": 2.1e+01 1361 | }, 1362 | { 1363 | "x0": 169, 1364 | "y0": 28, 1365 | "x1": 198, 1366 | "y1": 47, 1367 | "xoff": 0.0e+00, 1368 | "yoff": -1.8e+01, 1369 | "xadvance": 2.868000030517578e+01 1370 | }, 1371 | { 1372 | "x0": 199, 1373 | "y0": 28, 1374 | "x1": 218, 1375 | "y1": 48, 1376 | "xoff": 0.0e+00, 1377 | "yoff": -1.9e+01, 1378 | "xadvance": 1.9992000579833984e+01 1379 | }, 1380 | { 1381 | "x0": 219, 1382 | "y0": 28, 1383 | "x1": 239, 1384 | "y1": 48, 1385 | "xoff": 0.0e+00, 1386 | "yoff": -1.9e+01, 1387 | "xadvance": 2.059200096130371e+01 1388 | }, 1389 | { 1390 | "x0": 240, 1391 | "y0": 28, 1392 | "x1": 259, 1393 | "y1": 48, 1394 | "xoff": 1.0e+00, 1395 | "yoff": -1.8e+01, 1396 | "xadvance": 2.0472000122070312e+01 1397 | }, 1398 | { 1399 | "x0": 260, 1400 | "y0": 28, 1401 | "x1": 260, 1402 | "y1": 28, 1403 | "xoff": 0.0e+00, 1404 | "yoff": 0.0e+00, 1405 | "xadvance": 1.2e+01 1406 | }, 1407 | { 1408 | "x0": 261, 1409 | "y0": 28, 1410 | "x1": 268, 1411 | "y1": 46, 1412 | "xoff": 2.0e+00, 1413 | "yoff": -1.7e+01, 1414 | "xadvance": 1.0440000534057617e+01 1415 | }, 1416 | { 1417 | "x0": 269, 1418 | "y0": 28, 1419 | "x1": 269, 1420 | "y1": 28, 1421 | "xoff": 0.0e+00, 1422 | "yoff": 0.0e+00, 1423 | "xadvance": 1.2e+01 1424 | }, 1425 | { 1426 | "x0": 270, 1427 | "y0": 28, 1428 | "x1": 270, 1429 | "y1": 28, 1430 | "xoff": 0.0e+00, 1431 | "yoff": 0.0e+00, 1432 | "xadvance": 1.2e+01 1433 | }, 1434 | { 1435 | "x0": 271, 1436 | "y0": 28, 1437 | "x1": 284, 1438 | "y1": 31, 1439 | "xoff": 1.0e+00, 1440 | "yoff": 3.0e+00, 1441 | "xadvance": 1.4880000114440918e+01 1442 | }, 1443 | { 1444 | "x0": 285, 1445 | "y0": 28, 1446 | "x1": 292, 1447 | "y1": 33, 1448 | "xoff": 5.0e+00, 1449 | "yoff": -1.7e+01, 1450 | "xadvance": 1.9992000579833984e+01 1451 | }, 1452 | { 1453 | "x0": 293, 1454 | "y0": 28, 1455 | "x1": 304, 1456 | "y1": 40, 1457 | "xoff": 0.0e+00, 1458 | "yoff": -1.2e+01, 1459 | "xadvance": 1.1784000396728516e+01 1460 | }, 1461 | { 1462 | "x0": 305, 1463 | "y0": 28, 1464 | "x1": 318, 1465 | "y1": 47, 1466 | "xoff": 0.0e+00, 1467 | "yoff": -1.8e+01, 1468 | "xadvance": 1.3584000587463379e+01 1469 | }, 1470 | { 1471 | "x0": 319, 1472 | "y0": 28, 1473 | "x1": 329, 1474 | "y1": 41, 1475 | "xoff": 0.0e+00, 1476 | "yoff": -1.2e+01, 1477 | "xadvance": 1.0079999923706055e+01 1478 | }, 1479 | { 1480 | "x0": 330, 1481 | "y0": 28, 1482 | "x1": 343, 1483 | "y1": 47, 1484 | "xoff": 1.0e+00, 1485 | "yoff": -1.8e+01, 1486 | "xadvance": 1.4303999900817871e+01 1487 | }, 1488 | { 1489 | "x0": 344, 1490 | "y0": 28, 1491 | "x1": 354, 1492 | "y1": 41, 1493 | "xoff": 0.0e+00, 1494 | "yoff": -1.2e+01, 1495 | "xadvance": 1.0848000526428223e+01 1496 | }, 1497 | { 1498 | "x0": 355, 1499 | "y0": 28, 1500 | "x1": 364, 1501 | "y1": 47, 1502 | "xoff": 1.0e+00, 1503 | "yoff": -1.8e+01, 1504 | "xadvance": 1.0223999977111816e+01 1505 | }, 1506 | { 1507 | "x0": 365, 1508 | "y0": 28, 1509 | "x1": 379, 1510 | "y1": 47, 1511 | "xoff": 0.0e+00, 1512 | "yoff": -1.2e+01, 1513 | "xadvance": 1.4472000122070312e+01 1514 | }, 1515 | { 1516 | "x0": 380, 1517 | "y0": 28, 1518 | "x1": 394, 1519 | "y1": 47, 1520 | "xoff": 0.0e+00, 1521 | "yoff": -1.8e+01, 1522 | "xadvance": 1.4496000289916992e+01 1523 | }, 1524 | { 1525 | "x0": 395, 1526 | "y0": 28, 1527 | "x1": 401, 1528 | "y1": 47, 1529 | "xoff": 0.0e+00, 1530 | "yoff": -1.8e+01, 1531 | "xadvance": 6.02400016784668e+00 1532 | }, 1533 | { 1534 | "x0": 402, 1535 | "y0": 28, 1536 | "x1": 408, 1537 | "y1": 52, 1538 | "xoff": 0.0e+00, 1539 | "yoff": -1.8e+01, 1540 | "xadvance": 6.168000221252441e+00 1541 | }, 1542 | { 1543 | "x0": 409, 1544 | "y0": 28, 1545 | "x1": 423, 1546 | "y1": 48, 1547 | "xoff": 1.0e+00, 1548 | "yoff": -1.9e+01, 1549 | "xadvance": 1.5e+01 1550 | }, 1551 | { 1552 | "x0": 424, 1553 | "y0": 28, 1554 | "x1": 431, 1555 | "y1": 48, 1556 | "xoff": 1.0e+00, 1557 | "yoff": -1.9e+01, 1558 | "xadvance": 8.039999961853027e+00 1559 | }, 1560 | { 1561 | "x0": 432, 1562 | "y0": 28, 1563 | "x1": 452, 1564 | "y1": 41, 1565 | "xoff": 1.0e+00, 1566 | "yoff": -1.2e+01, 1567 | "xadvance": 2.104800033569336e+01 1568 | }, 1569 | { 1570 | "x0": 453, 1571 | "y0": 28, 1572 | "x1": 467, 1573 | "y1": 41, 1574 | "xoff": 0.0e+00, 1575 | "yoff": -1.2e+01, 1576 | "xadvance": 1.4064000129699707e+01 1577 | }, 1578 | { 1579 | "x0": 468, 1580 | "y0": 28, 1581 | "x1": 481, 1582 | "y1": 41, 1583 | "xoff": 0.0e+00, 1584 | "yoff": -1.2e+01, 1585 | "xadvance": 1.2696000099182129e+01 1586 | }, 1587 | { 1588 | "x0": 482, 1589 | "y0": 28, 1590 | "x1": 495, 1591 | "y1": 47, 1592 | "xoff": 0.0e+00, 1593 | "yoff": -1.2e+01, 1594 | "xadvance": 1.3704000473022461e+01 1595 | }, 1596 | { 1597 | "x0": 496, 1598 | "y0": 28, 1599 | "x1": 510, 1600 | "y1": 47, 1601 | "xoff": 0.0e+00, 1602 | "yoff": -1.2e+01, 1603 | "xadvance": 1.3656000137329102e+01 1604 | }, 1605 | { 1606 | "x0": 1, 1607 | "y0": 54, 1608 | "x1": 11, 1609 | "y1": 67, 1610 | "xoff": 0.0e+00, 1611 | "yoff": -1.2e+01, 1612 | "xadvance": 1.0199999809265137e+01 1613 | }, 1614 | { 1615 | "x0": 12, 1616 | "y0": 54, 1617 | "x1": 22, 1618 | "y1": 67, 1619 | "xoff": 0.0e+00, 1620 | "yoff": -1.2e+01, 1621 | "xadvance": 9.815999984741211e+00 1622 | }, 1623 | { 1624 | "x0": 23, 1625 | "y0": 54, 1626 | "x1": 33, 1627 | "y1": 69, 1628 | "xoff": 0.0e+00, 1629 | "yoff": -1.4e+01, 1630 | "xadvance": 1.0032000541687012e+01 1631 | }, 1632 | { 1633 | "x0": 34, 1634 | "y0": 54, 1635 | "x1": 46, 1636 | "y1": 67, 1637 | "xoff": 0.0e+00, 1638 | "yoff": -1.2e+01, 1639 | "xadvance": 1.2432000160217285e+01 1640 | }, 1641 | { 1642 | "x0": 47, 1643 | "y0": 54, 1644 | "x1": 61, 1645 | "y1": 67, 1646 | "xoff": 0.0e+00, 1647 | "yoff": -1.2e+01, 1648 | "xadvance": 1.4303999900817871e+01 1649 | }, 1650 | { 1651 | "x0": 62, 1652 | "y0": 54, 1653 | "x1": 83, 1654 | "y1": 67, 1655 | "xoff": 0.0e+00, 1656 | "yoff": -1.2e+01, 1657 | "xadvance": 2.1624000549316406e+01 1658 | }, 1659 | { 1660 | "x0": 84, 1661 | "y0": 54, 1662 | "x1": 97, 1663 | "y1": 66, 1664 | "xoff": 0.0e+00, 1665 | "yoff": -1.2e+01, 1666 | "xadvance": 1.3104000091552734e+01 1667 | }, 1668 | { 1669 | "x0": 98, 1670 | "y0": 54, 1671 | "x1": 113, 1672 | "y1": 72, 1673 | "xoff": 0.0e+00, 1674 | "yoff": -1.2e+01, 1675 | "xadvance": 1.4904000282287598e+01 1676 | }, 1677 | { 1678 | "x0": 114, 1679 | "y0": 54, 1680 | "x1": 126, 1681 | "y1": 67, 1682 | "xoff": 0.0e+00, 1683 | "yoff": -1.2e+01, 1684 | "xadvance": 1.2239999771118164e+01 1685 | }, 1686 | { 1687 | "x0": 127, 1688 | "y0": 54, 1689 | "x1": 127, 1690 | "y1": 54, 1691 | "xoff": 0.0e+00, 1692 | "yoff": 0.0e+00, 1693 | "xadvance": 1.2e+01 1694 | }, 1695 | { 1696 | "x0": 128, 1697 | "y0": 54, 1698 | "x1": 128, 1699 | "y1": 54, 1700 | "xoff": 0.0e+00, 1701 | "yoff": 0.0e+00, 1702 | "xadvance": 1.2e+01 1703 | }, 1704 | { 1705 | "x0": 129, 1706 | "y0": 54, 1707 | "x1": 129, 1708 | "y1": 54, 1709 | "xoff": 0.0e+00, 1710 | "yoff": 0.0e+00, 1711 | "xadvance": 1.2e+01 1712 | }, 1713 | { 1714 | "x0": 130, 1715 | "y0": 54, 1716 | "x1": 141, 1717 | "y1": 60, 1718 | "xoff": 1.0e+00, 1719 | "yoff": -8.0e+00, 1720 | "xadvance": 1.2720000267028809e+01 1721 | }, 1722 | { 1723 | "x0": 142, 1724 | "y0": 54, 1725 | "x1": 142, 1726 | "y1": 54, 1727 | "xoff": 0.0e+00, 1728 | "yoff": 0.0e+00, 1729 | "xadvance": 1.2e+01 1730 | }, 1731 | { 1732 | "x0": 1, 1733 | "y0": 1, 1734 | "x1": 1, 1735 | "y1": 1, 1736 | "xoff": 0.0e+00, 1737 | "yoff": 0.0e+00, 1738 | "xadvance": 4.724409580230713e+00 1739 | }, 1740 | { 1741 | "x0": 2, 1742 | "y0": 1, 1743 | "x1": 5, 1744 | "y1": 15, 1745 | "xoff": 2.0e+00, 1746 | "yoff": -1.3e+01, 1747 | "xadvance": 6.141732215881348e+00 1748 | }, 1749 | { 1750 | "x0": 6, 1751 | "y0": 1, 1752 | "x1": 13, 1753 | "y1": 7, 1754 | "xoff": 0.0e+00, 1755 | "yoff": -1.4e+01, 1756 | "xadvance": 7.3511810302734375e+00 1757 | }, 1758 | { 1759 | "x0": 14, 1760 | "y0": 1, 1761 | "x1": 23, 1762 | "y1": 14, 1763 | "xoff": 1.0e+00, 1764 | "yoff": -1.2e+01, 1765 | "xadvance": 1.0431495666503906e+01 1766 | }, 1767 | { 1768 | "x0": 24, 1769 | "y0": 1, 1770 | "x1": 32, 1771 | "y1": 16, 1772 | "xoff": 0.0e+00, 1773 | "yoff": -1.3e+01, 1774 | "xadvance": 9.108661651611328e+00 1775 | }, 1776 | { 1777 | "x0": 33, 1778 | "y0": 1, 1779 | "x1": 48, 1780 | "y1": 16, 1781 | "xoff": 0.0e+00, 1782 | "yoff": -1.2e+01, 1783 | "xadvance": 1.5496063232421875e+01 1784 | }, 1785 | { 1786 | "x0": 49, 1787 | "y0": 1, 1788 | "x1": 65, 1789 | "y1": 16, 1790 | "xoff": 0.0e+00, 1791 | "yoff": -1.4e+01, 1792 | "xadvance": 1.5647244453430176e+01 1793 | }, 1794 | { 1795 | "x0": 66, 1796 | "y0": 1, 1797 | "x1": 70, 1798 | "y1": 7, 1799 | "xoff": 0.0e+00, 1800 | "yoff": -1.4e+01, 1801 | "xadvance": 4.2330708503723145e+00 1802 | }, 1803 | { 1804 | "x0": 71, 1805 | "y0": 1, 1806 | "x1": 78, 1807 | "y1": 19, 1808 | "xoff": 0.0e+00, 1809 | "yoff": -1.4e+01, 1810 | "xadvance": 6.859842300415039e+00 1811 | }, 1812 | { 1813 | "x0": 79, 1814 | "y0": 1, 1815 | "x1": 86, 1816 | "y1": 19, 1817 | "xoff": 0.0e+00, 1818 | "yoff": -1.4e+01, 1819 | "xadvance": 6.859842300415039e+00 1820 | }, 1821 | { 1822 | "x0": 87, 1823 | "y0": 1, 1824 | "x1": 94, 1825 | "y1": 8, 1826 | "xoff": 0.0e+00, 1827 | "yoff": -1.4e+01, 1828 | "xadvance": 6.3307085037231445e+00 1829 | }, 1830 | { 1831 | "x0": 95, 1832 | "y0": 1, 1833 | "x1": 107, 1834 | "y1": 13, 1835 | "xoff": 1.0e+00, 1836 | "yoff": -1.1e+01, 1837 | "xadvance": 1.3247243881225586e+01 1838 | }, 1839 | { 1840 | "x0": 108, 1841 | "y0": 1, 1842 | "x1": 112, 1843 | "y1": 6, 1844 | "xoff": 0.0e+00, 1845 | "yoff": -2.0e+00, 1846 | "xadvance": 4.289763927459717e+00 1847 | }, 1848 | { 1849 | "x0": 113, 1850 | "y0": 1, 1851 | "x1": 118, 1852 | "y1": 6, 1853 | "xoff": 0.0e+00, 1854 | "yoff": -7.0e+00, 1855 | "xadvance": 4.289763927459717e+00 1856 | }, 1857 | { 1858 | "x0": 119, 1859 | "y0": 1, 1860 | "x1": 122, 1861 | "y1": 4, 1862 | "xoff": 1.0e+00, 1863 | "yoff": -2.0e+00, 1864 | "xadvance": 4.289763927459717e+00 1865 | }, 1866 | { 1867 | "x0": 123, 1868 | "y0": 1, 1869 | "x1": 132, 1870 | "y1": 16, 1871 | "xoff": 0.0e+00, 1872 | "yoff": -1.1e+01, 1873 | "xadvance": 8.22047233581543e+00 1874 | }, 1875 | { 1876 | "x0": 133, 1877 | "y0": 1, 1878 | "x1": 142, 1879 | "y1": 11, 1880 | "xoff": 0.0e+00, 1881 | "yoff": -9.0e+00, 1882 | "xadvance": 8.976377487182617e+00 1883 | }, 1884 | { 1885 | "x0": 143, 1886 | "y0": 1, 1887 | "x1": 150, 1888 | "y1": 11, 1889 | "xoff": 0.0e+00, 1890 | "yoff": -9.0e+00, 1891 | "xadvance": 6.7275590896606445e+00 1892 | }, 1893 | { 1894 | "x0": 151, 1895 | "y0": 1, 1896 | "x1": 159, 1897 | "y1": 11, 1898 | "xoff": 0.0e+00, 1899 | "yoff": -9.0e+00, 1900 | "xadvance": 8.503936767578125e+00 1901 | }, 1902 | { 1903 | "x0": 160, 1904 | "y0": 1, 1905 | "x1": 167, 1906 | "y1": 14, 1907 | "xoff": 0.0e+00, 1908 | "yoff": -9.0e+00, 1909 | "xadvance": 6.803149700164795e+00 1910 | }, 1911 | { 1912 | "x0": 168, 1913 | "y0": 1, 1914 | "x1": 176, 1915 | "y1": 14, 1916 | "xoff": 0.0e+00, 1917 | "yoff": -9.0e+00, 1918 | "xadvance": 8.447243690490723e+00 1919 | }, 1920 | { 1921 | "x0": 177, 1922 | "y0": 1, 1923 | "x1": 184, 1924 | "y1": 14, 1925 | "xoff": 0.0e+00, 1926 | "yoff": -9.0e+00, 1927 | "xadvance": 7.199999809265137e+00 1928 | }, 1929 | { 1930 | "x0": 185, 1931 | "y0": 1, 1932 | "x1": 194, 1933 | "y1": 14, 1934 | "xoff": 0.0e+00, 1935 | "yoff": -1.2e+01, 1936 | "xadvance": 9.127558708190918e+00 1937 | }, 1938 | { 1939 | "x0": 195, 1940 | "y0": 1, 1941 | "x1": 204, 1942 | "y1": 15, 1943 | "xoff": 0.0e+00, 1944 | "yoff": -9.0e+00, 1945 | "xadvance": 8.503936767578125e+00 1946 | }, 1947 | { 1948 | "x0": 205, 1949 | "y0": 1, 1950 | "x1": 214, 1951 | "y1": 15, 1952 | "xoff": 0.0e+00, 1953 | "yoff": -1.3e+01, 1954 | "xadvance": 8.447243690490723e+00 1955 | }, 1956 | { 1957 | "x0": 215, 1958 | "y0": 1, 1959 | "x1": 224, 1960 | "y1": 14, 1961 | "xoff": 0.0e+00, 1962 | "yoff": -9.0e+00, 1963 | "xadvance": 9.618897438049316e+00 1964 | }, 1965 | { 1966 | "x0": 225, 1967 | "y0": 1, 1968 | "x1": 228, 1969 | "y1": 10, 1970 | "xoff": 1.0e+00, 1971 | "yoff": -8.0e+00, 1972 | "xadvance": 4.308661460876465e+00 1973 | }, 1974 | { 1975 | "x0": 229, 1976 | "y0": 1, 1977 | "x1": 233, 1978 | "y1": 12, 1979 | "xoff": 1.0e+00, 1980 | "yoff": -8.0e+00, 1981 | "xadvance": 4.289763927459717e+00 1982 | }, 1983 | { 1984 | "x0": 234, 1985 | "y0": 1, 1986 | "x1": 246, 1987 | "y1": 10, 1988 | "xoff": 0.0e+00, 1989 | "yoff": -9.0e+00, 1990 | "xadvance": 1.2699213027954102e+01 1991 | }, 1992 | { 1993 | "x0": 247, 1994 | "y0": 1, 1995 | "x1": 259, 1996 | "y1": 6, 1997 | "xoff": 1.0e+00, 1998 | "yoff": -7.0e+00, 1999 | "xadvance": 1.3247243881225586e+01 2000 | }, 2001 | { 2002 | "x0": 260, 2003 | "y0": 1, 2004 | "x1": 272, 2005 | "y1": 10, 2006 | "xoff": 0.0e+00, 2007 | "yoff": -9.0e+00, 2008 | "xadvance": 1.2699213027954102e+01 2009 | }, 2010 | { 2011 | "x0": 273, 2012 | "y0": 1, 2013 | "x1": 280, 2014 | "y1": 16, 2015 | "xoff": 0.0e+00, 2016 | "yoff": -1.4e+01, 2017 | "xadvance": 7.181102275848389e+00 2018 | }, 2019 | { 2020 | "x0": 281, 2021 | "y0": 1, 2022 | "x1": 297, 2023 | "y1": 17, 2024 | "xoff": 1.0e+00, 2025 | "yoff": -1.2e+01, 2026 | "xadvance": 1.889763832092285e+01 2027 | }, 2028 | { 2029 | "x0": 298, 2030 | "y0": 1, 2031 | "x1": 314, 2032 | "y1": 16, 2033 | "xoff": 0.0e+00, 2034 | "yoff": -1.4e+01, 2035 | "xadvance": 1.5514960289001465e+01 2036 | }, 2037 | { 2038 | "x0": 315, 2039 | "y0": 1, 2040 | "x1": 326, 2041 | "y1": 16, 2042 | "xoff": 0.0e+00, 2043 | "yoff": -1.4e+01, 2044 | "xadvance": 1.0658267974853516e+01 2045 | }, 2046 | { 2047 | "x0": 327, 2048 | "y0": 1, 2049 | "x1": 341, 2050 | "y1": 16, 2051 | "xoff": 0.0e+00, 2052 | "yoff": -1.4e+01, 2053 | "xadvance": 1.3228346824645996e+01 2054 | }, 2055 | { 2056 | "x0": 342, 2057 | "y0": 1, 2058 | "x1": 356, 2059 | "y1": 14, 2060 | "xoff": 0.0e+00, 2061 | "yoff": -1.3e+01, 2062 | "xadvance": 1.3398425102233887e+01 2063 | }, 2064 | { 2065 | "x0": 357, 2066 | "y0": 1, 2067 | "x1": 368, 2068 | "y1": 16, 2069 | "xoff": 0.0e+00, 2070 | "yoff": -1.4e+01, 2071 | "xadvance": 1.0922834396362305e+01 2072 | }, 2073 | { 2074 | "x0": 369, 2075 | "y0": 1, 2076 | "x1": 379, 2077 | "y1": 16, 2078 | "xoff": 0.0e+00, 2079 | "yoff": -1.4e+01, 2080 | "xadvance": 1.022362232208252e+01 2081 | }, 2082 | { 2083 | "x0": 380, 2084 | "y0": 1, 2085 | "x1": 393, 2086 | "y1": 16, 2087 | "xoff": 0.0e+00, 2088 | "yoff": -1.4e+01, 2089 | "xadvance": 1.3058267593383789e+01 2090 | }, 2091 | { 2092 | "x0": 394, 2093 | "y0": 1, 2094 | "x1": 409, 2095 | "y1": 16, 2096 | "xoff": 0.0e+00, 2097 | "yoff": -1.4e+01, 2098 | "xadvance": 1.4399999618530273e+01 2099 | }, 2100 | { 2101 | "x0": 410, 2102 | "y0": 1, 2103 | "x1": 416, 2104 | "y1": 15, 2105 | "xoff": 0.0e+00, 2106 | "yoff": -1.3e+01, 2107 | "xadvance": 6.103937149047852e+00 2108 | }, 2109 | { 2110 | "x0": 417, 2111 | "y0": 1, 2112 | "x1": 425, 2113 | "y1": 19, 2114 | "xoff": -2.0e+00, 2115 | "yoff": -1.3e+01, 2116 | "xadvance": 5.971653461456299e+00 2117 | }, 2118 | { 2119 | "x0": 426, 2120 | "y0": 1, 2121 | "x1": 439, 2122 | "y1": 16, 2123 | "xoff": 0.0e+00, 2124 | "yoff": -1.4e+01, 2125 | "xadvance": 1.2359055519104004e+01 2126 | }, 2127 | { 2128 | "x0": 440, 2129 | "y0": 1, 2130 | "x1": 451, 2131 | "y1": 15, 2132 | "xoff": 0.0e+00, 2133 | "yoff": -1.3e+01, 2134 | "xadvance": 1.063936996459961e+01 2135 | }, 2136 | { 2137 | "x0": 452, 2138 | "y0": 1, 2139 | "x1": 471, 2140 | "y1": 15, 2141 | "xoff": 0.0e+00, 2142 | "yoff": -1.3e+01, 2143 | "xadvance": 1.885984230041504e+01 2144 | }, 2145 | { 2146 | "x0": 472, 2147 | "y0": 1, 2148 | "x1": 487, 2149 | "y1": 15, 2150 | "xoff": 0.0e+00, 2151 | "yoff": -1.3e+01, 2152 | "xadvance": 1.5099212646484375e+01 2153 | }, 2154 | { 2155 | "x0": 488, 2156 | "y0": 1, 2157 | "x1": 503, 2158 | "y1": 16, 2159 | "xoff": 0.0e+00, 2160 | "yoff": -1.4e+01, 2161 | "xadvance": 1.4702362060546875e+01 2162 | }, 2163 | { 2164 | "x0": 1, 2165 | "y0": 20, 2166 | "x1": 12, 2167 | "y1": 35, 2168 | "xoff": 0.0e+00, 2169 | "yoff": -1.4e+01, 2170 | "xadvance": 1.0809449195861816e+01 2171 | }, 2172 | { 2173 | "x0": 13, 2174 | "y0": 20, 2175 | "x1": 29, 2176 | "y1": 38, 2177 | "xoff": 0.0e+00, 2178 | "yoff": -1.4e+01, 2179 | "xadvance": 1.4211023330688477e+01 2180 | }, 2181 | { 2182 | "x0": 30, 2183 | "y0": 20, 2184 | "x1": 44, 2185 | "y1": 34, 2186 | "xoff": 0.0e+00, 2187 | "yoff": -1.3e+01, 2188 | "xadvance": 1.3152755737304688e+01 2189 | }, 2190 | { 2191 | "x0": 45, 2192 | "y0": 20, 2193 | "x1": 54, 2194 | "y1": 34, 2195 | "xoff": 0.0e+00, 2196 | "yoff": -1.3e+01, 2197 | "xadvance": 9.297637939453125e+00 2198 | }, 2199 | { 2200 | "x0": 55, 2201 | "y0": 20, 2202 | "x1": 69, 2203 | "y1": 35, 2204 | "xoff": 0.0e+00, 2205 | "yoff": -1.4e+01, 2206 | "xadvance": 1.3077165603637695e+01 2207 | }, 2208 | { 2209 | "x0": 70, 2210 | "y0": 20, 2211 | "x1": 85, 2212 | "y1": 35, 2213 | "xoff": 0.0e+00, 2214 | "yoff": -1.4e+01, 2215 | "xadvance": 1.4286614418029785e+01 2216 | }, 2217 | { 2218 | "x0": 86, 2219 | "y0": 20, 2220 | "x1": 102, 2221 | "y1": 35, 2222 | "xoff": 0.0e+00, 2223 | "yoff": -1.4e+01, 2224 | "xadvance": 1.5685039520263672e+01 2225 | }, 2226 | { 2227 | "x0": 103, 2228 | "y0": 20, 2229 | "x1": 122, 2230 | "y1": 35, 2231 | "xoff": 0.0e+00, 2232 | "yoff": -1.4e+01, 2233 | "xadvance": 1.893543243408203e+01 2234 | }, 2235 | { 2236 | "x0": 123, 2237 | "y0": 20, 2238 | "x1": 136, 2239 | "y1": 35, 2240 | "xoff": 0.0e+00, 2241 | "yoff": -1.4e+01, 2242 | "xadvance": 1.256692886352539e+01 2243 | }, 2244 | { 2245 | "x0": 137, 2246 | "y0": 20, 2247 | "x1": 150, 2248 | "y1": 35, 2249 | "xoff": 0.0e+00, 2250 | "yoff": -1.4e+01, 2251 | "xadvance": 1.2604723930358887e+01 2252 | }, 2253 | { 2254 | "x0": 151, 2255 | "y0": 20, 2256 | "x1": 163, 2257 | "y1": 34, 2258 | "xoff": 0.0e+00, 2259 | "yoff": -1.4e+01, 2260 | "xadvance": 1.1886613845825195e+01 2261 | }, 2262 | { 2263 | "x0": 164, 2264 | "y0": 20, 2265 | "x1": 171, 2266 | "y1": 40, 2267 | "xoff": 0.0e+00, 2268 | "yoff": -1.5e+01, 2269 | "xadvance": 7.143307209014893e+00 2270 | }, 2271 | { 2272 | "x0": 172, 2273 | "y0": 20, 2274 | "x1": 177, 2275 | "y1": 37, 2276 | "xoff": 0.0e+00, 2277 | "yoff": -1.2e+01, 2278 | "xadvance": 4.440944671630859e+00 2279 | }, 2280 | { 2281 | "x0": 178, 2282 | "y0": 20, 2283 | "x1": 185, 2284 | "y1": 40, 2285 | "xoff": 0.0e+00, 2286 | "yoff": -1.5e+01, 2287 | "xadvance": 7.143307209014893e+00 2288 | }, 2289 | { 2290 | "x0": 186, 2291 | "y0": 20, 2292 | "x1": 194, 2293 | "y1": 26, 2294 | "xoff": 0.0e+00, 2295 | "yoff": -1.4e+01, 2296 | "xadvance": 7.993700981140137e+00 2297 | }, 2298 | { 2299 | "x0": 195, 2300 | "y0": 20, 2301 | "x1": 205, 2302 | "y1": 22, 2303 | "xoff": 0.0e+00, 2304 | "yoff": 0.0e+00, 2305 | "xadvance": 9.959054946899414e+00 2306 | }, 2307 | { 2308 | "x0": 206, 2309 | "y0": 20, 2310 | "x1": 210, 2311 | "y1": 26, 2312 | "xoff": 0.0e+00, 2313 | "yoff": -1.4e+01, 2314 | "xadvance": 4.289763927459717e+00 2315 | }, 2316 | { 2317 | "x0": 211, 2318 | "y0": 20, 2319 | "x1": 219, 2320 | "y1": 30, 2321 | "xoff": 0.0e+00, 2322 | "yoff": -9.0e+00, 2323 | "xadvance": 7.993700981140137e+00 2324 | }, 2325 | { 2326 | "x0": 220, 2327 | "y0": 20, 2328 | "x1": 230, 2329 | "y1": 35, 2330 | "xoff": 0.0e+00, 2331 | "yoff": -1.4e+01, 2332 | "xadvance": 9.18425178527832e+00 2333 | }, 2334 | { 2335 | "x0": 231, 2336 | "y0": 20, 2337 | "x1": 239, 2338 | "y1": 30, 2339 | "xoff": 0.0e+00, 2340 | "yoff": -9.0e+00, 2341 | "xadvance": 7.199999809265137e+00 2342 | }, 2343 | { 2344 | "x0": 240, 2345 | "y0": 20, 2346 | "x1": 250, 2347 | "y1": 35, 2348 | "xoff": 0.0e+00, 2349 | "yoff": -1.4e+01, 2350 | "xadvance": 9.335433006286621e+00 2351 | }, 2352 | { 2353 | "x0": 251, 2354 | "y0": 20, 2355 | "x1": 259, 2356 | "y1": 30, 2357 | "xoff": 0.0e+00, 2358 | "yoff": -9.0e+00, 2359 | "xadvance": 7.729133605957031e+00 2360 | }, 2361 | { 2362 | "x0": 260, 2363 | "y0": 20, 2364 | "x1": 268, 2365 | "y1": 35, 2366 | "xoff": 0.0e+00, 2367 | "yoff": -1.4e+01, 2368 | "xadvance": 5.518110275268555e+00 2369 | }, 2370 | { 2371 | "x0": 269, 2372 | "y0": 20, 2373 | "x1": 278, 2374 | "y1": 34, 2375 | "xoff": 0.0e+00, 2376 | "yoff": -9.0e+00, 2377 | "xadvance": 8.314960479736328e+00 2378 | }, 2379 | { 2380 | "x0": 279, 2381 | "y0": 20, 2382 | "x1": 289, 2383 | "y1": 35, 2384 | "xoff": 0.0e+00, 2385 | "yoff": -1.4e+01, 2386 | "xadvance": 9.713385581970215e+00 2387 | }, 2388 | { 2389 | "x0": 290, 2390 | "y0": 20, 2391 | "x1": 295, 2392 | "y1": 34, 2393 | "xoff": 0.0e+00, 2394 | "yoff": -1.3e+01, 2395 | "xadvance": 4.59212589263916e+00 2396 | }, 2397 | { 2398 | "x0": 296, 2399 | "y0": 20, 2400 | "x1": 301, 2401 | "y1": 38, 2402 | "xoff": -1.0e+00, 2403 | "yoff": -1.3e+01, 2404 | "xadvance": 4.384252071380615e+00 2405 | }, 2406 | { 2407 | "x0": 302, 2408 | "y0": 20, 2409 | "x1": 311, 2410 | "y1": 35, 2411 | "xoff": 0.0e+00, 2412 | "yoff": -1.4e+01, 2413 | "xadvance": 8.579527854919434e+00 2414 | }, 2415 | { 2416 | "x0": 312, 2417 | "y0": 20, 2418 | "x1": 317, 2419 | "y1": 35, 2420 | "xoff": 0.0e+00, 2421 | "yoff": -1.4e+01, 2422 | "xadvance": 4.51653528213501e+00 2423 | }, 2424 | { 2425 | "x0": 318, 2426 | "y0": 20, 2427 | "x1": 332, 2428 | "y1": 30, 2429 | "xoff": 0.0e+00, 2430 | "yoff": -9.0e+00, 2431 | "xadvance": 1.3662992477416992e+01 2432 | }, 2433 | { 2434 | "x0": 333, 2435 | "y0": 20, 2436 | "x1": 343, 2437 | "y1": 30, 2438 | "xoff": 0.0e+00, 2439 | "yoff": -9.0e+00, 2440 | "xadvance": 9.42992115020752e+00 2441 | }, 2442 | { 2443 | "x0": 344, 2444 | "y0": 20, 2445 | "x1": 353, 2446 | "y1": 30, 2447 | "xoff": 0.0e+00, 2448 | "yoff": -9.0e+00, 2449 | "xadvance": 8.825197219848633e+00 2450 | }, 2451 | { 2452 | "x0": 354, 2453 | "y0": 20, 2454 | "x1": 363, 2455 | "y1": 34, 2456 | "xoff": 0.0e+00, 2457 | "yoff": -9.0e+00, 2458 | "xadvance": 9.335433006286621e+00 2459 | }, 2460 | { 2461 | "x0": 364, 2462 | "y0": 20, 2463 | "x1": 374, 2464 | "y1": 34, 2465 | "xoff": 0.0e+00, 2466 | "yoff": -9.0e+00, 2467 | "xadvance": 9.921259880065918e+00 2468 | }, 2469 | { 2470 | "x0": 375, 2471 | "y0": 20, 2472 | "x1": 382, 2473 | "y1": 30, 2474 | "xoff": 0.0e+00, 2475 | "yoff": -9.0e+00, 2476 | "xadvance": 6.765354156494141e+00 2477 | }, 2478 | { 2479 | "x0": 383, 2480 | "y0": 20, 2481 | "x1": 390, 2482 | "y1": 30, 2483 | "xoff": 0.0e+00, 2484 | "yoff": -9.0e+00, 2485 | "xadvance": 6.387401580810547e+00 2486 | }, 2487 | { 2488 | "x0": 391, 2489 | "y0": 20, 2490 | "x1": 398, 2491 | "y1": 31, 2492 | "xoff": 0.0e+00, 2493 | "yoff": -1.0e+01, 2494 | "xadvance": 6.292913436889648e+00 2495 | }, 2496 | { 2497 | "x0": 399, 2498 | "y0": 20, 2499 | "x1": 409, 2500 | "y1": 29, 2501 | "xoff": 0.0e+00, 2502 | "yoff": -8.0e+00, 2503 | "xadvance": 9.543307304382324e+00 2504 | }, 2505 | { 2506 | "x0": 410, 2507 | "y0": 20, 2508 | "x1": 420, 2509 | "y1": 29, 2510 | "xoff": 0.0e+00, 2511 | "yoff": -8.0e+00, 2512 | "xadvance": 9.259842872619629e+00 2513 | }, 2514 | { 2515 | "x0": 421, 2516 | "y0": 20, 2517 | "x1": 436, 2518 | "y1": 29, 2519 | "xoff": 0.0e+00, 2520 | "yoff": -8.0e+00, 2521 | "xadvance": 1.4211023330688477e+01 2522 | }, 2523 | { 2524 | "x0": 437, 2525 | "y0": 20, 2526 | "x1": 446, 2527 | "y1": 29, 2528 | "xoff": 0.0e+00, 2529 | "yoff": -8.0e+00, 2530 | "xadvance": 8.37165355682373e+00 2531 | }, 2532 | { 2533 | "x0": 447, 2534 | "y0": 20, 2535 | "x1": 456, 2536 | "y1": 33, 2537 | "xoff": 0.0e+00, 2538 | "yoff": -8.0e+00, 2539 | "xadvance": 8.844094276428223e+00 2540 | }, 2541 | { 2542 | "x0": 457, 2543 | "y0": 20, 2544 | "x1": 464, 2545 | "y1": 31, 2546 | "xoff": 0.0e+00, 2547 | "yoff": -9.0e+00, 2548 | "xadvance": 6.822047233581543e+00 2549 | }, 2550 | { 2551 | "x0": 465, 2552 | "y0": 20, 2553 | "x1": 469, 2554 | "y1": 38, 2555 | "xoff": 0.0e+00, 2556 | "yoff": -1.4e+01, 2557 | "xadvance": 4.743307113647461e+00 2558 | }, 2559 | { 2560 | "x0": 470, 2561 | "y0": 20, 2562 | "x1": 472, 2563 | "y1": 38, 2564 | "xoff": 0.0e+00, 2565 | "yoff": -1.4e+01, 2566 | "xadvance": 2.891338586807251e+00 2567 | }, 2568 | { 2569 | "x0": 473, 2570 | "y0": 20, 2571 | "x1": 477, 2572 | "y1": 38, 2573 | "xoff": 0.0e+00, 2574 | "yoff": -1.4e+01, 2575 | "xadvance": 4.743307113647461e+00 2576 | }, 2577 | { 2578 | "x0": 478, 2579 | "y0": 20, 2580 | "x1": 487, 2581 | "y1": 23, 2582 | "xoff": 0.0e+00, 2583 | "yoff": -6.0e+00, 2584 | "xadvance": 8.522834777832031e+00 2585 | }, 2586 | { 2587 | "x0": 488, 2588 | "y0": 20, 2589 | "x1": 497, 2590 | "y1": 30, 2591 | "xoff": 0.0e+00, 2592 | "yoff": -1.0e+01, 2593 | "xadvance": 9.448819160461426e+00 2594 | } 2595 | ], 2596 | "fonts_data": [ 2597 | { 2598 | "type_": "debug", 2599 | "start_glyph_index": 0, 2600 | "start_row": 0, 2601 | "num_rows": "12297829382473034410" 2602 | }, 2603 | { 2604 | "type_": "display", 2605 | "start_glyph_index": 96, 2606 | "start_row": 43, 2607 | "num_rows": "12297829382473034410" 2608 | }, 2609 | { 2610 | "type_": "info", 2611 | "start_glyph_index": 192, 2612 | "start_row": 116, 2613 | "num_rows": "12297829382473034410" 2614 | } 2615 | ] 2616 | } -------------------------------------------------------------------------------- /src/embeds/shaders/fragment_texalpha.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | layout(location = 0) out vec4 frag_color; 4 | 5 | in vec4 vert_color; 6 | in vec2 vert_texCoord; 7 | 8 | uniform sampler2D tex; 9 | 10 | void main() 11 | { 12 | vec4 col; 13 | col = texture(tex, vert_texCoord.xy); 14 | // float bw = (vert_color.r + vert_color.g + vert_color.b) / 3; 15 | // frag_color = vec4(bw, bw, bw, col.r*vert_color.a); 16 | frag_color = vec4(vert_color.rgb, col.r*vert_color.a); 17 | } 18 | -------------------------------------------------------------------------------- /src/embeds/shaders/vertex.glsl: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 position; 3 | layout (location = 1) in vec4 in_color; 4 | layout (location = 2) in vec2 in_texCoord; 5 | 6 | out vec4 vert_color; 7 | out vec2 vert_texCoord; 8 | 9 | void main() 10 | { 11 | gl_Position = vec4(position, 1.0); 12 | vert_color = in_color; 13 | vert_texCoord = in_texCoord; 14 | } 15 | -------------------------------------------------------------------------------- /src/embeds/shaders/web_fragment_texalpha.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision mediump float; 3 | 4 | layout(location = 0) out vec4 frag_color; 5 | 6 | in vec4 vert_color; 7 | in vec2 vert_texCoord; 8 | 9 | uniform sampler2D tex; 10 | 11 | void main() 12 | { 13 | vec4 col; 14 | col = texture(tex, vert_texCoord.xy); 15 | // float bw = (vert_color.r + vert_color.g + vert_color.b) / 3; 16 | // frag_color = vec4(bw, bw, bw, col.r*vert_color.a); 17 | // frag_color = vert_color; 18 | frag_color = vec4(vert_color.rgb, vert_color.a*col.x); 19 | } 20 | -------------------------------------------------------------------------------- /src/embeds/shaders/web_vertex.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision mediump float; 3 | layout (location = 0) in vec3 position; 4 | layout (location = 1) in vec4 in_color; 5 | layout (location = 2) in vec2 in_texCoord; 6 | 7 | out vec4 vert_color; 8 | out vec2 vert_texCoord; 9 | 10 | void main() 11 | { 12 | gl_Position = vec4(position, 1.0); 13 | vert_color = in_color; 14 | vert_texCoord = in_texCoord; 15 | } 16 | -------------------------------------------------------------------------------- /src/glyphee.zig: -------------------------------------------------------------------------------- 1 | // File to store all the information about text and things like that one. 2 | const std = @import("std"); 3 | const c = @import("platform.zig"); 4 | const constants = @import("constants.zig"); 5 | 6 | pub const FontType = enum { 7 | /// Font for debug purposes and things. 8 | debug, 9 | /// Font for large headers and such. Harder to read. Legible only at larger sizes. 10 | display, 11 | /// Font for large amounts of text. Easier to read. Legible at small sizes 12 | info, 13 | }; 14 | const DEBUG_FONT_FILE = @embedFile("embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Light.ttf"); 15 | const DISPLAY_FONT_FILE = @embedFile("embeds/fonts/Leander/Leander.ttf"); 16 | const INFO_FONT_FILE = @embedFile("embeds/fonts/Goudy/goudy_bookletter_1911.otf"); 17 | const NUM_FONTS = @typeInfo(FontType).Enum.fields.len; 18 | const FONT_FILES = [NUM_FONTS][:0]const u8{ 19 | DEBUG_FONT_FILE, 20 | DISPLAY_FONT_FILE, 21 | INFO_FONT_FILE, 22 | }; 23 | const DEFAULT_FONT: FontType = .debug; 24 | pub const FONT_TEX_SIZE = 512; 25 | const GLYPH_CAPACITY = 2048; 26 | const FONT_SIZE = 24.0; 27 | 28 | const helpers = @import("helpers.zig"); 29 | const Camera = helpers.Camera; 30 | const Vector2 = helpers.Vector2; 31 | const Vector4_gl = helpers.Vector4_gl; 32 | const BLACK: Vector4_gl = .{ .x = 24.0 / 255.0, .y = 24.0 / 255.0, .z = 24.0 / 255.0, .w = 1.0 }; 33 | const DUMP_TEXT_DATA = false; 34 | const DUMP_TEXT_IMAGE_PATH = "data/fonts/text_atlas.png"; 35 | const DUMP_TEXT_INFO_PATH = "data/fonts/text_info.json"; 36 | const TEXT_IMAGE_DATA = if (!DUMP_TEXT_DATA) @embedFile("embeds/fonts/text_atlas.png") else void; 37 | const TEXT_IMAGE_INFO = if (!DUMP_TEXT_DATA) @embedFile("embeds/fonts/text_info.json") else void; 38 | 39 | comptime { 40 | if (constants.WEB_BUILD and DUMP_TEXT_DATA) { 41 | @compileError("cannot dump text data in web build\n"); 42 | } 43 | } 44 | 45 | const GlyphData = struct { 46 | const Self = @This(); 47 | glyphs: [96 * NUM_FONTS]c.stbtt_bakedchar = undefined, 48 | 49 | pub fn json_serialize(self: *const Self, js: *helpers.JsonSerializer) !void { 50 | try js.beginArray(); 51 | for (self.glyphs) |glyph| { 52 | try js.arrayElem(); 53 | try js.beginObject(); 54 | try js.objectField("x0"); 55 | try js.emitNumber(glyph.x0); 56 | try js.objectField("y0"); 57 | try js.emitNumber(glyph.y0); 58 | try js.objectField("x1"); 59 | try js.emitNumber(glyph.x1); 60 | try js.objectField("y1"); 61 | try js.emitNumber(glyph.y1); 62 | try js.objectField("xoff"); 63 | try js.emitNumber(glyph.xoff); 64 | try js.objectField("yoff"); 65 | try js.emitNumber(glyph.yoff); 66 | try js.objectField("xadvance"); 67 | try js.emitNumber(glyph.xadvance); 68 | try js.endObject(); 69 | } 70 | try js.endArray(); 71 | } 72 | 73 | pub fn json_load(self: *Self, js: std.json.Value) void { 74 | for (js.Array.items) |glyph, i| { 75 | self.glyphs[i].x0 = @intCast(u16, glyph.Object.get("x0").?.Integer); 76 | self.glyphs[i].y0 = @intCast(u16, glyph.Object.get("y0").?.Integer); 77 | self.glyphs[i].x1 = @intCast(u16, glyph.Object.get("x1").?.Integer); 78 | self.glyphs[i].y1 = @intCast(u16, glyph.Object.get("y1").?.Integer); 79 | self.glyphs[i].xoff = @floatCast(f32, glyph.Object.get("xoff").?.Float); 80 | self.glyphs[i].yoff = @floatCast(f32, glyph.Object.get("yoff").?.Float); 81 | self.glyphs[i].xadvance = @floatCast(f32, glyph.Object.get("xadvance").?.Float); 82 | } 83 | } 84 | }; 85 | 86 | const FontData = struct { 87 | const Self = @This(); 88 | type_: FontType = DEFAULT_FONT, 89 | start_glyph_index: usize = 0, 90 | start_row: usize = 0, 91 | num_rows: usize = 0, 92 | 93 | pub fn json_serialize(self: *const Self, js: *helpers.JsonSerializer) !void { 94 | try js.beginObject(); 95 | try js.objectField("type_"); 96 | try js.emitString(@tagName(self.type_)); 97 | try js.objectField("start_glyph_index"); 98 | try js.emitNumber(self.start_glyph_index); 99 | try js.objectField("start_row"); 100 | try js.emitNumber(self.start_row); 101 | try js.objectField("num_rows"); 102 | try js.emitNumber(self.num_rows); 103 | try js.endObject(); 104 | } 105 | 106 | pub fn json_load(self: *Self, js: std.json.Value) void { 107 | self.type_ = std.meta.stringToEnum(FontType, js.Object.get("type_").?.String).?; 108 | self.start_glyph_index = @intCast(usize, js.Object.get("start_glyph_index").?.Integer); 109 | self.start_row = @intCast(usize, js.Object.get("start_row").?.Integer); 110 | // self.num_rows = @intCast(usize, js.Object.get("num_rows").?.Integer); 111 | } 112 | }; 113 | 114 | const Glyph = struct { 115 | const Self = @This(); 116 | char: u8, 117 | font: FontType, 118 | color: Vector4_gl, 119 | quad: c.stbtt_aligned_quad, 120 | z: f32, 121 | 122 | pub fn json_serialize(self: *const Self, js: *helpers.JsonSerializer) !void { 123 | try js.beginObject(); 124 | try js.objectField("char"); 125 | try js.emitNumber(self.char); 126 | try js.objectField("font"); 127 | try js.emitString(@tagName(self.font)); 128 | try self.color.json_serialize(js); 129 | try js.objectField("quad"); 130 | try js.beginObject(); 131 | try js.objectField("x0"); 132 | try js.emitNumber(self.quad.x0); 133 | try js.objectField("y0"); 134 | try js.emitNumber(self.quad.y0); 135 | try js.objectField("s0"); 136 | try js.emitNumber(self.quad.s0); 137 | try js.objectField("t0"); 138 | try js.emitNumber(self.quad.t0); 139 | try js.objectField("x1"); 140 | try js.emitNumber(self.quad.x1); 141 | try js.objectField("y1"); 142 | try js.emitNumber(self.quad.y1); 143 | try js.objectField("s1"); 144 | try js.emitNumber(self.quad.s1); 145 | try js.objectField("t1"); 146 | try js.emitNumber(self.quad.t1); 147 | try js.endObject(); 148 | try js.endObject(); 149 | } 150 | 151 | pub fn json_load(self: *Self, js: std.json.Value) void { 152 | self.char = @intCast(u8, js.Object.get("char").?.Integer); 153 | self.font = std.meta.stringToEnum(FontType, js.object.get("font").?.String).?; 154 | self.color.json_load(js.Object.get("color").?); 155 | var quad = js.Object.get("quad").?; 156 | self.quad.x0 = @floatCast(c.GLfloat, quad.Object.get("x0").?.Integer); 157 | self.quad.y0 = @floatCast(c.GLfloat, quad.Object.get("y0").?.Integer); 158 | self.quad.s0 = @floatCast(c.GLfloat, quad.Object.get("s0").?.Integer); 159 | self.quad.t0 = @floatCast(c.GLfloat, quad.Object.get("t0").?.Integer); 160 | self.quad.x1 = @floatCast(c.GLfloat, quad.Object.get("x1").?.Integer); 161 | self.quad.y1 = @floatCast(c.GLfloat, quad.Object.get("y1").?.Integer); 162 | self.quad.s1 = @floatCast(c.GLfloat, quad.Object.get("s1").?.Integer); 163 | self.quad.t1 = @floatCast(c.GLfloat, quad.Object.get("t1").?.Integer); 164 | } 165 | }; 166 | 167 | pub const TypeSetter = struct { 168 | const Self = @This(); 169 | glyph_data: GlyphData = .{}, 170 | texture_data: []u8 = undefined, 171 | allocator: std.mem.Allocator, 172 | glyphs: std.ArrayList(Glyph), 173 | camera: *const Camera, 174 | fonts_data: [NUM_FONTS]FontData = undefined, 175 | 176 | pub fn init(self: *Self, camera: *const Camera, allocator: std.mem.Allocator) !void { 177 | self.allocator = allocator; 178 | self.camera = camera; 179 | self.glyphs = std.ArrayList(Glyph).initCapacity(self.allocator, GLYPH_CAPACITY) catch unreachable; 180 | try self.load_font_data(); 181 | } 182 | 183 | pub fn deinit(self: *Self) void { 184 | self.glyphs.deinit(); 185 | } 186 | 187 | pub fn reset(self: *Self) void { 188 | self.glyphs.shrinkRetainingCapacity(0); 189 | } 190 | 191 | fn load_font_data(self: *Self) !void { 192 | if (DUMP_TEXT_DATA) { 193 | // if (!constants.WEB_BUILD) { 194 | // We load all the fonts into the same texture. 195 | // TODO (29 Jul 2021 sam): See if we should be using different API for text loading. The simple 196 | // one doesn't support multiple fonts easily, and we've had to jump through some hoops. 197 | self.texture_data = try self.allocator.alloc(u8, FONT_TEX_SIZE * FONT_TEX_SIZE); 198 | // @@UnimplementedTrueType 199 | var row: usize = 0; 200 | var glyphs_used: usize = 0; 201 | var i: usize = 0; 202 | while (i < NUM_FONTS) : (i += 1) { 203 | const bitmap_index = row * FONT_TEX_SIZE; 204 | const glyph_index = glyphs_used; 205 | // TODO (09 Nov 2021 sam): Figure out the failure conditions on this. 206 | const num_rows_used = c.stbtt_BakeFontBitmap(FONT_FILES[i], 0, FONT_SIZE, &self.texture_data[bitmap_index], FONT_TEX_SIZE, FONT_TEX_SIZE - @intCast(c_int, row), 32, 96, &self.glyph_data.glyphs[glyph_index]); 207 | self.fonts_data[i].type_ = @intToEnum(FontType, @intCast(u2, i)); 208 | self.fonts_data[i].start_row = row; 209 | self.fonts_data[i].start_glyph_index = glyph_index; 210 | row += @intCast(usize, num_rows_used); 211 | glyphs_used += 96; 212 | } 213 | std.debug.print("dumping text data\n", .{}); 214 | { 215 | const file = try std.fs.cwd().createFile(DUMP_TEXT_IMAGE_PATH, .{}); 216 | defer file.close(); 217 | _ = try file.writeAll(self.texture_data); 218 | } 219 | { 220 | var stream = helpers.JsonStream.new(self.allocator); 221 | defer stream.deinit(); 222 | var jser = stream.serializer(); 223 | jser.whitespace = std.json.StringifyOptions.Whitespace{ .indent = .{ .Space = 2 } }; 224 | var js = &jser; 225 | { 226 | try js.beginObject(); 227 | try js.objectField("glyph_data"); 228 | try self.glyph_data.json_serialize(js); 229 | try js.objectField("fonts_data"); 230 | try js.beginArray(); 231 | for (self.fonts_data) |font| { 232 | try js.arrayElem(); 233 | try font.json_serialize(js); 234 | } 235 | try js.endArray(); 236 | try js.endObject(); 237 | } 238 | stream.save_data_to_file(DUMP_TEXT_INFO_PATH) catch unreachable; 239 | } 240 | } else { 241 | // TODO (12 May 2022 sam): Apparently there is a memory leak caused by this scope... 242 | self.texture_data = try self.allocator.alloc(u8, FONT_TEX_SIZE * FONT_TEX_SIZE); 243 | std.mem.copy(u8, self.texture_data, TEXT_IMAGE_DATA); 244 | var parser = std.json.Parser.init(self.allocator, false); 245 | defer parser.deinit(); 246 | var tree = try parser.parse(TEXT_IMAGE_INFO); 247 | var js = tree.root; 248 | self.glyph_data.json_load(js.Object.get("glyph_data").?); 249 | for (js.Object.get("fonts_data").?.Array.items) |font, i| { 250 | self.fonts_data[i].json_load(font); 251 | } 252 | } 253 | } 254 | 255 | pub fn free_texture_data(self: *Self) void { 256 | self.allocator.free(self.texture_data); 257 | } 258 | 259 | pub fn draw_char_world(self: *Self, pos: Vector2, char: u8) Vector2 { 260 | return self.draw_char(self.camera.world_pos_to_screen(pos), char, self.camera); 261 | } 262 | 263 | pub fn get_char_offset(self: *Self, char: u8) Vector2 { 264 | return self.get_char_offset_font(char, DEFAULT_FONT); 265 | } 266 | 267 | pub fn get_char_offset_font(self: *Self, char: u8, font: FontType) Vector2 { 268 | const glyph = self.get_char_glyph(char, font); 269 | return Vector2{ .x = glyph.xadvance }; 270 | } 271 | 272 | pub fn draw_char(self: *Self, pos: Vector2, char: u8, camera: *const Camera) Vector2 { 273 | return self.draw_char_color(pos, char, 0.9, camera, BLACK); 274 | } 275 | 276 | pub fn draw_char_color(self: *Self, pos: Vector2, char: u8, z: f32, camera: *const Camera, color: Vector4_gl) Vector2 { 277 | return self.draw_char_color_font(pos, char, z, camera, color, DEFAULT_FONT); 278 | } 279 | 280 | pub fn get_char_glyph(self: *Self, char: u8, font: FontType) c.stbtt_bakedchar { 281 | const font_data = self.fonts_data[@enumToInt(font)]; 282 | const char_index = if (char < 32 or char > (32 + 96)) 32 else char; 283 | return self.glyph_data.glyphs[font_data.start_glyph_index + @intCast(usize, char_index) - 32]; 284 | } 285 | 286 | // TODO (07 May 2021 sam): Text also scales with the zoom. We don't want that to be the case. 287 | pub fn draw_char_color_font(self: *Self, pos: Vector2, char: u8, z: f32, camera: *const Camera, color: Vector4_gl, font: FontType) Vector2 { 288 | // TODO (20 Sep 2021 sam): Should we be using/saving camera somewhere here? 289 | _ = camera; 290 | const font_data = self.fonts_data[@enumToInt(font)]; 291 | const glyph = self.get_char_glyph(char, font); 292 | const inv_tex_width = 1.0 / @intToFloat(f32, FONT_TEX_SIZE); 293 | const inv_tex_height = 1.0 / @intToFloat(f32, FONT_TEX_SIZE - font_data.start_row); 294 | const round_x = @floor((pos.x + glyph.xoff) + 0.5); 295 | const round_y = @floor((pos.y + glyph.yoff) + 0.5); 296 | var quad: c.stbtt_aligned_quad = .{ 297 | .x0 = round_x, 298 | .y0 = round_y, 299 | .x1 = round_x + @intToFloat(f32, glyph.x1 - glyph.x0), 300 | .y1 = round_y + @intToFloat(f32, glyph.y1 - glyph.y0), 301 | .s0 = @intToFloat(f32, glyph.x0) * inv_tex_width, 302 | .t0 = @intToFloat(f32, glyph.y0) * inv_tex_height, 303 | .s1 = @intToFloat(f32, glyph.x1) * inv_tex_width, 304 | .t1 = @intToFloat(f32, glyph.y1) * inv_tex_height, 305 | }; 306 | // TODO (29 Jul 2021 sam): This could be cleaner if we understand the stbtt glyph.y values 307 | quad.t0 = helpers.tex_remap(quad.t0, (FONT_TEX_SIZE - font_data.start_row), font_data.start_row); 308 | quad.t1 = helpers.tex_remap(quad.t1, (FONT_TEX_SIZE - font_data.start_row), font_data.start_row); 309 | // These lines fix the size to be constant regardless of the size of the window. 310 | // But the offset of the letters gets affected, so maybe it isn't the best way to do this. 311 | // It should ideally be done a level higher. 312 | // quad.x1 = quad.x0 + ((quad.x1 - quad.x0) / camera.combined_zoom()); 313 | // quad.y1 = quad.y0 + ((quad.y1 - quad.y0) / camera.combined_zoom()); 314 | // return Vector2{ .x = glyph.xadvance / camera.combined_zoom() }; 315 | const new_glyph = Glyph{ 316 | .char = char, 317 | .color = color, 318 | .font = font, 319 | .quad = quad, 320 | .z = z, 321 | }; 322 | self.glyphs.append(new_glyph) catch unreachable; 323 | return Vector2{ .x = glyph.xadvance }; 324 | } 325 | 326 | pub fn draw_text_world(self: *Self, pos: Vector2, text: []const u8) void { 327 | self.draw_text_world_font(pos, text, DEFAULT_FONT); 328 | } 329 | 330 | pub fn draw_text_world_font(self: *Self, pos: Vector2, text: []const u8, font: FontType) void { 331 | self.draw_text_width_font(self.camera.world_pos_to_screen(pos), text, self.camera, 1000000.0, font); 332 | } 333 | 334 | pub fn draw_text_world_font_color(self: *Self, pos: Vector2, text: []const u8, font: FontType, color: Vector4_gl) void { 335 | // TODO (30 Jul 2021 sam): Do we need to do world_pos_to_screen here? 336 | self.draw_text_width_color_font(pos, text, self.camera, 1000000.0, color, font); 337 | } 338 | 339 | pub fn draw_text_world_centered(self: *Self, pos: Vector2, text: []const u8) void { 340 | self.draw_text_world_centered_font(pos, text, DEFAULT_FONT); 341 | } 342 | 343 | pub fn draw_text_world_centered_font(self: *Self, pos: Vector2, text: []const u8, font: FontType) void { 344 | self.draw_text_world_centered_font_color(pos, text, font, BLACK); 345 | } 346 | 347 | pub fn draw_text_world_centered_font_color(self: *Self, pos: Vector2, text: []const u8, font: FontType, color: Vector4_gl) void { 348 | const width = self.get_text_width_font(text, font); 349 | const c_pos = Vector2.subtract(pos, width.scaled(0.5)); 350 | self.draw_text_world_font_color(c_pos, text, font, color); 351 | } 352 | 353 | pub fn draw_text_width_world(self: *Self, pos: Vector2, text: []const u8, width: f32) void { 354 | self.draw_text_width(self.camera.world_pos_to_screen(pos), text, self.camera, width); 355 | } 356 | 357 | pub fn draw_text_width_camera(self: *Self, pos: Vector2, text: []const u8, width: f32, camera: *const Camera) void { 358 | self.draw_text_width(camera.world_pos_to_screen(pos), text, camera, width); 359 | } 360 | 361 | pub fn draw_text_width_world_color(self: *Self, pos: Vector2, text: []const u8, width: f32, color: Vector4_gl) void { 362 | self.draw_text_width_color(self.camera.world_pos_to_screen(pos), text, self.camera, width, color); 363 | } 364 | 365 | pub fn draw_text(self: *Self, pos: Vector2, text: []const u8, camera: *const Camera) void { 366 | self.draw_text_width(pos, text, camera, 1000000.0); 367 | } 368 | 369 | pub fn draw_text_width(self: *Self, pos: Vector2, text: []const u8, camera: *const Camera, width: f32) void { 370 | self.draw_text_width_font(pos, text, camera, width, DEFAULT_FONT); 371 | } 372 | 373 | pub fn draw_text_width_font_world(self: *Self, pos: Vector2, text: []const u8, width: f32, font: FontType) void { 374 | self.draw_text_width_color_font(self.camera.world_pos_to_screen(pos), text, self.camera, width, BLACK, font); 375 | } 376 | 377 | pub fn draw_text_width_font(self: *Self, pos: Vector2, text: []const u8, camera: *const Camera, width: f32, font: FontType) void { 378 | self.draw_text_width_color_font(pos, text, camera, width, BLACK, font); 379 | } 380 | 381 | pub fn draw_text_width_color(self: *Self, pos: Vector2, text: []const u8, camera: *const Camera, width: f32, color: Vector4_gl) void { 382 | self.draw_text_width_color_font(pos, text, camera, width, color, DEFAULT_FONT); 383 | } 384 | 385 | pub fn draw_text_width_color_font(self: *Self, pos: Vector2, text: []const u8, camera: *const Camera, width: f32, color: Vector4_gl, font: FontType) void { 386 | var offsets = Vector2{}; 387 | var new_line = false; 388 | for (text) |char| { 389 | const char_offset = self.draw_char_color_font(Vector2.add(pos, offsets), char, 1, camera, color, font); 390 | offsets = Vector2.add(offsets, char_offset); 391 | if (offsets.x > width) new_line = true; 392 | if (char == '\n' or (new_line and char == ' ')) { 393 | offsets.x = 0.0; 394 | offsets.y += FONT_SIZE * 1.2; 395 | new_line = false; 396 | } 397 | } 398 | } 399 | 400 | pub fn get_text_width(self: *Self, text: []const u8) Vector2 { 401 | return self.get_text_width_font(text, DEFAULT_FONT); 402 | } 403 | 404 | pub fn get_text_width_font(self: *Self, text: []const u8, font: FontType) Vector2 { 405 | var width: f32 = 0.0; 406 | for (text) |char| { 407 | const glyph = self.get_char_glyph(char, font); 408 | width += glyph.xadvance; 409 | } 410 | return Vector2{ .x = width }; 411 | } 412 | 413 | pub fn get_text_offset(self: *Self, text: []const u8, width: f32, camera: *const Camera) Vector2 { 414 | var offsets = Vector2{}; 415 | var new_line = false; 416 | for (text) |char| { 417 | const char_offset = self.get_char_offset(char); 418 | offsets = Vector2.add(offsets, char_offset); 419 | if (offsets.x > width) new_line = true; 420 | if (char == '\n' or (new_line and char == ' ')) { 421 | offsets.x = 0.0; 422 | offsets.y += FONT_SIZE * 1.2; 423 | new_line = false; 424 | } 425 | } 426 | return camera.screen_vec_to_world(offsets); 427 | } 428 | 429 | // TODO (21 Sep 2021 sam): This could maybe be cleaned up a bit. I've just copy pasted another 430 | // function here, and it looks like we are overriding the things in most cases... 431 | pub fn get_text_bounding_box(self: *Self, text: []const u8, width: f32, camera: *const Camera) Vector2 { 432 | var num_lines: f32 = 1; 433 | var offsets = Vector2{}; 434 | var new_line = false; 435 | for (text) |char| { 436 | const char_offset = self.get_char_offset(char); 437 | offsets = Vector2.add(offsets, char_offset); 438 | if (offsets.x > width) new_line = true; 439 | if (char == '\n' or (new_line and char == ' ')) { 440 | offsets.x = 0.0; 441 | new_line = false; 442 | num_lines += 1; 443 | } 444 | } 445 | if (num_lines > 1) offsets.x = width; 446 | offsets.y = FONT_SIZE * 1.2 * num_lines; 447 | return camera.screen_vec_to_world(offsets); 448 | } 449 | }; 450 | -------------------------------------------------------------------------------- /src/helpers.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const c = @import("platform.zig"); 3 | 4 | const constants = @import("constants.zig"); 5 | pub const PI = std.math.pi; 6 | pub const HALF_PI = PI / 2.0; 7 | pub const TWO_PI = PI * 2.0; 8 | 9 | pub const Vector2 = struct { 10 | const Self = @This(); 11 | x: f32 = 0.0, 12 | y: f32 = 0.0, 13 | 14 | pub fn lerp(v1: Vector2, v2: Vector2, t: f32) Vector2 { 15 | return Vector2{ 16 | .x = lerpf(v1.x, v2.x, t), 17 | .y = lerpf(v1.y, v2.y, t), 18 | }; 19 | } 20 | 21 | /// We assume that v lies along the line v1-v2 (can be outside the segment) 22 | /// So we don't check both x and y unlerp. We just return the first one that we find. 23 | pub fn unlerp(v1: Vector2, v2: Vector2, v: Vector2) f32 { 24 | if (v1.x != v2.x) { 25 | return unlerpf(v1.x, v2.x, v.x); 26 | } else if (v1.y != v2.y) { 27 | return unlerpf(v1.y, v2.y, v.y); 28 | } else { 29 | return 0; 30 | } 31 | } 32 | 33 | pub fn ease(v1: Vector2, v2: Vector2, t: f32) Vector2 { 34 | return Vector2{ 35 | .x = easeinoutf(v1.x, v2.x, t), 36 | .y = easeinoutf(v1.y, v2.y, t), 37 | }; 38 | } 39 | 40 | pub fn add(v1: Vector2, v2: Vector2) Vector2 { 41 | return Vector2{ 42 | .x = v1.x + v2.x, 43 | .y = v1.y + v2.y, 44 | }; 45 | } 46 | 47 | pub fn added(v1: *const Vector2, v2: Vector2) Vector2 { 48 | return Vector2.add(v1.*, v2); 49 | } 50 | 51 | pub fn add3(v1: Vector2, v2: Vector2, v3: Vector2) Vector2 { 52 | return Vector2{ 53 | .x = v1.x + v2.x + v3.x, 54 | .y = v1.y + v2.y + v3.y, 55 | }; 56 | } 57 | 58 | pub fn subtract(v1: Vector2, v2: Vector2) Vector2 { 59 | return Vector2{ 60 | .x = v1.x - v2.x, 61 | .y = v1.y - v2.y, 62 | }; 63 | } 64 | 65 | pub fn distance(v1: Vector2, v2: Vector2) f32 { 66 | return @sqrt(((v2.x - v1.x) * (v2.x - v1.x)) + ((v2.y - v1.y) * (v2.y - v1.y))); 67 | } 68 | 69 | pub fn distance_sqr(v1: Vector2, v2: Vector2) f32 { 70 | return ((v2.x - v1.x) * (v2.x - v1.x)) + ((v2.y - v1.y) * (v2.y - v1.y)); 71 | } 72 | 73 | pub fn distance_to_sqr(v1: *const Vector2, v2: Vector2) f32 { 74 | return ((v2.x - v1.x) * (v2.x - v1.x)) + ((v2.y - v1.y) * (v2.y - v1.y)); 75 | } 76 | 77 | pub fn length(v1: Vector2) f32 { 78 | return @sqrt((v1.x * v1.x) + (v1.y * v1.y)); 79 | } 80 | 81 | pub fn length_sqr(v1: Vector2) f32 { 82 | return (v1.x * v1.x) + (v1.y * v1.y); 83 | } 84 | 85 | pub fn scale(v1: Vector2, t: f32) Vector2 { 86 | return Vector2{ 87 | .x = v1.x * t, 88 | .y = v1.y * t, 89 | }; 90 | } 91 | 92 | pub fn scaled(v1: *const Vector2, t: f32) Vector2 { 93 | return Vector2{ 94 | .x = v1.x * t, 95 | .y = v1.y * t, 96 | }; 97 | } 98 | 99 | pub fn scale_anchor(v1: *const Vector2, anchor: Vector2, f: f32) Vector2 { 100 | const translated = Vector2.subtract(v1.*, anchor); 101 | return Vector2.add(anchor, Vector2.scale(translated, f)); 102 | } 103 | 104 | pub fn scale_vec(v1: Vector2, v2: Vector2) Vector2 { 105 | return Vector2{ 106 | .x = v1.x * v2.x, 107 | .y = v1.y * v2.y, 108 | }; 109 | } 110 | 111 | pub fn negated(v1: *const Vector2) Vector2 { 112 | return Vector2{ 113 | .x = -v1.x, 114 | .y = -v1.y, 115 | }; 116 | } 117 | 118 | pub fn subtract_half(v1: Vector2, v2: Vector2) Vector2 { 119 | return Vector2{ 120 | .x = v1.x - (0.5 * v2.x), 121 | .y = v1.y - (0.5 * v2.y), 122 | }; 123 | } 124 | 125 | pub fn normalize(v1: Vector2) Vector2 { 126 | const l = Vector2.length(v1); 127 | return Vector2{ 128 | .x = v1.x / l, 129 | .y = v1.y / l, 130 | }; 131 | } 132 | 133 | /// Gives the clockwise angle in radians from first vector to second vector 134 | /// Assumes vectors are normalized 135 | pub fn angle_cw(v1: Vector2, v2: Vector2) f32 { 136 | std.debug.assert(!v1.is_nan()); 137 | std.debug.assert(!v2.is_nan()); 138 | const dot_product = std.math.clamp(Vector2.dot(v1, v2), -1, 1); 139 | var a = std.math.acos(dot_product); 140 | std.debug.assert(!is_nanf(a)); 141 | const winding = Vector2.cross_z(v1, v2); 142 | std.debug.assert(!is_nanf(winding)); 143 | if (winding < 0) a = TWO_PI - a; 144 | return a; 145 | } 146 | 147 | pub fn dot(v1: Vector2, v2: Vector2) f32 { 148 | std.debug.assert(!is_nanf(v1.x)); 149 | std.debug.assert(!is_nanf(v1.y)); 150 | std.debug.assert(!is_nanf(v2.x)); 151 | std.debug.assert(!is_nanf(v2.y)); 152 | return v1.x * v2.x + v1.y * v2.y; 153 | } 154 | 155 | /// Returns the z element of the 3d cross product of the two vectors. Useful to find the 156 | /// winding of the points 157 | pub fn cross_z(v1: Vector2, v2: Vector2) f32 { 158 | return (v1.x * v2.y) - (v1.y * v2.x); 159 | } 160 | 161 | pub fn equals(v1: Vector2, v2: Vector2) bool { 162 | return v1.x == v2.x and v1.y == v2.y; 163 | } 164 | 165 | pub fn is_equal(v1: *const Vector2, v2: Vector2) bool { 166 | return v1.x == v2.x and v1.y == v2.y; 167 | } 168 | 169 | pub fn reflect(v1: Vector2, surface: Vector2) Vector2 { 170 | // Since we're reflecting off the surface, we first need to find the component 171 | // of v1 that is perpendicular to the surface. We then need to "reverse" that 172 | // component. Or we can just subtract double the negative of that from v1. 173 | // TODO (25 Apr 2021 sam): See if this can be done without normalizing. @@Performance 174 | const n_surf = Vector2.normalize(surface); 175 | const v1_par = Vector2.scale(n_surf, Vector2.dot(v1, n_surf)); 176 | const v1_perp = Vector2.subtract(v1, v1_par); 177 | return Vector2.subtract(v1, Vector2.scale(v1_perp, 2.0)); 178 | } 179 | 180 | pub fn from_int(x: i32, y: i32) Vector2 { 181 | return Vector2{ .x = @intToFloat(f32, x), .y = @intToFloat(f32, y) }; 182 | } 183 | 184 | pub fn from_usize(x: usize, y: usize) Vector2 { 185 | return Vector2{ .x = @intToFloat(f32, x), .y = @intToFloat(f32, y) }; 186 | } 187 | 188 | pub fn rotate(v: Vector2, a: f32) Vector2 { 189 | const cosa = @cos(a); 190 | const sina = @sin(a); 191 | return Vector2{ 192 | .x = (cosa * v.x) - (sina * v.y), 193 | .y = (sina * v.x) + (cosa * v.y), 194 | }; 195 | } 196 | 197 | pub fn rotate_deg(v: Vector2, d: f32) Vector2 { 198 | const a = d * std.math.pi / 180.0; 199 | const cosa = @cos(a); 200 | const sina = @sin(a); 201 | return Vector2{ 202 | .x = (cosa * v.x) - (sina * v.y), 203 | .y = (sina * v.x) + (cosa * v.y), 204 | }; 205 | } 206 | 207 | /// If we have a line v1-v2, where v1 is 0 and v2 is 1, this function 208 | /// returns what value the point p has. It is assumed that p lies along 209 | /// the line. 210 | pub fn get_fraction(v1: Vector2, v2: Vector2, p: Vector2) f32 { 211 | const len = Vector2.distance(v1, v2); 212 | const p_len = Vector2.distance(v1, p); 213 | return p_len / len; 214 | } 215 | 216 | pub fn rotate_about_point(v1: Vector2, anchor: Vector2, a: f32) Vector2 { 217 | const adjusted = Vector2.subtract(v1, anchor); 218 | const rotated = Vector2.rotate(adjusted, a); 219 | return Vector2.add(anchor, rotated); 220 | } 221 | 222 | pub fn rotate_about_point_deg(v1: Vector2, anchor: Vector2, a: f32) Vector2 { 223 | const adjusted = Vector2.subtract(v1, anchor); 224 | const rotated = Vector2.rotate_deg(adjusted, a); 225 | return Vector2.add(anchor, rotated); 226 | } 227 | 228 | pub fn is_zero(v1: *const Vector2) bool { 229 | return v1.x == 0 and v1.y == 0; 230 | } 231 | 232 | pub fn is_nan(v1: *const Vector2) bool { 233 | return is_nanf(v1.x) or is_nanf(v1.y); 234 | } 235 | 236 | pub fn get_perp(v1: Vector2, v2: Vector2) Vector2 { 237 | const line = Vector2.subtract(v2, v1); 238 | const perp = Vector2.normalize(Vector2{ .x = line.y, .y = -line.x }); 239 | return perp; 240 | } 241 | }; 242 | 243 | pub const Vector2i = struct { 244 | x: i32, 245 | y: i32, 246 | }; 247 | 248 | pub const Camera = struct { 249 | const Self = @This(); 250 | size_updated: bool = true, 251 | origin: Vector2 = .{}, 252 | window_size: Vector2 = .{ .x = constants.DEFAULT_WINDOW_WIDTH * constants.DEFAULT_USER_WINDOW_SCALE, .y = constants.DEFAULT_WINDOW_HEIGHT * constants.DEFAULT_USER_WINDOW_SCALE }, 253 | zoom_factor: f32 = 1.0, 254 | window_scale: f32 = constants.DEFAULT_USER_WINDOW_SCALE, 255 | // This is used to store the window scale in case the user goes full screen and wants 256 | // to come back to windowed. 257 | user_window_scale: f32 = constants.DEFAULT_USER_WINDOW_SCALE, 258 | 259 | pub fn world_pos_to_screen(self: *const Self, pos: Vector2) Vector2 { 260 | const tmp1 = Vector2.subtract(pos, self.origin); 261 | // TODO (20 Oct 2021 sam): Why is this zoom_factor? and not combined 262 | return Vector2.scale(tmp1, self.zoom_factor); 263 | } 264 | 265 | pub fn screen_pos_to_world(self: *const Self, pos: Vector2) Vector2 { 266 | // TODO (10 Jun 2021 sam): I wish I knew why this were the case. But I have no clue. Jiggle and 267 | // test method got me here for the most part. 268 | // pos goes from (0,0) to (x,y) where x and y are the actual screen 269 | // sizes. (pixel size on screen as per OS) 270 | // we need to map this to a rect where the 0,0 maps to origin 271 | // and x,y maps to origin + w/zoom*scale 272 | const scaled = Vector2.scale(pos, 1.0 / (self.zoom_factor * self.combined_zoom())); 273 | return Vector2.add(scaled, self.origin); 274 | } 275 | 276 | pub fn render_size(self: *const Self) Vector2 { 277 | // TODO (27 Apr 2021 sam): See whether this causes any performance issues? Is it better to store 278 | // as a member variable, or is it okay to calculate as a method everytime? @@Performance 279 | return Vector2.scale(self.window_size, 1.0 / self.combined_zoom()); 280 | } 281 | 282 | pub fn combined_zoom(self: *const Self) f32 { 283 | return self.zoom_factor * self.window_scale; 284 | } 285 | 286 | pub fn world_size_to_screen(self: *const Self, size: Vector2) Vector2 { 287 | return Vector2.scale(size, self.zoom_factor); 288 | } 289 | 290 | pub fn screen_size_to_world(self: *const Self, size: Vector2) Vector2 { 291 | return Vector2.scale(size, 1.0 / (self.zoom_factor * self.zoom_factor)); 292 | } 293 | 294 | // TODO (10 May 2021 sam): There is some confusion here when we move from screen to world. In some 295 | // cases, we want to maintain the positions for rendering, in which case we need the zoom_factor 296 | // squared. In other cases, we don't need that. This is a little confusing to me, so we need to 297 | // sort it all out properly. 298 | // (02 Jun 2021 sam): I think it has something to do with window_scale and combined_zoom as well. 299 | // In some cases, we want to use zoom factor, in other cases, combined_zoom, and that needs to be 300 | // properly understood as well. 301 | pub fn screen_vec_to_world(self: *const Self, size: Vector2) Vector2 { 302 | return Vector2.scale(size, 1.0 / self.zoom_factor); 303 | } 304 | 305 | pub fn ui_pos_to_world(self: *const Self, pos: Vector2) Vector2 { 306 | const scaled = Vector2.scale(pos, 1.0 / (self.zoom_factor * self.zoom_factor)); 307 | return Vector2.add(scaled, self.origin); 308 | } 309 | 310 | pub fn world_units_to_screen(self: *const Self, unit: f32) f32 { 311 | return unit * self.zoom_factor; 312 | } 313 | 314 | pub fn screen_units_to_world(self: *const Self, unit: f32) f32 { 315 | return unit / self.zoom_factor; 316 | } 317 | }; 318 | 319 | pub const Vector2_gl = extern struct { 320 | x: c.GLfloat = 0.0, 321 | y: c.GLfloat = 0.0, 322 | }; 323 | 324 | pub const Vector3_gl = extern struct { 325 | x: c.GLfloat = 0.0, 326 | y: c.GLfloat = 0.0, 327 | z: c.GLfloat = 0.0, 328 | }; 329 | 330 | pub const Vector4_gl = extern struct { 331 | const Self = @This(); 332 | x: c.GLfloat = 0.0, 333 | y: c.GLfloat = 0.0, 334 | z: c.GLfloat = 0.0, 335 | w: c.GLfloat = 0.0, 336 | 337 | pub fn lerp(v1: Vector4_gl, v2: Vector4_gl, t: f32) Vector4_gl { 338 | return Vector4_gl{ 339 | .x = lerpf(v1.x, v2.x, t), 340 | .y = lerpf(v1.y, v2.y, t), 341 | .z = lerpf(v1.z, v2.z, t), 342 | .w = lerpf(v1.w, v2.w, t), 343 | }; 344 | } 345 | 346 | pub fn equals(v1: Vector4_gl, v2: Vector4_gl) bool { 347 | return v1.x == v2.x and v1.y == v2.y and v1.z == v2.z and v1.w == v2.w; 348 | } 349 | 350 | /// Returns black and white version of the color 351 | pub fn bw(v1: *const Vector4_gl) Vector4_gl { 352 | const col = (v1.x + v1.y + v1.z) / 3.0; 353 | return Vector4_gl{ 354 | .x = col, 355 | .y = col, 356 | .z = col, 357 | .w = v1.w, 358 | }; 359 | } 360 | 361 | pub fn with_alpha(v1: *const Vector4_gl, a: f32) Vector4_gl { 362 | return Vector4_gl{ .x = v1.x, .y = v1.y, .z = v1.z, .w = a }; 363 | } 364 | 365 | pub fn is_equal_to(v1: *const Vector4_gl, v2: Vector4_gl) bool { 366 | return Vector4_gl.equals(v1.*, v2); 367 | } 368 | 369 | pub fn json_serialize(self: *const Self, js: *JsonSerializer) !void { 370 | try js.beginObject(); 371 | try js.objectField("x"); 372 | try js.emitNumber(self.x); 373 | try js.objectField("y"); 374 | try js.emitNumber(self.y); 375 | try js.objectField("z"); 376 | try js.emitNumber(self.z); 377 | try js.objectField("w"); 378 | try js.emitNumber(self.w); 379 | try js.endObject(); 380 | } 381 | 382 | pub fn json_load(self: *Self, js: std.json.Value) void { 383 | self.x = @floatCast(f32, js.Object.get("x").?.Float); 384 | self.y = @floatCast(f32, js.Object.get("y").?.Float); 385 | self.z = @floatCast(f32, js.Object.get("z").?.Float); 386 | self.w = @floatCast(f32, js.Object.get("w").?.Float); 387 | } 388 | }; 389 | 390 | pub fn lerpf(start: f32, end: f32, t: f32) f32 { 391 | return (start * (1.0 - t)) + (end * t); 392 | } 393 | 394 | pub fn unlerpf(start: f32, end: f32, t: f32) f32 { 395 | // TODO (09 Jun 2021 sam): This should work even if start > end 396 | if (end == t) return 1.0; 397 | return (t - start) / (end - start); 398 | } 399 | 400 | pub fn is_nanf(f: f32) bool { 401 | return f != f; 402 | } 403 | 404 | pub fn easeinoutf(start: f32, end: f32, t: f32) f32 { 405 | // Bezier Blend as per StackOverflow : https://stackoverflow.com/a/25730573/5453127 406 | // t goes between 0 and 1. 407 | const x = t * t * (3.0 - (2.0 * t)); 408 | return start + ((end - start) * x); 409 | } 410 | 411 | pub const SingleInput = struct { 412 | is_down: bool = false, 413 | is_clicked: bool = false, // For one frame when key is pressed 414 | is_released: bool = false, // For one frame when key is released 415 | down_from: u32 = 0, 416 | 417 | pub fn reset(self: *SingleInput) void { 418 | self.is_clicked = false; 419 | self.is_released = false; 420 | } 421 | 422 | pub fn set_down(self: *SingleInput, ticks: u32) void { 423 | self.is_down = true; 424 | self.is_clicked = true; 425 | self.down_from = ticks; 426 | } 427 | 428 | pub fn set_release(self: *SingleInput) void { 429 | self.is_down = false; 430 | self.is_released = true; 431 | } 432 | }; 433 | 434 | pub const MouseButton = enum { 435 | left, 436 | right, 437 | middle, 438 | 439 | pub fn from_js(b: i32) MouseButton { 440 | return switch (b) { 441 | 0 => .left, 442 | 1 => .middle, 443 | 2 => .right, 444 | else => .left, 445 | }; 446 | } 447 | }; 448 | 449 | pub const MouseEventType = enum { 450 | button_up, 451 | button_down, 452 | scroll, 453 | movement, 454 | }; 455 | 456 | pub const MouseEvent = union(MouseEventType) { 457 | button_up: MouseButton, 458 | button_down: MouseButton, 459 | scroll: i32, 460 | movement: Vector2i, 461 | }; 462 | 463 | pub const MouseState = struct { 464 | const Self = @This(); 465 | current_pos: Vector2 = .{}, 466 | previous_pos: Vector2 = .{}, 467 | l_down_pos: Vector2 = .{}, 468 | r_down_pos: Vector2 = .{}, 469 | m_down_pos: Vector2 = .{}, 470 | l_button: SingleInput = .{}, 471 | r_button: SingleInput = .{}, 472 | m_button: SingleInput = .{}, 473 | wheel_y: i32 = 0, 474 | 475 | pub fn reset_mouse(self: *Self) void { 476 | self.previous_pos = self.current_pos; 477 | self.l_button.reset(); 478 | self.r_button.reset(); 479 | self.m_button.reset(); 480 | self.wheel_y = 0; 481 | } 482 | 483 | pub fn l_single_pos_click(self: *Self) bool { 484 | if (self.l_button.is_released == false) return false; 485 | if (self.l_down_pos.distance_to_sqr(self.current_pos) == 0) return true; 486 | return false; 487 | } 488 | 489 | pub fn l_moved(self: *Self) bool { 490 | return (self.l_down_pos.distance_to_sqr(self.current_pos) > 0); 491 | } 492 | 493 | pub fn movement(self: *Self) Vector2 { 494 | return Vector2.subtract(self.previous_pos, self.current_pos); 495 | } 496 | 497 | pub fn handle_input(self: *Self, event: c.SDL_Event, ticks: u32, camera: *Camera) void { 498 | switch (event.@"type") { 499 | c.SDL_MOUSEBUTTONDOWN, c.SDL_MOUSEBUTTONUP => { 500 | const button = switch (event.button.button) { 501 | c.SDL_BUTTON_LEFT => &self.l_button, 502 | c.SDL_BUTTON_RIGHT => &self.r_button, 503 | c.SDL_BUTTON_MIDDLE => &self.m_button, 504 | else => &self.l_button, 505 | }; 506 | const pos = switch (event.button.button) { 507 | c.SDL_BUTTON_LEFT => &self.l_down_pos, 508 | c.SDL_BUTTON_RIGHT => &self.r_down_pos, 509 | c.SDL_BUTTON_MIDDLE => &self.m_down_pos, 510 | else => &self.l_down_pos, 511 | }; 512 | if (event.@"type" == c.SDL_MOUSEBUTTONDOWN) { 513 | // This specific line just feels a bit off. I don't intuitively get it yet. 514 | pos.* = self.current_pos; 515 | self.l_down_pos = self.current_pos; 516 | button.is_down = true; 517 | button.is_clicked = true; 518 | button.down_from = ticks; 519 | } 520 | if (event.@"type" == c.SDL_MOUSEBUTTONUP) { 521 | button.is_down = false; 522 | button.is_released = true; 523 | } 524 | }, 525 | c.SDL_MOUSEWHEEL => { 526 | self.wheel_y = event.wheel.y; 527 | }, 528 | c.SDL_MOUSEMOTION => { 529 | self.current_pos = camera.screen_pos_to_world(Vector2.from_int(event.motion.x, event.motion.y)); 530 | }, 531 | else => {}, 532 | } 533 | } 534 | 535 | pub fn web_handle_input(self: *Self, event: MouseEvent, ticks: u32, camera: *Camera) void { 536 | switch (event) { 537 | .button_down, .button_up => |but| { 538 | const button = switch (but) { 539 | .left => &self.l_button, 540 | .right => &self.r_button, 541 | .middle => &self.m_button, 542 | }; 543 | const pos = switch (but) { 544 | .left => &self.l_down_pos, 545 | .right => &self.r_down_pos, 546 | .middle => &self.m_down_pos, 547 | }; 548 | if (event == .button_down) { 549 | // This specific line just feels a bit off. I don't intuitively get it yet. 550 | pos.* = self.current_pos; 551 | self.l_down_pos = self.current_pos; 552 | button.is_down = true; 553 | button.is_clicked = true; 554 | button.down_from = ticks; 555 | } 556 | if (event == .button_up) { 557 | button.is_down = false; 558 | button.is_released = true; 559 | } 560 | }, 561 | .scroll => |amount| { 562 | self.wheel_y = amount; 563 | }, 564 | .movement => |pos| { 565 | self.current_pos = camera.screen_pos_to_world(Vector2.from_int(pos.x, pos.y)); 566 | }, 567 | } 568 | } 569 | }; 570 | 571 | pub const EditableText = struct { 572 | const Self = @This(); 573 | text: std.ArrayList(u8), 574 | is_active: bool = false, 575 | position: Vector2 = .{}, 576 | size: Vector2 = .{ .x = 300 }, 577 | cursor_index: usize = 0, 578 | 579 | pub fn init(allocator: std.mem.Allocator) Self { 580 | return Self{ 581 | .text = std.ArrayList(u8).init(allocator), 582 | }; 583 | } 584 | 585 | pub fn set_text(self: *Self, str: []const u8) void { 586 | self.text.shrinkRetainingCapacity(0); 587 | self.text.appendSlice(str) catch unreachable; 588 | self.cursor_index = str.len; 589 | } 590 | 591 | pub fn deinit(self: *Self) void { 592 | self.text.deinit(); 593 | } 594 | 595 | pub fn handle_inputs(self: *Self, keys: []u8) void { 596 | for (keys) |k| { 597 | switch (k) { 598 | 8 => { 599 | if (self.cursor_index > 0) { 600 | _ = self.text.orderedRemove(self.cursor_index - 1); 601 | self.cursor_index -= 1; 602 | } 603 | }, 604 | 127 => { 605 | if (self.cursor_index < self.text.items.len) { 606 | _ = self.text.orderedRemove(self.cursor_index); 607 | } 608 | }, 609 | 128 => { 610 | if (self.cursor_index > 0) { 611 | self.cursor_index -= 1; 612 | } 613 | }, 614 | 129 => { 615 | if (self.cursor_index < self.text.items.len) { 616 | self.cursor_index += 1; 617 | } 618 | }, 619 | else => { 620 | self.text.insert(self.cursor_index, k) catch unreachable; 621 | self.cursor_index += 1; 622 | }, 623 | } 624 | } 625 | } 626 | }; 627 | 628 | // We load multiple fonts into the same texture, but the API doesn't process that perfectly, 629 | // and treats it as a smaller / narrower texture instead. So we have to wrangle the t0 and t1 630 | // values a little bit. 631 | pub fn tex_remap(y_in: f32, y_height: usize, y_padding: usize) f32 { 632 | const pixel = @floatToInt(usize, y_in * @intToFloat(f32, y_height)); 633 | const total_height = y_height + y_padding; 634 | return @intToFloat(f32, pixel + y_padding) / @intToFloat(f32, total_height); 635 | } 636 | 637 | pub fn debug_print(comptime fmt: []const u8, args: anytype) void { 638 | // TODO (09 May 2022): use some flags here. 639 | if (false) { 640 | std.debug.print(fmt, args); 641 | } 642 | } 643 | 644 | pub const WasmText = extern struct { 645 | text: [*]const u8, 646 | len: u32, 647 | }; 648 | 649 | pub fn handle_text(str: [:0]const u8) if (constants.WEB_BUILD) WasmText else [:0]const u8 { 650 | if (constants.WEB_BUILD) { 651 | return WasmText{ .text = str.ptr, .len = @intCast(u32, str.len) }; 652 | } else { 653 | return str; 654 | } 655 | } 656 | 657 | const JSON_SERIALIZER_MAX_DEPTH = 32; 658 | pub const JsonWriter = std.io.Writer(*JsonStream, JsonStreamError, JsonStream.write); 659 | pub const JsonStreamError = error{JsonWriteError}; 660 | pub const JsonSerializer = std.json.WriteStream(JsonWriter, JSON_SERIALIZER_MAX_DEPTH); 661 | pub const JsonStream = struct { 662 | const Self = @This(); 663 | buffer: std.ArrayList(u8), 664 | 665 | pub fn new(allocator: std.mem.Allocator) Self { 666 | return Self{ 667 | .buffer = std.ArrayList(u8).init(allocator), 668 | }; 669 | } 670 | 671 | pub fn deinit(self: *Self) void { 672 | self.buffer.deinit(); 673 | } 674 | 675 | pub fn writer(self: *Self) JsonWriter { 676 | return .{ .context = self }; 677 | } 678 | 679 | pub fn write(self: *Self, bytes: []const u8) JsonStreamError!usize { 680 | self.buffer.appendSlice(bytes) catch unreachable; 681 | return bytes.len; 682 | } 683 | 684 | pub fn save_data_to_file(self: *Self, filepath: []const u8) !void { 685 | // TODO (08 Dec 2021 sam): See whether we want to add a hash or base64 encoding 686 | const file = try std.fs.cwd().createFile(filepath, .{}); 687 | defer file.close(); 688 | _ = try file.writeAll(self.buffer.items); 689 | if (false) { 690 | debug_print("saving to file {s}\n", .{filepath}); 691 | } 692 | } 693 | 694 | pub fn serializer(self: *Self) JsonSerializer { 695 | return std.json.writeStream(self.writer(), JSON_SERIALIZER_MAX_DEPTH); 696 | } 697 | }; 698 | 699 | // TODO (12 May 2022 sam): Check if std has anything for this? 700 | fn c_strlen(str: [*]const u8) usize { 701 | c.console_log("checking ctrlen"); 702 | c.console_log(str); 703 | var size: usize = 0; 704 | while (true) : (size += 1) { 705 | if (str[size] == 0) break; 706 | } 707 | return size; 708 | } 709 | 710 | /// this reads the file into a buffer alloced by allocator. data to be freed by the 711 | /// caller. 712 | pub fn read_file_contents(path: []const u8, allocator: std.mem.Allocator) ![]u8 { 713 | if (!constants.WEB_BUILD) { 714 | const file = try std.fs.cwd().openFile(path, .{}); 715 | defer file.close(); 716 | const file_size = try file.getEndPos(); 717 | const data = try file.readToEndAlloc(allocator, file_size); 718 | return data; 719 | } else { 720 | const raw_size = c.readWebFileSize(path.ptr); 721 | if (raw_size < 0) { 722 | return error.FileNotFound; 723 | } 724 | const size = @intCast(usize, raw_size); 725 | { 726 | var buffer: [100]u8 = undefined; 727 | const message = std.fmt.bufPrint(&buffer, "contents_size = {d}", .{size}) catch unreachable; 728 | c.consoleLogS(message.ptr, message.len); 729 | } 730 | var data = try allocator.alloc(u8, size + 1); 731 | { 732 | var i: usize = 0; 733 | while (i < size) : (i += 1) { 734 | data[i] = '_'; 735 | } 736 | data[size] = 0; 737 | } 738 | c.consoleLogS(data.ptr, data.len - 1); 739 | const success = c.readWebFile(path.ptr, data.ptr, size); 740 | if (!success) { 741 | // not success. 742 | return error.FileReadFailed; 743 | } 744 | c.console_log(data.ptr); 745 | return data; 746 | } 747 | } 748 | 749 | /// this reads the file into a buffer alloced by allocator. data to be freed by the 750 | /// caller. 751 | /// writable file data is saved in html5 storage on web, 752 | pub fn read_writable_file_contents(path: []const u8, allocator: std.mem.Allocator) ![]u8 { 753 | if (!constants.WEB_BUILD) { 754 | return read_file_contents(path, allocator); 755 | } else { 756 | const raw_size = c.readStorageFileSize(path.ptr); 757 | if (raw_size < 0) { 758 | return error.FileNotFound; 759 | } 760 | const size = @intCast(usize, raw_size); 761 | var data = try allocator.alloc(u8, size + 1); 762 | data[size] = 0; 763 | const success = c.readStorageFile(path.ptr, data.ptr, size); 764 | if (!success) { 765 | // not success. 766 | return error.FileReadFailed; 767 | } 768 | return data; 769 | } 770 | } 771 | 772 | /// writable file data is saved in html5 storage on web, 773 | pub fn write_writable_file_contents(path: []const u8, contents: []const u8) !void { 774 | if (!constants.WEB_BUILD) { 775 | const file = try std.fs.cwd().createFile(path, .{}); 776 | defer file.close(); 777 | _ = try file.writeAll(contents); 778 | } else { 779 | // TODO (16 May 2022 sam): do the error handling? 780 | _ = c.writeStorageFile(path.ptr, contents.ptr); 781 | } 782 | } 783 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const c = @import("platform.zig"); 3 | const App = @import("app.zig").App; 4 | const Renderer = @import("renderer.zig").Renderer; 5 | const helpers = @import("helpers.zig"); 6 | const constants = @import("constants.zig"); 7 | 8 | var app: App = undefined; 9 | var renderer: Renderer = undefined; 10 | var web_allocator = if (constants.WEB_BUILD) std.heap.GeneralPurposeAllocator(.{}){} else void; 11 | var web_start_ticks = @as(i64, 0); 12 | 13 | pub fn main() anyerror!void { 14 | if (constants.WEB_BUILD) return; 15 | // TODO (14 Jul 2021 sam): Figure out how to handle this safety flag. 16 | var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = false }){}; 17 | defer { 18 | _ = gpa.deinit(); 19 | } 20 | if (constants.WEB_BUILD) {} else { 21 | if (c.SDL_Init(c.SDL_INIT_VIDEO | c.SDL_INIT_AUDIO) != 0) { 22 | c.SDL_Log("Unable to initialize SDL: %s", c.SDL_GetError()); 23 | return error.SDLInitializationFailed; 24 | } 25 | defer c.SDL_Quit(); 26 | } 27 | var loading_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 28 | app = App.new(gpa.allocator(), loading_arena.allocator()); 29 | try app.init(); 30 | defer app.deinit(); 31 | renderer = try Renderer.init(&app.typesetter, &app.camera, gpa.allocator(), "typeroo"); 32 | defer renderer.deinit(); 33 | loading_arena.deinit(); 34 | var event: c.SDL_Event = undefined; 35 | const start_ticks = std.time.milliTimestamp(); 36 | while (!app.quit) { 37 | while (c.SDL_PollEvent(&event) != 0) { 38 | switch (event.@"type") { 39 | c.SDL_QUIT => app.quit = true, 40 | else => app.handle_inputs(event), 41 | } 42 | } 43 | var frame_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator); 44 | const ticks = @intCast(u32, std.time.milliTimestamp() - start_ticks); 45 | app.update(ticks, frame_allocator.allocator()); 46 | renderer.render_app(ticks, &app); 47 | frame_allocator.deinit(); 48 | app.end_frame(); 49 | } 50 | } 51 | 52 | export fn web_init() void { 53 | if (!constants.WEB_BUILD) return; 54 | { 55 | const message = "hello from zig"; 56 | c.console_log(message); 57 | } 58 | var loading_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); 59 | c.console_log("reading file data/test.txt"); 60 | const contents = helpers.read_file_contents("data/test2.txt", loading_arena.allocator()) catch "could not find the file to read"; 61 | const saved = helpers.read_writable_file_contents("data/saved.txt", loading_arena.allocator()) catch "there is no saved file present"; 62 | { 63 | var buffer: [100]u8 = undefined; 64 | const m = std.fmt.bufPrint(&buffer, "app was last opened at {d}", .{c.milliTimestamp()}) catch unreachable; 65 | buffer[m.len] = 0; 66 | helpers.write_writable_file_contents("data/saved.txt", buffer[0 .. m.len + 1]) catch unreachable; 67 | } 68 | c.console_log(contents.ptr); 69 | c.console_log(saved.ptr); 70 | app = App.new(web_allocator.allocator(), loading_arena.allocator()); 71 | { 72 | const message = "app new done"; 73 | c.consoleLogS(message, message.len); 74 | } 75 | app.init() catch unreachable; 76 | { 77 | const message = "app init done"; 78 | c.consoleLogS(message, message.len); 79 | } 80 | // defer app.deinit(); 81 | renderer = Renderer.init(&app.typesetter, &app.camera, web_allocator.allocator(), "typeroo") catch unreachable; 82 | { 83 | const message = "renderer init done"; 84 | c.consoleLogS(message, message.len); 85 | } 86 | // defer renderer.deinit(); 87 | web_start_ticks = c.milliTimestamp(); 88 | { 89 | var buffer: [100]u8 = undefined; 90 | const message = std.fmt.bufPrint(&buffer, "web_start = {d}", .{web_start_ticks}) catch unreachable; 91 | c.consoleLogS(message.ptr, message.len); 92 | } 93 | } 94 | 95 | export fn web_render() void { 96 | const ticks = @intCast(u32, c.milliTimestamp() - web_start_ticks); 97 | var frame_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator); 98 | defer frame_allocator.deinit(); 99 | app.update(ticks, frame_allocator.allocator()); 100 | renderer.render_app(ticks, &app); 101 | app.end_frame(); 102 | } 103 | 104 | export fn mouse_motion(x: c_int, y: c_int) void { 105 | const event = helpers.MouseEvent{ 106 | .movement = helpers.Vector2i{ .x = x, .y = y }, 107 | }; 108 | app.inputs.mouse.web_handle_input(event, app.ticks, &app.camera); 109 | } 110 | 111 | export fn mouse_down(button: c_int) void { 112 | if (false) { 113 | var buffer: [100]u8 = undefined; 114 | const message = std.fmt.bufPrint(&buffer, "mouse_down = {d}", .{button}) catch unreachable; 115 | c.consoleLogS(message.ptr, message.len); 116 | } 117 | const event = helpers.MouseEvent{ 118 | .button_down = helpers.MouseButton.from_js(button), 119 | }; 120 | app.inputs.mouse.web_handle_input(event, app.ticks, &app.camera); 121 | } 122 | 123 | export fn mouse_up(button: c_int) void { 124 | if (false) { 125 | var buffer: [100]u8 = undefined; 126 | const message = std.fmt.bufPrint(&buffer, "mouse_up = {d}", .{button}) catch unreachable; 127 | c.consoleLogS(message.ptr, message.len); 128 | } 129 | const event = helpers.MouseEvent{ 130 | .button_up = helpers.MouseButton.from_js(button), 131 | }; 132 | app.inputs.mouse.web_handle_input(event, app.ticks, &app.camera); 133 | } 134 | -------------------------------------------------------------------------------- /src/platform.zig: -------------------------------------------------------------------------------- 1 | pub const web_build = @import("constants.zig").WEB_BUILD; 2 | 3 | pub usingnamespace if (web_build) @import("web.zig") else @import("c.zig"); 4 | -------------------------------------------------------------------------------- /src/renderer.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const constants = @import("constants.zig"); 3 | const c = @import("platform.zig"); 4 | 5 | const glyph_lib = @import("glyphee.zig"); 6 | const TypeSetter = glyph_lib.TypeSetter; 7 | const FONT_TEX_SIZE = glyph_lib.FONT_TEX_SIZE; 8 | const CIRCLE_TEXTURE_SIZE = 512; 9 | 10 | const helpers = @import("helpers.zig"); 11 | const Vector2 = helpers.Vector2; 12 | const Vector2_gl = helpers.Vector2_gl; 13 | const Vector3_gl = helpers.Vector3_gl; 14 | const Vector4_gl = helpers.Vector4_gl; 15 | const Camera = helpers.Camera; 16 | 17 | const App = @import("app.zig").App; 18 | const WEB_BUILD = constants.WEB_BUILD; 19 | 20 | const VERTEX_BASE_FILE: [:0]const u8 = if (WEB_BUILD) @embedFile("embeds/shaders/web_vertex.glsl") else @embedFile("embeds/shaders/vertex.glsl"); 21 | const FRAGMENT_ALPHA_FILE: [:0]const u8 = if (WEB_BUILD) @embedFile("embeds/shaders/web_fragment_texalpha.glsl") else @embedFile("embeds/shaders/fragment_texalpha.glsl"); 22 | 23 | const VertexData = struct { 24 | position: Vector3_gl = .{}, 25 | texCoord: Vector2_gl = .{}, 26 | color: Vector4_gl = .{}, 27 | }; 28 | // TODO (11 May 2022 sam): Do this at comptime. 29 | const NUM_FLOAT_IN_VERTEX_DATA = 9; 30 | 31 | const ShaderData = struct { 32 | const Self = @This(); 33 | has_tris: bool = true, 34 | has_lines: bool = false, 35 | program: c.GLuint = 0, 36 | texture: c.GLuint = 0, 37 | triangle_verts: std.ArrayList(VertexData), 38 | indices: std.ArrayList(c_uint), 39 | 40 | pub fn init(allocator: std.mem.Allocator) Self { 41 | return Self{ 42 | .triangle_verts = std.ArrayList(VertexData).init(allocator), 43 | .indices = std.ArrayList(c_uint).init(allocator), 44 | }; 45 | } 46 | 47 | pub fn deinit(self: *Self) void { 48 | self.triangle_verts.deinit(); 49 | self.indices.deinit(); 50 | } 51 | 52 | pub fn clear_buffers(self: *Self) void { 53 | self.triangle_verts.shrinkRetainingCapacity(0); 54 | self.indices.shrinkRetainingCapacity(0); 55 | } 56 | }; 57 | 58 | pub const Renderer = struct { 59 | const Self = @This(); 60 | window: if (WEB_BUILD) void else *c.SDL_Window, 61 | renderer: if (WEB_BUILD) void else *c.SDL_Renderer, 62 | gl_context: if (WEB_BUILD) void else c.SDL_GLContext, 63 | ticks: u32 = 0, 64 | vao: c.GLuint = 0, 65 | vbo: c.GLuint = 0, 66 | ebo: c.GLuint = 0, 67 | base_shader: ShaderData, 68 | text_shader: ShaderData, 69 | allocator: std.mem.Allocator, 70 | typesetter: *TypeSetter, 71 | camera: *Camera, 72 | z_val: f32 = 0.999, 73 | 74 | pub fn init(typesetter: *TypeSetter, camera: *Camera, allocator: std.mem.Allocator, window_title: []const u8) !Self { 75 | var self: Self = undefined; 76 | if (!WEB_BUILD) { 77 | _ = c.SDL_GL_SetAttribute(c.SDL_GL_MULTISAMPLESAMPLES, 16); 78 | _ = c.SDL_GL_SetAttribute(c.SDL_GL_CONTEXT_PROFILE_MASK, c.SDL_GL_CONTEXT_PROFILE_CORE); 79 | _ = c.SDL_GL_SetAttribute(c.SDL_GL_CONTEXT_MAJOR_VERSION, 3); // OpenGL 3+ 80 | _ = c.SDL_GL_SetAttribute(c.SDL_GL_CONTEXT_MINOR_VERSION, 3); // OpenGL 3.3 81 | const window = c.SDL_CreateWindow(window_title.ptr, c.SDL_WINDOWPOS_CENTERED, c.SDL_WINDOWPOS_CENTERED, @floatToInt(c_int, constants.DEFAULT_WINDOW_WIDTH * camera.window_scale), @floatToInt(c_int, constants.DEFAULT_WINDOW_HEIGHT * camera.window_scale), c.SDL_WINDOW_OPENGL).?; 82 | const gl_context = c.SDL_GL_CreateContext(window); 83 | _ = c.SDL_GL_MakeCurrent(window, gl_context); 84 | _ = c.gladLoadGLLoader(@ptrCast(c.GLADloadproc, &c.SDL_GL_GetProcAddress)); 85 | self = Self{ 86 | .window = window, 87 | .renderer = undefined, 88 | .gl_context = gl_context, 89 | .allocator = allocator, 90 | .base_shader = ShaderData.init(allocator), 91 | .text_shader = ShaderData.init(allocator), 92 | .camera = camera, 93 | .typesetter = typesetter, 94 | }; 95 | } else { 96 | self = Self{ 97 | .renderer = undefined, 98 | .window = {}, 99 | .gl_context = {}, 100 | .allocator = allocator, 101 | .base_shader = ShaderData.init(allocator), 102 | .text_shader = ShaderData.init(allocator), 103 | .camera = camera, 104 | .typesetter = typesetter, 105 | }; 106 | } 107 | try self.init_gl(); 108 | try self.init_main_texture(); 109 | try self.init_text_renderer(); 110 | self.typesetter.free_texture_data(); 111 | return self; 112 | } 113 | 114 | pub fn deinit(self: *Self) void { 115 | self.base_shader.deinit(); 116 | self.text_shader.deinit(); 117 | c.SDL_DestroyWindow(self.window); 118 | } 119 | 120 | fn init_gl(self: *Self) !void { 121 | c.glGenVertexArrays(1, &self.vao); 122 | c.glGenBuffers(1, &self.vbo); 123 | c.glGenBuffers(1, &self.ebo); 124 | c.glBindVertexArray(self.vao); 125 | c.glBindBuffer(c.GL_ARRAY_BUFFER, self.vbo); 126 | // TODO (13 Jun 2021 sam): Figure out where this gets saved. Currently both the vertex types have 127 | // the same attrib pointers, so it's okay for now, but once we have more programs, we would need 128 | // to see where this gets saved, and whether we need more vaos or vbos or whatever. 129 | c.glVertexAttribPointer(0, 3, c.GL_FLOAT, c.GL_FALSE, @sizeOf(VertexData), @intToPtr(*allowzero const anyopaque, @offsetOf(VertexData, "position"))); 130 | c.glEnableVertexAttribArray(0); 131 | c.glVertexAttribPointer(1, 4, c.GL_FLOAT, c.GL_FALSE, @sizeOf(VertexData), @intToPtr(*allowzero const anyopaque, @offsetOf(VertexData, "color"))); 132 | c.glEnableVertexAttribArray(1); 133 | c.glVertexAttribPointer(2, 2, c.GL_FLOAT, c.GL_FALSE, @sizeOf(VertexData), @intToPtr(*allowzero const anyopaque, @offsetOf(VertexData, "texCoord"))); 134 | c.glEnableVertexAttribArray(2); 135 | try self.init_shader_program(VERTEX_BASE_FILE, FRAGMENT_ALPHA_FILE, &self.base_shader); 136 | try self.init_shader_program(VERTEX_BASE_FILE, FRAGMENT_ALPHA_FILE, &self.text_shader); 137 | } 138 | 139 | fn init_shader_program(self: *Self, vertex_src: []const u8, fragment_src: []const u8, shader_prog: *ShaderData) !void { 140 | _ = self; 141 | const fs: ?[*]const u8 = fragment_src.ptr; 142 | const fragment_shader = c.glCreateShader(c.GL_FRAGMENT_SHADER); 143 | if (!WEB_BUILD) { 144 | c.glShaderSource(fragment_shader, 1, &fs, null); 145 | } else { 146 | c.glShaderSource(fragment_shader, 1, fragment_src.ptr, fragment_src.len); 147 | } 148 | c.glCompileShader(fragment_shader); 149 | if (!WEB_BUILD) { 150 | var compile_success: c_int = undefined; 151 | c.glGetShaderiv(fragment_shader, c.GL_COMPILE_STATUS, &compile_success); 152 | if (compile_success == 0) { 153 | helpers.debug_print("Fragment shader compilation failed\n", .{}); 154 | var compile_message: [1024]u8 = undefined; 155 | c.glGetShaderInfoLog(fragment_shader, 1024, null, &compile_message[0]); 156 | helpers.debug_print("{s}\n", .{compile_message}); 157 | return error.FragmentSyntaxError; 158 | } 159 | } 160 | var vs: ?[*]const u8 = vertex_src.ptr; 161 | const vertex_shader = c.glCreateShader(c.GL_VERTEX_SHADER); 162 | if (!WEB_BUILD) { 163 | c.glShaderSource(vertex_shader, 1, &vs, null); 164 | } else { 165 | c.glShaderSource(vertex_shader, 1, vertex_src.ptr, vertex_src.len); 166 | } 167 | c.glCompileShader(vertex_shader); 168 | if (!WEB_BUILD) { 169 | var compile_success: c_int = undefined; 170 | c.glGetShaderiv(vertex_shader, c.GL_COMPILE_STATUS, &compile_success); 171 | if (compile_success == 0) { 172 | helpers.debug_print("Vertex shader compilation failed\n", .{}); 173 | var compile_message: [1024]u8 = undefined; 174 | c.glGetShaderInfoLog(vertex_shader, 1024, null, &compile_message[0]); 175 | helpers.debug_print("{s}\n", .{compile_message}); 176 | return error.VertexSyntaxError; 177 | } 178 | } 179 | shader_prog.program = c.glCreateProgram(); 180 | c.glAttachShader(shader_prog.program, vertex_shader); 181 | c.glAttachShader(shader_prog.program, fragment_shader); 182 | c.glLinkProgram(shader_prog.program); 183 | c.glDeleteShader(vertex_shader); 184 | c.glDeleteShader(fragment_shader); 185 | } 186 | 187 | /// This currently only generates a circle texture that we can use to draw filled circles. 188 | fn init_main_texture(self: *Self) !void { 189 | const temp_bitmap = try self.allocator.alloc(u8, CIRCLE_TEXTURE_SIZE * CIRCLE_TEXTURE_SIZE); 190 | defer self.allocator.free(temp_bitmap); 191 | // The circle texture leaves one pixel at 0,0 as filled, so all other fills can use that 192 | var i: usize = 0; 193 | var j: usize = 0; 194 | // First we initialise the temp_bitmap to 0. 195 | // TODO (01 May 2021 sam): Can we use memset here? Or some equivalent. 196 | // Also is this necessary? We anyway explicity set the pixel values of all pixels we use. 197 | while (i < CIRCLE_TEXTURE_SIZE) : (i += 1) { 198 | j = 0; 199 | while (j < CIRCLE_TEXTURE_SIZE) : (j += 1) { 200 | temp_bitmap[(i * CIRCLE_TEXTURE_SIZE) + j] = 0; 201 | } 202 | } 203 | temp_bitmap[0] = 255; 204 | const radius: usize = (CIRCLE_TEXTURE_SIZE - 1) / 2; 205 | const center = Vector2.from_usize(radius, radius); 206 | i = 1; 207 | while (i < CIRCLE_TEXTURE_SIZE) : (i += 1) { 208 | j = 1; 209 | while (j < CIRCLE_TEXTURE_SIZE) : (j += 1) { 210 | const current = Vector2.from_usize(i - 1, j - 1); 211 | if (Vector2.distance(center, current) <= @intToFloat(f32, radius)) { 212 | temp_bitmap[(i * CIRCLE_TEXTURE_SIZE) + j] = 255; 213 | } else { 214 | temp_bitmap[(i * CIRCLE_TEXTURE_SIZE) + j] = 0; 215 | } 216 | } 217 | } 218 | c.glGenTextures(1, &self.base_shader.texture); 219 | c.glBindTexture(c.GL_TEXTURE_2D, self.base_shader.texture); 220 | const format = if (WEB_BUILD) c.GL_RED_OUT else c.GL_RED; 221 | c.glTexImage2D(c.GL_TEXTURE_2D, 0, c.GL_RED, CIRCLE_TEXTURE_SIZE, CIRCLE_TEXTURE_SIZE, 0, format, c.GL_UNSIGNED_BYTE, &temp_bitmap[0]); 222 | c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MIN_FILTER, c.GL_NEAREST); 223 | c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MAG_FILTER, c.GL_NEAREST); 224 | } 225 | 226 | fn init_text_renderer(self: *Self) !void { 227 | c.glGenTextures(1, &self.text_shader.texture); 228 | c.glBindTexture(c.GL_TEXTURE_2D, self.text_shader.texture); 229 | const format = if (WEB_BUILD) c.GL_RED_OUT else c.GL_RED; 230 | c.glTexImage2D(c.GL_TEXTURE_2D, 0, c.GL_RED, FONT_TEX_SIZE, FONT_TEX_SIZE, 0, format, c.GL_UNSIGNED_BYTE, &self.typesetter.texture_data[0]); 231 | c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MIN_FILTER, c.GL_LINEAR); 232 | } 233 | 234 | pub fn render_app(self: *Self, ticks: u32, app: *App) void { 235 | self.ticks = ticks; 236 | c.glClearColor(0.0, 0.0, 0.0, 1.0); 237 | c.glClear(c.GL_COLOR_BUFFER_BIT | c.GL_DEPTH_BUFFER_BIT); 238 | const posx = app.position.x; 239 | const posy = app.position.y; 240 | self.draw_triangle(.{ .x = posx - 10, .y = posy - 30 }, .{ .x = posx + 50, .y = posy + 50 }, .{ .x = posx - 40, .y = posy + 30 }, .{ .x = 0.3, .y = 0.3, .z = 0.6, .w = 1.0 }, self.camera); 241 | self.draw_buffers(); 242 | if (!WEB_BUILD) { 243 | c.SDL_GL_SwapWindow(self.window); 244 | } 245 | self.clear_buffers(); 246 | } 247 | 248 | fn draw_buffers(self: *Self) void { 249 | c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0); 250 | self.draw_shader_buffers(&self.base_shader); 251 | self.fill_text_buffers(); 252 | self.draw_shader_buffers(&self.text_shader); 253 | } 254 | 255 | fn draw_shader_buffers(self: *Self, shader: *ShaderData) void { 256 | c.glUseProgram(shader.program); 257 | c.glViewport(0, 0, @floatToInt(c_int, self.camera.window_size.x), @floatToInt(c_int, self.camera.window_size.y)); 258 | c.glEnable(c.GL_BLEND); 259 | c.glBlendFunc(c.GL_SRC_ALPHA, c.GL_ONE_MINUS_SRC_ALPHA); 260 | c.glUniform1i(c.glGetUniformLocation(shader.program, "tex"), 0); 261 | c.glActiveTexture(c.GL_TEXTURE0); 262 | c.glBindTexture(c.GL_TEXTURE_2D, shader.texture); 263 | c.glBindVertexArray(self.vao); 264 | c.glBindBuffer(c.GL_ARRAY_BUFFER, self.vbo); 265 | if (shader.triangle_verts.items.len > 0 and shader.indices.items.len > 0) { 266 | const vert_size = if (WEB_BUILD) NUM_FLOAT_IN_VERTEX_DATA * shader.triangle_verts.items.len else @sizeOf(VertexData) * @intCast(c_longlong, shader.triangle_verts.items.len); 267 | c.glBufferData(c.GL_ARRAY_BUFFER, vert_size, &shader.triangle_verts.items[0].position.x, c.GL_DYNAMIC_DRAW); 268 | c.glBindBuffer(c.GL_ELEMENT_ARRAY_BUFFER, self.ebo); 269 | const elem_size = if (WEB_BUILD) shader.indices.items.len else @sizeOf(c_uint) * @intCast(c_longlong, shader.indices.items.len); 270 | c.glBufferData(c.GL_ELEMENT_ARRAY_BUFFER, elem_size, &shader.indices.items[0], c.GL_DYNAMIC_DRAW); 271 | c.glDrawElements(c.GL_TRIANGLES, @intCast(c_int, shader.indices.items.len), c.GL_UNSIGNED_INT, null); 272 | } 273 | } 274 | 275 | fn fill_text_buffers(self: *Self) void { 276 | // This function fills the text buffers with character data from typesetter 277 | var i: usize = 0; 278 | while (i < self.typesetter.glyphs.items.len) : (i += 1) { 279 | const glyph = self.typesetter.glyphs.items[i]; 280 | const quad = glyph.quad; 281 | const color = glyph.color; 282 | const p1 = self.screen_pixel_to_gl(.{ .x = quad.x0, .y = quad.y0 }, self.camera.render_size()); 283 | const p2 = self.screen_pixel_to_gl(.{ .x = quad.x1, .y = quad.y1 }, self.camera.render_size()); 284 | const base = @intCast(c_uint, self.text_shader.triangle_verts.items.len); 285 | self.text_shader.triangle_verts.append(.{ .position = p1, .texCoord = .{ .x = quad.s0, .y = quad.t0 }, .color = color }) catch unreachable; 286 | self.text_shader.triangle_verts.append(.{ .position = .{ .x = p2.x, .y = p1.y, .z = 1.0 }, .texCoord = .{ .x = quad.s1, .y = quad.t0 }, .color = color }) catch unreachable; 287 | self.text_shader.triangle_verts.append(.{ .position = p2, .texCoord = .{ .x = quad.s1, .y = quad.t1 }, .color = color }) catch unreachable; 288 | self.text_shader.triangle_verts.append(.{ .position = .{ .x = p1.x, .y = p2.y, .z = 1.0 }, .texCoord = .{ .x = quad.s0, .y = quad.t1 }, .color = color }) catch unreachable; 289 | const indices = [_]c_uint{ base + 0, base + 1, base + 3, base + 1, base + 2, base + 3 }; 290 | self.text_shader.indices.appendSlice(indices[0..6]) catch unreachable; 291 | } 292 | } 293 | 294 | fn draw_triangle(self: *Self, p1: Vector2, p2: Vector2, p3: Vector2, color: Vector4_gl, camera: *const Camera) void { 295 | const v1 = self.screen_pixel_to_gl(p1, camera.render_size()); 296 | const v2 = self.screen_pixel_to_gl(p2, camera.render_size()); 297 | const v3 = self.screen_pixel_to_gl(p3, camera.render_size()); 298 | const base = @intCast(c_uint, self.base_shader.triangle_verts.items.len); 299 | self.base_shader.triangle_verts.append(.{ .position = v1, .color = color }) catch unreachable; 300 | self.base_shader.triangle_verts.append(.{ .position = v2, .color = color }) catch unreachable; 301 | self.base_shader.triangle_verts.append(.{ .position = v3, .color = color }) catch unreachable; 302 | self.base_shader.indices.append(base + 0) catch unreachable; 303 | self.base_shader.indices.append(base + 1) catch unreachable; 304 | self.base_shader.indices.append(base + 2) catch unreachable; 305 | } 306 | 307 | // TODO (30 Sep 2021 sam): We might want to start off by storing all the positions in screen 308 | // coordinates, and then batch convert them to gl coordinates. Or do that in the vertex shader 309 | // @@Performance 310 | fn screen_pixel_to_gl(self: *Self, screen_pos: Vector2, screen_size: Vector2) Vector3_gl { 311 | const x = ((screen_pos.x / screen_size.x) * 2.0) - 1.0; 312 | const y = 1.0 - ((screen_pos.y / screen_size.y) * 2.0); 313 | self.z_val -= 0.00001; 314 | return .{ .x = x, .y = y, .z = self.z_val }; 315 | } 316 | 317 | fn clear_buffers(self: *Self) void { 318 | self.base_shader.clear_buffers(); 319 | self.text_shader.clear_buffers(); 320 | self.typesetter.reset(); 321 | self.z_val = 0.999; 322 | } 323 | }; 324 | -------------------------------------------------------------------------------- /src/web.zig: -------------------------------------------------------------------------------- 1 | const helpers = @import("helpers.zig"); 2 | 3 | pub const stbtt_aligned_quad = struct { 4 | x0: f32, 5 | y0: f32, 6 | s0: f32, 7 | t0: f32, 8 | x1: f32, 9 | y1: f32, 10 | s1: f32, 11 | t1: f32, 12 | }; 13 | 14 | pub const stbtt_bakedchar = struct { 15 | x0: u16, 16 | y0: u16, 17 | x1: u16, 18 | y1: u16, 19 | xoff: f32, 20 | yoff: f32, 21 | xadvance: f32, 22 | }; 23 | 24 | // Types 25 | pub const GLuint = c_uint; 26 | pub const GLint = c_int; 27 | pub const GLfloat = f32; 28 | 29 | // Identifier constants pulled from WebGLRenderingContext 30 | pub const GL_VERTEX_SHADER: c_uint = 35633; 31 | pub const GL_FRAGMENT_SHADER: c_uint = 35632; 32 | pub const GL_ARRAY_BUFFER: c_uint = 34962; 33 | pub const GL_ELEMENT_ARRAY_BUFFER = 0x8893; 34 | pub const GL_TRIANGLES: c_uint = 4; 35 | pub const GL_TRIANGLE_STRIP = 5; 36 | pub const GL_STATIC_DRAW: c_uint = 35044; 37 | pub const GL_FLOAT: c_uint = 5126; 38 | pub const GL_DEPTH_TEST: c_uint = 2929; 39 | pub const GL_LEQUAL: c_uint = 515; 40 | pub const GL_COLOR_BUFFER_BIT: c_uint = 16384; 41 | pub const GL_DEPTH_BUFFER_BIT: c_uint = 256; 42 | pub const GL_STENCIL_BUFFER_BIT = 1024; 43 | pub const GL_TEXTURE_2D: c_uint = 3553; 44 | pub const GL_RGBA: c_uint = 6408; 45 | pub const GL_UNSIGNED_BYTE: c_uint = 5121; 46 | pub const GL_TEXTURE_MAG_FILTER: c_uint = 10240; 47 | pub const GL_TEXTURE_MIN_FILTER: c_uint = 10241; 48 | pub const GL_NEAREST: c_uint = 9728; 49 | pub const GL_TEXTURE0: c_uint = 33984; 50 | pub const GL_BLEND: c_uint = 3042; 51 | pub const GL_SRC_ALPHA: c_uint = 770; 52 | pub const GL_ONE_MINUS_SRC_ALPHA: c_uint = 771; 53 | pub const GL_ONE: c_uint = 1; 54 | pub const GL_NO_ERROR = 0; 55 | pub const GL_FALSE = 0; 56 | pub const GL_TRUE = 1; 57 | pub const GL_UNPACK_ALIGNMENT = 3317; 58 | pub const GL_RED = 0x8229; 59 | pub const GL_RED_OUT = 0x1903; 60 | pub const GL_LINEAR = 9729; 61 | 62 | pub const GL_TEXTURE_WRAP_S = 10242; 63 | pub const GL_CLAMP_TO_EDGE = 33071; 64 | pub const GL_TEXTURE_WRAP_T = 10243; 65 | pub const GL_PACK_ALIGNMENT = 3333; 66 | pub const GL_FRAMEBUFFER = 0x8D40; 67 | pub const GL_DYNAMIC_DRAW = 0x88E8; 68 | pub const GL_UNSIGNED_INT = 0x1405; 69 | 70 | // Helpers 71 | pub extern fn console_log(_: [*]const u8) void; 72 | pub extern fn consoleLogS(_: [*]const u8, _: c_uint) void; 73 | pub extern fn milliTimestamp() i64; 74 | pub extern fn readWebFile(_: [*]const u8, _: [*]u8, _: c_uint) bool; 75 | pub extern fn readWebFileSize(_: [*]const u8) c_int; 76 | pub extern fn readStorageFile(_: [*]const u8, _: [*]u8, _: c_uint) bool; 77 | pub extern fn readStorageFileSize(_: [*]const u8) c_int; 78 | pub extern fn writeStorageFile(_: [*]const u8, _: [*]const u8) bool; 79 | 80 | // GL 81 | pub extern fn glViewport(_: c_int, _: c_int, _: c_int, _: c_int) void; 82 | pub extern fn glClearColor(_: f32, _: f32, _: f32, _: f32) void; 83 | pub extern fn glEnable(_: c_uint) void; 84 | pub extern fn glDepthFunc(_: c_uint) void; 85 | pub extern fn glBlendFunc(_: c_uint, _: c_uint) void; 86 | pub extern fn glClear(_: c_uint) void; 87 | pub extern fn glGetAttribLocation(_: c_uint, _: [*]const u8, _: c_uint) c_int; 88 | pub extern fn glGetUniformLocation(_: c_uint, _: helpers.WasmText) c_int; 89 | pub extern fn glUniform4fv(_: c_int, _: f32, _: f32, _: f32, _: f32) void; 90 | pub extern fn glUniform1i(_: c_int, _: c_int) void; 91 | pub extern fn glUniform1f(_: c_int, _: f32) void; 92 | pub extern fn glUniformMatrix4fv(_: c_int, _: c_int, _: c_uint, _: [*]const f32) void; 93 | pub extern fn glCreateVertexArray() c_uint; 94 | pub extern fn glGenVertexArrays(_: c_int, [*c]c_uint) void; 95 | pub extern fn glDeleteVertexArrays(_: c_int, [*c]c_uint) void; 96 | pub extern fn glBindVertexArray(_: c_uint) void; 97 | pub extern fn glCreateBuffer() c_uint; 98 | pub extern fn glGenBuffers(_: c_int, _: [*c]c_uint) void; 99 | pub extern fn glDeleteBuffers(_: c_int, _: [*c]c_uint) void; 100 | pub extern fn glDeleteBuffer(_: c_uint) void; 101 | pub extern fn glBindBuffer(_: c_uint, _: c_uint) void; 102 | pub extern fn glBufferData(_: c_uint, _: c_uint, _: ?*const anyopaque, _: c_uint) void; 103 | pub extern fn glPixelStorei(_: c_uint, _: c_int) void; 104 | pub extern fn glShaderSource(_: c_uint, _: c_uint, _: [*]const u8, _: c_uint) void; 105 | pub extern fn glCreateShader(_: c_uint) c_uint; 106 | pub extern fn glCompileShader(_: c_uint) void; 107 | pub extern fn glAttachShader(_: c_uint, _: c_uint) void; 108 | pub extern fn glDetachShader(_: c_uint, _: c_uint) void; 109 | pub extern fn glDeleteShader(_: c_uint) void; 110 | pub extern fn glCreateProgram() c_uint; 111 | pub extern fn glLinkProgram(_: c_uint) void; 112 | pub extern fn glUseProgram(_: c_uint) void; 113 | pub extern fn glDeleteProgram(_: c_uint) void; 114 | pub extern fn glEnableVertexAttribArray(_: c_uint) void; 115 | pub extern fn glVertexAttribPointer(_: c_uint, _: c_uint, _: c_uint, _: c_uint, _: c_uint, _: *allowzero const anyopaque) void; 116 | pub extern fn glDrawArrays(_: c_uint, _: c_uint, _: c_uint) void; 117 | pub extern fn glCreateTexture() c_uint; 118 | pub extern fn glGenTextures(_: c_int, _: [*c]c_uint) void; 119 | pub extern fn glDeleteTextures(_: c_int, _: [*c]const c_uint) void; 120 | pub extern fn glDeleteTexture(_: c_uint) void; 121 | pub extern fn glBindTexture(_: c_uint, _: c_uint) void; 122 | pub extern fn glTexImage2D(_: c_uint, _: c_uint, _: c_uint, _: c_int, _: c_int, _: c_uint, _: c_uint, _: c_uint, _: *u8) void; 123 | pub extern fn glTexParameteri(_: c_uint, _: c_uint, _: c_uint) void; 124 | pub extern fn glActiveTexture(_: c_uint) void; 125 | pub extern fn glGetError() c_int; 126 | pub extern fn glBindFramebuffer(_: c_uint, _: c_uint) void; 127 | pub extern fn glDrawElements(_: c_uint, _: c_int, _: c_uint, _: ?*const anyopaque) void; 128 | -------------------------------------------------------------------------------- /web/data/test.txt: -------------------------------------------------------------------------------- 1 | this is some text test data. -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Hi from Zig 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 17 |
18 |
19 |
20 |
21 | 23 | 24 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /web/interface.js: -------------------------------------------------------------------------------- 1 | const bigToUint8Array = (big) => { 2 | const big0 = BigInt(0) 3 | const big1 = BigInt(1) 4 | const big8 = BigInt(8) 5 | if (big < big0) { 6 | const bits = (BigInt(big.toString(2).length) / big8 + big1) * big8 7 | const prefix1 = big1 << bits 8 | big += prefix1 9 | } 10 | let hex = big.toString(16) 11 | if (hex.length % 2) { 12 | hex = '0' + hex 13 | } 14 | const len = hex.length / 2 15 | const u8 = new Uint8Array(len) 16 | let i = 0 17 | let j = 0 18 | while (i < len) { 19 | u8[i] = parseInt(hex.slice(j, j + 2), 16) 20 | i += 1 21 | j += 2 22 | } 23 | return u8 24 | } 25 | 26 | const u8ToNumber = (array) => { 27 | let number = 0; 28 | let pow = 0; 29 | for (let i = array.length - 1; i >= 0; i--) { 30 | number += array[i] * (256 ** pow); 31 | pow += 1; 32 | } 33 | return number; 34 | } 35 | 36 | const getFileText = (path) => { 37 | let request = new XMLHttpRequest(); 38 | // TODO (12 May 2022 sam): This is being deprecated... How can we do sync otherwise? 39 | request.open('GET', path, false); 40 | request.send(null); 41 | if (request.status !== 200) return false; 42 | return request.responseText; 43 | } 44 | 45 | const readWebFile = (path, ptr, len) => { 46 | path = wasmString(path); 47 | // read text from URL location 48 | const text = getFileText(path); 49 | if (text === false) return false; 50 | if (text.length != len) { 51 | console.log("file length does not match requested length", path, len); 52 | return false; 53 | } 54 | const fileContents = new Uint8Array(memory.buffer, ptr, len); 55 | for (let i=0; i { 62 | path = wasmString(path); 63 | // read text from URL location 64 | const text = getFileText(path); 65 | if (text === false) return -1; 66 | return text.length; 67 | } 68 | 69 | const getStorageText = (path) => { 70 | const text = localStorage.getItem(path); 71 | if (text === null) return false; 72 | return text; 73 | } 74 | 75 | const readStorageFileSize = (path) => { 76 | path = wasmString(path); 77 | const text = getStorageText(path); 78 | if (text === false) return -1; 79 | return text.length; 80 | } 81 | 82 | const readStorageFile = (path, ptr, len) => { 83 | path = wasmString(path); 84 | // read text from URL location 85 | const text = getStorageText(path); 86 | if (text === false) return false; 87 | if (text.length != len) { 88 | console.log("file length does not match requested length", path, len); 89 | return false; 90 | } 91 | const fileContents = new Uint8Array(memory.buffer, ptr, len); 92 | for (let i=0; i { 99 | path = wasmString(path); 100 | text = wasmString(text); 101 | localStorage.setItem(path, text); 102 | } 103 | 104 | const wasmString = (ptr) => { 105 | const bytes = new Uint8Array(memory.buffer, ptr, 1024); 106 | let str = ''; 107 | for (let i = 0; ; i++) { 108 | const c = String.fromCharCode(bytes[i]); 109 | if (c == '\0') break; 110 | str += c; 111 | } 112 | return str; 113 | } 114 | 115 | 116 | const parseWebText = (webText) => { 117 | // webText is a struct. We get it in binary as a BigInt. However, since the system 118 | // is little endian, we cannot directly read and parse the BigInt as is. We need to 119 | // have a littleEndian aware converter. like the one above 120 | // webText struct -> { text: u32 (pointer), len: u32 } 121 | // after conversion -> { text: last 4 bytes, len: first 1-4 bytes } 122 | const bytes = bigToUint8Array(webText); 123 | // these are the reverse order of the struct because of the endianness. 124 | const start = bytes.length - 4; 125 | const len = bytes.slice(0, start); 126 | const text = bytes.slice(start, start+4); 127 | return {text: u8ToNumber(text), len: u8ToNumber(len)}; 128 | } 129 | 130 | const getString = (webText) => { 131 | const str = parseWebText(webText); 132 | const bytes = new Uint8Array(memory.buffer, str.text, str.len); 133 | let s = "" 134 | for (let i = 0; i < str.len ; i++) { 135 | s += String.fromCharCode(bytes[i]); 136 | } 137 | return s; 138 | } 139 | 140 | const consoleLog = (value, len) => { 141 | const bytes = new Uint8Array(memory.buffer, value, len); 142 | let str = ''; 143 | for (let i = 0; i < len; i++) { 144 | str += String.fromCharCode(bytes[i]); 145 | } 146 | console.log('zig1:', str); 147 | // console.log('zig2:', getString(value)); 148 | }; 149 | 150 | const console_log = (value) => { 151 | const bytes = new Uint8Array(memory.buffer, value, 1024); 152 | let str = ''; 153 | for (let i = 0; ; i++) { 154 | const c = String.fromCharCode(bytes[i]); 155 | if (c == '\0') break; 156 | str += c; 157 | } 158 | console.log('zig2:', str); 159 | } 160 | 161 | const milliTimestamp = () => { 162 | return BigInt(Date.now()); 163 | } 164 | 165 | // we choose to always init the webgl context. 166 | var canvas = document.getElementById("webgl_canvas"); 167 | var gl = canvas.getContext("webgl2"); 168 | 169 | // webgl does not store the state like opengl. So we have to do some of that work. 170 | const glShaders = []; 171 | const glPrograms = []; 172 | const glVertexArrays = []; 173 | const glBuffers = []; 174 | const glTextures = []; 175 | const glUniformLocations = []; 176 | 177 | const glClearColor = (r,g,b,a) => { 178 | gl.clearColor(r, g, b, a); 179 | } 180 | 181 | const glClear = (mask) => { 182 | gl.clear(mask); 183 | } 184 | 185 | const glBindFramebuffer = (target, framebuffer) => { 186 | let fb = null; 187 | if (framebuffer != 0) fb = framebuffer; 188 | gl.bindFramebuffer(target, fb) 189 | } 190 | 191 | const glUseProgram = (program) => { 192 | gl.useProgram(glPrograms[program]); 193 | } 194 | 195 | const glViewport = (x, y, width, height) => { 196 | gl.viewport(x, y, width, height) 197 | } 198 | 199 | const glEnable = (cap) => { 200 | gl.enable(cap); 201 | } 202 | 203 | const glBlendFunc = (sfactor, dfactor) => { 204 | gl.blendFunc(sfactor, dfactor); 205 | } 206 | 207 | // might need fix 208 | const glGetUniformLocation = (programId, webText) => { 209 | glUniformLocations.push(gl.getUniformLocation(glPrograms[programId], getString(webText))); 210 | return glUniformLocations.length - 1; 211 | }; 212 | 213 | const glUniform1i = (uniform, v0) => { 214 | gl.uniform1i(glUniformLocations[uniform], glUniformLocations[v0]); 215 | } 216 | 217 | const glCreateVertexArray = () => { 218 | glVertexArrays.push(gl.createVertexArray()); 219 | return glVertexArrays.length - 1; 220 | }; 221 | 222 | const glGenVertexArrays = (num, dataPtr) => { 223 | const vaos = new Uint32Array(memory.buffer, dataPtr, num); 224 | for (let n = 0; n < num; n++) { 225 | const b = glCreateVertexArray(); 226 | vaos[n] = b; 227 | } 228 | } 229 | 230 | const glActiveTexture = (texture) => { 231 | gl.activeTexture(texture); 232 | } 233 | 234 | const glBindVertexArray = (va) => { 235 | gl.bindVertexArray(glVertexArrays[va]); 236 | } 237 | 238 | const glBindBuffer = (target, buffer) => { 239 | gl.bindBuffer(target, glBuffers[buffer]); 240 | } 241 | 242 | const glBufferData = (target, size, data, usage) => { 243 | if (target == 34962) { // GL_ARRAY_BUFFER 244 | size = Number(size); 245 | const buffer = new Float32Array(memory.buffer, data, size); 246 | gl.bufferData(target, buffer, usage); 247 | } 248 | if (target ==0x8893 ) { // GL_ELEMENT_ARRAY_BUFFER 249 | size = Number(size); 250 | const buffer = new Uint32Array(memory.buffer, data, size); 251 | gl.bufferData(target, buffer, usage); 252 | } 253 | 254 | } 255 | 256 | const glDrawElements = (mode, count, type, offset) => { 257 | gl.drawElements(mode, count, type, offset); 258 | } 259 | 260 | const glGenBuffers = (num, dataPtr) => { 261 | const buffers = new Uint32Array(memory.buffer, dataPtr, num); 262 | for (let n = 0; n < num; n++) { 263 | const b = glCreateBuffer(); 264 | buffers[n] = b; 265 | } 266 | } 267 | 268 | const glVertexAttribPointer = (attribLocation, size, type, normalize, stride, offset) => { 269 | gl.vertexAttribPointer(attribLocation, size, type, normalize, stride, offset); 270 | } 271 | 272 | const glEnableVertexAttribArray = (x) => { 273 | gl.enableVertexAttribArray(x); 274 | } 275 | 276 | const glGenTextures = (num, dataPtr) => { 277 | const textures = new Uint32Array(memory.buffer, dataPtr, num); 278 | for (let n = 0; n < num; n++) { 279 | const b = glCreateTexture(); 280 | textures[n] = b; 281 | } 282 | } 283 | 284 | const glTexImage2D = (target, level, internalFormat, width, height, border, format, type, dataPtr) => { 285 | const data = new Uint8Array(memory.buffer, dataPtr, width*height); 286 | gl.texImage2D(target, level, internalFormat, width, height, border, format, type, data); 287 | }; 288 | 289 | const glTexParameteri = (target, pname, param) => { 290 | gl.texParameteri(target, pname, param); 291 | } 292 | 293 | const glCreateShader = (type) => { 294 | let shader = gl.createShader(type); 295 | glShaders.push(shader); 296 | return glShaders.length - 1; 297 | } 298 | 299 | const glShaderSource = (shader, count, data, len) => { 300 | if (count != 1) console.log("we only support count = 1 for glShaderSource"); 301 | const source = new Uint8Array(memory.buffer, data, len); 302 | let str = ''; 303 | for (let i = 0; i < len; i++) { 304 | str += String.fromCharCode(source[i]); 305 | } 306 | gl.shaderSource(glShaders[shader], str); 307 | } 308 | 309 | const glCompileShader = (shader) => { 310 | gl.compileShader(glShaders[shader]); 311 | } 312 | 313 | const glCreateProgram = () => { 314 | let program = gl.createProgram(); 315 | glPrograms.push(program); 316 | return glPrograms.length - 1; 317 | } 318 | 319 | const glAttachShader = (program, shader) => { 320 | gl.attachShader(glPrograms[program], glShaders[shader]); 321 | } 322 | 323 | const glLinkProgram = (program) => { 324 | gl.linkProgram(glPrograms[program]); 325 | } 326 | 327 | const glDeleteShader = (shader) => { 328 | // eh who will delete and all 329 | } 330 | 331 | const glCreateBuffer = () => { 332 | glBuffers.push(gl.createBuffer()); 333 | return glBuffers.length - 1; 334 | } 335 | 336 | const glCreateTexture = () => { 337 | glTextures.push(gl.createTexture()); 338 | return glTextures.length - 1; 339 | }; 340 | 341 | const glBindTexture = (target, textureId) => { 342 | gl.bindTexture(target, glTextures[textureId]); 343 | } 344 | 345 | var api = { 346 | consoleLogS: consoleLog, 347 | console_log, 348 | readWebFile, 349 | readWebFileSize, 350 | readStorageFileSize, 351 | readStorageFile, 352 | writeStorageFile, 353 | milliTimestamp, 354 | glClearColor, 355 | glClear, 356 | glBindFramebuffer, 357 | glUseProgram, 358 | glViewport, 359 | glEnable, 360 | glBlendFunc, 361 | glGetUniformLocation, 362 | glUniform1i, 363 | glActiveTexture, 364 | glBindTexture, 365 | glBindVertexArray, 366 | glBindBuffer, 367 | glBufferData, 368 | glDrawElements, 369 | glGenBuffers, 370 | glGenVertexArrays, 371 | glVertexAttribPointer, 372 | glEnableVertexAttribArray, 373 | glGenTextures, 374 | glTexImage2D, 375 | glTexParameteri, 376 | glCreateShader, 377 | glShaderSource, 378 | glCompileShader, 379 | glCreateProgram, 380 | glAttachShader, 381 | glLinkProgram, 382 | glDeleteShader, 383 | glCreateVertexArray, 384 | glCreateBuffer, 385 | glCreateTexture, 386 | } 387 | -------------------------------------------------------------------------------- /web_build.bat: -------------------------------------------------------------------------------- 1 | zig build-lib -target wasm32-freestanding C:\Users\user\projects\sdl_base\src\main.zig --cache-dir C:\Users\user\projects\sdl_base\zig-cache --global-cache-dir C:\Users\user\AppData\Local\zig --name typeroo --pkg-begin build_options C:\Users\user\projects\sdl_base\zig-cache\options\RwV5Tl1x39B2k9RWTOv298QHj0zrBnPaHS8dz5ZLL0A3lzE4GExYqU1FpXa4rmI- --pkg-end -isystem C:\Users\user\projects\sdl_base\src -dynamic 2 | 3 | move typeroo.wasm web 4 | --------------------------------------------------------------------------------