Add 'layout' function to obtain the size of the outer container (#557)

This commit is contained in:
Pg Biel 2023-04-06 10:36:36 -03:00 committed by GitHub
parent 5cb226026e
commit ca71081d05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 116 additions and 0 deletions

View File

@ -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);

View File

@ -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)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -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
]))

View File

@ -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))
})