1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
use super::*;
const FRAC_AROUND: Em = Em::new(0.1);
/// A mathematical fraction.
///
/// ## Example { #example }
/// ```example
/// $ 1/2 < (x+1)/2 $
/// $ ((x+1)) / 2 = frac(a, b) $
/// ```
///
/// ## Syntax { #syntax }
/// This function also has dedicated syntax: Use a slash to turn neighbouring
/// expressions into a fraction. Multiple atoms can be grouped into a single
/// expression using round grouping parenthesis. Such parentheses are removed
/// from the output, but you can nest multiple to force them.
///
/// Display: Fraction
/// Category: math
#[element(LayoutMath)]
pub struct FracElem {
/// The fraction's numerator.
#[required]
pub num: Content,
/// The fraction's denominator.
#[required]
pub denom: Content,
}
impl LayoutMath for FracElem {
#[tracing::instrument(skip(ctx))]
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout(ctx, &self.num(), &self.denom(), false, self.span())
}
}
/// A binomial expression.
///
/// ## Example { #example }
/// ```example
/// $ binom(n, k) $
/// ```
///
/// Display: Binomial
/// Category: math
#[element(LayoutMath)]
pub struct BinomElem {
/// The binomial's upper index.
#[required]
pub upper: Content,
/// The binomial's lower index.
#[required]
pub lower: Content,
}
impl LayoutMath for BinomElem {
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
layout(ctx, &self.upper(), &self.lower(), true, self.span())
}
}
/// Layout a fraction or binomial.
fn layout(
ctx: &mut MathContext,
num: &Content,
denom: &Content,
binom: bool,
span: Span,
) -> SourceResult<()> {
let short_fall = DELIM_SHORT_FALL.scaled(ctx);
let axis = scaled!(ctx, axis_height);
let thickness = scaled!(ctx, fraction_rule_thickness);
let shift_up = scaled!(
ctx,
text: fraction_numerator_shift_up,
display: fraction_numerator_display_style_shift_up,
);
let shift_down = scaled!(
ctx,
text: fraction_denominator_shift_down,
display: fraction_denominator_display_style_shift_down,
);
let num_min = scaled!(
ctx,
text: fraction_numerator_gap_min,
display: fraction_num_display_style_gap_min,
);
let denom_min = scaled!(
ctx,
text: fraction_denominator_gap_min,
display: fraction_denom_display_style_gap_min,
);
ctx.style(ctx.style.for_numerator());
let num = ctx.layout_frame(num)?;
ctx.unstyle();
ctx.style(ctx.style.for_denominator());
let denom = ctx.layout_frame(denom)?;
ctx.unstyle();
let around = FRAC_AROUND.scaled(ctx);
let num_gap = (shift_up - axis - num.descent()).max(num_min + thickness / 2.0);
let denom_gap = (shift_down + axis - denom.ascent()).max(denom_min + thickness / 2.0);
let line_width = num.width().max(denom.width());
let width = line_width + 2.0 * around;
let height = num.height() + num_gap + thickness + denom_gap + denom.height();
let size = Size::new(width, height);
let num_pos = Point::with_x((width - num.width()) / 2.0);
let line_pos =
Point::new((width - line_width) / 2.0, num.height() + num_gap + thickness / 2.0);
let denom_pos = Point::new((width - denom.width()) / 2.0, height - denom.height());
let baseline = line_pos.y + axis;
let mut frame = Frame::new(size);
frame.set_baseline(baseline);
frame.push_frame(num_pos, num);
frame.push_frame(denom_pos, denom);
if binom {
ctx.push(
GlyphFragment::new(ctx, '(', span).stretch_vertical(ctx, height, short_fall),
);
ctx.push(FrameFragment::new(ctx, frame));
ctx.push(
GlyphFragment::new(ctx, ')', span).stretch_vertical(ctx, height, short_fall),
);
} else {
frame.push(
line_pos,
FrameItem::Shape(
Geometry::Line(Point::with_x(line_width)).stroked(Stroke {
paint: TextElem::fill_in(ctx.styles()),
thickness,
..Stroke::default()
}),
span,
),
);
ctx.push(FrameFragment::new(ctx, frame));
}
Ok(())
}
|