use crate::types::FontId;
use crate::{FontHandle, FontMetrics, ShapedGlyph, ShapedLine, TextError};
use std::cell::RefCell;
use std::rc::Rc;
use windows::Win32::Graphics::DirectWrite::{
DWRITE_GLYPH_RUN, DWRITE_GLYPH_RUN_DESCRIPTION, DWRITE_MATRIX, DWRITE_MEASURING_MODE,
DWRITE_READING_DIRECTION_LEFT_TO_RIGHT, DWRITE_READING_DIRECTION_RIGHT_TO_LEFT,
DWRITE_STRIKETHROUGH, DWRITE_UNDERLINE, IDWriteFactory5, IDWriteFontFace, IDWriteInlineObject,
IDWritePixelSnapping_Impl, IDWriteTextFormat, IDWriteTextRenderer, IDWriteTextRenderer_Impl,
};
use windows::core::{BOOL, IUnknown, Ref, Result, implement};
pub(crate) struct CapturedFace {
pub(crate) handle: FontHandle,
pub(crate) face: IDWriteFontFace,
}
type GlyphStore = Rc<RefCell<Vec<ShapedGlyph>>>;
type FaceStore = Rc<RefCell<Vec<CapturedFace>>>;
#[implement(IDWriteTextRenderer)]
pub(crate) struct ShapingRenderer {
glyphs: GlyphStore,
faces: FaceStore,
size_lpx: f32,
scale: f32,
primary_handle: FontHandle,
utf16_to_utf8: Vec<u32>,
}
impl ShapingRenderer {
pub(crate) fn new(
glyphs: GlyphStore,
faces: FaceStore,
size_lpx: f32,
scale: f32,
primary_handle: FontHandle,
utf16_to_utf8: Vec<u32>,
) -> Self {
Self {
glyphs,
faces,
size_lpx,
scale,
primary_handle,
utf16_to_utf8,
}
}
}
#[allow(non_snake_case)]
impl IDWritePixelSnapping_Impl for ShapingRenderer_Impl {
fn IsPixelSnappingDisabled(
&self,
_clientdrawingcontext: *const core::ffi::c_void,
) -> Result<BOOL> {
Ok(BOOL(0)) }
fn GetCurrentTransform(
&self,
_clientdrawingcontext: *const core::ffi::c_void,
transform: *mut DWRITE_MATRIX,
) -> Result<()> {
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, _clientdrawingcontext: *const core::ffi::c_void) -> Result<f32> {
Ok(1.0)
}
}
#[allow(non_snake_case)]
impl IDWriteTextRenderer_Impl for ShapingRenderer_Impl {
fn DrawGlyphRun(
&self,
_clientdrawingcontext: *const core::ffi::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<()> {
let run = unsafe { &*glyphrun };
let count = run.glyphCount as usize;
if count == 0 {
return Ok(());
}
let indices = unsafe { std::slice::from_raw_parts(run.glyphIndices, count) };
let advances = unsafe { std::slice::from_raw_parts(run.glyphAdvances, count) };
let offsets = if run.glyphOffsets.is_null() {
None
} else {
Some(unsafe { std::slice::from_raw_parts(run.glyphOffsets, count) })
};
let clusters: Vec<u32> = if !glyphrundescription.is_null() {
let desc = unsafe { &*glyphrundescription };
let text_position = desc.textPosition as usize;
let string_length = desc.stringLength as usize;
let cluster_map = if !desc.clusterMap.is_null() && string_length > 0 {
Some(unsafe { std::slice::from_raw_parts(desc.clusterMap, string_length) })
} else {
None
};
let mut glyph_to_char: Vec<Option<u32>> = vec![None; count];
if let Some(cm) = cluster_map {
for (j, &g) in cm.iter().enumerate() {
let g = g as usize;
if g < count && glyph_to_char[g].is_none() {
glyph_to_char[g] = Some(j as u32);
}
}
let mut last: u32 = 0;
for slot in glyph_to_char.iter_mut() {
match slot {
Some(v) => last = *v,
None => *slot = Some(last),
}
}
}
glyph_to_char
.iter()
.map(|slot| {
let char_in_run = slot.unwrap_or(0) as usize;
let utf16_pos = text_position + char_in_run;
self.utf16_to_utf8.get(utf16_pos).copied().unwrap_or(0)
})
.collect()
} else {
vec![0u32; count]
};
let face_opt: Option<IDWriteFontFace> = (*run.fontFace).clone();
let font_handle = match face_opt.as_ref() {
Some(face) => {
let id = super::font_id::idwrite_font_face_id(face);
FontHandle::from_face_id(id, self.size_lpx, self.scale)
}
None => FontHandle::default(),
};
if font_handle != self.primary_handle
&& let Some(face) = face_opt
{
let mut faces = self.faces.borrow_mut();
if !faces.iter().any(|cf| cf.handle == font_handle) {
faces.push(CapturedFace {
handle: font_handle,
face,
});
}
}
let mut glyphs = self.glyphs.borrow_mut();
glyphs.reserve(count);
let mut pen: f32 = baselineoriginx;
for i in 0..count {
let (off_x, off_y) = offsets
.map(|o| (o[i].advanceOffset, -o[i].ascenderOffset))
.unwrap_or((0.0, 0.0));
let position = [pen + off_x, off_y];
glyphs.push(ShapedGlyph {
glyph_id: indices[i] as u32,
font_id: FontId::PRIMARY,
font_handle,
x_advance_lpx: advances[i],
position_lpx: position,
cluster: clusters[i],
direction: crate::types::Direction::Ltr,
});
pen += advances[i];
}
Ok(())
}
fn DrawUnderline(
&self,
_clientdrawingcontext: *const core::ffi::c_void,
_baselineoriginx: f32,
_baselineoriginy: f32,
_underline: *const DWRITE_UNDERLINE,
_clientdrawingeffect: Ref<'_, IUnknown>,
) -> Result<()> {
Ok(()) }
fn DrawStrikethrough(
&self,
_clientdrawingcontext: *const core::ffi::c_void,
_baselineoriginx: f32,
_baselineoriginy: f32,
_strikethrough: *const DWRITE_STRIKETHROUGH,
_clientdrawingeffect: Ref<'_, IUnknown>,
) -> Result<()> {
Ok(()) }
fn DrawInlineObject(
&self,
_clientdrawingcontext: *const core::ffi::c_void,
_originx: f32,
_originy: f32,
_inlineobject: Ref<'_, IDWriteInlineObject>,
_issideways: BOOL,
_isrighttoleft: BOOL,
_clientdrawingeffect: Ref<'_, IUnknown>,
) -> Result<()> {
Ok(()) }
}
pub(crate) struct ShapeResult {
pub(crate) line: ShapedLine,
pub(crate) captured_faces: Vec<CapturedFace>,
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn shape_line(
factory: &IDWriteFactory5,
text_format: &IDWriteTextFormat,
text: &str,
metrics: &FontMetrics,
size_lpx: f32,
scale: f32,
primary_handle: FontHandle,
forced_direction: Option<crate::types::Direction>,
) -> std::result::Result<ShapeResult, TextError> {
if text.is_empty() {
return Ok(ShapeResult {
line: ShapedLine {
glyphs: vec![],
width_lpx: 0.0,
ascent_lpx: metrics.ascent_lpx,
descent_lpx: metrics.descent_lpx,
y_offset_lpx: 0.0,
base_direction: crate::types::Direction::Ltr,
runs: Vec::new(),
},
captured_faces: Vec::new(),
});
}
let wide: Vec<u16> = text.encode_utf16().collect();
let utf16_to_utf8 = crate::cluster::utf16_to_utf8_byte_map(text);
let layout = unsafe { factory.CreateTextLayout(&wide, text_format, f32::MAX, f32::MAX) }
.map_err(|e| TextError::ShapingFailed(format!("CreateTextLayout: {e}")))?;
if let Some(direction) = forced_direction {
let reading_direction = match direction {
crate::types::Direction::Ltr => DWRITE_READING_DIRECTION_LEFT_TO_RIGHT,
crate::types::Direction::Rtl => DWRITE_READING_DIRECTION_RIGHT_TO_LEFT,
};
unsafe { layout.SetReadingDirection(reading_direction) }
.map_err(|e| TextError::ShapingFailed(format!("SetReadingDirection: {e}")))?;
}
let mut text_metrics = windows::Win32::Graphics::DirectWrite::DWRITE_TEXT_METRICS::default();
unsafe { layout.GetMetrics(&mut text_metrics) }
.map_err(|e| TextError::ShapingFailed(format!("GetMetrics: {e}")))?;
let measured = text_metrics
.width
.max(text_metrics.widthIncludingTrailingWhitespace);
let box_width = if measured.is_finite() && measured > 0.0 {
measured
} else {
(wide.len() as f32 + 1.0) * size_lpx
};
unsafe { layout.SetMaxWidth(box_width + 1.0) }
.map_err(|e| TextError::ShapingFailed(format!("SetMaxWidth: {e}")))?;
let glyphs_store: GlyphStore = Rc::new(RefCell::new(Vec::new()));
let faces_store: FaceStore = Rc::new(RefCell::new(Vec::new()));
let renderer = ShapingRenderer::new(
Rc::clone(&glyphs_store),
Rc::clone(&faces_store),
size_lpx,
scale,
primary_handle,
utf16_to_utf8,
);
let renderer_iface: IDWriteTextRenderer = renderer.into();
unsafe { layout.Draw(None, &renderer_iface, 0.0, 0.0) }
.map_err(|e| TextError::ShapingFailed(format!("IDWriteTextLayout::Draw: {e}")))?;
let mut glyphs: Vec<ShapedGlyph> = glyphs_store.borrow_mut().drain(..).collect();
let captured_faces: Vec<CapturedFace> = faces_store.borrow_mut().drain(..).collect();
if let Some(min_x) = glyphs
.iter()
.map(|g| g.position_lpx[0])
.fold(None, |acc: Option<f32>, x| {
Some(acc.map_or(x, |a| a.min(x)))
})
&& min_x != 0.0
{
for g in &mut glyphs {
g.position_lpx[0] -= min_x;
}
}
let width_lpx = glyphs.iter().map(|g| g.x_advance_lpx).sum();
Ok(ShapeResult {
line: ShapedLine {
glyphs,
width_lpx,
ascent_lpx: metrics.ascent_lpx,
descent_lpx: metrics.descent_lpx,
y_offset_lpx: 0.0,
base_direction: crate::types::Direction::Ltr,
runs: Vec::new(),
},
captured_faces,
})
}