Skip to content

Commit 1980f9a

Browse files
authored
font/freetype: disable SVG glyphs, simplify color check (#6824)
We don't currently support rendering SVG glyphs so they should be ignored when loading. Additionally, the check for whether a glyph is colored has been simplified by just checking the pixel mode of the rendered bitmap. This commit also fixes a bug caused by calling the color check inside of `renderGlyph`, which caused the bitmap to be freed creating a chance for memory corruption and garbled glyphs. This bug was introduced by 40c1140 (#6602) and discussed in #6781.
2 parents 141b697 + f008052 commit 1980f9a

File tree

5 files changed

+83
-45
lines changed

5 files changed

+83
-45
lines changed

pkg/freetype/face.zig

+3-1
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,9 @@ pub const LoadFlags = packed struct {
278278
color: bool = false,
279279
compute_metrics: bool = false,
280280
bitmap_metrics_only: bool = false,
281-
_padding2: u9 = 0,
281+
_padding2: u1 = 0,
282+
no_svg: bool = false,
283+
_padding3: u7 = 0,
282284

283285
test {
284286
// This must always be an i32 size so we can bitcast directly.

src/font/face/freetype.zig

+31-20
Original file line numberDiff line numberDiff line change
@@ -270,25 +270,21 @@ pub const Face = struct {
270270

271271
/// Returns true if the given glyph ID is colorized.
272272
pub fn isColorGlyph(self: *const Face, glyph_id: u32) bool {
273-
// sbix table is always true for now
274-
if (self.face.hasSBIX()) return true;
275-
276-
// CBDT/CBLC tables always imply colorized glyphs.
277-
// These are used by Noto.
278-
if (self.face.hasSfntTable(freetype.Tag.init("CBDT"))) return true;
279-
if (self.face.hasSfntTable(freetype.Tag.init("CBLC"))) return true;
280-
281-
// Otherwise, load the glyph and see what format it is in.
273+
// Load the glyph and see what pixel mode it renders with.
274+
// All modes other than BGRA are non-color.
275+
// If the glyph fails to load, just return false.
282276
self.face.loadGlyph(glyph_id, .{
283277
.render = true,
284278
.color = self.face.hasColor(),
279+
// NO_SVG set to true because we don't currently support rendering
280+
// SVG glyphs under FreeType, since that requires bundling another
281+
// dependency to handle rendering the SVG.
282+
.no_svg = true,
285283
}) catch return false;
286284

287-
// If the glyph is SVG we assume colorized
288285
const glyph = self.face.handle.*.glyph;
289-
if (glyph.*.format == freetype.c.FT_GLYPH_FORMAT_SVG) return true;
290286

291-
return false;
287+
return glyph.*.bitmap.pixel_mode == freetype.c.FT_PIXEL_MODE_BGRA;
292288
}
293289

294290
/// Render a glyph using the glyph index. The rendered glyph is stored in the
@@ -321,6 +317,11 @@ pub const Face = struct {
321317
.force_autohint = !self.load_flags.@"force-autohint",
322318
.monochrome = !self.load_flags.monochrome,
323319
.no_autohint = !self.load_flags.autohint,
320+
321+
// NO_SVG set to true because we don't currently support rendering
322+
// SVG glyphs under FreeType, since that requires bundling another
323+
// dependency to handle rendering the SVG.
324+
.no_svg = true,
324325
});
325326
const glyph = self.face.handle.*.glyph;
326327

@@ -393,12 +394,13 @@ pub const Face = struct {
393394
const original_width = bitmap_original.width;
394395
const original_height = bitmap_original.rows;
395396
var result = bitmap_original;
396-
// TODO: We are limiting this to only emoji. We can rework this after a future
397-
// improvement (promised by Qwerasd) which implements more flexible resizing rules. For
398-
// now, this will suffice
399-
if (self.isColorGlyph(glyph_index) and opts.cell_width != null) {
397+
// TODO: We are limiting this to only color glyphs, so mainly emoji.
398+
// We can rework this after a future improvement (promised by Qwerasd)
399+
// which implements more flexible resizing rules.
400+
if (atlas.format != .grayscale and opts.cell_width != null) {
400401
const cell_width = opts.cell_width orelse unreachable;
401-
// If we have a cell_width, we constrain the glyph to fit within the cell
402+
// If we have a cell_width, we constrain
403+
// the glyph to fit within the cell(s).
402404
result.width = metrics.cell_width * @as(u32, cell_width);
403405
result.rows = (result.width * original_height) / original_width;
404406
} else {
@@ -743,7 +745,10 @@ pub const Face = struct {
743745
var c: u8 = ' ';
744746
while (c < 127) : (c += 1) {
745747
if (face.getCharIndex(c)) |glyph_index| {
746-
if (face.loadGlyph(glyph_index, .{ .render = true })) {
748+
if (face.loadGlyph(glyph_index, .{
749+
.render = true,
750+
.no_svg = true,
751+
})) {
747752
max = @max(
748753
f26dot6ToF64(face.handle.*.glyph.*.advance.x),
749754
max,
@@ -776,15 +781,21 @@ pub const Face = struct {
776781
break :heights .{
777782
cap: {
778783
if (face.getCharIndex('H')) |glyph_index| {
779-
if (face.loadGlyph(glyph_index, .{ .render = true })) {
784+
if (face.loadGlyph(glyph_index, .{
785+
.render = true,
786+
.no_svg = true,
787+
})) {
780788
break :cap f26dot6ToF64(face.handle.*.glyph.*.metrics.height);
781789
} else |_| {}
782790
}
783791
break :cap null;
784792
},
785793
ex: {
786794
if (face.getCharIndex('x')) |glyph_index| {
787-
if (face.loadGlyph(glyph_index, .{ .render = true })) {
795+
if (face.loadGlyph(glyph_index, .{
796+
.render = true,
797+
.no_svg = true,
798+
})) {
788799
break :ex f26dot6ToF64(face.handle.*.glyph.*.metrics.height);
789800
} else |_| {}
790801
}

src/font/shaper/coretext.zig

+21-11
Original file line numberDiff line numberDiff line change
@@ -1015,25 +1015,35 @@ test "shape emoji width long" {
10151015
var testdata = try testShaper(alloc);
10161016
defer testdata.deinit();
10171017

1018-
var buf: [32]u8 = undefined;
1019-
var buf_idx: usize = 0;
1020-
buf_idx += try std.unicode.utf8Encode(0x1F9D4, buf[buf_idx..]); // man: beard
1021-
buf_idx += try std.unicode.utf8Encode(0x1F3FB, buf[buf_idx..]); // light skin tone (Fitz 1-2)
1022-
buf_idx += try std.unicode.utf8Encode(0x200D, buf[buf_idx..]); // ZWJ
1023-
buf_idx += try std.unicode.utf8Encode(0x2642, buf[buf_idx..]); // male sign
1024-
buf_idx += try std.unicode.utf8Encode(0xFE0F, buf[buf_idx..]); // emoji representation
1025-
1026-
// Make a screen with some data
1018+
// Make a screen and add a long emoji sequence to it.
10271019
var screen = try terminal.Screen.init(alloc, 30, 3, 0);
10281020
defer screen.deinit();
1029-
try screen.testWriteString(buf[0..buf_idx]);
1021+
1022+
var page = screen.pages.pages.first.?.data;
1023+
var row = page.getRow(1);
1024+
const cell = &row.cells.ptr(page.memory)[0];
1025+
cell.* = .{
1026+
.content_tag = .codepoint,
1027+
.content = .{ .codepoint = 0x1F9D4 }, // Person with beard
1028+
};
1029+
var graphemes = [_]u21{
1030+
0x1F3FB, // Light skin tone (Fitz 1-2)
1031+
0x200D, // ZWJ
1032+
0x2642, // Male sign
1033+
0xFE0F, // Emoji presentation selector
1034+
};
1035+
try page.setGraphemes(
1036+
row,
1037+
cell,
1038+
graphemes[0..],
1039+
);
10301040

10311041
// Get our run iterator
10321042
var shaper = &testdata.shaper;
10331043
var it = shaper.runIterator(
10341044
testdata.grid,
10351045
&screen,
1036-
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
1046+
screen.pages.pin(.{ .screen = .{ .y = 1 } }).?,
10371047
null,
10381048
null,
10391049
);

src/font/shaper/harfbuzz.zig

+21-11
Original file line numberDiff line numberDiff line change
@@ -540,25 +540,35 @@ test "shape emoji width long" {
540540
var testdata = try testShaper(alloc);
541541
defer testdata.deinit();
542542

543-
var buf: [32]u8 = undefined;
544-
var buf_idx: usize = 0;
545-
buf_idx += try std.unicode.utf8Encode(0x1F9D4, buf[buf_idx..]); // man: beard
546-
buf_idx += try std.unicode.utf8Encode(0x1F3FB, buf[buf_idx..]); // light skin tone (Fitz 1-2)
547-
buf_idx += try std.unicode.utf8Encode(0x200D, buf[buf_idx..]); // ZWJ
548-
buf_idx += try std.unicode.utf8Encode(0x2642, buf[buf_idx..]); // male sign
549-
buf_idx += try std.unicode.utf8Encode(0xFE0F, buf[buf_idx..]); // emoji representation
550-
551-
// Make a screen with some data
543+
// Make a screen and add a long emoji sequence to it.
552544
var screen = try terminal.Screen.init(alloc, 30, 3, 0);
553545
defer screen.deinit();
554-
try screen.testWriteString(buf[0..buf_idx]);
546+
547+
var page = screen.pages.pages.first.?.data;
548+
var row = page.getRow(1);
549+
const cell = &row.cells.ptr(page.memory)[0];
550+
cell.* = .{
551+
.content_tag = .codepoint,
552+
.content = .{ .codepoint = 0x1F9D4 }, // Person with beard
553+
};
554+
var graphemes = [_]u21{
555+
0x1F3FB, // Light skin tone (Fitz 1-2)
556+
0x200D, // ZWJ
557+
0x2642, // Male sign
558+
0xFE0F, // Emoji presentation selector
559+
};
560+
try page.setGraphemes(
561+
row,
562+
cell,
563+
graphemes[0..],
564+
);
555565

556566
// Get our run iterator
557567
var shaper = &testdata.shaper;
558568
var it = shaper.runIterator(
559569
testdata.grid,
560570
&screen,
561-
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
571+
screen.pages.pin(.{ .screen = .{ .y = 1 } }).?,
562572
null,
563573
null,
564574
);

src/font/shaper/run.zig

+7-2
Original file line numberDiff line numberDiff line change
@@ -360,11 +360,16 @@ pub const RunIterator = struct {
360360

361361
// Find a font that supports this codepoint. If none support this
362362
// then the whole grapheme can't be rendered so we return null.
363+
//
364+
// We explicitly do not require the additional grapheme components
365+
// to support the base presentation, since it is common for emoji
366+
// fonts to support the base emoji with emoji presentation but not
367+
// certain ZWJ-combined characters like the male and female signs.
363368
const idx = try self.grid.getIndex(
364369
alloc,
365370
cp,
366371
style,
367-
presentation,
372+
null,
368373
) orelse return null;
369374
candidates.appendAssumeCapacity(idx);
370375
}
@@ -375,7 +380,7 @@ pub const RunIterator = struct {
375380
for (cps) |cp| {
376381
// Ignore Emoji ZWJs
377382
if (cp == 0xFE0E or cp == 0xFE0F or cp == 0x200D) continue;
378-
if (!self.grid.hasCodepoint(idx, cp, presentation)) break;
383+
if (!self.grid.hasCodepoint(idx, cp, null)) break;
379384
} else {
380385
// If the while completed, then we have a candidate that
381386
// supports all of our codepoints.

0 commit comments

Comments
 (0)