crop text images that are too big
This commit is contained in:
@@ -33,9 +33,7 @@ impl TextEdit {
|
|||||||
|
|
||||||
impl Widget for TextEdit {
|
impl Widget for TextEdit {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
let tex = self.view.draw(&mut painter.size_ctx());
|
let region = self.view.draw(painter);
|
||||||
let region = text_region(&tex, self.align);
|
|
||||||
painter.texture_within(&tex.handle, region);
|
|
||||||
|
|
||||||
if let Some(cursor) = &self.cursor
|
if let Some(cursor) = &self.cursor
|
||||||
&& let Some(offset) = cursor_pos(cursor, &self.buf)
|
&& let Some(offset) = cursor_pos(cursor, &self.buf)
|
||||||
@@ -49,11 +47,11 @@ impl Widget for TextEdit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
Len::abs(self.view.draw(ctx).size.x)
|
Len::abs(self.view.render(ctx).size.x)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
Len::abs(self.view.draw(ctx).size.y)
|
Len::abs(self.view.render(ctx).size.y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ pub struct TextView {
|
|||||||
pub attrs: MutDetect<TextAttrs>,
|
pub attrs: MutDetect<TextAttrs>,
|
||||||
pub buf: MutDetect<TextBuffer>,
|
pub buf: MutDetect<TextBuffer>,
|
||||||
// cache
|
// cache
|
||||||
tex: Option<TextTexture>,
|
tex: Option<RenderedText>,
|
||||||
width: Option<f32>,
|
width: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ impl TextView {
|
|||||||
width: None,
|
width: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn draw(&mut self, ctx: &mut SizeCtx) -> TextTexture {
|
pub fn render(&mut self, ctx: &mut SizeCtx) -> RenderedText {
|
||||||
let width = if self.attrs.wrap {
|
let width = if self.attrs.wrap {
|
||||||
Some(ctx.px_size().x)
|
Some(ctx.px_size().x)
|
||||||
} else {
|
} else {
|
||||||
@@ -55,9 +55,25 @@ impl TextView {
|
|||||||
self.buf.changed = false;
|
self.buf.changed = false;
|
||||||
tex
|
tex
|
||||||
}
|
}
|
||||||
pub fn tex(&self) -> Option<&TextTexture> {
|
pub fn tex(&self) -> Option<&RenderedText> {
|
||||||
self.tex.as_ref()
|
self.tex.as_ref()
|
||||||
}
|
}
|
||||||
|
pub fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
|
Len::abs(self.render(ctx).size.x)
|
||||||
|
}
|
||||||
|
pub fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
|
Len::abs(self.render(ctx).size.y)
|
||||||
|
}
|
||||||
|
pub fn draw(&mut self, painter: &mut Painter) -> UiRegion {
|
||||||
|
let tex = self.render(&mut painter.size_ctx());
|
||||||
|
let region = tex.size.align(self.align);
|
||||||
|
let dims = tex.handle.size();
|
||||||
|
let mut region = region.offset(tex.top_left_offset);
|
||||||
|
region.x.end = region.x.start + UiScalar::abs(dims.x);
|
||||||
|
region.y.end = region.y.start + UiScalar::abs(dims.y);
|
||||||
|
painter.texture_within(&tex.handle, region);
|
||||||
|
region
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Text {
|
impl Text {
|
||||||
@@ -69,7 +85,7 @@ impl Text {
|
|||||||
view: TextView::new(buf, attrs),
|
view: TextView::new(buf, attrs),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn update_buf(&mut self, ctx: &mut SizeCtx) -> TextTexture {
|
fn update_buf(&mut self, ctx: &mut SizeCtx) {
|
||||||
if self.content.changed {
|
if self.content.changed {
|
||||||
self.content.changed = false;
|
self.content.changed = false;
|
||||||
self.view.buf.set_text(
|
self.view.buf.set_text(
|
||||||
@@ -80,36 +96,26 @@ impl Text {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
self.view.draw(ctx)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Text {
|
impl Widget for Text {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
let tex = self.update_buf(&mut painter.size_ctx());
|
self.update_buf(&mut painter.size_ctx());
|
||||||
let region = text_region(&tex, self.align);
|
self.view.draw(painter);
|
||||||
painter.texture_within(&tex.handle, region);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
fn desired_width(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
Len::abs(self.update_buf(ctx).size.x)
|
self.update_buf(ctx);
|
||||||
|
self.view.desired_width(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
fn desired_height(&mut self, ctx: &mut SizeCtx) -> Len {
|
||||||
Len::abs(self.update_buf(ctx).size.y)
|
self.update_buf(ctx);
|
||||||
|
self.view.desired_height(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text_region(tex: &TextTexture, align: RegionAlign) -> UiRegion {
|
|
||||||
let tex_dims = tex.handle.size();
|
|
||||||
let mut region = tex.size.align(align);
|
|
||||||
region.x.start.abs += tex.top_left_offset.x;
|
|
||||||
region.y.start.abs += tex.top_left_offset.y;
|
|
||||||
region.x.end.abs = region.x.start.abs + tex_dims.x;
|
|
||||||
region.y.end.abs = region.y.start.abs + tex_dims.y;
|
|
||||||
region
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Text {
|
impl Deref for Text {
|
||||||
type Target = TextAttrs;
|
type Target = TextAttrs;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
/// stored in linear for sane manipulation
|
/// stored in linear for sane manipulation
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, bytemuck::Zeroable, Debug)]
|
#[derive(Clone, Copy, Hash, PartialEq, Eq, bytemuck::Zeroable, Debug)]
|
||||||
pub struct Color<T: ColorNum> {
|
pub struct Color<T: ColorNum> {
|
||||||
pub r: T,
|
pub r: T,
|
||||||
pub g: T,
|
pub g: T,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
layout::{
|
layout::{
|
||||||
Axis, Layers, Len, Modules, Size, TextAttrs, TextBuffer, TextData, TextTexture,
|
Axis, Layers, Len, Modules, Size, TextAttrs, TextBuffer, TextData, RenderedText,
|
||||||
TextureHandle, Textures, UiRegion, UiVec2, Vec2, WidgetId, Widgets,
|
TextureHandle, Textures, UiRegion, UiVec2, Vec2, WidgetId, Widgets,
|
||||||
},
|
},
|
||||||
render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
|
render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst},
|
||||||
@@ -422,7 +422,7 @@ impl<'a, 'c> Painter<'a, 'c> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// returns (handle, offset from top left)
|
/// returns (handle, offset from top left)
|
||||||
pub fn render_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> TextTexture {
|
pub fn render_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText {
|
||||||
self.ctx.text.draw(buffer, attrs, self.ctx.textures)
|
self.ctx.text.draw(buffer, attrs, self.ctx.textures)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,7 +539,6 @@ impl SizeCtx<'_> {
|
|||||||
// if let Some(&(outer, len)) = self.cache_height.get(&id)
|
// if let Some(&(outer, len)) = self.cache_height.get(&id)
|
||||||
// && outer == self.outer
|
// && outer == self.outer
|
||||||
// {
|
// {
|
||||||
// prntln!("FAIL {id:?}");
|
|
||||||
// self.checked_height.insert(id, (self.outer, len));
|
// self.checked_height.insert(id, (self.outer, len));
|
||||||
// return len;
|
// return len;
|
||||||
// }
|
// }
|
||||||
@@ -584,7 +583,7 @@ impl SizeCtx<'_> {
|
|||||||
self.output_size
|
self.output_size
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> TextTexture {
|
pub fn draw_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText {
|
||||||
self.text.draw(buffer, attrs, self.textures)
|
self.text.draw(buffer, attrs, self.textures)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
use cosmic_text::{
|
use std::simd::{Simd, num::SimdUint};
|
||||||
Attrs, AttrsList, Buffer, Family, FontSystem, Metrics, SwashCache, SwashContent,
|
|
||||||
};
|
|
||||||
use image::{Rgba, RgbaImage};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::layout::{Align, RegionAlign, TextureHandle, Textures, UiColor, Vec2};
|
||||||
layout::{Align, RegionAlign, TextureHandle, Textures, UiColor, Vec2},
|
use cosmic_text::{
|
||||||
util::HashMap,
|
Attrs, AttrsList, Buffer, CacheKey, Color, Family, FontSystem, Metrics, Placement, SwashCache,
|
||||||
|
SwashContent,
|
||||||
};
|
};
|
||||||
|
use image::{GenericImageView, RgbaImage};
|
||||||
|
|
||||||
/// TODO: properly wrap this
|
/// TODO: properly wrap this
|
||||||
pub mod text_lib {
|
pub mod text_lib {
|
||||||
@@ -16,6 +15,7 @@ pub mod text_lib {
|
|||||||
pub struct TextData {
|
pub struct TextData {
|
||||||
pub font_system: FontSystem,
|
pub font_system: FontSystem,
|
||||||
pub swash_cache: SwashCache,
|
pub swash_cache: SwashCache,
|
||||||
|
glyph_cache: Vec<(Placement, CacheKey, Color)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TextData {
|
impl Default for TextData {
|
||||||
@@ -23,6 +23,7 @@ impl Default for TextData {
|
|||||||
Self {
|
Self {
|
||||||
font_system: FontSystem::new(),
|
font_system: FontSystem::new(),
|
||||||
swash_cache: SwashCache::new(),
|
swash_cache: SwashCache::new(),
|
||||||
|
glyph_cache: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,29 +77,30 @@ impl TextData {
|
|||||||
buffer: &mut TextBuffer,
|
buffer: &mut TextBuffer,
|
||||||
attrs: &TextAttrs,
|
attrs: &TextAttrs,
|
||||||
textures: &mut Textures,
|
textures: &mut Textures,
|
||||||
) -> TextTexture {
|
) -> RenderedText {
|
||||||
// TODO: either this or the layout stuff (or both) is super slow,
|
// TODO: either this or the layout stuff (or both) is super slow,
|
||||||
// should probably do texture packing and things if possible.
|
// should probably do texture packing and things if possible.
|
||||||
// very visible if you add just a couple of wrapping texts and resize window
|
// very visible if you add just a couple of wrapping texts and resize window
|
||||||
// should also be timed to figure out exactly what points need to be sped up
|
// should also be timed to figure out exactly what points need to be sped up
|
||||||
let mut pixels = HashMap::<_, [u8; 4]>::default();
|
// let mut pixels = HashMap::<_, [u8; 4]>::default();
|
||||||
let mut min_x = 0;
|
let mut min_x = 0;
|
||||||
let mut min_y = 0;
|
let mut min_y = 0;
|
||||||
let mut max_x = 0;
|
let mut max_x = 0;
|
||||||
let mut max_y = 0;
|
let mut max_y = 0;
|
||||||
let cosmic_color = {
|
let text_color = {
|
||||||
let c = attrs.color;
|
let c = attrs.color;
|
||||||
cosmic_text::Color::rgba(c.r, c.g, c.b, c.a)
|
cosmic_text::Color::rgba(c.r, c.g, c.b, c.a)
|
||||||
};
|
};
|
||||||
let mut max_width = 0.0f32;
|
let mut max_width = 0.0f32;
|
||||||
let mut height = 0.0;
|
let mut height = 0.0;
|
||||||
|
|
||||||
for run in buffer.layout_runs() {
|
for run in buffer.layout_runs() {
|
||||||
for glyph in run.glyphs.iter() {
|
for glyph in run.glyphs.iter() {
|
||||||
let physical_glyph = glyph.physical((0., 0.), 1.0);
|
let physical_glyph = glyph.physical((0., 0.), 1.0);
|
||||||
|
|
||||||
let glyph_color = match glyph.color_opt {
|
let glyph_color = match glyph.color_opt {
|
||||||
Some(some) => some,
|
Some(some) => some,
|
||||||
None => cosmic_color,
|
None => text_color,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(img) = self
|
if let Some(img) = self
|
||||||
@@ -112,35 +114,8 @@ impl TextData {
|
|||||||
min_y = min_y.min(pos.top);
|
min_y = min_y.min(pos.top);
|
||||||
max_x = max_x.max(pos.left + pos.width as i32);
|
max_x = max_x.max(pos.left + pos.width as i32);
|
||||||
max_y = max_y.max(pos.top + pos.height as i32);
|
max_y = max_y.max(pos.top + pos.height as i32);
|
||||||
let mut merge = |i, color: [u8; 4]| {
|
self.glyph_cache
|
||||||
let x = i % pos.width as usize;
|
.push((pos, physical_glyph.cache_key, glyph_color));
|
||||||
let y = i / pos.width as usize;
|
|
||||||
let pos = (x as i32 + pos.left, y as i32 + pos.top);
|
|
||||||
if let Some(pixel) = pixels.get_mut(&pos) {
|
|
||||||
for i in 0..4 {
|
|
||||||
// TODO: no clue if proper alpha blending should be done
|
|
||||||
pixel[i] = color[i].saturating_add(pixel[i]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pixels.insert(pos, color);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
match img.content {
|
|
||||||
SwashContent::Mask => {
|
|
||||||
for (i, a) in img.data.iter().enumerate() {
|
|
||||||
let mut color = glyph_color.as_rgba();
|
|
||||||
color[3] = ((color[3] as u32 * *a as u32) / u8::MAX as u32) as u8;
|
|
||||||
merge(i, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SwashContent::SubpixelMask => todo!(),
|
|
||||||
SwashContent::Color => {
|
|
||||||
let (colors, _) = img.data.as_chunks::<4>();
|
|
||||||
for (i, color) in colors.iter().enumerate() {
|
|
||||||
merge(i, *color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
max_width = max_width.max(run.line_w);
|
max_width = max_width.max(run.line_w);
|
||||||
@@ -150,12 +125,52 @@ impl TextData {
|
|||||||
let img_height = (max_y - min_y + 1) as u32;
|
let img_height = (max_y - min_y + 1) as u32;
|
||||||
let mut image = RgbaImage::new(img_width, img_height);
|
let mut image = RgbaImage::new(img_width, img_height);
|
||||||
|
|
||||||
for ((x, y), color) in pixels {
|
for (pos, key, color) in self.glyph_cache.drain(..) {
|
||||||
let x = (x - min_x) as u32;
|
let img = self
|
||||||
let y = (y - min_y) as u32;
|
.swash_cache
|
||||||
image.put_pixel(x, y, Rgba(color));
|
.get_image(&mut self.font_system, key)
|
||||||
|
.as_ref()
|
||||||
|
.unwrap();
|
||||||
|
let mut merge = |i, color: [u8; 4]| {
|
||||||
|
let i = i as i32;
|
||||||
|
let x = (i % pos.width as i32 + pos.left - min_x) as u32;
|
||||||
|
let y = (i / pos.width as i32 + pos.top - min_y) as u32;
|
||||||
|
let pixel = &mut image[(x, y)].0;
|
||||||
|
// TODO: no clue if proper alpha blending should be done
|
||||||
|
*pixel = Simd::from(color).saturating_add(Simd::from(*pixel)).into();
|
||||||
|
};
|
||||||
|
|
||||||
|
match img.content {
|
||||||
|
SwashContent::Mask => {
|
||||||
|
for (i, a) in img.data.iter().enumerate() {
|
||||||
|
let mut color = color.as_rgba();
|
||||||
|
color[3] = ((color[3] as u32 * *a as u32) / u8::MAX as u32) as u8;
|
||||||
|
merge(i, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SwashContent::SubpixelMask => todo!("subpixel mask text rendering"),
|
||||||
|
SwashContent::Color => {
|
||||||
|
let (colors, _) = img.data.as_chunks::<4>();
|
||||||
|
for (i, color) in colors.iter().enumerate() {
|
||||||
|
merge(i, *color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
TextTexture {
|
|
||||||
|
let max_dim = 8192;
|
||||||
|
if image.width() > max_dim || image.height() > max_dim {
|
||||||
|
let width = image.width().min(max_dim);
|
||||||
|
let height = image.height().min(max_dim);
|
||||||
|
eprintln!(
|
||||||
|
"WARNING: image of size {:?} cropped to {:?} (texture too big)",
|
||||||
|
image.dimensions(),
|
||||||
|
(width, height)
|
||||||
|
);
|
||||||
|
image = image.view(0, 0, width, height).to_image();
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderedText {
|
||||||
handle: textures.add(image),
|
handle: textures.add(image),
|
||||||
top_left_offset: Vec2::new(min_x as f32, min_y as f32),
|
top_left_offset: Vec2::new(min_x as f32, min_y as f32),
|
||||||
size: Vec2::new(max_width, height),
|
size: Vec2::new(max_width, height),
|
||||||
@@ -164,9 +179,8 @@ impl TextData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TextTexture {
|
pub struct RenderedText {
|
||||||
pub handle: TextureHandle,
|
pub handle: TextureHandle,
|
||||||
pub top_left_offset: Vec2,
|
pub top_left_offset: Vec2,
|
||||||
pub size: Vec2,
|
pub size: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#![feature(fn_traits)]
|
#![feature(fn_traits)]
|
||||||
#![feature(const_cmp)]
|
#![feature(const_cmp)]
|
||||||
#![feature(const_destruct)]
|
#![feature(const_destruct)]
|
||||||
|
#![feature(portable_simd)]
|
||||||
|
|
||||||
pub mod core;
|
pub mod core;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ macro_rules! impl_op {
|
|||||||
}
|
}
|
||||||
impl const $opa for $T {
|
impl const $opa for $T {
|
||||||
fn $fna(&mut self, rhs: Self) {
|
fn $fna(&mut self, rhs: Self) {
|
||||||
$(self.$field.$fna(rhs.$field);)*
|
*self = self.$fn(rhs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl const $op<f32> for $T {
|
impl const $op<f32> for $T {
|
||||||
@@ -71,7 +71,7 @@ macro_rules! impl_op {
|
|||||||
}
|
}
|
||||||
impl const $opa<f32> for $T {
|
impl const $opa<f32> for $T {
|
||||||
fn $fna(&mut self, rhs: f32) {
|
fn $fna(&mut self, rhs: f32) {
|
||||||
$(self.$field.$fna(rhs);)*
|
*self = self.$fn(rhs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user