DVUI

getTextureAtlas

This needs to be called before rendering of glyphs as the uv coordinates of the glyphs will not be correct if the atlas needs to be generated.

Parameters

#
self:*Entry
*Entry
gpa:std.mem.Allocator
std.mem.Allocator
backend:Backend
Backend

Source

Implementation

#
pub fn getTextureAtlas(self: *Entry, gpa: std.mem.Allocator, backend: Backend) Backend.TextureError!Texture {
            if (self.texture_atlas_cache) |tex| return tex;

            // number of extra pixels to add on each side of each glyph
            const pad = 1;

            const total = self.glyph_info_ascii.len + self.glyph_info.count();
            const row_glyphs: u32 = @ceil(@as(f32, @sqrt(@as(f32, @floatFromInt(total)))));

            var s = Size{};
            {
                var i: u32 = 0;
                var row: Size = .{};
                var it = self.glyph_info.iterator();

                while (i < total) {
                    const gi, const codepoint = if (i < self.glyph_info_ascii.len) .{ &self.glyph_info_ascii[i], i + ascii_start } else blk: {
                        const e = it.next().?;
                        break :blk .{ e.value_ptr, e.key_ptr.* };
                    };
                    _ = codepoint;

                    row.w += gi.w + 2 * pad;
                    row.h = @max(row.h, gi.h + 2 * pad);

                    i += 1;
                    if (i % row_glyphs == 0) {
                        s.w = @max(s.w, row.w);
                        s.h += row.h;
                        row = .{};
                    }
                } else {
                    s.w = @max(s.w, row.w);
                    s.h += row.h;
                }

                s = s.ceil();
            }

            // also add an extra padding around whole texture
            s.w += 2 * pad;
            s.h += 2 * pad;

            var pixels = try gpa.alloc(dvui.Color.PMA, @as(usize, @trunc(s.w * s.h)));
            defer gpa.free(pixels);
            // set all pixels to zero alpha
            @memset(pixels, .transparent);

            //const num_glyphs = fce.glyph_info.count();
            //std.debug.print("font size {d} regen glyph atlas num {d} max size {}\n", .{ sized_font.size, num_glyphs, s });

            var x: i32 = pad;
            var y: i32 = pad;
            var it = self.glyph_info.iterator();
            var row_height: u32 = 0;
            var i: u32 = 0;
            while (i < total) {
                var gi, const codepoint = if (i < self.glyph_info_ascii.len) .{ &self.glyph_info_ascii[i], i + ascii_start } else blk: {
                    const e = it.next().?;
                    break :blk .{ e.value_ptr, e.key_ptr.* };
                };

                gi.uv[0] = @as(f32, @floatFromInt(x + pad)) / s.w;
                gi.uv[1] = @as(f32, @floatFromInt(y + pad)) / s.h;

                if (impl == .FreeType) blk: {
                    FreeType.intToError(c.FT_Load_Char(self.face, codepoint, @as(i32, @bitCast(FreeType.LoadFlags{ .render = true })))) catch |err| {
                        dvui.log.warn("renderText: freetype error {any} trying to FT_Load_Char codepoint {d}", .{ err, codepoint });
                        break :blk; // will skip the failing glyph
                    };

                    // https://freetype.org/freetype2/docs/tutorial/step1.html#section-6
                    if (self.face.*.glyph.*.format != c.FT_GLYPH_FORMAT_BITMAP) {
                        FreeType.intToError(c.FT_Render_Glyph(self.face.*.glyph, c.FT_RENDER_MODE_NORMAL)) catch |err| {
                            dvui.log.warn("renderText freetype error {any} trying to FT_Render_Glyph codepoint {d}", .{ err, codepoint });
                            break :blk; // will skip the failing glyph
                        };
                    }

                    const bitmap = self.face.*.glyph.*.bitmap;
                    //std.debug.print("bitmap,{d},{d},{d}\n", .{codepoint, bitmap.width, bitmap.rows});
                    row_height = @max(row_height, bitmap.rows);
                    var row: i32 = 0;
                    while (row < bitmap.rows) : (row += 1) {
                        var col: i32 = 0;
                        while (col < bitmap.width) : (col += 1) {
                            const src = bitmap.buffer[@as(usize, @intCast(row * bitmap.pitch + col))];

                            // because of the extra edge, offset by 1 row and 1 col
                            const di = @as(usize, @intCast((y + row + pad) * @as(i32, @trunc(s.w)) + (x + col + pad)));

                            // premultiplied white
                            pixels[di] = .{ .r = src, .g = src, .b = src, .a = src };
                        }
                    }
                } else {
                    //var ow: c_int = undefined;
                    //var oh: c_int = undefined;
                    //const bm = c.stbtt_GetCodepointBitmap(&self.face, self.scaleFactor, self.scaleFactor, @as(c_int, @intCast(codepoint)), &ow, &oh, null, null);
                    //std.debug.print("bitmap,{d},{d},{d}\n", .{codepoint, ow, oh});

                    //c.stbtt_FreeBitmap(bm, null);

                    const out_w: u32 = @trunc(gi.w);
                    const out_h: u32 = @trunc(gi.h);
                    row_height = @max(row_height, out_h);

                    // single channel
                    const bitmap = try gpa.alloc(u8, @as(usize, out_w * out_h));
                    defer gpa.free(bitmap);

                    //log.debug("makecodepointBitmap size x {d} y {d} w {d} h {d} out w {d} h {d}", .{ x, y, s.w, s.h, out_w, out_h });

                    c.stbtt_MakeCodepointBitmapSubpixel(&self.face, bitmap.ptr, @as(c_int, @intCast(out_w)), @as(c_int, @intCast(out_h)), @as(c_int, @intCast(out_w)), self.scaleFactor, self.scaleFactor, 0.0, 0.0, @as(c_int, @intCast(codepoint)));

                    const stride: usize = @trunc(s.w);
                    const di = @as(usize, @intCast(y)) * stride + @as(usize, @intCast(x));
                    for (0..out_h) |row| {
                        for (0..out_w) |col| {
                            const src = bitmap[row * out_w + col];
                            const dest = di + (row + pad) * stride + (col + pad);

                            // premultiplied white
                            pixels[dest] = .{ .r = src, .g = src, .b = src, .a = src };
                        }
                    }
                }

                x += @as(i32, @trunc(gi.w)) + 2 * pad;

                i += 1;
                if (i % row_glyphs == 0) {
                    x = pad;
                    y += @intCast(row_height + 2 * pad);
                    row_height = 0;
                }
            }

            self.texture_atlas_cache = try backend.textureCreate(@ptrCast(pixels.ptr), .{ .width = @trunc(s.w), .height = @trunc(s.h) });
            return self.texture_atlas_cache.?;
        }