summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--library/src/lib.rs1
-rw-r--r--library/src/meta/context.rs98
-rw-r--r--tests/ref/layout/block-sizing.pngbin23032 -> 24503 bytes
-rw-r--r--tests/ref/layout/page.pngbin8104 -> 14273 bytes
-rw-r--r--tests/typ/layout/block-sizing.typ8
-rw-r--r--tests/typ/layout/page.typ9
6 files changed, 116 insertions, 0 deletions
diff --git a/library/src/lib.rs b/library/src/lib.rs
index c11b818e..ac99425f 100644
--- a/library/src/lib.rs
+++ b/library/src/lib.rs
@@ -97,6 +97,7 @@ fn global(math: Module, calc: Module) -> Module {
global.define("bibliography", meta::BibliographyElem::func());
global.define("locate", meta::locate);
global.define("style", meta::style);
+ global.define("layout", meta::layout);
global.define("counter", meta::counter);
global.define("numbering", meta::numbering);
global.define("state", meta::state);
diff --git a/library/src/meta/context.rs b/library/src/meta/context.rs
index 7426d27d..3902f47a 100644
--- a/library/src/meta/context.rs
+++ b/library/src/meta/context.rs
@@ -121,3 +121,101 @@ impl Show for StyleElem {
Ok(self.func().call_vt(vt, [styles.to_map().into()])?.display())
}
}
+
+/// Provides access to the current outer container's (or page's, if none) size (width and height).
+///
+/// The given function must accept a single parameter, `size`, which is a dictionary with keys
+/// `width` and `height`, both having the type [`length`]($type/length).
+///
+/// That is, if this `layout` call is done inside (for example) a box of size 800pt (width)
+/// by 400pt (height), then the specified function will be given the parameter
+/// `(width: 800pt, height: 400pt)`.
+///
+/// If, however, this `layout` call is placed directly on the page, not inside any container,
+/// then the function will be given `(width: page_width, height: page_height)`, where `page_width`
+/// and `page_height` correspond to the current page's respective dimensions, minus its margins.
+///
+/// This is useful, for example, to convert a [`ratio`]($type/ratio) value (such as `5%`, `100%`
+/// etc.), which are usually based upon the outer container's dimensions (precisely what this
+/// function gives), to a fixed length (in `pt`).
+///
+/// This is also useful if you're trying to make content fit a certain box, and doing certain
+/// arithmetic using `pt` (for example, comparing different lengths) is required.
+///
+/// Please note: This function may provide a width or height of `infpt` if one of the page
+/// dimensions is `auto`, under certain circumstances. This should not normally occur for
+/// usual page sizes, however.
+///
+/// ```example
+/// layout(size => {
+/// // work with the width and height of the container we're in
+/// // using size.width and size.height
+/// })
+///
+/// layout(size => {
+/// // convert 49% (of page width) to 'pt'
+/// // note that "ratio" values are always relative to a certain, possibly arbitrary length,
+/// // but it's usually the current container's width or height (e.g., for table columns,
+/// // 15% would be relative to the width, but, for rows, it would be relative to the height).
+/// let percentage_of_width = (49% / 1%) * 0.01 * size.width
+/// // ... use the converted value ...
+/// })
+///
+/// // The following two boxes are equivalent, and will have rectangles sized 200pt and 40pt:
+///
+/// #box(width: 200pt, height: 40pt, {
+/// rect(width: 100%, height: 100%)
+/// })
+///
+/// #box(width: 200pt, height: 40pt, layout(size => {
+/// rect(width: size.width, height: size.height)
+/// }))
+/// ```
+///
+/// Display: Layout
+/// Category: meta
+/// Returns: content
+#[func]
+pub fn layout(
+ /// A function to call with the outer container's size. Its return value is displayed
+ /// in the document.
+ ///
+ /// This function is called once for each time the content returned by
+ /// `layout` appears in the document. That makes it possible to generate
+ /// content that depends on the size of the container it is inside.
+ func: Func,
+) -> Value {
+ LayoutElem::new(func).pack().into()
+}
+
+/// Executes a `layout` call.
+///
+/// Display: Layout
+/// Category: special
+#[element(Layout)]
+struct LayoutElem {
+ /// The function to call with the outer container's (or page's) size.
+ #[required]
+ func: Func,
+}
+
+impl Layout for LayoutElem {
+ fn layout(
+ &self,
+ vt: &mut Vt,
+ styles: StyleChain,
+ regions: Regions,
+ ) -> SourceResult<Fragment> {
+ // Gets the current region's base size, which will be the size of the outer container,
+ // or of the page if there is no such container.
+ let Size { x, y } = regions.base();
+ let size_dict = dict! { "width" => x, "height" => y }.into();
+
+ let result = self
+ .func()
+ .call_vt(vt, [size_dict])? // calls func(size)
+ .display();
+
+ result.layout(vt, styles, regions)
+ }
+}
diff --git a/tests/ref/layout/block-sizing.png b/tests/ref/layout/block-sizing.png
index ff95c34c..f6655e63 100644
--- a/tests/ref/layout/block-sizing.png
+++ b/tests/ref/layout/block-sizing.png
Binary files differ
diff --git a/tests/ref/layout/page.png b/tests/ref/layout/page.png
index 4881fa2b..bcf32526 100644
--- a/tests/ref/layout/page.png
+++ b/tests/ref/layout/page.png
Binary files differ
diff --git a/tests/typ/layout/block-sizing.typ b/tests/typ/layout/block-sizing.typ
index a768c3e3..181bbe31 100644
--- a/tests/typ/layout/block-sizing.typ
+++ b/tests/typ/layout/block-sizing.typ
@@ -14,3 +14,11 @@
fill: aqua,
lorem(8) + colbreak(),
)
+
+---
+// Layout inside a block with certain dimensions should provide those dimensions.
+
+#set page(height: 120pt)
+#block(width: 60pt, height: 80pt, layout(size => [
+ This block has a width of #size.width and height of #size.height
+]))
diff --git a/tests/typ/layout/page.typ b/tests/typ/layout/page.typ
index ff050e55..f5c7822d 100644
--- a/tests/typ/layout/page.typ
+++ b/tests/typ/layout/page.typ
@@ -31,3 +31,12 @@
// Should result in one forest-colored A11 page and one auto-sized page.
#page("a11", flipped: true, fill: forest)[]
#pagebreak()
+
+---
+// Layout without any container should provide the page's dimensions, minus its margins.
+
+#page(width: 100pt, height: 100pt, {
+ layout(size => [This page has a width of #size.width and height of #size.height ])
+ h(1em)
+ place(left, rect(width: 80pt, stroke: blue))
+})