sdocx 1.0.0

Reverse-engineered tooling and SDK for converting Samsung Notes (.sdocx) files
Documentation
use serde::Deserialize;
use sha2::{Digest, Sha256};

use super::*;

const CAPTURE: &str = include_str!("../../../../../../conformance/table-text-object-feedback.json");

#[derive(Deserialize)]
struct Capture {
    cases: Vec<Case>,
}
#[derive(Deserialize)]
struct Case {
    name: String,
    route: String,
    supplied_advances_bits: Vec<u32>,
    supplied_kinds: Vec<u32>,
    supplied_break_ends_utf16: Vec<usize>,
    object_utf16: usize,
    source_start_utf16: usize,
    budget_bits: u32,
    paragraph_full_width_bits: u32,
    entry_before: ObjectEntry,
    stages: Vec<Stage>,
}
#[derive(Deserialize)]
struct ObjectEntry {
    advance_bits: u32,
    over_pages: bool,
}
#[derive(Deserialize)]
struct Stage {
    stage: String,
    selection: Option<Selection>,
    entry_after: ObjectEntry,
    callback_calls: usize,
    events: Vec<Event>,
}
#[derive(Deserialize)]
struct Selection {
    range_utf16_inclusive: [i32; 2],
    layout_bits: [u32; 4],
    flags: [u8; 3],
}
#[derive(Deserialize)]
struct Event {
    stage: String,
    relative_utf16: Option<usize>,
    candidate_register_bits: Option<u32>,
    source_utf16_anchor: Option<usize>,
}

#[test]
fn captured_object_admission_retains_old_candidate_and_reloads_prepared_width() {
    assert_eq!(
        format!("{:x}", Sha256::digest(CAPTURE.as_bytes())),
        "3c0e26208b66a67c7b90d7941b5bebf3633af73a4eb53dfc50a643b818d15d19"
    );
    let capture: Capture = serde_json::from_str(CAPTURE).unwrap();
    assert_eq!(capture.cases.len(), 34);
    let mut checked = 0;
    for case in capture.cases.into_iter().filter(|case| {
        case.route == "GetBlockInfo"
            && case.entry_before.over_pages
            && case
                .supplied_kinds
                .iter()
                .all(|kind| matches!(kind, 0 | 1 | 2 | 5))
    }) {
        let mut entries = case
            .supplied_advances_bits
            .iter()
            .enumerate()
            .map(|(index, &bits)| NativeWrapEntry {
                advance: f32::from_bits(bits),
                kind: match case.supplied_kinds[index] {
                    1 => NativeWrapKind::Space,
                    2 => NativeWrapKind::Tab,
                    _ => NativeWrapKind::Ordinary,
                },
                break_end_utf16: Some(case.supplied_break_ends_utf16[index]),
                metrics: NativeWrapMetrics {
                    font_size: 17.25,
                    height: 17.25,
                },
            })
            .collect::<Vec<_>>();
        let mut objects = vec![false; entries.len()];
        objects[case.object_utf16] = true;
        for stage in case.stages {
            let mut callbacks = Vec::new();
            let mut candidates = Vec::new();
            let block = select_with_feedback(
                &entries,
                0..entries.len(),
                NativeWrapWidths {
                    available: f32::from_bits(case.budget_bits),
                    full: f32::from_bits(case.paragraph_full_width_bits),
                },
                Some(&objects),
                |slot, _, _| {
                    callbacks.push(case.source_start_utf16 + slot);
                    Ok(f32::from_bits(stage.entry_after.advance_bits))
                },
                |operation| {
                    if let NativeWrapOperation::Candidate {
                        entry_utf16,
                        candidate,
                        ..
                    } = operation
                    {
                        candidates.push((entry_utf16, candidate.to_bits()));
                    }
                },
            )
            .unwrap();
            let selection = stage.selection.as_ref().unwrap();
            if let Some(block) = &block {
                assert_eq!(
                    block.encountered_object_metric,
                    selection.flags[1] != 0,
                    "{} {} encountered object metric",
                    case.name,
                    stage.stage
                );
            }
            assert_eq!(
                block
                    .as_ref()
                    .map(|block| [
                        *block.range_utf16_inclusive.start() as i32,
                        *block.range_utf16_inclusive.end() as i32
                    ])
                    .unwrap_or([0, -1]),
                selection.range_utf16_inclusive,
                "{} {} range",
                case.name,
                stage.stage
            );
            assert_eq!(
                block.map_or(0.0, |block| block.width).to_bits(),
                selection.layout_bits[2],
                "{} {} grouped width",
                case.name,
                stage.stage
            );
            assert_eq!(
                callbacks.len(),
                stage.callback_calls,
                "{} {} callbacks",
                case.name,
                stage.stage
            );
            let actual_anchors = stage
                .events
                .iter()
                .filter_map(|event| event.source_utf16_anchor)
                .collect::<Vec<_>>();
            assert_eq!(
                callbacks, actual_anchors,
                "{} {} source anchors",
                case.name, stage.stage
            );
            let actual_candidates = stage
                .events
                .iter()
                .filter(|event| event.stage == "Text+0x6ae1c")
                .map(|event| {
                    (
                        event.relative_utf16.unwrap(),
                        event.candidate_register_bits.unwrap(),
                    )
                })
                .collect::<Vec<_>>();
            assert_eq!(
                candidates, actual_candidates,
                "{} {} saved candidates",
                case.name, stage.stage
            );
            entries[case.object_utf16].advance = f32::from_bits(stage.entry_after.advance_bits);
            checked += 1;
        }
    }
    assert_eq!(checked, 27);
}

