use std::ptr::null;
use core_foundation::{
attributed_string::CFMutableAttributedString,
base::{CFRange, TCFType},
number::CFNumber,
string::CFString,
};
#[cfg(target_os = "ios")]
use core_graphics::context::CGContextRef;
use core_graphics::{
affine_transform::CGAffineTransform,
color::CGColor,
context::{CGContext, CGInterpolationQuality},
geometry::CGRect,
gradient::CGGradientDrawingOptions,
path::CGPath,
};
use core_text::{
frame::CTFrame,
framesetter::CTFramesetter,
line::CTLine,
string_attributes::{kCTFontAttributeName, kCTForegroundColorAttributeName, kCTParagraphStyleAttributeName, kCTUnderlineStyleAttributeName},
};
#[cfg(target_os = "macos")]
use objc2::msg_send;
use objc2::rc::Id;
#[cfg(target_os = "macos")]
use objc2_app_kit::{NSGraphicsContext, NSView, NSWindow};
use objc2_foundation;
#[cfg(target_os = "ios")]
use objc2_ui_kit::{UIView, UIWindow};
#[cfg(target_os = "macos")]
use os_ver::{if_greater_than, macos};
use winit::{
raw_window_handle::{HasWindowHandle, RawWindowHandle},
window::Window,
};
use super::{
bitmap::CoreGraphicsBitmap,
brush::{BrushData, CoreGraphicsBrush},
device::CoreGraphicsDeviceContext,
layer::CoreGraphicsLayer,
matrix::CoreGraphicsMatrix,
path::CoreGraphicsPath,
text::*,
};
use crate::{
base::Dimension,
canvas::CanvasBackend,
error::GraphicsError,
font::{FontStyle, FontWeight},
geometry::{FRect, FSize, ISize},
gradient::GradientData,
paint::*,
path::{FillType, PathBackend},
text::TextAlignment,
Float,
};
#[cfg(target_os = "ios")]
extern "C" {
fn UIGraphicsGetCurrentContext() -> CGContextRef;
}
#[cfg(target_os = "ios")]
fn ui_graphics_current_context() -> Option<CGContext> {
unsafe {
let context = UIGraphicsGetCurrentContext();
if context.is_null() {
None
} else {
Some(TCFType::wrap_under_get_rule(context))
}
}
}
#[derive(Clone, Debug)]
pub struct WindowContainer {
#[cfg(target_os = "macos")]
window: Id<NSWindow>,
#[cfg(target_os = "ios")]
window: Id<UIWindow>,
context: CGContext,
}
impl Dimension for WindowContainer {
fn size(&self) -> FSize {
let size = self.frame_size();
FSize::new(size.width, size.height)
}
fn pixel_size(&self) -> ISize {
let size = self.frame_size();
let scale_factor = self.scale_factor();
ISize::new((size.width * scale_factor) as i32, (size.height * scale_factor) as i32)
}
}
impl WindowContainer {
fn new(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())),
};
#[cfg(target_os = "macos")]
if let RawWindowHandle::AppKit(handle) = handle.as_raw() {
let view = handle.ns_view.as_ptr();
let view: Id<NSView> = unsafe { Id::retain(view.cast()) }.ok_or(invalid_handle_error!(NSView))?;
let window = view.window().ok_or(invalid_handle_error!(NSWindow))?;
let context = unsafe { NSGraphicsContext::currentContext() }.ok_or(invalid_handle_error!(NSGraphicsContext))?;
let context = unsafe { CGContext::wrap_under_get_rule(msg_send![&context, CGContext]) };
return Ok(Self {
window,
context,
});
}
#[cfg(target_os = "ios")]
if let RawWindowHandle::UiKit(handle) = handle.as_raw() {
let view = handle.ui_view.as_ptr();
let view: Id<UIView> = unsafe { Id::retain(view.cast()) }.ok_or(invalid_handle_error!(UIView))?;
let window = view.window().ok_or(invalid_handle_error!(UIWindow))?;
let context = ui_graphics_current_context().ok_or(invalid_handle_error!(CGContext))?;
return Ok(Self {
window,
context,
});
}
Err(invalid_handle_error!(WindowHandle))
}
fn context(&self) -> &CGContext {
&self.context
}
fn context_mut(&mut self) -> &mut CGContext {
&mut self.context
}
#[cfg(target_os = "macos")]
fn frame_size(&self) -> objc2_foundation::CGSize {
if_greater_than! {macos::YOSEMITE => {
unsafe { self.window.contentLayoutRect().size }
} else {
self.window.contentRectForFrameRect(self.window.frame()).size
}}
}
#[cfg(target_os = "ios")]
fn frame_size(&self) -> objc2_foundation::CGSize {
self.window.frame().size
}
#[cfg(target_os = "macos")]
fn scale_factor(&self) -> Float {
self.window.backingScaleFactor()
}
#[cfg(target_os = "ios")]
fn scale_factor(&self) -> Float {
self.window.screen().scale()
}
}
#[derive(Clone, Debug)]
pub enum RenderTarget {
Bitmap(CoreGraphicsBitmap),
Layer(CoreGraphicsLayer),
Window(WindowContainer),
}
impl RenderTarget {
fn as_dimension(&self) -> &dyn Dimension {
match self {
RenderTarget::Bitmap(bitmap) => bitmap,
RenderTarget::Layer(layer) => layer,
RenderTarget::Window(window) => window,
}
}
}
#[derive(Clone, Debug)]
pub struct CoreGraphicsCanvas {
render_target: RenderTarget,
current_paint: Paint<CoreGraphicsBrush>,
paint_stack: Vec<Paint<CoreGraphicsBrush>>,
}
impl CanvasBackend for CoreGraphicsCanvas {
type DeviceContextType = CoreGraphicsDeviceContext;
type BitmapType = CoreGraphicsBitmap;
type BrushType = CoreGraphicsBrush;
type LayerType = CoreGraphicsLayer;
type MatrixType = CoreGraphicsMatrix;
type PathType = CoreGraphicsPath;
fn from_layer(_context: Option<&Self::DeviceContextType>, layer: &Self::LayerType) -> Result<Self, GraphicsError> {
let paint = Paint::new();
let paint_stack = Vec::new();
Ok(Self {
render_target: RenderTarget::Layer(layer.clone()),
current_paint: paint,
paint_stack,
})
}
fn from_window(_context: Option<&Self::DeviceContextType>, window: &Window) -> Result<Self, GraphicsError> {
let paint = Paint::new();
let paint_stack = Vec::new();
let window = WindowContainer::new(window)?;
Ok(Self {
render_target: RenderTarget::Window(window),
current_paint: paint,
paint_stack,
})
}
fn device_context(&self) -> Option<&Self::DeviceContextType> {
None
}
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 {
self.paint_stack.push(self.current_paint.clone());
self.context().save_state();
true
}
fn restore(&mut self) -> bool {
self.paint_stack
.pop()
.map(|paint| {
self.current_paint = paint;
self.context().restore_state();
})
.is_some()
}
fn begin_draw(&mut self) {
}
fn end_draw(&mut self) {
}
fn get_opacity(&self) -> Float {
self.current_paint.get_opacity()
}
fn set_opacity(&mut self, opacity: Float) {
if self.current_paint.get_opacity() != opacity {
self.current_paint.set_opacity(opacity);
self.context().set_alpha(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);
self.context().set_blend_mode(mode.into());
}
}
fn get_draw_mode(&self) -> DrawMode {
self.current_paint.get_draw_mode()
}
fn set_draw_mode(&mut self, mode: DrawMode) {
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.context().set_line_dash(offset, dashes);
}
fn get_matrix(&self) -> Self::MatrixType {
CoreGraphicsMatrix::from_transform(self.context().get_ctm())
}
fn set_matrix(&mut self, matrix: &Self::MatrixType) {
let matrix = self.context().get_ctm().invert().concat(&matrix.transform());
self.context().concat_ctm(matrix);
}
fn concat_matrix(&mut self, matrix: &Self::MatrixType) {
self.context().concat_ctm(matrix.transform());
}
fn get_miter_limit(&self) -> Float {
self.current_paint.get_miter_limit()
}
fn set_miter_limit(&mut self, miter_limit: Float) {
if self.current_paint.get_miter_limit() != miter_limit {
self.current_paint.set_miter_limit(miter_limit);
self.context().set_miter_limit(miter_limit);
}
}
fn get_smooth(&self) -> bool {
self.current_paint.get_smooth()
}
fn set_smooth(&mut self, smooth: bool) {
self.current_paint.set_smooth(smooth);
self.context().set_allows_antialiasing(smooth);
self.context().set_allows_font_smoothing(smooth);
self.context().set_should_antialias(smooth);
self.context().set_should_smooth_fonts(smooth);
self.context().set_interpolation_quality(if smooth {
CGInterpolationQuality::Default
} else {
CGInterpolationQuality::None
});
}
fn get_stroke_cap(&self) -> Cap {
self.current_paint.get_stroke_cap()
}
fn set_stroke_cap(&mut self, cap: Cap) {
if self.current_paint.get_stroke_cap() != cap {
self.current_paint.set_stroke_cap(cap);
self.context().set_line_cap(cap.into());
}
}
fn get_stroke_join(&self) -> Join {
self.current_paint.get_stroke_join()
}
fn set_stroke_join(&mut self, join: Join) {
if self.current_paint.get_stroke_join() != join {
self.current_paint.set_stroke_join(join);
self.context().set_line_join(join.into());
}
}
fn get_stroke_width(&self) -> Float {
self.current_paint.get_stroke_width()
}
fn set_stroke_width(&mut self, width: Float) {
if self.current_paint.get_stroke_width() != width {
self.current_paint.set_stroke_width(width);
self.context().set_line_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) {
self.current_paint.set_text_color(color);
}
fn rotate(&mut self, angle: Float) {
self.context().rotate(angle);
}
fn scale(&mut self, sx: Float, sy: Float) {
self.context().scale(sx, sy);
}
fn translate(&mut self, dx: Float, dy: Float) {
self.context().translate(dx, dy);
}
fn clear(&mut self, color: Color) {
let ctx = self.context();
let (r, g, b, a) = color.get_float_value();
ctx.save_state();
ctx.set_rgb_fill_color(r, g, b, a);
ctx.fill_rect(self.bounds().into());
ctx.restore_state();
}
fn clip_to_rect(&mut self, rect: FRect) {
self.context().clip_to_rect(rect.into());
}
fn clip_to_path(&mut self, path: &Self::PathType) {
let ctx = self.context();
let current_path = ctx.copy_path();
ctx.begin_path();
ctx.add_path(&path.path());
if path.get_fill_type() == FillType::EvenOdd {
ctx.eo_clip();
} else {
ctx.clip();
}
ctx.begin_path();
if let Some(current_path) = current_path {
ctx.add_path(¤t_path);
}
}
fn draw_path(&mut self, path: &Self::PathType) -> bool {
if path.is_empty() {
return false;
};
let ctx = self.context_mut().clone();
ctx.save_state();
ctx.begin_path();
ctx.add_path(&path.path());
match self.current_paint.get_brush().data() {
BrushData::Solid(color) => {
let (r, g, b, a) = color.get_float_value();
match self.current_paint.get_draw_mode() {
DrawMode::Fill => {
ctx.set_rgb_fill_color(r, g, b, a);
}
DrawMode::Stroke => {
ctx.set_rgb_stroke_color(r, g, b, a);
}
DrawMode::FillStroke => {
ctx.set_rgb_fill_color(r, g, b, a);
ctx.set_rgb_stroke_color(r, g, b, a);
}
}
ctx.draw_path(self.current_paint.draw_mode(path.get_fill_type()));
}
BrushData::Gradient(_) => match self.current_paint.get_draw_mode() {
DrawMode::Fill => {
self.draw_clip_gradient();
}
DrawMode::Stroke => {
ctx.replace_path_with_stroked_path();
self.draw_clip_gradient();
}
DrawMode::FillStroke => {
self.draw_clip_gradient();
ctx.begin_path();
ctx.add_path(&path.path());
ctx.replace_path_with_stroked_path();
self.draw_clip_gradient();
}
},
}
ctx.restore_state();
true
}
fn draw_bitmap(&mut self, bitmap: &Self::BitmapType, source_rect: Option<FRect>, dest_rect: FRect) -> bool {
let ctx = self.context_mut();
ctx.save_state();
ctx.translate(dest_rect.point.x, dest_rect.point.y + dest_rect.size.height);
ctx.scale(1.0, -1.0);
ctx.translate(-dest_rect.point.x, -dest_rect.point.y);
if let Some(source_rect) = source_rect {
let bitmap_size = bitmap.size();
let bitmap_rect = FRect::new(0.0, 0.0, bitmap_size.width, bitmap_size.height);
if bitmap_rect != source_rect {
let img = bitmap.image().cropped(source_rect.into());
if let Some(img) = img {
ctx.draw_image(dest_rect.into(), &img);
return true;
} else {
return false;
}
}
}
ctx.draw_image(dest_rect.into(), bitmap.image());
ctx.restore_state();
true
}
fn draw_layer(&mut self, layer: &Self::LayerType, rect: FRect) -> bool {
let ctx = self.context_mut();
ctx.save_state();
ctx.translate(rect.point.x, rect.point.y + rect.size.height);
ctx.scale(1.0, -1.0);
ctx.translate(-rect.point.x, -rect.point.y);
layer.layer().draw_in_rect(ctx, rect.into());
ctx.restore_state();
true
}
fn draw_text(&mut self, text: &str, rect: FRect) -> bool {
let (mut attributed_string, range) = self.new_attributed_string(text);
let alignment = self.current_paint.get_text_alignment().into();
let paragraph_style_settings = CTParagraphStyleSetting::from_alignment(alignment);
let paragraph_style = CTParagraphStyle::new(&[paragraph_style_settings]);
attributed_string.set_attribute(range, unsafe { kCTParagraphStyleAttributeName }, ¶graph_style);
let framesetter = CTFramesetter::new_with_attributed_string(attributed_string.as_concrete_TypeRef());
let path = CGPath::from_rect(rect.into(), None);
let frame = unsafe {
CTFrame::wrap_under_create_rule(CTFramesetterCreateFrame(framesetter.as_concrete_TypeRef(), range, path.as_concrete_TypeRef(), null()))
};
let ctx = self.context();
ctx.save_state();
ctx.translate(rect.point.x, rect.point.y + rect.size.height);
ctx.scale(1.0, -1.0);
ctx.translate(-rect.point.x, -rect.point.y);
unsafe {
CTFrameDraw(frame.as_concrete_TypeRef(), ctx.as_concrete_TypeRef());
}
ctx.restore_state();
true
}
fn draw_text_at(&mut self, text: &str, x: Float, y: Float) -> bool {
let (attributed_string, _) = self.new_attributed_string(text);
let line = CTLine::new_with_attributed_string(attributed_string.as_concrete_TypeRef());
let ctx = self.context();
ctx.save_state();
ctx.set_text_matrix(&CGAffineTransform::new(1.0, 0.0, 0.0, -1.0, x, y));
unsafe {
CTLineDraw(line.as_concrete_TypeRef(), ctx.as_concrete_TypeRef());
}
ctx.restore_state();
true
}
fn fill_rect(&mut self, x: Float, y: Float, width: Float, height: Float, mask: Option<&Self::BitmapType>) -> bool {
let ctx = self.context().clone();
let rect = CGRect::new(x, y, width, height);
ctx.save_state();
ctx.translate(x, y + height);
ctx.scale(1.0, -1.0);
ctx.translate(-x, -y);
if let Some(mask) = mask {
ctx.clip_to_mask(rect, mask.image());
}
match self.current_paint.get_brush().data() {
BrushData::Solid(color) => {
let (r, g, b, a) = color.get_float_value();
ctx.set_rgb_fill_color(r, g, b, a);
ctx.fill_rect(rect);
}
BrushData::Gradient(_) => self.draw_gradient(),
}
ctx.fill_rect(rect);
ctx.restore_state();
true
}
}
impl CoreGraphicsCanvas {
pub(super) fn context(&self) -> &CGContext {
match &self.render_target {
RenderTarget::Bitmap(bitmap) => bitmap.context(),
RenderTarget::Layer(layer) => layer.context(),
RenderTarget::Window(window) => window.context(),
}
}
pub(super) fn context_mut(&mut self) -> &mut CGContext {
match &mut self.render_target {
RenderTarget::Bitmap(bitmap) => bitmap.context_mut(),
RenderTarget::Layer(layer) => layer.context_mut(),
RenderTarget::Window(window) => window.context_mut(),
}
}
fn draw_gradient(&mut self) {
if let BrushData::Gradient(gradient) = self.current_paint.get_brush_mut().data_mut() {
if let Some(cg_gradient) = gradient.cg_gradient() {
let options = CGGradientDrawingOptions::BeforeStartLocation | CGGradientDrawingOptions::AfterEndLocation;
match gradient.data() {
GradientData::Linear(linear) => {
self.context().draw_linear_gradient(&cg_gradient, linear.0.into(), linear.1.into(), options);
}
GradientData::Radial(radial) => {
self.context().draw_radial_gradient(&cg_gradient, radial.0.into(), 0.0, radial.1.into(), radial.2, options);
}
}
}
}
}
fn draw_clip_gradient(&mut self) {
let ctx = self.context_mut().clone();
ctx.save_state();
ctx.clip();
self.draw_gradient();
ctx.restore_state();
}
fn new_attributed_string(&mut self, text: &str) -> (CFMutableAttributedString, CFRange) {
let font = self.current_paint.font();
let mut attributed_string = CFMutableAttributedString::new();
attributed_string.replace_str(&CFString::new(text), CFRange::init(0, 0));
let range = CFRange::init(0, attributed_string.char_len());
attributed_string.set_attribute(range, unsafe { kCTFontAttributeName }, font.font());
let (r, g, b, a) = self.current_paint.get_text_color().get_float_value();
let cg_color = CGColor::new_generic_rgb(r, g, b, a);
attributed_string.set_attribute(range, unsafe { kCTForegroundColorAttributeName }, &cg_color);
let font_style = self.current_paint.get_font_style();
let underline_style_value = CFNumber::from(if font_style.contains(FontStyle::Underline) {
CTUnderlineStyle::Single.bits()
} else {
CTUnderlineStyle::None.bits()
});
attributed_string.set_attribute(range, unsafe { kCTUnderlineStyleAttributeName }, &underline_style_value);
(attributed_string, range)
}
}