adding together a jump-flood algorithm for rendering outlines
Some checks are pending
Format / all (push) Waiting to run
Lint / all (push) Waiting to run
Test / all (macOS-latest, beta) (push) Waiting to run
Test / all (macOS-latest, stable) (push) Waiting to run
Test / all (ubuntu-latest, beta) (push) Waiting to run
Test / all (ubuntu-latest, stable) (push) Waiting to run
Test / all (windows-latest, beta) (push) Waiting to run
Test / all (windows-latest, stable) (push) Waiting to run
Some checks are pending
Format / all (push) Waiting to run
Lint / all (push) Waiting to run
Test / all (macOS-latest, beta) (push) Waiting to run
Test / all (macOS-latest, stable) (push) Waiting to run
Test / all (ubuntu-latest, beta) (push) Waiting to run
Test / all (ubuntu-latest, stable) (push) Waiting to run
Test / all (windows-latest, beta) (push) Waiting to run
Test / all (windows-latest, stable) (push) Waiting to run
This commit is contained in:
parent
84de74e7c2
commit
da3b02cf8a
6 changed files with 332 additions and 209 deletions
|
@ -1,6 +1,7 @@
|
||||||
use cryoglyph::{
|
use cryoglyph::{
|
||||||
Attrs, Buffer, Cache, Color, Family, FontSystem, Metrics, Resolution, Shaping, SwashCache,
|
cosmic_text::LetterSpacing, Attrs, AttrsOwned, Buffer, Cache, Color, Family, FontSystem,
|
||||||
TextArea, TextAtlas, TextBounds, TextRenderer, Viewport,
|
Metrics, Resolution, Shaping, SwashCache, TextArea, TextAtlas, TextBounds, TextRenderer,
|
||||||
|
Viewport,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wgpu::{
|
use wgpu::{
|
||||||
|
@ -169,6 +170,21 @@ impl winit::application::ApplicationHandler for Application {
|
||||||
|
|
||||||
let mut encoder =
|
let mut encoder =
|
||||||
device.create_command_encoder(&CommandEncoderDescriptor { label: None });
|
device.create_command_encoder(&CommandEncoderDescriptor { label: None });
|
||||||
|
let text_area = TextArea {
|
||||||
|
buffer: text_buffer,
|
||||||
|
left: 10.0,
|
||||||
|
top: 10.0,
|
||||||
|
scale: 3.5,
|
||||||
|
bounds: TextBounds {
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
right: 600,
|
||||||
|
bottom: 160,
|
||||||
|
},
|
||||||
|
default_color: Color::rgb(255, 255, 255),
|
||||||
|
stroke_size: 5.5,
|
||||||
|
stroke_color: Color::rgb(50, 50, 255),
|
||||||
|
};
|
||||||
|
|
||||||
text_renderer
|
text_renderer
|
||||||
.prepare(
|
.prepare(
|
||||||
|
@ -178,19 +194,7 @@ impl winit::application::ApplicationHandler for Application {
|
||||||
font_system,
|
font_system,
|
||||||
atlas,
|
atlas,
|
||||||
viewport,
|
viewport,
|
||||||
[TextArea {
|
[text_area],
|
||||||
buffer: text_buffer,
|
|
||||||
left: 10.0,
|
|
||||||
top: 10.0,
|
|
||||||
scale: 1.0,
|
|
||||||
bounds: TextBounds {
|
|
||||||
left: 0,
|
|
||||||
top: 0,
|
|
||||||
right: 600,
|
|
||||||
bottom: 160,
|
|
||||||
},
|
|
||||||
default_color: Color::rgb(0, 0, 0),
|
|
||||||
}],
|
|
||||||
swash_cache,
|
swash_cache,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -205,7 +209,7 @@ impl winit::application::ApplicationHandler for Application {
|
||||||
view: &view,
|
view: &view,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: Operations {
|
ops: Operations {
|
||||||
load: LoadOp::Clear(wgpu::Color::WHITE),
|
load: LoadOp::Clear(wgpu::Color::BLACK),
|
||||||
store: wgpu::StoreOp::Store,
|
store: wgpu::StoreOp::Store,
|
||||||
},
|
},
|
||||||
})],
|
})],
|
||||||
|
|
10
src/cache.rs
10
src/cache.rs
|
@ -87,6 +87,16 @@ impl Cache {
|
||||||
offset: mem::size_of::<u32>() as u64 * 6,
|
offset: mem::size_of::<u32>() as u64 * 6,
|
||||||
shader_location: 5,
|
shader_location: 5,
|
||||||
},
|
},
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
format: VertexFormat::Uint32,
|
||||||
|
offset: mem::size_of::<u32>() as u64 * 7,
|
||||||
|
shader_location: 6,
|
||||||
|
},
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
format: VertexFormat::Float32,
|
||||||
|
offset: mem::size_of::<u32>() as u64 * 8,
|
||||||
|
shader_location: 7,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
12
src/lib.rs
12
src/lib.rs
|
@ -21,11 +21,11 @@ use text_render::ContentType;
|
||||||
// Re-export all top-level types from `cosmic-text` for convenience.
|
// Re-export all top-level types from `cosmic-text` for convenience.
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use cosmic_text::{
|
pub use cosmic_text::{
|
||||||
self, Action, Affinity, Attrs, AttrsList, AttrsOwned, Buffer, BufferLine, CacheKey, Color,
|
self, fontdb, Action, Affinity, Attrs, AttrsList, AttrsOwned, Buffer, BufferLine, CacheKey,
|
||||||
Command, Cursor, Edit, Editor, Family, FamilyOwned, Font, FontSystem, LayoutCursor,
|
Color, Command, Cursor, Edit, Editor, Family, FamilyOwned, Font, FontSystem, LayoutCursor,
|
||||||
LayoutGlyph, LayoutLine, LayoutRun, LayoutRunIter, Metrics, ShapeGlyph, ShapeLine, ShapeSpan,
|
LayoutGlyph, LayoutLine, LayoutRun, LayoutRunIter, Metrics, ShapeGlyph, ShapeLine, ShapeSpan,
|
||||||
ShapeWord, Shaping, Stretch, Style, SubpixelBin, SwashCache, SwashContent, SwashImage, Weight,
|
ShapeWord, Shaping, Stretch, Style, SubpixelBin, SwashCache, SwashContent, SwashImage, Weight,
|
||||||
Wrap, fontdb,
|
Wrap,
|
||||||
};
|
};
|
||||||
|
|
||||||
use etagere::AllocId;
|
use etagere::AllocId;
|
||||||
|
@ -57,6 +57,8 @@ pub(crate) struct GlyphToRender {
|
||||||
color: u32,
|
color: u32,
|
||||||
content_type_with_srgb: [u16; 2],
|
content_type_with_srgb: [u16; 2],
|
||||||
depth: f32,
|
depth: f32,
|
||||||
|
stroke_color: u32,
|
||||||
|
stroke_size: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The screen resolution to use when rendering text.
|
/// The screen resolution to use when rendering text.
|
||||||
|
@ -117,4 +119,8 @@ pub struct TextArea<'a> {
|
||||||
pub bounds: TextBounds,
|
pub bounds: TextBounds,
|
||||||
// The default color of the text area.
|
// The default color of the text area.
|
||||||
pub default_color: Color,
|
pub default_color: Color,
|
||||||
|
/// The stroke (outline) size
|
||||||
|
pub stroke_size: f32,
|
||||||
|
/// The stroke (outline) color
|
||||||
|
pub stroke_color: Color,
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ struct VertexInput {
|
||||||
@location(3) color: u32,
|
@location(3) color: u32,
|
||||||
@location(4) content_type_with_srgb: u32,
|
@location(4) content_type_with_srgb: u32,
|
||||||
@location(5) depth: f32,
|
@location(5) depth: f32,
|
||||||
|
@location(6) stroke_color: u32,
|
||||||
|
@location(7) stroke_size: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VertexOutput {
|
struct VertexOutput {
|
||||||
|
@ -13,6 +15,8 @@ struct VertexOutput {
|
||||||
@location(0) color: vec4<f32>,
|
@location(0) color: vec4<f32>,
|
||||||
@location(1) uv: vec2<f32>,
|
@location(1) uv: vec2<f32>,
|
||||||
@location(2) @interpolate(flat) content_type: u32,
|
@location(2) @interpolate(flat) content_type: u32,
|
||||||
|
@location(3) stroke_color: vec4<f32>,
|
||||||
|
@location(4) stroke_size: f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Params {
|
struct Params {
|
||||||
|
@ -42,11 +46,13 @@ fn srgb_to_linear(c: f32) -> f32 {
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vs_main(in_vert: VertexInput) -> VertexOutput {
|
fn vs_main(in_vert: VertexInput) -> VertexOutput {
|
||||||
var pos = in_vert.pos;
|
var stroke = in_vert.stroke_size;
|
||||||
let width = in_vert.dim & 0xffffu;
|
var width = in_vert.dim & 0xffffu;
|
||||||
let height = (in_vert.dim & 0xffff0000u) >> 16u;
|
var height = (in_vert.dim & 0xffff0000u) >> 16u;
|
||||||
|
var pos = in_vert.pos;
|
||||||
|
var uv = vec2<u32>(in_vert.uv & 0xffffu, (in_vert.uv & 0xffff0000u) >> 16u);
|
||||||
let color = in_vert.color;
|
let color = in_vert.color;
|
||||||
var uv = vec2<u32>(in_vert.uv & 0xffffu, (in_vert.uv & 0xffff0000u) >> 16u);
|
let stroke_color = in_vert.stroke_color;
|
||||||
let v = in_vert.vertex_idx;
|
let v = in_vert.vertex_idx;
|
||||||
|
|
||||||
let corner_position = vec2<u32>(
|
let corner_position = vec2<u32>(
|
||||||
|
@ -80,6 +86,12 @@ fn vs_main(in_vert: VertexInput) -> VertexOutput {
|
||||||
f32(color & 0x000000ffu) / 255.0,
|
f32(color & 0x000000ffu) / 255.0,
|
||||||
f32((color & 0xff000000u) >> 24u) / 255.0,
|
f32((color & 0xff000000u) >> 24u) / 255.0,
|
||||||
);
|
);
|
||||||
|
vert_output.stroke_color = vec4<f32>(
|
||||||
|
f32((stroke_color & 0x00ff0000u) >> 16u) / 255.0,
|
||||||
|
f32((stroke_color & 0x0000ff00u) >> 8u) / 255.0,
|
||||||
|
f32(stroke_color & 0x000000ffu) / 255.0,
|
||||||
|
f32((stroke_color & 0xff000000u) >> 24u) / 255.0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
case 1u: {
|
case 1u: {
|
||||||
vert_output.color = vec4<f32>(
|
vert_output.color = vec4<f32>(
|
||||||
|
@ -88,6 +100,12 @@ fn vs_main(in_vert: VertexInput) -> VertexOutput {
|
||||||
srgb_to_linear(f32(color & 0x000000ffu) / 255.0),
|
srgb_to_linear(f32(color & 0x000000ffu) / 255.0),
|
||||||
f32((color & 0xff000000u) >> 24u) / 255.0,
|
f32((color & 0xff000000u) >> 24u) / 255.0,
|
||||||
);
|
);
|
||||||
|
vert_output.stroke_color = vec4<f32>(
|
||||||
|
srgb_to_linear(f32((stroke_color & 0x00ff0000u) >> 16u) / 255.0),
|
||||||
|
srgb_to_linear(f32((stroke_color & 0x0000ff00u) >> 8u) / 255.0),
|
||||||
|
srgb_to_linear(f32(stroke_color & 0x000000ffu) / 255.0),
|
||||||
|
f32((stroke_color & 0xff000000u) >> 24u) / 255.0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
default: {}
|
default: {}
|
||||||
}
|
}
|
||||||
|
@ -108,20 +126,54 @@ fn vs_main(in_vert: VertexInput) -> VertexOutput {
|
||||||
vert_output.content_type = content_type;
|
vert_output.content_type = content_type;
|
||||||
|
|
||||||
vert_output.uv = vec2<f32>(uv) / vec2<f32>(dim);
|
vert_output.uv = vec2<f32>(uv) / vec2<f32>(dim);
|
||||||
|
vert_output.stroke_size = in_vert.stroke_size;
|
||||||
|
|
||||||
return vert_output;
|
return vert_output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fs_main(in_frag: VertexOutput) -> @location(0) vec4<f32> {
|
fn fs_main(in_frag: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
let dims = vec2<f32>(textureDimensions(mask_atlas_texture));
|
||||||
|
var bit: u32 = 1;
|
||||||
|
|
||||||
|
let current = textureSample(mask_atlas_texture, atlas_sampler, dims);
|
||||||
|
var closest_dist = distance(vec2<f32>(in_frag.position.xy), vec2<f32>(current.xy));
|
||||||
|
var result = current;
|
||||||
|
|
||||||
|
for (var step: u32 = 0; step <= 32; step++) {
|
||||||
|
bit = 1u << step;
|
||||||
|
for (var dy = -1; dy <= 1; dy++) {
|
||||||
|
for (var dx = -1; dx <= 1; dx++) {
|
||||||
|
if (dx == 0 && dy == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let neighbour_coord = in_frag.position.xy + vec2<f32>(f32(dx * bit), f32(dy * bit));
|
||||||
|
let neighbour = textureSample(mask_atlas_texture, atlas_sampler, neighbour_coord / dims);
|
||||||
|
let dist = distance(vec2<f32>(in_frag.position.xy), vec2<f32>(neighbour.xy));
|
||||||
|
|
||||||
|
if (dist < closest_dist) {
|
||||||
|
closest_dist = dist;
|
||||||
|
result = vec4<f32>(in_frag.stroke_color.rgb, in_frag.stroke_color.a * textureSampleLevel(mask_atlas_texture, atlas_sampler, neighbour.xy, 0.0).x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
switch in_frag.content_type {
|
switch in_frag.content_type {
|
||||||
case 0u: {
|
case 0u: {
|
||||||
return textureSampleLevel(color_atlas_texture, atlas_sampler, in_frag.uv, 0.0);
|
return textureSampleLevel(color_atlas_texture, atlas_sampler, in_frag.uv, 0.0);
|
||||||
}
|
}
|
||||||
case 1u: {
|
case 1u: {
|
||||||
|
if in_frag.stroke_size > 0.0 {
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
return vec4<f32>(in_frag.color.rgb, in_frag.color.a * textureSampleLevel(mask_atlas_texture, atlas_sampler, in_frag.uv, 0.0).x);
|
return vec4<f32>(in_frag.color.rgb, in_frag.color.a * textureSampleLevel(mask_atlas_texture, atlas_sampler, in_frag.uv, 0.0).x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
// return result;
|
||||||
return vec4<f32>(0.0);
|
return vec4<f32>(0.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
Cache, CacheKey, FontSystem, GlyphDetails, GpuCacheStatus, SwashCache, text_render::ContentType,
|
text_render::ContentType, Cache, CacheKey, FontSystem, GlyphDetails, GpuCacheStatus, SwashCache,
|
||||||
};
|
};
|
||||||
use etagere::{Allocation, BucketedAtlasAllocator, size2};
|
use etagere::{size2, Allocation, BucketedAtlasAllocator};
|
||||||
use lru::LruCache;
|
use lru::LruCache;
|
||||||
use rustc_hash::FxHasher;
|
use rustc_hash::FxHasher;
|
||||||
use std::{collections::HashSet, hash::BuildHasherDefault};
|
use std::{collections::HashSet, hash::BuildHasherDefault};
|
||||||
|
|
|
@ -2,12 +2,13 @@ use crate::{
|
||||||
ColorMode, FontSystem, GlyphDetails, GlyphToRender, GpuCacheStatus, PrepareError, RenderError,
|
ColorMode, FontSystem, GlyphDetails, GlyphToRender, GpuCacheStatus, PrepareError, RenderError,
|
||||||
SwashCache, SwashContent, TextArea, TextAtlas, Viewport,
|
SwashCache, SwashContent, TextArea, TextAtlas, Viewport,
|
||||||
};
|
};
|
||||||
|
use cosmic_text::LayoutRun;
|
||||||
use std::{num::NonZeroU64, slice};
|
use std::{num::NonZeroU64, slice};
|
||||||
use wgpu::util::StagingBelt;
|
use wgpu::util::StagingBelt;
|
||||||
use wgpu::{
|
use wgpu::{
|
||||||
Buffer, BufferDescriptor, BufferUsages, COPY_BUFFER_ALIGNMENT, CommandEncoder,
|
Buffer, BufferDescriptor, BufferUsages, CommandEncoder, DepthStencilState, Device, Extent3d,
|
||||||
DepthStencilState, Device, Extent3d, MultisampleState, Origin3d, Queue, RenderPass,
|
MultisampleState, Origin3d, Queue, RenderPass, RenderPipeline, TexelCopyBufferLayout,
|
||||||
RenderPipeline, TexelCopyBufferLayout, TexelCopyTextureInfo, TextureAspect,
|
TexelCopyTextureInfo, TextureAspect, COPY_BUFFER_ALIGNMENT,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A text renderer that uses cached glyphs to render text into an existing render pass.
|
/// A text renderer that uses cached glyphs to render text into an existing render pass.
|
||||||
|
@ -87,188 +88,40 @@ impl TextRenderer {
|
||||||
.take_while(is_run_visible);
|
.take_while(is_run_visible);
|
||||||
|
|
||||||
for run in layout_runs {
|
for run in layout_runs {
|
||||||
for glyph in run.glyphs.iter() {
|
if text_area.stroke_size > 0.0 {
|
||||||
let physical_glyph =
|
for i in 0..2 {
|
||||||
glyph.physical((text_area.left, text_area.top), text_area.scale);
|
self.prepare_glyphs(
|
||||||
|
&run,
|
||||||
let cache_key = physical_glyph.cache_key;
|
&text_area,
|
||||||
|
atlas,
|
||||||
let details = if let Some(details) =
|
cache,
|
||||||
atlas.mask_atlas.glyph_cache.get(&cache_key)
|
font_system,
|
||||||
{
|
device,
|
||||||
atlas.mask_atlas.glyphs_in_use.insert(cache_key);
|
queue,
|
||||||
details
|
bounds_max_x,
|
||||||
} else if let Some(details) = atlas.color_atlas.glyph_cache.get(&cache_key) {
|
bounds_max_y,
|
||||||
atlas.color_atlas.glyphs_in_use.insert(cache_key);
|
bounds_min_y,
|
||||||
details
|
bounds_min_x,
|
||||||
} else {
|
i == 0,
|
||||||
let Some(image) =
|
&mut metadata_to_depth,
|
||||||
cache.get_image_uncached(font_system, physical_glyph.cache_key)
|
)?;
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let content_type = match image.content {
|
|
||||||
SwashContent::Color => ContentType::Color,
|
|
||||||
SwashContent::Mask => ContentType::Mask,
|
|
||||||
SwashContent::SubpixelMask => {
|
|
||||||
// Not implemented yet, but don't panic if this happens.
|
|
||||||
ContentType::Mask
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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, inner) = if should_rasterize {
|
|
||||||
let mut inner = atlas.inner_for_content_mut(content_type);
|
|
||||||
|
|
||||||
// Find a position in the packer
|
|
||||||
let allocation = loop {
|
|
||||||
match inner.try_allocate(width, height) {
|
|
||||||
Some(a) => break a,
|
|
||||||
None => {
|
|
||||||
if !atlas.grow(
|
|
||||||
device,
|
|
||||||
queue,
|
|
||||||
font_system,
|
|
||||||
cache,
|
|
||||||
content_type,
|
|
||||||
) {
|
|
||||||
return Err(PrepareError::AtlasFull);
|
|
||||||
}
|
|
||||||
|
|
||||||
inner = atlas.inner_for_content_mut(content_type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let atlas_min = allocation.rectangle.min;
|
|
||||||
|
|
||||||
queue.write_texture(
|
|
||||||
TexelCopyTextureInfo {
|
|
||||||
texture: &inner.texture,
|
|
||||||
mip_level: 0,
|
|
||||||
origin: Origin3d {
|
|
||||||
x: atlas_min.x as u32,
|
|
||||||
y: atlas_min.y as u32,
|
|
||||||
z: 0,
|
|
||||||
},
|
|
||||||
aspect: TextureAspect::All,
|
|
||||||
},
|
|
||||||
&image.data,
|
|
||||||
TexelCopyBufferLayout {
|
|
||||||
offset: 0,
|
|
||||||
bytes_per_row: Some(width as u32 * inner.num_channels() as u32),
|
|
||||||
rows_per_image: None,
|
|
||||||
},
|
|
||||||
Extent3d {
|
|
||||||
width: width as u32,
|
|
||||||
height: height as u32,
|
|
||||||
depth_or_array_layers: 1,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
(
|
|
||||||
GpuCacheStatus::InAtlas {
|
|
||||||
x: atlas_min.x as u16,
|
|
||||||
y: atlas_min.y as u16,
|
|
||||||
content_type,
|
|
||||||
},
|
|
||||||
Some(allocation.id),
|
|
||||||
inner,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
let inner = &mut atlas.color_atlas;
|
|
||||||
(GpuCacheStatus::SkipRasterization, None, inner)
|
|
||||||
};
|
|
||||||
|
|
||||||
inner.glyphs_in_use.insert(cache_key);
|
|
||||||
// Insert the glyph into the cache and return the details reference
|
|
||||||
inner.glyph_cache.get_or_insert(cache_key, || GlyphDetails {
|
|
||||||
width: image.placement.width as u16,
|
|
||||||
height: image.placement.height as u16,
|
|
||||||
gpu_cache,
|
|
||||||
atlas_id,
|
|
||||||
top: image.placement.top as i16,
|
|
||||||
left: image.placement.left as i16,
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut x = physical_glyph.x + details.left as i32;
|
|
||||||
let mut y = (run.line_y * text_area.scale).round() as i32 + physical_glyph.y
|
|
||||||
- details.top as i32;
|
|
||||||
|
|
||||||
let (mut atlas_x, mut atlas_y, content_type) = match details.gpu_cache {
|
|
||||||
GpuCacheStatus::InAtlas { x, y, content_type } => (x, y, content_type),
|
|
||||||
GpuCacheStatus::SkipRasterization => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut width = details.width as i32;
|
|
||||||
let mut height = details.height as i32;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
// Starts beyond bottom edge or ends beyond top edge
|
self.prepare_glyphs(
|
||||||
let max_y = y + height;
|
&run,
|
||||||
if y > bounds_max_y || max_y < bounds_min_y {
|
&text_area,
|
||||||
continue;
|
atlas,
|
||||||
}
|
cache,
|
||||||
|
font_system,
|
||||||
// Clip left ege
|
device,
|
||||||
if x < bounds_min_x {
|
queue,
|
||||||
let right_shift = bounds_min_x - x;
|
bounds_max_x,
|
||||||
|
bounds_max_y,
|
||||||
x = bounds_min_x;
|
bounds_min_y,
|
||||||
width = max_x - bounds_min_x;
|
bounds_min_x,
|
||||||
atlas_x += right_shift as u16;
|
false,
|
||||||
}
|
&mut metadata_to_depth,
|
||||||
|
)?;
|
||||||
// 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 = match glyph.color_opt {
|
|
||||||
Some(some) => some,
|
|
||||||
None => text_area.default_color,
|
|
||||||
};
|
|
||||||
|
|
||||||
let depth = metadata_to_depth(glyph.metadata);
|
|
||||||
|
|
||||||
self.glyph_vertices.push(GlyphToRender {
|
|
||||||
pos: [x, y],
|
|
||||||
dim: [width as u16, height as u16],
|
|
||||||
uv: [atlas_x, atlas_y],
|
|
||||||
color: color.0,
|
|
||||||
content_type_with_srgb: [
|
|
||||||
content_type as u16,
|
|
||||||
match atlas.color_mode {
|
|
||||||
ColorMode::Accurate => TextColorConversion::ConvertToLinear,
|
|
||||||
ColorMode::Web => TextColorConversion::None,
|
|
||||||
} as u16,
|
|
||||||
],
|
|
||||||
depth,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -363,6 +216,204 @@ impl TextRenderer {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prepare_glyphs(
|
||||||
|
&mut self,
|
||||||
|
run: &LayoutRun,
|
||||||
|
text_area: &TextArea,
|
||||||
|
atlas: &mut TextAtlas,
|
||||||
|
cache: &mut SwashCache,
|
||||||
|
font_system: &mut FontSystem,
|
||||||
|
device: &Device,
|
||||||
|
queue: &Queue,
|
||||||
|
bounds_max_x: i32,
|
||||||
|
bounds_max_y: i32,
|
||||||
|
bounds_min_y: i32,
|
||||||
|
bounds_min_x: i32,
|
||||||
|
stroke: bool,
|
||||||
|
mut metadata_to_depth: impl FnMut(usize) -> f32,
|
||||||
|
) -> Result<(), PrepareError> {
|
||||||
|
for glyph in run.glyphs.iter() {
|
||||||
|
let physical_glyph = glyph.physical((text_area.left, text_area.top), text_area.scale);
|
||||||
|
|
||||||
|
let cache_key = physical_glyph.cache_key;
|
||||||
|
|
||||||
|
let details = if let Some(details) = atlas.mask_atlas.glyph_cache.get(&cache_key) {
|
||||||
|
atlas.mask_atlas.glyphs_in_use.insert(cache_key);
|
||||||
|
details
|
||||||
|
} else if let Some(details) = atlas.color_atlas.glyph_cache.get(&cache_key) {
|
||||||
|
atlas.color_atlas.glyphs_in_use.insert(cache_key);
|
||||||
|
details
|
||||||
|
} else {
|
||||||
|
let Some(image) = cache.get_image_uncached(font_system, physical_glyph.cache_key)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let content_type = match image.content {
|
||||||
|
SwashContent::Color => ContentType::Color,
|
||||||
|
SwashContent::Mask => ContentType::Mask,
|
||||||
|
SwashContent::SubpixelMask => {
|
||||||
|
// Not implemented yet, but don't panic if this happens.
|
||||||
|
ContentType::Mask
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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, inner) = if should_rasterize {
|
||||||
|
let mut inner = atlas.inner_for_content_mut(content_type);
|
||||||
|
|
||||||
|
// Find a position in the packer
|
||||||
|
let allocation = loop {
|
||||||
|
match inner.try_allocate(width, height) {
|
||||||
|
Some(a) => break a,
|
||||||
|
None => {
|
||||||
|
if !atlas.grow(device, queue, font_system, cache, content_type) {
|
||||||
|
return Err(PrepareError::AtlasFull);
|
||||||
|
}
|
||||||
|
|
||||||
|
inner = atlas.inner_for_content_mut(content_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let atlas_min = allocation.rectangle.min;
|
||||||
|
|
||||||
|
queue.write_texture(
|
||||||
|
TexelCopyTextureInfo {
|
||||||
|
texture: &inner.texture,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: Origin3d {
|
||||||
|
x: atlas_min.x as u32,
|
||||||
|
y: atlas_min.y as u32,
|
||||||
|
z: 0,
|
||||||
|
},
|
||||||
|
aspect: TextureAspect::All,
|
||||||
|
},
|
||||||
|
&image.data,
|
||||||
|
TexelCopyBufferLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: Some(width as u32 * inner.num_channels() as u32),
|
||||||
|
rows_per_image: None,
|
||||||
|
},
|
||||||
|
Extent3d {
|
||||||
|
width: width as u32,
|
||||||
|
height: height as u32,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
(
|
||||||
|
GpuCacheStatus::InAtlas {
|
||||||
|
x: atlas_min.x as u16,
|
||||||
|
y: atlas_min.y as u16,
|
||||||
|
content_type,
|
||||||
|
},
|
||||||
|
Some(allocation.id),
|
||||||
|
inner,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let inner = &mut atlas.color_atlas;
|
||||||
|
(GpuCacheStatus::SkipRasterization, None, inner)
|
||||||
|
};
|
||||||
|
|
||||||
|
inner.glyphs_in_use.insert(cache_key);
|
||||||
|
// Insert the glyph into the cache and return the details reference
|
||||||
|
inner.glyph_cache.get_or_insert(cache_key, || GlyphDetails {
|
||||||
|
width: image.placement.width as u16,
|
||||||
|
height: image.placement.height as u16,
|
||||||
|
gpu_cache,
|
||||||
|
atlas_id,
|
||||||
|
top: image.placement.top as i16,
|
||||||
|
left: image.placement.left as i16,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut x = physical_glyph.x + details.left as i32;
|
||||||
|
let mut y = (run.line_y * text_area.scale).round() as i32 + physical_glyph.y
|
||||||
|
- details.top as i32;
|
||||||
|
|
||||||
|
let (mut atlas_x, mut atlas_y, content_type) = match details.gpu_cache {
|
||||||
|
GpuCacheStatus::InAtlas { x, y, content_type } => (x, y, content_type),
|
||||||
|
GpuCacheStatus::SkipRasterization => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut width = details.width as i32;
|
||||||
|
let mut height = details.height as i32;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
let color = if stroke {
|
||||||
|
text_area.stroke_color
|
||||||
|
} else {
|
||||||
|
match glyph.color_opt {
|
||||||
|
Some(some) => some,
|
||||||
|
None => text_area.default_color,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let depth = metadata_to_depth(glyph.metadata);
|
||||||
|
|
||||||
|
self.glyph_vertices.push(GlyphToRender {
|
||||||
|
pos: [x, y],
|
||||||
|
dim: [width as u16, height as u16],
|
||||||
|
uv: [atlas_x, atlas_y],
|
||||||
|
color: color.0,
|
||||||
|
content_type_with_srgb: [
|
||||||
|
content_type as u16,
|
||||||
|
match atlas.color_mode {
|
||||||
|
ColorMode::Accurate => TextColorConversion::ConvertToLinear,
|
||||||
|
ColorMode::Web => TextColorConversion::None,
|
||||||
|
} as u16,
|
||||||
|
],
|
||||||
|
depth,
|
||||||
|
stroke_color: text_area.stroke_color.0,
|
||||||
|
stroke_size: if stroke { text_area.stroke_size } else { 0.0 },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue