sdocx 1.1.0

Reverse-engineered tooling and SDK for converting Samsung Notes (.sdocx) files
Documentation
use std::sync::Arc;

use serde::Deserialize;
use sha2::{Digest, Sha256};

use super::*;

#[derive(Deserialize)]
struct Capture {
    memory_fills: [u8; 3],
    repeat_zero_fill: bool,
    text_library_sha256: String,
    base_library_sha256: String,
    cases: Vec<Case>,
}

#[derive(Deserialize)]
struct Case {
    name: String,
    source_utf16: Vec<u16>,
    placed_entries: Vec<Entry>,
    emission_input: EmissionInput,
    emitted_runs: Emission,
}

#[derive(Deserialize)]
struct EmissionInput {
    offset_bits: [u32; 2],
    gravity_bits: u32,
    range_inclusive: [usize; 2],
    layout_paragraphs: Vec<LayoutParagraph>,
}

#[derive(Deserialize)]
struct LayoutParagraph {
    source_start_utf16: usize,
    source_length_utf16: usize,
    inverse_logical_map_utf16: Vec<usize>,
}

#[derive(Deserialize)]
struct Entry {
    utf16_slot: usize,
    advance_bits: u32,
    position_bits: [u32; 2],
    layout_rect_bits: [u32; 4],
    ink_rect_bits: [u32; 4],
    kind: u32,
    raw_direction: i32,
    drawable: bool,
    glyph_records: Vec<[u32; 3]>,
    font: Option<Font>,
    span: Span,
    paragraph_override: bool,
}

#[derive(Deserialize)]
struct Font {
    source_id: i32,
    bitmap: bool,
    language: String,
}

#[derive(Deserialize)]
struct Span {
    font_size_bits: u32,
    foreground: u32,
    background: u32,
    composing_background: u32,
    style_bits: u8,
    family: Option<String>,
    underline: u32,
    correction_foreground: u32,
    flags: u8,
    correction_foreground_enabled: bool,
}

impl Span {
    fn native(&self) -> NativeDrawSpan {
        NativeDrawSpan {
            font_size: f32::from_bits(self.font_size_bits),
            foreground: self.foreground,
            background: self.background,
            composing_background: self.composing_background,
            style_bits: self.style_bits,
            family: self.family.as_deref().map(Arc::from),
            underline: self.underline,
            correction_foreground: self.correction_foreground,
            flags: self.flags,
            correction_foreground_enabled: self.correction_foreground_enabled,
        }
    }
}

#[derive(Deserialize)]
struct Emission {
    accepted: bool,
    runs: Vec<ExpectedRun>,
}

#[derive(Deserialize)]
struct ExpectedRun {
    range_inclusive: [usize; 2],
    codewords: Vec<u32>,
    position_bits: Vec<u32>,
    origin_bits: [u32; 2],
    ink_rect_bits: [u32; 4],
    layout_rect_bits: [u32; 4],
    is_object: bool,
    source_id: u32,
    font_size_bits: u32,
    foreground: u32,
    style_byte: u8,
    background: u32,
}

fn check_run(
    actual: &NativeEmittedRun<i32>,
    expected: &ExpectedRun,
    payloads: &[(u32, usize)],
    name: &str,
) {
    assert_eq!(
        [*actual.source.start(), *actual.source.end()],
        expected.range_inclusive,
        "{name}: source range"
    );
    assert_eq!(
        actual
            .glyphs
            .iter()
            .map(|glyph| payloads[glyph.payload].0)
            .collect::<Vec<_>>(),
        expected.codewords,
        "{name}: codewords"
    );
    for glyph in &actual.glyphs {
        assert_eq!(
            glyph.owner_utf16, payloads[glyph.payload].1,
            "{name}: input owner"
        );
    }
    assert_eq!(
        actual
            .glyphs
            .iter()
            .map(|glyph| glyph.x.to_bits())
            .collect::<Vec<_>>(),
        expected.position_bits,
        "{name}: glyph positions"
    );
    assert_eq!(
        actual.origin.map(f32::to_bits),
        expected.origin_bits,
        "{name}: origin"
    );
    assert_eq!(
        actual.ink.0.map(f32::to_bits),
        expected.ink_rect_bits,
        "{name}: ink rectangle"
    );
    assert_eq!(
        actual.layout.0.map(f32::to_bits),
        expected.layout_rect_bits,
        "{name}: layout rectangle"
    );
    assert_eq!(
        actual
            .font_source
            .map_or(u32::MAX, |source| u32::from_le_bytes(source.to_le_bytes())),
        expected.source_id,
        "{name}: font source"
    );
    assert_eq!(
        actual.paint.font_size.to_bits(),
        expected.font_size_bits,
        "{name}: font size"
    );
    assert_eq!(
        actual.paint.foreground, expected.foreground,
        "{name}: foreground"
    );
    assert_eq!(
        actual.paint.style_bits, expected.style_byte,
        "{name}: style"
    );
    assert_eq!(
        actual.paint.background, expected.background,
        "{name}: background"
    );
    assert_eq!(
        actual.paint.object, expected.is_object,
        "{name}: object flag"
    );
}

fn check_case(case: &Case) {
    assert_eq!(
        case.placed_entries.len(),
        case.source_utf16.len(),
        "{}: dense input",
        case.name
    );
    for paragraph in &case.emission_input.layout_paragraphs {
        assert_eq!(
            paragraph.inverse_logical_map_utf16.len(),
            paragraph.source_length_utf16,
            "{}: paragraph map length",
            case.name
        );
        assert!(
            paragraph.source_start_utf16 + paragraph.source_length_utf16 <= case.source_utf16.len(),
            "{}: paragraph source bounds",
            case.name
        );
    }
    let spans: Vec<_> = case
        .placed_entries
        .iter()
        .map(|entry| entry.span.native())
        .collect();
    let mut payloads = Vec::new();
    let glyphs: Vec<Vec<_>> = case
        .placed_entries
        .iter()
        .enumerate()
        .map(|(slot, entry)| {
            assert_eq!(entry.utf16_slot, slot, "{}: native slot", case.name);
            entry
                .glyph_records
                .iter()
                .map(|record| {
                    let payload = payloads.len();
                    payloads.push((record[0], slot));
                    NativeCachedGlyph {
                        payload,
                        offset: [f32::from_bits(record[1]), f32::from_bits(record[2])],
                    }
                })
                .collect()
        })
        .collect();
    let entries: Vec<_> = case
        .placed_entries
        .iter()
        .zip(&spans)
        .zip(&glyphs)
        .map(|((entry, span), glyphs)| NativeRunEntry {
            kind: entry.kind,
            direction: entry.raw_direction,
            advance: f32::from_bits(entry.advance_bits),
            position: entry.position_bits.map(f32::from_bits),
            layout: NativeRect(entry.layout_rect_bits.map(f32::from_bits)),
            ink: NativeRect(entry.ink_rect_bits.map(f32::from_bits)),
            cache: NativeGlyphCache {
                drawable: entry.drawable,
                glyphs,
            },
            span,
            font: entry
                .font
                .as_ref()
                .map_or(NativeFontState::Missing, |font| NativeFontState::Known {
                    source: font.source_id,
                    bitmap: font.bitmap,
                    language: &font.language,
                }),
            paragraph_override: entry.paragraph_override,
        })
        .collect();
    let [x, y] = case.emission_input.offset_bits.map(f32::from_bits);
    let [start, end] = case.emission_input.range_inclusive;
    let result = native_runs(
        &entries,
        start..=end,
        NativeRunOffset {
            x,
            y,
            gravity: f32::from_bits(case.emission_input.gravity_bits),
        },
    );
    if entries.is_empty() {
        assert!(
            !case.emitted_runs.accepted,
            "{}: native empty admission",
            case.name
        );
        assert!(
            case.emitted_runs.runs.is_empty(),
            "{}: native empty output",
            case.name
        );
        assert_eq!(
            result,
            Err(NativeRunError::InvalidRange),
            "{}: bounded Rust empty admission",
            case.name
        );
        return;
    }
    assert!(
        case.emitted_runs.accepted,
        "{}: native admission",
        case.name
    );
    let actual = result.unwrap_or_else(|error| panic!("{}: {error}", case.name));
    assert_eq!(
        actual.len(),
        case.emitted_runs.runs.len(),
        "{}: run count",
        case.name
    );
    for (actual, expected) in actual.iter().zip(&case.emitted_runs.runs) {
        check_run(actual, expected, &payloads, &case.name);
    }
}

#[test]
fn actual_native_cell_placed_entries_match_cached_run_emission() {
    let bytes = include_bytes!("../../../../../../conformance/table-text-cell-emission.json");
    assert_eq!(
        format!("{:x}", Sha256::digest(bytes)),
        "0098cfcd93274b210892fd0653677fd4cef3a35ebc4f0419b4b661857cbfa4ce"
    );
    let capture: Capture = serde_json::from_slice(bytes).unwrap();
    assert_eq!(capture.memory_fills, [0, 165, 255]);
    assert!(capture.repeat_zero_fill);
    assert_eq!(
        capture.text_library_sha256,
        "5483711673a499743625eb3275e34b37a006919af346212b46b8d8857834308b"
    );
    assert_eq!(
        capture.base_library_sha256,
        "e10da0116946691cf68302437ef261282e1dfe0eec15bf2dfa66093286985deb"
    );
    assert_eq!(capture.cases.len(), 17);
    assert_eq!(
        capture
            .cases
            .iter()
            .map(|case| case.placed_entries.len())
            .sum::<usize>(),
        78
    );
    assert_eq!(
        capture
            .cases
            .iter()
            .flat_map(|case| &case.placed_entries)
            .map(|entry| entry.glyph_records.len())
            .sum::<usize>(),
        67
    );
    assert_eq!(
        capture
            .cases
            .iter()
            .map(|case| case.emitted_runs.runs.len())
            .sum::<usize>(),
        27
    );
    assert_eq!(
        capture
            .cases
            .iter()
            .flat_map(|case| &case.emitted_runs.runs)
            .map(|run| run.codewords.len())
            .sum::<usize>(),
        67
    );
    for case in &capture.cases {
        check_case(case);
    }
}