1use common::Scaled;
2
3use super::keyword::parse_keyword;
4use crate::prelude as txl;
5use crate::token::Value;
6use crate::traits::*;
7use crate::*;
8
9impl Parsable for Scaled {
10 fn parse_impl<S: TexlangState>(input: &mut vm::ExpandedStream<S>) -> txl::Result<Self> {
11 scan_dimen(input, None)
12 }
13}
14
15pub(crate) fn scan_dimen<S: TexlangState>(
20 input: &mut vm::ExpandedStream<S>,
21 glue_order: Option<&mut common::GlueOrder>,
22) -> txl::Result<common::Scaled> {
23 let negative = match super::integer::parse_optional_signs(input)? {
24 None => 1,
25 Some(_) => -1,
26 };
27 let first_token = input.next_or_err(DimenEndOfInputError {})?;
28 let (negative, integer_part, fractional_part) = match first_token.value() {
29 Value::CommandRef(command_ref) => {
30 use super::integer::InternalNumber;
32 match super::integer::parse_internal_number(input, first_token, command_ref)? {
33 InternalNumber::Integer(i) => (negative * i.signum(), i.abs(), Scaled::ZERO),
34 InternalNumber::Dimen(d) => {
35 return Ok(d * negative);
36 }
37 InternalNumber::Glue(g) => {
38 return Ok(g.width * negative);
39 }
40 }
41 }
42 _ => {
43 let (i, f) = scan_constant_dimen(input, first_token)?;
44 (negative, i, f)
45 }
46 };
47 Ok(scan_and_apply_units(
48 input,
49 first_token,
50 integer_part,
51 fractional_part,
52 glue_order,
53 )? * negative)
54}
55
56pub(crate) fn scan_constant_dimen<S: TexlangState>(
58 input: &mut vm::ExpandedStream<S>,
59 first_token: token::Token,
60) -> txl::Result<(i32, common::Scaled)> {
61 Ok(match first_token.value() {
62 Value::Other(',' | '.') => (0, scan_decimal_fraction(input)?),
63 _ => {
64 input.back(first_token);
65 let (_, i, radix) = super::integer::parse_integer(input)?;
66 let fractional_part = if radix == Some(10) {
69 match input.next()? {
70 Some(next) => match next.value() {
71 Value::Other(',' | '.') => scan_decimal_fraction(input)?,
72 _ => {
73 input.back(next);
74 Scaled::ZERO
75 }
76 },
77 None => Scaled::ZERO,
78 }
79 } else {
80 Scaled::ZERO
81 };
82 (i, fractional_part)
83 }
84 })
85}
86
87pub(crate) fn scan_and_apply_units<S: TexlangState>(
89 input: &mut vm::ExpandedStream<S>,
90 first_token: token::Token,
91 integer_part: i32,
92 fractional_part: Scaled,
93 glue_order: Option<&mut common::GlueOrder>,
94) -> txl::Result<common::Scaled> {
95 if let Some(glue_order) = glue_order {
100 if parse_keyword(input, "fil")? {
101 *glue_order = common::GlueOrder::Fil;
102 while let Some(token) = input.next()? {
103 match token.value() {
104 token::Value::Letter('l' | 'L') => match glue_order.next() {
105 None => {
106 input.error(fillll_error(token))?;
107 }
108 Some(o) => *glue_order = o,
109 },
110 _ => {
111 input.back(token);
112 break;
113 }
114 }
115 }
116 super::OptionalSpace::parse(input)?;
117 return match Scaled::from_integer(integer_part) {
118 Ok(integer_part) => Ok(integer_part + fractional_part),
119 Err(_) => handle_overflow(input, first_token, false),
120 };
121 }
122 }
123 if let Some(next) = input.next()? {
126 let v_or = match next.value() {
127 Value::CommandRef(command_ref) => {
128 use super::integer::InternalNumber;
129 Some(
130 match super::integer::parse_internal_number(input, next, command_ref)? {
131 InternalNumber::Integer(i) => Scaled(i),
133 InternalNumber::Dimen(scaled) => scaled,
134 InternalNumber::Glue(glue) => glue.width,
135 },
136 )
137 }
138 _ => {
139 input.back(next);
140 if parse_keyword(input, "em")? {
141 super::OptionalSpace::parse(input)?;
142 Some(input.state().em_width())
143 } else if parse_keyword(input, "ex")? {
144 super::OptionalSpace::parse(input)?;
145 Some(input.state().ex_height())
146 } else {
147 None
148 }
149 }
150 };
151 if let Some(v) = v_or {
152 let adjusted_fractional_part = v
153 .xn_over_d(fractional_part.0, Scaled::ONE.0)
154 .expect("n<d=Scaled::ONE, so overflow can't occur");
155 return match v.nx_plus_y(integer_part, adjusted_fractional_part.0) {
156 Ok(s) => Ok(s),
157 Err(_) => handle_overflow(input, first_token, v < Scaled::ZERO),
158 };
159 }
160 }
161
162 if parse_keyword(input, "true")? {
164 }
167
168 let scaled_unit = <common::ScaledUnit as Parsable>::parse(input)?;
170 super::OptionalSpace::parse(input)?;
171 match Scaled::new(integer_part, fractional_part, scaled_unit) {
172 Ok(s) => Ok(s),
173 Err(_) => handle_overflow(input, first_token, false),
174 }
175}
176
177fn handle_overflow<S: TexlangState>(
178 input: &mut vm::ExpandedStream<S>,
179 first_token: token::Token,
180 neg: bool,
181) -> txl::Result<common::Scaled> {
182 input.error(
183 parse::Error::new(
184 "a dimension in the range (-2^14pt,2^14pt)",
185 Some(first_token),
186 "",
187 )
188 .with_got_override("a dimension that's too large"),
189 )?;
190 Ok(if neg {
191 -common::Scaled::MAX_DIMEN
192 } else {
193 common::Scaled::MAX_DIMEN
194 })
195}
196
197fn fillll_error(token: token::Token) -> parse::Error {
198 parse::Error::new("an infinite glue stretch or shrink order", Some(token), "")
199 .with_got_override("too many l characters")
200}
201
202impl Parsable for common::ScaledUnit {
203 fn parse_impl<S: TexlangState>(input: &mut vm::ExpandedStream<S>) -> txl::Result<Self> {
204 use common::ScaledUnit;
205 for (keyword, unit) in [
206 ("pt", ScaledUnit::Point),
207 ("in", ScaledUnit::Inch),
208 ("pc", ScaledUnit::Pica),
209 ("cm", ScaledUnit::Centimeter),
210 ("mm", ScaledUnit::Millimeter),
211 ("bp", ScaledUnit::BigPoint),
212 ("dd", ScaledUnit::DidotPoint),
213 ("cc", ScaledUnit::Cicero),
214 ("sp", ScaledUnit::ScaledPoint),
215 ] {
216 if parse_keyword(input, keyword)? {
217 return Ok(unit);
218 }
219 }
220 input.error(error::TODO())?;
221 Ok(common::ScaledUnit::Point)
222 }
223}
224
225fn scan_decimal_fraction<S: TexlangState>(
227 input: &mut vm::ExpandedStream<S>,
228) -> txl::Result<Scaled> {
229 let mut digits = [0_u8; 17];
233 let mut i = 0_usize;
234 while let Some(token) = input.next()? {
235 let d: u8 = match token.value() {
236 Value::Other('0') => 0,
237 Value::Other('1') => 1,
238 Value::Other('2') => 2,
239 Value::Other('3') => 3,
240 Value::Other('4') => 4,
241 Value::Other('5') => 5,
242 Value::Other('6') => 6,
243 Value::Other('7') => 7,
244 Value::Other('8') => 8,
245 Value::Other('9') => 9,
246 Value::Space(_) => {
247 break;
248 }
249 _ => {
250 input.back(token);
251 break;
252 }
253 };
254 if let Some(digit) = digits.get_mut(i) {
255 *digit = d;
256 i += 1;
257 }
258 }
259 Ok(Scaled::from_decimal_digits(&digits[0..i]))
260}
261
262#[derive(Debug)]
263struct DimenEndOfInputError;
264
265impl error::EndOfInputError for DimenEndOfInputError {
266 fn doing(&self) -> String {
267 "parsing a dimension".into()
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274 use crate::parse::testing::*;
275
276 #[derive(Default)]
277 struct State;
278
279 impl TexlangState for State {}
280
281 parse_success_tests![
282 (zero_pt, "0pt", Scaled::ZERO),
283 (one_pt, "1pt", Scaled::ONE),
284 (one_pt_negative, "-1pt", -Scaled::ONE),
285 (two_pt, "2pt", Scaled::TWO),
286 (empty_point, ".pt", Scaled::ZERO), (fraction_1, "0.5pt", Scaled::from_decimal_digits(&[5])),
288 (fraction_2, "-0.5pt", -Scaled::from_decimal_digits(&[5])),
289 (
290 fraction_3,
291 "1.5pt",
292 Scaled::ONE + Scaled::from_decimal_digits(&[5])
293 ),
294 (
295 fraction_4,
296 "-1.5pt",
297 -Scaled::ONE - Scaled::from_decimal_digits(&[5])
298 ),
299 (units_in_1, "1in", (Scaled::ONE * 7227) / 100),
300 (units_in_2, "1 in", (Scaled::ONE * 7227) / 100),
301 (units_in_3, "0.075in", Scaled(355207)),
302 (units_pc, "1pc", Scaled::ONE * 12),
303 (units_cm, "1cm", (Scaled::ONE * 7227) / 254),
304 (units_mm, "1mm", (Scaled::ONE * 7227) / 2540),
305 (units_bp, "1bp", (Scaled::ONE * 7227) / 7200),
306 (units_dd, "1dd", (Scaled::ONE * 1238) / 1157),
307 (units_cc, "1cc", (Scaled::ONE * 14856) / 1157),
308 (units_sp_1, "1sp", Scaled(1)),
309 (units_sp_2, "1.999999sp", Scaled(1)),
310 (nearly_overflow_pt, "16383.99998pt", Scaled::MAX_DIMEN,),
311 (nearly_overflow_sp_1, "1073741823sp", Scaled::MAX_DIMEN,),
315 (
316 nearly_overflow_sp_2,
317 "1073741823.99999999sp",
318 Scaled::MAX_DIMEN,
319 )
320 ];
321
322 parse_failure_tests!(
323 Scaled,
324 State,
325 (invalid_unit, "1xy", Scaled::ONE),
326 (overflow_pt, "16384pt", Scaled::MAX_DIMEN),
327 (overflow_pt_neg, "-16384pt", -Scaled::MAX_DIMEN),
328 (overflow_in_1, "300in", Scaled::MAX_DIMEN),
329 (overflow_in_2, "300000000in", Scaled::MAX_DIMEN),
330 (overflow_in_3, "-300in", -Scaled::MAX_DIMEN),
331 (overflow_in_4, "-300000000in", -Scaled::MAX_DIMEN),
332 (overflow_sp, "1073741824sp", Scaled::MAX_DIMEN),
333 (overflow_sp_neg, "-1073741824sp", -Scaled::MAX_DIMEN),
334 );
335}