mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Refactor
This commit is contained in:
parent
4c81a5d43e
commit
f7e8624b4c
@ -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('>')
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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),
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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)))
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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")?;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 |
Loading…
x
Reference in New Issue
Block a user