1use std::collections::HashMap;
9use std::collections::HashSet;
10
11use crate::Char;
12use crate::FixWord;
13
14#[derive(Clone, Debug, Default, PartialEq, Eq)]
20pub struct Program {
21 pub instructions: Vec<Instruction>,
22 pub left_boundary_char_entrypoint: Option<u16>,
23 pub right_boundary_char: Option<Char>,
24 pub passthrough: HashSet<u16>,
25}
26
27#[derive(Clone, Debug, PartialEq, Eq)]
34pub struct Instruction {
35 pub next_instruction: Option<u8>,
41 pub right_char: Char,
51 pub operation: Operation,
53}
54
55#[derive(Debug, PartialEq, Eq, Copy, Clone)]
57pub enum Operation {
58 Kern(FixWord),
62 KernAtIndex(u16),
66 Ligature {
70 char_to_insert: Char,
72 post_lig_operation: PostLigOperation,
74 post_lig_tag_invalid: bool,
81 },
82 EntrypointRedirect(u16, bool),
92}
93
94impl Operation {
95 pub(crate) fn lig_kern_operation_from_bytes(op_byte: u8, remainder: u8) -> Self {
96 match op_byte.checked_sub(128) {
97 Some(r) => Self::KernAtIndex(u16::from_be_bytes([r, remainder])),
98 None => {
99 let delete_next_char = op_byte.is_multiple_of(2);
101 let op_byte = op_byte / 2;
102 let delete_current_char = op_byte.is_multiple_of(2);
103 let skip = op_byte / 2;
104 use PostLigOperation::*;
105 let (post_lig_operation, post_lig_tag_invalid) =
106 match (delete_current_char, delete_next_char, skip) {
107 (false, false, 0) => (RetainBothMoveNowhere, false),
108 (false, false, 1) => (RetainBothMoveToInserted, false),
109 (false, false, 2) => (RetainBothMoveToRight, false),
110 (false, true, 0) => (RetainLeftMoveNowhere, false),
111 (false, true, 1) => (RetainLeftMoveToInserted, false),
112 (true, false, 0) => (RetainRightMoveToInserted, false),
113 (true, false, 1) => (RetainRightMoveToRight, false),
114 (true, true, 0) => (RetainNeitherMoveToInserted, false),
115 _ => (RetainNeitherMoveToInserted, true),
116 };
117 Self::Ligature {
118 char_to_insert: Char(remainder),
119 post_lig_operation,
120 post_lig_tag_invalid,
121 }
122 }
123 }
124 }
125}
126
127#[derive(Debug, PartialEq, Eq, Clone, Copy)]
153#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
154pub enum PostLigOperation {
155 RetainBothMoveNowhere,
157 RetainBothMoveToInserted,
159 RetainBothMoveToRight,
161 RetainRightMoveToInserted,
163 RetainRightMoveToRight,
165 RetainLeftMoveNowhere,
167 RetainLeftMoveToInserted,
169 RetainNeitherMoveToInserted,
171}
172
173#[derive(Clone, Debug)]
174pub enum InvalidEntrypointError {
175 Direct { entrypoint: u8 },
176 Indirect { packed: u8, unpacked: u16 },
177}
178
179impl Program {
180 pub fn unpack_entrypoint(&mut self, entrypoint: u8) -> Result<u16, InvalidEntrypointError> {
181 match self.instructions.get(entrypoint as usize) {
182 None => Err(InvalidEntrypointError::Direct { entrypoint }),
183 Some(instruction) => match instruction.operation {
184 Operation::EntrypointRedirect(u_big, _) => {
185 if (u_big as usize) < self.instructions.len() {
186 self.passthrough.insert(entrypoint as u16);
187 Ok(u_big)
188 } else {
189 Err(InvalidEntrypointError::Indirect {
190 packed: entrypoint,
191 unpacked: u_big,
192 })
193 }
194 }
195 _ => Ok(entrypoint as u16),
196 },
197 }
198 }
199
200 pub fn pack_entrypoints(&mut self, entrypoints: HashMap<Char, u16>) -> HashMap<Char, u8> {
201 let instructions = &mut self.instructions;
202 let ordered_entrypoints = {
203 let mut m: HashMap<u16, Vec<Char>> = Default::default();
204 for (c, u) in entrypoints {
205 m.entry(u).or_default().push(c);
206 }
207 let mut v: Vec<(u16, Vec<Char>)> = m.into_iter().collect();
208 v.sort_by_key(|(u, _)| *u);
209 v
210 };
211 let mut offset: u8 = if self.right_boundary_char.is_some() {
212 instructions.push(Instruction {
216 next_instruction: None,
217 right_char: self.right_boundary_char.unwrap_or(Char(0)),
218 operation: Operation::EntrypointRedirect(0, true),
219 });
220 1
221 } else {
222 0
223 };
224 let mut new_entrypoints: HashMap<Char, u8> = Default::default();
225 let mut redirects: Vec<u16> = vec![];
226 for (i, (u16_entrypoint, chars)) in ordered_entrypoints.into_iter().rev().enumerate() {
227 let u: u8 = match (u16_entrypoint + offset as u16).try_into() {
228 Ok(u) => u,
229 Err(_) => {
230 redirects.push(u16_entrypoint);
231 if i == 0 && self.right_boundary_char.is_some() {
232 instructions.pop();
234 offset = 0;
235 }
236 let u = offset;
237 offset = offset.checked_add(1).expect(
238 "offset is incremented at most once per 8-bit-char and so cannot exceed 256",
239 );
240 u
241 }
242 };
243 for c in chars {
244 new_entrypoints.insert(c, u);
245 }
246 }
247 for redirect in redirects {
248 instructions.push(Instruction {
249 next_instruction: None,
250 right_char: self.right_boundary_char.unwrap_or(Char(0)),
251 operation: Operation::EntrypointRedirect(
252 redirect.checked_add(offset as u16).expect("the inputted lig/kern instructions vector doesn't have enough space for new instructions"),
253 true,
254 ),
255 });
256 }
257 instructions.rotate_right(offset as usize);
258 if let Some(boundary_char_entrypoint) = self.left_boundary_char_entrypoint {
259 instructions.push(Instruction {
260 next_instruction: None,
261 right_char: Char(0),
262 operation: Operation::EntrypointRedirect(
263 boundary_char_entrypoint + offset as u16,
264 false,
265 ),
266 })
267 }
268 new_entrypoints
269 }
270
271 pub fn unpack_kerns(&mut self) -> Vec<FixWord> {
272 let mut kerns = vec![];
273 let mut kerns_dedup = HashMap::<FixWord, usize>::new();
274 for instruction in &mut self.instructions {
275 if let Operation::Kern(kern) = instruction.operation {
276 use std::collections::hash_map::Entry;
277 let index = match kerns_dedup.entry(kern) {
278 Entry::Occupied(o) => *o.get(),
279 Entry::Vacant(v) => {
280 let l = kerns.len();
281 v.insert(l);
282 kerns.push(kern);
283 l
284 }
285 };
286 instruction.operation = Operation::KernAtIndex(index.try_into().unwrap());
287 }
288 }
289 kerns
290 }
291
292 pub fn pack_kerns(&mut self, kerns: &[FixWord]) {
293 for i in &mut self.instructions {
294 if let Operation::KernAtIndex(index) = &i.operation {
295 i.operation =
298 Operation::Kern(kerns.get(*index as usize).copied().unwrap_or_default())
299 }
300 }
301 }
302
303 pub fn reachable_iter<I: Iterator<Item = (Char, u16)>>(
304 &self,
305 entrypoints: I,
306 ) -> ReachableIter<'_> {
307 ReachableIter {
308 next: 0,
309 reachable: self.reachable_array(entrypoints),
310 program: self,
311 }
312 }
313
314 pub fn instructions_for_entrypoint(
316 &self,
317 entrypoint: u16,
318 ) -> InstructionsForEntrypointIter<'_> {
319 InstructionsForEntrypointIter {
320 next: entrypoint as usize,
321 instructions: &self.instructions,
322 }
323 }
324
325 pub fn is_seven_bit_safe(&self, entrypoints: HashMap<Char, u16>) -> bool {
326 entrypoints
327 .into_iter()
328 .filter(|(c, _)| c.is_seven_bit())
329 .flat_map(|(_, e)| self.instructions_for_entrypoint(e))
330 .filter(|(_, instruction)| instruction.right_char.is_seven_bit())
331 .filter_map(|(_, instruction)| match instruction.operation {
332 Operation::Ligature { char_to_insert, .. } => Some(char_to_insert),
333 _ => None,
334 })
335 .all(|c| c.is_seven_bit())
336 }
337
338 pub fn validate_and_fix<I, T>(
339 &mut self,
340 smallest_char: Char,
341 entrypoints: I,
342 char_exists: T,
343 kerns: &[FixWord],
344 ) -> Vec<ValidationWarning>
345 where
346 I: Iterator<Item = (Char, u8)>,
347 T: Fn(Char) -> bool,
348 {
349 let mut warnings = vec![];
350 if let Some(entrypoint) = self.left_boundary_char_entrypoint {
352 if self.instructions.len() <= entrypoint as usize {
353 self.left_boundary_char_entrypoint = None;
354 warnings.push(ValidationWarning::InvalidBoundaryCharEntrypoint);
355 }
356 }
357 let unpacked_entrypoints: Vec<(Char, u16)> = entrypoints
359 .into_iter()
360 .filter_map(|(c, e)| match self.unpack_entrypoint(e) {
361 Ok(e) => Some((c, e)),
362 Err(_) => {
363 warnings.push(ValidationWarning::InvalidEntrypoint(c));
364 None
365 }
366 })
367 .collect();
368 let reachable = self.reachable_array(unpacked_entrypoints.iter().cloned());
369 let n = self.instructions.len();
370 self.instructions
372 .iter_mut()
373 .zip(reachable.iter())
374 .enumerate()
375 .filter(|(_, (_, reachable))| **reachable)
376 .filter_map(|(i, (instruction, _))| {
377 instruction
378 .next_instruction
379 .map(|inc| (i, inc, instruction))
380 })
381 .filter(|(i, inc, _)| *i + (*inc as usize) + 1 >= n)
382 .for_each(|(i, _, instruction)| {
383 instruction.next_instruction = None;
384 warnings.push(ValidationWarning::SkipTooLarge(i));
385 });
386
387 for (i, (instruction, reachable)) in self.instructions.iter_mut().zip(reachable).enumerate()
388 {
389 let is_kern_step = match instruction.operation {
390 Operation::Kern(_) | Operation::KernAtIndex(_) => true,
391 Operation::Ligature { .. } => false,
392 Operation::EntrypointRedirect(r, _) => {
393 if let Ok(u) = i.try_into() {
394 if !reachable && self.passthrough.contains(&u) {
396 continue;
397 }
398 }
399 if r as usize >= n {
400 warnings.push(ValidationWarning::EntrypointRedirectTooBig(i));
401 }
402 continue;
403 }
404 };
405 if !char_exists(instruction.right_char)
406 && Some(instruction.right_char) != self.right_boundary_char
407 {
408 warnings.push(if is_kern_step {
409 ValidationWarning::KernStepForNonExistentCharacter {
410 instruction_index: i,
411 right_char: instruction.right_char,
412 new_right_char: smallest_char,
413 }
414 } else {
415 ValidationWarning::LigatureStepForNonExistentCharacter {
416 instruction_index: i,
417 right_char: instruction.right_char,
418 new_right_char: smallest_char,
419 }
420 });
421 instruction.right_char = smallest_char;
422 }
423 match &mut instruction.operation {
424 Operation::Kern(_) => {}
425 Operation::KernAtIndex(k) => {
426 if *k as usize >= kerns.len() {
427 warnings.push(ValidationWarning::KernIndexTooBig(i));
428 instruction.operation = Operation::Kern(FixWord::ZERO);
429 }
430 }
431 Operation::Ligature {
432 char_to_insert,
433 post_lig_tag_invalid,
434 ..
435 } => {
436 if !char_exists(*char_to_insert) {
437 warnings.push(
438 ValidationWarning::LigatureStepProducesNonExistentCharacter {
439 instruction_index: i,
440 replacement_char: *char_to_insert,
441 new_replacement_char: smallest_char,
442 },
443 );
444 *char_to_insert = smallest_char;
445 }
446 if *post_lig_tag_invalid {
447 warnings.push(ValidationWarning::InvalidLigTag(i));
448 *post_lig_tag_invalid = false;
449 }
450 }
451 Operation::EntrypointRedirect(_, _) => {}
452 }
453 }
454
455 let entrypoints: HashMap<Char, u16> = unpacked_entrypoints.into_iter().collect();
456 let dummy_design_size = FixWord::ONE * 10;
460 let (_, errors) =
461 super::CompiledProgram::compile(self, dummy_design_size, kerns, entrypoints);
462 for err in errors {
463 warnings.push(ValidationWarning::InfiniteLoop(err));
464 }
465 warnings
466 }
467
468 fn reachable_array<I: Iterator<Item = (Char, u16)>>(&self, entrypoints: I) -> Vec<bool> {
469 let mut reachable = vec![false; self.instructions.len()];
470 for (_, entrypoint) in entrypoints {
472 if let Some(slot) = reachable.get_mut(entrypoint as usize) {
473 *slot = true;
474 }
475 }
476 if let Some(entrypoint) = self.left_boundary_char_entrypoint {
478 if entrypoint as usize != self.instructions.len() - 1 {
483 if let Some(slot) = reachable.get_mut(entrypoint as usize) {
484 *slot = true;
485 }
486 }
487 }
488 for i in 0..reachable.len() {
490 if !reachable[i] {
491 continue;
492 }
493 if let Some(inc) = self.instructions[i].next_instruction {
494 if let Some(slot) = reachable.get_mut(i + inc as usize + 1) {
495 *slot = true;
496 }
497 }
498 }
499 reachable
500 }
501}
502
503#[derive(Clone, Debug, PartialEq, Eq)]
504pub enum ValidationWarning {
505 SkipTooLarge(usize),
506 LigatureStepForNonExistentCharacter {
507 instruction_index: usize,
509 right_char: Char,
511 new_right_char: Char,
516 },
517 KernStepForNonExistentCharacter {
518 instruction_index: usize,
520 right_char: Char,
522 new_right_char: Char,
527 },
528 LigatureStepProducesNonExistentCharacter {
529 instruction_index: usize,
531 replacement_char: Char,
533 new_replacement_char: Char,
538 },
539 KernIndexTooBig(usize),
540 InvalidLigTag(usize),
541 EntrypointRedirectTooBig(usize),
542 InvalidEntrypoint(Char),
543 InvalidBoundaryCharEntrypoint,
544 InfiniteLoop(super::InfiniteLoopError),
545}
546
547impl ValidationWarning {
548 pub fn tftopl_message(&self) -> String {
550 use ValidationWarning::*;
551 match self {
552 SkipTooLarge(i) => {
553 format!["Bad TFM file: Ligature/kern step {i} skips too far;\nI made it stop."]
554 }
555 LigatureStepForNonExistentCharacter { right_char, .. } => format![
556 "Bad TFM file: Ligature step for nonexistent character '{:03o}.",
557 right_char.0
558 ],
559 KernStepForNonExistentCharacter { right_char, .. } => format![
560 "Bad TFM file: Kern step for nonexistent character '{:03o}.",
561 right_char.0
562 ],
563 LigatureStepProducesNonExistentCharacter {
564 replacement_char, ..
565 } => format![
566 "Bad TFM file: Ligature step produces the nonexistent character '{:03o}.",
567 replacement_char.0
568 ],
569 KernIndexTooBig(_) => "Bad TFM file: Kern index too large.".to_string(),
570 InvalidLigTag(_) => "Ligature step with nonstandard code changed to LIG".to_string(),
571 EntrypointRedirectTooBig(_) => {
572 "Bad TFM file: Ligature unconditional stop command address is too big.".to_string()
573 }
574 InvalidEntrypoint(c) => {
575 format![" \nLigature/kern starting index for character '{:03o} is too large;\nso I removed it.", c.0]
576 }
577 InvalidBoundaryCharEntrypoint => {
578 " \nLigature/kern starting index for boundarychar is too large;so I removed it."
579 .to_string()
580 }
581 InfiniteLoop(err) => err.pltotf_message(),
582 }
583 }
584
585 pub fn tftopl_section(&self) -> u8 {
587 use ValidationWarning::*;
588 match self {
589 SkipTooLarge(_) => 70,
590 LigatureStepForNonExistentCharacter { .. }
591 | LigatureStepProducesNonExistentCharacter { .. }
592 | InvalidLigTag(_) => 77,
593 KernStepForNonExistentCharacter { .. } | KernIndexTooBig(_) => 76,
594 EntrypointRedirectTooBig(_) => 74,
595 InvalidEntrypoint(_) => 67,
596 InvalidBoundaryCharEntrypoint => 69,
597 InfiniteLoop(_) => 90,
598 }
599 }
600}
601
602pub struct ReachableIter<'a> {
603 next: u16,
604 reachable: Vec<bool>,
605 program: &'a Program,
606}
607
608#[derive(Debug)]
609pub enum ReachableIterItem {
610 Reachable { adjusted_skip: Option<u8> },
611 Unreachable,
612 Passthrough,
613}
614
615impl<'a> Iterator for ReachableIter<'a> {
616 type Item = ReachableIterItem;
617
618 fn next(&mut self) -> Option<Self::Item> {
619 let this = self.next;
620 let instruction = self.program.instructions.get(this as usize)?;
621 self.next += 1;
622 Some(if self.reachable[this as usize] {
623 let adjusted_skip = match instruction.next_instruction {
624 None | Some(0) => None,
625 Some(inc) => {
626 match self
627 .reachable
628 .get(this as usize + 1..this as usize + 1 + inc as usize)
629 {
630 None => None,
631 Some(n) => {
632 let reachable_skipped: u8 =
633 n.iter()
634 .filter(|reachable| **reachable)
635 .count()
636 .try_into()
637 .expect("iterating over at most u8::MAX elements, so the count will be at most u8::MAX");
638 Some(reachable_skipped)
639 }
640 }
641 }
642 };
643 ReachableIterItem::Reachable { adjusted_skip }
644 } else if self.program.passthrough.contains(&this) {
645 ReachableIterItem::Passthrough
646 } else {
647 ReachableIterItem::Unreachable
648 })
649 }
650}
651
652pub struct InstructionsForEntrypointIter<'a> {
656 next: usize,
657 instructions: &'a [Instruction],
658}
659
660impl<'a> Iterator for InstructionsForEntrypointIter<'a> {
661 type Item = (usize, &'a Instruction);
662
663 fn next(&mut self) -> Option<Self::Item> {
664 self.instructions.get(self.next).map(|i| {
665 let this = self.next;
666 self.next = match i.next_instruction {
667 None => usize::MAX,
668 Some(inc) => self.next + inc as usize + 1,
669 };
670 (this, i)
671 })
672 }
673}