Skip to main content

qrcode_core/
optimize.rs

1#![allow(clippy::unicode_not_nfc)]
2//! Data mode segmentation optimizer.
3//!
4//! QR codes support four data modes (Numeric, Alphanumeric, Byte, Kanji),
5//! each with different efficiency for different character types. This module
6//! finds the optimal sequence of mode switches to minimize the total number
7//! of bits required to encode the input data.
8//!
9//! The optimizer uses dynamic programming to explore all possible mode
10//! transitions and selects the segmentation that produces the shortest
11//! bit stream for the target QR code version.
12#[cfg(not(feature = "std"))]
13#[allow(unused_imports)]
14use alloc::{
15    borrow::ToOwned,
16    format,
17    string::{String, ToString},
18    vec,
19    vec::Vec,
20};
21
22use crate::types::{Mode, Version};
23use core::cmp::Reverse;
24use core::marker::PhantomData;
25use core::slice::Iter;
26
27//------------------------------------------------------------------------------
28//{{{ Segment
29
30/// A segment of data committed to an encoding mode.
31#[derive(PartialEq, Eq, Debug, Copy, Clone)]
32pub struct Segment {
33    /// The encoding mode of the segment of data.
34    pub mode: Mode,
35
36    /// The start index of the segment.
37    pub begin: usize,
38
39    /// The end index (exclusive) of the segment.
40    pub end: usize,
41}
42
43impl Segment {
44    /// Compute the number of bits (including the size of the mode indicator and
45    /// length bits) when this segment is encoded.
46    pub fn encoded_len(&self, version: Version) -> usize {
47        let byte_size = self.end - self.begin;
48        let chars_count = if self.mode == Mode::Kanji { byte_size / 2 } else { byte_size };
49
50        let mode_bits_count = version.mode_bits_count();
51        let length_bits_count = self.mode.length_bits_count(version);
52        let data_bits_count = self.mode.data_bits_count(chars_count);
53
54        mode_bits_count + length_bits_count + data_bits_count
55    }
56}
57
58//}}}
59//------------------------------------------------------------------------------
60//{{{ Parser
61
62/// This iterator is basically equivalent to
63///
64/// ```ignore
65/// data.map(|c| ExclCharSet::from_u8(*c))
66///     .chain(Some(ExclCharSet::End).move_iter())
67///     .enumerate()
68/// ```
69///
70/// But the type is too hard to write, thus the new type.
71///
72struct EcsIter<I> {
73    base: I,
74    index: usize,
75    ended: bool,
76}
77
78impl<'a, I: Iterator<Item = &'a u8>> Iterator for EcsIter<I> {
79    type Item = (usize, ExclCharSet);
80
81    fn next(&mut self) -> Option<(usize, ExclCharSet)> {
82        if self.ended {
83            return None;
84        }
85
86        match self.base.next() {
87            None => {
88                self.ended = true;
89                Some((self.index, ExclCharSet::End))
90            }
91            Some(c) => {
92                let old_index = self.index;
93                self.index += 1;
94                Some((old_index, ExclCharSet::from_u8(*c)))
95            }
96        }
97    }
98}
99
100/// QR code data parser to classify the input into distinct segments.
101pub struct Parser<'a> {
102    ecs_iter: EcsIter<Iter<'a, u8>>,
103    state: State,
104    begin: usize,
105    pending_single_byte: bool,
106}
107
108impl<'a> Parser<'a> {
109    /// Creates a new iterator which parse the data into segments that only
110    /// contains their exclusive subsets. No optimization is done at this point.
111    ///
112    ///     use qrcode_core::optimize::{Parser, Segment};
113    ///     use qrcode_core::types::Mode::{Alphanumeric, Numeric, Byte};
114    ///
115    ///     let parse_res = Parser::new(b"ABC123abcd").collect::<Vec<Segment>>();
116    ///     assert_eq!(parse_res, vec![Segment { mode: Alphanumeric, begin: 0, end: 3 },
117    ///                                Segment { mode: Numeric, begin: 3, end: 6 },
118    ///                                Segment { mode: Byte, begin: 6, end: 10 }]);
119    ///
120    pub fn new(data: &[u8]) -> Parser<'_> {
121        Parser {
122            ecs_iter: EcsIter { base: data.iter(), index: 0, ended: false },
123            state: State::Init,
124            begin: 0,
125            pending_single_byte: false,
126        }
127    }
128}
129
130impl<'a> Iterator for Parser<'a> {
131    type Item = Segment;
132
133    fn next(&mut self) -> Option<Segment> {
134        if self.pending_single_byte {
135            self.pending_single_byte = false;
136            self.begin += 1;
137            return Some(Segment { mode: Mode::Byte, begin: self.begin - 1, end: self.begin });
138        }
139
140        loop {
141            let (i, ecs) = self.ecs_iter.next()?;
142            let (next_state, action) = STATE_TRANSITION[self.state as usize + ecs as usize];
143            self.state = next_state;
144
145            let old_begin = self.begin;
146            let push_mode = match action {
147                Action::Idle => continue,
148                Action::Numeric => Mode::Numeric,
149                Action::Alpha => Mode::Alphanumeric,
150                Action::Byte => Mode::Byte,
151                Action::Kanji => Mode::Kanji,
152                Action::KanjiAndSingleByte => {
153                    let next_begin = i - 1;
154                    if self.begin == next_begin {
155                        Mode::Byte
156                    } else {
157                        self.pending_single_byte = true;
158                        self.begin = next_begin;
159                        return Some(Segment { mode: Mode::Kanji, begin: old_begin, end: next_begin });
160                    }
161                }
162            };
163
164            self.begin = i;
165            return Some(Segment { mode: push_mode, begin: old_begin, end: i });
166        }
167    }
168}
169
170#[cfg(test)]
171mod parse_tests {
172    use crate::optimize::{Parser, Segment};
173    use crate::types::Mode;
174
175    fn parse(data: &[u8]) -> Vec<Segment> {
176        Parser::new(data).collect()
177    }
178
179    #[test]
180    fn test_parse_1() {
181        let segs = parse(b"01049123451234591597033130128%10ABC123");
182        assert_eq!(
183            segs,
184            vec![
185                Segment { mode: Mode::Numeric, begin: 0, end: 29 },
186                Segment { mode: Mode::Alphanumeric, begin: 29, end: 30 },
187                Segment { mode: Mode::Numeric, begin: 30, end: 32 },
188                Segment { mode: Mode::Alphanumeric, begin: 32, end: 35 },
189                Segment { mode: Mode::Numeric, begin: 35, end: 38 },
190            ]
191        );
192    }
193
194    #[test]
195    fn test_parse_shift_jis_example_1() {
196        let segs = parse(b"\x82\xa0\x81\x41\x41\xb1\x81\xf0"); // "あ、AアÅ"
197        assert_eq!(
198            segs,
199            vec![
200                Segment { mode: Mode::Kanji, begin: 0, end: 4 },
201                Segment { mode: Mode::Alphanumeric, begin: 4, end: 5 },
202                Segment { mode: Mode::Byte, begin: 5, end: 6 },
203                Segment { mode: Mode::Kanji, begin: 6, end: 8 },
204            ]
205        );
206    }
207
208    #[test]
209    fn test_parse_utf_8() {
210        // Mojibake?
211        let segs = parse(b"\xe3\x81\x82\xe3\x80\x81A\xef\xbd\xb1\xe2\x84\xab");
212        assert_eq!(
213            segs,
214            vec![
215                Segment { mode: Mode::Kanji, begin: 0, end: 4 },
216                Segment { mode: Mode::Byte, begin: 4, end: 5 },
217                Segment { mode: Mode::Kanji, begin: 5, end: 7 },
218                Segment { mode: Mode::Byte, begin: 7, end: 10 },
219                Segment { mode: Mode::Kanji, begin: 10, end: 12 },
220                Segment { mode: Mode::Byte, begin: 12, end: 13 },
221            ]
222        );
223    }
224
225    #[test]
226    fn test_not_kanji_1() {
227        let segs = parse(b"\x81\x30");
228        assert_eq!(
229            segs,
230            vec![Segment { mode: Mode::Byte, begin: 0, end: 1 }, Segment { mode: Mode::Numeric, begin: 1, end: 2 }]
231        );
232    }
233
234    #[test]
235    fn test_not_kanji_2() {
236        // Note that it's implementation detail that the byte seq is split into
237        // two. Perhaps adjust the test to check for this.
238        let segs = parse(b"\xeb\xc0");
239        assert_eq!(
240            segs,
241            vec![Segment { mode: Mode::Byte, begin: 0, end: 1 }, Segment { mode: Mode::Byte, begin: 1, end: 2 }]
242        );
243    }
244
245    #[test]
246    fn test_not_kanji_3() {
247        let segs = parse(b"\x81\x7f");
248        assert_eq!(
249            segs,
250            vec![Segment { mode: Mode::Byte, begin: 0, end: 1 }, Segment { mode: Mode::Byte, begin: 1, end: 2 }]
251        );
252    }
253
254    #[test]
255    fn test_not_kanji_4() {
256        let segs = parse(b"\x81\x40\x81");
257        assert_eq!(
258            segs,
259            vec![Segment { mode: Mode::Kanji, begin: 0, end: 2 }, Segment { mode: Mode::Byte, begin: 2, end: 3 }]
260        );
261    }
262}
263
264//}}}
265//------------------------------------------------------------------------------
266//{{{ Optimizer
267
268/// Iterator that merges consecutive parser segments to minimize the total
269/// encoded length for a given [`Version`]. Created via [`Parser::optimize`].
270pub struct Optimizer<I> {
271    optimized: Vec<Segment>,
272    index: usize,
273    _source: PhantomData<I>,
274}
275
276impl<I: Iterator<Item = Segment>> Optimizer<I> {
277    /// Optimize the segments by combining adjacent segments when beneficial.
278    ///
279    /// This uses dynamic programming over the parser's segment boundaries. It
280    /// finds the minimum-size contiguous merge plan for those boundaries, but
281    /// does not split a parser segment into smaller pieces.
282    ///
283    pub fn new(segments: I, version: Version) -> Self {
284        let segments = segments.collect::<Vec<_>>();
285        Self { optimized: optimize_segments(&segments, version), index: 0, _source: PhantomData }
286    }
287}
288
289impl<'a> Parser<'a> {
290    /// Turns this parser into an [`Optimizer`] for `version`, which yields
291    /// optimally merged segments.
292    pub fn optimize(self, version: Version) -> Optimizer<Parser<'a>> {
293        Optimizer::new(self, version)
294    }
295}
296
297impl<I: Iterator<Item = Segment>> Iterator for Optimizer<I> {
298    type Item = Segment;
299
300    fn next(&mut self) -> Option<Segment> {
301        let segment = self.optimized.get(self.index).copied();
302        if segment.is_some() {
303            self.index += 1;
304        }
305        segment
306    }
307
308    fn size_hint(&self) -> (usize, Option<usize>) {
309        let remaining = self.optimized.len().saturating_sub(self.index);
310        (remaining, Some(remaining))
311    }
312}
313
314impl<I: Iterator<Item = Segment>> ExactSizeIterator for Optimizer<I> {}
315impl<I: Iterator<Item = Segment>> core::iter::FusedIterator for Optimizer<I> {}
316
317/// Computes the total encoded length of all segments.
318pub fn total_encoded_len(segments: &[Segment], version: Version) -> usize {
319    segments.iter().map(|seg| seg.encoded_len(version)).sum()
320}
321
322/// Computes the minimum-size merge plan for parser segments.
323///
324/// Segment boundaries are preserved; adjacent segments may be merged into the
325/// smallest common data mode that can encode the merged range.
326///
327/// Large inputs use an exact linear-time dynamic program with a fixed number of
328/// candidate buckets. Small inputs and exceptional coordinate ranges use the
329/// quadratic reference algorithm.
330#[must_use]
331pub fn optimize_segments(segments: &[Segment], version: Version) -> Vec<Segment> {
332    if segments.len() <= 32 || !supports_linear_costs(segments, version) {
333        optimize_segments_quadratic(segments, version)
334    } else {
335        optimize_segments_linear(segments, version)
336    }
337}
338
339#[derive(Clone, Copy)]
340struct ModeCost {
341    mode: Mode,
342    period: usize,
343    bits_per_period: i128,
344    bucket_offset: usize,
345}
346
347const MODE_COSTS: [ModeCost; 4] = [
348    ModeCost { mode: Mode::Numeric, period: 3, bits_per_period: 10, bucket_offset: 0 },
349    ModeCost { mode: Mode::Alphanumeric, period: 2, bits_per_period: 11, bucket_offset: 3 },
350    ModeCost { mode: Mode::Byte, period: 1, bits_per_period: 8, bucket_offset: 5 },
351    ModeCost { mode: Mode::Kanji, period: 2, bits_per_period: 13, bucket_offset: 6 },
352];
353
354fn mode_index(mode: Mode) -> usize {
355    match mode {
356        Mode::Numeric => 0,
357        Mode::Alphanumeric => 1,
358        Mode::Byte => 2,
359        Mode::Kanji => 3,
360    }
361}
362
363fn supports_linear_costs(segments: &[Segment], version: Version) -> bool {
364    // Public segments may have arbitrary coordinates. Keep reference behavior
365    // when suffix lengths or the original cost arithmetic could overflow.
366    let mut min_begin = usize::MAX;
367    let mut max_begin = 0;
368    let mut max_end = 0;
369    for segment in segments {
370        min_begin = min_begin.min(segment.begin);
371        max_begin = max_begin.max(segment.begin);
372        max_end = max_end.max(segment.end);
373        if segment.end < max_begin {
374            return false;
375        }
376    }
377    let max_header = MODE_COSTS
378        .iter()
379        .map(|cost| version.mode_bits_count() + cost.mode.length_bits_count(version))
380        .max()
381        .unwrap_or(0);
382    (max_end - min_begin).checked_mul(13).and_then(|bits| bits.checked_add(max_header)).is_some()
383}
384
385fn prefer_start(
386    candidate: usize,
387    current: usize,
388    cost: ModeCost,
389    segments: &[Segment],
390    best_bits: &[usize],
391    best_count: &[usize],
392) -> bool {
393    if current == usize::MAX {
394        return true;
395    }
396    // For equal begin residues, the endpoint and rounding terms are shared.
397    // Signed wide keys also handle large absolute offsets without underflow.
398    let key = |start: usize| {
399        (
400            best_bits[start] as i128 - (segments[start].begin / cost.period) as i128 * cost.bits_per_period,
401            best_count[start],
402            Reverse(start),
403        )
404    };
405    key(candidate) < key(current)
406}
407
408fn optimize_segments_linear(segments: &[Segment], version: Version) -> Vec<Segment> {
409    let len = segments.len();
410    if len <= 1 {
411        return segments.to_vec();
412    }
413    let mut best_bits = vec![usize::MAX; len + 1];
414    let mut best_count = vec![usize::MAX; len + 1];
415    let mut previous = vec![0_usize; len + 1];
416    let mut previous_mode = vec![Mode::Byte; len + 1];
417    best_bits[0] = 0;
418    best_count[0] = 0;
419
420    // Each suffix-mode group retains its best start under every future mode's
421    // cost, since the best Numeric start need not stay best after a Byte join.
422    let mut groups = [[usize::MAX; 8]; 4];
423    for end in 1..=len {
424        let incoming = segments[end - 1].mode;
425        // The join is idempotent, so destination groups will not move again
426        // when encountered later in this same pass.
427        for (source_index, source_cost) in MODE_COSTS.iter().enumerate() {
428            let destination = source_cost.mode.max(incoming);
429            if destination == source_cost.mode {
430                continue;
431            }
432            let migrating = core::mem::replace(&mut groups[source_index], [usize::MAX; 8]);
433            let destination_group = &mut groups[mode_index(destination)];
434            for cost in MODE_COSTS {
435                for bucket in cost.bucket_offset..cost.bucket_offset + cost.period {
436                    let start = migrating[bucket];
437                    if start != usize::MAX
438                        && prefer_start(start, destination_group[bucket], cost, segments, &best_bits, &best_count)
439                    {
440                        destination_group[bucket] = start;
441                    }
442                }
443            }
444        }
445
446        let start = end - 1;
447        if best_bits[start] != usize::MAX {
448            for cost in MODE_COSTS {
449                let bucket = cost.bucket_offset + segments[start].begin % cost.period;
450                let group = &mut groups[mode_index(incoming)];
451                if prefer_start(start, group[bucket], cost, segments, &best_bits, &best_count) {
452                    group[bucket] = start;
453                }
454            }
455        }
456
457        for (group_index, cost) in MODE_COSTS.iter().enumerate() {
458            for &start in &groups[group_index][cost.bucket_offset..cost.bucket_offset + cost.period] {
459                if start == usize::MAX {
460                    continue;
461                }
462                let merged = Segment { mode: cost.mode, begin: segments[start].begin, end: segments[end - 1].end };
463                let Some(candidate_bits) = best_bits[start].checked_add(merged.encoded_len(version)) else {
464                    continue;
465                };
466                let candidate_count = best_count[start] + 1;
467                // An exact tie keeps the rightmost start, matching the
468                // reference algorithm's backwards candidate scan.
469                if (candidate_bits, candidate_count, Reverse(start))
470                    < (best_bits[end], best_count[end], Reverse(previous[end]))
471                {
472                    best_bits[end] = candidate_bits;
473                    best_count[end] = candidate_count;
474                    previous[end] = start;
475                    previous_mode[end] = cost.mode;
476                }
477            }
478        }
479    }
480
481    let mut cursor = len;
482    let mut optimized = Vec::with_capacity(best_count[len]);
483    while cursor > 0 {
484        let start = previous[cursor];
485        optimized.push(Segment {
486            mode: previous_mode[cursor],
487            begin: segments[start].begin,
488            end: segments[cursor - 1].end,
489        });
490        cursor = start;
491    }
492    optimized.reverse();
493    optimized
494}
495
496fn optimize_segments_quadratic(segments: &[Segment], version: Version) -> Vec<Segment> {
497    let len = segments.len();
498    if len <= 1 {
499        return segments.to_vec();
500    }
501
502    let mut best_bits = vec![usize::MAX; len + 1];
503    let mut best_count = vec![usize::MAX; len + 1];
504    let mut previous = vec![0_usize; len + 1];
505    let mut previous_mode = vec![Mode::Byte; len + 1];
506    best_bits[0] = 0;
507    best_count[0] = 0;
508
509    for end in 1..=len {
510        let mut mode = segments[end - 1].mode;
511        for start in (0..end).rev() {
512            if start + 1 < end {
513                mode = segments[start].mode.max(mode);
514            }
515            let merged = Segment { mode, begin: segments[start].begin, end: segments[end - 1].end };
516            let Some(candidate_bits) = best_bits[start].checked_add(merged.encoded_len(version)) else {
517                continue;
518            };
519            let candidate_count = best_count[start] + 1;
520            if candidate_bits < best_bits[end] || candidate_bits == best_bits[end] && candidate_count < best_count[end]
521            {
522                best_bits[end] = candidate_bits;
523                best_count[end] = candidate_count;
524                previous[end] = start;
525                previous_mode[end] = mode;
526            }
527        }
528    }
529
530    let mut cursor = len;
531    let mut optimized = Vec::with_capacity(best_count[len]);
532    while cursor > 0 {
533        let start = previous[cursor];
534        optimized.push(Segment {
535            mode: previous_mode[cursor],
536            begin: segments[start].begin,
537            end: segments[cursor - 1].end,
538        });
539        cursor = start;
540    }
541    optimized.reverse();
542    optimized
543}
544
545#[cfg(test)]
546mod optimize_tests {
547    use crate::optimize::{
548        Optimizer, Parser, Segment, optimize_segments, optimize_segments_linear, optimize_segments_quadratic,
549        supports_linear_costs, total_encoded_len,
550    };
551    use crate::types::{Mode, Version};
552
553    fn test_optimization_result(given: &[Segment], expected: &[Segment], version: Version) {
554        let prev_len = total_encoded_len(given, version);
555        let opt_segs = Optimizer::new(given.iter().copied(), version).collect::<Vec<_>>();
556        let new_len = total_encoded_len(&opt_segs, version);
557        if given != opt_segs {
558            assert!(prev_len > new_len, "{prev_len} > {new_len}");
559        }
560        assert_eq!(
561            opt_segs,
562            expected,
563            "Optimization gave something better: {} < {} ({:?})",
564            new_len,
565            total_encoded_len(expected, version),
566            opt_segs
567        );
568    }
569
570    #[test]
571    fn zero_or_one_segment_preserves_the_input_for_every_version_group() {
572        let versions =
573            [Version::Normal(1), Version::Normal(10), Version::Normal(27), Version::Micro(1), Version::Micro(4)];
574        for version in versions {
575            assert!(optimize_segments(&[], version).is_empty());
576            for mode in [Mode::Numeric, Mode::Alphanumeric, Mode::Byte, Mode::Kanji] {
577                let segment = Segment { mode, begin: 12, end: 30 };
578                assert_eq!(optimize_segments(&[segment], version), vec![segment]);
579            }
580        }
581    }
582
583    #[test]
584    fn test_example_1() {
585        test_optimization_result(
586            &[
587                Segment { mode: Mode::Alphanumeric, begin: 0, end: 3 },
588                Segment { mode: Mode::Numeric, begin: 3, end: 6 },
589                Segment { mode: Mode::Byte, begin: 6, end: 10 },
590            ],
591            &[Segment { mode: Mode::Alphanumeric, begin: 0, end: 6 }, Segment { mode: Mode::Byte, begin: 6, end: 10 }],
592            Version::Normal(1),
593        );
594    }
595
596    #[test]
597    fn test_example_2() {
598        test_optimization_result(
599            &[
600                Segment { mode: Mode::Numeric, begin: 0, end: 29 },
601                Segment { mode: Mode::Alphanumeric, begin: 29, end: 30 },
602                Segment { mode: Mode::Numeric, begin: 30, end: 32 },
603                Segment { mode: Mode::Alphanumeric, begin: 32, end: 35 },
604                Segment { mode: Mode::Numeric, begin: 35, end: 38 },
605            ],
606            &[
607                Segment { mode: Mode::Numeric, begin: 0, end: 29 },
608                Segment { mode: Mode::Alphanumeric, begin: 29, end: 38 },
609            ],
610            Version::Normal(9),
611        );
612    }
613
614    #[test]
615    fn test_example_3() {
616        test_optimization_result(
617            &[
618                Segment { mode: Mode::Kanji, begin: 0, end: 4 },
619                Segment { mode: Mode::Alphanumeric, begin: 4, end: 5 },
620                Segment { mode: Mode::Byte, begin: 5, end: 6 },
621                Segment { mode: Mode::Kanji, begin: 6, end: 8 },
622            ],
623            &[Segment { mode: Mode::Byte, begin: 0, end: 8 }],
624            Version::Normal(1),
625        );
626    }
627
628    #[test]
629    fn test_example_4() {
630        test_optimization_result(
631            &[Segment { mode: Mode::Kanji, begin: 0, end: 10 }, Segment { mode: Mode::Byte, begin: 10, end: 11 }],
632            &[Segment { mode: Mode::Kanji, begin: 0, end: 10 }, Segment { mode: Mode::Byte, begin: 10, end: 11 }],
633            Version::Normal(1),
634        );
635    }
636
637    #[test]
638    fn test_annex_j_guideline_1a() {
639        test_optimization_result(
640            &[
641                Segment { mode: Mode::Numeric, begin: 0, end: 3 },
642                Segment { mode: Mode::Alphanumeric, begin: 3, end: 4 },
643            ],
644            &[
645                Segment { mode: Mode::Numeric, begin: 0, end: 3 },
646                Segment { mode: Mode::Alphanumeric, begin: 3, end: 4 },
647            ],
648            Version::Micro(2),
649        );
650    }
651
652    #[test]
653    fn test_annex_j_guideline_1b() {
654        test_optimization_result(
655            &[
656                Segment { mode: Mode::Numeric, begin: 0, end: 2 },
657                Segment { mode: Mode::Alphanumeric, begin: 2, end: 4 },
658            ],
659            &[Segment { mode: Mode::Alphanumeric, begin: 0, end: 4 }],
660            Version::Micro(2),
661        );
662    }
663
664    #[test]
665    fn test_annex_j_guideline_1c() {
666        test_optimization_result(
667            &[
668                Segment { mode: Mode::Numeric, begin: 0, end: 3 },
669                Segment { mode: Mode::Alphanumeric, begin: 3, end: 4 },
670            ],
671            &[Segment { mode: Mode::Alphanumeric, begin: 0, end: 4 }],
672            Version::Micro(3),
673        );
674    }
675
676    #[test]
677    fn dynamic_programming_can_skip_a_local_merge_for_a_better_total() {
678        let given = [
679            Segment { mode: Mode::Numeric, begin: 0, end: 7 },
680            Segment { mode: Mode::Alphanumeric, begin: 7, end: 8 },
681            Segment { mode: Mode::Numeric, begin: 8, end: 9 },
682        ];
683
684        let optimized = optimize_segments(&given, Version::Normal(1));
685
686        assert_eq!(
687            optimized,
688            vec![
689                Segment { mode: Mode::Numeric, begin: 0, end: 7 },
690                Segment { mode: Mode::Alphanumeric, begin: 7, end: 9 },
691            ]
692        );
693        assert!(
694            total_encoded_len(&optimized, Version::Normal(1))
695                < total_encoded_len(&[Segment { mode: Mode::Alphanumeric, begin: 0, end: 9 }], Version::Normal(1))
696        );
697    }
698
699    const COST_VERSIONS: [Version; 7] = [
700        Version::Normal(1),
701        Version::Normal(10),
702        Version::Normal(27),
703        Version::Micro(1),
704        Version::Micro(2),
705        Version::Micro(3),
706        Version::Micro(4),
707    ];
708    const MODES: [Mode; 4] = [Mode::Numeric, Mode::Alphanumeric, Mode::Byte, Mode::Kanji];
709
710    fn assert_matches_quadratic(segments: &[Segment], version: Version) {
711        let expected = optimize_segments_quadratic(segments, version);
712        assert_eq!(
713            optimize_segments_linear(segments, version),
714            expected,
715            "linear plan: version {version:?}, segments {segments:?}"
716        );
717        assert_eq!(
718            optimize_segments(segments, version),
719            expected,
720            "hybrid plan: version {version:?}, segments {segments:?}"
721        );
722    }
723
724    #[test]
725    fn linear_plan_matches_quadratic_for_exhaustive_modes_and_length_residues() {
726        for len in 0..=6 {
727            for mut encoded_modes in 0..4_usize.pow(len) {
728                let modes = (0..len)
729                    .map(|_| {
730                        let mode = MODES[encoded_modes % 4];
731                        encoded_modes /= 4;
732                        mode
733                    })
734                    .collect::<Vec<_>>();
735                for length_pattern in 0..6 {
736                    let mut begin = 17;
737                    let segments = modes
738                        .iter()
739                        .enumerate()
740                        .map(|(index, &mode)| {
741                            let length = match length_pattern {
742                                0..=3 => length_pattern,
743                                4 => 7,
744                                _ => index % 6 + 1,
745                            };
746                            let segment = Segment { mode, begin, end: begin + length };
747                            begin = segment.end;
748                            segment
749                        })
750                        .collect::<Vec<_>>();
751                    for version in COST_VERSIONS {
752                        assert_matches_quadratic(&segments, version);
753                    }
754                }
755            }
756        }
757    }
758
759    fn next_random(seed: &mut u64) -> u64 {
760        *seed ^= *seed << 13;
761        *seed ^= *seed >> 7;
762        *seed ^= *seed << 17;
763        *seed
764    }
765
766    #[test]
767    fn linear_plan_matches_quadratic_for_long_random_sequences_and_shifted_offsets() {
768        let versions = [
769            Version::Normal(1),
770            Version::Normal(9),
771            Version::Normal(10),
772            Version::Normal(26),
773            Version::Normal(27),
774            Version::Normal(40),
775            Version::Micro(1),
776            Version::Micro(2),
777            Version::Micro(3),
778            Version::Micro(4),
779        ];
780        let mut seed = 2_712_u64;
781        for case in 0..512 {
782            let len = next_random(&mut seed) as usize % 224 + 33;
783            let mut begin = if case % 8 == 0 { usize::MAX - 65_536 } else { next_random(&mut seed) as usize % 1024 };
784            let segments = (0..len)
785                .map(|_| {
786                    let mode = MODES[next_random(&mut seed) as usize % 4];
787                    let length = next_random(&mut seed) as usize % 71;
788                    let gap = next_random(&mut seed) as usize % 5;
789                    let segment = Segment { mode, begin, end: begin + length };
790                    begin = segment.end + gap;
791                    segment
792                })
793                .collect::<Vec<_>>();
794            assert!(supports_linear_costs(&segments, versions[case % versions.len()]));
795            assert_matches_quadratic(&segments, versions[case % versions.len()]);
796        }
797    }
798
799    #[test]
800    fn linear_plan_matches_quadratic_at_the_hybrid_boundary() {
801        for len in [31, 32, 33, 64, 256, 1024] {
802            let data = (0..len).map(|index| if index % 2 == 0 { b'A' } else { b'1' }).collect::<Vec<_>>();
803            let segments = Parser::new(&data).collect::<Vec<_>>();
804            for version in COST_VERSIONS {
805                assert_matches_quadratic(&segments, version);
806            }
807        }
808    }
809
810    #[test]
811    fn linear_plan_keeps_the_rightmost_start_when_bits_and_segment_count_tie() {
812        let given = [
813            Segment { mode: Mode::Numeric, begin: 0, end: 7 },
814            Segment { mode: Mode::Alphanumeric, begin: 7, end: 8 },
815            Segment { mode: Mode::Numeric, begin: 8, end: 15 },
816        ];
817        let expected = vec![
818            Segment { mode: Mode::Alphanumeric, begin: 0, end: 8 },
819            Segment { mode: Mode::Numeric, begin: 8, end: 15 },
820        ];
821        let optimized = optimize_segments_linear(&given, Version::Normal(1));
822        assert_eq!(total_encoded_len(&optimized, Version::Normal(1)), 95);
823        assert_eq!(optimized, expected);
824        assert_matches_quadratic(&given, Version::Normal(1));
825    }
826
827    #[test]
828    fn exceptional_coordinates_preserve_the_quadratic_fallback() {
829        for segments in [
830            vec![Segment { mode: Mode::Byte, begin: 2, end: 1 }; 33],
831            (0..33)
832                .map(|index| {
833                    let begin = if index == 0 { 0 } else { usize::MAX / 4 };
834                    Segment { mode: Mode::Byte, begin, end: begin }
835                })
836                .collect::<Vec<_>>(),
837        ] {
838            assert!(!supports_linear_costs(&segments, Version::Normal(1)));
839            let actual = std::panic::catch_unwind(|| optimize_segments(&segments, Version::Normal(1)));
840            let expected = std::panic::catch_unwind(|| optimize_segments_quadratic(&segments, Version::Normal(1)));
841            match (actual, expected) {
842                (Ok(actual), Ok(expected)) => assert_eq!(actual, expected),
843                (Err(_), Err(_)) => {}
844                _ => panic!("the hybrid path changed the exceptional-coordinate behavior"),
845            }
846        }
847    }
848}
849
850//}}}
851//------------------------------------------------------------------------------
852//{{{ Internal types and data for parsing
853
854/// All values of `u8` can be split into 9 different character sets when
855/// determining which encoding to use. This enum represents these groupings for
856/// parsing purpose.
857#[derive(Copy, Clone)]
858enum ExclCharSet {
859    /// The end of string.
860    End = 0,
861
862    /// All symbols supported by the Alphanumeric encoding, i.e. space, `$`, `%`,
863    /// `*`, `+`, `-`, `.`, `/` and `:`.
864    Symbol = 1,
865
866    /// All numbers (0–9).
867    Numeric = 2,
868
869    /// All uppercase letters (A–Z). These characters may also appear in the
870    /// second byte of a Shift JIS 2-byte encoding.
871    Alpha = 3,
872
873    /// The first byte of a Shift JIS 2-byte encoding, in the range 0x81–0x9f.
874    KanjiHi1 = 4,
875
876    /// The first byte of a Shift JIS 2-byte encoding, in the range 0xe0–0xea.
877    KanjiHi2 = 5,
878
879    /// The first byte of a Shift JIS 2-byte encoding, of value 0xeb. This is
880    /// different from the other two range that the second byte has a smaller
881    /// range.
882    KanjiHi3 = 6,
883
884    /// The second byte of a Shift JIS 2-byte encoding, in the range 0x40–0xbf,
885    /// excluding letters (covered by `Alpha`), 0x81–0x9f (covered by `KanjiHi1`),
886    /// and the invalid byte 0x7f.
887    KanjiLo1 = 7,
888
889    /// The second byte of a Shift JIS 2-byte encoding, in the range 0xc0–0xfc,
890    /// excluding the range 0xe0–0xeb (covered by `KanjiHi2` and `KanjiHi3`).
891    /// This half of byte-pair cannot appear as the second byte leaded by
892    /// `KanjiHi3`.
893    KanjiLo2 = 8,
894
895    /// Any other values not covered by the above character sets.
896    Byte = 9,
897}
898
899impl ExclCharSet {
900    /// Determines which character set a byte is in.
901    fn from_u8(c: u8) -> Self {
902        match c {
903            0x20 | 0x24 | 0x25 | 0x2a | 0x2b | 0x2d..=0x2f | 0x3a => ExclCharSet::Symbol,
904            0x30..=0x39 => ExclCharSet::Numeric,
905            0x41..=0x5a => ExclCharSet::Alpha,
906            0x81..=0x9f => ExclCharSet::KanjiHi1,
907            0xe0..=0xea => ExclCharSet::KanjiHi2,
908            0xeb => ExclCharSet::KanjiHi3,
909            0x40 | 0x5b..=0x7e | 0x80 | 0xa0..=0xbf => ExclCharSet::KanjiLo1,
910            0xc0..=0xdf | 0xec..=0xfc => ExclCharSet::KanjiLo2,
911            _ => ExclCharSet::Byte,
912        }
913    }
914}
915
916/// The current parsing state.
917#[derive(Copy, Clone)]
918enum State {
919    /// Just initialized.
920    Init = 0,
921
922    /// Inside a string that can be exclusively encoded as Numeric.
923    Numeric = 10,
924
925    /// Inside a string that can be exclusively encoded as Alphanumeric.
926    Alpha = 20,
927
928    /// Inside a string that can be exclusively encoded as 8-Bit Byte.
929    Byte = 30,
930
931    /// Just encountered the first byte of a Shift JIS 2-byte sequence of the
932    /// set `KanjiHi1` or `KanjiHi2`.
933    KanjiHi12 = 40,
934
935    /// Just encountered the first byte of a Shift JIS 2-byte sequence of the
936    /// set `KanjiHi3`.
937    KanjiHi3 = 50,
938
939    /// Inside a string that can be exclusively encoded as Kanji.
940    Kanji = 60,
941}
942
943/// What should the parser do after a state transition.
944#[derive(Copy, Clone)]
945enum Action {
946    /// The parser should do nothing.
947    Idle,
948
949    /// Push the current segment as a Numeric string, and reset the marks.
950    Numeric,
951
952    /// Push the current segment as an Alphanumeric string, and reset the marks.
953    Alpha,
954
955    /// Push the current segment as a 8-Bit Byte string, and reset the marks.
956    Byte,
957
958    /// Push the current segment as a Kanji string, and reset the marks.
959    Kanji,
960
961    /// Push the current segment excluding the last byte as a Kanji string, then
962    /// push the remaining single byte as a Byte string, and reset the marks.
963    KanjiAndSingleByte,
964}
965
966static STATE_TRANSITION: [(State, Action); 70] = [
967    // STATE_TRANSITION[current_state + next_character] == (next_state, what_to_do)
968
969    // Init state:
970    (State::Init, Action::Idle),      // End
971    (State::Alpha, Action::Idle),     // Symbol
972    (State::Numeric, Action::Idle),   // Numeric
973    (State::Alpha, Action::Idle),     // Alpha
974    (State::KanjiHi12, Action::Idle), // KanjiHi1
975    (State::KanjiHi12, Action::Idle), // KanjiHi2
976    (State::KanjiHi3, Action::Idle),  // KanjiHi3
977    (State::Byte, Action::Idle),      // KanjiLo1
978    (State::Byte, Action::Idle),      // KanjiLo2
979    (State::Byte, Action::Idle),      // Byte
980    // Numeric state:
981    (State::Init, Action::Numeric),      // End
982    (State::Alpha, Action::Numeric),     // Symbol
983    (State::Numeric, Action::Idle),      // Numeric
984    (State::Alpha, Action::Numeric),     // Alpha
985    (State::KanjiHi12, Action::Numeric), // KanjiHi1
986    (State::KanjiHi12, Action::Numeric), // KanjiHi2
987    (State::KanjiHi3, Action::Numeric),  // KanjiHi3
988    (State::Byte, Action::Numeric),      // KanjiLo1
989    (State::Byte, Action::Numeric),      // KanjiLo2
990    (State::Byte, Action::Numeric),      // Byte
991    // Alpha state:
992    (State::Init, Action::Alpha),      // End
993    (State::Alpha, Action::Idle),      // Symbol
994    (State::Numeric, Action::Alpha),   // Numeric
995    (State::Alpha, Action::Idle),      // Alpha
996    (State::KanjiHi12, Action::Alpha), // KanjiHi1
997    (State::KanjiHi12, Action::Alpha), // KanjiHi2
998    (State::KanjiHi3, Action::Alpha),  // KanjiHi3
999    (State::Byte, Action::Alpha),      // KanjiLo1
1000    (State::Byte, Action::Alpha),      // KanjiLo2
1001    (State::Byte, Action::Alpha),      // Byte
1002    // Byte state:
1003    (State::Init, Action::Byte),      // End
1004    (State::Alpha, Action::Byte),     // Symbol
1005    (State::Numeric, Action::Byte),   // Numeric
1006    (State::Alpha, Action::Byte),     // Alpha
1007    (State::KanjiHi12, Action::Byte), // KanjiHi1
1008    (State::KanjiHi12, Action::Byte), // KanjiHi2
1009    (State::KanjiHi3, Action::Byte),  // KanjiHi3
1010    (State::Byte, Action::Idle),      // KanjiLo1
1011    (State::Byte, Action::Idle),      // KanjiLo2
1012    (State::Byte, Action::Idle),      // Byte
1013    // KanjiHi12 state:
1014    (State::Init, Action::KanjiAndSingleByte),    // End
1015    (State::Alpha, Action::KanjiAndSingleByte),   // Symbol
1016    (State::Numeric, Action::KanjiAndSingleByte), // Numeric
1017    (State::Kanji, Action::Idle),                 // Alpha
1018    (State::Kanji, Action::Idle),                 // KanjiHi1
1019    (State::Kanji, Action::Idle),                 // KanjiHi2
1020    (State::Kanji, Action::Idle),                 // KanjiHi3
1021    (State::Kanji, Action::Idle),                 // KanjiLo1
1022    (State::Kanji, Action::Idle),                 // KanjiLo2
1023    (State::Byte, Action::KanjiAndSingleByte),    // Byte
1024    // KanjiHi3 state:
1025    (State::Init, Action::KanjiAndSingleByte),      // End
1026    (State::Alpha, Action::KanjiAndSingleByte),     // Symbol
1027    (State::Numeric, Action::KanjiAndSingleByte),   // Numeric
1028    (State::Kanji, Action::Idle),                   // Alpha
1029    (State::Kanji, Action::Idle),                   // KanjiHi1
1030    (State::KanjiHi12, Action::KanjiAndSingleByte), // KanjiHi2
1031    (State::KanjiHi3, Action::KanjiAndSingleByte),  // KanjiHi3
1032    (State::Kanji, Action::Idle),                   // KanjiLo1
1033    (State::Byte, Action::KanjiAndSingleByte),      // KanjiLo2
1034    (State::Byte, Action::KanjiAndSingleByte),      // Byte
1035    // Kanji state:
1036    (State::Init, Action::Kanji),     // End
1037    (State::Alpha, Action::Kanji),    // Symbol
1038    (State::Numeric, Action::Kanji),  // Numeric
1039    (State::Alpha, Action::Kanji),    // Alpha
1040    (State::KanjiHi12, Action::Idle), // KanjiHi1
1041    (State::KanjiHi12, Action::Idle), // KanjiHi2
1042    (State::KanjiHi3, Action::Idle),  // KanjiHi3
1043    (State::Byte, Action::Kanji),     // KanjiLo1
1044    (State::Byte, Action::Kanji),     // KanjiLo2
1045    (State::Byte, Action::Kanji),     // Byte
1046];
1047
1048//}}}