#![allow(clippy::too_many_arguments)]
use std::cell::Cell;
use std::ops::{Deref, MulAssign};
use std::rc::Rc;
use ecow::EcoString;
use typst_syntax::Span;
use typst_utils::{Get, default_math_class};
use unicode_math_class::MathClass;
use unicode_segmentation::UnicodeSegmentation;
use super::multiline::AlignedRow;
use crate::diag::SourceResult;
use crate::foundations::{Content, Packed, Smart, StyleChain};
use crate::introspection::{Locator, Tag};
use crate::layout::{
Abs, Axes, Axis, BoxElem, Em, FixedAlignment, Length, PlaceElem, Ratio, Rel,
};
use crate::math::{
Augment, CancelAngle, EquationElem, LeftRightAlternator, Limits, MathSize,
};
use crate::visualize::FixedStroke;
#[derive(Debug)]
pub(crate) enum RawMathItem<'a> {
Item(MathItem<'a>),
Linebreak,
Align,
}
impl<'a> From<MathItem<'a>> for RawMathItem<'a> {
fn from(item: MathItem<'a>) -> Self {
Self::Item(item)
}
}
impl<'a> RawMathItem<'a> {
pub(crate) fn is_ignorant(&self) -> bool {
match self {
Self::Item(item) => item.is_ignorant(),
Self::Linebreak | Self::Align => false,
}
}
pub(crate) fn into_item(self) -> Option<MathItem<'a>> {
match self {
Self::Item(item) => Some(item),
Self::Linebreak | Self::Align => None,
}
}
}
#[derive(Debug)]
pub enum MathItem<'a> {
Component(MathComponent<'a>),
Spacing(Length, Abs, bool),
Space,
Tag(Tag),
}
impl<'a> From<MathComponent<'a>> for MathItem<'a> {
fn from(comp: MathComponent<'a>) -> Self {
Self::Component(comp)
}
}
impl<'a> MathItem<'a> {
pub(crate) fn wrap(
mut items: Vec<MathItem<'a>>,
styles: StyleChain<'a>,
) -> MathItem<'a> {
if items.len() == 1 {
items.pop().unwrap()
} else {
GroupItem::create(items, styles)
}
}
pub(crate) fn limits(&self) -> Limits {
match self {
Self::Component(comp) => comp.props.limits,
_ => Limits::Never,
}
}
pub(crate) fn class(&self) -> MathClass {
self.raw_class().unwrap_or(MathClass::Normal)
}
pub(crate) fn raw_class(&self) -> Option<MathClass> {
match self {
Self::Component(comp) => comp.props.class,
Self::Spacing(..) | Self::Space => Some(MathClass::Space),
Self::Tag(_) => Some(MathClass::Special),
}
}
pub(crate) fn rclass(&self) -> MathClass {
match self {
Self::Component(MathComponent {
kind: MathKind::Fenced(fence),
props: MathProperties { class: None, .. },
..
}) if fence.close.is_some() => MathClass::Closing,
_ => self.class(),
}
}
pub(crate) fn lclass(&self) -> MathClass {
match self {
Self::Component(MathComponent {
kind: MathKind::Fenced(fence),
props: MathProperties { class: None, .. },
..
}) if fence.open.is_some() => MathClass::Opening,
_ => self.class(),
}
}
pub(crate) fn size(&self) -> Option<MathSize> {
match self {
Self::Component(comp) => Some(comp.props.size),
_ => None,
}
}
pub(crate) fn is_spaced(&self) -> bool {
if self.class() == MathClass::Fence {
return true;
}
if let Self::Component(comp) = self
&& comp.props.spaced
&& matches!(comp.props.class(), MathClass::Normal | MathClass::Alphabetic)
{
true
} else {
false
}
}
pub fn is_ignorant(&self) -> bool {
match self {
Self::Component(comp) => comp.props.ignorant,
Self::Tag(_) => true,
_ => false,
}
}
pub fn span(&self) -> Span {
match self {
Self::Component(comp) => comp.props.span,
_ => Span::detached(),
}
}
pub fn styles(&self) -> Option<StyleChain<'a>> {
match self {
Self::Component(comp) => Some(comp.styles),
_ => None,
}
}
pub fn mid_stretched(&self) -> Option<bool> {
if let Self::Component(comp) = self
&& let MathKind::Glyph(glyph) = &comp.kind
{
glyph.mid_stretched.get()
} else {
None
}
}
pub fn is_multiline(&self) -> bool {
matches!(
self,
MathItem::Component(MathComponent { kind: MathKind::Multiline(_), .. })
)
}
pub fn as_slice(&self) -> &[MathItem<'a>] {
if let MathItem::Component(comp) = self
&& let MathKind::Group(group) = &comp.kind
{
&group.items
} else {
core::slice::from_ref(self)
}
}
pub(crate) fn set_limits(&mut self, limits: Limits) {
if let Self::Component(comp) = self {
comp.props.limits = limits;
}
}
pub(crate) fn set_class(&mut self, class: MathClass) {
if let Self::Component(comp) = self {
comp.props.class = Some(class);
if let MathKind::Glyph(glyph) = &comp.kind
&& class == MathClass::Large
&& comp.props.size == MathSize::Display
&& !glyph.stretch.get().is_explicit(Axis::Y)
{
let info = StretchInfo::default();
glyph.stretch.update(|stretch| stretch.with_y(info));
}
}
}
pub(crate) fn set_lspace(&mut self, lspace: Option<Em>) {
if let Self::Component(comp) = self
&& comp.props.lspace.is_none()
{
comp.props.lspace = lspace;
}
}
pub(crate) fn set_rspace(&mut self, rspace: Option<Em>) {
if let Self::Component(comp) = self
&& comp.props.rspace.is_none()
{
comp.props.rspace = rspace;
}
}
pub(crate) fn with_multiline_centering(mut self) -> Self {
if let Self::Component(comp) = &mut self
&& let MathKind::Multiline(multiline) = &mut comp.kind
{
multiline.centered = true;
}
self
}
pub(crate) fn set_mid_stretched(&self, mid_stretched: Option<bool>) {
if let Self::Component(comp) = self
&& let MathKind::Glyph(glyph) = &comp.kind
{
glyph.mid_stretched.set(mid_stretched);
}
}
pub(crate) fn set_stretch(&self, mut stretch: Stretch) {
if let Some(info) = &mut stretch.0.x {
info.explicit = true;
}
if let Some(info) = &mut stretch.0.y {
info.explicit = true;
}
self.replace_stretch(stretch);
}
pub(crate) fn replace_stretch(&self, stretch: Stretch) {
if let Self::Component(comp) = self
&& let MathKind::Glyph(glyph) = &comp.kind
{
glyph.stretch.replace(stretch);
}
}
pub(crate) fn set_y_stretch(&self, mut info: StretchInfo) {
if let Self::Component(comp) = self
&& let MathKind::Glyph(glyph) = &comp.kind
{
info.explicit = true;
glyph.stretch.update(|stretch| stretch.with_y(info));
}
}
pub(crate) fn update_stretch(&self, info: StretchInfo) {
if let Self::Component(comp) = self
&& let MathKind::Glyph(glyph) = &comp.kind
{
glyph.stretch.update(|stretch| stretch.update(info));
}
}
pub fn set_stretch_relative_to(&self, relative_to: Abs, axis: Axis) {
if let Self::Component(comp) = self
&& let MathKind::Glyph(glyph) = &comp.kind
{
glyph.stretch.update(|stretch| stretch.relative_to(relative_to, axis));
}
}
pub fn set_stretch_font_size(&self, font_size: Abs, axis: Axis) {
if let Self::Component(comp) = self
&& let MathKind::Glyph(glyph) = &comp.kind
{
glyph.stretch.update(|stretch| stretch.font_size(font_size, axis));
}
}
pub fn set_flac(&self) {
if let Self::Component(comp) = self
&& let MathKind::Glyph(glyph) = &comp.kind
{
glyph.flac.set(true);
}
}
}
#[derive(Debug)]
pub struct MathComponent<'a> {
pub kind: MathKind<'a>,
pub props: MathProperties,
pub styles: StyleChain<'a>,
}
#[derive(Debug)]
pub enum MathKind<'a> {
Group(GroupItem<'a>),
Multiline(MultilineItem<'a>),
Radical(Box<RadicalItem<'a>>),
Fenced(Box<FencedItem<'a>>),
Fraction(Box<FractionItem<'a>>),
SkewedFraction(Box<SkewedFractionItem<'a>>),
Table(Box<TableItem<'a>>),
Scripts(Box<ScriptsItem<'a>>),
Accent(Box<AccentItem<'a>>),
Cancel(Box<CancelItem<'a>>),
Line(Box<LineItem<'a>>),
Primes(Box<PrimesItem>),
Text(TextItem<'a>),
Number(NumberItem),
Glyph(Box<GlyphItem>),
Box(BoxItem<'a>),
Mathml(Box<MathmlItem<'a>>),
External(ExternalItem<'a>),
}
#[derive(Debug, Copy, Clone)]
pub struct MathProperties {
pub(crate) limits: Limits,
pub class: Option<MathClass>,
pub size: MathSize,
pub cramped: bool,
pub(crate) ignorant: bool,
pub(crate) spaced: bool,
pub lspace: Option<Em>,
pub rspace: Option<Em>,
pub align_form_infix: bool,
pub span: Span,
}
impl MathProperties {
fn new(styles: StyleChain, class: Option<MathClass>, span: Span) -> MathProperties {
Self {
limits: Limits::Never,
class,
size: styles.get(EquationElem::size),
cramped: styles.get(EquationElem::cramped),
ignorant: false,
spaced: false,
lspace: None,
rspace: None,
align_form_infix: false,
span,
}
}
pub fn default(styles: StyleChain, span: Span) -> MathProperties {
Self::new(styles, None, span)
}
pub fn class(&self) -> MathClass {
self.class.unwrap_or(MathClass::Normal)
}
fn with_limits(mut self, limits: Limits) -> Self {
self.limits = limits;
self
}
fn with_ignorant(mut self, ignorant: bool) -> Self {
self.ignorant = ignorant;
self
}
fn with_spaced(mut self, spaced: bool) -> Self {
self.spaced = spaced;
self
}
}
#[derive(Debug)]
pub struct GroupItem<'a> {
pub items: Vec<MathItem<'a>>,
}
impl<'a> GroupItem<'a> {
pub(crate) fn create(
items: Vec<MathItem<'a>>,
styles: StyleChain<'a>,
) -> MathItem<'a> {
let props = MathProperties::default(styles, Span::detached());
let kind = MathKind::Group(Self { items });
MathComponent { kind, props, styles }.into()
}
}
#[derive(Debug)]
pub struct MultilineItem<'a> {
pub rows: Vec<AlignedRow<'a>>,
pub centered: bool,
}
impl<'a> MultilineItem<'a> {
pub(crate) fn create(
rows: Vec<AlignedRow<'a>>,
styles: StyleChain<'a>,
) -> MathItem<'a> {
let kind = MathKind::Multiline(Self { rows, centered: false });
let props = MathProperties::default(styles, Span::detached());
MathComponent { kind, props, styles }.into()
}
}
#[derive(Debug)]
pub struct RadicalItem<'a> {
pub radicand: MathItem<'a>,
pub index: Option<MathItem<'a>>,
pub sqrt: MathItem<'a>,
}
impl<'a> RadicalItem<'a> {
pub(crate) fn create(
radicand: MathItem<'a>,
index: Option<MathItem<'a>>,
sqrt: MathItem<'a>,
styles: StyleChain<'a>,
span: Span,
) -> MathItem<'a> {
let kind = MathKind::Radical(Box::new(Self { radicand, index, sqrt }));
let props = MathProperties::default(styles, span);
MathComponent { kind, props, styles }.into()
}
}
#[derive(Debug)]
pub struct FencedItem<'a> {
pub open: Option<MathItem<'a>>,
pub close: Option<MathItem<'a>>,
pub body: FencedBody<'a>,
pub balanced: bool,
}
impl<'a> FencedItem<'a> {
pub(crate) fn create(
open: Option<MathItem<'a>>,
close: Option<MathItem<'a>>,
body: impl Into<FencedBody<'a>>,
balanced: bool,
styles: StyleChain<'a>,
span: Span,
) -> MathItem<'a> {
let kind =
MathKind::Fenced(Box::new(Self { open, close, body: body.into(), balanced }));
let props = MathProperties::default(styles, span);
MathComponent { kind, props, styles }.into()
}
}
#[derive(Debug)]
pub struct FractionItem<'a> {
pub numerator: MathItem<'a>,
pub denominator: MathItem<'a>,
pub line: bool,
pub padding: Em,
}
impl<'a> FractionItem<'a> {
pub(crate) fn create(
numerator: MathItem<'a>,
denominator: MathItem<'a>,
line: bool,
padding: Em,
styles: StyleChain<'a>,
span: Span,
) -> MathItem<'a> {
let kind =
MathKind::Fraction(Box::new(Self { numerator, denominator, line, padding }));
let props = MathProperties::default(styles, span);
MathComponent { kind, props, styles }.into()
}
}
#[derive(Debug)]
pub struct SkewedFractionItem<'a> {
pub numerator: MathItem<'a>,
pub denominator: MathItem<'a>,
pub slash: MathItem<'a>,
}
impl<'a> SkewedFractionItem<'a> {
pub(crate) fn create(
numerator: MathItem<'a>,
denominator: MathItem<'a>,
slash: MathItem<'a>,
styles: StyleChain<'a>,
span: Span,
) -> MathItem<'a> {
let kind =
MathKind::SkewedFraction(Box::new(Self { numerator, denominator, slash }));
let props = MathProperties::default(styles, span);
MathComponent { kind, props, styles }.into()
}
}
#[derive(Debug)]
pub struct TableItem<'a> {
pub cells: Vec<Vec<AlignedRow<'a>>>,
pub gap: Axes<Rel<Abs>>,
pub augment: Option<Augment<Abs>>,
pub align: FixedAlignment,
pub alternator: LeftRightAlternator,
}
impl<'a> TableItem<'a> {
pub(crate) fn create(
cells: Vec<Vec<AlignedRow<'a>>>,
gap: Axes<Rel<Abs>>,
augment: Option<Augment<Abs>>,
align: FixedAlignment,
alternator: LeftRightAlternator,
styles: StyleChain<'a>,
span: Span,
) -> MathItem<'a> {
let kind =
MathKind::Table(Box::new(Self { cells, gap, augment, align, alternator }));
let props = MathProperties::default(styles, span);
MathComponent { kind, props, styles }.into()
}
}
#[derive(Debug)]
pub struct ScriptsItem<'a> {
pub base: MathItem<'a>,
pub top: Option<MathItem<'a>>,
pub bottom: Option<MathItem<'a>>,
pub top_left: Option<MathItem<'a>>,
pub bottom_left: Option<MathItem<'a>>,
pub top_right: Option<MathItem<'a>>,
pub bottom_right: Option<MathItem<'a>>,
}
impl<'a> ScriptsItem<'a> {
pub(crate) fn create(
base: MathItem<'a>,
top: Option<MathItem<'a>>,
bottom: Option<MathItem<'a>>,
top_left: Option<MathItem<'a>>,
bottom_left: Option<MathItem<'a>>,
top_right: Option<MathItem<'a>>,
bottom_right: Option<MathItem<'a>>,
styles: StyleChain<'a>,
) -> MathItem<'a> {
let props = MathProperties::new(styles, base.raw_class(), Span::detached());
let kind = MathKind::Scripts(Box::new(Self {
base,
top,
bottom,
top_left,
bottom_left,
top_right,
bottom_right,
}));
MathComponent { kind, props, styles }.into()
}
}
#[derive(Debug)]
pub struct AccentItem<'a> {
pub base: MathItem<'a>,
pub accent: MathItem<'a>,
pub position: Position,
pub dotless: bool,
pub exact_frame_width: bool,
}
impl<'a> AccentItem<'a> {
pub(crate) fn create(
base: MathItem<'a>,
accent: MathItem<'a>,
position: Position,
dotless: bool,
exact_frame_width: bool,
styles: StyleChain<'a>,
) -> MathItem<'a> {
let props = MathProperties::new(styles, base.raw_class(), Span::detached());
let kind = MathKind::Accent(Box::new(Self {
base,
accent,
position,
dotless,
exact_frame_width,
}));
MathComponent { kind, props, styles }.into()
}
}
#[derive(Debug)]
pub struct CancelItem<'a> {
pub base: MathItem<'a>,
pub length: Rel<Abs>,
pub stroke: FixedStroke,
pub cross: bool,
pub invert_first_line: bool,
pub angle: Smart<CancelAngle>,
}
impl<'a> CancelItem<'a> {
pub(crate) fn create(
base: MathItem<'a>,
length: Rel<Abs>,
stroke: FixedStroke,
cross: bool,
invert_first_line: bool,
angle: Smart<CancelAngle>,
styles: StyleChain<'a>,
span: Span,
) -> MathItem<'a> {
let props = MathProperties::new(styles, base.raw_class(), span);
let kind = MathKind::Cancel(Box::new(Self {
base,
length,
stroke,
cross,
invert_first_line,
angle,
}));
MathComponent { kind, props, styles }.into()
}
}
#[derive(Debug)]
pub struct LineItem<'a> {
pub base: MathItem<'a>,
pub position: Position,
}
impl<'a> LineItem<'a> {
pub(crate) fn create(
base: MathItem<'a>,
position: Position,
styles: StyleChain<'a>,
span: Span,
) -> MathItem<'a> {
let props = MathProperties::new(styles, base.raw_class(), span);
let kind = MathKind::Line(Box::new(Self { base, position }));
MathComponent { kind, props, styles }.into()
}
}
pub const PRIME_CHAR: char = '′';
#[derive(Debug)]
pub struct PrimesItem {
pub count: usize,
}
impl PrimesItem {
pub(crate) fn create<'a>(count: usize, styles: StyleChain<'a>) -> MathItem<'a> {
let kind = MathKind::Primes(Box::new(Self { count }));
let props = MathProperties::default(styles, Span::detached());
MathComponent { kind, props, styles }.into()
}
}
#[derive(Debug)]
pub struct TextItem<'a> {
pub text: EcoString,
pub locator: Locator<'a>,
}
impl<'a> TextItem<'a> {
pub(crate) fn create(
text: EcoString,
styles: StyleChain<'a>,
span: Span,
locator: Locator<'a>,
) -> MathItem<'a> {
let kind = MathKind::Text(Self { text, locator });
let props = MathProperties::new(styles, Some(MathClass::Alphabetic), span)
.with_spaced(true);
MathComponent { kind, props, styles }.into()
}
}
#[derive(Debug)]
pub struct NumberItem {
pub text: EcoString,
}
impl NumberItem {
pub(crate) fn create<'a>(
text: EcoString,
styles: StyleChain<'a>,
span: Span,
) -> MathItem<'a> {
let kind = MathKind::Number(Self { text });
let props = MathProperties::default(styles, span);
MathComponent { kind, props, styles }.into()
}
}
#[derive(Debug)]
pub struct GlyphItem {
pub text: EcoString,
pub stretch: Cell<Stretch>,
pub mid_stretched: Cell<Option<bool>>,
pub flac: Cell<bool>,
}
impl GlyphItem {
pub(crate) fn create<'a>(
text: EcoString,
styles: StyleChain<'a>,
span: Span,
) -> MathItem<'a> {
assert!(text.graphemes(true).count() == 1);
let c = text.chars().next().unwrap();
let class = default_math_class(c);
let limits = Limits::for_char_with_class(c, class);
let kind = MathKind::Glyph(Box::new(Self {
text,
stretch: Cell::new(Stretch::new()),
mid_stretched: Cell::new(None),
flac: Cell::new(false),
}));
let props = MathProperties::new(styles, class, span).with_limits(limits);
MathComponent { kind, props, styles }.into()
}
}
#[derive(Debug)]
pub struct BoxItem<'a> {
pub elem: &'a Packed<BoxElem>,
pub locator: Locator<'a>,
}
impl<'a> BoxItem<'a> {
pub(crate) fn create(
elem: &'a Packed<BoxElem>,
styles: StyleChain<'a>,
locator: Locator<'a>,
) -> MathItem<'a> {
let kind = MathKind::Box(Self { elem, locator });
let props = MathProperties::default(styles, elem.span()).with_spaced(true);
MathComponent { kind, props, styles }.into()
}
}
#[derive(Debug)]
pub struct MathmlItem<'a> {
pub elem: &'a Content,
pub body: Option<MathItem<'a>>,
}
impl<'a> MathmlItem<'a> {
pub(crate) fn create(
elem: &'a Content,
body: Option<MathItem<'a>>,
styles: StyleChain<'a>,
) -> MathItem<'a> {
let kind = MathKind::Mathml(Box::new(Self { elem, body }));
let props = MathProperties::default(styles, elem.span());
MathComponent { kind, props, styles }.into()
}
}
#[derive(Debug)]
pub struct ExternalItem<'a> {
pub content: &'a Content,
pub locator: Locator<'a>,
}
impl<'a> ExternalItem<'a> {
pub(crate) fn create(
content: &'a Content,
styles: StyleChain<'a>,
locator: Locator<'a>,
) -> MathItem<'a> {
let kind = MathKind::External(Self { content, locator });
let props = MathProperties::default(styles, content.span())
.with_spaced(true)
.with_ignorant(content.is::<PlaceElem>());
MathComponent { kind, props, styles }.into()
}
}
#[derive(Debug)]
pub struct SharedFenceSizing<'a> {
items: Vec<MathItem<'a>>,
relative_to: Cell<Option<Abs>>,
styles: StyleChain<'a>,
}
impl<'a> SharedFenceSizing<'a> {
pub(crate) fn new(items: Vec<MathItem<'a>>, styles: StyleChain<'a>) -> Rc<Self> {
Rc::new(Self { items, relative_to: Cell::new(None), styles })
}
pub fn try_get_or_update(
&self,
f: impl FnOnce(&[MathItem<'a>], StyleChain<'a>) -> SourceResult<Abs>,
) -> SourceResult<Abs> {
Ok(if let Some(relative_to) = self.relative_to.get() {
relative_to
} else {
let relative_to = f(&self.items, self.styles)?;
self.relative_to.set(Some(relative_to));
relative_to
})
}
}
#[derive(Debug)]
pub enum FencedBody<'a> {
Owned(MathItem<'a>),
Shared { index: usize, sizing: Rc<SharedFenceSizing<'a>> },
}
impl<'a> FencedBody<'a> {
pub(crate) fn shared(index: usize, sizing: Rc<SharedFenceSizing<'a>>) -> Self {
Self::Shared { index, sizing }
}
pub fn sizing(&self) -> Option<&SharedFenceSizing<'a>> {
match self {
Self::Owned(_) => None,
Self::Shared { sizing, .. } => Some(sizing),
}
}
}
impl<'a> From<MathItem<'a>> for FencedBody<'a> {
fn from(item: MathItem<'a>) -> Self {
Self::Owned(item)
}
}
impl<'a> Deref for FencedBody<'a> {
type Target = MathItem<'a>;
fn deref(&self) -> &Self::Target {
match self {
Self::Owned(item) => item,
Self::Shared { index, sizing } => &sizing.items[*index],
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Stretch(Axes<Option<StretchInfo>>);
impl Stretch {
pub(crate) fn new() -> Self {
Self(Axes::splat(None))
}
pub(crate) fn with_x(mut self, info: StretchInfo) -> Self {
self.0.x = Some(info);
self
}
pub(crate) fn with_y(mut self, info: StretchInfo) -> Self {
self.0.y = Some(info);
self
}
pub(crate) fn update(mut self, mut info: StretchInfo) -> Self {
info.explicit = true;
match &mut self.0.x {
Some(val) => *val *= info,
None => self.0.x = Some(info),
}
match &mut self.0.y {
Some(val) => *val *= info,
None => self.0.y = Some(info),
}
self
}
pub(crate) fn relative_to(mut self, relative_to: Abs, axis: Axis) -> Self {
if let Some(info) = self.0.get_mut(axis)
&& info.relative_to.is_none()
{
info.relative_to = Some(relative_to);
}
self
}
pub(crate) fn font_size(mut self, font_size: Abs, axis: Axis) -> Self {
if let Some(info) = self.0.get_mut(axis)
&& info.font_size.is_none()
{
info.font_size = Some(font_size);
}
self
}
pub fn resolve(mut self, axis: Axis) -> Option<StretchInfo> {
if let Some(info) = self.0.get_mut(axis)
&& let Some(buffer) = info.buffer
{
if info.relative_to.is_some() {
info.target = buffer;
} else {
info.target = Rel::new(
info.target.rel * buffer.rel,
buffer.rel.of(info.target.abs) + buffer.abs,
);
}
}
self.0.get(axis)
}
pub fn resolve_requested(self, axis: Axis) -> Option<Rel<Length>> {
self.0
.get(axis)
.and_then(|info| info.requested_target)
.filter(|target| !target.is_one())
}
pub fn is_explicit(self, axis: Axis) -> bool {
self.0.get(axis).is_some_and(|info| info.explicit)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct StretchInfo {
pub target: Rel<Abs>,
buffer: Option<Rel<Abs>>,
pub(crate) explicit: bool,
pub(crate) requested_target: Option<Rel<Length>>,
pub short_fall: Em,
pub relative_to: Option<Abs>,
pub font_size: Option<Abs>,
}
impl StretchInfo {
pub(crate) fn new(target: Rel<Abs>, short_fall: Em) -> Self {
Self {
target,
buffer: None,
explicit: false,
requested_target: None,
short_fall,
relative_to: None,
font_size: None,
}
}
pub(crate) fn from_size(size: Rel<Length>, short_fall: Em, font_size: Abs) -> Self {
Self {
target: size.map(|l| l.at(font_size)),
buffer: None,
explicit: false,
requested_target: (!size.is_one()).then_some(size),
short_fall,
relative_to: None,
font_size: None,
}
}
}
impl Default for StretchInfo {
fn default() -> Self {
let target = Rel::new(Ratio::one(), Abs::zero());
Self::new(target, Em::zero())
}
}
impl MulAssign for StretchInfo {
fn mul_assign(&mut self, rhs: Self) {
if let Some(buffer) = self.buffer {
self.target = Rel::new(
self.target.rel * buffer.rel,
buffer.rel.of(self.target.abs) + buffer.abs,
);
}
self.buffer = Some(rhs.target);
if let Some(requested) = rhs.requested_target {
self.requested_target =
Some(self.requested_target.map_or(requested, |target| {
Rel::new(
target.rel * requested.rel,
requested.rel.of(target.abs) + requested.abs,
)
}));
}
self.explicit = self.explicit || rhs.explicit;
self.short_fall = rhs.short_fall;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Position {
Above,
Below,
}