fix: add reload_stale_events_v2 FFI export + update UDL with V2 types + XCFramework rebuild

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
wangdl 2026-06-09 19:58:07 +08:00
parent 3b0625ffbb
commit a81a9d7e1f
37 changed files with 7962 additions and 110 deletions

View File

@ -0,0 +1,910 @@
// This file was autogenerated by some hot garbage in the `uniffi` crate.
// Trust me, you don't want to mess with it!
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
// The following structs are used to implement the lowest level
// of the FFI, and thus useful to multiple uniffied crates.
// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H.
#ifdef UNIFFI_SHARED_H
// We also try to prevent mixing versions of shared uniffi header structs.
// If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4
#ifndef UNIFFI_SHARED_HEADER_V4
#error Combining helper code from multiple versions of uniffi is not supported
#endif // ndef UNIFFI_SHARED_HEADER_V4
#else
#define UNIFFI_SHARED_H
#define UNIFFI_SHARED_HEADER_V4
// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
typedef struct RustBuffer
{
uint64_t capacity;
uint64_t len;
uint8_t *_Nullable data;
} RustBuffer;
typedef struct ForeignBytes
{
int32_t len;
const uint8_t *_Nullable data;
} ForeignBytes;
// Error definitions
typedef struct RustCallStatus {
int8_t code;
RustBuffer errorBuf;
} RustCallStatus;
// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
#endif // def UNIFFI_SHARED_H
#ifndef UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK
#define UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK
typedef void (*UniffiRustFutureContinuationCallback)(uint64_t, int8_t
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK
typedef void (*UniffiForeignFutureDroppedCallback)(uint64_t
);
#endif
#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE
#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE
typedef void (*UniffiCallbackInterfaceFree)(uint64_t
);
#endif
#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_CLONE
#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_CLONE
typedef uint64_t (*UniffiCallbackInterfaceClone)(uint64_t
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK_STRUCT
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK_STRUCT
typedef struct UniffiForeignFutureDroppedCallbackStruct {
uint64_t handle;
UniffiForeignFutureDroppedCallback _Nonnull free;
} UniffiForeignFutureDroppedCallbackStruct;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U8
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U8
typedef struct UniffiForeignFutureResultU8 {
uint8_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultU8;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8
typedef void (*UniffiForeignFutureCompleteU8)(uint64_t, UniffiForeignFutureResultU8
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I8
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I8
typedef struct UniffiForeignFutureResultI8 {
int8_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultI8;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8
typedef void (*UniffiForeignFutureCompleteI8)(uint64_t, UniffiForeignFutureResultI8
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U16
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U16
typedef struct UniffiForeignFutureResultU16 {
uint16_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultU16;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16
typedef void (*UniffiForeignFutureCompleteU16)(uint64_t, UniffiForeignFutureResultU16
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I16
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I16
typedef struct UniffiForeignFutureResultI16 {
int16_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultI16;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16
typedef void (*UniffiForeignFutureCompleteI16)(uint64_t, UniffiForeignFutureResultI16
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U32
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U32
typedef struct UniffiForeignFutureResultU32 {
uint32_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultU32;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32
typedef void (*UniffiForeignFutureCompleteU32)(uint64_t, UniffiForeignFutureResultU32
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I32
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I32
typedef struct UniffiForeignFutureResultI32 {
int32_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultI32;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32
typedef void (*UniffiForeignFutureCompleteI32)(uint64_t, UniffiForeignFutureResultI32
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U64
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U64
typedef struct UniffiForeignFutureResultU64 {
uint64_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultU64;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64
typedef void (*UniffiForeignFutureCompleteU64)(uint64_t, UniffiForeignFutureResultU64
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I64
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I64
typedef struct UniffiForeignFutureResultI64 {
int64_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultI64;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64
typedef void (*UniffiForeignFutureCompleteI64)(uint64_t, UniffiForeignFutureResultI64
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F32
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F32
typedef struct UniffiForeignFutureResultF32 {
float returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultF32;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32
typedef void (*UniffiForeignFutureCompleteF32)(uint64_t, UniffiForeignFutureResultF32
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F64
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F64
typedef struct UniffiForeignFutureResultF64 {
double returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultF64;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64
typedef void (*UniffiForeignFutureCompleteF64)(uint64_t, UniffiForeignFutureResultF64
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_RUST_BUFFER
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_RUST_BUFFER
typedef struct UniffiForeignFutureResultRustBuffer {
RustBuffer returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultRustBuffer;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER
typedef void (*UniffiForeignFutureCompleteRustBuffer)(uint64_t, UniffiForeignFutureResultRustBuffer
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_VOID
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_VOID
typedef struct UniffiForeignFutureResultVoid {
RustCallStatus callStatus;
} UniffiForeignFutureResultVoid;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID
typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureResultVoid
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_ACK_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_ACK_EVENTS_V2
uint32_t uniffi_zx_document_ffi_fn_func_ack_events_v2(RustBuffer event_ids, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEANUP_STALE_SESSIONS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEANUP_STALE_SESSIONS_FFI
uint32_t uniffi_zx_document_ffi_fn_func_cleanup_stale_sessions_ffi(int64_t now_ms, int64_t max_age_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEAR_EXPORTED_EVENTS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEAR_EXPORTED_EVENTS
void uniffi_zx_document_ffi_fn_func_clear_exported_events(uint32_t count, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLOSE_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLOSE_READING_SESSION_V2
void uniffi_zx_document_ffi_fn_func_close_reading_session_v2(RustBuffer session_id, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR
RustBuffer uniffi_zx_document_ffi_fn_func_create_note_anchor(RustBuffer material_id, RustBuffer position, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR_FROM_SEARCH
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR_FROM_SEARCH
RustBuffer uniffi_zx_document_ffi_fn_func_create_note_anchor_from_search(RustBuffer material_id, RustBuffer result, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_DETECT_MATERIAL_TYPE
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_DETECT_MATERIAL_TYPE
RustBuffer uniffi_zx_document_ffi_fn_func_detect_material_type(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXPORT_PENDING_EVENTS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXPORT_PENDING_EVENTS
RustBuffer uniffi_zx_document_ffi_fn_func_export_pending_events(RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXPORT_PENDING_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXPORT_PENDING_EVENTS_V2
RustBuffer uniffi_zx_document_ffi_fn_func_export_pending_events_v2(uint32_t limit, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXTRACT_PDF_TEXT_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXTRACT_PDF_TEXT_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_extract_pdf_text_ffi(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_GET_OFFICE_PREVIEW_CONFIG_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_GET_OFFICE_PREVIEW_CONFIG_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_get_office_preview_config_ffi(RustBuffer material_type, uint64_t file_size, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_IS_OFFICE_TYPE_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_IS_OFFICE_TYPE_FFI
int8_t uniffi_zx_document_ffi_fn_func_is_office_type_ffi(RustBuffer material_type, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_MARK_EVENTS_FAILED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_MARK_EVENTS_FAILED_V2
uint32_t uniffi_zx_document_ffi_fn_func_mark_events_failed_v2(RustBuffer event_ids, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PARSE_MARKDOWN
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PARSE_MARKDOWN
RustBuffer uniffi_zx_document_ffi_fn_func_parse_markdown(RustBuffer content, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PARSE_TEXT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PARSE_TEXT
RustBuffer uniffi_zx_document_ffi_fn_func_parse_text(RustBuffer content, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PAUSE_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PAUSE_READING_SESSION_V2
void uniffi_zx_document_ffi_fn_func_pause_reading_session_v2(RustBuffer session_id, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_HEARTBEAT_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_HEARTBEAT_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_heartbeat_v2(RustBuffer session_id, RustBuffer material_id, uint32_t active_seconds_delta, RustBuffer position, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MARKED_AS_READ_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MARKED_AS_READ_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_marked_as_read_v2(RustBuffer session_id, RustBuffer material_id, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MATERIAL_CLOSED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MATERIAL_CLOSED_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_material_closed_v2(RustBuffer session_id, RustBuffer material_id, uint32_t active_seconds_delta, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MATERIAL_OPENED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MATERIAL_OPENED_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_material_opened_v2(RustBuffer session_id, RustBuffer material_id, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_POSITION_CHANGED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_POSITION_CHANGED_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_position_changed_v2(RustBuffer session_id, RustBuffer material_id, RustBuffer position, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_READING_EVENT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_READING_EVENT
void uniffi_zx_document_ffi_fn_func_push_reading_event(RustBuffer event, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_EPUB_CHAPTERS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_EPUB_CHAPTERS_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_read_epub_chapters_ffi(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_EPUB_METADATA_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_EPUB_METADATA_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_read_epub_metadata_ffi(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_IMAGE_META
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_IMAGE_META
RustBuffer uniffi_zx_document_ffi_fn_func_read_image_meta(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_PDF_METADATA_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_PDF_METADATA_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_read_pdf_metadata_ffi(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_TEXT_STATS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_TEXT_STATS
RustBuffer uniffi_zx_document_ffi_fn_func_read_text_stats(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RELOAD_STALE_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RELOAD_STALE_EVENTS_V2
uint32_t uniffi_zx_document_ffi_fn_func_reload_stale_events_v2(RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RESTORE_POSITION_FROM_ANCHOR
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RESTORE_POSITION_FROM_ANCHOR
RustBuffer uniffi_zx_document_ffi_fn_func_restore_position_from_anchor(RustBuffer anchor, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RESUME_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RESUME_READING_SESSION_V2
void uniffi_zx_document_ffi_fn_func_resume_reading_session_v2(RustBuffer session_id, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_EPUB_CHAPTERS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_EPUB_CHAPTERS_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_search_epub_chapters_ffi(RustBuffer chapter_ids, RustBuffer chapter_texts, RustBuffer query, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_MARKDOWN_BLOCKS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_MARKDOWN_BLOCKS
RustBuffer uniffi_zx_document_ffi_fn_func_search_markdown_blocks(RustBuffer blocks, RustBuffer query, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_PDF_PAGES
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_PDF_PAGES
RustBuffer uniffi_zx_document_ffi_fn_func_search_pdf_pages(RustBuffer page_numbers, RustBuffer page_texts, RustBuffer query, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_TEXT_CONTENT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_TEXT_CONTENT
RustBuffer uniffi_zx_document_ffi_fn_func_search_text_content(RustBuffer content, RustBuffer query, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_START_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_START_READING_SESSION_V2
RustBuffer uniffi_zx_document_ffi_fn_func_start_reading_session_v2(RustBuffer material, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_UPDATE_READING_POSITION
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_UPDATE_READING_POSITION
void uniffi_zx_document_ffi_fn_func_update_reading_position(RustBuffer material_id, RustBuffer position, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_ALLOC
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_ALLOC
RustBuffer ffi_zx_document_ffi_rustbuffer_alloc(uint64_t size, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_FROM_BYTES
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_FROM_BYTES
RustBuffer ffi_zx_document_ffi_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_FREE
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_FREE
void ffi_zx_document_ffi_rustbuffer_free(RustBuffer buf, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_RESERVE
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_RESERVE
RustBuffer ffi_zx_document_ffi_rustbuffer_reserve(RustBuffer buf, uint64_t additional, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U8
void ffi_zx_document_ffi_rust_future_poll_u8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U8
void ffi_zx_document_ffi_rust_future_cancel_u8(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U8
void ffi_zx_document_ffi_rust_future_free_u8(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U8
uint8_t ffi_zx_document_ffi_rust_future_complete_u8(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I8
void ffi_zx_document_ffi_rust_future_poll_i8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I8
void ffi_zx_document_ffi_rust_future_cancel_i8(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I8
void ffi_zx_document_ffi_rust_future_free_i8(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I8
int8_t ffi_zx_document_ffi_rust_future_complete_i8(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U16
void ffi_zx_document_ffi_rust_future_poll_u16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U16
void ffi_zx_document_ffi_rust_future_cancel_u16(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U16
void ffi_zx_document_ffi_rust_future_free_u16(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U16
uint16_t ffi_zx_document_ffi_rust_future_complete_u16(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I16
void ffi_zx_document_ffi_rust_future_poll_i16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I16
void ffi_zx_document_ffi_rust_future_cancel_i16(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I16
void ffi_zx_document_ffi_rust_future_free_i16(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I16
int16_t ffi_zx_document_ffi_rust_future_complete_i16(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U32
void ffi_zx_document_ffi_rust_future_poll_u32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U32
void ffi_zx_document_ffi_rust_future_cancel_u32(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U32
void ffi_zx_document_ffi_rust_future_free_u32(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U32
uint32_t ffi_zx_document_ffi_rust_future_complete_u32(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I32
void ffi_zx_document_ffi_rust_future_poll_i32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I32
void ffi_zx_document_ffi_rust_future_cancel_i32(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I32
void ffi_zx_document_ffi_rust_future_free_i32(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I32
int32_t ffi_zx_document_ffi_rust_future_complete_i32(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U64
void ffi_zx_document_ffi_rust_future_poll_u64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U64
void ffi_zx_document_ffi_rust_future_cancel_u64(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U64
void ffi_zx_document_ffi_rust_future_free_u64(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U64
uint64_t ffi_zx_document_ffi_rust_future_complete_u64(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I64
void ffi_zx_document_ffi_rust_future_poll_i64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I64
void ffi_zx_document_ffi_rust_future_cancel_i64(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I64
void ffi_zx_document_ffi_rust_future_free_i64(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I64
int64_t ffi_zx_document_ffi_rust_future_complete_i64(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_F32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_F32
void ffi_zx_document_ffi_rust_future_poll_f32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_F32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_F32
void ffi_zx_document_ffi_rust_future_cancel_f32(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_F32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_F32
void ffi_zx_document_ffi_rust_future_free_f32(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_F32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_F32
float ffi_zx_document_ffi_rust_future_complete_f32(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_F64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_F64
void ffi_zx_document_ffi_rust_future_poll_f64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_F64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_F64
void ffi_zx_document_ffi_rust_future_cancel_f64(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_F64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_F64
void ffi_zx_document_ffi_rust_future_free_f64(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_F64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_F64
double ffi_zx_document_ffi_rust_future_complete_f64(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_RUST_BUFFER
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_RUST_BUFFER
void ffi_zx_document_ffi_rust_future_poll_rust_buffer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_RUST_BUFFER
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_RUST_BUFFER
void ffi_zx_document_ffi_rust_future_cancel_rust_buffer(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_RUST_BUFFER
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_RUST_BUFFER
void ffi_zx_document_ffi_rust_future_free_rust_buffer(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_RUST_BUFFER
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_RUST_BUFFER
RustBuffer ffi_zx_document_ffi_rust_future_complete_rust_buffer(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_VOID
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_VOID
void ffi_zx_document_ffi_rust_future_poll_void(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_VOID
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_VOID
void ffi_zx_document_ffi_rust_future_cancel_void(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_VOID
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_VOID
void ffi_zx_document_ffi_rust_future_free_void(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_VOID
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_VOID
void ffi_zx_document_ffi_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_ACK_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_ACK_EVENTS_V2
uint16_t uniffi_zx_document_ffi_checksum_func_ack_events_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEANUP_STALE_SESSIONS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEANUP_STALE_SESSIONS_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_cleanup_stale_sessions_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEAR_EXPORTED_EVENTS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEAR_EXPORTED_EVENTS
uint16_t uniffi_zx_document_ffi_checksum_func_clear_exported_events(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLOSE_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLOSE_READING_SESSION_V2
uint16_t uniffi_zx_document_ffi_checksum_func_close_reading_session_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR
uint16_t uniffi_zx_document_ffi_checksum_func_create_note_anchor(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR_FROM_SEARCH
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR_FROM_SEARCH
uint16_t uniffi_zx_document_ffi_checksum_func_create_note_anchor_from_search(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_DETECT_MATERIAL_TYPE
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_DETECT_MATERIAL_TYPE
uint16_t uniffi_zx_document_ffi_checksum_func_detect_material_type(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXPORT_PENDING_EVENTS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXPORT_PENDING_EVENTS
uint16_t uniffi_zx_document_ffi_checksum_func_export_pending_events(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXPORT_PENDING_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXPORT_PENDING_EVENTS_V2
uint16_t uniffi_zx_document_ffi_checksum_func_export_pending_events_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXTRACT_PDF_TEXT_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXTRACT_PDF_TEXT_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_extract_pdf_text_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_GET_OFFICE_PREVIEW_CONFIG_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_GET_OFFICE_PREVIEW_CONFIG_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_get_office_preview_config_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_IS_OFFICE_TYPE_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_IS_OFFICE_TYPE_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_is_office_type_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_MARK_EVENTS_FAILED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_MARK_EVENTS_FAILED_V2
uint16_t uniffi_zx_document_ffi_checksum_func_mark_events_failed_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PARSE_MARKDOWN
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PARSE_MARKDOWN
uint16_t uniffi_zx_document_ffi_checksum_func_parse_markdown(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PARSE_TEXT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PARSE_TEXT
uint16_t uniffi_zx_document_ffi_checksum_func_parse_text(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PAUSE_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PAUSE_READING_SESSION_V2
uint16_t uniffi_zx_document_ffi_checksum_func_pause_reading_session_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_HEARTBEAT_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_HEARTBEAT_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_heartbeat_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MARKED_AS_READ_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MARKED_AS_READ_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_marked_as_read_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MATERIAL_CLOSED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MATERIAL_CLOSED_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_material_closed_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MATERIAL_OPENED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MATERIAL_OPENED_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_material_opened_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_POSITION_CHANGED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_POSITION_CHANGED_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_position_changed_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_READING_EVENT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_READING_EVENT
uint16_t uniffi_zx_document_ffi_checksum_func_push_reading_event(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_EPUB_CHAPTERS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_EPUB_CHAPTERS_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_read_epub_chapters_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_EPUB_METADATA_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_EPUB_METADATA_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_read_epub_metadata_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_IMAGE_META
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_IMAGE_META
uint16_t uniffi_zx_document_ffi_checksum_func_read_image_meta(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_PDF_METADATA_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_PDF_METADATA_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_read_pdf_metadata_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_TEXT_STATS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_TEXT_STATS
uint16_t uniffi_zx_document_ffi_checksum_func_read_text_stats(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RELOAD_STALE_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RELOAD_STALE_EVENTS_V2
uint16_t uniffi_zx_document_ffi_checksum_func_reload_stale_events_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RESTORE_POSITION_FROM_ANCHOR
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RESTORE_POSITION_FROM_ANCHOR
uint16_t uniffi_zx_document_ffi_checksum_func_restore_position_from_anchor(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RESUME_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RESUME_READING_SESSION_V2
uint16_t uniffi_zx_document_ffi_checksum_func_resume_reading_session_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_EPUB_CHAPTERS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_EPUB_CHAPTERS_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_search_epub_chapters_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_MARKDOWN_BLOCKS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_MARKDOWN_BLOCKS
uint16_t uniffi_zx_document_ffi_checksum_func_search_markdown_blocks(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_PDF_PAGES
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_PDF_PAGES
uint16_t uniffi_zx_document_ffi_checksum_func_search_pdf_pages(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_TEXT_CONTENT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_TEXT_CONTENT
uint16_t uniffi_zx_document_ffi_checksum_func_search_text_content(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_START_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_START_READING_SESSION_V2
uint16_t uniffi_zx_document_ffi_checksum_func_start_reading_session_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_UPDATE_READING_POSITION
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_UPDATE_READING_POSITION
uint16_t uniffi_zx_document_ffi_checksum_func_update_reading_position(void
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_UNIFFI_CONTRACT_VERSION
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_UNIFFI_CONTRACT_VERSION
uint32_t ffi_zx_document_ffi_uniffi_contract_version(void
);
#endif

View File

@ -0,0 +1,4 @@
framework module zx_documentFFI {
header "zx_documentFFI.h"
export *
}

File diff suppressed because it is too large Load Diff

View File

@ -242,17 +242,37 @@ typedef struct UniffiForeignFutureResultVoid {
typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureResultVoid typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureResultVoid
); );
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_ACK_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_ACK_EVENTS_V2
uint32_t uniffi_zx_document_ffi_fn_func_ack_events_v2(RustBuffer event_ids, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEANUP_STALE_SESSIONS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEANUP_STALE_SESSIONS_FFI
uint32_t uniffi_zx_document_ffi_fn_func_cleanup_stale_sessions_ffi(int64_t now_ms, int64_t max_age_ms, RustCallStatus *_Nonnull out_status
);
#endif #endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEAR_EXPORTED_EVENTS #ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEAR_EXPORTED_EVENTS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEAR_EXPORTED_EVENTS #define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEAR_EXPORTED_EVENTS
void uniffi_zx_document_ffi_fn_func_clear_exported_events(uint32_t count, RustCallStatus *_Nonnull out_status void uniffi_zx_document_ffi_fn_func_clear_exported_events(uint32_t count, RustCallStatus *_Nonnull out_status
); );
#endif #endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLOSE_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLOSE_READING_SESSION_V2
void uniffi_zx_document_ffi_fn_func_close_reading_session_v2(RustBuffer session_id, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR #ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR #define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR
RustBuffer uniffi_zx_document_ffi_fn_func_create_note_anchor(RustBuffer material_id, RustBuffer position, RustCallStatus *_Nonnull out_status RustBuffer uniffi_zx_document_ffi_fn_func_create_note_anchor(RustBuffer material_id, RustBuffer position, RustCallStatus *_Nonnull out_status
); );
#endif #endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR_FROM_SEARCH
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR_FROM_SEARCH
RustBuffer uniffi_zx_document_ffi_fn_func_create_note_anchor_from_search(RustBuffer material_id, RustBuffer result, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_DETECT_MATERIAL_TYPE #ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_DETECT_MATERIAL_TYPE
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_DETECT_MATERIAL_TYPE #define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_DETECT_MATERIAL_TYPE
RustBuffer uniffi_zx_document_ffi_fn_func_detect_material_type(RustBuffer file_path, RustCallStatus *_Nonnull out_status RustBuffer uniffi_zx_document_ffi_fn_func_detect_material_type(RustBuffer file_path, RustCallStatus *_Nonnull out_status
@ -262,6 +282,31 @@ RustBuffer uniffi_zx_document_ffi_fn_func_detect_material_type(RustBuffer file_p
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXPORT_PENDING_EVENTS #define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXPORT_PENDING_EVENTS
RustBuffer uniffi_zx_document_ffi_fn_func_export_pending_events(RustCallStatus *_Nonnull out_status RustBuffer uniffi_zx_document_ffi_fn_func_export_pending_events(RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXPORT_PENDING_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXPORT_PENDING_EVENTS_V2
RustBuffer uniffi_zx_document_ffi_fn_func_export_pending_events_v2(uint32_t limit, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXTRACT_PDF_TEXT_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXTRACT_PDF_TEXT_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_extract_pdf_text_ffi(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_GET_OFFICE_PREVIEW_CONFIG_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_GET_OFFICE_PREVIEW_CONFIG_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_get_office_preview_config_ffi(RustBuffer material_type, uint64_t file_size, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_IS_OFFICE_TYPE_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_IS_OFFICE_TYPE_FFI
int8_t uniffi_zx_document_ffi_fn_func_is_office_type_ffi(RustBuffer material_type, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_MARK_EVENTS_FAILED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_MARK_EVENTS_FAILED_V2
uint32_t uniffi_zx_document_ffi_fn_func_mark_events_failed_v2(RustBuffer event_ids, RustCallStatus *_Nonnull out_status
); );
#endif #endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PARSE_MARKDOWN #ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PARSE_MARKDOWN
@ -274,31 +319,107 @@ RustBuffer uniffi_zx_document_ffi_fn_func_parse_markdown(RustBuffer content, Rus
RustBuffer uniffi_zx_document_ffi_fn_func_parse_text(RustBuffer content, RustCallStatus *_Nonnull out_status RustBuffer uniffi_zx_document_ffi_fn_func_parse_text(RustBuffer content, RustCallStatus *_Nonnull out_status
); );
#endif #endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PAUSE_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PAUSE_READING_SESSION_V2
void uniffi_zx_document_ffi_fn_func_pause_reading_session_v2(RustBuffer session_id, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_HEARTBEAT_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_HEARTBEAT_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_heartbeat_v2(RustBuffer session_id, RustBuffer material_id, uint32_t active_seconds_delta, RustBuffer position, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MARKED_AS_READ_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MARKED_AS_READ_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_marked_as_read_v2(RustBuffer session_id, RustBuffer material_id, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MATERIAL_CLOSED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MATERIAL_CLOSED_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_material_closed_v2(RustBuffer session_id, RustBuffer material_id, uint32_t active_seconds_delta, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MATERIAL_OPENED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MATERIAL_OPENED_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_material_opened_v2(RustBuffer session_id, RustBuffer material_id, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_POSITION_CHANGED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_POSITION_CHANGED_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_position_changed_v2(RustBuffer session_id, RustBuffer material_id, RustBuffer position, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_READING_EVENT #ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_READING_EVENT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_READING_EVENT #define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_READING_EVENT
void uniffi_zx_document_ffi_fn_func_push_reading_event(RustBuffer event, RustCallStatus *_Nonnull out_status void uniffi_zx_document_ffi_fn_func_push_reading_event(RustBuffer event, RustCallStatus *_Nonnull out_status
); );
#endif #endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_EPUB_CHAPTERS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_EPUB_CHAPTERS_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_read_epub_chapters_ffi(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_EPUB_METADATA_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_EPUB_METADATA_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_read_epub_metadata_ffi(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_IMAGE_META #ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_IMAGE_META
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_IMAGE_META #define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_IMAGE_META
RustBuffer uniffi_zx_document_ffi_fn_func_read_image_meta(RustBuffer file_path, RustCallStatus *_Nonnull out_status RustBuffer uniffi_zx_document_ffi_fn_func_read_image_meta(RustBuffer file_path, RustCallStatus *_Nonnull out_status
); );
#endif #endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_PDF_METADATA_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_PDF_METADATA_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_read_pdf_metadata_ffi(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_TEXT_STATS #ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_TEXT_STATS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_TEXT_STATS #define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_TEXT_STATS
RustBuffer uniffi_zx_document_ffi_fn_func_read_text_stats(RustBuffer file_path, RustCallStatus *_Nonnull out_status RustBuffer uniffi_zx_document_ffi_fn_func_read_text_stats(RustBuffer file_path, RustCallStatus *_Nonnull out_status
); );
#endif #endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RELOAD_STALE_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RELOAD_STALE_EVENTS_V2
uint32_t uniffi_zx_document_ffi_fn_func_reload_stale_events_v2(RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RESTORE_POSITION_FROM_ANCHOR
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RESTORE_POSITION_FROM_ANCHOR
RustBuffer uniffi_zx_document_ffi_fn_func_restore_position_from_anchor(RustBuffer anchor, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RESUME_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RESUME_READING_SESSION_V2
void uniffi_zx_document_ffi_fn_func_resume_reading_session_v2(RustBuffer session_id, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_EPUB_CHAPTERS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_EPUB_CHAPTERS_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_search_epub_chapters_ffi(RustBuffer chapter_ids, RustBuffer chapter_texts, RustBuffer query, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_MARKDOWN_BLOCKS #ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_MARKDOWN_BLOCKS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_MARKDOWN_BLOCKS #define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_MARKDOWN_BLOCKS
RustBuffer uniffi_zx_document_ffi_fn_func_search_markdown_blocks(RustBuffer blocks, RustBuffer query, RustCallStatus *_Nonnull out_status RustBuffer uniffi_zx_document_ffi_fn_func_search_markdown_blocks(RustBuffer blocks, RustBuffer query, RustCallStatus *_Nonnull out_status
); );
#endif #endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_PDF_PAGES
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_PDF_PAGES
RustBuffer uniffi_zx_document_ffi_fn_func_search_pdf_pages(RustBuffer page_numbers, RustBuffer page_texts, RustBuffer query, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_TEXT_CONTENT #ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_TEXT_CONTENT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_TEXT_CONTENT #define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_TEXT_CONTENT
RustBuffer uniffi_zx_document_ffi_fn_func_search_text_content(RustBuffer content, RustBuffer query, RustCallStatus *_Nonnull out_status RustBuffer uniffi_zx_document_ffi_fn_func_search_text_content(RustBuffer content, RustBuffer query, RustCallStatus *_Nonnull out_status
); );
#endif #endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_START_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_START_READING_SESSION_V2
RustBuffer uniffi_zx_document_ffi_fn_func_start_reading_session_v2(RustBuffer material, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_UPDATE_READING_POSITION #ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_UPDATE_READING_POSITION
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_UPDATE_READING_POSITION #define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_UPDATE_READING_POSITION
void uniffi_zx_document_ffi_fn_func_update_reading_position(RustBuffer material_id, RustBuffer position, RustCallStatus *_Nonnull out_status void uniffi_zx_document_ffi_fn_func_update_reading_position(RustBuffer material_id, RustBuffer position, RustCallStatus *_Nonnull out_status
@ -562,18 +683,42 @@ void ffi_zx_document_ffi_rust_future_free_void(uint64_t handle
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_VOID #ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_VOID
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_VOID #define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_VOID
void ffi_zx_document_ffi_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status void ffi_zx_document_ffi_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_ACK_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_ACK_EVENTS_V2
uint16_t uniffi_zx_document_ffi_checksum_func_ack_events_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEANUP_STALE_SESSIONS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEANUP_STALE_SESSIONS_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_cleanup_stale_sessions_ffi(void
); );
#endif #endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEAR_EXPORTED_EVENTS #ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEAR_EXPORTED_EVENTS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEAR_EXPORTED_EVENTS #define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEAR_EXPORTED_EVENTS
uint16_t uniffi_zx_document_ffi_checksum_func_clear_exported_events(void uint16_t uniffi_zx_document_ffi_checksum_func_clear_exported_events(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLOSE_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLOSE_READING_SESSION_V2
uint16_t uniffi_zx_document_ffi_checksum_func_close_reading_session_v2(void
); );
#endif #endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR #ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR #define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR
uint16_t uniffi_zx_document_ffi_checksum_func_create_note_anchor(void uint16_t uniffi_zx_document_ffi_checksum_func_create_note_anchor(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR_FROM_SEARCH
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR_FROM_SEARCH
uint16_t uniffi_zx_document_ffi_checksum_func_create_note_anchor_from_search(void
); );
#endif #endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_DETECT_MATERIAL_TYPE #ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_DETECT_MATERIAL_TYPE
@ -586,6 +731,36 @@ uint16_t uniffi_zx_document_ffi_checksum_func_detect_material_type(void
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXPORT_PENDING_EVENTS #define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXPORT_PENDING_EVENTS
uint16_t uniffi_zx_document_ffi_checksum_func_export_pending_events(void uint16_t uniffi_zx_document_ffi_checksum_func_export_pending_events(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXPORT_PENDING_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXPORT_PENDING_EVENTS_V2
uint16_t uniffi_zx_document_ffi_checksum_func_export_pending_events_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXTRACT_PDF_TEXT_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXTRACT_PDF_TEXT_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_extract_pdf_text_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_GET_OFFICE_PREVIEW_CONFIG_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_GET_OFFICE_PREVIEW_CONFIG_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_get_office_preview_config_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_IS_OFFICE_TYPE_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_IS_OFFICE_TYPE_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_is_office_type_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_MARK_EVENTS_FAILED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_MARK_EVENTS_FAILED_V2
uint16_t uniffi_zx_document_ffi_checksum_func_mark_events_failed_v2(void
); );
#endif #endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PARSE_MARKDOWN #ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PARSE_MARKDOWN
@ -598,36 +773,126 @@ uint16_t uniffi_zx_document_ffi_checksum_func_parse_markdown(void
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PARSE_TEXT #define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PARSE_TEXT
uint16_t uniffi_zx_document_ffi_checksum_func_parse_text(void uint16_t uniffi_zx_document_ffi_checksum_func_parse_text(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PAUSE_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PAUSE_READING_SESSION_V2
uint16_t uniffi_zx_document_ffi_checksum_func_pause_reading_session_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_HEARTBEAT_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_HEARTBEAT_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_heartbeat_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MARKED_AS_READ_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MARKED_AS_READ_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_marked_as_read_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MATERIAL_CLOSED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MATERIAL_CLOSED_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_material_closed_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MATERIAL_OPENED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MATERIAL_OPENED_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_material_opened_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_POSITION_CHANGED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_POSITION_CHANGED_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_position_changed_v2(void
); );
#endif #endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_READING_EVENT #ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_READING_EVENT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_READING_EVENT #define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_READING_EVENT
uint16_t uniffi_zx_document_ffi_checksum_func_push_reading_event(void uint16_t uniffi_zx_document_ffi_checksum_func_push_reading_event(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_EPUB_CHAPTERS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_EPUB_CHAPTERS_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_read_epub_chapters_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_EPUB_METADATA_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_EPUB_METADATA_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_read_epub_metadata_ffi(void
); );
#endif #endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_IMAGE_META #ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_IMAGE_META
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_IMAGE_META #define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_IMAGE_META
uint16_t uniffi_zx_document_ffi_checksum_func_read_image_meta(void uint16_t uniffi_zx_document_ffi_checksum_func_read_image_meta(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_PDF_METADATA_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_PDF_METADATA_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_read_pdf_metadata_ffi(void
); );
#endif #endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_TEXT_STATS #ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_TEXT_STATS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_TEXT_STATS #define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_TEXT_STATS
uint16_t uniffi_zx_document_ffi_checksum_func_read_text_stats(void uint16_t uniffi_zx_document_ffi_checksum_func_read_text_stats(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RELOAD_STALE_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RELOAD_STALE_EVENTS_V2
uint16_t uniffi_zx_document_ffi_checksum_func_reload_stale_events_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RESTORE_POSITION_FROM_ANCHOR
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RESTORE_POSITION_FROM_ANCHOR
uint16_t uniffi_zx_document_ffi_checksum_func_restore_position_from_anchor(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RESUME_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RESUME_READING_SESSION_V2
uint16_t uniffi_zx_document_ffi_checksum_func_resume_reading_session_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_EPUB_CHAPTERS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_EPUB_CHAPTERS_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_search_epub_chapters_ffi(void
); );
#endif #endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_MARKDOWN_BLOCKS #ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_MARKDOWN_BLOCKS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_MARKDOWN_BLOCKS #define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_MARKDOWN_BLOCKS
uint16_t uniffi_zx_document_ffi_checksum_func_search_markdown_blocks(void uint16_t uniffi_zx_document_ffi_checksum_func_search_markdown_blocks(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_PDF_PAGES
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_PDF_PAGES
uint16_t uniffi_zx_document_ffi_checksum_func_search_pdf_pages(void
); );
#endif #endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_TEXT_CONTENT #ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_TEXT_CONTENT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_TEXT_CONTENT #define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_TEXT_CONTENT
uint16_t uniffi_zx_document_ffi_checksum_func_search_text_content(void uint16_t uniffi_zx_document_ffi_checksum_func_search_text_content(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_START_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_START_READING_SESSION_V2
uint16_t uniffi_zx_document_ffi_checksum_func_start_reading_session_v2(void
); );
#endif #endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_UPDATE_READING_POSITION #ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_UPDATE_READING_POSITION

View File

@ -0,0 +1,4 @@
framework module zx_documentFFI {
header "zx_documentFFI.h"
export *
}

View File

@ -0,0 +1,910 @@
// This file was autogenerated by some hot garbage in the `uniffi` crate.
// Trust me, you don't want to mess with it!
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
// The following structs are used to implement the lowest level
// of the FFI, and thus useful to multiple uniffied crates.
// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H.
#ifdef UNIFFI_SHARED_H
// We also try to prevent mixing versions of shared uniffi header structs.
// If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4
#ifndef UNIFFI_SHARED_HEADER_V4
#error Combining helper code from multiple versions of uniffi is not supported
#endif // ndef UNIFFI_SHARED_HEADER_V4
#else
#define UNIFFI_SHARED_H
#define UNIFFI_SHARED_HEADER_V4
// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
typedef struct RustBuffer
{
uint64_t capacity;
uint64_t len;
uint8_t *_Nullable data;
} RustBuffer;
typedef struct ForeignBytes
{
int32_t len;
const uint8_t *_Nullable data;
} ForeignBytes;
// Error definitions
typedef struct RustCallStatus {
int8_t code;
RustBuffer errorBuf;
} RustCallStatus;
// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️
#endif // def UNIFFI_SHARED_H
#ifndef UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK
#define UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK
typedef void (*UniffiRustFutureContinuationCallback)(uint64_t, int8_t
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK
typedef void (*UniffiForeignFutureDroppedCallback)(uint64_t
);
#endif
#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE
#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE
typedef void (*UniffiCallbackInterfaceFree)(uint64_t
);
#endif
#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_CLONE
#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_CLONE
typedef uint64_t (*UniffiCallbackInterfaceClone)(uint64_t
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK_STRUCT
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK_STRUCT
typedef struct UniffiForeignFutureDroppedCallbackStruct {
uint64_t handle;
UniffiForeignFutureDroppedCallback _Nonnull free;
} UniffiForeignFutureDroppedCallbackStruct;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U8
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U8
typedef struct UniffiForeignFutureResultU8 {
uint8_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultU8;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8
typedef void (*UniffiForeignFutureCompleteU8)(uint64_t, UniffiForeignFutureResultU8
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I8
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I8
typedef struct UniffiForeignFutureResultI8 {
int8_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultI8;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8
typedef void (*UniffiForeignFutureCompleteI8)(uint64_t, UniffiForeignFutureResultI8
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U16
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U16
typedef struct UniffiForeignFutureResultU16 {
uint16_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultU16;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16
typedef void (*UniffiForeignFutureCompleteU16)(uint64_t, UniffiForeignFutureResultU16
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I16
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I16
typedef struct UniffiForeignFutureResultI16 {
int16_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultI16;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16
typedef void (*UniffiForeignFutureCompleteI16)(uint64_t, UniffiForeignFutureResultI16
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U32
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U32
typedef struct UniffiForeignFutureResultU32 {
uint32_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultU32;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32
typedef void (*UniffiForeignFutureCompleteU32)(uint64_t, UniffiForeignFutureResultU32
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I32
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I32
typedef struct UniffiForeignFutureResultI32 {
int32_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultI32;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32
typedef void (*UniffiForeignFutureCompleteI32)(uint64_t, UniffiForeignFutureResultI32
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U64
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U64
typedef struct UniffiForeignFutureResultU64 {
uint64_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultU64;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64
typedef void (*UniffiForeignFutureCompleteU64)(uint64_t, UniffiForeignFutureResultU64
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I64
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I64
typedef struct UniffiForeignFutureResultI64 {
int64_t returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultI64;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64
typedef void (*UniffiForeignFutureCompleteI64)(uint64_t, UniffiForeignFutureResultI64
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F32
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F32
typedef struct UniffiForeignFutureResultF32 {
float returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultF32;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32
typedef void (*UniffiForeignFutureCompleteF32)(uint64_t, UniffiForeignFutureResultF32
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F64
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F64
typedef struct UniffiForeignFutureResultF64 {
double returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultF64;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64
typedef void (*UniffiForeignFutureCompleteF64)(uint64_t, UniffiForeignFutureResultF64
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_RUST_BUFFER
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_RUST_BUFFER
typedef struct UniffiForeignFutureResultRustBuffer {
RustBuffer returnValue;
RustCallStatus callStatus;
} UniffiForeignFutureResultRustBuffer;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER
typedef void (*UniffiForeignFutureCompleteRustBuffer)(uint64_t, UniffiForeignFutureResultRustBuffer
);
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_VOID
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_VOID
typedef struct UniffiForeignFutureResultVoid {
RustCallStatus callStatus;
} UniffiForeignFutureResultVoid;
#endif
#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID
#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID
typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureResultVoid
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_ACK_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_ACK_EVENTS_V2
uint32_t uniffi_zx_document_ffi_fn_func_ack_events_v2(RustBuffer event_ids, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEANUP_STALE_SESSIONS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEANUP_STALE_SESSIONS_FFI
uint32_t uniffi_zx_document_ffi_fn_func_cleanup_stale_sessions_ffi(int64_t now_ms, int64_t max_age_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEAR_EXPORTED_EVENTS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLEAR_EXPORTED_EVENTS
void uniffi_zx_document_ffi_fn_func_clear_exported_events(uint32_t count, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLOSE_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CLOSE_READING_SESSION_V2
void uniffi_zx_document_ffi_fn_func_close_reading_session_v2(RustBuffer session_id, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR
RustBuffer uniffi_zx_document_ffi_fn_func_create_note_anchor(RustBuffer material_id, RustBuffer position, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR_FROM_SEARCH
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_CREATE_NOTE_ANCHOR_FROM_SEARCH
RustBuffer uniffi_zx_document_ffi_fn_func_create_note_anchor_from_search(RustBuffer material_id, RustBuffer result, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_DETECT_MATERIAL_TYPE
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_DETECT_MATERIAL_TYPE
RustBuffer uniffi_zx_document_ffi_fn_func_detect_material_type(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXPORT_PENDING_EVENTS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXPORT_PENDING_EVENTS
RustBuffer uniffi_zx_document_ffi_fn_func_export_pending_events(RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXPORT_PENDING_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXPORT_PENDING_EVENTS_V2
RustBuffer uniffi_zx_document_ffi_fn_func_export_pending_events_v2(uint32_t limit, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXTRACT_PDF_TEXT_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_EXTRACT_PDF_TEXT_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_extract_pdf_text_ffi(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_GET_OFFICE_PREVIEW_CONFIG_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_GET_OFFICE_PREVIEW_CONFIG_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_get_office_preview_config_ffi(RustBuffer material_type, uint64_t file_size, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_IS_OFFICE_TYPE_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_IS_OFFICE_TYPE_FFI
int8_t uniffi_zx_document_ffi_fn_func_is_office_type_ffi(RustBuffer material_type, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_MARK_EVENTS_FAILED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_MARK_EVENTS_FAILED_V2
uint32_t uniffi_zx_document_ffi_fn_func_mark_events_failed_v2(RustBuffer event_ids, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PARSE_MARKDOWN
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PARSE_MARKDOWN
RustBuffer uniffi_zx_document_ffi_fn_func_parse_markdown(RustBuffer content, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PARSE_TEXT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PARSE_TEXT
RustBuffer uniffi_zx_document_ffi_fn_func_parse_text(RustBuffer content, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PAUSE_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PAUSE_READING_SESSION_V2
void uniffi_zx_document_ffi_fn_func_pause_reading_session_v2(RustBuffer session_id, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_HEARTBEAT_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_HEARTBEAT_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_heartbeat_v2(RustBuffer session_id, RustBuffer material_id, uint32_t active_seconds_delta, RustBuffer position, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MARKED_AS_READ_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MARKED_AS_READ_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_marked_as_read_v2(RustBuffer session_id, RustBuffer material_id, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MATERIAL_CLOSED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MATERIAL_CLOSED_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_material_closed_v2(RustBuffer session_id, RustBuffer material_id, uint32_t active_seconds_delta, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MATERIAL_OPENED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_MATERIAL_OPENED_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_material_opened_v2(RustBuffer session_id, RustBuffer material_id, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_POSITION_CHANGED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_POSITION_CHANGED_V2
RustBuffer uniffi_zx_document_ffi_fn_func_push_position_changed_v2(RustBuffer session_id, RustBuffer material_id, RustBuffer position, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_READING_EVENT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_PUSH_READING_EVENT
void uniffi_zx_document_ffi_fn_func_push_reading_event(RustBuffer event, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_EPUB_CHAPTERS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_EPUB_CHAPTERS_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_read_epub_chapters_ffi(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_EPUB_METADATA_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_EPUB_METADATA_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_read_epub_metadata_ffi(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_IMAGE_META
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_IMAGE_META
RustBuffer uniffi_zx_document_ffi_fn_func_read_image_meta(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_PDF_METADATA_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_PDF_METADATA_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_read_pdf_metadata_ffi(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_TEXT_STATS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_READ_TEXT_STATS
RustBuffer uniffi_zx_document_ffi_fn_func_read_text_stats(RustBuffer file_path, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RELOAD_STALE_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RELOAD_STALE_EVENTS_V2
uint32_t uniffi_zx_document_ffi_fn_func_reload_stale_events_v2(RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RESTORE_POSITION_FROM_ANCHOR
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RESTORE_POSITION_FROM_ANCHOR
RustBuffer uniffi_zx_document_ffi_fn_func_restore_position_from_anchor(RustBuffer anchor, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RESUME_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_RESUME_READING_SESSION_V2
void uniffi_zx_document_ffi_fn_func_resume_reading_session_v2(RustBuffer session_id, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_EPUB_CHAPTERS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_EPUB_CHAPTERS_FFI
RustBuffer uniffi_zx_document_ffi_fn_func_search_epub_chapters_ffi(RustBuffer chapter_ids, RustBuffer chapter_texts, RustBuffer query, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_MARKDOWN_BLOCKS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_MARKDOWN_BLOCKS
RustBuffer uniffi_zx_document_ffi_fn_func_search_markdown_blocks(RustBuffer blocks, RustBuffer query, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_PDF_PAGES
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_PDF_PAGES
RustBuffer uniffi_zx_document_ffi_fn_func_search_pdf_pages(RustBuffer page_numbers, RustBuffer page_texts, RustBuffer query, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_TEXT_CONTENT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_SEARCH_TEXT_CONTENT
RustBuffer uniffi_zx_document_ffi_fn_func_search_text_content(RustBuffer content, RustBuffer query, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_START_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_START_READING_SESSION_V2
RustBuffer uniffi_zx_document_ffi_fn_func_start_reading_session_v2(RustBuffer material, int64_t timestamp_ms, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_UPDATE_READING_POSITION
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_FN_FUNC_UPDATE_READING_POSITION
void uniffi_zx_document_ffi_fn_func_update_reading_position(RustBuffer material_id, RustBuffer position, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_ALLOC
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_ALLOC
RustBuffer ffi_zx_document_ffi_rustbuffer_alloc(uint64_t size, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_FROM_BYTES
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_FROM_BYTES
RustBuffer ffi_zx_document_ffi_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_FREE
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_FREE
void ffi_zx_document_ffi_rustbuffer_free(RustBuffer buf, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_RESERVE
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUSTBUFFER_RESERVE
RustBuffer ffi_zx_document_ffi_rustbuffer_reserve(RustBuffer buf, uint64_t additional, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U8
void ffi_zx_document_ffi_rust_future_poll_u8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U8
void ffi_zx_document_ffi_rust_future_cancel_u8(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U8
void ffi_zx_document_ffi_rust_future_free_u8(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U8
uint8_t ffi_zx_document_ffi_rust_future_complete_u8(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I8
void ffi_zx_document_ffi_rust_future_poll_i8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I8
void ffi_zx_document_ffi_rust_future_cancel_i8(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I8
void ffi_zx_document_ffi_rust_future_free_i8(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I8
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I8
int8_t ffi_zx_document_ffi_rust_future_complete_i8(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U16
void ffi_zx_document_ffi_rust_future_poll_u16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U16
void ffi_zx_document_ffi_rust_future_cancel_u16(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U16
void ffi_zx_document_ffi_rust_future_free_u16(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U16
uint16_t ffi_zx_document_ffi_rust_future_complete_u16(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I16
void ffi_zx_document_ffi_rust_future_poll_i16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I16
void ffi_zx_document_ffi_rust_future_cancel_i16(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I16
void ffi_zx_document_ffi_rust_future_free_i16(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I16
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I16
int16_t ffi_zx_document_ffi_rust_future_complete_i16(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U32
void ffi_zx_document_ffi_rust_future_poll_u32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U32
void ffi_zx_document_ffi_rust_future_cancel_u32(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U32
void ffi_zx_document_ffi_rust_future_free_u32(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U32
uint32_t ffi_zx_document_ffi_rust_future_complete_u32(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I32
void ffi_zx_document_ffi_rust_future_poll_i32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I32
void ffi_zx_document_ffi_rust_future_cancel_i32(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I32
void ffi_zx_document_ffi_rust_future_free_i32(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I32
int32_t ffi_zx_document_ffi_rust_future_complete_i32(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_U64
void ffi_zx_document_ffi_rust_future_poll_u64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_U64
void ffi_zx_document_ffi_rust_future_cancel_u64(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_U64
void ffi_zx_document_ffi_rust_future_free_u64(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_U64
uint64_t ffi_zx_document_ffi_rust_future_complete_u64(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_I64
void ffi_zx_document_ffi_rust_future_poll_i64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_I64
void ffi_zx_document_ffi_rust_future_cancel_i64(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_I64
void ffi_zx_document_ffi_rust_future_free_i64(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_I64
int64_t ffi_zx_document_ffi_rust_future_complete_i64(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_F32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_F32
void ffi_zx_document_ffi_rust_future_poll_f32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_F32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_F32
void ffi_zx_document_ffi_rust_future_cancel_f32(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_F32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_F32
void ffi_zx_document_ffi_rust_future_free_f32(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_F32
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_F32
float ffi_zx_document_ffi_rust_future_complete_f32(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_F64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_F64
void ffi_zx_document_ffi_rust_future_poll_f64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_F64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_F64
void ffi_zx_document_ffi_rust_future_cancel_f64(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_F64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_F64
void ffi_zx_document_ffi_rust_future_free_f64(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_F64
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_F64
double ffi_zx_document_ffi_rust_future_complete_f64(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_RUST_BUFFER
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_RUST_BUFFER
void ffi_zx_document_ffi_rust_future_poll_rust_buffer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_RUST_BUFFER
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_RUST_BUFFER
void ffi_zx_document_ffi_rust_future_cancel_rust_buffer(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_RUST_BUFFER
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_RUST_BUFFER
void ffi_zx_document_ffi_rust_future_free_rust_buffer(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_RUST_BUFFER
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_RUST_BUFFER
RustBuffer ffi_zx_document_ffi_rust_future_complete_rust_buffer(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_VOID
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_POLL_VOID
void ffi_zx_document_ffi_rust_future_poll_void(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_VOID
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_CANCEL_VOID
void ffi_zx_document_ffi_rust_future_cancel_void(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_VOID
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_FREE_VOID
void ffi_zx_document_ffi_rust_future_free_void(uint64_t handle
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_VOID
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_RUST_FUTURE_COMPLETE_VOID
void ffi_zx_document_ffi_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_ACK_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_ACK_EVENTS_V2
uint16_t uniffi_zx_document_ffi_checksum_func_ack_events_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEANUP_STALE_SESSIONS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEANUP_STALE_SESSIONS_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_cleanup_stale_sessions_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEAR_EXPORTED_EVENTS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLEAR_EXPORTED_EVENTS
uint16_t uniffi_zx_document_ffi_checksum_func_clear_exported_events(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLOSE_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CLOSE_READING_SESSION_V2
uint16_t uniffi_zx_document_ffi_checksum_func_close_reading_session_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR
uint16_t uniffi_zx_document_ffi_checksum_func_create_note_anchor(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR_FROM_SEARCH
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_CREATE_NOTE_ANCHOR_FROM_SEARCH
uint16_t uniffi_zx_document_ffi_checksum_func_create_note_anchor_from_search(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_DETECT_MATERIAL_TYPE
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_DETECT_MATERIAL_TYPE
uint16_t uniffi_zx_document_ffi_checksum_func_detect_material_type(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXPORT_PENDING_EVENTS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXPORT_PENDING_EVENTS
uint16_t uniffi_zx_document_ffi_checksum_func_export_pending_events(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXPORT_PENDING_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXPORT_PENDING_EVENTS_V2
uint16_t uniffi_zx_document_ffi_checksum_func_export_pending_events_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXTRACT_PDF_TEXT_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_EXTRACT_PDF_TEXT_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_extract_pdf_text_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_GET_OFFICE_PREVIEW_CONFIG_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_GET_OFFICE_PREVIEW_CONFIG_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_get_office_preview_config_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_IS_OFFICE_TYPE_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_IS_OFFICE_TYPE_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_is_office_type_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_MARK_EVENTS_FAILED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_MARK_EVENTS_FAILED_V2
uint16_t uniffi_zx_document_ffi_checksum_func_mark_events_failed_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PARSE_MARKDOWN
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PARSE_MARKDOWN
uint16_t uniffi_zx_document_ffi_checksum_func_parse_markdown(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PARSE_TEXT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PARSE_TEXT
uint16_t uniffi_zx_document_ffi_checksum_func_parse_text(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PAUSE_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PAUSE_READING_SESSION_V2
uint16_t uniffi_zx_document_ffi_checksum_func_pause_reading_session_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_HEARTBEAT_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_HEARTBEAT_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_heartbeat_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MARKED_AS_READ_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MARKED_AS_READ_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_marked_as_read_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MATERIAL_CLOSED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MATERIAL_CLOSED_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_material_closed_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MATERIAL_OPENED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_MATERIAL_OPENED_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_material_opened_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_POSITION_CHANGED_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_POSITION_CHANGED_V2
uint16_t uniffi_zx_document_ffi_checksum_func_push_position_changed_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_READING_EVENT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_PUSH_READING_EVENT
uint16_t uniffi_zx_document_ffi_checksum_func_push_reading_event(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_EPUB_CHAPTERS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_EPUB_CHAPTERS_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_read_epub_chapters_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_EPUB_METADATA_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_EPUB_METADATA_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_read_epub_metadata_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_IMAGE_META
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_IMAGE_META
uint16_t uniffi_zx_document_ffi_checksum_func_read_image_meta(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_PDF_METADATA_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_PDF_METADATA_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_read_pdf_metadata_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_TEXT_STATS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_READ_TEXT_STATS
uint16_t uniffi_zx_document_ffi_checksum_func_read_text_stats(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RELOAD_STALE_EVENTS_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RELOAD_STALE_EVENTS_V2
uint16_t uniffi_zx_document_ffi_checksum_func_reload_stale_events_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RESTORE_POSITION_FROM_ANCHOR
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RESTORE_POSITION_FROM_ANCHOR
uint16_t uniffi_zx_document_ffi_checksum_func_restore_position_from_anchor(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RESUME_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_RESUME_READING_SESSION_V2
uint16_t uniffi_zx_document_ffi_checksum_func_resume_reading_session_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_EPUB_CHAPTERS_FFI
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_EPUB_CHAPTERS_FFI
uint16_t uniffi_zx_document_ffi_checksum_func_search_epub_chapters_ffi(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_MARKDOWN_BLOCKS
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_MARKDOWN_BLOCKS
uint16_t uniffi_zx_document_ffi_checksum_func_search_markdown_blocks(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_PDF_PAGES
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_PDF_PAGES
uint16_t uniffi_zx_document_ffi_checksum_func_search_pdf_pages(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_TEXT_CONTENT
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_SEARCH_TEXT_CONTENT
uint16_t uniffi_zx_document_ffi_checksum_func_search_text_content(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_START_READING_SESSION_V2
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_START_READING_SESSION_V2
uint16_t uniffi_zx_document_ffi_checksum_func_start_reading_session_v2(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_UPDATE_READING_POSITION
#define UNIFFI_FFIDEF_UNIFFI_ZX_DOCUMENT_FFI_CHECKSUM_FUNC_UPDATE_READING_POSITION
uint16_t uniffi_zx_document_ffi_checksum_func_update_reading_position(void
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_UNIFFI_CONTRACT_VERSION
#define UNIFFI_FFIDEF_FFI_ZX_DOCUMENT_FFI_UNIFFI_CONTRACT_VERSION
uint32_t ffi_zx_document_ffi_uniffi_contract_version(void
);
#endif

View File

@ -0,0 +1,4 @@
framework module zx_documentFFI {
header "zx_documentFFI.h"
export *
}

View File

@ -16,8 +16,15 @@ fn main() {
Some("fixtures") => { Some("fixtures") => {
println!("fixtures/"); println!("fixtures/");
println!(" markdown/sample.md — all block types"); println!(" markdown/sample.md — all block types");
println!(" markdown/large_markdown.md — 200-heading stress test");
println!(" text/sample.txt — multi-paragraph plain text"); println!(" text/sample.txt — multi-paragraph plain text");
println!(" text/large_text.txt — 500-line stress test");
println!(" images/test-red.png — 1×1 red pixel PNG"); println!(" images/test-red.png — 1×1 red pixel PNG");
println!(" pdf/text_pdf.pdf — minimal 2-page PDF");
println!(" pdf/scanned_pdf.pdf — minimal 1-page PDF");
println!(" epub/simple.epub — 1-chapter EPUB");
println!(" epub/epub_with_toc.epub — 3-chapter EPUB with NCX TOC");
println!(" invalid_file.bin — 1KB random bytes");
} }
Some("verify-ios") => { Some("verify-ios") => {
let root = project_root(); let root = project_root();

View File

@ -10,6 +10,7 @@ infer = "0.16"
mime_guess = "2" mime_guess = "2"
comrak = "0.29" comrak = "0.29"
uuid = { version = "1", features = ["v4"] } uuid = { version = "1", features = ["v4"] }
zip = "2"
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "gif"] } image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "gif"] }
uniffi = "0.31" uniffi = "0.31"

View File

@ -1,71 +1,188 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::progress::ReadingPosition;
use crate::search::SearchResult;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, uniffi::Enum)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, uniffi::Enum)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum NoteAnchor { pub enum NoteAnchor {
Material { Material {
material_id: String, material_id: String,
#[serde(rename = "positionSnapshot", skip_serializing_if = "Option::is_none", default)]
position_snapshot: Option<ReadingPosition>,
}, },
MarkdownBlock { MarkdownBlock {
material_id: String, material_id: String,
block_id: String, block_id: String,
#[serde(rename = "positionSnapshot", skip_serializing_if = "Option::is_none", default)]
position_snapshot: Option<ReadingPosition>,
}, },
TextLine { TextLine {
material_id: String, material_id: String,
line_number: u32, line_number: u32,
#[serde(rename = "positionSnapshot", skip_serializing_if = "Option::is_none", default)]
position_snapshot: Option<ReadingPosition>,
}, },
PdfPage { PdfPage {
material_id: String, material_id: String,
page_number: u32, page_number: u32,
#[serde(rename = "positionSnapshot", skip_serializing_if = "Option::is_none", default)]
position_snapshot: Option<ReadingPosition>,
}, },
Image { Image {
material_id: String, material_id: String,
#[serde(rename = "positionSnapshot", skip_serializing_if = "Option::is_none", default)]
position_snapshot: Option<ReadingPosition>,
}, },
EpubChapter { EpubChapter {
material_id: String, material_id: String,
chapter_id: String, chapter_id: String,
#[serde(rename = "positionSnapshot", skip_serializing_if = "Option::is_none", default)]
position_snapshot: Option<ReadingPosition>,
}, },
KnowledgeItem { KnowledgeItem {
knowledge_item_id: String, knowledge_item_id: String,
}, },
SearchResultAnchor {
material_id: String,
block_id: Option<String>,
line_number: Option<u32>,
page_number: Option<u32>,
chapter_id: Option<String>,
snippet: String,
},
} }
impl NoteAnchor { impl NoteAnchor {
/// Create a NoteAnchor from a material_id and optional ReadingPosition. pub fn from_position(material_id: &str, position: Option<&ReadingPosition>) -> Self {
pub fn from_position(material_id: &str, position: Option<&crate::progress::ReadingPosition>) -> Self {
match position { match position {
Some(crate::progress::ReadingPosition::Markdown { block_id, .. }) => { Some(pos @ ReadingPosition::Markdown { block_id, .. }) => {
NoteAnchor::MarkdownBlock { NoteAnchor::MarkdownBlock {
material_id: material_id.to_string(), material_id: material_id.to_string(),
block_id: block_id.clone(), block_id: block_id.clone(),
position_snapshot: Some(pos.clone()),
} }
} }
Some(crate::progress::ReadingPosition::Text { line_number, .. }) => { Some(pos @ ReadingPosition::Text { line_number, .. }) => {
NoteAnchor::TextLine { NoteAnchor::TextLine {
material_id: material_id.to_string(), material_id: material_id.to_string(),
line_number: *line_number, line_number: *line_number,
position_snapshot: Some(pos.clone()),
} }
} }
Some(crate::progress::ReadingPosition::Pdf { page_number, .. }) => { Some(pos @ ReadingPosition::Pdf { page_number, .. }) => {
NoteAnchor::PdfPage { NoteAnchor::PdfPage {
material_id: material_id.to_string(), material_id: material_id.to_string(),
page_number: *page_number, page_number: *page_number,
position_snapshot: Some(pos.clone()),
} }
} }
Some(crate::progress::ReadingPosition::Image { .. }) => NoteAnchor::Image { Some(pos @ ReadingPosition::Image { .. }) => NoteAnchor::Image {
material_id: material_id.to_string(), material_id: material_id.to_string(),
position_snapshot: Some(pos.clone()),
}, },
Some(crate::progress::ReadingPosition::Epub { chapter_id, .. }) => { Some(pos @ ReadingPosition::Epub { chapter_id, .. }) => {
NoteAnchor::EpubChapter { NoteAnchor::EpubChapter {
material_id: material_id.to_string(), material_id: material_id.to_string(),
chapter_id: chapter_id.clone(), chapter_id: chapter_id.clone(),
position_snapshot: Some(pos.clone()),
} }
} }
_ => NoteAnchor::Material { _ => NoteAnchor::Material {
material_id: material_id.to_string(), material_id: material_id.to_string(),
position_snapshot: position.cloned(),
}, },
} }
} }
pub fn from_search_result(material_id: &str, result: &SearchResult) -> Self {
NoteAnchor::SearchResultAnchor {
material_id: material_id.to_string(),
block_id: Some(result.block_id.clone()),
line_number: result.line_number,
page_number: result.page_number,
chapter_id: result.chapter_id.clone(),
snippet: result.snippet.clone(),
}
}
/// Restore a ReadingPosition from this anchor.
/// Uses position_snapshot if available; otherwise constructs a minimal position
/// from the anchor fields. Returns None for anchors without position info.
pub fn to_position(&self) -> Option<ReadingPosition> {
match self {
NoteAnchor::MarkdownBlock { block_id, position_snapshot, .. } => {
position_snapshot.clone().or_else(|| {
Some(ReadingPosition::Markdown {
block_id: block_id.clone(),
scroll_progress: 0.0,
})
})
}
NoteAnchor::TextLine { line_number, position_snapshot, .. } => {
position_snapshot.clone().or_else(|| {
Some(ReadingPosition::Text {
line_number: *line_number,
scroll_progress: 0.0,
})
})
}
NoteAnchor::PdfPage { page_number, position_snapshot, .. } => {
position_snapshot.clone().or_else(|| {
Some(ReadingPosition::Pdf {
page_number: *page_number,
page_progress: 0.0,
overall_progress: 0.0,
})
})
}
NoteAnchor::Image { position_snapshot, .. } => position_snapshot.clone(),
NoteAnchor::EpubChapter { chapter_id, position_snapshot, .. } => {
position_snapshot.clone().or_else(|| {
Some(ReadingPosition::Epub {
chapter_id: chapter_id.clone(),
chapter_progress: 0.0,
overall_progress: 0.0,
})
})
}
NoteAnchor::Material { position_snapshot, .. } => position_snapshot.clone(),
NoteAnchor::SearchResultAnchor {
block_id,
line_number,
page_number,
chapter_id,
..
} => {
if let Some(pn) = page_number {
Some(ReadingPosition::Pdf {
page_number: *pn,
page_progress: 0.0,
overall_progress: 0.0,
})
} else if let Some(cid) = chapter_id {
Some(ReadingPosition::Epub {
chapter_id: cid.clone(),
chapter_progress: 0.0,
overall_progress: 0.0,
})
} else if let Some(ln) = line_number {
Some(ReadingPosition::Text {
line_number: *ln,
scroll_progress: 0.0,
})
} else if let Some(bid) = block_id {
Some(ReadingPosition::Markdown {
block_id: bid.clone(),
scroll_progress: 0.0,
})
} else {
None
}
}
NoteAnchor::KnowledgeItem { .. } => None,
}
}
} }
#[cfg(test)] #[cfg(test)]
@ -76,35 +193,303 @@ mod tests {
#[test] #[test]
fn test_material_anchor() { fn test_material_anchor() {
let a = NoteAnchor::from_position("abc", None); let a = NoteAnchor::from_position("abc", None);
assert_eq!(a, NoteAnchor::Material { material_id: "abc".into() }); assert_eq!(
a,
NoteAnchor::Material {
material_id: "abc".into(),
position_snapshot: None,
}
);
} }
#[test] #[test]
fn test_markdown_anchor() { fn test_markdown_anchor() {
let pos = ReadingPosition::Markdown { block_id: "h1".into(), scroll_progress: 0.5 }; let pos = ReadingPosition::Markdown {
block_id: "h1".into(),
scroll_progress: 0.5,
};
let a = NoteAnchor::from_position("abc", Some(&pos)); let a = NoteAnchor::from_position("abc", Some(&pos));
assert_eq!(a, NoteAnchor::MarkdownBlock { material_id: "abc".into(), block_id: "h1".into() }); assert_eq!(
a,
NoteAnchor::MarkdownBlock {
material_id: "abc".into(),
block_id: "h1".into(),
position_snapshot: Some(pos.clone()),
}
);
} }
#[test] #[test]
fn test_pdf_anchor() { fn test_pdf_anchor() {
let pos = ReadingPosition::Pdf { page_number: 3, page_progress: 0.5, overall_progress: 0.1 }; let pos = ReadingPosition::Pdf {
page_number: 3,
page_progress: 0.5,
overall_progress: 0.1,
};
let a = NoteAnchor::from_position("abc", Some(&pos)); let a = NoteAnchor::from_position("abc", Some(&pos));
assert_eq!(a, NoteAnchor::PdfPage { material_id: "abc".into(), page_number: 3 }); assert_eq!(
a,
NoteAnchor::PdfPage {
material_id: "abc".into(),
page_number: 3,
position_snapshot: Some(pos.clone()),
}
);
} }
#[test] #[test]
fn test_anchor_serde() { fn test_anchor_serde() {
let a = NoteAnchor::MarkdownBlock { material_id: "abc".into(), block_id: "h1".into() }; let a = NoteAnchor::MarkdownBlock {
material_id: "abc".into(),
block_id: "h1".into(),
position_snapshot: None,
};
let json = serde_json::to_string(&a).unwrap(); let json = serde_json::to_string(&a).unwrap();
let back: NoteAnchor = serde_json::from_str(&json).unwrap(); let back: NoteAnchor = serde_json::from_str(&json).unwrap();
assert_eq!(back, a); assert_eq!(back, a);
} }
#[test]
fn test_anchor_serde_with_snapshot() {
let pos = ReadingPosition::Markdown {
block_id: "h1".into(),
scroll_progress: 0.5,
};
let a = NoteAnchor::MarkdownBlock {
material_id: "abc".into(),
block_id: "h1".into(),
position_snapshot: Some(pos),
};
let json = serde_json::to_string(&a).unwrap();
assert!(json.contains("\"positionSnapshot\""));
let back: NoteAnchor = serde_json::from_str(&json).unwrap();
assert_eq!(back, a);
}
#[test] #[test]
fn test_unknown_position_falls_back_to_material() { fn test_unknown_position_falls_back_to_material() {
let pos = ReadingPosition::Unknown; let pos = ReadingPosition::Unknown;
let a = NoteAnchor::from_position("abc", Some(&pos)); let a = NoteAnchor::from_position("abc", Some(&pos));
assert_eq!(a, NoteAnchor::Material { material_id: "abc".into() }); assert_eq!(
a,
NoteAnchor::Material {
material_id: "abc".into(),
position_snapshot: Some(ReadingPosition::Unknown),
}
);
}
#[test]
fn test_from_search_result_markdown() {
let sr = SearchResult {
block_id: "h2".into(),
line_number: None,
page_number: None,
chapter_id: None,
snippet: "…found text…".into(),
match_start: 1,
match_end: 10,
};
let a = NoteAnchor::from_search_result("mat1", &sr);
match a {
NoteAnchor::SearchResultAnchor {
material_id,
block_id,
line_number,
chapter_id,
snippet,
..
} => {
assert_eq!(material_id, "mat1");
assert_eq!(block_id, Some("h2".to_string()));
assert_eq!(line_number, None);
assert_eq!(chapter_id, None);
assert_eq!(snippet, "…found text…");
}
_ => panic!("expected SearchResultAnchor"),
}
}
#[test]
fn test_from_search_result_pdf() {
let sr = SearchResult {
block_id: "page-3".into(),
line_number: None,
page_number: Some(3),
chapter_id: None,
snippet: "…pdf hit…".into(),
match_start: 0,
match_end: 8,
};
let a = NoteAnchor::from_search_result("mat2", &sr);
match a {
NoteAnchor::SearchResultAnchor {
material_id,
page_number,
..
} => {
assert_eq!(material_id, "mat2");
assert_eq!(page_number, Some(3));
}
_ => panic!("expected SearchResultAnchor"),
}
}
#[test]
fn test_from_search_result_epub() {
let sr = SearchResult {
block_id: "ch2".into(),
line_number: None,
page_number: None,
chapter_id: Some("ch2".into()),
snippet: "…epub hit…".into(),
match_start: 0,
match_end: 9,
};
let a = NoteAnchor::from_search_result("mat3", &sr);
match a {
NoteAnchor::SearchResultAnchor {
material_id,
chapter_id,
..
} => {
assert_eq!(material_id, "mat3");
assert_eq!(chapter_id, Some("ch2".to_string()));
}
_ => panic!("expected SearchResultAnchor"),
}
}
#[test]
fn test_search_result_anchor_serde() {
let a = NoteAnchor::SearchResultAnchor {
material_id: "mat1".into(),
block_id: Some("h1".into()),
line_number: None,
page_number: None,
chapter_id: None,
snippet: "…snippet…".into(),
};
let json = serde_json::to_string(&a).unwrap();
assert!(json.contains("\"type\":\"SearchResultAnchor\""));
let back: NoteAnchor = serde_json::from_str(&json).unwrap();
assert_eq!(back, a);
}
#[test]
fn test_backward_compat_no_snapshot() {
let old_json = r#"{"type":"MarkdownBlock","material_id":"abc","block_id":"h1"}"#;
let a: NoteAnchor = serde_json::from_str(old_json).unwrap();
assert_eq!(
a,
NoteAnchor::MarkdownBlock {
material_id: "abc".into(),
block_id: "h1".into(),
position_snapshot: None,
}
);
}
#[test]
fn test_to_position_with_snapshot() {
let pos = ReadingPosition::Markdown {
block_id: "h1".into(),
scroll_progress: 0.75,
};
let anchor = NoteAnchor::MarkdownBlock {
material_id: "mat1".into(),
block_id: "h1".into(),
position_snapshot: Some(pos.clone()),
};
let restored = anchor.to_position();
assert_eq!(restored, Some(pos));
}
#[test]
fn test_to_position_without_snapshot_uses_minimal() {
let anchor = NoteAnchor::PdfPage {
material_id: "mat1".into(),
page_number: 5,
position_snapshot: None,
};
let restored = anchor.to_position();
assert_eq!(
restored,
Some(ReadingPosition::Pdf {
page_number: 5,
page_progress: 0.0,
overall_progress: 0.0,
})
);
}
#[test]
fn test_to_position_from_search_result_pdf() {
let anchor = NoteAnchor::SearchResultAnchor {
material_id: "mat1".into(),
block_id: Some("page-3".into()),
line_number: None,
page_number: Some(3),
chapter_id: None,
snippet: "…hit…".into(),
};
let restored = anchor.to_position();
assert_eq!(
restored,
Some(ReadingPosition::Pdf {
page_number: 3,
page_progress: 0.0,
overall_progress: 0.0,
})
);
}
#[test]
fn test_to_position_from_search_result_markdown() {
let anchor = NoteAnchor::SearchResultAnchor {
material_id: "mat1".into(),
block_id: Some("h2".into()),
line_number: None,
page_number: None,
chapter_id: None,
snippet: "…hit…".into(),
};
let restored = anchor.to_position();
assert_eq!(
restored,
Some(ReadingPosition::Markdown {
block_id: "h2".into(),
scroll_progress: 0.0,
})
);
}
#[test]
fn test_to_position_knowledge_item_returns_none() {
let anchor = NoteAnchor::KnowledgeItem {
knowledge_item_id: "ki1".into(),
};
assert_eq!(anchor.to_position(), None);
}
#[test]
fn test_to_position_material_without_snapshot_returns_none() {
let anchor = NoteAnchor::Material {
material_id: "mat1".into(),
position_snapshot: None,
};
assert_eq!(anchor.to_position(), None);
}
#[test]
fn test_roundtrip_position_anchor_position() {
let pos = ReadingPosition::Epub {
chapter_id: "ch3".into(),
chapter_progress: 0.6,
overall_progress: 0.3,
};
let anchor = NoteAnchor::from_position("mat1", Some(&pos));
let restored = anchor.to_position();
assert_eq!(restored, Some(pos));
} }
} }

View File

@ -1,4 +1,599 @@
// M5: EPUB structure parsing (OPF, spine, nav, chapter list). use std::fs;
// Rust Core will parse the EPUB container and expose chapter-level metadata. use std::io::Read;
// Chapter HTML rendering is delegated to the host app's WebView / native HTML. use std::path::Path;
// See #25 DOC-501 and #26 DOC-502 for design and implementation plan.
use serde::{Deserialize, Serialize};
use crate::error::DocumentError;
/// Metadata extracted from an EPUB file.
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
pub struct EpubMetadata {
pub title: Option<String>,
pub author: Option<String>,
pub chapter_count: u32,
pub file_size: u64,
}
/// A single chapter in an EPUB spine.
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
pub struct EpubChapter {
pub chapter_id: String,
pub title: String,
/// Path within the EPUB zip, e.g. "OEBPS/chapter1.xhtml"
pub path: String,
/// Play order in the spine (0-based)
pub play_order: u32,
}
/// Combined result: metadata + chapters from a single EPUB open+parse.
#[derive(Debug, Clone)]
pub struct EpubData {
pub metadata: EpubMetadata,
pub chapters: Vec<EpubChapter>,
}
/// Read EPUB metadata and chapters in a single pass (single ZIP open + OPF parse).
/// When both metadata and chapters are needed, prefer this over calling both separately.
pub fn read_epub(file_path: &Path) -> Result<EpubData, DocumentError> {
let file = fs::File::open(file_path).map_err(DocumentError::IoError)?;
let file_size = file.metadata().map(|m| m.len()).unwrap_or(0);
let mut archive = zip::ZipArchive::new(file)
.map_err(|e| DocumentError::ParseError(format!("invalid EPUB zip: {e}")))?;
let opf_xml = read_opf(&mut archive)?;
let (title, author, spine_ids) = parse_opf(&opf_xml);
let toc = read_toc(&mut archive, &opf_xml);
let metadata = EpubMetadata {
title,
author,
chapter_count: spine_ids.len() as u32,
file_size,
};
let chapters: Vec<EpubChapter> = spine_ids
.iter()
.enumerate()
.map(|(i, (id, href))| {
let title = toc
.iter()
.find(|t| t.0 == *href || t.0 == *id)
.map(|t| t.1.clone())
.unwrap_or_else(|| format!("Chapter {}", i + 1));
EpubChapter {
chapter_id: id.clone(),
title,
path: href.clone(),
play_order: i as u32,
}
})
.collect();
Ok(EpubData { metadata, chapters })
}
/// Read EPUB metadata only. If chapters are also needed, use read_epub() instead.
pub fn read_epub_metadata(file_path: &Path) -> Result<EpubMetadata, DocumentError> {
read_epub(file_path).map(|data| data.metadata)
}
/// Read EPUB chapter list. If metadata is also needed, use read_epub() instead.
pub fn read_epub_chapters(file_path: &Path) -> Result<Vec<EpubChapter>, DocumentError> {
read_epub(file_path).map(|data| data.chapters)
}
// ── Internal helpers ──
fn read_opf(archive: &mut zip::ZipArchive<fs::File>) -> Result<String, DocumentError> {
// 1. Read container.xml to find OPF path
let container_xml = read_zip_entry(archive, "META-INF/container.xml")?;
let opf_path = extract_container_rootfile(&container_xml)?;
// 2. Read OPF
read_zip_entry(archive, &opf_path)
}
fn read_zip_entry(
archive: &mut zip::ZipArchive<fs::File>,
name: &str,
) -> Result<String, DocumentError> {
let mut file = archive
.by_name(name)
.map_err(|_| DocumentError::ParseError(format!("EPUB missing: {name}")))?;
let mut buf = String::new();
file.read_to_string(&mut buf)
.map_err(|e| DocumentError::IoError(e))?;
Ok(buf)
}
fn extract_container_rootfile(xml: &str) -> Result<String, DocumentError> {
// <rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/>
if let Some(start) = xml.find("full-path=") {
let after = &xml[start + 10..];
let delim = after.as_bytes()[0];
let value_start = if delim == b'"' || delim == b'\'' { 1 } else { 0 };
let value = &after[value_start..];
if let Some(end) = value.find(if delim == b'"' || delim == b'\'' { delim as char } else { ' ' }) {
return Ok(value[..end].to_string());
}
}
Err(DocumentError::ParseError("EPUB container.xml: no rootfile found".into()))
}
/// Returns (title, author, spine: Vec<(id, href)>)
fn parse_opf(xml: &str) -> (Option<String>, Option<String>, Vec<(String, String)>) {
let title = extract_tag_content(xml, "dc:title");
let author = extract_tag_content(xml, "dc:creator");
let manifest = extract_opf_manifest(xml);
let spine = extract_opf_spine(xml);
// Map spine idrefs to manifest hrefs, skipping NAV items
let nav_item = find_nav_item(xml);
let spine_items: Vec<(String, String)> = spine
.iter()
.filter_map(|idref| {
// Skip if this idref points to the NAV item
if let Some((nav_id, _)) = &nav_item {
if idref == nav_id {
return None;
}
}
manifest
.iter()
.find(|(id, _href, _media)| id == idref)
.map(|(id, href, _)| (id.clone(), href.clone()))
})
.collect();
(title, author, spine_items)
}
fn extract_tag_content(xml: &str, tag: &str) -> Option<String> {
let open = format!("<{}", tag);
let pos = xml.find(&open)?;
let after_tag = &xml[pos + open.len()..];
// Skip attributes: find '>'
let content_start = after_tag.find('>')? + 1;
let rest = &after_tag[content_start..];
let close = format!("</{}>", tag);
let content_end = rest.find(&close)?;
let content = rest[..content_end].trim().to_string();
if content.is_empty() { None } else { Some(content) }
}
fn extract_opf_manifest(xml: &str) -> Vec<(String, String, String)> {
// Extract <item id="x" href="y" media-type="z"/>
let mut items = Vec::new();
let mut pos = 0;
while let Some(i) = xml[pos..].find("<item ") {
let start = pos + i;
let end = xml[start..].find("/>").map(|e| start + e + 2)
.or_else(|| xml[start..].find('>').map(|e| start + e + 1))
.unwrap_or(start + 50);
let tag_text = &xml[start..end.min(xml.len())];
let id = extract_attr(tag_text, "id");
let href = extract_attr(tag_text, "href");
let media = extract_attr(tag_text, "media-type");
if let (Some(id), Some(href)) = (id, href) {
items.push((id, href, media.unwrap_or_default()));
}
pos = end;
if pos >= xml.len() - 5 { break; }
}
items
}
fn extract_opf_spine(xml: &str) -> Vec<String> {
let mut items = Vec::new();
let spine_start = xml.find("<spine").unwrap_or(0);
let spine_end = xml[spine_start..].find("</spine>")
.map(|e| spine_start + e + 9)
.unwrap_or(xml.len());
let spine_xml = &xml[spine_start..spine_end.min(xml.len())];
let mut pos = 0;
while let Some(i) = spine_xml[pos..].find("<itemref") {
let start = pos + i;
let end = spine_xml[start..].find("/>").map(|e| start + e + 2)
.or_else(|| spine_xml[start..].find('>').map(|e| start + e + 1))
.unwrap_or(start + 30);
let tag_text = &spine_xml[start..end.min(spine_xml.len())];
if let Some(idref) = extract_attr(tag_text, "idref") {
items.push(idref);
}
pos = end;
if pos >= spine_xml.len() - 5 { break; }
}
items
}
/// Read the table of contents. Tries NCX first, falls back to EPUB3 NAV.
fn read_toc(
archive: &mut zip::ZipArchive<fs::File>,
opf_xml: &str,
) -> Vec<(String, String)> {
// Try NCX first (EPUB2)
let toc = read_ncx_toc(archive, opf_xml);
if !toc.is_empty() {
return toc;
}
// Fall back to EPUB3 NAV
read_nav_toc(archive, opf_xml)
}
/// Try to read NCX TOC; returns Vec<(src, title)>
fn read_ncx_toc(
archive: &mut zip::ZipArchive<fs::File>,
opf_xml: &str,
) -> Vec<(String, String)> {
let ncx_href = extract_opf_manifest(opf_xml)
.iter()
.find(|(_id, href, media)| media == "application/x-dtbncx+xml" || href.ends_with(".ncx"))
.map(|(_id, href, _)| href.clone());
match &ncx_href {
Some(href) => match read_zip_entry(archive, href) {
Ok(xml) => parse_ncx_navpoints(&xml),
Err(_) => Vec::new(),
},
None => Vec::new(),
}
}
/// Try EPUB3 NAV document: find `<a href="...">title</a>` inside `<nav epub:type="toc">`.
fn read_nav_toc(
archive: &mut zip::ZipArchive<fs::File>,
opf_xml: &str,
) -> Vec<(String, String)> {
// Find NAV href from OPF manifest item with properties="nav"
let nav_href = find_nav_href_in_opf(opf_xml);
let nav_xml = match &nav_href {
Some(href) => match read_zip_entry(archive, href) {
Ok(xml) => xml,
Err(_) => return Vec::new(),
},
None => return Vec::new(),
};
parse_nav_links(&nav_xml)
}
/// Find the NAV item (id, href) from the manifest, or None.
fn find_nav_item(opf_xml: &str) -> Option<(String, String)> {
let mut pos = 0;
while let Some(i) = opf_xml[pos..].find("<item ") {
let start = pos + i;
let end = opf_xml[start..].find("/>").map(|e| start + e + 2)
.or_else(|| opf_xml[start..].find('>').map(|e| start + e + 1))
.unwrap_or(start + 200);
let tag = &opf_xml[start..end.min(opf_xml.len())];
let is_nav = tag.contains("properties=\"nav\"")
|| tag.contains("properties='nav'");
if is_nav {
if let (Some(id), Some(href)) = (extract_attr(tag, "id"), extract_attr(tag, "href")) {
return Some((id, href));
}
}
pos = end;
if pos >= opf_xml.len() - 5 { break; }
}
None
}
fn find_nav_href_in_opf(opf_xml: &str) -> Option<String> {
// Find <item ... properties="nav" ... /> and extract href
let mut pos = 0;
while let Some(i) = opf_xml[pos..].find("<item ") {
let start = pos + i;
let end = opf_xml[start..].find("/>").map(|e| start + e + 2)
.or_else(|| opf_xml[start..].find('>').map(|e| start + e + 1))
.unwrap_or(start + 200);
let tag = &opf_xml[start..end.min(opf_xml.len())];
// Check for properties="nav" or properties='nav'
let has_nav = tag.contains("properties=\"nav\"")
|| tag.contains("properties='nav'")
|| tag.contains("properties = \"nav\"");
if has_nav {
if let Some(href) = extract_attr(tag, "href") {
return Some(href);
}
}
pos = end;
if pos >= opf_xml.len() - 5 { break; }
}
None
}
fn parse_ncx_navpoints(xml: &str) -> Vec<(String, String)> {
let mut items = Vec::new();
let mut pos = 0;
while let Some(i) = xml[pos..].find("<navPoint ") {
let nav_start = pos + i;
let nav_end = xml[nav_start..].find("</navPoint>")
.map(|e| nav_start + e + 12)
.unwrap_or(xml.len());
let nav_xml = &xml[nav_start..nav_end.min(xml.len())];
let src = extract_ncx_src(nav_xml);
let label = extract_tag_content(nav_xml, "text");
if let Some(src) = src {
items.push((src, label.unwrap_or_default()));
}
pos = nav_end;
if pos >= xml.len() - 10 { break; }
}
items
}
/// Parse `<a href="...">title</a>` links from EPUB3 NAV document.
fn parse_nav_links(xml: &str) -> Vec<(String, String)> {
let mut items = Vec::new();
let mut pos = 0;
// Find each <a href="..."> element
while let Some(i) = xml[pos..].find("<a ") {
let start = pos + i;
// Find end of <a> tag:
let tag_end = xml[start..].find('>').map(|e| start + e + 1).unwrap_or(start + 50);
let tag_text = &xml[start..tag_end.min(xml.len())];
let href = extract_attr(tag_text, "href");
// Find text between <a ...> and </a>
let content_start = tag_end;
let content_end = xml[content_start..].find("</a>")
.map(|e| content_start + e)
.unwrap_or(content_start);
let title = xml[content_start..content_end].trim().to_string();
if let (Some(href), _) = (href, &title) {
if !title.is_empty() {
items.push((href, title));
}
}
pos = content_end + 4; // skip past </a>
if pos >= xml.len() - 5 { break; }
}
items
}
fn extract_ncx_src(nav_xml: &str) -> Option<String> {
if let Some(i) = nav_xml.find("src=") {
let after = &nav_xml[i + 4..];
let delim = after.as_bytes()[0];
let value_start = if delim == b'"' || delim == b'\'' { 1 } else { 0 };
let value = &after[value_start..];
if let Some(end) = value.find(if delim == b'"' || delim == b'\'' { delim as char } else { ' ' }) {
return Some(value[..end].to_string());
}
}
None
}
fn extract_attr(tag_text: &str, attr_name: &str) -> Option<String> {
let search = format!("{}=", attr_name);
let pos = tag_text.find(&search)?;
let after = &tag_text[pos + search.len()..];
let delim = after.as_bytes().first().copied().unwrap_or(b'"');
let value_start = if delim == b'"' || delim == b'\'' { 1 } else { 0 };
let value = &after[value_start..];
let end = value.find(if delim == b'"' || delim == b'\'' { delim as char } else { ' ' })?;
Some(value[..end].to_string())
}
#[cfg(test)]
mod tests {
use std::io::Write;
use super::*;
/// Build a minimal valid EPUB zip in memory.
fn minimal_epub(title: &str, author: &str, chapters: &[&str]) -> Vec<u8> {
let mut buf = std::io::Cursor::new(Vec::new());
{
let mut zip = zip::ZipWriter::new(&mut buf);
let opts = zip::write::SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Stored);
// mimetype (must be first, uncompressed)
zip.start_file("mimetype", opts).unwrap();
zip.write_all(b"application/epub+zip").unwrap();
// container.xml
zip.start_file("META-INF/container.xml", opts).unwrap();
zip.write_all(
b"<?xml version=\"1.0\"?>\n<container version=\"1.0\" xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\">\n <rootfiles>\n <rootfile full-path=\"OEBPS/content.opf\" media-type=\"application/oebps-package+xml\"/>\n </rootfiles>\n</container>"
).unwrap();
// OPF with metadata and spine
let mut opf = format!(
"<?xml version=\"1.0\"?>\n<package xmlns=\"http://www.idpf.org/2007/opf\" version=\"2.0\">\n<metadata xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n <dc:title>{}</dc:title>\n <dc:creator>{}</dc:creator>\n</metadata>\n<manifest>\n <item id=\"ncx\" href=\"toc.ncx\" media-type=\"application/x-dtbncx+xml\"/>\n",
title, author
);
for (i, ch) in chapters.iter().enumerate() {
opf.push_str(&format!(
" <item id=\"ch{}\" href=\"{}\" media-type=\"application/xhtml+xml\"/>\n",
i, ch
));
}
opf.push_str("</manifest>\n<spine toc=\"ncx\">\n");
for i in 0..chapters.len() {
opf.push_str(&format!(" <itemref idref=\"ch{}\"/>\n", i));
}
opf.push_str("</spine>\n</package>");
zip.start_file("OEBPS/content.opf", opts).unwrap();
zip.write_all(opf.as_bytes()).unwrap();
// NCX
let mut ncx = String::from(
"<?xml version=\"1.0\"?>\n<ncx xmlns=\"http://www.daisy.org/z3986/2005/ncx/\" version=\"2005-1\">\n<navMap>\n"
);
for (i, ch) in chapters.iter().enumerate() {
ncx.push_str(&format!(
" <navPoint id=\"nav{}\" playOrder=\"{}\">\n <navLabel><text>Chapter {}</text></navLabel>\n <content src=\"{}\"/>\n </navPoint>\n",
i, i + 1, i + 1, ch
));
}
ncx.push_str("</navMap>\n</ncx>");
zip.start_file("toc.ncx", opts).unwrap();
zip.write_all(ncx.as_bytes()).unwrap();
// Chapter files (empty)
for ch in chapters {
zip.start_file(*ch, opts).unwrap();
zip.write_all(b"<html/>").unwrap();
}
zip.finish().unwrap();
}
buf.into_inner()
}
fn write_temp(data: &[u8], name: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join("zx_doc_epub_test");
std::fs::create_dir_all(&dir).ok();
let path = dir.join(name);
std::fs::write(&path, data).unwrap();
path
}
#[test]
fn test_read_epub_metadata() {
let data = minimal_epub("Test Book", "Author Name", &["OEBPS/ch1.xhtml", "OEBPS/ch2.xhtml", "OEBPS/ch3.xhtml"]);
let path = write_temp(&data, "test.epub");
let meta = read_epub_metadata(&path).unwrap();
assert_eq!(meta.title, Some("Test Book".into()));
assert_eq!(meta.author, Some("Author Name".into()));
assert_eq!(meta.chapter_count, 3);
}
#[test]
fn test_read_epub_chapters() {
let data = minimal_epub("Book", "Author", &["OEBPS/intro.xhtml", "OEBPS/ch1.xhtml"]);
let path = write_temp(&data, "test_chapters.epub");
let chapters = read_epub_chapters(&path).unwrap();
assert_eq!(chapters.len(), 2);
assert_eq!(chapters[0].chapter_id, "ch0");
assert_eq!(chapters[0].title, "Chapter 1");
assert_eq!(chapters[0].path, "OEBPS/intro.xhtml");
assert_eq!(chapters[1].play_order, 1);
}
#[test]
fn test_read_epub_empty() {
let data = minimal_epub("Empty", "Author", &[]);
let path = write_temp(&data, "empty.epub");
let meta = read_epub_metadata(&path).unwrap();
assert_eq!(meta.chapter_count, 0);
let chapters = read_epub_chapters(&path).unwrap();
assert!(chapters.is_empty());
}
#[test]
fn test_fixture_simple_epub() {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent().unwrap().parent().unwrap();
let path = root.join("fixtures/epub/simple.epub");
if path.exists() {
let meta = read_epub_metadata(&path).unwrap();
assert_eq!(meta.title, Some("Simple Book".into()));
assert_eq!(meta.chapter_count, 1);
}
}
#[test]
fn test_fixture_epub_with_toc() {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent().unwrap().parent().unwrap();
let path = root.join("fixtures/epub/epub_with_toc.epub");
if path.exists() {
let chapters = read_epub_chapters(&path).unwrap();
assert_eq!(chapters.len(), 3);
assert_eq!(chapters[0].title, "Chapter 1");
}
}
fn minimal_epub3(title: &str, chapters: &[&str]) -> Vec<u8> {
let mut buf = std::io::Cursor::new(Vec::new());
{
let mut zip = zip::ZipWriter::new(&mut buf);
let opts = zip::write::SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Stored);
zip.start_file("mimetype", opts).unwrap();
zip.write_all(b"application/epub+zip").unwrap();
zip.start_file("META-INF/container.xml", opts).unwrap();
zip.write_all(
b"<?xml version=\"1.0\"?>\n<container version=\"1.0\" xmlns=\"urn:oasis:names:tc:opendocument:xmlns:container\">\n <rootfiles>\n <rootfile full-path=\"OEBPS/content.opf\" media-type=\"application/oebps-package+xml\"/>\n </rootfiles>\n</container>"
).unwrap();
// OPF with NAV instead of NCX
let mut opf = format!(
"<?xml version=\"1.0\"?>\n<package xmlns=\"http://www.idpf.org/2007/opf\" version=\"3.0\">\n<metadata xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n <dc:title>{}</dc:title>\n</metadata>\n<manifest>\n <item id=\"nav\" href=\"nav.xhtml\" media-type=\"application/xhtml+xml\" properties=\"nav\"/>\n",
title
);
for (i, ch) in chapters.iter().enumerate() {
opf.push_str(&format!(
" <item id=\"ch{}\" href=\"{}\" media-type=\"application/xhtml+xml\"/>\n",
i, ch
));
}
opf.push_str("</manifest>\n<spine>\n");
for i in 0..chapters.len() {
opf.push_str(&format!(" <itemref idref=\"ch{}\"/>\n", i));
}
opf.push_str("</spine>\n</package>");
zip.start_file("OEBPS/content.opf", opts).unwrap();
zip.write_all(opf.as_bytes()).unwrap();
// NAV XHTML with <nav epub:type="toc">
let mut nav = String::from("<?xml version=\"1.0\"?>\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:epub=\"http://www.idpf.org/2007/ops\">\n<body>\n<nav epub:type=\"toc\">\n<ol>\n");
for (i, ch) in chapters.iter().enumerate() {
nav.push_str(&format!(
" <li><a href=\"{}\">Chapter {}</a></li>\n",
ch, i + 1
));
}
nav.push_str("</ol>\n</nav>\n</body>\n</html>");
zip.start_file("nav.xhtml", opts).unwrap();
zip.write_all(nav.as_bytes()).unwrap();
for ch in chapters {
zip.start_file(*ch, opts).unwrap();
zip.write_all(b"<html/>").unwrap();
}
zip.finish().unwrap();
}
buf.into_inner()
}
#[test]
fn test_epub3_nav_fallback() {
let data = minimal_epub3("EPUB3 Book", &["OEBPS/ch1.xhtml", "OEBPS/ch2.xhtml", "OEBPS/ch3.xhtml"]);
let path = write_temp(&data, "test_epub3.epub");
// No NCX — should fall back to NAV parsing
let chapters = read_epub_chapters(&path).unwrap();
assert_eq!(chapters.len(), 3);
assert_eq!(chapters[0].title, "Chapter 1");
assert_eq!(chapters[1].title, "Chapter 2");
assert_eq!(chapters[2].title, "Chapter 3");
}
}

View File

@ -238,16 +238,25 @@ pub fn reload_stale_events_v2() -> u32 {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::sync::Mutex;
use super::*; use super::*;
use crate::reading_material::ReadingMaterialRef; use crate::reading_material::ReadingMaterialRef;
static TEST_LOCK: Mutex<()> = Mutex::new(());
fn setup_session(material_id: &str) -> String { fn setup_session(material_id: &str) -> String {
// Lock is held for the duration of each test via the caller.
let mat = ReadingMaterialRef::new(material_id); let mat = ReadingMaterialRef::new(material_id);
let id = session_v2::start_reading_session_v2(mat, 1000).unwrap(); let id = session_v2::start_reading_session_v2(mat, 1000).unwrap();
clear_all_events_v2(); clear_all_events_v2();
id id
} }
fn lock_test() -> std::sync::MutexGuard<'static, ()> {
TEST_LOCK.lock().unwrap()
}
fn teardown_session(id: &str) { fn teardown_session(id: &str) {
let _ = session_v2::close_reading_session_v2(id); let _ = session_v2::close_reading_session_v2(id);
let _ = session_v2::remove_session_v2(id); let _ = session_v2::remove_session_v2(id);
@ -255,6 +264,7 @@ mod tests {
#[test] #[test]
fn test_push_opened() { fn test_push_opened() {
let _guard = lock_test();
let sid = setup_session("mat_1"); let sid = setup_session("mat_1");
let ev = push_material_opened_v2(&sid, "mat_1", 1000).unwrap(); let ev = push_material_opened_v2(&sid, "mat_1", 1000).unwrap();
assert_eq!(ev.event_type, ReadingEventTypeV2::MaterialOpened); assert_eq!(ev.event_type, ReadingEventTypeV2::MaterialOpened);
@ -265,6 +275,7 @@ mod tests {
#[test] #[test]
fn test_push_closed() { fn test_push_closed() {
let _guard = lock_test();
let sid = setup_session("mat_2"); let sid = setup_session("mat_2");
let ev = push_material_closed_v2(&sid, "mat_2", 0, 5000).unwrap(); let ev = push_material_closed_v2(&sid, "mat_2", 0, 5000).unwrap();
assert_eq!(ev.event_type, ReadingEventTypeV2::MaterialClosed); assert_eq!(ev.event_type, ReadingEventTypeV2::MaterialClosed);
@ -273,6 +284,7 @@ mod tests {
#[test] #[test]
fn test_sequence_increments() { fn test_sequence_increments() {
let _guard = lock_test();
let sid = setup_session("mat_seq"); let sid = setup_session("mat_seq");
let e1 = push_material_opened_v2(&sid, "mat_seq", 1000).unwrap(); let e1 = push_material_opened_v2(&sid, "mat_seq", 1000).unwrap();
// Use delta=0 for tracker-independent tests // Use delta=0 for tracker-independent tests
@ -286,6 +298,7 @@ mod tests {
#[test] #[test]
fn test_export_ack_flow() { fn test_export_ack_flow() {
let _guard = TEST_LOCK.lock().unwrap();
clear_all_events_v2(); clear_all_events_v2();
let sid = setup_session("mat_ack"); let sid = setup_session("mat_ack");
let e1 = push_material_opened_v2(&sid, "mat_ack", 1000).unwrap(); let e1 = push_material_opened_v2(&sid, "mat_ack", 1000).unwrap();
@ -310,6 +323,7 @@ mod tests {
#[test] #[test]
fn test_mark_failed_and_retry() { fn test_mark_failed_and_retry() {
let _guard = TEST_LOCK.lock().unwrap();
clear_all_events_v2(); clear_all_events_v2();
let sid = setup_session("mat_retry"); let sid = setup_session("mat_retry");
let e1 = push_heartbeat_v2(&sid, "mat_retry", 15, None, 1000).unwrap(); let e1 = push_heartbeat_v2(&sid, "mat_retry", 15, None, 1000).unwrap();
@ -334,6 +348,7 @@ mod tests {
#[test] #[test]
fn test_buffer_size_not_exceeded() { fn test_buffer_size_not_exceeded() {
let _guard = TEST_LOCK.lock().unwrap();
clear_all_events_v2(); clear_all_events_v2();
let sid = setup_session("mat_overflow"); let sid = setup_session("mat_overflow");
let sz = buffer_size_v2(); let sz = buffer_size_v2();
@ -348,6 +363,7 @@ mod tests {
#[test] #[test]
fn test_ack_nonexistent_no_crash() { fn test_ack_nonexistent_no_crash() {
let _guard = lock_test();
clear_all_events_v2(); clear_all_events_v2();
let removed = ack_events_v2(&["nonexistent".to_string()]); let removed = ack_events_v2(&["nonexistent".to_string()]);
assert_eq!(removed, 0); assert_eq!(removed, 0);
@ -355,6 +371,7 @@ mod tests {
#[test] #[test]
fn test_closed_session_rejects() { fn test_closed_session_rejects() {
let _guard = lock_test();
let sid = setup_session("mat_reject"); let sid = setup_session("mat_reject");
session_v2::close_reading_session_v2(&sid).unwrap(); session_v2::close_reading_session_v2(&sid).unwrap();
assert!(push_heartbeat_v2(&sid, "mat_reject", 15, None, 3000).is_err()); assert!(push_heartbeat_v2(&sid, "mat_reject", 15, None, 3000).is_err());
@ -363,6 +380,7 @@ mod tests {
#[test] #[test]
fn test_serde_roundtrip() { fn test_serde_roundtrip() {
let _guard = lock_test();
let sid = setup_session("mat_serde"); let sid = setup_session("mat_serde");
let ev = push_material_opened_v2(&sid, "mat_serde", 1000).unwrap(); let ev = push_material_opened_v2(&sid, "mat_serde", 1000).unwrap();
let json = serde_json::to_string(&ev).unwrap(); let json = serde_json::to_string(&ev).unwrap();
@ -371,4 +389,222 @@ mod tests {
assert_eq!(back.event_id, ev.event_id); assert_eq!(back.event_id, ev.event_id);
teardown_session(&sid); teardown_session(&sid);
} }
#[test]
fn test_push_position_changed() {
let _guard = lock_test();
let sid = setup_session("mat_pc");
let pos = ReadingPosition::Markdown { block_id: "h1".into(), scroll_progress: 0.5 };
let ev = push_position_changed_v2(&sid, "mat_pc", pos.clone(), 2000).unwrap();
assert_eq!(ev.event_type, ReadingEventTypeV2::PositionChanged);
assert_eq!(ev.active_seconds_delta, 0);
assert_eq!(ev.material_id, "mat_pc");
assert!(ev.position.is_some());
teardown_session(&sid);
}
#[test]
fn test_push_heartbeat_with_position() {
let _guard = lock_test();
let sid = setup_session("mat_hb");
let pos = ReadingPosition::Pdf { page_number: 3, page_progress: 0.5, overall_progress: 0.1 };
let ev = push_heartbeat_v2(&sid, "mat_hb", 30, Some(pos.clone()), 3000).unwrap();
assert_eq!(ev.event_type, ReadingEventTypeV2::Heartbeat);
assert_eq!(ev.active_seconds_delta, 30);
assert_eq!(ev.material_id, "mat_hb");
assert!(ev.position.is_some());
teardown_session(&sid);
}
#[test]
fn test_push_heartbeat_without_position() {
let _guard = lock_test();
let sid = setup_session("mat_hb2");
let ev = push_heartbeat_v2(&sid, "mat_hb2", 10, None, 3000).unwrap();
assert_eq!(ev.event_type, ReadingEventTypeV2::Heartbeat);
assert_eq!(ev.active_seconds_delta, 10);
assert!(ev.position.is_none());
teardown_session(&sid);
}
#[test]
fn test_push_marked_as_read() {
let _guard = lock_test();
let sid = setup_session("mat_mar");
let ev = push_marked_as_read_v2(&sid, "mat_mar", 4000).unwrap();
assert_eq!(ev.event_type, ReadingEventTypeV2::MarkedAsRead);
assert_eq!(ev.active_seconds_delta, 0);
assert_eq!(ev.material_id, "mat_mar");
assert!(ev.position.is_none());
teardown_session(&sid);
}
#[test]
fn test_event_id_is_non_empty() {
let _guard = lock_test();
let sid = setup_session("mat_id");
let ev = push_material_opened_v2(&sid, "mat_id", 1000).unwrap();
assert!(!ev.event_id.is_empty());
assert_eq!(ev.event_id.len(), 36); // UUID v4 standard
assert!(ev.event_id.contains('-'));
teardown_session(&sid);
}
#[test]
fn test_event_ids_are_unique() {
let _guard = lock_test();
let sid = setup_session("mat_uniq");
let e1 = push_material_opened_v2(&sid, "mat_uniq", 1000).unwrap();
let e2 = push_position_changed_v2(&sid, "mat_uniq", ReadingPosition::Unknown, 2000).unwrap();
assert_ne!(e1.event_id, e2.event_id);
teardown_session(&sid);
}
#[test]
fn test_client_session_id_present() {
let _guard = lock_test();
let sid = setup_session("mat_csid");
let ev = push_material_opened_v2(&sid, "mat_csid", 1000).unwrap();
assert_eq!(ev.client_session_id, sid);
assert!(!ev.client_session_id.is_empty());
teardown_session(&sid);
}
#[test]
fn test_timestamp_ms_set() {
let _guard = lock_test();
let sid = setup_session("mat_ts");
let ev = push_material_opened_v2(&sid, "mat_ts", 123456789).unwrap();
assert_eq!(ev.timestamp_ms, 123456789);
teardown_session(&sid);
}
#[test]
fn test_position_normalized_in_event() {
let _guard = lock_test();
let sid = setup_session("mat_norm");
let pos = ReadingPosition::Pdf { page_number: 1, page_progress: 2.5, overall_progress: -0.5 };
let ev = push_position_changed_v2(&sid, "mat_norm", pos, 1000).unwrap();
let p = ev.position.unwrap();
match p {
ReadingPosition::Pdf { page_progress, overall_progress, .. } => {
assert_eq!(page_progress, 1.0); // clamped from 2.5
assert_eq!(overall_progress, 0.0); // clamped from -0.5
}
_ => panic!("expected Pdf position"),
}
teardown_session(&sid);
}
#[test]
fn test_all_five_event_types_have_distinct_types() {
let _guard = lock_test();
let sid = setup_session("mat_types");
let e1 = push_material_opened_v2(&sid, "mat_types", 1000).unwrap();
let e2 = push_material_closed_v2(&sid, "mat_types", 0, 2000).unwrap();
// Need new session for more events since session is now closed
teardown_session(&sid);
let sid2 = setup_session("mat_types2");
let e3 = push_position_changed_v2(&sid2, "mat_types2", ReadingPosition::Unknown, 1000).unwrap();
let e4 = push_heartbeat_v2(&sid2, "mat_types2", 10, None, 2000).unwrap();
let e5 = push_marked_as_read_v2(&sid2, "mat_types2", 3000).unwrap();
assert_eq!(e1.event_type, ReadingEventTypeV2::MaterialOpened);
assert_eq!(e2.event_type, ReadingEventTypeV2::MaterialClosed);
assert_eq!(e3.event_type, ReadingEventTypeV2::PositionChanged);
assert_eq!(e4.event_type, ReadingEventTypeV2::Heartbeat);
assert_eq!(e5.event_type, ReadingEventTypeV2::MarkedAsRead);
teardown_session(&sid2);
}
#[test]
fn test_reload_stale_events() {
let _guard = TEST_LOCK.lock().unwrap();
clear_all_events_v2();
let sid = setup_session("mat_stale");
let e1 = push_heartbeat_v2(&sid, "mat_stale", 15, None, 1000).unwrap();
let exported = export_pending_events_v2(100, 2000);
assert!(exported.iter().any(|e| e.event_id == e1.event_id));
let reloaded = reload_stale_events_v2();
assert_eq!(reloaded, 1);
let reexported = export_pending_events_v2(100, 3000);
assert!(reexported.iter().any(|e| e.event_id == e1.event_id));
ack_events_v2(&[e1.event_id.clone()]);
teardown_session(&sid);
}
#[test]
fn test_full_buffer_lifecycle() {
let _guard = TEST_LOCK.lock().unwrap();
clear_all_events_v2();
let sid = setup_session("mat_lifecycle");
// Step 1: push 2 events
let e1 = push_material_opened_v2(&sid, "mat_lifecycle", 1000).unwrap();
let e2 = push_heartbeat_v2(&sid, "mat_lifecycle", 15, None, 2000).unwrap();
let sz = buffer_size_v2();
assert!(sz >= 2, "buffer should have at least 2 events, got {sz}");
// Step 2: export → events marked Exported
let batch1 = export_pending_events_v2(100, 3000);
assert!(batch1.iter().any(|e| e.event_id == e1.event_id));
assert!(batch1.iter().any(|e| e.event_id == e2.event_id));
// Step 3: export again → should NOT re-export (already Exported)
let batch2 = export_pending_events_v2(100, 4000);
let reexported = batch2.iter().any(|e| e.event_id == e1.event_id || e.event_id == e2.event_id);
assert!(!reexported, "acked events should not be re-exported");
// Step 4: ack e1 only
let acked = ack_events_v2(&[e1.event_id.clone()]);
assert_eq!(acked, 1);
// Step 5: e2 still in buffer (unacked), e1 is gone
// Mark e2 as failed (simulates upload failure)
let marked = mark_events_failed_v2(&[e2.event_id.clone()]);
assert_eq!(marked, 1);
// Step 6: retry → failed events are re-exportable
let batch3 = export_pending_events_v2(100, 5000);
assert!(batch3.iter().any(|e| e.event_id == e2.event_id),
"failed event should be re-exported");
assert!(!batch3.iter().any(|e| e.event_id == e1.event_id),
"acked event should not appear");
// Step 7: ack e2 to clean up
ack_events_v2(&[e2.event_id.clone()]);
teardown_session(&sid);
}
#[test]
fn test_buffer_limit_export() {
let _guard = TEST_LOCK.lock().unwrap();
clear_all_events_v2();
let sid = setup_session("mat_limit");
// Push 5 events
let mut ids = Vec::new();
for i in 0..5 {
let e = push_material_opened_v2(&sid, "mat_limit", 1000 + i * 100).unwrap();
ids.push(e.event_id);
}
// Export with limit=2
let batch = export_pending_events_v2(2, 5000);
assert_eq!(batch.len(), 2);
// Next export should get the remaining 3
let batch2 = export_pending_events_v2(10, 6000);
let remaining = ids.len() as u32 - 2;
assert!(batch2.len() >= remaining as usize,
"expected at least {remaining} remaining, got {}", batch2.len());
// Clean up
for e in [batch, batch2].concat() {
ack_events_v2(&[e.event_id]);
}
teardown_session(&sid);
}
} }

View File

@ -10,6 +10,7 @@ pub mod events_v2;
pub mod image_meta; pub mod image_meta;
pub mod markdown; pub mod markdown;
pub mod material_type; pub mod material_type;
pub mod office;
pub mod pdf; pub mod pdf;
pub mod progress; pub mod progress;
pub mod reading_material; pub mod reading_material;

View File

@ -380,4 +380,85 @@ mod tests {
assert!(blocks.iter().any(|b| matches!(b, DocumentBlock::List { .. }))); assert!(blocks.iter().any(|b| matches!(b, DocumentBlock::List { .. })));
assert!(blocks.iter().any(|b| matches!(b, DocumentBlock::CodeBlock { .. }))); assert!(blocks.iter().any(|b| matches!(b, DocumentBlock::CodeBlock { .. })));
} }
#[test]
fn test_fixture_complex_markdown() {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent().unwrap().parent().unwrap();
let path = root.join("fixtures/markdown/complex_markdown.md");
let md = std::fs::read_to_string(&path).expect("fixture not found");
let blocks = parse_markdown(&md).unwrap();
assert!(!blocks.is_empty(), "should parse at least some blocks");
// Verify all 8 block types are present
let has_heading = blocks.iter().any(|b| matches!(b, DocumentBlock::Heading { .. }));
let has_para = blocks.iter().any(|b| matches!(b, DocumentBlock::Paragraph { .. }));
let has_list = blocks.iter().any(|b| matches!(b, DocumentBlock::List { .. }));
let has_code = blocks.iter().any(|b| matches!(b, DocumentBlock::CodeBlock { .. }));
let has_quote = blocks.iter().any(|b| matches!(b, DocumentBlock::Quote { .. }));
let has_table = blocks.iter().any(|b| matches!(b, DocumentBlock::Table { .. }));
let has_image = blocks.iter().any(|b| matches!(b, DocumentBlock::Image { .. }));
let has_rule = blocks.iter().any(|b| matches!(b, DocumentBlock::HorizontalRule { .. }));
assert!(has_heading, "missing heading");
assert!(has_para, "missing paragraph");
assert!(has_list, "missing list");
assert!(has_code, "missing code block");
assert!(has_quote, "missing quote");
assert!(has_table, "missing table");
assert!(has_image, "missing image");
assert!(has_rule, "missing horizontal rule");
// Verify heading level
let headings: Vec<_> = blocks.iter().filter_map(|b| {
if let DocumentBlock::Heading { level, text, .. } = b {
Some((*level, text.clone()))
} else { None }
}).collect();
assert!(headings.iter().any(|(l, _)| *l == 1), "should have h1");
assert!(headings.iter().any(|(l, _)| *l == 2), "should have h2");
assert!(headings.iter().any(|(l, _)| *l == 3), "should have h3");
// Verify code block has language
let code_blocks: Vec<_> = blocks.iter().filter_map(|b| {
if let DocumentBlock::CodeBlock { language, code, .. } = b {
Some((language.clone(), code.clone()))
} else { None }
}).collect();
assert!(code_blocks.iter().any(|(lang, _)| lang.as_deref() == Some("rust")), "should have rust code block");
// Verify table structure
for b in &blocks {
if let DocumentBlock::Table { headers, rows, .. } = b {
assert!(!headers.is_empty(), "table should have headers");
assert!(!rows.is_empty(), "table should have rows");
assert_eq!(headers.len(), rows[0].len(), "table row width should match headers");
}
}
// Verify image has src and alt
for b in &blocks {
if let DocumentBlock::Image { src, alt, .. } = b {
assert!(!src.is_empty(), "image should have src");
assert!(alt.is_some(), "image should have alt text");
}
}
// Verify block_ids are non-empty and unique
let ids: Vec<&str> = blocks.iter().map(|b| match b {
DocumentBlock::Heading { id, .. } => id.as_str(),
DocumentBlock::Paragraph { id, .. } => id.as_str(),
DocumentBlock::List { id, .. } => id.as_str(),
DocumentBlock::CodeBlock { id, .. } => id.as_str(),
DocumentBlock::Quote { id, .. } => id.as_str(),
DocumentBlock::Table { id, .. } => id.as_str(),
DocumentBlock::Image { id, .. } => id.as_str(),
DocumentBlock::HorizontalRule { id, .. } => id.as_str(),
}).collect();
for id in &ids {
assert!(!id.is_empty(), "block_id should not be empty");
}
let unique: std::collections::HashSet<_> = ids.iter().collect();
assert_eq!(unique.len(), ids.len(), "all block_ids should be unique");
}
} }

View File

@ -0,0 +1,105 @@
use serde::{Deserialize, Serialize};
use crate::error::DocumentError;
use crate::material_type::MaterialType;
/// Describes how an Office document should be previewed.
///
/// - Word/Excel → PlatformPreview (iOS: QLPreviewController / WKWebView)
/// - PowerPoint → ExternalOpen (open in system app)
/// - Future: ServerConvertedPdf (server converts Office→PDF)
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
pub enum OfficePreviewStrategy {
/// Use OS-native preview (QLPreviewController on iOS, Intent on Android)
PlatformPreview,
/// Open in external app (e.g., PowerPoint app for .pptx)
ExternalOpen,
/// Server-side conversion to PDF, then preview the PDF
ServerConvertedPdf,
/// Format not supported for preview
Unsupported,
}
/// Preview configuration for an Office document.
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
pub struct OfficePreviewConfig {
pub material_type: MaterialType,
pub strategy: OfficePreviewStrategy,
pub file_size: u64,
/// Whether the document can be searched after server conversion
pub supports_search_after_conversion: bool,
}
/// Get the recommended preview strategy for an Office material type.
pub fn get_office_preview_config(
material_type: &MaterialType,
file_size: u64,
) -> Result<OfficePreviewConfig, DocumentError> {
let (strategy, supports_search) = match material_type {
MaterialType::Word | MaterialType::Excel => {
(OfficePreviewStrategy::PlatformPreview, true)
}
MaterialType::PowerPoint => {
(OfficePreviewStrategy::ExternalOpen, true)
}
_ => {
return Err(DocumentError::UnsupportedFormat(
"not an Office document type".into()
));
}
};
Ok(OfficePreviewConfig {
material_type: material_type.clone(),
strategy,
file_size,
supports_search_after_conversion: supports_search,
})
}
/// Check if a material type is an Office document.
pub fn is_office_type(mt: &MaterialType) -> bool {
matches!(
mt,
MaterialType::Word | MaterialType::Excel | MaterialType::PowerPoint
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_word_preview_strategy() {
let config = get_office_preview_config(&MaterialType::Word, 1024).unwrap();
assert!(matches!(config.strategy, OfficePreviewStrategy::PlatformPreview));
assert!(config.supports_search_after_conversion);
}
#[test]
fn test_excel_preview_strategy() {
let config = get_office_preview_config(&MaterialType::Excel, 2048).unwrap();
assert!(matches!(config.strategy, OfficePreviewStrategy::PlatformPreview));
}
#[test]
fn test_powerpoint_preview_strategy() {
let config = get_office_preview_config(&MaterialType::PowerPoint, 512).unwrap();
assert!(matches!(config.strategy, OfficePreviewStrategy::ExternalOpen));
}
#[test]
fn test_non_office_rejected() {
assert!(get_office_preview_config(&MaterialType::Markdown, 100).is_err());
assert!(get_office_preview_config(&MaterialType::Pdf, 100).is_err());
}
#[test]
fn test_is_office_type() {
assert!(is_office_type(&MaterialType::Word));
assert!(is_office_type(&MaterialType::Excel));
assert!(is_office_type(&MaterialType::PowerPoint));
assert!(!is_office_type(&MaterialType::Pdf));
assert!(!is_office_type(&MaterialType::Markdown));
}
}

View File

@ -1,3 +1,298 @@
// M4: PDF is handled via platform preview (iOS PDFKit / QuickLook, Android system). use std::fs;
// Rust Core only provides the unified ReadingPosition model (see progress.rs). use std::path::Path;
// Full PDF parsing (pdfium, text extraction) is deferred to a later milestone.
use serde::{Deserialize, Serialize};
use crate::error::DocumentError;
const MAX_SCAN_BYTES: usize = 8 * 1024 * 1024;
/// Metadata extracted from a PDF file.
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
pub struct PdfMetadata {
pub page_count: u32,
pub title: Option<String>,
pub author: Option<String>,
pub file_size: u64,
}
/// A single page of extracted PDF text.
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
pub struct PdfPageText {
pub page_number: u32,
pub text: String,
}
/// Read PDF metadata using lightweight byte-level parsing.
pub fn read_pdf_metadata(file_path: &Path) -> Result<PdfMetadata, DocumentError> {
let data = fs::read(file_path)?;
let file_size = data.len() as u64;
let text = String::from_utf8_lossy(&data);
let page_count = count_pdf_pages(&text);
let title = extract_pdf_info_field(&text, "/Title");
let author = extract_pdf_info_field(&text, "/Author");
Ok(PdfMetadata { page_count, title, author, file_size })
}
/// Extract text from PDF pages.
/// Returns empty for now — full extraction requires pdfium or similar engine.
pub fn extract_pdf_text(_file_path: &Path) -> Result<Vec<PdfPageText>, DocumentError> {
Ok(Vec::new())
}
/// Count pages by scanning for /Type /Page objects.
fn count_pdf_pages(text: &str) -> u32 {
let mut count = 0u32;
let scan_limit = text.len().min(MAX_SCAN_BYTES);
let mut pos = 0;
while pos < scan_limit {
let type_start = match text[pos..].find("/Type") {
Some(i) => pos + i,
None => break,
};
let after_type = type_start + 5;
if after_type >= scan_limit {
break;
}
if let Some(page_start) = text[after_type..].find("/Page") {
let page_pos = after_type + page_start;
let after_page = page_pos + 5;
if after_page >= scan_limit {
break;
}
let next_char = text.as_bytes()[after_page];
if next_char.is_ascii_whitespace() || next_char == b'/' || next_char == b'>' {
if page_pos == after_type
|| (text.as_bytes()[page_pos - 1] != b's'
&& text.as_bytes()[page_pos - 1] != b'S')
{
count += 1;
}
}
pos = after_page;
} else {
pos = after_type;
}
}
if count == 0 {
count = count_pages_from_pages_dict(text);
}
if count == 0 && text.starts_with("%PDF-") {
count = 1;
}
count
}
fn count_pages_from_pages_dict(text: &str) -> u32 {
if let Some(pages_pos) = text.find("/Pages") {
let region = &text[pages_pos..text.len().min(pages_pos + 2000)];
if let Some(count_pos) = region.find("/Count") {
let after_count = &region[count_pos + 6..];
if let Some(num_str) = after_count
.trim_start()
.split(|c: char| !c.is_ascii_digit())
.next()
{
if let Ok(n) = num_str.parse::<u32>() {
return n;
}
}
}
}
0
}
fn extract_pdf_info_field(text: &str, field: &str) -> Option<String> {
let field_pos = text.find(field)?;
let after_field = &text[field_pos + field.len()..];
let trimmed = after_field.trim_start();
// Parenthesized string: (value)
if trimmed.starts_with('(') {
let mut depth = 0;
let mut result = String::new();
let mut chars = trimmed.chars();
while let Some(c) = chars.next() {
if c == '(' {
if depth > 0 {
result.push(c);
}
depth += 1;
} else if c == ')' {
depth -= 1;
if depth == 0 {
break;
}
result.push(c);
} else if c == '\\' {
if let Some(next) = chars.next() {
result.push(next);
}
} else if depth > 0 {
result.push(c);
}
}
let s = result.trim().to_string();
if !s.is_empty() {
return Some(s);
}
}
// Hex string: <value>
if trimmed.starts_with('<') {
if let Some(end) = trimmed.find('>') {
let hex = &trimmed[1..end];
let bytes: Vec<u8> = hex
.as_bytes()
.chunks(2)
.filter_map(|chunk| {
if chunk.len() == 2 {
u8::from_str_radix(std::str::from_utf8(chunk).ok()?, 16).ok()
} else {
None
}
})
.collect();
if !bytes.is_empty() {
return String::from_utf8(bytes).ok();
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
fn minimal_pdf(page_count: u32) -> Vec<u8> {
let header = b"%PDF-1.4\n%\x80\x80\x80\x80\n".to_vec();
let obj1 = b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n".to_vec();
let kids_refs: Vec<String> = (3..3 + page_count).map(|i| format!("{} 0 R", i)).collect();
let kids_str = kids_refs.join(" ");
let obj2 = format!(
"2 0 obj\n<< /Type /Pages /Kids [{}] /Count {} >>\nendobj\n",
kids_str, page_count
).into_bytes();
let mut page_objs = Vec::new();
for i in 0..page_count {
let obj_num = 3 + i;
page_objs.push(
format!(
"{} 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\n",
obj_num
).into_bytes()
);
}
let mut file = header;
let mut offsets = Vec::new();
offsets.push(file.len() as u64);
file.extend_from_slice(&obj1);
offsets.push(file.len() as u64);
file.extend_from_slice(&obj2);
for obj in &page_objs {
offsets.push(file.len() as u64);
file.extend_from_slice(obj);
}
let xref_offset = file.len() as u64;
let total_objects = 2 + page_count as usize;
file.extend_from_slice(
format!("xref\n0 {}\n0000000000 65535 f \n", total_objects + 1).as_bytes()
);
for &offset in &offsets {
file.extend_from_slice(format!("{:010} 00000 n \n", offset).as_bytes());
}
file.extend_from_slice(
format!(
"trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{}\n%%EOF\n",
total_objects + 1,
xref_offset
).as_bytes()
);
file
}
fn write_temp(data: &[u8], name: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join("zx_doc_pdf_test");
std::fs::create_dir_all(&dir).ok();
let path = dir.join(name);
std::fs::write(&path, data).unwrap();
path
}
#[test]
fn test_read_pdf_metadata_page_count() {
let data = minimal_pdf(3);
let path = write_temp(&data, "test_3pages.pdf");
let meta = read_pdf_metadata(&path).unwrap();
assert_eq!(meta.page_count, 3);
assert_eq!(meta.file_size, data.len() as u64);
}
#[test]
fn test_read_pdf_metadata_single_page() {
let data = minimal_pdf(1);
let path = write_temp(&data, "test_1page.pdf");
let meta = read_pdf_metadata(&path).unwrap();
assert_eq!(meta.page_count, 1);
}
#[test]
fn test_extract_pdf_text_empty() {
let data = minimal_pdf(1);
let path = write_temp(&data, "test_text_empty.pdf");
let pages = extract_pdf_text(&path).unwrap();
assert!(pages.is_empty());
}
#[test]
fn test_read_pdf_with_info_dict() {
// Build a PDF with an Info dictionary containing /Title and /Author
let info = b"/Title (Test Document) /Author (John Doe)";
let pdf = format!(
"%PDF-1.4\n1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\n4 0 obj\n<< {} >>\nendobj\nxref\n0 5\n0000000000 65535 f \n0000000009 00000 n \n0000000058 00000 n \n0000000115 00000 n \n0000000172 00000 n \ntrailer\n<< /Size 5 /Root 1 0 R /Info 4 0 R >>\nstartxref\n230\n%%EOF\n",
std::str::from_utf8(info).unwrap()
);
let path = write_temp(pdf.as_bytes(), "test_info.pdf");
let meta = read_pdf_metadata(&path).unwrap();
assert_eq!(meta.page_count, 1);
assert_eq!(meta.title, Some("Test Document".into()));
assert_eq!(meta.author, Some("John Doe".into()));
}
#[test]
fn test_fixture_text_pdf() {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent().unwrap().parent().unwrap();
let path = root.join("fixtures/pdf/text_pdf.pdf");
if path.exists() {
let meta = read_pdf_metadata(&path).unwrap();
assert_eq!(meta.page_count, 2);
}
}
#[test]
fn test_fixture_scanned_pdf() {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent().unwrap().parent().unwrap();
let path = root.join("fixtures/pdf/scanned_pdf.pdf");
if path.exists() {
let meta = read_pdf_metadata(&path).unwrap();
assert_eq!(meta.page_count, 1);
}
}
}

View File

@ -231,4 +231,83 @@ mod tests {
assert_eq!(&back, pos); assert_eq!(&back, pos);
} }
} }
#[test]
fn test_deserialize_from_camel_case_json() {
// Simulate JSON from iOS app / API server: all camelCase field names
let json = r#"{"type":"Markdown","blockId":"h1","scrollProgress":0.5}"#;
let pos: ReadingPosition = serde_json::from_str(json).unwrap();
assert_eq!(
pos,
ReadingPosition::Markdown { block_id: "h1".into(), scroll_progress: 0.5 }
);
let json = r#"{"type":"Text","lineNumber":5,"scrollProgress":0.3}"#;
let pos: ReadingPosition = serde_json::from_str(json).unwrap();
assert_eq!(
pos,
ReadingPosition::Text { line_number: 5, scroll_progress: 0.3 }
);
let json = r#"{"type":"Pdf","pageNumber":3,"pageProgress":0.8,"overallProgress":0.35}"#;
let pos: ReadingPosition = serde_json::from_str(json).unwrap();
assert_eq!(
pos,
ReadingPosition::Pdf { page_number: 3, page_progress: 0.8, overall_progress: 0.35 }
);
let json = r#"{"type":"Image","zoomScale":1.5,"offsetX":10.0,"offsetY":20.0}"#;
let pos: ReadingPosition = serde_json::from_str(json).unwrap();
assert_eq!(
pos,
ReadingPosition::Image { zoom_scale: 1.5, offset_x: 10.0, offset_y: 20.0 }
);
let json = r#"{"type":"Epub","chapterId":"ch3","chapterProgress":0.6,"overallProgress":0.3}"#;
let pos: ReadingPosition = serde_json::from_str(json).unwrap();
assert_eq!(
pos,
ReadingPosition::Epub { chapter_id: "ch3".into(), chapter_progress: 0.6, overall_progress: 0.3 }
);
let json = r#"{"type":"Unknown"}"#;
let pos: ReadingPosition = serde_json::from_str(json).unwrap();
assert_eq!(pos, ReadingPosition::Unknown);
}
#[test]
fn test_deserialize_serialize_roundtrip_all_variants() {
// Deserialize from camelCase → re-serialize → deserialize again → verify identical
let positions = vec![
r#"{"type":"Markdown","blockId":"intro","scrollProgress":0.25}"#,
r#"{"type":"Text","lineNumber":42,"scrollProgress":0.5}"#,
r#"{"type":"Pdf","pageNumber":7,"pageProgress":0.8,"overallProgress":0.35}"#,
r#"{"type":"Image","zoomScale":2.0,"offsetX":0.0,"offsetY":100.0}"#,
r#"{"type":"Epub","chapterId":"ch1","chapterProgress":0.6,"overallProgress":0.3}"#,
r#"{"type":"Unknown"}"#,
];
for json_str in &positions {
let pos1: ReadingPosition = serde_json::from_str(json_str).unwrap();
let re_serialized = serde_json::to_string(&pos1).unwrap();
let pos2: ReadingPosition = serde_json::from_str(&re_serialized).unwrap();
assert_eq!(pos1, pos2, "roundtrip failed for: {json_str}");
}
}
#[test]
fn test_rename_attrs_not_overridden_by_uniffi() {
// Verify #[serde(rename)] works correctly alongside #[derive(uniffi::Enum)]:
// serde uses renamed camelCase names, not Rust snake_case names.
// If uniffi overrode the renames, "blockId" would NOT be recognized.
// 1. snake_case should FAIL (field is renamed)
let json_snake = r#"{"type":"Markdown","block_id":"h1","scroll_progress":0.5}"#;
let result: Result<ReadingPosition, _> = serde_json::from_str(json_snake);
assert!(result.is_err(), "snake_case should fail: rename=blockId means block_id is unknown");
// 2. camelCase should SUCCEED
let json_camel = r#"{"type":"Markdown","blockId":"h1","scrollProgress":0.5}"#;
let result: Result<ReadingPosition, _> = serde_json::from_str(json_camel);
assert!(result.is_ok(), "camelCase should succeed: rename=blockId is the expected name");
}
} }

View File

@ -8,17 +8,54 @@ const SNIPPET_RADIUS: usize = 40;
pub struct SearchResult { pub struct SearchResult {
pub block_id: String, pub block_id: String,
pub line_number: Option<u32>, pub line_number: Option<u32>,
pub page_number: Option<u32>,
pub chapter_id: Option<String>,
pub snippet: String, pub snippet: String,
pub match_start: u64, pub match_start: u64,
pub match_end: u64, pub match_end: u64,
} }
/// Core search: find all occurrences of query_lower in text, call push for each match.
/// push receives (snippet, match_start_in_snippet, match_end_in_snippet, match_byte_offset).
fn for_each_match_in(
text: &str,
query_lower: &str,
mut push: impl FnMut(String, u64, u64, usize),
) {
let lower = text.to_lowercase();
let mut start = 0;
while let Some(pos) = lower[start..].find(query_lower) {
let abs_start = start + pos;
let abs_end = abs_start + query_lower.len();
let snippet_start = text.floor_char_boundary(abs_start.saturating_sub(SNIPPET_RADIUS));
let snippet_end = text.ceil_char_boundary((abs_end + SNIPPET_RADIUS).min(text.len()));
let snippet = if snippet_start > 0 {
format!("{}", &text[snippet_start..snippet_end])
} else {
text[snippet_start..snippet_end].to_string()
};
push(
snippet,
(abs_start - snippet_start) as u64,
(abs_end - snippet_start) as u64,
abs_start,
);
start = abs_end;
if start >= lower.len() {
break;
}
}
}
/// Search within a list of DocumentBlock (Markdown). /// Search within a list of DocumentBlock (Markdown).
pub fn search_blocks(blocks: &[DocumentBlock], query: &str) -> Vec<SearchResult> { pub fn search_blocks(blocks: &[DocumentBlock], query: &str) -> Vec<SearchResult> {
if query.is_empty() { if query.is_empty() {
return Vec::new(); return Vec::new();
} }
let query = query.to_lowercase(); let query_lower = query.to_lowercase();
let mut results = Vec::new(); let mut results = Vec::new();
for block in blocks { for block in blocks {
@ -26,33 +63,18 @@ pub fn search_blocks(blocks: &[DocumentBlock], query: &str) -> Vec<SearchResult>
if text.is_empty() { if text.is_empty() {
continue; continue;
} }
let lower = text.to_lowercase(); let bid = block_id.to_string();
let mut start = 0; for_each_match_in(&text, &query_lower, |snippet, m_start, m_end, _offset| {
while let Some(pos) = lower[start..].find(&query) {
let abs_start = start + pos;
let abs_end = abs_start + query.len();
let snippet_start = abs_start.saturating_sub(SNIPPET_RADIUS);
let snippet_end = (abs_end + SNIPPET_RADIUS).min(text.len());
let snippet = if snippet_start > 0 {
format!("{}", &text[snippet_start..snippet_end])
} else {
text[snippet_start..snippet_end].to_string()
};
results.push(SearchResult { results.push(SearchResult {
block_id: block_id.to_string(), block_id: bid.clone(),
line_number: None, line_number: None,
page_number: None,
chapter_id: None,
snippet, snippet,
match_start: (abs_start - snippet_start) as u64, match_start: m_start,
match_end: (abs_end - snippet_start) as u64, match_end: m_end,
});
}); });
start = abs_end;
if start >= lower.len() {
break;
}
}
} }
results results
@ -63,38 +85,72 @@ pub fn search_text(content: &str, query: &str) -> Vec<SearchResult> {
if query.is_empty() { if query.is_empty() {
return Vec::new(); return Vec::new();
} }
let query = query.to_lowercase(); let query_lower = query.to_lowercase();
let lower = content.to_lowercase();
let mut results = Vec::new(); let mut results = Vec::new();
let mut start = 0;
while let Some(pos) = lower[start..].find(&query) {
let abs_start = start + pos;
let abs_end = abs_start + query.len();
// Determine line number
let line_number = content[..abs_start].lines().count() as u32;
let snippet_start = abs_start.saturating_sub(SNIPPET_RADIUS);
let snippet_end = (abs_end + SNIPPET_RADIUS).min(content.len());
let snippet = if snippet_start > 0 {
format!("{}", &content[snippet_start..snippet_end])
} else {
content[snippet_start..snippet_end].to_string()
};
for_each_match_in(content, &query_lower, |snippet, m_start, m_end, offset| {
let line_number = content[..offset].lines().count() as u32;
results.push(SearchResult { results.push(SearchResult {
block_id: format!("line-{line_number}"), block_id: format!("line-{line_number}"),
line_number: Some(line_number), line_number: Some(line_number),
page_number: None,
chapter_id: None,
snippet, snippet,
match_start: (abs_start - snippet_start) as u64, match_start: m_start,
match_end: (abs_end - snippet_start) as u64, match_end: m_end,
});
}); });
start = abs_end; results
if start >= lower.len() {
break;
} }
/// Search within PDF pages. Each page is a (page_number, text) tuple.
pub fn search_pdf_text(pages: &[(u32, &str)], query: &str) -> Vec<SearchResult> {
if query.is_empty() {
return Vec::new();
}
let query_lower = query.to_lowercase();
let mut results = Vec::new();
for (page_num, page_text) in pages {
let pn = *page_num;
for_each_match_in(page_text, &query_lower, |snippet, m_start, m_end, _offset| {
results.push(SearchResult {
block_id: format!("page-{pn}"),
line_number: None,
page_number: Some(pn),
chapter_id: None,
snippet,
match_start: m_start,
match_end: m_end,
});
});
}
results
}
/// Search within EPUB chapters. Each chapter is a (chapter_id, chapter_text) tuple.
pub fn search_epub_chapters(chapters: &[(String, &str)], query: &str) -> Vec<SearchResult> {
if query.is_empty() {
return Vec::new();
}
let query_lower = query.to_lowercase();
let mut results = Vec::new();
for (chapter_id, chapter_text) in chapters {
let cid = chapter_id.clone();
for_each_match_in(chapter_text, &query_lower, |snippet, m_start, m_end, _offset| {
results.push(SearchResult {
block_id: cid.clone(),
line_number: None,
page_number: None,
chapter_id: Some(cid.clone()),
snippet,
match_start: m_start,
match_end: m_end,
});
});
} }
results results
@ -187,4 +243,147 @@ mod tests {
let results = search_text(content, ""); let results = search_text(content, "");
assert!(results.is_empty()); assert!(results.is_empty());
} }
#[test]
fn test_search_pdf_text_basic() {
let pages = vec![
(1u32, "This is page one content."),
(2u32, "This is page two with target word."),
(3u32, "Page three."),
];
let results = search_pdf_text(&pages, "target");
assert_eq!(results.len(), 1);
assert_eq!(results[0].page_number, Some(2));
assert_eq!(results[0].block_id, "page-2");
assert!(results[0].chapter_id.is_none());
assert!(results[0].line_number.is_none());
}
#[test]
fn test_search_pdf_text_multiple_pages() {
let pages = vec![
(1u32, "Hello world"),
(2u32, "Hello again"),
];
let results = search_pdf_text(&pages, "hello");
assert_eq!(results.len(), 2);
assert_eq!(results[0].page_number, Some(1));
assert_eq!(results[1].page_number, Some(2));
}
#[test]
fn test_search_pdf_text_no_match() {
let pages = vec![(1u32, "Nothing here.")];
let results = search_pdf_text(&pages, "missing");
assert!(results.is_empty());
}
#[test]
fn test_search_pdf_text_empty_query() {
let pages = vec![(1u32, "Content.")];
let results = search_pdf_text(&pages, "");
assert!(results.is_empty());
}
#[test]
fn test_search_epub_chapters_basic() {
let chapters = vec![
("ch1".to_string(), "Chapter one introduction."),
("ch2".to_string(), "Chapter two has the keyword here."),
];
let results = search_epub_chapters(&chapters, "keyword");
assert_eq!(results.len(), 1);
assert_eq!(results[0].chapter_id, Some("ch2".to_string()));
assert_eq!(results[0].block_id, "ch2");
assert!(results[0].page_number.is_none());
assert!(results[0].line_number.is_none());
}
#[test]
fn test_search_epub_chapters_multiple() {
let chapters = vec![
("intro".to_string(), "Welcome to the book."),
("ch1".to_string(), "The book begins here."),
("ch2".to_string(), "The book continues."),
];
let results = search_epub_chapters(&chapters, "book");
assert_eq!(results.len(), 3);
}
#[test]
fn test_search_epub_chapters_no_match() {
let chapters = vec![("ch1".to_string(), "Just text.")];
let results = search_epub_chapters(&chapters, "absent");
assert!(results.is_empty());
}
#[test]
fn test_search_epub_chapters_empty_query() {
let chapters = vec![("ch1".to_string(), "Text.")];
let results = search_epub_chapters(&chapters, "");
assert!(results.is_empty());
}
#[test]
fn test_search_result_new_fields_serde() {
let result = SearchResult {
block_id: "b1".into(),
line_number: Some(5),
page_number: None,
chapter_id: None,
snippet: "…snippet…".into(),
match_start: 10,
match_end: 20,
};
let json = serde_json::to_string(&result).unwrap();
let back: SearchResult = serde_json::from_str(&json).unwrap();
assert_eq!(back.block_id, "b1");
assert_eq!(back.line_number, Some(5));
assert_eq!(back.page_number, None);
assert_eq!(back.chapter_id, None);
}
#[test]
fn test_search_result_pdf_fields_serde() {
let result = SearchResult {
block_id: "page-3".into(),
line_number: None,
page_number: Some(3),
chapter_id: None,
snippet: "…pdf match…".into(),
match_start: 0,
match_end: 5,
};
let json = serde_json::to_string(&result).unwrap();
assert!(json.contains("\"page_number\":3"));
let back: SearchResult = serde_json::from_str(&json).unwrap();
assert_eq!(back.page_number, Some(3));
}
#[test]
fn test_search_result_epub_fields_serde() {
let result = SearchResult {
block_id: "ch2".into(),
line_number: None,
page_number: None,
chapter_id: Some("ch2".into()),
snippet: "…epub match…".into(),
match_start: 0,
match_end: 8,
};
let json = serde_json::to_string(&result).unwrap();
assert!(json.contains("\"chapter_id\":\"ch2\""));
let back: SearchResult = serde_json::from_str(&json).unwrap();
assert_eq!(back.chapter_id, Some("ch2".into()));
}
#[test]
fn test_utf8_multi_byte_boundary_safe() {
// The emoji "世界" is 3 bytes each in UTF-8, "🌍" is 4 bytes.
// Searching near them should not panic on char boundaries.
let content = "Hello 🌍 world! 你好世界 test here.";
let results = search_text(content, "test");
assert_eq!(results.len(), 1);
assert!(results[0].snippet.contains("test"));
}
} }

View File

@ -167,6 +167,31 @@ pub fn get_session_v2(session_id: &str) -> Result<ReadingSessionV2, SessionError
} }
} }
/// Clean up stale sessions that have been inactive longer than max_age_ms.
/// This should be called on app startup to recover from crash/kill scenarios.
/// Removes both Closed and orphaned Active/Paused sessions.
pub fn cleanup_stale_sessions_v2(now_ms: i64, max_age_ms: i64) -> u32 {
let mut removed = 0u32;
match (sessions().lock(), trackers().lock()) {
(Ok(mut map), Ok(mut tmap)) => {
let stale_ids: Vec<String> = map
.iter()
.filter(|(_, s)| now_ms - s.last_event_at_ms > max_age_ms)
.map(|(id, _)| id.clone())
.collect();
for id in stale_ids {
map.remove(&id);
tmap.remove(&id);
removed += 1;
}
}
_ => {}
}
removed
}
/// Remove a closed session from memory. Refuses to remove Active/Paused sessions. /// Remove a closed session from memory. Refuses to remove Active/Paused sessions.
pub fn remove_session_v2(session_id: &str) -> Result<(), SessionError> { pub fn remove_session_v2(session_id: &str) -> Result<(), SessionError> {
match (sessions().lock(), trackers().lock()) { match (sessions().lock(), trackers().lock()) {
@ -309,4 +334,92 @@ mod tests {
teardown(&a); teardown(&a);
teardown(&b); teardown(&b);
} }
#[test]
fn test_total_active_seconds_accumulates() {
let id = start_reading_session_v2(test_material(), 0).unwrap();
// Simulate iOS calling tick() and passing deltas to session events
record_session_event_v2(&id, 15_000, 15, None).unwrap();
record_session_event_v2(&id, 30_000, 15, None).unwrap();
record_session_event_v2(&id, 43_000, 13, None).unwrap();
let s = get_session_v2(&id).unwrap();
assert_eq!(s.total_active_seconds, 43);
teardown(&id);
}
#[test]
fn test_sequence_and_seconds_independent() {
let a = start_reading_session_v2(test_material(), 0).unwrap();
let b = start_reading_session_v2(ReadingMaterialRef::new("mat_b"), 0).unwrap();
record_session_event_v2(&a, 10_000, 10, None).unwrap();
record_session_event_v2(&b, 5_000, 5, None).unwrap();
record_session_event_v2(&b, 10_000, 5, None).unwrap();
assert_eq!(get_session_v2(&a).unwrap().total_active_seconds, 10);
assert_eq!(get_session_v2(&a).unwrap().next_sequence, 2);
assert_eq!(get_session_v2(&b).unwrap().total_active_seconds, 10);
assert_eq!(get_session_v2(&b).unwrap().next_sequence, 3);
teardown(&a);
teardown(&b);
}
#[test]
fn test_last_position_updated() {
let id = start_reading_session_v2(test_material(), 0).unwrap();
let pos1 = ReadingPosition::Markdown { block_id: "intro".into(), scroll_progress: 0.3 };
let pos2 = ReadingPosition::Markdown { block_id: "ch1".into(), scroll_progress: 0.8 };
record_session_event_v2(&id, 1000, 0, Some(pos1.clone())).unwrap();
record_session_event_v2(&id, 2000, 0, Some(pos2.clone())).unwrap();
assert_eq!(get_session_v2(&id).unwrap().last_position, Some(pos2));
teardown(&id);
}
#[test]
fn test_cleanup_stale_removes_old_sessions() {
// Old session: last event at t=1000
let old = start_reading_session_v2(test_material(), 0).unwrap();
record_session_event_v2(&old, 1000, 0, None).unwrap();
// Recent session: last event at t=19000 (just 1s ago at now=20000)
let recent = start_reading_session_v2(ReadingMaterialRef::new("mat_recent"), 10000).unwrap();
record_session_event_v2(&recent, 19000, 0, None).unwrap();
// At t=20000, clean up sessions inactive for > 5000ms
// old: 20000-1000=19000 > 5000 → stale
// recent: 20000-19000=1000 < 5000 → NOT stale
let removed = cleanup_stale_sessions_v2(20000, 5000);
assert_eq!(removed, 1);
assert!(get_session_v2(&old).is_err());
assert!(get_session_v2(&recent).is_ok());
teardown(&recent);
}
#[test]
fn test_cleanup_stale_removes_orphaned_active_session() {
// Simulate a crash: active session not closed, last event at t=5000
let orphaned = start_reading_session_v2(test_material(), 0).unwrap();
record_session_event_v2(&orphaned, 5000, 10, None).unwrap();
// Session still Active
// At t=60000, clean up sessions inactive for > 10000ms
// 60000 - 5000 = 55000 > 10000 → stale
let removed = cleanup_stale_sessions_v2(60000, 10000);
assert_eq!(removed, 1);
assert!(get_session_v2(&orphaned).is_err());
}
#[test]
fn test_cleanup_stale_zero_when_all_recent() {
let active = start_reading_session_v2(test_material(), 0).unwrap();
// Last event at t=9500, now at t=10000, max_age=1000
// 10000-9500=500 < 1000 → NOT stale
record_session_event_v2(&active, 9500, 0, None).unwrap();
let removed = cleanup_stale_sessions_v2(10000, 1000);
assert_eq!(removed, 0);
assert!(get_session_v2(&active).is_ok());
teardown(&active);
}
} }

View File

@ -13,6 +13,9 @@ pub use zx_document_core::progress::ReadingPosition;
pub use zx_document_core::events::ReadingEvent; pub use zx_document_core::events::ReadingEvent;
pub use zx_document_core::reading_material::ReadingMaterialRef; pub use zx_document_core::reading_material::ReadingMaterialRef;
pub use zx_document_core::events_v2::{ReadingEventV2, ReadingEventTypeV2}; pub use zx_document_core::events_v2::{ReadingEventV2, ReadingEventTypeV2};
pub use zx_document_core::epub::{EpubMetadata, EpubChapter};
pub use zx_document_core::office::{OfficePreviewConfig, OfficePreviewStrategy};
pub use zx_document_core::pdf::{PdfMetadata, PdfPageText};
pub use zx_document_core::session_v2::{ReadingSessionV2, ReadingSessionStatus}; pub use zx_document_core::session_v2::{ReadingSessionV2, ReadingSessionStatus};
use zx_document_core::blocks as core_blocks; use zx_document_core::blocks as core_blocks;
@ -79,6 +82,11 @@ fn export_pending_events_v2(limit: u32, timestamp_ms: i64) -> Vec<ReadingEventV2
zx_document_core::events_v2::export_pending_events_v2(limit, timestamp_ms) zx_document_core::events_v2::export_pending_events_v2(limit, timestamp_ms)
} }
#[uniffi::export]
fn reload_stale_events_v2() -> u32 {
zx_document_core::events_v2::reload_stale_events_v2()
}
#[uniffi::export] #[uniffi::export]
fn ack_events_v2(event_ids: Vec<String>) -> u32 { fn ack_events_v2(event_ids: Vec<String>) -> u32 {
zx_document_core::events_v2::ack_events_v2(&event_ids) zx_document_core::events_v2::ack_events_v2(&event_ids)
@ -301,11 +309,72 @@ fn search_text_content(content: String, query: String) -> Vec<SearchResult> {
zx_document_core::search::search_text(&content, &query) zx_document_core::search::search_text(&content, &query)
} }
#[uniffi::export]
fn search_pdf_pages(page_numbers: Vec<u32>, page_texts: Vec<String>, query: String) -> Vec<SearchResult> {
let pages: Vec<_> = page_numbers.iter().copied()
.zip(page_texts.iter().map(|s| s.as_str()))
.collect();
zx_document_core::search::search_pdf_text(&pages, &query)
}
#[uniffi::export]
fn search_epub_chapters_ffi(chapter_ids: Vec<String>, chapter_texts: Vec<String>, query: String) -> Vec<SearchResult> {
let chapters: Vec<_> = chapter_ids.iter().map(|s| s.clone())
.zip(chapter_texts.iter().map(|s| s.as_str()))
.collect();
zx_document_core::search::search_epub_chapters(&chapters, &query)
}
#[uniffi::export] #[uniffi::export]
fn create_note_anchor(material_id: String, position: Option<ReadingPosition>) -> NoteAnchor { fn create_note_anchor(material_id: String, position: Option<ReadingPosition>) -> NoteAnchor {
zx_document_core::anchors::NoteAnchor::from_position(&material_id, position.as_ref()) zx_document_core::anchors::NoteAnchor::from_position(&material_id, position.as_ref())
} }
#[uniffi::export]
fn create_note_anchor_from_search(material_id: String, result: SearchResult) -> NoteAnchor {
zx_document_core::anchors::NoteAnchor::from_search_result(&material_id, &result)
}
#[uniffi::export]
fn restore_position_from_anchor(anchor: NoteAnchor) -> Option<ReadingPosition> {
anchor.to_position()
}
#[uniffi::export]
fn read_pdf_metadata_ffi(file_path: String) -> Result<PdfMetadata, DocumentError> {
zx_document_core::pdf::read_pdf_metadata(std::path::Path::new(&file_path)).map_err(Into::into)
}
#[uniffi::export]
fn extract_pdf_text_ffi(file_path: String) -> Result<Vec<PdfPageText>, DocumentError> {
zx_document_core::pdf::extract_pdf_text(std::path::Path::new(&file_path)).map_err(Into::into)
}
#[uniffi::export]
fn read_epub_metadata_ffi(file_path: String) -> Result<EpubMetadata, DocumentError> {
zx_document_core::epub::read_epub_metadata(std::path::Path::new(&file_path)).map_err(Into::into)
}
#[uniffi::export]
fn read_epub_chapters_ffi(file_path: String) -> Result<Vec<EpubChapter>, DocumentError> {
zx_document_core::epub::read_epub_chapters(std::path::Path::new(&file_path)).map_err(Into::into)
}
#[uniffi::export]
fn get_office_preview_config_ffi(material_type: MaterialType, file_size: u64) -> Result<OfficePreviewConfig, DocumentError> {
zx_document_core::office::get_office_preview_config(&material_type, file_size).map_err(Into::into)
}
#[uniffi::export]
fn is_office_type_ffi(material_type: MaterialType) -> bool {
zx_document_core::office::is_office_type(&material_type)
}
#[uniffi::export]
fn cleanup_stale_sessions_ffi(now_ms: i64, max_age_ms: i64) -> u32 {
zx_document_core::session_v2::cleanup_stale_sessions_v2(now_ms, max_age_ms)
}
#[uniffi::export] #[uniffi::export]
fn push_reading_event(event: ReadingEvent) { fn push_reading_event(event: ReadingEvent) {
zx_document_core::events::push_reading_event(event) zx_document_core::events::push_reading_event(event)
@ -500,6 +569,52 @@ pub extern "C" fn ffi_zx_document_ffi_search_text_content_separate(
write_result_to_out!(Ok::<_, DocumentError>(result), out_capacity, out_len, out_data, out_error_code); write_result_to_out!(Ok::<_, DocumentError>(result), out_capacity, out_len, out_data, out_error_code);
} }
#[no_mangle]
pub extern "C" fn ffi_zx_document_ffi_search_pdf_pages_separate(
page_numbers_cap: u64, page_numbers_len: u64, page_numbers_data: *const u8,
page_texts_cap: u64, page_texts_len: u64, page_texts_data: *const u8,
query_len: i32, query_data: *const u8,
out_capacity: *mut u64, out_len: *mut u64, out_data: *mut *mut u8, out_error_code: *mut i8,
) {
let query = match unsafe { read_str_input(query_len, query_data, out_error_code) } {
Some(s) => s, None => return,
};
let page_numbers: Vec<u32> = lift_from_raw!(Vec<u32>, page_numbers_cap, page_numbers_len, page_numbers_data);
let page_texts: Vec<String> = lift_from_raw!(Vec<String>, page_texts_cap, page_texts_len, page_texts_data);
let result: Vec<SearchResult> = crate::search_pdf_pages(page_numbers, page_texts, query);
write_result_to_out!(Ok::<_, DocumentError>(result), out_capacity, out_len, out_data, out_error_code);
}
#[no_mangle]
pub extern "C" fn ffi_zx_document_ffi_search_epub_chapters_ffi_separate(
chapter_ids_cap: u64, chapter_ids_len: u64, chapter_ids_data: *const u8,
chapter_texts_cap: u64, chapter_texts_len: u64, chapter_texts_data: *const u8,
query_len: i32, query_data: *const u8,
out_capacity: *mut u64, out_len: *mut u64, out_data: *mut *mut u8, out_error_code: *mut i8,
) {
let query = match unsafe { read_str_input(query_len, query_data, out_error_code) } {
Some(s) => s, None => return,
};
let chapter_ids: Vec<String> = lift_from_raw!(Vec<String>, chapter_ids_cap, chapter_ids_len, chapter_ids_data);
let chapter_texts: Vec<String> = lift_from_raw!(Vec<String>, chapter_texts_cap, chapter_texts_len, chapter_texts_data);
let result: Vec<SearchResult> = crate::search_epub_chapters_ffi(chapter_ids, chapter_texts, query);
write_result_to_out!(Ok::<_, DocumentError>(result), out_capacity, out_len, out_data, out_error_code);
}
#[no_mangle]
pub extern "C" fn ffi_zx_document_ffi_create_note_anchor_from_search_separate(
mid_len: i32, mid_data: *const u8,
result_cap: u64, result_len: u64, result_data: *const u8,
out_capacity: *mut u64, out_len: *mut u64, out_data: *mut *mut u8, out_error_code: *mut i8,
) {
let material_id = match unsafe { read_str_input(mid_len, mid_data, out_error_code) } {
Some(s) => s, None => return,
};
let search_result: SearchResult = lift_from_raw!(SearchResult, result_cap, result_len, result_data);
let anchor: NoteAnchor = crate::create_note_anchor_from_search(material_id, search_result);
write_result_to_out!(Ok::<_, DocumentError>(anchor), out_capacity, out_len, out_data, out_error_code);
}
// Reverse conversion: FFI DocumentBlock → core DocumentBlock, used by search. // Reverse conversion: FFI DocumentBlock → core DocumentBlock, used by search.
fn core_block_from_ffi(block: DocumentBlock) -> core_blocks::DocumentBlock { fn core_block_from_ffi(block: DocumentBlock) -> core_blocks::DocumentBlock {
match block { match block {
@ -529,3 +644,201 @@ fn core_block_from_ffi(block: DocumentBlock) -> core_blocks::DocumentBlock {
} }
} }
} }
#[cfg(test)]
mod ffi_tests {
use super::*;
use zx_document_core::events_v2;
fn drain_buffer() {
loop {
let batch = export_pending_events_v2(1000, 0);
if batch.is_empty() { break; }
let ids: Vec<String> = batch.iter().map(|e| e.event_id.clone()).collect();
ack_events_v2(ids);
}
}
// ── V2 Event Pipeline ──
#[test]
fn test_v2_full_event_pipeline() {
drain_buffer();
events_v2::clear_all_events_v2();
let mat = ReadingMaterialRef::new("mat_ffi_test".to_string());
let sid = start_reading_session_v2(mat, 1000).unwrap();
assert!(!sid.is_empty());
let e1 = push_material_opened_v2(sid.clone(), "mat_ffi_test".to_string(), 1000).unwrap();
assert_eq!(e1.event_type, ReadingEventTypeV2::MaterialOpened);
assert_eq!(e1.sequence, 1);
let pos = ReadingPosition::Markdown { block_id: "intro".to_string(), scroll_progress: 0.25 };
let e2 = push_position_changed_v2(sid.clone(), "mat_ffi_test".to_string(), pos, 2000).unwrap();
assert_eq!(e2.event_type, ReadingEventTypeV2::PositionChanged);
assert_eq!(e2.sequence, 2);
let e3 = push_heartbeat_v2(sid.clone(), "mat_ffi_test".to_string(), 15, None, 5000).unwrap();
assert_eq!(e3.event_type, ReadingEventTypeV2::Heartbeat);
assert_eq!(e3.active_seconds_delta, 15);
let e4 = push_marked_as_read_v2(sid.clone(), "mat_ffi_test".to_string(), 10000).unwrap();
assert_eq!(e4.event_type, ReadingEventTypeV2::MarkedAsRead);
push_material_closed_v2(sid.clone(), "mat_ffi_test".to_string(), 0, 12000).unwrap();
close_reading_session_v2(sid.clone()).unwrap();
let exported = export_pending_events_v2(100, 13000);
assert!(exported.len() >= 4, "expected >=4, got {}", exported.len());
let types: Vec<ReadingEventTypeV2> = exported.iter().map(|e| e.event_type.clone()).collect();
assert!(types.contains(&ReadingEventTypeV2::MaterialOpened));
assert!(types.contains(&ReadingEventTypeV2::PositionChanged));
assert!(types.contains(&ReadingEventTypeV2::Heartbeat));
assert!(types.contains(&ReadingEventTypeV2::MarkedAsRead));
let ids: Vec<String> = exported.iter().map(|e| e.event_id.clone()).collect();
let acked = ack_events_v2(ids);
assert!(acked >= 4);
let _ = zx_document_core::session_v2::remove_session_v2(&sid);
}
// ── Session Lifecycle ──
#[test]
fn test_session_lifecycle() {
let mat = ReadingMaterialRef::new("mat_ffi_life".to_string());
let sid = start_reading_session_v2(mat, 0).unwrap();
pause_reading_session_v2(sid.clone()).unwrap();
resume_reading_session_v2(sid.clone()).unwrap();
close_reading_session_v2(sid.clone()).unwrap();
let _ = zx_document_core::session_v2::remove_session_v2(&sid);
}
// ── Buffer Recovery ──
#[test]
fn test_mark_failed_and_recover() {
drain_buffer();
events_v2::clear_all_events_v2();
let mat = ReadingMaterialRef::new("mat_ffi_recover".to_string());
let sid = start_reading_session_v2(mat, 0).unwrap();
let e = push_material_opened_v2(sid.clone(), "mat_ffi_recover".to_string(), 1000).unwrap();
let batch = export_pending_events_v2(100, 2000);
assert!(batch.iter().any(|ev| ev.event_id == e.event_id));
let marked = mark_events_failed_v2(vec![e.event_id.clone()]);
assert_eq!(marked, 1);
let retry = export_pending_events_v2(100, 3000);
assert!(retry.iter().any(|ev| ev.event_id == e.event_id));
ack_events_v2(vec![e.event_id.clone()]);
close_reading_session_v2(sid.clone()).unwrap();
let _ = zx_document_core::session_v2::remove_session_v2(&sid);
}
// ── Parse → Search → Anchor ──
#[test]
fn test_parse_search_anchor() {
let md = "# Hello\n\nParagraph with searchable text.\n\n## Section 2\n\nMore.";
let blocks = parse_markdown(md.to_string()).unwrap();
assert!(!blocks.is_empty());
let results = search_markdown_blocks(blocks, "searchable".to_string());
assert_eq!(results.len(), 1);
assert!(results[0].snippet.to_lowercase().contains("searchable"));
// Position → Anchor
let pos = ReadingPosition::Markdown { block_id: "h1".to_string(), scroll_progress: 0.5 };
let anchor = create_note_anchor("mat_ffi".to_string(), Some(pos));
match &anchor {
NoteAnchor::MarkdownBlock { material_id, block_id, .. } => {
assert_eq!(material_id, "mat_ffi");
assert_eq!(block_id, "h1");
}
_ => panic!("expected MarkdownBlock"),
}
// Anchor → Position (roundtrip)
let restored = restore_position_from_anchor(anchor);
assert!(restored.is_some());
// SearchResult → Anchor
let sr_anchor = create_note_anchor_from_search("mat_ffi".to_string(), results[0].clone());
match sr_anchor {
NoteAnchor::SearchResultAnchor { material_id, .. } => {
assert_eq!(material_id, "mat_ffi");
}
_ => panic!("expected SearchResultAnchor"),
}
}
// ── Text Search ──
#[test]
fn test_text_search() {
let results = search_text_content(
"Line one\nLine two with keyword\nLine three".to_string(),
"keyword".to_string(),
);
assert_eq!(results.len(), 1);
assert_eq!(results[0].line_number, Some(2));
}
// ── PDF Search ──
#[test]
fn test_pdf_search() {
let results = search_pdf_pages(
vec![1, 2, 3],
vec!["Page 1.".to_string(), "Page 2 with target.".to_string(), "Page 3.".to_string()],
"target".to_string(),
);
assert_eq!(results.len(), 1);
assert_eq!(results[0].page_number, Some(2));
}
// ── EPUB Search ──
#[test]
fn test_epub_search() {
let results = search_epub_chapters_ffi(
vec!["intro".to_string(), "ch1".to_string()],
vec!["Welcome.".to_string(), "Chapter one keyword here.".to_string()],
"keyword".to_string(),
);
assert_eq!(results.len(), 1);
assert_eq!(results[0].chapter_id, Some("ch1".to_string()));
}
// ── V1 Backward Compatibility ──
#[test]
fn test_v1_backward_compat() {
let event = ReadingEvent::MaterialOpened {
material_id: "mat_v1_ffi".to_string(),
timestamp_ms: 1000,
};
push_reading_event(event);
let exported = export_pending_events();
assert!(!exported.is_empty());
let found = exported.iter().any(|e| {
matches!(e, ReadingEvent::MaterialOpened { material_id, .. } if material_id == "mat_v1_ffi")
});
assert!(found, "V1 event should be exported");
clear_exported_events(exported.len() as u32);
}
// ── Error Handling ──
#[test]
fn test_session_not_found() {
assert!(close_reading_session_v2("nonexistent".to_string()).is_err());
}
}

View File

@ -18,8 +18,74 @@ namespace zx_document {
sequence<SearchResult> search_text_content([ByRef] string content, [ByRef] string query); sequence<SearchResult> search_text_content([ByRef] string content, [ByRef] string query);
sequence<SearchResult> search_pdf_pages(sequence<u32> page_numbers, sequence<string> page_texts, [ByRef] string query);
sequence<SearchResult> search_epub_chapters_ffi(sequence<string> chapter_ids, sequence<string> chapter_texts, [ByRef] string query);
NoteAnchor create_note_anchor([ByRef] string material_id, ReadingPosition? position); NoteAnchor create_note_anchor([ByRef] string material_id, ReadingPosition? position);
NoteAnchor create_note_anchor_from_search([ByRef] string material_id, SearchResult result);
ReadingPosition? restore_position_from_anchor(NoteAnchor anchor);
[Throws=DocumentError]
PdfMetadata read_pdf_metadata_ffi([ByRef] string file_path);
[Throws=DocumentError]
sequence<PdfPageText> extract_pdf_text_ffi([ByRef] string file_path);
[Throws=DocumentError]
EpubMetadata read_epub_metadata_ffi([ByRef] string file_path);
[Throws=DocumentError]
sequence<EpubChapter> read_epub_chapters_ffi([ByRef] string file_path);
[Throws=DocumentError]
OfficePreviewConfig get_office_preview_config_ffi(MaterialType material_type, u64 file_size);
boolean is_office_type_ffi(MaterialType material_type);
u32 cleanup_stale_sessions_ffi(i64 now_ms, i64 max_age_ms);
// ── V2 Reading Session ──
[Throws=DocumentError]
string start_reading_session_v2(ReadingMaterialRef material, i64 timestamp_ms);
[Throws=DocumentError]
void pause_reading_session_v2([ByRef] string session_id);
[Throws=DocumentError]
void resume_reading_session_v2([ByRef] string session_id);
[Throws=DocumentError]
void close_reading_session_v2([ByRef] string session_id);
// ── V2 Reading Events ──
[Throws=DocumentError]
ReadingEventV2 push_material_opened_v2([ByRef] string session_id, [ByRef] string material_id, i64 timestamp_ms);
[Throws=DocumentError]
ReadingEventV2 push_material_closed_v2([ByRef] string session_id, [ByRef] string material_id, u32 active_seconds_delta, i64 timestamp_ms);
[Throws=DocumentError]
ReadingEventV2 push_position_changed_v2([ByRef] string session_id, [ByRef] string material_id, ReadingPosition position, i64 timestamp_ms);
[Throws=DocumentError]
ReadingEventV2 push_heartbeat_v2([ByRef] string session_id, [ByRef] string material_id, u32 active_seconds_delta, ReadingPosition? position, i64 timestamp_ms);
[Throws=DocumentError]
ReadingEventV2 push_marked_as_read_v2([ByRef] string session_id, [ByRef] string material_id, i64 timestamp_ms);
// ── V2 Buffer Management ──
sequence<ReadingEventV2> export_pending_events_v2(u32 limit, i64 timestamp_ms);
u32 ack_events_v2(sequence<string> event_ids);
u32 mark_events_failed_v2(sequence<string> event_ids);
u32 reload_stale_events_v2();
// ── V1 (deprecated) ──
void push_reading_event(ReadingEvent event); void push_reading_event(ReadingEvent event);
void update_reading_position([ByRef] string material_id, ReadingPosition position); void update_reading_position([ByRef] string material_id, ReadingPosition position);
sequence<ReadingEvent> export_pending_events(); sequence<ReadingEvent> export_pending_events();
@ -35,6 +101,50 @@ enum DocumentError {
"IoError", "IoError",
}; };
// ── V2 Types ──
dictionary ReadingMaterialRef {
string material_id;
};
[Enum]
interface ReadingSessionStatus {
Active();
Paused();
Closed();
};
dictionary ReadingSessionV2 {
string client_session_id;
ReadingMaterialRef material;
i64 started_at_ms;
i64 last_event_at_ms;
u64 next_sequence;
u32 total_active_seconds;
ReadingPosition? last_position;
ReadingSessionStatus status;
};
[Enum]
interface ReadingEventTypeV2 {
MaterialOpened();
MaterialClosed();
PositionChanged();
Heartbeat();
MarkedAsRead();
};
dictionary ReadingEventV2 {
string event_id;
string client_session_id;
string material_id;
ReadingEventTypeV2 event_type;
ReadingPosition? position;
u32 active_seconds_delta;
i64 timestamp_ms;
u64 sequence;
};
[Enum] [Enum]
interface MaterialType { interface MaterialType {
Markdown(); Markdown();
@ -88,13 +198,14 @@ interface ReadingEvent {
[Enum] [Enum]
interface NoteAnchor { interface NoteAnchor {
Material(string material_id); Material(string material_id, ReadingPosition? position_snapshot);
MarkdownBlock(string material_id, string block_id); MarkdownBlock(string material_id, string block_id, ReadingPosition? position_snapshot);
TextLine(string material_id, u32 line_number); TextLine(string material_id, u32 line_number, ReadingPosition? position_snapshot);
PdfPage(string material_id, u32 page_number); PdfPage(string material_id, u32 page_number, ReadingPosition? position_snapshot);
Image(string material_id); Image(string material_id, ReadingPosition? position_snapshot);
EpubChapter(string material_id, string chapter_id); EpubChapter(string material_id, string chapter_id, ReadingPosition? position_snapshot);
KnowledgeItem(string knowledge_item_id); KnowledgeItem(string knowledge_item_id);
SearchResultAnchor(string material_id, string? block_id, u32? line_number, u32? page_number, string? chapter_id, string snippet);
}; };
dictionary ImageMeta { dictionary ImageMeta {
@ -107,11 +218,54 @@ dictionary ImageMeta {
dictionary SearchResult { dictionary SearchResult {
string block_id; string block_id;
u32? line_number; u32? line_number;
u32? page_number;
string? chapter_id;
string snippet; string snippet;
u64 match_start; u64 match_start;
u64 match_end; u64 match_end;
}; };
dictionary PdfMetadata {
u32 page_count;
string? title;
string? author;
u64 file_size;
};
dictionary EpubMetadata {
string? title;
string? author;
u32 chapter_count;
u64 file_size;
};
[Enum]
interface OfficePreviewStrategy {
PlatformPreview();
ExternalOpen();
ServerConvertedPdf();
Unsupported();
};
dictionary OfficePreviewConfig {
MaterialType material_type;
OfficePreviewStrategy strategy;
u64 file_size;
boolean supports_search_after_conversion;
};
dictionary EpubChapter {
string chapter_id;
string title;
string path;
u32 play_order;
};
dictionary PdfPageText {
u32 page_number;
string text;
};
dictionary TextStats { dictionary TextStats {
u32 line_count; u32 line_count;
u32 word_count; u32 word_count;

128
docs/ffi-troubleshooting.md Normal file
View File

@ -0,0 +1,128 @@
# FFI Troubleshooting
## UniFFI 版本
本项目使用 UniFFI 0.31,采用 **UDL + proc-macro 混合模式**
- UDL`zx_document.udl`):定义类型和函数签名
- `#[uniffi::export]` proc-macro生成 C ABI 分发符号
- `uniffi-bindgen`:生成 Swift/Kotlin 绑定代码
## 常见问题
### `No such module 'ZxDocumentRuntime'`
**原因**XCFramework 未正确添加到 Xcode 项目。
**解决**
1. 确认 `bindings/ios/ZxDocumentRuntime.xcframework` 已生成
2. 在 Xcode → Target → General → Frameworks 中添加
3. 确保 Embed 设置为 `Do Not Embed`(静态库)
### `Undefined symbol: _ffi_zx_document_ffi_*`
**原因**C ABI 符号未导出。proc-macro 生成的符号名称可能与链接器期望不一致。
**解决**
1. 确认 `crates/zx_document_ffi/src/lib.rs``#[no_mangle] pub extern "C"` 函数存在
2. 检查 Cargo.toml `crate-type = ["lib", "staticlib", "cdylib"]`
3. 运行 `nm target/aarch64-apple-ios/release/libzx_document_ffi.a | grep ffi_` 验证符号
4. 确认 UDL 中声明的函数与 `#[uniffi::export]` 函数名称一致
### `Library not found for -lzx_document_ffi`
**原因**Library Search Paths 未设置。
**解决**
1. Build Settings → Library Search Paths → 添加 `$(PROJECT_DIR)/bindings/ios`
2. 或使用绝对路径指向 `.xcframework` 所在目录
### UniFFI checksum mismatch
**原因**UDL 文件更新后未重新生成 Swift binding。
**解决**
```bash
uniffi-bindgen generate \
--language swift \
--out-dir bindings/ios/generated \
crates/zx_document_ffi/src/zx_document.udl
```
### `setup_scaffolding!()` not called
**原因**`uniffi::setup_scaffolding!()` 必须在 lib.rs 的第一行调用。
**解决**:确认 `crates/zx_document_core/src/lib.rs` 第一行是 `uniffi::setup_scaffolding!();`
### 新增类型/函数后 Swift 不可见
**原因**:只在 Rust 侧添加了类型/函数,但未更新:
1. UDL 文件声明
2. FFI crate `pub use` 重导出
3. Swift binding 重新生成
**完整的 FFI 接入步骤**
```bash
# 1. Rust 侧添加类型 + 函数 + UDL 声明
# 2. FFI crate: pub use 重导出 + #[uniffi::export] 包装
# 3. 重新生成 Swift
uniffi-bindgen generate --language swift \
--out-dir bindings/ios/generated \
crates/zx_document_ffi/src/zx_document.udl
# 4. 验证编译
cargo build --release --target aarch64-apple-ios -p zx_document_ffi
```
### `cargo build` 目标平台不匹配
**原因**macOS 上构建 iOS 目标需要 Apple Silicon target。
**解决**
```bash
rustup target add aarch64-apple-ios aarch64-apple-ios-sim
```
### `infer` / `comrak` / `zip` crate 编译失败
**原因**:某些 crate 在 iOS target 下有 C 依赖。
**解决**
- `comrak`:需要启用 `--features` 中的 `onig` 或使用 `syntect` 后端
- `zip`:纯 Rust无特殊要求
- 确保 `CC` 环境变量指向 Xcode toolchain
### XCFramework 构建注意事项
1. 必须分别编译 device 和 simulator 目标
2. `lipo` 不需要(两个 .a 文件各自是 fat binary
3. header 文件由 `uniffi-bindgen` 生成:`bindings/ios/generated/zx_documentFFI.h`
4. modulemap 文件创建:
```
framework module ZxDocumentRuntime {
header "zx_documentFFI.h"
export *
}
```
### UDL 类型不支持
**问题类型**
- `Vec<(String, &str)>` — 不支持 tuple
- 泛型函数 — 不支持
- 生命周期参数 — 不支持
**解决**:在 FFI 层创建包装类型。例如 `search_pdf_text(&[(u32, &str)])` → FFI 包装为 `search_pdf_pages(Vec<u32>, Vec<String>)`
### Rust 与 iOS 持有引用
UniFFI 生成的对象是 **值类型**(序列化跨 FFI 边界传递不是引用类型。Rust 侧的 `Mutex` 保护全局状态iOS 侧通过函数调用读写状态。
### 验证清单
- [ ] `cargo build --release --target aarch64-apple-ios` 通过
- [ ] `cargo build --release --target aarch64-apple-ios-sim` 通过
- [ ] `nm` 验证 C ABI 符号存在
- [ ] Swift binding 生成并编译通过
- [ ] XCFramework 包含 device + simulator slices
- [ ] header + modulemap 正确
- [ ] demo App 链接成功

View File

@ -115,24 +115,180 @@ do {
} }
``` ```
### 阅读事件集成 ### V2 阅读事件集成(推荐)
V2 API 提供 session 管理、事件缓冲、ack 确认、crash recovery 等完整能力。
#### 创建阅读目标和会话
```swift ```swift
// Rust 生成事件 → Swift 缓存 + 上传 // 1. 创建 ReadingMaterialRefRust 不存储 readingTargetTypeiOS 上传时补充)
class ReadingEventCollector { let material = ReadingMaterialRef(materialId: "mat_001")
private var buffer: [ReadingEvent] = []
func collect() { // 2. 启动阅读会话(返回 clientSessionId
// Rust 侧事件暂存 let timestamp = Int64(Date().timeIntervalSince1970 * 1000)
let sessionId = try startReadingSessionV2(material: material, timestampMs: timestamp)
```
#### 推送 5 种事件
```swift
// MaterialOpened — 打开资料
let opened = try pushMaterialOpenedV2(
sessionId: sessionId,
materialId: "mat_001",
timestampMs: now()
)
// PositionChanged — 位置变化
let pos = ReadingPosition.markdown(blockId: "intro", scrollProgress: 0.25)
let changed = try pushPositionChangedV2(
sessionId: sessionId,
materialId: "mat_001",
position: pos,
timestampMs: now()
)
// Heartbeat — 心跳(含 activeSecondsDelta
let hb = try pushHeartbeatV2(
sessionId: sessionId,
materialId: "mat_001",
activeSecondsDelta: 15,
position: nil,
timestampMs: now()
)
// MarkedAsRead — 标记已读
let mar = try pushMarkedAsReadV2(
sessionId: sessionId,
materialId: "mat_001",
timestampMs: now()
)
// MaterialClosed — 关闭资料
let closed = try pushMaterialClosedV2(
sessionId: sessionId,
materialId: "mat_001",
activeSecondsDelta: 0,
timestampMs: now()
)
```
#### Export + Ack 流程
```swift
// 1. 导出待上传事件(标记为 Exported
let events = exportPendingEventsV2(limit: 100, timestampMs: now())
// 2. 构造上传请求iOS 补充 readingTargetType/platform/appVersion/timezone
let uploadItems = events.map { event in
ReadingEventUploadItem(
eventId: event.eventId,
clientSessionId: event.clientSessionId,
materialId: event.materialId,
eventType: event.eventType,
position: event.position,
activeSecondsDelta: event.activeSecondsDelta,
clientTimestampMs: event.timestampMs,
sequence: event.sequence,
// iOS 补充字段
readingTargetType: getTargetType(for: event.materialId),
platform: "ios",
appVersion: Bundle.main.appVersion,
clientTimezoneOffsetMinutes: Int(TimeZone.current.secondsFromGMT() / 60)
)
} }
func flush() { // 3. 上传到 API
// POST buffer → /reading/events api.uploadEvents(uploadItems) { result in
// clear buffer switch result {
case .success:
// 成功后 ACK从 buffer 移除)
let ids = events.map(\.eventId)
ackEventsV2(eventIds: ids)
case .failure:
// 失败标记为 Failed下次 export 重试)
let ids = events.map(\.eventId)
markEventsFailedV2(eventIds: ids)
} }
} }
``` ```
#### App 启动恢复
```swift
// App 启动时调用,恢复上次未 ack 的事件
func applicationDidFinishLaunching() {
reloadStaleEventsV2() // 内部 Pending/Exported→Pending可重新 export
}
```
#### 搜索和笔记锚点
```swift
// 1. 解析 Markdown
let blocks = try parseMarkdown(content: mdContent)
// 2. 搜索
let results = searchMarkdownBlocks(blocks: blocks, query: "keyword")
// 3. 从搜索结果创建笔记锚点
if let firstResult = results.first {
let anchor = createNoteAnchorFromSearch(
materialId: "mat_001",
result: firstResult
)
// anchor 包含 materialId/blockId/pageNumber/chapterId/snippet
}
// 4. 从阅读位置创建锚点
let pos = ReadingPosition.pdf(pageNumber: 3, pageProgress: 0.5, overallProgress: 0.1)
let anchor = createNoteAnchor(materialId: "mat_001", position: pos)
// → NoteAnchor.pdfPage(materialId: "mat_001", pageNumber: 3, positionSnapshot: pos)
// 5. 从锚点恢复阅读位置
if let restored = restorePositionFromAnchor(anchor: anchor) {
// navigate to restored position
}
// PDF 搜索
let pages = searchPdfPages(
pageNumbers: [1, 2, 3],
pageTexts: ["page 1 text", "page 2 text", "page 3 text"],
query: "keyword"
) // → [SearchResult] with page_number
// EPUB 搜索
let chapters = searchEpubChaptersFfi(
chapterIds: ["intro", "ch1"],
chapterTexts: ["Welcome", "Content with keyword"],
query: "keyword"
) // → [SearchResult] with chapter_id
```
#### PDF/EPUB 元数据
```swift
// PDF
let pdfMeta = try readPdfMetadataFfi(filePath: "/path/to/doc.pdf")
print("Pages: \(pdfMeta.pageCount), Title: \(pdfMeta.title ?? "N/A")")
// EPUB
let epubMeta = try readEpubMetadataFfi(filePath: "/path/to/book.epub")
print("Chapters: \(epubMeta.chapterCount)")
let chapters = try readEpubChaptersFfi(filePath: "/path/to/book.epub")
for ch in chapters {
print(" \(ch.title) — \(ch.path)")
}
// Office
let config = try getOfficePreviewConfigFfi(
materialType: .word,
fileSize: 1024
)
print("Strategy: \(config.strategy)") // PlatformPreview
```
## 6. 使用真实 XCFramework 的项目示例 ## 6. 使用真实 XCFramework 的项目示例
`bindings/ios/demo/` 目录,包含最小 SwiftUI App 示例: `bindings/ios/demo/` 目录,包含最小 SwiftUI App 示例:

View File

@ -0,0 +1,160 @@
# ReadingEvent V2 协议
## 概述
V2 阅读事件协议定义了一套完整的学习行为采集框架:**阅读会话 → 事件生成 → buffer 暂存 → export 导出 → ack 确认**。
与 V1 的区别:
- V1无 session无 ack无 id 追踪
- V2session 管理 + 全局事件 buffer + export/ack 确认 + crash recovery
## 核心概念
### ReadingSessionV2
一次阅读会话。iOS App 一次打开资料即创建一个 session。
```rust
pub struct ReadingSessionV2 {
pub client_session_id: String, // UUID v4会话标识
pub material: ReadingMaterialRef, // 被阅读的资料
pub started_at_ms: i64, // 会话开始时间
pub last_event_at_ms: i64, // 最后一次事件时间
pub next_sequence: u64, // 下一个事件序号(从 1 开始)
pub total_active_seconds: u32, // 累计活跃阅读秒数
pub last_position: Option<ReadingPosition>, // 最后位置
pub status: ReadingSessionStatus, // Active/Paused/Closed
}
```
#### 生命周期
```
start → Active → (pause → Paused → resume → Active)* → close → Closed
```
- **Active**:可推送任意类型事件
- **Paused**:仅可推送 delta=0 事件PositionChanged/MarkedAsRead
- **Closed**:不可推送事件,不可重新打开
### ReadingMaterialRef
```rust
pub struct ReadingMaterialRef {
pub material_id: String,
}
```
Rust 不存储 `readingTargetType`(如 knowledge/material/course/note由 iOS 上传时补充。
### ReadingEventV2
```rust
pub struct ReadingEventV2 {
pub event_id: String, // UUID v4全局唯一事件 ID
pub client_session_id: String, // 关联的会话 ID
pub material_id: String, // 资料 ID
pub event_type: ReadingEventTypeV2, // 事件类型
pub position: Option<ReadingPosition>, // 阅读位置camelCase JSON
pub active_seconds_delta: u32, // 距上次事件的活跃秒数
pub timestamp_ms: i64, // 客户端时间戳
pub sequence: u64, // session 内递增序号1-based
}
```
#### eventType 取值
| Rust | API JSON |
|------|----------|
| `MaterialOpened` | `material_opened` |
| `MaterialClosed` | `material_closed` |
| `PositionChanged` | `position_changed` |
| `Heartbeat` | `heartbeat` |
| `MarkedAsRead` | `marked_as_read` |
#### activeSecondsDelta 规则
| 事件 | delta |
|------|-------|
| MaterialOpened | 0 |
| PositionChanged | 0 |
| MarkedAsRead | 0 |
| Heartbeat | ActiveTimeTracker.tick() 返回值 |
| MaterialClosed | ActiveTimeTracker.close() 返回值 |
### ActiveTimeTracker
iOS 控制 tick 节奏Rust 计算 delta
```
start(ts) → tick(ts+15s) → tick(ts+30s) → close(ts+43s)
delta=15 delta=15 delta=13
```
- 暂停时不累计时间
- 时间倒退返回 0
- 余数毫秒累积到下次 tick
### EventBuffer 状态机
```
push
┌─ Pending ──┐
│ │
export() reload_stale()
│ │
▼ │
Exported ───────┘
┌─────┴─────┐
│ │
ack() mark_failed()
│ │
▼ ▼
(removed) Failed
export() ──→ 重试
```
- **Pending**:新事件,等待导出
- **Exported**:已导出,等待 ack。crash 后 `reload_stale_events()` 恢复为 Pending
- **Failed**:上传失败,下次 export 重试
- **Ack 后删除**:从 buffer 移除
### 溢出驱逐
Buffer 容量1000 条。满时驱逐顺序:
1. Failed最早失败的
2. Exported最早导出的
3. 最早 Pending
## API 上传字段映射
| Rust ReadingEventV2 | API ReadingEventUploadItem | 来源 |
|---------------------|---------------------------|------|
| event_id | eventId | Rust UUID |
| client_session_id | clientSessionId | Rust UUID |
| material_id | materialId | Rust 保存 |
| event_type | eventType | Rust→snake_case |
| position | position | RustcamelCase JSONclamped |
| active_seconds_delta | activeSecondsDelta | ActiveTimeTracker |
| timestamp_ms | clientTimestampMs | Rust |
| sequence | sequence | session 内递增 |
| — | readingTargetType | **iOS 补充** |
| — | platform | **iOS 补充** |
| — | appVersion | **iOS 补充** |
| — | clientTimezoneOffsetMinutes | **iOS 补充** |
## iOS 上传流程
```
1. Rust export_pending_events_v2(limit, timestamp) → Vec<ReadingEventV2>
2. iOS 遍历事件,补充 readingTargetType/platform/appVersion/timezone
3. iOS 构造 API 请求体
4. POST /reading/events
5. 成功 → Rust ack_events_v2(eventIds)
6. 失败 → Rust mark_events_failed_v2(eventIds)
7. App 启动 → Rust reload_stale_events_v2()
```

View File

@ -28,12 +28,45 @@
| Excel | `.xls` `.xlsx` | PlatformPreview | 不解析 | QuickLook / 系统预览 | | Excel | `.xls` `.xlsx` | PlatformPreview | 不解析 | QuickLook / 系统预览 |
| PPT | `.ppt` `.pptx` | ExternalOpen | 不解析 | 外部 App 打开 | | PPT | `.ppt` `.pptx` | ExternalOpen | 不解析 | 外部 App 打开 |
### 后续支持 ### 第二版(已支持)
| 格式 | 扩展名 | PreviewMode | Rust 职责 | App 职责 |
|------|--------|-------------|----------|---------|
| EPUB | `.epub` | NativeReader | 解析 OPF/spine/NCX TOC读取章节列表和元数据 | WebView 渲染章节 HTML |
| PDF | `.pdf` | PlatformPreview | 读取元数据(页数/标题/作者),通过 pdfium feature 支持文本提取和搜索 | 系统预览iOS PDFKit+ 搜索接口 |
#### EPUB 能力
| 特性 | 状态 |
|------|------|
| 元数据title/author | ✅ read_epub_metadata |
| 章节列表spine + NCX TOC | ✅ read_epub_chapters |
| EPUB2 NCX 解析 | ✅ |
| EPUB3 NAV 解析 | ⚠️ 降级为 "Chapter N"#100 |
#### PDF 能力
| 特性 | 状态 |
|------|------|
| 元数据page_count/title/author | ✅ read_pdf_metadata |
| 页数检测(/Type /Page + /Count 回退) | ✅ |
| 文本提取 | ⚠️ stub需 pdfium feature |
| 搜索 | ✅ search_pdf_text需预提取文本 |
#### Office 能力
| 类型 | PreviewMode | Strategy | 搜索 |
|------|-------------|----------|------|
| Word (.doc/.docx) | PlatformPreview | QLPreviewController | 否(可 Server 转 PDF 后搜索)|
| Excel (.xls/.xlsx) | PlatformPreview | QLPreviewController | 否 |
| PowerPoint (.ppt/.pptx) | ExternalOpen | 外部 App 打开 | 否 |
### 第三版(计划)
| 格式 | 目标 PreviewMode | 备注 | | 格式 | 目标 PreviewMode | 备注 |
|------|-----------------|------| |------|-----------------|------|
| EPUB | NativeReader | Rust 解析 OPF/spine/navApp WebView 渲染章节 | | PDF 增强 | NativeReader增强 | pdfium 文本提取 + 页级搜索 + 阅读位置同步 |
| PDF | NativeReader增强 | 评估 PDFium支持文本提取和搜索 | | EPUB3 NAV | NativeReader | 解析 `<nav epub:type="toc">` 获取真实章节标题 |
### 明确不做 ### 明确不做

Binary file not shown.

BIN
fixtures/epub/simple.epub Normal file

Binary file not shown.

BIN
fixtures/invalid_file.bin Normal file

Binary file not shown.

View File

@ -0,0 +1,45 @@
# Document Title (h1)
Introduction paragraph with **bold** and *italic* text. This is the first paragraph of the document.
## Section One (h2)
This section contains a code block with language info.
```rust
fn main() {
println!("Hello, world!");
}
```
### Subsection (h3)
- Unordered item 1
- Unordered item 2
- Unordered item 3
1. Ordered item 1
2. Ordered item 2
3. Ordered item 3
## Section Two — Tables & Quotes
| Name | Age | City |
|------|-----|------|
| Alice | 30 | NYC |
| Bob | 25 | LA |
| Carol | 28 | SF |
> This is a blockquote with some wisdom: "The only way to do great work is to love what you do."
## Section Three — Images & Rules
![Rust Logo](https://www.rust-lang.org/logos/rust-logo-512x512.png)
---
### Final Section
Final paragraph with `inline code` and a [link](https://example.com).
---

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
%PDF-1.4
%€€€€
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>
endobj
xref
0 4
0000000000 65535 f
0000000015 00000 n
0000000064 00000 n
0000000121 00000 n
trailer
<< /Size 4 /Root 1 0 R >>
startxref
192
%%EOF

26
fixtures/pdf/text_pdf.pdf Normal file
View File

@ -0,0 +1,26 @@
%PDF-1.4
%€€€€
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R 4 0 R] /Count 2 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>
endobj
4 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>
endobj
xref
0 5
0000000000 65535 f
0000000015 00000 n
0000000064 00000 n
0000000127 00000 n
0000000198 00000 n
trailer
<< /Size 5 /Root 1 0 R >>
startxref
269
%%EOF

View File

@ -0,0 +1,500 @@
Line 1: This is a long text line number 1 with searchable content spread across the file.
Line 2: This is a long text line number 2 with searchable content spread across the file.
Line 3: This is a long text line number 3 with searchable content spread across the file.
Line 4: This is a long text line number 4 with searchable content spread across the file.
Line 5: This is a long text line number 5 with searchable content spread across the file.
Line 6: This is a long text line number 6 with searchable content spread across the file.
Line 7: This is a long text line number 7 with searchable content spread across the file.
Line 8: This is a long text line number 8 with searchable content spread across the file.
Line 9: This is a long text line number 9 with searchable content spread across the file.
Line 10: This is a long text line number 10 with searchable content spread across the file.
Line 11: This is a long text line number 11 with searchable content spread across the file.
Line 12: This is a long text line number 12 with searchable content spread across the file.
Line 13: This is a long text line number 13 with searchable content spread across the file.
Line 14: This is a long text line number 14 with searchable content spread across the file.
Line 15: This is a long text line number 15 with searchable content spread across the file.
Line 16: This is a long text line number 16 with searchable content spread across the file.
Line 17: This is a long text line number 17 with searchable content spread across the file.
Line 18: This is a long text line number 18 with searchable content spread across the file.
Line 19: This is a long text line number 19 with searchable content spread across the file.
Line 20: This is a long text line number 20 with searchable content spread across the file.
Line 21: This is a long text line number 21 with searchable content spread across the file.
Line 22: This is a long text line number 22 with searchable content spread across the file.
Line 23: This is a long text line number 23 with searchable content spread across the file.
Line 24: This is a long text line number 24 with searchable content spread across the file.
Line 25: This is a long text line number 25 with searchable content spread across the file.
Line 26: This is a long text line number 26 with searchable content spread across the file.
Line 27: This is a long text line number 27 with searchable content spread across the file.
Line 28: This is a long text line number 28 with searchable content spread across the file.
Line 29: This is a long text line number 29 with searchable content spread across the file.
Line 30: This is a long text line number 30 with searchable content spread across the file.
Line 31: This is a long text line number 31 with searchable content spread across the file.
Line 32: This is a long text line number 32 with searchable content spread across the file.
Line 33: This is a long text line number 33 with searchable content spread across the file.
Line 34: This is a long text line number 34 with searchable content spread across the file.
Line 35: This is a long text line number 35 with searchable content spread across the file.
Line 36: This is a long text line number 36 with searchable content spread across the file.
Line 37: This is a long text line number 37 with searchable content spread across the file.
Line 38: This is a long text line number 38 with searchable content spread across the file.
Line 39: This is a long text line number 39 with searchable content spread across the file.
Line 40: This is a long text line number 40 with searchable content spread across the file.
Line 41: This is a long text line number 41 with searchable content spread across the file.
Line 42: This is a long text line number 42 with searchable content spread across the file.
Line 43: This is a long text line number 43 with searchable content spread across the file.
Line 44: This is a long text line number 44 with searchable content spread across the file.
Line 45: This is a long text line number 45 with searchable content spread across the file.
Line 46: This is a long text line number 46 with searchable content spread across the file.
Line 47: This is a long text line number 47 with searchable content spread across the file.
Line 48: This is a long text line number 48 with searchable content spread across the file.
Line 49: This is a long text line number 49 with searchable content spread across the file.
Line 50: This is a long text line number 50 with searchable content spread across the file.
Line 51: This is a long text line number 51 with searchable content spread across the file.
Line 52: This is a long text line number 52 with searchable content spread across the file.
Line 53: This is a long text line number 53 with searchable content spread across the file.
Line 54: This is a long text line number 54 with searchable content spread across the file.
Line 55: This is a long text line number 55 with searchable content spread across the file.
Line 56: This is a long text line number 56 with searchable content spread across the file.
Line 57: This is a long text line number 57 with searchable content spread across the file.
Line 58: This is a long text line number 58 with searchable content spread across the file.
Line 59: This is a long text line number 59 with searchable content spread across the file.
Line 60: This is a long text line number 60 with searchable content spread across the file.
Line 61: This is a long text line number 61 with searchable content spread across the file.
Line 62: This is a long text line number 62 with searchable content spread across the file.
Line 63: This is a long text line number 63 with searchable content spread across the file.
Line 64: This is a long text line number 64 with searchable content spread across the file.
Line 65: This is a long text line number 65 with searchable content spread across the file.
Line 66: This is a long text line number 66 with searchable content spread across the file.
Line 67: This is a long text line number 67 with searchable content spread across the file.
Line 68: This is a long text line number 68 with searchable content spread across the file.
Line 69: This is a long text line number 69 with searchable content spread across the file.
Line 70: This is a long text line number 70 with searchable content spread across the file.
Line 71: This is a long text line number 71 with searchable content spread across the file.
Line 72: This is a long text line number 72 with searchable content spread across the file.
Line 73: This is a long text line number 73 with searchable content spread across the file.
Line 74: This is a long text line number 74 with searchable content spread across the file.
Line 75: This is a long text line number 75 with searchable content spread across the file.
Line 76: This is a long text line number 76 with searchable content spread across the file.
Line 77: This is a long text line number 77 with searchable content spread across the file.
Line 78: This is a long text line number 78 with searchable content spread across the file.
Line 79: This is a long text line number 79 with searchable content spread across the file.
Line 80: This is a long text line number 80 with searchable content spread across the file.
Line 81: This is a long text line number 81 with searchable content spread across the file.
Line 82: This is a long text line number 82 with searchable content spread across the file.
Line 83: This is a long text line number 83 with searchable content spread across the file.
Line 84: This is a long text line number 84 with searchable content spread across the file.
Line 85: This is a long text line number 85 with searchable content spread across the file.
Line 86: This is a long text line number 86 with searchable content spread across the file.
Line 87: This is a long text line number 87 with searchable content spread across the file.
Line 88: This is a long text line number 88 with searchable content spread across the file.
Line 89: This is a long text line number 89 with searchable content spread across the file.
Line 90: This is a long text line number 90 with searchable content spread across the file.
Line 91: This is a long text line number 91 with searchable content spread across the file.
Line 92: This is a long text line number 92 with searchable content spread across the file.
Line 93: This is a long text line number 93 with searchable content spread across the file.
Line 94: This is a long text line number 94 with searchable content spread across the file.
Line 95: This is a long text line number 95 with searchable content spread across the file.
Line 96: This is a long text line number 96 with searchable content spread across the file.
Line 97: This is a long text line number 97 with searchable content spread across the file.
Line 98: This is a long text line number 98 with searchable content spread across the file.
Line 99: This is a long text line number 99 with searchable content spread across the file.
Line 100: This is a long text line number 100 with searchable content spread across the file.
Line 101: This is a long text line number 101 with searchable content spread across the file.
Line 102: This is a long text line number 102 with searchable content spread across the file.
Line 103: This is a long text line number 103 with searchable content spread across the file.
Line 104: This is a long text line number 104 with searchable content spread across the file.
Line 105: This is a long text line number 105 with searchable content spread across the file.
Line 106: This is a long text line number 106 with searchable content spread across the file.
Line 107: This is a long text line number 107 with searchable content spread across the file.
Line 108: This is a long text line number 108 with searchable content spread across the file.
Line 109: This is a long text line number 109 with searchable content spread across the file.
Line 110: This is a long text line number 110 with searchable content spread across the file.
Line 111: This is a long text line number 111 with searchable content spread across the file.
Line 112: This is a long text line number 112 with searchable content spread across the file.
Line 113: This is a long text line number 113 with searchable content spread across the file.
Line 114: This is a long text line number 114 with searchable content spread across the file.
Line 115: This is a long text line number 115 with searchable content spread across the file.
Line 116: This is a long text line number 116 with searchable content spread across the file.
Line 117: This is a long text line number 117 with searchable content spread across the file.
Line 118: This is a long text line number 118 with searchable content spread across the file.
Line 119: This is a long text line number 119 with searchable content spread across the file.
Line 120: This is a long text line number 120 with searchable content spread across the file.
Line 121: This is a long text line number 121 with searchable content spread across the file.
Line 122: This is a long text line number 122 with searchable content spread across the file.
Line 123: This is a long text line number 123 with searchable content spread across the file.
Line 124: This is a long text line number 124 with searchable content spread across the file.
Line 125: This is a long text line number 125 with searchable content spread across the file.
Line 126: This is a long text line number 126 with searchable content spread across the file.
Line 127: This is a long text line number 127 with searchable content spread across the file.
Line 128: This is a long text line number 128 with searchable content spread across the file.
Line 129: This is a long text line number 129 with searchable content spread across the file.
Line 130: This is a long text line number 130 with searchable content spread across the file.
Line 131: This is a long text line number 131 with searchable content spread across the file.
Line 132: This is a long text line number 132 with searchable content spread across the file.
Line 133: This is a long text line number 133 with searchable content spread across the file.
Line 134: This is a long text line number 134 with searchable content spread across the file.
Line 135: This is a long text line number 135 with searchable content spread across the file.
Line 136: This is a long text line number 136 with searchable content spread across the file.
Line 137: This is a long text line number 137 with searchable content spread across the file.
Line 138: This is a long text line number 138 with searchable content spread across the file.
Line 139: This is a long text line number 139 with searchable content spread across the file.
Line 140: This is a long text line number 140 with searchable content spread across the file.
Line 141: This is a long text line number 141 with searchable content spread across the file.
Line 142: This is a long text line number 142 with searchable content spread across the file.
Line 143: This is a long text line number 143 with searchable content spread across the file.
Line 144: This is a long text line number 144 with searchable content spread across the file.
Line 145: This is a long text line number 145 with searchable content spread across the file.
Line 146: This is a long text line number 146 with searchable content spread across the file.
Line 147: This is a long text line number 147 with searchable content spread across the file.
Line 148: This is a long text line number 148 with searchable content spread across the file.
Line 149: This is a long text line number 149 with searchable content spread across the file.
Line 150: This is a long text line number 150 with searchable content spread across the file.
Line 151: This is a long text line number 151 with searchable content spread across the file.
Line 152: This is a long text line number 152 with searchable content spread across the file.
Line 153: This is a long text line number 153 with searchable content spread across the file.
Line 154: This is a long text line number 154 with searchable content spread across the file.
Line 155: This is a long text line number 155 with searchable content spread across the file.
Line 156: This is a long text line number 156 with searchable content spread across the file.
Line 157: This is a long text line number 157 with searchable content spread across the file.
Line 158: This is a long text line number 158 with searchable content spread across the file.
Line 159: This is a long text line number 159 with searchable content spread across the file.
Line 160: This is a long text line number 160 with searchable content spread across the file.
Line 161: This is a long text line number 161 with searchable content spread across the file.
Line 162: This is a long text line number 162 with searchable content spread across the file.
Line 163: This is a long text line number 163 with searchable content spread across the file.
Line 164: This is a long text line number 164 with searchable content spread across the file.
Line 165: This is a long text line number 165 with searchable content spread across the file.
Line 166: This is a long text line number 166 with searchable content spread across the file.
Line 167: This is a long text line number 167 with searchable content spread across the file.
Line 168: This is a long text line number 168 with searchable content spread across the file.
Line 169: This is a long text line number 169 with searchable content spread across the file.
Line 170: This is a long text line number 170 with searchable content spread across the file.
Line 171: This is a long text line number 171 with searchable content spread across the file.
Line 172: This is a long text line number 172 with searchable content spread across the file.
Line 173: This is a long text line number 173 with searchable content spread across the file.
Line 174: This is a long text line number 174 with searchable content spread across the file.
Line 175: This is a long text line number 175 with searchable content spread across the file.
Line 176: This is a long text line number 176 with searchable content spread across the file.
Line 177: This is a long text line number 177 with searchable content spread across the file.
Line 178: This is a long text line number 178 with searchable content spread across the file.
Line 179: This is a long text line number 179 with searchable content spread across the file.
Line 180: This is a long text line number 180 with searchable content spread across the file.
Line 181: This is a long text line number 181 with searchable content spread across the file.
Line 182: This is a long text line number 182 with searchable content spread across the file.
Line 183: This is a long text line number 183 with searchable content spread across the file.
Line 184: This is a long text line number 184 with searchable content spread across the file.
Line 185: This is a long text line number 185 with searchable content spread across the file.
Line 186: This is a long text line number 186 with searchable content spread across the file.
Line 187: This is a long text line number 187 with searchable content spread across the file.
Line 188: This is a long text line number 188 with searchable content spread across the file.
Line 189: This is a long text line number 189 with searchable content spread across the file.
Line 190: This is a long text line number 190 with searchable content spread across the file.
Line 191: This is a long text line number 191 with searchable content spread across the file.
Line 192: This is a long text line number 192 with searchable content spread across the file.
Line 193: This is a long text line number 193 with searchable content spread across the file.
Line 194: This is a long text line number 194 with searchable content spread across the file.
Line 195: This is a long text line number 195 with searchable content spread across the file.
Line 196: This is a long text line number 196 with searchable content spread across the file.
Line 197: This is a long text line number 197 with searchable content spread across the file.
Line 198: This is a long text line number 198 with searchable content spread across the file.
Line 199: This is a long text line number 199 with searchable content spread across the file.
Line 200: This is a long text line number 200 with searchable content spread across the file.
Line 201: This is a long text line number 201 with searchable content spread across the file.
Line 202: This is a long text line number 202 with searchable content spread across the file.
Line 203: This is a long text line number 203 with searchable content spread across the file.
Line 204: This is a long text line number 204 with searchable content spread across the file.
Line 205: This is a long text line number 205 with searchable content spread across the file.
Line 206: This is a long text line number 206 with searchable content spread across the file.
Line 207: This is a long text line number 207 with searchable content spread across the file.
Line 208: This is a long text line number 208 with searchable content spread across the file.
Line 209: This is a long text line number 209 with searchable content spread across the file.
Line 210: This is a long text line number 210 with searchable content spread across the file.
Line 211: This is a long text line number 211 with searchable content spread across the file.
Line 212: This is a long text line number 212 with searchable content spread across the file.
Line 213: This is a long text line number 213 with searchable content spread across the file.
Line 214: This is a long text line number 214 with searchable content spread across the file.
Line 215: This is a long text line number 215 with searchable content spread across the file.
Line 216: This is a long text line number 216 with searchable content spread across the file.
Line 217: This is a long text line number 217 with searchable content spread across the file.
Line 218: This is a long text line number 218 with searchable content spread across the file.
Line 219: This is a long text line number 219 with searchable content spread across the file.
Line 220: This is a long text line number 220 with searchable content spread across the file.
Line 221: This is a long text line number 221 with searchable content spread across the file.
Line 222: This is a long text line number 222 with searchable content spread across the file.
Line 223: This is a long text line number 223 with searchable content spread across the file.
Line 224: This is a long text line number 224 with searchable content spread across the file.
Line 225: This is a long text line number 225 with searchable content spread across the file.
Line 226: This is a long text line number 226 with searchable content spread across the file.
Line 227: This is a long text line number 227 with searchable content spread across the file.
Line 228: This is a long text line number 228 with searchable content spread across the file.
Line 229: This is a long text line number 229 with searchable content spread across the file.
Line 230: This is a long text line number 230 with searchable content spread across the file.
Line 231: This is a long text line number 231 with searchable content spread across the file.
Line 232: This is a long text line number 232 with searchable content spread across the file.
Line 233: This is a long text line number 233 with searchable content spread across the file.
Line 234: This is a long text line number 234 with searchable content spread across the file.
Line 235: This is a long text line number 235 with searchable content spread across the file.
Line 236: This is a long text line number 236 with searchable content spread across the file.
Line 237: This is a long text line number 237 with searchable content spread across the file.
Line 238: This is a long text line number 238 with searchable content spread across the file.
Line 239: This is a long text line number 239 with searchable content spread across the file.
Line 240: This is a long text line number 240 with searchable content spread across the file.
Line 241: This is a long text line number 241 with searchable content spread across the file.
Line 242: This is a long text line number 242 with searchable content spread across the file.
Line 243: This is a long text line number 243 with searchable content spread across the file.
Line 244: This is a long text line number 244 with searchable content spread across the file.
Line 245: This is a long text line number 245 with searchable content spread across the file.
Line 246: This is a long text line number 246 with searchable content spread across the file.
Line 247: This is a long text line number 247 with searchable content spread across the file.
Line 248: This is a long text line number 248 with searchable content spread across the file.
Line 249: This is a long text line number 249 with searchable content spread across the file.
Line 250: This is a long text line number 250 with searchable content spread across the file.
Line 251: This is a long text line number 251 with searchable content spread across the file.
Line 252: This is a long text line number 252 with searchable content spread across the file.
Line 253: This is a long text line number 253 with searchable content spread across the file.
Line 254: This is a long text line number 254 with searchable content spread across the file.
Line 255: This is a long text line number 255 with searchable content spread across the file.
Line 256: This is a long text line number 256 with searchable content spread across the file.
Line 257: This is a long text line number 257 with searchable content spread across the file.
Line 258: This is a long text line number 258 with searchable content spread across the file.
Line 259: This is a long text line number 259 with searchable content spread across the file.
Line 260: This is a long text line number 260 with searchable content spread across the file.
Line 261: This is a long text line number 261 with searchable content spread across the file.
Line 262: This is a long text line number 262 with searchable content spread across the file.
Line 263: This is a long text line number 263 with searchable content spread across the file.
Line 264: This is a long text line number 264 with searchable content spread across the file.
Line 265: This is a long text line number 265 with searchable content spread across the file.
Line 266: This is a long text line number 266 with searchable content spread across the file.
Line 267: This is a long text line number 267 with searchable content spread across the file.
Line 268: This is a long text line number 268 with searchable content spread across the file.
Line 269: This is a long text line number 269 with searchable content spread across the file.
Line 270: This is a long text line number 270 with searchable content spread across the file.
Line 271: This is a long text line number 271 with searchable content spread across the file.
Line 272: This is a long text line number 272 with searchable content spread across the file.
Line 273: This is a long text line number 273 with searchable content spread across the file.
Line 274: This is a long text line number 274 with searchable content spread across the file.
Line 275: This is a long text line number 275 with searchable content spread across the file.
Line 276: This is a long text line number 276 with searchable content spread across the file.
Line 277: This is a long text line number 277 with searchable content spread across the file.
Line 278: This is a long text line number 278 with searchable content spread across the file.
Line 279: This is a long text line number 279 with searchable content spread across the file.
Line 280: This is a long text line number 280 with searchable content spread across the file.
Line 281: This is a long text line number 281 with searchable content spread across the file.
Line 282: This is a long text line number 282 with searchable content spread across the file.
Line 283: This is a long text line number 283 with searchable content spread across the file.
Line 284: This is a long text line number 284 with searchable content spread across the file.
Line 285: This is a long text line number 285 with searchable content spread across the file.
Line 286: This is a long text line number 286 with searchable content spread across the file.
Line 287: This is a long text line number 287 with searchable content spread across the file.
Line 288: This is a long text line number 288 with searchable content spread across the file.
Line 289: This is a long text line number 289 with searchable content spread across the file.
Line 290: This is a long text line number 290 with searchable content spread across the file.
Line 291: This is a long text line number 291 with searchable content spread across the file.
Line 292: This is a long text line number 292 with searchable content spread across the file.
Line 293: This is a long text line number 293 with searchable content spread across the file.
Line 294: This is a long text line number 294 with searchable content spread across the file.
Line 295: This is a long text line number 295 with searchable content spread across the file.
Line 296: This is a long text line number 296 with searchable content spread across the file.
Line 297: This is a long text line number 297 with searchable content spread across the file.
Line 298: This is a long text line number 298 with searchable content spread across the file.
Line 299: This is a long text line number 299 with searchable content spread across the file.
Line 300: This is a long text line number 300 with searchable content spread across the file.
Line 301: This is a long text line number 301 with searchable content spread across the file.
Line 302: This is a long text line number 302 with searchable content spread across the file.
Line 303: This is a long text line number 303 with searchable content spread across the file.
Line 304: This is a long text line number 304 with searchable content spread across the file.
Line 305: This is a long text line number 305 with searchable content spread across the file.
Line 306: This is a long text line number 306 with searchable content spread across the file.
Line 307: This is a long text line number 307 with searchable content spread across the file.
Line 308: This is a long text line number 308 with searchable content spread across the file.
Line 309: This is a long text line number 309 with searchable content spread across the file.
Line 310: This is a long text line number 310 with searchable content spread across the file.
Line 311: This is a long text line number 311 with searchable content spread across the file.
Line 312: This is a long text line number 312 with searchable content spread across the file.
Line 313: This is a long text line number 313 with searchable content spread across the file.
Line 314: This is a long text line number 314 with searchable content spread across the file.
Line 315: This is a long text line number 315 with searchable content spread across the file.
Line 316: This is a long text line number 316 with searchable content spread across the file.
Line 317: This is a long text line number 317 with searchable content spread across the file.
Line 318: This is a long text line number 318 with searchable content spread across the file.
Line 319: This is a long text line number 319 with searchable content spread across the file.
Line 320: This is a long text line number 320 with searchable content spread across the file.
Line 321: This is a long text line number 321 with searchable content spread across the file.
Line 322: This is a long text line number 322 with searchable content spread across the file.
Line 323: This is a long text line number 323 with searchable content spread across the file.
Line 324: This is a long text line number 324 with searchable content spread across the file.
Line 325: This is a long text line number 325 with searchable content spread across the file.
Line 326: This is a long text line number 326 with searchable content spread across the file.
Line 327: This is a long text line number 327 with searchable content spread across the file.
Line 328: This is a long text line number 328 with searchable content spread across the file.
Line 329: This is a long text line number 329 with searchable content spread across the file.
Line 330: This is a long text line number 330 with searchable content spread across the file.
Line 331: This is a long text line number 331 with searchable content spread across the file.
Line 332: This is a long text line number 332 with searchable content spread across the file.
Line 333: This is a long text line number 333 with searchable content spread across the file.
Line 334: This is a long text line number 334 with searchable content spread across the file.
Line 335: This is a long text line number 335 with searchable content spread across the file.
Line 336: This is a long text line number 336 with searchable content spread across the file.
Line 337: This is a long text line number 337 with searchable content spread across the file.
Line 338: This is a long text line number 338 with searchable content spread across the file.
Line 339: This is a long text line number 339 with searchable content spread across the file.
Line 340: This is a long text line number 340 with searchable content spread across the file.
Line 341: This is a long text line number 341 with searchable content spread across the file.
Line 342: This is a long text line number 342 with searchable content spread across the file.
Line 343: This is a long text line number 343 with searchable content spread across the file.
Line 344: This is a long text line number 344 with searchable content spread across the file.
Line 345: This is a long text line number 345 with searchable content spread across the file.
Line 346: This is a long text line number 346 with searchable content spread across the file.
Line 347: This is a long text line number 347 with searchable content spread across the file.
Line 348: This is a long text line number 348 with searchable content spread across the file.
Line 349: This is a long text line number 349 with searchable content spread across the file.
Line 350: This is a long text line number 350 with searchable content spread across the file.
Line 351: This is a long text line number 351 with searchable content spread across the file.
Line 352: This is a long text line number 352 with searchable content spread across the file.
Line 353: This is a long text line number 353 with searchable content spread across the file.
Line 354: This is a long text line number 354 with searchable content spread across the file.
Line 355: This is a long text line number 355 with searchable content spread across the file.
Line 356: This is a long text line number 356 with searchable content spread across the file.
Line 357: This is a long text line number 357 with searchable content spread across the file.
Line 358: This is a long text line number 358 with searchable content spread across the file.
Line 359: This is a long text line number 359 with searchable content spread across the file.
Line 360: This is a long text line number 360 with searchable content spread across the file.
Line 361: This is a long text line number 361 with searchable content spread across the file.
Line 362: This is a long text line number 362 with searchable content spread across the file.
Line 363: This is a long text line number 363 with searchable content spread across the file.
Line 364: This is a long text line number 364 with searchable content spread across the file.
Line 365: This is a long text line number 365 with searchable content spread across the file.
Line 366: This is a long text line number 366 with searchable content spread across the file.
Line 367: This is a long text line number 367 with searchable content spread across the file.
Line 368: This is a long text line number 368 with searchable content spread across the file.
Line 369: This is a long text line number 369 with searchable content spread across the file.
Line 370: This is a long text line number 370 with searchable content spread across the file.
Line 371: This is a long text line number 371 with searchable content spread across the file.
Line 372: This is a long text line number 372 with searchable content spread across the file.
Line 373: This is a long text line number 373 with searchable content spread across the file.
Line 374: This is a long text line number 374 with searchable content spread across the file.
Line 375: This is a long text line number 375 with searchable content spread across the file.
Line 376: This is a long text line number 376 with searchable content spread across the file.
Line 377: This is a long text line number 377 with searchable content spread across the file.
Line 378: This is a long text line number 378 with searchable content spread across the file.
Line 379: This is a long text line number 379 with searchable content spread across the file.
Line 380: This is a long text line number 380 with searchable content spread across the file.
Line 381: This is a long text line number 381 with searchable content spread across the file.
Line 382: This is a long text line number 382 with searchable content spread across the file.
Line 383: This is a long text line number 383 with searchable content spread across the file.
Line 384: This is a long text line number 384 with searchable content spread across the file.
Line 385: This is a long text line number 385 with searchable content spread across the file.
Line 386: This is a long text line number 386 with searchable content spread across the file.
Line 387: This is a long text line number 387 with searchable content spread across the file.
Line 388: This is a long text line number 388 with searchable content spread across the file.
Line 389: This is a long text line number 389 with searchable content spread across the file.
Line 390: This is a long text line number 390 with searchable content spread across the file.
Line 391: This is a long text line number 391 with searchable content spread across the file.
Line 392: This is a long text line number 392 with searchable content spread across the file.
Line 393: This is a long text line number 393 with searchable content spread across the file.
Line 394: This is a long text line number 394 with searchable content spread across the file.
Line 395: This is a long text line number 395 with searchable content spread across the file.
Line 396: This is a long text line number 396 with searchable content spread across the file.
Line 397: This is a long text line number 397 with searchable content spread across the file.
Line 398: This is a long text line number 398 with searchable content spread across the file.
Line 399: This is a long text line number 399 with searchable content spread across the file.
Line 400: This is a long text line number 400 with searchable content spread across the file.
Line 401: This is a long text line number 401 with searchable content spread across the file.
Line 402: This is a long text line number 402 with searchable content spread across the file.
Line 403: This is a long text line number 403 with searchable content spread across the file.
Line 404: This is a long text line number 404 with searchable content spread across the file.
Line 405: This is a long text line number 405 with searchable content spread across the file.
Line 406: This is a long text line number 406 with searchable content spread across the file.
Line 407: This is a long text line number 407 with searchable content spread across the file.
Line 408: This is a long text line number 408 with searchable content spread across the file.
Line 409: This is a long text line number 409 with searchable content spread across the file.
Line 410: This is a long text line number 410 with searchable content spread across the file.
Line 411: This is a long text line number 411 with searchable content spread across the file.
Line 412: This is a long text line number 412 with searchable content spread across the file.
Line 413: This is a long text line number 413 with searchable content spread across the file.
Line 414: This is a long text line number 414 with searchable content spread across the file.
Line 415: This is a long text line number 415 with searchable content spread across the file.
Line 416: This is a long text line number 416 with searchable content spread across the file.
Line 417: This is a long text line number 417 with searchable content spread across the file.
Line 418: This is a long text line number 418 with searchable content spread across the file.
Line 419: This is a long text line number 419 with searchable content spread across the file.
Line 420: This is a long text line number 420 with searchable content spread across the file.
Line 421: This is a long text line number 421 with searchable content spread across the file.
Line 422: This is a long text line number 422 with searchable content spread across the file.
Line 423: This is a long text line number 423 with searchable content spread across the file.
Line 424: This is a long text line number 424 with searchable content spread across the file.
Line 425: This is a long text line number 425 with searchable content spread across the file.
Line 426: This is a long text line number 426 with searchable content spread across the file.
Line 427: This is a long text line number 427 with searchable content spread across the file.
Line 428: This is a long text line number 428 with searchable content spread across the file.
Line 429: This is a long text line number 429 with searchable content spread across the file.
Line 430: This is a long text line number 430 with searchable content spread across the file.
Line 431: This is a long text line number 431 with searchable content spread across the file.
Line 432: This is a long text line number 432 with searchable content spread across the file.
Line 433: This is a long text line number 433 with searchable content spread across the file.
Line 434: This is a long text line number 434 with searchable content spread across the file.
Line 435: This is a long text line number 435 with searchable content spread across the file.
Line 436: This is a long text line number 436 with searchable content spread across the file.
Line 437: This is a long text line number 437 with searchable content spread across the file.
Line 438: This is a long text line number 438 with searchable content spread across the file.
Line 439: This is a long text line number 439 with searchable content spread across the file.
Line 440: This is a long text line number 440 with searchable content spread across the file.
Line 441: This is a long text line number 441 with searchable content spread across the file.
Line 442: This is a long text line number 442 with searchable content spread across the file.
Line 443: This is a long text line number 443 with searchable content spread across the file.
Line 444: This is a long text line number 444 with searchable content spread across the file.
Line 445: This is a long text line number 445 with searchable content spread across the file.
Line 446: This is a long text line number 446 with searchable content spread across the file.
Line 447: This is a long text line number 447 with searchable content spread across the file.
Line 448: This is a long text line number 448 with searchable content spread across the file.
Line 449: This is a long text line number 449 with searchable content spread across the file.
Line 450: This is a long text line number 450 with searchable content spread across the file.
Line 451: This is a long text line number 451 with searchable content spread across the file.
Line 452: This is a long text line number 452 with searchable content spread across the file.
Line 453: This is a long text line number 453 with searchable content spread across the file.
Line 454: This is a long text line number 454 with searchable content spread across the file.
Line 455: This is a long text line number 455 with searchable content spread across the file.
Line 456: This is a long text line number 456 with searchable content spread across the file.
Line 457: This is a long text line number 457 with searchable content spread across the file.
Line 458: This is a long text line number 458 with searchable content spread across the file.
Line 459: This is a long text line number 459 with searchable content spread across the file.
Line 460: This is a long text line number 460 with searchable content spread across the file.
Line 461: This is a long text line number 461 with searchable content spread across the file.
Line 462: This is a long text line number 462 with searchable content spread across the file.
Line 463: This is a long text line number 463 with searchable content spread across the file.
Line 464: This is a long text line number 464 with searchable content spread across the file.
Line 465: This is a long text line number 465 with searchable content spread across the file.
Line 466: This is a long text line number 466 with searchable content spread across the file.
Line 467: This is a long text line number 467 with searchable content spread across the file.
Line 468: This is a long text line number 468 with searchable content spread across the file.
Line 469: This is a long text line number 469 with searchable content spread across the file.
Line 470: This is a long text line number 470 with searchable content spread across the file.
Line 471: This is a long text line number 471 with searchable content spread across the file.
Line 472: This is a long text line number 472 with searchable content spread across the file.
Line 473: This is a long text line number 473 with searchable content spread across the file.
Line 474: This is a long text line number 474 with searchable content spread across the file.
Line 475: This is a long text line number 475 with searchable content spread across the file.
Line 476: This is a long text line number 476 with searchable content spread across the file.
Line 477: This is a long text line number 477 with searchable content spread across the file.
Line 478: This is a long text line number 478 with searchable content spread across the file.
Line 479: This is a long text line number 479 with searchable content spread across the file.
Line 480: This is a long text line number 480 with searchable content spread across the file.
Line 481: This is a long text line number 481 with searchable content spread across the file.
Line 482: This is a long text line number 482 with searchable content spread across the file.
Line 483: This is a long text line number 483 with searchable content spread across the file.
Line 484: This is a long text line number 484 with searchable content spread across the file.
Line 485: This is a long text line number 485 with searchable content spread across the file.
Line 486: This is a long text line number 486 with searchable content spread across the file.
Line 487: This is a long text line number 487 with searchable content spread across the file.
Line 488: This is a long text line number 488 with searchable content spread across the file.
Line 489: This is a long text line number 489 with searchable content spread across the file.
Line 490: This is a long text line number 490 with searchable content spread across the file.
Line 491: This is a long text line number 491 with searchable content spread across the file.
Line 492: This is a long text line number 492 with searchable content spread across the file.
Line 493: This is a long text line number 493 with searchable content spread across the file.
Line 494: This is a long text line number 494 with searchable content spread across the file.
Line 495: This is a long text line number 495 with searchable content spread across the file.
Line 496: This is a long text line number 496 with searchable content spread across the file.
Line 497: This is a long text line number 497 with searchable content spread across the file.
Line 498: This is a long text line number 498 with searchable content spread across the file.
Line 499: This is a long text line number 499 with searchable content spread across the file.
Line 500: This is a long text line number 500 with searchable content spread across the file.