Em units
@ -456,7 +456,7 @@ impl<'a> Builder<'a> {
|
|||||||
})
|
})
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
{
|
{
|
||||||
par.push_front(ParChild::Spacing(Spacing::Relative(indent)))
|
par.push_front(ParChild::Spacing(indent.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let node = ParNode(par).pack();
|
let node = ParNode(par).pack();
|
||||||
|
@ -5,12 +5,10 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::{Barrier, RawAlign, StyleChain};
|
use super::{Barrier, RawAlign, RawLength, Resolve, StyleChain};
|
||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::frame::{Element, Frame, Geometry, Shape, Stroke};
|
use crate::frame::{Element, Frame, Geometry, Shape, Stroke};
|
||||||
use crate::geom::{
|
use crate::geom::{Align, Length, Paint, Point, Relative, Sides, Size, Spec};
|
||||||
Align, Length, Numeric, Paint, Point, Relative, Sides, Size, Spec, Transform,
|
|
||||||
};
|
|
||||||
use crate::library::graphics::MoveNode;
|
use crate::library::graphics::MoveNode;
|
||||||
use crate::library::layout::{AlignNode, PadNode};
|
use crate::library::layout::{AlignNode, PadNode};
|
||||||
use crate::util::Prehashed;
|
use crate::util::Prehashed;
|
||||||
@ -163,7 +161,7 @@ impl LayoutNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Force a size for this node.
|
/// Force a size for this node.
|
||||||
pub fn sized(self, sizing: Spec<Option<Relative<Length>>>) -> Self {
|
pub fn sized(self, sizing: Spec<Option<Relative<RawLength>>>) -> Self {
|
||||||
if sizing.any(Option::is_some) {
|
if sizing.any(Option::is_some) {
|
||||||
SizedNode { sizing, child: self }.pack()
|
SizedNode { sizing, child: self }.pack()
|
||||||
} else {
|
} else {
|
||||||
@ -191,7 +189,7 @@ impl LayoutNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Pad this node at the sides.
|
/// Pad this node at the sides.
|
||||||
pub fn padded(self, padding: Sides<Relative<Length>>) -> Self {
|
pub fn padded(self, padding: Sides<Relative<RawLength>>) -> Self {
|
||||||
if !padding.left.is_zero()
|
if !padding.left.is_zero()
|
||||||
|| !padding.top.is_zero()
|
|| !padding.top.is_zero()
|
||||||
|| !padding.right.is_zero()
|
|| !padding.right.is_zero()
|
||||||
@ -204,13 +202,9 @@ impl LayoutNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Transform this node's contents without affecting layout.
|
/// Transform this node's contents without affecting layout.
|
||||||
pub fn moved(self, offset: Point) -> Self {
|
pub fn moved(self, delta: Spec<Relative<RawLength>>) -> Self {
|
||||||
if !offset.is_zero() {
|
if delta.any(|r| !r.is_zero()) {
|
||||||
MoveNode {
|
MoveNode { delta, child: self }.pack()
|
||||||
transform: Transform::translate(offset.x, offset.y),
|
|
||||||
child: self,
|
|
||||||
}
|
|
||||||
.pack()
|
|
||||||
} else {
|
} else {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -294,7 +288,7 @@ impl Layout for EmptyNode {
|
|||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
struct SizedNode {
|
struct SizedNode {
|
||||||
/// How to size the node horizontally and vertically.
|
/// How to size the node horizontally and vertically.
|
||||||
sizing: Spec<Option<Relative<Length>>>,
|
sizing: Spec<Option<Relative<RawLength>>>,
|
||||||
/// The node to be sized.
|
/// The node to be sized.
|
||||||
child: LayoutNode,
|
child: LayoutNode,
|
||||||
}
|
}
|
||||||
@ -311,8 +305,9 @@ impl Layout for SizedNode {
|
|||||||
// Resolve the sizing to a concrete size.
|
// Resolve the sizing to a concrete size.
|
||||||
let size = self
|
let size = self
|
||||||
.sizing
|
.sizing
|
||||||
|
.resolve(styles)
|
||||||
.zip(regions.base)
|
.zip(regions.base)
|
||||||
.map(|(s, b)| s.map(|v| v.resolve(b)))
|
.map(|(s, b)| s.map(|v| v.relative_to(b)))
|
||||||
.unwrap_or(regions.first);
|
.unwrap_or(regions.first);
|
||||||
|
|
||||||
// Select the appropriate base and expansion for the child depending
|
// Select the appropriate base and expansion for the child depending
|
||||||
|
@ -43,7 +43,7 @@ use parking_lot::{MappedRwLockWriteGuard, RwLockWriteGuard};
|
|||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::diag::{At, StrResult, Trace, Tracepoint, TypResult};
|
use crate::diag::{At, StrResult, Trace, Tracepoint, TypResult};
|
||||||
use crate::geom::{Angle, Fraction, Length, Ratio};
|
use crate::geom::{Angle, Em, Fraction, Length, Ratio};
|
||||||
use crate::library;
|
use crate::library;
|
||||||
use crate::syntax::ast::*;
|
use crate::syntax::ast::*;
|
||||||
use crate::syntax::{Span, Spanned};
|
use crate::syntax::{Span, Spanned};
|
||||||
@ -245,10 +245,13 @@ impl Eval for Lit {
|
|||||||
LitKind::Bool(v) => Value::Bool(v),
|
LitKind::Bool(v) => Value::Bool(v),
|
||||||
LitKind::Int(v) => Value::Int(v),
|
LitKind::Int(v) => Value::Int(v),
|
||||||
LitKind::Float(v) => Value::Float(v),
|
LitKind::Float(v) => Value::Float(v),
|
||||||
LitKind::Length(v, unit) => Value::Length(Length::with_unit(v, unit)),
|
LitKind::Numeric(v, unit) => match unit {
|
||||||
LitKind::Angle(v, unit) => Value::Angle(Angle::with_unit(v, unit)),
|
Unit::Length(unit) => Length::with_unit(v, unit).into(),
|
||||||
LitKind::Percent(v) => Value::Ratio(Ratio::new(v / 100.0)),
|
Unit::Angle(unit) => Angle::with_unit(v, unit).into(),
|
||||||
LitKind::Fractional(v) => Value::Fraction(Fraction::new(v)),
|
Unit::Em => Em::new(v).into(),
|
||||||
|
Unit::Fr => Fraction::new(v).into(),
|
||||||
|
Unit::Percent => Ratio::new(v / 100.0).into(),
|
||||||
|
},
|
||||||
LitKind::Str(ref v) => Value::Str(v.clone()),
|
LitKind::Str(ref v) => Value::Str(v.clone()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -735,7 +738,7 @@ impl Eval for IncludeExpr {
|
|||||||
/// Process an import of a module relative to the current location.
|
/// Process an import of a module relative to the current location.
|
||||||
fn import(ctx: &mut Context, path: &str, span: Span) -> TypResult<Module> {
|
fn import(ctx: &mut Context, path: &str, span: Span) -> TypResult<Module> {
|
||||||
// Load the source file.
|
// Load the source file.
|
||||||
let full = ctx.resolve(path);
|
let full = ctx.complete_path(path);
|
||||||
let id = ctx.sources.load(&full).map_err(|err| match err.kind() {
|
let id = ctx.sources.load(&full).map_err(|err| match err.kind() {
|
||||||
std::io::ErrorKind::NotFound => error!(span, "file not found"),
|
std::io::ErrorKind::NotFound => error!(span, "file not found"),
|
||||||
_ => error!(span, "failed to load source file ({})", err),
|
_ => error!(span, "failed to load source file ({})", err),
|
||||||
|
@ -150,8 +150,8 @@ pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> {
|
|||||||
|
|
||||||
(Length(a), Int(b)) => Length(a * b as f64),
|
(Length(a), Int(b)) => Length(a * b as f64),
|
||||||
(Length(a), Float(b)) => Length(a * b),
|
(Length(a), Float(b)) => Length(a * b),
|
||||||
(Int(a), Length(b)) => Length(a as f64 * b),
|
(Int(a), Length(b)) => Length(b * a as f64),
|
||||||
(Float(a), Length(b)) => Length(a * b),
|
(Float(a), Length(b)) => Length(b * a),
|
||||||
|
|
||||||
(Angle(a), Int(b)) => Angle(a * b as f64),
|
(Angle(a), Int(b)) => Angle(a * b as f64),
|
||||||
(Angle(a), Float(b)) => Angle(a * b),
|
(Angle(a), Float(b)) => Angle(a * b),
|
||||||
@ -194,7 +194,6 @@ pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> {
|
|||||||
|
|
||||||
(Length(a), Int(b)) => Length(a / b as f64),
|
(Length(a), Int(b)) => Length(a / b as f64),
|
||||||
(Length(a), Float(b)) => Length(a / b),
|
(Length(a), Float(b)) => Length(a / b),
|
||||||
(Length(a), Length(b)) => Float(a / b),
|
|
||||||
|
|
||||||
(Angle(a), Int(b)) => Angle(a / b as f64),
|
(Angle(a), Int(b)) => Angle(a / b as f64),
|
||||||
(Angle(a), Float(b)) => Angle(a / b),
|
(Angle(a), Float(b)) => Angle(a / b),
|
||||||
|
133
src/eval/raw.rs
@ -1,8 +1,9 @@
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
use std::ops::{Add, Div, Mul, Neg};
|
||||||
|
|
||||||
use super::{Resolve, StyleChain};
|
use super::{Resolve, StyleChain};
|
||||||
use crate::geom::{Align, SpecAxis};
|
use crate::geom::{Align, Em, Length, Numeric, Relative, SpecAxis};
|
||||||
use crate::library::text::ParNode;
|
use crate::library::text::{ParNode, TextNode};
|
||||||
|
|
||||||
/// The unresolved alignment representation.
|
/// The unresolved alignment representation.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
@ -47,3 +48,131 @@ impl Debug for RawAlign {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The unresolved length representation.
|
||||||
|
///
|
||||||
|
/// Currently supports absolute and em units, but support could quite easily be
|
||||||
|
/// extended to other units that can be resolved through a style chain.
|
||||||
|
/// Probably, it would be a good idea to then move to an enum representation
|
||||||
|
/// that has a small footprint and allocates for the rare case that units are
|
||||||
|
/// mixed.
|
||||||
|
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
pub struct RawLength {
|
||||||
|
/// The absolute part.
|
||||||
|
pub length: Length,
|
||||||
|
/// The font-relative part.
|
||||||
|
pub em: Em,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawLength {
|
||||||
|
/// The zero length.
|
||||||
|
pub const fn zero() -> Self {
|
||||||
|
Self { length: Length::zero(), em: Em::zero() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for RawLength {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
match (self.length.is_zero(), self.em.is_zero()) {
|
||||||
|
(false, false) => write!(f, "{:?} + {:?}", self.length, self.em),
|
||||||
|
(true, false) => self.em.fmt(f),
|
||||||
|
(_, true) => self.length.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resolve for Em {
|
||||||
|
type Output = Length;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
if self.is_zero() {
|
||||||
|
Length::zero()
|
||||||
|
} else {
|
||||||
|
self.at(styles.get(TextNode::SIZE))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resolve for RawLength {
|
||||||
|
type Output = Length;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
self.length + self.em.resolve(styles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Length> for RawLength {
|
||||||
|
fn from(length: Length) -> Self {
|
||||||
|
Self { length, em: Em::zero() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Em> for RawLength {
|
||||||
|
fn from(em: Em) -> Self {
|
||||||
|
Self { length: Length::zero(), em }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Length> for Relative<RawLength> {
|
||||||
|
fn from(length: Length) -> Self {
|
||||||
|
Relative::from(RawLength::from(length))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Numeric for RawLength {
|
||||||
|
fn zero() -> Self {
|
||||||
|
Self::zero()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_finite(self) -> bool {
|
||||||
|
self.length.is_finite() && self.em.is_finite()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Neg for RawLength {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn neg(self) -> Self::Output {
|
||||||
|
Self { length: -self.length, em: -self.em }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for RawLength {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
Self {
|
||||||
|
length: self.length + rhs.length,
|
||||||
|
em: self.em + rhs.em,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub_impl!(RawLength - RawLength -> RawLength);
|
||||||
|
|
||||||
|
impl Mul<f64> for RawLength {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn mul(self, rhs: f64) -> Self::Output {
|
||||||
|
Self {
|
||||||
|
length: self.length * rhs,
|
||||||
|
em: self.em * rhs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Div<f64> for RawLength {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn div(self, rhs: f64) -> Self::Output {
|
||||||
|
Self {
|
||||||
|
length: self.length / rhs,
|
||||||
|
em: self.em / rhs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assign_impl!(RawLength += RawLength);
|
||||||
|
assign_impl!(RawLength -= RawLength);
|
||||||
|
assign_impl!(RawLength *= f64);
|
||||||
|
assign_impl!(RawLength /= f64);
|
||||||
|
@ -4,9 +4,9 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::{ops, Args, Array, Content, Context, Dict, Func, Layout, StrExt};
|
use super::{ops, Args, Array, Content, Context, Dict, Func, Layout, RawLength, StrExt};
|
||||||
use crate::diag::{with_alternative, At, StrResult, TypResult};
|
use crate::diag::{with_alternative, At, StrResult, TypResult};
|
||||||
use crate::geom::{Angle, Color, Fraction, Length, Ratio, Relative, RgbaColor};
|
use crate::geom::{Angle, Color, Em, Fraction, Length, Ratio, Relative, RgbaColor};
|
||||||
use crate::library::text::RawNode;
|
use crate::library::text::RawNode;
|
||||||
use crate::syntax::{Span, Spanned};
|
use crate::syntax::{Span, Spanned};
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
@ -25,13 +25,13 @@ pub enum Value {
|
|||||||
/// A floating-point number: `1.2`, `10e-4`.
|
/// A floating-point number: `1.2`, `10e-4`.
|
||||||
Float(f64),
|
Float(f64),
|
||||||
/// A length: `12pt`, `3cm`.
|
/// A length: `12pt`, `3cm`.
|
||||||
Length(Length),
|
Length(RawLength),
|
||||||
/// An angle: `1.5rad`, `90deg`.
|
/// An angle: `1.5rad`, `90deg`.
|
||||||
Angle(Angle),
|
Angle(Angle),
|
||||||
/// A ratio: `50%`.
|
/// A ratio: `50%`.
|
||||||
Ratio(Ratio),
|
Ratio(Ratio),
|
||||||
/// A relative length, combination of a ratio and a length: `20% + 5cm`.
|
/// A relative length, combination of a ratio and a length: `20% + 5cm`.
|
||||||
Relative(Relative<Length>),
|
Relative(Relative<RawLength>),
|
||||||
/// A fraction: `1fr`.
|
/// A fraction: `1fr`.
|
||||||
Fraction(Fraction),
|
Fraction(Fraction),
|
||||||
/// A color value: `#f79143ff`.
|
/// A color value: `#f79143ff`.
|
||||||
@ -77,10 +77,10 @@ impl Value {
|
|||||||
Self::Bool(_) => bool::TYPE_NAME,
|
Self::Bool(_) => bool::TYPE_NAME,
|
||||||
Self::Int(_) => i64::TYPE_NAME,
|
Self::Int(_) => i64::TYPE_NAME,
|
||||||
Self::Float(_) => f64::TYPE_NAME,
|
Self::Float(_) => f64::TYPE_NAME,
|
||||||
Self::Length(_) => Length::TYPE_NAME,
|
Self::Length(_) => RawLength::TYPE_NAME,
|
||||||
Self::Angle(_) => Angle::TYPE_NAME,
|
Self::Angle(_) => Angle::TYPE_NAME,
|
||||||
Self::Ratio(_) => Ratio::TYPE_NAME,
|
Self::Ratio(_) => Ratio::TYPE_NAME,
|
||||||
Self::Relative(_) => Relative::TYPE_NAME,
|
Self::Relative(_) => Relative::<RawLength>::TYPE_NAME,
|
||||||
Self::Fraction(_) => Fraction::TYPE_NAME,
|
Self::Fraction(_) => Fraction::TYPE_NAME,
|
||||||
Self::Color(_) => Color::TYPE_NAME,
|
Self::Color(_) => Color::TYPE_NAME,
|
||||||
Self::Str(_) => EcoString::TYPE_NAME,
|
Self::Str(_) => EcoString::TYPE_NAME,
|
||||||
@ -320,6 +320,18 @@ impl From<usize> for Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Length> for Value {
|
||||||
|
fn from(v: Length) -> Self {
|
||||||
|
Self::Length(v.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Em> for Value {
|
||||||
|
fn from(v: Em) -> Self {
|
||||||
|
Self::Length(v.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<RgbaColor> for Value {
|
impl From<RgbaColor> for Value {
|
||||||
fn from(v: RgbaColor) -> Self {
|
fn from(v: RgbaColor) -> Self {
|
||||||
Self::Color(v.into())
|
Self::Color(v.into())
|
||||||
@ -546,10 +558,10 @@ macro_rules! castable {
|
|||||||
primitive! { bool: "boolean", Bool }
|
primitive! { bool: "boolean", Bool }
|
||||||
primitive! { i64: "integer", Int }
|
primitive! { i64: "integer", Int }
|
||||||
primitive! { f64: "float", Float, Int(v) => v as f64 }
|
primitive! { f64: "float", Float, Int(v) => v as f64 }
|
||||||
primitive! { Length: "length", Length }
|
primitive! { RawLength: "length", Length }
|
||||||
primitive! { Angle: "angle", Angle }
|
primitive! { Angle: "angle", Angle }
|
||||||
primitive! { Ratio: "ratio", Ratio }
|
primitive! { Ratio: "ratio", Ratio }
|
||||||
primitive! { Relative<Length>: "relative length", Relative, Length(v) => v.into(), Ratio(v) => v.into() }
|
primitive! { Relative<RawLength>: "relative length", Relative, Length(v) => v.into(), Ratio(v) => v.into() }
|
||||||
primitive! { Fraction: "fraction", Fraction }
|
primitive! { Fraction: "fraction", Fraction }
|
||||||
primitive! { Color: "color", Color }
|
primitive! { Color: "color", Color }
|
||||||
primitive! { EcoString: "string", Str }
|
primitive! { EcoString: "string", Str }
|
||||||
@ -685,7 +697,10 @@ mod tests {
|
|||||||
test(Length::pt(5.5), "5.5pt");
|
test(Length::pt(5.5), "5.5pt");
|
||||||
test(Angle::deg(90.0), "90deg");
|
test(Angle::deg(90.0), "90deg");
|
||||||
test(Ratio::one() / 2.0, "50%");
|
test(Ratio::one() / 2.0, "50%");
|
||||||
test(Ratio::new(0.3) + Length::cm(2.0), "30% + 56.69pt");
|
test(
|
||||||
|
Ratio::new(0.3) + RawLength::from(Length::cm(2.0)),
|
||||||
|
"30% + 56.69pt",
|
||||||
|
);
|
||||||
test(Fraction::one() * 7.55, "7.55fr");
|
test(Fraction::one() * 7.55, "7.55fr");
|
||||||
test(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101");
|
test(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101");
|
||||||
|
|
||||||
|
@ -117,14 +117,14 @@ fn render_text(
|
|||||||
let mut x = 0.0;
|
let mut x = 0.0;
|
||||||
for glyph in &text.glyphs {
|
for glyph in &text.glyphs {
|
||||||
let id = GlyphId(glyph.id);
|
let id = GlyphId(glyph.id);
|
||||||
let offset = x + glyph.x_offset.resolve(text.size).to_f32();
|
let offset = x + glyph.x_offset.at(text.size).to_f32();
|
||||||
let ts = ts.pre_translate(offset, 0.0);
|
let ts = ts.pre_translate(offset, 0.0);
|
||||||
|
|
||||||
render_svg_glyph(canvas, ts, mask, ctx, text, id)
|
render_svg_glyph(canvas, ts, mask, ctx, text, id)
|
||||||
.or_else(|| render_bitmap_glyph(canvas, ts, mask, ctx, text, id))
|
.or_else(|| render_bitmap_glyph(canvas, ts, mask, ctx, text, id))
|
||||||
.or_else(|| render_outline_glyph(canvas, ts, mask, ctx, text, id));
|
.or_else(|| render_outline_glyph(canvas, ts, mask, ctx, text, id));
|
||||||
|
|
||||||
x += glyph.x_advance.resolve(text.size).to_f32();
|
x += glyph.x_advance.at(text.size).to_f32();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
18
src/font.rs
@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use ttf_parser::{name_id, GlyphId, PlatformId, Tag};
|
use ttf_parser::{name_id, GlyphId, PlatformId, Tag};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::geom::{Em, Length, Relative};
|
use crate::geom::Em;
|
||||||
use crate::loading::{FileHash, Loader};
|
use crate::loading::{FileHash, Loader};
|
||||||
use crate::util::decode_mac_roman;
|
use crate::util::decode_mac_roman;
|
||||||
|
|
||||||
@ -372,14 +372,13 @@ impl FaceMetrics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Look up a vertical metric at the given font size.
|
/// Look up a vertical metric at the given font size.
|
||||||
pub fn vertical(&self, metric: VerticalFontMetric, size: Length) -> Length {
|
pub fn vertical(&self, metric: VerticalFontMetric) -> Em {
|
||||||
match metric {
|
match metric {
|
||||||
VerticalFontMetric::Ascender => self.ascender.resolve(size),
|
VerticalFontMetric::Ascender => self.ascender,
|
||||||
VerticalFontMetric::CapHeight => self.cap_height.resolve(size),
|
VerticalFontMetric::CapHeight => self.cap_height,
|
||||||
VerticalFontMetric::XHeight => self.x_height.resolve(size),
|
VerticalFontMetric::XHeight => self.x_height,
|
||||||
VerticalFontMetric::Baseline => Length::zero(),
|
VerticalFontMetric::Baseline => Em::zero(),
|
||||||
VerticalFontMetric::Descender => self.descender.resolve(size),
|
VerticalFontMetric::Descender => self.descender,
|
||||||
VerticalFontMetric::Relative(v) => v.resolve(size),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -413,9 +412,6 @@ pub enum VerticalFontMetric {
|
|||||||
/// Corresponds to the typographic descender from the `OS/2` table if
|
/// Corresponds to the typographic descender from the `OS/2` table if
|
||||||
/// present and falls back to the descender from the `hhea` table otherwise.
|
/// present and falls back to the descender from the `hhea` table otherwise.
|
||||||
Descender,
|
Descender,
|
||||||
/// An font-size dependent distance from the baseline (positive goes up, negative
|
|
||||||
/// down).
|
|
||||||
Relative(Relative<Length>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Properties of a single font face.
|
/// Properties of a single font face.
|
||||||
|
@ -197,7 +197,7 @@ pub struct Text {
|
|||||||
impl Text {
|
impl Text {
|
||||||
/// The width of the text run.
|
/// The width of the text run.
|
||||||
pub fn width(&self) -> Length {
|
pub fn width(&self) -> Length {
|
||||||
self.glyphs.iter().map(|g| g.x_advance.resolve(self.size)).sum()
|
self.glyphs.iter().map(|g| g.x_advance.at(self.size)).sum()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,18 +16,18 @@ impl Angle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create an angle from a value in a unit.
|
/// Create an angle from a value in a unit.
|
||||||
pub fn with_unit(val: f64, unit: AngularUnit) -> Self {
|
pub fn with_unit(val: f64, unit: AngleUnit) -> Self {
|
||||||
Self(Scalar(val * unit.raw_scale()))
|
Self(Scalar(val * unit.raw_scale()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an angle from a number of radians.
|
/// Create an angle from a number of radians.
|
||||||
pub fn rad(rad: f64) -> Self {
|
pub fn rad(rad: f64) -> Self {
|
||||||
Self::with_unit(rad, AngularUnit::Rad)
|
Self::with_unit(rad, AngleUnit::Rad)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an angle from a number of degrees.
|
/// Create an angle from a number of degrees.
|
||||||
pub fn deg(deg: f64) -> Self {
|
pub fn deg(deg: f64) -> Self {
|
||||||
Self::with_unit(deg, AngularUnit::Deg)
|
Self::with_unit(deg, AngleUnit::Deg)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value of this angle in raw units.
|
/// Get the value of this angle in raw units.
|
||||||
@ -36,18 +36,18 @@ impl Angle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value of this length in unit.
|
/// Get the value of this length in unit.
|
||||||
pub fn to_unit(self, unit: AngularUnit) -> f64 {
|
pub fn to_unit(self, unit: AngleUnit) -> f64 {
|
||||||
self.to_raw() / unit.raw_scale()
|
self.to_raw() / unit.raw_scale()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert this to a number of radians.
|
/// Convert this to a number of radians.
|
||||||
pub fn to_rad(self) -> f64 {
|
pub fn to_rad(self) -> f64 {
|
||||||
self.to_unit(AngularUnit::Rad)
|
self.to_unit(AngleUnit::Rad)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert this to a number of degrees.
|
/// Convert this to a number of degrees.
|
||||||
pub fn to_deg(self) -> f64 {
|
pub fn to_deg(self) -> f64 {
|
||||||
self.to_unit(AngularUnit::Deg)
|
self.to_unit(AngleUnit::Deg)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The absolute value of the this angle.
|
/// The absolute value of the this angle.
|
||||||
@ -145,14 +145,14 @@ impl Sum for Angle {
|
|||||||
|
|
||||||
/// Different units of angular measurement.
|
/// Different units of angular measurement.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum AngularUnit {
|
pub enum AngleUnit {
|
||||||
/// Radians.
|
/// Radians.
|
||||||
Rad,
|
Rad,
|
||||||
/// Degrees.
|
/// Degrees.
|
||||||
Deg,
|
Deg,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AngularUnit {
|
impl AngleUnit {
|
||||||
/// How many raw units correspond to a value of `1.0` in this unit.
|
/// How many raw units correspond to a value of `1.0` in this unit.
|
||||||
fn raw_scale(self) -> f64 {
|
fn raw_scale(self) -> f64 {
|
||||||
match self {
|
match self {
|
||||||
@ -162,7 +162,7 @@ impl AngularUnit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for AngularUnit {
|
impl Debug for AngleUnit {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.pad(match self {
|
f.pad(match self {
|
||||||
Self::Rad => "rad",
|
Self::Rad => "rad",
|
||||||
|
@ -29,7 +29,12 @@ impl Em {
|
|||||||
|
|
||||||
/// Create an em length from a length at the given font size.
|
/// Create an em length from a length at the given font size.
|
||||||
pub fn from_length(length: Length, font_size: Length) -> Self {
|
pub fn from_length(length: Length, font_size: Length) -> Self {
|
||||||
Self(Scalar(length / font_size))
|
let result = length / font_size;
|
||||||
|
if result.is_finite() {
|
||||||
|
Self(Scalar(result))
|
||||||
|
} else {
|
||||||
|
Self::zero()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The number of em units.
|
/// The number of em units.
|
||||||
@ -38,8 +43,9 @@ impl Em {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convert to a length at the given font size.
|
/// Convert to a length at the given font size.
|
||||||
pub fn resolve(self, font_size: Length) -> Length {
|
pub fn at(self, font_size: Length) -> Length {
|
||||||
self.get() * font_size
|
let resolved = font_size * self.get();
|
||||||
|
if resolved.is_finite() { resolved } else { Length::zero() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,8 +30,8 @@ impl Fraction {
|
|||||||
Self::new(self.get().abs())
|
Self::new(self.get().abs())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve this fraction's share in the remaining space.
|
/// Determine this fraction's share in the remaining space.
|
||||||
pub fn resolve(self, total: Self, remaining: Length) -> Length {
|
pub fn share(self, total: Self, remaining: Length) -> Length {
|
||||||
let ratio = self / total;
|
let ratio = self / total;
|
||||||
if ratio.is_finite() && remaining.is_finite() {
|
if ratio.is_finite() && remaining.is_finite() {
|
||||||
ratio * remaining
|
ratio * remaining
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/// Implement the `Sub` trait based on existing `Neg` and `Add` impls.
|
/// Implement the `Sub` trait based on existing `Neg` and `Add` impls.
|
||||||
macro_rules! sub_impl {
|
macro_rules! sub_impl {
|
||||||
($a:ident - $b:ident -> $c:ident) => {
|
($a:ident - $b:ident -> $c:ident) => {
|
||||||
impl Sub<$b> for $a {
|
impl std::ops::Sub<$b> for $a {
|
||||||
type Output = $c;
|
type Output = $c;
|
||||||
|
|
||||||
fn sub(self, other: $b) -> $c {
|
fn sub(self, other: $b) -> $c {
|
||||||
@ -14,7 +14,7 @@ macro_rules! sub_impl {
|
|||||||
/// Implement an assign trait based on an existing non-assign trait.
|
/// Implement an assign trait based on an existing non-assign trait.
|
||||||
macro_rules! assign_impl {
|
macro_rules! assign_impl {
|
||||||
($a:ident += $b:ident) => {
|
($a:ident += $b:ident) => {
|
||||||
impl AddAssign<$b> for $a {
|
impl std::ops::AddAssign<$b> for $a {
|
||||||
fn add_assign(&mut self, other: $b) {
|
fn add_assign(&mut self, other: $b) {
|
||||||
*self = *self + other;
|
*self = *self + other;
|
||||||
}
|
}
|
||||||
@ -22,7 +22,7 @@ macro_rules! assign_impl {
|
|||||||
};
|
};
|
||||||
|
|
||||||
($a:ident -= $b:ident) => {
|
($a:ident -= $b:ident) => {
|
||||||
impl SubAssign<$b> for $a {
|
impl std::ops::SubAssign<$b> for $a {
|
||||||
fn sub_assign(&mut self, other: $b) {
|
fn sub_assign(&mut self, other: $b) {
|
||||||
*self = *self - other;
|
*self = *self - other;
|
||||||
}
|
}
|
||||||
@ -30,7 +30,7 @@ macro_rules! assign_impl {
|
|||||||
};
|
};
|
||||||
|
|
||||||
($a:ident *= $b:ident) => {
|
($a:ident *= $b:ident) => {
|
||||||
impl MulAssign<$b> for $a {
|
impl std::ops::MulAssign<$b> for $a {
|
||||||
fn mul_assign(&mut self, other: $b) {
|
fn mul_assign(&mut self, other: $b) {
|
||||||
*self = *self * other;
|
*self = *self * other;
|
||||||
}
|
}
|
||||||
@ -38,7 +38,7 @@ macro_rules! assign_impl {
|
|||||||
};
|
};
|
||||||
|
|
||||||
($a:ident /= $b:ident) => {
|
($a:ident /= $b:ident) => {
|
||||||
impl DivAssign<$b> for $a {
|
impl std::ops::DivAssign<$b> for $a {
|
||||||
fn div_assign(&mut self, other: $b) {
|
fn div_assign(&mut self, other: $b) {
|
||||||
*self = *self / other;
|
*self = *self / other;
|
||||||
}
|
}
|
||||||
|
@ -72,15 +72,15 @@ pub trait Numeric:
|
|||||||
+ Mul<f64, Output = Self>
|
+ Mul<f64, Output = Self>
|
||||||
+ Div<f64, Output = Self>
|
+ Div<f64, Output = Self>
|
||||||
{
|
{
|
||||||
/// The identity element.
|
/// The identity element for addition.
|
||||||
fn zero() -> Self;
|
fn zero() -> Self;
|
||||||
|
|
||||||
/// Whether `self` is the identity element.
|
/// Whether `self` is zero.
|
||||||
fn is_zero(self) -> bool {
|
fn is_zero(self) -> bool {
|
||||||
self == Self::zero()
|
self == Self::zero()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether `self` contains only finite parts.
|
/// Whether `self` consists only of finite parts.
|
||||||
fn is_finite(self) -> bool;
|
fn is_finite(self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,8 +38,8 @@ impl Point {
|
|||||||
/// Transform the point with the given transformation.
|
/// Transform the point with the given transformation.
|
||||||
pub fn transform(self, ts: Transform) -> Self {
|
pub fn transform(self, ts: Transform) -> Self {
|
||||||
Self::new(
|
Self::new(
|
||||||
ts.sx.resolve(self.x) + ts.kx.resolve(self.y) + ts.tx,
|
ts.sx.of(self.x) + ts.kx.of(self.y) + ts.tx,
|
||||||
ts.ky.resolve(self.x) + ts.sy.resolve(self.y) + ts.ty,
|
ts.ky.of(self.x) + ts.sy.of(self.y) + ts.ty,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use super::*;
|
|||||||
/// A ratio of a whole.
|
/// A ratio of a whole.
|
||||||
///
|
///
|
||||||
/// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the
|
/// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the
|
||||||
/// corresponding [literal](crate::syntax::ast::LitKind::Percent).
|
/// corresponding [literal](crate::syntax::ast::LitKind::Numeric).
|
||||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
pub struct Ratio(Scalar);
|
pub struct Ratio(Scalar);
|
||||||
|
|
||||||
@ -38,13 +38,13 @@ impl Ratio {
|
|||||||
self.0 == 1.0
|
self.0 == 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The absolute value of the this ratio.
|
/// The absolute value of this ratio.
|
||||||
pub fn abs(self) -> Self {
|
pub fn abs(self) -> Self {
|
||||||
Self::new(self.get().abs())
|
Self::new(self.get().abs())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve this relative to the given `whole`.
|
/// Return the ratio of the given `whole`.
|
||||||
pub fn resolve<T: Numeric>(self, whole: T) -> T {
|
pub fn of<T: Numeric>(self, whole: T) -> T {
|
||||||
let resolved = whole * self.get();
|
let resolved = whole * self.get();
|
||||||
if resolved.is_finite() { resolved } else { T::zero() }
|
if resolved.is_finite() { resolved } else { T::zero() }
|
||||||
}
|
}
|
||||||
|
@ -27,12 +27,17 @@ impl<T: Numeric> Relative<T> {
|
|||||||
|
|
||||||
/// Whether both parts are zero.
|
/// Whether both parts are zero.
|
||||||
pub fn is_zero(self) -> bool {
|
pub fn is_zero(self) -> bool {
|
||||||
self.rel.is_zero() && self.abs.is_zero()
|
self.rel.is_zero() && self.abs == T::zero()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve this relative to the given `whole`.
|
/// Whether the relative part is one and the absolute part is zero.
|
||||||
pub fn resolve(self, whole: T) -> T {
|
pub fn is_one(self) -> bool {
|
||||||
self.rel.resolve(whole) + self.abs
|
self.rel.is_one() && self.abs == T::zero()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluate this relative to the given `whole`.
|
||||||
|
pub fn relative_to(self, whole: T) -> T {
|
||||||
|
self.rel.of(whole) + self.abs
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Map the absolute part with `f`.
|
/// Map the absolute part with `f`.
|
||||||
@ -120,27 +125,31 @@ impl<T: Numeric> Div<f64> for Relative<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Numeric> AddAssign for Relative<T> {
|
impl<T: Numeric + AddAssign> AddAssign for Relative<T> {
|
||||||
fn add_assign(&mut self, other: Self) {
|
fn add_assign(&mut self, other: Self) {
|
||||||
*self = *self + other;
|
self.rel += other.rel;
|
||||||
|
self.abs += other.abs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Numeric> SubAssign for Relative<T> {
|
impl<T: Numeric + SubAssign> SubAssign for Relative<T> {
|
||||||
fn sub_assign(&mut self, other: Self) {
|
fn sub_assign(&mut self, other: Self) {
|
||||||
*self = *self - other;
|
self.rel -= other.rel;
|
||||||
|
self.abs -= other.abs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Numeric> MulAssign<f64> for Relative<T> {
|
impl<T: Numeric + MulAssign<f64>> MulAssign<f64> for Relative<T> {
|
||||||
fn mul_assign(&mut self, other: f64) {
|
fn mul_assign(&mut self, other: f64) {
|
||||||
*self = *self * other;
|
self.rel *= other;
|
||||||
|
self.abs *= other;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Numeric> DivAssign<f64> for Relative<T> {
|
impl<T: Numeric + DivAssign<f64>> DivAssign<f64> for Relative<T> {
|
||||||
fn div_assign(&mut self, other: f64) {
|
fn div_assign(&mut self, other: f64) {
|
||||||
*self = *self * other;
|
self.rel /= other;
|
||||||
|
self.abs /= other;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,13 +44,13 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Sides<Relative<Length>> {
|
impl Sides<Relative<Length>> {
|
||||||
/// Resolve the sides relative to the given `size`.
|
/// Evaluate the sides relative to the given `size`.
|
||||||
pub fn resolve(self, size: Size) -> Sides<Length> {
|
pub fn relative_to(self, size: Size) -> Sides<Length> {
|
||||||
Sides {
|
Sides {
|
||||||
left: self.left.resolve(size.x),
|
left: self.left.relative_to(size.x),
|
||||||
top: self.top.resolve(size.y),
|
top: self.top.relative_to(size.y),
|
||||||
right: self.right.resolve(size.x),
|
right: self.right.relative_to(size.x),
|
||||||
bottom: self.bottom.resolve(size.y),
|
bottom: self.bottom.relative_to(size.y),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,8 +59,8 @@ impl Transform {
|
|||||||
ky: self.ky * prev.sx + self.sy * prev.ky,
|
ky: self.ky * prev.sx + self.sy * prev.ky,
|
||||||
kx: self.sx * prev.kx + self.kx * prev.sy,
|
kx: self.sx * prev.kx + self.kx * prev.sy,
|
||||||
sy: self.ky * prev.kx + self.sy * prev.sy,
|
sy: self.ky * prev.kx + self.sy * prev.sy,
|
||||||
tx: self.sx.resolve(prev.tx) + self.kx.resolve(prev.ty) + self.tx,
|
tx: self.sx.of(prev.tx) + self.kx.of(prev.ty) + self.tx,
|
||||||
ty: self.ky.resolve(prev.tx) + self.sy.resolve(prev.ty) + self.ty,
|
ty: self.ky.of(prev.tx) + self.sy.of(prev.ty) + self.ty,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,13 +34,14 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod util;
|
pub mod util;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
pub mod geom;
|
||||||
|
#[macro_use]
|
||||||
pub mod diag;
|
pub mod diag;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod eval;
|
pub mod eval;
|
||||||
pub mod export;
|
pub mod export;
|
||||||
pub mod font;
|
pub mod font;
|
||||||
pub mod frame;
|
pub mod frame;
|
||||||
pub mod geom;
|
|
||||||
pub mod image;
|
pub mod image;
|
||||||
pub mod library;
|
pub mod library;
|
||||||
pub mod loading;
|
pub mod loading;
|
||||||
@ -163,7 +164,7 @@ impl Context {
|
|||||||
|
|
||||||
/// Resolve a user-entered path (relative to the current evaluation
|
/// Resolve a user-entered path (relative to the current evaluation
|
||||||
/// location) to be relative to the compilation environment's root.
|
/// location) to be relative to the compilation environment's root.
|
||||||
pub fn resolve(&self, path: &str) -> PathBuf {
|
pub fn complete_path(&self, path: &str) -> PathBuf {
|
||||||
if let Some(&id) = self.route.last() {
|
if let Some(&id) = self.route.last() {
|
||||||
if let Some(dir) = self.sources.get(id).path().parent() {
|
if let Some(dir) = self.sources.get(id).path().parent() {
|
||||||
return dir.join(path);
|
return dir.join(path);
|
||||||
|
@ -13,7 +13,7 @@ impl ImageNode {
|
|||||||
|
|
||||||
fn construct(ctx: &mut Context, args: &mut Args) -> TypResult<Content> {
|
fn construct(ctx: &mut Context, args: &mut Args) -> TypResult<Content> {
|
||||||
let path = args.expect::<Spanned<EcoString>>("path to image file")?;
|
let path = args.expect::<Spanned<EcoString>>("path to image file")?;
|
||||||
let full = ctx.resolve(&path.v);
|
let full = ctx.complete_path(&path.v);
|
||||||
let id = ctx.images.load(&full).map_err(|err| match err.kind() {
|
let id = ctx.images.load(&full).map_err(|err| match err.kind() {
|
||||||
std::io::ErrorKind::NotFound => error!(path.span, "file not found"),
|
std::io::ErrorKind::NotFound => error!(path.span, "file not found"),
|
||||||
_ => error!(path.span, "failed to load image ({})", err),
|
_ => error!(path.span, "failed to load image ({})", err),
|
||||||
|
@ -4,9 +4,9 @@ use crate::library::prelude::*;
|
|||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct LineNode {
|
pub struct LineNode {
|
||||||
/// Where the line starts.
|
/// Where the line starts.
|
||||||
origin: Spec<Relative<Length>>,
|
origin: Spec<Relative<RawLength>>,
|
||||||
/// The offset from the `origin` where the line ends.
|
/// The offset from the `origin` where the line ends.
|
||||||
delta: Spec<Relative<Length>>,
|
delta: Spec<Relative<RawLength>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
@ -14,15 +14,17 @@ impl LineNode {
|
|||||||
/// How to stroke the line.
|
/// How to stroke the line.
|
||||||
pub const STROKE: Paint = Color::BLACK.into();
|
pub const STROKE: Paint = Color::BLACK.into();
|
||||||
/// The line's thickness.
|
/// The line's thickness.
|
||||||
pub const THICKNESS: Length = Length::pt(1.0);
|
#[property(resolve)]
|
||||||
|
pub const THICKNESS: RawLength = Length::pt(1.0).into();
|
||||||
|
|
||||||
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
||||||
let origin = args.named("origin")?.unwrap_or_default();
|
let origin = args.named("origin")?.unwrap_or_default();
|
||||||
let delta = match args.named::<Spec<Relative<Length>>>("to")? {
|
|
||||||
|
let delta = match args.named::<Spec<Relative<RawLength>>>("to")? {
|
||||||
Some(to) => to.zip(origin).map(|(to, from)| to - from),
|
Some(to) => to.zip(origin).map(|(to, from)| to - from),
|
||||||
None => {
|
None => {
|
||||||
let length = args
|
let length = args
|
||||||
.named::<Relative<Length>>("length")?
|
.named::<Relative<RawLength>>("length")?
|
||||||
.unwrap_or(Length::cm(1.0).into());
|
.unwrap_or(Length::cm(1.0).into());
|
||||||
|
|
||||||
let angle = args.named::<Angle>("angle")?.unwrap_or_default();
|
let angle = args.named::<Angle>("angle")?.unwrap_or_default();
|
||||||
@ -50,18 +52,37 @@ impl Layout for LineNode {
|
|||||||
thickness,
|
thickness,
|
||||||
});
|
});
|
||||||
|
|
||||||
let resolved_origin =
|
let origin = self
|
||||||
self.origin.zip(regions.base).map(|(l, b)| Relative::resolve(l, b));
|
.origin
|
||||||
let resolved_delta =
|
.resolve(styles)
|
||||||
self.delta.zip(regions.base).map(|(l, b)| Relative::resolve(l, b));
|
.zip(regions.base)
|
||||||
|
.map(|(l, b)| l.relative_to(b));
|
||||||
|
|
||||||
let geometry = Geometry::Line(resolved_delta.to_point());
|
let delta = self
|
||||||
|
.delta
|
||||||
|
.resolve(styles)
|
||||||
|
.zip(regions.base)
|
||||||
|
.map(|(l, b)| l.relative_to(b));
|
||||||
|
|
||||||
|
let geometry = Geometry::Line(delta.to_point());
|
||||||
let shape = Shape { geometry, fill: None, stroke };
|
let shape = Shape { geometry, fill: None, stroke };
|
||||||
|
|
||||||
let target = regions.expand.select(regions.first, Size::zero());
|
let target = regions.expand.select(regions.first, Size::zero());
|
||||||
let mut frame = Frame::new(target);
|
let mut frame = Frame::new(target);
|
||||||
frame.push(resolved_origin.to_point(), Element::Shape(shape));
|
frame.push(origin.to_point(), Element::Shape(shape));
|
||||||
|
|
||||||
Ok(vec![Arc::new(frame)])
|
Ok(vec![Arc::new(frame)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
castable! {
|
||||||
|
Spec<Relative<RawLength>>,
|
||||||
|
Expected: "array of two relative lengths",
|
||||||
|
Value::Array(array) => {
|
||||||
|
let mut iter = array.into_iter();
|
||||||
|
match (iter.next(), iter.next(), iter.next()) {
|
||||||
|
(Some(a), Some(b), None) => Spec::new(a.cast()?, b.cast()?),
|
||||||
|
_ => Err("point array must contain exactly two entries")?,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
@ -26,14 +26,15 @@ impl<const S: ShapeKind> ShapeNode<S> {
|
|||||||
/// How to stroke the shape.
|
/// How to stroke the shape.
|
||||||
pub const STROKE: Smart<Option<Paint>> = Smart::Auto;
|
pub const STROKE: Smart<Option<Paint>> = Smart::Auto;
|
||||||
/// The stroke's thickness.
|
/// The stroke's thickness.
|
||||||
pub const THICKNESS: Length = Length::pt(1.0);
|
#[property(resolve)]
|
||||||
|
pub const THICKNESS: RawLength = Length::pt(1.0).into();
|
||||||
/// How much to pad the shape's content.
|
/// How much to pad the shape's content.
|
||||||
pub const PADDING: Relative<Length> = Relative::zero();
|
pub const PADDING: Relative<RawLength> = Relative::zero();
|
||||||
|
|
||||||
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
||||||
let size = match S {
|
let size = match S {
|
||||||
SQUARE => args.named::<Length>("size")?.map(Relative::from),
|
SQUARE => args.named::<RawLength>("size")?.map(Relative::from),
|
||||||
CIRCLE => args.named::<Length>("radius")?.map(|r| 2.0 * Relative::from(r)),
|
CIRCLE => args.named::<RawLength>("radius")?.map(|r| 2.0 * Relative::from(r)),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,46 @@
|
|||||||
use crate::geom::Transform;
|
use crate::geom::Transform;
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
|
|
||||||
|
/// Move a node without affecting layout.
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
pub struct MoveNode {
|
||||||
|
/// The offset by which to move the node.
|
||||||
|
pub delta: Spec<Relative<RawLength>>,
|
||||||
|
/// The node whose contents should be moved.
|
||||||
|
pub child: LayoutNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node]
|
||||||
|
impl MoveNode {
|
||||||
|
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
||||||
|
let dx = args.named("x")?.unwrap_or_default();
|
||||||
|
let dy = args.named("y")?.unwrap_or_default();
|
||||||
|
Ok(Content::inline(Self {
|
||||||
|
delta: Spec::new(dx, dy),
|
||||||
|
child: args.expect("body")?,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Layout for MoveNode {
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
ctx: &mut Context,
|
||||||
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
|
let mut frames = self.child.layout(ctx, regions, styles)?;
|
||||||
|
|
||||||
|
let delta = self.delta.resolve(styles);
|
||||||
|
for frame in &mut frames {
|
||||||
|
let delta = delta.zip(frame.size).map(|(d, s)| d.relative_to(s));
|
||||||
|
Arc::make_mut(frame).translate(delta.to_point());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(frames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Transform a node without affecting layout.
|
/// Transform a node without affecting layout.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct TransformNode<const T: TransformKind> {
|
pub struct TransformNode<const T: TransformKind> {
|
||||||
@ -10,13 +50,10 @@ pub struct TransformNode<const T: TransformKind> {
|
|||||||
pub child: LayoutNode,
|
pub child: LayoutNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transform a node by translating it without affecting layout.
|
/// Rotate a node without affecting layout.
|
||||||
pub type MoveNode = TransformNode<MOVE>;
|
|
||||||
|
|
||||||
/// Transform a node by rotating it without affecting layout.
|
|
||||||
pub type RotateNode = TransformNode<ROTATE>;
|
pub type RotateNode = TransformNode<ROTATE>;
|
||||||
|
|
||||||
/// Transform a node by scaling it without affecting layout.
|
/// Scale a node without affecting layout.
|
||||||
pub type ScaleNode = TransformNode<SCALE>;
|
pub type ScaleNode = TransformNode<SCALE>;
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
@ -27,11 +64,6 @@ impl<const T: TransformKind> TransformNode<T> {
|
|||||||
|
|
||||||
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
||||||
let transform = match T {
|
let transform = match T {
|
||||||
MOVE => {
|
|
||||||
let tx = args.named("x")?.unwrap_or_default();
|
|
||||||
let ty = args.named("y")?.unwrap_or_default();
|
|
||||||
Transform::translate(tx, ty)
|
|
||||||
}
|
|
||||||
ROTATE => {
|
ROTATE => {
|
||||||
let angle = args.named_or_find("angle")?.unwrap_or_default();
|
let angle = args.named_or_find("angle")?.unwrap_or_default();
|
||||||
Transform::rotate(angle)
|
Transform::rotate(angle)
|
||||||
@ -77,9 +109,6 @@ impl<const T: TransformKind> Layout for TransformNode<T> {
|
|||||||
/// Kinds of transformations.
|
/// Kinds of transformations.
|
||||||
pub type TransformKind = usize;
|
pub type TransformKind = usize;
|
||||||
|
|
||||||
/// A translation on the X and Y axes.
|
|
||||||
const MOVE: TransformKind = 0;
|
|
||||||
|
|
||||||
/// A rotational transformation.
|
/// A rotational transformation.
|
||||||
const ROTATE: TransformKind = 1;
|
const ROTATE: TransformKind = 1;
|
||||||
|
|
||||||
|
@ -14,7 +14,8 @@ pub struct ColumnsNode {
|
|||||||
#[node]
|
#[node]
|
||||||
impl ColumnsNode {
|
impl ColumnsNode {
|
||||||
/// The size of the gutter space between each column.
|
/// The size of the gutter space between each column.
|
||||||
pub const GUTTER: Relative<Length> = Ratio::new(0.04).into();
|
#[property(resolve)]
|
||||||
|
pub const GUTTER: Relative<RawLength> = Ratio::new(0.04).into();
|
||||||
|
|
||||||
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
||||||
Ok(Content::block(Self {
|
Ok(Content::block(Self {
|
||||||
@ -39,7 +40,7 @@ impl Layout for ColumnsNode {
|
|||||||
|
|
||||||
// Determine the width of the gutter and each column.
|
// Determine the width of the gutter and each column.
|
||||||
let columns = self.columns.get();
|
let columns = self.columns.get();
|
||||||
let gutter = styles.get(Self::GUTTER).resolve(regions.base.x);
|
let gutter = styles.get(Self::GUTTER).relative_to(regions.base.x);
|
||||||
let width = (regions.first.x - gutter * (columns - 1) as f64) / columns as f64;
|
let width = (regions.first.x - gutter * (columns - 1) as f64) / columns as f64;
|
||||||
|
|
||||||
// Create the pod regions.
|
// Create the pod regions.
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use super::{AlignNode, PlaceNode, Spacing};
|
use super::{AlignNode, PlaceNode, Spacing};
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::library::text::{ParNode, TextNode};
|
use crate::library::text::ParNode;
|
||||||
|
|
||||||
/// Arrange spacing, paragraphs and other block-level nodes into a flow.
|
/// Arrange spacing, paragraphs and other block-level nodes into a flow.
|
||||||
///
|
///
|
||||||
@ -37,22 +37,20 @@ impl Layout for FlowNode {
|
|||||||
let styles = map.chain(&styles);
|
let styles = map.chain(&styles);
|
||||||
match child {
|
match child {
|
||||||
FlowChild::Leading => {
|
FlowChild::Leading => {
|
||||||
let em = styles.get(TextNode::SIZE);
|
let amount = styles.get(ParNode::LEADING);
|
||||||
let amount = styles.get(ParNode::LEADING).resolve(em);
|
layouter.layout_spacing(amount.into(), styles);
|
||||||
layouter.layout_spacing(amount.into());
|
|
||||||
}
|
}
|
||||||
FlowChild::Parbreak => {
|
FlowChild::Parbreak => {
|
||||||
let em = styles.get(TextNode::SIZE);
|
|
||||||
let leading = styles.get(ParNode::LEADING);
|
let leading = styles.get(ParNode::LEADING);
|
||||||
let spacing = styles.get(ParNode::SPACING);
|
let spacing = styles.get(ParNode::SPACING);
|
||||||
let amount = (leading + spacing).resolve(em);
|
let amount = leading + spacing;
|
||||||
layouter.layout_spacing(amount.into());
|
layouter.layout_spacing(amount.into(), styles);
|
||||||
}
|
}
|
||||||
FlowChild::Colbreak => {
|
FlowChild::Colbreak => {
|
||||||
layouter.finish_region();
|
layouter.finish_region();
|
||||||
}
|
}
|
||||||
FlowChild::Spacing(kind) => {
|
FlowChild::Spacing(kind) => {
|
||||||
layouter.layout_spacing(*kind);
|
layouter.layout_spacing(*kind, styles);
|
||||||
}
|
}
|
||||||
FlowChild::Node(ref node) => {
|
FlowChild::Node(ref node) => {
|
||||||
layouter.layout_node(ctx, node, styles)?;
|
layouter.layout_node(ctx, node, styles)?;
|
||||||
@ -142,11 +140,11 @@ impl FlowLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Layout spacing.
|
/// Layout spacing.
|
||||||
pub fn layout_spacing(&mut self, spacing: Spacing) {
|
pub fn layout_spacing(&mut self, spacing: Spacing, styles: StyleChain) {
|
||||||
match spacing {
|
match spacing {
|
||||||
Spacing::Relative(v) => {
|
Spacing::Relative(v) => {
|
||||||
// Resolve the spacing and limit it to the remaining space.
|
// Resolve the spacing and limit it to the remaining space.
|
||||||
let resolved = v.resolve(self.full.y);
|
let resolved = v.resolve(styles).relative_to(self.full.y);
|
||||||
let limited = resolved.min(self.regions.first.y);
|
let limited = resolved.min(self.regions.first.y);
|
||||||
self.regions.first.y -= limited;
|
self.regions.first.y -= limited;
|
||||||
self.used.y += limited;
|
self.used.y += limited;
|
||||||
@ -235,7 +233,7 @@ impl FlowLayouter {
|
|||||||
offset += v;
|
offset += v;
|
||||||
}
|
}
|
||||||
FlowItem::Fractional(v) => {
|
FlowItem::Fractional(v) => {
|
||||||
offset += v.resolve(self.fr, remaining);
|
offset += v.share(self.fr, remaining);
|
||||||
}
|
}
|
||||||
FlowItem::Frame(frame, aligns) => {
|
FlowItem::Frame(frame, aligns) => {
|
||||||
ruler = ruler.max(aligns.y);
|
ruler = ruler.max(aligns.y);
|
||||||
|
@ -58,7 +58,7 @@ pub enum TrackSizing {
|
|||||||
Auto,
|
Auto,
|
||||||
/// A track size specified in absolute terms and relative to the parent's
|
/// A track size specified in absolute terms and relative to the parent's
|
||||||
/// size.
|
/// size.
|
||||||
Relative(Relative<Length>),
|
Relative(Relative<RawLength>),
|
||||||
/// A track size specified as a fraction of the remaining free space in the
|
/// A track size specified as a fraction of the remaining free space in the
|
||||||
/// parent.
|
/// parent.
|
||||||
Fractional(Fraction),
|
Fractional(Fraction),
|
||||||
@ -236,7 +236,8 @@ impl<'a> GridLayouter<'a> {
|
|||||||
match col {
|
match col {
|
||||||
TrackSizing::Auto => {}
|
TrackSizing::Auto => {}
|
||||||
TrackSizing::Relative(v) => {
|
TrackSizing::Relative(v) => {
|
||||||
let resolved = v.resolve(self.regions.base.x);
|
let resolved =
|
||||||
|
v.resolve(self.styles).relative_to(self.regions.base.x);
|
||||||
*rcol = resolved;
|
*rcol = resolved;
|
||||||
rel += resolved;
|
rel += resolved;
|
||||||
}
|
}
|
||||||
@ -295,7 +296,8 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// base, for auto it's already correct and for fr we could
|
// base, for auto it's already correct and for fr we could
|
||||||
// only guess anyway.
|
// only guess anyway.
|
||||||
if let TrackSizing::Relative(v) = self.rows[y] {
|
if let TrackSizing::Relative(v) = self.rows[y] {
|
||||||
pod.base.y = v.resolve(self.regions.base.y);
|
pod.base.y =
|
||||||
|
v.resolve(self.styles).relative_to(self.regions.base.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
let frame = node.layout(ctx, &pod, self.styles)?.remove(0);
|
let frame = node.layout(ctx, &pod, self.styles)?.remove(0);
|
||||||
@ -315,7 +317,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
fn grow_fractional_columns(&mut self, remaining: Length, fr: Fraction) {
|
fn grow_fractional_columns(&mut self, remaining: Length, fr: Fraction) {
|
||||||
for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
|
for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
|
||||||
if let TrackSizing::Fractional(v) = col {
|
if let TrackSizing::Fractional(v) = col {
|
||||||
*rcol = v.resolve(fr, remaining);
|
*rcol = v.share(fr, remaining);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -422,10 +424,10 @@ impl<'a> GridLayouter<'a> {
|
|||||||
fn layout_relative_row(
|
fn layout_relative_row(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &mut Context,
|
ctx: &mut Context,
|
||||||
v: Relative<Length>,
|
v: Relative<RawLength>,
|
||||||
y: usize,
|
y: usize,
|
||||||
) -> TypResult<()> {
|
) -> TypResult<()> {
|
||||||
let resolved = v.resolve(self.regions.base.y);
|
let resolved = v.resolve(self.styles).relative_to(self.regions.base.y);
|
||||||
let frame = self.layout_single_row(ctx, resolved, y)?;
|
let frame = self.layout_single_row(ctx, resolved, y)?;
|
||||||
|
|
||||||
// Skip to fitting region.
|
// Skip to fitting region.
|
||||||
@ -543,7 +545,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
Row::Frame(frame) => frame,
|
Row::Frame(frame) => frame,
|
||||||
Row::Fr(v, y) => {
|
Row::Fr(v, y) => {
|
||||||
let remaining = self.full - self.used.y;
|
let remaining = self.full - self.used.y;
|
||||||
let height = v.resolve(self.fr, remaining);
|
let height = v.share(self.fr, remaining);
|
||||||
self.layout_single_row(ctx, height, y)?
|
self.layout_single_row(ctx, height, y)?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@ use crate::library::prelude::*;
|
|||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct PadNode {
|
pub struct PadNode {
|
||||||
/// The amount of padding.
|
/// The amount of padding.
|
||||||
pub padding: Sides<Relative<Length>>,
|
pub padding: Sides<Relative<RawLength>>,
|
||||||
/// The child node whose sides to pad.
|
/// The child node whose sides to pad.
|
||||||
pub child: LayoutNode,
|
pub child: LayoutNode,
|
||||||
}
|
}
|
||||||
@ -33,14 +33,15 @@ impl Layout for PadNode {
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Arc<Frame>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
// Layout child into padded regions.
|
// Layout child into padded regions.
|
||||||
let pod = regions.map(|size| shrink(size, self.padding));
|
let padding = self.padding.resolve(styles);
|
||||||
|
let pod = regions.map(|size| shrink(size, padding));
|
||||||
let mut frames = self.child.layout(ctx, &pod, styles)?;
|
let mut frames = self.child.layout(ctx, &pod, styles)?;
|
||||||
|
|
||||||
for frame in &mut frames {
|
for frame in &mut frames {
|
||||||
// Apply the padding inversely such that the grown size padded
|
// Apply the padding inversely such that the grown size padded
|
||||||
// yields the frame's size.
|
// yields the frame's size.
|
||||||
let padded = grow(frame.size, self.padding);
|
let padded = grow(frame.size, padding);
|
||||||
let padding = self.padding.resolve(padded);
|
let padding = padding.relative_to(padded);
|
||||||
let offset = Point::new(padding.left, padding.top);
|
let offset = Point::new(padding.left, padding.top);
|
||||||
|
|
||||||
// Grow the frame and translate everything in the frame inwards.
|
// Grow the frame and translate everything in the frame inwards.
|
||||||
@ -55,7 +56,7 @@ impl Layout for PadNode {
|
|||||||
|
|
||||||
/// Shrink a size by padding relative to the size itself.
|
/// Shrink a size by padding relative to the size itself.
|
||||||
fn shrink(size: Size, padding: Sides<Relative<Length>>) -> Size {
|
fn shrink(size: Size, padding: Sides<Relative<Length>>) -> Size {
|
||||||
size - padding.resolve(size).sum_by_axis()
|
size - padding.relative_to(size).sum_by_axis()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Grow a size by padding relative to the grown size.
|
/// Grow a size by padding relative to the grown size.
|
||||||
|
@ -10,19 +10,21 @@ pub struct PageNode(pub LayoutNode);
|
|||||||
#[node]
|
#[node]
|
||||||
impl PageNode {
|
impl PageNode {
|
||||||
/// The unflipped width of the page.
|
/// The unflipped width of the page.
|
||||||
pub const WIDTH: Smart<Length> = Smart::Custom(Paper::A4.width());
|
#[property(resolve)]
|
||||||
|
pub const WIDTH: Smart<RawLength> = Smart::Custom(Paper::A4.width().into());
|
||||||
/// The unflipped height of the page.
|
/// The unflipped height of the page.
|
||||||
pub const HEIGHT: Smart<Length> = Smart::Custom(Paper::A4.height());
|
#[property(resolve)]
|
||||||
|
pub const HEIGHT: Smart<RawLength> = Smart::Custom(Paper::A4.height().into());
|
||||||
/// Whether the page is flipped into landscape orientation.
|
/// Whether the page is flipped into landscape orientation.
|
||||||
pub const FLIPPED: bool = false;
|
pub const FLIPPED: bool = false;
|
||||||
/// The left margin.
|
/// The left margin.
|
||||||
pub const LEFT: Smart<Relative<Length>> = Smart::Auto;
|
pub const LEFT: Smart<Relative<RawLength>> = Smart::Auto;
|
||||||
/// The right margin.
|
/// The right margin.
|
||||||
pub const RIGHT: Smart<Relative<Length>> = Smart::Auto;
|
pub const RIGHT: Smart<Relative<RawLength>> = Smart::Auto;
|
||||||
/// The top margin.
|
/// The top margin.
|
||||||
pub const TOP: Smart<Relative<Length>> = Smart::Auto;
|
pub const TOP: Smart<Relative<RawLength>> = Smart::Auto;
|
||||||
/// The bottom margin.
|
/// The bottom margin.
|
||||||
pub const BOTTOM: Smart<Relative<Length>> = Smart::Auto;
|
pub const BOTTOM: Smart<Relative<RawLength>> = Smart::Auto;
|
||||||
/// The page's background color.
|
/// The page's background color.
|
||||||
pub const FILL: Option<Paint> = None;
|
pub const FILL: Option<Paint> = None;
|
||||||
/// How many columns the page has.
|
/// How many columns the page has.
|
||||||
@ -42,8 +44,8 @@ impl PageNode {
|
|||||||
let mut styles = StyleMap::new();
|
let mut styles = StyleMap::new();
|
||||||
|
|
||||||
if let Some(paper) = args.named_or_find::<Paper>("paper")? {
|
if let Some(paper) = args.named_or_find::<Paper>("paper")? {
|
||||||
styles.set(Self::WIDTH, Smart::Custom(paper.width()));
|
styles.set(Self::WIDTH, Smart::Custom(paper.width().into()));
|
||||||
styles.set(Self::HEIGHT, Smart::Custom(paper.height()));
|
styles.set(Self::HEIGHT, Smart::Custom(paper.height().into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
styles.set_opt(Self::WIDTH, args.named("width")?);
|
styles.set_opt(Self::WIDTH, args.named("width")?);
|
||||||
@ -115,7 +117,7 @@ impl PageNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Layout the child.
|
// Layout the child.
|
||||||
let regions = Regions::repeat(size, size, size.map(Numeric::is_finite));
|
let regions = Regions::repeat(size, size, size.map(Length::is_finite));
|
||||||
let mut frames = child.layout(ctx, ®ions, styles)?;
|
let mut frames = child.layout(ctx, ®ions, styles)?;
|
||||||
|
|
||||||
let header = styles.get(Self::HEADER);
|
let header = styles.get(Self::HEADER);
|
||||||
@ -124,7 +126,7 @@ impl PageNode {
|
|||||||
// Realize header and footer.
|
// Realize header and footer.
|
||||||
for frame in &mut frames {
|
for frame in &mut frames {
|
||||||
let size = frame.size;
|
let size = frame.size;
|
||||||
let padding = padding.resolve(size);
|
let padding = padding.resolve(styles).relative_to(size);
|
||||||
for (y, h, marginal) in [
|
for (y, h, marginal) in [
|
||||||
(Length::zero(), padding.top, header),
|
(Length::zero(), padding.top, header),
|
||||||
(size.y - padding.bottom, padding.bottom, footer),
|
(size.y - padding.bottom, padding.bottom, footer),
|
||||||
@ -133,7 +135,7 @@ impl PageNode {
|
|||||||
let pos = Point::new(padding.left, y);
|
let pos = Point::new(padding.left, y);
|
||||||
let w = size.x - padding.left - padding.right;
|
let w = size.x - padding.left - padding.right;
|
||||||
let area = Size::new(w, h);
|
let area = Size::new(w, h);
|
||||||
let pod = Regions::one(area, area, area.map(Numeric::is_finite));
|
let pod = Regions::one(area, area, area.map(Length::is_finite));
|
||||||
let sub = Layout::layout(&content, ctx, &pod, styles)?.remove(0);
|
let sub = Layout::layout(&content, ctx, &pod, styles)?.remove(0);
|
||||||
Arc::make_mut(frame).push_frame(pos, sub);
|
Arc::make_mut(frame).push_frame(pos, sub);
|
||||||
}
|
}
|
||||||
|
@ -8,12 +8,12 @@ pub struct PlaceNode(pub LayoutNode);
|
|||||||
#[node]
|
#[node]
|
||||||
impl PlaceNode {
|
impl PlaceNode {
|
||||||
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
||||||
let tx = args.named("dx")?.unwrap_or_default();
|
|
||||||
let ty = args.named("dy")?.unwrap_or_default();
|
|
||||||
let aligns = args.find()?.unwrap_or(Spec::with_x(Some(RawAlign::Start)));
|
let aligns = args.find()?.unwrap_or(Spec::with_x(Some(RawAlign::Start)));
|
||||||
|
let dx = args.named("dx")?.unwrap_or_default();
|
||||||
|
let dy = args.named("dy")?.unwrap_or_default();
|
||||||
let body: LayoutNode = args.expect("body")?;
|
let body: LayoutNode = args.expect("body")?;
|
||||||
Ok(Content::block(Self(
|
Ok(Content::block(Self(
|
||||||
body.moved(Point::new(tx, ty)).aligned(aligns),
|
body.moved(Spec::new(dx, dy)).aligned(aligns),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ impl VNode {
|
|||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum Spacing {
|
pub enum Spacing {
|
||||||
/// Spacing specified in absolute terms and relative to the parent's size.
|
/// Spacing specified in absolute terms and relative to the parent's size.
|
||||||
Relative(Relative<Length>),
|
Relative(Relative<RawLength>),
|
||||||
/// Spacing specified as a fraction of the remaining free space in the parent.
|
/// Spacing specified as a fraction of the remaining free space in the parent.
|
||||||
Fractional(Fraction),
|
Fractional(Fraction),
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ impl Layout for StackNode {
|
|||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Arc<Frame>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
let mut layouter = StackLayouter::new(self.dir, regions);
|
let mut layouter = StackLayouter::new(self.dir, regions, styles);
|
||||||
|
|
||||||
// Spacing to insert before the next node.
|
// Spacing to insert before the next node.
|
||||||
let mut deferred = None;
|
let mut deferred = None;
|
||||||
@ -85,13 +85,15 @@ castable! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Performs stack layout.
|
/// Performs stack layout.
|
||||||
pub struct StackLayouter {
|
pub struct StackLayouter<'a> {
|
||||||
/// The stacking direction.
|
/// The stacking direction.
|
||||||
dir: Dir,
|
dir: Dir,
|
||||||
/// The axis of the stacking direction.
|
/// The axis of the stacking direction.
|
||||||
axis: SpecAxis,
|
axis: SpecAxis,
|
||||||
/// The regions to layout children into.
|
/// The regions to layout children into.
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
|
/// The inherited styles.
|
||||||
|
styles: StyleChain<'a>,
|
||||||
/// Whether the stack itself should expand to fill the region.
|
/// Whether the stack itself should expand to fill the region.
|
||||||
expand: Spec<bool>,
|
expand: Spec<bool>,
|
||||||
/// The full size of the current region that was available at the start.
|
/// The full size of the current region that was available at the start.
|
||||||
@ -117,9 +119,9 @@ enum StackItem {
|
|||||||
Frame(Arc<Frame>, Align),
|
Frame(Arc<Frame>, Align),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StackLayouter {
|
impl<'a> StackLayouter<'a> {
|
||||||
/// Create a new stack layouter.
|
/// Create a new stack layouter.
|
||||||
pub fn new(dir: Dir, regions: &Regions) -> Self {
|
pub fn new(dir: Dir, regions: &Regions, styles: StyleChain<'a>) -> Self {
|
||||||
let axis = dir.axis();
|
let axis = dir.axis();
|
||||||
let expand = regions.expand;
|
let expand = regions.expand;
|
||||||
let full = regions.first;
|
let full = regions.first;
|
||||||
@ -132,6 +134,7 @@ impl StackLayouter {
|
|||||||
dir,
|
dir,
|
||||||
axis,
|
axis,
|
||||||
regions,
|
regions,
|
||||||
|
styles,
|
||||||
expand,
|
expand,
|
||||||
full,
|
full,
|
||||||
used: Gen::zero(),
|
used: Gen::zero(),
|
||||||
@ -146,7 +149,8 @@ impl StackLayouter {
|
|||||||
match spacing {
|
match spacing {
|
||||||
Spacing::Relative(v) => {
|
Spacing::Relative(v) => {
|
||||||
// Resolve the spacing and limit it to the remaining space.
|
// Resolve the spacing and limit it to the remaining space.
|
||||||
let resolved = v.resolve(self.regions.base.get(self.axis));
|
let resolved =
|
||||||
|
v.resolve(self.styles).relative_to(self.regions.base.get(self.axis));
|
||||||
let remaining = self.regions.first.get_mut(self.axis);
|
let remaining = self.regions.first.get_mut(self.axis);
|
||||||
let limited = resolved.min(*remaining);
|
let limited = resolved.min(*remaining);
|
||||||
*remaining -= limited;
|
*remaining -= limited;
|
||||||
@ -219,7 +223,7 @@ impl StackLayouter {
|
|||||||
for item in self.items.drain(..) {
|
for item in self.items.drain(..) {
|
||||||
match item {
|
match item {
|
||||||
StackItem::Absolute(v) => cursor += v,
|
StackItem::Absolute(v) => cursor += v,
|
||||||
StackItem::Fractional(v) => cursor += v.resolve(self.fr, remaining),
|
StackItem::Fractional(v) => cursor += v.share(self.fr, remaining),
|
||||||
StackItem::Frame(frame, align) => {
|
StackItem::Frame(frame, align) => {
|
||||||
if self.dir.is_positive() {
|
if self.dir.is_positive() {
|
||||||
ruler = ruler.max(align);
|
ruler = ruler.max(align);
|
||||||
|
@ -186,15 +186,3 @@ castable! {
|
|||||||
Expected: "content",
|
Expected: "content",
|
||||||
Value::Content(content) => content.pack(),
|
Value::Content(content) => content.pack(),
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
|
||||||
Spec<Relative<Length>>,
|
|
||||||
Expected: "array of two relative lengths",
|
|
||||||
Value::Array(array) => {
|
|
||||||
let mut iter = array.into_iter();
|
|
||||||
match (iter.next(), iter.next(), iter.next()) {
|
|
||||||
(Some(a), Some(b), None) => Spec::new(a.cast()?, b.cast()?),
|
|
||||||
_ => Err("point array must contain exactly two entries")?,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
@ -10,8 +10,8 @@ pub use typst_macros::node;
|
|||||||
pub use crate::diag::{with_alternative, At, Error, StrResult, TypError, TypResult};
|
pub use crate::diag::{with_alternative, At, Error, StrResult, TypError, TypResult};
|
||||||
pub use crate::eval::{
|
pub use crate::eval::{
|
||||||
Arg, Args, Array, Cast, Content, Dict, Fold, Func, Key, Layout, LayoutNode, Merge,
|
Arg, Args, Array, Cast, Content, Dict, Fold, Func, Key, Layout, LayoutNode, Merge,
|
||||||
Node, RawAlign, Regions, Resolve, Scope, Show, ShowNode, Smart, StyleChain, StyleMap,
|
Node, RawAlign, RawLength, Regions, Resolve, Scope, Show, ShowNode, Smart,
|
||||||
StyleVec, Value,
|
StyleChain, StyleMap, StyleVec, Value,
|
||||||
};
|
};
|
||||||
pub use crate::frame::*;
|
pub use crate::frame::*;
|
||||||
pub use crate::geom::*;
|
pub use crate::geom::*;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::library::text::{FontFamily, FontSize, TextNode, Toggle};
|
use crate::library::text::{FontFamily, TextNode, TextSize, Toggle};
|
||||||
|
|
||||||
/// A section heading.
|
/// A section heading.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -21,9 +21,9 @@ impl HeadingNode {
|
|||||||
pub const FILL: Leveled<Smart<Paint>> = Leveled::Value(Smart::Auto);
|
pub const FILL: Leveled<Smart<Paint>> = Leveled::Value(Smart::Auto);
|
||||||
/// The size of text in the heading.
|
/// The size of text in the heading.
|
||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
pub const SIZE: Leveled<FontSize> = Leveled::Mapping(|level| {
|
pub const SIZE: Leveled<TextSize> = Leveled::Mapping(|level| {
|
||||||
let upscale = (1.6 - 0.1 * level as f64).max(0.75);
|
let upscale = (1.6 - 0.1 * level as f64).max(0.75);
|
||||||
FontSize(Ratio::new(upscale).into())
|
TextSize(Em::new(upscale).into())
|
||||||
});
|
});
|
||||||
/// Whether text in the heading is strengthend.
|
/// Whether text in the heading is strengthend.
|
||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
@ -36,10 +36,10 @@ impl HeadingNode {
|
|||||||
pub const UNDERLINE: Leveled<bool> = Leveled::Value(false);
|
pub const UNDERLINE: Leveled<bool> = Leveled::Value(false);
|
||||||
/// The extra padding above the heading.
|
/// The extra padding above the heading.
|
||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
pub const ABOVE: Leveled<Length> = Leveled::Value(Length::zero());
|
pub const ABOVE: Leveled<RawLength> = Leveled::Value(Length::zero().into());
|
||||||
/// The extra padding below the heading.
|
/// The extra padding below the heading.
|
||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
pub const BELOW: Leveled<Length> = Leveled::Value(Length::zero());
|
pub const BELOW: Leveled<RawLength> = Leveled::Value(Length::zero().into());
|
||||||
/// Whether the heading is block-level.
|
/// Whether the heading is block-level.
|
||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
pub const BLOCK: Leveled<bool> = Leveled::Value(true);
|
pub const BLOCK: Leveled<bool> = Leveled::Value(true);
|
||||||
@ -95,14 +95,14 @@ impl Show for HeadingNode {
|
|||||||
|
|
||||||
let above = resolve!(Self::ABOVE);
|
let above = resolve!(Self::ABOVE);
|
||||||
if !above.is_zero() {
|
if !above.is_zero() {
|
||||||
seq.push(Content::Vertical(above.into()));
|
seq.push(Content::Vertical(above.resolve(styles).into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
seq.push(body);
|
seq.push(body);
|
||||||
|
|
||||||
let below = resolve!(Self::BELOW);
|
let below = resolve!(Self::BELOW);
|
||||||
if !below.is_zero() {
|
if !below.is_zero() {
|
||||||
seq.push(Content::Vertical(below.into()));
|
seq.push(Content::Vertical(below.resolve(styles).into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut content = Content::sequence(seq).styled_with_map(map);
|
let mut content = Content::sequence(seq).styled_with_map(map);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::library::layout::{GridNode, TrackSizing};
|
use crate::library::layout::{GridNode, TrackSizing};
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::library::text::{ParNode, TextNode};
|
use crate::library::text::ParNode;
|
||||||
use crate::library::utility::Numbering;
|
use crate::library::utility::Numbering;
|
||||||
use crate::parse::Scanner;
|
use crate::parse::Scanner;
|
||||||
|
|
||||||
@ -34,15 +34,20 @@ impl<const L: ListKind> ListNode<L> {
|
|||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
pub const LABEL: Label = Label::Default;
|
pub const LABEL: Label = Label::Default;
|
||||||
/// The spacing between the list items of a non-wide list.
|
/// The spacing between the list items of a non-wide list.
|
||||||
pub const SPACING: Relative<Length> = Relative::zero();
|
#[property(resolve)]
|
||||||
|
pub const SPACING: RawLength = RawLength::zero();
|
||||||
/// The indentation of each item's label.
|
/// The indentation of each item's label.
|
||||||
pub const INDENT: Relative<Length> = Ratio::new(0.0).into();
|
#[property(resolve)]
|
||||||
|
pub const INDENT: RawLength = RawLength::zero();
|
||||||
/// The space between the label and the body of each item.
|
/// The space between the label and the body of each item.
|
||||||
pub const BODY_INDENT: Relative<Length> = Ratio::new(0.5).into();
|
#[property(resolve)]
|
||||||
|
pub const BODY_INDENT: RawLength = Em::new(0.5).into();
|
||||||
/// The extra padding above the list.
|
/// The extra padding above the list.
|
||||||
pub const ABOVE: Length = Length::zero();
|
#[property(resolve)]
|
||||||
|
pub const ABOVE: RawLength = RawLength::zero();
|
||||||
/// The extra padding below the list.
|
/// The extra padding below the list.
|
||||||
pub const BELOW: Length = Length::zero();
|
#[property(resolve)]
|
||||||
|
pub const BELOW: RawLength = RawLength::zero();
|
||||||
|
|
||||||
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
||||||
Ok(Content::show(Self {
|
Ok(Content::show(Self {
|
||||||
@ -77,7 +82,6 @@ impl<const L: ListKind> Show for ListNode<L> {
|
|||||||
number += 1;
|
number += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let em = styles.get(TextNode::SIZE);
|
|
||||||
let leading = styles.get(ParNode::LEADING);
|
let leading = styles.get(ParNode::LEADING);
|
||||||
let spacing = if self.wide {
|
let spacing = if self.wide {
|
||||||
styles.get(ParNode::SPACING)
|
styles.get(ParNode::SPACING)
|
||||||
@ -85,9 +89,9 @@ impl<const L: ListKind> Show for ListNode<L> {
|
|||||||
styles.get(Self::SPACING)
|
styles.get(Self::SPACING)
|
||||||
};
|
};
|
||||||
|
|
||||||
let gutter = (leading + spacing).resolve(em);
|
let gutter = leading + spacing;
|
||||||
let indent = styles.get(Self::INDENT).resolve(em);
|
let indent = styles.get(Self::INDENT);
|
||||||
let body_indent = styles.get(Self::BODY_INDENT).resolve(em);
|
let body_indent = styles.get(Self::BODY_INDENT);
|
||||||
|
|
||||||
Content::block(GridNode {
|
Content::block(GridNode {
|
||||||
tracks: Spec::with_x(vec![
|
tracks: Spec::with_x(vec![
|
||||||
|
@ -21,9 +21,10 @@ impl TableNode {
|
|||||||
/// How to stroke the cells.
|
/// How to stroke the cells.
|
||||||
pub const STROKE: Option<Paint> = Some(Color::BLACK.into());
|
pub const STROKE: Option<Paint> = Some(Color::BLACK.into());
|
||||||
/// The stroke's thickness.
|
/// The stroke's thickness.
|
||||||
pub const THICKNESS: Length = Length::pt(1.0);
|
#[property(resolve)]
|
||||||
|
pub const THICKNESS: RawLength = Length::pt(1.0).into();
|
||||||
/// How much to pad the cells's content.
|
/// How much to pad the cells's content.
|
||||||
pub const PADDING: Relative<Length> = Length::pt(5.0).into();
|
pub const PADDING: Relative<RawLength> = Length::pt(5.0).into();
|
||||||
|
|
||||||
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
||||||
let columns = args.named("columns")?.unwrap_or_default();
|
let columns = args.named("columns")?.unwrap_or_default();
|
||||||
|
@ -23,16 +23,16 @@ impl<const L: DecoLine> DecoNode<L> {
|
|||||||
/// Stroke color of the line, defaults to the text color if `None`.
|
/// Stroke color of the line, defaults to the text color if `None`.
|
||||||
#[property(shorthand)]
|
#[property(shorthand)]
|
||||||
pub const STROKE: Option<Paint> = None;
|
pub const STROKE: Option<Paint> = None;
|
||||||
/// Thickness of the line's strokes (dependent on scaled font size), read
|
/// Thickness of the line's strokes, read from the font tables if `auto`.
|
||||||
/// from the font tables if `None`.
|
#[property(shorthand, resolve)]
|
||||||
#[property(shorthand)]
|
pub const THICKNESS: Smart<RawLength> = Smart::Auto;
|
||||||
pub const THICKNESS: Option<Relative<Length>> = None;
|
/// Position of the line relative to the baseline, read from the font tables
|
||||||
/// Position of the line relative to the baseline (dependent on scaled font
|
/// if `auto`.
|
||||||
/// size), read from the font tables if `None`.
|
#[property(resolve)]
|
||||||
pub const OFFSET: Option<Relative<Length>> = None;
|
pub const OFFSET: Smart<RawLength> = Smart::Auto;
|
||||||
/// Amount that the line will be longer or shorter than its associated text
|
/// Amount that the line will be longer or shorter than its associated text.
|
||||||
/// (dependent on scaled font size).
|
#[property(resolve)]
|
||||||
pub const EXTENT: Relative<Length> = Relative::zero();
|
pub const EXTENT: RawLength = RawLength::zero();
|
||||||
/// Whether the line skips sections in which it would collide
|
/// Whether the line skips sections in which it would collide
|
||||||
/// with the glyphs. Does not apply to strikethrough.
|
/// with the glyphs. Does not apply to strikethrough.
|
||||||
pub const EVADE: bool = true;
|
pub const EVADE: bool = true;
|
||||||
@ -66,9 +66,9 @@ impl<const L: DecoLine> Show for DecoNode<L> {
|
|||||||
pub struct Decoration {
|
pub struct Decoration {
|
||||||
pub line: DecoLine,
|
pub line: DecoLine,
|
||||||
pub stroke: Option<Paint>,
|
pub stroke: Option<Paint>,
|
||||||
pub thickness: Option<Relative<Length>>,
|
pub thickness: Smart<Length>,
|
||||||
pub offset: Option<Relative<Length>>,
|
pub offset: Smart<Length>,
|
||||||
pub extent: Relative<Length>,
|
pub extent: Length,
|
||||||
pub evade: bool,
|
pub evade: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,25 +102,18 @@ pub fn decorate(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let evade = deco.evade && deco.line != STRIKETHROUGH;
|
let evade = deco.evade && deco.line != STRIKETHROUGH;
|
||||||
let extent = deco.extent.resolve(text.size);
|
let offset = deco.offset.unwrap_or(-metrics.position.at(text.size));
|
||||||
let offset = deco
|
|
||||||
.offset
|
|
||||||
.map(|s| s.resolve(text.size))
|
|
||||||
.unwrap_or(-metrics.position.resolve(text.size));
|
|
||||||
|
|
||||||
let stroke = Stroke {
|
let stroke = Stroke {
|
||||||
paint: deco.stroke.unwrap_or(text.fill),
|
paint: deco.stroke.unwrap_or(text.fill),
|
||||||
thickness: deco
|
thickness: deco.thickness.unwrap_or(metrics.thickness.at(text.size)),
|
||||||
.thickness
|
|
||||||
.map(|s| s.resolve(text.size))
|
|
||||||
.unwrap_or(metrics.thickness.resolve(text.size)),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let gap_padding = 0.08 * text.size;
|
let gap_padding = 0.08 * text.size;
|
||||||
let min_width = 0.162 * text.size;
|
let min_width = 0.162 * text.size;
|
||||||
|
|
||||||
let mut start = pos.x - extent;
|
let mut start = pos.x - deco.extent;
|
||||||
let end = pos.x + (width + 2.0 * extent);
|
let end = pos.x + (width + 2.0 * deco.extent);
|
||||||
|
|
||||||
let mut push_segment = |from: Length, to: Length| {
|
let mut push_segment = |from: Length, to: Length| {
|
||||||
let origin = Point::new(from, pos.y + offset);
|
let origin = Point::new(from, pos.y + offset);
|
||||||
@ -146,20 +139,20 @@ pub fn decorate(
|
|||||||
let mut intersections = vec![];
|
let mut intersections = vec![];
|
||||||
|
|
||||||
for glyph in text.glyphs.iter() {
|
for glyph in text.glyphs.iter() {
|
||||||
let dx = glyph.x_offset.resolve(text.size) + x;
|
let dx = glyph.x_offset.at(text.size) + x;
|
||||||
let mut builder =
|
let mut builder =
|
||||||
BezPathBuilder::new(face_metrics.units_per_em, text.size, dx.to_raw());
|
BezPathBuilder::new(face_metrics.units_per_em, text.size, dx.to_raw());
|
||||||
|
|
||||||
let bbox = face.ttf().outline_glyph(GlyphId(glyph.id), &mut builder);
|
let bbox = face.ttf().outline_glyph(GlyphId(glyph.id), &mut builder);
|
||||||
let path = builder.finish();
|
let path = builder.finish();
|
||||||
|
|
||||||
x += glyph.x_advance.resolve(text.size);
|
x += glyph.x_advance.at(text.size);
|
||||||
|
|
||||||
// Only do the costly segments intersection test if the line
|
// Only do the costly segments intersection test if the line
|
||||||
// intersects the bounding box.
|
// intersects the bounding box.
|
||||||
if bbox.map_or(false, |bbox| {
|
if bbox.map_or(false, |bbox| {
|
||||||
let y_min = -face.to_em(bbox.y_max).resolve(text.size);
|
let y_min = -face.to_em(bbox.y_max).at(text.size);
|
||||||
let y_max = -face.to_em(bbox.y_min).resolve(text.size);
|
let y_max = -face.to_em(bbox.y_min).at(text.size);
|
||||||
|
|
||||||
offset >= y_min && offset <= y_max
|
offset >= y_min && offset <= y_max
|
||||||
}) {
|
}) {
|
||||||
@ -225,7 +218,7 @@ impl BezPathBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn s(&self, v: f32) -> f64 {
|
fn s(&self, v: f32) -> f64 {
|
||||||
Em::from_units(v, self.units_per_em).resolve(self.font_size).to_raw()
|
Em::from_units(v, self.units_per_em).at(self.font_size).to_raw()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,9 @@ use std::borrow::Cow;
|
|||||||
|
|
||||||
use ttf_parser::Tag;
|
use ttf_parser::Tag;
|
||||||
|
|
||||||
use crate::font::{Face, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
|
use crate::font::{
|
||||||
|
Face, FaceMetrics, FontStretch, FontStyle, FontWeight, VerticalFontMetric,
|
||||||
|
};
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
@ -39,23 +41,25 @@ impl TextNode {
|
|||||||
pub const WEIGHT: FontWeight = FontWeight::REGULAR;
|
pub const WEIGHT: FontWeight = FontWeight::REGULAR;
|
||||||
/// The width of the glyphs.
|
/// The width of the glyphs.
|
||||||
pub const STRETCH: FontStretch = FontStretch::NORMAL;
|
pub const STRETCH: FontStretch = FontStretch::NORMAL;
|
||||||
|
|
||||||
/// The size of the glyphs.
|
/// The size of the glyphs.
|
||||||
#[property(shorthand, fold)]
|
#[property(shorthand, fold)]
|
||||||
pub const SIZE: FontSize = Length::pt(11.0);
|
pub const SIZE: TextSize = Length::pt(11.0);
|
||||||
/// The glyph fill color.
|
/// The glyph fill color.
|
||||||
#[property(shorthand)]
|
#[property(shorthand)]
|
||||||
pub const FILL: Paint = Color::BLACK.into();
|
pub const FILL: Paint = Color::BLACK.into();
|
||||||
|
|
||||||
/// The amount of space that should be added between characters.
|
/// The amount of space that should be added between characters.
|
||||||
pub const TRACKING: Em = Em::zero();
|
#[property(resolve)]
|
||||||
/// The ratio by which spaces should be stretched.
|
pub const TRACKING: RawLength = RawLength::zero();
|
||||||
pub const SPACING: Ratio = Ratio::one();
|
/// The width of spaces relative to the default space width.
|
||||||
|
#[property(resolve)]
|
||||||
|
pub const SPACING: Relative<RawLength> = Relative::one();
|
||||||
/// Whether glyphs can hang over into the margin.
|
/// Whether glyphs can hang over into the margin.
|
||||||
pub const OVERHANG: bool = true;
|
pub const OVERHANG: bool = true;
|
||||||
/// The top end of the text bounding box.
|
/// The top end of the text bounding box.
|
||||||
pub const TOP_EDGE: VerticalFontMetric = VerticalFontMetric::CapHeight;
|
pub const TOP_EDGE: TextEdge = TextEdge::Metric(VerticalFontMetric::CapHeight);
|
||||||
/// The bottom end of the text bounding box.
|
/// The bottom end of the text bounding box.
|
||||||
pub const BOTTOM_EDGE: VerticalFontMetric = VerticalFontMetric::Baseline;
|
pub const BOTTOM_EDGE: TextEdge = TextEdge::Metric(VerticalFontMetric::Baseline);
|
||||||
|
|
||||||
/// Whether to apply kerning ("kern").
|
/// Whether to apply kerning ("kern").
|
||||||
pub const KERNING: bool = true;
|
pub const KERNING: bool = true;
|
||||||
@ -188,44 +192,53 @@ castable! {
|
|||||||
|
|
||||||
/// The size of text.
|
/// The size of text.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct FontSize(pub Relative<Length>);
|
pub struct TextSize(pub RawLength);
|
||||||
|
|
||||||
impl Fold for FontSize {
|
impl Fold for TextSize {
|
||||||
type Output = Length;
|
type Output = Length;
|
||||||
|
|
||||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||||
self.0.rel.resolve(outer) + self.0.abs
|
self.0.em.at(outer) + self.0.length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
FontSize,
|
TextSize,
|
||||||
Expected: "relative length",
|
Expected: "length",
|
||||||
Value::Length(v) => Self(v.into()),
|
Value::Length(v) => Self(v),
|
||||||
Value::Ratio(v) => Self(v.into()),
|
}
|
||||||
Value::Relative(v) => Self(v),
|
|
||||||
|
/// Specifies the bottom or top edge of text.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum TextEdge {
|
||||||
|
/// An edge specified using one of the well-known font metrics.
|
||||||
|
Metric(VerticalFontMetric),
|
||||||
|
/// An edge specified as a length.
|
||||||
|
Length(RawLength),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextEdge {
|
||||||
|
/// Resolve the value of the text edge given a font face.
|
||||||
|
pub fn resolve(self, styles: StyleChain, metrics: &FaceMetrics) -> Length {
|
||||||
|
match self {
|
||||||
|
Self::Metric(metric) => metrics.vertical(metric).resolve(styles),
|
||||||
|
Self::Length(length) => length.resolve(styles),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Em,
|
TextEdge,
|
||||||
Expected: "float",
|
Expected: "string or length",
|
||||||
Value::Float(v) => Self::new(v),
|
Value::Length(v) => Self::Length(v),
|
||||||
}
|
Value::Str(string) => Self::Metric(match string.as_str() {
|
||||||
|
"ascender" => VerticalFontMetric::Ascender,
|
||||||
castable! {
|
"cap-height" => VerticalFontMetric::CapHeight,
|
||||||
VerticalFontMetric,
|
"x-height" => VerticalFontMetric::XHeight,
|
||||||
Expected: "string or relative length",
|
"baseline" => VerticalFontMetric::Baseline,
|
||||||
Value::Length(v) => Self::Relative(v.into()),
|
"descender" => VerticalFontMetric::Descender,
|
||||||
Value::Ratio(v) => Self::Relative(v.into()),
|
|
||||||
Value::Relative(v) => Self::Relative(v),
|
|
||||||
Value::Str(string) => match string.as_str() {
|
|
||||||
"ascender" => Self::Ascender,
|
|
||||||
"cap-height" => Self::CapHeight,
|
|
||||||
"x-height" => Self::XHeight,
|
|
||||||
"baseline" => Self::Baseline,
|
|
||||||
"descender" => Self::Descender,
|
|
||||||
_ => Err("unknown font metric")?,
|
_ => Err("unknown font metric")?,
|
||||||
},
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A stylistic set in a font face.
|
/// A stylistic set in a font face.
|
||||||
|
@ -42,12 +42,15 @@ impl ParNode {
|
|||||||
/// Whether to hyphenate text to improve line breaking. When `auto`, words
|
/// Whether to hyphenate text to improve line breaking. When `auto`, words
|
||||||
/// will will be hyphenated if and only if justification is enabled.
|
/// will will be hyphenated if and only if justification is enabled.
|
||||||
pub const HYPHENATE: Smart<bool> = Smart::Auto;
|
pub const HYPHENATE: Smart<bool> = Smart::Auto;
|
||||||
/// The spacing between lines (dependent on scaled font size).
|
/// The spacing between lines.
|
||||||
pub const LEADING: Relative<Length> = Ratio::new(0.65).into();
|
#[property(resolve)]
|
||||||
/// The extra spacing between paragraphs (dependent on scaled font size).
|
pub const LEADING: RawLength = Em::new(0.65).into();
|
||||||
pub const SPACING: Relative<Length> = Ratio::new(0.55).into();
|
/// The extra spacing between paragraphs.
|
||||||
|
#[property(resolve)]
|
||||||
|
pub const SPACING: RawLength = Em::new(0.55).into();
|
||||||
/// The indent the first line of a consecutive paragraph should have.
|
/// The indent the first line of a consecutive paragraph should have.
|
||||||
pub const INDENT: Relative<Length> = Relative::zero();
|
#[property(resolve)]
|
||||||
|
pub const INDENT: RawLength = RawLength::zero();
|
||||||
|
|
||||||
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
||||||
// The paragraph constructor is special: It doesn't create a paragraph
|
// The paragraph constructor is special: It doesn't create a paragraph
|
||||||
@ -370,7 +373,7 @@ fn prepare<'a>(
|
|||||||
}
|
}
|
||||||
ParChild::Spacing(spacing) => match *spacing {
|
ParChild::Spacing(spacing) => match *spacing {
|
||||||
Spacing::Relative(v) => {
|
Spacing::Relative(v) => {
|
||||||
let resolved = v.resolve(regions.base.x);
|
let resolved = v.resolve(styles).relative_to(regions.base.x);
|
||||||
items.push(ParItem::Absolute(resolved));
|
items.push(ParItem::Absolute(resolved));
|
||||||
ranges.push(range);
|
ranges.push(range);
|
||||||
}
|
}
|
||||||
@ -772,8 +775,7 @@ fn stack(
|
|||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> Vec<Arc<Frame>> {
|
) -> Vec<Arc<Frame>> {
|
||||||
let em = styles.get(TextNode::SIZE);
|
let leading = styles.get(ParNode::LEADING);
|
||||||
let leading = styles.get(ParNode::LEADING).resolve(em);
|
|
||||||
let align = styles.get(ParNode::ALIGN);
|
let align = styles.get(ParNode::ALIGN);
|
||||||
let justify = styles.get(ParNode::JUSTIFY);
|
let justify = styles.get(ParNode::JUSTIFY);
|
||||||
|
|
||||||
@ -837,7 +839,7 @@ fn commit(
|
|||||||
if text.styles.get(TextNode::OVERHANG) {
|
if text.styles.get(TextNode::OVERHANG) {
|
||||||
let start = text.dir.is_positive();
|
let start = text.dir.is_positive();
|
||||||
let em = text.styles.get(TextNode::SIZE);
|
let em = text.styles.get(TextNode::SIZE);
|
||||||
let amount = overhang(glyph.c, start) * glyph.x_advance.resolve(em);
|
let amount = overhang(glyph.c, start) * glyph.x_advance.at(em);
|
||||||
offset -= amount;
|
offset -= amount;
|
||||||
remaining += amount;
|
remaining += amount;
|
||||||
}
|
}
|
||||||
@ -852,7 +854,7 @@ fn commit(
|
|||||||
{
|
{
|
||||||
let start = !text.dir.is_positive();
|
let start = !text.dir.is_positive();
|
||||||
let em = text.styles.get(TextNode::SIZE);
|
let em = text.styles.get(TextNode::SIZE);
|
||||||
let amount = overhang(glyph.c, start) * glyph.x_advance.resolve(em);
|
let amount = overhang(glyph.c, start) * glyph.x_advance.at(em);
|
||||||
remaining += amount;
|
remaining += amount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -887,7 +889,7 @@ fn commit(
|
|||||||
|
|
||||||
match item {
|
match item {
|
||||||
ParItem::Absolute(v) => offset += *v,
|
ParItem::Absolute(v) => offset += *v,
|
||||||
ParItem::Fractional(v) => offset += v.resolve(line.fr, remaining),
|
ParItem::Fractional(v) => offset += v.share(line.fr, remaining),
|
||||||
ParItem::Text(shaped) => position(shaped.build(fonts, justification)),
|
ParItem::Text(shaped) => position(shaped.build(fonts, justification)),
|
||||||
ParItem::Frame(frame) => position(frame.clone()),
|
ParItem::Frame(frame) => position(frame.clone()),
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
.filter(|g| g.is_justifiable())
|
.filter(|g| g.is_justifiable())
|
||||||
.map(|g| g.x_advance)
|
.map(|g| g.x_advance)
|
||||||
.sum::<Em>()
|
.sum::<Em>()
|
||||||
.resolve(self.styles.get(TextNode::SIZE))
|
.at(self.styles.get(TextNode::SIZE))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reshape a range of the shaped text, reusing information from this
|
/// Reshape a range of the shaped text, reusing information from this
|
||||||
@ -168,7 +168,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
let glyph_id = ttf.glyph_index('-')?;
|
let glyph_id = ttf.glyph_index('-')?;
|
||||||
let x_advance = face.to_em(ttf.glyph_hor_advance(glyph_id)?);
|
let x_advance = face.to_em(ttf.glyph_hor_advance(glyph_id)?);
|
||||||
let cluster = self.glyphs.last().map(|g| g.cluster).unwrap_or_default();
|
let cluster = self.glyphs.last().map(|g| g.cluster).unwrap_or_default();
|
||||||
self.size.x += x_advance.resolve(size);
|
self.size.x += x_advance.at(size);
|
||||||
self.glyphs.to_mut().push(ShapedGlyph {
|
self.glyphs.to_mut().push(ShapedGlyph {
|
||||||
face_id,
|
face_id,
|
||||||
glyph_id: glyph_id.0,
|
glyph_id: glyph_id.0,
|
||||||
@ -443,8 +443,10 @@ fn shape_tofus(ctx: &mut ShapingContext, base: usize, text: &str, face_id: FaceI
|
|||||||
|
|
||||||
/// Apply tracking and spacing to a slice of shaped glyphs.
|
/// Apply tracking and spacing to a slice of shaped glyphs.
|
||||||
fn track_and_space(ctx: &mut ShapingContext) {
|
fn track_and_space(ctx: &mut ShapingContext) {
|
||||||
let tracking = ctx.styles.get(TextNode::TRACKING);
|
let em = ctx.styles.get(TextNode::SIZE);
|
||||||
let spacing = ctx.styles.get(TextNode::SPACING);
|
let tracking = Em::from_length(ctx.styles.get(TextNode::TRACKING), em);
|
||||||
|
let spacing = ctx.styles.get(TextNode::SPACING).map(|abs| Em::from_length(abs, em));
|
||||||
|
|
||||||
if tracking.is_zero() && spacing.is_one() {
|
if tracking.is_zero() && spacing.is_one() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -452,7 +454,7 @@ fn track_and_space(ctx: &mut ShapingContext) {
|
|||||||
let mut glyphs = ctx.glyphs.iter_mut().peekable();
|
let mut glyphs = ctx.glyphs.iter_mut().peekable();
|
||||||
while let Some(glyph) = glyphs.next() {
|
while let Some(glyph) = glyphs.next() {
|
||||||
if glyph.is_space() {
|
if glyph.is_space() {
|
||||||
glyph.x_advance *= spacing.get();
|
glyph.x_advance = spacing.relative_to(glyph.x_advance);
|
||||||
}
|
}
|
||||||
|
|
||||||
if glyphs.peek().map_or(false, |next| glyph.cluster != next.cluster) {
|
if glyphs.peek().map_or(false, |next| glyph.cluster != next.cluster) {
|
||||||
@ -479,8 +481,8 @@ fn measure(
|
|||||||
// Expand top and bottom by reading the face's vertical metrics.
|
// Expand top and bottom by reading the face's vertical metrics.
|
||||||
let mut expand = |face: &Face| {
|
let mut expand = |face: &Face| {
|
||||||
let metrics = face.metrics();
|
let metrics = face.metrics();
|
||||||
top.set_max(metrics.vertical(top_edge, size));
|
top.set_max(top_edge.resolve(styles, metrics));
|
||||||
bottom.set_max(-metrics.vertical(bottom_edge, size));
|
bottom.set_max(-bottom_edge.resolve(styles, metrics));
|
||||||
};
|
};
|
||||||
|
|
||||||
if glyphs.is_empty() {
|
if glyphs.is_empty() {
|
||||||
@ -499,7 +501,7 @@ fn measure(
|
|||||||
expand(face);
|
expand(face);
|
||||||
|
|
||||||
for glyph in group {
|
for glyph in group {
|
||||||
width += glyph.x_advance.resolve(size);
|
width += glyph.x_advance.at(size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,12 +37,11 @@ pub fn abs(_: &mut Context, args: &mut Args) -> TypResult<Value> {
|
|||||||
Ok(match v {
|
Ok(match v {
|
||||||
Value::Int(v) => Value::Int(v.abs()),
|
Value::Int(v) => Value::Int(v.abs()),
|
||||||
Value::Float(v) => Value::Float(v.abs()),
|
Value::Float(v) => Value::Float(v.abs()),
|
||||||
Value::Length(v) => Value::Length(v.abs()),
|
|
||||||
Value::Angle(v) => Value::Angle(v.abs()),
|
Value::Angle(v) => Value::Angle(v.abs()),
|
||||||
Value::Ratio(v) => Value::Ratio(v.abs()),
|
Value::Ratio(v) => Value::Ratio(v.abs()),
|
||||||
Value::Fraction(v) => Value::Fraction(v.abs()),
|
Value::Fraction(v) => Value::Fraction(v.abs()),
|
||||||
Value::Relative(_) => {
|
Value::Length(_) | Value::Relative(_) => {
|
||||||
bail!(span, "cannot take absolute value of a relative length")
|
bail!(span, "cannot take absolute value of a length")
|
||||||
}
|
}
|
||||||
v => bail!(span, "expected numeric value, found {}", v.type_name()),
|
v => bail!(span, "expected numeric value, found {}", v.type_name()),
|
||||||
})
|
})
|
||||||
|
@ -478,10 +478,7 @@ fn literal(p: &mut Parser) -> bool {
|
|||||||
| NodeKind::Int(_)
|
| NodeKind::Int(_)
|
||||||
| NodeKind::Float(_)
|
| NodeKind::Float(_)
|
||||||
| NodeKind::Bool(_)
|
| NodeKind::Bool(_)
|
||||||
| NodeKind::Fraction(_)
|
| NodeKind::Numeric(_, _)
|
||||||
| NodeKind::Length(_, _)
|
|
||||||
| NodeKind::Angle(_, _)
|
|
||||||
| NodeKind::Percentage(_)
|
|
||||||
| NodeKind::Str(_),
|
| NodeKind::Str(_),
|
||||||
) => {
|
) => {
|
||||||
p.eat();
|
p.eat();
|
||||||
|
@ -4,8 +4,8 @@ use super::{
|
|||||||
is_id_continue, is_id_start, is_newline, resolve_hex, resolve_raw, resolve_string,
|
is_id_continue, is_id_start, is_newline, resolve_hex, resolve_raw, resolve_string,
|
||||||
Scanner,
|
Scanner,
|
||||||
};
|
};
|
||||||
use crate::geom::{AngularUnit, LengthUnit};
|
use crate::geom::{AngleUnit, LengthUnit};
|
||||||
use crate::syntax::ast::{MathNode, RawNode};
|
use crate::syntax::ast::{MathNode, RawNode, Unit};
|
||||||
use crate::syntax::{ErrorPos, NodeKind};
|
use crate::syntax::{ErrorPos, NodeKind};
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
@ -462,7 +462,8 @@ impl<'s> Tokens<'s> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read the exponent.
|
// Read the exponent.
|
||||||
if self.s.eat_if('e') || self.s.eat_if('E') {
|
let em = self.s.rest().starts_with("em");
|
||||||
|
if !em && self.s.eat_if('e') || self.s.eat_if('E') {
|
||||||
let _ = self.s.eat_if('+') || self.s.eat_if('-');
|
let _ = self.s.eat_if('+') || self.s.eat_if('-');
|
||||||
self.s.eat_while(|c| c.is_ascii_digit());
|
self.s.eat_while(|c| c.is_ascii_digit());
|
||||||
}
|
}
|
||||||
@ -487,14 +488,15 @@ impl<'s> Tokens<'s> {
|
|||||||
if let Ok(f) = number.parse::<f64>() {
|
if let Ok(f) = number.parse::<f64>() {
|
||||||
match suffix {
|
match suffix {
|
||||||
"" => NodeKind::Float(f),
|
"" => NodeKind::Float(f),
|
||||||
"%" => NodeKind::Percentage(f),
|
"pt" => NodeKind::Numeric(f, Unit::Length(LengthUnit::Pt)),
|
||||||
"fr" => NodeKind::Fraction(f),
|
"mm" => NodeKind::Numeric(f, Unit::Length(LengthUnit::Mm)),
|
||||||
"pt" => NodeKind::Length(f, LengthUnit::Pt),
|
"cm" => NodeKind::Numeric(f, Unit::Length(LengthUnit::Cm)),
|
||||||
"mm" => NodeKind::Length(f, LengthUnit::Mm),
|
"in" => NodeKind::Numeric(f, Unit::Length(LengthUnit::In)),
|
||||||
"cm" => NodeKind::Length(f, LengthUnit::Cm),
|
"deg" => NodeKind::Numeric(f, Unit::Angle(AngleUnit::Deg)),
|
||||||
"in" => NodeKind::Length(f, LengthUnit::In),
|
"rad" => NodeKind::Numeric(f, Unit::Angle(AngleUnit::Rad)),
|
||||||
"deg" => NodeKind::Angle(f, AngularUnit::Deg),
|
"em" => NodeKind::Numeric(f, Unit::Em),
|
||||||
"rad" => NodeKind::Angle(f, AngularUnit::Rad),
|
"fr" => NodeKind::Numeric(f, Unit::Fr),
|
||||||
|
"%" => NodeKind::Numeric(f, Unit::Percent),
|
||||||
_ => NodeKind::Unknown(all.into()),
|
_ => NodeKind::Unknown(all.into()),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1017,19 +1019,20 @@ mod tests {
|
|||||||
// Combined integers and floats.
|
// Combined integers and floats.
|
||||||
let nums = ints.iter().map(|&(k, v)| (k, v as f64)).chain(floats);
|
let nums = ints.iter().map(|&(k, v)| (k, v as f64)).chain(floats);
|
||||||
|
|
||||||
let suffixes = [
|
let suffixes: &[(&str, fn(f64) -> NodeKind)] = &[
|
||||||
("%", Percentage as fn(f64) -> NodeKind),
|
("mm", |x| Numeric(x, Unit::Length(LengthUnit::Mm))),
|
||||||
("fr", Fraction as fn(f64) -> NodeKind),
|
("pt", |x| Numeric(x, Unit::Length(LengthUnit::Pt))),
|
||||||
("mm", |x| Length(x, LengthUnit::Mm)),
|
("cm", |x| Numeric(x, Unit::Length(LengthUnit::Cm))),
|
||||||
("pt", |x| Length(x, LengthUnit::Pt)),
|
("in", |x| Numeric(x, Unit::Length(LengthUnit::In))),
|
||||||
("cm", |x| Length(x, LengthUnit::Cm)),
|
("rad", |x| Numeric(x, Unit::Angle(AngleUnit::Rad))),
|
||||||
("in", |x| Length(x, LengthUnit::In)),
|
("deg", |x| Numeric(x, Unit::Angle(AngleUnit::Deg))),
|
||||||
("rad", |x| Angle(x, AngularUnit::Rad)),
|
("em", |x| Numeric(x, Unit::Em)),
|
||||||
("deg", |x| Angle(x, AngularUnit::Deg)),
|
("fr", |x| Numeric(x, Unit::Fr)),
|
||||||
|
("%", |x| Numeric(x, Unit::Percent)),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Numeric types.
|
// Numeric types.
|
||||||
for &(suffix, build) in &suffixes {
|
for &(suffix, build) in suffixes {
|
||||||
for (s, v) in nums.clone() {
|
for (s, v) in nums.clone() {
|
||||||
t!(Code[" /"]: format!("{}{}", s, suffix) => build(v));
|
t!(Code[" /"]: format!("{}{}", s, suffix) => build(v));
|
||||||
}
|
}
|
||||||
@ -1112,6 +1115,6 @@ mod tests {
|
|||||||
// Test invalid number suffixes.
|
// Test invalid number suffixes.
|
||||||
t!(Code[" /"]: "1foo" => Invalid("1foo"));
|
t!(Code[" /"]: "1foo" => Invalid("1foo"));
|
||||||
t!(Code: "1p%" => Invalid("1p"), Invalid("%"));
|
t!(Code: "1p%" => Invalid("1p"), Invalid("%"));
|
||||||
t!(Code: "1%%" => Percentage(1.0), Invalid("%"));
|
t!(Code: "1%%" => Numeric(1.0, Unit::Percent), Invalid("%"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use super::{Green, GreenData, NodeKind, RedNode, RedRef, Span};
|
use super::{Green, GreenData, NodeKind, RedNode, RedRef, Span};
|
||||||
use crate::geom::{AngularUnit, LengthUnit};
|
use crate::geom::{AngleUnit, LengthUnit};
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
/// A typed AST node.
|
/// A typed AST node.
|
||||||
@ -352,10 +352,7 @@ node! {
|
|||||||
| NodeKind::Bool(_)
|
| NodeKind::Bool(_)
|
||||||
| NodeKind::Int(_)
|
| NodeKind::Int(_)
|
||||||
| NodeKind::Float(_)
|
| NodeKind::Float(_)
|
||||||
| NodeKind::Length(_, _)
|
| NodeKind::Numeric(_, _)
|
||||||
| NodeKind::Angle(_, _)
|
|
||||||
| NodeKind::Percentage(_)
|
|
||||||
| NodeKind::Fraction(_)
|
|
||||||
| NodeKind::Str(_)
|
| NodeKind::Str(_)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -368,10 +365,7 @@ impl Lit {
|
|||||||
NodeKind::Bool(v) => LitKind::Bool(v),
|
NodeKind::Bool(v) => LitKind::Bool(v),
|
||||||
NodeKind::Int(v) => LitKind::Int(v),
|
NodeKind::Int(v) => LitKind::Int(v),
|
||||||
NodeKind::Float(v) => LitKind::Float(v),
|
NodeKind::Float(v) => LitKind::Float(v),
|
||||||
NodeKind::Length(v, unit) => LitKind::Length(v, unit),
|
NodeKind::Numeric(v, unit) => LitKind::Numeric(v, unit),
|
||||||
NodeKind::Angle(v, unit) => LitKind::Angle(v, unit),
|
|
||||||
NodeKind::Percentage(v) => LitKind::Percent(v),
|
|
||||||
NodeKind::Fraction(v) => LitKind::Fractional(v),
|
|
||||||
NodeKind::Str(ref v) => LitKind::Str(v.clone()),
|
NodeKind::Str(ref v) => LitKind::Str(v.clone()),
|
||||||
_ => panic!("literal is of wrong kind"),
|
_ => panic!("literal is of wrong kind"),
|
||||||
}
|
}
|
||||||
@ -391,21 +385,27 @@ pub enum LitKind {
|
|||||||
Int(i64),
|
Int(i64),
|
||||||
/// A floating-point literal: `1.2`, `10e-4`.
|
/// A floating-point literal: `1.2`, `10e-4`.
|
||||||
Float(f64),
|
Float(f64),
|
||||||
/// A length literal: `12pt`, `3cm`.
|
/// A numeric literal with a unit: `12pt`, `3cm`, `2em`, `90deg`, `50%`.
|
||||||
Length(f64, LengthUnit),
|
Numeric(f64, Unit),
|
||||||
/// An angle literal: `1.5rad`, `90deg`.
|
|
||||||
Angle(f64, AngularUnit),
|
|
||||||
/// A percent literal: `50%`.
|
|
||||||
///
|
|
||||||
/// _Note_: `50%` is stored as `50.0` here, but as `0.5` in the
|
|
||||||
/// corresponding [value](crate::geom::Relative).
|
|
||||||
Percent(f64),
|
|
||||||
/// A fraction unit literal: `1fr`.
|
|
||||||
Fractional(f64),
|
|
||||||
/// A string literal: `"hello!"`.
|
/// A string literal: `"hello!"`.
|
||||||
Str(EcoString),
|
Str(EcoString),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Unit of a numeric value.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum Unit {
|
||||||
|
/// An absolute length unit.
|
||||||
|
Length(LengthUnit),
|
||||||
|
/// An angular unit.
|
||||||
|
Angle(AngleUnit),
|
||||||
|
/// Font-relative: `1em` is the same as the font size.
|
||||||
|
Em,
|
||||||
|
/// Fractions: `fr`.
|
||||||
|
Fr,
|
||||||
|
/// Percentage: `%`.
|
||||||
|
Percent,
|
||||||
|
}
|
||||||
|
|
||||||
node! {
|
node! {
|
||||||
/// A code block: `{ let x = 1; x + 2 }`.
|
/// A code block: `{ let x = 1; x + 2 }`.
|
||||||
CodeBlock: CodeBlock
|
CodeBlock: CodeBlock
|
||||||
|
@ -187,10 +187,7 @@ impl Category {
|
|||||||
NodeKind::Bool(_) => Some(Category::Bool),
|
NodeKind::Bool(_) => Some(Category::Bool),
|
||||||
NodeKind::Int(_) => Some(Category::Number),
|
NodeKind::Int(_) => Some(Category::Number),
|
||||||
NodeKind::Float(_) => Some(Category::Number),
|
NodeKind::Float(_) => Some(Category::Number),
|
||||||
NodeKind::Length(_, _) => Some(Category::Number),
|
NodeKind::Numeric(_, _) => Some(Category::Number),
|
||||||
NodeKind::Angle(_, _) => Some(Category::Number),
|
|
||||||
NodeKind::Percentage(_) => Some(Category::Number),
|
|
||||||
NodeKind::Fraction(_) => Some(Category::Number),
|
|
||||||
NodeKind::Str(_) => Some(Category::String),
|
NodeKind::Str(_) => Some(Category::String),
|
||||||
NodeKind::Error(_, _) => Some(Category::Invalid),
|
NodeKind::Error(_, _) => Some(Category::Invalid),
|
||||||
NodeKind::Unknown(_) => Some(Category::Invalid),
|
NodeKind::Unknown(_) => Some(Category::Invalid),
|
||||||
|
@ -12,9 +12,8 @@ use std::sync::Arc;
|
|||||||
pub use highlight::*;
|
pub use highlight::*;
|
||||||
pub use span::*;
|
pub use span::*;
|
||||||
|
|
||||||
use self::ast::{MathNode, RawNode, TypedNode};
|
use self::ast::{MathNode, RawNode, TypedNode, Unit};
|
||||||
use crate::diag::Error;
|
use crate::diag::Error;
|
||||||
use crate::geom::{AngularUnit, LengthUnit};
|
|
||||||
use crate::parse::TokenMode;
|
use crate::parse::TokenMode;
|
||||||
use crate::source::SourceId;
|
use crate::source::SourceId;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
@ -629,17 +628,8 @@ pub enum NodeKind {
|
|||||||
Int(i64),
|
Int(i64),
|
||||||
/// A floating-point number: `1.2`, `10e-4`.
|
/// A floating-point number: `1.2`, `10e-4`.
|
||||||
Float(f64),
|
Float(f64),
|
||||||
/// A length: `12pt`, `3cm`.
|
/// A numeric value with a unit: `12pt`, `3cm`, `2em`, `90deg`, `50%`.
|
||||||
Length(f64, LengthUnit),
|
Numeric(f64, Unit),
|
||||||
/// An angle: `90deg`.
|
|
||||||
Angle(f64, AngularUnit),
|
|
||||||
/// A percentage: `50%`.
|
|
||||||
///
|
|
||||||
/// _Note_: `50%` is stored as `50.0` here, as in the corresponding
|
|
||||||
/// [literal](ast::LitKind::Percent).
|
|
||||||
Percentage(f64),
|
|
||||||
/// A fraction unit: `3fr`.
|
|
||||||
Fraction(f64),
|
|
||||||
/// A quoted string: `"..."`.
|
/// A quoted string: `"..."`.
|
||||||
Str(EcoString),
|
Str(EcoString),
|
||||||
/// A code block: `{ let x = 1; x + 2 }`.
|
/// A code block: `{ let x = 1; x + 2 }`.
|
||||||
@ -886,10 +876,7 @@ impl NodeKind {
|
|||||||
Self::Bool(_) => "boolean",
|
Self::Bool(_) => "boolean",
|
||||||
Self::Int(_) => "integer",
|
Self::Int(_) => "integer",
|
||||||
Self::Float(_) => "float",
|
Self::Float(_) => "float",
|
||||||
Self::Length(_, _) => "length",
|
Self::Numeric(_, _) => "numeric value",
|
||||||
Self::Angle(_, _) => "angle",
|
|
||||||
Self::Percentage(_) => "percentage",
|
|
||||||
Self::Fraction(_) => "`fr` value",
|
|
||||||
Self::Str(_) => "string",
|
Self::Str(_) => "string",
|
||||||
Self::CodeBlock => "code block",
|
Self::CodeBlock => "code block",
|
||||||
Self::ContentBlock => "content block",
|
Self::ContentBlock => "content block",
|
||||||
@ -1010,10 +997,7 @@ impl Hash for NodeKind {
|
|||||||
Self::Bool(v) => v.hash(state),
|
Self::Bool(v) => v.hash(state),
|
||||||
Self::Int(v) => v.hash(state),
|
Self::Int(v) => v.hash(state),
|
||||||
Self::Float(v) => v.to_bits().hash(state),
|
Self::Float(v) => v.to_bits().hash(state),
|
||||||
Self::Length(v, u) => (v.to_bits(), u).hash(state),
|
Self::Numeric(v, u) => (v.to_bits(), u).hash(state),
|
||||||
Self::Angle(v, u) => (v.to_bits(), u).hash(state),
|
|
||||||
Self::Percentage(v) => v.to_bits().hash(state),
|
|
||||||
Self::Fraction(v) => v.to_bits().hash(state),
|
|
||||||
Self::Str(v) => v.hash(state),
|
Self::Str(v) => v.hash(state),
|
||||||
Self::CodeBlock => {}
|
Self::CodeBlock => {}
|
||||||
Self::ContentBlock => {}
|
Self::ContentBlock => {}
|
||||||
|
BIN
tests/ref/text/edge.png
Normal file
After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 17 KiB |
BIN
tests/ref/text/tracking-spacing.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 3.8 KiB |
@ -63,8 +63,8 @@
|
|||||||
test(v + v, 2.0 * v)
|
test(v + v, 2.0 * v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Relative lengths cannot be divided by themselves.
|
// Lengths cannot be divided by themselves.
|
||||||
if type(v) != "relative length" {
|
if "length" not in type(v) {
|
||||||
test(v / v, 1.0)
|
test(v / v, 1.0)
|
||||||
test(v / v == 1, true)
|
test(v / v == 1, true)
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
header: align(horizon, {
|
header: align(horizon, {
|
||||||
text(eastern)[*Typst*]
|
text(eastern)[*Typst*]
|
||||||
h(1fr)
|
h(1fr)
|
||||||
text(80%)[_Chapter 1_]
|
text(0.8em)[_Chapter 1_]
|
||||||
}),
|
}),
|
||||||
footer: page => v(5pt) + align(center)[\~ #page \~],
|
footer: page => v(5pt) + align(center)[\~ #page \~],
|
||||||
)
|
)
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
---
|
---
|
||||||
// The inner rectangle should also be yellow here.
|
// The inner rectangle should also be yellow here.
|
||||||
// (and therefore invisible)
|
// (and therefore invisible)
|
||||||
[#set rect(fill: yellow);#text(100%, rect(padding: 5pt, rect()))]
|
[#set rect(fill: yellow);#text(1em, rect(padding: 5pt, rect()))]
|
||||||
|
|
||||||
---
|
---
|
||||||
// The inner rectangle should not be yellow here.
|
// The inner rectangle should not be yellow here.
|
||||||
@ -30,4 +30,4 @@ A #rect(fill: yellow, padding: 5pt, rect()) B
|
|||||||
|
|
||||||
---
|
---
|
||||||
// The inner list should not be indented extra.
|
// The inner list should not be indented extra.
|
||||||
[#set text(100%);#list(indent: 20pt, list[A])]
|
[#set text(1em);#list(indent: 20pt, list[A])]
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
#set page("a8", footer: p => v(-5pt) + align(right, [#p]))
|
#set page("a8", footer: p => v(-5pt) + align(right, [#p]))
|
||||||
|
|
||||||
#let i = 1
|
#let i = 1
|
||||||
#set heading(size: 100%)
|
#set heading(size: 1em)
|
||||||
#show heading(level, body) as {
|
#show heading(level, body) as {
|
||||||
if level == 1 {
|
if level == 1 {
|
||||||
v(10pt)
|
v(10pt)
|
||||||
underline(text(150%, blue)[{i}. #body])
|
underline(text(1.5em, blue)[{i}. #body])
|
||||||
i += 1
|
i += 1
|
||||||
} else {
|
} else {
|
||||||
text(red, body)
|
text(red, body)
|
||||||
@ -29,7 +29,7 @@ Some more text.
|
|||||||
Another text.
|
Another text.
|
||||||
|
|
||||||
---
|
---
|
||||||
#set heading(size: 100%, strong: false, block: false)
|
#set heading(size: 1em, strong: false, block: false)
|
||||||
#show heading(a, b) as [B]
|
#show heading(a, b) as [B]
|
||||||
A [= Heading] C
|
A [= Heading] C
|
||||||
|
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
#set page(height: 130pt)
|
#set page(height: 130pt)
|
||||||
#set text(70%)
|
#set text(0.7em)
|
||||||
|
|
||||||
#align(center)[
|
#align(center)[
|
||||||
#text(130%)[*Essay on typography*] \
|
#text(1.3em)[*Essay on typography*] \
|
||||||
T. Ypst
|
T. Ypst
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ A [_B #wrap c in [*#c*]; C_] D
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Test wrap style precedence.
|
// Test wrap style precedence.
|
||||||
#set text(fill: eastern, size: 150%)
|
#set text(fill: eastern, size: 1.5em)
|
||||||
#wrap body in text(fill: forest, body)
|
#wrap body in text(fill: forest, body)
|
||||||
Forest
|
Forest
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Test text baseline.
|
// Test text baseline.
|
||||||
|
|
||||||
---
|
---
|
||||||
Hi #text(150%)[You], #text(75%)[how are you?]
|
Hi #text(1.5em)[You], #text(0.75em)[how are you?]
|
||||||
|
@ -19,11 +19,11 @@
|
|||||||
#overline(underline[Running amongst the wolves.])
|
#overline(underline[Running amongst the wolves.])
|
||||||
|
|
||||||
---
|
---
|
||||||
#let redact = strike.with(10pt, extent: 5%)
|
#let redact = strike.with(10pt, extent: 0.05em)
|
||||||
#let highlight = strike.with(
|
#let highlight = strike.with(
|
||||||
stroke: rgb("abcdef88"),
|
stroke: rgb("abcdef88"),
|
||||||
thickness: 10pt,
|
thickness: 10pt,
|
||||||
extent: 5%,
|
extent: 0.05em,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Abuse thickness and transparency for redacting and highlighting stuff.
|
// Abuse thickness and transparency for redacting and highlighting stuff.
|
||||||
|
25
tests/typ/text/edge.typ
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Test top and bottom text edge.
|
||||||
|
|
||||||
|
---
|
||||||
|
#set page(width: 160pt)
|
||||||
|
#set text(size: 8pt)
|
||||||
|
|
||||||
|
#let try(top, bottom) = rect(fill: conifer)[
|
||||||
|
#set text("IBM Plex Mono", top-edge: top, bottom-edge: bottom)
|
||||||
|
From #top to #bottom
|
||||||
|
]
|
||||||
|
|
||||||
|
#try("ascender", "descender")
|
||||||
|
#try("ascender", "baseline")
|
||||||
|
#try("cap-height", "baseline")
|
||||||
|
#try("x-height", "baseline")
|
||||||
|
#try(4pt, -2pt)
|
||||||
|
#try(1pt + 0.3em, -0.15em)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 21-23 expected string or length, found array
|
||||||
|
#set text(top-edge: ())
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 24-26 unknown font metric
|
||||||
|
#set text(bottom-edge: "")
|
@ -4,14 +4,31 @@
|
|||||||
#set text(size: 5pt)
|
#set text(size: 5pt)
|
||||||
A // 5pt
|
A // 5pt
|
||||||
[
|
[
|
||||||
#set text(size: 200%)
|
#set text(size: 2em)
|
||||||
B // 10pt
|
B // 10pt
|
||||||
[
|
[
|
||||||
#set text(size: 150% + 1pt)
|
#set text(size: 1.5em + 1pt)
|
||||||
C // 16pt
|
C // 16pt
|
||||||
#text(size: 200%)[D] // 32pt
|
#text(size: 2em)[D] // 32pt
|
||||||
E // 16pt
|
E // 16pt
|
||||||
]
|
]
|
||||||
F // 10pt
|
F // 10pt
|
||||||
]
|
]
|
||||||
G // 5pt
|
G // 5pt
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test using ems in arbitrary places.
|
||||||
|
#set text(size: 5pt)
|
||||||
|
#set text(size: 2em)
|
||||||
|
#set square(fill: red)
|
||||||
|
|
||||||
|
#let size = {
|
||||||
|
let size = 0.25em + 1pt
|
||||||
|
for _ in range(3) {
|
||||||
|
size *= 2
|
||||||
|
}
|
||||||
|
size - 3pt
|
||||||
|
}
|
||||||
|
|
||||||
|
#square(size: size)
|
||||||
|
#square(size: 25pt)
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
---
|
---
|
||||||
// Set same font size in three different ways.
|
// Set same font size in three different ways.
|
||||||
#text(20pt)[A]
|
#text(20pt)[A]
|
||||||
#text(200%)[A]
|
#text(2em)[A]
|
||||||
#text(size: 15pt + 50%)[A]
|
#text(size: 15pt + 0.5em)[A]
|
||||||
|
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
#text()[Normal]
|
#text()[Normal]
|
||||||
@ -38,23 +38,6 @@ Emoji: 🐪, 🌋, 🏞
|
|||||||
#set text("PT Sans", "Twitter Color Emoji", fallback: false)
|
#set text("PT Sans", "Twitter Color Emoji", fallback: false)
|
||||||
2π = 𝛼 + 𝛽. ✅
|
2π = 𝛼 + 𝛽. ✅
|
||||||
|
|
||||||
---
|
|
||||||
// Test top and bottom edge.
|
|
||||||
#set page(width: 150pt)
|
|
||||||
#set text(size: 8pt)
|
|
||||||
|
|
||||||
#let try(top, bottom) = rect(fill: conifer)[
|
|
||||||
#set text("IBM Plex Mono", top-edge: top, bottom-edge: bottom)
|
|
||||||
From #top to #bottom
|
|
||||||
]
|
|
||||||
|
|
||||||
#try("ascender", "descender")
|
|
||||||
#try("ascender", "baseline")
|
|
||||||
#try("cap-height", "baseline")
|
|
||||||
#try("x-height", "baseline")
|
|
||||||
#try(4pt, -2pt)
|
|
||||||
#try(1pt + 27%, -18%)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 11-16 unexpected argument
|
// Error: 11-16 unexpected argument
|
||||||
#set text(false)
|
#set text(false)
|
||||||
@ -63,14 +46,6 @@ Emoji: 🐪, 🌋, 🏞
|
|||||||
// Error: 18-24 expected "normal", "italic" or "oblique"
|
// Error: 18-24 expected "normal", "italic" or "oblique"
|
||||||
#set text(style: "bold", weight: "thin")
|
#set text(style: "bold", weight: "thin")
|
||||||
|
|
||||||
---
|
|
||||||
// Error: 21-23 expected string or relative length, found array
|
|
||||||
#set text(top-edge: ())
|
|
||||||
|
|
||||||
---
|
|
||||||
// Error: 21-23 unknown font metric
|
|
||||||
#set text(top-edge: "")
|
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 23-27 unexpected argument
|
// Error: 23-27 unexpected argument
|
||||||
#set text(size: 10pt, 12pt)
|
#set text(size: 10pt, 12pt)
|
||||||
|
@ -31,7 +31,7 @@ A #set par(spacing: 0pt, leading: 0pt); B #parbreak() C
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Test weird metrics.
|
// Test weird metrics.
|
||||||
#set par(spacing: 100%, leading: 0pt)
|
#set par(spacing: 1em, leading: 0pt)
|
||||||
But, soft! what light through yonder window breaks?
|
But, soft! what light through yonder window breaks?
|
||||||
|
|
||||||
It is the east, and Juliet is the sun.
|
It is the east, and Juliet is the sun.
|
||||||
|
30
tests/typ/text/tracking-spacing.typ
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Test tracking characters apart or together.
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test tracking.
|
||||||
|
#set text(tracking: -0.01em)
|
||||||
|
I saw Zoe yӛsterday, on the tram.
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test tracking for only part of paragraph.
|
||||||
|
I'm in#text(tracking: 0.15em + 1.5pt)[ spaace]!
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test that tracking doesn't disrupt mark placement.
|
||||||
|
#set text(tracking: 0.3em)
|
||||||
|
טֶקסט
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test tracking in arabic text (makes no sense whatsoever)
|
||||||
|
#set text(tracking: 0.3em)
|
||||||
|
النص
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test word spacing.
|
||||||
|
#set text(spacing: 1em)
|
||||||
|
My text has spaces.
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test word spacing relative to the font's space width.
|
||||||
|
#set text(spacing: 50% + 1pt)
|
||||||
|
This is tight.
|
@ -1,12 +0,0 @@
|
|||||||
// Test tracking characters apart or together.
|
|
||||||
|
|
||||||
---
|
|
||||||
#set text(tracking: -0.01)
|
|
||||||
I saw Zoe yӛsterday, on the tram.
|
|
||||||
|
|
||||||
---
|
|
||||||
I'm in#text(tracking: 0.3)[ spaace]!
|
|
||||||
|
|
||||||
---
|
|
||||||
#set text("Noto Serif Hebrew", tracking: 0.3)
|
|
||||||
טֶקסט
|
|
@ -35,8 +35,20 @@
|
|||||||
#test(abs(-0.0), 0.0)
|
#test(abs(-0.0), 0.0)
|
||||||
#test(abs(0.0), -0.0)
|
#test(abs(0.0), -0.0)
|
||||||
#test(abs(-3.14), 3.14)
|
#test(abs(-3.14), 3.14)
|
||||||
#test(abs(-12pt), 12pt)
|
|
||||||
#test(abs(50%), 50%)
|
#test(abs(50%), 50%)
|
||||||
|
#test(abs(-25%), 25%)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 6-17 expected numeric value, found string
|
||||||
|
#abs("no number")
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 6-11 cannot take absolute value of a length
|
||||||
|
#abs(-12pt)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 6-16 cannot take absolute value of a length
|
||||||
|
#abs(50% - 12pt)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test the `even` and `odd` functions.
|
// Test the `even` and `odd` functions.
|
||||||
@ -61,14 +73,6 @@
|
|||||||
// Error: 11-14 divisor must not be zero
|
// Error: 11-14 divisor must not be zero
|
||||||
#mod(3.0, 0.0)
|
#mod(3.0, 0.0)
|
||||||
|
|
||||||
---
|
|
||||||
// Error: 6-16 cannot take absolute value of a relative length
|
|
||||||
#abs(10pt + 50%)
|
|
||||||
|
|
||||||
---
|
|
||||||
// Error: 6-17 expected numeric value, found string
|
|
||||||
#abs("no number")
|
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test the `min` and `max` functions.
|
// Test the `min` and `max` functions.
|
||||||
#test(min(2, -4), -4)
|
#test(min(2, -4), -4)
|
||||||
|
@ -13,7 +13,7 @@ use typst::eval::{Smart, StyleMap, Value};
|
|||||||
use typst::frame::{Element, Frame};
|
use typst::frame::{Element, Frame};
|
||||||
use typst::geom::{Length, RgbaColor};
|
use typst::geom::{Length, RgbaColor};
|
||||||
use typst::library::layout::PageNode;
|
use typst::library::layout::PageNode;
|
||||||
use typst::library::text::{FontSize, TextNode};
|
use typst::library::text::{TextNode, TextSize};
|
||||||
use typst::loading::FsLoader;
|
use typst::loading::FsLoader;
|
||||||
use typst::parse::Scanner;
|
use typst::parse::Scanner;
|
||||||
use typst::source::SourceFile;
|
use typst::source::SourceFile;
|
||||||
@ -61,13 +61,13 @@ fn main() {
|
|||||||
// exactly 100pt wide. Page height is unbounded and font size is 10pt so
|
// exactly 100pt wide. Page height is unbounded and font size is 10pt so
|
||||||
// that it multiplies to nice round numbers.
|
// that it multiplies to nice round numbers.
|
||||||
let mut styles = StyleMap::new();
|
let mut styles = StyleMap::new();
|
||||||
styles.set(PageNode::WIDTH, Smart::Custom(Length::pt(120.0)));
|
styles.set(PageNode::WIDTH, Smart::Custom(Length::pt(120.0).into()));
|
||||||
styles.set(PageNode::HEIGHT, Smart::Auto);
|
styles.set(PageNode::HEIGHT, Smart::Auto);
|
||||||
styles.set(PageNode::LEFT, Smart::Custom(Length::pt(10.0).into()));
|
styles.set(PageNode::LEFT, Smart::Custom(Length::pt(10.0).into()));
|
||||||
styles.set(PageNode::TOP, Smart::Custom(Length::pt(10.0).into()));
|
styles.set(PageNode::TOP, Smart::Custom(Length::pt(10.0).into()));
|
||||||
styles.set(PageNode::RIGHT, Smart::Custom(Length::pt(10.0).into()));
|
styles.set(PageNode::RIGHT, Smart::Custom(Length::pt(10.0).into()));
|
||||||
styles.set(PageNode::BOTTOM, Smart::Custom(Length::pt(10.0).into()));
|
styles.set(PageNode::BOTTOM, Smart::Custom(Length::pt(10.0).into()));
|
||||||
styles.set(TextNode::SIZE, FontSize(Length::pt(10.0).into()));
|
styles.set(TextNode::SIZE, TextSize(Length::pt(10.0).into()));
|
||||||
|
|
||||||
// Hook up an assert function into the global scope.
|
// Hook up an assert function into the global scope.
|
||||||
let mut std = typst::library::new();
|
let mut std = typst::library::new();
|
||||||
|