Replace rustybuzz with HarfRust (#417)
* Use HarfRust for shaping * Replace ttf-parser with skrifa entirely * Fix clippy lints * Add shape plan cache * Bump harfrust and skrifa * Fix no_std build * Simplify the shape plan cache * Please the paperclip * Cache font ID with plan * Tune shape plan cache for "BiDi Processing" bench
This commit is contained in:
parent
3c1f6c9e8a
commit
2610c869f6
7 changed files with 221 additions and 121 deletions
12
Cargo.toml
12
Cargo.toml
|
|
@ -7,25 +7,25 @@ edition = "2021"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
documentation = "https://docs.rs/cosmic-text/latest/cosmic_text/"
|
documentation = "https://docs.rs/cosmic-text/latest/cosmic_text/"
|
||||||
repository = "https://github.com/pop-os/cosmic-text"
|
repository = "https://github.com/pop-os/cosmic-text"
|
||||||
rust-version = "1.75"
|
rust-version = "1.80"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = "2.4.1"
|
bitflags = "2.4.1"
|
||||||
core_maths = { version = "0.1.1", optional = true }
|
core_maths = { version = "0.1.1", optional = true }
|
||||||
cosmic_undo_2 = { version = "0.2.0", optional = true }
|
cosmic_undo_2 = { version = "0.2.0", optional = true }
|
||||||
fontdb = { version = "0.23", default-features = false }
|
fontdb = { version = "0.23", default-features = false }
|
||||||
|
harfrust = { version = "0.2.0", default-features = false }
|
||||||
hashbrown = { version = "0.14.1", optional = true, default-features = false }
|
hashbrown = { version = "0.14.1", optional = true, default-features = false }
|
||||||
libm = { version = "0.2.8", optional = true }
|
libm = { version = "0.2.8", optional = true }
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
modit = { version = "0.1.4", optional = true }
|
modit = { version = "0.1.4", optional = true }
|
||||||
rangemap = "1.4.0"
|
rangemap = "1.4.0"
|
||||||
rustc-hash = { version = "1.1.0", default-features = false }
|
rustc-hash = { version = "1.1.0", default-features = false }
|
||||||
rustybuzz = { version = "0.14", default-features = false, features = ["libm"] }
|
|
||||||
self_cell = "1.0.1"
|
self_cell = "1.0.1"
|
||||||
|
skrifa = { version = "0.36.0", default-features = false }
|
||||||
smol_str = { version = "0.2.2", default-features = false }
|
smol_str = { version = "0.2.2", default-features = false }
|
||||||
syntect = { version = "5.1.0", optional = true }
|
syntect = { version = "5.1.0", optional = true }
|
||||||
sys-locale = { version = "0.3.1", optional = true }
|
sys-locale = { version = "0.3.1", optional = true }
|
||||||
ttf-parser = { version = "0.21", default-features = false }
|
|
||||||
unicode-linebreak = "0.1.5"
|
unicode-linebreak = "0.1.5"
|
||||||
unicode-script = "0.5.5"
|
unicode-script = "0.5.5"
|
||||||
unicode-segmentation = "1.10.1"
|
unicode-segmentation = "1.10.1"
|
||||||
|
|
@ -49,16 +49,16 @@ optional = true
|
||||||
default = ["std", "swash", "fontconfig"]
|
default = ["std", "swash", "fontconfig"]
|
||||||
fontconfig = ["fontdb/fontconfig", "std"]
|
fontconfig = ["fontdb/fontconfig", "std"]
|
||||||
monospace_fallback = []
|
monospace_fallback = []
|
||||||
no_std = ["rustybuzz/libm", "hashbrown", "dep:libm", "core_maths"]
|
no_std = ["hashbrown", "dep:libm", "skrifa/libm", "core_maths"]
|
||||||
peniko = ["dep:peniko"]
|
peniko = ["dep:peniko"]
|
||||||
shape-run-cache = []
|
shape-run-cache = []
|
||||||
std = [
|
std = [
|
||||||
"fontdb/memmap",
|
"fontdb/memmap",
|
||||||
"fontdb/std",
|
"fontdb/std",
|
||||||
"rustybuzz/std",
|
"harfrust/std",
|
||||||
|
"skrifa/std",
|
||||||
"swash?/std",
|
"swash?/std",
|
||||||
"sys-locale",
|
"sys-locale",
|
||||||
"ttf-parser/std",
|
|
||||||
"unicode-bidi/std",
|
"unicode-bidi/std",
|
||||||
]
|
]
|
||||||
vi = ["modit", "syntect", "cosmic_undo_2"]
|
vi = ["modit", "syntect", "cosmic_undo_2"]
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
Pure Rust multi-line text handling.
|
Pure Rust multi-line text handling.
|
||||||
|
|
||||||
COSMIC Text provides advanced text shaping, layout, and rendering wrapped up
|
COSMIC Text provides advanced text shaping, layout, and rendering wrapped up
|
||||||
into a simple abstraction. Shaping is provided by rustybuzz, and supports a
|
into a simple abstraction. Shaping is provided by HarfRust, and supports a
|
||||||
wide variety of advanced shaping operations. Rendering is provided by swash,
|
wide variety of advanced shaping operations. Rendering is provided by swash,
|
||||||
which supports ligatures and color emoji. Layout is implemented custom, in safe
|
which supports ligatures and color emoji. Layout is implemented custom, in safe
|
||||||
Rust, and supports bidirectional text. Font fallback is also a custom
|
Rust, and supports bidirectional text. Font fallback is also a custom
|
||||||
|
|
@ -37,7 +37,7 @@ The following features must be supported before this is "ready":
|
||||||
- [x] Text styles (bold, italic, etc.)
|
- [x] Text styles (bold, italic, etc.)
|
||||||
- [x] Per-buffer
|
- [x] Per-buffer
|
||||||
- [x] Per-span
|
- [x] Per-span
|
||||||
- [x] Font shaping (using rustybuzz)
|
- [x] Font shaping (using HarfRust)
|
||||||
- [x] Cache results
|
- [x] Cache results
|
||||||
- [x] RTL
|
- [x] RTL
|
||||||
- [x] Bidirectional rendering
|
- [x] Bidirectional rendering
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,28 @@ fn bench_bidi_processing(c: &mut Criterion) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bench_lang_mixed(c: &mut Criterion) {
|
||||||
|
let mut fs = ct::FontSystem::new();
|
||||||
|
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0));
|
||||||
|
buffer.set_size(&mut fs, Some(500.0), None);
|
||||||
|
|
||||||
|
let bidi_text = include_str!("../sample/hello.txt");
|
||||||
|
|
||||||
|
c.benchmark_group("bench_lang_mixed")
|
||||||
|
.sample_size(10)
|
||||||
|
.bench_function("ShapeLine/Mixed-Language Text", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
buffer.set_text(
|
||||||
|
&mut fs,
|
||||||
|
black_box(&bidi_text),
|
||||||
|
&ct::Attrs::new(),
|
||||||
|
ct::Shaping::Advanced,
|
||||||
|
);
|
||||||
|
buffer.shape_until_scroll(&mut fs, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn bench_layout_heavy(c: &mut Criterion) {
|
fn bench_layout_heavy(c: &mut Criterion) {
|
||||||
let mut fs = ct::FontSystem::new();
|
let mut fs = ct::FontSystem::new();
|
||||||
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0));
|
let mut buffer = ct::Buffer::new(&mut fs, ct::Metrics::new(14.0, 20.0));
|
||||||
|
|
@ -117,6 +139,7 @@ criterion_group!(
|
||||||
benches,
|
benches,
|
||||||
bench_ascii_fast_path,
|
bench_ascii_fast_path,
|
||||||
bench_bidi_processing,
|
bench_bidi_processing,
|
||||||
|
bench_lang_mixed,
|
||||||
bench_layout_heavy,
|
bench_layout_heavy,
|
||||||
bench_combined_stress,
|
bench_combined_stress,
|
||||||
bench_bidi_paragraphs_ascii,
|
bench_bidi_paragraphs_ascii,
|
||||||
|
|
|
||||||
193
src/font/mod.rs
193
src/font/mod.rs
|
|
@ -1,7 +1,10 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
// re-export ttf_parser
|
use harfrust::Shaper;
|
||||||
pub use ttf_parser;
|
use skrifa::raw::{ReadError, TableProvider as _};
|
||||||
|
use skrifa::{metrics::Metrics, prelude::*};
|
||||||
|
// re-export skrifa
|
||||||
|
pub use skrifa;
|
||||||
// re-export peniko::Font;
|
// re-export peniko::Font;
|
||||||
#[cfg(feature = "peniko")]
|
#[cfg(feature = "peniko")]
|
||||||
pub use peniko::Font as PenikoFont;
|
pub use peniko::Font as PenikoFont;
|
||||||
|
|
@ -12,7 +15,6 @@ use alloc::sync::Arc;
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
use rustybuzz::Face as RustybuzzFace;
|
|
||||||
use self_cell::self_cell;
|
use self_cell::self_cell;
|
||||||
|
|
||||||
pub mod fallback;
|
pub mod fallback;
|
||||||
|
|
@ -21,12 +23,19 @@ pub use fallback::{Fallback, PlatformFallback};
|
||||||
pub use self::system::*;
|
pub use self::system::*;
|
||||||
mod system;
|
mod system;
|
||||||
|
|
||||||
|
struct OwnedFaceData {
|
||||||
|
data: Arc<dyn AsRef<[u8]> + Send + Sync>,
|
||||||
|
shaper_data: harfrust::ShaperData,
|
||||||
|
shaper_instance: harfrust::ShaperInstance,
|
||||||
|
metrics: Metrics,
|
||||||
|
}
|
||||||
|
|
||||||
self_cell!(
|
self_cell!(
|
||||||
struct OwnedFace {
|
struct OwnedFace {
|
||||||
owner: Arc<dyn AsRef<[u8]> + Send + Sync>,
|
owner: OwnedFaceData,
|
||||||
|
|
||||||
#[covariant]
|
#[covariant]
|
||||||
dependent: RustybuzzFace,
|
dependent: Shaper,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -40,7 +49,7 @@ struct FontMonospaceFallback {
|
||||||
pub struct Font {
|
pub struct Font {
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
swash: (u32, swash::CacheKey),
|
swash: (u32, swash::CacheKey),
|
||||||
rustybuzz: OwnedFace,
|
harfrust: OwnedFace,
|
||||||
#[cfg(not(feature = "peniko"))]
|
#[cfg(not(feature = "peniko"))]
|
||||||
data: Arc<dyn AsRef<[u8]> + Send + Sync>,
|
data: Arc<dyn AsRef<[u8]> + Send + Sync>,
|
||||||
#[cfg(feature = "peniko")]
|
#[cfg(feature = "peniko")]
|
||||||
|
|
@ -89,8 +98,16 @@ impl Font {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rustybuzz(&self) -> &RustybuzzFace<'_> {
|
pub fn shaper(&self) -> &harfrust::Shaper<'_> {
|
||||||
self.rustybuzz.borrow_dependent()
|
self.harfrust.borrow_dependent()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn shaper_instance(&self) -> &harfrust::ShaperInstance {
|
||||||
|
&self.harfrust.borrow_owner().shaper_instance
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn metrics(&self) -> &Metrics {
|
||||||
|
&self.harfrust.borrow_owner().metrics
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "peniko")]
|
#[cfg(feature = "peniko")]
|
||||||
|
|
@ -113,59 +130,6 @@ impl Font {
|
||||||
pub fn new(db: &fontdb::Database, id: fontdb::ID, weight: fontdb::Weight) -> Option<Self> {
|
pub fn new(db: &fontdb::Database, id: fontdb::ID, weight: fontdb::Weight) -> Option<Self> {
|
||||||
let info = db.face(id)?;
|
let info = db.face(id)?;
|
||||||
|
|
||||||
let monospace_fallback = if cfg!(feature = "monospace_fallback") {
|
|
||||||
db.with_face_data(id, |font_data, face_index| {
|
|
||||||
let face = ttf_parser::Face::parse(font_data, face_index).ok()?;
|
|
||||||
let monospace_em_width = info
|
|
||||||
.monospaced
|
|
||||||
.then(|| {
|
|
||||||
let hor_advance = face.glyph_hor_advance(face.glyph_index(' ')?)?;
|
|
||||||
let upem = face.units_per_em();
|
|
||||||
Some(f32::from(hor_advance) / f32::from(upem))
|
|
||||||
})
|
|
||||||
.flatten();
|
|
||||||
|
|
||||||
if info.monospaced && monospace_em_width.is_none() {
|
|
||||||
None?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let scripts = face
|
|
||||||
.tables()
|
|
||||||
.gpos
|
|
||||||
.into_iter()
|
|
||||||
.chain(face.tables().gsub)
|
|
||||||
.flat_map(|table| table.scripts)
|
|
||||||
.map(|script| script.tag.to_bytes())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut unicode_codepoints = Vec::new();
|
|
||||||
|
|
||||||
face.tables()
|
|
||||||
.cmap?
|
|
||||||
.subtables
|
|
||||||
.into_iter()
|
|
||||||
.filter(ttf_parser::cmap::Subtable::is_unicode)
|
|
||||||
.for_each(|subtable| {
|
|
||||||
unicode_codepoints.reserve(1024);
|
|
||||||
subtable.codepoints(|code_point| {
|
|
||||||
if subtable.glyph_index(code_point).is_some() {
|
|
||||||
unicode_codepoints.push(code_point);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
unicode_codepoints.shrink_to_fit();
|
|
||||||
|
|
||||||
Some(FontMonospaceFallback {
|
|
||||||
monospace_em_width,
|
|
||||||
scripts,
|
|
||||||
unicode_codepoints,
|
|
||||||
})
|
|
||||||
})?
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let data = match &info.source {
|
let data = match &info.source {
|
||||||
fontdb::Source::Binary(data) => Arc::clone(data),
|
fontdb::Source::Binary(data) => Arc::clone(data),
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
|
|
@ -177,6 +141,77 @@ impl Font {
|
||||||
fontdb::Source::SharedFile(_path, data) => Arc::clone(data),
|
fontdb::Source::SharedFile(_path, data) => Arc::clone(data),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// It's a bit unfortunate but we need to parse the data into a `FontRef`
|
||||||
|
// twice--once to construct the HarfRust `ShaperInstance` and
|
||||||
|
// `ShaperData`, and once to create the persistent `FontRef` tied to the
|
||||||
|
// lifetime of the face data.
|
||||||
|
let font_ref = FontRef::from_index((*data).as_ref(), info.index).ok()?;
|
||||||
|
let location = font_ref
|
||||||
|
.axes()
|
||||||
|
.location([(Tag::new(b"wght"), weight.0 as f32)]);
|
||||||
|
let metrics = font_ref.metrics(Size::unscaled(), &location);
|
||||||
|
|
||||||
|
let monospace_fallback = if cfg!(feature = "monospace_fallback") {
|
||||||
|
(|| {
|
||||||
|
let glyph_metrics = font_ref.glyph_metrics(Size::unscaled(), &location);
|
||||||
|
let charmap = font_ref.charmap();
|
||||||
|
let monospace_em_width = info
|
||||||
|
.monospaced
|
||||||
|
.then(|| {
|
||||||
|
let hor_advance = glyph_metrics.advance_width(charmap.map(' ')?)?;
|
||||||
|
let upem = metrics.units_per_em;
|
||||||
|
Some(hor_advance / f32::from(upem))
|
||||||
|
})
|
||||||
|
.flatten();
|
||||||
|
|
||||||
|
if info.monospaced && monospace_em_width.is_none() {
|
||||||
|
None?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let scripts = font_ref
|
||||||
|
.gpos()
|
||||||
|
.ok()?
|
||||||
|
.script_list()
|
||||||
|
.ok()?
|
||||||
|
.script_records()
|
||||||
|
.iter()
|
||||||
|
.chain(
|
||||||
|
font_ref
|
||||||
|
.gsub()
|
||||||
|
.ok()?
|
||||||
|
.script_list()
|
||||||
|
.ok()?
|
||||||
|
.script_records()
|
||||||
|
.iter(),
|
||||||
|
)
|
||||||
|
.map(|script| script.script_tag().into_bytes())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut unicode_codepoints = Vec::new();
|
||||||
|
|
||||||
|
for (code_point, _) in charmap.mappings() {
|
||||||
|
unicode_codepoints.push(code_point);
|
||||||
|
}
|
||||||
|
|
||||||
|
unicode_codepoints.shrink_to_fit();
|
||||||
|
|
||||||
|
Some(FontMonospaceFallback {
|
||||||
|
monospace_em_width,
|
||||||
|
scripts,
|
||||||
|
unicode_codepoints,
|
||||||
|
})
|
||||||
|
})()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let (shaper_instance, shaper_data) = {
|
||||||
|
(
|
||||||
|
harfrust::ShaperInstance::from_coords(&font_ref, location.coords().iter().copied()),
|
||||||
|
harfrust::ShaperData::new(&font_ref),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
Some(Self {
|
Some(Self {
|
||||||
id: info.id,
|
id: info.id,
|
||||||
monospace_fallback,
|
monospace_fallback,
|
||||||
|
|
@ -185,21 +220,27 @@ impl Font {
|
||||||
let swash = swash::FontRef::from_index((*data).as_ref(), info.index as usize)?;
|
let swash = swash::FontRef::from_index((*data).as_ref(), info.index as usize)?;
|
||||||
(swash.offset, swash.key)
|
(swash.offset, swash.key)
|
||||||
},
|
},
|
||||||
rustybuzz: OwnedFace::try_new(Arc::clone(&data), |data| {
|
harfrust: OwnedFace::try_new(
|
||||||
RustybuzzFace::from_slice((**data).as_ref(), info.index)
|
OwnedFaceData {
|
||||||
.ok_or(())
|
data: Arc::clone(&data),
|
||||||
.map(|mut face| {
|
shaper_data,
|
||||||
if let Some(axis) = face
|
shaper_instance,
|
||||||
.variation_axes()
|
metrics,
|
||||||
.into_iter()
|
},
|
||||||
.find(|axis| axis.tag == ttf_parser::Tag::from_bytes(b"wght"))
|
|OwnedFaceData {
|
||||||
{
|
data,
|
||||||
let wght = f32::from(weight.0).clamp(axis.min_value, axis.max_value);
|
shaper_data,
|
||||||
let _ = face.set_variation(ttf_parser::Tag::from_bytes(b"wght"), wght);
|
shaper_instance,
|
||||||
}
|
..
|
||||||
face
|
}| {
|
||||||
})
|
let font_ref = FontRef::from_index((**data).as_ref(), info.index)?;
|
||||||
})
|
let shaper = shaper_data
|
||||||
|
.shaper(&font_ref)
|
||||||
|
.instance(Some(shaper_instance))
|
||||||
|
.build();
|
||||||
|
Ok::<_, ReadError>(shaper)
|
||||||
|
},
|
||||||
|
)
|
||||||
.ok()?,
|
.ok()?,
|
||||||
#[cfg(not(feature = "peniko"))]
|
#[cfg(not(feature = "peniko"))]
|
||||||
data,
|
data,
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,11 @@ use alloc::vec::Vec;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use core::ops::{Deref, DerefMut};
|
use core::ops::{Deref, DerefMut};
|
||||||
use fontdb::Query;
|
use fontdb::Query;
|
||||||
|
use skrifa::raw::{ReadError, TableProvider as _};
|
||||||
|
|
||||||
// re-export fontdb and rustybuzz
|
// re-export fontdb and harfrust
|
||||||
pub use fontdb;
|
pub use fontdb;
|
||||||
pub use rustybuzz;
|
pub use harfrust;
|
||||||
|
|
||||||
use super::fallback::{Fallback, Fallbacks, MonospaceFallbackInfo, PlatformFallback};
|
use super::fallback::{Fallback, Fallbacks, MonospaceFallbackInfo, PlatformFallback};
|
||||||
|
|
||||||
|
|
@ -182,19 +183,20 @@ impl FontSystem {
|
||||||
if cfg!(feature = "monospace_fallback") {
|
if cfg!(feature = "monospace_fallback") {
|
||||||
for &id in &monospace_font_ids {
|
for &id in &monospace_font_ids {
|
||||||
db.with_face_data(id, |font_data, face_index| {
|
db.with_face_data(id, |font_data, face_index| {
|
||||||
let _ = ttf_parser::Face::parse(font_data, face_index).map(|face| {
|
let face = skrifa::FontRef::from_index(font_data, face_index)?;
|
||||||
face.tables()
|
for script in face
|
||||||
.gpos
|
.gpos()?
|
||||||
.into_iter()
|
.script_list()?
|
||||||
.chain(face.tables().gsub)
|
.script_records()
|
||||||
.flat_map(|table| table.scripts)
|
.iter()
|
||||||
.inspect(|script| {
|
.chain(face.gsub()?.script_list()?.script_records().iter())
|
||||||
per_script_monospace_font_ids
|
{
|
||||||
.entry(script.tag.to_bytes())
|
per_script_monospace_font_ids
|
||||||
.or_default()
|
.entry(script.script_tag().into_bytes())
|
||||||
.insert(id);
|
.or_default()
|
||||||
})
|
.insert(id);
|
||||||
});
|
}
|
||||||
|
Ok::<_, ReadError>(())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
//!
|
//!
|
||||||
//! This library provides advanced text handling in a generic way. It provides abstractions for
|
//! This library provides advanced text handling in a generic way. It provides abstractions for
|
||||||
//! shaping, font discovery, font fallback, layout, rasterization, and editing. Shaping utilizes
|
//! shaping, font discovery, font fallback, layout, rasterization, and editing. Shaping utilizes
|
||||||
//! rustybuzz, font discovery utilizes fontdb, and the rasterization is optional and utilizes
|
//! harfrust, font discovery utilizes fontdb, and the rasterization is optional and utilizes
|
||||||
//! swash. The other features are developed internal to this library.
|
//! swash. The other features are developed internal to this library.
|
||||||
//!
|
//!
|
||||||
//! It is recommended that you start by creating a [`FontSystem`], after which you can create a
|
//! It is recommended that you start by creating a [`FontSystem`], after which you can create a
|
||||||
|
|
|
||||||
76
src/shape.rs
76
src/shape.rs
|
|
@ -10,6 +10,7 @@ use crate::{
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use alloc::collections::VecDeque;
|
||||||
use core::cmp::{max, min};
|
use core::cmp::{max, min};
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use core::mem;
|
use core::mem;
|
||||||
|
|
@ -78,11 +79,17 @@ impl Shaping {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const NUM_SHAPE_PLANS: usize = 6;
|
||||||
|
|
||||||
/// A set of buffers containing allocations for shaped text.
|
/// A set of buffers containing allocations for shaped text.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ShapeBuffer {
|
pub struct ShapeBuffer {
|
||||||
|
/// Cache for harfrust shape plans. Stores up to [`NUM_SHAPE_PLANS`] plans at once. Inserting a new one past that
|
||||||
|
/// will remove the one that was least recently added (not least recently used).
|
||||||
|
shape_plan_cache: VecDeque<(fontdb::ID, harfrust::ShapePlan)>,
|
||||||
|
|
||||||
/// Buffer for holding unicode text.
|
/// Buffer for holding unicode text.
|
||||||
rustybuzz_buffer: Option<rustybuzz::UnicodeBuffer>,
|
harfrust_buffer: Option<harfrust::UnicodeBuffer>,
|
||||||
|
|
||||||
/// Temporary buffers for scripts.
|
/// Temporary buffers for scripts.
|
||||||
scripts: Vec<Script>,
|
scripts: Vec<Script>,
|
||||||
|
|
@ -119,15 +126,15 @@ fn shape_fallback(
|
||||||
) -> Vec<usize> {
|
) -> Vec<usize> {
|
||||||
let run = &line[start_run..end_run];
|
let run = &line[start_run..end_run];
|
||||||
|
|
||||||
let font_scale = font.rustybuzz().units_per_em() as f32;
|
let font_scale = font.metrics().units_per_em as f32;
|
||||||
let ascent = f32::from(font.rustybuzz().ascender()) / font_scale;
|
let ascent = font.metrics().ascent / font_scale;
|
||||||
let descent = -f32::from(font.rustybuzz().descender()) / font_scale;
|
let descent = -font.metrics().descent / font_scale;
|
||||||
|
|
||||||
let mut buffer = scratch.rustybuzz_buffer.take().unwrap_or_default();
|
let mut buffer = scratch.harfrust_buffer.take().unwrap_or_default();
|
||||||
buffer.set_direction(if span_rtl {
|
buffer.set_direction(if span_rtl {
|
||||||
rustybuzz::Direction::RightToLeft
|
harfrust::Direction::RightToLeft
|
||||||
} else {
|
} else {
|
||||||
rustybuzz::Direction::LeftToRight
|
harfrust::Direction::LeftToRight
|
||||||
});
|
});
|
||||||
if run.contains('\t') {
|
if run.contains('\t') {
|
||||||
// Push string to buffer, replacing tabs with spaces
|
// Push string to buffer, replacing tabs with spaces
|
||||||
|
|
@ -140,29 +147,56 @@ fn shape_fallback(
|
||||||
}
|
}
|
||||||
buffer.guess_segment_properties();
|
buffer.guess_segment_properties();
|
||||||
|
|
||||||
let rtl = matches!(buffer.direction(), rustybuzz::Direction::RightToLeft);
|
let rtl = matches!(buffer.direction(), harfrust::Direction::RightToLeft);
|
||||||
assert_eq!(rtl, span_rtl);
|
assert_eq!(rtl, span_rtl);
|
||||||
|
|
||||||
let attrs = attrs_list.get_span(start_run);
|
let attrs = attrs_list.get_span(start_run);
|
||||||
let mut rb_font_features = Vec::new();
|
let mut rb_font_features = Vec::new();
|
||||||
|
|
||||||
// Convert attrs::Feature to rustybuzz::Feature
|
// Convert attrs::Feature to harfrust::Feature
|
||||||
for feature in attrs.font_features.features {
|
for feature in &attrs.font_features.features {
|
||||||
rb_font_features.push(rustybuzz::Feature::new(
|
rb_font_features.push(harfrust::Feature::new(
|
||||||
rustybuzz::ttf_parser::Tag::from_bytes(feature.tag.as_bytes()),
|
harfrust::Tag::new(feature.tag.as_bytes()),
|
||||||
feature.value,
|
feature.value,
|
||||||
0..usize::MAX,
|
0..usize::MAX,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let shape_plan = rustybuzz::ShapePlan::new(
|
let language = buffer.language();
|
||||||
font.rustybuzz(),
|
let key = harfrust::ShapePlanKey::new(Some(buffer.script()), buffer.direction())
|
||||||
buffer.direction(),
|
.features(&rb_font_features)
|
||||||
Some(buffer.script()),
|
.instance(Some(font.shaper_instance()))
|
||||||
buffer.language().as_ref(),
|
.language(language.as_ref());
|
||||||
&rb_font_features,
|
|
||||||
);
|
let shape_plan = match scratch
|
||||||
let glyph_buffer = rustybuzz::shape_with_plan(font.rustybuzz(), &shape_plan, buffer);
|
.shape_plan_cache
|
||||||
|
.iter()
|
||||||
|
.find(|(id, plan)| *id == font.id() && key.matches(plan))
|
||||||
|
{
|
||||||
|
Some((_font_id, plan)) => plan,
|
||||||
|
None => {
|
||||||
|
let plan = harfrust::ShapePlan::new(
|
||||||
|
font.shaper(),
|
||||||
|
buffer.direction(),
|
||||||
|
Some(buffer.script()),
|
||||||
|
buffer.language().as_ref(),
|
||||||
|
&rb_font_features,
|
||||||
|
);
|
||||||
|
if scratch.shape_plan_cache.len() >= NUM_SHAPE_PLANS {
|
||||||
|
scratch.shape_plan_cache.pop_front();
|
||||||
|
}
|
||||||
|
scratch.shape_plan_cache.push_back((font.id(), plan));
|
||||||
|
&scratch
|
||||||
|
.shape_plan_cache
|
||||||
|
.back()
|
||||||
|
.expect("we just pushed the shape plan")
|
||||||
|
.1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let glyph_buffer = font
|
||||||
|
.shaper()
|
||||||
|
.shape_with_plan(shape_plan, buffer, &rb_font_features);
|
||||||
let glyph_infos = glyph_buffer.glyph_infos();
|
let glyph_infos = glyph_buffer.glyph_infos();
|
||||||
let glyph_positions = glyph_buffer.glyph_positions();
|
let glyph_positions = glyph_buffer.glyph_positions();
|
||||||
|
|
||||||
|
|
@ -230,7 +264,7 @@ fn shape_fallback(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore the buffer to save an allocation.
|
// Restore the buffer to save an allocation.
|
||||||
scratch.rustybuzz_buffer = Some(glyph_buffer.clear());
|
scratch.harfrust_buffer = Some(glyph_buffer.clear());
|
||||||
|
|
||||||
missing
|
missing
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue