Skip to content

Commit 32030ed

Browse files
wabzqemRichard Nelson
andauthored
text: Fix inline-code heading layout and list alignment (longbridge#3038)
## Summary Markdown containing inline code uses a different rendering path, and that path has two bugs: - **Incorrect wrapping:** it could measure text using one font style, then paint it using another—for example, measuring normal text but drawing bold. Words wrapped inside a box that hadn’t reserved enough height, so the heading overlapped the next paragraph. - **Misaligned bullets:** it mixed two font-metric conventions when calculating the baseline, shifting list text downward relative to its bullet. The fix makes measurement and painting use the same typography and baseline calculations. - Preserve inherited typography across deferred inline-flow measurement and painting, preventing inline-code headings from wrapping outside their allocated height and overlapping following text. - Use shaped-line metrics consistently for baselines, fixing inline-code list text sitting below its bullet. - Add regressions for wrapping, preview zoom, painted bounds, and native-compatible font metrics. ## Before / after <img width="1878" height="404" alt="markdown-inline-size-bug" src="https://github.com/user-attachments/assets/7b5c8262-33aa-4a43-b4a6-594b39588a54" /> **Original on the left; patched on the right**, in `example-markdown`. The original wraps “with” into the following paragraph and misaligns list text. The patch preserves reading order, block height, and baseline alignment. ## Validation - `cargo test -p gpui-base --lib` — **925 passed** - `cargo fmt --all -- --check` - `cargo clippy --all-targets --all-features -- -D warnings` - `cargo build -p example-markdown` - Visual comparison in the example app. Co-authored-by: Richard Nelson <rnelson@atlassian.com>
1 parent 42721ea commit 32030ed

3 files changed

Lines changed: 379 additions & 74 deletions

File tree

‎crates/base/src/text/inline.rs‎

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ pub(super) struct Inline {
166166
links: Rc<Vec<(Range<usize>, LinkMark)>>,
167167
highlights: Vec<(Range<usize>, InlineHighlight)>,
168168
styled_text: StyledText,
169+
/// The resolved style from a parent deferred layout, when there is one.
170+
text_style: Option<TextStyle>,
169171
paint_origin: Option<Point<Pixels>>,
170172
selection_bounds: Option<Bounds<Pixels>>,
171173
selection_source: Option<(Arc<Mutex<InlineState>>, Range<usize>)>,
@@ -209,6 +211,7 @@ impl Inline {
209211
highlights,
210212
text: text.clone(),
211213
styled_text: StyledText::new(text),
214+
text_style: None,
212215
paint_origin: None,
213216
selection_bounds: None,
214217
selection_source: None,
@@ -217,6 +220,12 @@ impl Inline {
217220
}
218221
}
219222

223+
/// Use the resolved style captured by a deferred parent layout.
224+
pub(super) fn text_style(mut self, text_style: TextStyle) -> Self {
225+
self.text_style = Some(text_style);
226+
self
227+
}
228+
220229
/// Preserve the shared inline-flow baseline through GPUI's element-bound snapping.
221230
pub(super) fn paint_origin(mut self, origin: Point<Pixels>) -> Self {
222231
self.paint_origin = Some(origin);
@@ -555,7 +564,10 @@ impl Element for Inline {
555564
window: &mut Window,
556565
cx: &mut App,
557566
) -> (LayoutId, Self::RequestLayoutState) {
558-
let text_style = window.text_style();
567+
let text_style = self
568+
.text_style
569+
.clone()
570+
.unwrap_or_else(|| window.text_style());
559571
let runs = text_runs(self.text.len(), &text_style, &self.highlights);
560572

561573
self.styled_text = StyledText::new(self.text.clone()).with_runs(runs);
@@ -920,24 +932,32 @@ pub(super) mod test_draw {
920932
#[cfg(test)]
921933
pub(super) mod test_fonts {
922934
use gpui::{
923-
Bounds, DevicePixels, Font, FontId, FontMetrics, FontRun, GlyphId, LineLayout, Pixels,
924-
PlatformTextSystem, RenderGlyphParams, ShapedGlyph, ShapedRun, Size, TextRenderingMode,
925-
point, px, size,
935+
Bounds, DevicePixels, Font, FontId, FontMetrics, FontRun, FontWeight, GlyphId, LineLayout,
936+
Pixels, PlatformTextSystem, RenderGlyphParams, ShapedGlyph, ShapedRun, Size,
937+
TextRenderingMode, point, px, size,
926938
};
927939
use std::borrow::Cow;
928940

929941
pub(crate) const BODY: &str = "Body";
930942
pub(crate) const MONO: &str = "Mono";
931943
const BODY_ID: FontId = FontId(1);
932944
const MONO_ID: FontId = FontId(2);
945+
const BOLD_BODY_ID: FontId = FontId(3);
946+
const BOLD_MONO_ID: FontId = FontId(4);
933947
const UNITS_PER_EM: f32 = 1000.;
934948

935949
pub(crate) struct WideMonoTextSystem;
936950

937951
impl WideMonoTextSystem {
938952
/// Advance of one glyph in `font_id`, in em units.
939953
fn advance_units(font_id: FontId) -> f32 {
940-
if font_id == MONO_ID { 1000. } else { 500. }
954+
match font_id {
955+
MONO_ID => 1000.,
956+
BOLD_MONO_ID => 1250.,
957+
BODY_ID => 500.,
958+
BOLD_BODY_ID => 750.,
959+
_ => 500.,
960+
}
941961
}
942962

943963
/// Width of `text` shaped entirely in `family` at `font_size`.
@@ -957,11 +977,17 @@ pub(super) mod test_fonts {
957977
}
958978

959979
fn font_id(&self, descriptor: &Font) -> anyhow::Result<FontId> {
960-
Ok(if descriptor.family.as_ref() == MONO {
961-
MONO_ID
962-
} else {
963-
BODY_ID
964-
})
980+
Ok(
981+
match (
982+
descriptor.family.as_ref() == MONO,
983+
descriptor.weight == FontWeight::BOLD,
984+
) {
985+
(true, true) => BOLD_MONO_ID,
986+
(true, false) => MONO_ID,
987+
(false, true) => BOLD_BODY_ID,
988+
(false, false) => BODY_ID,
989+
},
990+
)
965991
}
966992

967993
fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
@@ -1043,7 +1069,8 @@ pub(super) mod test_fonts {
10431069
font_size,
10441070
width: position,
10451071
ascent: font_size * (metrics.ascent / UNITS_PER_EM),
1046-
descent: font_size * (metrics.descent / UNITS_PER_EM),
1072+
// Native backends normalize the signed font metric for shaped lines.
1073+
descent: font_size * (-metrics.descent / UNITS_PER_EM),
10471074
runs: shaped_runs,
10481075
len: text.len(),
10491076
}

0 commit comments

Comments
 (0)