use std::{
cell::RefCell,
mem::{ManuallyDrop, MaybeUninit},
};
use libc::c_void;
use windows::{
core::{Interface, PCWSTR},
Foundation::Numerics::Matrix3x2,
Win32::{
Foundation::HWND,
Globalization::GetUserDefaultLocaleName,
Graphics::{
Direct2D::{
Common::{D2D1_ALPHA_MODE_PREMULTIPLIED, D2D1_PIXEL_FORMAT, D2D_POINT_2F, D2D_RECT_F, D2D_SIZE_U},
ID2D1Brush, ID2D1DrawingStateBlock, ID2D1Geometry, ID2D1HwndRenderTarget, ID2D1RenderTarget, ID2D1StrokeStyle,
D2D1_ANTIALIAS_MODE_ALIASED, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR,
D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2D1_BRUSH_PROPERTIES, D2D1_DASH_STYLE_CUSTOM, D2D1_DASH_STYLE_SOLID,
D2D1_DRAW_TEXT_OPTIONS_CLIP, D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT, D2D1_ELLIPSE, D2D1_HWND_RENDER_TARGET_PROPERTIES,
D2D1_LAYER_OPTIONS_NONE, D2D1_LAYER_PARAMETERS, D2D1_OPACITY_MASK_CONTENT_GRAPHICS, D2D1_RENDER_TARGET_PROPERTIES, D2D1_ROUNDED_RECT,
D2D1_STROKE_STYLE_PROPERTIES, D2D1_TEXT_ANTIALIAS_MODE_ALIASED, D2D1_TEXT_ANTIALIAS_MODE_DEFAULT,
},
DirectWrite::{IDWriteTextFormat, DWRITE_FONT_STRETCH_NORMAL, DWRITE_LINE_METRICS, DWRITE_TEXT_ALIGNMENT_LEADING, DWRITE_TEXT_RANGE},
Dxgi::Common::DXGI_FORMAT_B8G8R8A8_UNORM,
},
System::SystemServices::LOCALE_NAME_MAX_LENGTH,
},
};
use winit::raw_window_handle::{HasWindowHandle, RawWindowHandle};
use super::{bitmap::D2DBitmap, brush::D2DBrush, device::D2DDeviceContext, layer::D2DLayer, matrix::D2DMatrix, path::D2DPath};
pub use crate::canvas::CanvasBackend;
use crate::{
base::Dimension,
error::GraphicsError,
font::{FontStyle, FontWeight},
geometry::{FRect, FSize, ISize},
paint::{BlendMode, Cap, Color, DrawMode, Join, Paint, BLACK},
path::PathBackend,
text::TextAlignment,
window::Window,
Float,
};
const DEFAULT_DPI: f32 = 96.0;
#[derive(Clone, Debug)]
pub struct WindowContainer {
render_target: ID2D1HwndRenderTarget,
}
impl Dimension for WindowContainer {
fn size(&self) -> FSize {
unsafe { self.render_target.GetSize().into() }
}
fn pixel_size(&self) -> ISize {
let size = unsafe { self.render_target.GetPixelSize() };
ISize::new(size.width as i32, size.height as i32)
}
}
impl WindowContainer {
fn new(context: &D2DDeviceContext, window: &Window) -> Result<Self, GraphicsError> {
let handle = match window.window_handle() {
Ok(handle) => handle,
Err(err) => return Err(GraphicsError::InvalidParameter(stringify!(window).to_string(), err.to_string())),
};
if let RawWindowHandle::Win32(handle) = handle.as_raw() {
let hwnd = HWND(handle.hwnd.get() as *mut c_void);
let window_size = window.inner_size();
let pixel_size = D2D_SIZE_U {
width: window_size.width,
height: window_size.height,
};
let render_target_props = D2D1_RENDER_TARGET_PROPERTIES {
pixelFormat: D2D1_PIXEL_FORMAT {
format: DXGI_FORMAT_B8G8R8A8_UNORM,
alphaMode: D2D1_ALPHA_MODE_PREMULTIPLIED,
},
dpiX: DEFAULT_DPI * window.scale_factor() as f32,
dpiY: DEFAULT_DPI * window.scale_factor() as f32,
..Default::default()
};
let hwnd_props = D2D1_HWND_RENDER_TARGET_PROPERTIES {
hwnd,
pixelSize: pixel_size,
..Default::default()
};
let render_target = unsafe {
context
.factory
.CreateHwndRenderTarget(&render_target_props, &hwnd_props)
.map_err(|err| GraphicsError::CreationFailed(err.to_string()))?
};
return Ok(Self {
render_target,
});
}
Err(invalid_handle_error!(WindowHandle))
}
fn render_target(&self) -> &ID2D1HwndRenderTarget {
&self.render_target
}
}
#[derive(Clone, Debug)]
pub enum RenderTarget {
Layer(D2DLayer),
Window(WindowContainer),
}
impl RenderTarget {
fn as_dimension(&self) -> &dyn Dimension {
match self {
RenderTarget::Layer(layer) => layer,
RenderTarget::Window(window) => window,
}
}
}
struct DrawingState<'a> {
canvas: &'a mut D2DCanvas,
is_drawing: bool,
}
impl<'a> DrawingState<'a> {
fn new(canvas: &'a mut D2DCanvas) -> Self {
let is_drawing = canvas.try_begin_draw();
Self {
canvas,
is_drawing,
}
}
}
impl<'a> Drop for DrawingState<'a> {
fn drop(&mut self) {
if !self.is_drawing {
self.canvas.end_draw();
}
}
}
#[derive(Clone, Debug)]
pub struct D2DCanvas {
render_target: RenderTarget,
current_paint: Paint<D2DBrush>,
stroke_style: RefCell<Option<ID2D1StrokeStyle>>,
paint_stack: Vec<Paint<D2DBrush>>,
drawing_state_stack: Vec<ID2D1DrawingStateBlock>,
is_drawing: bool,
context: D2DDeviceContext,
clip_push_count: usize,
geometry_stack: Vec<ManuallyDrop<Option<ID2D1Geometry>>>,
}
impl CanvasBackend for D2DCanvas {
type DeviceContextType = D2DDeviceContext;
type BitmapType = D2DBitmap;
type BrushType = D2DBrush;
type LayerType = D2DLayer;
type MatrixType = D2DMatrix;
type PathType = D2DPath;
fn from_layer(context: Option<&Self::DeviceContextType>, layer: &Self::LayerType) -> Result<Self, GraphicsError> {
let context = context.ok_or(none_param_error!(context))?;
let mut canvas = Self {
render_target: RenderTarget::Layer(layer.clone()),
current_paint: Paint::new(),
stroke_style: RefCell::new(None),
paint_stack: Vec::new(),
drawing_state_stack: Vec::new(),
is_drawing: false,
context: context.clone(),
clip_push_count: 0,
geometry_stack: Vec::new(),
};
canvas.set_text_color(BLACK);
Ok(canvas)
}
fn from_window(context: Option<&Self::DeviceContextType>, window: &Window) -> Result<Self, GraphicsError> {
let context = context.ok_or(none_param_error!(context))?;
let window = WindowContainer::new(context, window)?;
let mut canvas = Self {
render_target: RenderTarget::Window(window),
current_paint: Paint::new(),
stroke_style: RefCell::new(None),
paint_stack: Vec::new(),
drawing_state_stack: Vec::new(),
is_drawing: false,
context: context.clone(),
clip_push_count: 0,
geometry_stack: Vec::new(),
};
canvas.set_text_color(BLACK);
Ok(canvas)
}
fn device_context(&self) -> Option<&Self::DeviceContextType> {
Some(&self.context)
}
fn size(&self) -> FSize {
self.render_target.as_dimension().size()
}
fn pixel_size(&self) -> ISize {
self.render_target.as_dimension().pixel_size()
}
fn save(&mut self) -> bool {
unsafe {
self.context
.factory
.CreateDrawingStateBlock(None, None)
.map(|drawing_state| {
self.render_target().SaveDrawingState(&drawing_state);
self.drawing_state_stack.push(drawing_state);
self.paint_stack.push(self.current_paint.clone());
true
})
.unwrap_or(false)
}
}
fn restore(&mut self) -> bool {
if let (Some(drawing_state), Some(current_paint)) = (self.drawing_state_stack.pop(), self.paint_stack.pop()) {
unsafe {
self.render_target().RestoreDrawingState(&drawing_state);
}
self.current_paint = current_paint;
true
} else {
false
}
}
fn begin_draw(&mut self) {
unsafe {
self.render_target().BeginDraw();
}
self.is_drawing = true;
}
fn end_draw(&mut self) {
while self.clip_push_count > 0 {
unsafe {
self.render_target().PopAxisAlignedClip();
}
self.clip_push_count -= 1;
}
let mut layer_push_count = self.geometry_stack.len();
while layer_push_count > 0 {
unsafe {
self.render_target().PopLayer();
}
layer_push_count -= 1;
}
for geometry in self.geometry_stack.iter_mut() {
unsafe {
ManuallyDrop::drop(geometry);
}
}
self.geometry_stack.clear();
unsafe {
self.render_target().EndDraw(None, None).ok();
}
self.is_drawing = false;
}
fn get_opacity(&self) -> Float {
self.current_paint.get_opacity()
}
fn set_opacity(&mut self, opacity: Float) {
self.current_paint.set_opacity(opacity);
self.current_paint.get_brush_mut().set_opacity(opacity);
}
fn get_blend_mode(&self) -> BlendMode {
self.current_paint.get_blend_mode()
}
fn set_blend_mode(&mut self, mode: BlendMode) {
if self.current_paint.get_blend_mode() != mode {
self.current_paint.set_blend_mode(mode);
}
}
fn get_draw_mode(&self) -> DrawMode {
self.current_paint.get_draw_mode()
}
fn set_draw_mode(&mut self, mode: DrawMode) {
if self.current_paint.get_draw_mode() != mode {
self.current_paint.set_draw_mode(mode);
}
}
fn get_line_dash(&self) -> (Option<Vec<Float>>, Float) {
let dashes = self.current_paint.get_line_dash();
let offset = self.current_paint.get_line_dash_offset();
(dashes.map(|dashes| dashes.to_vec()), offset)
}
fn set_line_dash(&mut self, dashes: &[Float], offset: Float) {
self.current_paint.set_line_dash(dashes);
self.current_paint.set_line_dash_offset(offset);
self.stroke_style.replace(None);
}
fn get_matrix(&self) -> Self::MatrixType {
let mut matrix: Matrix3x2 = Matrix3x2::identity();
unsafe {
self.render_target().GetTransform(&mut matrix);
}
D2DMatrix::from_matrix(matrix)
}
fn set_matrix(&mut self, matrix: &Self::MatrixType) {
unsafe {
self.render_target().SetTransform(&matrix.matrix);
}
}
fn concat_matrix(&mut self, matrix: &Self::MatrixType) {
let mut current_matrix: Matrix3x2 = Matrix3x2::identity();
unsafe {
self.render_target().GetTransform(&mut current_matrix);
self.render_target().SetTransform(&(matrix.matrix * current_matrix));
}
}
fn get_miter_limit(&self) -> Float {
self.current_paint.get_miter_limit()
}
fn set_miter_limit(&mut self, limit: Float) {
self.current_paint.set_miter_limit(limit);
self.stroke_style.replace(None);
}
fn get_smooth(&self) -> bool {
self.current_paint.get_smooth()
}
fn set_smooth(&mut self, smooth: bool) {
self.current_paint.set_smooth(smooth);
unsafe {
let render_target = self.render_target();
if smooth {
render_target.SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
render_target.SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_DEFAULT);
} else {
render_target.SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
render_target.SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
}
}
}
fn get_stroke_cap(&self) -> Cap {
self.current_paint.get_stroke_cap()
}
fn set_stroke_cap(&mut self, cap: Cap) {
self.current_paint.set_stroke_cap(cap);
self.stroke_style.replace(None);
}
fn get_stroke_join(&self) -> Join {
self.current_paint.get_stroke_join()
}
fn set_stroke_join(&mut self, join: Join) {
self.current_paint.set_stroke_join(join);
self.stroke_style.replace(None);
}
fn get_stroke_width(&self) -> Float {
self.current_paint.get_stroke_width()
}
fn set_stroke_width(&mut self, width: Float) {
self.current_paint.set_stroke_width(width);
}
fn set_brush(&mut self, brush: &Self::BrushType) {
self.current_paint.set_brush(brush);
}
fn get_font_family(&self) -> &str {
self.current_paint.get_font_family()
}
fn set_font_family(&mut self, font_family: &str) {
self.current_paint.set_font_family(font_family);
}
fn get_font_size(&self) -> Float {
self.current_paint.get_font_size()
}
fn set_font_size(&mut self, font_size: Float) {
self.current_paint.set_font_size(font_size);
}
fn get_font_style(&self) -> FontStyle {
self.current_paint.get_font_style()
}
fn set_font_style(&mut self, font_style: FontStyle) {
self.current_paint.set_font_style(font_style);
}
fn get_font_weight(&self) -> FontWeight {
self.current_paint.get_font_weight()
}
fn set_font_weight(&mut self, font_weight: FontWeight) {
self.current_paint.set_font_weight(font_weight);
}
fn get_text_alignment(&self) -> TextAlignment {
self.current_paint.get_text_alignment()
}
fn set_text_alignment(&mut self, text_alignment: TextAlignment) {
self.current_paint.set_text_alignment(text_alignment);
}
fn get_text_color(&self) -> Color {
self.current_paint.get_text_color()
}
fn set_text_color(&mut self, color: Color) {
if self.current_paint.get_text_color() != color {
self.current_paint.set_text_color(color);
self.current_paint.clear_text_brush();
}
if self.current_paint.get_text_brush().is_none() {
let brush_properties = D2D1_BRUSH_PROPERTIES {
opacity: self.current_paint.get_opacity(),
transform: Matrix3x2::identity(),
};
let brush = unsafe { self.render_target().CreateSolidColorBrush(&color.into(), Some(&brush_properties)).ok() };
if let Some(brush) = brush {
self.current_paint.set_text_brush(brush);
}
}
}
fn rotate(&mut self, angle: Float) {
let mut matrix: Matrix3x2 = Matrix3x2::identity();
unsafe {
self.render_target().GetTransform(&mut matrix);
matrix = D2DMatrix::rotate_matrix(angle) * matrix;
self.render_target().SetTransform(&matrix);
}
}
fn scale(&mut self, x: Float, y: Float) {
let mut matrix: Matrix3x2 = Matrix3x2::identity();
unsafe {
self.render_target().GetTransform(&mut matrix);
matrix = D2DMatrix::scale_matrix(x, y) * matrix;
self.render_target().SetTransform(&matrix);
}
}
fn translate(&mut self, x: Float, y: Float) {
let mut matrix: Matrix3x2 = Matrix3x2::identity();
unsafe {
self.render_target().GetTransform(&mut matrix);
matrix = Matrix3x2::translation(x, y) * matrix;
self.render_target().SetTransform(&matrix);
}
}
fn clear(&mut self, color: Color) {
let canvas = &DrawingState::new(self).canvas;
unsafe {
canvas.render_target().Clear(Some(&color.into()));
}
}
fn clip_to_rect(&mut self, rect: FRect) {
if !self.is_drawing {
return;
}
let rect = D2D_RECT_F {
left: rect.point.x,
top: rect.point.y,
right: rect.point.x + rect.size.width,
bottom: rect.point.y + rect.size.height,
};
unsafe {
self.render_target().PushAxisAlignedClip(&rect, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
}
self.clip_push_count += 1;
}
fn clip_to_path(&mut self, path: &Self::PathType) {
if !self.is_drawing {
return;
}
let geometry = path.path().cast::<ID2D1Geometry>();
let geometry = match geometry {
Ok(geometry) => ManuallyDrop::new(Some(geometry)),
Err(_) => return,
};
let layer_parameters = D2D1_LAYER_PARAMETERS {
contentBounds: path.bounds().into(),
geometricMask: geometry,
maskAntialiasMode: D2D1_ANTIALIAS_MODE_PER_PRIMITIVE,
maskTransform: Matrix3x2::identity(),
opacity: 1.0,
opacityBrush: ManuallyDrop::new(None),
layerOptions: D2D1_LAYER_OPTIONS_NONE,
};
unsafe {
self.render_target().PushLayer(&layer_parameters, None);
}
self.geometry_stack.push(layer_parameters.geometricMask);
}
fn draw_line(&mut self, x1: Float, y1: Float, x2: Float, y2: Float) -> bool {
let brush = match self.brush() {
Some(brush) => brush,
None => return false,
};
let stroke_width = self.current_paint.get_stroke_width();
let stroke_style = self.stroke_style();
let canvas = &DrawingState::new(self).canvas;
unsafe {
canvas.render_target().DrawLine(
D2D_POINT_2F {
x: x1,
y: y1,
},
D2D_POINT_2F {
x: x2,
y: y2,
},
&brush,
stroke_width,
stroke_style.as_ref(),
)
};
true
}
fn draw_rect(&mut self, x: Float, y: Float, width: Float, height: Float) -> bool {
let rect = D2D_RECT_F {
left: x,
top: y,
right: x + width,
bottom: y + height,
};
let brush = match self.brush() {
Some(brush) => brush,
None => return false,
};
let canvas = &DrawingState::new(self).canvas;
let current_paint = &canvas.current_paint;
let render_target = canvas.render_target();
match current_paint.get_draw_mode() {
DrawMode::Fill => unsafe {
render_target.FillRectangle(&rect, &brush);
},
mode @ DrawMode::Stroke | mode @ DrawMode::FillStroke => {
let stroke_width = current_paint.get_stroke_width();
let stroke_style = canvas.stroke_style();
if let DrawMode::FillStroke = mode {
unsafe { render_target.FillRectangle(&rect, &brush) };
}
unsafe { render_target.DrawRectangle(&rect, &brush, stroke_width, stroke_style.as_ref()) };
}
}
true
}
fn draw_rounded_rect(&mut self, x: Float, y: Float, width: Float, height: Float, radius: Float) -> bool {
let rect = D2D1_ROUNDED_RECT {
rect: D2D_RECT_F {
left: x,
top: y,
right: x + width,
bottom: y + height,
},
radiusX: radius,
radiusY: radius,
};
let brush = match self.brush() {
Some(brush) => brush,
None => return false,
};
let canvas = &DrawingState::new(self).canvas;
let current_paint = &canvas.current_paint;
let render_target = canvas.render_target();
match current_paint.get_draw_mode() {
DrawMode::Fill => unsafe {
render_target.FillRoundedRectangle(&rect, &brush);
},
mode @ DrawMode::Stroke | mode @ DrawMode::FillStroke => {
let stroke_width = current_paint.get_stroke_width();
let stroke_style = canvas.stroke_style();
if let DrawMode::FillStroke = mode {
unsafe { render_target.FillRoundedRectangle(&rect, &brush) };
}
unsafe { render_target.DrawRoundedRectangle(&rect, &brush, stroke_width, stroke_style.as_ref()) };
}
}
true
}
fn draw_circle(&mut self, x: Float, y: Float, radius: Float) -> bool {
let ellipse = D2D1_ELLIPSE {
point: D2D_POINT_2F {
x,
y,
},
radiusX: radius,
radiusY: radius,
};
let brush = match self.brush() {
Some(brush) => brush,
None => return false,
};
let canvas = &DrawingState::new(self).canvas;
let current_paint = &canvas.current_paint;
let render_target = canvas.render_target();
match current_paint.get_draw_mode() {
DrawMode::Fill => unsafe { render_target.FillEllipse(&ellipse, &brush) },
mode @ DrawMode::Stroke | mode @ DrawMode::FillStroke => {
let stroke_width = current_paint.get_stroke_width();
let stroke_style = canvas.stroke_style();
if let DrawMode::FillStroke = mode {
unsafe { render_target.FillEllipse(&ellipse, &brush) };
}
unsafe { render_target.DrawEllipse(&ellipse, &brush, stroke_width, stroke_style.as_ref()) };
}
}
true
}
fn draw_ellipse(&mut self, x: Float, y: Float, width: Float, height: Float) -> bool {
let ellipse = D2D1_ELLIPSE {
point: D2D_POINT_2F {
x: x + width / 2.0,
y: y + height / 2.0,
},
radiusX: width / 2.0,
radiusY: height / 2.0,
};
let brush = match self.brush() {
Some(brush) => brush,
None => return false,
};
let canvas = &DrawingState::new(self).canvas;
let current_paint = &canvas.current_paint;
let render_target = canvas.render_target();
match current_paint.get_draw_mode() {
DrawMode::Fill => unsafe { render_target.FillEllipse(&ellipse, &brush) },
mode @ DrawMode::Stroke | mode @ DrawMode::FillStroke => {
let stroke_width = current_paint.get_stroke_width();
let stroke_style = canvas.stroke_style();
if let DrawMode::FillStroke = mode {
unsafe { render_target.FillEllipse(&ellipse, &brush) };
}
unsafe { render_target.DrawEllipse(&ellipse, &brush, stroke_width, stroke_style.as_ref()) };
}
}
true
}
fn draw_path(&mut self, path: &Self::PathType) -> bool {
if path.is_empty() {
return false;
}
let brush = match self.brush() {
Some(brush) => brush,
None => return false,
};
let canvas = &DrawingState::new(self).canvas;
let current_paint = &canvas.current_paint;
let render_target = canvas.render_target();
match current_paint.get_draw_mode() {
DrawMode::Fill => unsafe { render_target.FillGeometry(path.path(), &brush, None) },
mode @ DrawMode::Stroke | mode @ DrawMode::FillStroke => {
let stroke_width = current_paint.get_stroke_width();
let stroke_style = canvas.stroke_style();
if let DrawMode::FillStroke = mode {
unsafe { render_target.FillGeometry(path.path(), &brush, None) };
}
unsafe { render_target.DrawGeometry(path.path(), &brush, stroke_width, stroke_style.as_ref()) };
}
}
true
}
fn draw_bitmap(&mut self, bitmap: &Self::BitmapType, source_rect: Option<FRect>, dest_rect: FRect) -> bool {
let source_rect = source_rect.map(|rect| rect.into());
let dest_rect = dest_rect.into();
let opacity = self.current_paint.get_opacity();
let interpolation_mode = if self.current_paint.get_smooth() {
D2D1_BITMAP_INTERPOLATION_MODE_LINEAR
} else {
D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR
};
let canvas = &DrawingState::new(self).canvas;
unsafe {
canvas.render_target().DrawBitmap(
Some(bitmap.bitmap()),
Some(&dest_rect),
opacity,
interpolation_mode,
source_rect.as_ref().map(|rect| rect as *const _),
)
};
true
}
fn draw_layer(&mut self, layer: &Self::LayerType, dest_rect: FRect) -> bool {
let dest_rect = dest_rect.into();
let opacity = self.current_paint.get_opacity();
let interpolation_mode = if self.current_paint.get_smooth() {
D2D1_BITMAP_INTERPOLATION_MODE_LINEAR
} else {
D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR
};
let canvas = &DrawingState::new(self).canvas;
unsafe { canvas.render_target().DrawBitmap(layer.bitmap().as_ref(), Some(&dest_rect), opacity, interpolation_mode, None) };
true
}
fn draw_text(&mut self, text: &str, rect: FRect) -> bool {
let brush = match self.current_paint.get_text_brush() {
Some(brush) => brush.clone(),
None => return false,
};
let text = text.encode_utf16().collect::<Vec<u16>>();
let text_format = self.text_format();
if let Some(text_format) = text_format {
let text_format = text_format.clone();
let text_layout = unsafe { self.context.dwrite_factory.CreateTextLayout(&text, &text_format, rect.size.width, rect.size.height) };
if let Ok(text_layout) = text_layout {
let font_style = self.current_paint.get_font_style();
let text_range = DWRITE_TEXT_RANGE {
startPosition: 0,
length: text.len() as u32,
};
unsafe {
if font_style.contains(FontStyle::Underline) {
text_layout.SetUnderline(true, text_range).ok();
}
if font_style.contains(FontStyle::Strikethrough) {
text_layout.SetStrikethrough(true, text_range).ok();
}
let canvas = &DrawingState::new(self).canvas;
canvas.render_target().DrawTextLayout(
rect.point.into(),
&text_layout,
&brush,
D2D1_DRAW_TEXT_OPTIONS_CLIP | D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT,
);
}
}
}
true
}
fn draw_text_at(&mut self, text: &str, x: Float, y: Float) -> bool {
let brush = match self.current_paint.get_text_brush() {
Some(brush) => brush.clone(),
None => return false,
};
let text = text.encode_utf16().collect::<Vec<u16>>();
let text_format = match self.text_format() {
Some(text_format) => text_format.clone(),
None => return false,
};
unsafe {
text_format.SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING).ok();
}
let text_layout = unsafe { self.context.dwrite_factory.CreateTextLayout(&text, &text_format, 0xFFFF as f32, 0xFFFF as f32) };
if let Ok(text_layout) = text_layout {
let font_style = self.current_paint.get_font_style();
let text_range = DWRITE_TEXT_RANGE {
startPosition: 0,
length: text.len() as u32,
};
let mut line_metrics = MaybeUninit::<DWRITE_LINE_METRICS>::uninit();
unsafe {
let mut actual_count: u32 = 0;
let _ = text_layout.GetLineMetrics(Some(std::slice::from_raw_parts_mut(line_metrics.as_mut_ptr(), 1)), &mut actual_count);
let baseline: Float = if actual_count > 0 {
line_metrics.assume_init().baseline
} else {
0.0
};
if font_style.contains(FontStyle::Underline) {
text_layout.SetUnderline(true, text_range).ok();
}
if font_style.contains(FontStyle::Strikethrough) {
text_layout.SetStrikethrough(true, text_range).ok();
}
let canvas = &DrawingState::new(self).canvas;
canvas.render_target().DrawTextLayout(
D2D_POINT_2F {
x,
y: y - baseline,
},
&text_layout,
&brush,
D2D1_DRAW_TEXT_OPTIONS_CLIP | D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT,
);
}
}
true
}
fn fill_rect(&mut self, x: Float, y: Float, width: Float, height: Float, mask: Option<&Self::BitmapType>) -> bool {
let brush = match self.brush() {
Some(brush) => brush,
None => return false,
};
let rect = D2D_RECT_F {
left: x,
top: y,
right: x + width,
bottom: y + height,
};
let canvas = &DrawingState::new(self).canvas;
let render_target = canvas.render_target();
unsafe {
if let Some(mask) = mask {
let mode = render_target.GetAntialiasMode();
render_target.SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
render_target.FillOpacityMask(mask.bitmap(), &brush, D2D1_OPACITY_MASK_CONTENT_GRAPHICS, Some(&rect), None);
render_target.SetAntialiasMode(mode);
} else {
render_target.FillRectangle(&rect, &brush);
}
}
true
}
}
impl D2DCanvas {
pub(super) fn render_target(&self) -> &ID2D1RenderTarget {
match &self.render_target {
RenderTarget::Layer(layer) => layer.render_target(),
RenderTarget::Window(window) => window.render_target(),
}
}
fn try_begin_draw(&mut self) -> bool {
let is_drawing = self.is_drawing;
if !is_drawing {
self.begin_draw();
}
is_drawing
}
fn stroke_style(&self) -> Option<ID2D1StrokeStyle> {
if self.stroke_style.borrow().is_none() {
let cap = self.current_paint.get_stroke_cap().into();
let join = self.current_paint.get_stroke_join().into();
let dashes = self.current_paint.get_line_dash();
let (dash_style, offset) = match dashes {
Some(_) => (D2D1_DASH_STYLE_CUSTOM, self.current_paint.get_line_dash_offset()),
None => (D2D1_DASH_STYLE_SOLID, 0.0),
};
let properties = D2D1_STROKE_STYLE_PROPERTIES {
startCap: cap,
endCap: cap,
dashCap: cap,
lineJoin: join,
miterLimit: self.current_paint.get_miter_limit(),
dashStyle: dash_style,
dashOffset: offset,
};
self.stroke_style.replace(unsafe { self.context.factory.CreateStrokeStyle(&properties, dashes).ok() });
}
self.stroke_style.borrow().clone()
}
fn brush(&mut self) -> Option<ID2D1Brush> {
let render_target = &self.render_target().clone();
let opacity = self.current_paint.get_opacity();
self.current_paint.get_brush_mut().d2d_brush(render_target, opacity)
}
fn text_format(&mut self) -> Option<&IDWriteTextFormat> {
if self.current_paint.get_text_format().is_none() {
let font_family = self.current_paint.get_font_family().encode_utf16().chain(Some(0)).collect::<Vec<u16>>();
let mut local_name = [0u16; LOCALE_NAME_MAX_LENGTH as usize];
unsafe {
GetUserDefaultLocaleName(&mut local_name);
}
let text_format = unsafe {
self.context.dwrite_factory.CreateTextFormat(
PCWSTR::from_raw(font_family.as_ptr()),
None,
self.current_paint.get_font_weight().into(),
self.current_paint.get_font_style().into(),
DWRITE_FONT_STRETCH_NORMAL,
self.current_paint.get_font_size(),
PCWSTR::from_raw(local_name.as_ptr()),
)
};
if let Ok(text_format) = text_format {
unsafe {
text_format.SetTextAlignment(self.current_paint.get_text_alignment().into()).ok();
}
self.current_paint.set_text_format(text_format);
}
}
self.current_paint.get_text_format()
}
}