#[test]
fn object_inputs_are_validated_before_any_preparation() {
    let entry = NativeWrapEntry {
        advance: 1.0,
        kind: NativeWrapKind::Ordinary,
        break_end_utf16: Some(2),
        metrics: NativeWrapMetrics::default(),
    };
    let entries = [
        entry,
        NativeWrapEntry {
            advance: f32::NAN,
            ..entry
        },
    ];
    let result = select_native_block_with_objects(
        &entries,
        0..2,
        NativeWrapWidths {
            available: 10.0,
            full: 10.0,
        },
        &[true, false],
        |_, _| panic!("invalid subsequent input must not partially prepare earlier objects"),
    );
    assert_eq!(result, Err(NativeWrapError::InvalidAdvance));
}

#[test]
fn raw_wrapping_validates_only_the_requested_source_before_callbacks() {
    let valid = NativeWrapEntry {
        advance: 1.0,
        kind: NativeWrapKind::Ordinary,
        break_end_utf16: None,
        metrics: NativeWrapMetrics {
            font_size: 17.0,
            height: 17.0,
        },
    };
    let entries = [
        valid,
        NativeWrapEntry {
            advance: f32::NAN,
            ..valid
        },
    ];
    let widths = NativeWrapWidths {
        available: 10.0,
        full: 10.0,
    };
    assert!(
        select_native_block(&entries, 0..1, widths)
            .unwrap()
            .is_some()
    );
    let mut calls = 0;
    assert!(
        select_native_block_with_objects(&entries, 0..1, widths, &[true, false], |_, _| {
            calls += 1;
            Ok(2.0)
        })
        .unwrap()
        .is_some()
    );
    assert_eq!(calls, 1);
    assert_eq!(
        select_native_block_with_objects(&entries, 0..2, widths, &[true, false], |_, _| {
            panic!("a future invalid requested entry must precede object preparation")
        }),
        Err(NativeWrapError::InvalidAdvance)
    );
}

#[test]
fn cached_object_updates_reject_requested_invalid_feedback_and_recover_on_restore() {
    let entry = NativeWrapEntry {
        advance: 1.0,
        kind: NativeWrapKind::Ordinary,
        break_end_utf16: None,
        metrics: NativeWrapMetrics {
            font_size: 17.0,
            height: 17.0,
        },
    };
    let mut entries =
        NativeWrapEntries::new(vec![entry; 3], Some(vec![true, false, true])).unwrap();
    let widths = NativeWrapWidths {
        available: 10.0,
        full: 10.0,
    };
    assert_eq!(
        entries.update_advance(2, f32::INFINITY),
        Err(NativeWrapError::InvalidAdvance)
    );
    assert_eq!(
        entries.select_with_objects(0..3, widths, |_, _| {
            panic!("invalid future object feedback must precede all callbacks")
        }),
        Err(NativeWrapError::InvalidAdvance)
    );
    let mut prepared = Vec::new();
    assert!(
        entries
            .select_with_objects(0..2, widths, |index, _| {
                prepared.push(index);
                Ok(2.0)
            })
            .unwrap()
            .is_some()
    );
    assert_eq!(prepared, [0]);
    entries.update_advance(2, 4.0).unwrap();
    prepared.clear();
    let block = entries
        .select_with_objects(0..3, widths, |index, _| {
            prepared.push(index);
            Ok(entries[index].advance)
        })
        .unwrap()
        .unwrap();
    assert_eq!(prepared, [0, 2]);
    assert_eq!(block.width, 6.0);
}