windows: Fix wrong glyph index being reported (#33193)
Closes #32424 This PR fixes two bugs: * In cases like `fi ~~something~~`, the `fi` gets rendered as a ligature, meaning the two characters are combined into a single glyph. The final glyph index didn’t account for this change, which caused issues. * On Windows, some emojis are composed of multiple glyphs. These composite emojis can now be rendered correctly as well.   Release Notes: - N/A
This commit is contained in:
parent
af8f26dd34
commit
5244085bd0
1 changed files with 152 additions and 42 deletions
|
@ -598,7 +598,6 @@ impl DirectWriteState {
|
||||||
text_system: self,
|
text_system: self,
|
||||||
index_converter: StringIndexConverter::new(text),
|
index_converter: StringIndexConverter::new(text),
|
||||||
runs: &mut runs,
|
runs: &mut runs,
|
||||||
utf16_index: 0,
|
|
||||||
width: 0.0,
|
width: 0.0,
|
||||||
};
|
};
|
||||||
text_layout.Draw(
|
text_layout.Draw(
|
||||||
|
@ -1003,10 +1002,65 @@ struct RendererContext<'t, 'a, 'b> {
|
||||||
text_system: &'t mut DirectWriteState,
|
text_system: &'t mut DirectWriteState,
|
||||||
index_converter: StringIndexConverter<'a>,
|
index_converter: StringIndexConverter<'a>,
|
||||||
runs: &'b mut Vec<ShapedRun>,
|
runs: &'b mut Vec<ShapedRun>,
|
||||||
utf16_index: usize,
|
|
||||||
width: f32,
|
width: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ClusterAnalyzer<'t> {
|
||||||
|
utf16_idx: usize,
|
||||||
|
glyph_idx: usize,
|
||||||
|
glyph_count: usize,
|
||||||
|
cluster_map: &'t [u16],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'t> ClusterAnalyzer<'t> {
|
||||||
|
pub fn new(cluster_map: &'t [u16], glyph_count: usize) -> Self {
|
||||||
|
ClusterAnalyzer {
|
||||||
|
utf16_idx: 0,
|
||||||
|
glyph_idx: 0,
|
||||||
|
glyph_count,
|
||||||
|
cluster_map,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for ClusterAnalyzer<'_> {
|
||||||
|
type Item = (usize, usize);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<(usize, usize)> {
|
||||||
|
if self.utf16_idx >= self.cluster_map.len() {
|
||||||
|
return None; // No more clusters
|
||||||
|
}
|
||||||
|
let start_utf16_idx = self.utf16_idx;
|
||||||
|
let current_glyph = self.cluster_map[start_utf16_idx] as usize;
|
||||||
|
|
||||||
|
// Find the end of current cluster (where glyph index changes)
|
||||||
|
let mut end_utf16_idx = start_utf16_idx + 1;
|
||||||
|
while end_utf16_idx < self.cluster_map.len()
|
||||||
|
&& self.cluster_map[end_utf16_idx] as usize == current_glyph
|
||||||
|
{
|
||||||
|
end_utf16_idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let utf16_len = end_utf16_idx - start_utf16_idx;
|
||||||
|
|
||||||
|
// Calculate glyph count for this cluster
|
||||||
|
let next_glyph = if end_utf16_idx < self.cluster_map.len() {
|
||||||
|
self.cluster_map[end_utf16_idx] as usize
|
||||||
|
} else {
|
||||||
|
self.glyph_count
|
||||||
|
};
|
||||||
|
|
||||||
|
let glyph_count = next_glyph - current_glyph;
|
||||||
|
|
||||||
|
// Update state for next call
|
||||||
|
self.utf16_idx = end_utf16_idx;
|
||||||
|
self.glyph_idx = next_glyph;
|
||||||
|
|
||||||
|
Some((utf16_len, glyph_count))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
impl IDWritePixelSnapping_Impl for TextRenderer_Impl {
|
impl IDWritePixelSnapping_Impl for TextRenderer_Impl {
|
||||||
fn IsPixelSnappingDisabled(
|
fn IsPixelSnappingDisabled(
|
||||||
|
@ -1054,59 +1108,73 @@ impl IDWriteTextRenderer_Impl for TextRenderer_Impl {
|
||||||
glyphrundescription: *const DWRITE_GLYPH_RUN_DESCRIPTION,
|
glyphrundescription: *const DWRITE_GLYPH_RUN_DESCRIPTION,
|
||||||
_clientdrawingeffect: windows::core::Ref<windows::core::IUnknown>,
|
_clientdrawingeffect: windows::core::Ref<windows::core::IUnknown>,
|
||||||
) -> windows::core::Result<()> {
|
) -> windows::core::Result<()> {
|
||||||
unsafe {
|
let glyphrun = unsafe { &*glyphrun };
|
||||||
let glyphrun = &*glyphrun;
|
let glyph_count = glyphrun.glyphCount as usize;
|
||||||
let glyph_count = glyphrun.glyphCount as usize;
|
if glyph_count == 0 || glyphrun.fontFace.is_none() {
|
||||||
if glyph_count == 0 {
|
return Ok(());
|
||||||
return Ok(());
|
}
|
||||||
}
|
let desc = unsafe { &*glyphrundescription };
|
||||||
let desc = &*glyphrundescription;
|
let context = unsafe {
|
||||||
let utf16_length_per_glyph = desc.stringLength as usize / glyph_count;
|
&mut *(clientdrawingcontext as *const RendererContext as *mut RendererContext)
|
||||||
let context =
|
};
|
||||||
&mut *(clientdrawingcontext as *const RendererContext as *mut RendererContext);
|
let font_face = glyphrun.fontFace.as_ref().unwrap();
|
||||||
|
// This `cast()` action here should never fail since we are running on Win10+, and
|
||||||
|
// `IDWriteFontFace3` requires Win10
|
||||||
|
let font_face = &font_face.cast::<IDWriteFontFace3>().unwrap();
|
||||||
|
let Some((font_identifier, font_struct, color_font)) =
|
||||||
|
get_font_identifier_and_font_struct(font_face, &self.locale)
|
||||||
|
else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
if glyphrun.fontFace.is_none() {
|
let font_id = if let Some(id) = context
|
||||||
return Ok(());
|
.text_system
|
||||||
}
|
.font_id_by_identifier
|
||||||
|
.get(&font_identifier)
|
||||||
|
{
|
||||||
|
*id
|
||||||
|
} else {
|
||||||
|
context.text_system.select_font(&font_struct)
|
||||||
|
};
|
||||||
|
|
||||||
let font_face = glyphrun.fontFace.as_ref().unwrap();
|
let glyph_ids = unsafe { std::slice::from_raw_parts(glyphrun.glyphIndices, glyph_count) };
|
||||||
// This `cast()` action here should never fail since we are running on Win10+, and
|
let glyph_advances =
|
||||||
// `IDWriteFontFace3` requires Win10
|
unsafe { std::slice::from_raw_parts(glyphrun.glyphAdvances, glyph_count) };
|
||||||
let font_face = &font_face.cast::<IDWriteFontFace3>().unwrap();
|
let glyph_offsets =
|
||||||
let Some((font_identifier, font_struct, color_font)) =
|
unsafe { std::slice::from_raw_parts(glyphrun.glyphOffsets, glyph_count) };
|
||||||
get_font_identifier_and_font_struct(font_face, &self.locale)
|
let cluster_map =
|
||||||
else {
|
unsafe { std::slice::from_raw_parts(desc.clusterMap, desc.stringLength as usize) };
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let font_id = if let Some(id) = context
|
let mut cluster_analyzer = ClusterAnalyzer::new(cluster_map, glyph_count);
|
||||||
.text_system
|
let mut utf16_idx = desc.textPosition as usize;
|
||||||
.font_id_by_identifier
|
let mut glyph_idx = 0;
|
||||||
.get(&font_identifier)
|
let mut glyphs = Vec::with_capacity(glyph_count);
|
||||||
|
for (cluster_utf16_len, cluster_glyph_count) in cluster_analyzer {
|
||||||
|
context.index_converter.advance_to_utf16_ix(utf16_idx);
|
||||||
|
utf16_idx += cluster_utf16_len;
|
||||||
|
for (cluster_glyph_idx, glyph_id) in glyph_ids
|
||||||
|
[glyph_idx..(glyph_idx + cluster_glyph_count)]
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
{
|
{
|
||||||
*id
|
let id = GlyphId(*glyph_id as u32);
|
||||||
} else {
|
|
||||||
context.text_system.select_font(&font_struct)
|
|
||||||
};
|
|
||||||
let mut glyphs = Vec::with_capacity(glyph_count);
|
|
||||||
for index in 0..glyph_count {
|
|
||||||
let id = GlyphId(*glyphrun.glyphIndices.add(index) as u32);
|
|
||||||
context
|
|
||||||
.index_converter
|
|
||||||
.advance_to_utf16_ix(context.utf16_index);
|
|
||||||
let is_emoji = color_font
|
let is_emoji = color_font
|
||||||
&& is_color_glyph(font_face, id, &context.text_system.components.factory);
|
&& is_color_glyph(font_face, id, &context.text_system.components.factory);
|
||||||
|
let this_glyph_idx = glyph_idx + cluster_glyph_idx;
|
||||||
glyphs.push(ShapedGlyph {
|
glyphs.push(ShapedGlyph {
|
||||||
id,
|
id,
|
||||||
position: point(px(context.width), px(0.0)),
|
position: point(
|
||||||
|
px(context.width + glyph_offsets[this_glyph_idx].advanceOffset),
|
||||||
|
px(0.0),
|
||||||
|
),
|
||||||
index: context.index_converter.utf8_ix,
|
index: context.index_converter.utf8_ix,
|
||||||
is_emoji,
|
is_emoji,
|
||||||
});
|
});
|
||||||
context.utf16_index += utf16_length_per_glyph;
|
context.width += glyph_advances[this_glyph_idx];
|
||||||
context.width += *glyphrun.glyphAdvances.add(index);
|
|
||||||
}
|
}
|
||||||
context.runs.push(ShapedRun { font_id, glyphs });
|
glyph_idx += cluster_glyph_count;
|
||||||
}
|
}
|
||||||
|
context.runs.push(ShapedRun { font_id, glyphs });
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1499,3 +1567,45 @@ const BRUSH_COLOR: D2D1_COLOR_F = D2D1_COLOR_F {
|
||||||
b: 1.0,
|
b: 1.0,
|
||||||
a: 1.0,
|
a: 1.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::platform::windows::direct_write::ClusterAnalyzer;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cluster_map() {
|
||||||
|
let cluster_map = [0];
|
||||||
|
let mut analyzer = ClusterAnalyzer::new(&cluster_map, 1);
|
||||||
|
let next = analyzer.next();
|
||||||
|
assert_eq!(next, Some((1, 1)));
|
||||||
|
let next = analyzer.next();
|
||||||
|
assert_eq!(next, None);
|
||||||
|
|
||||||
|
let cluster_map = [0, 1, 2];
|
||||||
|
let mut analyzer = ClusterAnalyzer::new(&cluster_map, 3);
|
||||||
|
let next = analyzer.next();
|
||||||
|
assert_eq!(next, Some((1, 1)));
|
||||||
|
let next = analyzer.next();
|
||||||
|
assert_eq!(next, Some((1, 1)));
|
||||||
|
let next = analyzer.next();
|
||||||
|
assert_eq!(next, Some((1, 1)));
|
||||||
|
let next = analyzer.next();
|
||||||
|
assert_eq!(next, None);
|
||||||
|
// 👨👩👧👦👩💻
|
||||||
|
let cluster_map = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4];
|
||||||
|
let mut analyzer = ClusterAnalyzer::new(&cluster_map, 5);
|
||||||
|
let next = analyzer.next();
|
||||||
|
assert_eq!(next, Some((11, 4)));
|
||||||
|
let next = analyzer.next();
|
||||||
|
assert_eq!(next, Some((5, 1)));
|
||||||
|
let next = analyzer.next();
|
||||||
|
assert_eq!(next, None);
|
||||||
|
// 👩💻
|
||||||
|
let cluster_map = [0, 0, 0, 0, 0];
|
||||||
|
let mut analyzer = ClusterAnalyzer::new(&cluster_map, 1);
|
||||||
|
let next = analyzer.next();
|
||||||
|
assert_eq!(next, Some((5, 1)));
|
||||||
|
let next = analyzer.next();
|
||||||
|
assert_eq!(next, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue