///|
fn text_align_extra(
align : @core.TextAlign,
frame_width : Double,
text_width : Double,
) -> Double {
let extra = (frame_width - text_width).max(0.0)
match align {
@core.TextAlign::TextStart => 0.0
@core.TextAlign::TextCenter => extra / 2.0
@core.TextAlign::TextEnd => extra
}
}
///|
fn build_native_text_run(
cache : NativeFontCache,
atlas : GlyphAtlas,
run : @core.TextRun,
state : NativePaintState,
size : @core.Size,
scale_factor : Double,
) -> TextRunBuildResult {
let vertices : Array[TextVertex] = []
let atlas_full = append_text_run_vertices(
cache, atlas, run, state, size, scale_factor, vertices,
)
{ vertices, atlas_full }
}
///|
fn append_text_run_vertices(
cache : NativeFontCache,
atlas : GlyphAtlas,
run : @core.TextRun,
state : NativePaintState,
size : @core.Size,
scale_factor : Double,
vertices : Array[TextVertex],
) -> Bool {
match cache.text_engine.layout_run(cache, run, scale_factor) {
Some(layout) =>
append_text_layout_or_fallback(
cache, atlas, run, state, size, scale_factor, layout, vertices,
)
None => false
}
}
///|
fn append_text_layout_or_fallback(
cache : NativeFontCache,
atlas : GlyphAtlas,
run : @core.TextRun,
state : NativePaintState,
size : @core.Size,
scale_factor : Double,
layout : NativeTextLayout,
vertices : Array[TextVertex],
) -> Bool {
let attempt_vertices : Array[TextVertex] = []
match
append_text_layout_vertices(
cache, atlas, run, state, size, scale_factor, layout, attempt_vertices,
) {
TextRunAppendComplete(atlas_full) => {
append_text_vertices(vertices, attempt_vertices)
atlas_full
}
TextRunAppendNeedsFallback =>
match cache.text_engine.fallback_layout_run(run, scale_factor) {
None => false
Some(fallback_layout) => {
let fallback_vertices : Array[TextVertex] = []
match
append_text_layout_vertices(
cache, atlas, run, state, size, scale_factor, fallback_layout, fallback_vertices,
) {
TextRunAppendComplete(atlas_full) => {
append_text_vertices(vertices, fallback_vertices)
atlas_full
}
TextRunAppendNeedsFallback => false
}
}
}
}
}
///|
fn append_text_layout_vertices(
cache : NativeFontCache,
atlas : GlyphAtlas,
run : @core.TextRun,
state : NativePaintState,
size : @core.Size,
scale_factor : Double,
layout : NativeTextLayout,
vertices : Array[TextVertex],
) -> TextRunAppendResult {
let start_extra = text_align_extra(
run.align,
run.frame.size.width,
layout.size.width,
)
let vertical_extra = native_text_vertical_extra(run.frame, layout)
let scale = native_text_scale(scale_factor)
let mut appended_count = 0
for glyph in layout.glyphs {
match atlas.ensure_glyph(cache, glyph.key) {
GlyphReady(entry) =>
if entry.width > 0 && entry.height > 0 {
appended_count = appended_count + 1
append_text_quad(
vertices,
run.frame.origin.x +
start_extra +
glyph.x +
entry.bearing_x.to_double() / scale,
run.frame.origin.y +
vertical_extra +
layout.baseline +
glyph.y +
entry.bearing_y.to_double() / scale,
entry,
atlas,
state,
size,
scale,
run.color.multiply_alpha(state.opacity),
)
}
GlyphMissing => return TextRunAppendNeedsFallback
GlyphAtlasFull => return TextRunAppendComplete(true)
}
}
if appended_count == 0 && !run.text.is_empty() {
return TextRunAppendNeedsFallback
}
TextRunAppendComplete(false)
}
///|
fn append_text_vertices(
vertices : Array[TextVertex],
appended : Array[TextVertex],
) -> Unit {
for vertex in appended {
vertices.push(vertex)
}
}
///|
fn native_text_scale(scale_factor : Double) -> Double {
if scale_factor > 0.0 {
scale_factor
} else {
1.0
}
}
///|
fn append_text_quad(
vertices : Array[TextVertex],
x : Double,
y : Double,
entry : GlyphAtlasEntry,
atlas : GlyphAtlas,
state : NativePaintState,
size : @core.Size,
scale_factor : Double,
color : @core.Color,
) -> Unit {
let width = entry.width.to_double() / native_text_scale(scale_factor)
let height = entry.height.to_double() / native_text_scale(scale_factor)
let p0 = transform_point(state.transform, x, y)
let p1 = transform_point(state.transform, x, y + height)
let p2 = transform_point(state.transform, x + width, y + height)
let p3 = transform_point(state.transform, x + width, y)
let u0 = entry.x.to_double() / atlas.width.to_double()
let v0 = entry.y.to_double() / atlas.height.to_double()
let u1 = (entry.x + entry.width).to_double() / atlas.width.to_double()
let v1 = (entry.y + entry.height).to_double() / atlas.height.to_double()
let color_glyph = match entry.format {
NativeRasterGlyphFormat::AlphaMask => 0.0
NativeRasterGlyphFormat::ColorRgba => 1.0
}
push_text_vertex(vertices, p0, size, u0, v0, color, color_glyph)
push_text_vertex(vertices, p1, size, u0, v1, color, color_glyph)
push_text_vertex(vertices, p2, size, u1, v1, color, color_glyph)
push_text_vertex(vertices, p0, size, u0, v0, color, color_glyph)
push_text_vertex(vertices, p2, size, u1, v1, color, color_glyph)
push_text_vertex(vertices, p3, size, u1, v0, color, color_glyph)
}
///|
fn push_text_vertex(
vertices : Array[TextVertex],
point : @core.Point,
size : @core.Size,
u : Double,
v : Double,
color : @core.Color,
color_glyph : Double,
) -> Unit {
vertices.push({
x: pixel_x_to_ndc(point.x, size.width),
y: pixel_y_to_ndc(point.y, size.height),
u,
v,
r: color.r,
g: color.g,
b: color.b,
a: color.a,
color_glyph,
})
}
///|
fn pixel_x_to_ndc(x : Double, width : Double) -> Double {
if width <= 0.0 {
-1.0
} else {
x / width * 2.0 - 1.0
}
}
///|
fn pixel_y_to_ndc(y : Double, height : Double) -> Double {
if height <= 0.0 {
1.0
} else {
1.0 - y / height * 2.0
}
}
///|
fn text_vertices_to_bytes(vertices : Array[TextVertex]) -> Bytes {
let out : Array[Byte] = []
for vertex in vertices {
push_text_f32le(out, vertex.x)
push_text_f32le(out, vertex.y)
push_text_f32le(out, vertex.u)
push_text_f32le(out, vertex.v)
push_text_f32le(out, vertex.r)
push_text_f32le(out, vertex.g)
push_text_f32le(out, vertex.b)
push_text_f32le(out, vertex.a)
push_text_f32le(out, vertex.color_glyph)
}
Bytes::from_array(out)
}
///|
fn push_text_f32le(out : Array[Byte], value : Double) -> Unit {
let bits : UInt = Float::from_double(value)
.reinterpret_as_int()
.reinterpret_as_uint()
out.push((bits & 0xFF).to_byte())
out.push(((bits >> 8) & 0xFF).to_byte())
out.push(((bits >> 16) & 0xFF).to_byte())
out.push(((bits >> 24) & 0xFF).to_byte())
}
///|
fn text_shader_wgsl() -> String {
let shader =
#|struct VSOut {
#| @builtin(position) pos: vec4,
#| @location(0) uv: vec2,
#| @location(1) color: vec4,
#| @location(2) color_glyph: f32,
#|};
#|
#|@vertex
#|fn vs_main(
#| @location(0) pos: vec2,
#| @location(1) uv: vec2,
#| @location(2) color: vec4,
#| @location(3) color_glyph: f32,
#|) -> VSOut {
#| var out: VSOut;
#| out.pos = vec4(pos, 0.0, 1.0);
#| out.uv = uv;
#| out.color = color;
#| out.color_glyph = color_glyph;
#| return out;
#|}
#|
#|@group(0) @binding(0) var glyph_sampler : sampler;
#|@group(0) @binding(1) var glyph_atlas : texture_2d;
#|
#|@fragment
#|fn fs_main(in: VSOut) -> @location(0) vec4 {
#| let sample = textureSample(glyph_atlas, glyph_sampler, in.uv);
#| let rgb = select(in.color.rgb, sample.rgb, in.color_glyph > 0.5);
#| return vec4(rgb, in.color.a * sample.a);
#|}
#|
shader
}