use crate::atlas::allocator::GlyphAtlas;
use crate::atlas::cache::{CachedGlyph, GlyphCache, GlyphCacheKey};
use crate::atlas::rasterizer::rasterize_glyph;
use crate::font::registry::FontRegistry;
use crate::layout::block::BlockLayout;
use crate::layout::flow::{FlowItem, FlowLayout};
use crate::layout::table::generate_table_decorations;
use crate::render::cursor::generate_cursor_decorations;
use crate::render::decoration::generate_block_decorations;
use crate::types::{CursorDisplay, GlyphQuad, ImageQuad, RenderFrame};
#[allow(clippy::too_many_arguments)]
pub fn build_render_frame(
flow: &FlowLayout,
registry: &FontRegistry,
atlas: &mut GlyphAtlas,
cache: &mut GlyphCache,
scale_context: &mut swash::scale::ScaleContext,
scroll_offset: f32,
viewport_width: f32,
viewport_height: f32,
cursors: &[CursorDisplay],
cursor_color: [f32; 4],
selection_color: [f32; 4],
text_color: [f32; 4],
render_frame: &mut RenderFrame,
) {
let scale_factor = flow.scale_factor;
render_frame.glyphs.clear();
render_frame.images.clear();
render_frame.decorations.clear();
render_frame.block_glyphs.clear();
render_frame.block_decorations.clear();
render_frame.block_images.clear();
render_frame.block_heights.clear();
cache.advance_generation();
let evicted = cache.evict_unused();
for alloc_id in evicted {
atlas.deallocate(alloc_id);
}
let view_top = scroll_offset;
let view_bottom = scroll_offset + viewport_height;
for item in &flow.flow_order {
let (block_id, item_y, item_height) = match item {
FlowItem::Block {
block_id,
y,
height,
} => (*block_id, *y, *height),
FlowItem::Table {
table_id,
y,
height,
} => {
if *y + *height < view_top || *y > view_bottom {
continue;
}
if let Some(table) = flow.tables.get(table_id) {
let g_start = render_frame.glyphs.len();
let i_start = render_frame.images.len();
render_table_cells(
table,
0.0,
0.0,
registry,
atlas,
cache,
scale_context,
scroll_offset,
viewport_height,
text_color,
scale_factor,
render_frame,
);
let table_g: Vec<GlyphQuad> = render_frame.glyphs[g_start..].to_vec();
let table_i: Vec<ImageQuad> = render_frame.images[i_start..].to_vec();
render_frame.block_glyphs.push((*table_id, table_g));
render_frame.block_images.push((*table_id, table_i));
let decos = generate_table_decorations(table, scroll_offset);
render_frame.decorations.extend(decos);
}
continue;
}
FlowItem::Frame {
frame_id,
y,
height,
} => {
if *y + *height < view_top || *y > view_bottom {
continue;
}
if let Some(frame_layout) = flow.frames.get(frame_id) {
let g_start = render_frame.glyphs.len();
let i_start = render_frame.images.len();
render_frame_layout(
frame_layout,
registry,
atlas,
cache,
scale_context,
scroll_offset,
viewport_height,
text_color,
scale_factor,
render_frame,
);
let frame_g: Vec<GlyphQuad> = render_frame.glyphs[g_start..].to_vec();
let frame_i: Vec<ImageQuad> = render_frame.images[i_start..].to_vec();
render_frame.block_glyphs.push((*frame_id, frame_g));
render_frame.block_images.push((*frame_id, frame_i));
}
continue;
}
};
if item_y + item_height < view_top || item_y > view_bottom {
continue;
}
if let Some(block) = flow.blocks.get(&block_id) {
let g_start = render_frame.glyphs.len();
let i_start = render_frame.images.len();
render_block_at_offset(
block,
0.0,
0.0,
registry,
atlas,
cache,
scale_context,
scroll_offset,
viewport_height,
text_color,
scale_factor,
render_frame,
);
let block_g: Vec<GlyphQuad> = render_frame.glyphs[g_start..].to_vec();
let block_i: Vec<ImageQuad> = render_frame.images[i_start..].to_vec();
render_frame.block_glyphs.push((block_id, block_g));
render_frame.block_images.push((block_id, block_i));
let decos = generate_block_decorations(
block,
registry,
scroll_offset,
viewport_height,
0.0,
0.0,
viewport_width,
text_color,
scale_factor,
);
render_frame
.block_decorations
.push((block_id, decos.clone()));
render_frame.decorations.extend(decos);
render_frame.block_heights.insert(block_id, block.height);
}
}
let cursor_decos = generate_cursor_decorations(
flow,
cursors,
scroll_offset,
cursor_color,
selection_color,
viewport_width,
viewport_height,
);
render_frame.decorations.extend(cursor_decos);
render_frame.atlas_dirty = atlas.dirty;
render_frame.atlas_width = atlas.width;
render_frame.atlas_height = atlas.height;
if atlas.dirty {
render_frame.atlas_pixels.clone_from(&atlas.pixels);
atlas.dirty = false;
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn render_block_at_offset(
block: &BlockLayout,
offset_x: f32,
offset_y: f32,
registry: &FontRegistry,
atlas: &mut GlyphAtlas,
cache: &mut GlyphCache,
scale_context: &mut swash::scale::ScaleContext,
scroll_offset: f32,
viewport_height: f32,
default_text_color: [f32; 4],
scale_factor: f32,
render_frame: &mut RenderFrame,
) {
if let Some(marker) = &block.list_marker
&& let Some(first_line) = block.lines.first()
{
let baseline_y = offset_y + block.y + first_line.y;
let screen_top = baseline_y - first_line.ascent - scroll_offset;
if screen_top + first_line.line_height >= 0.0 && screen_top <= viewport_height {
render_run_glyphs(
&marker.run,
offset_x + marker.x,
baseline_y,
registry,
atlas,
cache,
scale_context,
scroll_offset,
default_text_color,
scale_factor,
render_frame,
);
}
}
for line in &block.lines {
let line_y = offset_y + block.y + line.y;
let screen_top = line_y - line.ascent - scroll_offset;
if screen_top + line.line_height < 0.0 {
continue; }
if screen_top > viewport_height {
break; }
for positioned_run in &line.runs {
let pen_x = offset_x + block.left_margin + positioned_run.x;
if let Some(ref image_name) = positioned_run.shaped_run.image_name {
let image_w = positioned_run.shaped_run.advance_width;
let image_h = positioned_run.shaped_run.image_height;
let image_y = line_y - image_h - scroll_offset;
render_frame.images.push(crate::types::ImageQuad {
screen: [pen_x, image_y, image_w, image_h],
name: image_name.clone(),
});
continue;
}
let baseline_offset = match positioned_run.decorations.vertical_alignment {
crate::types::VerticalAlignment::SuperScript => {
-(positioned_run.shaped_run.size_px * 0.35)
}
crate::types::VerticalAlignment::SubScript => {
positioned_run.shaped_run.size_px * 0.2
}
crate::types::VerticalAlignment::Normal => 0.0,
};
render_run_glyphs(
&positioned_run.shaped_run,
pen_x,
line_y + baseline_offset,
registry,
atlas,
cache,
scale_context,
scroll_offset,
default_text_color,
scale_factor,
render_frame,
);
}
}
}
#[allow(clippy::too_many_arguments)]
fn render_table_cells(
table: &crate::layout::table::TableLayout,
offset_x: f32,
offset_y: f32,
registry: &FontRegistry,
atlas: &mut GlyphAtlas,
cache: &mut GlyphCache,
scale_context: &mut swash::scale::ScaleContext,
scroll_offset: f32,
viewport_height: f32,
default_text_color: [f32; 4],
scale_factor: f32,
render_frame: &mut RenderFrame,
) {
for cell in &table.cell_layouts {
if cell.row >= table.row_ys.len() || cell.column >= table.column_xs.len() {
continue;
}
let cell_x = offset_x + table.column_xs[cell.column];
let cell_y = offset_y + table.y + table.row_ys[cell.row];
for block in &cell.blocks {
render_block_at_offset(
block,
cell_x,
cell_y,
registry,
atlas,
cache,
scale_context,
scroll_offset,
viewport_height,
default_text_color,
scale_factor,
render_frame,
);
let cell_w = table
.column_content_widths
.get(cell.column)
.copied()
.unwrap_or(200.0);
let decos = generate_block_decorations(
block,
registry,
scroll_offset,
viewport_height,
cell_x,
cell_y,
cell_w,
default_text_color,
scale_factor,
);
render_frame.decorations.extend(decos);
}
}
}
#[allow(clippy::too_many_arguments)]
fn render_frame_layout(
frame: &crate::layout::frame::FrameLayout,
registry: &FontRegistry,
atlas: &mut GlyphAtlas,
cache: &mut GlyphCache,
scale_context: &mut swash::scale::ScaleContext,
scroll_offset: f32,
viewport_height: f32,
default_text_color: [f32; 4],
scale_factor: f32,
render_frame: &mut RenderFrame,
) {
let offset_x = frame.x + frame.content_x;
let offset_y = frame.y + frame.content_y;
for block in &frame.blocks {
render_block_at_offset(
block,
offset_x,
offset_y,
registry,
atlas,
cache,
scale_context,
scroll_offset,
viewport_height,
default_text_color,
scale_factor,
render_frame,
);
let decos = generate_block_decorations(
block,
registry,
scroll_offset,
viewport_height,
offset_x,
offset_y,
frame.content_width,
default_text_color,
scale_factor,
);
render_frame.decorations.extend(decos);
}
for table in &frame.tables {
render_table_cells(
table,
offset_x,
offset_y,
registry,
atlas,
cache,
scale_context,
scroll_offset,
viewport_height,
default_text_color,
scale_factor,
render_frame,
);
}
for nested in &frame.frames {
render_nested_frame(
nested,
offset_x,
offset_y,
registry,
atlas,
cache,
scale_context,
scroll_offset,
viewport_height,
default_text_color,
scale_factor,
render_frame,
);
}
append_frame_border_decorations(frame, scroll_offset, &mut render_frame.decorations);
}
pub fn append_frame_border_decorations(
frame: &crate::layout::frame::FrameLayout,
scroll_offset: f32,
decorations: &mut Vec<crate::types::DecorationRect>,
) {
if frame.border_width > 0.0
&& frame.border_style != crate::layout::frame::FrameBorderStyle::None
{
let bw = frame.border_width;
let fx = frame.x;
let fy = frame.y - scroll_offset;
let fw = frame.total_width;
let fh = frame.total_height;
let color = match frame.border_style {
crate::layout::frame::FrameBorderStyle::LeftOnly => [0.5, 0.5, 0.6, 1.0],
_ => [0.6, 0.6, 0.6, 1.0],
};
match frame.border_style {
crate::layout::frame::FrameBorderStyle::LeftOnly => {
decorations.push(crate::types::DecorationRect {
rect: [fx, fy, bw, fh],
color,
kind: crate::types::DecorationKind::Background,
});
}
crate::layout::frame::FrameBorderStyle::Full => {
decorations.push(crate::types::DecorationRect {
rect: [fx, fy, fw, bw],
color,
kind: crate::types::DecorationKind::Background,
});
decorations.push(crate::types::DecorationRect {
rect: [fx, fy + fh - bw, fw, bw],
color,
kind: crate::types::DecorationKind::Background,
});
decorations.push(crate::types::DecorationRect {
rect: [fx, fy, bw, fh],
color,
kind: crate::types::DecorationKind::Background,
});
decorations.push(crate::types::DecorationRect {
rect: [fx + fw - bw, fy, bw, fh],
color,
kind: crate::types::DecorationKind::Background,
});
}
crate::layout::frame::FrameBorderStyle::None => {}
}
}
}
#[allow(clippy::too_many_arguments)]
fn render_nested_frame(
nested: &crate::layout::frame::FrameLayout,
parent_x: f32,
parent_y: f32,
registry: &FontRegistry,
atlas: &mut GlyphAtlas,
cache: &mut GlyphCache,
scale_context: &mut swash::scale::ScaleContext,
scroll_offset: f32,
viewport_height: f32,
default_text_color: [f32; 4],
scale_factor: f32,
render_frame: &mut RenderFrame,
) {
let frame_x = parent_x + nested.x;
let frame_y = parent_y + nested.y;
let content_x = frame_x + nested.content_x;
let content_y = frame_y + nested.content_y;
for block in &nested.blocks {
render_block_at_offset(
block,
content_x,
content_y,
registry,
atlas,
cache,
scale_context,
scroll_offset,
viewport_height,
default_text_color,
scale_factor,
render_frame,
);
let decos = generate_block_decorations(
block,
registry,
scroll_offset,
viewport_height,
content_x,
content_y,
nested.content_width,
default_text_color,
scale_factor,
);
render_frame.decorations.extend(decos);
}
for table in &nested.tables {
render_table_cells(
table,
content_x,
content_y,
registry,
atlas,
cache,
scale_context,
scroll_offset,
viewport_height,
default_text_color,
scale_factor,
render_frame,
);
}
for inner in &nested.frames {
render_nested_frame(
inner,
content_x,
content_y,
registry,
atlas,
cache,
scale_context,
scroll_offset,
viewport_height,
default_text_color,
scale_factor,
render_frame,
);
}
let border_frame = crate::layout::frame::FrameLayout {
frame_id: nested.frame_id,
x: frame_x,
y: frame_y,
total_width: nested.total_width,
total_height: nested.total_height,
content_x: nested.content_x,
content_y: nested.content_y,
content_width: nested.content_width,
content_height: nested.content_height,
blocks: Vec::new(),
tables: Vec::new(),
frames: Vec::new(),
border_width: nested.border_width,
border_style: nested.border_style,
};
append_frame_border_decorations(&border_frame, scroll_offset, &mut render_frame.decorations);
}
#[allow(clippy::too_many_arguments)]
fn render_run_glyphs(
run: &crate::shaping::run::ShapedRun,
start_x: f32,
baseline_y: f32,
registry: &FontRegistry,
atlas: &mut GlyphAtlas,
cache: &mut GlyphCache,
scale_context: &mut swash::scale::ScaleContext,
scroll_offset: f32,
default_text_color: [f32; 4],
scale_factor: f32,
render_frame: &mut RenderFrame,
) {
let sf = scale_factor.max(f32::MIN_POSITIVE);
let inv_sf = 1.0 / sf;
let physical_size_px = run.size_px * sf;
let mut pen_x = start_x;
for glyph in &run.glyphs {
if glyph.glyph_id == 0 {
pen_x += glyph.x_advance;
continue;
}
let entry = match registry.get(glyph.font_face_id) {
Some(e) => e,
None => {
pen_x += glyph.x_advance;
continue;
}
};
let cache_key = GlyphCacheKey::with_weight(
glyph.font_face_id,
glyph.glyph_id,
physical_size_px,
run.weight as u32,
);
ensure_glyph_cached(
&cache_key,
cache,
atlas,
scale_context,
&entry.data,
entry.face_index,
entry.swash_cache_key,
physical_size_px,
run.weight as u32,
);
if let Some(cached) = cache.get(&cache_key) {
let logical_w = cached.width as f32 * inv_sf;
let logical_h = cached.height as f32 * inv_sf;
let logical_left = cached.placement_left as f32 * inv_sf;
let logical_top = cached.placement_top as f32 * inv_sf;
let screen_x = pen_x + glyph.x_offset + logical_left;
let screen_y = baseline_y - glyph.y_offset - logical_top - scroll_offset;
let color = if cached.is_color {
[1.0, 1.0, 1.0, 1.0]
} else {
run.foreground_color.unwrap_or(default_text_color)
};
render_frame.glyphs.push(GlyphQuad {
screen: [screen_x, screen_y, logical_w, logical_h],
atlas: [
cached.atlas_x as f32,
cached.atlas_y as f32,
cached.width as f32,
cached.height as f32,
],
color,
is_color: cached.is_color,
});
}
pen_x += glyph.x_advance;
}
}
#[allow(clippy::too_many_arguments)]
fn ensure_glyph_cached(
key: &GlyphCacheKey,
cache: &mut GlyphCache,
atlas: &mut GlyphAtlas,
scale_context: &mut swash::scale::ScaleContext,
font_data: &[u8],
face_index: u32,
swash_cache_key: swash::CacheKey,
size_px: f32,
font_weight: u32,
) {
if cache.peek(key).is_some() {
return;
}
let image = match rasterize_glyph(
scale_context,
font_data,
face_index,
swash_cache_key,
key.glyph_id,
size_px,
font_weight,
) {
Some(img) => img,
None => return,
};
if image.width == 0 || image.height == 0 {
return;
}
let alloc = match atlas.allocate(image.width, image.height) {
Some(a) => a,
None => return,
};
let rect = alloc.rectangle;
let atlas_x = rect.min.x as u32;
let atlas_y = rect.min.y as u32;
if image.is_color {
atlas.blit_rgba(atlas_x, atlas_y, image.width, image.height, &image.data);
} else {
atlas.blit_mask(atlas_x, atlas_y, image.width, image.height, &image.data);
}
cache.insert(
*key,
CachedGlyph {
alloc_id: alloc.id,
atlas_x,
atlas_y,
width: image.width,
height: image.height,
placement_left: image.placement_left,
placement_top: image.placement_top,
is_color: image.is_color,
last_used: 0, },
);
}