Skip to content

Commit

Permalink
List item support (#134)
Browse files Browse the repository at this point in the history
* Work on rendering list items

* Make collected list items show

* Renders different markers, but no font

* Render alphanumeric bullets

* Right align and pad markers

* remove unused import

* Register moz bullet font with fontique

* Use bullet font

* Use list-style-type from the <li> rather than the <ul> or <ol>

* Use comrak fork that generates classes for tasklists

* Remove unused bullet_family_id property

* Simplify node_list_item_child return type

* Fix clippy warnings

* Add tests, support for disclosure list styles

* Don't nest example lis in ols

* Update text example

* Attempt to render single glyphs without constructing parley layout

* Scale font size

* Attempt to center glyphs in line

* add some comments

* Fix clippy warnings

---------

Co-authored-by: Nico Burns <[email protected]>
  • Loading branch information
cfraz89 and nicoburns committed Sep 16, 2024
1 parent ac37659 commit 0c49b8a
Show file tree
Hide file tree
Showing 10 changed files with 499 additions and 69 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ incremental = false
# mozbuild = "0.1.0"
blitz = { path = "./packages/blitz" }
blitz-dom = { path = "./packages/dom" }
comrak = { version = "0.21.0", default-features = false, features = ["syntect"] }
comrak = { git = "https://github.com/nicoburns/comrak", branch = "tasklist-class", default-features = false, features = ["syntect"] }
png = { version = "0.17" }
dioxus-blitz = { path = "./packages/dioxus-blitz" }
dioxus = { workspace = true }
Expand Down
62 changes: 62 additions & 0 deletions examples/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,73 @@ fn app() -> Element {
em { "Another block of text" }
"Should connect no space between"
}
h1 { "ul" }
ul {
li { "Item 1" }
li { "Item 2" }
li {
class: "square",
"Square item"
}
li {
class: "circle",
"Circle item"
}
li {
class: "disclosure-open",
"Disclosure open item"
}
li {
class: "disclosure-closed",
"Disclosure closed item"
}
}
h1 { "ol - decimal" }
ol {
li { "Item 1" }
li { "Item 2" }
li {
ul {
li { "Nested Item 1" }
li { "Nested Item 2" }
}
}
li { "Item 3" }
li { "Item 4" }
ol {
li { "Sub 1" }
li { "Sub 2" }
}
}
h1 { "ol - alpha" }
ol { class: "alpha",
li { "Item 1" }
li { "Item 2" }
li { "Item 3" }
}
}
}
}

const CSS: &str = r#"
#a {
}
h1 {
font-size: 20px;
}
ol.alpha {
list-style-type: lower-alpha;
}
li.square {
list-style-type: square;
}
li.circle {
list-style-type: circle;
}
li.disclosure-open {
list-style-type: disclosure-open;
}
li.disclosure-closed {
list-style-type: disclosure-closed;
}
"#;
99 changes: 95 additions & 4 deletions packages/blitz/src/renderer/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use crate::{
devtools::Devtools,
util::{GradientSlice, StyloGradient, ToVelloColor},
};
use blitz_dom::node::{NodeData, TextBrush, TextInputData, TextNodeData};
use blitz_dom::node::{
ListItemLayout, ListItemLayoutPosition, NodeData, TextBrush, TextInputData, TextNodeData,
};
use blitz_dom::{local_name, Document, Node};

use style::{
Expand Down Expand Up @@ -44,8 +46,11 @@ use style::values::generics::image::{
};
use style::values::specified::percentage::ToPercentage;
use taffy::prelude::Layout;
use vello::glyph::skrifa::prelude::{NormalizedCoord, Size};
use vello::glyph::skrifa::{FontRef, GlyphId};
use vello::kurbo::{BezPath, Cap, Join};
use vello::peniko::Gradient;
use vello::skrifa::MetadataProvider;
use vello::{
kurbo::{Affine, Point, Rect, Shape, Stroke, Vec2},
peniko::{self, Brush, Color, Fill, Mix},
Expand Down Expand Up @@ -273,7 +278,7 @@ impl<'dom> VelloSceneGenerator<'dom> {
// - background, border, font, margin, outline, padding,
//
// Not Implemented:
// - list, position, table, text, ui,
// - position, table, text, ui,
// - custom_properties, writing_mode, rules, visited_style, flags, box_, column, counters, effects,
// - inherited_box, inherited_table, inherited_text, inherited_ui,
let element = &self.dom.as_ref().tree()[node_id];
Expand Down Expand Up @@ -404,12 +409,68 @@ impl<'dom> VelloSceneGenerator<'dom> {
&line,
);
}
} else if element.is_inline_root {
} else if let Some(ListItemLayout {
marker: _,
position,
}) = cx.list_item
{
match position {
ListItemLayoutPosition::OutsideGlyph {
font,
glyph_id,
font_size_px,
line_height_px,
color,
} => {
let font_ref = FontRef::from_index(font.data.data(), font.index).unwrap();
let variations: &[(&str, f32)] = &[];
let location = font_ref.axes().location(variations);
let glyph_metrics = font_ref.glyph_metrics(Size::new(*font_size_px), &location);
let glyph_width = glyph_metrics
.advance_width(GlyphId::new(*glyph_id))
.unwrap();
let metrics = font_ref.metrics(Size::new(*font_size_px), &location);
let coords = location.coords();
let pos = Point {
// Right align the glyph, and add some gap between the glyph and the following list item text
x: pos.x - glyph_width as f64 - 8.0,
// Center the glyph on the line
y: pos.y
+ (*line_height_px as f64 + metrics.ascent as f64
- metrics.descent as f64)
/ 2.0,
};
cx.stroke_glyph(
scene,
Glyph {
glyph_id: *glyph_id,
font,
font_size: *font_size_px,
coords,
brush: &Brush::Solid(*color),
},
pos,
);
}
ListItemLayoutPosition::OutsideString { layout } => {
//Right align and pad the bullet when rendering outside
let pos = Point {
x: pos.x - (layout.full_width() / layout.scale()) as f64,
y: pos.y,
};
cx.stroke_text(scene, layout, pos);
}
ListItemLayoutPosition::Inside => {}
}
}

if element.is_inline_root {
let text_layout = &element
.raw_dom_data
.downcast_element()
.unwrap()
.inline_layout_data()
.inline_layout_data
.as_ref()
.unwrap_or_else(|| {
panic!("Tried to render node marked as inline root that does not have an inline layout: {:?}", element);
});
Expand Down Expand Up @@ -499,11 +560,22 @@ impl<'dom> VelloSceneGenerator<'dom> {
.map(|data| &*data.image),
svg: element.element_data().unwrap().svg_data(),
text_input: element.element_data().unwrap().text_input_data(),
list_item: element.element_data().unwrap().list_item_data.as_deref(),
devtools: &self.devtools,
}
}
}

