DVUI

textSizeRaw

Doesn't scale the font or max_width, always stops at newlines

Assumes the text is valid utf8. Will exit early with non-full size on invalid utf8

Parameters

#
self:*Entry
*Entry
gpa:std.mem.Allocator
std.mem.Allocator
text:[]const u8
[]const u8
opts:Font.TextSizeOptions
Font.TextSizeOptions

Source

Implementation

#
pub fn textSizeRaw(
            self: *Entry,
            gpa: std.mem.Allocator,
            text: []const u8,
            opts: Font.TextSizeOptions,
        ) std.mem.Allocator.Error!Size {
            const mwidth = opts.max_width orelse dvui.max_float_safe;

            const snap = dvui.snapToPixels();
            var x: f32 = 0;
            var minx: f32 = 0;
            var maxx: f32 = 0;
            var miny: f32 = 0;
            var maxy: f32 = self.height;
            var tw: f32 = 0;
            var th: f32 = self.height;

            var ei: usize = 0;
            var nearest_break: bool = false;

            const kerning: bool = opts.kerning orelse true;
            var last_codepoint: u32 = 0;
            var next_kern_idx: u32 = 0;
            var next_kern_byte: u32 = 0;
            if (opts.kern_in) |ki| {
                next_kern_byte = ki[next_kern_idx];
                next_kern_idx += 1;
            }

            var i: usize = 0;
            while (i < text.len) {
                const cplen = std.unicode.utf8ByteSequenceLength(text[i]) catch unreachable;
                const codepoint = std.unicode.utf8Decode(text[i..][0..cplen]) catch unreachable;

                if (codepoint == '\n') {
                    // newlines always terminate, and don't use any space
                    ei += 1;
                    break;
                }

                const gi = try self.glyphInfoGetOrReplacement(gpa, codepoint);

                if (kerning and last_codepoint != 0 and i >= next_kern_byte) {
                    const kk = self.kern(last_codepoint, codepoint);
                    x += if (snap) @round(kk) else kk;

                    if (opts.kern_in) |ki| {
                        if (next_kern_idx < ki.len) {
                            next_kern_byte = ki[next_kern_idx];
                            next_kern_idx += 1;
                        }
                    }

                    if (kk != 0) {
                        if (opts.kern_out) |ko| {
                            // fill in first 0
                            for (ko) |*k| {
                                if (k.* == 0) {
                                    k.* = @intCast(i);
                                    break;
                                }
                            }
                        }
                    }
                }

                i += cplen;
                last_codepoint = codepoint;

                const adv = if (snap) @round(gi.advance) else gi.advance;

                minx = @min(minx, x + gi.leftBearing);
                maxx = @max(maxx, x + gi.leftBearing + gi.w);
                maxx = @max(maxx, x + adv);

                miny = @min(miny, self.ascent - gi.topBearing);
                maxy = @max(maxy, self.ascent - gi.topBearing + gi.h);

                if ((maxx - minx) > mwidth) {
                    switch (opts.end_metric) {
                        .before => break, // went too far
                        .nearest => {
                            if ((maxx - minx) - mwidth >= mwidth - tw) {
                                break; // current one is closest
                            } else {
                                // get the next glyph and then break
                                nearest_break = true;
                            }
                        },
                    }
                }

                // record that we processed this codepoint
                ei += cplen;

                // update space taken by glyph
                tw = maxx - minx;
                th = maxy - miny;
                x += adv;

                if (nearest_break) break;
            }

            // TODO: xstart and ystart

            if (opts.end_idx) |endout| {
                endout.* = ei;
            }

            if (opts.kern_out) |ko| {
                // fill in first 0
                for (ko) |*k| {
                    if (k.* == 0) {
                        k.* = @intCast(i);
                        break;
                    }
                }
            }

            //std.debug.print("textSizeRaw size {d} for \"{s}\" {d}x{d} {d}\n", .{ self.size, text, tw, th, ei });
            return Size{ .w = tw, .h = th };
        }