diff --git a/assets/fonts/Vollkorn/OFL.txt b/assets/fonts/Vollkorn/OFL.txt new file mode 100644 index 0000000..51cedbb --- /dev/null +++ b/assets/fonts/Vollkorn/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2017 The Vollkorn Project Authors (https://github.com/FAlthausen/Vollkorn-Typeface) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/assets/fonts/Vollkorn/README.txt b/assets/fonts/Vollkorn/README.txt new file mode 100644 index 0000000..49591c1 --- /dev/null +++ b/assets/fonts/Vollkorn/README.txt @@ -0,0 +1,75 @@ +Vollkorn Variable Font +====================== + +This download contains Vollkorn as both variable fonts and static fonts. + +Vollkorn is a variable font with this axis: + wght + +This means all the styles are contained in these files: + Vollkorn-VariableFont_wght.ttf + Vollkorn-Italic-VariableFont_wght.ttf + +If your app fully supports variable fonts, you can now pick intermediate styles +that aren’t available as static fonts. Not all apps support variable fonts, and +in those cases you can use the static font files for Vollkorn: + static/Vollkorn-Regular.ttf + static/Vollkorn-Medium.ttf + static/Vollkorn-SemiBold.ttf + static/Vollkorn-Bold.ttf + static/Vollkorn-ExtraBold.ttf + static/Vollkorn-Black.ttf + static/Vollkorn-Italic.ttf + static/Vollkorn-MediumItalic.ttf + static/Vollkorn-SemiBoldItalic.ttf + static/Vollkorn-BoldItalic.ttf + static/Vollkorn-ExtraBoldItalic.ttf + static/Vollkorn-BlackItalic.ttf + +Get started +----------- + +1. Install the font files you want to use + +2. Use your app's font picker to view the font family and all the +available styles + +Learn more about variable fonts +------------------------------- + + https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts + https://variablefonts.typenetwork.com + https://medium.com/variable-fonts + +In desktop apps + + https://theblog.adobe.com/can-variable-fonts-illustrator-cc + https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts + +Online + + https://developers.google.com/fonts/docs/getting_started + https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide + https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts + +Installing fonts + + MacOS: https://support.apple.com/en-us/HT201749 + Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux + Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows + +Android Apps + + https://developers.google.com/fonts/docs/android + https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts + +License +------- +Please read the full license text (OFL.txt) to understand the permissions, +restrictions and requirements for usage, redistribution, and modification. + +You can use them in your products & projects – print or digital, +commercial or otherwise. + +This isn't legal advice, please consider consulting a lawyer and see the full +license for all details. diff --git a/assets/fonts/Vollkorn/Vollkorn-Italic-VariableFont_wght.ttf b/assets/fonts/Vollkorn/Vollkorn-Italic-VariableFont_wght.ttf new file mode 100644 index 0000000..80b2cdc Binary files /dev/null and b/assets/fonts/Vollkorn/Vollkorn-Italic-VariableFont_wght.ttf differ diff --git a/assets/fonts/Vollkorn/Vollkorn-VariableFont_wght.ttf b/assets/fonts/Vollkorn/Vollkorn-VariableFont_wght.ttf new file mode 100644 index 0000000..188fd06 Binary files /dev/null and b/assets/fonts/Vollkorn/Vollkorn-VariableFont_wght.ttf differ diff --git a/assets/fonts/Vollkorn/static/Vollkorn-Black.ttf b/assets/fonts/Vollkorn/static/Vollkorn-Black.ttf new file mode 100644 index 0000000..57962b4 Binary files /dev/null and b/assets/fonts/Vollkorn/static/Vollkorn-Black.ttf differ diff --git a/assets/fonts/Vollkorn/static/Vollkorn-BlackItalic.ttf b/assets/fonts/Vollkorn/static/Vollkorn-BlackItalic.ttf new file mode 100644 index 0000000..3a03044 Binary files /dev/null and b/assets/fonts/Vollkorn/static/Vollkorn-BlackItalic.ttf differ diff --git a/assets/fonts/Vollkorn/static/Vollkorn-Bold.ttf b/assets/fonts/Vollkorn/static/Vollkorn-Bold.ttf new file mode 100644 index 0000000..4b3618d Binary files /dev/null and b/assets/fonts/Vollkorn/static/Vollkorn-Bold.ttf differ diff --git a/assets/fonts/Vollkorn/static/Vollkorn-BoldItalic.ttf b/assets/fonts/Vollkorn/static/Vollkorn-BoldItalic.ttf new file mode 100644 index 0000000..51f2c25 Binary files /dev/null and b/assets/fonts/Vollkorn/static/Vollkorn-BoldItalic.ttf differ diff --git a/assets/fonts/Vollkorn/static/Vollkorn-ExtraBold.ttf b/assets/fonts/Vollkorn/static/Vollkorn-ExtraBold.ttf new file mode 100644 index 0000000..d83593d Binary files /dev/null and b/assets/fonts/Vollkorn/static/Vollkorn-ExtraBold.ttf differ diff --git a/assets/fonts/Vollkorn/static/Vollkorn-ExtraBoldItalic.ttf b/assets/fonts/Vollkorn/static/Vollkorn-ExtraBoldItalic.ttf new file mode 100644 index 0000000..1ef5a27 Binary files /dev/null and b/assets/fonts/Vollkorn/static/Vollkorn-ExtraBoldItalic.ttf differ diff --git a/assets/fonts/Vollkorn/static/Vollkorn-Italic.ttf b/assets/fonts/Vollkorn/static/Vollkorn-Italic.ttf new file mode 100644 index 0000000..d218ced Binary files /dev/null and b/assets/fonts/Vollkorn/static/Vollkorn-Italic.ttf differ diff --git a/assets/fonts/Vollkorn/static/Vollkorn-Medium.ttf b/assets/fonts/Vollkorn/static/Vollkorn-Medium.ttf new file mode 100644 index 0000000..6476dbf Binary files /dev/null and b/assets/fonts/Vollkorn/static/Vollkorn-Medium.ttf differ diff --git a/assets/fonts/Vollkorn/static/Vollkorn-MediumItalic.ttf b/assets/fonts/Vollkorn/static/Vollkorn-MediumItalic.ttf new file mode 100644 index 0000000..4d25898 Binary files /dev/null and b/assets/fonts/Vollkorn/static/Vollkorn-MediumItalic.ttf differ diff --git a/assets/fonts/Vollkorn/static/Vollkorn-Regular.ttf b/assets/fonts/Vollkorn/static/Vollkorn-Regular.ttf new file mode 100644 index 0000000..88b0657 Binary files /dev/null and b/assets/fonts/Vollkorn/static/Vollkorn-Regular.ttf differ diff --git a/assets/fonts/Vollkorn/static/Vollkorn-SemiBold.ttf b/assets/fonts/Vollkorn/static/Vollkorn-SemiBold.ttf new file mode 100644 index 0000000..abf5721 Binary files /dev/null and b/assets/fonts/Vollkorn/static/Vollkorn-SemiBold.ttf differ diff --git a/assets/fonts/Vollkorn/static/Vollkorn-SemiBoldItalic.ttf b/assets/fonts/Vollkorn/static/Vollkorn-SemiBoldItalic.ttf new file mode 100644 index 0000000..a2fa642 Binary files /dev/null and b/assets/fonts/Vollkorn/static/Vollkorn-SemiBoldItalic.ttf differ diff --git a/assets/test.frag b/assets/test.frag index 73161da..dafce02 100644 --- a/assets/test.frag +++ b/assets/test.frag @@ -23,5 +23,5 @@ void main() // float x = fract(fragTexCoord.s); // float final = smoothstep(divider - 0.1, divider + 0.1, x); - finalColor = fragColor; + finalColor = vec4(fragTexCoord, 0.0, 1.0); } diff --git a/build.zig b/build.zig index 4b501ce..7c71889 100644 --- a/build.zig +++ b/build.zig @@ -103,6 +103,14 @@ pub fn build(b: *std.Build) void { }); client.root_module.addImport("kb", kb_text_shape.createModule()); + client.root_module.addCSourceFile(.{ .file = b.path("src/stb/stb_rect_pack.h"), .language = .c, .flags = &.{} }); + const stb_rect_pack = b.addTranslateC(.{ + .root_source_file = b.path("src/stb/stb_rect_pack.h"), + .target = target, + .optimize = optimize, + }); + client.root_module.addImport("stb_rect_pack", stb_rect_pack.createModule()); + const zmath = b.dependency("zmath", .{}); client.root_module.addImport("zmath", zmath.module("root")); server.root_module.addImport("zmath", zmath.module("root")); diff --git a/src/client/font.zig b/src/client/font.zig new file mode 100644 index 0000000..dc45b34 --- /dev/null +++ b/src/client/font.zig @@ -0,0 +1,372 @@ +const std = @import("std"); + +const rl = @import("raylib"); +const kb = @import("kb"); +const ft = @import("freetype"); +const rp = @import("stb_rect_pack"); + +pub var ft_lib : ft.Library = undefined; + +const ATLAS_SIZE = 4096; + +const Glyph = struct { + utf32 : u32, + index : u32, + + x : i16, + y : i16, + width : u32, + height : u32, + rwidth : i16, + rheight : i16, + + bearing_x : i32, + bearing_y : i32, + + // y_max : i16, + // y_min : i16, + + ascent : i32, + descent : i32, + advance : i16, + + st0 : rl.Vector2, + st1 : rl.Vector2, + + bitmap : []u8, +}; + +pub const Font = struct { + face : ft.Face, + glyphs : std.AutoHashMap(u32, Glyph), + kb : kb.kbts_font, + texture : rl.Texture2D, + font_data : []u8, + + pub fn init(filename : []const u8, size : i32, allocator: std.mem.Allocator) !Font { + + // const pixel_size = size * 96.0 / 72.0; + + const font_data = try std.fs.cwd().readFileAlloc(allocator, filename, 10 * 1024 * 1024); + + + // if !success { + // log("[Error] Could not read the font file."); + // return; + // } + + var face = try ft_lib.createFaceMemory(font_data, 0); + var glyphs = std.AutoHashMap(u32, Glyph).init(allocator); + // .FT_New_Memory_Face(ftlib, font_data.data, xx font_data.count, 0, *face); + // if error { + // log("[Error] %", to_string(FT_Error_String(error))); + // } + + //FT_Set_Pixel_Sizes(face, 0, size); + try face.setCharSize(0, size * 64, 0, 96); + // error = FT_Set_Char_Size(face, 0, size * 64, 0, 96); + // if error { + // log("[Error] %", to_string(FT_Error_String(error))); + // } + + var rects = try std.ArrayList(rp.stbrp_rect).initCapacity(allocator, 1024); + defer rects.deinit(allocator); + + var nodes : [ATLAS_SIZE]rp.stbrp_node = std.mem.zeroes([ATLAS_SIZE]rp.stbrp_node); + + var stbrpcontext : rp.stbrp_context = .{}; + rp.stbrp_init_target(&stbrpcontext, ATLAS_SIZE, ATLAS_SIZE, &nodes, nodes.len); + + const _atlas : []u8 = try allocator.alloc(u8, ATLAS_SIZE * ATLAS_SIZE); + defer allocator.free(_atlas); + + var atlas : rl.Image = .{ + .data = _atlas.ptr, + .width = ATLAS_SIZE, + .height = ATLAS_SIZE, + .mipmaps = 1, + .format = .uncompressed_grayscale + }; + + std.log.info("We have {} glyphs", .{face.numGlyphs()}); + // for (0..1500) |i| { + for (0..face.numGlyphs()) |i| { + //for 65..90 { + //index := FT_Get_Char_Index(face, xx it); + try face.loadGlyph(@intCast(i), .{ .render = true }); + + // array_add(*rects, stbrp_rect.{cast(s32) face.glyph.glyph_index, cast(s32) face.glyph.bitmap.width, + // cast(s32) face.glyph.bitmap.rows, 0, 0, 0}); + try rects.append(allocator, + rp.stbrp_rect{ + .id = @intCast(face.glyph().glyphIndex()), + .w = @intCast(face.glyph().bitmap().width()), + .h = @intCast(face.glyph().bitmap().rows()), + .x = 0, + .y = 0, + .was_packed = 0, + } + ); + + const width = face.glyph().bitmap().width(); + const height = face.glyph().bitmap().rows(); + const bearing_y = face.glyph().bitmapTop(); + const descent = @as(i32, @intCast(height)) - bearing_y; + const ascent = @as(i32, @intCast(height)) - descent; + + const glyph = Glyph{ + .x = 0, + .y = 0, + .utf32 = @intCast(i), + .index = face.glyph().glyphIndex(), + .bearing_x = face.glyph().bitmapLeft(), + .bearing_y = face.glyph().bitmapTop(), + .width = width, + .height = height, + .rwidth = @intCast(face.glyph().metrics().width >> 6), + .rheight = @intCast(face.glyph().metrics().height >> 6), + .descent = @as(i32, @intCast(face.glyph().bitmap().rows())) - face.glyph().bitmapTop(), + .ascent = ascent, + .advance = @intCast(face.glyph().advance().x >> 6), + .st0 = rl.Vector2.zero(), + .st1 = rl.Vector2.zero(), + .bitmap = try allocator.alloc(u8, width * height), + }; + const s = glyph.height * glyph.width; + const buf = face.glyph().bitmap().buffer() orelse continue; + @memcpy(glyph.bitmap, buf[0..s]); + + // table_set(*font.glyphs, glyph.index, glyph); + try glyphs.put(glyph.index, glyph); + // if (try glyphs.fetchPut(glyph.index, glyph)) |old| { + // allocator.free(old.value.bitmap); + // } + } + + _ = rp.stbrp_pack_rects(&stbrpcontext, rects.items.ptr, @intCast(rects.items.len)); + // stbrp_pack_rects(*stbrpcontext, rects.data, cast(s32) rects.count); + + for (rects.items) |rect| { + // for rect : rects { + const glyph = glyphs.getPtr(@intCast(rect.id)) orelse continue; + if (glyph.bitmap.len != 0) { + glyph.x = @intCast(rect.x); + glyph.y = @as(i16, @intCast(rect.y)) + @as(i16, @intCast(glyph.height)); + + const fx = @as(f32, @floatFromInt(glyph.x)); + const fy = @as(f32, @floatFromInt(glyph.y)); + const fw = @as(f32, @floatFromInt(glyph.width)); + const fh = @as(f32, @floatFromInt(glyph.height)); + const fs = @as(f32, ATLAS_SIZE); + + glyph.st0 = rl.Vector2.init((fx + 0.5) / fs, (fy - 0.5) / fs); + + glyph.st1 = .{ + .x = (fx + fw) / fs, + .y = (fy - fh) / fs, + }; + + const g = rl.Image{ + .data = glyph.bitmap.ptr, + .width = @intCast(glyph.width), + .height = @intCast(glyph.height), + .mipmaps = 1, + .format = .uncompressed_grayscale + }; + + atlas.drawImage(g, + .{ .x = 0, .y = 0, .width = @floatFromInt(g.width), .height = @floatFromInt(g.height) }, + .{ .x = @floatFromInt(rect.x), .y = @floatFromInt(rect.y), .width = @floatFromInt(g.width), .height = @floatFromInt(g.height)}, + .white + ); + + // copyBitmapToAtlas(glyph.bitmap, glyph.width, glyph.height, atlas, @intCast(rect.x), @intCast(rect.y)); + } + // glyph : *Glyph = table_find_pointer(*font.glyphs, cast(u32) rect.id); + // if (glyph.bitmap) { + // glyph.x = cast,trunc(s16, rect.x); + // glyph.y = cast,trunc(s16, rect.y) + xx glyph.height; + // glyph.st0 = .{cast(float, glyph.x + 0.5) / ATLAS_SIZE, cast(float, glyph.y - 0.5) / ATLAS_SIZE}; + // //glyph.st1 = glyph.st0 + .{cast(float, glyph.width) / ATLAS_SIZE, -cast(float, glyph.height) / ATLAS_SIZE}; + // glyph.st1 = .{(cast(float, glyph.x + glyph.width )) / cast(float, ATLAS_SIZE), (cast(float, glyph.y - glyph.height )) / cast(float, ATLAS_SIZE)}; + // copyBitmapToAtlas(glyph.bitmap, cast(s32) glyph.width, cast(s32) glyph.height, atlas, cast(s32) rect.x, cast(s32) rect.y); + // } + } + + const texture = try rl.Texture.fromImage(atlas); + + // image.flipVertical(); + // texture = make_texture_from_data(atlas, ATLAS_SIZE, ATLAS_SIZE); + + // const kb = kbts_FontFromMemory(font_data.data, xx font_data.count, 0, null, null); + var _kb = kb.kbts_FontFromMemory(font_data.ptr, @intCast(font_data.len), 0, null, null); + + if (kb.kbts_FontIsValid(&_kb) != 0) { + std.log.info("[Error] [kb_text_shape] Failed to load the font.", .{}); + } + + return .{ + .face = face, + .glyphs = glyphs, + .kb = _kb, + .texture = texture, + .font_data = font_data, + }; + } + + pub fn deinit(self: *Font, allocator: std.mem.Allocator) void { + var it = self.glyphs.valueIterator(); + while (it.next()) |g| { + allocator.free(g.bitmap); + } + self.glyphs.deinit(); + self.face.deinit(); + kb.kbts_FreeFont(&self.kb); + self.texture.unload(); + allocator.free(self.font_data); + } + + pub fn render_text( + self: *Font, + text: []const u8, + pos: rl.Vector2, + window_space: bool, + colour: rl.Color, + background: rl.Color, + nice_background: bool, + count_descent: bool + ) void { + if (text.len == 0) + return; + + if (nice_background) { + self.render_text(text, pos.add(.{.x = 3.0, .y = -3.0}), window_space, rl.Color.init(0, 0, 0, 255), rl.Color.init(0, 0, 0, 0), false, false); + } + + var render_pos = pos; + //_ = count_descent; + //_ = colour; + _ = background; + + const draw_size, const max_ascent, const max_descent = self.size_row(text, 0, 0); + _ = max_ascent; + _ = max_descent; + + const Context = kb.kbts_CreateShapeContext(null, null); + const kb_font = kb.kbts_ShapePushFont(Context, &self.kb); + if (kb_font == null) { + std.log.info("Could not open font!", .{}); + return; + } + + kb.kbts_ShapeBegin(Context, kb.KBTS_DIRECTION_DONT_KNOW, kb.KBTS_LANGUAGE_DONT_KNOW); + kb.kbts_ShapeUtf8(Context, text.ptr, @intCast(text.len), kb.KBTS_USER_ID_GENERATION_MODE_CODEPOINT_INDEX); + kb.kbts_ShapeEnd(Context); + + var x_offset : f32 = 0; + var y_offset : f32 = 0; + + // Layout runs naively left to right. + var Run = kb.kbts_run{}; + while (kb.kbts_ShapeRun(Context, &Run) != 0) { + if ((Run.Flags & kb.KBTS_BREAK_FLAG_LINE_HARD) != 0) { + + } + + var g : [*c]kb.kbts_glyph = null; + while (kb.kbts_GlyphIteratorNext(&Run.Glyphs, &g) != 0) { + const RunGlyph : *kb.kbts_glyph = g.?; // assert non-null + const CodepointIndex : i32 = RunGlyph.UserIdOrCodepointIndex; + var ShapeCodepoint : kb.kbts_shape_codepoint = undefined; + if (kb.kbts_ShapeGetShapeCodepoint(Context, CodepointIndex, &ShapeCodepoint) == 0) { + std.log.info("aah it's wrong!! idk how to handle the error rn!!", .{}); + } + + // glyph : *Glyph = table_find_pointer(*text.font.glyphs, RunGlyph.Id); + if (self.glyphs.getPtr(RunGlyph.Id)) |glyph| { + var v0 = rl.Vector2.zero(); + var v1 = rl.Vector2.zero(); + if (count_descent) { + v0 = render_pos.add(.{ .x = x_offset,// + glyph.bearing_x, + .y = y_offset - @as(f32, @floatFromInt(glyph.bearing_y)) + draw_size.y });// /*- max_descent*/}; + } else { + v0 = render_pos.add(.{ .x = x_offset,// + glyph.bearing_x, + .y = y_offset - @as(f32, @floatFromInt(@as(i32, @intCast(glyph.height)) - glyph.bearing_y)) });//* - glyph.height + draw_size.y*/}; + } + + v1 = v0.add(rl.Vector2{ .x = @floatFromInt(glyph.width), .y = @floatFromInt(glyph.height) }); + const p0 : rl.Vector4 = .{ .x = v0.x, .y = v0.y, .z = 0.0, .w = 1.0 }; + const p1 : rl.Vector4 = .{ .x = v1.x, .y = v1.y, .z = 0.0, .w = 1.0 }; + x_offset += @floatFromInt(ft.mulFix(RunGlyph.AdvanceX, @intCast(self.face.size().metrics().x_scale)) >> 6); + y_offset += @floatFromInt(ft.mulFix(RunGlyph.AdvanceY, @intCast(self.face.size().metrics().y_scale)) >> 6); + + // #if Y_IS_UP { + // t0 := Vector2.{cast(float, glyph.x) / cast(float, ATLAS_SIZE), cast(float, glyph.y) / cast(float, ATLAS_SIZE)}; + // t1 := t0 + Vector2.{cast(float, glyph.width) / cast(float, ATLAS_SIZE), -cast(float, glyph.height) / cast(float, ATLAS_SIZE)}; + const st0 = glyph.st0; + const st1 = glyph.st1; + // } else { + // t0 := Vector2.{cast(float, glyph.x / ATLAS_SIZE), cast(float, glyph.y / ATLAS_SIZE)}; + // t1 := t0 + .{cast(float, glyph.width / ATLAS_SIZE), cast(float, glyph.height / ATLAS_SIZE)}; + // } + + rl.gl.rlColor4ub(colour.r, colour.g, colour.b, colour.a); rl.gl.rlTexCoord2f(st0.x, st0.y); rl.gl.rlVertex2f(p0.x, p0.y); + rl.gl.rlColor4ub(colour.r, colour.g, colour.b, colour.a); rl.gl.rlTexCoord2f(st1.x, st1.y); rl.gl.rlVertex2f(p1.x, p1.y); + rl.gl.rlColor4ub(colour.r, colour.g, colour.b, colour.a); rl.gl.rlTexCoord2f(st0.x, st1.y); rl.gl.rlVertex2f(p0.x, p1.y); + + rl.gl.rlColor4ub(colour.r, colour.g, colour.b, colour.a); rl.gl.rlTexCoord2f(st1.x, st0.y); rl.gl.rlVertex2f(p1.x, p0.y); + rl.gl.rlColor4ub(colour.r, colour.g, colour.b, colour.a); rl.gl.rlTexCoord2f(st1.x, st1.y); rl.gl.rlVertex2f(p1.x, p1.y); + rl.gl.rlColor4ub(colour.r, colour.g, colour.b, colour.a); rl.gl.rlTexCoord2f(st0.x, st0.y); rl.gl.rlVertex2f(p0.x, p0.y); + } else { + continue; + } + } + } + } + + // -> vec2, float, s32 + pub fn size_row(self: *Font, str: []const u8, n: i32, max_width: f32) struct {rl.Vector2, f32, i32} { + _ = max_width; + _ = n; + const Context = kb.kbts_CreateShapeContext(null, null); + const kb_font = kb.kbts_ShapePushFont(Context, &self.kb); + if (kb_font == null) { + std.log.info("Could not open font!", .{}); + return .{ rl.Vector2.zero(), 0, 0 }; + } + + kb.kbts_ShapeBegin(Context, kb.KBTS_DIRECTION_DONT_KNOW, kb.KBTS_LANGUAGE_DONT_KNOW); + kb.kbts_ShapeUtf8(Context, str.ptr, @intCast(str.len), kb.KBTS_USER_ID_GENERATION_MODE_CODEPOINT_INDEX); + kb.kbts_ShapeEnd(Context); + + var size = rl.Vector2.zero(); + var max_descent : f32 = 0; + + // Layout runs naively left to right. + var Run = kb.kbts_run{}; + while(kb.kbts_ShapeRun(Context, &Run) != 0) { + if ((Run.Flags & kb.KBTS_BREAK_FLAG_LINE_HARD) != 0) { + + } + + var g : [*c]kb.kbts_glyph = null; + while (kb.kbts_GlyphIteratorNext(&Run.Glyphs, &g) != 0) { + const RunGlyph : *kb.kbts_glyph = g.?; // assert non-null + const CodepointIndex : i32 = RunGlyph.UserIdOrCodepointIndex; + var ShapeCodepoint : kb.kbts_shape_codepoint = undefined; + if (kb.kbts_ShapeGetShapeCodepoint(Context, CodepointIndex, &ShapeCodepoint) == 0) { + std.log.info("aah it's wrong!! idk how to handle the error rn!!", .{}); + } + + if (self.glyphs.getPtr(@intCast(ShapeCodepoint.Codepoint))) |glyph| { + size.y = @max(size.y, @as(f32, @floatFromInt(glyph.height))); + // size.x += (ft.mulFix(RunGlyph.AdvanceX, self.face.size().metrics().x_scale) >> 6); + size.x += @floatFromInt(ft.mulFix(RunGlyph.AdvanceX, @intCast(self.face.size().metrics().x_scale)) >> 6); + max_descent = @max(max_descent, @as(f32, @floatFromInt(glyph.descent))); + } + } + } + + return .{ size, max_descent, 0}; + } +}; diff --git a/src/client/main.zig b/src/client/main.zig index 5a08e30..f138940 100644 --- a/src/client/main.zig +++ b/src/client/main.zig @@ -3,14 +3,14 @@ const zm = @import("zmath"); const znet = @import("znet"); const rl = @import("raylib"); const bufzilla = @import("bufzilla"); +const ft = @import("freetype"); const shared = @import("shared"); -const kb = @import("kb"); -const ft = @import("freetype"); const client = @import("client.zig"); const entity = @import("entity.zig"); const misc = @import("misc.zig"); +const font = @import("font.zig"); const dt : f32 = 1.0 / 200.0; var t : f32 = 0; @@ -35,6 +35,7 @@ pub fn main() !void { std.log.info("Hello Client!", .{}); try init(); + defer _ = dbg_allocator.deinit(); try znet.init(); defer znet.deinit(); rl.initWindow(1280, 720, "zzz"); @@ -42,11 +43,12 @@ pub fn main() !void { // kbts_shape_context *Context = kbts_CreateShapeContext(0, 0); - _ = try ft.Library.init(); + font.ft_lib = try ft.Library.init(); - _ = kb.kbts_CreateShapeContext(null, null); + var f = try font.Font.init("assets/fonts/Vollkorn/static/Vollkorn-Regular.ttf", 48, allocator); + defer f.deinit(allocator); - // const shader = try rl.loadShader(null, "assets/test.frag"); + const shader = try rl.loadShader(null, "assets/test.frag"); // const img = rl.genImageColor(32, 32, .blank); // const tx = try rl.loadTextureFromImage(img); @@ -70,8 +72,6 @@ pub fn main() !void { .data = 0, }); - defer _ = dbg_allocator.deinit(); - // connect() catch |err| switch (err) { // error.ConnectionRefused => { // std.log.err("server refused connection", .{}); @@ -180,28 +180,44 @@ pub fn main() !void { rl.beginDrawing(); - // rl.beginShaderMode(shader); - rl.gl.rlBegin(rl.gl.rl_triangles); + rl.beginShaderMode(shader); - // const color = rl.Color.init(255, 0, 255, 255); - const top : rl.Vector2 = .{ .x = @as(f32, @floatFromInt(rl.getScreenWidth())) / 2.0, .y = 0.0 }; - const bottomLeft : rl.Vector2 = .{ .x = 0.0, .y = @floatFromInt(rl.getScreenHeight()) }; - const bottomRight : rl.Vector2 = .{ .x = @floatFromInt(rl.getScreenWidth()), .y = @floatFromInt(rl.getScreenHeight()) }; + f.render_text( + "H", + rl.Vector2.init(400, 400), + true, + rl.Color.white, + rl.Color.blank, + true, + true + ); - // rl.gl.vertex - rl.gl.rlColor4ub(0, 255, 0, 255); - rl.gl.rlVertex2f(top.x, top.y); - rl.gl.rlColor4ub(255, 0, 0, 255); - rl.gl.rlVertex2f(bottomLeft.x, bottomLeft.y); - rl.gl.rlColor4ub(0, 0, 255, 255); - rl.gl.rlVertex2f(bottomRight.x, bottomRight.y); - // rl.gl.rlVertex2f(topRight.x, topRight.y); - // rl.gl.rlVertex2f(bottomLeft.x, bottomLeft.y); - // rl.gl.rlVertex2f(bottomRight.x, bottomRight.y); + // rl.gl.rlBegin(rl.gl.rl_triangles); - rl.gl.rlEnd(); - // rl.endShaderMode(); + // { + // const topLeft : rl.Vector2 = .{ .x = 0.0, .y = 0.0 }; + // const topRight : rl.Vector2 = .{ .x = @floatFromInt(rl.getScreenWidth()), .y = 0.0 }; + // const bottomLeft : rl.Vector2 = .{ .x = 0.0, .y = @floatFromInt(rl.getScreenHeight()) }; + // const bottomRight : rl.Vector2 = .{ .x = @floatFromInt(rl.getScreenWidth()), .y = @floatFromInt(rl.getScreenHeight()) }; + + // rl.gl.rlTexCoord2f(0.0, 0.0); + // rl.gl.rlVertex2f(topLeft.x, topLeft.y); + // rl.gl.rlTexCoord2f(0.0, 1.0); + // rl.gl.rlVertex2f(bottomLeft.x, bottomLeft.y); + // rl.gl.rlTexCoord2f(1.0, 0.0); + // rl.gl.rlVertex2f(topRight.x, topRight.y); + + // rl.gl.rlTexCoord2f(1.0, 0.0); + // rl.gl.rlVertex2f(topRight.x, topRight.y); + // rl.gl.rlTexCoord2f(0.0, 1.0); + // rl.gl.rlVertex2f(bottomLeft.x, bottomLeft.y); + // rl.gl.rlTexCoord2f(1.0, 1.0); + // rl.gl.rlVertex2f(bottomRight.x, bottomRight.y); + // } + + // rl.gl.rlEnd(); + rl.endShaderMode(); // const connected_text = "Connected"; //const not_connected_text = "Not Connected"; @@ -232,7 +248,7 @@ pub fn main() !void { //rl.drawText("Congrats! You created your first window!", rl.getMouseX(), rl.getMouseY(), 20, .white); //rl.drawRectangleLines(0, 0, 100, 100, .red); - //misc.drawFPS(0, 0, frame_time, frame); + misc.drawFPS(0, 0, frame_time, frame); //elf.draw(); diff --git a/src/stb/stb_rect_pack.h b/src/stb/stb_rect_pack.h new file mode 100644 index 0000000..6a633ce --- /dev/null +++ b/src/stb/stb_rect_pack.h @@ -0,0 +1,623 @@ +// stb_rect_pack.h - v1.01 - public domain - rectangle packing +// Sean Barrett 2014 +// +// Useful for e.g. packing rectangular textures into an atlas. +// Does not do rotation. +// +// Before #including, +// +// #define STB_RECT_PACK_IMPLEMENTATION +// +// in the file that you want to have the implementation. +// +// Not necessarily the awesomest packing method, but better than +// the totally naive one in stb_truetype (which is primarily what +// this is meant to replace). +// +// Has only had a few tests run, may have issues. +// +// More docs to come. +// +// No memory allocations; uses qsort() and assert() from stdlib. +// Can override those by defining STBRP_SORT and STBRP_ASSERT. +// +// This library currently uses the Skyline Bottom-Left algorithm. +// +// Please note: better rectangle packers are welcome! Please +// implement them to the same API, but with a different init +// function. +// +// Credits +// +// Library +// Sean Barrett +// Minor features +// Martins Mozeiko +// github:IntellectualKitty +// +// Bugfixes / warning fixes +// Jeremy Jaussaud +// Fabian Giesen +// +// Version history: +// +// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section +// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles +// 0.99 (2019-02-07) warning fixes +// 0.11 (2017-03-03) return packing success/fail result +// 0.10 (2016-10-25) remove cast-away-const to avoid warnings +// 0.09 (2016-08-27) fix compiler warnings +// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) +// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) +// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort +// 0.05: added STBRP_ASSERT to allow replacing assert +// 0.04: fixed minor bug in STBRP_LARGE_RECTS support +// 0.01: initial release +// +// LICENSE +// +// See end of file for license information. + +////////////////////////////////////////////////////////////////////////////// +// +// INCLUDE SECTION +// + +#ifndef STB_INCLUDE_STB_RECT_PACK_H +#define STB_INCLUDE_STB_RECT_PACK_H + +#define STB_RECT_PACK_VERSION 1 + +#ifdef STBRP_STATIC +#define STBRP_DEF static +#else +#define STBRP_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct stbrp_context stbrp_context; +typedef struct stbrp_node stbrp_node; +typedef struct stbrp_rect stbrp_rect; + +typedef int stbrp_coord; + +#define STBRP__MAXVAL 0x7fffffff +// Mostly for internal use, but this is the maximum supported coordinate value. + +STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); +// Assign packed locations to rectangles. The rectangles are of type +// 'stbrp_rect' defined below, stored in the array 'rects', and there +// are 'num_rects' many of them. +// +// Rectangles which are successfully packed have the 'was_packed' flag +// set to a non-zero value and 'x' and 'y' store the minimum location +// on each axis (i.e. bottom-left in cartesian coordinates, top-left +// if you imagine y increasing downwards). Rectangles which do not fit +// have the 'was_packed' flag set to 0. +// +// You should not try to access the 'rects' array from another thread +// while this function is running, as the function temporarily reorders +// the array while it executes. +// +// To pack into another rectangle, you need to call stbrp_init_target +// again. To continue packing into the same rectangle, you can call +// this function again. Calling this multiple times with multiple rect +// arrays will probably produce worse packing results than calling it +// a single time with the full rectangle array, but the option is +// available. +// +// The function returns 1 if all of the rectangles were successfully +// packed and 0 otherwise. + +struct stbrp_rect +{ + // reserved for your use: + int id; + + // input: + stbrp_coord w, h; + + // output: + stbrp_coord x, y; + int was_packed; // non-zero if valid packing + +}; // 16 bytes, nominally + + +STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); +// Initialize a rectangle packer to: +// pack a rectangle that is 'width' by 'height' in dimensions +// using temporary storage provided by the array 'nodes', which is 'num_nodes' long +// +// You must call this function every time you start packing into a new target. +// +// There is no "shutdown" function. The 'nodes' memory must stay valid for +// the following stbrp_pack_rects() call (or calls), but can be freed after +// the call (or calls) finish. +// +// Note: to guarantee best results, either: +// 1. make sure 'num_nodes' >= 'width' +// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' +// +// If you don't do either of the above things, widths will be quantized to multiples +// of small integers to guarantee the algorithm doesn't run out of temporary storage. +// +// If you do #2, then the non-quantized algorithm will be used, but the algorithm +// may run out of temporary storage and be unable to pack some rectangles. + +STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); +// Optionally call this function after init but before doing any packing to +// change the handling of the out-of-temp-memory scenario, described above. +// If you call init again, this will be reset to the default (false). + + +STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); +// Optionally select which packing heuristic the library should use. Different +// heuristics will produce better/worse results for different data sets. +// If you call init again, this will be reset to the default. + +enum +{ + STBRP_HEURISTIC_Skyline_default=0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight +}; + + +////////////////////////////////////////////////////////////////////////////// +// +// the details of the following structures don't matter to you, but they must +// be visible so you can handle the memory allocations for them + +struct stbrp_node +{ + stbrp_coord x,y; + stbrp_node *next; +}; + +struct stbrp_context +{ + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node *active_head; + stbrp_node *free_head; + stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' +}; + +#ifdef __cplusplus +} +#endif + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION SECTION +// + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#ifndef STBRP_SORT +#include +#define STBRP_SORT qsort +#endif + +#ifndef STBRP_ASSERT +#include +#define STBRP_ASSERT assert +#endif + +#ifdef _MSC_VER +#define STBRP__NOTUSED(v) (void)(v) +#define STBRP__CDECL __cdecl +#else +#define STBRP__NOTUSED(v) (void)sizeof(v) +#define STBRP__CDECL +#endif + +enum +{ + STBRP__INIT_skyline = 1 +}; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) +{ + switch (context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) +{ + if (allow_out_of_mem) + // if it's ok to run out of memory, then don't bother aligning them; + // this gives better packing, but may fail due to OOM (even though + // the rectangles easily fit). @TODO a smarter approach would be to only + // quantize once we've hit OOM, then we could get rid of this parameter. + context->align = 1; + else { + // if it's not ok to run out of memory, then quantize the widths + // so that num_nodes is always enough nodes. + // + // I.e. num_nodes * align >= width + // align >= width / num_nodes + // align = ceil(width/num_nodes) + + context->align = (context->width + context->num_nodes-1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) +{ + int i; + + for (i=0; i < num_nodes-1; ++i) + nodes[i].next = &nodes[i+1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord) width; + context->extra[1].y = (1<<30); + context->extra[1].next = NULL; +} + +// find minimum y position if it starts at x1 +static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) +{ + stbrp_node *node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + + STBRP__NOTUSED(c); + + STBRP_ASSERT(first->x <= x0); + + #if 0 + // skip in case we're past the node + while (node->next->x <= x0) + ++node; + #else + STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency + #endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while (node->x < x1) { + if (node->y > min_y) { + // raise min_y higher. + // we've accounted for all waste up to min_y, + // but we'll now add more waste for everything we've visted + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + // the first time through, visited_width might be reduced + if (node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } else { + // add waste area + int under_width = node->next->x - node->x; + if (under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct +{ + int x,y; + stbrp_node **prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) +{ + int best_waste = (1<<30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node **prev, *node, *tail, **best = NULL; + + // align to multiple of c->align + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + // if it can't possibly fit, bail immediately + if (width > c->width || height > c->height) { + fr.prev_link = NULL; + fr.x = fr.y = 0; + return fr; + } + + node = c->active_head; + prev = &c->active_head; + while (node->x + width <= c->width) { + int y,waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL + // bottom left + if (y < best_y) { + best_y = y; + best = prev; + } + } else { + // best-fit + if (y + height <= c->height) { + // can only use it if it first vertically + if (y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + // if doing best-fit (BF), we also have to try aligning right edge to each node position + // + // e.g, if fitting + // + // ____________________ + // |____________________| + // + // into + // + // | | + // | ____________| + // |____________| + // + // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned + // + // This makes BF take about 2x the time + + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + // find first node that's admissible + while (tail->x < width) + tail = tail->next; + while (tail) { + int xpos = tail->x - width; + int y,waste; + STBRP_ASSERT(xpos >= 0); + // find the left position that matches this + while (node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if (y + height <= c->height) { + if (y <= best_y) { + if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { + best_x = xpos; + STBRP_ASSERT(y <= best_y); + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) +{ + // find best position according to heuristic + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node *node, *cur; + + // bail if: + // 1. it failed + // 2. the best node doesn't fit (we don't always check this) + // 3. we're out of memory + if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + // on success, create new node + node = context->free_head; + node->x = (stbrp_coord) res.x; + node->y = (stbrp_coord) (res.y + height); + + context->free_head = node->next; + + // insert the new node into the right starting point, and + // let 'cur' point to the remaining nodes needing to be + // stiched back in + + cur = *res.prev_link; + if (cur->x < res.x) { + // preserve the existing one, so start testing with the next one + stbrp_node *next = cur->next; + cur->next = node; + cur = next; + } else { + *res.prev_link = node; + } + + // from here, traverse cur and free the nodes, until we get to one + // that shouldn't be freed + while (cur->next && cur->next->x <= res.x + width) { + stbrp_node *next = cur->next; + // move the current node to the free list + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + // stitch the list back in + node->next = cur; + + if (cur->x < res.x + width) + cur->x = (stbrp_coord) (res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while (cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + int count=0; + cur = context->active_head; + while (cur) { + cur = cur->next; + ++count; + } + cur = context->free_head; + while (cur) { + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes+2); + } +#endif + + return res; +} + +static int STBRP__CDECL rect_height_compare(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + if (p->h > q->h) + return -1; + if (p->h < q->h) + return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +static int STBRP__CDECL rect_original_order(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) +{ + int i, all_rects_packed = 1; + + // we use the 'was_packed' field internally to allow sorting/unsorting + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = i; + } + + // sort according to heuristic + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); + + for (i=0; i < num_rects; ++i) { + if (rects[i].w == 0 || rects[i].h == 0) { + rects[i].x = rects[i].y = 0; // empty rect needs no space + } else { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if (fr.prev_link) { + rects[i].x = (stbrp_coord) fr.x; + rects[i].y = (stbrp_coord) fr.y; + } else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + } + + // unsort + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); + + // set was_packed flags and all_rects_packed status + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); + if (!rects[i].was_packed) + all_rects_packed = 0; + } + + // return the all_rects_packed status + return all_rects_packed; +} +#endif + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/