/// A glyph to be rendered by an ELementCx
#[derive(Debug)]
struct Glyph<'a> {
glyph_id: u16,
font: &'a parley::Font,
font_size: f32,
coords: &'a [NormalizedCoord],
brush: &'a Brush,
}

/// A context of loaded and hot data to draw the element from
struct ElementCx<'a> {
frame: ElementFrame,
Expand All @@ -515,10 +587,29 @@ struct ElementCx<'a> {
image: Option<&'a DynamicImage>,
svg: Option<&'a usvg::Tree>,
text_input: Option<&'a TextInputData>,
list_item: Option<&'a ListItemLayout>,
devtools: &'a Devtools,
}

impl ElementCx<'_> {
fn stroke_glyph(&self, scene: &mut Scene, glyph: Glyph, pos: Point) {
let transform = Affine::translate((pos.x * self.scale, pos.y * self.scale));
scene
.draw_glyphs(glyph.font)
.font_size(glyph.font_size * self.scale as f32)
.normalized_coords(glyph.coords)
.brush(glyph.brush)
.transform(transform)
.draw(
Fill::NonZero,
std::iter::once(vello::glyph::Glyph {
id: glyph.glyph_id as u32,
x: 0.0,
y: 0.0,
}),
)
}

fn stroke_text(&self, scene: &mut Scene, text_layout: &parley::Layout<TextBrush>, pos: Point) {
let transform = Affine::translate((pos.x * self.scale, pos.y * self.scale));

Expand Down
3 changes: 2 additions & 1 deletion packages/dom/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ impl Document {
.raw_dom_data
.downcast_element()
.unwrap()
.inline_layout_data()
.inline_layout_data
.as_ref()
.unwrap();

println!("Text content: {:?}", inline_layout.text);
Expand Down
4 changes: 2 additions & 2 deletions packages/dom/src/default.css
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ h2 {
margin-block-end: .83em;
}

h3{
h3 {
display: block;
font-size: 1.17em;
font-weight: bold;
Expand Down Expand Up @@ -1020,4 +1020,4 @@ slot {
pointer-events: none !important;
background-color: transparent;
}
}
}
17 changes: 16 additions & 1 deletion packages/dom/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::node::{NodeSpecificData, TextBrush};
use crate::{ElementNodeData, Node, NodeData, TextNodeData, Viewport};
use app_units::Au;
use html5ever::local_name;
use parley::fontique::FamilyId;
use peniko::kurbo;
// use quadtree_rs::Quadtree;
use parley::editor::{PointerButton, TextEvent};
Expand Down Expand Up @@ -103,6 +104,9 @@ pub struct Document {

/// A Parley font context
pub(crate) font_ctx: parley::FontContext,

pub(crate) bullet_font_id: FamilyId,

/// A Parley layout context
pub(crate) layout_ctx: parley::LayoutContext<TextBrush>,

Expand Down Expand Up @@ -219,6 +223,8 @@ impl Document {
style_config::set_bool("layout.legacy_layout", true);
style_config::set_bool("layout.columns.enabled", true);

let (font_ctx, bullet_font_id) = Self::create_font_context();

let mut doc = Self {
guard,
nodes,
Expand All @@ -230,7 +236,8 @@ impl Document {
base_url: None,
// quadtree: Quadtree::new(20),
stylesheets: HashMap::new(),
font_ctx: parley::FontContext::default(),
font_ctx,
bullet_font_id,
layout_ctx: parley::LayoutContext::new(),

hover_node_id: None,
Expand All @@ -244,6 +251,14 @@ impl Document {
doc
}

fn create_font_context() -> (parley::FontContext, FamilyId) {
let mut ctx = parley::FontContext::default();
let names = ctx
.collection
.register_fonts(include_bytes!("moz-bullet-font.otf").to_vec());
(ctx, names[0].0)
}

/// Set base url for resolving linked resources (stylesheets, images, fonts, etc)
pub fn set_base_url(&mut self, url: &str) {
self.base_url = Url::parse(url).ok();
Expand Down
Loading

0 comments on commit 0c49b8a

Please sign in to comment.