mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Merge pull request #71 from typst/pins
This commit is contained in:
commit
73086b5a7c
83
Cargo.lock
generated
83
Cargo.lock
generated
@ -433,16 +433,6 @@ dependencies = [
|
|||||||
"rand_chacha 0.3.1",
|
"rand_chacha 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lock_api"
|
|
||||||
version = "0.4.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"scopeguard",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.17"
|
version = "0.4.17"
|
||||||
@ -568,29 +558,6 @@ version = "1.10.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
|
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot"
|
|
||||||
version = "0.12.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58"
|
|
||||||
dependencies = [
|
|
||||||
"lock_api",
|
|
||||||
"parking_lot_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot_core"
|
|
||||||
version = "0.9.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"libc",
|
|
||||||
"redox_syscall",
|
|
||||||
"smallvec",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathfinder_color"
|
name = "pathfinder_color"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@ -943,12 +910,6 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "scopeguard"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@ -1177,7 +1138,6 @@ dependencies = [
|
|||||||
"memmap2",
|
"memmap2",
|
||||||
"miniz_oxide 0.4.4",
|
"miniz_oxide 0.4.4",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot",
|
|
||||||
"pdf-writer",
|
"pdf-writer",
|
||||||
"pico-args",
|
"pico-args",
|
||||||
"pixglyph",
|
"pixglyph",
|
||||||
@ -1420,49 +1380,6 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.36.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
|
|
||||||
dependencies = [
|
|
||||||
"windows_aarch64_msvc",
|
|
||||||
"windows_i686_gnu",
|
|
||||||
"windows_i686_msvc",
|
|
||||||
"windows_x86_64_gnu",
|
|
||||||
"windows_x86_64_msvc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_msvc"
|
|
||||||
version = "0.36.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnu"
|
|
||||||
version = "0.36.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_msvc"
|
|
||||||
version = "0.36.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnu"
|
|
||||||
version = "0.36.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_msvc"
|
|
||||||
version = "0.36.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xi-unicode"
|
name = "xi-unicode"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -21,7 +21,6 @@ lipsum = { git = "https://github.com/reknih/lipsum", default-features = false }
|
|||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
typed-arena = "2"
|
typed-arena = "2"
|
||||||
parking_lot = "0.12"
|
|
||||||
unscanny = { git = "https://github.com/typst/unscanny" }
|
unscanny = { git = "https://github.com/typst/unscanny" }
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
|
||||||
|
@ -39,6 +39,15 @@ impl Args {
|
|||||||
Self { span, items }
|
Self { span, items }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Push a positional argument.
|
||||||
|
pub fn push(&mut self, span: Span, value: Value) {
|
||||||
|
self.items.push(Arg {
|
||||||
|
span: self.span,
|
||||||
|
name: None,
|
||||||
|
value: Spanned::new(value, span),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Consume and cast the first positional argument if there is one.
|
/// Consume and cast the first positional argument if there is one.
|
||||||
pub fn eat<T>(&mut self) -> TypResult<Option<T>>
|
pub fn eat<T>(&mut self) -> TypResult<Option<T>>
|
||||||
where
|
where
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use super::{Scope, Scopes, Value};
|
use super::{Scope, Scopes, Value};
|
||||||
use crate::syntax::ast::{ClosureParam, Expr, Ident, Imports, TypedNode};
|
use crate::syntax::ast::{ClosureParam, Expr, Ident, Imports, TypedNode};
|
||||||
use crate::syntax::RedRef;
|
use crate::syntax::RedRef;
|
||||||
@ -28,14 +26,14 @@ impl<'a> CapturesVisitor<'a> {
|
|||||||
|
|
||||||
/// Bind a new internal variable.
|
/// Bind a new internal variable.
|
||||||
pub fn bind(&mut self, ident: Ident) {
|
pub fn bind(&mut self, ident: Ident) {
|
||||||
self.internal.top.def_mut(ident.take(), Value::None);
|
self.internal.top.define(ident.take(), Value::None);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Capture a variable if it isn't internal.
|
/// Capture a variable if it isn't internal.
|
||||||
pub fn capture(&mut self, ident: Ident) {
|
pub fn capture(&mut self, ident: Ident) {
|
||||||
if self.internal.get(&ident).is_none() {
|
if self.internal.get(&ident).is_err() {
|
||||||
if let Some(slot) = self.external.get(&ident) {
|
if let Ok(value) = self.external.get(&ident) {
|
||||||
self.captures.def_slot(ident.take(), Arc::clone(slot));
|
self.captures.define_captured(ident.take(), value.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -145,9 +143,9 @@ mod tests {
|
|||||||
let red = RedNode::from_root(green, SourceId::from_raw(0));
|
let red = RedNode::from_root(green, SourceId::from_raw(0));
|
||||||
|
|
||||||
let mut scopes = Scopes::new(None);
|
let mut scopes = Scopes::new(None);
|
||||||
scopes.top.def_const("x", 0);
|
scopes.top.define("x", 0);
|
||||||
scopes.top.def_const("y", 0);
|
scopes.top.define("y", 0);
|
||||||
scopes.top.def_const("z", 0);
|
scopes.top.define("z", 0);
|
||||||
|
|
||||||
let mut visitor = CapturesVisitor::new(&scopes);
|
let mut visitor = CapturesVisitor::new(&scopes);
|
||||||
visitor.visit(red.as_ref());
|
visitor.visit(red.as_ref());
|
||||||
|
@ -206,7 +206,7 @@ impl Closure {
|
|||||||
|
|
||||||
// Parse the arguments according to the parameter list.
|
// Parse the arguments according to the parameter list.
|
||||||
for (param, default) in &self.params {
|
for (param, default) in &self.params {
|
||||||
scopes.top.def_mut(param, match default {
|
scopes.top.define(param, match default {
|
||||||
None => args.expect::<Value>(param)?,
|
None => args.expect::<Value>(param)?,
|
||||||
Some(default) => {
|
Some(default) => {
|
||||||
args.named::<Value>(param)?.unwrap_or_else(|| default.clone())
|
args.named::<Value>(param)?.unwrap_or_else(|| default.clone())
|
||||||
@ -216,7 +216,7 @@ impl Closure {
|
|||||||
|
|
||||||
// Put the remaining arguments into the sink.
|
// Put the remaining arguments into the sink.
|
||||||
if let Some(sink) = &self.sink {
|
if let Some(sink) = &self.sink {
|
||||||
scopes.top.def_mut(sink, args.take());
|
scopes.top.define(sink, args.take());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the route inside the closure.
|
// Determine the route inside the closure.
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use super::{Args, Machine, Regex, StrExt, Value};
|
use super::{Args, Machine, Regex, StrExt, Value};
|
||||||
use crate::diag::{At, TypResult};
|
use crate::diag::{At, TypResult};
|
||||||
|
use crate::model::{Content, Group};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
@ -66,18 +67,32 @@ pub fn call(
|
|||||||
_ => missing()?,
|
_ => missing()?,
|
||||||
},
|
},
|
||||||
|
|
||||||
Value::Dyn(dynamic) => {
|
Value::Dyn(dynamic) => match method {
|
||||||
if let Some(regex) = dynamic.downcast::<Regex>() {
|
"matches" => {
|
||||||
match method {
|
if let Some(regex) = dynamic.downcast::<Regex>() {
|
||||||
"matches" => {
|
Value::Bool(regex.matches(&args.expect::<EcoString>("text")?))
|
||||||
Value::Bool(regex.matches(&args.expect::<EcoString>("text")?))
|
} else {
|
||||||
}
|
missing()?
|
||||||
_ => missing()?,
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
missing()?
|
|
||||||
}
|
}
|
||||||
}
|
"entry" => {
|
||||||
|
if let Some(group) = dynamic.downcast::<Group>() {
|
||||||
|
Value::Content(Content::Locate(
|
||||||
|
group.entry(args.expect("recipe")?, args.named("value")?),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
missing()?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"all" => {
|
||||||
|
if let Some(group) = dynamic.downcast::<Group>() {
|
||||||
|
Value::Content(Content::Locate(group.all(args.expect("recipe")?)))
|
||||||
|
} else {
|
||||||
|
missing()?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => missing()?,
|
||||||
|
},
|
||||||
|
|
||||||
_ => missing()?,
|
_ => missing()?,
|
||||||
};
|
};
|
||||||
|
102
src/eval/mod.rs
102
src/eval/mod.rs
@ -31,7 +31,6 @@ pub use value::*;
|
|||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
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};
|
||||||
@ -165,7 +164,7 @@ fn eval_markup(
|
|||||||
}
|
}
|
||||||
MarkupNode::Expr(Expr::Wrap(wrap)) => {
|
MarkupNode::Expr(Expr::Wrap(wrap)) => {
|
||||||
let tail = eval_markup(vm, nodes)?;
|
let tail = eval_markup(vm, nodes)?;
|
||||||
vm.scopes.top.def_mut(wrap.binding().take(), tail);
|
vm.scopes.top.define(wrap.binding().take(), tail);
|
||||||
wrap.body().eval(vm)?.display()
|
wrap.body().eval(vm)?.display()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,10 +353,7 @@ impl Eval for Ident {
|
|||||||
type Output = Value;
|
type Output = Value;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
fn eval(&self, vm: &mut Machine) -> TypResult<Self::Output> {
|
||||||
match vm.scopes.get(self) {
|
vm.scopes.get(self).cloned().at(self.span())
|
||||||
Some(slot) => Ok(slot.read().clone()),
|
|
||||||
None => bail!(self.span(), "unknown variable"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,7 +400,7 @@ fn eval_code(
|
|||||||
}
|
}
|
||||||
Expr::Wrap(wrap) => {
|
Expr::Wrap(wrap) => {
|
||||||
let tail = eval_code(vm, exprs)?;
|
let tail = eval_code(vm, exprs)?;
|
||||||
vm.scopes.top.def_mut(wrap.binding().take(), tail);
|
vm.scopes.top.define(wrap.binding().take(), tail);
|
||||||
wrap.body().eval(vm)?
|
wrap.body().eval(vm)?
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -565,8 +561,7 @@ impl BinaryExpr {
|
|||||||
op: fn(Value, Value) -> StrResult<Value>,
|
op: fn(Value, Value) -> StrResult<Value>,
|
||||||
) -> TypResult<Value> {
|
) -> TypResult<Value> {
|
||||||
let rhs = self.rhs().eval(vm)?;
|
let rhs = self.rhs().eval(vm)?;
|
||||||
let lhs = self.lhs();
|
let location = self.lhs().access(vm)?;
|
||||||
let mut location = lhs.access(vm)?;
|
|
||||||
let lhs = std::mem::take(&mut *location);
|
let lhs = std::mem::take(&mut *location);
|
||||||
*location = op(lhs, rhs).at(self.span())?;
|
*location = op(lhs, rhs).at(self.span())?;
|
||||||
Ok(Value::None)
|
Ok(Value::None)
|
||||||
@ -748,7 +743,7 @@ impl Eval for LetExpr {
|
|||||||
Some(expr) => expr.eval(vm)?,
|
Some(expr) => expr.eval(vm)?,
|
||||||
None => Value::None,
|
None => Value::None,
|
||||||
};
|
};
|
||||||
vm.scopes.top.def_mut(self.binding().take(), value);
|
vm.scopes.top.define(self.binding().take(), value);
|
||||||
Ok(Value::None)
|
Ok(Value::None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -797,7 +792,7 @@ impl Eval for ShowExpr {
|
|||||||
body,
|
body,
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(Recipe { pattern, func, span })
|
Ok(Recipe { pattern, func: Spanned::new(func, span) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -860,7 +855,7 @@ impl Eval for ForExpr {
|
|||||||
(for ($($binding:ident => $value:ident),*) in $iter:expr) => {{
|
(for ($($binding:ident => $value:ident),*) in $iter:expr) => {{
|
||||||
#[allow(unused_parens)]
|
#[allow(unused_parens)]
|
||||||
for ($($value),*) in $iter {
|
for ($($value),*) in $iter {
|
||||||
$(vm.scopes.top.def_mut(&$binding, $value);)*
|
$(vm.scopes.top.define(&$binding, $value);)*
|
||||||
|
|
||||||
let body = self.body();
|
let body = self.body();
|
||||||
let value = body.eval(vm)?;
|
let value = body.eval(vm)?;
|
||||||
@ -937,14 +932,14 @@ impl Eval for ImportExpr {
|
|||||||
|
|
||||||
match self.imports() {
|
match self.imports() {
|
||||||
Imports::Wildcard => {
|
Imports::Wildcard => {
|
||||||
for (var, slot) in module.scope.iter() {
|
for (var, value) in module.scope.iter() {
|
||||||
vm.scopes.top.def_mut(var, slot.read().clone());
|
vm.scopes.top.define(var, value.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Imports::Items(idents) => {
|
Imports::Items(idents) => {
|
||||||
for ident in idents {
|
for ident in idents {
|
||||||
if let Some(slot) = module.scope.get(&ident) {
|
if let Some(value) = module.scope.get(&ident) {
|
||||||
vm.scopes.top.def_mut(ident.take(), slot.read().clone());
|
vm.scopes.top.define(ident.take(), value.clone());
|
||||||
} else {
|
} else {
|
||||||
bail!(ident.span(), "unresolved import");
|
bail!(ident.span(), "unresolved import");
|
||||||
}
|
}
|
||||||
@ -1028,11 +1023,11 @@ impl Eval for ReturnExpr {
|
|||||||
/// Access an expression mutably.
|
/// Access an expression mutably.
|
||||||
pub trait Access {
|
pub trait Access {
|
||||||
/// Access the value.
|
/// Access the value.
|
||||||
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<Location<'a>>;
|
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Access for Expr {
|
impl Access for Expr {
|
||||||
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<Location<'a>> {
|
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value> {
|
||||||
match self {
|
match self {
|
||||||
Expr::Ident(v) => v.access(vm),
|
Expr::Ident(v) => v.access(vm),
|
||||||
Expr::FieldAccess(v) => v.access(vm),
|
Expr::FieldAccess(v) => v.access(vm),
|
||||||
@ -1043,68 +1038,35 @@ impl Access for Expr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Access for Ident {
|
impl Access for Ident {
|
||||||
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<Location<'a>> {
|
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value> {
|
||||||
match vm.scopes.get(self) {
|
vm.scopes.get_mut(self).at(self.span())
|
||||||
Some(slot) => match slot.try_write() {
|
|
||||||
Some(guard) => Ok(RwLockWriteGuard::map(guard, |v| v)),
|
|
||||||
None => bail!(self.span(), "cannot mutate a constant"),
|
|
||||||
},
|
|
||||||
None => bail!(self.span(), "unknown variable"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Access for FieldAccess {
|
impl Access for FieldAccess {
|
||||||
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<Location<'a>> {
|
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value> {
|
||||||
let guard = self.object().access(vm)?;
|
Ok(match self.object().access(vm)? {
|
||||||
try_map(guard, |value| {
|
Value::Dict(dict) => dict.get_mut(self.field().take()),
|
||||||
Ok(match value {
|
v => bail!(
|
||||||
Value::Dict(dict) => dict.get_mut(self.field().take()),
|
self.object().span(),
|
||||||
v => bail!(
|
"expected dictionary, found {}",
|
||||||
self.object().span(),
|
v.type_name(),
|
||||||
"expected dictionary, found {}",
|
),
|
||||||
v.type_name(),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Access for FuncCall {
|
impl Access for FuncCall {
|
||||||
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<Location<'a>> {
|
fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value> {
|
||||||
let args = self.args().eval(vm)?;
|
let args = self.args().eval(vm)?;
|
||||||
let guard = self.callee().access(vm)?;
|
Ok(match self.callee().access(vm)? {
|
||||||
try_map(guard, |value| {
|
Value::Array(array) => array.get_mut(args.into_index()?).at(self.span())?,
|
||||||
Ok(match value {
|
Value::Dict(dict) => dict.get_mut(args.into_key()?),
|
||||||
Value::Array(array) => {
|
v => bail!(
|
||||||
array.get_mut(args.into_index()?).at(self.span())?
|
self.callee().span(),
|
||||||
}
|
"expected collection, found {}",
|
||||||
Value::Dict(dict) => dict.get_mut(args.into_key()?),
|
v.type_name(),
|
||||||
v => bail!(
|
),
|
||||||
self.callee().span(),
|
|
||||||
"expected collection, found {}",
|
|
||||||
v.type_name(),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A mutable location.
|
|
||||||
type Location<'a> = MappedRwLockWriteGuard<'a, Value>;
|
|
||||||
|
|
||||||
/// Map a reader-writer lock with a function.
|
|
||||||
fn try_map<F>(location: Location, f: F) -> TypResult<Location>
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut Value) -> TypResult<&mut Value>,
|
|
||||||
{
|
|
||||||
let mut error = None;
|
|
||||||
MappedRwLockWriteGuard::try_map(location, |value| match f(value) {
|
|
||||||
Ok(value) => Some(value),
|
|
||||||
Err(err) => {
|
|
||||||
error = Some(err);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map_err(|_| error.unwrap())
|
|
||||||
}
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use super::{Dynamic, RawAlign, RawLength, RawStroke, Smart, StrExt, Value};
|
use super::{RawAlign, RawLength, RawStroke, Smart, StrExt, Value};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::geom::{Numeric, Relative, Spec, SpecAxis};
|
use crate::geom::{Numeric, Relative, Spec, SpecAxis};
|
||||||
use crate::model;
|
use crate::model;
|
||||||
@ -94,10 +94,10 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
|
|||||||
(Dict(a), Dict(b)) => Dict(a + b),
|
(Dict(a), Dict(b)) => Dict(a + b),
|
||||||
|
|
||||||
(Color(color), Length(thickness)) | (Length(thickness), Color(color)) => {
|
(Color(color), Length(thickness)) | (Length(thickness), Color(color)) => {
|
||||||
Dyn(Dynamic::new(RawStroke {
|
Value::dynamic(RawStroke {
|
||||||
paint: Smart::Custom(color.into()),
|
paint: Smart::Custom(color.into()),
|
||||||
thickness: Smart::Custom(thickness),
|
thickness: Smart::Custom(thickness),
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
(Dyn(a), Dyn(b)) => {
|
(Dyn(a), Dyn(b)) => {
|
||||||
@ -106,10 +106,10 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
|
|||||||
(a.downcast::<RawAlign>(), b.downcast::<RawAlign>())
|
(a.downcast::<RawAlign>(), b.downcast::<RawAlign>())
|
||||||
{
|
{
|
||||||
if a.axis() != b.axis() {
|
if a.axis() != b.axis() {
|
||||||
Dyn(Dynamic::new(match a.axis() {
|
Value::dynamic(match a.axis() {
|
||||||
SpecAxis::Horizontal => Spec { x: a, y: b },
|
SpecAxis::Horizontal => Spec { x: a, y: b },
|
||||||
SpecAxis::Vertical => Spec { x: b, y: a },
|
SpecAxis::Vertical => Spec { x: b, y: a },
|
||||||
}))
|
})
|
||||||
} else {
|
} else {
|
||||||
return Err(format!("cannot add two {:?} alignments", a.axis()));
|
return Err(format!("cannot add two {:?} alignments", a.axis()));
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,11 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::Hash;
|
||||||
use std::iter;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use parking_lot::RwLock;
|
|
||||||
|
|
||||||
use super::{Args, Func, Machine, Node, Value};
|
use super::{Args, Func, Machine, Node, Value};
|
||||||
use crate::diag::TypResult;
|
use crate::diag::{StrResult, TypResult};
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
/// A slot where a variable is stored.
|
|
||||||
pub type Slot = Arc<RwLock<Value>>;
|
|
||||||
|
|
||||||
/// A stack of scopes.
|
/// A stack of scopes.
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct Scopes<'a> {
|
pub struct Scopes<'a> {
|
||||||
@ -42,21 +35,33 @@ impl<'a> Scopes<'a> {
|
|||||||
self.top = self.scopes.pop().expect("no pushed scope");
|
self.top = self.scopes.pop().expect("no pushed scope");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look up the slot of a variable.
|
/// Try to access a variable immutably.
|
||||||
pub fn get(&self, var: &str) -> Option<&Slot> {
|
pub fn get(&self, var: &str) -> StrResult<&Value> {
|
||||||
iter::once(&self.top)
|
Ok(std::iter::once(&self.top)
|
||||||
.chain(self.scopes.iter().rev())
|
.chain(self.scopes.iter().rev())
|
||||||
.chain(self.base.into_iter())
|
.chain(self.base.into_iter())
|
||||||
.find_map(|scope| scope.get(var))
|
.find_map(|scope| scope.get(var))
|
||||||
|
.ok_or("unknown variable")?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to access a variable mutably.
|
||||||
|
pub fn get_mut(&mut self, var: &str) -> StrResult<&mut Value> {
|
||||||
|
std::iter::once(&mut self.top)
|
||||||
|
.chain(&mut self.scopes.iter_mut().rev())
|
||||||
|
.find_map(|scope| scope.get_mut(var))
|
||||||
|
.ok_or_else(|| {
|
||||||
|
if self.base.map_or(false, |base| base.get(var).is_some()) {
|
||||||
|
"cannot mutate a constant"
|
||||||
|
} else {
|
||||||
|
"unknown variable"
|
||||||
|
}
|
||||||
|
})?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A map from variable names to variable slots.
|
/// A map from binding names to values.
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone, Hash)]
|
||||||
pub struct Scope {
|
pub struct Scope(BTreeMap<EcoString, Slot>);
|
||||||
/// The mapping from names to slots.
|
|
||||||
values: BTreeMap<EcoString, Slot>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scope {
|
impl Scope {
|
||||||
/// Create a new empty scope.
|
/// Create a new empty scope.
|
||||||
@ -64,24 +69,9 @@ impl Scope {
|
|||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a constant variable with a value.
|
/// Bind a value to a name.
|
||||||
pub fn def_const(&mut self, var: impl Into<EcoString>, value: impl Into<Value>) {
|
pub fn define(&mut self, name: impl Into<EcoString>, value: impl Into<Value>) {
|
||||||
let cell = RwLock::new(value.into());
|
self.0.insert(name.into(), Slot::new(value.into(), Kind::Normal));
|
||||||
|
|
||||||
// Make it impossible to write to this value again.
|
|
||||||
std::mem::forget(cell.read());
|
|
||||||
|
|
||||||
self.values.insert(var.into(), Arc::new(cell));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Define a mutable variable with a value.
|
|
||||||
pub fn def_mut(&mut self, var: impl Into<EcoString>, value: impl Into<Value>) {
|
|
||||||
self.values.insert(var.into(), Arc::new(RwLock::new(value.into())));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Define a variable with a slot.
|
|
||||||
pub fn def_slot(&mut self, var: impl Into<EcoString>, slot: Slot) {
|
|
||||||
self.values.insert(var.into(), slot);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a function through a native rust function.
|
/// Define a function through a native rust function.
|
||||||
@ -90,32 +80,36 @@ impl Scope {
|
|||||||
name: &'static str,
|
name: &'static str,
|
||||||
func: fn(&mut Machine, &mut Args) -> TypResult<Value>,
|
func: fn(&mut Machine, &mut Args) -> TypResult<Value>,
|
||||||
) {
|
) {
|
||||||
self.def_const(name, Func::from_fn(name, func));
|
self.define(name, Func::from_fn(name, func));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a function through a native rust node.
|
/// Define a function through a native rust node.
|
||||||
pub fn def_node<T: Node>(&mut self, name: &'static str) {
|
pub fn def_node<T: Node>(&mut self, name: &'static str) {
|
||||||
self.def_const(name, Func::from_node::<T>(name));
|
self.define(name, Func::from_node::<T>(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look up the value of a variable.
|
/// Define a captured, immutable binding.
|
||||||
pub fn get(&self, var: &str) -> Option<&Slot> {
|
pub fn define_captured(
|
||||||
self.values.get(var)
|
&mut self,
|
||||||
|
var: impl Into<EcoString>,
|
||||||
|
value: impl Into<Value>,
|
||||||
|
) {
|
||||||
|
self.0.insert(var.into(), Slot::new(value.into(), Kind::Captured));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to access a variable immutably.
|
||||||
|
pub fn get(&self, var: &str) -> Option<&Value> {
|
||||||
|
self.0.get(var).map(Slot::read)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to access a variable mutably.
|
||||||
|
pub fn get_mut(&mut self, var: &str) -> Option<StrResult<&mut Value>> {
|
||||||
|
self.0.get_mut(var).map(Slot::write)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all definitions.
|
/// Iterate over all definitions.
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (&str, &Slot)> {
|
pub fn iter(&self) -> impl Iterator<Item = (&str, &Value)> {
|
||||||
self.values.iter().map(|(k, v)| (k.as_str(), v))
|
self.0.iter().map(|(k, v)| (k.as_str(), v.read()))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for Scope {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
self.values.len().hash(state);
|
|
||||||
for (name, value) in self.values.iter() {
|
|
||||||
name.hash(state);
|
|
||||||
value.read().hash(state);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +117,45 @@ impl Debug for Scope {
|
|||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.write_str("Scope ")?;
|
f.write_str("Scope ")?;
|
||||||
f.debug_map()
|
f.debug_map()
|
||||||
.entries(self.values.iter().map(|(k, v)| (k, v.read())))
|
.entries(self.0.iter().map(|(k, v)| (k, v.read())))
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A slot where a variable is stored.
|
||||||
|
#[derive(Clone, Hash)]
|
||||||
|
struct Slot {
|
||||||
|
/// The stored value.
|
||||||
|
value: Value,
|
||||||
|
/// The kind of slot, determines how the value can be accessed.
|
||||||
|
kind: Kind,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The different kinds of slots.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
enum Kind {
|
||||||
|
/// A normal, mutable binding.
|
||||||
|
Normal,
|
||||||
|
/// A captured copy of another variable.
|
||||||
|
Captured,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Slot {
|
||||||
|
/// Create a new constant slot.
|
||||||
|
fn new(value: Value, kind: Kind) -> Self {
|
||||||
|
Self { value, kind }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the variable.
|
||||||
|
fn read(&self) -> &Value {
|
||||||
|
&self.value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to write to the variable.
|
||||||
|
fn write(&mut self) -> StrResult<&mut Value> {
|
||||||
|
match self.kind {
|
||||||
|
Kind::Normal => Ok(&mut self.value),
|
||||||
|
Kind::Captured => Err("cannot mutate a captured variable")?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -11,7 +11,7 @@ use crate::geom::{
|
|||||||
Angle, Color, Dir, Em, Fraction, Length, Paint, Ratio, Relative, RgbaColor, Sides,
|
Angle, Color, Dir, Em, Fraction, Length, Paint, Ratio, Relative, RgbaColor, Sides,
|
||||||
};
|
};
|
||||||
use crate::library::text::RawNode;
|
use crate::library::text::RawNode;
|
||||||
use crate::model::{Content, Layout, LayoutNode, Pattern};
|
use crate::model::{Content, Group, Layout, LayoutNode, Pattern};
|
||||||
use crate::syntax::Spanned;
|
use crate::syntax::Spanned;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
@ -73,6 +73,14 @@ impl Value {
|
|||||||
Self::Content(Content::block(node))
|
Self::Content(Content::block(node))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new dynamic value.
|
||||||
|
pub fn dynamic<T>(any: T) -> Self
|
||||||
|
where
|
||||||
|
T: Type + Debug + PartialEq + Hash + Sync + Send + 'static,
|
||||||
|
{
|
||||||
|
Self::Dyn(Dynamic::new(any))
|
||||||
|
}
|
||||||
|
|
||||||
/// The name of the stored value's type.
|
/// The name of the stored value's type.
|
||||||
pub fn type_name(&self) -> &'static str {
|
pub fn type_name(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
@ -653,6 +661,10 @@ dynamic! {
|
|||||||
Regex: "regular expression",
|
Regex: "regular expression",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dynamic! {
|
||||||
|
Group: "group",
|
||||||
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
usize,
|
usize,
|
||||||
Expected: "non-negative integer",
|
Expected: "non-negative integer",
|
||||||
|
@ -333,14 +333,14 @@ impl<'a> PdfExporter<'a> {
|
|||||||
.action_type(ActionType::Uri)
|
.action_type(ActionType::Uri)
|
||||||
.uri(Str(uri.as_str().as_bytes()));
|
.uri(Str(uri.as_str().as_bytes()));
|
||||||
}
|
}
|
||||||
Destination::Internal(page, point) => {
|
Destination::Internal(loc) => {
|
||||||
let page = page - 1;
|
let index = loc.page - 1;
|
||||||
let height = page_heights[page];
|
let height = page_heights[index];
|
||||||
link.action()
|
link.action()
|
||||||
.action_type(ActionType::GoTo)
|
.action_type(ActionType::GoTo)
|
||||||
.destination_direct()
|
.destination_direct()
|
||||||
.page(page_refs[page])
|
.page(page_refs[index])
|
||||||
.xyz(point.x.to_f32(), height - point.y.to_f32(), None);
|
.xyz(loc.pos.x.to_f32(), height - loc.pos.y.to_f32(), None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -490,6 +490,7 @@ impl<'a> PageExporter<'a> {
|
|||||||
Element::Shape(ref shape) => self.write_shape(x, y, shape),
|
Element::Shape(ref shape) => self.write_shape(x, y, shape),
|
||||||
Element::Image(id, size) => self.write_image(x, y, id, size),
|
Element::Image(id, size) => self.write_image(x, y, id, size),
|
||||||
Element::Link(ref dest, size) => self.write_link(pos, dest, size),
|
Element::Link(ref dest, size) => self.write_link(pos, dest, size),
|
||||||
|
Element::Pin(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,7 @@ fn render_frame(
|
|||||||
render_image(canvas, ts, mask, ctx.images.get(id), size);
|
render_image(canvas, ts, mask, ctx.images.get(id), size);
|
||||||
}
|
}
|
||||||
Element::Link(_, _) => {}
|
Element::Link(_, _) => {}
|
||||||
|
Element::Pin(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
34
src/frame.rs
34
src/frame.rs
@ -3,6 +3,7 @@
|
|||||||
use std::fmt::{self, Debug, Formatter, Write};
|
use std::fmt::{self, Debug, Formatter, Write};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::eval::{Dict, Value};
|
||||||
use crate::font::FaceId;
|
use crate::font::FaceId;
|
||||||
use crate::geom::{
|
use crate::geom::{
|
||||||
Align, Em, Length, Numeric, Paint, Point, Shape, Size, Spec, Transform,
|
Align, Em, Length, Numeric, Paint, Point, Shape, Size, Spec, Transform,
|
||||||
@ -218,6 +219,9 @@ pub enum Element {
|
|||||||
Image(ImageId, Size),
|
Image(ImageId, Size),
|
||||||
/// A link to an external resource and its trigger region.
|
/// A link to an external resource and its trigger region.
|
||||||
Link(Destination, Size),
|
Link(Destination, Size),
|
||||||
|
/// A pin identified by index. This is used to find elements on the pages
|
||||||
|
/// and use their location in formatting. Exporters can just ignore it.
|
||||||
|
Pin(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Element {
|
impl Debug for Element {
|
||||||
@ -227,7 +231,8 @@ impl Debug for Element {
|
|||||||
Self::Text(text) => write!(f, "{text:?}"),
|
Self::Text(text) => write!(f, "{text:?}"),
|
||||||
Self::Shape(shape) => write!(f, "{shape:?}"),
|
Self::Shape(shape) => write!(f, "{shape:?}"),
|
||||||
Self::Image(image, _) => write!(f, "{image:?}"),
|
Self::Image(image, _) => write!(f, "{image:?}"),
|
||||||
Self::Link(target, _) => write!(f, "Link({target:?})"),
|
Self::Link(dest, _) => write!(f, "Link({dest:?})"),
|
||||||
|
Self::Pin(idx) => write!(f, "Pin({idx})"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -310,21 +315,30 @@ pub struct Glyph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A link destination.
|
/// A link destination.
|
||||||
#[derive(Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum Destination {
|
pub enum Destination {
|
||||||
/// A link to a point on a page.
|
/// A link to a point on a page.
|
||||||
Internal(usize, Point),
|
Internal(Location),
|
||||||
/// A link to a URL.
|
/// A link to a URL.
|
||||||
Url(EcoString),
|
Url(EcoString),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Destination {
|
/// A physical location in a document.
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
match self {
|
pub struct Location {
|
||||||
Self::Internal(page, point) => {
|
/// The page, starting at 1.
|
||||||
write!(f, "Internal(Page {}, {:?})", page, point)
|
pub page: usize,
|
||||||
}
|
/// The exact coordinates on the page (from the top left, as usual).
|
||||||
Self::Url(url) => write!(f, "Url({})", url),
|
pub pos: Point,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Location {
|
||||||
|
/// Encode into a user-facing dictionary.
|
||||||
|
pub fn encode(&self) -> Dict {
|
||||||
|
dict! {
|
||||||
|
"page" => Value::Int(self.page as i64),
|
||||||
|
"x" => Value::Length(self.pos.x.into()),
|
||||||
|
"y" => Value::Length(self.pos.y.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
src/lib.rs
13
src/lib.rs
@ -61,7 +61,7 @@ use crate::font::FontStore;
|
|||||||
use crate::frame::Frame;
|
use crate::frame::Frame;
|
||||||
use crate::image::ImageStore;
|
use crate::image::ImageStore;
|
||||||
use crate::loading::Loader;
|
use crate::loading::Loader;
|
||||||
use crate::model::StyleMap;
|
use crate::model::{PinBoard, StyleMap};
|
||||||
use crate::source::{SourceId, SourceStore};
|
use crate::source::{SourceId, SourceStore};
|
||||||
|
|
||||||
/// Typeset a source file into a collection of layouted frames.
|
/// Typeset a source file into a collection of layouted frames.
|
||||||
@ -76,27 +76,30 @@ pub fn typeset(ctx: &mut Context, id: SourceId) -> TypResult<Vec<Arc<Frame>>> {
|
|||||||
|
|
||||||
/// The core context which holds the configuration and stores.
|
/// The core context which holds the configuration and stores.
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
/// The context's configuration.
|
|
||||||
pub config: Config,
|
|
||||||
/// Stores loaded source files.
|
/// Stores loaded source files.
|
||||||
pub sources: SourceStore,
|
pub sources: SourceStore,
|
||||||
/// Stores parsed font faces.
|
/// Stores parsed font faces.
|
||||||
pub fonts: FontStore,
|
pub fonts: FontStore,
|
||||||
/// Stores decoded images.
|
/// Stores decoded images.
|
||||||
pub images: ImageStore,
|
pub images: ImageStore,
|
||||||
|
/// The context's configuration.
|
||||||
|
config: Config,
|
||||||
/// Stores evaluated modules.
|
/// Stores evaluated modules.
|
||||||
pub modules: HashMap<SourceId, Module>,
|
modules: HashMap<SourceId, Module>,
|
||||||
|
/// Stores document pins.
|
||||||
|
pins: PinBoard,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
/// Create a new context.
|
/// Create a new context.
|
||||||
pub fn new(loader: Arc<dyn Loader>, config: Config) -> Self {
|
pub fn new(loader: Arc<dyn Loader>, config: Config) -> Self {
|
||||||
Self {
|
Self {
|
||||||
config,
|
|
||||||
sources: SourceStore::new(Arc::clone(&loader)),
|
sources: SourceStore::new(Arc::clone(&loader)),
|
||||||
fonts: FontStore::new(Arc::clone(&loader)),
|
fonts: FontStore::new(Arc::clone(&loader)),
|
||||||
images: ImageStore::new(loader),
|
images: ImageStore::new(loader),
|
||||||
|
config,
|
||||||
modules: HashMap::new(),
|
modules: HashMap::new(),
|
||||||
|
pins: PinBoard::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,7 +204,9 @@ impl<'a> GridLayouter<'a> {
|
|||||||
|
|
||||||
/// Determines the columns sizes and then layouts the grid row-by-row.
|
/// Determines the columns sizes and then layouts the grid row-by-row.
|
||||||
pub fn layout(mut self) -> TypResult<Vec<Arc<Frame>>> {
|
pub fn layout(mut self) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
|
self.ctx.pins.freeze();
|
||||||
self.measure_columns()?;
|
self.measure_columns()?;
|
||||||
|
self.ctx.pins.unfreeze();
|
||||||
|
|
||||||
for y in 0 .. self.rows.len() {
|
for y in 0 .. self.rows.len() {
|
||||||
// Skip to next region if current one is full, but only for content
|
// Skip to next region if current one is full, but only for content
|
||||||
@ -370,10 +372,12 @@ impl<'a> GridLayouter<'a> {
|
|||||||
pod.base.x = self.regions.base.x;
|
pod.base.x = self.regions.base.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.ctx.pins.freeze();
|
||||||
let mut sizes = node
|
let mut sizes = node
|
||||||
.layout(self.ctx, &pod, self.styles)?
|
.layout(self.ctx, &pod, self.styles)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|frame| frame.size.y);
|
.map(|frame| frame.size.y);
|
||||||
|
self.ctx.pins.unfreeze();
|
||||||
|
|
||||||
// For each region, we want to know the maximum height any
|
// For each region, we want to know the maximum height any
|
||||||
// column requires.
|
// column requires.
|
||||||
|
14
src/library/layout/locate.rs
Normal file
14
src/library/layout/locate.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
use crate::library::prelude::*;
|
||||||
|
use crate::model::{Group, LocateNode};
|
||||||
|
|
||||||
|
/// Format content with access to its location on the page.
|
||||||
|
pub fn locate(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
||||||
|
let node = LocateNode::single(args.expect("recipe")?);
|
||||||
|
Ok(Value::Content(Content::Locate(node)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new group of locatable elements.
|
||||||
|
pub fn group(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
||||||
|
let key = args.expect("key")?;
|
||||||
|
Ok(Value::dynamic(Group::new(key)))
|
||||||
|
}
|
@ -5,6 +5,7 @@ mod columns;
|
|||||||
mod container;
|
mod container;
|
||||||
mod flow;
|
mod flow;
|
||||||
mod grid;
|
mod grid;
|
||||||
|
mod locate;
|
||||||
mod pad;
|
mod pad;
|
||||||
mod page;
|
mod page;
|
||||||
mod place;
|
mod place;
|
||||||
@ -16,6 +17,7 @@ pub use columns::*;
|
|||||||
pub use container::*;
|
pub use container::*;
|
||||||
pub use flow::*;
|
pub use flow::*;
|
||||||
pub use grid::*;
|
pub use grid::*;
|
||||||
|
pub use locate::*;
|
||||||
pub use pad::*;
|
pub use pad::*;
|
||||||
pub use page::*;
|
pub use page::*;
|
||||||
pub use place::*;
|
pub use place::*;
|
||||||
|
@ -54,6 +54,8 @@ pub fn new() -> Scope {
|
|||||||
std.def_node::<layout::ColumnsNode>("columns");
|
std.def_node::<layout::ColumnsNode>("columns");
|
||||||
std.def_node::<layout::ColbreakNode>("colbreak");
|
std.def_node::<layout::ColbreakNode>("colbreak");
|
||||||
std.def_node::<layout::PlaceNode>("place");
|
std.def_node::<layout::PlaceNode>("place");
|
||||||
|
std.def_fn("locate", layout::locate);
|
||||||
|
std.def_fn("group", layout::group);
|
||||||
|
|
||||||
// Graphics.
|
// Graphics.
|
||||||
std.def_node::<graphics::ImageNode>("image");
|
std.def_node::<graphics::ImageNode>("image");
|
||||||
@ -94,38 +96,38 @@ pub fn new() -> Scope {
|
|||||||
std.def_fn("lorem", utility::lorem);
|
std.def_fn("lorem", utility::lorem);
|
||||||
|
|
||||||
// Predefined colors.
|
// Predefined colors.
|
||||||
std.def_const("black", Color::BLACK);
|
std.define("black", Color::BLACK);
|
||||||
std.def_const("gray", Color::GRAY);
|
std.define("gray", Color::GRAY);
|
||||||
std.def_const("silver", Color::SILVER);
|
std.define("silver", Color::SILVER);
|
||||||
std.def_const("white", Color::WHITE);
|
std.define("white", Color::WHITE);
|
||||||
std.def_const("navy", Color::NAVY);
|
std.define("navy", Color::NAVY);
|
||||||
std.def_const("blue", Color::BLUE);
|
std.define("blue", Color::BLUE);
|
||||||
std.def_const("aqua", Color::AQUA);
|
std.define("aqua", Color::AQUA);
|
||||||
std.def_const("teal", Color::TEAL);
|
std.define("teal", Color::TEAL);
|
||||||
std.def_const("eastern", Color::EASTERN);
|
std.define("eastern", Color::EASTERN);
|
||||||
std.def_const("purple", Color::PURPLE);
|
std.define("purple", Color::PURPLE);
|
||||||
std.def_const("fuchsia", Color::FUCHSIA);
|
std.define("fuchsia", Color::FUCHSIA);
|
||||||
std.def_const("maroon", Color::MAROON);
|
std.define("maroon", Color::MAROON);
|
||||||
std.def_const("red", Color::RED);
|
std.define("red", Color::RED);
|
||||||
std.def_const("orange", Color::ORANGE);
|
std.define("orange", Color::ORANGE);
|
||||||
std.def_const("yellow", Color::YELLOW);
|
std.define("yellow", Color::YELLOW);
|
||||||
std.def_const("olive", Color::OLIVE);
|
std.define("olive", Color::OLIVE);
|
||||||
std.def_const("green", Color::GREEN);
|
std.define("green", Color::GREEN);
|
||||||
std.def_const("lime", Color::LIME);
|
std.define("lime", Color::LIME);
|
||||||
|
|
||||||
// Other constants.
|
// Other constants.
|
||||||
std.def_const("ltr", Dir::LTR);
|
std.define("ltr", Dir::LTR);
|
||||||
std.def_const("rtl", Dir::RTL);
|
std.define("rtl", Dir::RTL);
|
||||||
std.def_const("ttb", Dir::TTB);
|
std.define("ttb", Dir::TTB);
|
||||||
std.def_const("btt", Dir::BTT);
|
std.define("btt", Dir::BTT);
|
||||||
std.def_const("start", RawAlign::Start);
|
std.define("start", RawAlign::Start);
|
||||||
std.def_const("end", RawAlign::End);
|
std.define("end", RawAlign::End);
|
||||||
std.def_const("left", RawAlign::Specific(Align::Left));
|
std.define("left", RawAlign::Specific(Align::Left));
|
||||||
std.def_const("center", RawAlign::Specific(Align::Center));
|
std.define("center", RawAlign::Specific(Align::Center));
|
||||||
std.def_const("right", RawAlign::Specific(Align::Right));
|
std.define("right", RawAlign::Specific(Align::Right));
|
||||||
std.def_const("top", RawAlign::Specific(Align::Top));
|
std.define("top", RawAlign::Specific(Align::Top));
|
||||||
std.def_const("horizon", RawAlign::Specific(Align::Horizon));
|
std.define("horizon", RawAlign::Specific(Align::Horizon));
|
||||||
std.def_const("bottom", RawAlign::Specific(Align::Bottom));
|
std.define("bottom", RawAlign::Specific(Align::Bottom));
|
||||||
|
|
||||||
std
|
std
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,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, Dict, Func, Machine, Node, RawAlign, RawLength, RawStroke,
|
Arg, Args, Array, Cast, Dict, Dynamic, Func, Machine, Node, RawAlign, RawLength,
|
||||||
Scope, Smart, Value,
|
RawStroke, Scope, Smart, Value,
|
||||||
};
|
};
|
||||||
pub use crate::frame::*;
|
pub use crate::frame::*;
|
||||||
pub use crate::geom::*;
|
pub use crate::geom::*;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use super::TextNode;
|
use super::TextNode;
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::util::EcoString;
|
|
||||||
|
|
||||||
/// Link text and other elements to an URL.
|
/// Link text and other elements to an URL.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -24,7 +23,7 @@ impl LinkNode {
|
|||||||
let dest = args.expect::<Destination>("destination")?;
|
let dest = args.expect::<Destination>("destination")?;
|
||||||
let body = match dest {
|
let body = match dest {
|
||||||
Destination::Url(_) => args.eat()?,
|
Destination::Url(_) => args.eat()?,
|
||||||
Destination::Internal(_, _) => Some(args.expect("body")?),
|
Destination::Internal(_) => Some(args.expect("body")?),
|
||||||
};
|
};
|
||||||
Self { dest, body }
|
Self { dest, body }
|
||||||
}))
|
}))
|
||||||
@ -36,10 +35,10 @@ castable! {
|
|||||||
Expected: "string or dictionary with `page`, `x`, and `y` keys",
|
Expected: "string or dictionary with `page`, `x`, and `y` keys",
|
||||||
Value::Str(string) => Self::Url(string),
|
Value::Str(string) => Self::Url(string),
|
||||||
Value::Dict(dict) => {
|
Value::Dict(dict) => {
|
||||||
let page: i64 = dict.get(&EcoString::from_str("page"))?.clone().cast()?;
|
let page: i64 = dict.get(&"page".into())?.clone().cast()?;
|
||||||
let x: RawLength = dict.get(&EcoString::from_str("x"))?.clone().cast()?;
|
let x: RawLength = dict.get(&"x".into())?.clone().cast()?;
|
||||||
let y: RawLength = dict.get(&EcoString::from_str("y"))?.clone().cast()?;
|
let y: RawLength = dict.get(&"y".into())?.clone().cast()?;
|
||||||
Self::Internal(page as usize, Point::new(x.length, y.length))
|
Self::Internal(Location { page: page as usize, pos: Point::new(x.length, y.length) })
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,11 +55,7 @@ impl Show for LinkNode {
|
|||||||
dict! {
|
dict! {
|
||||||
"url" => match &self.dest {
|
"url" => match &self.dest {
|
||||||
Destination::Url(url) => Value::Str(url.clone()),
|
Destination::Url(url) => Value::Str(url.clone()),
|
||||||
Destination::Internal(page, point) => Value::Dict(dict!{
|
Destination::Internal(loc) => Value::Dict(loc.encode()),
|
||||||
"page" => Value::Int(*page as i64),
|
|
||||||
"x" => Value::Length(point.x.into()),
|
|
||||||
"y" => Value::Length(point.y.into()),
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
"body" => match &self.body {
|
"body" => match &self.body {
|
||||||
Some(body) => Value::Content(body.clone()),
|
Some(body) => Value::Content(body.clone()),
|
||||||
@ -79,7 +74,7 @@ impl Show for LinkNode {
|
|||||||
let shorter = text.len() < url.len();
|
let shorter = text.len() < url.len();
|
||||||
Content::Text(if shorter { text.into() } else { url.clone() })
|
Content::Text(if shorter { text.into() } else { url.clone() })
|
||||||
}
|
}
|
||||||
Destination::Internal(_, _) => panic!("missing body"),
|
Destination::Internal(_) => Content::Empty,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +94,7 @@ impl Show for LinkNode {
|
|||||||
if match styles.get(Self::UNDERLINE) {
|
if match styles.get(Self::UNDERLINE) {
|
||||||
Smart::Auto => match &self.dest {
|
Smart::Auto => match &self.dest {
|
||||||
Destination::Url(_) => true,
|
Destination::Url(_) => true,
|
||||||
Destination::Internal(_, _) => false,
|
Destination::Internal(_) => false,
|
||||||
},
|
},
|
||||||
Smart::Custom(underline) => underline,
|
Smart::Custom(underline) => underline,
|
||||||
} {
|
} {
|
||||||
|
@ -26,6 +26,8 @@ pub enum ParChild {
|
|||||||
Spacing(Spacing),
|
Spacing(Spacing),
|
||||||
/// An arbitrary inline-level node.
|
/// An arbitrary inline-level node.
|
||||||
Node(LayoutNode),
|
Node(LayoutNode),
|
||||||
|
/// A pin identified by index.
|
||||||
|
Pin(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
@ -100,6 +102,7 @@ impl Debug for ParChild {
|
|||||||
Self::Quote { double } => write!(f, "Quote({double})"),
|
Self::Quote { double } => write!(f, "Quote({double})"),
|
||||||
Self::Spacing(kind) => write!(f, "{:?}", kind),
|
Self::Spacing(kind) => write!(f, "{:?}", kind),
|
||||||
Self::Node(node) => node.fmt(f),
|
Self::Node(node) => node.fmt(f),
|
||||||
|
Self::Pin(idx) => write!(f, "Pin({idx})"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,10 +194,11 @@ impl LinebreakNode {
|
|||||||
/// Range of a substring of text.
|
/// Range of a substring of text.
|
||||||
type Range = std::ops::Range<usize>;
|
type Range = std::ops::Range<usize>;
|
||||||
|
|
||||||
// The characters by which spacing and nodes are replaced in the paragraph's
|
// The characters by which spacing, nodes and pins are replaced in the
|
||||||
// full text.
|
// paragraph's full text.
|
||||||
const SPACING_REPLACE: char = ' ';
|
const SPACING_REPLACE: char = ' '; // Space
|
||||||
const NODE_REPLACE: char = '\u{FFFC}';
|
const NODE_REPLACE: char = '\u{FFFC}'; // Object Replacement Character
|
||||||
|
const PIN_REPLACE: char = '\u{200D}'; // Zero Width Joiner
|
||||||
|
|
||||||
/// A paragraph representation in which children are already layouted and text
|
/// A paragraph representation in which children are already layouted and text
|
||||||
/// is already preshaped.
|
/// is already preshaped.
|
||||||
@ -275,6 +279,8 @@ enum Segment<'a> {
|
|||||||
Spacing(Spacing),
|
Spacing(Spacing),
|
||||||
/// An arbitrary inline-level layout node.
|
/// An arbitrary inline-level layout node.
|
||||||
Node(&'a LayoutNode),
|
Node(&'a LayoutNode),
|
||||||
|
/// A pin identified by index.
|
||||||
|
Pin(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Segment<'_> {
|
impl Segment<'_> {
|
||||||
@ -284,6 +290,7 @@ impl Segment<'_> {
|
|||||||
Self::Text(len) => len,
|
Self::Text(len) => len,
|
||||||
Self::Spacing(_) => SPACING_REPLACE.len_utf8(),
|
Self::Spacing(_) => SPACING_REPLACE.len_utf8(),
|
||||||
Self::Node(_) => NODE_REPLACE.len_utf8(),
|
Self::Node(_) => NODE_REPLACE.len_utf8(),
|
||||||
|
Self::Pin(_) => PIN_REPLACE.len_utf8(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -301,6 +308,8 @@ enum Item<'a> {
|
|||||||
Frame(Arc<Frame>),
|
Frame(Arc<Frame>),
|
||||||
/// A repeating node.
|
/// A repeating node.
|
||||||
Repeat(&'a RepeatNode, StyleChain<'a>),
|
Repeat(&'a RepeatNode, StyleChain<'a>),
|
||||||
|
/// A pin identified by index.
|
||||||
|
Pin(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Item<'a> {
|
impl<'a> Item<'a> {
|
||||||
@ -318,16 +327,17 @@ impl<'a> Item<'a> {
|
|||||||
Self::Text(shaped) => shaped.text.len(),
|
Self::Text(shaped) => shaped.text.len(),
|
||||||
Self::Absolute(_) | Self::Fractional(_) => SPACING_REPLACE.len_utf8(),
|
Self::Absolute(_) | Self::Fractional(_) => SPACING_REPLACE.len_utf8(),
|
||||||
Self::Frame(_) | Self::Repeat(_, _) => NODE_REPLACE.len_utf8(),
|
Self::Frame(_) | Self::Repeat(_, _) => NODE_REPLACE.len_utf8(),
|
||||||
|
Self::Pin(_) => PIN_REPLACE.len_utf8(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The natural width of the item.
|
/// The natural width of the item.
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
match self {
|
match self {
|
||||||
Item::Text(shaped) => shaped.width,
|
Self::Text(shaped) => shaped.width,
|
||||||
Item::Absolute(v) => *v,
|
Self::Absolute(v) => *v,
|
||||||
Item::Fractional(_) | Self::Repeat(_, _) => Length::zero(),
|
Self::Frame(frame) => frame.size.x,
|
||||||
Item::Frame(frame) => frame.size.x,
|
Self::Fractional(_) | Self::Repeat(_, _) | Self::Pin(_) => Length::zero(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -447,7 +457,7 @@ fn collect<'a>(
|
|||||||
}
|
}
|
||||||
Segment::Text(full.len() - prev)
|
Segment::Text(full.len() - prev)
|
||||||
}
|
}
|
||||||
ParChild::Quote { double } => {
|
&ParChild::Quote { double } => {
|
||||||
let prev = full.len();
|
let prev = full.len();
|
||||||
if styles.get(TextNode::SMART_QUOTES) {
|
if styles.get(TextNode::SMART_QUOTES) {
|
||||||
let lang = styles.get(TextNode::LANG);
|
let lang = styles.get(TextNode::LANG);
|
||||||
@ -458,22 +468,27 @@ fn collect<'a>(
|
|||||||
ParChild::Quote { .. } => Some('"'),
|
ParChild::Quote { .. } => Some('"'),
|
||||||
ParChild::Spacing(_) => Some(SPACING_REPLACE),
|
ParChild::Spacing(_) => Some(SPACING_REPLACE),
|
||||||
ParChild::Node(_) => Some(NODE_REPLACE),
|
ParChild::Node(_) => Some(NODE_REPLACE),
|
||||||
|
ParChild::Pin(_) => Some(PIN_REPLACE),
|
||||||
});
|
});
|
||||||
|
|
||||||
full.push_str(quoter.quote("es, *double, peeked));
|
full.push_str(quoter.quote("es, double, peeked));
|
||||||
} else {
|
} else {
|
||||||
full.push(if *double { '"' } else { '\'' });
|
full.push(if double { '"' } else { '\'' });
|
||||||
}
|
}
|
||||||
Segment::Text(full.len() - prev)
|
Segment::Text(full.len() - prev)
|
||||||
}
|
}
|
||||||
ParChild::Spacing(spacing) => {
|
&ParChild::Spacing(spacing) => {
|
||||||
full.push(SPACING_REPLACE);
|
full.push(SPACING_REPLACE);
|
||||||
Segment::Spacing(*spacing)
|
Segment::Spacing(spacing)
|
||||||
}
|
}
|
||||||
ParChild::Node(node) => {
|
ParChild::Node(node) => {
|
||||||
full.push(NODE_REPLACE);
|
full.push(NODE_REPLACE);
|
||||||
Segment::Node(node)
|
Segment::Node(node)
|
||||||
}
|
}
|
||||||
|
&ParChild::Pin(idx) => {
|
||||||
|
full.push(PIN_REPLACE);
|
||||||
|
Segment::Pin(idx)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(last) = full.chars().last() {
|
if let Some(last) = full.chars().last() {
|
||||||
@ -540,6 +555,7 @@ fn prepare<'a>(
|
|||||||
items.push(Item::Frame(frame));
|
items.push(Item::Frame(frame));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Segment::Pin(idx) => items.push(Item::Pin(idx)),
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor = end;
|
cursor = end;
|
||||||
@ -1171,6 +1187,11 @@ fn commit(
|
|||||||
}
|
}
|
||||||
offset = before + width;
|
offset = before + width;
|
||||||
}
|
}
|
||||||
|
Item::Pin(idx) => {
|
||||||
|
let mut frame = Frame::new(Size::zero());
|
||||||
|
frame.push(Point::zero(), Element::Pin(*idx));
|
||||||
|
push(&mut offset, MaybeShared::Owned(frame));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
20
src/memo.rs
20
src/memo.rs
@ -4,7 +4,7 @@ use std::any::Any;
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
use std::hash::Hash;
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
/// The thread-local cache.
|
/// The thread-local cache.
|
||||||
@ -60,7 +60,7 @@ where
|
|||||||
O: 'static,
|
O: 'static,
|
||||||
G: Fn(&O) -> R,
|
G: Fn(&O) -> R,
|
||||||
{
|
{
|
||||||
let hash = fxhash::hash64(&input);
|
let hash = fxhash::hash64(&(f, &input));
|
||||||
let result = with(|cache| {
|
let result = with(|cache| {
|
||||||
let entry = cache.get_mut(&hash)?;
|
let entry = cache.get_mut(&hash)?;
|
||||||
entry.age = 0;
|
entry.age = 0;
|
||||||
@ -111,13 +111,13 @@ impl Display for Eviction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// These impls are temporary and incorrect.
|
// These impls are temporary and incorrect.
|
||||||
macro_rules! skip {
|
|
||||||
($ty:ty) => {
|
impl Hash for crate::font::FontStore {
|
||||||
impl Hash for $ty {
|
fn hash<H: Hasher>(&self, _: &mut H) {}
|
||||||
fn hash<H: std::hash::Hasher>(&self, _: &mut H) {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
skip!(crate::font::FontStore);
|
impl Hash for crate::Context {
|
||||||
skip!(crate::Context);
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.pins.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -7,8 +7,8 @@ use std::ops::{Add, AddAssign};
|
|||||||
use typed_arena::Arena;
|
use typed_arena::Arena;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Barrier, CollapsingBuilder, Interruption, Key, Layout, LayoutNode, Property, Show,
|
Barrier, CollapsingBuilder, Interruption, Key, Layout, LayoutNode, LocateNode,
|
||||||
ShowNode, StyleEntry, StyleMap, StyleVecBuilder, Target,
|
Property, Show, ShowNode, StyleEntry, StyleMap, StyleVecBuilder, Target,
|
||||||
};
|
};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, Spacing};
|
use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, Spacing};
|
||||||
@ -20,7 +20,33 @@ use crate::library::text::{
|
|||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
/// Layout content into a collection of pages.
|
/// Layout content into a collection of pages.
|
||||||
|
///
|
||||||
|
/// Relayouts until all pinned locations are converged.
|
||||||
pub fn layout(ctx: &mut Context, content: &Content) -> TypResult<Vec<Arc<Frame>>> {
|
pub fn layout(ctx: &mut Context, content: &Content) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
|
let mut pass = 0;
|
||||||
|
let mut frames;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let prev = ctx.pins.clone();
|
||||||
|
let result = layout_once(ctx, content);
|
||||||
|
ctx.pins.reset();
|
||||||
|
frames = result?;
|
||||||
|
pass += 1;
|
||||||
|
|
||||||
|
ctx.pins.locate(&frames);
|
||||||
|
|
||||||
|
// Quit if we're done or if we've had five passes.
|
||||||
|
let unresolved = ctx.pins.unresolved(&prev);
|
||||||
|
if unresolved == 0 || pass >= 5 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(frames)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Layout content into a collection of pages once.
|
||||||
|
fn layout_once(ctx: &mut Context, content: &Content) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
let copy = ctx.config.styles.clone();
|
let copy = ctx.config.styles.clone();
|
||||||
let styles = StyleChain::with_root(©);
|
let styles = StyleChain::with_root(©);
|
||||||
let scratch = Scratch::default();
|
let scratch = Scratch::default();
|
||||||
@ -88,6 +114,10 @@ pub enum Content {
|
|||||||
/// A node that can be realized with styles, optionally with attached
|
/// A node that can be realized with styles, optionally with attached
|
||||||
/// properties.
|
/// properties.
|
||||||
Show(ShowNode, Option<Dict>),
|
Show(ShowNode, Option<Dict>),
|
||||||
|
/// A node that can be realized with its location on the page.
|
||||||
|
Locate(LocateNode),
|
||||||
|
/// A pin identified by index.
|
||||||
|
Pin(usize),
|
||||||
/// Content with attached styles.
|
/// Content with attached styles.
|
||||||
Styled(Arc<(Self, StyleMap)>),
|
Styled(Arc<(Self, StyleMap)>),
|
||||||
/// A sequence of multiple nodes.
|
/// A sequence of multiple nodes.
|
||||||
@ -272,6 +302,8 @@ impl Debug for Content {
|
|||||||
Self::Pagebreak { weak } => write!(f, "Pagebreak({weak})"),
|
Self::Pagebreak { weak } => write!(f, "Pagebreak({weak})"),
|
||||||
Self::Page(page) => page.fmt(f),
|
Self::Page(page) => page.fmt(f),
|
||||||
Self::Show(node, _) => node.fmt(f),
|
Self::Show(node, _) => node.fmt(f),
|
||||||
|
Self::Locate(node) => node.fmt(f),
|
||||||
|
Self::Pin(idx) => write!(f, "Pin({idx})"),
|
||||||
Self::Styled(styled) => {
|
Self::Styled(styled) => {
|
||||||
let (sub, map) = styled.as_ref();
|
let (sub, map) = styled.as_ref();
|
||||||
map.fmt(f)?;
|
map.fmt(f)?;
|
||||||
@ -388,6 +420,7 @@ impl<'a, 'ctx> Builder<'a, 'ctx> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Content::Show(node, _) => return self.show(node, styles),
|
Content::Show(node, _) => return self.show(node, styles),
|
||||||
|
Content::Locate(node) => return self.locate(node, styles),
|
||||||
Content::Styled(styled) => return self.styled(styled, styles),
|
Content::Styled(styled) => return self.styled(styled, styles),
|
||||||
Content::Sequence(seq) => return self.sequence(seq, styles),
|
Content::Sequence(seq) => return self.sequence(seq, styles),
|
||||||
|
|
||||||
@ -436,6 +469,12 @@ impl<'a, 'ctx> Builder<'a, 'ctx> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn locate(&mut self, node: &LocateNode, styles: StyleChain<'a>) -> TypResult<()> {
|
||||||
|
let realized = node.realize(self.ctx)?;
|
||||||
|
let stored = self.scratch.templates.alloc(realized);
|
||||||
|
self.accept(stored, styles)
|
||||||
|
}
|
||||||
|
|
||||||
fn styled(
|
fn styled(
|
||||||
&mut self,
|
&mut self,
|
||||||
(content, map): &'a (Content, StyleMap),
|
(content, map): &'a (Content, StyleMap),
|
||||||
@ -641,6 +680,9 @@ impl<'a> ParBuilder<'a> {
|
|||||||
Content::Inline(node) => {
|
Content::Inline(node) => {
|
||||||
self.0.supportive(ParChild::Node(node.clone()), styles);
|
self.0.supportive(ParChild::Node(node.clone()), styles);
|
||||||
}
|
}
|
||||||
|
&Content::Pin(idx) => {
|
||||||
|
self.0.ignorant(ParChild::Pin(idx), styles);
|
||||||
|
}
|
||||||
_ => return false,
|
_ => return false,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -660,7 +702,7 @@ impl<'a> ParBuilder<'a> {
|
|||||||
&& children
|
&& children
|
||||||
.items()
|
.items()
|
||||||
.find_map(|child| match child {
|
.find_map(|child| match child {
|
||||||
ParChild::Spacing(_) => None,
|
ParChild::Spacing(_) | ParChild::Pin(_) => None,
|
||||||
ParChild::Text(_) | ParChild::Quote { .. } => Some(true),
|
ParChild::Text(_) | ParChild::Quote { .. } => Some(true),
|
||||||
ParChild::Node(_) => Some(false),
|
ParChild::Node(_) => Some(false),
|
||||||
})
|
})
|
||||||
|
@ -221,13 +221,20 @@ impl Layout for LayoutNode {
|
|||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Arc<Frame>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
crate::memo::memoized(
|
let (result, at, pins) = crate::memo::memoized(
|
||||||
(self, ctx, regions, styles),
|
(self, &mut *ctx, regions, styles),
|
||||||
|(node, ctx, regions, styles)| {
|
|(node, ctx, regions, styles)| {
|
||||||
|
let at = ctx.pins.cursor();
|
||||||
let entry = StyleEntry::Barrier(Barrier::new(node.id()));
|
let entry = StyleEntry::Barrier(Barrier::new(node.id()));
|
||||||
node.0.layout(ctx, regions, entry.chain(&styles))
|
let result = node.0.layout(ctx, regions, entry.chain(&styles));
|
||||||
|
(result, at, ctx.pins.from(at))
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
|
// Replay the side effect in case of caching. This should currently be
|
||||||
|
// more or less the only relevant side effect on the context.
|
||||||
|
ctx.pins.replay(at, pins);
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pack(self) -> LayoutNode {
|
fn pack(self) -> LayoutNode {
|
||||||
|
342
src/model/locate.rs
Normal file
342
src/model/locate.rs
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::Content;
|
||||||
|
use crate::diag::TypResult;
|
||||||
|
use crate::eval::{Args, Array, Dict, Func, Value};
|
||||||
|
use crate::frame::{Element, Frame, Location};
|
||||||
|
use crate::geom::{Point, Transform};
|
||||||
|
use crate::syntax::Spanned;
|
||||||
|
use crate::util::EcoString;
|
||||||
|
use crate::Context;
|
||||||
|
|
||||||
|
/// A group of locatable elements.
|
||||||
|
#[derive(Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct Group(EcoString);
|
||||||
|
|
||||||
|
impl Group {
|
||||||
|
/// Create a group of elements that is identified by a string key.
|
||||||
|
pub fn new(key: EcoString) -> Self {
|
||||||
|
Self(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an entry to the group.
|
||||||
|
pub fn entry(&self, recipe: Spanned<Func>, value: Option<Value>) -> LocateNode {
|
||||||
|
LocateNode::entry(self.clone(), recipe, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Do something with all entries of a group.
|
||||||
|
pub fn all(&self, recipe: Spanned<Func>) -> LocateNode {
|
||||||
|
LocateNode::all(self.clone(), recipe)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Group {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
write!(f, "group({:?})", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A node that can be realized with pinned document locations.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
|
pub struct LocateNode(Arc<Repr>);
|
||||||
|
|
||||||
|
impl LocateNode {
|
||||||
|
/// Create a new locatable single node.
|
||||||
|
pub fn single(recipe: Spanned<Func>) -> Self {
|
||||||
|
Self(Arc::new(Repr::Single(SingleNode(recipe))))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new locatable group entry node.
|
||||||
|
pub fn entry(group: Group, recipe: Spanned<Func>, value: Option<Value>) -> Self {
|
||||||
|
Self(Arc::new(Repr::Entry(EntryNode { group, recipe, value })))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new node with access to a group's members.
|
||||||
|
pub fn all(group: Group, recipe: Spanned<Func>) -> Self {
|
||||||
|
Self(Arc::new(Repr::All(AllNode { group, recipe })))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Realize the node.
|
||||||
|
pub fn realize(&self, ctx: &mut Context) -> TypResult<Content> {
|
||||||
|
match self.0.as_ref() {
|
||||||
|
Repr::Single(single) => single.realize(ctx),
|
||||||
|
Repr::Entry(entry) => entry.realize(ctx),
|
||||||
|
Repr::All(all) => all.realize(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The different kinds of locate nodes.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
|
enum Repr {
|
||||||
|
/// A single `locate(me => ...)`.
|
||||||
|
Single(SingleNode),
|
||||||
|
/// A locatable group entry.
|
||||||
|
Entry(EntryNode),
|
||||||
|
/// A recipe for all entries of a group.
|
||||||
|
All(AllNode),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An ungrouped locatable node.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
|
struct SingleNode(Spanned<Func>);
|
||||||
|
|
||||||
|
impl SingleNode {
|
||||||
|
fn realize(&self, ctx: &mut Context) -> TypResult<Content> {
|
||||||
|
let idx = ctx.pins.cursor();
|
||||||
|
let pin = ctx.pins.get_or_create(None, None);
|
||||||
|
let dict = pin.encode(None);
|
||||||
|
let args = Args::new(self.0.span, [Value::Dict(dict)]);
|
||||||
|
Ok(Content::Pin(idx) + self.0.v.call_detached(ctx, args)?.display())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A locatable grouped node which can interact with its peers' details.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
|
struct EntryNode {
|
||||||
|
/// Which group the node belongs to.
|
||||||
|
group: Group,
|
||||||
|
/// The recipe to execute.
|
||||||
|
recipe: Spanned<Func>,
|
||||||
|
/// An arbitrary attached value.
|
||||||
|
value: Option<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EntryNode {
|
||||||
|
fn realize(&self, ctx: &mut Context) -> TypResult<Content> {
|
||||||
|
let idx = ctx.pins.cursor();
|
||||||
|
let pin = ctx.pins.get_or_create(Some(self.group.clone()), self.value.clone());
|
||||||
|
|
||||||
|
// Determine the index among the peers.
|
||||||
|
let index = ctx
|
||||||
|
.pins
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|&(k, other)| {
|
||||||
|
other.is_in(&self.group)
|
||||||
|
&& if k < idx {
|
||||||
|
other.flow <= pin.flow
|
||||||
|
} else {
|
||||||
|
other.flow < pin.flow
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
|
||||||
|
// Prepare first argument.
|
||||||
|
let dict = pin.encode(Some(index));
|
||||||
|
let mut args = Args::new(self.recipe.span, [Value::Dict(dict)]);
|
||||||
|
|
||||||
|
// Collect all group members if second argument is requested.
|
||||||
|
if self.recipe.v.argc() == Some(2) {
|
||||||
|
let all = ctx.pins.encode_group(&self.group);
|
||||||
|
args.push(self.recipe.span, Value::Array(all))
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Content::Pin(idx) + self.recipe.v.call_detached(ctx, args)?.display())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A node with access to a group's members.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
|
struct AllNode {
|
||||||
|
/// Which group the node has access to.
|
||||||
|
group: Group,
|
||||||
|
/// The recipe to execute.
|
||||||
|
recipe: Spanned<Func>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AllNode {
|
||||||
|
fn realize(&self, ctx: &mut Context) -> TypResult<Content> {
|
||||||
|
let all = ctx.pins.encode_group(&self.group);
|
||||||
|
let args = Args::new(self.recipe.span, [Value::Array(all)]);
|
||||||
|
Ok(self.recipe.v.call_detached(ctx, args)?.display())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Manages document pins.
|
||||||
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
pub struct PinBoard {
|
||||||
|
/// All currently active pins.
|
||||||
|
list: Vec<Pin>,
|
||||||
|
/// The index of the next pin, in order.
|
||||||
|
cursor: usize,
|
||||||
|
/// If larger than zero, the board is frozen and the cursor will not be
|
||||||
|
/// advanced. This is used to disable pinning during measure-only layouting.
|
||||||
|
frozen: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PinBoard {
|
||||||
|
/// Create an empty pin board.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { list: vec![], cursor: 0, frozen: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The current cursor.
|
||||||
|
pub fn cursor(&self) -> usize {
|
||||||
|
self.cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All pins from `prev` to the current cursor.
|
||||||
|
pub fn from(&self, prev: usize) -> Vec<Pin> {
|
||||||
|
self.list[prev .. self.cursor].to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add the given pins at the given location and set the cursor behind them.
|
||||||
|
pub fn replay(&mut self, at: usize, pins: Vec<Pin>) {
|
||||||
|
if !self.frozen() {
|
||||||
|
self.cursor = at + pins.len();
|
||||||
|
let end = self.cursor.min(self.list.len());
|
||||||
|
self.list.splice(at .. end, pins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Freeze the board to prevent modifications.
|
||||||
|
pub fn freeze(&mut self) {
|
||||||
|
self.frozen += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Freeze the board to prevent modifications.
|
||||||
|
pub fn unfreeze(&mut self) {
|
||||||
|
self.frozen -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the board is currently frozen.
|
||||||
|
pub fn frozen(&self) -> bool {
|
||||||
|
self.frozen > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset the cursor and remove all unused pins.
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.list.truncate(self.cursor);
|
||||||
|
self.cursor = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Locate all pins in the frames.
|
||||||
|
pub fn locate(&mut self, frames: &[Arc<Frame>]) {
|
||||||
|
let mut flow = 0;
|
||||||
|
for (i, frame) in frames.iter().enumerate() {
|
||||||
|
locate_in_frame(
|
||||||
|
&mut self.list,
|
||||||
|
&mut flow,
|
||||||
|
1 + i,
|
||||||
|
frame,
|
||||||
|
Transform::identity(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How many pins are unresolved in comparison to an earlier snapshot.
|
||||||
|
pub fn unresolved(&self, prev: &Self) -> usize {
|
||||||
|
self.list.len() - self.list.iter().zip(&prev.list).filter(|(a, b)| a == b).count()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access or create the next pin.
|
||||||
|
fn get_or_create(&mut self, group: Option<Group>, value: Option<Value>) -> Pin {
|
||||||
|
if self.frozen() {
|
||||||
|
return Pin::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
let cursor = self.cursor;
|
||||||
|
self.cursor += 1;
|
||||||
|
if self.cursor >= self.list.len() {
|
||||||
|
self.list.resize(self.cursor, Pin::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
let pin = &mut self.list[cursor];
|
||||||
|
pin.group = group;
|
||||||
|
pin.value = value;
|
||||||
|
pin.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over all pins on the board.
|
||||||
|
fn iter(&self) -> std::slice::Iter<Pin> {
|
||||||
|
self.list.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encode a group into a user-facing array.
|
||||||
|
fn encode_group(&self, group: &Group) -> Array {
|
||||||
|
let mut all: Vec<_> = self.iter().filter(|pin| pin.is_in(group)).collect();
|
||||||
|
all.sort_by_key(|pin| pin.flow);
|
||||||
|
all.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, member)| Value::Dict(member.encode(Some(index))))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Locate all pins in a frame.
|
||||||
|
fn locate_in_frame(
|
||||||
|
pins: &mut [Pin],
|
||||||
|
flow: &mut usize,
|
||||||
|
page: usize,
|
||||||
|
frame: &Frame,
|
||||||
|
ts: Transform,
|
||||||
|
) {
|
||||||
|
for &(pos, ref element) in &frame.elements {
|
||||||
|
match element {
|
||||||
|
Element::Group(group) => {
|
||||||
|
let ts = ts
|
||||||
|
.pre_concat(Transform::translate(pos.x, pos.y))
|
||||||
|
.pre_concat(group.transform);
|
||||||
|
locate_in_frame(pins, flow, page, &group.frame, ts);
|
||||||
|
}
|
||||||
|
|
||||||
|
Element::Pin(idx) => {
|
||||||
|
let pin = &mut pins[*idx];
|
||||||
|
pin.loc.page = page;
|
||||||
|
pin.loc.pos = pos.transform(ts);
|
||||||
|
pin.flow = *flow;
|
||||||
|
*flow += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A document pin.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
|
pub struct Pin {
|
||||||
|
/// The physical location of the pin in the document.
|
||||||
|
loc: Location,
|
||||||
|
/// The flow index.
|
||||||
|
flow: usize,
|
||||||
|
/// The group the pin belongs to, if any.
|
||||||
|
group: Option<Group>,
|
||||||
|
/// An arbitrary attached value.
|
||||||
|
value: Option<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pin {
|
||||||
|
/// Whether the pin is part of the given group.
|
||||||
|
fn is_in(&self, group: &Group) -> bool {
|
||||||
|
self.group.as_ref() == Some(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encode into a user-facing dictionary.
|
||||||
|
fn encode(&self, index: Option<usize>) -> Dict {
|
||||||
|
let mut dict = self.loc.encode();
|
||||||
|
|
||||||
|
if let Some(value) = &self.value {
|
||||||
|
dict.insert("value".into(), value.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(index) = index {
|
||||||
|
dict.insert("index".into(), Value::Int(index as i64));
|
||||||
|
}
|
||||||
|
|
||||||
|
dict
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Pin {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
loc: Location { page: 0, pos: Point::zero() },
|
||||||
|
flow: 0,
|
||||||
|
group: None,
|
||||||
|
value: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ mod styles;
|
|||||||
mod collapse;
|
mod collapse;
|
||||||
mod content;
|
mod content;
|
||||||
mod layout;
|
mod layout;
|
||||||
|
mod locate;
|
||||||
mod property;
|
mod property;
|
||||||
mod recipe;
|
mod recipe;
|
||||||
mod show;
|
mod show;
|
||||||
@ -12,6 +13,7 @@ mod show;
|
|||||||
pub use collapse::*;
|
pub use collapse::*;
|
||||||
pub use content::*;
|
pub use content::*;
|
||||||
pub use layout::*;
|
pub use layout::*;
|
||||||
|
pub use locate::*;
|
||||||
pub use property::*;
|
pub use property::*;
|
||||||
pub use recipe::*;
|
pub use recipe::*;
|
||||||
pub use show::*;
|
pub use show::*;
|
||||||
|
@ -4,7 +4,7 @@ use super::{Content, Interruption, NodeId, Show, ShowNode, StyleChain, StyleEntr
|
|||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::eval::{Args, Func, Regex, Value};
|
use crate::eval::{Args, Func, Regex, Value};
|
||||||
use crate::library::structure::{EnumNode, ListNode};
|
use crate::library::structure::{EnumNode, ListNode};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Spanned;
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
|
||||||
/// A show rule recipe.
|
/// A show rule recipe.
|
||||||
@ -13,9 +13,7 @@ pub struct Recipe {
|
|||||||
/// The patterns to customize.
|
/// The patterns to customize.
|
||||||
pub pattern: Pattern,
|
pub pattern: Pattern,
|
||||||
/// The function that defines the recipe.
|
/// The function that defines the recipe.
|
||||||
pub func: Func,
|
pub func: Spanned<Func>,
|
||||||
/// The span to report all erros with.
|
|
||||||
pub span: Span,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Recipe {
|
impl Recipe {
|
||||||
@ -81,13 +79,13 @@ impl Recipe {
|
|||||||
where
|
where
|
||||||
F: FnOnce() -> Value,
|
F: FnOnce() -> Value,
|
||||||
{
|
{
|
||||||
let args = if self.func.argc() == Some(0) {
|
let args = if self.func.v.argc() == Some(0) {
|
||||||
Args::new(self.span, [])
|
Args::new(self.func.span, [])
|
||||||
} else {
|
} else {
|
||||||
Args::new(self.span, [arg()])
|
Args::new(self.func.span, [arg()])
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(self.func.call_detached(ctx, args)?.display())
|
Ok(self.func.v.call_detached(ctx, args)?.display())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What kind of structure the property interrupts.
|
/// What kind of structure the property interrupts.
|
||||||
@ -104,7 +102,11 @@ impl Recipe {
|
|||||||
|
|
||||||
impl Debug for Recipe {
|
impl Debug for Recipe {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "Recipe matching {:?} from {:?}", self.pattern, self.span)
|
write!(
|
||||||
|
f,
|
||||||
|
"Recipe matching {:?} from {:?}",
|
||||||
|
self.pattern, self.func.span
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
tests/ref/layout/locate-break.png
Normal file
BIN
tests/ref/layout/locate-break.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 122 B |
BIN
tests/ref/layout/locate-group.png
Normal file
BIN
tests/ref/layout/locate-group.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
BIN
tests/ref/layout/locate.png
Normal file
BIN
tests/ref/layout/locate.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
@ -31,7 +31,7 @@
|
|||||||
---
|
---
|
||||||
// Capture environment.
|
// Capture environment.
|
||||||
{
|
{
|
||||||
let mark = "?"
|
let mark = "!"
|
||||||
let greet = {
|
let greet = {
|
||||||
let hi = "Hi"
|
let hi = "Hi"
|
||||||
name => {
|
name => {
|
||||||
@ -39,9 +39,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test(greet("Typst"), "Hi, Typst?")
|
test(greet("Typst"), "Hi, Typst!")
|
||||||
|
|
||||||
mark = "!"
|
// Changing the captured variable after the closure definition has no effect.
|
||||||
|
mark = "?"
|
||||||
test(greet("Typst"), "Hi, Typst!")
|
test(greet("Typst"), "Hi, Typst!")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,12 +72,12 @@
|
|||||||
// For loop bindings.
|
// For loop bindings.
|
||||||
{
|
{
|
||||||
let v = (1, 2, 3)
|
let v = (1, 2, 3)
|
||||||
let s = 0
|
|
||||||
let f() = {
|
let f() = {
|
||||||
|
let s = 0
|
||||||
for v in v { s += v }
|
for v in v { s += v }
|
||||||
|
s
|
||||||
}
|
}
|
||||||
f()
|
test(f(), 6)
|
||||||
test(s, 6)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -55,30 +55,28 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Test that the expression is evaluated to the end.
|
// Test that the expression is evaluated to the end.
|
||||||
#let y = 1
|
#let sum(..args) = {
|
||||||
#let identity(x, ..rest) = x
|
let s = 0
|
||||||
#let f(x) = {
|
for v in args.positional() {
|
||||||
identity(
|
s += v
|
||||||
..return,
|
}
|
||||||
x + 1,
|
s
|
||||||
y = 2,
|
}
|
||||||
)
|
|
||||||
|
#let f() = {
|
||||||
|
sum(..return, 1, 2, 3)
|
||||||
"nope"
|
"nope"
|
||||||
}
|
}
|
||||||
|
|
||||||
#test(f(1), 2)
|
#test(f(), 6)
|
||||||
#test(y, 2)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test value return from content.
|
// Test value return from content.
|
||||||
#let x = 3
|
#let x = 3
|
||||||
#let f() = [
|
#let f() = [
|
||||||
Hello 😀
|
Hello 😀
|
||||||
{ x = 1 }
|
|
||||||
#return "nope"
|
#return "nope"
|
||||||
{ x = 2 }
|
|
||||||
World
|
World
|
||||||
]
|
]
|
||||||
|
|
||||||
#test(f(), "nope")
|
#test(f(), "nope")
|
||||||
#test(x, 1)
|
|
||||||
|
@ -23,16 +23,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test storing arguments in a variable.
|
// Test doing things with arguments.
|
||||||
{
|
{
|
||||||
let args
|
let save(..args) = {
|
||||||
let save(..sink) = {
|
test(type(args), "arguments")
|
||||||
args = sink
|
test(repr(args), "(1, 2, three: true)")
|
||||||
}
|
}
|
||||||
|
|
||||||
save(1, 2, three: true)
|
save(1, 2, three: true)
|
||||||
test(type(args), "arguments")
|
|
||||||
test(repr(args), "(1, 2, three: true)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
5
tests/typ/layout/locate-break.typ
Normal file
5
tests/typ/layout/locate-break.typ
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// Test locate with crazy pagebreaks.
|
||||||
|
|
||||||
|
---
|
||||||
|
#set page(height: 10pt)
|
||||||
|
{3 * locate(me => me.page * pagebreak())}
|
62
tests/typ/layout/locate-group.typ
Normal file
62
tests/typ/layout/locate-group.typ
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// Test locatable groups.
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test counting.
|
||||||
|
#let letters = group("\u{1F494}")
|
||||||
|
#let counter = letters.entry(
|
||||||
|
(me, all) => [{1 + me.index} / {all.len()}]
|
||||||
|
)
|
||||||
|
|
||||||
|
#counter \
|
||||||
|
#box(counter) \
|
||||||
|
#counter \
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test minimal citation engine with references before the document.
|
||||||
|
#let cited = group("citations")
|
||||||
|
#let num(cited, key) = {
|
||||||
|
let index = 0
|
||||||
|
for item in cited {
|
||||||
|
if item.value == key {
|
||||||
|
index = item.index
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[\[{index + 1}\]]
|
||||||
|
}
|
||||||
|
|
||||||
|
#let cite(key) = cited.entry(value: key, (_, all) => num(all, key))
|
||||||
|
{cited.all(all => grid(
|
||||||
|
columns: (auto, 1fr),
|
||||||
|
gutter: 5pt,
|
||||||
|
..{
|
||||||
|
let seen = ()
|
||||||
|
for item in all {
|
||||||
|
if item.value not in seen {
|
||||||
|
seen.push(item.value)
|
||||||
|
(num(all, item.value), item.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
))}
|
||||||
|
|
||||||
|
As shown in #cite("abc") and #cite("def") and #cite("abc") ...
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test that `all` contains `me`.
|
||||||
|
// Ref: false
|
||||||
|
#show it: heading as group("headings").entry(
|
||||||
|
(me, all) => {
|
||||||
|
let last
|
||||||
|
for prev in all {
|
||||||
|
last = prev
|
||||||
|
if prev.index == me.index {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(last == me)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
= A
|
||||||
|
== B
|
22
tests/typ/layout/locate.typ
Normal file
22
tests/typ/layout/locate.typ
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Test locate me.
|
||||||
|
|
||||||
|
---
|
||||||
|
#set page(height: 60pt)
|
||||||
|
#let pin = locate(me => box({
|
||||||
|
let c(length) = str(int(length / 1pt ) )
|
||||||
|
square(size: 1.5pt, fill: blue)
|
||||||
|
h(0.15em)
|
||||||
|
text(0.5em)[{me.page}, #c(me.x), #c(me.y)]
|
||||||
|
}))
|
||||||
|
|
||||||
|
#place(rotate(origin: top + left, 25deg, move(dx: 40pt, pin)))
|
||||||
|
|
||||||
|
#pin
|
||||||
|
#h(10pt)
|
||||||
|
#box(pin) \
|
||||||
|
#pin
|
||||||
|
|
||||||
|
#place(bottom + right, pin)
|
||||||
|
|
||||||
|
#pagebreak()
|
||||||
|
#align(center + horizon, pin + [\ ] + pin)
|
@ -7,13 +7,14 @@
|
|||||||
30pt, 50%, 20pt, 100%,
|
30pt, 50%, 20pt, 100%,
|
||||||
)
|
)
|
||||||
|
|
||||||
#let shaded = {
|
#let shaded(i, w) = {
|
||||||
let v = 0%
|
let v = (i + 1) * 10%
|
||||||
let next() = { v += 10%; rgb(v, v, v) }
|
rect(width: w, height: 10pt, fill: rgb(v, v, v))
|
||||||
w => rect(width: w, height: 10pt, fill: next())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#let items = for w in widths { (align(right, shaded(w)),) }
|
#let items = for i, w in widths {
|
||||||
|
(align(right, shaded(i, w)),)
|
||||||
|
}
|
||||||
|
|
||||||
#set page(width: 50pt, margins: 0pt)
|
#set page(width: 50pt, margins: 0pt)
|
||||||
#stack(dir: btt, ..items)
|
#stack(dir: btt, ..items)
|
||||||
|
@ -70,10 +70,10 @@ fn main() {
|
|||||||
);
|
);
|
||||||
styles.set(TextNode::SIZE, TextSize(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 helpers into the global scope.
|
||||||
let mut std = typst::library::new();
|
let mut std = typst::library::new();
|
||||||
std.def_const("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));
|
std.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF));
|
||||||
std.def_const("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF));
|
std.define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF));
|
||||||
std.def_fn("test", move |_, args| {
|
std.def_fn("test", move |_, args| {
|
||||||
let lhs = args.expect::<Value>("left-hand side")?;
|
let lhs = args.expect::<Value>("left-hand side")?;
|
||||||
let rhs = args.expect::<Value>("right-hand side")?;
|
let rhs = args.expect::<Value>("right-hand side")?;
|
||||||
@ -82,6 +82,17 @@ fn main() {
|
|||||||
}
|
}
|
||||||
Ok(Value::None)
|
Ok(Value::None)
|
||||||
});
|
});
|
||||||
|
std.def_fn("print", move |_, args| {
|
||||||
|
print!("> ");
|
||||||
|
for (i, value) in args.all::<Value>()?.into_iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
print!(", ")
|
||||||
|
}
|
||||||
|
print!("{value:?}");
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
Ok(Value::None)
|
||||||
|
});
|
||||||
|
|
||||||
// Create loader and context.
|
// Create loader and context.
|
||||||
let loader = FsLoader::new().with_path(FONT_DIR);
|
let loader = FsLoader::new().with_path(FONT_DIR);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user