Skip to documentation
SLOP

tiny.filigree.caret

Reference tiny.filigree caret

Defined in tiny.filigree.

API (4)

Actions

Public operations.

Types and contracts

Public types and contracts.

No direct callersNo direct callstiny.filigreecaret
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallstest sourcelib.filigree.src.carettest: caret hit test chooses grapheme...test sourcelib.filigree.src.carettest: caret hit test uses OpenType GD...test sourcelib.filigree.src.carettest: caret hit test uses ligature st...private sourcelib.filigree.src.caretadvanceWithinClusterprivate sourcelib.filigree.src.caretclampedSourceOffsetprivate sourcelib.filigree.src.caretclusterAdvancecaretadvanceForByteOffset
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallstest sourcelib.filigree.src.carettest: caret hit test chooses grapheme...test sourcelib.filigree.src.carettest: caret hit test uses OpenType GD...test sourcelib.filigree.src.carettest: caret hit test uses ligature st...private sourcelib.filigree.src.caretclampedSourceOffsetprivate sourcelib.filigree.src.caretclusterAdvanceprivate sourcelib.filigree.src.caretnearestStopInClustercarethitTestAdvance
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callersprivate sourcelib.filigree.src.caretclampedSourceOffsetprivate sourcelib.filigree.src.caretclusterAdvanceprivate sourcelib.filigree.src.caretnearestEndpointcarethitTestAdvanceByLength
Static calls · unresolved targets: 0 · external targets: 1.

Source: lib/filigree/src/caret.zig

