fix: vertical positioning of spans
This commit is contained in:
parent
78951ce42e
commit
6f318242d7
|
@ -139,7 +139,9 @@ fn Prototype(comptime Self: type) type {
|
|||
);
|
||||
}
|
||||
|
||||
/// Draw the given span of text at the given position.
|
||||
/// Draw the given span of text at the given position, where the position is the start
|
||||
/// of the span baseline. To work with the top-left corner of the span, you should
|
||||
/// use origin_off or layoutPosition() accordingly.
|
||||
/// The caller asserts that the font of the span is from the same platform as this painter.
|
||||
pub fn span(self: *Self, pos: Position, text_span: Span) !void {
|
||||
if (zenolith.debug_render) {
|
||||
|
@ -174,7 +176,7 @@ fn Prototype(comptime Self: type) type {
|
|||
// Baseline
|
||||
try self.rect(
|
||||
.{
|
||||
.pos = pos.add(.{ .x = 0, .y = @intCast(text_span.baseline_y) }),
|
||||
.pos = pos,
|
||||
.size = .{ .width = text_span.baseline_width, .height = 2 },
|
||||
},
|
||||
Color.fromInt(0x00ff00ff),
|
||||
|
|
47
src/text.zig
47
src/text.zig
|
@ -1,13 +1,56 @@
|
|||
const Position = @import("layout/Position.zig");
|
||||
const Size = @import("layout/Size.zig");
|
||||
|
||||
test {
|
||||
_ = Chunk;
|
||||
_ = Font;
|
||||
_ = Glyph;
|
||||
_ = Span;
|
||||
_ = Style;
|
||||
}
|
||||
|
||||
pub const Chunk = @import("text/Chunk.zig");
|
||||
pub const Font = @import("text/font.zig").Font;
|
||||
pub const Glyph = @import("text/Glyph.zig");
|
||||
pub const Span = @import("text/Span.zig");
|
||||
pub const Style = @import("text/Style.zig");
|
||||
|
||||
/// A glyph represents the smallet possible unit of text with positioning information.
|
||||
/// It is used for text layout and rendering.
|
||||
pub const Glyph = struct {
|
||||
/// The unicode codepoint this glyph corresponds to.
|
||||
/// This is intentionally only one codepoint. While Unicode glyphs an consist of up to 7
|
||||
/// codepoints (citation needed), these are not supported. Support might be implemented in the
|
||||
/// future, but this is currently not planned.
|
||||
codepoint: u21,
|
||||
|
||||
/// The size of this glyph. This is typically the boundig box of the glyph as drawn.
|
||||
size: Size,
|
||||
|
||||
/// The offset of the character's top-left corner from the baseline of the text span.
|
||||
/// When performing span layout, this is added onto the position of the cursor.
|
||||
/// This being 0/0 will thus result in the glyph being aligned below the baseline.
|
||||
/// This is typically has a negative Y offset to align the glyph above the baseline.
|
||||
bearing: Position,
|
||||
|
||||
/// How much the cursor should move to the right after this glyph.
|
||||
advance: u31,
|
||||
};
|
||||
|
||||
/// Information about the height of a font at a certain size. You may obtain this using
|
||||
/// Font.heightMetrics.
|
||||
pub const HeightMetrics = struct {
|
||||
/// The distance two baselines should be offset from another.
|
||||
/// This thus also represents the maximum height a glyph may have ABOVE the baseline.
|
||||
y_offset: u31,
|
||||
|
||||
/// The maximum space a glyph may take up below the baseline.
|
||||
/// Note that this is space is not inserted between lines,
|
||||
/// but is instead used as padding below the last line of a chunk.
|
||||
bottom_padding: u31,
|
||||
|
||||
/// Returns the total height a line will have.
|
||||
/// This is simply the sum of y_offset and bottom_padding.
|
||||
pub inline fn totalHeight(self: HeightMetrics) u31 {
|
||||
return self.y_offset + self.bottom_padding;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
//! It may do wrapping on span boundaries depending on the wrap_mode.
|
||||
//! You may access the spans field to modify children, but layout must be called again afterwards.
|
||||
const std = @import("std");
|
||||
const zenolith = @import("../main.zig");
|
||||
|
||||
const Position = @import("../layout/Position.zig");
|
||||
const Size = @import("../layout/Size.zig");
|
||||
|
@ -114,7 +115,7 @@ pub fn layout(self: *Chunk, opts: LayoutOptions) void {
|
|||
};
|
||||
|
||||
if (should_wrap) {
|
||||
cursor.y += self.offsetLineByHeight(line_start_idx, i);
|
||||
cursor.y += self.offsetLineByHeight(line_start_idx, i).y_offset;
|
||||
line_start_idx = i;
|
||||
if (cursor.x > self.size.width) self.size.width = @intCast(cursor.x);
|
||||
cursor.x = -span.span.origin_off.x;
|
||||
|
@ -122,28 +123,37 @@ pub fn layout(self: *Chunk, opts: LayoutOptions) void {
|
|||
|
||||
span.position = .{
|
||||
.x = cursor.x,
|
||||
.y = cursor.y - span.span.baseline_y,
|
||||
.y = cursor.y,
|
||||
};
|
||||
|
||||
cursor.x += span.span.baseline_width;
|
||||
}
|
||||
cursor.y += self.offsetLineByHeight(line_start_idx, self.spans.items.len);
|
||||
const last_metrics = self.offsetLineByHeight(line_start_idx, self.spans.items.len);
|
||||
cursor.y += last_metrics.y_offset;
|
||||
|
||||
self.size.height = @intCast(cursor.y);
|
||||
self.size.height = @intCast(cursor.y + last_metrics.bottom_padding);
|
||||
if (cursor.x > self.size.width) self.size.width = @intCast(cursor.x);
|
||||
}
|
||||
|
||||
/// Offsets all chunks in the given range downwards by their line height and returns that line height.
|
||||
fn offsetLineByHeight(self: *const Chunk, start_idx: usize, end_idx: usize) u31 {
|
||||
var max_height: u31 = 0;
|
||||
/// Offsets all chunks in the given range downwards by their line y_offset and returns that line's
|
||||
/// height metrics..
|
||||
fn offsetLineByHeight(self: *const Chunk, start_idx: usize, end_idx: usize) zenolith.text.HeightMetrics {
|
||||
var max = zenolith.text.HeightMetrics{
|
||||
.y_offset = 0,
|
||||
.bottom_padding = 0,
|
||||
};
|
||||
|
||||
for (self.spans.items[start_idx..end_idx]) |span| {
|
||||
max_height = @max(max_height, span.span.font.yOffset(span.span.style.size));
|
||||
const metrics = span.span.font.heightMetrics(span.span.style.size);
|
||||
max = .{
|
||||
.y_offset = @max(max.y_offset, metrics.y_offset),
|
||||
.bottom_padding = @max(max.bottom_padding, metrics.bottom_padding),
|
||||
};
|
||||
}
|
||||
|
||||
for (self.spans.items[start_idx..end_idx]) |*span| {
|
||||
span.position.y += max_height;
|
||||
span.position.y += max.y_offset;
|
||||
}
|
||||
|
||||
return max_height;
|
||||
return max;
|
||||
}
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
//! A glyph represents the smallet possible unit of text with positioning information.
|
||||
//! It is used for text layout and rendering.
|
||||
const Position = @import("../layout/Position.zig");
|
||||
const Size = @import("../layout/Size.zig");
|
||||
|
||||
/// The unicode codepoint this glyph corresponds to.
|
||||
/// This is intentionally only one codepoint. While Unicode glyphs an consist of up to 7
|
||||
/// codepoints (citation needed), these are not supported. Support might be implemented in the
|
||||
/// future, but this is currently not planned.
|
||||
codepoint: u21,
|
||||
|
||||
/// The size of this glyph. This is typically the boundig box of the glyph as drawn.
|
||||
size: Size,
|
||||
|
||||
/// The offset of the character's top-left corner from the baseline of the text span.
|
||||
/// When performing span layout, this is added onto the position of the cursor.
|
||||
/// This being 0/0 will thus result in the glyph being aligned below the baseline.
|
||||
/// This is typically has a negative Y offset to align the glyph above the baseline.
|
||||
bearing: Position,
|
||||
|
||||
/// How much the cursor should move to the right after this glyph.
|
||||
advance: u31,
|
|
@ -1,9 +1,9 @@
|
|||
//! A span is a one-line piece of text. It consists of glyphs and performs single-line layout on them.
|
||||
//! It also has information on the font, style, color as well as bounding boxes.
|
||||
const std = @import("std");
|
||||
const zenolith = @import("../main.zig");
|
||||
|
||||
const Font = @import("font.zig").Font;
|
||||
const Glyph = @import("Glyph.zig");
|
||||
const Position = @import("../layout/Position.zig");
|
||||
const Size = @import("../layout/Size.zig");
|
||||
const Style = @import("Style.zig");
|
||||
|
@ -12,15 +12,12 @@ glyphs: std.ArrayList(PositionedGlyph),
|
|||
font: *Font,
|
||||
style: Style,
|
||||
|
||||
/// The position offset from this span's position to it's origin.
|
||||
/// Spans may have negative origins relative to their position when glyphs have a negative bearing.
|
||||
/// The top left corner of this span would be calculated as position + origin_off.
|
||||
/// This represents an offset from relative 0/0 to the start of the baseline.
|
||||
/// This means that the y component is the height of the highest glyph (minus the below-baseline part).
|
||||
/// The x component is a horizontal offset the first glyph may have. This is commonly the case with
|
||||
/// letters such as 'j' where the hook would be a little to the left of where the text should be aligned.
|
||||
origin_off: Position = Position.zero,
|
||||
|
||||
/// The Y-coordinate of the baseline of this span relative to it's top.
|
||||
/// The chunker uses this for aligning spans.
|
||||
baseline_y: u31 = 0,
|
||||
|
||||
/// The width of the baseline. This is calculated as the distance the cursor moved during layout.
|
||||
/// This does not always correspond to renderSize().width due to padding between glyphs.
|
||||
baseline_width: u31 = 0,
|
||||
|
@ -28,7 +25,7 @@ baseline_width: u31 = 0,
|
|||
const Span = @This();
|
||||
|
||||
pub const PositionedGlyph = struct {
|
||||
glyph: Glyph,
|
||||
glyph: zenolith.text.Glyph,
|
||||
position: Position,
|
||||
};
|
||||
|
||||
|
@ -93,12 +90,13 @@ pub fn layout(self: *Span) void {
|
|||
cursor.x += pglyph.glyph.advance;
|
||||
}
|
||||
|
||||
for (self.glyphs.items) |*pglyph| {
|
||||
pglyph.position.y -= min_y;
|
||||
}
|
||||
//for (self.glyphs.items) |*pglyph| {
|
||||
// pglyph.position.y -= min_y;
|
||||
//}
|
||||
|
||||
self.origin_off = if (self.glyphs.items.len > 0) self.glyphs.items[0].position else Position.zero;
|
||||
self.baseline_y = @intCast(-min_y);
|
||||
self.origin_off.y = -min_y;
|
||||
//self.baseline_y = @intCast(-min_y);
|
||||
self.baseline_width = @intCast(cursor.x);
|
||||
}
|
||||
|
||||
|
@ -121,3 +119,23 @@ pub fn renderSize(self: Span) Size {
|
|||
|
||||
return max.size();
|
||||
}
|
||||
|
||||
/// The size to be used when this span is laid out stand-alone. It will always fully contain all
|
||||
/// glayphs, but unlike render size, it will include padding as required by the font.
|
||||
/// Use this if you want to include spans in widgets.
|
||||
pub fn layoutSize(self: Span) Size {
|
||||
return .{
|
||||
.width = @intCast(@as(i32, self.baseline_width) + self.origin_off.x),
|
||||
.height = self.font.heightMetrics(self.style.size).totalHeight(),
|
||||
};
|
||||
}
|
||||
|
||||
/// This is similar to self.origin_off, with the difference that the the baseline won't be
|
||||
/// positioned in accordance with the largest glyph but instead using the font height metrics.
|
||||
/// This is typically preferred, but may overly complex in some situations.
|
||||
pub fn layoutOffset(self: Span) Position {
|
||||
return .{
|
||||
.x = self.origin_off.x,
|
||||
.y = self.font.heightMetrics(self.style.size).y_offset,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,23 +5,28 @@ const zenolith = @import("../main.zig");
|
|||
|
||||
const Size = @import("../layout/Size.zig");
|
||||
const Style = @import("../text/Style.zig");
|
||||
const Glyph = @import("Glyph.zig");
|
||||
|
||||
fn FontPrototype(comptime Self: type) type {
|
||||
std.debug.assert(Self == Font);
|
||||
|
||||
return struct {
|
||||
/// For a given font size in pixels, returns the offset between lines.
|
||||
pub fn yOffset(self: *Self, size: u31) u31 {
|
||||
return statspatch.implcall(self, .ptr, "yOffset", u31, .{size});
|
||||
/// For a given font size in pixels, returns the height metrics.
|
||||
pub fn heightMetrics(self: *Self, size: u31) zenolith.text.HeightMetrics {
|
||||
return statspatch.implcall(
|
||||
self,
|
||||
.ptr,
|
||||
"heightMetrics",
|
||||
zenolith.text.HeightMetrics,
|
||||
.{size},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn getGlyph(self: *Self, codepoint: u21, style: Style) !Glyph {
|
||||
pub fn getGlyph(self: *Self, codepoint: u21, style: Style) !zenolith.text.Glyph {
|
||||
return try statspatch.implcall(
|
||||
self,
|
||||
.ptr,
|
||||
"getGlyph",
|
||||
anyerror!Glyph,
|
||||
anyerror!zenolith.text.Glyph,
|
||||
.{ codepoint, style },
|
||||
);
|
||||
}
|
||||
|
|
|
@ -51,10 +51,7 @@ pub fn treevent(self: *Button, selfw: *Widget, tv: anytype) !void {
|
|||
self.span.?.layout();
|
||||
}
|
||||
|
||||
selfw.data.size = layout.Size.two(style.padding * 2).add(.{
|
||||
.width = self.span.?.baseline_width,
|
||||
.height = self.span.?.font.yOffset(self.span.?.style.size),
|
||||
});
|
||||
selfw.data.size = layout.Size.two(style.padding * 2).add(self.span.?.layoutSize());
|
||||
},
|
||||
|
||||
treev.Draw => {
|
||||
|
@ -82,10 +79,7 @@ pub fn treevent(self: *Button, selfw: *Widget, tv: anytype) !void {
|
|||
);
|
||||
|
||||
try tv.painter.span(
|
||||
selfw.data.position.add(layout.Position.two(style.padding)).add(.{
|
||||
.x = 0,
|
||||
.y = self.span.?.font.yOffset(self.span.?.style.size) - self.span.?.baseline_y,
|
||||
}),
|
||||
selfw.data.position.add(layout.Position.two(style.padding)).add(self.span.?.layoutOffset()),
|
||||
self.span.?,
|
||||
);
|
||||
},
|
||||
|
|
|
@ -49,10 +49,7 @@ pub fn treevent(self: *Label, selfw: *Widget, tv: anytype) !void {
|
|||
});
|
||||
},
|
||||
treev.LayoutSize => {
|
||||
selfw.data.size = tv.constraints.clamp(.{
|
||||
.width = self.span.?.baseline_width,
|
||||
.height = self.span.?.font.yOffset(self.span.?.style.size),
|
||||
});
|
||||
selfw.data.size = tv.constraints.clamp(self.span.?.layoutSize());
|
||||
},
|
||||
treev.Draw => {
|
||||
const style = selfw.getAttreebute(LabelStyle) orelse
|
||||
|
@ -61,10 +58,7 @@ pub fn treevent(self: *Label, selfw: *Widget, tv: anytype) !void {
|
|||
self.span.?.style = style.font_style;
|
||||
self.span.?.layout();
|
||||
|
||||
try tv.painter.span(selfw.data.position.add(.{
|
||||
.x = 0,
|
||||
.y = self.span.?.font.yOffset(self.span.?.style.size) - self.span.?.baseline_y,
|
||||
}), self.span.?);
|
||||
try tv.painter.span(selfw.data.position.add(self.span.?.layoutOffset()), self.span.?);
|
||||
},
|
||||
else => try tv.dispatch(selfw),
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue