pub mod paint;
pub(crate) mod raster;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;
use fontdb::{
Database, Family as DbFamily, Query, Style as DbStyle, Weight as DbWeight, ID as FaceId,
};
use harfrust::{FontRef as HbFontRef, ShapeOptions, ShaperData, UnicodeBuffer};
use swash::scale::image::Content;
use swash::scale::{Render, ScaleContext, Source, StrikeWith};
use swash::{FontRef, GlyphId};
pub use raster::{decode_png, Framebuffer};
pub(crate) use raster::encode_rgba_png;
type DecodedIcon = Rc<(Vec<u8>, u32, u32)>;
type ShapeKey = (String, Option<FaceId>, u16, u32);
type IconCacheEntry = (Arc<[u8]>, DecodedIcon);
use crate::geometry::{LogicalPoint, LogicalRect, LogicalSize};
use crate::style::{Font, FontFamily, Rgba, Weight};
#[derive(Clone, Debug)]
pub struct TextRun<'a> {
pub text: &'a str,
pub origin: LogicalPoint,
pub font: &'a Font,
pub color: Rgba,
pub weight: Weight,
}
pub trait SceneDrawer {
fn begin_frame(&mut self, size: LogicalSize);
fn fill_round_rect(&mut self, rect: LogicalRect, corner_radius: f32, color: Rgba);
fn draw_separator(&mut self, rect: LogicalRect, color: Rgba);
fn measure_text(&self, text: &str, font: &Font) -> f32;
fn line_height(&self, font: &Font) -> f32;
fn draw_text(&mut self, run: &TextRun<'_>);
fn draw_image(&mut self, rgba: &[u8], src_w: u32, src_h: u32, dest: LogicalRect);
fn decode_icon(&self, bytes: &Arc<[u8]>) -> Option<DecodedIcon> {
raster::decode_png(bytes).map(Rc::new)
}
}
const LINE_HEIGHT_FACTOR: f32 = 1.3;
const FALLBACK_FAMILIES: &[&str] = &[
"Apple Color Emoji",
"Segoe UI Emoji",
"Noto Color Emoji",
"Apple Symbols",
"Segoe UI Symbol",
"Symbola",
"PingFang SC",
"Hiragino Sans",
"Hiragino Sans GB",
"Microsoft YaHei",
"Yu Gothic",
"Malgun Gothic",
"Noto Sans CJK SC",
"Noto Sans SC",
"Noto Sans",
"Arial Unicode MS",
];
pub struct RasterDrawer {
scale: f32,
fb: Framebuffer,
fonts: FontStore,
icons: RefCell<HashMap<usize, IconCacheEntry>>,
}
const ICON_CACHE_CAP: usize = 64;
impl std::fmt::Debug for RasterDrawer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RasterDrawer")
.field("scale", &self.scale)
.field("width", &self.fb.width())
.field("height", &self.fb.height())
.finish_non_exhaustive()
}
}
impl RasterDrawer {
pub fn new(scale: f32) -> Self {
let mut db = Database::new();
db.load_system_fonts();
let ui_family = resolve_ui_family(&db);
Self::from_parts(scale, db, ui_family)
}
pub fn new_headless(scale: f32) -> Self {
const DEJAVU_SANS: &[u8] = include_bytes!("../../tests/fonts/DejaVuSans.ttf");
const DEJAVU_SANS_BOLD: &[u8] = include_bytes!("../../tests/fonts/DejaVuSans-Bold.ttf");
let mut db = Database::new();
db.load_font_data(DEJAVU_SANS.to_vec());
db.load_font_data(DEJAVU_SANS_BOLD.to_vec());
let ui_family = resolve_ui_family(&db).or_else(|| Some("DejaVu Sans".to_string()));
Self::from_parts(scale, db, ui_family)
}
fn from_parts(scale: f32, db: Database, ui_family: Option<String>) -> Self {
RasterDrawer {
scale: scale.max(0.1),
fb: Framebuffer::new(1, 1),
fonts: FontStore::new(db, ui_family),
icons: RefCell::new(HashMap::new()),
}
}
pub fn scale(&self) -> f32 {
self.scale
}
pub fn framebuffer(&self) -> &Framebuffer {
&self.fb
}
pub fn device_size(&self) -> (u32, u32) {
(self.fb.width(), self.fb.height())
}
pub fn encode_png(&self) -> Vec<u8> {
self.fb.encode_png()
}
#[cfg(test)]
pub(crate) fn shape_miss_count(&self) -> usize {
self.fonts.shape_misses.get()
}
}
struct ShapedGlyph {
face: FaceId,
glyph: u16,
x: f32,
y: f32,
}
struct ShapedLine {
glyphs: Vec<ShapedGlyph>,
width: f32,
}
struct GlyphImage {
left: i32,
top: i32,
width: u32,
height: u32,
content: Content,
data: Vec<u8>,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
struct GlyphKey {
face: FaceId,
glyph: u16,
size_bits: u32,
}
struct FaceBytes {
data: Vec<u8>,
index: u32,
}
const GLYPH_CACHE_CAP: usize = 512;
const SHAPED_CACHE_CAP: usize = 1024;
struct FontStore {
db: Database,
ui_family: Option<String>,
face_data: RefCell<HashMap<FaceId, Rc<FaceBytes>>>,
glyphs: RefCell<HashMap<GlyphKey, Option<Rc<GlyphImage>>>>,
shaper_data: RefCell<HashMap<FaceId, Rc<ShaperData>>>,
coverage: RefCell<HashMap<(FaceId, char), bool>>,
fallback_cache: RefCell<HashMap<(char, u16), Option<FaceId>>>,
shaped: RefCell<HashMap<ShapeKey, Rc<ShapedLine>>>,
#[cfg(test)]
shape_misses: std::cell::Cell<usize>,
scale_ctx: RefCell<ScaleContext>,
fallback_order: Vec<FaceId>,
}
impl FontStore {
fn new(db: Database, ui_family: Option<String>) -> Self {
let fallback_order: Vec<FaceId> = db.faces().map(|f| f.id).collect();
FontStore {
db,
ui_family,
face_data: RefCell::new(HashMap::new()),
glyphs: RefCell::new(HashMap::new()),
shaper_data: RefCell::new(HashMap::new()),
coverage: RefCell::new(HashMap::new()),
fallback_cache: RefCell::new(HashMap::new()),
shaped: RefCell::new(HashMap::new()),
#[cfg(test)]
shape_misses: std::cell::Cell::new(0),
scale_ctx: RefCell::new(ScaleContext::new()),
fallback_order,
}
}
fn face_bytes(&self, id: FaceId) -> Option<Rc<FaceBytes>> {
if let Some(b) = self.face_data.borrow().get(&id) {
return Some(b.clone());
}
let bytes = self.db.with_face_data(id, |data, index| FaceBytes {
data: data.to_vec(),
index,
})?;
let rc = Rc::new(bytes);
self.face_data.borrow_mut().insert(id, rc.clone());
Some(rc)
}
fn db_family<'a>(&'a self, family: &'a FontFamily) -> DbFamily<'a> {
match family {
FontFamily::System => match &self.ui_family {
Some(name) => DbFamily::Name(name),
None => DbFamily::SansSerif,
},
FontFamily::SystemMono => DbFamily::Monospace,
FontFamily::Named(name) => DbFamily::Name(name),
}
}
fn resolve_face(&self, family: &FontFamily, ot_weight: u16) -> Option<FaceId> {
query_face(&self.db, self.db_family(family), ot_weight)
}
fn face_has_glyph(&self, id: FaceId, ch: char) -> bool {
if let Some(&hit) = self.coverage.borrow().get(&(id, ch)) {
return hit;
}
let hit = self
.face_bytes(id)
.and_then(|b| {
FontRef::from_index(&b.data, b.index as usize).map(|f| f.charmap().map(ch) != 0)
})
.unwrap_or(false);
self.coverage.borrow_mut().insert((id, ch), hit);
hit
}
fn fallback_face_for(&self, ch: char, ot_weight: u16) -> Option<FaceId> {
if let Some(&cached) = self.fallback_cache.borrow().get(&(ch, ot_weight)) {
return cached;
}
let result = 'find: {
for fam in FALLBACK_FAMILIES {
if let Some(id) = query_face(&self.db, DbFamily::Name(fam), ot_weight) {
if self.face_has_glyph(id, ch) {
break 'find Some(id);
}
}
}
self.fallback_order
.iter()
.copied()
.find(|&id| self.face_has_glyph(id, ch))
};
self.fallback_cache
.borrow_mut()
.insert((ch, ot_weight), result);
result
}
fn segment_faces(
&self,
text: &str,
primary: Option<FaceId>,
ot_weight: u16,
) -> Vec<(FaceId, String)> {
let mut runs: Vec<(FaceId, String)> = Vec::new();
for ch in text.chars() {
let face = match primary {
Some(p) if self.face_has_glyph(p, ch) => Some(p),
Some(p) => self.fallback_face_for(ch, ot_weight).or(Some(p)),
None => self.fallback_face_for(ch, ot_weight),
};
let Some(face) = face else { continue };
match runs.last_mut() {
Some((f, s)) if *f == face => s.push(ch),
_ => runs.push((face, ch.to_string())),
}
}
runs
}
fn shape(
&self,
text: &str,
primary: Option<FaceId>,
ot_weight: u16,
px: f32,
) -> Rc<ShapedLine> {
let key = (text.to_owned(), primary, ot_weight, px.to_bits());
if let Some(cached) = self.shaped.borrow().get(&key) {
return Rc::clone(cached);
}
#[cfg(test)]
self.shape_misses.set(self.shape_misses.get() + 1);
let mut glyphs = Vec::new();
let mut pen = 0.0_f32;
for (face_id, sub) in self.segment_faces(text, primary, ot_weight) {
let Some(bytes) = self.face_bytes(face_id) else {
continue;
};
let Ok(font) = HbFontRef::from_index(&bytes.data, bytes.index) else {
continue;
};
let shaper_data = {
let mut cache = self.shaper_data.borrow_mut();
Rc::clone(
cache
.entry(face_id)
.or_insert_with(|| Rc::new(ShaperData::new(&font))),
)
};
let shaper = shaper_data.shaper(&font).build();
let upem = shaper.units_per_em() as f32;
let s = if upem > 0.0 { px / upem } else { 0.0 };
let mut buffer = UnicodeBuffer::new();
buffer.push_str(&sub);
buffer.guess_segment_properties();
let shaped = shaper.shape(buffer, ShapeOptions::default());
for (info, pos) in shaped
.glyph_infos()
.iter()
.zip(shaped.glyph_positions().iter())
{
glyphs.push(ShapedGlyph {
face: face_id,
glyph: info.glyph_id as u16,
x: pen + pos.x_offset as f32 * s,
y: pos.y_offset as f32 * s,
});
pen += pos.x_advance as f32 * s;
}
}
let line = Rc::new(ShapedLine { glyphs, width: pen });
let mut cache = self.shaped.borrow_mut();
if cache.len() >= SHAPED_CACHE_CAP {
cache.clear();
}
cache.insert(key, Rc::clone(&line));
line
}
fn v_metrics(&self, primary: Option<FaceId>, px: f32) -> (f32, f32) {
if let Some(id) = primary {
if let Some(bytes) = self.face_bytes(id) {
if let Some(face) = FontRef::from_index(&bytes.data, bytes.index as usize) {
let m = face.metrics(&[]);
if m.units_per_em > 0 {
let sm = m.scale(px);
return (sm.ascent, -sm.descent);
}
}
}
}
(px * 0.8, -px * 0.2)
}
fn glyph_image(&self, face_id: FaceId, glyph: u16, px: f32) -> Option<Rc<GlyphImage>> {
let key = GlyphKey {
face: face_id,
glyph,
size_bits: px.to_bits(),
};
{
let cache = self.glyphs.borrow();
if let Some(cached) = cache.get(&key) {
return cached.clone();
}
}
let rendered = self.render_glyph(face_id, glyph, px);
let mut cache = self.glyphs.borrow_mut();
if cache.len() >= GLYPH_CACHE_CAP && !cache.contains_key(&key) {
cache.clear();
}
cache.insert(key, rendered.clone());
rendered
}
fn render_glyph(&self, face_id: FaceId, glyph: u16, px: f32) -> Option<Rc<GlyphImage>> {
let bytes = self.face_bytes(face_id)?;
let font = FontRef::from_index(&bytes.data, bytes.index as usize)?;
let mut ctx = self.scale_ctx.borrow_mut();
let mut scaler = ctx.builder(font).size(px).hint(false).build();
let image = Render::new(&[
Source::ColorOutline(0),
Source::ColorBitmap(StrikeWith::BestFit),
Source::Outline,
])
.render(&mut scaler, glyph as GlyphId)?;
if image.placement.width == 0 || image.placement.height == 0 {
return None;
}
Some(Rc::new(GlyphImage {
left: image.placement.left,
top: image.placement.top,
width: image.placement.width,
height: image.placement.height,
content: image.content,
data: image.data,
}))
}
}
fn query_face(db: &Database, family: DbFamily, ot_weight: u16) -> Option<FaceId> {
db.query(&Query {
families: &[family],
weight: DbWeight(ot_weight),
stretch: fontdb::Stretch::Normal,
style: DbStyle::Normal,
})
}
fn resolve_ui_family(db: &Database) -> Option<String> {
for cand in [
".SF NS",
"SF Pro Text",
"SF Pro",
"Segoe UI",
"Helvetica Neue",
"Arial",
"DejaVu Sans",
"Liberation Sans",
] {
if family_shapes_both_weights(db, cand) {
return Some(cand.to_string());
}
}
None
}
fn family_shapes_both_weights(db: &Database, name: &str) -> bool {
let regular = query_face(db, DbFamily::Name(name), 400);
let bold = query_face(db, DbFamily::Name(name), 700);
match (regular, bold) {
(Some(r), Some(b)) => {
r != b
&& face_in_family(db, r, name)
&& face_in_family(db, b, name)
&& db
.face(b)
.map(|f| f.weight >= DbWeight(600))
.unwrap_or(false)
&& face_can_shape(db, r)
&& face_can_shape(db, b)
}
_ => false,
}
}
fn face_in_family(db: &Database, id: FaceId, name: &str) -> bool {
db.face(id)
.map(|f| f.families.iter().any(|(n, _)| n == name))
.unwrap_or(false)
}
fn face_can_shape(db: &Database, id: FaceId) -> bool {
db.with_face_data(id, |data, index| {
FontRef::from_index(data, index as usize)
.map(|f| {
let cmap = f.charmap();
"Agy0".chars().all(|c| cmap.map(c) != 0)
})
.unwrap_or(false)
})
.unwrap_or(false)
}
impl SceneDrawer for RasterDrawer {
fn begin_frame(&mut self, size: LogicalSize) {
let w = ((size.width * self.scale).round() as u32).max(1);
let h = ((size.height * self.scale).round() as u32).max(1);
self.fb = Framebuffer::new(w, h);
}
fn fill_round_rect(&mut self, rect: LogicalRect, corner_radius: f32, color: Rgba) {
let s = self.scale;
let (x, y, w, h) = raster::scaled(rect, s);
raster::fill_round_rect(&mut self.fb, x, y, w, h, corner_radius * s, color);
}
fn draw_separator(&mut self, rect: LogicalRect, color: Rgba) {
let s = self.scale;
let x = rect.origin.x * s;
let y = (rect.origin.y * s).round();
let w = rect.size.width * s;
let h = (rect.size.height * s).max(1.0);
raster::fill_rect(&mut self.fb, x, y, w, h, color);
}
fn measure_text(&self, text: &str, font: &Font) -> f32 {
if text.is_empty() {
return 0.0;
}
let ot_weight = font.weight.ot_weight();
let primary = self.fonts.resolve_face(&font.family, ot_weight);
self.fonts.shape(text, primary, ot_weight, font.size).width
}
fn line_height(&self, font: &Font) -> f32 {
font.size * LINE_HEIGHT_FACTOR
}
fn draw_text(&mut self, run: &TextRun<'_>) {
if run.text.is_empty() {
return;
}
let scale = self.scale;
let px = run.font.size * scale;
let ox = run.origin.x * scale;
let oy = run.origin.y * scale;
let ot_weight = run.weight.ot_weight();
let primary = self.fonts.resolve_face(&run.font.family, ot_weight);
let shaped = self.fonts.shape(run.text, primary, ot_weight, px);
let (ascent, descent) = self.fonts.v_metrics(primary, px);
let line_height = px * LINE_HEIGHT_FACTOR;
let baseline = ascent + (line_height - (ascent - descent)) / 2.0;
let color = run.color;
let (pw, ph) = (self.fb.width() as i32, self.fb.height() as i32);
for g in &shaped.glyphs {
let Some(image) = self.fonts.glyph_image(g.face, g.glyph, px) else {
continue;
};
let gx = (ox + g.x).round() as i32 + image.left;
let gy = (oy + baseline - g.y).round() as i32 - image.top;
let pixels = self.fb.pixels_mut();
blit_glyph(pixels, pw, ph, &image, gx, gy, color);
}
}
fn draw_image(&mut self, rgba: &[u8], src_w: u32, src_h: u32, dest: LogicalRect) {
if src_w == 0 || src_h == 0 {
return;
}
let s = self.scale;
let dx = (dest.origin.x * s).round() as i32;
let dy = (dest.origin.y * s).round() as i32;
let dw = (dest.size.width * s).round().max(1.0) as i32;
let dh = (dest.size.height * s).round().max(1.0) as i32;
let (pw, ph) = (self.fb.width() as i32, self.fb.height() as i32);
let pixels = self.fb.pixels_mut();
for row in 0..dh {
for col in 0..dw {
let sx = (col * src_w as i32 / dw).clamp(0, src_w as i32 - 1);
let sy = (row * src_h as i32 / dh).clamp(0, src_h as i32 - 1);
let idx = ((sy * src_w as i32 + sx) * 4) as usize;
if idx + 3 >= rgba.len() {
continue;
}
let a = rgba[idx + 3];
if a == 0 {
continue;
}
let px = dx + col;
let py = dy + row;
if px < 0 || py < 0 || px >= pw || py >= ph {
continue;
}
raster::blend_pixel(
pixels,
((py * pw + px) * 4) as usize,
Rgba::new(rgba[idx], rgba[idx + 1], rgba[idx + 2], 255),
a,
);
}
}
}
fn decode_icon(&self, bytes: &Arc<[u8]>) -> Option<DecodedIcon> {
let key = Arc::as_ptr(bytes) as *const u8 as usize;
if let Some(hit) = icon_cache_hit(self.icons.borrow().get(&key), bytes) {
return Some(hit);
}
let decoded = Rc::new(raster::decode_png(bytes)?);
let mut cache = self.icons.borrow_mut();
if cache.len() >= ICON_CACHE_CAP && !cache.contains_key(&key) {
cache.clear();
}
cache.insert(key, (bytes.clone(), decoded.clone()));
Some(decoded)
}
}
fn icon_cache_hit(entry: Option<&IconCacheEntry>, bytes: &Arc<[u8]>) -> Option<DecodedIcon> {
match entry {
Some((cached_bytes, hit)) if Arc::ptr_eq(cached_bytes, bytes) => Some(hit.clone()),
_ => None,
}
}
fn blit_glyph(
pixels: &mut [u8],
pw: i32,
ph: i32,
image: &GlyphImage,
gx: i32,
gy: i32,
color: Rgba,
) {
let iw = image.width as i32;
let ih = image.height as i32;
match image.content {
Content::Mask | Content::SubpixelMask => {
for row in 0..ih {
for col in 0..iw {
let cov = image.data[(row * iw + col) as usize];
if cov == 0 {
continue;
}
let px = gx + col;
let py = gy + row;
if px < 0 || py < 0 || px >= pw || py >= ph {
continue;
}
let a = (cov as u16 * color.a as u16 / 255) as u8;
raster::blend_pixel(pixels, ((py * pw + px) * 4) as usize, color, a);
}
}
}
Content::Color => {
for row in 0..ih {
for col in 0..iw {
let idx = ((row * iw + col) * 4) as usize;
let (r, g, b, a) = (
image.data[idx],
image.data[idx + 1],
image.data[idx + 2],
image.data[idx + 3],
);
if a == 0 {
continue;
}
let px = gx + col;
let py = gy + row;
if px < 0 || py < 0 || px >= pw || py >= ph {
continue;
}
raster::blend_pixel(
pixels,
((py * pw + px) * 4) as usize,
Rgba::new(r, g, b, 255),
a,
);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::style::FontFamily;
#[test]
fn segment_faces_keeps_primary_face_when_no_fallback_face_covers_the_gap() {
let d = RasterDrawer::new_headless(1.0);
let primary = d.fonts.resolve_face(&FontFamily::System, 400);
assert!(primary.is_some(), "headless db must resolve a UI family");
let runs = d.fonts.segment_faces("a\u{1F600}b", primary, 400);
assert_eq!(runs.len(), 1, "expected one merged run, got {runs:?}");
assert_eq!(runs[0].0, primary.unwrap());
assert_eq!(runs[0].1, "a\u{1F600}b");
}
#[test]
fn segment_faces_drops_uncovered_codepoints_when_no_primary_face_is_pinned() {
let d = RasterDrawer::new_headless(1.0);
let runs = d.fonts.segment_faces("a\u{1F600}b", None, 400);
assert_eq!(runs.len(), 1, "expected one merged run, got {runs:?}");
assert_eq!(runs[0].1, "ab");
}
#[test]
fn segment_faces_keeps_one_run_when_the_primary_face_covers_everything() {
let d = RasterDrawer::new_headless(1.0);
let primary = d.fonts.resolve_face(&FontFamily::System, 400);
assert!(primary.is_some());
let runs = d.fonts.segment_faces("hello", primary, 400);
assert_eq!(runs.len(), 1);
assert_eq!(runs[0].0, primary.unwrap());
assert_eq!(runs[0].1, "hello");
}
#[test]
fn segment_faces_uses_fallback_order_when_no_primary_face_is_given() {
let d = RasterDrawer::new_headless(1.0);
let runs = d.fonts.segment_faces("hi", None, 400);
assert_eq!(runs.len(), 1, "expected one run, got {runs:?}");
assert_eq!(runs[0].1, "hi");
}
fn solid_png(color: Rgba) -> Arc<[u8]> {
let mut fb = Framebuffer::new(2, 2);
fb.fill(color);
let bytes: Vec<u8> = fb.encode_png();
Arc::from(bytes.into_boxed_slice())
}
#[test]
fn decode_icon_hits_the_cache_for_the_same_arc_across_calls() {
let d = RasterDrawer::new_headless(1.0);
let bytes = solid_png(Rgba::opaque(10, 20, 30));
let first = d.decode_icon(&bytes).expect("valid png decodes");
let second = d.decode_icon(&bytes).expect("valid png decodes");
assert_eq!(first.0, second.0);
assert_eq!((first.1, first.2), (second.1, second.2));
assert!(
Rc::ptr_eq(&first, &second),
"expected a cache hit, got a fresh decode"
);
}
#[test]
fn decode_icon_never_conflates_two_different_arcs() {
let d = RasterDrawer::new_headless(1.0);
let red = solid_png(Rgba::opaque(255, 0, 0));
let blue = solid_png(Rgba::opaque(0, 0, 255));
let red_decoded = d.decode_icon(&red).expect("valid png decodes");
let blue_decoded = d.decode_icon(&blue).expect("valid png decodes");
assert_ne!(
red_decoded.0, blue_decoded.0,
"distinct icons must decode to distinct pixels"
);
assert_eq!(&red_decoded.0[0..4], &[255, 0, 0, 255]);
assert_eq!(&blue_decoded.0[0..4], &[0, 0, 255, 255]);
let red_again = d.decode_icon(&red).expect("valid png decodes");
let blue_again = d.decode_icon(&blue).expect("valid png decodes");
assert_eq!(red_again.0, red_decoded.0);
assert_eq!(blue_again.0, blue_decoded.0);
assert!(
Rc::ptr_eq(&red_again, &red_decoded),
"expected a cache hit for `red`"
);
assert!(
Rc::ptr_eq(&blue_again, &blue_decoded),
"expected a cache hit for `blue`"
);
}
#[test]
fn decode_icon_cache_entry_survives_the_callers_arc_being_dropped() {
let d = RasterDrawer::new_headless(1.0);
let bytes = solid_png(Rgba::opaque(4, 5, 6));
let decoded = d.decode_icon(&bytes).expect("valid png decodes");
drop(bytes);
assert_eq!(d.icons.borrow().len(), 1);
let (kept_bytes, kept_decoded) = d.icons.borrow().values().next().cloned().unwrap();
assert!(Rc::ptr_eq(&kept_decoded, &decoded));
assert!(!kept_bytes.is_empty());
}
#[test]
fn icon_cache_hit_rejects_a_key_hit_on_a_different_allocation() {
let cached: Arc<[u8]> = Arc::from(vec![1u8, 2, 3].into_boxed_slice());
let query: Arc<[u8]> = Arc::from(vec![1u8, 2, 3].into_boxed_slice());
assert!(!Arc::ptr_eq(&cached, &query));
let decoded: DecodedIcon = Rc::new((vec![9, 9, 9, 9], 1, 1));
let entry: IconCacheEntry = (cached.clone(), decoded.clone());
assert!(icon_cache_hit(Some(&entry), &query).is_none());
let hit = icon_cache_hit(Some(&entry), &cached).expect("same-Arc hit");
assert!(Rc::ptr_eq(&hit, &decoded));
assert!(icon_cache_hit(None, &cached).is_none());
}
}