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::{FontBook, ResolvedFace};

const CAPTURE: &[u8] = include_bytes!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/../../conformance/table-text-shaping-gpos.json"
));
const CAPTURE_SHA256: &str = "9280953ac5de0b6b745baedcda73cf4d88a95a7b52cbfbc99188e0b66a71efb2";

#[derive(Deserialize)]
struct Capture {
    font_sha256: String,
    font_face_index: u32,
    memory_fills: [u8; 3],
    repeat_zero_fill: bool,
    gpos_hook_addresses: [u64; 4],
    cases: Vec<Case>,
}

#[derive(Deserialize)]
struct Case {
    name: String,
    hb_calls: Vec<ShapeCall>,
    callbacks: Callbacks,
    gpos_apply_values: Vec<ValueTrace>,
}

#[derive(Deserialize)]
struct ShapeCall {
    nativefont_scale: [i32; 2],
    output: ShapeOutput,
}

#[derive(Deserialize)]
struct ShapeOutput {
    infos: Vec<GlyphInfo>,
    positions: Vec<[i32; 4]>,
}

#[derive(Deserialize)]
struct GlyphInfo {
    glyph_id: u32,
    cluster: u32,
}

#[derive(Deserialize)]
struct Callbacks {
    vector_glyphs: Vec<Vec<u32>>,
    vector_quantized: Vec<i32>,
}

#[derive(Deserialize)]
struct ValueTrace {
    source_font_offset: usize,
    source_bytes: [u8; 2],
    value_i16: i16,
    multiplier_i64: i64,
    product_i64: i64,
    adjustment_i32: i32,
    value_format: u16,
    destination: String,
    glyph_index: usize,
    glyph_id: u32,
    cluster_utf16: u32,
    position_before: [i32; 4],
    position_after: [i32; 4],
}

fn capture() -> (Capture, ResolvedFace) {
    assert_eq!(format!("{:x}", Sha256::digest(CAPTURE)), CAPTURE_SHA256);
    let capture: Capture = serde_json::from_slice(CAPTURE).unwrap();
    let face = FontBook::default().resolve("Roboto", false, false).unwrap();
    assert_eq!(
        format!("{:x}", Sha256::digest(face.bytes())),
        capture.font_sha256
    );
    assert_eq!(face.index, capture.font_face_index);
    assert_eq!(capture.memory_fills, [0, 0xa5, 0xff]);
    assert!(capture.repeat_zero_fill);
    assert_eq!(
        capture.gpos_hook_addresses,
        [0xd013c, 0xd014c, 0xd0150, 0xd015c]
    );
    assert_eq!(capture.cases.len(), 4);
    (capture, face)
}

fn gpos_range(font: &[u8]) -> std::ops::Range<usize> {
    let count = usize::from(u16::from_be_bytes(font[4..6].try_into().unwrap()));
    let record = font[12..12 + count * 16]
        .as_chunks::<16>()
        .0
        .iter()
        .find(|record| &record[..4] == b"GPOS")
        .unwrap();
    let start = u32::from_be_bytes(record[8..12].try_into().unwrap()) as usize;
    let length = u32::from_be_bytes(record[12..16].try_into().unwrap()) as usize;
    assert!(start + length <= font.len());
    start..start + length
}

#[test]
fn native_pair_adjustment_operands_are_actual_pinned_gpos_values() {
    let (capture, face) = capture();
    let font = face.bytes();
    let table = gpos_range(font);
    assert_eq!(table.start, 0x71bf0);
    let expected = [
        ("av_default", 0x79464, -87),
        ("to_default", 0x79760, -99),
        ("large_default", 0x79464, -87),
        ("pair_combining_fma", 0x79464, -87),
    ];
    for (case, (name, offset, value)) in capture.cases.iter().zip(expected) {
        assert_eq!(case.name, name);
        assert_eq!(case.hb_calls.len(), 1);
        assert_eq!(case.gpos_apply_values.len(), 1);
        let trace = &case.gpos_apply_values[0];
        assert_eq!(trace.source_font_offset, offset);
        assert!(table.contains(&offset) && table.contains(&(offset + 1)));
        assert_eq!(font[offset..offset + 2], trace.source_bytes);
        assert_eq!(i16::from_be_bytes(trace.source_bytes), value);
        assert_eq!(trace.value_i16, value);
        assert_eq!(trace.value_format, 4);
        assert_eq!(trace.destination, "x_advance");
        let call = &case.hb_calls[0];
        let info = &call.output.infos[trace.glyph_index];
        assert_eq!(trace.glyph_id, info.glyph_id);
        assert_eq!(trace.cluster_utf16, info.cluster);
        assert_eq!(
            trace.position_after,
            call.output.positions[trace.glyph_index]
        );
        let callback_glyphs: Vec<_> = case
            .callbacks
            .vector_glyphs
            .iter()
            .flatten()
            .copied()
            .collect();
        assert_eq!(callback_glyphs[trace.glyph_index], trace.glyph_id);
        assert_eq!(
            case.callbacks.vector_quantized[trace.glyph_index],
            trace.position_before[0]
        );
    }
}

#[test]
fn native_signed_pair_scaling_floors_each_adjustment_before_adding() {
    let (capture, face) = capture();
    let mut round_to_nearest_differences = 0;
    for case in &capture.cases {
        let scale = case.hb_calls[0].nativefont_scale[0];
        for trace in &case.gpos_apply_values {
            let multiplier = (i64::from(scale) << 16) / i64::from(face.metrics.units_per_em);
            assert_eq!(trace.multiplier_i64, multiplier);
            let product = i64::from(trace.value_i16) * multiplier;
            assert_eq!(trace.product_i64, product);
            assert_eq!(trace.adjustment_i32, (product >> 16) as i32);
            assert!(product < 0 && trace.adjustment_i32 < 0);
            assert_ne!(product % 65536, 0);
            let mut expected = trace.position_before;
            expected[0] = expected[0].wrapping_add(trace.adjustment_i32);
            assert_eq!(trace.position_after, expected);
            let modern_rounded = ((product + 32768) >> 16) as i32;
            if modern_rounded != trace.adjustment_i32 {
                round_to_nearest_differences += 1;
            }
        }
    }
    assert_eq!(round_to_nearest_differences, 4);
    let av = &capture.cases[0].gpos_apply_values[0];
    assert_eq!(av.position_before[0], 283904);
    assert_eq!(av.adjustment_i32, -18488);
    assert_eq!(av.position_after[0], 265416);
}