tiny.gui.paint.synth
Defined in paint.
API (3)
Actions
Public operations.
Source
Source: lib/gui/src/paint/root.zig:11
zig
pub const synth = @import("synth.zig");Source: lib/gui/src/paint/synth.zig
zig
const std = @import("std");const gui = @import("../root.zig");const Color = gui.model.UiColor;const Rect = gui.layout.Rect;pub fn covered(codepoint: u21) bool { return switch (codepoint) { 0x2500...0x257F => boxSegments(codepoint) != null, 0x2580...0x259F => true, 0x2800...0x28FF => true, 0x2295...0x2299 => true, 0x22EF, 0x276F, 0x2726 => true, else => false, };}pub fn draw(writer: anytype, codepoint: u21, cell: Rect, color: Color) bool { switch (codepoint) { 0x2500...0x257F => { const segments = boxSegments(codepoint) orelse return false; drawBox(writer, segments, cell, color); return true; }, 0x2580...0x259F => { drawBlock(writer, codepoint, cell, color); return true; }, 0x2800...0x28FF => { drawBraille(writer, codepoint, cell, color); return true; }, 0x2295...0x2299 => { drawCircled(writer, codepoint, cell, color); return true; }, 0x22EF => { drawMidlineEllipsis(writer, cell, color); return true; }, 0x276F => { drawChevron(writer, cell, color); return true; }, 0x2726 => { drawStar(writer, cell, color); return true; }, else => return false, }}pub fn drawNotdef(writer: anytype, cell: Rect, color: Color) void { const thickness = strokeThickness(cell); const x = snap(cell.x + thickness); const y = snap(cell.y + thickness); const width = @max(thickness, snap(cell.width - thickness * 2)); const height = @max(thickness, snap(cell.height - thickness * 2)); fill(writer, .{ .x = x, .y = y, .width = width, .height = thickness }, color); fill(writer, .{ .x = x, .y = y + height - thickness, .width = width, .height = thickness }, color); fill(writer, .{ .x = x, .y = y, .width = thickness, .height = height }, color); fill(writer, .{ .x = x + width - thickness, .y = y, .width = thickness, .height = height }, color);}fn drawCircled(writer: anytype, codepoint: u21, cell: Rect, color: Color) void { const t = strokeThickness(cell); const side = @min(cell.width, cell.height * 0.72); const x0 = snap(cell.x + (cell.width - side) / 2); const y0 = snap(cell.y + (cell.height - side) / 2); const x1 = x0 + side; const y1 = y0 + side; const corner = side / 4; fill(writer, .{ .x = x0 + corner, .y = y0, .width = side - corner * 2, .height = t }, color); fill(writer, .{ .x = x0 + corner, .y = y1 - t, .width = side - corner * 2, .height = t }, color); fill(writer, .{ .x = x0, .y = y0 + corner, .width = t, .height = side - corner * 2 }, color); fill(writer, .{ .x = x1 - t, .y = y0 + corner, .width = t, .height = side - corner * 2 }, color); fill(writer, .{ .x = x0 + corner / 2, .y = y0 + corner / 2, .width = t, .height = t }, color); fill(writer, .{ .x = x1 - corner / 2 - t, .y = y0 + corner / 2, .width = t, .height = t }, color); fill(writer, .{ .x = x0 + corner / 2, .y = y1 - corner / 2 - t, .width = t, .height = t }, color); fill(writer, .{ .x = x1 - corner / 2 - t, .y = y1 - corner / 2 - t, .width = t, .height = t }, color); const cx = x0 + side / 2; const cy = y0 + side / 2; const reach = side / 2 - corner / 2 - t; switch (codepoint) { 0x2295 => { fill(writer, .{ .x = cx - reach, .y = cy - t / 2, .width = reach * 2, .height = t }, color); fill(writer, .{ .x = cx - t / 2, .y = cy - reach, .width = t, .height = reach * 2 }, color); }, 0x2296 => fill(writer, .{ .x = cx - reach, .y = cy - t / 2, .width = reach * 2, .height = t }, color), 0x2297 => { drawDiagonal(writer, cx, cy, reach, t, color, .both); }, 0x2298 => drawDiagonal(writer, cx, cy, reach, t, color, .rising), 0x2299 => fill(writer, .{ .x = cx - t, .y = cy - t, .width = t * 2, .height = t * 2 }, color), else => unreachable, }}const DiagonalArms = enum { rising, both };fn drawDiagonal(writer: anytype, cx: f32, cy: f32, reach: f32, t: f32, color: Color, arms: DiagonalArms) void { const steps: usize = 3; const step = reach / @as(f32, @floatFromInt(steps)); for (0..steps) |index| { const offset = step * @as(f32, @floatFromInt(index)) + step / 2; fill(writer, .{ .x = cx + offset - t / 2, .y = cy - offset - t / 2, .width = t, .height = t }, color); fill(writer, .{ .x = cx - offset - t / 2, .y = cy + offset - t / 2, .width = t, .height = t }, color); if (arms == .both) { fill(writer, .{ .x = cx - offset - t / 2, .y = cy - offset - t / 2, .width = t, .height = t }, color); fill(writer, .{ .x = cx + offset - t / 2, .y = cy + offset - t / 2, .width = t, .height = t }, color); } }}fn drawMidlineEllipsis(writer: anytype, cell: Rect, color: Color) void { const t = strokeThickness(cell); const cy = cell.y + (cell.height - t) / 2; const spacing = cell.width / 4; for (0..3) |index| { const offset: f32 = @floatFromInt(index + 1); fill(writer, .{ .x = cell.x + spacing * offset - t / 2, .y = cy, .width = t, .height = t }, color); }}const BoxSegments = struct { left: bool = false, right: bool = false, up: bool = false, down: bool = false,};fn boxSegments(codepoint: u21) ?BoxSegments { return switch (codepoint) { 0x2500, 0x2501 => .{ .left = true, .right = true }, 0x2502, 0x2503 => .{ .up = true, .down = true }, 0x250C, 0x250F, 0x256D => .{ .right = true, .down = true }, 0x2510, 0x2513, 0x256E => .{ .left = true, .down = true }, 0x2514, 0x2517, 0x2570 => .{ .right = true, .up = true }, 0x2518, 0x251B, 0x256F => .{ .left = true, .up = true }, 0x251C => .{ .up = true, .down = true, .right = true }, 0x2524 => .{ .up = true, .down = true, .left = true }, 0x252C => .{ .left = true, .right = true, .down = true }, 0x2534 => .{ .left = true, .right = true, .up = true }, 0x253C => .{ .left = true, .right = true, .up = true, .down = true }, 0x2574 => .{ .left = true }, 0x2575 => .{ .up = true }, 0x2576 => .{ .right = true }, 0x2577 => .{ .down = true }, else => null, };}fn drawBox(writer: anytype, segments: BoxSegments, cell: Rect, color: Color) void { const t = strokeThickness(cell); const cx = snap(cell.x + (cell.width - t) / 2); const cy = snap(cell.y + (cell.height - t) / 2); if (segments.left) fill(writer, .{ .x = cell.x, .y = cy, .width = cx - cell.x + t, .height = t }, color); if (segments.right) fill(writer, .{ .x = cx, .y = cy, .width = cell.x + cell.width - cx, .height = t }, color); if (segments.up) fill(writer, .{ .x = cx, .y = cell.y, .width = t, .height = cy - cell.y + t }, color); if (segments.down) fill(writer, .{ .x = cx, .y = cy, .width = t, .height = cell.y + cell.height - cy }, color);}fn drawBlock(writer: anytype, codepoint: u21, cell: Rect, color: Color) void { switch (codepoint) { 0x2580 => fillFraction(writer, cell, color, 0, 0, 8, 4), 0x2581...0x2588 => { const eighths: f32 = @floatFromInt(codepoint - 0x2580); const height = cell.height * eighths / 8; fill(writer, .{ .x = cell.x, .y = cell.y + cell.height - height, .width = cell.width, .height = height }, color); }, 0x2589...0x258F => { const eighths: f32 = @floatFromInt(8 - (codepoint - 0x2588)); fill(writer, .{ .x = cell.x, .y = cell.y, .width = cell.width * eighths / 8, .height = cell.height }, color); }, 0x2590 => fillFraction(writer, cell, color, 4, 0, 8, 8), 0x2591 => fill(writer, cell, shaded(color, 64)), 0x2592 => fill(writer, cell, shaded(color, 128)), 0x2593 => fill(writer, cell, shaded(color, 192)), 0x2594 => fill(writer, .{ .x = cell.x, .y = cell.y, .width = cell.width, .height = cell.height / 8 }, color), 0x2595 => fill(writer, .{ .x = cell.x + cell.width * 7 / 8, .y = cell.y, .width = cell.width / 8, .height = cell.height }, color), 0x2596...0x259F => { const quads = quadrants(codepoint); if (quads.upper_left) fillFraction(writer, cell, color, 0, 0, 4, 4); if (quads.upper_right) fillFraction(writer, cell, color, 4, 0, 8, 4); if (quads.lower_left) fillFraction(writer, cell, color, 0, 4, 4, 8); if (quads.lower_right) fillFraction(writer, cell, color, 4, 4, 8, 8); }, else => unreachable, }}const Quadrants = struct { upper_left: bool = false, upper_right: bool = false, lower_left: bool = false, lower_right: bool = false,};fn quadrants(codepoint: u21) Quadrants { return switch (codepoint) { 0x2596 => .{ .lower_left = true }, 0x2597 => .{ .lower_right = true }, 0x2598 => .{ .upper_left = true }, 0x2599 => .{ .upper_left = true, .lower_left = true, .lower_right = true }, 0x259A => .{ .upper_left = true, .lower_right = true }, 0x259B => .{ .upper_left = true, .upper_right = true, .lower_left = true }, 0x259C => .{ .upper_left = true, .upper_right = true, .lower_right = true }, 0x259D => .{ .upper_right = true }, 0x259E => .{ .upper_right = true, .lower_left = true }, 0x259F => .{ .upper_right = true, .lower_left = true, .lower_right = true }, else => .{}, };}fn drawBraille(writer: anytype, codepoint: u21, cell: Rect, color: Color) void { const bits: u8 = @intCast(codepoint - 0x2800); const dot_width = @max(1, snap(cell.width / 4)); const dot_height = @max(1, snap(cell.height / 8)); const columns = [2]f32{ cell.x + cell.width * 0.18, cell.x + cell.width * 0.58 }; const rows = [4]f32{ cell.y + cell.height * 0.12, cell.y + cell.height * 0.34, cell.y + cell.height * 0.56, cell.y + cell.height * 0.78, }; const dots = [8]struct { column: usize, row: usize }{ .{ .column = 0, .row = 0 }, .{ .column = 0, .row = 1 }, .{ .column = 0, .row = 2 }, .{ .column = 1, .row = 0 }, .{ .column = 1, .row = 1 }, .{ .column = 1, .row = 2 }, .{ .column = 0, .row = 3 }, .{ .column = 1, .row = 3 }, }; for (dots, 0..) |dot, bit| { if ((bits >> @intCast(bit)) & 1 == 0) continue; fill(writer, .{ .x = snap(columns[dot.column]), .y = snap(rows[dot.row]), .width = dot_width, .height = dot_height, }, color); }}fn drawChevron(writer: anytype, cell: Rect, color: Color) void { const steps: usize = 4; const inset_x = cell.width * 0.2; const inset_y = cell.height * 0.22; const span_x = cell.width - inset_x * 2; const half_y = cell.height / 2 - inset_y; const step_x = span_x / @as(f32, @floatFromInt(steps)); const step_y = half_y / @as(f32, @floatFromInt(steps)); const thickness = @max(step_y, strokeThickness(cell)); for (0..steps) |index| { const offset: f32 = @floatFromInt(index); fill(writer, .{ .x = snap(cell.x + inset_x + step_x * offset), .y = snap(cell.y + inset_y + step_y * offset), .width = @max(1, snap(step_x + 1)), .height = @max(1, snap(thickness)), }, color); fill(writer, .{ .x = snap(cell.x + inset_x + step_x * offset), .y = snap(cell.y + cell.height - inset_y - step_y * (offset + 1)), .width = @max(1, snap(step_x + 1)), .height = @max(1, snap(thickness)), }, color); }}fn drawStar(writer: anytype, cell: Rect, color: Color) void { const rows = [5]f32{ 1, 3, 5, 3, 1 }; const unit_height = cell.height / 7; const unit_width = cell.width / 6; for (rows, 0..) |width_units, index| { const row_width = unit_width * width_units; const row_index: f32 = @floatFromInt(index); fill(writer, .{ .x = snap(cell.x + (cell.width - row_width) / 2), .y = snap(cell.y + unit_height * (row_index + 1)), .width = @max(1, snap(row_width)), .height = @max(1, snap(unit_height)), }, color); }}fn fillFraction(writer: anytype, cell: Rect, color: Color, x0: f32, y0: f32, x1: f32, y1: f32) void { fill(writer, .{ .x = cell.x + cell.width * x0 / 8, .y = cell.y + cell.height * y0 / 8, .width = cell.width * (x1 - x0) / 8, .height = cell.height * (y1 - y0) / 8, }, color);}fn fill(writer: anytype, rect: Rect, color: Color) void { writer.fillRect(.{ .x = snap(rect.x), .y = snap(rect.y), .width = @max(1, snap(rect.width)), .height = @max(1, snap(rect.height)), }, color);}fn shaded(color: Color, alpha: u8) Color { return .{ .r = color.r, .g = color.g, .b = color.b, .a = @intCast(@as(u16, color.a) * alpha / 255) };}fn strokeThickness(cell: Rect) f32 { return @max(1, snap(cell.height / 12));}fn snap(value: f32) f32 { return @round(value);}const RecordingWriter = struct { rects: std.ArrayListUnmanaged(Rect) = .empty, colors: std.ArrayListUnmanaged(Color) = .empty, allocator: std.mem.Allocator, fn fillRect(self: *RecordingWriter, rect: Rect, color: Color) void { self.rects.append(self.allocator, rect) catch @panic("synth test allocation failed"); self.colors.append(self.allocator, color) catch @panic("synth test allocation failed"); } fn deinit(self: *RecordingWriter) void { self.rects.deinit(self.allocator); self.colors.deinit(self.allocator); }};const test_cell = Rect{ .x = 10, .y = 20, .width = 10, .height = 24 };const test_color = Color{ .r = 200, .g = 100, .b = 50, .a = 255 };test "covered spans the terminal vocabulary and rejects letters" { try std.testing.expect(covered(0x2502)); try std.testing.expect(covered(0x258F)); try std.testing.expect(covered(0x280B)); try std.testing.expect(covered(0x276F)); try std.testing.expect(covered(0x2726)); try std.testing.expect(covered(0x2297)); try std.testing.expect(covered(0x22EF)); try std.testing.expect(!covered('a')); try std.testing.expect(!covered(0x2560));}test "notdef draws a closed box without a font glyph" { var writer = RecordingWriter{ .allocator = std.testing.allocator }; defer writer.deinit(); drawNotdef(&writer, test_cell, test_color); try std.testing.expectEqual(@as(usize, 4), writer.rects.items.len); try std.testing.expectEqual(writer.rects.items[0].y, writer.rects.items[2].y); try std.testing.expectEqual(writer.rects.items[1].y + writer.rects.items[1].height, writer.rects.items[2].y + writer.rects.items[2].height);}test "circled operators draw a ring with an inner mark" { var ring_only = RecordingWriter{ .allocator = std.testing.allocator }; defer ring_only.deinit(); try std.testing.expect(draw(&ring_only, 0x2299, test_cell, test_color)); const dot_rects = ring_only.rects.items.len; try std.testing.expectEqual(@as(usize, 9), dot_rects); var crossed = RecordingWriter{ .allocator = std.testing.allocator }; defer crossed.deinit(); try std.testing.expect(draw(&crossed, 0x2297, test_cell, test_color)); try std.testing.expect(crossed.rects.items.len > dot_rects);}test "midline ellipsis draws three centered dots" { var writer = RecordingWriter{ .allocator = std.testing.allocator }; defer writer.deinit(); try std.testing.expect(draw(&writer, 0x22EF, test_cell, test_color)); try std.testing.expectEqual(@as(usize, 3), writer.rects.items.len); for (writer.rects.items) |rect| { try std.testing.expect(rect.y > test_cell.y + test_cell.height / 4); try std.testing.expect(rect.y < test_cell.y + test_cell.height * 3 / 4); }}test "left one eighth block fills a narrow left bar" { var writer = RecordingWriter{ .allocator = std.testing.allocator }; defer writer.deinit(); try std.testing.expect(draw(&writer, 0x258F, test_cell, test_color)); try std.testing.expectEqual(@as(usize, 1), writer.rects.items.len); const rect = writer.rects.items[0]; try std.testing.expectEqual(test_cell.x, rect.x); try std.testing.expectEqual(test_cell.y, rect.y); try std.testing.expectApproxEqAbs(test_cell.width / 8, rect.width, 1.0); try std.testing.expectEqual(test_cell.height, rect.height);}test "full block covers the cell and shades keep the hue at reduced alpha" { var writer = RecordingWriter{ .allocator = std.testing.allocator }; defer writer.deinit(); try std.testing.expect(draw(&writer, 0x2588, test_cell, test_color)); try std.testing.expectEqual(test_cell.width, writer.rects.items[0].width); try std.testing.expectEqual(test_cell.height, writer.rects.items[0].height); try std.testing.expect(draw(&writer, 0x2592, test_cell, test_color)); try std.testing.expectEqual(@as(u8, 128), writer.colors.items[1].a); try std.testing.expectEqual(test_color.r, writer.colors.items[1].r);}test "braille dot count follows the bit pattern" { var writer = RecordingWriter{ .allocator = std.testing.allocator }; defer writer.deinit(); try std.testing.expect(draw(&writer, 0x280B, test_cell, test_color)); try std.testing.expectEqual(@as(usize, 3), writer.rects.items.len); var full = RecordingWriter{ .allocator = std.testing.allocator }; defer full.deinit(); try std.testing.expect(draw(&full, 0x28FF, test_cell, test_color)); try std.testing.expectEqual(@as(usize, 8), full.rects.items.len);}test "box drawing tee composes three segments" { var writer = RecordingWriter{ .allocator = std.testing.allocator }; defer writer.deinit(); try std.testing.expect(draw(&writer, 0x251C, test_cell, test_color)); try std.testing.expectEqual(@as(usize, 3), writer.rects.items.len);}test "chevron and star emit stepped fills inside the cell" { var writer = RecordingWriter{ .allocator = std.testing.allocator }; defer writer.deinit(); try std.testing.expect(draw(&writer, 0x276F, test_cell, test_color)); try std.testing.expect(draw(&writer, 0x2726, test_cell, test_color)); for (writer.rects.items) |rect| { try std.testing.expect(rect.x >= test_cell.x - 1); try std.testing.expect(rect.x + rect.width <= test_cell.x + test_cell.width + 2); try std.testing.expect(rect.y >= test_cell.y - 1); try std.testing.expect(rect.y + rect.height <= test_cell.y + test_cell.height + 2); }}Complete caller list for paint.synth.draw
8 direct callers.
lib.gui.src.paint.synth.test_box_drawing_tee_composes_three_segments[function] — test source atlib/gui/src/paint/synth.zig:421in nearest public ownertiny.gui.paint.synthlib.gui.src.paint.synth.test_braille_dot_count_follows_the_bit_pattern[function] — test source atlib/gui/src/paint/synth.zig:409in nearest public ownertiny.gui.paint.synthlib.gui.src.paint.synth.test_chevron_and_star_emit_stepped_fills_inside_the_cell[function] — test source atlib/gui/src/paint/synth.zig:428in nearest public ownertiny.gui.paint.synthlib.gui.src.paint.synth.test_circled_operators_draw_a_ring_with_an_inner_mark[function] — test source atlib/gui/src/paint/synth.zig:361in nearest public ownertiny.gui.paint.synthlib.gui.src.paint.synth.test_full_block_covers_the_cell_and_shades_keep_the_hue_at_reduced_alpha[function] — test source atlib/gui/src/paint/synth.zig:397in nearest public ownertiny.gui.paint.synthlib.gui.src.paint.synth.test_left_one_eighth_block_fills_a_narrow_left_bar[function] — test source atlib/gui/src/paint/synth.zig:385in nearest public ownertiny.gui.paint.synthlib.gui.src.paint.synth.test_midline_ellipsis_draws_three_centered_dots[function] — test source atlib/gui/src/paint/synth.zig:374in nearest public ownertiny.gui.paint.synthlib.gui.src.paint.text.drawSyntheticOrNotdef[function] — private source atlib/gui/src/paint/text.zig:3536in nearest public ownertiny.gui.paint.text
Complete call list for paint.synth.draw
8 direct calls.
lib.gui.src.paint.synth.boxSegments[function] — private source atlib/gui/src/paint/synth.zig:134in nearest public ownertiny.gui.paint.synthlib.gui.src.paint.synth.drawBlock[function] — private source atlib/gui/src/paint/synth.zig:165in nearest public ownertiny.gui.paint.synthlib.gui.src.paint.synth.drawBox[function] — private source atlib/gui/src/paint/synth.zig:155in nearest public ownertiny.gui.paint.synthlib.gui.src.paint.synth.drawBraille[function] — private source atlib/gui/src/paint/synth.zig:217in nearest public ownertiny.gui.paint.synthlib.gui.src.paint.synth.drawChevron[function] — private source atlib/gui/src/paint/synth.zig:249in nearest public ownertiny.gui.paint.synthlib.gui.src.paint.synth.drawCircled[function] — private source atlib/gui/src/paint/synth.zig:66in nearest public ownertiny.gui.paint.synthlib.gui.src.paint.synth.drawMidlineEllipsis[function] — private source atlib/gui/src/paint/synth.zig:117in nearest public ownertiny.gui.paint.synthlib.gui.src.paint.synth.drawStar[function] — private source atlib/gui/src/paint/synth.zig:275in nearest public ownertiny.gui.paint.synth
Audit
| Definitions | 4 |
|---|---|
| Public names | 4 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |