summaryrefslogtreecommitdiff
path: root/src/layout
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-08-02 21:17:42 +0200
committerLaurenz <laurmaedje@gmail.com>2020-08-02 21:17:42 +0200
commitcbbc46215fe0a0ad8a50e991ec442890b8eadc0a (patch)
tree2efbac21cec46787f1efe0a859564b9614eefa98 /src/layout
parentd5ff97f42ed1e682a66ea8d51e5f9ed1be547b9c (diff)
Layout elements and pure rust rendering 🥏
Diffstat (limited to 'src/layout')
-rw-r--r--src/layout/actions.rs166
-rw-r--r--src/layout/elements.rs84
-rw-r--r--src/layout/line.rs12
-rw-r--r--src/layout/mod.rs28
-rw-r--r--src/layout/model.rs3
-rw-r--r--src/layout/stack.rs17
-rw-r--r--src/layout/text.rs52
7 files changed, 128 insertions, 234 deletions
diff --git a/src/layout/actions.rs b/src/layout/actions.rs
deleted file mode 100644
index 7a32a46a..00000000
--- a/src/layout/actions.rs
+++ /dev/null
@@ -1,166 +0,0 @@
-//! Drawing and configuration actions composing layouts.
-
-use std::fmt::{self, Debug, Formatter};
-
-#[cfg(feature = "serialize")]
-use serde::ser::{Serialize, Serializer, SerializeTuple};
-
-use fontdock::FaceId;
-use crate::geom::Size;
-use super::Layout;
-use self::LayoutAction::*;
-
-/// A layouting action, which is the basic building block layouts are composed
-/// of.
-#[derive(Clone, PartialEq)]
-pub enum LayoutAction {
- /// Move to an absolute position.
- MoveAbsolute(Size),
- /// Set the font given the index from the font loader and font size.
- SetFont(FaceId, f64),
- /// Write text at the current position.
- WriteText(String),
- /// Visualize a box for debugging purposes.
- DebugBox(Size),
-}
-
-#[cfg(feature = "serialize")]
-impl Serialize for LayoutAction {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
- match self {
- LayoutAction::MoveAbsolute(pos) => {
- let mut tup = serializer.serialize_tuple(2)?;
- tup.serialize_element(&0u8)?;
- tup.serialize_element(&pos)?;
- tup.end()
- }
- LayoutAction::SetFont(id, size) => {
- let mut tup = serializer.serialize_tuple(4)?;
- tup.serialize_element(&1u8)?;
- tup.serialize_element(id)?;
- tup.serialize_element(size)?;
- tup.end()
- }
- LayoutAction::WriteText(text) => {
- let mut tup = serializer.serialize_tuple(2)?;
- tup.serialize_element(&2u8)?;
- tup.serialize_element(text)?;
- tup.end()
- }
- LayoutAction::DebugBox(size) => {
- let mut tup = serializer.serialize_tuple(2)?;
- tup.serialize_element(&3u8)?;
- tup.serialize_element(&size)?;
- tup.end()
- }
- }
- }
-}
-
-impl Debug for LayoutAction {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- use LayoutAction::*;
- match self {
- MoveAbsolute(s) => write!(f, "move {} {}", s.x, s.y),
- SetFont(id, s) => write!(f, "font {}-{} {}", id.index, id.variant, s),
- WriteText(s) => write!(f, "write {:?}", s),
- DebugBox(s) => write!(f, "box {} {}", s.x, s.y),
- }
- }
-}
-
-/// A sequence of layouting actions.
-///
-/// The sequence of actions is optimized as the actions are added. For example,
-/// a font changing option will only be added if the selected font is not
-/// already active. All configuration actions (like moving, setting fonts, ...)
-/// are only flushed when content is written.
-///
-/// Furthermore, the action list can translate absolute position into a
-/// coordinate system with a different origin. This is realized in the
-/// `add_layout` method, which allows a layout to be added at a position,
-/// effectively translating all movement actions inside the layout by the
-/// position.
-#[derive(Debug, Clone, PartialEq)]
-pub struct LayoutActions {
- origin: Size,
- actions: Vec<LayoutAction>,
- active_font: (FaceId, f64),
- next_pos: Option<Size>,
- next_font: Option<(FaceId, f64)>,
-}
-
-impl LayoutActions {
- /// Create a new action list.
- pub fn new() -> LayoutActions {
- LayoutActions {
- actions: vec![],
- origin: Size::ZERO,
- active_font: (FaceId::MAX, 0.0),
- next_pos: None,
- next_font: None,
- }
- }
-
- /// Add an action to the list.
- pub fn add(&mut self, action: LayoutAction) {
- match action {
- MoveAbsolute(pos) => self.next_pos = Some(self.origin + pos),
- SetFont(index, size) => {
- self.next_font = Some((index, size));
- }
-
- _ => {
- self.flush_position();
- self.flush_font();
-
- self.actions.push(action);
- }
- }
- }
-
- /// Add a series of actions.
- pub fn extend<I>(&mut self, actions: I) where I: IntoIterator<Item = LayoutAction> {
- for action in actions.into_iter() {
- self.add(action);
- }
- }
-
- /// Add a layout at a position. All move actions inside the layout are
- /// translated by the position.
- pub fn add_layout(&mut self, position: Size, layout: Layout) {
- self.flush_position();
-
- self.origin = position;
- self.next_pos = Some(position);
-
- self.extend(layout.actions);
- }
-
- /// Whether there are any actions in this list.
- pub fn is_empty(&self) -> bool {
- self.actions.is_empty()
- }
-
- /// Return the list of actions as a vector.
- pub fn into_vec(self) -> Vec<LayoutAction> {
- self.actions
- }
-
- /// Append a cached move action if one is cached.
- fn flush_position(&mut self) {
- if let Some(target) = self.next_pos.take() {
- self.actions.push(MoveAbsolute(target));
- }
- }
-
- /// Append a cached font-setting action if one is cached.
- fn flush_font(&mut self) {
- if let Some((index, size)) = self.next_font.take() {
- if (index, size) != self.active_font {
- self.actions.push(SetFont(index, size));
- self.active_font = (index, size);
- }
- }
- }
-}
diff --git a/src/layout/elements.rs b/src/layout/elements.rs
new file mode 100644
index 00000000..e524e1fd
--- /dev/null
+++ b/src/layout/elements.rs
@@ -0,0 +1,84 @@
+//! The elements layouts are composed of.
+
+use std::fmt::{self, Debug, Formatter};
+
+use ttf_parser::GlyphId;
+use fontdock::FaceId;
+use crate::geom::Size;
+
+/// A sequence of positioned layout elements.
+#[derive(Debug, Clone, PartialEq)]
+pub struct LayoutElements(pub Vec<(Size, LayoutElement)>);
+
+impl LayoutElements {
+ /// Create an empty sequence.
+ pub fn new() -> Self {
+ LayoutElements(vec![])
+ }
+
+ /// Add an element at a position.
+ pub fn push(&mut self, pos: Size, element: LayoutElement) {
+ self.0.push((pos, element));
+ }
+
+ /// Add a sequence of elements offset by an `offset`.
+ pub fn extend_offset(&mut self, offset: Size, more: Self) {
+ for (subpos, element) in more.0 {
+ self.0.push((subpos + offset, element));
+ }
+ }
+}
+
+impl Default for LayoutElements {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+/// A layouting action, which is the basic building block layouts are composed
+/// of.
+#[derive(Debug, Clone, PartialEq)]
+pub enum LayoutElement {
+ /// Shaped text.
+ Text(Shaped),
+}
+
+/// A shaped run of text.
+#[derive(Clone, PartialEq)]
+pub struct Shaped {
+ pub text: String,
+ pub face: FaceId,
+ pub glyphs: Vec<GlyphId>,
+ pub offsets: Vec<f64>,
+ pub size: f64,
+}
+
+impl Shaped {
+ /// Create an empty shape run.
+ pub fn new(face: FaceId, size: f64) -> Shaped {
+ Shaped {
+ text: String::new(),
+ face,
+ glyphs: vec![],
+ offsets: vec![],
+ size,
+ }
+ }
+
+ /// Encode the glyph ids into a big-endian byte buffer.
+ pub fn encode_glyphs(&self) -> Vec<u8> {
+ const BYTES_PER_GLYPH: usize = 2;
+ let mut bytes = Vec::with_capacity(BYTES_PER_GLYPH * self.glyphs.len());
+ for g in &self.glyphs {
+ bytes.push((g.0 >> 8) as u8);
+ bytes.push((g.0 & 0xff) as u8);
+ }
+ bytes
+ }
+}
+
+impl Debug for Shaped {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "Shaped({})", self.text)
+ }
+}
diff --git a/src/layout/line.rs b/src/layout/line.rs
index aafdab60..6b2fd3c6 100644
--- a/src/layout/line.rs
+++ b/src/layout/line.rs
@@ -35,9 +35,6 @@ pub struct LineContext {
pub align: LayoutAlign,
/// Whether to have repeated spaces or to use only the first and only once.
pub repeat: bool,
- /// Whether to output a command which renders a debugging box showing the
- /// extent of the layout.
- pub debug: bool,
/// The line spacing.
pub line_spacing: f64,
}
@@ -73,7 +70,6 @@ impl LineLayouter {
axes: ctx.axes,
align: ctx.align,
repeat: ctx.repeat,
- debug: ctx.debug,
}),
ctx,
run: LineRun::new(),
@@ -252,9 +248,9 @@ impl LineLayouter {
/// Finish the line and start a new one.
pub fn finish_line(&mut self) {
- let mut actions = LayoutActions::new();
+ let mut elements = LayoutElements::new();
- let layouts = std::mem::replace(&mut self.run.layouts, vec![]);
+ let layouts = std::mem::take(&mut self.run.layouts);
for (offset, layout) in layouts {
let x = match self.ctx.axes.primary.is_positive() {
true => offset,
@@ -264,14 +260,14 @@ impl LineLayouter {
};
let pos = Size::with_x(x);
- actions.add_layout(pos, layout);
+ elements.extend_offset(pos, layout.elements);
}
self.stack.add(Layout {
dimensions: self.run.size.specialized(self.ctx.axes),
align: self.run.align
.unwrap_or(LayoutAlign::new(Start, Start)),
- actions: actions.into_vec(),
+ elements
});
self.run = LineRun::new();
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 64a2825b..41a314f0 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -2,17 +2,14 @@
use std::fmt::{self, Display, Formatter};
-#[cfg(feature = "serialize")]
-use serde::Serialize;
-
-use fontdock::FaceId;
use crate::geom::{Size, Margins};
-use self::prelude::*;
+use elements::LayoutElements;
+use prelude::*;
pub mod line;
pub mod stack;
pub mod text;
-pub_use_mod!(actions);
+pub mod elements;
pub_use_mod!(model);
/// Basic types used across the layouting engine.
@@ -33,30 +30,13 @@ pub type MultiLayout = Vec<Layout>;
/// A finished box with content at fixed positions.
#[derive(Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct Layout {
/// The size of the box.
pub dimensions: Size,
/// How to align this layout in a parent container.
- #[cfg_attr(feature = "serialize", serde(skip))]
pub align: LayoutAlign,
/// The actions composing this layout.
- pub actions: Vec<LayoutAction>,
-}
-
-impl Layout {
- /// Returns a vector with all used font indices.
- pub fn find_used_fonts(&self) -> Vec<FaceId> {
- let mut fonts = Vec::new();
- for action in &self.actions {
- if let &LayoutAction::SetFont(id, _) = action {
- if !fonts.contains(&id) {
- fonts.push(id);
- }
- }
- }
- fonts
- }
+ pub elements: LayoutElements,
}
/// A vector of layout spaces, that is stack allocated as long as it only
diff --git a/src/layout/model.rs b/src/layout/model.rs
index bde451e6..db069870 100644
--- a/src/layout/model.rs
+++ b/src/layout/model.rs
@@ -46,8 +46,6 @@ pub struct LayoutContext<'a> {
/// Whether the layout that is to be created will be nested in a parent
/// container.
pub nested: bool,
- /// Whether to render debug boxs around layouts if `nested` is true.
- pub debug: bool,
}
/// A sequence of layouting commands.
@@ -117,7 +115,6 @@ impl<'a> ModelLayouter<'a> {
axes: ctx.axes,
align: ctx.align,
repeat: ctx.repeat,
- debug: ctx.debug && ctx.nested,
line_spacing: ctx.style.text.line_spacing(),
}),
style: ctx.style.clone(),
diff --git a/src/layout/stack.rs b/src/layout/stack.rs
index 48c7b40a..4f4d3d8b 100644
--- a/src/layout/stack.rs
+++ b/src/layout/stack.rs
@@ -48,9 +48,6 @@ pub struct StackContext {
pub align: LayoutAlign,
/// Whether to have repeated spaces or to use only the first and only once.
pub repeat: bool,
- /// Whether to output a command which renders a debugging box showing the
- /// extent of the layout.
- pub debug: bool,
}
/// A layout space composed of subspaces which can have different axes and
@@ -139,7 +136,7 @@ impl StackLayouter {
self.space.layouts.push((self.ctx.axes, Layout {
dimensions: dimensions.specialized(self.ctx.axes),
align: LayoutAlign::new(Start, Start),
- actions: vec![]
+ elements: LayoutElements::new(),
}));
self.space.last_spacing = LastSpacing::Hard;
@@ -367,13 +364,9 @@ impl StackLayouter {
// Step 4: Align each layout in its bounding box and collect everything
// into a single finished layout.
- let mut actions = LayoutActions::new();
+ let mut elements = LayoutElements::new();
- if self.ctx.debug {
- actions.add(LayoutAction::DebugBox(dimensions));
- }
-
- let layouts = std::mem::replace(&mut self.space.layouts, vec![]);
+ let layouts = std::mem::take(&mut self.space.layouts);
for ((axes, layout), bound) in layouts.into_iter().zip(bounds) {
let size = layout.dimensions.specialized(axes);
let align = layout.align;
@@ -387,13 +380,13 @@ impl StackLayouter {
let local = usable.anchor(align, axes) - size.anchor(align, axes);
let pos = Size::new(bound.left, bound.top) + local.specialized(axes);
- actions.add_layout(pos, layout);
+ elements.extend_offset(pos, layout.elements);
}
self.layouts.push(Layout {
dimensions,
align: self.ctx.align,
- actions: actions.into_vec(),
+ elements,
});
// ------------------------------------------------------------------ //
diff --git a/src/layout/text.rs b/src/layout/text.rs
index 6698a0fa..477099e2 100644
--- a/src/layout/text.rs
+++ b/src/layout/text.rs
@@ -4,10 +4,12 @@
//! When the primary layouting axis horizontally inversed, the word is spelled
//! backwards. Vertical word layout is not yet supported.
+use ttf_parser::GlyphId;
use fontdock::{FaceId, FaceQuery, FontStyle};
use crate::font::SharedFontLoader;
use crate::geom::Size;
use crate::style::TextStyle;
+use super::elements::{LayoutElement, Shaped};
use super::*;
/// Performs the text layouting.
@@ -15,9 +17,9 @@ use super::*;
struct TextLayouter<'a> {
ctx: TextContext<'a>,
text: &'a str,
- actions: LayoutActions,
- buffer: String,
- active_font: FaceId,
+ shaped: Shaped,
+ elements: LayoutElements,
+ start: f64,
width: f64,
}
@@ -48,9 +50,9 @@ impl<'a> TextLayouter<'a> {
TextLayouter {
ctx,
text,
- actions: LayoutActions::new(),
- buffer: String::new(),
- active_font: FaceId::MAX,
+ shaped: Shaped::new(FaceId::MAX, ctx.style.font_size()),
+ elements: LayoutElements::new(),
+ start: 0.0,
width: 0.0,
}
}
@@ -69,45 +71,53 @@ impl<'a> TextLayouter<'a> {
}
// Flush the last buffered parts of the word.
- if !self.buffer.is_empty() {
- self.actions.add(LayoutAction::WriteText(self.buffer));
+ if !self.shaped.text.is_empty() {
+ let pos = Size::new(self.start, 0.0);
+ self.elements.push(pos, LayoutElement::Text(self.shaped));
}
Layout {
dimensions: Size::new(self.width, self.ctx.style.font_size()),
align: self.ctx.align,
- actions: self.actions.into_vec(),
+ elements: self.elements,
}
}
/// Layout an individual character.
async fn layout_char(&mut self, c: char) {
- let (index, char_width) = match self.select_font(c).await {
+ let (index, glyph, char_width) = match self.select_font(c).await {
Some(selected) => selected,
// TODO: Issue warning about missing character.
None => return,
};
- self.width += char_width;
-
// Flush the buffer and issue a font setting action if the font differs
// from the last character's one.
- if self.active_font != index {
- if !self.buffer.is_empty() {
- let text = std::mem::replace(&mut self.buffer, String::new());
- self.actions.add(LayoutAction::WriteText(text));
+ if self.shaped.face != index {
+ if !self.shaped.text.is_empty() {
+ let pos = Size::new(self.start, 0.0);
+ let shaped = std::mem::replace(
+ &mut self.shaped,
+ Shaped::new(FaceId::MAX, self.ctx.style.font_size()),
+ );
+
+ self.elements.push(pos, LayoutElement::Text(shaped));
+ self.start = self.width;
}
- self.actions.add(LayoutAction::SetFont(index, self.ctx.style.font_size()));
- self.active_font = index;
+ self.shaped.face = index;
}
- self.buffer.push(c);
+ self.shaped.text.push(c);
+ self.shaped.glyphs.push(glyph);
+ self.shaped.offsets.push(self.width);
+
+ self.width += char_width;
}
/// Select the best font for a character and return its index along with
/// the width of the char in the font.
- async fn select_font(&mut self, c: char) -> Option<(FaceId, f64)> {
+ async fn select_font(&mut self, c: char) -> Option<(FaceId, GlyphId, f64)> {
let mut loader = self.ctx.loader.borrow_mut();
let mut variant = self.ctx.style.variant;
@@ -140,7 +150,7 @@ impl<'a> TextLayouter<'a> {
let glyph_width = face.glyph_hor_advance(glyph)?;
let char_width = to_raw(glyph_width) * self.ctx.style.font_size();
- Some((id, char_width))
+ Some((id, glyph, char_width))
} else {
None
}