├── web
├── data
│ └── test.txt
├── index.html
└── interface.js
├── run.sh
├── .gitignore
├── README.md
├── dependencies
├── stb_truetype-1.24
│ └── stb_truetype_impl.c
└── gl
│ └── KHR
│ └── khrplatform.h
├── src
├── embeds
│ ├── fonts
│ │ ├── text_atlas.png
│ │ ├── Leander
│ │ │ ├── Leander.ttf
│ │ │ └── Tension Type Font License.txt
│ │ ├── Goudy
│ │ │ ├── goudy_bookletter_1911.otf
│ │ │ └── Barry Schwartz License.txt
│ │ ├── JetBrainsMono
│ │ │ ├── ttf
│ │ │ │ ├── JetBrainsMono-Bold.ttf
│ │ │ │ ├── JetBrainsMono-Light.ttf
│ │ │ │ ├── JetBrainsMono-Italic.ttf
│ │ │ │ ├── JetBrainsMono-Medium.ttf
│ │ │ │ ├── JetBrainsMono-Regular.ttf
│ │ │ │ ├── JetBrainsMono-ExtraBold.ttf
│ │ │ │ ├── JetBrainsMono-ExtraLight.ttf
│ │ │ │ ├── JetBrainsMono-SemiLight.ttf
│ │ │ │ ├── JetBrainsMono-Bold-Italic.ttf
│ │ │ │ ├── JetBrainsMono-Light-Italic.ttf
│ │ │ │ ├── JetBrainsMono-Medium-Italic.ttf
│ │ │ │ ├── JetBrainsMono-ExtraBold-Italic.ttf
│ │ │ │ ├── JetBrainsMono-ExtraLight-Italic.ttf
│ │ │ │ ├── JetBrainsMono-SemiLight-Italic.ttf
│ │ │ │ ├── Variable
│ │ │ │ │ ├── JetBrainsMono-Variable.ttf
│ │ │ │ │ └── JetBrainsMono-Variable-Italic.ttf
│ │ │ │ └── No ligatures
│ │ │ │ │ ├── JetBrainsMonoNL-Bold.ttf
│ │ │ │ │ ├── JetBrainsMonoNL-Light.ttf
│ │ │ │ │ ├── JetBrainsMonoNL-Italic.ttf
│ │ │ │ │ ├── JetBrainsMonoNL-Medium.ttf
│ │ │ │ │ ├── JetBrainsMonoNL-Regular.ttf
│ │ │ │ │ ├── JetBrainsMonoNL-ExtraBold.ttf
│ │ │ │ │ ├── JetBrainsMonoNL-ExtraLight.ttf
│ │ │ │ │ ├── JetBrainsMonoNL-SemiLight.ttf
│ │ │ │ │ ├── JetBrainsMonoNL-Bold-Italic.ttf
│ │ │ │ │ ├── JetBrainsMonoNL-Light-Italic.ttf
│ │ │ │ │ ├── JetBrainsMonoNL-Medium-Italic.ttf
│ │ │ │ │ ├── JetBrainsMonoNL-ExtraBold-Italic.ttf
│ │ │ │ │ ├── JetBrainsMonoNL-ExtraLight-Italic.ttf
│ │ │ │ │ └── JetBrainsMonoNL-SemiLight-Italic.ttf
│ │ │ └── LICENSE
│ │ └── text_info.json
│ └── shaders
│ │ ├── vertex.glsl
│ │ ├── web_vertex.glsl
│ │ ├── fragment_texalpha.glsl
│ │ └── web_fragment_texalpha.glsl
├── platform.zig
├── c.zig
├── constants.zig
├── app.zig
├── main.zig
├── web.zig
├── renderer.zig
├── glyphee.zig
└── helpers.zig
├── run.bat
└── web_build.bat
/web/data/test.txt:
--------------------------------------------------------------------------------
1 | this is some text test data.
--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------
1 | time zig build -Dwindows=false & zig build run -Dwindows=false
2 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/dependencies/stb_truetype-1.24/stb_truetype_impl.c:
--------------------------------------------------------------------------------
1 | #define STB_TRUETYPE_IMPLEMENTATION
2 | #include "stb_truetype.h"
3 |
--------------------------------------------------------------------------------
/src/embeds/fonts/text_atlas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/text_atlas.png
--------------------------------------------------------------------------------
/src/embeds/fonts/Leander/Leander.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/Leander/Leander.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/Goudy/goudy_bookletter_1911.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/Goudy/goudy_bookletter_1911.otf
--------------------------------------------------------------------------------
/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/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Bold.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Light.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Italic.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Medium.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Regular.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-ExtraBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-ExtraBold.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-ExtraLight.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-ExtraLight.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-SemiLight.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-SemiLight.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Bold-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Bold-Italic.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Light-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Light-Italic.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Medium-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-Medium-Italic.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-ExtraBold-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-ExtraBold-Italic.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-ExtraLight-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-ExtraLight-Italic.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-SemiLight-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/JetBrainsMono-SemiLight-Italic.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/Variable/JetBrainsMono-Variable.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/Variable/JetBrainsMono-Variable.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Bold.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Light.ttf
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Italic.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/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/HEAD/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Regular.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-ExtraBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-ExtraBold.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-ExtraLight.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-ExtraLight.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-SemiLight.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/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/HEAD/src/embeds/fonts/JetBrainsMono/ttf/Variable/JetBrainsMono-Variable-Italic.ttf
--------------------------------------------------------------------------------
/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/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Bold-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Bold-Italic.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Light-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Light-Italic.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Medium-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-Medium-Italic.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-ExtraBold-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-ExtraBold-Italic.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-ExtraLight-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-ExtraLight-Italic.ttf
--------------------------------------------------------------------------------
/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-SemiLight-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samhattangady/zig_sdl_base/HEAD/src/embeds/fonts/JetBrainsMono/ttf/No ligatures/JetBrainsMonoNL-SemiLight-Italic.ttf
--------------------------------------------------------------------------------
/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/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_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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Hi from Zig
7 |
8 |
9 |
10 |
11 |
12 |
21 |
23 |
24 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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 | }
--------------------------------------------------------------------------------