mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 08:16:13 +03:00
Ligatures
This introduces a naive first pass at integrating ligatures. The basic ligatures (such as "==" in some fonts) work great. Skin-toned emoji are struggling a bit. This isn't the most performant way to do this, either, and I plan on improving that.
This commit is contained in:
@ -37,6 +37,9 @@ layout (location = 5) in vec4 bg_color_in;
|
|||||||
// the entire terminal grid in a single GPU pass.
|
// the entire terminal grid in a single GPU pass.
|
||||||
layout (location = 6) in uint mode_in;
|
layout (location = 6) in uint mode_in;
|
||||||
|
|
||||||
|
// The width in cells of this item.
|
||||||
|
layout (location = 7) in uint grid_width;
|
||||||
|
|
||||||
// The background or foreground color for the fragment, depending on
|
// The background or foreground color for the fragment, depending on
|
||||||
// whether this is a background or foreground pass.
|
// whether this is a background or foreground pass.
|
||||||
flat out vec4 color;
|
flat out vec4 color;
|
||||||
@ -133,7 +136,7 @@ void main() {
|
|||||||
// The "+ 3" here is to give some wiggle room for fonts that are
|
// The "+ 3" here is to give some wiggle room for fonts that are
|
||||||
// BARELY over it.
|
// BARELY over it.
|
||||||
vec2 glyph_size_downsampled = glyph_size;
|
vec2 glyph_size_downsampled = glyph_size;
|
||||||
if (glyph_size.x > (cell_size.x + 3)) {
|
if (glyph_size.y > cell_size.y + 2) {
|
||||||
glyph_size_downsampled.x = cell_size_scaled.x;
|
glyph_size_downsampled.x = cell_size_scaled.x;
|
||||||
glyph_size_downsampled.y = glyph_size.y * (glyph_size_downsampled.x / glyph_size.x);
|
glyph_size_downsampled.y = glyph_size.y * (glyph_size_downsampled.x / glyph_size.x);
|
||||||
glyph_offset_calc.y = glyph_offset.y * (glyph_size_downsampled.x / glyph_size.x);
|
glyph_offset_calc.y = glyph_offset.y * (glyph_size_downsampled.x / glyph_size.x);
|
||||||
|
75
src/Grid.zig
75
src/Grid.zig
@ -109,6 +109,9 @@ const GPUCell = struct {
|
|||||||
|
|
||||||
/// uint mode
|
/// uint mode
|
||||||
mode: GPUCellMode,
|
mode: GPUCellMode,
|
||||||
|
|
||||||
|
/// The width in grid cells that a rendering takes.
|
||||||
|
grid_width: u16 = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const GPUCellMode = enum(u8) {
|
const GPUCellMode = enum(u8) {
|
||||||
@ -224,6 +227,8 @@ pub fn init(
|
|||||||
try vbobind.attributeAdvanced(5, 4, gl.c.GL_UNSIGNED_BYTE, false, @sizeOf(GPUCell), offset);
|
try vbobind.attributeAdvanced(5, 4, gl.c.GL_UNSIGNED_BYTE, false, @sizeOf(GPUCell), offset);
|
||||||
offset += 4 * @sizeOf(u8);
|
offset += 4 * @sizeOf(u8);
|
||||||
try vbobind.attributeIAdvanced(6, 1, gl.c.GL_UNSIGNED_BYTE, @sizeOf(GPUCell), offset);
|
try vbobind.attributeIAdvanced(6, 1, gl.c.GL_UNSIGNED_BYTE, @sizeOf(GPUCell), offset);
|
||||||
|
offset += 1 * @sizeOf(u8);
|
||||||
|
try vbobind.attributeAdvanced(7, 1, gl.c.GL_UNSIGNED_SHORT, false, @sizeOf(GPUCell), offset);
|
||||||
try vbobind.enableAttribArray(0);
|
try vbobind.enableAttribArray(0);
|
||||||
try vbobind.enableAttribArray(1);
|
try vbobind.enableAttribArray(1);
|
||||||
try vbobind.enableAttribArray(2);
|
try vbobind.enableAttribArray(2);
|
||||||
@ -231,6 +236,7 @@ pub fn init(
|
|||||||
try vbobind.enableAttribArray(4);
|
try vbobind.enableAttribArray(4);
|
||||||
try vbobind.enableAttribArray(5);
|
try vbobind.enableAttribArray(5);
|
||||||
try vbobind.enableAttribArray(6);
|
try vbobind.enableAttribArray(6);
|
||||||
|
try vbobind.enableAttribArray(7);
|
||||||
try vbobind.attributeDivisor(0, 1);
|
try vbobind.attributeDivisor(0, 1);
|
||||||
try vbobind.attributeDivisor(1, 1);
|
try vbobind.attributeDivisor(1, 1);
|
||||||
try vbobind.attributeDivisor(2, 1);
|
try vbobind.attributeDivisor(2, 1);
|
||||||
@ -238,6 +244,7 @@ pub fn init(
|
|||||||
try vbobind.attributeDivisor(4, 1);
|
try vbobind.attributeDivisor(4, 1);
|
||||||
try vbobind.attributeDivisor(5, 1);
|
try vbobind.attributeDivisor(5, 1);
|
||||||
try vbobind.attributeDivisor(6, 1);
|
try vbobind.attributeDivisor(6, 1);
|
||||||
|
try vbobind.attributeDivisor(7, 1);
|
||||||
|
|
||||||
// Build our texture
|
// Build our texture
|
||||||
const tex = try gl.Texture.create();
|
const tex = try gl.Texture.create();
|
||||||
@ -341,17 +348,31 @@ pub fn rebuildCells(self: *Grid, term: *Terminal) !void {
|
|||||||
// We've written no data to the GPU, refresh it all
|
// We've written no data to the GPU, refresh it all
|
||||||
self.gl_cells_written = 0;
|
self.gl_cells_written = 0;
|
||||||
|
|
||||||
|
// Create a text shaper we'll use for the screen
|
||||||
|
var shape_buf = try self.alloc.alloc(font.Shaper.Cell, term.screen.cols * 2);
|
||||||
|
defer self.alloc.free(shape_buf);
|
||||||
|
var shaper = try font.Shaper.init(&self.font_group, shape_buf);
|
||||||
|
defer shaper.deinit();
|
||||||
|
|
||||||
// Build each cell
|
// Build each cell
|
||||||
var rowIter = term.screen.rowIterator(.viewport);
|
var rowIter = term.screen.rowIterator(.viewport);
|
||||||
var y: usize = 0;
|
var y: usize = 0;
|
||||||
while (rowIter.next()) |row| {
|
while (rowIter.next()) |row| {
|
||||||
defer y += 1;
|
defer y += 1;
|
||||||
|
|
||||||
var cellIter = row.cellIterator();
|
// Split our row into runs and shape each one.
|
||||||
var x: usize = 0;
|
var iter = shaper.runIterator(row);
|
||||||
while (cellIter.next()) |cell| {
|
while (try iter.next(self.alloc)) |run| {
|
||||||
defer x += 1;
|
for (try shaper.shape(run)) |shaper_cell| {
|
||||||
assert(try self.updateCell(term, cell, x, y));
|
assert(try self.updateCell(
|
||||||
|
term,
|
||||||
|
row[shaper_cell.x],
|
||||||
|
shaper_cell,
|
||||||
|
run,
|
||||||
|
shaper_cell.x,
|
||||||
|
y,
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,6 +419,7 @@ fn addCursor(self: *Grid, term: *Terminal) void {
|
|||||||
.mode = mode,
|
.mode = mode,
|
||||||
.grid_col = @intCast(u16, term.screen.cursor.x),
|
.grid_col = @intCast(u16, term.screen.cursor.x),
|
||||||
.grid_row = @intCast(u16, term.screen.cursor.y),
|
.grid_row = @intCast(u16, term.screen.cursor.y),
|
||||||
|
.grid_width = if (cell.attrs.wide) 2 else 1,
|
||||||
.fg_r = 0,
|
.fg_r = 0,
|
||||||
.fg_g = 0,
|
.fg_g = 0,
|
||||||
.fg_b = 0,
|
.fg_b = 0,
|
||||||
@ -417,6 +439,8 @@ pub fn updateCell(
|
|||||||
self: *Grid,
|
self: *Grid,
|
||||||
term: *Terminal,
|
term: *Terminal,
|
||||||
cell: terminal.Screen.Cell,
|
cell: terminal.Screen.Cell,
|
||||||
|
shaper_cell: font.Shaper.Cell,
|
||||||
|
shaper_run: font.Shaper.TextRun,
|
||||||
x: usize,
|
x: usize,
|
||||||
y: usize,
|
y: usize,
|
||||||
) !bool {
|
) !bool {
|
||||||
@ -471,9 +495,6 @@ pub fn updateCell(
|
|||||||
break :colors res;
|
break :colors res;
|
||||||
};
|
};
|
||||||
|
|
||||||
// If we are a trailing spacer, we never render anything.
|
|
||||||
if (cell.attrs.wide_spacer_tail) return true;
|
|
||||||
|
|
||||||
// Calculate the amount of space we need in the cells list.
|
// Calculate the amount of space we need in the cells list.
|
||||||
const needed = needed: {
|
const needed = needed: {
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
@ -496,6 +517,7 @@ pub fn updateCell(
|
|||||||
.mode = mode,
|
.mode = mode,
|
||||||
.grid_col = @intCast(u16, x),
|
.grid_col = @intCast(u16, x),
|
||||||
.grid_row = @intCast(u16, y),
|
.grid_row = @intCast(u16, y),
|
||||||
|
.grid_width = shaper_cell.width,
|
||||||
.glyph_x = 0,
|
.glyph_x = 0,
|
||||||
.glyph_y = 0,
|
.glyph_y = 0,
|
||||||
.glyph_width = 0,
|
.glyph_width = 0,
|
||||||
@ -515,37 +537,16 @@ pub fn updateCell(
|
|||||||
|
|
||||||
// If the cell is empty then we draw nothing in the box.
|
// If the cell is empty then we draw nothing in the box.
|
||||||
if (!cell.empty()) {
|
if (!cell.empty()) {
|
||||||
// Determine our glyph styling
|
|
||||||
const style: font.Style = if (cell.attrs.bold)
|
|
||||||
.bold
|
|
||||||
else
|
|
||||||
.regular;
|
|
||||||
|
|
||||||
var mode: GPUCellMode = .fg;
|
|
||||||
|
|
||||||
// Get the glyph that we're going to use. We first try what the cell
|
|
||||||
// wants, then the Unicode replacement char, then finally a space.
|
|
||||||
const FontInfo = struct { index: font.Group.FontIndex, ch: u32 };
|
|
||||||
const font_info: FontInfo = font_info: {
|
|
||||||
var chars = [_]u32{ @intCast(u32, cell.char), 0xFFFD, ' ' };
|
|
||||||
for (chars) |char| {
|
|
||||||
if (try self.font_group.indexForCodepoint(self.alloc, style, char)) |idx| {
|
|
||||||
break :font_info FontInfo{
|
|
||||||
.index = idx,
|
|
||||||
.ch = char,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@panic("all fonts require at least space");
|
|
||||||
};
|
|
||||||
|
|
||||||
// Render
|
// Render
|
||||||
const face = self.font_group.group.faceFromIndex(font_info.index);
|
const face = self.font_group.group.faceFromIndex(shaper_run.font_index);
|
||||||
const glyph_index = face.glyphIndex(font_info.ch).?;
|
const glyph = try self.font_group.renderGlyph(
|
||||||
const glyph = try self.font_group.renderGlyph(self.alloc, font_info.index, glyph_index);
|
self.alloc,
|
||||||
|
shaper_run.font_index,
|
||||||
|
shaper_cell.glyph_index,
|
||||||
|
);
|
||||||
|
|
||||||
// If we're rendering a color font, we use the color atlas
|
// If we're rendering a color font, we use the color atlas
|
||||||
|
var mode: GPUCellMode = .fg;
|
||||||
if (face.hasColor()) mode = .fg_color;
|
if (face.hasColor()) mode = .fg_color;
|
||||||
|
|
||||||
// If the cell is wide, we need to note that in the mode
|
// If the cell is wide, we need to note that in the mode
|
||||||
@ -555,6 +556,7 @@ pub fn updateCell(
|
|||||||
.mode = mode,
|
.mode = mode,
|
||||||
.grid_col = @intCast(u16, x),
|
.grid_col = @intCast(u16, x),
|
||||||
.grid_row = @intCast(u16, y),
|
.grid_row = @intCast(u16, y),
|
||||||
|
.grid_width = shaper_cell.width,
|
||||||
.glyph_x = glyph.atlas_x,
|
.glyph_x = glyph.atlas_x,
|
||||||
.glyph_y = glyph.atlas_y,
|
.glyph_y = glyph.atlas_y,
|
||||||
.glyph_width = glyph.width,
|
.glyph_width = glyph.width,
|
||||||
@ -580,6 +582,7 @@ pub fn updateCell(
|
|||||||
.mode = mode,
|
.mode = mode,
|
||||||
.grid_col = @intCast(u16, x),
|
.grid_col = @intCast(u16, x),
|
||||||
.grid_row = @intCast(u16, y),
|
.grid_row = @intCast(u16, y),
|
||||||
|
.grid_width = shaper_cell.width,
|
||||||
.glyph_x = 0,
|
.glyph_x = 0,
|
||||||
.glyph_y = 0,
|
.glyph_y = 0,
|
||||||
.glyph_width = 0,
|
.glyph_width = 0,
|
||||||
|
@ -22,10 +22,16 @@ group: *GroupCache,
|
|||||||
/// calls to prevent allocations.
|
/// calls to prevent allocations.
|
||||||
hb_buf: harfbuzz.Buffer,
|
hb_buf: harfbuzz.Buffer,
|
||||||
|
|
||||||
pub fn init(group: *GroupCache) !Shaper {
|
/// The shared memory used for shaping results.
|
||||||
|
cell_buf: []Cell,
|
||||||
|
|
||||||
|
/// The cell_buf argument is the buffer to use for storing shaped results.
|
||||||
|
/// This should be at least the number of columns in the terminal.
|
||||||
|
pub fn init(group: *GroupCache, cell_buf: []Cell) !Shaper {
|
||||||
return Shaper{
|
return Shaper{
|
||||||
.group = group,
|
.group = group,
|
||||||
.hb_buf = try harfbuzz.Buffer.create(),
|
.hb_buf = try harfbuzz.Buffer.create(),
|
||||||
|
.cell_buf = cell_buf,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,27 +50,85 @@ pub fn runIterator(self: *Shaper, row: terminal.Screen.Row) RunIterator {
|
|||||||
/// text run that was iterated since the text run does share state with the
|
/// text run that was iterated since the text run does share state with the
|
||||||
/// Shaper struct.
|
/// Shaper struct.
|
||||||
///
|
///
|
||||||
/// NOTE: there is no return value here yet because its still WIP
|
/// The return value is only valid until the next shape call is called.
|
||||||
pub fn shape(self: Shaper, run: TextRun) void {
|
///
|
||||||
|
/// If there is not enough space in the cell buffer, an error is returned.
|
||||||
|
pub fn shape(self: *Shaper, run: TextRun) ![]Cell {
|
||||||
const face = self.group.group.faceFromIndex(run.font_index);
|
const face = self.group.group.faceFromIndex(run.font_index);
|
||||||
harfbuzz.shape(face.hb_font, self.hb_buf, null);
|
harfbuzz.shape(face.hb_font, self.hb_buf, null);
|
||||||
|
|
||||||
|
// If our buffer is empty, we short-circuit the rest of the work
|
||||||
|
// return nothing.
|
||||||
|
if (self.hb_buf.getLength() == 0) return self.cell_buf[0..0];
|
||||||
const info = self.hb_buf.getGlyphInfos();
|
const info = self.hb_buf.getGlyphInfos();
|
||||||
const pos = self.hb_buf.getGlyphPositions() orelse return;
|
const pos = self.hb_buf.getGlyphPositions() orelse return error.HarfbuzzFailed;
|
||||||
|
|
||||||
// This is perhaps not true somewhere, but we currently assume it is true.
|
// This is perhaps not true somewhere, but we currently assume it is true.
|
||||||
// If it isn't true, I'd like to catch it and learn more.
|
// If it isn't true, I'd like to catch it and learn more.
|
||||||
assert(info.len == pos.len);
|
assert(info.len == pos.len);
|
||||||
|
|
||||||
// log.warn("info={} pos={}", .{ info.len, pos.len });
|
// Convert all our info/pos to cells and set it.
|
||||||
// for (info) |v, i| {
|
if (info.len > self.cell_buf.len) return error.OutOfMemory;
|
||||||
// log.warn("info {} = {}", .{ i, v });
|
// log.debug("info={} pos={}", .{ info.len, pos.len });
|
||||||
// }
|
|
||||||
|
// x is the column that we currently occupy. We start at the offset.
|
||||||
|
var x: u16 = run.offset;
|
||||||
|
|
||||||
|
for (info) |v, i| {
|
||||||
|
// The number of codepoints is used as the cell "width". If
|
||||||
|
// we're the last cell, this is remaining otherwise we use cluster numbers
|
||||||
|
// to detect since we set the cluster number to the column it
|
||||||
|
// originated.
|
||||||
|
const cp_width = if (i == info.len - 1)
|
||||||
|
run.max_cluster - v.cluster
|
||||||
|
else width: {
|
||||||
|
const next_cluster = info[i + 1].cluster;
|
||||||
|
break :width next_cluster - v.cluster;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.cell_buf[i] = .{
|
||||||
|
.x = x,
|
||||||
|
.glyph_index = v.codepoint,
|
||||||
|
.width = if (cp_width > 2) 2 else @intCast(u8, cp_width),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Increase x by the amount of codepoints we replaced so that
|
||||||
|
// we retain the grid.
|
||||||
|
x += @intCast(u16, cp_width);
|
||||||
|
|
||||||
|
// log.debug("i={} info={} pos={} cell={}", .{ i, v, pos[i], self.cell_buf[i] });
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.cell_buf[0..info.len];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const Cell = struct {
|
||||||
|
/// The column that this cell occupies. Since a set of shaper cells is
|
||||||
|
/// always on the same line, only the X is stored. It is expected the
|
||||||
|
/// caller has access to the original screen cell.
|
||||||
|
x: u16,
|
||||||
|
|
||||||
|
/// The glyph index for this cell. The font index to use alongside
|
||||||
|
/// this cell is available in the text run.
|
||||||
|
glyph_index: u32,
|
||||||
|
|
||||||
|
/// The width that this cell consumes.
|
||||||
|
width: u8,
|
||||||
|
};
|
||||||
|
|
||||||
/// A single text run. A text run is only valid for one Shaper and
|
/// A single text run. A text run is only valid for one Shaper and
|
||||||
/// until the next run is created.
|
/// until the next run is created.
|
||||||
pub const TextRun = struct {
|
pub const TextRun = struct {
|
||||||
|
/// The offset in the row where this run started
|
||||||
|
offset: u16,
|
||||||
|
|
||||||
|
/// The total number of cells produced by this run.
|
||||||
|
cells: u16,
|
||||||
|
|
||||||
|
/// The maximum cluster value used
|
||||||
|
max_cluster: u16,
|
||||||
|
|
||||||
|
/// The font index to use for the glyphs of this run.
|
||||||
font_index: Group.FontIndex,
|
font_index: Group.FontIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -85,6 +149,7 @@ pub const RunIterator = struct {
|
|||||||
|
|
||||||
// Go through cell by cell and accumulate while we build our run.
|
// Go through cell by cell and accumulate while we build our run.
|
||||||
var j: usize = self.i;
|
var j: usize = self.i;
|
||||||
|
var max_cluster: usize = j;
|
||||||
while (j < self.row.lenCells()) : (j += 1) {
|
while (j < self.row.lenCells()) : (j += 1) {
|
||||||
const cell = self.row.getCell(j);
|
const cell = self.row.getCell(j);
|
||||||
|
|
||||||
@ -96,8 +161,19 @@ pub const RunIterator = struct {
|
|||||||
else
|
else
|
||||||
.regular;
|
.regular;
|
||||||
|
|
||||||
// Determine the font for this cell
|
// Determine the font for this cell. We'll use fallbacks
|
||||||
const font_idx_opt = try self.shaper.group.indexForCodepoint(alloc, style, cell.char);
|
// manually here to try replacement chars and then a space
|
||||||
|
// for unknown glyphs.
|
||||||
|
const font_idx_opt = (try self.shaper.group.indexForCodepoint(
|
||||||
|
alloc,
|
||||||
|
style,
|
||||||
|
cell.char,
|
||||||
|
)) orelse (try self.shaper.group.indexForCodepoint(
|
||||||
|
alloc,
|
||||||
|
style,
|
||||||
|
0xFFFD,
|
||||||
|
)) orelse
|
||||||
|
try self.shaper.group.indexForCodepoint(alloc, style, ' ');
|
||||||
const font_idx = font_idx_opt.?;
|
const font_idx = font_idx_opt.?;
|
||||||
//log.warn("char={x} idx={}", .{ cell.char, font_idx });
|
//log.warn("char={x} idx={}", .{ cell.char, font_idx });
|
||||||
if (j == self.i) current_font = font_idx;
|
if (j == self.i) current_font = font_idx;
|
||||||
@ -116,15 +192,22 @@ pub const RunIterator = struct {
|
|||||||
self.shaper.hb_buf.add(cp, @intCast(u32, j));
|
self.shaper.hb_buf.add(cp, @intCast(u32, j));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
max_cluster = j;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finalize our buffer
|
// Finalize our buffer
|
||||||
self.shaper.hb_buf.guessSegmentProperties();
|
self.shaper.hb_buf.guessSegmentProperties();
|
||||||
|
|
||||||
// Move our cursor
|
// Move our cursor. Must defer since we use self.i below.
|
||||||
self.i = j;
|
defer self.i = j;
|
||||||
|
|
||||||
return TextRun{ .font_index = current_font };
|
return TextRun{
|
||||||
|
.offset = @intCast(u16, self.i),
|
||||||
|
.cells = @intCast(u16, j - self.i),
|
||||||
|
.max_cluster = @intCast(u16, max_cluster),
|
||||||
|
.font_index = current_font,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -194,7 +277,7 @@ test "shape" {
|
|||||||
while (try it.next(alloc)) |run| {
|
while (try it.next(alloc)) |run| {
|
||||||
count += 1;
|
count += 1;
|
||||||
try testing.expectEqual(@as(u32, 3), shaper.hb_buf.getLength());
|
try testing.expectEqual(@as(u32, 3), shaper.hb_buf.getLength());
|
||||||
shaper.shape(run);
|
_ = try shaper.shape(run);
|
||||||
}
|
}
|
||||||
try testing.expectEqual(@as(usize, 1), count);
|
try testing.expectEqual(@as(usize, 1), count);
|
||||||
}
|
}
|
||||||
@ -204,11 +287,13 @@ const TestShaper = struct {
|
|||||||
shaper: Shaper,
|
shaper: Shaper,
|
||||||
cache: *GroupCache,
|
cache: *GroupCache,
|
||||||
lib: Library,
|
lib: Library,
|
||||||
|
cell_buf: []Cell,
|
||||||
|
|
||||||
pub fn deinit(self: *TestShaper) void {
|
pub fn deinit(self: *TestShaper) void {
|
||||||
self.shaper.deinit();
|
self.shaper.deinit();
|
||||||
self.cache.deinit(self.alloc);
|
self.cache.deinit(self.alloc);
|
||||||
self.alloc.destroy(self.cache);
|
self.alloc.destroy(self.cache);
|
||||||
|
self.alloc.free(self.cell_buf);
|
||||||
self.lib.deinit();
|
self.lib.deinit();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -230,7 +315,10 @@ fn testShaper(alloc: Allocator) !TestShaper {
|
|||||||
try cache_ptr.group.addFace(alloc, .regular, try Face.init(lib, testFont, .{ .points = 12 }));
|
try cache_ptr.group.addFace(alloc, .regular, try Face.init(lib, testFont, .{ .points = 12 }));
|
||||||
try cache_ptr.group.addFace(alloc, .regular, try Face.init(lib, testEmoji, .{ .points = 12 }));
|
try cache_ptr.group.addFace(alloc, .regular, try Face.init(lib, testEmoji, .{ .points = 12 }));
|
||||||
|
|
||||||
var shaper = try init(cache_ptr);
|
var cell_buf = try alloc.alloc(Cell, 80);
|
||||||
|
errdefer alloc.free(cell_buf);
|
||||||
|
|
||||||
|
var shaper = try init(cache_ptr, cell_buf);
|
||||||
errdefer shaper.deinit();
|
errdefer shaper.deinit();
|
||||||
|
|
||||||
return TestShaper{
|
return TestShaper{
|
||||||
@ -238,5 +326,6 @@ fn testShaper(alloc: Allocator) !TestShaper {
|
|||||||
.shaper = shaper,
|
.shaper = shaper,
|
||||||
.cache = cache_ptr,
|
.cache = cache_ptr,
|
||||||
.lib = lib,
|
.lib = lib,
|
||||||
|
.cell_buf = cell_buf,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user