This commit is contained in:
Laurenz 2022-01-05 14:49:14 +01:00
parent 4c81a5d43e
commit f7e8624b4c
14 changed files with 149 additions and 139 deletions

View File

@ -84,7 +84,7 @@ impl Class {
impl Debug for Class {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("<class ")?;
f.write_str(&self.0.name)?;
f.write_str(self.name())?;
f.write_char('>')
}
}

View File

@ -70,8 +70,8 @@ impl StyleMap {
/// `outer`. The ones from `self` take precedence over the ones from
/// `outer`. For folded properties `self` contributes the inner value.
pub fn chain<'a>(&'a self, outer: &'a StyleChain<'a>) -> StyleChain<'a> {
// No need to chain an empty map.
if self.is_empty() {
// No need to chain an empty map.
*outer
} else {
StyleChain { inner: self, outer: Some(outer) }
@ -86,12 +86,12 @@ impl StyleMap {
/// style maps, whereas `chain` would be used during layouting to combine
/// immutable style maps from different levels of the hierarchy.
pub fn apply(&mut self, outer: &Self) {
'outer: for outer in &outer.0 {
for inner in &mut self.0 {
if inner.style_id() == outer.style_id() {
inner.fold(outer);
continue 'outer;
}
for outer in &outer.0 {
if let Some(inner) =
self.0.iter_mut().find(|inner| inner.style_id() == outer.style_id())
{
*inner = inner.fold(outer);
continue;
}
self.0.push(outer.clone());
@ -158,16 +158,16 @@ impl<'a> StyleChain<'a> {
/// Get the (folded) value of a copyable style property.
///
/// This is the method you should reach for first. If it doesn't work
/// because your property is not copyable, use `get_ref`. If that doesn't
/// work either because your property needs folding, use `get_cloned`.
///
/// Returns the property's default value if no map in the chain contains an
/// entry for it.
pub fn get<P>(self, key: P) -> P::Value
pub fn get<P: Property>(self, key: P) -> P::Value
where
P: Property,
P::Value: Copy,
{
// This exists separately to `get_cloned` for `Copy` types so that
// people don't just naively use `get` / `get_cloned` where they should
// use `get_ref`.
self.get_cloned(key)
}
@ -177,13 +177,13 @@ impl<'a> StyleChain<'a> {
/// Prefer `get` if possible or resort to `get_cloned` for non-`Copy`
/// properties that need folding.
///
/// Returns a reference to the property's default value if no map in the
/// chain contains an entry for it.
pub fn get_ref<P>(self, key: P) -> &'a P::Value
/// Returns a lazily-initialized reference to the property's default value
/// if no map in the chain contains an entry for it.
pub fn get_ref<P: Property>(self, key: P) -> &'a P::Value
where
P: Property + Nonfolding,
P: Nonfolding,
{
if let Some(value) = self.get_locally(key) {
if let Some(value) = self.find(key) {
value
} else if let Some(outer) = self.outer {
outer.get_ref(key)
@ -197,11 +197,11 @@ impl<'a> StyleChain<'a> {
/// While this works for all properties, you should prefer `get` or
/// `get_ref` where possible. This is only needed for non-`Copy` properties
/// that need folding.
pub fn get_cloned<P>(self, key: P) -> P::Value
where
P: Property,
{
if let Some(value) = self.get_locally(key).cloned() {
///
/// Returns the property's default value if no map in the chain contains an
/// entry for it.
pub fn get_cloned<P: Property>(self, key: P) -> P::Value {
if let Some(value) = self.find(key).cloned() {
if P::FOLDABLE {
if let Some(outer) = self.outer {
P::fold(value, outer.get_cloned(key))
@ -218,8 +218,8 @@ impl<'a> StyleChain<'a> {
}
}
/// Find a property directly in the most local map.
fn get_locally<P: Property>(&self, _: P) -> Option<&'a P::Value> {
/// Find a property directly in the localmost map.
fn find<P: Property>(self, _: P) -> Option<&'a P::Value> {
self.inner
.0
.iter()
@ -309,8 +309,8 @@ impl Entry {
self.0.as_any().downcast_ref()
}
fn fold(&mut self, outer: &Self) {
*self = self.0.fold(outer);
fn fold(&self, outer: &Self) -> Self {
self.0.fold(outer)
}
}

View File

@ -85,10 +85,19 @@ impl<T> Spec<T> {
}
}
impl<T> Spec<T>
where
T: Ord,
{
impl<T: Default> Spec<T> {
/// Create a new instance with y set to its default value.
pub fn with_x(x: T) -> Self {
Self { x, y: T::default() }
}
/// Create a new instance with x set to its default value.
pub fn with_y(y: T) -> Self {
Self { x: T::default(), y }
}
}
impl<T: Ord> Spec<T> {
/// The component-wise minimum of this and another instance.
pub fn min(self, other: Self) -> Self {
Self {

View File

@ -7,33 +7,7 @@ use super::ParNode;
pub fn align(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let aligns: Spec<_> = args.find().unwrap_or_default();
let body: PackedNode = args.expect("body")?;
let mut styles = StyleMap::new();
if let Some(align) = aligns.x {
styles.set(ParNode::ALIGN, align);
}
Ok(Value::block(body.styled(styles).aligned(aligns)))
}
dynamic! {
Align: "alignment",
}
dynamic! {
Spec<Align>: "2d alignment",
}
castable! {
Spec<Option<Align>>,
Expected: "1d or 2d alignment",
@align: Align => {
let mut aligns = Spec::default();
aligns.set(align.axis(), Some(*align));
aligns
},
@aligns: Spec<Align> => aligns.map(Some),
Ok(Value::block(body.aligned(aligns)))
}
/// A node that aligns its child.
@ -56,8 +30,14 @@ impl Layout for AlignNode {
let mut pod = regions.clone();
pod.expand &= self.aligns.map_is_none();
// Align paragraphs inside the child.
let mut passed = StyleMap::new();
if let Some(align) = self.aligns.x {
passed.set(ParNode::ALIGN, align);
}
// Layout the child.
let mut frames = self.child.layout(ctx, &pod, styles);
let mut frames = self.child.layout(ctx, &pod, passed.chain(&styles));
for ((current, base), Constrained { item: frame, cts }) in
regions.iter().zip(&mut frames)
@ -78,3 +58,23 @@ impl Layout for AlignNode {
frames
}
}
dynamic! {
Align: "alignment",
}
dynamic! {
Spec<Align>: "2d alignment",
}
castable! {
Spec<Option<Align>>,
Expected: "1d or 2d alignment",
@align: Align => {
let mut aligns = Spec::default();
aligns.set(align.axis(), Some(*align));
aligns
},
@aligns: Spec<Align> => aligns.map(Some),
}

View File

@ -98,7 +98,7 @@ impl Layout for ColumnsNode {
// case, the frame is first created with zero height and then
// resized.
let height = if regions.expand.y { current.y } else { Length::zero() };
let mut output = Frame::new(Spec::new(regions.current.x, height));
let mut output = Frame::new(Size::new(regions.current.x, height));
let mut cursor = Length::zero();
for _ in 0 .. columns {

View File

@ -6,18 +6,18 @@ use super::{FontFamily, TextNode};
/// A section heading.
#[derive(Debug, Hash)]
pub struct HeadingNode {
/// The node that produces the heading's contents.
pub child: PackedNode,
/// The logical nesting depth of the section, starting from one. In the
/// default style, this controls the text size of the heading.
pub level: usize,
/// The node that produces the heading's contents.
pub child: PackedNode,
}
#[properties]
impl HeadingNode {
/// The heading's font family.
pub const FAMILY: Smart<FontFamily> = Smart::Auto;
/// The fill color of heading in the text. Just the surrounding text color
/// The fill color of text in the heading. Just the surrounding text color
/// if `auto`.
pub const FILL: Smart<Paint> = Smart::Auto;
}
@ -48,12 +48,12 @@ impl Layout for HeadingNode {
) -> Vec<Constrained<Rc<Frame>>> {
let upscale = (1.6 - 0.1 * self.level as f64).max(0.75);
let mut local = StyleMap::new();
local.set(TextNode::STRONG, true);
local.set(TextNode::SIZE, Relative::new(upscale).into());
let mut passed = StyleMap::new();
passed.set(TextNode::STRONG, true);
passed.set(TextNode::SIZE, Relative::new(upscale).into());
if let Smart::Custom(family) = styles.get_ref(Self::FAMILY) {
local.set(
passed.set(
TextNode::FAMILY_LIST,
std::iter::once(family)
.chain(styles.get_ref(TextNode::FAMILY_LIST))
@ -63,9 +63,9 @@ impl Layout for HeadingNode {
}
if let Smart::Custom(fill) = styles.get(Self::FILL) {
local.set(TextNode::FILL, fill);
passed.set(TextNode::FILL, fill);
}
self.child.layout(ctx, regions, local.chain(&styles))
self.child.layout(ctx, regions, passed.chain(&styles))
}
}

View File

@ -3,18 +3,13 @@
use std::io;
use super::prelude::*;
use super::LinkNode;
use crate::diag::Error;
use crate::image::ImageId;
/// `image`: An image.
pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let path = args.expect::<Spanned<EcoString>>("path to image file")?;
let width = args.named("width")?;
let height = args.named("height")?;
let fit = args.named("fit")?.unwrap_or_default();
// Load the image.
let path = args.expect::<Spanned<EcoString>>("path to image file")?;
let full = ctx.make_path(&path.v);
let id = ctx.images.load(&full).map_err(|err| {
Error::boxed(path.span, match err.kind() {
@ -23,6 +18,10 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
})
})?;
let width = args.named("width")?;
let height = args.named("height")?;
let fit = args.named("fit")?.unwrap_or_default();
Ok(Value::inline(
ImageNode { id, fit }.pack().sized(Spec::new(width, height)),
))
@ -37,6 +36,12 @@ pub struct ImageNode {
pub fit: ImageFit,
}
#[properties]
impl ImageNode {
/// An URL the image should link to.
pub const LINK: Option<String> = None;
}
impl Layout for ImageNode {
fn layout(
&self,
@ -90,7 +95,7 @@ impl Layout for ImageNode {
}
// Apply link if it exists.
if let Some(url) = styles.get_ref(LinkNode::URL) {
if let Some(url) = styles.get_ref(Self::LINK) {
frame.link(url);
}

View File

@ -1,9 +1,10 @@
//! Hyperlinking.
use super::prelude::*;
use super::{ImageNode, ShapeNode, TextNode};
use crate::util::EcoString;
/// `link`: Link text or other elements.
/// `link`: Link text and other elements to an URL.
pub fn link(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let url: String = args.expect::<EcoString>("url")?.into();
let body = args.find().unwrap_or_else(|| {
@ -14,17 +15,9 @@ pub fn link(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
Node::Text(text.into())
});
Ok(Value::Node(
body.styled(StyleMap::with(LinkNode::URL, Some(url))),
))
}
/// Host for link styles.
#[derive(Debug, Hash)]
pub struct LinkNode;
#[properties]
impl LinkNode {
/// An URL to link to.
pub const URL: Option<String> = None;
let mut passed = StyleMap::new();
passed.set(TextNode::LINK, Some(url.clone()));
passed.set(ImageNode::LINK, Some(url.clone()));
passed.set(ShapeNode::LINK, Some(url));
Ok(Value::Node(body.styled(passed)))
}

View File

@ -50,26 +50,23 @@ impl<L: Labelling> Layout for ListNode<L> {
let label_indent = styles.get(Self::LABEL_INDENT).resolve(em);
let body_indent = styles.get(Self::BODY_INDENT).resolve(em);
let columns = vec![
TrackSizing::Linear(label_indent.into()),
TrackSizing::Auto,
TrackSizing::Linear(body_indent.into()),
TrackSizing::Auto,
];
let children = vec![
PackedNode::default(),
Node::Text(self.labelling.label()).into_block(),
PackedNode::default(),
self.child.clone(),
];
GridNode {
tracks: Spec::new(columns, vec![]),
let grid = GridNode {
tracks: Spec::with_x(vec![
TrackSizing::Linear(label_indent.into()),
TrackSizing::Auto,
TrackSizing::Linear(body_indent.into()),
TrackSizing::Auto,
]),
gutter: Spec::default(),
children,
}
.layout(ctx, regions, styles)
children: vec![
PackedNode::default(),
Node::Text(self.labelling.label()).into_block(),
PackedNode::default(),
self.child.clone(),
],
};
grid.layout(ctx, regions, styles)
}
}

View File

@ -166,31 +166,6 @@ pub fn pagebreak(_: &mut EvalContext, _: &mut Args) -> TypResult<Value> {
Ok(Value::Node(Node::Pagebreak))
}
/// Defines default margins for a class of related papers.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum PaperClass {
Custom,
Base,
US,
Newspaper,
Book,
}
impl PaperClass {
/// The default margins for this page class.
fn default_margins(self) -> Sides<Linear> {
let f = |r| Relative::new(r).into();
let s = |l, t, r, b| Sides::new(f(l), f(t), f(r), f(b));
match self {
Self::Custom => s(0.1190, 0.0842, 0.1190, 0.0842),
Self::Base => s(0.1190, 0.0842, 0.1190, 0.0842),
Self::US => s(0.1760, 0.1092, 0.1760, 0.0910),
Self::Newspaper => s(0.0455, 0.0587, 0.0455, 0.0294),
Self::Book => s(0.1200, 0.0852, 0.1500, 0.0965),
}
}
}
/// Specification of a paper.
#[derive(Debug, Copy, Clone)]
pub struct Paper {
@ -413,6 +388,31 @@ castable! {
Value::Str(string) => Paper::from_str(&string).map_err(|e| e.to_string())?,
}
/// Defines default margins for a class of related papers.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum PaperClass {
Custom,
Base,
US,
Newspaper,
Book,
}
impl PaperClass {
/// The default margins for this page class.
fn default_margins(self) -> Sides<Linear> {
let f = |r| Relative::new(r).into();
let s = |l, t, r, b| Sides::new(f(l), f(t), f(r), f(b));
match self {
Self::Custom => s(0.1190, 0.0842, 0.1190, 0.0842),
Self::Base => s(0.1190, 0.0842, 0.1190, 0.0842),
Self::US => s(0.1760, 0.1092, 0.1760, 0.0910),
Self::Newspaper => s(0.0455, 0.0587, 0.0455, 0.0294),
Self::Book => s(0.1200, 0.0852, 0.1500, 0.0965),
}
}
}
/// The error when parsing a [`Paper`] from a string fails.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct PaperError;

View File

@ -5,7 +5,7 @@ use super::AlignNode;
/// `place`: Place content at an absolute position.
pub fn place(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let aligns = args.find().unwrap_or(Spec::new(Some(Align::Left), None));
let aligns = args.find().unwrap_or(Spec::with_x(Some(Align::Left)));
let tx = args.named("dx")?.unwrap_or_default();
let ty = args.named("dy")?.unwrap_or_default();
let body: PackedNode = args.expect("body")?;

View File

@ -3,7 +3,6 @@
use std::f64::consts::SQRT_2;
use super::prelude::*;
use super::LinkNode;
/// `rect`: A rectangle with optional content.
pub fn rect(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
@ -101,6 +100,12 @@ pub struct ShapeNode {
pub child: Option<PackedNode>,
}
#[properties]
impl ShapeNode {
/// An URL the shape should link to.
pub const LINK: Option<String> = None;
}
impl Layout for ShapeNode {
fn layout(
&self,
@ -170,7 +175,7 @@ impl Layout for ShapeNode {
}
// Apply link if it exists.
if let Some(url) = styles.get_ref(LinkNode::URL) {
if let Some(url) = styles.get_ref(Self::LINK) {
frame.link(url);
}

View File

@ -9,7 +9,6 @@ use rustybuzz::{Feature, UnicodeBuffer};
use ttf_parser::Tag;
use super::prelude::*;
use super::LinkNode;
use crate::font::{
Face, FaceId, FontStore, FontStretch, FontStyle, FontVariant, FontWeight,
VerticalFontMetric,
@ -59,6 +58,8 @@ impl TextNode {
/// Decorative lines.
#[fold(|a, b| a.into_iter().chain(b).collect())]
pub const LINES: Vec<LineDecoration> = vec![];
/// An URL the text should link to.
pub const LINK: Option<String> = None;
/// The size of the glyphs.
#[fold(Linear::compose)]
@ -889,7 +890,7 @@ impl<'a> ShapedText<'a> {
}
// Apply link if it exists.
if let Some(url) = self.styles.get_ref(LinkNode::URL) {
if let Some(url) = self.styles.get_ref(TextNode::LINK) {
frame.link(url);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 23 KiB