Skip to content

Commit

Permalink
fix text layout problems
Browse files Browse the repository at this point in the history
  • Loading branch information
Sharktheone committed Jan 8, 2025
1 parent b19d8ae commit e8259d1
Show file tree
Hide file tree
Showing 12 changed files with 220 additions and 167 deletions.
84 changes: 47 additions & 37 deletions crates/gosub_cairo/src/elements/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::CairoBackend;
use gosub_interface::layout::{Decoration, TextLayout};
use gosub_interface::render_backend::{RenderText, Text as TText};
use gosub_shared::font::{Glyph, GlyphID};
use gosub_shared::geo::FP;
use gosub_shared::geo::{Point, FP};
use peniko::Font;
use skrifa::instance::NormalizedCoord;
use std::cell::RefCell;
Expand Down Expand Up @@ -122,6 +122,8 @@ pub struct GsText {
coords: Vec<NormalizedCoord>,
// Text decoration (strike-through, underline, etc.)
decoration: Decoration,
// offset in the element
offset: Point,
}

impl TText for GsText {
Expand Down Expand Up @@ -152,6 +154,7 @@ impl TText for GsText {
fs,
coords,
decoration: layout.decorations().clone(),
offset: layout.offset(),
}
}
}
Expand All @@ -169,7 +172,6 @@ impl GsText {
cr.move_to(base_x, base_y);

// Setup brush for rendering text
GsBrush::render(&obj.brush, cr);

// This should be moved to the GosubFontContext::get_cairo_font_face(family: &str) method)
let font_face = unsafe {
Expand All @@ -184,49 +186,57 @@ impl GsText {
};
cr.set_font_face(&font_face);

cr.set_font_size(obj.text.fs.into());

// Convert glyphs that are in parley / taffy format to cairo glyphs. Also make sure we
// offset the glyphs by the base_x and base_y.
let mut cairo_glyphs = vec![];
for glyph in &obj.text.glyphs {
let cairo_glyph = cairo::Glyph::new(glyph.id as u64, base_x + glyph.x as f64, base_y + glyph.y as f64);
cairo_glyphs.push(cairo_glyph);
}
for text in &obj.text {
GsBrush::render(&obj.brush, cr);
cr.move_to(base_x + text.offset.x as f64, base_y + text.offset.y as f64);
cr.set_font_size(text.fs.into());

// Convert glyphs that are in parley / taffy format to cairo glyphs. Also make sure we
// offset the glyphs by the base_x and base_y.
let mut cairo_glyphs = vec![];
for glyph in &text.glyphs {
let cairo_glyph = cairo::Glyph::new(
glyph.id as u64,
base_x + glyph.x as f64 + text.offset.x as f64,
base_y + glyph.y as f64 + text.offset.y as f64,
);
cairo_glyphs.push(cairo_glyph);
}

_ = cr.show_glyphs(&cairo_glyphs);
_ = cr.show_glyphs(&cairo_glyphs);

// Set decoration (underline, overline, line-through)
{
let decoration = &obj.text.decoration;
let _stroke = Stroke::new(decoration.width as f64);
// Set decoration (underline, overline, line-through)
{
let decoration = &text.decoration;
let _stroke = Stroke::new(decoration.width as f64);

let c = decoration.color;
let brush = GsBrush::solid(GsColor::rgba32(c.0, c.1, c.2, 1.0));
GsBrush::render(&brush, cr);
let c = decoration.color;
let brush = GsBrush::solid(GsColor::rgba32(c.0, c.1, c.2, 1.0));
GsBrush::render(&brush, cr);

let offset = decoration.x_offset as f64;
if decoration.underline {
let y = base_y + decoration.underline_offset as f64;
let offset = decoration.x_offset as f64;
if decoration.underline {
let y = base_y + decoration.underline_offset as f64 + obj.rect.height;

cr.move_to(base_x + offset, y);
cr.line_to(base_x + obj.rect.width, y);
_ = cr.stroke();
}
if decoration.overline {
let y = base_y - obj.rect.height;
cr.move_to(base_x + offset, y);
cr.line_to(base_x + obj.rect.width, y);
_ = cr.stroke();
}
if decoration.overline {
let y = base_y - obj.rect.height;

cr.move_to(base_x + offset, y);
cr.line_to(base_x + obj.rect.width, y);
_ = cr.stroke();
}
cr.move_to(base_x + offset, y);
cr.line_to(base_x + obj.rect.width, y);
_ = cr.stroke();
}

if decoration.line_through {
let y = base_y - obj.rect.height / 2.0;
if decoration.line_through {
let y = base_y + obj.rect.height / 2.0;

cr.move_to(base_x + offset, y);
cr.line_to(base_x + obj.rect.width, y);
_ = cr.stroke();
cr.move_to(base_x + offset, y);
cr.line_to(base_x + obj.rect.width, y);
_ = cr.stroke();
}
}
}
}
Expand Down
13 changes: 7 additions & 6 deletions crates/gosub_interface/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub trait Layouter: Sized + Clone + Send + 'static {
type Cache: LayoutCache;
type Layout: Layout + Send;

type TextLayout: TextLayout + Send;
type TextLayout: TextLayout + Send + Debug;

const COLLAPSE_INLINE: bool;

Expand Down Expand Up @@ -127,21 +127,20 @@ pub trait LayoutNode<C: HasLayouter>: HasTextLayout<C> {
fn get_property(&self, name: &str) -> Option<&C::CssProperty>;
fn text_data(&self) -> Option<&str>;

fn text_size(&self) -> Option<Size>;

/// This can only return true if the `Layout::COLLAPSE_INLINE` is set true for the layouter
///
fn is_anon_inline_parent(&self) -> bool;
}

pub trait HasTextLayout<C: HasLayouter> {
fn set_text_layout(&mut self, layout: <C::Layouter as Layouter>::TextLayout);
fn clear_text_layout(&mut self);
fn add_text_layout(&mut self, layout: <C::Layouter as Layouter>::TextLayout);
fn get_text_layouts(&self) -> Option<&[<C::Layouter as Layouter>::TextLayout]>;
fn get_text_layouts_mut(&mut self) -> Option<&mut Vec<<C::Layouter as Layouter>::TextLayout>>;
}

pub trait TextLayout {
type Font: Font;
fn dbg_layout(&self) -> String;

fn size(&self) -> Size;

fn glyphs(&self) -> &[Glyph];
Expand All @@ -153,6 +152,8 @@ pub trait TextLayout {
fn coords(&self) -> &[i16];

fn decorations(&self) -> &Decoration;

fn offset(&self) -> Point;
}

#[derive(Debug, Clone, Default)]
Expand Down
4 changes: 2 additions & 2 deletions crates/gosub_interface/src/render_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,15 @@ impl<B: RenderBackend> RenderRect<B> {

#[derive(Clone, Debug)]
pub struct RenderText<B: RenderBackend> {
pub text: B::Text,
pub text: Vec<B::Text>,
pub rect: B::Rect,
pub transform: Option<B::Transform>,
pub brush: B::Brush,
pub brush_transform: Option<B::Transform>,
}

impl<B: RenderBackend> RenderText<B> {
pub fn new(text: B::Text, rect: B::Rect, brush: B::Brush) -> Self {
pub fn new(text: Vec<B::Text>, rect: B::Rect, brush: B::Brush) -> Self {
Self {
text,
rect,
Expand Down
2 changes: 1 addition & 1 deletion crates/gosub_interface/src/render_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ pub trait RenderTreeNode<C: HasLayouter>: Debug {
fn layout_mut(&mut self) -> &mut <C::Layouter as Layouter>::Layout;

fn element_attributes(&self) -> Option<&HashMap<String, String>>;
fn text_data(&self) -> Option<(&str, Option<&<C::Layouter as Layouter>::TextLayout>)>;
fn text_data(&self) -> Option<(&str, &[<C::Layouter as Layouter>::TextLayout])>;
fn name(&self) -> &str;
}
18 changes: 10 additions & 8 deletions crates/gosub_renderer/src/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use gosub_rendering::render_tree::RenderTree;
use gosub_shared::geo::{Size, SizeU32, FP};
use gosub_shared::node::NodeId;
use gosub_shared::types::Result;
use log::{error, info, warn};
use log::{error, info};
use std::future::Future;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex};
Expand Down Expand Up @@ -495,13 +495,15 @@ fn render_text<C: HasDrawComponents>(
.unwrap_or(Color::BLACK);

if let Some((_, layout)) = &node.text_data() {
let Some(layout) = layout else {
warn!("No layout for text node");
return;
};

let text: <C::RenderBackend as RenderBackend>::Text =
Text::new::<<C::Layouter as Layouter>::TextLayout>(layout);
let text = layout
.iter()
.map(|layout| {
let text: <C::RenderBackend as RenderBackend>::Text =
Text::new::<<C::Layouter as Layouter>::TextLayout>(layout);

text
})
.collect::<Vec<_>>();

let size = node.layout().size();

Expand Down
2 changes: 1 addition & 1 deletion crates/gosub_renderer/src/draw/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub(crate) fn test_add_element<C: HasDrawComponents<RenderTree = RenderTree<C>,
"#text".to_string(),
RenderNodeData::Text(Box::new(TextData {
text: "test add element".to_string(),
layout: None,
layout: Vec::new(),
})),
props,
);
Expand Down
50 changes: 33 additions & 17 deletions crates/gosub_rendering/src/render_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ use gosub_interface::config::{HasDocument, HasLayouter, HasRenderTree};
use gosub_interface::css3::{CssProperty, CssPropertyMap, CssSystem};
use gosub_interface::document::Document;
use gosub_interface::document_handle::DocumentHandle;
use gosub_interface::layout::{HasTextLayout, Layout, LayoutCache, LayoutNode, LayoutTree, Layouter, TextLayout};
use gosub_interface::layout::{HasTextLayout, Layout, LayoutCache, LayoutNode, LayoutTree, Layouter};
use gosub_interface::node::NodeData;
use gosub_interface::node::{ElementDataType, Node as DocumentNode, TextDataType};
use gosub_interface::render_backend::Size;
use gosub_interface::render_tree;
use gosub_shared::node::NodeId;
use gosub_shared::types::Result;
Expand Down Expand Up @@ -603,9 +602,9 @@ impl<C: HasLayouter> render_tree::RenderTreeNode<C> for RenderTreeNode<C> {
None
}

fn text_data(&self) -> Option<(&str, Option<&<C::Layouter as Layouter>::TextLayout>)> {
fn text_data(&self) -> Option<(&str, &[<C::Layouter as Layouter>::TextLayout])> {
if let RenderNodeData::Text(data) = &self.data {
return Some((&data.text, data.layout.as_ref()));
return Some((&data.text, &data.layout));
}

None
Expand Down Expand Up @@ -654,14 +653,14 @@ impl<L: Layouter> Debug for RenderNodeData<L> {

pub struct TextData<L: Layouter> {
pub text: String,
pub layout: Option<L::TextLayout>,
pub layout: Vec<L::TextLayout>,
}

impl<L: Layouter> Debug for TextData<L> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TextData")
.field("text", &self.text)
.field("layout", &self.layout.as_ref().map(|x| x.dbg_layout()))
.field("layout", &self.layout)
.finish()
}
}
Expand All @@ -681,7 +680,10 @@ impl<L: Layouter> RenderNodeData<L> {
NodeData::Text(data) => {
let text = pre_transform_text(data.string_value());

RenderNodeData::Text(Box::new(TextData { text, layout: None }))
RenderNodeData::Text(Box::new(TextData {
text,
layout: Vec::new(),
}))
}
NodeData::Document(_) => RenderNodeData::Document,
_ => return ControlFlow::Drop,
Expand Down Expand Up @@ -780,28 +782,42 @@ impl<C: HasLayouter> RenderTreeNode<C> {
}

impl<C: HasLayouter> HasTextLayout<C> for RenderTreeNode<C> {
fn set_text_layout(&mut self, layout: <C::Layouter as Layouter>::TextLayout) {
fn clear_text_layout(&mut self) {
if let RenderNodeData::Text(text) = &mut self.data {
text.layout = Some(layout);
text.layout.clear();
}
}
}

impl<C: HasLayouter> LayoutNode<C> for RenderTreeNode<C> {
fn get_property(&self, name: &str) -> Option<&C::CssProperty> {
self.properties.get(name)
fn add_text_layout(&mut self, layout: <C::Layouter as Layouter>::TextLayout) {
if let RenderNodeData::Text(text) = &mut self.data {
text.layout.push(layout);
}
}
fn text_data(&self) -> Option<&str> {

fn get_text_layouts(&self) -> Option<&[<C::Layouter as Layouter>::TextLayout]> {
if let RenderNodeData::Text(text) = &self.data {
Some(&text.text)
Some(&text.layout)
} else {
None
}
}

fn get_text_layouts_mut(&mut self) -> Option<&mut Vec<<C::Layouter as Layouter>::TextLayout>> {
if let RenderNodeData::Text(text) = &mut self.data {
Some(&mut text.layout)
} else {
None
}
}
}

fn text_size(&self) -> Option<Size> {
impl<C: HasLayouter> LayoutNode<C> for RenderTreeNode<C> {
fn get_property(&self, name: &str) -> Option<&C::CssProperty> {
self.properties.get(name)
}
fn text_data(&self) -> Option<&str> {
if let RenderNodeData::Text(text) = &self.data {
text.layout.as_ref().map(|layout| layout.size())
Some(&text.text)
} else {
None
}
Expand Down
Loading

0 comments on commit e8259d1

Please sign in to comment.