Clarify text layout and rasterization state
This commit is contained in:
1 parent
2d15195a58
commit
71e46c125a
1 file changed
+91
-50
+91
-50
@@ -1,8 +1,12 @@
|
|||||||
use crate::{Align, GlyphAtlas, GlyphKey, PlacedGlyph, RegionAlign, Textures, UiColor, util::Vec2};
|
use crate::{
|
||||||
|
Align, GlyphAtlas, GlyphEntry, GlyphKey, PlacedGlyph, RegionAlign, Textures, UiColor,
|
||||||
|
util::Vec2,
|
||||||
|
};
|
||||||
use parley::{
|
use parley::{
|
||||||
Alignment, AlignmentOptions, FontContext, FontFamily, FontFamilyName, GenericFamily, Layout,
|
Alignment, AlignmentOptions, FontContext, FontFamily, FontFamilyName, GenericFamily, Layout,
|
||||||
LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty,
|
LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty,
|
||||||
};
|
};
|
||||||
|
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||||
use swash::{
|
use swash::{
|
||||||
FontRef,
|
FontRef,
|
||||||
scale::{Render, ScaleContext, Source, StrikeWith},
|
scale::{Render, ScaleContext, Source, StrikeWith},
|
||||||
@@ -10,18 +14,18 @@ use swash::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub struct TextData {
|
pub struct TextData {
|
||||||
pub font_cx: FontContext,
|
pub font_ctx: FontContext,
|
||||||
pub layout_cx: LayoutContext<UiColor>,
|
pub layout_ctx: LayoutContext<UiColor>,
|
||||||
scale_cx: ScaleContext,
|
scale_ctx: ScaleContext,
|
||||||
pub atlas: GlyphAtlas,
|
pub atlas: GlyphAtlas,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TextData {
|
impl Default for TextData {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
font_cx: FontContext::new(),
|
font_ctx: FontContext::new(),
|
||||||
layout_cx: LayoutContext::new(),
|
layout_ctx: LayoutContext::new(),
|
||||||
scale_cx: ScaleContext::new(),
|
scale_ctx: ScaleContext::new(),
|
||||||
atlas: GlyphAtlas::default(),
|
atlas: GlyphAtlas::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,7 +81,13 @@ impl Default for TextAttrs {
|
|||||||
pub struct TextBuffer {
|
pub struct TextBuffer {
|
||||||
text: String,
|
text: String,
|
||||||
layout: Layout<UiColor>,
|
layout: Layout<UiColor>,
|
||||||
shaped: Option<(TextAttrs, Option<f32>)>,
|
layout_key: Option<LayoutKey>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
struct LayoutKey {
|
||||||
|
attrs: TextAttrs,
|
||||||
|
max_width: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextBuffer {
|
impl TextBuffer {
|
||||||
@@ -85,7 +95,7 @@ impl TextBuffer {
|
|||||||
Self {
|
Self {
|
||||||
text: text.into(),
|
text: text.into(),
|
||||||
layout: Layout::new(),
|
layout: Layout::new(),
|
||||||
shaped: None,
|
layout_key: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,13 +119,13 @@ impl TextBuffer {
|
|||||||
let text = text.into();
|
let text = text.into();
|
||||||
if text != self.text {
|
if text != self.text {
|
||||||
self.text = text;
|
self.text = text;
|
||||||
self.shaped = None;
|
self.layout_key = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invalidates the layout and returns the underlying string for editing.
|
/// Invalidates the layout and returns the underlying string for editing.
|
||||||
pub fn edit(&mut self) -> &mut String {
|
pub fn edit(&mut self) -> &mut String {
|
||||||
self.shaped = None;
|
self.layout_key = None;
|
||||||
&mut self.text
|
&mut self.text
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,12 +134,16 @@ impl TextBuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn shape(&mut self, data: &mut TextData, attrs: &TextAttrs, width: Option<f32>) {
|
pub fn shape(&mut self, data: &mut TextData, attrs: &TextAttrs, width: Option<f32>) {
|
||||||
if self.shaped.as_ref() == Some(&(attrs.clone(), width)) {
|
let layout_key = LayoutKey {
|
||||||
|
attrs: attrs.clone(),
|
||||||
|
max_width: width,
|
||||||
|
};
|
||||||
|
if self.layout_key.as_ref() == Some(&layout_key) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut builder = data
|
let mut builder = data
|
||||||
.layout_cx
|
.layout_ctx
|
||||||
.ranged_builder(&mut data.font_cx, &self.text, 1.0, true);
|
.ranged_builder(&mut data.font_ctx, &self.text, 1.0, true);
|
||||||
builder.push_default(StyleProperty::FontFamily(attrs.family.family()));
|
builder.push_default(StyleProperty::FontFamily(attrs.family.family()));
|
||||||
builder.push_default(StyleProperty::FontSize(attrs.font_size));
|
builder.push_default(StyleProperty::FontSize(attrs.font_size));
|
||||||
builder.push_default(StyleProperty::LineHeight(LineHeight::Absolute(
|
builder.push_default(StyleProperty::LineHeight(LineHeight::Absolute(
|
||||||
@@ -140,7 +154,7 @@ impl TextBuffer {
|
|||||||
self.layout.break_all_lines(width);
|
self.layout.break_all_lines(width);
|
||||||
self.layout
|
self.layout
|
||||||
.align(Alignment::Start, AlignmentOptions::default());
|
.align(Alignment::Start, AlignmentOptions::default());
|
||||||
self.shaped = Some((attrs.clone(), width));
|
self.layout_key = Some(layout_key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,8 +174,6 @@ impl TextData {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let coords_hash = hash_coords(coords);
|
let coords_hash = hash_coords(coords);
|
||||||
// `font.data.id()` rather than the pointer, so the same font
|
|
||||||
// loaded twice is still one set of entries.
|
|
||||||
let font_id = font.data.id();
|
let font_id = font.data.id();
|
||||||
|
|
||||||
for glyph in run.positioned_glyphs() {
|
for glyph in run.positioned_glyphs() {
|
||||||
@@ -169,37 +181,21 @@ impl TextData {
|
|||||||
let key = GlyphKey {
|
let key = GlyphKey {
|
||||||
font: font_id,
|
font: font_id,
|
||||||
glyph: glyph.id,
|
glyph: glyph.id,
|
||||||
size: (font_size * 16.0).round() as u32,
|
size: glyph_size_key(font_size),
|
||||||
subpixel,
|
subpixel,
|
||||||
coords: coords_hash,
|
coords: coords_hash,
|
||||||
};
|
};
|
||||||
let entry = match self.atlas.get(&key) {
|
let entry = self.glyph_entry(
|
||||||
Some(entry) => entry,
|
GlyphRaster {
|
||||||
None => {
|
key,
|
||||||
let mut scaler = self
|
font: font_ref,
|
||||||
.scale_cx
|
font_size,
|
||||||
.builder(font_ref)
|
coords,
|
||||||
.size(font_size)
|
subpixel,
|
||||||
.hint(true)
|
glyph_id: glyph.id,
|
||||||
.normalized_coords(coords)
|
},
|
||||||
.build();
|
textures,
|
||||||
let image = Render::new(&[
|
);
|
||||||
Source::ColorOutline(0),
|
|
||||||
Source::ColorBitmap(StrikeWith::BestFit),
|
|
||||||
Source::Outline,
|
|
||||||
])
|
|
||||||
.format(Format::Alpha)
|
|
||||||
.offset(Vector::new(subpixel as f32 / 4.0, 0.0))
|
|
||||||
.render(&mut scaler, glyph.id as u16);
|
|
||||||
match image {
|
|
||||||
Some(image) => self.atlas.insert(key, &image, textures),
|
|
||||||
None => {
|
|
||||||
self.atlas.insert_empty(key);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let Some(entry) = entry else { continue };
|
let Some(entry) = entry else { continue };
|
||||||
placed.push(PlacedGlyph {
|
placed.push(PlacedGlyph {
|
||||||
entry,
|
entry,
|
||||||
@@ -213,15 +209,60 @@ impl TextData {
|
|||||||
}
|
}
|
||||||
placed
|
placed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn glyph_entry(
|
||||||
|
&mut self,
|
||||||
|
glyph: GlyphRaster<'_>,
|
||||||
|
textures: &mut Textures,
|
||||||
|
) -> Option<GlyphEntry> {
|
||||||
|
if let Some(entry) = self.atlas.get(&glyph.key) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut scaler = self
|
||||||
|
.scale_ctx
|
||||||
|
.builder(glyph.font)
|
||||||
|
.size(glyph.font_size)
|
||||||
|
.hint(true)
|
||||||
|
.normalized_coords(glyph.coords)
|
||||||
|
.build();
|
||||||
|
let image = Render::new(&[
|
||||||
|
Source::ColorOutline(0),
|
||||||
|
Source::ColorBitmap(StrikeWith::BestFit),
|
||||||
|
Source::Outline,
|
||||||
|
])
|
||||||
|
.format(Format::Alpha)
|
||||||
|
.offset(Vector::new(glyph.subpixel as f32 / 4.0, 0.0))
|
||||||
|
.render(&mut scaler, glyph.glyph_id as u16);
|
||||||
|
|
||||||
|
if let Some(image) = image {
|
||||||
|
self.atlas.insert(glyph.key, &image, textures)
|
||||||
|
} else {
|
||||||
|
self.atlas.insert_empty(glyph.key);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GlyphRaster<'a> {
|
||||||
|
key: GlyphKey,
|
||||||
|
font: FontRef<'a>,
|
||||||
|
font_size: f32,
|
||||||
|
coords: &'a [i16],
|
||||||
|
subpixel: u8,
|
||||||
|
glyph_id: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_coords(coords: &[i16]) -> u64 {
|
fn hash_coords(coords: &[i16]) -> u64 {
|
||||||
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
|
let mut hasher = DefaultHasher::new();
|
||||||
for c in coords {
|
coords.hash(&mut hasher);
|
||||||
h ^= *c as u16 as u64;
|
hasher.finish()
|
||||||
h = h.wrapping_mul(0x1000_0000_01b3);
|
|
||||||
}
|
}
|
||||||
h
|
|
||||||
|
const GLYPH_SIZE_STEPS_PER_PIXEL: f32 = 16.0;
|
||||||
|
|
||||||
|
fn glyph_size_key(font_size: f32) -> u32 {
|
||||||
|
(font_size * GLYPH_SIZE_STEPS_PER_PIXEL).round() as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|||||||
Reference in new issue
Block a user