zig
const std = @import("std");const fixtures = @import("fixture/root.zig");const shape = @import("shape/root.zig");const unicode = @import("unicode");pub const LineHit = struct {    byte_offset: usize,    advance: f32,};pub fn advanceForByteOffset(run: shape.GlyphRun, byte_offset: usize, text_len: usize) f32 {    const target = @min(byte_offset, text_len);    var pen: f32 = 0;    const map = run.clusterMap();    for (run.clusters, 0..) |cluster, cluster_index| {        const start = clampedSourceOffset(cluster.source.start, text_len);        const end = clampedSourceOffset(cluster.source.end, text_len);        const advance = clusterAdvance(run, cluster.glyphs);        if (target <= start) return pen;        if (target < end) return pen + advanceWithinCluster(map, cluster_index, target, advance, text_len);        pen += advance;    }    return pen;}pub fn hitTestAdvance(run: shape.GlyphRun, x: f32, text: []const u8) LineHit {    var pen: f32 = 0;    const map = run.clusterMap();    for (run.clusters, 0..) |cluster, cluster_index| {        const advance = clusterAdvance(run, cluster.glyphs);        if (x <= pen) {            const byte_offset = clampedSourceOffset(cluster.source.start, text.len);            return .{ .byte_offset = byte_offset, .advance = pen };        }        if (x < pen + advance) {            const stop = nearestStopInCluster(map, cluster_index, x - pen, advance, text);            return .{ .byte_offset = stop.byte_offset, .advance = pen + stop.advance };        }        pen += advance;    }    return .{ .byte_offset = text.len, .advance = pen };}pub fn hitTestAdvanceByLength(run: shape.GlyphRun, x: f32, text_len: usize) LineHit {    var pen: f32 = 0;    const map = run.clusterMap();    for (run.clusters, 0..) |cluster, cluster_index| {        const advance = clusterAdvance(run, cluster.glyphs);        if (x <= pen) {            const byte_offset = clampedSourceOffset(cluster.source.start, text_len);            return .{ .byte_offset = byte_offset, .advance = pen };        }        if (x < pen + advance) {            const stop = nearestEndpoint(map, cluster_index, x - pen, advance, text_len);            return .{ .byte_offset = stop.byte_offset, .advance = pen + stop.advance };        }        pen += advance;    }    return .{ .byte_offset = text_len, .advance = pen };}fn clampedSourceOffset(source_offset: u32, text_len: usize) usize {    return @min(@as(usize, @intCast(source_offset)), text_len);}fn advanceWithinCluster(map: shape.ClusterMap, cluster_index: usize, byte_offset: usize, advance: f32, text_len: usize) f32 {    const count = map.clusterCaretStopCount(cluster_index) orelse return midpointAdvance(byte_offset, cluster_index, map, advance, text_len);    if (count == 0) return 0;    var previous = stopInCluster(map, cluster_index, 0, advance, text_len);    if (byte_offset <= previous.byte_offset) return previous.advance;    var stop_index: usize = 1;    while (stop_index < count) : (stop_index += 1) {        const current = stopInCluster(map, cluster_index, stop_index, advance, text_len);        if (byte_offset == current.byte_offset) return current.advance;        if (byte_offset < current.byte_offset) {            const span = current.byte_offset - previous.byte_offset;            if (span == 0) return current.advance;            const numerator: f32 = @floatFromInt(byte_offset - previous.byte_offset);            const denominator: f32 = @floatFromInt(span);            return previous.advance + (current.advance - previous.advance) * (numerator / denominator);        }        previous = current;    }    return previous.advance;}fn midpointAdvance(byte_offset: usize, cluster_index: usize, map: shape.ClusterMap, advance: f32, text_len: usize) f32 {    const range = map.clusterSourceRange(cluster_index) orelse return 0;    const start = clampedSourceOffset(range.start, text_len);    const end = clampedSourceOffset(range.end, text_len);    if (end <= start) return 0;    if (byte_offset <= start) return 0;    if (byte_offset >= end) return advance;    const numerator: f32 = @floatFromInt(byte_offset - start);    const denominator: f32 = @floatFromInt(end - start);    return advance * (numerator / denominator);}fn nearestStopInCluster(map: shape.ClusterMap, cluster_index: usize, x: f32, advance: f32, text: []const u8) LineHit {    const count = map.clusterCaretStopCount(cluster_index) orelse 0;    if (count > 2) return nearestExplicitStopInCluster(map, cluster_index, x, advance, text.len);    const range = map.clusterSourceRange(cluster_index) orelse return .{ .byte_offset = text.len, .advance = advance };    const start = clampedSourceOffset(range.start, text.len);    const end = clampedSourceOffset(range.end, text.len);    const fraction = if (advance <= 0) @as(f32, 0) else std.math.clamp(x / advance, @as(f32, 0), @as(f32, 1));    const byte_offset = byteOffsetInGraphemeSegments(text, start, end, fraction);    return .{        .byte_offset = byte_offset,        .advance = advanceWithinCluster(map, cluster_index, byte_offset, advance, text.len),    };}fn nearestExplicitStopInCluster(map: shape.ClusterMap, cluster_index: usize, x: f32, advance: f32, text_len: usize) LineHit {    const count = map.clusterCaretStopCount(cluster_index) orelse 0;    var nearest = stopInCluster(map, cluster_index, 0, advance, text_len);    var nearest_distance = @abs(x - nearest.advance);    var stop_index: usize = 1;    while (stop_index < count) : (stop_index += 1) {        const stop = stopInCluster(map, cluster_index, stop_index, advance, text_len);        const distance = @abs(x - stop.advance);        if (distance < nearest_distance or (distance == nearest_distance and stop.byte_offset > nearest.byte_offset)) {            nearest = stop;            nearest_distance = distance;        }    }    return nearest;}fn nearestEndpoint(map: shape.ClusterMap, cluster_index: usize, x: f32, advance: f32, text_len: usize) LineHit {    const range = map.clusterSourceRange(cluster_index) orelse return .{ .byte_offset = text_len, .advance = advance };    const start = clampedSourceOffset(range.start, text_len);    const end = clampedSourceOffset(range.end, text_len);    if (x < advance * 0.5) return .{ .byte_offset = start, .advance = 0 };    return .{ .byte_offset = end, .advance = advance };}fn stopInCluster(map: shape.ClusterMap, cluster_index: usize, stop_index: usize, advance: f32, text_len: usize) LineHit {    const count = map.clusterCaretStopCount(cluster_index) orelse 0;    const stop = map.clusterCaretStop(cluster_index, stop_index) orelse 0;    if (stop_index == 0) {        return .{ .byte_offset = clampedSourceOffset(stop, text_len), .advance = 0 };    }    if (stop_index + 1 >= count) {        return .{ .byte_offset = clampedSourceOffset(stop, text_len), .advance = advance };    }    if (map.clusterLigatureCaret(cluster_index, stop_index - 1)) |caret| {        return .{            .byte_offset = clampedSourceOffset(stop, text_len),            .advance = @as(f32, @floatFromInt(caret.x_offset)) / 64.0,        };    }    const range = map.clusterSourceRange(cluster_index) orelse return .{ .byte_offset = clampedSourceOffset(stop, text_len), .advance = advance };    const start = clampedSourceOffset(range.start, text_len);    const end = clampedSourceOffset(range.end, text_len);    if (end <= start) return .{ .byte_offset = clampedSourceOffset(stop, text_len), .advance = 0 };    const relative = clampedSourceOffset(stop, text_len) - start;    const span = end - start;    return .{        .byte_offset = clampedSourceOffset(stop, text_len),        .advance = advance * (@as(f32, @floatFromInt(relative)) / @as(f32, @floatFromInt(span))),    };}fn byteOffsetInGraphemeSegments(text: []const u8, start: usize, end: usize, fraction: f32) usize {    if (end <= start) return start;    const safe_start = @min(start, text.len);    const safe_end = @min(@max(end, safe_start), text.len);    if (safe_end <= safe_start) return safe_start;    const segment_count = graphemeSegmentCount(text, safe_start, safe_end) catch return nearestEndpointByFraction(safe_start, safe_end, fraction);    if (segment_count == 0) return safe_start;    const target = @min(        @as(usize, @intFromFloat(@round(std.math.clamp(fraction, @as(f32, 0), @as(f32, 1)) * @as(f32, @floatFromInt(segment_count))))),        segment_count,    );    if (target == 0) return safe_start;    if (target >= segment_count) return safe_end;    return graphemeBoundaryAt(text, safe_start, safe_end, target) catch nearestEndpointByFraction(safe_start, safe_end, fraction);}fn graphemeSegmentCount(text: []const u8, start: usize, end: usize) !usize {    var iterator = try unicode.SourceIterator.init(.{ .utf8 = text[start..end] }, @intCast(start));    var state: unicode.GraphemeState = .{};    var count: usize = 0;    while (try iterator.next()) |scalar| {        if (!state.consume(scalar.codepoint)) count += 1;    }    return count;}fn graphemeBoundaryAt(text: []const u8, start: usize, end: usize, target: usize) !usize {    var iterator = try unicode.SourceIterator.init(.{ .utf8 = text[start..end] }, @intCast(start));    var state: unicode.GraphemeState = .{};    var index: usize = 0;    while (try iterator.next()) |scalar| {        if (!state.consume(scalar.codepoint)) {            if (index == target) return @intCast(scalar.source.start);            index += 1;        }    }    return end;}fn nearestEndpointByFraction(start: usize, end: usize, fraction: f32) usize {    if (fraction < 0.5) return start;    return end;}fn clusterAdvance(run: shape.GlyphRun, span: shape.GlyphSpan) f32 {    var advance: f32 = 0;    var index: usize = @intCast(span.start);    const end: usize = @min(@as(usize, @intCast(span.end)), run.glyphs.len);    while (index < end) : (index += 1) {        advance += @as(f32, @floatFromInt(run.glyphs[index].x_advance)) / 64.0;    }    return advance;}test "caret hit test chooses grapheme boundaries inside clusters" {    const glyphs = [_]shape.ShapedGlyph{        .{ .glyph_id = 1, .cluster = 0, .cluster_index = 0, .source_start = 0, .source_end = 2, .source_codepoint_count = 2, .x_advance = 1152, .y_advance = 0, .x_offset = 0, .y_offset = 0 },        .{ .glyph_id = 2, .cluster = 2, .cluster_index = 1, .source_start = 2, .source_end = 3, .x_advance = 576, .y_advance = 0, .x_offset = 0, .y_offset = 0 },    };    const clusters = [_]shape.Cluster{        .{ .source = .{ .start = 0, .end = 2 }, .glyphs = .{ .start = 0, .end = 1 }, .codepoint_count = 2 },        .{ .source = .{ .start = 2, .end = 3 }, .glyphs = .{ .start = 1, .end = 2 } },    };    const run = shape.GlyphRun{        .glyphs = &glyphs,        .clusters = &clusters,        .ligature_carets = &.{},        .total_x_advance = 1728,        .total_y_advance = 0,        .direction = .ltr,        .writing_mode = .horizontal,        .output_order = .visual,    };    try std.testing.expectEqual(@as(f32, 0), advanceForByteOffset(run, 0, 3));    try std.testing.expectEqual(@as(f32, 9), advanceForByteOffset(run, 1, 3));    try std.testing.expectEqual(@as(f32, 18), advanceForByteOffset(run, 2, 3));    try std.testing.expectEqual(@as(f32, 27), advanceForByteOffset(run, 3, 3));    try std.testing.expectEqual(LineHit{ .byte_offset = 0, .advance = 0 }, hitTestAdvance(run, 4.4, "=>x"));    try std.testing.expectEqual(LineHit{ .byte_offset = 1, .advance = 9 }, hitTestAdvance(run, 9, "=>x"));    try std.testing.expectEqual(LineHit{ .byte_offset = 2, .advance = 18 }, hitTestAdvance(run, 16, "=>x"));    try std.testing.expectEqual(LineHit{ .byte_offset = 3, .advance = 27 }, hitTestAdvance(run, 24, "=>x"));}test "caret hit test uses ligature stops" {    const glyphs = [_]shape.ShapedGlyph{        .{            .glyph_id = 9,            .cluster = 0,            .cluster_index = 0,            .source_start = 0,            .source_end = 2,            .source_codepoint_count = 2,            .x_advance = 640,            .y_advance = 0,            .x_offset = 0,            .y_offset = 0,            .glyph_class = .ligature,            .ligature_caret_start = 0,            .ligature_caret_count = 1,        },    };    const clusters = [_]shape.Cluster{        .{ .source = .{ .start = 0, .end = 2 }, .glyphs = .{ .start = 0, .end = 1 }, .codepoint_count = 2 },    };    const carets = [_]shape.LigatureCaret{        .{ .x_offset = 384 },    };    const run = shape.GlyphRun{        .glyphs = &glyphs,        .clusters = &clusters,        .ligature_carets = &carets,        .total_x_advance = 640,        .total_y_advance = 0,        .direction = .ltr,        .writing_mode = .horizontal,        .output_order = .visual,    };    try std.testing.expectEqual(@as(f32, 6), advanceForByteOffset(run, 1, 2));    try std.testing.expectEqual(LineHit{ .byte_offset = 1, .advance = 6 }, hitTestAdvance(run, 5.1, "fi"));    try std.testing.expectEqual(LineHit{ .byte_offset = 2, .advance = 10 }, hitTestAdvance(run, 8.3, "fi"));}test "caret hit test uses OpenType GDEF ligature stops" {    const text = "fi";    const bytes = try fixtures.createWithGsubLigatureAndGdefCarets(std.testing.allocator);    defer std.testing.allocator.free(bytes);    var shaper = shape.Font.initFromBytes(bytes.ptr, bytes.len) orelse return error.TestUnexpectedResult;    defer shaper.deinit();    shaper.setPixelHeightScale(20);    var context = shape.Context.init(std.testing.allocator, .{});    defer context.deinit();    var output = try shape.Output.init(std.testing.allocator, .{        .max_glyphs = 256,        .max_ligature_carets = 256,    });    defer output.deinit(std.testing.allocator);    try context.shapeRun(.{ .font = &shaper, .text = .{ .utf8 = text } }, &output);    const run = output.run();    const map = run.clusterMap();    const caret = map.clusterLigatureCaret(0, 0).?;    try std.testing.expectEqual(@as(usize, 1), run.glyphs.len);    try std.testing.expectEqual(@as(usize, 1), run.ligature_carets.len);    try std.testing.expectEqual(false, caret.synthesized);    try std.testing.expectEqual(@as(f32, 5), advanceForByteOffset(run, 1, text.len));    try std.testing.expectEqual(LineHit{ .byte_offset = 1, .advance = 5 }, hitTestAdvance(run, 4.6, text));    try std.testing.expectEqual(LineHit{ .byte_offset = 2, .advance = 10 }, hitTestAdvance(run, 7.6, text));}

Source: lib/filigree/src/root.zig:10

zig
pub const caret = @import("caret.zig");

Audit

Definitions5
Public names5
Members2
Version26.7.0
Revisiondaab053ee433