use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
use std::sync::Arc;
use std::task::{Context, Poll, Waker};
use day_spec::{
BitmapId, BitmapInfo, Cap, DrawOp, EncodeSpec, ExportError, ImageError, ImageFormat,
ImageProperties, PdfInfo, PdfPage, Size,
};
use crate::with_tree;
day_reactive::tls_slots! {
image;
static PENDING_DECODE: RefCell<HashMap<u64, (BitmapId, Pending<(BitmapId, BitmapInfo)>)>> =
RefCell::new(HashMap::new());
static PENDING_ENCODE: RefCell<HashMap<u64, Pending<Vec<u8>>>> = RefCell::new(HashMap::new());
static PENDING_BITMAP: RefCell<HashMap<u64, (BitmapId, Waiting<(BitmapId, BitmapInfo), ExportError>)>> =
RefCell::new(HashMap::new());
static PENDING_PDF: RefCell<HashMap<u64, Waiting<Vec<u8>, ExportError>>> =
RefCell::new(HashMap::new());
static DEFERRED_RELEASE: RefCell<Vec<BitmapId>> = RefCell::new(Vec::new());
static NEXT_REQ: Cell<u64> = const { Cell::new(1) };
static EXPORTS: Cell<Option<CanvasExports>> = const { Cell::new(None) };
static NEXT_BITMAP: Cell<u64> = const { Cell::new(1) };
}
type ImageDone<T, E = ImageError> = Box<dyn FnOnce(Result<T, E>)>;
struct Waiting<T, E> {
shared: Rc<Shared<T, E>>,
callback: Option<ImageDone<T, E>>,
}
type Pending<T> = Waiting<T, ImageError>;
struct Shared<T, E = ImageError> {
result: RefCell<Option<Result<T, E>>>,
waker: RefCell<Option<Waker>>,
cancelled: Cell<bool>,
}
impl<T, E> Default for Shared<T, E> {
fn default() -> Self {
Shared {
result: RefCell::new(None),
waker: RefCell::new(None),
cancelled: Cell::new(false),
}
}
}
fn next_req() -> u64 {
NEXT_REQ.with(|c| {
let v = c.get();
c.set(v + 1);
v
})
}
fn next_bitmap() -> BitmapId {
BitmapId(NEXT_BITMAP.with(|c| {
let v = c.get();
c.set(v + 1);
v
}))
}
#[derive(Clone)]
pub struct Bitmap(Rc<BitmapInner>);
struct BitmapInner {
id: BitmapId,
info: BitmapInfo,
}
impl Drop for BitmapInner {
fn drop(&mut self) {
release(self.id);
}
}
fn release(id: BitmapId) {
if crate::tree_absent() {
return;
}
if crate::with_tree_if_free(|t| t.release_image(id)).is_none() {
DEFERRED_RELEASE.with(|q| q.borrow_mut().push(id));
crate::request_pump();
}
}
pub(crate) fn flush_deferred_releases() {
let ids: Vec<BitmapId> = DEFERRED_RELEASE.with(|q| std::mem::take(&mut *q.borrow_mut()));
for id in ids {
if crate::with_tree_if_free(|t| t.release_image(id)).is_none() {
DEFERRED_RELEASE.with(|q| q.borrow_mut().push(id));
}
}
}
impl Bitmap {
pub fn id(&self) -> BitmapId {
self.0.id
}
pub fn info(&self) -> BitmapInfo {
self.0.info
}
pub fn properties(&self) -> Option<ImageProperties> {
let id = self.0.id;
with_tree(|t| t.image_properties(id))
}
pub fn encode(&self, spec: EncodeSpec) -> EncodeFuture {
EncodeFuture {
source: self.clone(),
spec,
shared: Rc::new(Shared::default()),
started: false,
}
}
pub fn encode_async(
&self,
spec: EncodeSpec,
on_done: impl FnOnce(Result<Vec<u8>, ImageError>) + 'static,
) {
if support(Cap::ImageEncode) == day_spec::Support::Unsupported {
on_done(Err(ImageError::Unsupported));
return;
}
let keep = self.clone();
start_encode(
self.0.id,
spec,
None,
Some(Box::new(move |r| {
drop(keep);
on_done(r)
})),
);
}
}
impl std::fmt::Debug for Bitmap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Bitmap")
.field("id", &self.0.id.0)
.field("info", &self.0.info)
.finish()
}
}
impl PartialEq for Bitmap {
fn eq(&self, other: &Self) -> bool {
self.0.id == other.0.id
}
}
pub fn decode(bytes: Arc<Vec<u8>>) -> DecodeFuture {
DecodeFuture {
bytes: Some(bytes),
shared: Rc::new(Shared::default()),
started: false,
}
}
pub fn decode_async(
bytes: Arc<Vec<u8>>,
on_done: impl FnOnce(Result<Bitmap, ImageError>) + 'static,
) {
if support(Cap::ImageDecode) == day_spec::Support::Unsupported {
on_done(Err(ImageError::Unsupported));
return;
}
start_decode(
&bytes,
None,
Some(Box::new(move |r| on_done(r.map(bitmap_from)))),
);
}
pub fn image_decode_support() -> day_spec::Support {
support(Cap::ImageDecode)
}
pub fn image_encode_support() -> day_spec::Support {
support(Cap::ImageEncode)
}
pub fn image_encode_formats() -> Vec<ImageFormat> {
with_tree(|t| t.image_encode_formats())
}
fn support(cap: Cap) -> day_spec::Support {
with_tree(|t| t.capability(cap))
}
fn bitmap_from((id, info): (BitmapId, BitmapInfo)) -> Bitmap {
Bitmap(Rc::new(BitmapInner { id, info }))
}
fn start_decode(
bytes: &[u8],
shared: Option<Rc<Shared<(BitmapId, BitmapInfo)>>>,
callback: Option<ImageDone<(BitmapId, BitmapInfo)>>,
) -> u64 {
let req = next_req();
let id = next_bitmap();
PENDING_DECODE.with(|p| {
p.borrow_mut().insert(
req,
(
id,
Pending {
shared: shared.unwrap_or_default(),
callback,
},
),
)
});
with_tree(|t| t.decode_image(req, id, bytes));
req
}
fn start_encode(
id: BitmapId,
spec: EncodeSpec,
shared: Option<Rc<Shared<Vec<u8>>>>,
callback: Option<ImageDone<Vec<u8>>>,
) -> u64 {
let req = next_req();
PENDING_ENCODE.with(|p| {
p.borrow_mut().insert(
req,
Pending {
shared: shared.unwrap_or_default(),
callback,
},
)
});
with_tree(|t| t.encode_image(req, id, &spec));
req
}
pub struct DecodeFuture {
bytes: Option<Arc<Vec<u8>>>,
shared: Rc<Shared<(BitmapId, BitmapInfo)>>,
started: bool,
}
impl Future for DecodeFuture {
type Output = Result<Bitmap, ImageError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Some(result) = self.shared.result.borrow_mut().take() {
return Poll::Ready(result.map(bitmap_from));
}
if !self.started {
self.started = true;
if support(Cap::ImageDecode) == day_spec::Support::Unsupported {
return Poll::Ready(Err(ImageError::Unsupported));
}
let bytes = self.bytes.take().expect("decode bytes");
let shared = self.shared.clone();
start_decode(&bytes, Some(shared), None);
if let Some(result) = self.shared.result.borrow_mut().take() {
return Poll::Ready(result.map(bitmap_from));
}
}
*self.shared.waker.borrow_mut() = Some(cx.waker().clone());
Poll::Pending
}
}
impl Drop for DecodeFuture {
fn drop(&mut self) {
if let Some(Ok((id, _))) = self.shared.result.borrow_mut().take() {
release(id);
}
self.shared.cancelled.set(true);
}
}
pub struct EncodeFuture {
source: Bitmap,
spec: EncodeSpec,
shared: Rc<Shared<Vec<u8>>>,
started: bool,
}
impl Future for EncodeFuture {
type Output = Result<Vec<u8>, ImageError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Some(result) = self.shared.result.borrow_mut().take() {
return Poll::Ready(result);
}
if !self.started {
self.started = true;
if support(Cap::ImageEncode) == day_spec::Support::Unsupported {
return Poll::Ready(Err(ImageError::Unsupported));
}
let shared = self.shared.clone();
start_encode(self.source.id(), self.spec, Some(shared), None);
if let Some(result) = self.shared.result.borrow_mut().take() {
return Poll::Ready(result);
}
}
*self.shared.waker.borrow_mut() = Some(cx.waker().clone());
Poll::Pending
}
}
pub fn resolve_image_decode(req: u64, result: Result<BitmapInfo, ImageError>) {
let Some((id, entry)) = PENDING_DECODE.with(|p| p.borrow_mut().remove(&req)) else {
return;
};
if entry.callback.is_none() && entry.shared.cancelled.get() {
if result.is_ok() {
release(id);
}
return;
}
deliver(entry, result.map(|info| (id, info)));
}
pub fn resolve_image_encode(req: u64, result: Result<Vec<u8>, ImageError>) {
let Some(entry) = PENDING_ENCODE.with(|p| p.borrow_mut().remove(&req)) else {
return;
};
deliver(entry, result);
}
fn deliver<T, E>(entry: Waiting<T, E>, value: Result<T, E>) {
if let Some(cb) = entry.callback {
cb(value);
return;
}
*entry.shared.result.borrow_mut() = Some(value);
if let Some(waker) = entry.shared.waker.borrow_mut().take() {
waker.wake();
}
}
pub(crate) fn reset() {
let decodes: Vec<_> =
PENDING_DECODE.with(|p| p.borrow_mut().drain().map(|(_, (_, e))| e).collect());
for entry in decodes {
deliver(entry, Err(ImageError::Unsupported));
}
let encodes: Vec<_> = PENDING_ENCODE.with(|p| p.borrow_mut().drain().map(|(_, e)| e).collect());
for entry in encodes {
deliver(entry, Err(ImageError::Unsupported));
}
let bitmaps: Vec<_> =
PENDING_BITMAP.with(|p| p.borrow_mut().drain().map(|(_, (_, e))| e).collect());
for entry in bitmaps {
deliver(entry, Err(ExportError::Unsupported));
}
let pdfs: Vec<_> = PENDING_PDF.with(|p| p.borrow_mut().drain().map(|(_, e)| e).collect());
for entry in pdfs {
deliver(entry, Err(ExportError::Unsupported));
}
DEFERRED_RELEASE.with(|q| q.borrow_mut().clear());
EXPORTS.with(|c| c.set(None));
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct CanvasExports {
pub bitmap: bool,
pub pdf: bool,
png: bool,
}
impl CanvasExports {
pub fn png(&self) -> bool {
self.png
}
}
pub fn canvas_exports() -> CanvasExports {
if let Some(known) = EXPORTS.with(|c| c.get()) {
return known;
}
let Some(read) = crate::with_tree_if_free(|t| {
let yes = |cap| t.capability(cap) != day_spec::Support::Unsupported;
let bitmap = yes(Cap::CanvasBitmap);
CanvasExports {
bitmap,
pdf: yes(Cap::CanvasPdf),
png: bitmap && t.image_encode_formats().contains(&ImageFormat::Png),
}
}) else {
return CanvasExports::default();
};
EXPORTS.with(|c| c.set(Some(read)));
read
}
fn check_bitmap(size: Size, scale: f64) -> Result<(), ExportError> {
let positive = |v: f64| v.is_finite() && v > 0.0;
if !positive(size.width) || !positive(size.height) || !positive(scale) {
return Err(ExportError::Invalid);
}
let (width, height) = day_spec::export_pixels(size, scale);
if width > day_spec::EXPORT_MAX_SIDE
|| height > day_spec::EXPORT_MAX_SIDE
|| u64::from(width) * u64::from(height) > day_spec::EXPORT_MAX_PIXELS
{
return Err(ExportError::TooLarge { width, height });
}
Ok(())
}
fn check_pdf(pages: &[PdfPage]) -> Result<(), ExportError> {
let positive = |v: f64| v.is_finite() && v > 0.0;
if pages.is_empty()
|| pages
.iter()
.any(|p| !positive(p.size.width) || !positive(p.size.height))
{
return Err(ExportError::Invalid);
}
Ok(())
}
fn admit_bitmap(size: Size, scale: f64) -> Result<(), ExportError> {
if support(Cap::CanvasBitmap) == day_spec::Support::Unsupported {
return Err(ExportError::Unsupported);
}
check_bitmap(size, scale)
}
fn admit_pdf(pages: &[PdfPage]) -> Result<(), ExportError> {
if support(Cap::CanvasPdf) == day_spec::Support::Unsupported {
return Err(ExportError::Unsupported);
}
check_pdf(pages)
}
pub fn render_bitmap(ops: Vec<DrawOp>, size: Size, scale: f64) -> RenderBitmap {
RenderBitmap {
job: Some((ops, size, scale)),
shared: Rc::new(Shared::default()),
started: false,
}
}
pub fn render_bitmap_async(
ops: Vec<DrawOp>,
size: Size,
scale: f64,
on_done: impl FnOnce(Result<Bitmap, ExportError>) + 'static,
) {
if let Err(e) = admit_bitmap(size, scale) {
on_done(Err(e));
return;
}
start_bitmap(
&ops,
size,
scale,
None,
Some(Box::new(move |r| on_done(r.map(bitmap_from)))),
);
}
pub fn render_pdf(pages: Vec<PdfPage>, info: PdfInfo) -> RenderPdf {
RenderPdf {
job: Some((pages, info)),
shared: Rc::new(Shared::default()),
started: false,
}
}
pub fn render_pdf_async(
pages: Vec<PdfPage>,
info: PdfInfo,
on_done: impl FnOnce(Result<Vec<u8>, ExportError>) + 'static,
) {
if let Err(e) = admit_pdf(&pages) {
on_done(Err(e));
return;
}
start_pdf(&pages, &info, None, Some(Box::new(on_done)));
}
fn start_bitmap(
ops: &[DrawOp],
size: Size,
scale: f64,
shared: Option<Rc<Shared<(BitmapId, BitmapInfo), ExportError>>>,
callback: Option<ImageDone<(BitmapId, BitmapInfo), ExportError>>,
) {
let req = next_req();
let id = next_bitmap();
PENDING_BITMAP.with(|p| {
p.borrow_mut().insert(
req,
(
id,
Waiting {
shared: shared.unwrap_or_default(),
callback,
},
),
)
});
with_tree(|t| t.render_bitmap(req, id, ops, size, scale));
}
fn start_pdf(
pages: &[PdfPage],
info: &PdfInfo,
shared: Option<Rc<Shared<Vec<u8>, ExportError>>>,
callback: Option<ImageDone<Vec<u8>, ExportError>>,
) {
let req = next_req();
PENDING_PDF.with(|p| {
p.borrow_mut().insert(
req,
Waiting {
shared: shared.unwrap_or_default(),
callback,
},
)
});
with_tree(|t| t.render_pdf(req, pages, info));
}
pub struct RenderBitmap {
job: Option<(Vec<DrawOp>, Size, f64)>,
shared: Rc<Shared<(BitmapId, BitmapInfo), ExportError>>,
started: bool,
}
impl Future for RenderBitmap {
type Output = Result<Bitmap, ExportError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Some(result) = self.shared.result.borrow_mut().take() {
return Poll::Ready(result.map(bitmap_from));
}
if !self.started {
self.started = true;
let Some((ops, size, scale)) = self.job.take() else {
return Poll::Ready(Err(ExportError::Invalid));
};
if let Err(e) = admit_bitmap(size, scale) {
return Poll::Ready(Err(e));
}
start_bitmap(&ops, size, scale, Some(self.shared.clone()), None);
if let Some(result) = self.shared.result.borrow_mut().take() {
return Poll::Ready(result.map(bitmap_from));
}
}
*self.shared.waker.borrow_mut() = Some(cx.waker().clone());
Poll::Pending
}
}
impl Drop for RenderBitmap {
fn drop(&mut self) {
if let Some(Ok((id, _))) = self.shared.result.borrow_mut().take() {
release(id);
}
self.shared.cancelled.set(true);
}
}
pub struct RenderPdf {
job: Option<(Vec<PdfPage>, PdfInfo)>,
shared: Rc<Shared<Vec<u8>, ExportError>>,
started: bool,
}
impl Future for RenderPdf {
type Output = Result<Vec<u8>, ExportError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if let Some(result) = self.shared.result.borrow_mut().take() {
return Poll::Ready(result);
}
if !self.started {
self.started = true;
let Some((pages, info)) = self.job.take() else {
return Poll::Ready(Err(ExportError::Invalid));
};
if let Err(e) = admit_pdf(&pages) {
return Poll::Ready(Err(e));
}
start_pdf(&pages, &info, Some(self.shared.clone()), None);
if let Some(result) = self.shared.result.borrow_mut().take() {
return Poll::Ready(result);
}
}
*self.shared.waker.borrow_mut() = Some(cx.waker().clone());
Poll::Pending
}
}
pub fn resolve_bitmap_render(req: u64, result: Result<BitmapInfo, ExportError>) {
let Some((id, entry)) = PENDING_BITMAP.with(|p| p.borrow_mut().remove(&req)) else {
return;
};
if entry.callback.is_none() && entry.shared.cancelled.get() {
if result.is_ok() {
release(id);
}
return;
}
deliver(entry, result.map(|info| (id, info)));
}
pub fn resolve_pdf_render(req: u64, result: Result<Vec<u8>, ExportError>) {
let Some(entry) = PENDING_PDF.with(|p| p.borrow_mut().remove(&req)) else {
return;
};
deliver(entry, result);
}
#[cfg(test)]
mod export_tests {
use super::*;
#[test]
fn bitmap_sizes_are_checked_before_the_toolkit() {
let size = Size::new(100.0, 50.0);
assert_eq!(check_bitmap(size, 2.0), Ok(()));
assert_eq!(
check_bitmap(Size::new(0.0, 50.0), 1.0),
Err(ExportError::Invalid)
);
assert_eq!(check_bitmap(size, f64::NAN), Err(ExportError::Invalid));
assert_eq!(check_bitmap(size, -1.0), Err(ExportError::Invalid));
assert_eq!(
check_bitmap(Size::new(16_385.0, 1.0), 1.0),
Err(ExportError::TooLarge {
width: 16_385,
height: 1
})
);
assert_eq!(
check_bitmap(Size::new(5_000.0, 5_000.0), 2.0),
Err(ExportError::TooLarge {
width: 10_000,
height: 10_000
})
);
assert_eq!(day_spec::export_pixels(Size::new(10.2, 3.0), 2.0), (21, 6));
}
#[test]
fn a_pdf_needs_pages_with_a_size() {
assert_eq!(check_pdf(&[]), Err(ExportError::Invalid));
let page = |w, h| PdfPage {
size: Size::new(w, h),
ops: Vec::new(),
};
assert_eq!(check_pdf(&[page(612.0, 792.0)]), Ok(()));
assert_eq!(
check_pdf(&[page(612.0, 792.0), page(0.0, 10.0)]),
Err(ExportError::Invalid)
);
}
}
#[cfg(test)]
mod teardown_tests {
use super::*;
#[test]
fn bitmap_drop_during_reactive_tls_teardown() {
const CHILD: &str = "DAY_CORE_BITMAP_REACTIVE_TEARDOWN_CHILD";
if let Some(mode) = std::env::var_os(CHILD) {
struct TeardownBitmap {
bitmap: Option<Bitmap>,
dropped: std::sync::mpsc::Sender<()>,
}
impl Drop for TeardownBitmap {
fn drop(&mut self) {
drop(self.bitmap.take());
self.dropped.send(()).unwrap();
}
}
let (dropped, observed) = std::sync::mpsc::channel();
std::thread::spawn(move || {
day_reactive::Signal::global(TeardownBitmap {
bitmap: Some(bitmap_from((BitmapId(1), BitmapInfo::default()))),
dropped,
});
if mode == "initialized" {
assert!(crate::try_tls_root(|_| ()).is_ok());
}
})
.join()
.unwrap();
observed
.recv_timeout(std::time::Duration::from_secs(5))
.expect("bitmap was not dropped during reactive TLS teardown");
return;
}
for mode in ["uninitialized", "initialized"] {
let output = std::process::Command::new(std::env::current_exe().unwrap())
.args([
"--exact",
"image::teardown_tests::bitmap_drop_during_reactive_tls_teardown",
"--nocapture",
])
.env(CHILD, mode)
.output()
.unwrap();
assert!(
output.status.success(),
"{mode}: {}",
String::from_utf8_lossy(&output.stderr)
);
}
}
#[test]
fn bitmap_drop_during_core_tls_teardown() {
const CHILD: &str = "DAY_CORE_BITMAP_TEARDOWN_CHILD";
if std::env::var_os(CHILD).is_some() {
struct TeardownBitmap {
bitmap: Option<Bitmap>,
dropped: std::sync::mpsc::Sender<()>,
}
impl Drop for TeardownBitmap {
fn drop(&mut self) {
assert!(crate::try_tls_root(|_| ()).is_err());
drop(self.bitmap.take());
self.dropped.send(()).unwrap();
}
}
let (dropped, observed) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let held = TeardownBitmap {
bitmap: Some(bitmap_from((BitmapId(1), BitmapInfo::default()))),
dropped,
};
PENDING_ENCODE.with(|pending| {
pending.borrow_mut().insert(
1,
Pending {
shared: Rc::new(Shared::default()),
callback: Some(Box::new(move |_| drop(held))),
},
);
});
assert!(crate::try_tls_root(|_| ()).is_ok());
})
.join()
.unwrap();
observed
.recv_timeout(std::time::Duration::from_secs(5))
.expect("bitmap was not dropped during core TLS teardown");
return;
}
let output = std::process::Command::new(std::env::current_exe().unwrap())
.args([
"--exact",
"image::teardown_tests::bitmap_drop_during_core_tls_teardown",
"--nocapture",
])
.env(CHILD, "1")
.output()
.unwrap();
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
}
}