Start cosmic-text implementation
This commit is contained in:
parent
2a01a1b2e0
commit
75fc8f978f
6 changed files with 203 additions and 416 deletions
|
@ -11,6 +11,8 @@ license = "MIT OR Apache-2.0 OR Zlib"
|
||||||
wgpu = "0.14.0"
|
wgpu = "0.14.0"
|
||||||
fontdue = { git = "https://github.com/mooman219/fontdue", rev = "828b4f4" }
|
fontdue = { git = "https://github.com/mooman219/fontdue", rev = "828b4f4" }
|
||||||
etagere = "0.2.6"
|
etagere = "0.2.6"
|
||||||
|
#cosmic-text = { git = "https://github.com/pop-os/cosmic-text", rev = "ef686f8" }
|
||||||
|
cosmic-text = { path = "../cosmic-text" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
winit = "0.27.0"
|
winit = "0.27.0"
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
use fontdue::layout::{HorizontalAlign, VerticalAlign};
|
use cosmic_text::{fontdb, FontSystem, TextBuffer, TextMetrics};
|
||||||
use glyphon::{
|
use glyphon::{Color, HasColor, Resolution, TextAtlas, TextRenderer};
|
||||||
fontdue::{
|
|
||||||
layout::{CoordinateSystem, Layout, LayoutSettings, TextStyle},
|
|
||||||
Font, FontSettings,
|
|
||||||
},
|
|
||||||
Color, HasColor, Resolution, TextAtlas, TextOverflow, TextRenderer,
|
|
||||||
};
|
|
||||||
use wgpu::{
|
use wgpu::{
|
||||||
Backends, CommandEncoderDescriptor, CompositeAlphaMode, DeviceDescriptor, Features, Instance,
|
Backends, CommandEncoderDescriptor, CompositeAlphaMode, DeviceDescriptor, Features, Instance,
|
||||||
Limits, LoadOp, Operations, PresentMode, RenderPassColorAttachment, RenderPassDescriptor,
|
Limits, LoadOp, Operations, PresentMode, RenderPassColorAttachment, RenderPassDescriptor,
|
||||||
|
@ -35,6 +29,8 @@ impl HasColor for GlyphUserData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static mut FONT_SYSTEM: Option<FontSystem> = None;
|
||||||
|
|
||||||
async fn run() {
|
async fn run() {
|
||||||
let instance = Instance::new(Backends::all());
|
let instance = Instance::new(Backends::all());
|
||||||
let adapter = instance
|
let adapter = instance
|
||||||
|
@ -70,10 +66,14 @@ async fn run() {
|
||||||
|
|
||||||
let mut atlas = TextAtlas::new(&device, &queue, swapchain_format);
|
let mut atlas = TextAtlas::new(&device, &queue, swapchain_format);
|
||||||
let mut text_renderer = TextRenderer::new(&device, &queue);
|
let mut text_renderer = TextRenderer::new(&device, &queue);
|
||||||
|
unsafe { FONT_SYSTEM = Some(FontSystem::new()) };
|
||||||
let font = include_bytes!("./Inter-Bold.ttf") as &[u8];
|
let font_matches = unsafe {
|
||||||
let font = Font::from_bytes(font, FontSettings::default()).unwrap();
|
FONT_SYSTEM.as_ref().unwrap().matches(|info| {
|
||||||
let fonts = vec![font];
|
info.style == fontdb::Style::Normal
|
||||||
|
&& info.weight == fontdb::Weight::NORMAL
|
||||||
|
&& info.stretch == fontdb::Stretch::Normal
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
let _ = (&instance, &adapter);
|
let _ = (&instance, &adapter);
|
||||||
|
@ -90,45 +90,11 @@ async fn run() {
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
Event::RedrawRequested(_) => {
|
Event::RedrawRequested(_) => {
|
||||||
let mut layout1 = Layout::new(CoordinateSystem::PositiveYDown);
|
let mut buffer =
|
||||||
|
TextBuffer::new(font_matches.as_ref().unwrap(), TextMetrics::new(20, 50));
|
||||||
|
|
||||||
layout1.reset(&LayoutSettings {
|
buffer.set_text("HELLO_FROM_COSMIC_INSIDE_OF_GLYPHON_WGPU");
|
||||||
x: 0.0,
|
buffer.shape_until_cursor();
|
||||||
y: 0.0,
|
|
||||||
..LayoutSettings::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
layout1.append(
|
|
||||||
fonts.as_slice(),
|
|
||||||
&TextStyle::with_user_data(
|
|
||||||
"Hello world!\nI'm on a new line!",
|
|
||||||
50.0,
|
|
||||||
0,
|
|
||||||
GlyphUserData,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut layout2 = Layout::new(CoordinateSystem::PositiveYDown);
|
|
||||||
|
|
||||||
layout2.reset(&LayoutSettings {
|
|
||||||
x: 0.0,
|
|
||||||
y: 200.0,
|
|
||||||
max_width: Some(200.0),
|
|
||||||
max_height: Some(190.0),
|
|
||||||
horizontal_align: HorizontalAlign::Center,
|
|
||||||
vertical_align: VerticalAlign::Middle,
|
|
||||||
..LayoutSettings::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
layout2.append(
|
|
||||||
fonts.as_slice(),
|
|
||||||
&TextStyle::with_user_data(
|
|
||||||
"abcdefghijklmnopqrstuvwxyz\nThis should be partially clipped!\nabcdefghijklmnopqrstuvwxyz",
|
|
||||||
25.0,
|
|
||||||
0,
|
|
||||||
GlyphUserData,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
text_renderer
|
text_renderer
|
||||||
.prepare(
|
.prepare(
|
||||||
|
@ -139,8 +105,7 @@ async fn run() {
|
||||||
width: config.width,
|
width: config.width,
|
||||||
height: config.height,
|
height: config.height,
|
||||||
},
|
},
|
||||||
&fonts,
|
&mut buffer,
|
||||||
&[(layout1, TextOverflow::Hide), (layout2, TextOverflow::Hide)],
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -1,204 +0,0 @@
|
||||||
use fontdue::layout::{HorizontalAlign, VerticalAlign};
|
|
||||||
use glyphon::{
|
|
||||||
fontdue::{
|
|
||||||
layout::{CoordinateSystem, Layout, LayoutSettings, TextStyle},
|
|
||||||
Font, FontSettings,
|
|
||||||
},
|
|
||||||
Color, HasColor, Resolution, TextAtlas, TextOverflow, TextRenderer,
|
|
||||||
};
|
|
||||||
use wgpu::{
|
|
||||||
Backends, CommandEncoderDescriptor, CompositeAlphaMode, DeviceDescriptor, Features, Instance,
|
|
||||||
Limits, LoadOp, Operations, PresentMode, RenderPassColorAttachment, RenderPassDescriptor,
|
|
||||||
RequestAdapterOptions, SurfaceConfiguration, TextureUsages, TextureViewDescriptor,
|
|
||||||
};
|
|
||||||
use winit::{
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
|
||||||
window::Window,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
pollster::block_on(run());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
struct GlyphUserData {
|
|
||||||
color: Color,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasColor for GlyphUserData {
|
|
||||||
fn color(&self) -> Color {
|
|
||||||
self.color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run() {
|
|
||||||
let instance = Instance::new(Backends::all());
|
|
||||||
let adapter = instance
|
|
||||||
.request_adapter(&RequestAdapterOptions::default())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let (device, queue) = adapter
|
|
||||||
.request_device(
|
|
||||||
&DeviceDescriptor {
|
|
||||||
label: None,
|
|
||||||
features: Features::empty(),
|
|
||||||
limits: Limits::downlevel_defaults(),
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
let window = Window::new(&event_loop).unwrap();
|
|
||||||
let surface = unsafe { instance.create_surface(&window) };
|
|
||||||
let size = window.inner_size();
|
|
||||||
let swapchain_format = surface.get_supported_formats(&adapter)[0];
|
|
||||||
let mut config = SurfaceConfiguration {
|
|
||||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
|
||||||
format: swapchain_format,
|
|
||||||
width: size.width,
|
|
||||||
height: size.height,
|
|
||||||
present_mode: PresentMode::Mailbox,
|
|
||||||
alpha_mode: CompositeAlphaMode::Opaque,
|
|
||||||
};
|
|
||||||
surface.configure(&device, &config);
|
|
||||||
|
|
||||||
let min_x = 100f32;
|
|
||||||
let width = 250f32;
|
|
||||||
let min_y = 100f32;
|
|
||||||
let height = 250f32;
|
|
||||||
|
|
||||||
let mut atlas = TextAtlas::new(&device, &queue, swapchain_format);
|
|
||||||
let mut text_renderer = TextRenderer::new(&device, &queue);
|
|
||||||
|
|
||||||
let font = include_bytes!("./Inter-Bold.ttf") as &[u8];
|
|
||||||
let font = Font::from_bytes(font, FontSettings::default()).unwrap();
|
|
||||||
let fonts = vec![font];
|
|
||||||
|
|
||||||
let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
|
|
||||||
|
|
||||||
const YELLOW: Color = Color {
|
|
||||||
r: 255,
|
|
||||||
g: 255,
|
|
||||||
b: 0,
|
|
||||||
a: 255,
|
|
||||||
};
|
|
||||||
const BLUE: Color = Color {
|
|
||||||
r: 0,
|
|
||||||
g: 0,
|
|
||||||
b: 255,
|
|
||||||
a: 255,
|
|
||||||
};
|
|
||||||
let mut color = YELLOW;
|
|
||||||
let mut color_changed = true;
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
|
||||||
let _ = (&instance, &adapter);
|
|
||||||
|
|
||||||
*control_flow = ControlFlow::Poll;
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::Resized(size),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
config.width = size.width;
|
|
||||||
config.height = size.height;
|
|
||||||
surface.configure(&device, &config);
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CursorMoved { position, .. },
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let cursor_in_layout = min_x <= position.x as f32
|
|
||||||
&& position.x as f32 <= min_x + width
|
|
||||||
&& min_y < position.y as f32
|
|
||||||
&& position.y as f32 <= min_y + height;
|
|
||||||
if cursor_in_layout {
|
|
||||||
if color != BLUE {
|
|
||||||
color = BLUE;
|
|
||||||
color_changed = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if color != YELLOW {
|
|
||||||
color = YELLOW;
|
|
||||||
color_changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::RedrawRequested(_) => {
|
|
||||||
if color_changed {
|
|
||||||
color_changed = false;
|
|
||||||
println!("Recreating layout because color changed");
|
|
||||||
layout.reset(&LayoutSettings {
|
|
||||||
x: min_x,
|
|
||||||
y: min_y,
|
|
||||||
max_width: Some(width),
|
|
||||||
max_height: Some(height),
|
|
||||||
horizontal_align: HorizontalAlign::Center,
|
|
||||||
vertical_align: VerticalAlign::Middle,
|
|
||||||
..LayoutSettings::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
layout.append(
|
|
||||||
fonts.as_slice(),
|
|
||||||
&TextStyle::with_user_data(
|
|
||||||
"Move your mouse over this region to make it blue!",
|
|
||||||
20.0,
|
|
||||||
0,
|
|
||||||
GlyphUserData { color },
|
|
||||||
),
|
|
||||||
);
|
|
||||||
text_renderer
|
|
||||||
.prepare(
|
|
||||||
&device,
|
|
||||||
&queue,
|
|
||||||
&mut atlas,
|
|
||||||
Resolution {
|
|
||||||
width: config.width,
|
|
||||||
height: config.height,
|
|
||||||
},
|
|
||||||
&fonts,
|
|
||||||
&[(&layout, TextOverflow::Hide)],
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let frame = surface.get_current_texture().unwrap();
|
|
||||||
let view = frame.texture.create_view(&TextureViewDescriptor::default());
|
|
||||||
|
|
||||||
let mut encoder =
|
|
||||||
device.create_command_encoder(&CommandEncoderDescriptor { label: None });
|
|
||||||
{
|
|
||||||
let mut pass = encoder.begin_render_pass(&RenderPassDescriptor {
|
|
||||||
label: None,
|
|
||||||
color_attachments: &[Some(RenderPassColorAttachment {
|
|
||||||
view: &view,
|
|
||||||
resolve_target: None,
|
|
||||||
ops: Operations {
|
|
||||||
load: LoadOp::Clear(wgpu::Color::BLACK),
|
|
||||||
store: true,
|
|
||||||
},
|
|
||||||
})],
|
|
||||||
depth_stencil_attachment: None,
|
|
||||||
});
|
|
||||||
|
|
||||||
text_renderer.render(&atlas, &mut pass).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
queue.submit(Some(encoder.finish()));
|
|
||||||
frame.present();
|
|
||||||
}
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
..
|
|
||||||
} => *control_flow = ControlFlow::Exit,
|
|
||||||
Event::MainEventsCleared => {
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -6,11 +6,12 @@ mod text_atlas;
|
||||||
mod text_render;
|
mod text_render;
|
||||||
|
|
||||||
pub use error::{PrepareError, RenderError};
|
pub use error::{PrepareError, RenderError};
|
||||||
pub use fontdue;
|
|
||||||
use recently_used::RecentlyUsedMap;
|
use recently_used::RecentlyUsedMap;
|
||||||
pub use text_atlas::TextAtlas;
|
pub use text_atlas::TextAtlas;
|
||||||
pub use text_render::TextRenderer;
|
pub use text_render::TextRenderer;
|
||||||
|
|
||||||
|
pub use cosmic_text;
|
||||||
|
|
||||||
/// The color to use when rendering text.
|
/// The color to use when rendering text.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
use cosmic_text::CacheKey;
|
||||||
use etagere::{size2, BucketedAtlasAllocator};
|
use etagere::{size2, BucketedAtlasAllocator};
|
||||||
use fontdue::layout::GlyphRasterConfig;
|
|
||||||
use std::{borrow::Cow, mem::size_of, num::NonZeroU64, sync::Arc};
|
use std::{borrow::Cow, mem::size_of, num::NonZeroU64, sync::Arc};
|
||||||
use wgpu::{
|
use wgpu::{
|
||||||
BindGroup, BindGroupEntry, BindGroupLayoutEntry, BindingResource, BindingType, BlendState,
|
BindGroup, BindGroupEntry, BindGroupLayoutEntry, BindingResource, BindingType, BlendState,
|
||||||
|
@ -20,7 +20,7 @@ pub struct TextAtlas {
|
||||||
pub(crate) packer: BucketedAtlasAllocator,
|
pub(crate) packer: BucketedAtlasAllocator,
|
||||||
pub(crate) width: u32,
|
pub(crate) width: u32,
|
||||||
pub(crate) height: u32,
|
pub(crate) height: u32,
|
||||||
pub(crate) glyph_cache: RecentlyUsedMap<GlyphRasterConfig, GlyphDetails>,
|
pub(crate) glyph_cache: RecentlyUsedMap<CacheKey, GlyphDetails>,
|
||||||
pub(crate) params: Params,
|
pub(crate) params: Params,
|
||||||
pub(crate) params_buffer: Buffer,
|
pub(crate) params_buffer: Buffer,
|
||||||
pub(crate) pipeline: Arc<RenderPipeline>,
|
pub(crate) pipeline: Arc<RenderPipeline>,
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
|
use cosmic_text::{CacheKey, TextBuffer};
|
||||||
use etagere::{size2, Allocation};
|
use etagere::{size2, Allocation};
|
||||||
use fontdue::{
|
|
||||||
layout::{GlyphRasterConfig, Layout},
|
use std::{collections::HashSet, iter, mem::size_of, num::NonZeroU32, slice};
|
||||||
Font,
|
|
||||||
};
|
|
||||||
use std::{borrow::Borrow, collections::HashSet, iter, mem::size_of, num::NonZeroU32, slice};
|
|
||||||
use wgpu::{
|
use wgpu::{
|
||||||
Buffer, BufferDescriptor, BufferUsages, Device, Extent3d, ImageCopyTexture, ImageDataLayout,
|
Buffer, BufferDescriptor, BufferUsages, Device, Extent3d, ImageCopyTexture, ImageDataLayout,
|
||||||
IndexFormat, Origin3d, Queue, RenderPass, TextureAspect, COPY_BUFFER_ALIGNMENT,
|
IndexFormat, Origin3d, Queue, RenderPass, TextureAspect, COPY_BUFFER_ALIGNMENT,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
GlyphDetails, GlyphToRender, GpuCache, HasColor, Params, PrepareError, RenderError, Resolution,
|
GlyphDetails, GlyphToRender, GpuCache, Params, PrepareError, RenderError, Resolution,
|
||||||
TextAtlas, TextOverflow,
|
TextAtlas, TextOverflow,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,7 +19,7 @@ pub struct TextRenderer {
|
||||||
index_buffer: Buffer,
|
index_buffer: Buffer,
|
||||||
index_buffer_size: u64,
|
index_buffer_size: u64,
|
||||||
vertices_to_render: u32,
|
vertices_to_render: u32,
|
||||||
glyphs_in_use: HashSet<GlyphRasterConfig>,
|
glyphs_in_use: HashSet<CacheKey>,
|
||||||
screen_resolution: Resolution,
|
screen_resolution: Resolution,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,14 +57,13 @@ impl TextRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Prepares all of the provided layouts for rendering.
|
/// Prepares all of the provided layouts for rendering.
|
||||||
pub fn prepare<C: HasColor>(
|
pub fn prepare<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &Device,
|
device: &Device,
|
||||||
queue: &Queue,
|
queue: &Queue,
|
||||||
atlas: &mut TextAtlas,
|
atlas: &mut TextAtlas,
|
||||||
screen_resolution: Resolution,
|
screen_resolution: Resolution,
|
||||||
fonts: &[Font],
|
buffer: &mut TextBuffer<'a>,
|
||||||
layouts: &[(impl Borrow<Layout<C>>, TextOverflow)],
|
|
||||||
) -> Result<(), PrepareError> {
|
) -> Result<(), PrepareError> {
|
||||||
self.screen_resolution = screen_resolution;
|
self.screen_resolution = screen_resolution;
|
||||||
|
|
||||||
|
@ -92,75 +89,91 @@ impl TextRenderer {
|
||||||
|
|
||||||
self.glyphs_in_use.clear();
|
self.glyphs_in_use.clear();
|
||||||
|
|
||||||
for (layout, _) in layouts.iter() {
|
let mut buffers = [(buffer, TextOverflow::Hide)];
|
||||||
for glyph in layout.borrow().glyphs() {
|
|
||||||
self.glyphs_in_use.insert(glyph.key);
|
|
||||||
|
|
||||||
let already_on_gpu = atlas.glyph_cache.contains_key(&glyph.key);
|
for (buffer, _) in buffers.iter_mut() {
|
||||||
|
for line in buffer.lines.iter() {
|
||||||
if already_on_gpu {
|
let layout = match line.layout_opt.as_ref() {
|
||||||
continue;
|
Some(l) => l,
|
||||||
}
|
None => continue,
|
||||||
|
|
||||||
let font = &fonts[glyph.font_index];
|
|
||||||
let (metrics, bitmap) = font.rasterize_config(glyph.key);
|
|
||||||
|
|
||||||
let (gpu_cache, atlas_id) = if glyph.char_data.rasterize() {
|
|
||||||
// Find a position in the packer
|
|
||||||
let allocation = match try_allocate(atlas, metrics.width, metrics.height) {
|
|
||||||
Some(a) => a,
|
|
||||||
None => return Err(PrepareError::AtlasFull),
|
|
||||||
};
|
|
||||||
let atlas_min = allocation.rectangle.min;
|
|
||||||
let atlas_max = allocation.rectangle.max;
|
|
||||||
|
|
||||||
for row in 0..metrics.height {
|
|
||||||
let y_offset = atlas_min.y as usize;
|
|
||||||
let x_offset =
|
|
||||||
(y_offset + row) * atlas.width as usize + atlas_min.x as usize;
|
|
||||||
let bitmap_row = &bitmap[row * metrics.width..(row + 1) * metrics.width];
|
|
||||||
atlas.texture_pending[x_offset..x_offset + metrics.width]
|
|
||||||
.copy_from_slice(bitmap_row);
|
|
||||||
}
|
|
||||||
|
|
||||||
match upload_bounds.as_mut() {
|
|
||||||
Some(ub) => {
|
|
||||||
ub.x_min = ub.x_min.min(atlas_min.x as usize);
|
|
||||||
ub.x_max = ub.x_max.max(atlas_max.x as usize);
|
|
||||||
ub.y_min = ub.y_min.min(atlas_min.y as usize);
|
|
||||||
ub.y_max = ub.y_max.max(atlas_max.y as usize);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
upload_bounds = Some(UploadBounds {
|
|
||||||
x_min: atlas_min.x as usize,
|
|
||||||
x_max: atlas_max.x as usize,
|
|
||||||
y_min: atlas_min.y as usize,
|
|
||||||
y_max: atlas_max.y as usize,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(
|
|
||||||
GpuCache::InAtlas {
|
|
||||||
x: atlas_min.x as u16,
|
|
||||||
y: atlas_min.y as u16,
|
|
||||||
},
|
|
||||||
Some(allocation.id),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(GpuCache::SkipRasterization, None)
|
|
||||||
};
|
};
|
||||||
|
for layout_line in layout {
|
||||||
|
for glyph in layout_line.glyphs.iter() {
|
||||||
|
let key = glyph.inner.0;
|
||||||
|
|
||||||
if !atlas.glyph_cache.contains_key(&glyph.key) {
|
self.glyphs_in_use.insert(key);
|
||||||
atlas.glyph_cache.insert(
|
|
||||||
glyph.key,
|
let already_on_gpu = atlas.glyph_cache.contains_key(&key);
|
||||||
GlyphDetails {
|
|
||||||
width: metrics.width as u16,
|
if already_on_gpu {
|
||||||
height: metrics.height as u16,
|
continue;
|
||||||
gpu_cache,
|
}
|
||||||
atlas_id,
|
|
||||||
},
|
let image = layout_line.rasterize_glyph(glyph).unwrap();
|
||||||
);
|
let bitmap = image.data.clone();
|
||||||
|
let width = image.placement.width as usize;
|
||||||
|
let height = image.placement.height as usize;
|
||||||
|
|
||||||
|
let should_rasterize = width > 0 && height > 0;
|
||||||
|
|
||||||
|
let (gpu_cache, atlas_id) = if should_rasterize {
|
||||||
|
// Find a position in the packer
|
||||||
|
let allocation = match try_allocate(atlas, width, height) {
|
||||||
|
Some(a) => a,
|
||||||
|
None => return Err(PrepareError::AtlasFull),
|
||||||
|
};
|
||||||
|
let atlas_min = allocation.rectangle.min;
|
||||||
|
let atlas_max = allocation.rectangle.max;
|
||||||
|
|
||||||
|
for row in 0..height {
|
||||||
|
let y_offset = atlas_min.y as usize;
|
||||||
|
let x_offset =
|
||||||
|
(y_offset + row) * atlas.width as usize + atlas_min.x as usize;
|
||||||
|
let bitmap_row = &bitmap[row * width..(row + 1) * width];
|
||||||
|
atlas.texture_pending[x_offset..x_offset + width]
|
||||||
|
.copy_from_slice(bitmap_row);
|
||||||
|
}
|
||||||
|
|
||||||
|
match upload_bounds.as_mut() {
|
||||||
|
Some(ub) => {
|
||||||
|
ub.x_min = ub.x_min.min(atlas_min.x as usize);
|
||||||
|
ub.x_max = ub.x_max.max(atlas_max.x as usize);
|
||||||
|
ub.y_min = ub.y_min.min(atlas_min.y as usize);
|
||||||
|
ub.y_max = ub.y_max.max(atlas_max.y as usize);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
upload_bounds = Some(UploadBounds {
|
||||||
|
x_min: atlas_min.x as usize,
|
||||||
|
x_max: atlas_max.x as usize,
|
||||||
|
y_min: atlas_min.y as usize,
|
||||||
|
y_max: atlas_max.y as usize,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
GpuCache::InAtlas {
|
||||||
|
x: atlas_min.x as u16,
|
||||||
|
y: atlas_min.y as u16,
|
||||||
|
},
|
||||||
|
Some(allocation.id),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(GpuCache::SkipRasterization, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
if !atlas.glyph_cache.contains_key(&key) {
|
||||||
|
atlas.glyph_cache.insert(
|
||||||
|
key,
|
||||||
|
GlyphDetails {
|
||||||
|
width: width as u16,
|
||||||
|
height: height as u16,
|
||||||
|
gpu_cache,
|
||||||
|
atlas_id,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -191,101 +204,111 @@ impl TextRenderer {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut glyph_vertices = Vec::new();
|
let mut glyph_vertices: Vec<GlyphToRender> = Vec::new();
|
||||||
let mut glyph_indices = Vec::new();
|
let mut glyph_indices: Vec<u32> = Vec::new();
|
||||||
let mut glyphs_added = 0;
|
let mut glyphs_added = 0;
|
||||||
|
|
||||||
for (layout, overflow) in layouts.iter() {
|
for (buffer, overflow) in buffers.iter() {
|
||||||
let layout: &Layout<C> = layout.borrow();
|
|
||||||
let settings = layout.settings();
|
|
||||||
|
|
||||||
// Note: subpixel positioning is not currently handled, so we always truncate down to
|
// Note: subpixel positioning is not currently handled, so we always truncate down to
|
||||||
// the nearest pixel.
|
// the nearest pixel.
|
||||||
let bounds_min_x = settings.x.trunc();
|
let bounds_min_x = 0u32;
|
||||||
let bounds_max_x = settings
|
let bounds_max_x = u32::MAX;
|
||||||
.max_width
|
let bounds_min_y = 0u32;
|
||||||
.map(|w| bounds_min_x + w.trunc())
|
let bounds_max_y = u32::MAX;
|
||||||
.unwrap_or(f32::MAX);
|
|
||||||
let bounds_min_y = settings.y.trunc();
|
|
||||||
let bounds_max_y = settings
|
|
||||||
.max_height
|
|
||||||
.map(|h| bounds_min_y + h.trunc())
|
|
||||||
.unwrap_or(f32::MAX);
|
|
||||||
|
|
||||||
for glyph in layout.glyphs() {
|
for line in buffer.lines.iter() {
|
||||||
let mut x = glyph.x;
|
let layout = match line.layout_opt.as_ref() {
|
||||||
let mut y = glyph.y;
|
Some(l) => l,
|
||||||
|
None => continue,
|
||||||
let details = atlas.glyph_cache.get(&glyph.key).unwrap();
|
|
||||||
let (mut atlas_x, mut atlas_y) = match details.gpu_cache {
|
|
||||||
GpuCache::InAtlas { x, y } => (x, y),
|
|
||||||
GpuCache::SkipRasterization => continue,
|
|
||||||
};
|
};
|
||||||
|
for layout_line in layout {
|
||||||
|
for glyph in layout_line.glyphs.iter() {
|
||||||
|
let key = glyph.inner.0;
|
||||||
|
|
||||||
let mut width = details.width as f32;
|
let details = atlas.glyph_cache.get(&key).unwrap();
|
||||||
let mut height = details.height as f32;
|
|
||||||
|
|
||||||
match overflow {
|
let mut x = glyph.x.trunc() as u32;
|
||||||
TextOverflow::Overflow => {}
|
let mut y: u32 = (buffer.metrics.line_height as i32
|
||||||
TextOverflow::Hide => {
|
- details.height as i32)
|
||||||
// Starts beyond right edge or ends beyond left edge
|
.try_into()
|
||||||
let max_x = x + width;
|
.unwrap();
|
||||||
if x > bounds_max_x || max_x < bounds_min_x {
|
|
||||||
continue;
|
let (mut atlas_x, mut atlas_y) = match details.gpu_cache {
|
||||||
|
GpuCache::InAtlas { x, y } => (x, y),
|
||||||
|
GpuCache::SkipRasterization => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut width = details.width as u32;
|
||||||
|
let mut height = details.height as u32;
|
||||||
|
|
||||||
|
match overflow {
|
||||||
|
TextOverflow::Overflow => {}
|
||||||
|
TextOverflow::Hide => {
|
||||||
|
// Starts beyond right edge or ends beyond left edge
|
||||||
|
let max_x = x + width;
|
||||||
|
if x > bounds_max_x || max_x < bounds_min_x {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starts beyond bottom edge or ends beyond top edge
|
||||||
|
let max_y = y + height;
|
||||||
|
if y > bounds_max_y || max_y < bounds_min_y {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clip left ege
|
||||||
|
if x < bounds_min_x {
|
||||||
|
let right_shift = bounds_min_x - x;
|
||||||
|
|
||||||
|
x = bounds_min_x;
|
||||||
|
width = max_x - bounds_min_x;
|
||||||
|
atlas_x += right_shift as u16;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clip right edge
|
||||||
|
if x + width > bounds_max_x {
|
||||||
|
width = bounds_max_x - x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clip top edge
|
||||||
|
if y < bounds_min_y {
|
||||||
|
let bottom_shift = bounds_min_y - y;
|
||||||
|
|
||||||
|
y = bounds_min_y;
|
||||||
|
height = max_y - bounds_min_y;
|
||||||
|
atlas_y += bottom_shift as u16;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clip bottom edge
|
||||||
|
if y + height > bounds_max_y {
|
||||||
|
height = bounds_max_y - y;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starts beyond bottom edge or ends beyond top edge
|
glyph_vertices.extend(
|
||||||
let max_y = y + height;
|
iter::repeat(GlyphToRender {
|
||||||
if y > bounds_max_y || max_y < bounds_min_y {
|
pos: [x as i32, y as i32],
|
||||||
continue;
|
dim: [width as u16, height as u16],
|
||||||
}
|
uv: [atlas_x, atlas_y],
|
||||||
|
color: [255, 0, 255, 255],
|
||||||
|
})
|
||||||
|
.take(4),
|
||||||
|
);
|
||||||
|
|
||||||
// Clip left ege
|
let start = 4 * glyphs_added as u32;
|
||||||
if x < bounds_min_x {
|
glyph_indices.extend([
|
||||||
let right_shift = bounds_min_x - x;
|
start,
|
||||||
|
start + 1,
|
||||||
|
start + 2,
|
||||||
|
start,
|
||||||
|
start + 2,
|
||||||
|
start + 3,
|
||||||
|
]);
|
||||||
|
|
||||||
x = bounds_min_x;
|
glyphs_added += 1;
|
||||||
width = max_x - bounds_min_x;
|
|
||||||
atlas_x += right_shift as u16;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clip right edge
|
|
||||||
if x + width > bounds_max_x {
|
|
||||||
width = bounds_max_x - x;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clip top edge
|
|
||||||
if y < bounds_min_y {
|
|
||||||
let bottom_shift = bounds_min_y - y;
|
|
||||||
|
|
||||||
y = bounds_min_y;
|
|
||||||
height = max_y - bounds_min_y;
|
|
||||||
atlas_y += bottom_shift as u16;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clip bottom edge
|
|
||||||
if y + height > bounds_max_y {
|
|
||||||
height = bounds_max_y - y;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let color = glyph.user_data.color();
|
|
||||||
|
|
||||||
glyph_vertices.extend(
|
|
||||||
iter::repeat(GlyphToRender {
|
|
||||||
pos: [x as i32, y as i32],
|
|
||||||
dim: [width as u16, height as u16],
|
|
||||||
uv: [atlas_x, atlas_y],
|
|
||||||
color: [color.r, color.g, color.b, color.a],
|
|
||||||
})
|
|
||||||
.take(4),
|
|
||||||
);
|
|
||||||
|
|
||||||
let start = 4 * glyphs_added as u32;
|
|
||||||
glyph_indices.extend([start, start + 1, start + 2, start, start + 2, start + 3]);
|
|
||||||
|
|
||||||
glyphs_added += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue