mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
First iteration of outline items
This commit is contained in:
parent
a937462491
commit
4640585fbd
@ -16,7 +16,7 @@ use ttf_parser::{name_id, GlyphId, Tag};
|
|||||||
|
|
||||||
use super::subset::subset;
|
use super::subset::subset;
|
||||||
use crate::font::{find_name, FaceId, FontStore};
|
use crate::font::{find_name, FaceId, FontStore};
|
||||||
use crate::frame::{Destination, Element, Frame, Group, Text};
|
use crate::frame::{Destination, Element, Frame, Group, Role, Text};
|
||||||
use crate::geom::{
|
use crate::geom::{
|
||||||
self, Color, Dir, Em, Geometry, Length, Numeric, Paint, Point, Ratio, Shape, Size,
|
self, Color, Dir, Em, Geometry, Length, Numeric, Paint, Point, Ratio, Shape, Size,
|
||||||
Stroke, Transform,
|
Stroke, Transform,
|
||||||
@ -313,6 +313,8 @@ impl<'a> PdfExporter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut languages = HashMap::new();
|
let mut languages = HashMap::new();
|
||||||
|
let mut heading_tree: Vec<HeadingNode> = vec![];
|
||||||
|
|
||||||
for (page, page_id) in self.pages.into_iter().zip(page_refs.iter()) {
|
for (page, page_id) in self.pages.into_iter().zip(page_refs.iter()) {
|
||||||
let content_id = self.alloc.bump();
|
let content_id = self.alloc.bump();
|
||||||
|
|
||||||
@ -356,6 +358,20 @@ impl<'a> PdfExporter<'a> {
|
|||||||
.or_insert_with(|| count);
|
.or_insert_with(|| count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for heading in page.headings.into_iter() {
|
||||||
|
if let Some(last) = heading_tree.pop() {
|
||||||
|
let new = last.clone().insert(heading.clone(), *page_id, 1);
|
||||||
|
if let Some(new) = new {
|
||||||
|
heading_tree.push(new);
|
||||||
|
} else {
|
||||||
|
heading_tree.push(last);
|
||||||
|
heading_tree.push(HeadingNode::Leaf(heading, *page_id))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
heading_tree.push(HeadingNode::Leaf(heading, *page_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.writer
|
self.writer
|
||||||
.stream(content_id, &deflate(&page.content.finish()))
|
.stream(content_id, &deflate(&page.content.finish()))
|
||||||
.filter(Filter::FlateDecode);
|
.filter(Filter::FlateDecode);
|
||||||
@ -388,6 +404,39 @@ impl<'a> PdfExporter<'a> {
|
|||||||
resources.finish();
|
resources.finish();
|
||||||
pages.finish();
|
pages.finish();
|
||||||
|
|
||||||
|
// Build the heading tree.
|
||||||
|
let outline_root_id = self.alloc.bump();
|
||||||
|
|
||||||
|
let start_ref = self.alloc.bump();
|
||||||
|
let mut current_ref = start_ref;
|
||||||
|
let mut prev_ref = None;
|
||||||
|
|
||||||
|
for (i, node) in heading_tree.iter().enumerate() {
|
||||||
|
let next = write_outline_item(
|
||||||
|
&mut self.writer,
|
||||||
|
node,
|
||||||
|
current_ref,
|
||||||
|
prev_ref,
|
||||||
|
i == heading_tree.len() - 1,
|
||||||
|
outline_root_id,
|
||||||
|
);
|
||||||
|
prev_ref = Some(current_ref);
|
||||||
|
current_ref = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
self.alloc = Ref::new(
|
||||||
|
start_ref.get()
|
||||||
|
+ heading_tree.iter().map(HeadingNode::len).sum::<usize>() as i32,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(prev_ref) = prev_ref {
|
||||||
|
let mut outline_root = self.writer.outline(outline_root_id);
|
||||||
|
outline_root.first(start_ref);
|
||||||
|
outline_root.last(prev_ref);
|
||||||
|
outline_root.count(heading_tree.len() as i32);
|
||||||
|
}
|
||||||
|
|
||||||
let lang = languages
|
let lang = languages
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.max_by(|(_, v1), (_, v2)| v1.cmp(v2))
|
.max_by(|(_, v1), (_, v2)| v1.cmp(v2))
|
||||||
@ -405,6 +454,11 @@ impl<'a> PdfExporter<'a> {
|
|||||||
catalog.pages(page_tree_ref);
|
catalog.pages(page_tree_ref);
|
||||||
catalog.viewer_preferences().direction(dir);
|
catalog.viewer_preferences().direction(dir);
|
||||||
|
|
||||||
|
|
||||||
|
if !heading_tree.is_empty() {
|
||||||
|
catalog.outlines(outline_root_id);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(lang) = lang {
|
if let Some(lang) = lang {
|
||||||
catalog.lang(TextStr(lang.as_str()));
|
catalog.lang(TextStr(lang.as_str()));
|
||||||
}
|
}
|
||||||
@ -426,6 +480,7 @@ struct PageExporter<'a> {
|
|||||||
links: Vec<(Destination, Rect)>,
|
links: Vec<(Destination, Rect)>,
|
||||||
state: State,
|
state: State,
|
||||||
saves: Vec<State>,
|
saves: Vec<State>,
|
||||||
|
headings: Vec<Heading>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Data for an exported page.
|
/// Data for an exported page.
|
||||||
@ -434,6 +489,7 @@ struct Page {
|
|||||||
content: Content,
|
content: Content,
|
||||||
links: Vec<(Destination, Rect)>,
|
links: Vec<(Destination, Rect)>,
|
||||||
languages: HashMap<Lang, usize>,
|
languages: HashMap<Lang, usize>,
|
||||||
|
headings: Vec<Heading>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simulated graphics state used to deduplicate graphics state changes and
|
/// A simulated graphics state used to deduplicate graphics state changes and
|
||||||
@ -448,6 +504,64 @@ struct State {
|
|||||||
stroke_space: Option<Name<'static>>,
|
stroke_space: Option<Name<'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A heading that can later be linked in the outline panel.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Heading {
|
||||||
|
content: String,
|
||||||
|
level: usize,
|
||||||
|
position: Point,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum HeadingNode {
|
||||||
|
Leaf(Heading, Ref),
|
||||||
|
Branch(Heading, Ref, Vec<HeadingNode>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HeadingNode {
|
||||||
|
fn heading(&self) -> &Heading {
|
||||||
|
match self {
|
||||||
|
HeadingNode::Leaf(h, _) => h,
|
||||||
|
HeadingNode::Branch(h, _, _) => h,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reference(&self) -> Ref {
|
||||||
|
match self {
|
||||||
|
HeadingNode::Leaf(_, r) => *r,
|
||||||
|
HeadingNode::Branch(_, r, _) => *r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
HeadingNode::Leaf(_, _) => 1,
|
||||||
|
HeadingNode::Branch(_, _, children) => {
|
||||||
|
1 + children.iter().map(|c| c.len()).sum::<usize>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(self, other: Heading, page: Ref, level: usize) -> Option<Self> {
|
||||||
|
if level >= other.level {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut node = match self {
|
||||||
|
HeadingNode::Leaf(h, r) => (h, r, vec![]),
|
||||||
|
HeadingNode::Branch(h, r, v) if level + 1 == other.level => (h, r, v),
|
||||||
|
HeadingNode::Branch(h, r, mut v) => {
|
||||||
|
let new = v.pop().unwrap().insert(other, page, level + 1).unwrap();
|
||||||
|
v.push(new);
|
||||||
|
return Some(HeadingNode::Branch(h, r, v));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
node.2.push(HeadingNode::Leaf(other, page));
|
||||||
|
Some(HeadingNode::Branch(node.0, node.1, node.2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> PageExporter<'a> {
|
impl<'a> PageExporter<'a> {
|
||||||
fn new(exporter: &'a mut PdfExporter) -> Self {
|
fn new(exporter: &'a mut PdfExporter) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -461,6 +575,7 @@ impl<'a> PageExporter<'a> {
|
|||||||
links: vec![],
|
links: vec![],
|
||||||
state: State::default(),
|
state: State::default(),
|
||||||
saves: vec![],
|
saves: vec![],
|
||||||
|
headings: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,10 +596,22 @@ impl<'a> PageExporter<'a> {
|
|||||||
content: self.content,
|
content: self.content,
|
||||||
links: self.links,
|
links: self.links,
|
||||||
languages: self.languages,
|
languages: self.languages,
|
||||||
|
headings: self.headings,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_frame(&mut self, frame: &Frame) {
|
fn write_frame(&mut self, frame: &Frame) {
|
||||||
|
if let Some(Role::Heading(level)) = frame.role() {
|
||||||
|
self.headings.push(Heading {
|
||||||
|
position: Point::new(
|
||||||
|
self.state.transform.tx,
|
||||||
|
self.state.transform.ty + Length::pt(3.0),
|
||||||
|
),
|
||||||
|
content: frame.inner_text().to_string(),
|
||||||
|
level,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
for &(pos, ref element) in &frame.elements {
|
for &(pos, ref element) in &frame.elements {
|
||||||
let x = pos.x.to_f32();
|
let x = pos.x.to_f32();
|
||||||
let y = pos.y.to_f32();
|
let y = pos.y.to_f32();
|
||||||
@ -815,6 +942,67 @@ fn encode_image(img: &RasterImage) -> ImageResult<(Vec<u8>, Filter, bool)> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_outline_item(
|
||||||
|
writer: &mut PdfWriter,
|
||||||
|
node: &HeadingNode,
|
||||||
|
current_ref: Ref,
|
||||||
|
prev_ref: Option<Ref>,
|
||||||
|
is_last: bool,
|
||||||
|
parent_ref: Ref,
|
||||||
|
) -> Ref {
|
||||||
|
let mut outline = writer.outline_item(current_ref);
|
||||||
|
let next = Ref::new(current_ref.get() + node.len() as i32);
|
||||||
|
outline.parent(parent_ref);
|
||||||
|
|
||||||
|
if !is_last {
|
||||||
|
outline.next(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(prev_ref) = prev_ref {
|
||||||
|
outline.prev(prev_ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let HeadingNode::Branch(_, _, children) = node {
|
||||||
|
let current_child = Ref::new(current_ref.get() + 1);
|
||||||
|
if children.len() > 0 {
|
||||||
|
outline.first(current_child);
|
||||||
|
outline.last(Ref::new(next.get() - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
outline.count(-1 * children.len() as i32);
|
||||||
|
}
|
||||||
|
|
||||||
|
let heading = node.heading();
|
||||||
|
outline.title(TextStr(&heading.content));
|
||||||
|
outline.dest_direct().page(node.reference()).xyz(
|
||||||
|
heading.position.x.to_f32(),
|
||||||
|
heading.position.y.to_f32(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
outline.finish();
|
||||||
|
|
||||||
|
if let HeadingNode::Branch(_, _, children) = node {
|
||||||
|
let mut current_child = Ref::new(current_ref.get() + 1);
|
||||||
|
let mut prev_ref = None;
|
||||||
|
|
||||||
|
for (i, child) in children.iter().enumerate() {
|
||||||
|
write_outline_item(
|
||||||
|
writer,
|
||||||
|
child,
|
||||||
|
current_child,
|
||||||
|
prev_ref,
|
||||||
|
i == children.len() - 1,
|
||||||
|
current_ref,
|
||||||
|
);
|
||||||
|
prev_ref = Some(current_child);
|
||||||
|
current_child = Ref::new(current_child.get() + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next
|
||||||
|
}
|
||||||
|
|
||||||
/// Encode an image's alpha channel if present.
|
/// Encode an image's alpha channel if present.
|
||||||
fn encode_alpha(img: &RasterImage) -> (Vec<u8>, Filter) {
|
fn encode_alpha(img: &RasterImage) -> (Vec<u8>, Filter) {
|
||||||
let pixels: Vec<_> = img.buf.pixels().map(|(_, _, Rgba([_, _, _, a]))| a).collect();
|
let pixels: Vec<_> = img.buf.pixels().map(|(_, _, Rgba([_, _, _, a]))| a).collect();
|
||||||
|
99
src/frame.rs
99
src/frame.rs
@ -22,6 +22,8 @@ pub struct Frame {
|
|||||||
pub baseline: Option<Length>,
|
pub baseline: Option<Length>,
|
||||||
/// The elements composing this layout.
|
/// The elements composing this layout.
|
||||||
pub elements: Vec<(Point, Element)>,
|
pub elements: Vec<(Point, Element)>,
|
||||||
|
/// The semantic role of the frame.
|
||||||
|
role: Option<Role>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Frame {
|
impl Frame {
|
||||||
@ -29,7 +31,12 @@ impl Frame {
|
|||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn new(size: Size) -> Self {
|
pub fn new(size: Size) -> Self {
|
||||||
assert!(size.is_finite());
|
assert!(size.is_finite());
|
||||||
Self { size, baseline: None, elements: vec![] }
|
Self {
|
||||||
|
size,
|
||||||
|
baseline: None,
|
||||||
|
elements: vec![],
|
||||||
|
role: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The baseline of the frame.
|
/// The baseline of the frame.
|
||||||
@ -43,6 +50,11 @@ impl Frame {
|
|||||||
self.elements.len()
|
self.elements.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The role of the frame.
|
||||||
|
pub fn role(&self) -> Option<Role> {
|
||||||
|
self.role
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether the frame has comparatively few elements.
|
/// Whether the frame has comparatively few elements.
|
||||||
pub fn is_light(&self) -> bool {
|
pub fn is_light(&self) -> bool {
|
||||||
self.elements.len() <= 5
|
self.elements.len() <= 5
|
||||||
@ -58,7 +70,12 @@ impl Frame {
|
|||||||
/// Automatically decides whether to inline the frame or to include it as a
|
/// Automatically decides whether to inline the frame or to include it as a
|
||||||
/// group based on the number of elements in the frame.
|
/// group based on the number of elements in the frame.
|
||||||
pub fn push_frame(&mut self, pos: Point, frame: impl FrameRepr) {
|
pub fn push_frame(&mut self, pos: Point, frame: impl FrameRepr) {
|
||||||
if self.elements.is_empty() || frame.as_ref().is_light() {
|
if (self.elements.is_empty() || frame.as_ref().is_light())
|
||||||
|
&& (frame.as_ref().role().is_none() || self.role.is_none())
|
||||||
|
{
|
||||||
|
if self.role.is_none() {
|
||||||
|
self.role = frame.as_ref().role()
|
||||||
|
}
|
||||||
frame.inline(self, self.layer(), pos);
|
frame.inline(self, self.layer(), pos);
|
||||||
} else {
|
} else {
|
||||||
self.elements.push((pos, Element::Group(Group::new(frame.share()))));
|
self.elements.push((pos, Element::Group(Group::new(frame.share()))));
|
||||||
@ -80,7 +97,12 @@ impl Frame {
|
|||||||
|
|
||||||
/// Add a frame at a position in the background.
|
/// Add a frame at a position in the background.
|
||||||
pub fn prepend_frame(&mut self, pos: Point, frame: impl FrameRepr) {
|
pub fn prepend_frame(&mut self, pos: Point, frame: impl FrameRepr) {
|
||||||
if self.elements.is_empty() || frame.as_ref().is_light() {
|
if (self.elements.is_empty() || frame.as_ref().is_light())
|
||||||
|
&& (frame.as_ref().role().is_none() || self.role.is_none())
|
||||||
|
{
|
||||||
|
if self.role.is_none() {
|
||||||
|
self.role = frame.as_ref().role()
|
||||||
|
}
|
||||||
frame.inline(self, 0, pos);
|
frame.inline(self, 0, pos);
|
||||||
} else {
|
} else {
|
||||||
self.elements
|
self.elements
|
||||||
@ -125,6 +147,15 @@ impl Frame {
|
|||||||
self.group(|g| g.transform = transform);
|
self.group(|g| g.transform = transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply the given role to the frame if it doesn't already have one.
|
||||||
|
pub fn apply_role(&mut self, role: Role) {
|
||||||
|
match self.role {
|
||||||
|
None => self.role = Some(role),
|
||||||
|
Some(old) if old.is_weak() => self.role = Some(role),
|
||||||
|
Some(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Clip the contents of a frame to its size.
|
/// Clip the contents of a frame to its size.
|
||||||
pub fn clip(&mut self) {
|
pub fn clip(&mut self) {
|
||||||
self.group(|g| g.clips = true);
|
self.group(|g| g.clips = true);
|
||||||
@ -146,10 +177,26 @@ impl Frame {
|
|||||||
pub fn link(&mut self, dest: Destination) {
|
pub fn link(&mut self, dest: Destination) {
|
||||||
self.push(Point::zero(), Element::Link(dest, self.size));
|
self.push(Point::zero(), Element::Link(dest, self.size));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Recover the text inside of the frame and its children.
|
||||||
|
pub fn inner_text(&self) -> EcoString {
|
||||||
|
let mut res = EcoString::new();
|
||||||
|
for (_, element) in &self.elements {
|
||||||
|
match element {
|
||||||
|
Element::Text(text) => res.push_str(
|
||||||
|
&text.glyphs.iter().map(|glyph| glyph.c).collect::<EcoString>(),
|
||||||
|
),
|
||||||
|
Element::Group(group) => res.push_str(&group.frame.inner_text()),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Frame {
|
impl Debug for Frame {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
self.role.fmt(f)?;
|
||||||
f.debug_list()
|
f.debug_list()
|
||||||
.entries(self.elements.iter().map(|(_, element)| element))
|
.entries(self.elements.iter().map(|(_, element)| element))
|
||||||
.finish()
|
.finish()
|
||||||
@ -362,3 +409,49 @@ impl Location {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A semantic role of a frame.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum Role {
|
||||||
|
/// A paragraph.
|
||||||
|
Paragraph,
|
||||||
|
/// A heading with some level.
|
||||||
|
Heading(usize),
|
||||||
|
/// A generic block-level subdivision.
|
||||||
|
GenericBlock,
|
||||||
|
/// A generic inline subdivision.
|
||||||
|
GenericInline,
|
||||||
|
/// A list. The boolean indicates whether it is ordered.
|
||||||
|
List(bool),
|
||||||
|
/// A list item. Must have a list parent.
|
||||||
|
ListItem,
|
||||||
|
/// The label of a list item.
|
||||||
|
ListLabel,
|
||||||
|
/// The body of a list item.
|
||||||
|
ListItemBody,
|
||||||
|
/// A mathematical formula.
|
||||||
|
Formula,
|
||||||
|
/// A table.
|
||||||
|
Table,
|
||||||
|
/// A table row.
|
||||||
|
TableRow,
|
||||||
|
/// A table cell.
|
||||||
|
TableCell,
|
||||||
|
/// A code fragment.
|
||||||
|
Code,
|
||||||
|
/// A page header.
|
||||||
|
Header,
|
||||||
|
/// A page footer.
|
||||||
|
Footer,
|
||||||
|
/// A page background.
|
||||||
|
Background,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Role {
|
||||||
|
fn is_weak(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Paragraph | Self::GenericBlock | Self::GenericInline => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -22,7 +22,11 @@ impl Layout for HideNode {
|
|||||||
|
|
||||||
// Clear the frames.
|
// Clear the frames.
|
||||||
for frame in &mut frames {
|
for frame in &mut frames {
|
||||||
*frame = Arc::new(Frame { elements: vec![], ..**frame });
|
*frame = Arc::new({
|
||||||
|
let mut empty = Frame::new(frame.size);
|
||||||
|
empty.baseline = frame.baseline;
|
||||||
|
empty
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(frames)
|
Ok(frames)
|
||||||
|
@ -91,6 +91,9 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
|||||||
let child = child.clone().padded(inset.map(|side| side.map(RawLength::from)));
|
let child = child.clone().padded(inset.map(|side| side.map(RawLength::from)));
|
||||||
|
|
||||||
let mut pod = Regions::one(regions.first, regions.base, regions.expand);
|
let mut pod = Regions::one(regions.first, regions.base, regions.expand);
|
||||||
|
let role_map = StyleMap::with_role(Role::GenericBlock);
|
||||||
|
let styles = role_map.chain(&styles);
|
||||||
|
|
||||||
frames = child.layout(ctx, &pod, styles)?;
|
frames = child.layout(ctx, &pod, styles)?;
|
||||||
|
|
||||||
// Relayout with full expansion into square region to make sure
|
// Relayout with full expansion into square region to make sure
|
||||||
|
@ -182,7 +182,12 @@ impl FlowLayouter {
|
|||||||
|
|
||||||
let frames = node.layout(ctx, &self.regions, styles)?;
|
let frames = node.layout(ctx, &self.regions, styles)?;
|
||||||
let len = frames.len();
|
let len = frames.len();
|
||||||
for (i, frame) in frames.into_iter().enumerate() {
|
for (i, mut frame) in frames.into_iter().enumerate() {
|
||||||
|
// Set the generic block role.
|
||||||
|
if frame.role().is_none() {
|
||||||
|
Arc::make_mut(&mut frame).apply_role(Role::GenericBlock);
|
||||||
|
}
|
||||||
|
|
||||||
// Grow our size, shrink the region and save the frame for later.
|
// Grow our size, shrink the region and save the frame for later.
|
||||||
let size = frame.size;
|
let size = frame.size;
|
||||||
self.used.y += size.y;
|
self.used.y += size.y;
|
||||||
|
@ -9,6 +9,8 @@ pub struct GridNode {
|
|||||||
pub gutter: Spec<Vec<TrackSizing>>,
|
pub gutter: Spec<Vec<TrackSizing>>,
|
||||||
/// The nodes to be arranged in a grid.
|
/// The nodes to be arranged in a grid.
|
||||||
pub cells: Vec<LayoutNode>,
|
pub cells: Vec<LayoutNode>,
|
||||||
|
/// The role of the grid in the semantic tree.
|
||||||
|
pub semantic: GridSemantics,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
@ -26,6 +28,7 @@ impl GridNode {
|
|||||||
row_gutter.unwrap_or(base_gutter),
|
row_gutter.unwrap_or(base_gutter),
|
||||||
),
|
),
|
||||||
cells: args.all()?,
|
cells: args.all()?,
|
||||||
|
semantic: GridSemantics::None,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -45,6 +48,7 @@ impl Layout for GridNode {
|
|||||||
&self.cells,
|
&self.cells,
|
||||||
regions,
|
regions,
|
||||||
styles,
|
styles,
|
||||||
|
self.semantic,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Measure the columns and layout the grid row-by-row.
|
// Measure the columns and layout the grid row-by-row.
|
||||||
@ -65,6 +69,28 @@ pub enum TrackSizing {
|
|||||||
Fractional(Fraction),
|
Fractional(Fraction),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Defines what kind of semantics a grid should represent.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum GridSemantics {
|
||||||
|
/// The grid is transparent to the semantic tree.
|
||||||
|
None,
|
||||||
|
/// The grid is a list, its rows are list items. The bool indicates whether
|
||||||
|
/// the list is ordered.
|
||||||
|
List,
|
||||||
|
/// The grid is a table.
|
||||||
|
Table,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GridSemantics {
|
||||||
|
fn row(self) -> Option<Role> {
|
||||||
|
match self {
|
||||||
|
Self::None => None,
|
||||||
|
Self::List => Some(Role::ListItem),
|
||||||
|
Self::Table => Some(Role::TableRow),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Vec<TrackSizing>,
|
Vec<TrackSizing>,
|
||||||
Expected: "integer, auto, relative length, fraction, or array of the latter three)",
|
Expected: "integer, auto, relative length, fraction, or array of the latter three)",
|
||||||
@ -104,6 +130,8 @@ pub struct GridLayouter<'a> {
|
|||||||
regions: Regions,
|
regions: Regions,
|
||||||
/// The inherited styles.
|
/// The inherited styles.
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
|
/// The role of the grid in the semantic tree.
|
||||||
|
semantic: GridSemantics,
|
||||||
/// Resolved column sizes.
|
/// Resolved column sizes.
|
||||||
rcols: Vec<Length>,
|
rcols: Vec<Length>,
|
||||||
/// Rows in the current region.
|
/// Rows in the current region.
|
||||||
@ -139,6 +167,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
cells: &'a [LayoutNode],
|
cells: &'a [LayoutNode],
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
|
semantic: GridSemantics,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut cols = vec![];
|
let mut cols = vec![];
|
||||||
let mut rows = vec![];
|
let mut rows = vec![];
|
||||||
@ -193,6 +222,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
rows,
|
rows,
|
||||||
regions,
|
regions,
|
||||||
styles,
|
styles,
|
||||||
|
semantic,
|
||||||
rcols,
|
rcols,
|
||||||
lrows,
|
lrows,
|
||||||
full,
|
full,
|
||||||
@ -450,6 +480,10 @@ impl<'a> GridLayouter<'a> {
|
|||||||
/// Layout a row with fixed height and return its frame.
|
/// Layout a row with fixed height and return its frame.
|
||||||
fn layout_single_row(&mut self, height: Length, y: usize) -> TypResult<Frame> {
|
fn layout_single_row(&mut self, height: Length, y: usize) -> TypResult<Frame> {
|
||||||
let mut output = Frame::new(Size::new(self.used.x, height));
|
let mut output = Frame::new(Size::new(self.used.x, height));
|
||||||
|
if let Some(role) = self.semantic.row() {
|
||||||
|
output.apply_role(role);
|
||||||
|
}
|
||||||
|
|
||||||
let mut pos = Point::zero();
|
let mut pos = Point::zero();
|
||||||
|
|
||||||
for (x, &rcol) in self.rcols.iter().enumerate() {
|
for (x, &rcol) in self.rcols.iter().enumerate() {
|
||||||
@ -464,6 +498,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
|
|
||||||
let pod = Regions::one(size, base, Spec::splat(true));
|
let pod = Regions::one(size, base, Spec::splat(true));
|
||||||
let frame = node.layout(self.ctx, &pod, self.styles)?.remove(0);
|
let frame = node.layout(self.ctx, &pod, self.styles)?.remove(0);
|
||||||
|
|
||||||
output.push_frame(pos, frame);
|
output.push_frame(pos, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,7 +517,14 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// Prepare frames.
|
// Prepare frames.
|
||||||
let mut outputs: Vec<_> = heights
|
let mut outputs: Vec<_> = heights
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&h| Frame::new(Size::new(self.used.x, h)))
|
.map(|&h| {
|
||||||
|
let mut f = Frame::new(Size::new(self.used.x, h));
|
||||||
|
if let Some(role) = self.semantic.row() {
|
||||||
|
f.apply_role(role);
|
||||||
|
}
|
||||||
|
|
||||||
|
f
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Prepare regions.
|
// Prepare regions.
|
||||||
|
@ -110,15 +110,28 @@ impl PageNode {
|
|||||||
let pad = padding.resolve(styles).relative_to(size);
|
let pad = padding.resolve(styles).relative_to(size);
|
||||||
let pw = size.x - pad.left - pad.right;
|
let pw = size.x - pad.left - pad.right;
|
||||||
let py = size.y - pad.bottom;
|
let py = size.y - pad.bottom;
|
||||||
for (marginal, pos, area) in [
|
for (marginal, pos, area, role) in [
|
||||||
(header, Point::with_x(pad.left), Size::new(pw, pad.top)),
|
(
|
||||||
(footer, Point::new(pad.left, py), Size::new(pw, pad.bottom)),
|
header,
|
||||||
(foreground, Point::zero(), size),
|
Point::with_x(pad.left),
|
||||||
(background, Point::zero(), size),
|
Size::new(pw, pad.top),
|
||||||
|
Role::Header,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
footer,
|
||||||
|
Point::new(pad.left, py),
|
||||||
|
Size::new(pw, pad.bottom),
|
||||||
|
Role::Footer,
|
||||||
|
),
|
||||||
|
(foreground, Point::zero(), size, Role::Background),
|
||||||
|
(background, Point::zero(), size, Role::Background),
|
||||||
] {
|
] {
|
||||||
if let Some(content) = marginal.resolve(ctx, page)? {
|
if let Some(content) = marginal.resolve(ctx, page)? {
|
||||||
let pod = Regions::one(area, area, Spec::splat(true));
|
let pod = Regions::one(area, area, Spec::splat(true));
|
||||||
|
let role_map = StyleMap::with_role(role);
|
||||||
|
let styles = role_map.chain(&styles);
|
||||||
let sub = content.layout(ctx, &pod, styles)?.remove(0);
|
let sub = content.layout(ctx, &pod, styles)?.remove(0);
|
||||||
|
|
||||||
if std::ptr::eq(marginal, background) {
|
if std::ptr::eq(marginal, background) {
|
||||||
Arc::make_mut(frame).prepend_frame(pos, sub);
|
Arc::make_mut(frame).prepend_frame(pos, sub);
|
||||||
} else {
|
} else {
|
||||||
|
@ -192,6 +192,9 @@ impl<'a> StackLayouter<'a> {
|
|||||||
self.dir.start().into()
|
self.dir.start().into()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let role_map = StyleMap::with_role(Role::GenericBlock);
|
||||||
|
let styles = role_map.chain(&styles);
|
||||||
|
|
||||||
let frames = node.layout(ctx, &self.regions, styles)?;
|
let frames = node.layout(ctx, &self.regions, styles)?;
|
||||||
let len = frames.len();
|
let len = frames.len();
|
||||||
for (i, frame) in frames.into_iter().enumerate() {
|
for (i, frame) in frames.into_iter().enumerate() {
|
||||||
|
@ -66,6 +66,7 @@ impl Layout for RexNode {
|
|||||||
let mut backend = FrameBackend {
|
let mut backend = FrameBackend {
|
||||||
frame: {
|
frame: {
|
||||||
let mut frame = Frame::new(size);
|
let mut frame = Frame::new(size);
|
||||||
|
frame.apply_role(Role::Formula);
|
||||||
frame.baseline = Some(baseline);
|
frame.baseline = Some(baseline);
|
||||||
frame
|
frame
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::library::layout::BlockSpacing;
|
use crate::library::layout::BlockSpacing;
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::library::text::{FontFamily, TextNode, TextSize};
|
use crate::library::text::{FontFamily, TextNode, TextSize};
|
||||||
|
use crate::model::StyleEntry;
|
||||||
|
|
||||||
/// A section heading.
|
/// A section heading.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -65,7 +66,13 @@ impl HeadingNode {
|
|||||||
|
|
||||||
impl Show for HeadingNode {
|
impl Show for HeadingNode {
|
||||||
fn unguard(&self, sel: Selector) -> ShowNode {
|
fn unguard(&self, sel: Selector) -> ShowNode {
|
||||||
Self { body: self.body.unguard(sel), ..*self }.pack()
|
let mut map = StyleMap::with_role(Role::Heading(self.level.get()));
|
||||||
|
map.push(StyleEntry::Unguard(sel));
|
||||||
|
Self {
|
||||||
|
body: self.body.clone().styled_with_map(map),
|
||||||
|
..*self
|
||||||
|
}
|
||||||
|
.pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode(&self, _: StyleChain) -> Dict {
|
fn encode(&self, _: StyleChain) -> Dict {
|
||||||
@ -91,7 +98,8 @@ impl Show for HeadingNode {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut map = StyleMap::new();
|
let mut map = StyleMap::with_role(Role::Heading(self.level.get()));
|
||||||
|
|
||||||
map.set(TextNode::SIZE, resolve!(Self::SIZE));
|
map.set(TextNode::SIZE, resolve!(Self::SIZE));
|
||||||
|
|
||||||
if let Smart::Custom(family) = resolve!(Self::FAMILY) {
|
if let Smart::Custom(family) = resolve!(Self::FAMILY) {
|
||||||
|
@ -2,10 +2,11 @@ use std::fmt::Write;
|
|||||||
|
|
||||||
use unscanny::Scanner;
|
use unscanny::Scanner;
|
||||||
|
|
||||||
use crate::library::layout::{BlockSpacing, GridNode, TrackSizing};
|
use crate::library::layout::{BlockSpacing, GridNode, GridSemantics, TrackSizing};
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::library::text::ParNode;
|
use crate::library::text::ParNode;
|
||||||
use crate::library::utility::Numbering;
|
use crate::library::utility::Numbering;
|
||||||
|
use crate::model::StyleEntry;
|
||||||
|
|
||||||
/// An unordered (bulleted) or ordered (numbered) list.
|
/// An unordered (bulleted) or ordered (numbered) list.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -76,9 +77,12 @@ impl<const L: ListKind> ListNode<L> {
|
|||||||
|
|
||||||
impl<const L: ListKind> Show for ListNode<L> {
|
impl<const L: ListKind> Show for ListNode<L> {
|
||||||
fn unguard(&self, sel: Selector) -> ShowNode {
|
fn unguard(&self, sel: Selector) -> ShowNode {
|
||||||
|
let mut map = StyleMap::with_role(Role::ListItemBody);
|
||||||
|
map.push(StyleEntry::Unguard(sel));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
items: self.items.map(|item| ListItem {
|
items: self.items.map(|item| ListItem {
|
||||||
body: Box::new(item.body.unguard(sel)),
|
body: Box::new(item.body.clone().styled_with_map(map.clone())),
|
||||||
..*item
|
..*item
|
||||||
}),
|
}),
|
||||||
..*self
|
..*self
|
||||||
@ -108,9 +112,12 @@ impl<const L: ListKind> Show for ListNode<L> {
|
|||||||
|
|
||||||
for (item, map) in self.items.iter() {
|
for (item, map) in self.items.iter() {
|
||||||
number = item.number.unwrap_or(number);
|
number = item.number.unwrap_or(number);
|
||||||
|
|
||||||
|
let mut label_map = map.clone();
|
||||||
|
label_map.push(StyleEntry::Role(Role::ListLabel));
|
||||||
|
|
||||||
cells.push(LayoutNode::default());
|
cells.push(LayoutNode::default());
|
||||||
cells
|
cells.push(label.resolve(ctx, L, number)?.styled_with_map(label_map).pack());
|
||||||
.push(label.resolve(ctx, L, number)?.styled_with_map(map.clone()).pack());
|
|
||||||
cells.push(LayoutNode::default());
|
cells.push(LayoutNode::default());
|
||||||
cells.push((*item.body).clone().styled_with_map(map.clone()).pack());
|
cells.push((*item.body).clone().styled_with_map(map.clone()).pack());
|
||||||
number += 1;
|
number += 1;
|
||||||
@ -134,6 +141,7 @@ impl<const L: ListKind> Show for ListNode<L> {
|
|||||||
]),
|
]),
|
||||||
gutter: Spec::with_y(vec![TrackSizing::Relative(gutter.into())]),
|
gutter: Spec::with_y(vec![TrackSizing::Relative(gutter.into())]),
|
||||||
cells,
|
cells,
|
||||||
|
semantic: GridSemantics::List,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +163,9 @@ impl<const L: ListKind> Show for ListNode<L> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(realized.spaced(above, below))
|
Ok(realized
|
||||||
|
.styled_with_map(StyleMap::with_role(Role::List(L == ORDERED)))
|
||||||
|
.spaced(above, below))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use crate::library::layout::{BlockSpacing, GridNode, TrackSizing};
|
use crate::library::layout::{BlockSpacing, GridNode, GridSemantics, TrackSizing};
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
|
use crate::model::StyleEntry;
|
||||||
|
|
||||||
/// A table of items.
|
/// A table of items.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -49,10 +50,17 @@ impl TableNode {
|
|||||||
|
|
||||||
impl Show for TableNode {
|
impl Show for TableNode {
|
||||||
fn unguard(&self, sel: Selector) -> ShowNode {
|
fn unguard(&self, sel: Selector) -> ShowNode {
|
||||||
|
let mut map = StyleMap::with_role(Role::TableCell);
|
||||||
|
map.push(StyleEntry::Unguard(sel));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
tracks: self.tracks.clone(),
|
tracks: self.tracks.clone(),
|
||||||
gutter: self.gutter.clone(),
|
gutter: self.gutter.clone(),
|
||||||
cells: self.cells.iter().map(|cell| cell.unguard(sel)).collect(),
|
cells: self
|
||||||
|
.cells
|
||||||
|
.iter()
|
||||||
|
.map(|cell| cell.clone().styled_with_map(map.clone()))
|
||||||
|
.collect(),
|
||||||
}
|
}
|
||||||
.pack()
|
.pack()
|
||||||
}
|
}
|
||||||
@ -100,7 +108,9 @@ impl Show for TableNode {
|
|||||||
tracks: self.tracks.clone(),
|
tracks: self.tracks.clone(),
|
||||||
gutter: self.gutter.clone(),
|
gutter: self.gutter.clone(),
|
||||||
cells,
|
cells,
|
||||||
}))
|
semantic: GridSemantics::Table,
|
||||||
|
})
|
||||||
|
.styled_with_map(StyleMap::with_role(Role::Table)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize(
|
fn finalize(
|
||||||
|
@ -551,6 +551,9 @@ fn prepare<'a>(
|
|||||||
} else {
|
} else {
|
||||||
let size = Size::new(regions.first.x, regions.base.y);
|
let size = Size::new(regions.first.x, regions.base.y);
|
||||||
let pod = Regions::one(size, regions.base, Spec::splat(false));
|
let pod = Regions::one(size, regions.base, Spec::splat(false));
|
||||||
|
let role_map = StyleMap::with_role(Role::GenericInline);
|
||||||
|
let styles = role_map.chain(&styles);
|
||||||
|
|
||||||
let mut frame = node.layout(ctx, &pod, styles)?.remove(0);
|
let mut frame = node.layout(ctx, &pod, styles)?.remove(0);
|
||||||
let shift = styles.get(TextNode::BASELINE);
|
let shift = styles.get(TextNode::BASELINE);
|
||||||
|
|
||||||
@ -1063,6 +1066,7 @@ fn stack(
|
|||||||
let mut finished = vec![];
|
let mut finished = vec![];
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
let mut output = Frame::new(Size::with_x(width));
|
let mut output = Frame::new(Size::with_x(width));
|
||||||
|
output.apply_role(Role::Paragraph);
|
||||||
|
|
||||||
// Stack the lines into one frame per region.
|
// Stack the lines into one frame per region.
|
||||||
for line in lines {
|
for line in lines {
|
||||||
@ -1072,6 +1076,7 @@ fn stack(
|
|||||||
while !regions.first.y.fits(height) && !regions.in_last() {
|
while !regions.first.y.fits(height) && !regions.in_last() {
|
||||||
finished.push(Arc::new(output));
|
finished.push(Arc::new(output));
|
||||||
output = Frame::new(Size::with_x(width));
|
output = Frame::new(Size::with_x(width));
|
||||||
|
output.apply_role(Role::Paragraph);
|
||||||
regions.next();
|
regions.next();
|
||||||
first = true;
|
first = true;
|
||||||
}
|
}
|
||||||
|
@ -113,7 +113,7 @@ impl Show for RawNode {
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
mut realized: Content,
|
mut realized: Content,
|
||||||
) -> TypResult<Content> {
|
) -> TypResult<Content> {
|
||||||
let mut map = StyleMap::new();
|
let mut map = StyleMap::with_role(Role::Code);
|
||||||
map.set_family(styles.get(Self::FAMILY).clone(), styles);
|
map.set_family(styles.get(Self::FAMILY).clone(), styles);
|
||||||
map.set(TextNode::OVERHANG, false);
|
map.set(TextNode::OVERHANG, false);
|
||||||
map.set(TextNode::HYPHENATE, Smart::Custom(Hyphenate(false)));
|
map.set(TextNode::HYPHENATE, Smart::Custom(Hyphenate(false)));
|
||||||
|
@ -232,7 +232,16 @@ impl Layout for LayoutNode {
|
|||||||
let at = ctx.pins.cursor();
|
let at = ctx.pins.cursor();
|
||||||
|
|
||||||
let entry = StyleEntry::Barrier(Barrier::new(node.id()));
|
let entry = StyleEntry::Barrier(Barrier::new(node.id()));
|
||||||
let result = node.0.layout(ctx, regions, entry.chain(&styles));
|
let mut result = node.0.layout(ctx, regions, entry.chain(&styles));
|
||||||
|
|
||||||
|
if let Some(role) = styles.role() {
|
||||||
|
result = result.map(|mut frames| {
|
||||||
|
for frame in frames.iter_mut() {
|
||||||
|
Arc::make_mut(frame).apply_role(role);
|
||||||
|
}
|
||||||
|
frames
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let fresh = ctx.pins.from(at);
|
let fresh = ctx.pins.from(at);
|
||||||
let dirty = ctx.pins.dirty.get();
|
let dirty = ctx.pins.dirty.get();
|
||||||
|
@ -5,6 +5,7 @@ use std::marker::PhantomData;
|
|||||||
|
|
||||||
use super::{Barrier, Content, Key, Property, Recipe, Selector, Show, Target};
|
use super::{Barrier, Content, Key, Property, Recipe, Selector, Show, Target};
|
||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
|
use crate::frame::Role;
|
||||||
use crate::library::text::{FontFamily, TextNode};
|
use crate::library::text::{FontFamily, TextNode};
|
||||||
use crate::util::ReadableTypeId;
|
use crate::util::ReadableTypeId;
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
@ -36,6 +37,13 @@ impl StyleMap {
|
|||||||
styles
|
styles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a style map from a single role.
|
||||||
|
pub fn with_role(role: Role) -> Self {
|
||||||
|
let mut styles = Self::new();
|
||||||
|
styles.push(StyleEntry::Role(role));
|
||||||
|
styles
|
||||||
|
}
|
||||||
|
|
||||||
/// Set an inner value for a style property.
|
/// Set an inner value for a style property.
|
||||||
///
|
///
|
||||||
/// If the property needs folding and the value is already contained in the
|
/// If the property needs folding and the value is already contained in the
|
||||||
@ -170,6 +178,8 @@ pub enum StyleEntry {
|
|||||||
Property(Property),
|
Property(Property),
|
||||||
/// A show rule recipe.
|
/// A show rule recipe.
|
||||||
Recipe(Recipe),
|
Recipe(Recipe),
|
||||||
|
/// A semantic role.
|
||||||
|
Role(Role),
|
||||||
/// A barrier for scoped styles.
|
/// A barrier for scoped styles.
|
||||||
Barrier(Barrier),
|
Barrier(Barrier),
|
||||||
/// Guards against recursive show rules.
|
/// Guards against recursive show rules.
|
||||||
@ -229,6 +239,7 @@ impl Debug for StyleEntry {
|
|||||||
match self {
|
match self {
|
||||||
Self::Property(property) => property.fmt(f)?,
|
Self::Property(property) => property.fmt(f)?,
|
||||||
Self::Recipe(recipe) => recipe.fmt(f)?,
|
Self::Recipe(recipe) => recipe.fmt(f)?,
|
||||||
|
Self::Role(role) => role.fmt(f)?,
|
||||||
Self::Barrier(barrier) => barrier.fmt(f)?,
|
Self::Barrier(barrier) => barrier.fmt(f)?,
|
||||||
Self::Guard(sel) => write!(f, "Guard against {sel:?}")?,
|
Self::Guard(sel) => write!(f, "Guard against {sel:?}")?,
|
||||||
Self::Unguard(sel) => write!(f, "Unguard against {sel:?}")?,
|
Self::Unguard(sel) => write!(f, "Unguard against {sel:?}")?,
|
||||||
@ -324,8 +335,23 @@ impl<'a> StyleChain<'a> {
|
|||||||
Ok(realized)
|
Ok(realized)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieve the current role
|
||||||
|
pub fn role(self) -> Option<Role> {
|
||||||
|
let mut depth = 0;
|
||||||
|
|
||||||
|
for entry in self.entries() {
|
||||||
|
match *entry {
|
||||||
|
StyleEntry::Role(role) => return Some(role),
|
||||||
|
StyleEntry::Barrier(_) if depth == 1 => return None,
|
||||||
|
StyleEntry::Barrier(_) => depth += 1,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether the recipe identified by the selector is guarded.
|
/// Whether the recipe identified by the selector is guarded.
|
||||||
fn guarded(&self, sel: Selector) -> bool {
|
fn guarded(self, sel: Selector) -> bool {
|
||||||
for entry in self.entries() {
|
for entry in self.entries() {
|
||||||
match *entry {
|
match *entry {
|
||||||
StyleEntry::Guard(s) if s == sel => return true,
|
StyleEntry::Guard(s) if s == sel => return true,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user