mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Simplify decorations
This commit is contained in:
parent
5becb32ba4
commit
b42ecbd6a6
@ -22,7 +22,7 @@ pub struct Template(Rc<Vec<TemplateNode>>);
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum TemplateNode {
|
enum TemplateNode {
|
||||||
/// A word space.
|
/// A word space.
|
||||||
Space(Vec<Decoration>),
|
Space,
|
||||||
/// A line break.
|
/// A line break.
|
||||||
Linebreak,
|
Linebreak,
|
||||||
/// A paragraph break.
|
/// A paragraph break.
|
||||||
@ -30,11 +30,13 @@ enum TemplateNode {
|
|||||||
/// A page break.
|
/// A page break.
|
||||||
Pagebreak(bool),
|
Pagebreak(bool),
|
||||||
/// Plain text.
|
/// Plain text.
|
||||||
Text(EcoString, Vec<Decoration>),
|
Text(EcoString),
|
||||||
/// Spacing.
|
/// Spacing.
|
||||||
Spacing(GenAxis, Linear),
|
Spacing(GenAxis, Linear),
|
||||||
|
/// A decorated template.
|
||||||
|
Decorated(Decoration, Template),
|
||||||
/// An inline node builder.
|
/// An inline node builder.
|
||||||
Inline(Rc<dyn Fn(&Style) -> InlineNode>, Vec<Decoration>),
|
Inline(Rc<dyn Fn(&Style) -> InlineNode>),
|
||||||
/// An block node builder.
|
/// An block node builder.
|
||||||
Block(Rc<dyn Fn(&Style) -> BlockNode>),
|
Block(Rc<dyn Fn(&Style) -> BlockNode>),
|
||||||
/// Save the current style.
|
/// Save the current style.
|
||||||
@ -57,7 +59,7 @@ impl Template {
|
|||||||
F: Fn(&Style) -> T + 'static,
|
F: Fn(&Style) -> T + 'static,
|
||||||
T: Into<InlineNode>,
|
T: Into<InlineNode>,
|
||||||
{
|
{
|
||||||
let node = TemplateNode::Inline(Rc::new(move |s| f(s).into()), vec![]);
|
let node = TemplateNode::Inline(Rc::new(move |s| f(s).into()));
|
||||||
Self(Rc::new(vec![node]))
|
Self(Rc::new(vec![node]))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +75,7 @@ impl Template {
|
|||||||
|
|
||||||
/// Add a word space to the template.
|
/// Add a word space to the template.
|
||||||
pub fn space(&mut self) {
|
pub fn space(&mut self) {
|
||||||
self.make_mut().push(TemplateNode::Space(vec![]));
|
self.make_mut().push(TemplateNode::Space);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a line break to the template.
|
/// Add a line break to the template.
|
||||||
@ -93,7 +95,7 @@ impl Template {
|
|||||||
|
|
||||||
/// Add text to the template.
|
/// Add text to the template.
|
||||||
pub fn text(&mut self, text: impl Into<EcoString>) {
|
pub fn text(&mut self, text: impl Into<EcoString>) {
|
||||||
self.make_mut().push(TemplateNode::Text(text.into(), vec![]));
|
self.make_mut().push(TemplateNode::Text(text.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add text, but in monospace.
|
/// Add text, but in monospace.
|
||||||
@ -109,19 +111,6 @@ impl Template {
|
|||||||
self.make_mut().push(TemplateNode::Spacing(axis, spacing));
|
self.make_mut().push(TemplateNode::Spacing(axis, spacing));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a decoration to all contained nodes.
|
|
||||||
pub fn decorate(&mut self, deco: Decoration) {
|
|
||||||
for node in self.make_mut() {
|
|
||||||
let decos = match node {
|
|
||||||
TemplateNode::Space(decos) => decos,
|
|
||||||
TemplateNode::Text(_, decos) => decos,
|
|
||||||
TemplateNode::Inline(_, decos) => decos,
|
|
||||||
_ => continue,
|
|
||||||
};
|
|
||||||
decos.push(deco.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Register a restorable snapshot.
|
/// Register a restorable snapshot.
|
||||||
pub fn save(&mut self) {
|
pub fn save(&mut self) {
|
||||||
self.make_mut().push(TemplateNode::Save);
|
self.make_mut().push(TemplateNode::Save);
|
||||||
@ -154,6 +143,11 @@ impl Template {
|
|||||||
wrapper
|
wrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a decoration to all contained nodes.
|
||||||
|
pub fn decorate(self, deco: Decoration) -> Self {
|
||||||
|
Self(Rc::new(vec![TemplateNode::Decorated(deco, self)]))
|
||||||
|
}
|
||||||
|
|
||||||
/// Build the stack node resulting from instantiating the template with the
|
/// Build the stack node resulting from instantiating the template with the
|
||||||
/// given style.
|
/// given style.
|
||||||
pub fn to_stack(&self, style: &Style) -> StackNode {
|
pub fn to_stack(&self, style: &Style) -> StackNode {
|
||||||
@ -223,7 +217,7 @@ impl Add<Str> for Template {
|
|||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn add(mut self, rhs: Str) -> Self::Output {
|
fn add(mut self, rhs: Str) -> Self::Output {
|
||||||
Rc::make_mut(&mut self.0).push(TemplateNode::Text(rhs.into(), vec![]));
|
Rc::make_mut(&mut self.0).push(TemplateNode::Text(rhs.into()));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -232,7 +226,7 @@ impl Add<Template> for Str {
|
|||||||
type Output = Template;
|
type Output = Template;
|
||||||
|
|
||||||
fn add(self, mut rhs: Template) -> Self::Output {
|
fn add(self, mut rhs: Template) -> Self::Output {
|
||||||
Rc::make_mut(&mut rhs.0).insert(0, TemplateNode::Text(self.into(), vec![]));
|
Rc::make_mut(&mut rhs.0).insert(0, TemplateNode::Text(self.into()));
|
||||||
rhs
|
rhs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -283,26 +277,31 @@ impl Builder {
|
|||||||
self.pagebreak(true, false);
|
self.pagebreak(true, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TemplateNode::Space(decos) => self.space(decos),
|
TemplateNode::Space => self.space(),
|
||||||
TemplateNode::Linebreak => self.linebreak(),
|
TemplateNode::Linebreak => self.linebreak(),
|
||||||
TemplateNode::Parbreak => self.parbreak(),
|
TemplateNode::Parbreak => self.parbreak(),
|
||||||
TemplateNode::Pagebreak(keep) => self.pagebreak(*keep, true),
|
TemplateNode::Pagebreak(keep) => self.pagebreak(*keep, true),
|
||||||
TemplateNode::Text(text, decos) => self.text(text, decos),
|
TemplateNode::Text(text) => self.text(text),
|
||||||
TemplateNode::Spacing(axis, amount) => self.spacing(*axis, *amount),
|
TemplateNode::Spacing(axis, amount) => self.spacing(*axis, *amount),
|
||||||
TemplateNode::Inline(f, decos) => self.inline(f(&self.style), decos),
|
TemplateNode::Decorated(deco, template) => {
|
||||||
|
self.stack.par.push(ParChild::Decorate(deco.clone()));
|
||||||
|
self.template(template);
|
||||||
|
self.stack.par.push(ParChild::Undecorate);
|
||||||
|
}
|
||||||
|
TemplateNode::Inline(f) => self.inline(f(&self.style)),
|
||||||
TemplateNode::Block(f) => self.block(f(&self.style)),
|
TemplateNode::Block(f) => self.block(f(&self.style)),
|
||||||
TemplateNode::Modify(f) => f(&mut self.style),
|
TemplateNode::Modify(f) => f(&mut self.style),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a word space into the active paragraph.
|
/// Push a word space into the active paragraph.
|
||||||
fn space(&mut self, decos: &[Decoration]) {
|
fn space(&mut self) {
|
||||||
self.stack.par.push_soft(self.make_text_node(' ', decos.to_vec()));
|
self.stack.par.push_soft(self.make_text_node(' '));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a forced line break.
|
/// Apply a forced line break.
|
||||||
fn linebreak(&mut self) {
|
fn linebreak(&mut self) {
|
||||||
self.stack.par.push_hard(self.make_text_node('\n', vec![]));
|
self.stack.par.push_hard(self.make_text_node('\n'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a forced paragraph break.
|
/// Apply a forced paragraph break.
|
||||||
@ -322,14 +321,14 @@ impl Builder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Push text into the active paragraph.
|
/// Push text into the active paragraph.
|
||||||
fn text(&mut self, text: impl Into<EcoString>, decos: &[Decoration]) {
|
fn text(&mut self, text: impl Into<EcoString>) {
|
||||||
self.stack.par.push(self.make_text_node(text, decos.to_vec()));
|
self.stack.par.push(self.make_text_node(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push an inline node into the active paragraph.
|
/// Push an inline node into the active paragraph.
|
||||||
fn inline(&mut self, node: impl Into<InlineNode>, decos: &[Decoration]) {
|
fn inline(&mut self, node: impl Into<InlineNode>) {
|
||||||
let align = self.style.aligns.inline;
|
let align = self.style.aligns.inline;
|
||||||
self.stack.par.push(ParChild::Any(node.into(), align, decos.to_vec()));
|
self.stack.par.push(ParChild::Any(node.into(), align));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a block node into the active stack, finishing the active paragraph.
|
/// Push a block node into the active stack, finishing the active paragraph.
|
||||||
@ -367,16 +366,11 @@ impl Builder {
|
|||||||
|
|
||||||
/// Construct a text node with the given text and settings from the current
|
/// Construct a text node with the given text and settings from the current
|
||||||
/// style.
|
/// style.
|
||||||
fn make_text_node(
|
fn make_text_node(&self, text: impl Into<EcoString>) -> ParChild {
|
||||||
&self,
|
|
||||||
text: impl Into<EcoString>,
|
|
||||||
decos: Vec<Decoration>,
|
|
||||||
) -> ParChild {
|
|
||||||
ParChild::Text(
|
ParChild::Text(
|
||||||
text.into(),
|
text.into(),
|
||||||
self.style.aligns.inline,
|
self.style.aligns.inline,
|
||||||
Rc::clone(&self.style.text),
|
Rc::clone(&self.style.text),
|
||||||
decos,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -489,14 +483,11 @@ impl ParBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn push_inner(&mut self, child: ParChild) {
|
fn push_inner(&mut self, child: ParChild) {
|
||||||
if let ParChild::Text(curr_text, curr_align, curr_props, curr_decos) = &child {
|
if let ParChild::Text(curr_text, curr_align, curr_props) = &child {
|
||||||
if let Some(ParChild::Text(prev_text, prev_align, prev_props, prev_decos)) =
|
if let Some(ParChild::Text(prev_text, prev_align, prev_props)) =
|
||||||
self.children.last_mut()
|
self.children.last_mut()
|
||||||
{
|
{
|
||||||
if prev_align == curr_align
|
if prev_align == curr_align && Rc::ptr_eq(prev_props, curr_props) {
|
||||||
&& Rc::ptr_eq(prev_props, curr_props)
|
|
||||||
&& curr_decos == prev_decos
|
|
||||||
{
|
|
||||||
prev_text.push_str(&curr_text);
|
prev_text.push_str(&curr_text);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -113,7 +113,6 @@ fn walk_item(ctx: &mut EvalContext, label: Str, body: Template) {
|
|||||||
(&label).into(),
|
(&label).into(),
|
||||||
style.aligns.inline,
|
style.aligns.inline,
|
||||||
Rc::clone(&style.text),
|
Rc::clone(&style.text),
|
||||||
vec![],
|
|
||||||
)],
|
)],
|
||||||
};
|
};
|
||||||
StackNode {
|
StackNode {
|
||||||
|
92
src/layout/deco.rs
Normal file
92
src/layout/deco.rs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::util::EcoString;
|
||||||
|
|
||||||
|
/// A decoration for a frame.
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum Decoration {
|
||||||
|
/// A link to an external resource.
|
||||||
|
Link(EcoString),
|
||||||
|
/// An underline/strikethrough/overline decoration.
|
||||||
|
Line(LineDecoration),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decoration {
|
||||||
|
/// Apply a decoration to a child's frame.
|
||||||
|
pub fn apply(&self, ctx: &LayoutContext, frame: &mut Frame) {
|
||||||
|
match self {
|
||||||
|
Decoration::Link(href) => {
|
||||||
|
let link = Element::Link(href.to_string(), frame.size);
|
||||||
|
frame.push(Point::zero(), link);
|
||||||
|
}
|
||||||
|
Decoration::Line(line) => {
|
||||||
|
line.apply(ctx, frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines a line that is positioned over, under or on top of text.
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct LineDecoration {
|
||||||
|
/// The kind of line.
|
||||||
|
pub kind: LineKind,
|
||||||
|
/// Stroke color of the line, defaults to the text color if `None`.
|
||||||
|
pub stroke: Option<Paint>,
|
||||||
|
/// Thickness of the line's strokes (dependent on scaled font size), read
|
||||||
|
/// from the font tables if `None`.
|
||||||
|
pub thickness: Option<Linear>,
|
||||||
|
/// Position of the line relative to the baseline (dependent on scaled font
|
||||||
|
/// size), read from the font tables if `None`.
|
||||||
|
pub offset: Option<Linear>,
|
||||||
|
/// Amount that the line will be longer or shorter than its associated text
|
||||||
|
/// (dependent on scaled font size).
|
||||||
|
pub extent: Linear,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The kind of line decoration.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum LineKind {
|
||||||
|
/// A line under text.
|
||||||
|
Underline,
|
||||||
|
/// A line through text.
|
||||||
|
Strikethrough,
|
||||||
|
/// A line over text.
|
||||||
|
Overline,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LineDecoration {
|
||||||
|
/// Apply a line decoration to a all text elements in a frame.
|
||||||
|
pub fn apply(&self, ctx: &LayoutContext, frame: &mut Frame) {
|
||||||
|
for i in 0 .. frame.children.len() {
|
||||||
|
let (pos, child) = &frame.children[i];
|
||||||
|
if let FrameChild::Element(Element::Text(text)) = child {
|
||||||
|
let face = ctx.fonts.get(text.face_id);
|
||||||
|
let metrics = match self.kind {
|
||||||
|
LineKind::Underline => face.underline,
|
||||||
|
LineKind::Strikethrough => face.strikethrough,
|
||||||
|
LineKind::Overline => face.overline,
|
||||||
|
};
|
||||||
|
|
||||||
|
let stroke = self.stroke.unwrap_or(text.fill);
|
||||||
|
|
||||||
|
let thickness = self
|
||||||
|
.thickness
|
||||||
|
.map(|s| s.resolve(text.size))
|
||||||
|
.unwrap_or(metrics.strength.to_length(text.size));
|
||||||
|
|
||||||
|
let offset = self
|
||||||
|
.offset
|
||||||
|
.map(|s| s.resolve(text.size))
|
||||||
|
.unwrap_or(-metrics.position.to_length(text.size));
|
||||||
|
|
||||||
|
let extent = self.extent.resolve(text.size);
|
||||||
|
|
||||||
|
let subpos = Point::new(pos.x - extent, pos.y + offset);
|
||||||
|
let vector = Point::new(text.width + 2.0 * extent, Length::zero());
|
||||||
|
let line = Geometry::Line(vector, thickness);
|
||||||
|
|
||||||
|
frame.push(subpos, Element::Geometry(line, stroke));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
//! Layouting.
|
//! Layouting.
|
||||||
|
|
||||||
mod constraints;
|
mod constraints;
|
||||||
|
mod deco;
|
||||||
mod frame;
|
mod frame;
|
||||||
mod grid;
|
mod grid;
|
||||||
mod image;
|
mod image;
|
||||||
@ -15,6 +16,7 @@ mod text;
|
|||||||
|
|
||||||
pub use self::image::*;
|
pub use self::image::*;
|
||||||
pub use constraints::*;
|
pub use constraints::*;
|
||||||
|
pub use deco::*;
|
||||||
pub use frame::*;
|
pub use frame::*;
|
||||||
pub use grid::*;
|
pub use grid::*;
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
|
@ -29,9 +29,13 @@ pub enum ParChild {
|
|||||||
/// Spacing between other nodes.
|
/// Spacing between other nodes.
|
||||||
Spacing(Linear),
|
Spacing(Linear),
|
||||||
/// A run of text and how to align it in its line.
|
/// A run of text and how to align it in its line.
|
||||||
Text(EcoString, Align, Rc<TextStyle>, Vec<Decoration>),
|
Text(EcoString, Align, Rc<TextStyle>),
|
||||||
/// Any child node and how to align it in its line.
|
/// Any child node and how to align it in its line.
|
||||||
Any(InlineNode, Align, Vec<Decoration>),
|
Any(InlineNode, Align),
|
||||||
|
/// A decoration that applies until a matching `Undecorate`.
|
||||||
|
Decorate(Decoration),
|
||||||
|
/// The end of a decoration.
|
||||||
|
Undecorate,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlockLevel for ParNode {
|
impl BlockLevel for ParNode {
|
||||||
@ -84,6 +88,7 @@ impl ParNode {
|
|||||||
ParChild::Spacing(_) => " ",
|
ParChild::Spacing(_) => " ",
|
||||||
ParChild::Text(ref piece, ..) => piece,
|
ParChild::Text(ref piece, ..) => piece,
|
||||||
ParChild::Any(..) => "\u{FFFC}",
|
ParChild::Any(..) => "\u{FFFC}",
|
||||||
|
ParChild::Decorate(_) | ParChild::Undecorate => "",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,6 +105,8 @@ impl Debug for ParChild {
|
|||||||
Self::Spacing(v) => write!(f, "Spacing({:?})", v),
|
Self::Spacing(v) => write!(f, "Spacing({:?})", v),
|
||||||
Self::Text(text, ..) => write!(f, "Text({:?})", text),
|
Self::Text(text, ..) => write!(f, "Text({:?})", text),
|
||||||
Self::Any(node, ..) => node.fmt(f),
|
Self::Any(node, ..) => node.fmt(f),
|
||||||
|
Self::Decorate(deco) => write!(f, "Decorate({:?})", deco),
|
||||||
|
Self::Undecorate => write!(f, "Undecorate"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,6 +124,18 @@ struct ParLayouter<'a> {
|
|||||||
items: Vec<ParItem<'a>>,
|
items: Vec<ParItem<'a>>,
|
||||||
/// The ranges of the items in `bidi.text`.
|
/// The ranges of the items in `bidi.text`.
|
||||||
ranges: Vec<Range>,
|
ranges: Vec<Range>,
|
||||||
|
/// The decorations and the ranges they span.
|
||||||
|
decos: Vec<(Range, &'a Decoration)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A prepared item in a paragraph layout.
|
||||||
|
enum ParItem<'a> {
|
||||||
|
/// Spacing between other items.
|
||||||
|
Spacing(Length),
|
||||||
|
/// A shaped text run with consistent direction.
|
||||||
|
Text(ShapedText<'a>, Align),
|
||||||
|
/// A layouted child node.
|
||||||
|
Frame(Frame, Align),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ParLayouter<'a> {
|
impl<'a> ParLayouter<'a> {
|
||||||
@ -127,9 +146,10 @@ impl<'a> ParLayouter<'a> {
|
|||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
bidi: BidiInfo<'a>,
|
bidi: BidiInfo<'a>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Prepare an iterator over each child an the range it spans.
|
|
||||||
let mut items = vec![];
|
let mut items = vec![];
|
||||||
let mut ranges = vec![];
|
let mut ranges = vec![];
|
||||||
|
let mut starts = vec![];
|
||||||
|
let mut decos = vec![];
|
||||||
|
|
||||||
// Layout the children and collect them into items.
|
// Layout the children and collect them into items.
|
||||||
for (range, child) in par.ranges().zip(&par.children) {
|
for (range, child) in par.ranges().zip(&par.children) {
|
||||||
@ -139,20 +159,27 @@ impl<'a> ParLayouter<'a> {
|
|||||||
items.push(ParItem::Spacing(resolved));
|
items.push(ParItem::Spacing(resolved));
|
||||||
ranges.push(range);
|
ranges.push(range);
|
||||||
}
|
}
|
||||||
ParChild::Text(_, align, style, decos) => {
|
ParChild::Text(_, align, style) => {
|
||||||
// TODO: Also split by language and script.
|
// TODO: Also split by language and script.
|
||||||
for (subrange, dir) in split_runs(&bidi, range) {
|
for (subrange, dir) in split_runs(&bidi, range) {
|
||||||
let text = &bidi.text[subrange.clone()];
|
let text = &bidi.text[subrange.clone()];
|
||||||
let shaped = shape(ctx, text, style, dir);
|
let shaped = shape(ctx, text, style, dir);
|
||||||
items.push(ParItem::Text(shaped, *align, decos));
|
items.push(ParItem::Text(shaped, *align));
|
||||||
ranges.push(subrange);
|
ranges.push(subrange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ParChild::Any(node, align, decos) => {
|
ParChild::Any(node, align) => {
|
||||||
let frame = node.layout(ctx, regions.current.w, regions.base);
|
let frame = node.layout(ctx, regions.current.w, regions.base);
|
||||||
items.push(ParItem::Frame(frame, *align, decos));
|
items.push(ParItem::Frame(frame, *align));
|
||||||
ranges.push(range);
|
ranges.push(range);
|
||||||
}
|
}
|
||||||
|
ParChild::Decorate(deco) => {
|
||||||
|
starts.push((range.start, deco));
|
||||||
|
}
|
||||||
|
ParChild::Undecorate => {
|
||||||
|
let (start, deco) = starts.pop().unwrap();
|
||||||
|
decos.push((start .. range.end, deco));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,6 +189,7 @@ impl<'a> ParLayouter<'a> {
|
|||||||
bidi,
|
bidi,
|
||||||
items,
|
items,
|
||||||
ranges,
|
ranges,
|
||||||
|
decos,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,16 +313,6 @@ fn split_runs<'a>(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A prepared item in a paragraph layout.
|
|
||||||
enum ParItem<'a> {
|
|
||||||
/// Spacing between other items.
|
|
||||||
Spacing(Length),
|
|
||||||
/// A shaped text run with consistent direction.
|
|
||||||
Text(ShapedText<'a>, Align, &'a [Decoration]),
|
|
||||||
/// A layouted child node.
|
|
||||||
Frame(Frame, Align, &'a [Decoration]),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ParItem<'_> {
|
impl ParItem<'_> {
|
||||||
/// The size of the item.
|
/// The size of the item.
|
||||||
pub fn size(&self) -> Size {
|
pub fn size(&self) -> Size {
|
||||||
@ -319,10 +337,8 @@ impl ParItem<'_> {
|
|||||||
/// paragraph's text. This type enables you to cheaply measure the size of a
|
/// paragraph's text. This type enables you to cheaply measure the size of a
|
||||||
/// line in a range before comitting to building the line's frame.
|
/// line in a range before comitting to building the line's frame.
|
||||||
struct LineLayout<'a> {
|
struct LineLayout<'a> {
|
||||||
/// The direction of the line.
|
|
||||||
dir: Dir,
|
|
||||||
/// Bidi information about the paragraph.
|
/// Bidi information about the paragraph.
|
||||||
bidi: &'a BidiInfo<'a>,
|
par: &'a ParLayouter<'a>,
|
||||||
/// The range the line spans in the paragraph.
|
/// The range the line spans in the paragraph.
|
||||||
line: Range,
|
line: Range,
|
||||||
/// A reshaped text item if the line sliced up a text item at the start.
|
/// A reshaped text item if the line sliced up a text item at the start.
|
||||||
@ -359,7 +375,7 @@ impl<'a> LineLayout<'a> {
|
|||||||
|
|
||||||
// Reshape the last item if it's split in half.
|
// Reshape the last item if it's split in half.
|
||||||
let mut last = None;
|
let mut last = None;
|
||||||
if let Some((ParItem::Text(shaped, align, i), rest)) = items.split_last() {
|
if let Some((ParItem::Text(shaped, align), rest)) = items.split_last() {
|
||||||
// Compute the range we want to shape, trimming whitespace at the
|
// Compute the range we want to shape, trimming whitespace at the
|
||||||
// end of the line.
|
// end of the line.
|
||||||
let base = par.ranges[last_idx].start;
|
let base = par.ranges[last_idx].start;
|
||||||
@ -375,7 +391,7 @@ impl<'a> LineLayout<'a> {
|
|||||||
if !range.is_empty() || rest.is_empty() {
|
if !range.is_empty() || rest.is_empty() {
|
||||||
// Reshape that part.
|
// Reshape that part.
|
||||||
let reshaped = shaped.reshape(ctx, range);
|
let reshaped = shaped.reshape(ctx, range);
|
||||||
last = Some(ParItem::Text(reshaped, *align, *i));
|
last = Some(ParItem::Text(reshaped, *align));
|
||||||
}
|
}
|
||||||
|
|
||||||
items = rest;
|
items = rest;
|
||||||
@ -385,7 +401,7 @@ impl<'a> LineLayout<'a> {
|
|||||||
|
|
||||||
// Reshape the start item if it's split in half.
|
// Reshape the start item if it's split in half.
|
||||||
let mut first = None;
|
let mut first = None;
|
||||||
if let Some((ParItem::Text(shaped, align, i), rest)) = items.split_first() {
|
if let Some((ParItem::Text(shaped, align), rest)) = items.split_first() {
|
||||||
// Compute the range we want to shape.
|
// Compute the range we want to shape.
|
||||||
let Range { start: base, end: first_end } = par.ranges[first_idx];
|
let Range { start: base, end: first_end } = par.ranges[first_idx];
|
||||||
let start = line.start;
|
let start = line.start;
|
||||||
@ -396,7 +412,7 @@ impl<'a> LineLayout<'a> {
|
|||||||
if range.len() < shaped.text.len() {
|
if range.len() < shaped.text.len() {
|
||||||
if !range.is_empty() {
|
if !range.is_empty() {
|
||||||
let reshaped = shaped.reshape(ctx, range);
|
let reshaped = shaped.reshape(ctx, range);
|
||||||
first = Some(ParItem::Text(reshaped, *align, *i));
|
first = Some(ParItem::Text(reshaped, *align));
|
||||||
}
|
}
|
||||||
|
|
||||||
items = rest;
|
items = rest;
|
||||||
@ -417,8 +433,7 @@ impl<'a> LineLayout<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
dir: par.dir,
|
par,
|
||||||
bidi: &par.bidi,
|
|
||||||
line,
|
line,
|
||||||
first,
|
first,
|
||||||
items,
|
items,
|
||||||
@ -438,36 +453,29 @@ impl<'a> LineLayout<'a> {
|
|||||||
let mut offset = Length::zero();
|
let mut offset = Length::zero();
|
||||||
let mut ruler = Align::Start;
|
let mut ruler = Align::Start;
|
||||||
|
|
||||||
for item in self.reordered() {
|
for (range, item) in self.reordered() {
|
||||||
let mut position = |frame: &Frame, align| {
|
let mut position = |mut frame: Frame, align: Align| {
|
||||||
|
// Decorate.
|
||||||
|
for (deco_range, deco) in &self.par.decos {
|
||||||
|
if deco_range.contains(&range.start) {
|
||||||
|
deco.apply(ctx, &mut frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: Ruler alignment for RTL.
|
// FIXME: Ruler alignment for RTL.
|
||||||
ruler = ruler.max(align);
|
ruler = ruler.max(align);
|
||||||
let x = ruler.resolve(self.dir, offset .. free + offset);
|
let x = ruler.resolve(self.par.dir, offset .. free + offset);
|
||||||
let y = self.baseline - frame.baseline;
|
let y = self.baseline - frame.baseline;
|
||||||
offset += frame.size.w;
|
offset += frame.size.w;
|
||||||
Point::new(x, y)
|
|
||||||
|
// Add to the line's frame.
|
||||||
|
output.merge_frame(Point::new(x, y), frame);
|
||||||
};
|
};
|
||||||
|
|
||||||
match *item {
|
match *item {
|
||||||
ParItem::Spacing(amount) => {
|
ParItem::Spacing(amount) => offset += amount,
|
||||||
offset += amount;
|
ParItem::Text(ref shaped, align) => position(shaped.build(), align),
|
||||||
}
|
ParItem::Frame(ref frame, align) => position(frame.clone(), align),
|
||||||
ParItem::Text(ref shaped, align, decos) => {
|
|
||||||
let mut frame = shaped.build();
|
|
||||||
for deco in decos {
|
|
||||||
deco.apply(ctx, &mut frame);
|
|
||||||
}
|
|
||||||
let pos = position(&frame, align);
|
|
||||||
output.merge_frame(pos, frame);
|
|
||||||
}
|
|
||||||
ParItem::Frame(ref frame, align, decos) => {
|
|
||||||
let mut frame = frame.clone();
|
|
||||||
for deco in decos {
|
|
||||||
deco.apply(ctx, &mut frame);
|
|
||||||
}
|
|
||||||
let pos = position(&frame, align);
|
|
||||||
output.merge_frame(pos, frame);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -475,11 +483,12 @@ impl<'a> LineLayout<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate through the line's items in visual order.
|
/// Iterate through the line's items in visual order.
|
||||||
fn reordered(&self) -> impl Iterator<Item = &ParItem<'a>> {
|
fn reordered(&self) -> impl Iterator<Item = (Range, &ParItem<'a>)> {
|
||||||
// The bidi crate doesn't like empty lines.
|
// The bidi crate doesn't like empty lines.
|
||||||
let (levels, runs) = if !self.line.is_empty() {
|
let (levels, runs) = if !self.line.is_empty() {
|
||||||
// Find the paragraph that contains the line.
|
// Find the paragraph that contains the line.
|
||||||
let para = self
|
let para = self
|
||||||
|
.par
|
||||||
.bidi
|
.bidi
|
||||||
.paragraphs
|
.paragraphs
|
||||||
.iter()
|
.iter()
|
||||||
@ -487,7 +496,7 @@ impl<'a> LineLayout<'a> {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Compute the reordered ranges in visual order (left to right).
|
// Compute the reordered ranges in visual order (left to right).
|
||||||
self.bidi.visual_runs(para, self.line.clone())
|
self.par.bidi.visual_runs(para, self.line.clone())
|
||||||
} else {
|
} else {
|
||||||
<_>::default()
|
<_>::default()
|
||||||
};
|
};
|
||||||
@ -506,7 +515,7 @@ impl<'a> LineLayout<'a> {
|
|||||||
Either::Right(range.rev())
|
Either::Right(range.rev())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(move |idx| self.get(idx).unwrap())
|
.map(move |idx| (self.ranges[idx].clone(), self.get(idx).unwrap()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the index of the item whose range contains the `text_offset`.
|
/// Find the index of the item whose range contains the `text_offset`.
|
||||||
@ -604,96 +613,6 @@ impl<'a> LineStack<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A decoration for a paragraph child.
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub enum Decoration {
|
|
||||||
/// A link.
|
|
||||||
Link(EcoString),
|
|
||||||
/// An underline/strikethrough/overline decoration.
|
|
||||||
Line(LineDecoration),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Defines a line that is positioned over, under or on top of text.
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub struct LineDecoration {
|
|
||||||
/// The kind of line.
|
|
||||||
pub kind: LineKind,
|
|
||||||
/// Stroke color of the line, defaults to the text color if `None`.
|
|
||||||
pub stroke: Option<Paint>,
|
|
||||||
/// Thickness of the line's strokes (dependent on scaled font size), read
|
|
||||||
/// from the font tables if `None`.
|
|
||||||
pub thickness: Option<Linear>,
|
|
||||||
/// Position of the line relative to the baseline (dependent on scaled font
|
|
||||||
/// size), read from the font tables if `None`.
|
|
||||||
pub offset: Option<Linear>,
|
|
||||||
/// Amount that the line will be longer or shorter than its associated text
|
|
||||||
/// (dependent on scaled font size).
|
|
||||||
pub extent: Linear,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The kind of line decoration.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub enum LineKind {
|
|
||||||
/// A line under text.
|
|
||||||
Underline,
|
|
||||||
/// A line through text.
|
|
||||||
Strikethrough,
|
|
||||||
/// A line over text.
|
|
||||||
Overline,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Decoration {
|
|
||||||
/// Apply a decoration to a child's frame.
|
|
||||||
pub fn apply(&self, ctx: &LayoutContext, frame: &mut Frame) {
|
|
||||||
match self {
|
|
||||||
Decoration::Link(href) => {
|
|
||||||
let link = Element::Link(href.to_string(), frame.size);
|
|
||||||
frame.push(Point::zero(), link);
|
|
||||||
}
|
|
||||||
Decoration::Line(line) => {
|
|
||||||
line.apply(ctx, frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LineDecoration {
|
|
||||||
/// Apply a line decoration to a all text elements in a frame.
|
|
||||||
pub fn apply(&self, ctx: &LayoutContext, frame: &mut Frame) {
|
|
||||||
for i in 0 .. frame.children.len() {
|
|
||||||
let (pos, child) = &frame.children[i];
|
|
||||||
if let FrameChild::Element(Element::Text(text)) = child {
|
|
||||||
let face = ctx.fonts.get(text.face_id);
|
|
||||||
let metrics = match self.kind {
|
|
||||||
LineKind::Underline => face.underline,
|
|
||||||
LineKind::Strikethrough => face.strikethrough,
|
|
||||||
LineKind::Overline => face.overline,
|
|
||||||
};
|
|
||||||
|
|
||||||
let stroke = self.stroke.unwrap_or(text.fill);
|
|
||||||
|
|
||||||
let thickness = self
|
|
||||||
.thickness
|
|
||||||
.map(|s| s.resolve(text.size))
|
|
||||||
.unwrap_or(metrics.strength.to_length(text.size));
|
|
||||||
|
|
||||||
let offset = self
|
|
||||||
.offset
|
|
||||||
.map(|s| s.resolve(text.size))
|
|
||||||
.unwrap_or(-metrics.position.to_length(text.size));
|
|
||||||
|
|
||||||
let extent = self.extent.resolve(text.size);
|
|
||||||
|
|
||||||
let subpos = Point::new(pos.x - extent, pos.y + offset);
|
|
||||||
let vector = Point::new(text.width + 2.0 * extent, Length::zero());
|
|
||||||
let line = Geometry::Line(vector, thickness);
|
|
||||||
|
|
||||||
frame.push(subpos, Element::Geometry(line, stroke));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods for BiDi levels.
|
/// Additional methods for BiDi levels.
|
||||||
trait LevelExt: Sized {
|
trait LevelExt: Sized {
|
||||||
fn from_dir(dir: Dir) -> Option<Self>;
|
fn from_dir(dir: Dir) -> Option<Self>;
|
||||||
|
@ -181,30 +181,27 @@ fn line_impl(args: &mut Args, kind: LineKind) -> TypResult<Value> {
|
|||||||
let thickness = args.named::<Linear>("thickness")?.or_else(|| args.eat());
|
let thickness = args.named::<Linear>("thickness")?.or_else(|| args.eat());
|
||||||
let offset = args.named("offset")?;
|
let offset = args.named("offset")?;
|
||||||
let extent = args.named("extent")?.unwrap_or_default();
|
let extent = args.named("extent")?.unwrap_or_default();
|
||||||
|
let body: Template = args.expect("body")?;
|
||||||
|
|
||||||
let mut body: Template = args.expect("body")?;
|
Ok(Value::Template(body.decorate(Decoration::Line(
|
||||||
body.decorate(Decoration::Line(LineDecoration {
|
LineDecoration {
|
||||||
kind,
|
kind,
|
||||||
stroke: stroke.map(Paint::Color),
|
stroke: stroke.map(Paint::Color),
|
||||||
thickness,
|
thickness,
|
||||||
offset,
|
offset,
|
||||||
extent,
|
extent,
|
||||||
}));
|
},
|
||||||
|
))))
|
||||||
Ok(Value::Template(body))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `link`: Typeset text as a link.
|
/// `link`: Typeset text as a link.
|
||||||
pub fn link(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn link(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
let url = args.expect::<Str>("url")?;
|
let url = args.expect::<Str>("url")?;
|
||||||
|
let body = args.eat().unwrap_or_else(|| {
|
||||||
let mut body = args.eat().unwrap_or_else(|| {
|
|
||||||
let mut template = Template::new();
|
let mut template = Template::new();
|
||||||
template.text(&url);
|
template.text(&url);
|
||||||
template
|
template
|
||||||
});
|
});
|
||||||
|
|
||||||
body.decorate(Decoration::Link(url.into()));
|
Ok(Value::Template(body.decorate(Decoration::Link(url.into()))))
|
||||||
|
|
||||||
Ok(Value::Template(body))
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user