1use super::*;
2use crate::ligkern::lang;
3
4#[derive(Debug, PartialEq, Eq)]
5pub enum ValidationWarning {
6 DesignSizeIsTooSmall,
7 DesignSizeIsNegative,
8 StringIsTooLong(usize),
9 StringContainsParenthesis,
10 StringContainsNonstandardAsciiCharacter(char),
11 ParameterIsTooBig(usize),
12 UnusualNumberOfParameters {
17 is_math_symbols_font: bool,
19 got: usize,
21 },
22 InvalidCharacterInExtensibleRecipe(Char),
23 InvalidWidthIndex(Char, u8),
24 InvalidHeightIndex(Char, u8),
25 InvalidDepthIndex(Char, u8),
26 InvalidItalicCorrectionIndex(Char, u8),
27 InvalidExtensibleRecipeIndex(Char, u8),
28 FirstWidthIsNonZero,
29 FirstDepthIsNonZero,
30 FirstHeightIsNonZero,
31 FirstItalicCorrectionIsNonZero,
32 WidthIsTooBig(usize),
33 HeightIsTooBig(usize),
34 DepthIsTooBig(usize),
35 ItalicCorrectionIsTooBig(usize),
36 KernIsTooBig(usize),
37 NextLargerWarning(NextLargerProgramWarning),
38 LigKernWarning(lang::ValidationWarning),
39}
40
41impl ValidationWarning {
42 pub fn tftopl_message(&self) -> String {
44 use ValidationWarning::*;
45 match self {
46 DesignSizeIsTooSmall => "Bad TFM file: Design size too small!\nI've set it to 10 points.".to_string(),
47 DesignSizeIsNegative => "Bad TFM file: Design size negative!\nI've set it to 10 points.".to_string(),
48 StringIsTooLong(_) => "Bad TFM file: String is too long; I've shortened it drastically.".to_string(),
49 StringContainsParenthesis => "Bad TFM file: Parenthesis in string has been changed to slash.".to_string(),
50 StringContainsNonstandardAsciiCharacter(_) => "Bad TFM file: Nonstandard ASCII code has been blotted out.".to_string(),
51 ParameterIsTooBig(i) => format![
52 "Bad TFM file: Parameter {} is too big;\nI have set it to zero.",
53 i
54 ],
55 UnusualNumberOfParameters { is_math_symbols_font, got } => {
56 let (font_description, expected) = if *is_math_symbols_font {
57 ("a math symbols", 22)
58 } else {
59 ("an extension", 13)
60 };
61 format!["Unusual number of fontdimen parameters for {font_description} font ({got} not {expected})."]
62 },
63 InvalidCharacterInExtensibleRecipe(c) => format!["Bad TFM file: Extensible recipe involves the nonexistent character '{:03o}.", c.0],
64 InvalidWidthIndex(c, _) => format![" \nWidth index for character '{:03o} is too large;\nso I reset it to zero.", c.0],
69 InvalidHeightIndex(c, _) => format![" \nHeight index for character '{:03o} is too large;\nso I reset it to zero.", c.0],
70 InvalidDepthIndex(c, _) => format![" \nDepth index for character '{:03o} is too large;\nso I reset it to zero.", c.0],
71 InvalidItalicCorrectionIndex(c, _) => format![" \nItalic correction index for character '{:03o} is too large;\nso I reset it to zero.", c.0],
72 InvalidExtensibleRecipeIndex(c, _ ) => format![" \nExtensible index for character '{:03o} is too large;\nso I reset it to zero.", c.0],
73 FirstWidthIsNonZero => "Bad TFM file: width[0] should be zero.".into(),
74 FirstDepthIsNonZero => "Bad TFM file: depth[0] should be zero.".into(),
75 FirstHeightIsNonZero => "Bad TFM file: height[0] should be zero.".into(),
76 FirstItalicCorrectionIsNonZero => "Bad TFM file: italic[0] should be zero.".into(),
77 WidthIsTooBig(i) => format![
78 "Bad TFM file: Width {} is too big;\nI have set it to zero.",
79 i
80 ],
81 HeightIsTooBig(i) => format![
82 "Bad TFM file: Height {} is too big;\nI have set it to zero.",
83 i
84 ],
85 DepthIsTooBig(i) => format![
86 "Bad TFM file: Depth {} is too big;\nI have set it to zero.",
87 i
88 ],
89 ItalicCorrectionIsTooBig(i) => format![
90 "Bad TFM file: Italic correction {} is too big;\nI have set it to zero.",
91 i
92 ],
93 KernIsTooBig(i) => format![
94 "Bad TFM file: Kern {} is too big;\nI have set it to zero.",
95 i
96 ],
97 NextLargerWarning(warning) => warning.tftopl_message(),
98 LigKernWarning(warning) => warning.tftopl_message(),
99 }
100 }
101
102 pub fn tftopl_section(&self) -> u8 {
104 use ValidationWarning::*;
105 match self {
106 DesignSizeIsNegative | DesignSizeIsTooSmall => 51,
107 StringIsTooLong(_)
108 | StringContainsParenthesis
109 | StringContainsNonstandardAsciiCharacter(_) => 52,
110 ParameterIsTooBig(_) => 60,
111 UnusualNumberOfParameters { .. } => 59,
112 InvalidCharacterInExtensibleRecipe(_) => 87,
113 InvalidWidthIndex(_, _) => 79,
114 InvalidHeightIndex(_, _) => 80,
115 InvalidDepthIndex(_, _) => 81,
116 InvalidItalicCorrectionIndex(_, _) => 82,
117 InvalidExtensibleRecipeIndex(_, _) => 85,
118 FirstWidthIsNonZero
119 | FirstDepthIsNonZero
120 | FirstHeightIsNonZero
121 | FirstItalicCorrectionIsNonZero
122 | WidthIsTooBig(_)
123 | HeightIsTooBig(_)
124 | DepthIsTooBig(_)
125 | ItalicCorrectionIsTooBig(_)
126 | KernIsTooBig(_) => 62,
127 NextLargerWarning(warning) => warning.tftopl_section(),
128 LigKernWarning(warning) => warning.tftopl_section(),
129 }
130 }
131
132 pub fn tfm_file_modified(&self) -> bool {
134 use ValidationWarning::*;
135 match self {
136 DesignSizeIsNegative
137 | DesignSizeIsTooSmall
138 | StringIsTooLong(_)
139 | StringContainsParenthesis
140 | StringContainsNonstandardAsciiCharacter(_)
141 | ParameterIsTooBig(_) => true,
142 UnusualNumberOfParameters { .. } => false,
143 InvalidCharacterInExtensibleRecipe(_)
144 | InvalidWidthIndex(_, _)
145 | InvalidHeightIndex(_, _)
146 | InvalidDepthIndex(_, _)
147 | InvalidItalicCorrectionIndex(_, _)
148 | InvalidExtensibleRecipeIndex(_, _)
149 | FirstWidthIsNonZero
150 | FirstDepthIsNonZero
151 | FirstHeightIsNonZero
152 | FirstItalicCorrectionIsNonZero
153 | WidthIsTooBig(_)
154 | HeightIsTooBig(_)
155 | DepthIsTooBig(_)
156 | ItalicCorrectionIsTooBig(_)
157 | KernIsTooBig(_)
158 | NextLargerWarning(_) => true,
159 LigKernWarning(_) => true,
160 }
161 }
162}
163
164pub fn validate_and_fix(file: &mut File) -> Vec<ValidationWarning> {
165 let mut warnings = vec![];
166
167 if let Some(scheme) = &mut file.header.character_coding_scheme {
168 validate_string(scheme, 39, &mut warnings);
169 }
170 if let Some(family) = &mut file.header.font_family {
171 validate_string(family, 19, &mut warnings);
172 }
173 if file.header.design_size < FixWord::ZERO {
174 warnings.push(ValidationWarning::DesignSizeIsNegative);
175 file.header.design_size = FixWord::ONE * 10;
176 file.header.design_size_valid = false;
177 }
178 if file.header.design_size < FixWord::ONE {
179 warnings.push(ValidationWarning::DesignSizeIsTooSmall);
180 file.header.design_size = FixWord::ONE * 10;
181 file.header.design_size_valid = false;
182 }
183
184 for (i, elem) in file.params.iter_mut().enumerate() {
185 if i == 0 {
186 continue;
187 }
188 if !elem.is_abs_less_than_16() {
189 warnings.push(ValidationWarning::ParameterIsTooBig(i + 1));
190 *elem = FixWord::ZERO
191 }
192 }
193
194 {
195 let scheme = match &file.header.character_coding_scheme {
196 None => "".to_string(),
197 Some(scheme) => scheme.to_uppercase(),
198 };
199 let num_params = file.params.len();
200 if scheme.starts_with("TEX MATH SY") && num_params != 22 {
201 warnings.push(ValidationWarning::UnusualNumberOfParameters {
202 is_math_symbols_font: true,
203 got: num_params,
204 })
205 }
206 if scheme.starts_with("TEX MATH EX") && num_params != 13 {
207 warnings.push(ValidationWarning::UnusualNumberOfParameters {
208 is_math_symbols_font: false,
209 got: num_params,
210 })
211 }
212 }
213
214 for (array, first_dimension_non_zero) in [
215 (&mut file.widths, ValidationWarning::FirstWidthIsNonZero),
216 (&mut file.heights, ValidationWarning::FirstHeightIsNonZero),
217 (&mut file.depths, ValidationWarning::FirstDepthIsNonZero),
218 (
219 &mut file.italic_corrections,
220 ValidationWarning::FirstItalicCorrectionIsNonZero,
221 ),
222 ] {
223 if let Some(first) = array.first_mut() {
224 if *first != FixWord::ZERO {
225 warnings.push(first_dimension_non_zero);
226 }
229 }
230 }
231
232 for (array, dimension_too_big, zero_first_element) in [
233 (
234 &mut file.widths,
235 ValidationWarning::WidthIsTooBig as fn(usize) -> ValidationWarning,
236 true,
237 ),
238 (&mut file.heights, ValidationWarning::HeightIsTooBig, true),
239 (&mut file.depths, ValidationWarning::DepthIsTooBig, true),
240 (
241 &mut file.italic_corrections,
242 ValidationWarning::ItalicCorrectionIsTooBig,
243 true,
244 ),
245 (&mut file.kerns, ValidationWarning::KernIsTooBig, false),
246 ] {
247 for (i, elem) in array.iter_mut().enumerate() {
248 if !elem.is_abs_less_than_16() {
249 warnings.push(dimension_too_big(i));
250 *elem = FixWord::ZERO
251 }
252 if i == 0 && zero_first_element {
253 *elem = FixWord::ZERO
254 }
255 }
256 }
257
258 let (_, next_larger_warnings) = NextLargerProgram::new(
259 file.char_tags
260 .iter()
261 .filter_map(|(c, t)| t.list().map(|l| (*c, l))),
262 |c| file.char_dimens.contains_key(&c),
263 true,
264 );
265 let mut next_larger_warnings: HashMap<Char, NextLargerProgramWarning> = next_larger_warnings
266 .into_iter()
267 .map(|w| (w.bad_char(), w))
268 .collect();
269
270 let lig_kern_warnings = file.lig_kern_program.validate_and_fix(
271 file.smallest_char,
272 file.char_tags
273 .iter()
274 .filter_map(|(c, t)| t.ligature().map(|l| (*c, l))),
275 |c| file.char_dimens.contains_key(&c),
276 &file.kerns,
277 );
278 lig_kern_warnings
279 .iter()
280 .filter_map(|w| match w {
281 lang::ValidationWarning::InvalidEntrypoint(c) => Some(c),
282 _ => None,
283 })
284 .for_each(|c| {
285 file.char_tags.remove(c);
286 });
287 let duplicated_warnings = {
293 let mut m: HashMap<usize, Vec<lang::ValidationWarning>> = Default::default();
294 lig_kern_warnings
295 .iter()
296 .cloned()
297 .filter_map(|warning| match warning {
298 lang::ValidationWarning::KernIndexTooBig(u) => Some((u, warning.clone())),
301 lang::ValidationWarning::EntrypointRedirectTooBig(u) => Some((u, warning.clone())),
303 lang::ValidationWarning::LigatureStepForNonExistentCharacter {
308 instruction_index,
309 right_char: _,
310 new_right_char,
311 } => {
312 if file.char_dimens.contains_key(&new_right_char) {
313 None
314 } else {
315 Some((
316 instruction_index,
317 lang::ValidationWarning::LigatureStepForNonExistentCharacter {
318 instruction_index,
319 right_char: new_right_char,
320 new_right_char,
321 },
322 ))
323 }
324 }
325 lang::ValidationWarning::LigatureStepProducesNonExistentCharacter {
327 instruction_index,
328 replacement_char: _,
329 new_replacement_char,
330 } => {
331 if file.char_dimens.contains_key(&new_replacement_char) {
332 None
333 } else {
334 Some((
335 instruction_index,
336 lang::ValidationWarning::LigatureStepProducesNonExistentCharacter {
337 instruction_index,
338 replacement_char: new_replacement_char,
339 new_replacement_char,
340 },
341 ))
342 }
343 }
344 lang::ValidationWarning::KernStepForNonExistentCharacter {
346 instruction_index,
347 right_char: _,
348 new_right_char,
349 } => {
350 if file.char_dimens.contains_key(&new_right_char) {
351 None
352 } else {
353 Some((
354 instruction_index,
355 lang::ValidationWarning::KernStepForNonExistentCharacter {
356 instruction_index,
357 right_char: new_right_char,
358 new_right_char,
359 },
360 ))
361 }
362 }
363 _ => None,
364 })
365 .for_each(|(u, w)| m.entry(u).or_default().push(w));
366 m
367 };
368 let has_infinite_loop = lig_kern_warnings
369 .iter()
370 .any(|f| matches!(f, lang::ValidationWarning::InfiniteLoop(_)));
371 if has_infinite_loop {
372 file.char_dimens.clear();
373 file.extensible_chars.clear();
374 }
375 warnings.extend(
376 lig_kern_warnings
377 .into_iter()
378 .map(ValidationWarning::LigKernWarning),
379 );
380
381 file.extensible_chars.iter_mut().for_each(|e| {
382 for piece in [&mut e.top, &mut e.middle, &mut e.bottom] {
384 let c = match piece {
385 None => continue,
386 Some(c) => *c,
387 };
388 if file.char_dimens.contains_key(&c) {
389 continue;
390 }
391 warnings.push(ValidationWarning::InvalidCharacterInExtensibleRecipe(c));
392 *piece = None;
393 }
394 if !file.char_dimens.contains_key(&e.rep) {
395 warnings.push(ValidationWarning::InvalidCharacterInExtensibleRecipe(e.rep));
396 }
397 });
398
399 for (c, dimens) in &mut file.char_dimens {
400 if dimens.width_index.get() as usize >= file.widths.len() {
401 warnings.push(ValidationWarning::InvalidWidthIndex(
402 *c,
403 dimens.width_index.get(),
404 ));
405 dimens.width_index = WidthIndex::Invalid;
406 }
407 if dimens.height_index as usize >= file.heights.len() {
408 warnings.push(ValidationWarning::InvalidHeightIndex(
409 *c,
410 dimens.height_index,
411 ));
412 dimens.height_index = 0;
413 }
414 if dimens.depth_index as usize >= file.depths.len() {
415 warnings.push(ValidationWarning::InvalidDepthIndex(*c, dimens.depth_index));
416 dimens.depth_index = 0;
417 }
418 if dimens.italic_index as usize >= file.italic_corrections.len() {
419 warnings.push(ValidationWarning::InvalidItalicCorrectionIndex(
420 *c,
421 dimens.italic_index,
422 ));
423 dimens.italic_index = 0
424 }
425 match file.char_tags.get(c) {
426 Some(CharTag::List(_)) => {
427 if let Some(warning) = next_larger_warnings.remove(c) {
428 warnings.push(ValidationWarning::NextLargerWarning(warning));
429 file.char_tags.remove(c);
430 }
431 }
432 Some(CharTag::Extension(e)) => {
433 if *e as usize >= file.extensible_chars.len() {
434 warnings.push(ValidationWarning::InvalidExtensibleRecipeIndex(*c, *e));
435 file.char_tags.remove(c);
436 }
437 }
438 Some(CharTag::Ligature(l)) => {
439 if let Ok(entrypoint) = file.lig_kern_program.unpack_entrypoint(*l) {
440 warnings.extend(
441 file.lig_kern_program
442 .instructions_for_entrypoint(entrypoint)
443 .map(|t| t.0)
444 .filter_map(|u| duplicated_warnings.get(&u))
445 .flatten()
446 .cloned()
447 .map(ValidationWarning::LigKernWarning),
448 );
449 }
450 }
451 _ => {}
452 }
453 }
454
455 warnings
456}
457
458fn validate_string(s: &mut String, max_len: usize, warnings: &mut Vec<ValidationWarning>) {
459 if s.chars().count() > max_len {
460 warnings.push(ValidationWarning::StringIsTooLong(s.len()));
461 *s = format!("{}", s.chars().next().unwrap_or(' '))
462 }
463 let new_s: String = s
464 .chars()
465 .map(|c| match c {
466 '(' | ')' => {
467 warnings.push(ValidationWarning::StringContainsParenthesis);
468 '/'
469 }
470 ' '..='~' => c.to_ascii_uppercase(),
471 _ => {
472 warnings.push(ValidationWarning::StringContainsNonstandardAsciiCharacter(
473 c,
474 ));
475 '?'
476 }
477 })
478 .collect();
479 *s = new_s;
480}