diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-03-10 10:20:01 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-03-10 10:20:01 +0100 |
| commit | bbb9ed07ffe8a2a0ea0a232f6cfc52f82f7f7afe (patch) | |
| tree | 2b106b63e104652b66efc74a0ffa3080a8b0a134 /src | |
| parent | b2b8d37ce03de60582230e03c03efa356b6f31d3 (diff) | |
Better line spacing calculations ↕
- Only add line spacing between lines. Previously, line spacing was added below
every line, making `#box[word]` higher than just `word`.
- Compute box height of text as `ascender - descender` so that the full word is
contained in the box.
Diffstat (limited to 'src')
| -rw-r--r-- | src/exec/state.rs | 2 | ||||
| -rw-r--r-- | src/export/pdf.rs | 2 | ||||
| -rw-r--r-- | src/layout/par.rs | 8 | ||||
| -rw-r--r-- | src/shaping.rs | 57 |
4 files changed, 42 insertions, 27 deletions
diff --git a/src/exec/state.rs b/src/exec/state.rs index 22839c54..416b5d08 100644 --- a/src/exec/state.rs +++ b/src/exec/state.rs @@ -93,7 +93,7 @@ impl Default for ParState { fn default() -> Self { Self { word_spacing: Relative::new(0.25).into(), - line_spacing: Relative::new(0.2).into(), + line_spacing: Linear::ZERO, par_spacing: Relative::new(0.5).into(), } } diff --git a/src/export/pdf.rs b/src/export/pdf.rs index 9f355278..c30222b2 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -195,7 +195,7 @@ impl<'a> PdfExporter<'a> { } let x = pos.x.to_pt() as f32; - let y = (page.size.height - pos.y - size).to_pt() as f32; + let y = (page.size.height - pos.y).to_pt() as f32; text.matrix(1.0, 0.0, 0.0, 1.0, x, y); text.show(&shaped.encode_glyphs_be()); } diff --git a/src/layout/par.rs b/src/layout/par.rs index 7d876fc1..45494dec 100644 --- a/src/layout/par.rs +++ b/src/layout/par.rs @@ -163,13 +163,17 @@ impl<'a> ParLayouter<'a> { output.push_frame(pos, frame); } + // Add line spacing, but only between lines. + if !self.lines.is_empty() { + self.lines_size.main += self.par.line_spacing; + *self.areas.current.get_mut(self.main) -= self.par.line_spacing; + } + // Update metrics of the whole paragraph. self.lines.push((self.lines_size.main, output, self.line_ruler)); self.lines_size.main += full_size.main; - self.lines_size.main += self.par.line_spacing; self.lines_size.cross = self.lines_size.cross.max(full_size.cross); *self.areas.current.get_mut(self.main) -= full_size.main; - *self.areas.current.get_mut(self.main) -= self.par.line_spacing; // Reset metrics for the single line. self.line_size = Gen::ZERO; diff --git a/src/shaping.rs b/src/shaping.rs index 722d103a..a8d2b2bf 100644 --- a/src/shaping.rs +++ b/src/shaping.rs @@ -70,6 +70,8 @@ pub fn shape( let mut frame = Frame::new(Size::new(Length::ZERO, font_size)); let mut shaped = Shaped::new(FaceId::MAX, font_size); let mut offset = Length::ZERO; + let mut ascender = Length::ZERO; + let mut descender = Length::ZERO; // Create an iterator with conditional direction. let mut forwards = text.chars(); @@ -84,47 +86,56 @@ pub fn shape( let query = FaceQuery { fallback: fallback.iter(), variant, c }; if let Some(id) = loader.query(query) { let face = loader.face(id).get(); - let (glyph, width) = match lookup_glyph(face, c, font_size) { + let (glyph, width) = match lookup_glyph(face, c) { Some(v) => v, None => continue, }; - // Flush the buffer if we change the font face. - if shaped.face != id && !shaped.text.is_empty() { - let pos = Point::new(frame.size.width, Length::ZERO); - frame.push(pos, Element::Text(shaped)); - frame.size.width += offset; - shaped = Shaped::new(FaceId::MAX, font_size); + let units_per_em = f64::from(face.units_per_em().unwrap_or(1000)); + let convert = |units| units / units_per_em * font_size; + + // Flush the buffer and reset the metrics if we use a new font face. + if shaped.face != id { + place(&mut frame, shaped, offset, ascender, descender); + + shaped = Shaped::new(id, font_size); offset = Length::ZERO; + ascender = convert(f64::from(face.ascender())); + descender = convert(f64::from(face.descender())); } - shaped.face = id; shaped.text.push(c); shaped.glyphs.push(glyph); shaped.offsets.push(offset); - offset += width; + offset += convert(f64::from(width)); } } // Flush the last buffered parts of the word. - if !shaped.text.is_empty() { - let pos = Point::new(frame.size.width, Length::ZERO); - frame.push(pos, Element::Text(shaped)); - frame.size.width += offset; - } + place(&mut frame, shaped, offset, ascender, descender); frame } -/// Looks up the glyph for `c` and returns its index alongside its width at the -/// given `size`. -fn lookup_glyph(face: &Face, c: char, size: Length) -> Option<(GlyphId, Length)> { +/// Look up the glyph for `c` and returns its index alongside its advance width. +fn lookup_glyph(face: &Face, c: char) -> Option<(GlyphId, u16)> { let glyph = face.glyph_index(c)?; - - // Determine the width of the char. - let units_per_em = face.units_per_em().unwrap_or(1000) as f64; - let width_units = face.glyph_hor_advance(glyph)? as f64; - let width = width_units / units_per_em * size; - + let width = face.glyph_hor_advance(glyph)?; Some((glyph, width)) } + +/// Place shaped text into a frame. +fn place( + frame: &mut Frame, + shaped: Shaped, + offset: Length, + ascender: Length, + descender: Length, +) { + if !shaped.text.is_empty() { + let pos = Point::new(frame.size.width, ascender); + frame.push(pos, Element::Text(shaped)); + frame.size.width += offset; + frame.size.height = frame.size.height.max(ascender - descender); + } +} |
