diff --git a/library/src/basics/list.rs b/library/src/basics/list.rs index e35fe9fa8..f5a589772 100644 --- a/library/src/basics/list.rs +++ b/library/src/basics/list.rs @@ -216,7 +216,7 @@ pub const ENUM: ListKind = 1; pub const DESC: ListKind = 2; /// How to label a list or enumeration. -#[derive(Debug, Clone, PartialEq, Hash)] +#[derive(Debug, Clone, Hash)] pub enum Label { /// The default labelling. Default, diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs index e3691732e..047d78f9d 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -59,9 +59,11 @@ impl LayoutRoot for Content { fn cached( node: &Content, world: comemo::Tracked, + provider: TrackedMut, + introspector: Tracked, styles: StyleChain, ) -> SourceResult { - let mut vt = Vt { world }; + let mut vt = Vt { world, provider, introspector }; let scratch = Scratch::default(); let (realized, styles) = realize_root(&mut vt, &scratch, node, styles)?; realized @@ -70,7 +72,13 @@ impl LayoutRoot for Content { .layout_root(&mut vt, styles) } - cached(self, vt.world, styles) + cached( + self, + vt.world, + TrackedMut::reborrow_mut(&mut vt.provider), + vt.introspector, + styles, + ) } } @@ -97,10 +105,12 @@ impl Layout for Content { fn cached( node: &Content, world: comemo::Tracked, + provider: TrackedMut, + introspector: Tracked, styles: StyleChain, regions: &Regions, ) -> SourceResult { - let mut vt = Vt { world }; + let mut vt = Vt { world, provider, introspector }; let scratch = Scratch::default(); let (realized, styles) = realize_block(&mut vt, &scratch, node, styles)?; let barrier = Style::Barrier(realized.id()); @@ -111,7 +121,14 @@ impl Layout for Content { .layout(&mut vt, styles, regions) } - cached(self, vt.world, styles, regions) + cached( + self, + vt.world, + TrackedMut::reborrow_mut(&mut vt.provider), + vt.introspector, + styles, + regions, + ) } } @@ -290,6 +307,15 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> { content: &'a Content, styles: StyleChain<'a>, ) -> SourceResult<()> { + // Prepare only if this is the first application for this node. + if let Some(node) = content.with::() { + if !content.is_prepared() { + let prepared = node.prepare(self.vt, content.clone().prepared(), styles); + let stored = self.scratch.content.alloc(prepared); + return self.accept(stored, styles); + } + } + if let Some(styled) = content.to::() { return self.styled(styled, styles); } diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs index 08411ad1c..dee77abd1 100644 --- a/library/src/layout/page.rs +++ b/library/src/layout/page.rs @@ -5,7 +5,7 @@ use crate::prelude::*; use crate::text::TextNode; /// Layouts its child onto one or multiple pages. -#[derive(PartialEq, Clone, Hash)] +#[derive(Clone, Hash)] pub struct PageNode(pub Content); #[node] @@ -157,7 +157,7 @@ impl PagebreakNode { } /// A header, footer, foreground or background definition. -#[derive(Debug, Clone, PartialEq, Hash)] +#[derive(Debug, Clone, Hash)] pub enum Marginal { /// Nothing, None, diff --git a/library/src/layout/par.rs b/library/src/layout/par.rs index e96845dd0..78e9b2a3c 100644 --- a/library/src/layout/par.rs +++ b/library/src/layout/par.rs @@ -57,13 +57,15 @@ impl ParNode { fn cached( par: &ParNode, world: Tracked, + provider: TrackedMut, + introspector: Tracked, styles: StyleChain, consecutive: bool, width: Abs, base: Size, expand: bool, ) -> SourceResult { - let mut vt = Vt { world }; + let mut vt = Vt { world, provider, introspector }; // Collect all text into one string for BiDi analysis. let (text, segments) = collect(par, &styles, consecutive); @@ -80,7 +82,17 @@ impl ParNode { finalize(&mut vt, &p, &lines, width, base, expand) } - cached(self, vt.world, styles, consecutive, width, base, expand) + cached( + self, + vt.world, + TrackedMut::reborrow_mut(&mut vt.provider), + vt.introspector, + styles, + consecutive, + width, + base, + expand, + ) } } diff --git a/library/src/meta/document.rs b/library/src/meta/document.rs index 771816713..fbb62b6b5 100644 --- a/library/src/meta/document.rs +++ b/library/src/meta/document.rs @@ -27,11 +27,9 @@ impl LayoutRoot for DocumentNode { } Ok(Document { - metadata: Metadata { - title: styles.get(Self::TITLE).clone(), - author: styles.get(Self::AUTHOR).clone(), - }, pages, + title: styles.get(Self::TITLE).clone(), + author: styles.get(Self::AUTHOR).clone(), }) } } diff --git a/library/src/meta/link.rs b/library/src/meta/link.rs index e7d217e75..521633710 100644 --- a/library/src/meta/link.rs +++ b/library/src/meta/link.rs @@ -25,10 +25,6 @@ impl LinkNode { #[node(Show, Finalize)] impl LinkNode { - /// A destination the text should be linked to. - #[property(skip, referenced)] - pub(crate) const DEST: Option = None; - fn construct(_: &Vm, args: &mut Args) -> SourceResult { let dest = args.expect::("destination")?; Ok(match dest { @@ -61,6 +57,6 @@ impl Show for LinkNode { impl Finalize for LinkNode { fn finalize(&self, realized: Content) -> Content { - realized.styled(Self::DEST, Some(self.dest.clone())) + realized.styled(Meta::DATA, vec![Meta::Link(self.dest.clone())]) } } diff --git a/library/src/prelude.rs b/library/src/prelude.rs index e7d824cc0..26a3c6c32 100644 --- a/library/src/prelude.rs +++ b/library/src/prelude.rs @@ -16,8 +16,9 @@ pub use typst::geom::*; #[doc(no_inline)] pub use typst::model::{ array, capability, castable, dict, dynamic, format_str, node, Args, Array, Cast, - Content, Dict, Finalize, Fold, Func, Label, Node, NodeId, Resolve, Selector, Show, - Smart, Str, StyleChain, StyleMap, StyleVec, Unlabellable, Value, Vm, Vt, + Content, Dict, Finalize, Fold, Func, Introspector, Label, Node, NodeId, Prepare, + Resolve, Selector, Show, Smart, StabilityProvider, Str, StyleChain, StyleMap, + StyleVec, Unlabellable, Value, Vm, Vt, }; #[doc(no_inline)] pub use typst::syntax::{Span, Spanned}; diff --git a/library/src/shared/ext.rs b/library/src/shared/ext.rs index 2d2dc751b..4fd1f161a 100644 --- a/library/src/shared/ext.rs +++ b/library/src/shared/ext.rs @@ -13,6 +13,9 @@ pub trait ContentExt { /// Underline this content. fn underlined(self) -> Self; + /// Link the content to a destination. + fn linked(self, dest: Destination) -> Self; + /// Force a size for this content. fn boxed(self, sizing: Axes>>) -> Self; @@ -45,6 +48,10 @@ impl ContentExt for Content { crate::text::DecoNode::<{ crate::text::UNDERLINE }>(self).pack() } + fn linked(self, dest: Destination) -> Self { + self.styled(Meta::DATA, vec![Meta::Link(dest.clone())]) + } + fn boxed(self, sizing: Axes>>) -> Self { crate::layout::BoxNode { sizing, child: self }.pack() } diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index c0bf105fc..9c04fedff 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -42,6 +42,19 @@ impl RawNode { } } +impl Prepare for RawNode { + fn prepare(&self, _: &mut Vt, mut this: Content, styles: StyleChain) -> Content { + this.push_field( + "lang", + match styles.get(Self::LANG) { + Some(lang) => Value::Str(lang.clone().into()), + None => Value::None, + }, + ); + this + } +} + impl Show for RawNode { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> Content { let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase()); diff --git a/library/src/text/shaping.rs b/library/src/text/shaping.rs index 6dbbb535e..6d4e41dc6 100644 --- a/library/src/text/shaping.rs +++ b/library/src/text/shaping.rs @@ -6,7 +6,6 @@ use typst::font::{Font, FontVariant}; use typst::util::SliceExt; use super::*; -use crate::meta::LinkNode; use crate::prelude::*; /// The result of shaping text. @@ -93,7 +92,6 @@ impl<'a> ShapedText<'a> { let lang = self.styles.get(TextNode::LANG); let decos = self.styles.get(TextNode::DECO); let fill = self.styles.get(TextNode::FILL); - let link = self.styles.get(LinkNode::DEST); for ((font, y_offset), group) in self.glyphs.as_ref().group_by_key(|g| (g.font.clone(), g.y_offset)) @@ -128,10 +126,8 @@ impl<'a> ShapedText<'a> { offset += width; } - // Apply link if it exists. - if let Some(dest) = link { - frame.link(dest.clone()); - } + // Apply metadata. + frame.meta(self.styles); frame } diff --git a/library/src/visualize/image.rs b/library/src/visualize/image.rs index 077fe76d4..48764c373 100644 --- a/library/src/visualize/image.rs +++ b/library/src/visualize/image.rs @@ -2,7 +2,6 @@ use std::ffi::OsStr; use typst::image::{Image, ImageFormat, RasterFormat, VectorFormat}; -use crate::meta::LinkNode; use crate::prelude::*; /// Show a raster or vector graphic. @@ -89,10 +88,8 @@ impl Layout for ImageNode { frame.clip(); } - // Apply link if it exists. - if let Some(url) = styles.get(LinkNode::DEST) { - frame.link(url.clone()); - } + // Apply metadata. + frame.meta(styles); Ok(Fragment::frame(frame)) } diff --git a/library/src/visualize/shape.rs b/library/src/visualize/shape.rs index f2d15cab5..acb5a9e91 100644 --- a/library/src/visualize/shape.rs +++ b/library/src/visualize/shape.rs @@ -1,6 +1,5 @@ use std::f64::consts::SQRT_2; -use crate::meta::LinkNode; use crate::prelude::*; /// A sizable and fillable shape with optional content. @@ -160,10 +159,8 @@ impl Layout for ShapeNode { } } - // Apply link if it exists. - if let Some(url) = styles.get(LinkNode::DEST) { - frame.link(url.clone()); - } + // Apply metadata. + frame.meta(styles); Ok(Fragment::frame(frame)) } diff --git a/src/doc.rs b/src/doc.rs index 93cae90f1..2605bfc16 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -10,21 +10,14 @@ use crate::geom::{ Abs, Align, Axes, Dir, Em, Numeric, Paint, Point, Shape, Size, Transform, }; use crate::image::Image; -use crate::model::{dict, Dict, Value}; +use crate::model::{dict, node, Content, Dict, Fold, StableId, StyleChain, Value}; use crate::util::EcoString; /// A finished document with metadata and page frames. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Default, Clone)] pub struct Document { - /// The document's metadata. - pub metadata: Metadata, /// The page frames. pub pages: Vec, -} - -/// Document metadata. -#[derive(Debug, Default, Clone, Eq, PartialEq)] -pub struct Metadata { /// The document's title. pub title: Option, /// The document's author. @@ -32,7 +25,7 @@ pub struct Metadata { } /// A partial layout result. -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone)] pub struct Fragment(Vec); impl Fragment { @@ -113,7 +106,7 @@ impl<'a> IntoIterator for &'a mut Fragment { } /// A finished layout with elements at fixed positions. -#[derive(Default, Clone, Eq, PartialEq)] +#[derive(Default, Clone)] pub struct Frame { /// The size of the frame. size: Size, @@ -336,9 +329,11 @@ impl Frame { } } - /// Link the whole frame to a resource. - pub fn link(&mut self, dest: Destination) { - self.push(Point::zero(), Element::Link(dest, self.size)); + /// Attach the metadata from this style chain to the frame. + pub fn meta(&mut self, styles: StyleChain) { + for meta in styles.get(Meta::DATA) { + self.push(Point::zero(), Element::Meta(meta, self.size)); + } } /// Arbitrarily transform the contents of the frame. @@ -374,7 +369,7 @@ impl Debug for Frame { } /// The building block frames are composed of. -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone)] pub enum Element { /// A group of elements. Group(Group), @@ -384,8 +379,8 @@ pub enum Element { Shape(Shape), /// An image and its size. Image(Image, Size), - /// A link to an external resource and its trigger region. - Link(Destination, Size), + /// Meta information and the region it applies to. + Meta(Meta, Size), } impl Debug for Element { @@ -395,13 +390,13 @@ impl Debug for Element { Self::Text(text) => write!(f, "{text:?}"), Self::Shape(shape) => write!(f, "{shape:?}"), Self::Image(image, _) => write!(f, "{image:?}"), - Self::Link(dest, _) => write!(f, "Link({dest:?})"), + Self::Meta(meta, _) => write!(f, "{meta:?}"), } } } /// A group of elements with optional clipping. -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone)] pub struct Group { /// The group's frame. pub frame: Frame, @@ -543,6 +538,33 @@ impl FromStr for Region { } } +/// Meta information that isn't visible or renderable. +#[derive(Debug, Clone, Hash)] +pub enum Meta { + /// An internal or external link. + Link(Destination), + /// An identifiable piece of content that produces something within the + /// area this metadata is attached to. + Node(StableId, Content), +} + +#[node] +impl Meta { + /// Metadata that should be attached to all elements affected by this style + /// property. + #[property(fold, skip)] + pub const DATA: Vec = vec![]; +} + +impl Fold for Vec { + type Output = Self; + + fn fold(mut self, outer: Self::Output) -> Self::Output { + self.extend(outer); + self + } +} + /// A link destination. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum Destination { diff --git a/src/export/pdf/mod.rs b/src/export/pdf/mod.rs index 8f9d96374..60cd4ea2f 100644 --- a/src/export/pdf/mod.rs +++ b/src/export/pdf/mod.rs @@ -14,7 +14,7 @@ use pdf_writer::{Finish, Name, PdfWriter, Ref, TextStr}; use self::outline::HeadingNode; use self::page::Page; -use crate::doc::{Document, Lang, Metadata}; +use crate::doc::{Document, Lang}; use crate::font::Font; use crate::geom::{Abs, Dir, Em}; use crate::image::Image; @@ -23,7 +23,7 @@ use crate::image::Image; /// /// Returns the raw bytes making up the PDF file. pub fn pdf(document: &Document) -> Vec { - let mut ctx = PdfContext::new(&document.metadata); + let mut ctx = PdfContext::new(document); page::construct_pages(&mut ctx, &document.pages); font::write_fonts(&mut ctx); image::write_images(&mut ctx); @@ -38,7 +38,7 @@ const D65_GRAY: Name<'static> = Name(b"d65gray"); /// Context for exporting a whole PDF document. pub struct PdfContext<'a> { - metadata: &'a Metadata, + document: &'a Document, writer: PdfWriter, pages: Vec, page_heights: Vec, @@ -55,11 +55,11 @@ pub struct PdfContext<'a> { } impl<'a> PdfContext<'a> { - fn new(metadata: &'a Metadata) -> Self { + fn new(document: &'a Document) -> Self { let mut alloc = Ref::new(1); let page_tree_ref = alloc.bump(); Self { - metadata, + document, writer: PdfWriter::new(), pages: vec![], page_heights: vec![], @@ -116,10 +116,10 @@ fn write_catalog(ctx: &mut PdfContext) { // Write the document information. let mut info = ctx.writer.document_info(ctx.alloc.bump()); - if let Some(title) = &ctx.metadata.title { + if let Some(title) = &ctx.document.title { info.title(TextStr(title)); } - if let Some(author) = &ctx.metadata.author { + if let Some(author) = &ctx.document.author { info.author(TextStr(author)); } info.creator(TextStr("Typst")); diff --git a/src/export/pdf/page.rs b/src/export/pdf/page.rs index fc714e7ac..f2ab78e3a 100644 --- a/src/export/pdf/page.rs +++ b/src/export/pdf/page.rs @@ -3,7 +3,7 @@ use pdf_writer::writers::ColorSpace; use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str}; use super::{deflate, AbsExt, EmExt, PdfContext, RefExt, D65_GRAY, SRGB}; -use crate::doc::{Destination, Element, Frame, Group, Text}; +use crate::doc::{Destination, Element, Frame, Group, Meta, Text}; use crate::font::Font; use crate::geom::{ self, Abs, Color, Em, Geometry, Numeric, Paint, Point, Ratio, Shape, Size, Stroke, @@ -287,7 +287,10 @@ fn write_frame(ctx: &mut PageContext, frame: &Frame) { Element::Text(text) => write_text(ctx, x, y, text), Element::Shape(shape) => write_shape(ctx, x, y, shape), Element::Image(image, size) => write_image(ctx, x, y, image, *size), - Element::Link(dest, size) => write_link(ctx, pos, dest, *size), + Element::Meta(meta, size) => match meta { + Meta::Link(dest) => write_link(ctx, pos, dest, *size), + _ => {} + }, } } } diff --git a/src/export/render.rs b/src/export/render.rs index a61ff1be3..68625e230 100644 --- a/src/export/render.rs +++ b/src/export/render.rs @@ -57,7 +57,7 @@ fn render_frame( Element::Image(image, size) => { render_image(canvas, ts, mask, image, *size); } - Element::Link(_, _) => {} + Element::Meta(_, _) => {} } } } diff --git a/src/model/cast.rs b/src/model/cast.rs index 6a78eebdd..bfde1bdd3 100644 --- a/src/model/cast.rs +++ b/src/model/cast.rs @@ -255,17 +255,23 @@ castable! { } castable! { - Destination, - Expected: "string or dictionary with `page`, `x`, and `y` keys", - Value::Str(string) => Self::Url(string.into()), + Location, + Expected: "dictionary with `page`, `x`, and `y` keys", Value::Dict(dict) => { let page = dict.get("page")?.clone().cast()?; let x: Length = dict.get("x")?.clone().cast()?; let y: Length = dict.get("y")?.clone().cast()?; - Self::Internal(Location { page, pos: Point::new(x.abs, y.abs) }) + Self { page, pos: Point::new(x.abs, y.abs) } }, } +castable! { + Destination, + Expected: "string or dictionary with `page`, `x`, and `y` keys", + Value::Str(string) => Self::Url(string.into()), + v @ Value::Dict(_) => Self::Internal(v.cast()?), +} + castable! { FontStyle, Expected: "string", diff --git a/src/model/content.rs b/src/model/content.rs index 2f7e7671b..f261f9b16 100644 --- a/src/model/content.rs +++ b/src/model/content.rs @@ -21,14 +21,16 @@ use crate::World; pub struct Content { obj: Arc, span: Option, - meta: ThinVec, + modifiers: ThinVec, } -/// Metadata that can be attached to content. +/// Modifiers that can be attached to content. #[derive(Debug, Clone, PartialEq, Hash)] -enum Meta { +enum Modifier { + Prepared, Guard(Guard), Label(Label), + Field(EcoString, Value), } impl Content { @@ -62,17 +64,22 @@ impl Content { /// Attach a label to the content. pub fn labelled(mut self, label: Label) -> Self { - for meta in &mut self.meta { - if let Meta::Label(prev) = meta { + for modifier in &mut self.modifiers { + if let Modifier::Label(prev) = modifier { *prev = label; return self; } } - self.meta.push(Meta::Label(label)); + self.modifiers.push(Modifier::Label(label)); self } + /// Attach a field to the content. + pub fn push_field(&mut self, name: impl Into, value: Value) { + self.modifiers.push(Modifier::Field(name.into(), value)); + } + /// Style this content with a single style property. pub fn styled(self, key: K, value: K::Value) -> Self { self.styled_with_entry(Style::Property(Property::new(key, value))) @@ -146,8 +153,8 @@ impl Content { /// The content's label. pub fn label(&self) -> Option<&Label> { - self.meta.iter().find_map(|meta| match meta { - Meta::Label(label) => Some(label), + self.modifiers.iter().find_map(|modifier| match modifier { + Modifier::Label(label) => Some(label), _ => None, }) } @@ -161,6 +168,14 @@ impl Content { }); } + for modifier in &self.modifiers { + if let Modifier::Field(other, value) = modifier { + if name == other.as_str() { + return Some(value.clone()); + } + } + } + self.obj.field(name) } @@ -201,10 +216,23 @@ impl Content { /// Disable a show rule recipe. #[doc(hidden)] pub fn guarded(mut self, id: Guard) -> Self { - self.meta.push(Meta::Guard(id)); + self.modifiers.push(Modifier::Guard(id)); self } + /// Mark this content as prepared. + #[doc(hidden)] + pub fn prepared(mut self) -> Self { + self.modifiers.push(Modifier::Prepared); + self + } + + /// Whether this node was prepared. + #[doc(hidden)] + pub fn is_prepared(&self) -> bool { + self.modifiers.contains(&Modifier::Prepared) + } + /// Whether a label can be attached to the content. pub(super) fn labellable(&self) -> bool { !self.has::() @@ -212,18 +240,21 @@ impl Content { /// Whether no show rule was executed for this node so far. pub(super) fn is_pristine(&self) -> bool { - !self.meta.iter().any(|meta| matches!(meta, Meta::Guard(_))) + !self + .modifiers + .iter() + .any(|modifier| matches!(modifier, Modifier::Guard(_))) } /// Check whether a show rule recipe is disabled. pub(super) fn is_guarded(&self, id: Guard) -> bool { - self.meta.contains(&Meta::Guard(id)) + self.modifiers.contains(&Modifier::Guard(id)) } - /// Copy the metadata from other content. - pub(super) fn copy_meta(&mut self, from: &Content) { + /// Copy the modifiers from another piece of content. + pub(super) fn copy_modifiers(&mut self, from: &Content) { self.span = from.span; - self.meta = from.meta.clone(); + self.modifiers = from.modifiers.clone(); } } @@ -239,12 +270,6 @@ impl Default for Content { } } -impl PartialEq for Content { - fn eq(&self, other: &Self) -> bool { - (*self.obj).hash128() == (*other.obj).hash128() - } -} - impl Add for Content { type Output = Self; @@ -371,7 +396,7 @@ pub trait Node: 'static + Capable { Content { obj: Arc::new(self), span: None, - meta: ThinVec::new(), + modifiers: ThinVec::new(), } } diff --git a/src/model/ops.rs b/src/model/ops.rs index 60b1c4498..1a8dcb6b1 100644 --- a/src/model/ops.rs +++ b/src/model/ops.rs @@ -309,7 +309,6 @@ pub fn equal(lhs: &Value, rhs: &Value) -> bool { (Color(a), Color(b)) => a == b, (Str(a), Str(b)) => a == b, (Label(a), Label(b)) => a == b, - (Content(a), Content(b)) => a == b, (Array(a), Array(b)) => a == b, (Dict(a), Dict(b)) => a == b, (Func(a), Func(b)) => a == b, diff --git a/src/model/realize.rs b/src/model/realize.rs index 46e0d678e..abe20901d 100644 --- a/src/model/realize.rs +++ b/src/model/realize.rs @@ -3,6 +3,10 @@ use crate::diag::SourceResult; /// Whether the target is affected by show rules in the given style chain. pub fn applicable(target: &Content, styles: StyleChain) -> bool { + if target.has::() && !target.is_prepared() { + return true; + } + // Find out how many recipes there are. let mut n = styles.recipes().count(); @@ -90,7 +94,7 @@ fn try_apply( let make = |s| { let mut content = item!(text)(s); - content.copy_meta(&target); + content.copy_modifiers(&target); content }; @@ -124,6 +128,13 @@ fn try_apply( } } +/// Preparations before execution of any show rule. +#[capability] +pub trait Prepare { + /// Prepare the node for show rule application. + fn prepare(&self, vt: &mut Vt, this: Content, styles: StyleChain) -> Content; +} + /// The base recipe for a node. #[capability] pub trait Show { diff --git a/src/model/styles.rs b/src/model/styles.rs index 80ec0d1ec..37596b8d0 100644 --- a/src/model/styles.rs +++ b/src/model/styles.rs @@ -17,7 +17,7 @@ use crate::util::ReadableTypeId; use crate::World; /// A map of style properties. -#[derive(Default, Clone, PartialEq, Hash)] +#[derive(Default, Clone, Hash)] pub struct StyleMap(Vec