|
|
|
@ -62,7 +62,9 @@ pub fn deinit(self: *Collection, alloc: Allocator) void {
|
|
|
|
|
var it = self.faces.iterator();
|
|
|
|
|
while (it.next()) |array| {
|
|
|
|
|
var entry_it = array.value.iterator(0);
|
|
|
|
|
while (entry_it.next()) |entry| entry.deinit();
|
|
|
|
|
while (entry_it.next()) |entry_or_alias| {
|
|
|
|
|
if (entry_or_alias.unwrapNoAlias()) |entry| entry.deinit();
|
|
|
|
|
}
|
|
|
|
|
array.value.deinit(alloc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -105,123 +107,35 @@ pub fn add(
|
|
|
|
|
if (face.isDeferred() and self.load_options == null)
|
|
|
|
|
return error.DeferredLoadingUnavailable;
|
|
|
|
|
|
|
|
|
|
try list.append(alloc, face);
|
|
|
|
|
try list.append(alloc, .{ .entry = face });
|
|
|
|
|
|
|
|
|
|
var owned: *Entry = list.at(idx);
|
|
|
|
|
const owned: *Entry = list.at(idx).unwrapNoAlias().?;
|
|
|
|
|
|
|
|
|
|
// If the face is already loaded, apply font size adjustment
|
|
|
|
|
// now, otherwise we'll apply it whenever we do load it.
|
|
|
|
|
if (owned.getLoaded()) |loaded| {
|
|
|
|
|
if (try self.adjustedSize(loaded)) |opts| {
|
|
|
|
|
loaded.setSize(opts.faceOptions()) catch return error.SetSizeFailed;
|
|
|
|
|
}
|
|
|
|
|
// If we have load options, we update the size such that it's scaled
|
|
|
|
|
// to the primary if possible. If the face is not loaded, this is a
|
|
|
|
|
// no-op and scaling will be done when loading happens.
|
|
|
|
|
if (self.load_options) |opts| {
|
|
|
|
|
const primary_entry = self.getEntry(.{ .idx = 0 }) catch null;
|
|
|
|
|
try owned.setSize(opts.faceOptions(), primary_entry);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return .{ .style = style, .idx = @intCast(idx) };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub const AdjustSizeError = font.Face.GetMetricsError;
|
|
|
|
|
|
|
|
|
|
// Calculate a size for the provided face that will match it with the primary
|
|
|
|
|
// font, metrically, to improve consistency with fallback fonts. Right now we
|
|
|
|
|
// match the font based on the ex height, or the ideograph width if the font
|
|
|
|
|
// has ideographs in it.
|
|
|
|
|
//
|
|
|
|
|
// This returns null if load options is null or if self.load_options is null.
|
|
|
|
|
//
|
|
|
|
|
// This is very much like the `font-size-adjust` CSS property in how it works.
|
|
|
|
|
// ref: https://developer.mozilla.org/en-US/docs/Web/CSS/font-size-adjust
|
|
|
|
|
//
|
|
|
|
|
// TODO: In the future, provide config options that allow the user to select
|
|
|
|
|
// which metric should be matched for fallback fonts, instead of hard
|
|
|
|
|
// coding it as ex height.
|
|
|
|
|
pub fn adjustedSize(
|
|
|
|
|
self: *Collection,
|
|
|
|
|
face: *Face,
|
|
|
|
|
) AdjustSizeError!?LoadOptions {
|
|
|
|
|
const load_options = self.load_options orelse return null;
|
|
|
|
|
|
|
|
|
|
// We silently do nothing if we can't get the primary
|
|
|
|
|
// face, because this might be the primary face itself.
|
|
|
|
|
const primary_face = self.getFace(.{ .idx = 0 }) catch return null;
|
|
|
|
|
|
|
|
|
|
// We do nothing if the primary face and this face are the same.
|
|
|
|
|
if (@intFromPtr(primary_face) == @intFromPtr(face)) return null;
|
|
|
|
|
|
|
|
|
|
const primary_metrics = try primary_face.getMetrics();
|
|
|
|
|
const face_metrics = try face.getMetrics();
|
|
|
|
|
|
|
|
|
|
// We use the ex height to match our font sizes, so that the height of
|
|
|
|
|
// lower-case letters matches between all fonts in the fallback chain.
|
|
|
|
|
//
|
|
|
|
|
// If the fallback font has an ic_width we prefer that, for normalization
|
|
|
|
|
// of CJK font sizes when mixed with latin fonts.
|
|
|
|
|
const primary_ex = primary_metrics.exHeight();
|
|
|
|
|
const primary_ic = primary_metrics.icWidth();
|
|
|
|
|
|
|
|
|
|
const face_ex = face_metrics.exHeight();
|
|
|
|
|
const face_ic = face_metrics.icWidth();
|
|
|
|
|
|
|
|
|
|
// If the line height of the scaled font would be larger than
|
|
|
|
|
// the line height of the primary font, we don't want that, so
|
|
|
|
|
// we take the minimum between matching the ic/ex and the line
|
|
|
|
|
// height.
|
|
|
|
|
//
|
|
|
|
|
// NOTE: We actually allow the line height to be up to 1.2
|
|
|
|
|
// times the primary line height because empirically
|
|
|
|
|
// this is usually fine and is better for CJK.
|
|
|
|
|
//
|
|
|
|
|
// TODO: We should probably provide a config option that lets
|
|
|
|
|
// the user pick what metric to use for size adjustment.
|
|
|
|
|
const scale = @min(
|
|
|
|
|
1.2 * primary_metrics.lineHeight() / face_metrics.lineHeight(),
|
|
|
|
|
if ((face_metrics.ic_width != null) and (face_metrics.ic_width == face_ic))
|
|
|
|
|
// It's possible for .ic_width to be non-null and still invalid, e.g.,
|
|
|
|
|
// zero, so we only take this branch if it's also equal to .icWidth().
|
|
|
|
|
primary_ic / face_ic
|
|
|
|
|
else
|
|
|
|
|
primary_ex / face_ex,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Make a copy of our load options, set the size to the size of
|
|
|
|
|
// the provided face, and then multiply that by our scaling factor.
|
|
|
|
|
var opts = load_options;
|
|
|
|
|
opts.size = face.size;
|
|
|
|
|
opts.size.points *= @as(f32, @floatCast(scale));
|
|
|
|
|
|
|
|
|
|
return opts;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Return the Face represented by a given Index. The returned pointer
|
|
|
|
|
/// is only valid as long as this collection is not modified.
|
|
|
|
|
///
|
|
|
|
|
/// This will initialize the face if it is deferred and not yet loaded,
|
|
|
|
|
/// which can fail.
|
|
|
|
|
pub fn getFace(self: *Collection, index: Index) !*Face {
|
|
|
|
|
return try self.getFaceFromEntry(try self.getEntry(index));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the unaliased entry from an index
|
|
|
|
|
pub fn getEntry(self: *Collection, index: Index) !*Entry {
|
|
|
|
|
if (index.special() != null) return error.SpecialHasNoFace;
|
|
|
|
|
const list = self.faces.getPtr(index.style);
|
|
|
|
|
const item: *Entry = item: {
|
|
|
|
|
var item = list.at(index.idx);
|
|
|
|
|
switch (item.*) {
|
|
|
|
|
.alias => |ptr| item = ptr,
|
|
|
|
|
|
|
|
|
|
.deferred,
|
|
|
|
|
.fallback_deferred,
|
|
|
|
|
.loaded,
|
|
|
|
|
.fallback_loaded,
|
|
|
|
|
=> {},
|
|
|
|
|
}
|
|
|
|
|
assert(item.* != .alias);
|
|
|
|
|
break :item item;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const face = try self.getFaceFromEntry(
|
|
|
|
|
item,
|
|
|
|
|
// We only want to adjust the size if this isn't the primary face.
|
|
|
|
|
index.style != .regular or index.idx > 0,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return face;
|
|
|
|
|
return list.at(index.idx).unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the face from an entry.
|
|
|
|
@ -230,41 +144,33 @@ pub fn getFace(self: *Collection, index: Index) !*Face {
|
|
|
|
|
fn getFaceFromEntry(
|
|
|
|
|
self: *Collection,
|
|
|
|
|
entry: *Entry,
|
|
|
|
|
/// Whether to adjust the font size to match the primary face after loading.
|
|
|
|
|
adjust: bool,
|
|
|
|
|
) !*Face {
|
|
|
|
|
assert(entry.* != .alias);
|
|
|
|
|
|
|
|
|
|
return switch (entry.*) {
|
|
|
|
|
return switch (entry.face) {
|
|
|
|
|
inline .deferred, .fallback_deferred => |*d, tag| deferred: {
|
|
|
|
|
const opts = self.load_options orelse
|
|
|
|
|
return error.DeferredLoadingUnavailable;
|
|
|
|
|
var face = try d.load(opts.library, opts.faceOptions());
|
|
|
|
|
const face_opts = opts.faceOptions();
|
|
|
|
|
const face = try d.load(opts.library, face_opts);
|
|
|
|
|
d.deinit();
|
|
|
|
|
|
|
|
|
|
// If we need to adjust the size, do so.
|
|
|
|
|
if (adjust) if (try self.adjustedSize(&face)) |new_opts| {
|
|
|
|
|
try face.setSize(new_opts.faceOptions());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
entry.* = switch (tag) {
|
|
|
|
|
entry.face = switch (tag) {
|
|
|
|
|
.deferred => .{ .loaded = face },
|
|
|
|
|
.fallback_deferred => .{ .fallback_loaded = face },
|
|
|
|
|
else => unreachable,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Adjust the size, passing the primary font for scaling
|
|
|
|
|
const primary_entry = self.getEntry(.{ .idx = 0 }) catch null;
|
|
|
|
|
try entry.setSize(face_opts, primary_entry);
|
|
|
|
|
|
|
|
|
|
break :deferred switch (tag) {
|
|
|
|
|
.deferred => &entry.loaded,
|
|
|
|
|
.fallback_deferred => &entry.fallback_loaded,
|
|
|
|
|
.deferred => &entry.face.loaded,
|
|
|
|
|
.fallback_deferred => &entry.face.fallback_loaded,
|
|
|
|
|
else => unreachable,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.loaded, .fallback_loaded => |*f| f,
|
|
|
|
|
|
|
|
|
|
// When setting `entry` above, we ensure we don't end up with
|
|
|
|
|
// an alias.
|
|
|
|
|
.alias => unreachable,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -282,8 +188,8 @@ pub fn getIndex(
|
|
|
|
|
) ?Index {
|
|
|
|
|
var i: usize = 0;
|
|
|
|
|
var it = self.faces.get(style).constIterator(0);
|
|
|
|
|
while (it.next()) |entry| {
|
|
|
|
|
if (entry.hasCodepoint(cp, p_mode)) {
|
|
|
|
|
while (it.next()) |entry_or_alias| {
|
|
|
|
|
if (@constCast(entry_or_alias).unwrap().hasCodepoint(cp, p_mode)) {
|
|
|
|
|
return .{
|
|
|
|
|
.style = style,
|
|
|
|
|
.idx = @intCast(i),
|
|
|
|
@ -309,7 +215,7 @@ pub fn hasCodepoint(
|
|
|
|
|
) bool {
|
|
|
|
|
const list = self.faces.get(index.style);
|
|
|
|
|
if (index.idx >= list.count()) return false;
|
|
|
|
|
return list.at(index.idx).hasCodepoint(cp, p_mode);
|
|
|
|
|
return @constCast(list.at(index.idx)).unwrap().hasCodepoint(cp, p_mode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub const CompleteError = Allocator.Error || error{
|
|
|
|
@ -347,10 +253,11 @@ pub fn completeStyles(
|
|
|
|
|
|
|
|
|
|
// Find our first regular face that has text glyphs.
|
|
|
|
|
var it = list.iterator(0);
|
|
|
|
|
while (it.next()) |entry| {
|
|
|
|
|
while (it.next()) |entry_or_alias| {
|
|
|
|
|
// Load our face. If we fail to load it, we just skip it and
|
|
|
|
|
// continue on to try the next one.
|
|
|
|
|
const face = self.getFaceFromEntry(entry, false) catch |err| {
|
|
|
|
|
const entry = entry_or_alias.unwrap();
|
|
|
|
|
const face = self.getFaceFromEntry(entry) catch |err| {
|
|
|
|
|
log.warn("error loading regular entry={d} err={}", .{
|
|
|
|
|
it.index - 1,
|
|
|
|
|
err,
|
|
|
|
@ -393,8 +300,9 @@ pub fn completeStyles(
|
|
|
|
|
break :italic;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const synthetic_entry = regular_entry.initCopy(.{ .loaded = synthetic });
|
|
|
|
|
log.info("synthetic italic face created", .{});
|
|
|
|
|
try italic_list.append(alloc, .{ .loaded = synthetic });
|
|
|
|
|
try italic_list.append(alloc, .{ .entry = synthetic_entry });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we don't have bold, use the regular font.
|
|
|
|
@ -413,8 +321,9 @@ pub fn completeStyles(
|
|
|
|
|
break :bold;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const synthetic_entry = regular_entry.initCopy(.{ .loaded = synthetic });
|
|
|
|
|
log.info("synthetic bold face created", .{});
|
|
|
|
|
try bold_list.append(alloc, .{ .loaded = synthetic });
|
|
|
|
|
try bold_list.append(alloc, .{ .entry = synthetic_entry });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we don't have bold italic, we attempt to synthesize a bold variant
|
|
|
|
@ -430,9 +339,11 @@ pub fn completeStyles(
|
|
|
|
|
// Prefer to synthesize on top of the face we already had. If we
|
|
|
|
|
// have bold then we try to synthesize italic on top of bold.
|
|
|
|
|
if (have_bold) {
|
|
|
|
|
if (self.syntheticItalic(bold_list.at(0))) |synthetic| {
|
|
|
|
|
const base_entry: *Entry = bold_list.at(0).unwrap();
|
|
|
|
|
if (self.syntheticItalic(base_entry)) |synthetic| {
|
|
|
|
|
log.info("synthetic bold italic face created from bold", .{});
|
|
|
|
|
try bold_italic_list.append(alloc, .{ .loaded = synthetic });
|
|
|
|
|
const synthetic_entry = base_entry.initCopy(.{ .loaded = synthetic });
|
|
|
|
|
try bold_italic_list.append(alloc, .{ .entry = synthetic_entry });
|
|
|
|
|
break :bold_italic;
|
|
|
|
|
} else |_| {}
|
|
|
|
|
|
|
|
|
@ -440,23 +351,11 @@ pub fn completeStyles(
|
|
|
|
|
// bold on whatever italic font we have.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Nested alias isn't allowed so we need to unwrap the italic entry.
|
|
|
|
|
const base_entry = base: {
|
|
|
|
|
const italic_entry = italic_list.at(0);
|
|
|
|
|
break :base switch (italic_entry.*) {
|
|
|
|
|
.alias => |v| v,
|
|
|
|
|
|
|
|
|
|
.loaded,
|
|
|
|
|
.fallback_loaded,
|
|
|
|
|
.deferred,
|
|
|
|
|
.fallback_deferred,
|
|
|
|
|
=> italic_entry,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const base_entry: *Entry = italic_list.at(0).unwrap();
|
|
|
|
|
if (self.syntheticBold(base_entry)) |synthetic| {
|
|
|
|
|
log.info("synthetic bold italic face created from italic", .{});
|
|
|
|
|
try bold_italic_list.append(alloc, .{ .loaded = synthetic });
|
|
|
|
|
const synthetic_entry = base_entry.initCopy(.{ .loaded = synthetic });
|
|
|
|
|
try bold_italic_list.append(alloc, .{ .entry = synthetic_entry });
|
|
|
|
|
break :bold_italic;
|
|
|
|
|
} else |_| {}
|
|
|
|
|
|
|
|
|
@ -474,7 +373,12 @@ fn syntheticBold(self: *Collection, entry: *Entry) !Face {
|
|
|
|
|
const opts = self.load_options orelse return error.DeferredLoadingUnavailable;
|
|
|
|
|
|
|
|
|
|
// Try to bold it.
|
|
|
|
|
const regular = try self.getFaceFromEntry(entry, false);
|
|
|
|
|
const regular = try self.getFaceFromEntry(entry);
|
|
|
|
|
|
|
|
|
|
// Inherit size from regular; it may be different than opts.size
|
|
|
|
|
// due to scaling adjustments
|
|
|
|
|
var face_opts = opts.faceOptions();
|
|
|
|
|
face_opts.size = regular.size;
|
|
|
|
|
const face = try regular.syntheticBold(opts.faceOptions());
|
|
|
|
|
|
|
|
|
|
var buf: [256]u8 = undefined;
|
|
|
|
@ -494,7 +398,12 @@ fn syntheticItalic(self: *Collection, entry: *Entry) !Face {
|
|
|
|
|
const opts = self.load_options orelse return error.DeferredLoadingUnavailable;
|
|
|
|
|
|
|
|
|
|
// Try to italicize it.
|
|
|
|
|
const regular = try self.getFaceFromEntry(entry, false);
|
|
|
|
|
const regular = try self.getFaceFromEntry(entry);
|
|
|
|
|
|
|
|
|
|
// Inherit size from regular; it may be different than opts.size
|
|
|
|
|
// due to scaling adjustments
|
|
|
|
|
var face_opts = opts.faceOptions();
|
|
|
|
|
face_opts.size = regular.size;
|
|
|
|
|
const face = try regular.syntheticItalic(opts.faceOptions());
|
|
|
|
|
|
|
|
|
|
var buf: [256]u8 = undefined;
|
|
|
|
@ -517,31 +426,42 @@ pub fn setSize(self: *Collection, size: DesiredSize) !void {
|
|
|
|
|
else
|
|
|
|
|
return error.DeferredLoadingUnavailable;
|
|
|
|
|
opts.size = size;
|
|
|
|
|
const face_opts = opts.faceOptions();
|
|
|
|
|
|
|
|
|
|
// Get the primary face if we can, and update that first,
|
|
|
|
|
// such that the relative scaling of the remaining faces
|
|
|
|
|
// is correct
|
|
|
|
|
const primary_entry = primary_entry: {
|
|
|
|
|
if (self.getEntry(.{ .idx = 0 })) |pe| {
|
|
|
|
|
try pe.setSize(face_opts, null);
|
|
|
|
|
break :primary_entry pe;
|
|
|
|
|
} else |_| break :primary_entry null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Resize all our faces that are loaded
|
|
|
|
|
var it = self.faces.iterator();
|
|
|
|
|
while (it.next()) |array| {
|
|
|
|
|
var entry_it = array.value.iterator(0);
|
|
|
|
|
while (entry_it.next()) |entry| switch (entry.*) {
|
|
|
|
|
.loaded,
|
|
|
|
|
.fallback_loaded,
|
|
|
|
|
=> |*f| {
|
|
|
|
|
const new_opts = try self.adjustedSize(f) orelse opts.*;
|
|
|
|
|
try f.setSize(new_opts.faceOptions());
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Deferred aren't loaded so we don't need to set their size.
|
|
|
|
|
// The size for when they're loaded is set since `opts` changed.
|
|
|
|
|
.deferred, .fallback_deferred => continue,
|
|
|
|
|
|
|
|
|
|
// Alias faces don't own their size.
|
|
|
|
|
.alias => continue,
|
|
|
|
|
};
|
|
|
|
|
while (entry_it.next()) |entry_or_alias| {
|
|
|
|
|
if (entry_or_alias.unwrapNoAlias()) |entry| {
|
|
|
|
|
if (entry == primary_entry) continue;
|
|
|
|
|
try entry.setSize(face_opts, primary_entry);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try self.updateMetrics();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn setReferenceMetric(self: *Collection, index: Index, scale_reference: ReferenceMetric) !void {
|
|
|
|
|
var entry = try self.getEntry(index);
|
|
|
|
|
entry.scale_reference = scale_reference;
|
|
|
|
|
if (self.load_options) |opts| {
|
|
|
|
|
const primary_entry = self.getEntry(.{ .idx = 0 }) catch null;
|
|
|
|
|
try entry.setSize(opts.faceOptions(), primary_entry);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const UpdateMetricsError = font.Face.GetMetricsError || error{
|
|
|
|
|
CannotLoadPrimaryFont,
|
|
|
|
|
};
|
|
|
|
@ -574,7 +494,7 @@ pub fn updateMetrics(self: *Collection) UpdateMetricsError!void {
|
|
|
|
|
///
|
|
|
|
|
/// WARNING: We cannot use any prealloc yet for the segmented list because
|
|
|
|
|
/// the collection is copied around by value and pointers aren't stable.
|
|
|
|
|
const StyleArray = std.EnumArray(Style, std.SegmentedList(Entry, 0));
|
|
|
|
|
const StyleArray = std.EnumArray(Style, std.SegmentedList(EntryOrAlias, 0));
|
|
|
|
|
|
|
|
|
|
/// Load options are used to configure all the details a Collection
|
|
|
|
|
/// needs to load deferred faces.
|
|
|
|
@ -628,49 +548,70 @@ pub const LoadOptions = struct {
|
|
|
|
|
/// not "fallback"), they want to use any glyphs possible within that
|
|
|
|
|
/// font face. Fallback fonts on the other hand are picked as a
|
|
|
|
|
/// last resort, so we should prefer exactness if possible.
|
|
|
|
|
pub const Entry = union(enum) {
|
|
|
|
|
deferred: DeferredFace, // Not loaded
|
|
|
|
|
loaded: Face, // Loaded, explicit use
|
|
|
|
|
pub const Entry = struct {
|
|
|
|
|
const AnyFace = union(enum) {
|
|
|
|
|
deferred: DeferredFace, // Not loaded
|
|
|
|
|
loaded: Face, // Loaded, explicit use
|
|
|
|
|
|
|
|
|
|
// The same as deferred/loaded but fallback font semantics (see large
|
|
|
|
|
// comment above Entry).
|
|
|
|
|
fallback_deferred: DeferredFace,
|
|
|
|
|
fallback_loaded: Face,
|
|
|
|
|
// The same as deferred/loaded but fallback font semantics (see large
|
|
|
|
|
// comment above Entry).
|
|
|
|
|
fallback_deferred: DeferredFace,
|
|
|
|
|
fallback_loaded: Face,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// An alias to another entry. This is used to share the same face,
|
|
|
|
|
// avoid memory duplication. An alias must point to a non-alias entry.
|
|
|
|
|
alias: *Entry,
|
|
|
|
|
face: AnyFace,
|
|
|
|
|
|
|
|
|
|
// Metric by which to normalize the font's size to the primary font.
|
|
|
|
|
// Default to ic_width to ensure appropriate normalization of CJK
|
|
|
|
|
// font sizes when mixed with latin fonts. See scaleFactor()
|
|
|
|
|
// implementation for fallback rules when the font does not define
|
|
|
|
|
// the specified metric.
|
|
|
|
|
//
|
|
|
|
|
// NOTE: In the future, if additional modifiers are needed for the
|
|
|
|
|
// translation of global collection options to individual font
|
|
|
|
|
// options, this should be promoted to a new FontModifiers type.
|
|
|
|
|
scale_reference: ReferenceMetric = .ic_width,
|
|
|
|
|
|
|
|
|
|
/// Convenience initializer so that users won't have to write nested
|
|
|
|
|
/// expressions depending on internals, like .{ .face = .{ .loaded = face } }
|
|
|
|
|
pub fn init(face: AnyFace) Entry {
|
|
|
|
|
return .{ .face = face };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Convenience initializer that also takes a scale reference
|
|
|
|
|
pub fn initWithScaleReference(face: AnyFace, scale_reference: ReferenceMetric) Entry {
|
|
|
|
|
return .{ .face = face, .scale_reference = scale_reference };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Initialize a new entry with the same scale reference as an existing entry
|
|
|
|
|
pub fn initCopy(self: Entry, face: AnyFace) Entry {
|
|
|
|
|
return .{ .face = face, .scale_reference = self.scale_reference };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn deinit(self: *Entry) void {
|
|
|
|
|
switch (self.*) {
|
|
|
|
|
switch (self.face) {
|
|
|
|
|
inline .deferred,
|
|
|
|
|
.loaded,
|
|
|
|
|
.fallback_deferred,
|
|
|
|
|
.fallback_loaded,
|
|
|
|
|
=> |*v| v.deinit(),
|
|
|
|
|
|
|
|
|
|
// Aliased fonts are not owned by this entry so we let them
|
|
|
|
|
// be deallocated by the owner.
|
|
|
|
|
.alias => {},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// If this face is loaded, or is an alias to a loaded face,
|
|
|
|
|
/// then this returns the `Face`, otherwise returns null.
|
|
|
|
|
/// If this face is loaded, then this returns the `Face`,
|
|
|
|
|
/// otherwise returns null.
|
|
|
|
|
pub fn getLoaded(self: *Entry) ?*Face {
|
|
|
|
|
return switch (self.*) {
|
|
|
|
|
return switch (self.face) {
|
|
|
|
|
.deferred, .fallback_deferred => null,
|
|
|
|
|
.loaded, .fallback_loaded => |*face| face,
|
|
|
|
|
.alias => |v| v.getLoaded(),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// True if the entry is deferred.
|
|
|
|
|
fn isDeferred(self: Entry) bool {
|
|
|
|
|
return switch (self) {
|
|
|
|
|
return switch (self.face) {
|
|
|
|
|
.deferred, .fallback_deferred => true,
|
|
|
|
|
.loaded, .fallback_loaded => false,
|
|
|
|
|
.alias => |v| v.isDeferred(),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -680,9 +621,7 @@ pub const Entry = union(enum) {
|
|
|
|
|
cp: u32,
|
|
|
|
|
p_mode: PresentationMode,
|
|
|
|
|
) bool {
|
|
|
|
|
return switch (self) {
|
|
|
|
|
.alias => |v| v.hasCodepoint(cp, p_mode),
|
|
|
|
|
|
|
|
|
|
return switch (self.face) {
|
|
|
|
|
// Non-fallback fonts require explicit presentation matching but
|
|
|
|
|
// otherwise don't care about presentation
|
|
|
|
|
.deferred => |v| switch (p_mode) {
|
|
|
|
@ -721,6 +660,142 @@ pub const Entry = union(enum) {
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set the size of the face, rescaling to match the primary if given
|
|
|
|
|
pub fn setSize(self: *Entry, opts: font.face.Options, primary_entry: ?*Entry) !void {
|
|
|
|
|
// If not loaded, nothing to do
|
|
|
|
|
var face = self.getLoaded() orelse return;
|
|
|
|
|
|
|
|
|
|
// First set to the raw size from opts, even if we're scaling,
|
|
|
|
|
// otherwise scaling calculations will be incorrect.
|
|
|
|
|
face.setSize(opts) catch return error.SetSizeFailed;
|
|
|
|
|
|
|
|
|
|
// If we have a primary we rescale
|
|
|
|
|
if (primary_entry) |pe| if (try self.scaleFactor(pe)) |scale| {
|
|
|
|
|
var opts_scaled = opts;
|
|
|
|
|
opts_scaled.size.points *= scale;
|
|
|
|
|
face.setSize(opts_scaled) catch return error.SetSizeFailed;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate a size for the face that will match it with the primary font,
|
|
|
|
|
// metrically, to improve consistency with fallback fonts.
|
|
|
|
|
//
|
|
|
|
|
// This returns null if this or the primary face aren't loaded or, if
|
|
|
|
|
// scaling doesn't apply to this face.
|
|
|
|
|
//
|
|
|
|
|
// This is very much like the `font-size-adjust` CSS property in how it works.
|
|
|
|
|
// ref: https://developer.mozilla.org/en-US/docs/Web/CSS/font-size-adjust
|
|
|
|
|
//
|
|
|
|
|
// TODO: In the future, provide config options that allow the user to select
|
|
|
|
|
// which metric should be matched for fallback fonts, instead of hard
|
|
|
|
|
// coding at the point where a face is added to the collection.
|
|
|
|
|
pub fn scaleFactor(self: *Entry, primary_entry: *Entry) !?f32 {
|
|
|
|
|
// If the reference metric is the em size, no scaling
|
|
|
|
|
if (self.scale_reference == .em_size) return null;
|
|
|
|
|
|
|
|
|
|
// If the primary is us, no scaling
|
|
|
|
|
if (@intFromPtr(self) == @intFromPtr(primary_entry)) return null;
|
|
|
|
|
|
|
|
|
|
// If we or the primary face aren't loaded, we don't know our
|
|
|
|
|
// metrics, so we can't do anything
|
|
|
|
|
const primary_face = primary_entry.getLoaded() orelse return null;
|
|
|
|
|
const face = self.getLoaded() orelse return null;
|
|
|
|
|
|
|
|
|
|
// Verify that the two faces are currently loaded at the same
|
|
|
|
|
// size, otherwise what follows is nonsense
|
|
|
|
|
if (!std.meta.eql(face.size, primary_face.size)) {
|
|
|
|
|
return error.InconsistentSizesForScaling;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const primary_metrics = try primary_face.getMetrics();
|
|
|
|
|
const face_metrics = try face.getMetrics();
|
|
|
|
|
|
|
|
|
|
// The preferred metric to normalize by is self.scale_reference,
|
|
|
|
|
// however we don't want to use a metric not explicitly defined
|
|
|
|
|
// in `self`, so if needed we fall back through other metrics in
|
|
|
|
|
// the order shown in the switch statement below. If the metric
|
|
|
|
|
// is not defined in `primary`, that's OK, we'll use the estimate.
|
|
|
|
|
const line_height_ratio = primary_metrics.lineHeight() / face_metrics.lineHeight();
|
|
|
|
|
const scale: f64 = normalize_by: switch (self.scale_reference) {
|
|
|
|
|
// Even if a metric is non-null, it may be invalid (e.g., negative),
|
|
|
|
|
// so we check for equality with the estimator before using it
|
|
|
|
|
.ic_width => {
|
|
|
|
|
if (face_metrics.ic_width) |value| if (value == face_metrics.icWidth()) {
|
|
|
|
|
break :normalize_by primary_metrics.icWidth() / value;
|
|
|
|
|
};
|
|
|
|
|
continue :normalize_by .ex_height;
|
|
|
|
|
},
|
|
|
|
|
.ex_height => {
|
|
|
|
|
if (face_metrics.ex_height) |value| if (value == face_metrics.exHeight()) {
|
|
|
|
|
break :normalize_by primary_metrics.exHeight() / value;
|
|
|
|
|
};
|
|
|
|
|
continue :normalize_by .cap_height;
|
|
|
|
|
},
|
|
|
|
|
.cap_height => {
|
|
|
|
|
if (face_metrics.cap_height) |value| if (value == face_metrics.capHeight()) {
|
|
|
|
|
break :normalize_by primary_metrics.capHeight() / value;
|
|
|
|
|
};
|
|
|
|
|
continue :normalize_by .line_height;
|
|
|
|
|
},
|
|
|
|
|
.line_height => line_height_ratio,
|
|
|
|
|
.em_size => unreachable,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// If the line height of the scaled font would be larger than
|
|
|
|
|
// the line height of the primary font, we don't want that, so
|
|
|
|
|
// we take the minimum between matching the reference metric
|
|
|
|
|
// and keeping the line heights within some margin.
|
|
|
|
|
//
|
|
|
|
|
// NOTE: We actually allow the line height to be up to 1.2
|
|
|
|
|
// times the primary line height because empirically
|
|
|
|
|
// this is usually fine and is better for CJK.
|
|
|
|
|
const capped_scale = @min(scale, 1.2 * line_height_ratio);
|
|
|
|
|
|
|
|
|
|
return @floatCast(capped_scale);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub const EntryOrAlias = union(enum) {
|
|
|
|
|
entry: Entry,
|
|
|
|
|
|
|
|
|
|
// An alias to another entry. This is used to share the same face,
|
|
|
|
|
// avoid memory duplication. An alias must point to a non-alias entry.
|
|
|
|
|
alias: *Entry,
|
|
|
|
|
|
|
|
|
|
pub fn unwrap(self: *EntryOrAlias) *Entry {
|
|
|
|
|
return switch (self.*) {
|
|
|
|
|
.entry => |*v| v,
|
|
|
|
|
.alias => |v| v,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn unwrapNoAlias(self: *EntryOrAlias) ?*Entry {
|
|
|
|
|
return switch (self.*) {
|
|
|
|
|
.entry => |*v| v,
|
|
|
|
|
.alias => null,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pub const AdjustSizeError = font.Face.GetMetricsError || error{InconsistentSizesForScaling};
|
|
|
|
|
|
|
|
|
|
pub const ReferenceMetric = enum {
|
|
|
|
|
// The font's ideograph width
|
|
|
|
|
ic_width,
|
|
|
|
|
// The font's ex height
|
|
|
|
|
ex_height,
|
|
|
|
|
// The font's cap height
|
|
|
|
|
cap_height,
|
|
|
|
|
// The font's line height
|
|
|
|
|
line_height,
|
|
|
|
|
// The font's em size
|
|
|
|
|
// Conventionally equivalent to line height, but the semantics
|
|
|
|
|
// differ: using em_size directly sets the point sizes to the same
|
|
|
|
|
// value, while using line_height calculates the scaling ratio for
|
|
|
|
|
// matching line heights even if it differs from 1 em for one or
|
|
|
|
|
// both fonts
|
|
|
|
|
em_size,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// The requested presentation for a codepoint.
|
|
|
|
@ -823,11 +898,11 @@ test "add full" {
|
|
|
|
|
defer c.deinit(alloc);
|
|
|
|
|
|
|
|
|
|
for (0..Index.Special.start - 1) |_| {
|
|
|
|
|
_ = try c.add(alloc, .regular, .{ .loaded = try .init(
|
|
|
|
|
_ = try c.add(alloc, .regular, .init(.{ .loaded = try .init(
|
|
|
|
|
lib,
|
|
|
|
|
testFont,
|
|
|
|
|
.{ .size = .{ .points = 12 } },
|
|
|
|
|
) });
|
|
|
|
|
) }));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var face = try Face.init(
|
|
|
|
@ -840,7 +915,7 @@ test "add full" {
|
|
|
|
|
defer face.deinit();
|
|
|
|
|
try testing.expectError(
|
|
|
|
|
error.CollectionFull,
|
|
|
|
|
c.add(alloc, .regular, .{ .loaded = face }),
|
|
|
|
|
c.add(alloc, .regular, .init(.{ .loaded = face })),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -856,7 +931,7 @@ test "add deferred without loading options" {
|
|
|
|
|
.regular,
|
|
|
|
|
|
|
|
|
|
// This can be undefined because it should never be accessed.
|
|
|
|
|
.{ .deferred = undefined },
|
|
|
|
|
.init(.{ .deferred = undefined }),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -871,11 +946,11 @@ test getFace {
|
|
|
|
|
var c = init();
|
|
|
|
|
defer c.deinit(alloc);
|
|
|
|
|
|
|
|
|
|
const idx = try c.add(alloc, .regular, .{ .loaded = try .init(
|
|
|
|
|
const idx = try c.add(alloc, .regular, .init(.{ .loaded = try .init(
|
|
|
|
|
lib,
|
|
|
|
|
testFont,
|
|
|
|
|
.{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
|
|
|
|
|
) });
|
|
|
|
|
) }));
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
const face1 = try c.getFace(idx);
|
|
|
|
@ -895,11 +970,11 @@ test getIndex {
|
|
|
|
|
var c = init();
|
|
|
|
|
defer c.deinit(alloc);
|
|
|
|
|
|
|
|
|
|
_ = try c.add(alloc, .regular, .{ .loaded = try .init(
|
|
|
|
|
_ = try c.add(alloc, .regular, .init(.{ .loaded = try .init(
|
|
|
|
|
lib,
|
|
|
|
|
testFont,
|
|
|
|
|
.{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
|
|
|
|
|
) });
|
|
|
|
|
) }));
|
|
|
|
|
|
|
|
|
|
// Should find all visible ASCII
|
|
|
|
|
var i: u32 = 32;
|
|
|
|
@ -927,11 +1002,11 @@ test completeStyles {
|
|
|
|
|
defer c.deinit(alloc);
|
|
|
|
|
c.load_options = .{ .library = lib };
|
|
|
|
|
|
|
|
|
|
_ = try c.add(alloc, .regular, .{ .loaded = try .init(
|
|
|
|
|
_ = try c.add(alloc, .regular, .init(.{ .loaded = try .init(
|
|
|
|
|
lib,
|
|
|
|
|
testFont,
|
|
|
|
|
.{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
|
|
|
|
|
) });
|
|
|
|
|
) }));
|
|
|
|
|
|
|
|
|
|
try testing.expect(c.getIndex('A', .bold, .{ .any = {} }) == null);
|
|
|
|
|
try testing.expect(c.getIndex('A', .italic, .{ .any = {} }) == null);
|
|
|
|
@ -954,11 +1029,11 @@ test setSize {
|
|
|
|
|
defer c.deinit(alloc);
|
|
|
|
|
c.load_options = .{ .library = lib };
|
|
|
|
|
|
|
|
|
|
_ = try c.add(alloc, .regular, .{ .loaded = try .init(
|
|
|
|
|
_ = try c.add(alloc, .regular, .init(.{ .loaded = try .init(
|
|
|
|
|
lib,
|
|
|
|
|
testFont,
|
|
|
|
|
.{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
|
|
|
|
|
) });
|
|
|
|
|
) }));
|
|
|
|
|
|
|
|
|
|
try testing.expectEqual(@as(u32, 12), c.load_options.?.size.points);
|
|
|
|
|
try c.setSize(.{ .points = 24 });
|
|
|
|
@ -977,11 +1052,11 @@ test hasCodepoint {
|
|
|
|
|
defer c.deinit(alloc);
|
|
|
|
|
c.load_options = .{ .library = lib };
|
|
|
|
|
|
|
|
|
|
const idx = try c.add(alloc, .regular, .{ .loaded = try .init(
|
|
|
|
|
const idx = try c.add(alloc, .regular, .init(.{ .loaded = try .init(
|
|
|
|
|
lib,
|
|
|
|
|
testFont,
|
|
|
|
|
.{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
|
|
|
|
|
) });
|
|
|
|
|
) }));
|
|
|
|
|
|
|
|
|
|
try testing.expect(c.hasCodepoint(idx, 'A', .{ .any = {} }));
|
|
|
|
|
try testing.expect(!c.hasCodepoint(idx, '🥸', .{ .any = {} }));
|
|
|
|
@ -1001,11 +1076,11 @@ test "hasCodepoint emoji default graphical" {
|
|
|
|
|
defer c.deinit(alloc);
|
|
|
|
|
c.load_options = .{ .library = lib };
|
|
|
|
|
|
|
|
|
|
const idx = try c.add(alloc, .regular, .{ .loaded = try .init(
|
|
|
|
|
const idx = try c.add(alloc, .regular, .init(.{ .loaded = try .init(
|
|
|
|
|
lib,
|
|
|
|
|
testEmoji,
|
|
|
|
|
.{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
|
|
|
|
|
) });
|
|
|
|
|
) }));
|
|
|
|
|
|
|
|
|
|
try testing.expect(!c.hasCodepoint(idx, 'A', .{ .any = {} }));
|
|
|
|
|
try testing.expect(c.hasCodepoint(idx, '🥸', .{ .any = {} }));
|
|
|
|
@ -1025,11 +1100,11 @@ test "metrics" {
|
|
|
|
|
const size: DesiredSize = .{ .points = 12, .xdpi = 96, .ydpi = 96 };
|
|
|
|
|
c.load_options = .{ .library = lib, .size = size };
|
|
|
|
|
|
|
|
|
|
_ = try c.add(alloc, .regular, .{ .loaded = try .init(
|
|
|
|
|
_ = try c.add(alloc, .regular, .init(.{ .loaded = try .init(
|
|
|
|
|
lib,
|
|
|
|
|
testFont,
|
|
|
|
|
.{ .size = size },
|
|
|
|
|
) });
|
|
|
|
|
) }));
|
|
|
|
|
|
|
|
|
|
try c.updateMetrics();
|
|
|
|
|
|
|
|
|
@ -1084,6 +1159,7 @@ test "adjusted sizes" {
|
|
|
|
|
const alloc = testing.allocator;
|
|
|
|
|
const testFont = font.embedded.inconsolata;
|
|
|
|
|
const fallback = font.embedded.monaspace_neon;
|
|
|
|
|
const symbol = font.embedded.symbols_nerd_font;
|
|
|
|
|
|
|
|
|
|
var lib = try Library.init(alloc);
|
|
|
|
|
defer lib.deinit();
|
|
|
|
@ -1094,45 +1170,79 @@ test "adjusted sizes" {
|
|
|
|
|
c.load_options = .{ .library = lib, .size = size };
|
|
|
|
|
|
|
|
|
|
// Add our primary face.
|
|
|
|
|
_ = try c.add(alloc, .regular, .{ .loaded = try .init(
|
|
|
|
|
_ = try c.add(alloc, .regular, .init(.{ .loaded = try .init(
|
|
|
|
|
lib,
|
|
|
|
|
testFont,
|
|
|
|
|
.{ .size = size },
|
|
|
|
|
) });
|
|
|
|
|
) }));
|
|
|
|
|
|
|
|
|
|
try c.updateMetrics();
|
|
|
|
|
|
|
|
|
|
// Add the fallback face.
|
|
|
|
|
const fallback_idx = try c.add(alloc, .regular, .{ .loaded = try .init(
|
|
|
|
|
const fallback_idx = try c.add(alloc, .regular, .init(.{ .loaded = try .init(
|
|
|
|
|
lib,
|
|
|
|
|
fallback,
|
|
|
|
|
.{ .size = size },
|
|
|
|
|
) });
|
|
|
|
|
) }));
|
|
|
|
|
|
|
|
|
|
// The ex heights should match.
|
|
|
|
|
inline for ([_][]const u8{ "ex_height", "cap_height" }) |metric| {
|
|
|
|
|
try c.setReferenceMetric(fallback_idx, @field(ReferenceMetric, metric));
|
|
|
|
|
|
|
|
|
|
// The chosen metric should match.
|
|
|
|
|
{
|
|
|
|
|
const primary_metrics = try (try c.getFace(.{ .idx = 0 })).getMetrics();
|
|
|
|
|
const fallback_metrics = try (try c.getFace(fallback_idx)).getMetrics();
|
|
|
|
|
|
|
|
|
|
try std.testing.expectApproxEqRel(
|
|
|
|
|
@field(primary_metrics, metric).?,
|
|
|
|
|
@field(fallback_metrics, metric).?,
|
|
|
|
|
// We accept anything within 5 %.
|
|
|
|
|
0.05,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Resize should keep that relationship.
|
|
|
|
|
try c.setSize(.{ .points = 37, .xdpi = 96, .ydpi = 96 });
|
|
|
|
|
{
|
|
|
|
|
const primary_metrics = try (try c.getFace(.{ .idx = 0 })).getMetrics();
|
|
|
|
|
const fallback_metrics = try (try c.getFace(fallback_idx)).getMetrics();
|
|
|
|
|
|
|
|
|
|
try std.testing.expectApproxEqRel(
|
|
|
|
|
@field(primary_metrics, metric).?,
|
|
|
|
|
@field(fallback_metrics, metric).?,
|
|
|
|
|
// We accept anything within 5 %.
|
|
|
|
|
0.05,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
// Reset size for the next iteration
|
|
|
|
|
try c.setSize(size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add the symbol face.
|
|
|
|
|
const symbol_idx = try c.add(alloc, .regular, .initWithScaleReference(.{ .loaded = try .init(
|
|
|
|
|
lib,
|
|
|
|
|
symbol,
|
|
|
|
|
.{ .size = size },
|
|
|
|
|
) }, .ex_height));
|
|
|
|
|
|
|
|
|
|
// Test fallback to lineHeight() (ex_height and cap_height not defined in symbols font).
|
|
|
|
|
{
|
|
|
|
|
const primary_metrics = try (try c.getFace(.{ .idx = 0 })).getMetrics();
|
|
|
|
|
const fallback_metrics = try (try c.getFace(fallback_idx)).getMetrics();
|
|
|
|
|
const symbol_metrics = try (try c.getFace(symbol_idx)).getMetrics();
|
|
|
|
|
|
|
|
|
|
try std.testing.expectApproxEqAbs(
|
|
|
|
|
primary_metrics.ex_height.?,
|
|
|
|
|
fallback_metrics.ex_height.?,
|
|
|
|
|
// We accept anything within half a pixel.
|
|
|
|
|
0.5,
|
|
|
|
|
try std.testing.expectApproxEqRel(
|
|
|
|
|
primary_metrics.lineHeight(),
|
|
|
|
|
symbol_metrics.lineHeight(),
|
|
|
|
|
// We accept anything within 5 %.
|
|
|
|
|
0.05,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Resize should keep that relationship.
|
|
|
|
|
try c.setSize(.{ .points = 37, .xdpi = 96, .ydpi = 96 });
|
|
|
|
|
{
|
|
|
|
|
const primary_metrics = try (try c.getFace(.{ .idx = 0 })).getMetrics();
|
|
|
|
|
const fallback_metrics = try (try c.getFace(fallback_idx)).getMetrics();
|
|
|
|
|
// Test em_size giving exact font size equality
|
|
|
|
|
try c.setReferenceMetric(symbol_idx, .em_size);
|
|
|
|
|
|
|
|
|
|
try std.testing.expectApproxEqAbs(
|
|
|
|
|
primary_metrics.ex_height.?,
|
|
|
|
|
fallback_metrics.ex_height.?,
|
|
|
|
|
// We accept anything within half a pixel.
|
|
|
|
|
0.5,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
try std.testing.expectEqual(
|
|
|
|
|
(try c.getFace(.{ .idx = 0 })).size.points,
|
|
|
|
|
(try c.getFace(symbol_idx)).size.points,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|