Compare commits

...

3 Commits

Author SHA1 Message Date
Johann Birnick
513bab4d86
Merge 73340f70a6f36bc46514a81b220373112fc7399e into 7278d887cf05fadc9a96478830e5876739b78f53 2025-07-23 21:17:56 +03:00
Tobias Schmitz
7278d887cf
Fix bounding box computation for lines in curves (#6647)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
2025-07-23 14:17:03 +00:00
Johann Birnick
73340f70a6 initial draft 2025-01-19 04:42:47 -08:00
7 changed files with 169 additions and 17 deletions

View File

@ -0,0 +1,92 @@
use comemo::Tracked;
use crate::diag::HintedStrResult;
use crate::engine::Engine;
use crate::foundations::{func, Array, Context, IntoValue, LocatableSelector, Selector};
/// Counts element in the document.
///
/// The `count` function lets you count elements in your document of a
/// particular type or with a particular label. It also allows to count them
/// in a hierachical way. To use it, you first need to ensure that [context]
/// is available.
///
/// Always returns an array of integers, even if called with just a single
/// target.
/// # Counting elements - simple
///
/// To just count elements of a single type/label up to the current location,
/// pass the selector you want to count:
/// ```example
/// = Heading
///
/// = Another Heading
///
/// #context count(heading)
///
/// = Third Heading
/// ```
///
/// Note that it will not return an integer, but an array with a single integer
/// entry.
/// # Counting elements - hierarchical
///
/// If you pass multiple targets, then it starts by counting the first target.
/// Then the second target is counted _starting only from the last counted
/// element of the first target_.
///
/// ```example
/// = Some Heading
///
/// == Some Subheading
///
/// = Another Heading
///
/// == Some Subheading
///
/// #context count(heading.where(level: 1), heading.where(level: 2))
///
/// == Another Subheading
/// ```
#[func(contextual)]
pub fn count(
/// The engine.
engine: &mut Engine,
/// The callsite context.
context: Tracked<Context>,
/// Each target can be
/// - an element function like a `heading` or `figure`,
/// - a `{<label>}`,
/// - a more complex selector like `{heading.where(level: 1)}`,
/// - or `{selector(heading).before(here())}`.
///
/// Only [locatable]($location/#locatable) element functions are supported.
#[variadic]
targets: Vec<LocatableSelector>,
/// When passing this argument, it will count everything only from a
/// certain location on.
/// The selector must match exactly one element in the document. The most
/// useful kinds of selectors for this are [labels]($label) and
/// [locations]($location).
// TODO remove Option as soon as there is a special `start` location
#[named]
after: Option<LocatableSelector>,
) -> HintedStrResult<Array> {
// NOTE this could be made more efficient
// one could directly get a slice &[Selector] from Vec<LocatableSelector>
// by using #[repr(transparent)] on LocatableSelector
let selectors: Vec<Selector> = targets.into_iter().map(|sel| sel.0).collect();
// TODO add argument "at" with default value "here"
// and compute `before` accordingly
let before = Some(context.location()?);
let after = match after {
Some(selector) => Some(selector.resolve_unique(engine.introspector, context)?),
None => None,
};
let nums = engine.introspector.count(&selectors, after, before);
Ok(nums.into_iter().map(IntoValue::into_value).collect())
}

View File

@ -254,6 +254,60 @@ impl Introspector {
}
}
/// Count hierarchically.
pub fn count(
&self,
selectors: &[Selector],
// TODO remove Option as soon as there is a special `start` location
after: Option<Location>,
// TODO remove Option as soon as there is a special `end` location
before: Option<Location>,
) -> SmallVec<[usize; 3]> {
let mut nums = SmallVec::with_capacity(selectors.len());
// can't write `mut` directly into function parameters because not supported by comemo
let mut after = after;
for selector in selectors.iter() {
let list = self.query(selector);
// count how many elements in `list` are between after and end
let index_start = if let Some(after) = after {
if let Some(after) = self.get_by_loc(&after) {
match self.binary_search(&list, after) {
Ok(i) => i, // TODO if after is exclusive then must add 1 here
Err(i) => i,
}
} else {
0
}
} else {
0
};
let index_end = if let Some(before) = before {
if let Some(before) = self.get_by_loc(&before) {
match self.binary_search(&list, before) {
Ok(i) => i + 1,
Err(i) => i,
}
} else {
list.len()
}
} else {
list.len()
};
nums.push(index_end - index_start);
// the next level should be counted only from
// the last element of the previous level on
if index_end != 0 {
after = Some(list[index_end - 1].location().unwrap());
}
}
nums
}
/// The total number pages.
pub fn pages(&self) -> NonZeroUsize {
NonZeroUsize::new(self.pages).unwrap_or(NonZeroUsize::ONE)

View File

@ -1,5 +1,7 @@
//! Interaction between document parts.
#[path = "count.rs"]
mod count_;
mod counter;
#[path = "here.rs"]
mod here_;
@ -14,6 +16,7 @@ mod query_;
mod state;
mod tag;
pub use self::count_::*;
pub use self::counter::*;
pub use self::here_::*;
pub use self::introspector::*;
@ -36,6 +39,7 @@ pub fn define(global: &mut Scope) {
global.define_elem::<MetadataElem>();
global.define_func::<here>();
global.define_func::<query>();
global.define_func::<count>();
global.define_func::<locate>();
global.reset_category();
}

View File

@ -476,26 +476,18 @@ impl Curve {
/// Computes the size of the bounding box of this curve.
pub fn bbox_size(&self) -> Size {
let mut min_x = Abs::inf();
let mut min_y = Abs::inf();
let mut max_x = -Abs::inf();
let mut max_y = -Abs::inf();
let mut min = Point::splat(Abs::inf());
let mut max = Point::splat(-Abs::inf());
let mut cursor = Point::zero();
for item in self.0.iter() {
match item {
CurveItem::Move(to) => {
min_x = min_x.min(cursor.x);
min_y = min_y.min(cursor.y);
max_x = max_x.max(cursor.x);
max_y = max_y.max(cursor.y);
cursor = *to;
}
CurveItem::Line(to) => {
min_x = min_x.min(cursor.x);
min_y = min_y.min(cursor.y);
max_x = max_x.max(cursor.x);
max_y = max_y.max(cursor.y);
min = min.min(cursor).min(*to);
max = max.max(cursor).max(*to);
cursor = *to;
}
CurveItem::Cubic(c0, c1, end) => {
@ -507,17 +499,17 @@ impl Curve {
);
let bbox = cubic.bounding_box();
min_x = min_x.min(Abs::pt(bbox.x0)).min(Abs::pt(bbox.x1));
min_y = min_y.min(Abs::pt(bbox.y0)).min(Abs::pt(bbox.y1));
max_x = max_x.max(Abs::pt(bbox.x0)).max(Abs::pt(bbox.x1));
max_y = max_y.max(Abs::pt(bbox.y0)).max(Abs::pt(bbox.y1));
min.x = min.x.min(Abs::pt(bbox.x0)).min(Abs::pt(bbox.x1));
min.y = min.y.min(Abs::pt(bbox.y0)).min(Abs::pt(bbox.y1));
max.x = max.x.max(Abs::pt(bbox.x0)).max(Abs::pt(bbox.x1));
max.y = max.y.max(Abs::pt(bbox.y0)).max(Abs::pt(bbox.y1));
cursor = *end;
}
CurveItem::Close => (),
}
}
Size::new(max_x - min_x, max_y - min_y)
Size::new(max.x - min.x, max.y - min.y)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -130,6 +130,16 @@
down, up, down, up, down,
)
--- curve-stroke-gradient-sharp ---
#set page(width: auto)
#let down = curve.line((40pt, 40pt), relative: true)
#let up = curve.line((40pt, -40pt), relative: true)
#curve(
stroke: 4pt + gradient.linear(red, blue).sharp(3),
down, up, down, up, down,
)
--- curve-fill-rule ---
#stack(
dir: ltr,