use std::collections::HashMap;
use std::ffi::c_void;
use tiny_skia::{Pixmap, PremultipliedColorU8};
use windows::core::{implement, BOOL, Interface, IUnknown, Ref, Result, PCWSTR};
use windows::Win32::Foundation::{COLORREF, DWRITE_E_NOCOLOR, FALSE};
use windows::Win32::Graphics::Gdi::{GetCurrentObject, GetObjectW, DIBSECTION, OBJ_BITMAP};
use windows::Win32::Graphics::DirectWrite::{
DWriteCreateFactory, IDWriteBitmapRenderTarget, IDWriteFactory, IDWriteFactory2,
IDWriteGdiInterop, IDWriteInlineObject, IDWritePixelSnapping_Impl, IDWriteRenderingParams,
IDWriteTextFormat, IDWriteTextLayout, IDWriteTextRenderer, IDWriteTextRenderer_Impl,
DWRITE_COLOR_F, DWRITE_FACTORY_TYPE_SHARED, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_WEIGHT_NORMAL, DWRITE_GLYPH_RUN, DWRITE_GLYPH_RUN_DESCRIPTION, DWRITE_MATRIX,
DWRITE_MEASURING_MODE, DWRITE_STRIKETHROUGH, DWRITE_TEXT_METRICS, DWRITE_UNDERLINE,
};
use super::TextEngine;
use crate::geometry::{Color, Rect, Size};
use crate::spec::Align;
fn wide_nul(s: &str) -> Vec<u16> {
s.encode_utf16().chain(std::iter::once(0)).collect()
}
fn wide(s: &str) -> Vec<u16> {
s.encode_utf16().collect()
}
const DEFAULT_FAMILY: &str = "Microsoft YaHei UI";
pub struct DWriteEngine {
factory: IDWriteFactory,
gdi_interop: IDWriteGdiInterop,
renderer: IDWriteTextRenderer,
formats: HashMap<(String, u32), IDWriteTextFormat>,
scale: f32,
bitmap_target: Option<IDWriteBitmapRenderTarget>,
bitmap_w: i32,
bitmap_h: i32,
}
impl DWriteEngine {
pub fn new() -> Self {
unsafe {
let factory: IDWriteFactory =
DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED).expect("DWriteCreateFactory 失败");
let gdi_interop = factory.GetGdiInterop().expect("GetGdiInterop 失败");
let params = factory.CreateRenderingParams().expect("CreateRenderingParams 失败");
let factory2: Option<IDWriteFactory2> = factory.cast().ok();
let renderer: IDWriteTextRenderer =
GlyphRenderer { params: params.clone(), factory2 }.into();
Self {
factory,
gdi_interop,
renderer,
formats: HashMap::new(),
scale: 1.0,
bitmap_target: None,
bitmap_w: 0,
bitmap_h: 0,
}
}
}
fn format(&mut self, family: Option<&str>, size: f32) -> Option<IDWriteTextFormat> {
let fam = family.unwrap_or(DEFAULT_FAMILY).to_string();
let key = (fam.clone(), size.to_bits());
if let Some(f) = self.formats.get(&key) {
return Some(f.clone());
}
let fam_w = wide_nul(&fam);
let locale = wide_nul("zh-cn");
let format = unsafe {
self.factory
.CreateTextFormat(
PCWSTR(fam_w.as_ptr()),
None,
DWRITE_FONT_WEIGHT_NORMAL,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
size,
PCWSTR(locale.as_ptr()),
)
.ok()?
};
self.formats.insert(key, format.clone());
Some(format)
}
fn layout(
&mut self,
text: &str,
family: Option<&str>,
size: f32,
max_w: f32,
) -> Option<IDWriteTextLayout> {
let format = self.format(family, size)?;
let text_w = wide(text);
unsafe { self.factory.CreateTextLayout(&text_w, &format, max_w, f32::MAX).ok() }
}
fn ensure_bitmap(&mut self, w: i32, h: i32) -> Option<IDWriteBitmapRenderTarget> {
if self.bitmap_target.is_none() || w > self.bitmap_w || h > self.bitmap_h {
let nw = w.max(self.bitmap_w).max(1);
let nh = h.max(self.bitmap_h).max(1);
let brt =
unsafe { self.gdi_interop.CreateBitmapRenderTarget(None, nw as u32, nh as u32) }
.ok()?;
unsafe { brt.SetPixelsPerDip(1.0).ok() };
self.bitmap_target = Some(brt);
self.bitmap_w = nw;
self.bitmap_h = nh;
}
self.bitmap_target.clone()
}
}
impl Default for DWriteEngine {
fn default() -> Self {
Self::new()
}
}
impl TextEngine for DWriteEngine {
fn set_scale(&mut self, scale: f32) {
self.scale = scale.max(0.1);
}
fn measure(&mut self, text: &str, family: Option<&str>, size: f32, max_width: Option<f32>) -> Size {
if text.is_empty() {
return Size::new(0, size.ceil() as i32);
}
let s = self.scale;
let psize = size * s;
let pmw = max_width.map(|w| w * s).unwrap_or(f32::MAX);
let Some(layout) = self.layout(text, family, psize, pmw) else {
return Size::new(0, size.ceil() as i32);
};
let mut m = DWRITE_TEXT_METRICS::default();
unsafe { layout.GetMetrics(&mut m).ok() };
Size::new((m.width / s).ceil() as i32, (m.height / s).ceil() as i32)
}
fn draw(
&mut self,
pixmap: &mut Pixmap,
text: &str,
rect: Rect,
color: Color,
align: Align,
family: Option<&str>,
size: f32,
clip: Option<Rect>,
) {
if text.is_empty() || rect.is_empty() {
return;
}
let s = self.scale;
let prect = rect.scaled(s);
let pclip = clip.map(|c| c.scaled(s));
let psize = size * s;
let Some(layout) = self.layout(text, family, psize, prect.w as f32) else {
return;
};
let mut m = DWRITE_TEXT_METRICS::default();
if unsafe { layout.GetMetrics(&mut m) }.is_err() {
return;
}
let pw = pixmap.width() as i32;
let ph = pixmap.height() as i32;
let mw = m.width.ceil().max(1.0) as i32;
let th = (m.height.ceil().max(1.0) as i32).min(ph);
let text_x0 = match align {
Align::Start | Align::Stretch => prect.x,
Align::Center => prect.x + (prect.w - mw) / 2,
Align::End => prect.x + prect.w - mw,
};
let mut vis0 = text_x0.max(0);
let mut vis1 = (text_x0 + mw).min(pw);
if let Some(c) = pclip {
vis0 = vis0.max(c.x);
vis1 = vis1.min(c.x + c.w);
}
if vis1 <= vis0 {
return;
}
let tw = vis1 - vis0; let glyph_dx = (text_x0 - vis0) as f32;
let Some(brt) = self.ensure_bitmap(tw, th) else {
return;
};
let dc = unsafe { brt.GetMemoryDC() };
let hbm = unsafe { GetCurrentObject(dc, OBJ_BITMAP) };
let mut ds = DIBSECTION::default();
let got = unsafe {
GetObjectW(
hbm,
std::mem::size_of::<DIBSECTION>() as i32,
Some(&mut ds as *mut _ as *mut c_void),
)
};
if got == 0 || ds.dsBm.bmBits.is_null() {
return;
}
let stride_px = ds.dsBm.bmWidthBytes / 4; let bmw = ds.dsBm.bmWidth;
let bmh = ds.dsBm.bmHeight;
debug_assert!(bmh > 0, "expected top-down bitmap render target");
let bits = ds.dsBm.bmBits as *mut u32;
let cw = tw.min(bmw);
let ch = th.min(bmh);
let ox = vis0;
let oy = prect.y + (prect.h - th).max(0) / 2;
{
let px = pixmap.pixels();
for y in 0..ch {
let sy = oy + y;
for x in 0..cw {
let sx = ox + x;
let off = (y * stride_px + x) as usize;
let bgra = if sx >= 0 && sx < pw && sy >= 0 && sy < ph {
let p = px[(sy * pw + sx) as usize];
((p.alpha() as u32) << 24)
| ((p.red() as u32) << 16)
| ((p.green() as u32) << 8)
| (p.blue() as u32)
} else {
0
};
unsafe { bits.add(off).write_unaligned(bgra) };
}
}
}
let colorref =
COLORREF(((color.b as u32) << 16) | ((color.g as u32) << 8) | (color.r as u32));
let ctx = BitmapCtx { target: brt.clone(), color: colorref };
unsafe {
layout
.Draw(Some(&ctx as *const _ as *const c_void), &self.renderer, glyph_dx, 0.0)
.ok()
};
{
let px = pixmap.pixels_mut();
for y in 0..ch {
let dy = oy + y;
if dy < 0 || dy >= ph {
continue;
}
if let Some(c) = pclip {
if dy < c.y || dy >= c.y + c.h {
continue;
}
}
for x in 0..cw {
let dx = ox + x;
if dx < 0 || dx >= pw {
continue;
}
if let Some(c) = pclip {
if dx < c.x || dx >= c.x + c.w {
continue;
}
}
let off = (y * stride_px + x) as usize;
let new = unsafe { bits.add(off).read_unaligned() };
let idx = (dy * pw + dx) as usize;
let d = px[idx];
let nb = (new & 0xFF) as u8;
let ng = ((new >> 8) & 0xFF) as u8;
let nr = ((new >> 16) & 0xFF) as u8;
if nr == d.red() && ng == d.green() && nb == d.blue() {
continue;
}
let a = d.alpha() as u32;
let pr = (nr as u32 * a / 255) as u8;
let pg = (ng as u32 * a / 255) as u8;
let pb = (nb as u32 * a / 255) as u8;
if let Some(p) = PremultipliedColorU8::from_rgba(pr, pg, pb, a as u8) {
px[idx] = p;
}
}
}
}
}
}
struct BitmapCtx {
target: IDWriteBitmapRenderTarget,
color: COLORREF,
}
fn color_f_to_colorref(c: DWRITE_COLOR_F) -> COLORREF {
let q = |v: f32| (v.clamp(0.0, 1.0) * 255.0).round() as u32;
COLORREF((q(c.b) << 16) | (q(c.g) << 8) | q(c.r))
}
#[implement(IDWriteTextRenderer)]
struct GlyphRenderer {
params: IDWriteRenderingParams,
factory2: Option<IDWriteFactory2>,
}
#[allow(non_snake_case)]
impl IDWriteTextRenderer_Impl for GlyphRenderer_Impl {
fn DrawGlyphRun(
&self,
clientdrawingcontext: *const c_void,
baselineoriginx: f32,
baselineoriginy: f32,
measuringmode: DWRITE_MEASURING_MODE,
glyphrun: *const DWRITE_GLYPH_RUN,
glyphrundescription: *const DWRITE_GLYPH_RUN_DESCRIPTION,
_clientdrawingeffect: Ref<'_, IUnknown>,
) -> Result<()> {
if clientdrawingcontext.is_null() {
return Ok(());
}
let ctx = unsafe { &*(clientdrawingcontext as *const BitmapCtx) };
if let Some(f2) = &self.factory2 {
let desc = if glyphrundescription.is_null() { None } else { Some(glyphrundescription) };
let enumr = unsafe {
f2.TranslateColorGlyphRun(
baselineoriginx,
baselineoriginy,
glyphrun,
desc,
measuringmode,
None, 0, )
};
match enumr {
Ok(en) => {
unsafe {
while let Ok(more) = en.MoveNext() {
if !more.as_bool() {
break;
}
let Ok(run_ptr) = en.GetCurrentRun() else { break };
if run_ptr.is_null() {
break;
}
let run = &*run_ptr;
let color = if run.paletteIndex == 0xFFFF {
ctx.color
} else {
color_f_to_colorref(run.runColor)
};
let _ = ctx.target.DrawGlyphRun(
run.baselineOriginX,
run.baselineOriginY,
measuringmode,
&run.glyphRun,
&self.params,
color,
None,
);
}
}
return Ok(());
}
Err(e) if e.code() == DWRITE_E_NOCOLOR => {} Err(_) => {} }
}
unsafe {
let _ = ctx.target.DrawGlyphRun(
baselineoriginx,
baselineoriginy,
measuringmode,
glyphrun,
&self.params,
ctx.color,
None,
);
}
Ok(())
}
fn DrawUnderline(
&self,
_ctx: *const c_void,
_x: f32,
_y: f32,
_underline: *const DWRITE_UNDERLINE,
_effect: Ref<'_, IUnknown>,
) -> Result<()> {
Ok(())
}
fn DrawStrikethrough(
&self,
_ctx: *const c_void,
_x: f32,
_y: f32,
_strikethrough: *const DWRITE_STRIKETHROUGH,
_effect: Ref<'_, IUnknown>,
) -> Result<()> {
Ok(())
}
fn DrawInlineObject(
&self,
_ctx: *const c_void,
_x: f32,
_y: f32,
_inlineobject: Ref<'_, IDWriteInlineObject>,
_issideways: BOOL,
_isrtl: BOOL,
_effect: Ref<'_, IUnknown>,
) -> Result<()> {
Ok(())
}
}
#[allow(non_snake_case)]
impl IDWritePixelSnapping_Impl for GlyphRenderer_Impl {
fn IsPixelSnappingDisabled(&self, _ctx: *const c_void) -> Result<BOOL> {
Ok(FALSE)
}
fn GetCurrentTransform(&self, _ctx: *const c_void, transform: *mut DWRITE_MATRIX) -> Result<()> {
if transform.is_null() {
return Ok(());
}
unsafe {
*transform = DWRITE_MATRIX { m11: 1.0, m12: 0.0, m21: 0.0, m22: 1.0, dx: 0.0, dy: 0.0 };
}
Ok(())
}
fn GetPixelsPerDip(&self, _ctx: *const c_void) -> Result<f32> {
Ok(1.0)
}
}