mirror of
https://github.com/typst/typst
synced 2025-05-18 11:05:28 +08:00
Add vertical alignment to equation number [Better Equation Numbering Pt.3] (#3482)
This commit is contained in:
parent
7ed257a3c7
commit
e9ee00a7c0
@ -126,7 +126,7 @@ impl Alignment {
|
|||||||
pub fn fix(self, text_dir: Dir) -> Axes<FixedAlignment> {
|
pub fn fix(self, text_dir: Dir) -> Axes<FixedAlignment> {
|
||||||
Axes::new(
|
Axes::new(
|
||||||
self.x().unwrap_or_default().fix(text_dir),
|
self.x().unwrap_or_default().fix(text_dir),
|
||||||
self.y().unwrap_or_default().fix(),
|
self.y().unwrap_or_default().fix(text_dir),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -244,6 +244,12 @@ impl From<Side> for Alignment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Alignment on this axis can be fixed to an absolute direction.
|
||||||
|
pub trait FixAlignment {
|
||||||
|
/// Resolve to the absolute alignment.
|
||||||
|
fn fix(self, dir: Dir) -> FixedAlignment;
|
||||||
|
}
|
||||||
|
|
||||||
/// Where to align something horizontally.
|
/// Where to align something horizontally.
|
||||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum HAlignment {
|
pub enum HAlignment {
|
||||||
@ -266,9 +272,10 @@ impl HAlignment {
|
|||||||
Self::End => Self::Start,
|
Self::End => Self::Start,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Resolve the axis alignment based on the horizontal direction.
|
impl FixAlignment for HAlignment {
|
||||||
pub const fn fix(self, dir: Dir) -> FixedAlignment {
|
fn fix(self, dir: Dir) -> FixedAlignment {
|
||||||
match (self, dir.is_positive()) {
|
match (self, dir.is_positive()) {
|
||||||
(Self::Start, true) | (Self::End, false) => FixedAlignment::Start,
|
(Self::Start, true) | (Self::End, false) => FixedAlignment::Start,
|
||||||
(Self::Left, _) => FixedAlignment::Start,
|
(Self::Left, _) => FixedAlignment::Start,
|
||||||
@ -344,9 +351,8 @@ pub enum OuterHAlignment {
|
|||||||
End,
|
End,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OuterHAlignment {
|
impl FixAlignment for OuterHAlignment {
|
||||||
/// Resolve the axis alignment based on the horizontal direction.
|
fn fix(self, dir: Dir) -> FixedAlignment {
|
||||||
pub const fn fix(self, dir: Dir) -> FixedAlignment {
|
|
||||||
match (self, dir.is_positive()) {
|
match (self, dir.is_positive()) {
|
||||||
(Self::Start, true) | (Self::End, false) => FixedAlignment::Start,
|
(Self::Start, true) | (Self::End, false) => FixedAlignment::Start,
|
||||||
(Self::Left, _) => FixedAlignment::Start,
|
(Self::Left, _) => FixedAlignment::Start,
|
||||||
@ -413,9 +419,11 @@ impl VAlignment {
|
|||||||
Self::Bottom => Self::Top,
|
Self::Bottom => Self::Top,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Turns into a fixed alignment.
|
impl FixAlignment for VAlignment {
|
||||||
pub const fn fix(self) -> FixedAlignment {
|
fn fix(self, _: Dir) -> FixedAlignment {
|
||||||
|
// The vertical alignment does not depend on text direction.
|
||||||
match self {
|
match self {
|
||||||
Self::Top => FixedAlignment::Start,
|
Self::Top => FixedAlignment::Start,
|
||||||
Self::Horizon => FixedAlignment::Center,
|
Self::Horizon => FixedAlignment::Center,
|
||||||
@ -442,6 +450,14 @@ impl Add<HAlignment> for VAlignment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Resolve for VAlignment {
|
||||||
|
type Output = FixedAlignment;
|
||||||
|
|
||||||
|
fn resolve(self, _: StyleChain) -> Self::Output {
|
||||||
|
self.fix(Dir::TTB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<VAlignment> for Alignment {
|
impl From<VAlignment> for Alignment {
|
||||||
fn from(align: VAlignment) -> Self {
|
fn from(align: VAlignment) -> Self {
|
||||||
Self::V(align)
|
Self::V(align)
|
||||||
@ -474,9 +490,9 @@ pub enum OuterVAlignment {
|
|||||||
Bottom,
|
Bottom,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OuterVAlignment {
|
impl FixAlignment for OuterVAlignment {
|
||||||
/// Resolve the axis alignment based on the vertical direction.
|
fn fix(self, _: Dir) -> FixedAlignment {
|
||||||
pub const fn fix(self) -> FixedAlignment {
|
// The vertical alignment does not depend on text direction.
|
||||||
match self {
|
match self {
|
||||||
Self::Top => FixedAlignment::Start,
|
Self::Top => FixedAlignment::Start,
|
||||||
Self::Bottom => FixedAlignment::End,
|
Self::Bottom => FixedAlignment::End,
|
||||||
@ -526,8 +542,8 @@ pub enum SpecificAlignment<H, V> {
|
|||||||
|
|
||||||
impl<H, V> SpecificAlignment<H, V>
|
impl<H, V> SpecificAlignment<H, V>
|
||||||
where
|
where
|
||||||
H: Copy,
|
H: Default + Copy + FixAlignment,
|
||||||
V: Copy,
|
V: Default + Copy + FixAlignment,
|
||||||
{
|
{
|
||||||
/// The horizontal component.
|
/// The horizontal component.
|
||||||
pub const fn x(self) -> Option<H> {
|
pub const fn x(self) -> Option<H> {
|
||||||
@ -544,6 +560,26 @@ where
|
|||||||
Self::H(_) => None,
|
Self::H(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Normalize the alignment to a LTR-TTB space.
|
||||||
|
pub fn fix(self, text_dir: Dir) -> Axes<FixedAlignment> {
|
||||||
|
Axes::new(
|
||||||
|
self.x().unwrap_or_default().fix(text_dir),
|
||||||
|
self.y().unwrap_or_default().fix(text_dir),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H, V> Resolve for SpecificAlignment<H, V>
|
||||||
|
where
|
||||||
|
H: Default + Copy + FixAlignment,
|
||||||
|
V: Default + Copy + FixAlignment,
|
||||||
|
{
|
||||||
|
type Output = Axes<FixedAlignment>;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
self.fix(TextElem::dir_in(styles))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<H, V> From<SpecificAlignment<H, V>> for Alignment
|
impl<H, V> From<SpecificAlignment<H, V>> for Alignment
|
||||||
@ -631,6 +667,15 @@ impl FixedAlignment {
|
|||||||
Self::End => extent,
|
Self::End => extent,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The inverse alignment.
|
||||||
|
pub const fn inv(self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Start => Self::End,
|
||||||
|
Self::Center => Self::Center,
|
||||||
|
Self::End => Self::Start,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Side> for FixedAlignment {
|
impl From<Side> for FixedAlignment {
|
||||||
|
@ -9,7 +9,7 @@ use crate::introspection::{Meta, MetaElem};
|
|||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, AlignElem, Axes, BlockElem, ColbreakElem, ColumnsElem, FixedAlignment, Fr,
|
Abs, AlignElem, Axes, BlockElem, ColbreakElem, ColumnsElem, FixedAlignment, Fr,
|
||||||
Fragment, Frame, FrameItem, LayoutMultiple, LayoutSingle, PlaceElem, Point, Regions,
|
Fragment, Frame, FrameItem, LayoutMultiple, LayoutSingle, PlaceElem, Point, Regions,
|
||||||
Rel, Size, Spacing, VAlignment, VElem,
|
Rel, Size, Spacing, VElem,
|
||||||
};
|
};
|
||||||
use crate::model::{FootnoteElem, FootnoteEntry, ParElem};
|
use crate::model::{FootnoteElem, FootnoteEntry, ParElem};
|
||||||
use crate::util::Numeric;
|
use crate::util::Numeric;
|
||||||
@ -311,7 +311,7 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
let x_align = alignment.map_or(FixedAlignment::Center, |align| {
|
let x_align = alignment.map_or(FixedAlignment::Center, |align| {
|
||||||
align.x().unwrap_or_default().resolve(styles)
|
align.x().unwrap_or_default().resolve(styles)
|
||||||
});
|
});
|
||||||
let y_align = alignment.map(|align| align.y().map(VAlignment::fix));
|
let y_align = alignment.map(|align| align.y().map(|y| y.resolve(styles)));
|
||||||
let mut frame = placed.layout(engine, styles, self.regions)?.into_frame();
|
let mut frame = placed.layout(engine, styles, self.regions)?.into_frame();
|
||||||
frame.meta(styles, false);
|
frame.meta(styles, false);
|
||||||
let item = FlowItem::Placed { frame, x_align, y_align, delta, float, clearance };
|
let item = FlowItem::Placed { frame, x_align, y_align, delta, float, clearance };
|
||||||
|
@ -255,14 +255,18 @@ impl Frame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resize the frame to a new size, distributing new space according to the
|
/// Adjust the frame's size, translate the original content by an offset
|
||||||
/// given alignments.
|
/// computed according to the given alignments, and return the amount of
|
||||||
pub fn resize(&mut self, target: Size, align: Axes<FixedAlignment>) {
|
/// offset.
|
||||||
if self.size != target {
|
pub fn resize(&mut self, target: Size, align: Axes<FixedAlignment>) -> Point {
|
||||||
let offset = align.zip_map(target - self.size, FixedAlignment::position);
|
if self.size == target {
|
||||||
self.size = target;
|
return Point::zero();
|
||||||
self.translate(offset.to_point());
|
|
||||||
}
|
}
|
||||||
|
let offset =
|
||||||
|
align.zip_map(target - self.size, FixedAlignment::position).to_point();
|
||||||
|
self.size = target;
|
||||||
|
self.translate(offset);
|
||||||
|
offset
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move the baseline and contents of the frame by an offset.
|
/// Move the baseline and contents of the frame by an offset.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::foundations::{elem, Packed, StyleChain};
|
use crate::foundations::{elem, Packed, StyleChain};
|
||||||
use crate::layout::Abs;
|
use crate::layout::Abs;
|
||||||
use crate::math::{LayoutMath, MathContext, MathFragment, MathRow};
|
use crate::math::{LayoutMath, MathContext, MathFragment, MathRun};
|
||||||
|
|
||||||
/// A math alignment point: `&`, `&&`.
|
/// A math alignment point: `&`, `&&`.
|
||||||
#[elem(title = "Alignment Point", LayoutMath)]
|
#[elem(title = "Alignment Point", LayoutMath)]
|
||||||
@ -20,7 +20,7 @@ pub(super) struct AlignmentResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Determine the positions of the alignment points, according to the input rows combined.
|
/// Determine the positions of the alignment points, according to the input rows combined.
|
||||||
pub(super) fn alignments(rows: &[MathRow]) -> AlignmentResult {
|
pub(super) fn alignments(rows: &[MathRun]) -> AlignmentResult {
|
||||||
let mut widths = Vec::<Abs>::new();
|
let mut widths = Vec::<Abs>::new();
|
||||||
|
|
||||||
let mut pending_width = Abs::zero();
|
let mut pending_width = Abs::zero();
|
||||||
|
@ -15,7 +15,7 @@ use crate::foundations::{Content, Packed, Smart, StyleChain};
|
|||||||
use crate::layout::{Abs, Axes, BoxElem, Em, Frame, LayoutMultiple, Regions, Size};
|
use crate::layout::{Abs, Axes, BoxElem, Em, Frame, LayoutMultiple, Regions, Size};
|
||||||
use crate::math::{
|
use crate::math::{
|
||||||
scaled_font_size, styled_char, EquationElem, FrameFragment, GlyphFragment,
|
scaled_font_size, styled_char, EquationElem, FrameFragment, GlyphFragment,
|
||||||
LayoutMath, MathFragment, MathRow, MathSize, THICK,
|
LayoutMath, MathFragment, MathRun, MathSize, THICK,
|
||||||
};
|
};
|
||||||
use crate::model::ParElem;
|
use crate::model::ParElem;
|
||||||
use crate::syntax::{is_newline, Span};
|
use crate::syntax::{is_newline, Span};
|
||||||
@ -122,7 +122,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
|||||||
self.fragments.extend(fragments);
|
self.fragments.extend(fragments);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout the given element and return the resulting `MathFragment`s.
|
/// Layout the given element and return the resulting [`MathFragment`]s.
|
||||||
pub fn layout_into_fragments(
|
pub fn layout_into_fragments(
|
||||||
&mut self,
|
&mut self,
|
||||||
elem: &dyn LayoutMath,
|
elem: &dyn LayoutMath,
|
||||||
@ -137,27 +137,26 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
|||||||
Ok(std::mem::replace(&mut self.fragments, prev))
|
Ok(std::mem::replace(&mut self.fragments, prev))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout the given element and return the result as a `MathRow`.
|
/// Layout the given element and return the result as a [`MathRun`].
|
||||||
pub fn layout_into_row(
|
pub fn layout_into_run(
|
||||||
&mut self,
|
&mut self,
|
||||||
elem: &dyn LayoutMath,
|
elem: &dyn LayoutMath,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<MathRow> {
|
) -> SourceResult<MathRun> {
|
||||||
let row = self.layout_into_fragments(elem, styles)?;
|
Ok(MathRun::new(self.layout_into_fragments(elem, styles)?))
|
||||||
Ok(MathRow::new(row))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout the given element and return the result as a
|
/// Layout the given element and return the result as a
|
||||||
/// unified `MathFragment`.
|
/// unified [`MathFragment`].
|
||||||
pub fn layout_into_fragment(
|
pub fn layout_into_fragment(
|
||||||
&mut self,
|
&mut self,
|
||||||
elem: &dyn LayoutMath,
|
elem: &dyn LayoutMath,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<MathFragment> {
|
) -> SourceResult<MathFragment> {
|
||||||
Ok(self.layout_into_row(elem, styles)?.into_fragment(self, styles))
|
Ok(self.layout_into_run(elem, styles)?.into_fragment(self, styles))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout the given element and return the result as a `Frame`.
|
/// Layout the given element and return the result as a [`Frame`].
|
||||||
pub fn layout_into_frame(
|
pub fn layout_into_frame(
|
||||||
&mut self,
|
&mut self,
|
||||||
elem: &dyn LayoutMath,
|
elem: &dyn LayoutMath,
|
||||||
@ -166,7 +165,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
|||||||
Ok(self.layout_into_fragment(elem, styles)?.into_frame())
|
Ok(self.layout_into_fragment(elem, styles)?.into_frame())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout the given `BoxElem` into a `Frame`.
|
/// Layout the given [`BoxElem`] into a [`Frame`].
|
||||||
pub fn layout_box(
|
pub fn layout_box(
|
||||||
&mut self,
|
&mut self,
|
||||||
boxed: &Packed<BoxElem>,
|
boxed: &Packed<BoxElem>,
|
||||||
@ -177,7 +176,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
|||||||
boxed.layout(self.engine, styles.chain(&local), self.regions)
|
boxed.layout(self.engine, styles.chain(&local), self.regions)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout the given `Content` into a `Frame`.
|
/// Layout the given [`Content`] into a [`Frame`].
|
||||||
pub fn layout_content(
|
pub fn layout_content(
|
||||||
&mut self,
|
&mut self,
|
||||||
content: &Content,
|
content: &Content,
|
||||||
@ -190,7 +189,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
|||||||
.into_frame())
|
.into_frame())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout the given `TextElem` into a `MathFragment`.
|
/// Layout the given [`TextElem`] into a [`MathFragment`].
|
||||||
pub fn layout_text(
|
pub fn layout_text(
|
||||||
&mut self,
|
&mut self,
|
||||||
elem: &Packed<TextElem>,
|
elem: &Packed<TextElem>,
|
||||||
@ -238,7 +237,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
|||||||
let c = styled_char(styles, c);
|
let c = styled_char(styles, c);
|
||||||
fragments.push(GlyphFragment::new(self, styles, c, span).into());
|
fragments.push(GlyphFragment::new(self, styles, c, span).into());
|
||||||
}
|
}
|
||||||
let frame = MathRow::new(fragments).into_frame(self, styles);
|
let frame = MathRun::new(fragments).into_frame(self, styles);
|
||||||
FrameFragment::new(self, styles, frame).with_text_like(true).into()
|
FrameFragment::new(self, styles, frame).with_text_like(true).into()
|
||||||
} else {
|
} else {
|
||||||
let local = [
|
let local = [
|
||||||
@ -263,7 +262,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
|||||||
.push(self.layout_complex_text(piece, span, styles)?.into());
|
.push(self.layout_complex_text(piece, span, styles)?.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut frame = MathRow::new(fragments).into_frame(self, styles);
|
let mut frame = MathRun::new(fragments).into_frame(self, styles);
|
||||||
let axis = scaled!(self, styles, axis_height);
|
let axis = scaled!(self, styles, axis_height);
|
||||||
frame.set_baseline(frame.height() / 2.0 + axis);
|
frame.set_baseline(frame.height() / 2.0 + axis);
|
||||||
FrameFragment::new(self, styles, frame).into()
|
FrameFragment::new(self, styles, frame).into()
|
||||||
@ -274,7 +273,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
|||||||
Ok(fragment)
|
Ok(fragment)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout the given text string into a `FrameFragment`.
|
/// Layout the given text string into a [`FrameFragment`].
|
||||||
fn layout_complex_text(
|
fn layout_complex_text(
|
||||||
&mut self,
|
&mut self,
|
||||||
text: &str,
|
text: &str,
|
||||||
|
@ -11,9 +11,11 @@ use crate::foundations::{
|
|||||||
use crate::introspection::{Count, Counter, CounterUpdate, Locatable};
|
use crate::introspection::{Count, Counter, CounterUpdate, Locatable};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, AlignElem, Alignment, Axes, Em, FixedAlignment, Frame, LayoutMultiple,
|
Abs, AlignElem, Alignment, Axes, Em, FixedAlignment, Frame, LayoutMultiple,
|
||||||
LayoutSingle, OuterHAlignment, Point, Regions, Size,
|
LayoutSingle, OuterHAlignment, Point, Regions, Size, SpecificAlignment, VAlignment,
|
||||||
|
};
|
||||||
|
use crate::math::{
|
||||||
|
scaled_font_size, LayoutMath, MathContext, MathRunFrameBuilder, MathSize, MathVariant,
|
||||||
};
|
};
|
||||||
use crate::math::{scaled_font_size, LayoutMath, MathContext, MathSize, MathVariant};
|
|
||||||
use crate::model::{Numbering, Outlinable, ParElem, Refable, Supplement};
|
use crate::model::{Numbering, Outlinable, ParElem, Refable, Supplement};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::text::{
|
use crate::text::{
|
||||||
@ -78,18 +80,20 @@ pub struct EquationElem {
|
|||||||
|
|
||||||
/// The alignment of the equation numbering.
|
/// The alignment of the equation numbering.
|
||||||
///
|
///
|
||||||
/// By default, the number is put at the `{end}` of the equation block. Both
|
/// By default, the alignment is `{end} + {horizon}`. For the horizontal
|
||||||
/// `{start}` and `{end}` respects text direction; for absolute positioning,
|
/// component, you can use `{right}`, `{left}`, or `{start}` and `{end}`
|
||||||
/// use `{left}` or `{right}`.
|
/// of the text direction; for the vertical component, you can use
|
||||||
|
/// `{top}`, `{horizon}`, or `{bottom}`.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #set math.equation(numbering: "(1)", number-align: start)
|
/// #set math.equation(numbering: "(1)", number-align: bottom)
|
||||||
///
|
///
|
||||||
/// With natural units, we know:
|
/// We can calculate:
|
||||||
/// $ E^2 = m^2 + p^2 $
|
/// $ E &= sqrt(m_0^2 + p^2) \
|
||||||
|
/// &approx 125 "GeV" $
|
||||||
/// ```
|
/// ```
|
||||||
#[default(OuterHAlignment::End)]
|
#[default(SpecificAlignment::Both(OuterHAlignment::End, VAlignment::Horizon))]
|
||||||
pub number_align: OuterHAlignment,
|
pub number_align: SpecificAlignment<OuterHAlignment, VAlignment>,
|
||||||
|
|
||||||
/// A supplement for the equation.
|
/// A supplement for the equation.
|
||||||
///
|
///
|
||||||
@ -210,12 +214,12 @@ impl Packed<EquationElem> {
|
|||||||
let font = find_math_font(engine, styles, self.span())?;
|
let font = find_math_font(engine, styles, self.span())?;
|
||||||
|
|
||||||
let mut ctx = MathContext::new(engine, styles, regions, &font);
|
let mut ctx = MathContext::new(engine, styles, regions, &font);
|
||||||
let rows = ctx.layout_into_row(self, styles)?;
|
let run = ctx.layout_into_run(self, styles)?;
|
||||||
|
|
||||||
let mut items = if rows.row_count() == 1 {
|
let mut items = if run.row_count() == 1 {
|
||||||
rows.into_par_items()
|
run.into_par_items()
|
||||||
} else {
|
} else {
|
||||||
vec![MathParItem::Frame(rows.into_fragment(&ctx, styles).into_frame())]
|
vec![MathParItem::Frame(run.into_fragment(&ctx, styles).into_frame())]
|
||||||
};
|
};
|
||||||
|
|
||||||
for item in &mut items {
|
for item in &mut items {
|
||||||
@ -251,9 +255,14 @@ impl LayoutSingle for Packed<EquationElem> {
|
|||||||
let font = find_math_font(engine, styles, span)?;
|
let font = find_math_font(engine, styles, span)?;
|
||||||
|
|
||||||
let mut ctx = MathContext::new(engine, styles, regions, &font);
|
let mut ctx = MathContext::new(engine, styles, regions, &font);
|
||||||
let mut frame = ctx.layout_into_frame(self, styles)?;
|
let equation_builder = ctx
|
||||||
|
.layout_into_run(self, styles)?
|
||||||
|
.multiline_frame_builder(&ctx, styles);
|
||||||
|
|
||||||
|
let Some(numbering) = (**self).numbering(styles) else {
|
||||||
|
return Ok(equation_builder.build());
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(numbering) = (**self).numbering(styles) {
|
|
||||||
let pod = Regions::one(regions.base(), Axes::splat(false));
|
let pod = Regions::one(regions.base(), Axes::splat(false));
|
||||||
let number = Counter::of(EquationElem::elem())
|
let number = Counter::of(EquationElem::elem())
|
||||||
.at(engine, self.location().unwrap())?
|
.at(engine, self.location().unwrap())?
|
||||||
@ -265,15 +274,20 @@ impl LayoutSingle for Packed<EquationElem> {
|
|||||||
static NUMBER_GUTTER: Em = Em::new(0.5);
|
static NUMBER_GUTTER: Em = Em::new(0.5);
|
||||||
let full_number_width = number.width() + NUMBER_GUTTER.resolve(styles);
|
let full_number_width = number.width() + NUMBER_GUTTER.resolve(styles);
|
||||||
|
|
||||||
add_equation_number(
|
let number_align = match self.number_align(styles) {
|
||||||
&mut frame,
|
SpecificAlignment::H(h) => SpecificAlignment::Both(h, VAlignment::Horizon),
|
||||||
|
SpecificAlignment::V(v) => SpecificAlignment::Both(OuterHAlignment::End, v),
|
||||||
|
SpecificAlignment::Both(h, v) => SpecificAlignment::Both(h, v),
|
||||||
|
};
|
||||||
|
|
||||||
|
let frame = add_equation_number(
|
||||||
|
equation_builder,
|
||||||
number,
|
number,
|
||||||
self.number_align(styles).resolve(styles),
|
number_align.resolve(styles),
|
||||||
AlignElem::alignment_in(styles).resolve(styles).x,
|
AlignElem::alignment_in(styles).resolve(styles).x,
|
||||||
regions.size.x,
|
regions.size.x,
|
||||||
full_number_width,
|
full_number_width,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
Ok(frame)
|
Ok(frame)
|
||||||
}
|
}
|
||||||
@ -396,35 +410,74 @@ fn find_math_font(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add_equation_number(
|
fn add_equation_number(
|
||||||
equation: &mut Frame,
|
equation_builder: MathRunFrameBuilder,
|
||||||
number: Frame,
|
number: Frame,
|
||||||
number_align: FixedAlignment,
|
number_align: Axes<FixedAlignment>,
|
||||||
equation_align: FixedAlignment,
|
equation_align: FixedAlignment,
|
||||||
region_size_x: Abs,
|
region_size_x: Abs,
|
||||||
full_number_width: Abs,
|
full_number_width: Abs,
|
||||||
) {
|
) -> Frame {
|
||||||
|
let first = equation_builder
|
||||||
|
.frames
|
||||||
|
.first()
|
||||||
|
.map_or((equation_builder.size, Point::zero()), |(frame, point)| {
|
||||||
|
(frame.size(), *point)
|
||||||
|
});
|
||||||
|
let last = equation_builder
|
||||||
|
.frames
|
||||||
|
.last()
|
||||||
|
.map_or((equation_builder.size, Point::zero()), |(frame, point)| {
|
||||||
|
(frame.size(), *point)
|
||||||
|
});
|
||||||
|
let mut equation = equation_builder.build();
|
||||||
|
|
||||||
let width = if region_size_x.is_finite() {
|
let width = if region_size_x.is_finite() {
|
||||||
region_size_x
|
region_size_x
|
||||||
} else {
|
} else {
|
||||||
equation.width() + 2.0 * full_number_width
|
equation.width() + 2.0 * full_number_width
|
||||||
};
|
};
|
||||||
|
let height = match number_align.y {
|
||||||
let height = equation.height().max(number.height());
|
FixedAlignment::Start => {
|
||||||
equation.resize(Size::new(width, height), Axes::splat(equation_align));
|
let (size, point) = first;
|
||||||
|
let excess_above = (number.height() - size.y) / 2.0 - point.y;
|
||||||
let offset = match (equation_align, number_align) {
|
equation.height() + Abs::zero().max(excess_above)
|
||||||
|
}
|
||||||
|
FixedAlignment::Center => equation.height().max(number.height()),
|
||||||
|
FixedAlignment::End => {
|
||||||
|
let (size, point) = last;
|
||||||
|
let excess_below =
|
||||||
|
(number.height() + size.y) / 2.0 - equation.height() + point.y;
|
||||||
|
equation.height() + Abs::zero().max(excess_below)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let resizing_offset = equation.resize(
|
||||||
|
Size::new(width, height),
|
||||||
|
Axes::<FixedAlignment>::new(equation_align, number_align.y.inv()),
|
||||||
|
);
|
||||||
|
equation.translate(Point::with_x(match (equation_align, number_align.x) {
|
||||||
(FixedAlignment::Start, FixedAlignment::Start) => full_number_width,
|
(FixedAlignment::Start, FixedAlignment::Start) => full_number_width,
|
||||||
(FixedAlignment::End, FixedAlignment::End) => -full_number_width,
|
(FixedAlignment::End, FixedAlignment::End) => -full_number_width,
|
||||||
_ => Abs::zero(),
|
_ => Abs::zero(),
|
||||||
};
|
}));
|
||||||
equation.translate(Point::with_x(offset));
|
|
||||||
|
|
||||||
let x = match number_align {
|
let x = match number_align.x {
|
||||||
FixedAlignment::Start => Abs::zero(),
|
FixedAlignment::Start => Abs::zero(),
|
||||||
FixedAlignment::End => equation.width() - number.width(),
|
FixedAlignment::End => equation.width() - number.width(),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
let y = (equation.height() - number.height()) / 2.0;
|
let dh = |h1: Abs, h2: Abs| (h1 - h2) / 2.0;
|
||||||
|
let y = match number_align.y {
|
||||||
|
FixedAlignment::Start => {
|
||||||
|
let (size, point) = first;
|
||||||
|
resizing_offset.y + point.y + dh(size.y, number.height())
|
||||||
|
}
|
||||||
|
FixedAlignment::Center => dh(equation.height(), number.height()),
|
||||||
|
FixedAlignment::End => {
|
||||||
|
let (size, point) = last;
|
||||||
|
resizing_offset.y + point.y + dh(size.y, number.height())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
equation.push_frame(Point::new(x, y), number);
|
equation.push_frame(Point::new(x, y), number);
|
||||||
|
equation
|
||||||
}
|
}
|
||||||
|
@ -393,7 +393,7 @@ fn layout_vec_body(
|
|||||||
let denom_style = style_for_denominator(styles);
|
let denom_style = style_for_denominator(styles);
|
||||||
let mut flat = vec![];
|
let mut flat = vec![];
|
||||||
for child in column {
|
for child in column {
|
||||||
flat.push(ctx.layout_into_row(child, styles.chain(&denom_style))?);
|
flat.push(ctx.layout_into_run(child, styles.chain(&denom_style))?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(stack(flat, align, gap, 0))
|
Ok(stack(flat, align, gap, 0))
|
||||||
@ -455,7 +455,7 @@ fn layout_mat_body(
|
|||||||
let denom_style = style_for_denominator(styles);
|
let denom_style = style_for_denominator(styles);
|
||||||
for (row, (ascent, descent)) in rows.iter().zip(&mut heights) {
|
for (row, (ascent, descent)) in rows.iter().zip(&mut heights) {
|
||||||
for (cell, col) in row.iter().zip(&mut cols) {
|
for (cell, col) in row.iter().zip(&mut cols) {
|
||||||
let cell = ctx.layout_into_row(cell, styles.chain(&denom_style))?;
|
let cell = ctx.layout_into_run(cell, styles.chain(&denom_style))?;
|
||||||
|
|
||||||
ascent.set_max(cell.ascent());
|
ascent.set_max(cell.ascent());
|
||||||
descent.set_max(cell.descent());
|
descent.set_max(cell.descent());
|
||||||
|
@ -14,10 +14,12 @@ use super::fragment::SpacingFragment;
|
|||||||
|
|
||||||
pub const TIGHT_LEADING: Em = Em::new(0.25);
|
pub const TIGHT_LEADING: Em = Em::new(0.25);
|
||||||
|
|
||||||
|
/// A linear collection of [`MathFragment`]s.
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct MathRow(Vec<MathFragment>);
|
pub struct MathRun(Vec<MathFragment>);
|
||||||
|
|
||||||
impl MathRow {
|
impl MathRun {
|
||||||
|
/// Takes the given [`MathFragment`]s and do some basic processing.
|
||||||
pub fn new(fragments: Vec<MathFragment>) -> Self {
|
pub fn new(fragments: Vec<MathFragment>) -> Self {
|
||||||
let iter = fragments.into_iter().peekable();
|
let iter = fragments.into_iter().peekable();
|
||||||
let mut last: Option<usize> = None;
|
let mut last: Option<usize> = None;
|
||||||
@ -93,11 +95,7 @@ impl MathRow {
|
|||||||
self.0.iter()
|
self.0.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract the sublines of the row.
|
/// Split by linebreaks, and copy [`MathFragment`]s into rows.
|
||||||
///
|
|
||||||
/// It is very unintuitive, but in current state of things, a `MathRow` can
|
|
||||||
/// contain several actual rows. That function deconstructs it to "single"
|
|
||||||
/// rows. Hopefully this is only a temporary hack.
|
|
||||||
pub fn rows(&self) -> Vec<Self> {
|
pub fn rows(&self) -> Vec<Self> {
|
||||||
self.0
|
self.0
|
||||||
.split(|frag| matches!(frag, MathFragment::Linebreak))
|
.split(|frag| matches!(frag, MathFragment::Linebreak))
|
||||||
@ -141,11 +139,10 @@ impl MathRow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_frame(self, ctx: &MathContext, styles: StyleChain) -> Frame {
|
pub fn into_frame(self, ctx: &MathContext, styles: StyleChain) -> Frame {
|
||||||
let align = AlignElem::alignment_in(styles).resolve(styles).x;
|
|
||||||
if !self.is_multiline() {
|
if !self.is_multiline() {
|
||||||
self.into_line_frame(&[], align)
|
self.into_line_frame(&[], AlignElem::alignment_in(styles).resolve(styles).x)
|
||||||
} else {
|
} else {
|
||||||
self.multiline_frame_builder(ctx, styles, align).build()
|
self.multiline_frame_builder(ctx, styles).build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,14 +154,14 @@ impl MathRow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a builder that lays out `MathFragment`s into a multi-row frame. The set
|
/// Returns a builder that lays out the [`MathFragment`]s into a possibly
|
||||||
/// of alignment points are computed from those rows combined.
|
/// multi-row [`Frame`]. The rows are aligned using the same set of alignment
|
||||||
|
/// points computed from them as a whole.
|
||||||
pub fn multiline_frame_builder(
|
pub fn multiline_frame_builder(
|
||||||
self,
|
self,
|
||||||
ctx: &MathContext,
|
ctx: &MathContext,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
align: FixedAlignment,
|
) -> MathRunFrameBuilder {
|
||||||
) -> MathRowFrameBuilder {
|
|
||||||
let rows: Vec<_> = self.rows();
|
let rows: Vec<_> = self.rows();
|
||||||
let row_count = rows.len();
|
let row_count = rows.len();
|
||||||
let alignments = alignments(&rows);
|
let alignments = alignments(&rows);
|
||||||
@ -176,6 +173,7 @@ impl MathRow {
|
|||||||
TIGHT_LEADING.at(font_size)
|
TIGHT_LEADING.at(font_size)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let align = AlignElem::alignment_in(styles).resolve(styles).x;
|
||||||
let mut frames: Vec<(Frame, Point)> = vec![];
|
let mut frames: Vec<(Frame, Point)> = vec![];
|
||||||
let mut size = Size::zero();
|
let mut size = Size::zero();
|
||||||
for (i, row) in rows.into_iter().enumerate() {
|
for (i, row) in rows.into_iter().enumerate() {
|
||||||
@ -197,10 +195,11 @@ impl MathRow {
|
|||||||
frames.push((sub, pos));
|
frames.push((sub, pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
MathRowFrameBuilder { size, frames }
|
MathRunFrameBuilder { size, frames }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lay out `MathFragment`s into a one-row frame, with alignment points respected.
|
/// Lay out [`MathFragment`]s into a one-row [`Frame`], using the
|
||||||
|
/// caller-provided alignment points.
|
||||||
pub fn into_line_frame(self, points: &[Abs], align: FixedAlignment) -> Frame {
|
pub fn into_line_frame(self, points: &[Abs], align: FixedAlignment) -> Frame {
|
||||||
let ascent = self.ascent();
|
let ascent = self.ascent();
|
||||||
let mut frame = Frame::soft(Size::new(Abs::zero(), ascent + self.descent()));
|
let mut frame = Frame::soft(Size::new(Abs::zero(), ascent + self.descent()));
|
||||||
@ -347,7 +346,7 @@ impl MathRow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Into<MathFragment>> From<T> for MathRow {
|
impl<T: Into<MathFragment>> From<T> for MathRun {
|
||||||
fn from(fragment: T) -> Self {
|
fn from(fragment: T) -> Self {
|
||||||
Self(vec![fragment.into()])
|
Self(vec![fragment.into()])
|
||||||
}
|
}
|
||||||
@ -372,17 +371,17 @@ impl Iterator for LeftRightAlternator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How the rows should be aligned and merged into a Frame.
|
/// How the rows from the [`MathRun`] should be aligned and merged into a [`Frame`].
|
||||||
pub struct MathRowFrameBuilder {
|
pub struct MathRunFrameBuilder {
|
||||||
/// The size of the resulting frame.
|
/// The size of the resulting frame.
|
||||||
size: Size,
|
pub size: Size,
|
||||||
/// Sub frames, and the positions where they should be pushed into
|
/// Sub frames for each row, and the positions where they should be pushed into
|
||||||
/// the resulting frame.
|
/// the resulting frame.
|
||||||
frames: Vec<(Frame, Point)>,
|
pub frames: Vec<(Frame, Point)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MathRowFrameBuilder {
|
impl MathRunFrameBuilder {
|
||||||
/// Consumes the builder and returns a `Frame`.
|
/// Consumes the builder and returns a [`Frame`].
|
||||||
pub fn build(self) -> Frame {
|
pub fn build(self) -> Frame {
|
||||||
let mut frame = Frame::soft(self.size);
|
let mut frame = Frame::soft(self.size);
|
||||||
for (sub, pos) in self.frames.into_iter() {
|
for (sub, pos) in self.frames.into_iter() {
|
||||||
|
@ -3,7 +3,7 @@ use crate::foundations::{elem, Content, Packed, StyleChain};
|
|||||||
use crate::layout::{Abs, Em, FixedAlignment, Frame, FrameItem, Point, Size};
|
use crate::layout::{Abs, Em, FixedAlignment, Frame, FrameItem, Point, Size};
|
||||||
use crate::math::{
|
use crate::math::{
|
||||||
alignments, scaled_font_size, style_cramped, style_for_subscript, AlignmentResult,
|
alignments, scaled_font_size, style_cramped, style_for_subscript, AlignmentResult,
|
||||||
FrameFragment, GlyphFragment, LayoutMath, MathContext, MathRow, Scaled,
|
FrameFragment, GlyphFragment, LayoutMath, MathContext, MathRun, Scaled,
|
||||||
};
|
};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::text::TextElem;
|
use crate::text::TextElem;
|
||||||
@ -260,13 +260,13 @@ fn layout_underoverspreader(
|
|||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
let font_size = scaled_font_size(ctx, styles);
|
let font_size = scaled_font_size(ctx, styles);
|
||||||
let gap = gap.at(font_size);
|
let gap = gap.at(font_size);
|
||||||
let body = ctx.layout_into_row(body, styles)?;
|
let body = ctx.layout_into_run(body, styles)?;
|
||||||
let body_class = body.class();
|
let body_class = body.class();
|
||||||
let body = body.into_fragment(ctx, styles);
|
let body = body.into_fragment(ctx, styles);
|
||||||
let glyph = GlyphFragment::new(ctx, styles, c, span);
|
let glyph = GlyphFragment::new(ctx, styles, c, span);
|
||||||
let stretched = glyph.stretch_horizontal(ctx, body.width(), Abs::zero());
|
let stretched = glyph.stretch_horizontal(ctx, body.width(), Abs::zero());
|
||||||
|
|
||||||
let mut rows = vec![MathRow::new(vec![body]), stretched.into()];
|
let mut rows = vec![MathRun::new(vec![body]), stretched.into()];
|
||||||
|
|
||||||
let (sup_style, sub_style);
|
let (sup_style, sub_style);
|
||||||
let row_styles = if reverse {
|
let row_styles = if reverse {
|
||||||
@ -280,7 +280,7 @@ fn layout_underoverspreader(
|
|||||||
rows.extend(
|
rows.extend(
|
||||||
annotation
|
annotation
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|annotation| ctx.layout_into_row(annotation, row_styles))
|
.map(|annotation| ctx.layout_into_run(annotation, row_styles))
|
||||||
.transpose()?,
|
.transpose()?,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -301,7 +301,7 @@ fn layout_underoverspreader(
|
|||||||
/// Add a `gap` between each row and uses the baseline of the `baseline`th
|
/// Add a `gap` between each row and uses the baseline of the `baseline`th
|
||||||
/// row for the whole frame.
|
/// row for the whole frame.
|
||||||
pub(super) fn stack(
|
pub(super) fn stack(
|
||||||
rows: Vec<MathRow>,
|
rows: Vec<MathRun>,
|
||||||
align: FixedAlignment,
|
align: FixedAlignment,
|
||||||
gap: Abs,
|
gap: Abs,
|
||||||
baseline: usize,
|
baseline: usize,
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 23 KiB |
@ -94,3 +94,52 @@ $ a + b = c $
|
|||||||
---
|
---
|
||||||
// Error: 52-58 expected `start`, `left`, `right`, or `end`, found center
|
// Error: 52-58 expected `start`, `left`, `right`, or `end`, found center
|
||||||
#set math.equation(numbering: "(1)", number-align: center)
|
#set math.equation(numbering: "(1)", number-align: center)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 52-67 expected `start`, `left`, `right`, or `end`, found center
|
||||||
|
#set math.equation(numbering: "(1)", number-align: center + bottom)
|
||||||
|
|
||||||
|
---
|
||||||
|
#set math.equation(numbering: "(1)")
|
||||||
|
|
||||||
|
$ p &= ln a b \
|
||||||
|
&= ln a + ln b $
|
||||||
|
|
||||||
|
---
|
||||||
|
#set math.equation(numbering: "(1)", number-align: top+start)
|
||||||
|
|
||||||
|
$ p &= ln a b \
|
||||||
|
&= ln a + ln b $
|
||||||
|
|
||||||
|
---
|
||||||
|
#show math.equation: set align(left)
|
||||||
|
#set math.equation(numbering: "(1)", number-align: bottom)
|
||||||
|
|
||||||
|
$ q &= ln sqrt(a b) \
|
||||||
|
&= 1/2 (ln a + ln b) $
|
||||||
|
|
||||||
|
---
|
||||||
|
// Tests that if the numbering's layout box vertically exceeds the box of
|
||||||
|
// the equation frame's boundary, the latter's frame is resized correctly
|
||||||
|
// to encompass the numbering. #box() below delineates the resized frame.
|
||||||
|
//
|
||||||
|
// A row with "-" only has a height that's smaller than the height of the
|
||||||
|
// numbering's layout box. Note we use pattern "1" here, not "(1)", since
|
||||||
|
// the parenthesis exceeds the numbering's layout box, due to the default
|
||||||
|
// settings of top-edge and bottom-edge of the TextElem that laid it out.
|
||||||
|
#set math.equation(numbering: "1", number-align: top)
|
||||||
|
#box(
|
||||||
|
$ - &- - \
|
||||||
|
a &= b $,
|
||||||
|
fill: silver)
|
||||||
|
|
||||||
|
#set math.equation(numbering: "1", number-align: horizon)
|
||||||
|
#box(
|
||||||
|
$ - - - $,
|
||||||
|
fill: silver)
|
||||||
|
|
||||||
|
#set math.equation(numbering: "1", number-align: bottom)
|
||||||
|
#box(
|
||||||
|
$ a &= b \
|
||||||
|
- &- - $,
|
||||||
|
fill: silver)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user