Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/layout/symbol.zig
Original file line number Diff line number Diff line change
Expand Up @@ -237,17 +237,35 @@ fn wrapLines(
/// `line_height_em`, the block anchored per the spec's text-anchor
/// vocabulary. Returns the block's collision box, or null when nothing
/// inked.
/// Codepoints a layout wanted and the atlas did not hold, so a host can fetch
/// them and lay the run out again. The set, not the count: what a host needs is
/// which characters to bake.
pub const Misses = std.AutoArrayHashMapUnmanaged(u21, void);

pub fn layoutText(
gpa: std.mem.Allocator,
text: []const u8,
atlas: *const glyphs.GlyphAtlas,
opts: TextOpts,
common: Common,
quads: *std.ArrayList(types.Quad),
misses: ?*Misses,
) !?Box {
const scale = opts.size_px / glyph_em_px;
const em = opts.size_px;

// Every codepoint the run wants, before the wrap drops any of it: a label
// cut short for want of a glyph would otherwise never ask for the glyph
// that would have let it fit.
if (misses) |set| {
var scan = std.unicode.Utf8View.initUnchecked(text).iterator();
while (scan.nextCodepoint()) |cp| {
if (cp == '\n' or cp == ' ') continue;
if (atlas.get(cp) != null) continue;
set.put(gpa, cp, {}) catch {};
}
}

var lines: [max_lines][]const u8 = undefined;
const n_lines = wrapLines(text, atlas, scale, opts.max_width_em * em, &lines);
if (n_lines == 0) return null;
Expand Down
17 changes: 15 additions & 2 deletions src/map.zig
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ pub const Built = struct {
/// missing-image hook: the host renders them (tile57_render_symbol_run
/// for sounding digit runs), calls Sprite.addImage, and rebuilds.
missing_images: []const []const u8 = &.{},
/// Codepoints a label wanted and the glyph atlas did not hold,
/// deduplicated — the missing-glyph hook, the text twin of
/// missing_images: the host bakes them into the atlas (from whatever face
/// its platform has for the script) and rebuilds. Without it a chart
/// naming its features in a script the atlas has no glyphs for lays out
/// half-em blanks and nothing says why.
missing_glyphs: []const u21 = &.{},
/// Features that failed a paint evaluation and fell to defaults.
eval_errors: usize = 0,
/// Per-layer properties the compiled tier claimed. The rest keep
Expand Down Expand Up @@ -756,6 +763,7 @@ pub fn buildSceneWithRasters(
// cell is remembered as NO_PATTERN so the sheet is walked once per name.
var pattern_ids: std.StringHashMapUnmanaged(u32) = .empty;
var missing: std.StringArrayHashMapUnmanaged(void) = .empty;
var missing_glyphs: symbol.Misses = .empty;
var collider = symbol.Collider.init(arena);
// Projection of a world anchor to reference px for collision boxes; the
// view origin stands in for the screen center (they coincide for a
Expand Down Expand Up @@ -969,7 +977,7 @@ pub fn buildSceneWithRasters(
.depth = feat_depth,
.size_scale = view.size_scale,
.zoom = view.zoom,
}, &quads, &quad_paint, &text_scratch, &text_paint_scratch, &collider, &missing, &out.eval_errors);
}, &quads, &quad_paint, &text_scratch, &text_paint_scratch, &collider, &missing, &missing_glyphs, &out.eval_errors);
continue;
},
.fill => {
Expand Down Expand Up @@ -1146,6 +1154,7 @@ pub fn buildSceneWithRasters(
out.paint_zoom = view.zoom;
out.patterns = patterns.items;
out.missing_images = missing.keys();
out.missing_glyphs = missing_glyphs.keys();
return out;
}

Expand Down Expand Up @@ -1433,6 +1442,7 @@ pub fn concatScenes(arena: std.mem.Allocator, parts: []const ScenePart) !Built {
var patterns: std.ArrayList(types.PatternCell) = .empty;
var spans: std.ArrayList(PaintSpan) = .empty;
var missing: std.StringArrayHashMapUnmanaged(void) = .empty;
var missing_glyphs: symbol.Misses = .empty;

for (parts) |part| {
const b = part.built;
Expand Down Expand Up @@ -1513,6 +1523,7 @@ pub fn concatScenes(arena: std.mem.Allocator, parts: []const ScenePart) !Built {
spans.appendAssumeCapacity(moved);
}
for (b.missing_images) |name| try missing.put(arena, name, {});
for (b.missing_glyphs) |cp| try missing_glyphs.put(arena, cp, {});
if (b.background_set) {
out.background = b.background;
out.background_set = true;
Expand Down Expand Up @@ -1541,6 +1552,7 @@ pub fn concatScenes(arena: std.mem.Allocator, parts: []const ScenePart) !Built {
out.patterns = patterns.items;
out.paint_spans = spans.items;
out.missing_images = missing.keys();
out.missing_glyphs = missing_glyphs.keys();
return out;
}

Expand Down Expand Up @@ -1625,6 +1637,7 @@ fn layoutSymbolFeature(
text_paint_scratch: *std.ArrayList(types.PaintVertex),
collider: *symbol.Collider,
missing: *std.StringArrayHashMapUnmanaged(void),
missing_glyphs: *symbol.Misses,
errors: *usize,
) error{OutOfMemory}!void {
if (f.parts.len == 0 or f.parts[0].len == 0) return;
Expand Down Expand Up @@ -1740,7 +1753,7 @@ fn layoutSymbolFeature(
tcommon.rotate_deg = 0;
}
const scratch_before = text_scratch.items.len;
const box = (try symbol.layoutText(arena, text, ga, topts, tcommon, text_scratch)) orelse break :text;
const box = (try symbol.layoutText(arena, text, ga, topts, tcommon, text_scratch, missing_glyphs)) orelse break :text;
const text_allow = sc.sym.text_allow_overlap;
const sbox = scaledBox(box, px, py, sc.size_scale);
// An allow-overlap label always draws and is never gated; a
Expand Down
Loading