use std::path::{Path, PathBuf};
use std::process::Command;
fn sdk_root() -> PathBuf {
let out = Command::new("xcrun")
.args(["--sdk", "macosx", "--show-sdk-path"])
.output()
.expect("xcrun");
assert!(out.status.success());
PathBuf::from(String::from_utf8(out.stdout).unwrap().trim())
}
fn read(path: &Path) -> String {
let bytes =
std::fs::read(path).unwrap_or_else(|error| panic!("read {}: {error}", path.display()));
String::from_utf8_lossy(&bytes).into_owned()
}
fn read_header(name: &str) -> String {
read(&sdk_root().join(format!(
"System/Library/Frameworks/PDFKit.framework/Headers/{name}.h"
)))
}
fn read_bridge() -> String {
let bridge_dir =
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("swift-bridge/Sources/PDFKitBridge");
let mut paths = std::fs::read_dir(&bridge_dir)
.unwrap()
.filter_map(Result::ok)
.map(|entry| entry.path())
.filter(|path| path.extension().is_some_and(|ext| ext == "swift"))
.collect::<Vec<_>>();
paths.sort();
paths
.iter()
.map(|path| read(path))
.collect::<Vec<_>>()
.join("\n")
}
fn read_src(path: &str) -> String {
read(
&PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("src")
.join(path),
)
}
fn assert_contains_all(haystack: &str, needles: &[&str]) {
for needle in needles {
assert!(haystack.contains(needle), "missing `{needle}`");
}
}
#[test]
fn document_page_selection_outline_annotation_surface_is_present() {
let document_header = read_header("PDFDocument");
assert_contains_all(
&document_header,
&[
"- (instancetype)init NS_DESIGNATED_INITIALIZER;",
"- (void)insertPage:(PDFPage *)page atIndex:(NSUInteger)index;",
"- (nullable PDFSelection *)selectionFromPage:(PDFPage *)startPage atPoint:(PDFPoint)startPoint toPage:(PDFPage *)endPage atPoint:(PDFPoint)endPoint;",
"@property (nonatomic, strong, nullable) PDFOutline *outlineRoot",
"@property (nonatomic, weak, nullable) id<PDFDocumentDelegate> delegate;",
],
);
let page_header = read_header("PDFPage");
assert_contains_all(
&page_header,
&[
"- (nullable PDFAnnotation *)annotationAtPoint:(PDFPoint)point;",
"- (nullable PDFSelection *)selectionForRect:(PDFRect)rect;",
"- (nullable PDFSelection *)selectionForWordAtPoint:(PDFPoint)point;",
"- (nullable PDFSelection *)selectionForLineAtPoint:(PDFPoint)point;",
"- (nullable PDFSelection *)selectionFromPoint:(PDFPoint)startPoint toPoint:(PDFPoint)endPoint;",
"@class PDFDocument, PDFAnnotation, PDFSelection, PDFAccessibilityNode;",
],
);
let selection_header = read_header("PDFSelection");
assert_contains_all(
&selection_header,
&[
"- (NSUInteger)numberOfTextRangesOnPage:(PDFPage *)page",
"- (NSRange)rangeAtIndex:(NSUInteger)index onPage:(PDFPage *)page",
"- (NSArray<PDFSelection*>*)selectionsByLine",
"- (void)addSelection:(PDFSelection *)selection;",
],
);
let outline_header = read_header("PDFOutline");
assert_contains_all(
&outline_header,
&[
"- (instancetype)init NS_DESIGNATED_INITIALIZER;",
"- (void)insertChild:(PDFOutline *)child atIndex:(NSUInteger)index",
"@property (nonatomic, strong, nullable) PDFDestination *destination;",
"@property (nonatomic, strong, nullable) PDFAction *action",
],
);
let annotation_header = read_header("PDFAnnotation");
assert_contains_all(
&annotation_header,
&[
"- (instancetype)initWithBounds:(PDFRect)bounds forType:(PDFAnnotationSubtype)annotationType withProperties:(nullable NSDictionary*)properties",
"@property (nonatomic, strong, nullable) PDFBorder *border",
"@property (nonatomic, strong, nullable) PDFAction *action",
"@property (nonatomic, getter=isHighlighted) BOOL highlighted",
],
);
let bridge = read_bridge();
assert_contains_all(
&bridge,
&[
"pdf_document_new",
"pdf_document_insert_page",
"pdf_document_selection_from_pages_characters",
"pdf_document_set_delegate",
"pdf_page_selection_for_word_at_point",
"pdf_page_selection_for_line_at_point",
"pdf_annotation_action",
"pdf_outline_set_action",
"pdf_outline_set_destination",
"pdf_selection_add_selection",
],
);
}
#[test]
fn view_and_thumbnail_surface_is_present() {
let view_header = read_header("PDFView");
assert_contains_all(
&view_header,
&[
"@property (nonatomic, retain, nullable) PDFDocument *document;",
"@property (nonatomic) PDFDisplayMode displayMode;",
"@property (nonatomic) PDFDisplayDirection displayDirection",
"@property (nonatomic, readonly) NSArray<PDFPage *> *visiblePages",
"@property (nonatomic, getter=isInMarkupMode) BOOL inMarkupMode",
],
);
let thumbnail_header = read_header("PDFThumbnailView");
assert_contains_all(
&thumbnail_header,
&[
"@property (nonatomic, weak, nullable) PDFView *PDFView;",
"@property (nonatomic) PDFSize thumbnailSize;",
"@property (nonatomic) NSUInteger maximumNumberOfColumns;",
"@property (nonatomic) BOOL allowsMultipleSelection;",
],
);
let bridge = read_bridge();
assert_contains_all(
&bridge,
&[
"pdf_view_new",
"pdf_view_set_document",
"pdf_view_visible_page_at",
"pdf_thumbnail_view_new",
"pdf_thumbnail_view_set_pdf_view",
"pdf_thumbnail_view_selected_page_at",
],
);
}
#[test]
fn action_border_destination_and_appearance_surface_is_present() {
let action_url_header = read_header("PDFActionURL");
let action_goto_header = read_header("PDFActionGoTo");
let border_header = read_header("PDFBorder");
let destination_header = read_header("PDFDestination");
let appearance_header = read_header("PDFAppearanceCharacteristics");
assert_contains_all(
&action_url_header,
&[
"- (instancetype)initWithURL:(NSURL *)url",
"@property (nonatomic, copy, nullable) NSURL *URL;",
],
);
assert_contains_all(
&action_goto_header,
&[
"- (instancetype)initWithDestination:(PDFDestination *)destination",
"@property (nonatomic, strong) PDFDestination *destination;",
],
);
assert_contains_all(
&border_header,
&[
"@property (nonatomic) PDFBorderStyle style;",
"@property (nonatomic, copy, nullable) NSArray *dashPattern;",
],
);
assert_contains_all(
&destination_header,
&[
"- (instancetype)initWithPage:(PDFPage *)page atPoint:(PDFPoint)point",
"- (NSComparisonResult)compare:(PDFDestination *)destination",
],
);
assert_contains_all(
&appearance_header,
&[
"@property (nonatomic) PDFWidgetControlType controlType;",
"@property (nonatomic, copy, nullable) NSString *caption;",
],
);
let bridge = read_bridge();
assert_contains_all(
&bridge,
&[
"pdf_action_url_new",
"pdf_action_goto_new",
"pdf_border_set_dash_pattern",
"pdf_destination_compare",
"pdf_appearance_characteristics_set_control_type",
],
);
}
#[test]
fn advanced_action_delegate_notification_and_option_surface_is_present() {
let action_header = read_header("PDFAction");
let action_named_header = read_header("PDFActionNamed");
let action_remote_goto_header = read_header("PDFActionRemoteGoTo");
let document_header = read_header("PDFDocument");
let annotation_utilities_header = read_header("PDFAnnotationUtilities");
let view_header = read_header("PDFView");
let thumbnail_header = read_header("PDFThumbnailView");
assert_contains_all(
&action_header,
&["@property (nonatomic, readonly) NSString *type;"],
);
assert_contains_all(
&action_named_header,
&[
"typedef NS_ENUM(NSInteger, PDFActionNamedName)",
"- (instancetype)initWithName:(PDFActionNamedName)name NS_DESIGNATED_INITIALIZER;",
"@property (nonatomic) PDFActionNamedName name;",
],
);
assert_contains_all(
&action_remote_goto_header,
&[
"- (instancetype)initWithPageIndex:(NSUInteger)pageIndex atPoint:(PDFPoint)point fileURL:(NSURL *)url NS_DESIGNATED_INITIALIZER;",
"@property (nonatomic) NSUInteger pageIndex;",
"@property (nonatomic) PDFPoint point;",
"@property (nonatomic, copy) NSURL *URL;",
],
);
assert_contains_all(
&document_header,
&[
"PDFKIT_EXTERN NSNotificationName const PDFDocumentDidUnlockNotification",
"PDFKIT_EXTERN NSString* const PDFDocumentFoundSelectionKey",
"PDFKIT_EXTERN PDFDocumentWriteOption const PDFDocumentOwnerPasswordOption",
"@protocol PDFDocumentDelegate< NSObject >",
],
);
assert_contains_all(
&annotation_utilities_header,
&[
"typedef NS_ENUM(NSInteger, PDFLineStyle)",
"typedef NS_ENUM(NSInteger, PDFMarkupType)",
],
);
assert_contains_all(
&view_header,
&[
"PDFKIT_EXTERN NSNotificationName const PDFViewDocumentChangedNotification",
"PDFKIT_EXTERN NSNotificationName const PDFViewVisiblePagesChangedNotification",
],
);
assert_contains_all(
&thumbnail_header,
&["PDFKIT_EXTERN NSString* const PDFThumbnailViewDocumentEditedNotification"],
);
let bridge = read_bridge();
assert_contains_all(
&bridge,
&[
"pdf_action_type_string",
"pdf_action_named_new",
"pdf_action_remote_goto_new",
"pdf_document_delegate_new",
"pdf_document_write_to_url_with_options",
],
);
let notifications_src = read_src("notifications.rs");
assert_contains_all(
¬ifications_src,
&[
"pub enum PdfDocumentNotification",
"pub enum PdfDocumentNotificationUserInfoKey",
"pub enum PdfViewNotification",
"pub enum PdfThumbnailViewNotification",
],
);
let delegate_src = read_src("document_delegate.rs");
assert_contains_all(
&delegate_src,
&[
"pub trait PdfDocumentDelegate",
"pub struct PdfDocumentDelegateHandle",
"fn page_class_name(&mut self) -> Option<String>",
"fn annotation_class_name(&mut self, _annotation_type: &str) -> Option<String>",
],
);
let types_src = read_src("types.rs");
assert_contains_all(
&types_src,
&[
"pub enum PdfActionNamedName",
"pub enum PdfLineStyle",
"pub enum PdfMarkupType",
"pub struct PdfDocumentWriteOptions",
],
);
}
#[allow(clippy::too_many_lines)]
#[test]
fn exhaustive_gap_closure_surface_is_present() {
let action_reset_form_header = read_header("PDFActionResetForm");
let annotation_header = read_header("PDFAnnotation");
let annotation_utilities_header = read_header("PDFAnnotationUtilities");
let destination_header = read_header("PDFDestination");
let document_header = read_header("PDFDocument");
let page_header = read_header("PDFPage");
let page_overlay_header = read_header("PDFPageOverlayViewProvider");
let selection_header = read_header("PDFSelection");
let thumbnail_header = read_header("PDFThumbnailView");
let view_header = read_header("PDFView");
assert_contains_all(
&action_reset_form_header,
&[
"@interface PDFActionResetForm : PDFAction <NSCopying>",
"@property (nonatomic, copy, nullable) NSArray<NSString*> *fields;",
"@property (nonatomic) BOOL fieldsIncludedAreCleared;",
],
);
assert_contains_all(
&annotation_header,
&[
"typedef NSString* const PDFAnnotationKey NS_STRING_ENUM;",
"PDFAnnotationKey PDFAnnotationKeyAdditionalActions",
"PDFAnnotationKey PDFAnnotationKeyWidgetValue",
],
);
assert_contains_all(
&annotation_utilities_header,
&[
"typedef NS_ENUM(NSInteger, PDFTextAnnotationIconType)",
"typedef NS_ENUM(NSInteger, PDFWidgetCellState)",
"PDFAnnotationSubtype PDFAnnotationSubtypeWidget",
"PDFAnnotationHighlightingMode PDFAnnotationHighlightingModePush",
],
);
assert_contains_all(&destination_header, &["kPDFDestinationUnspecifiedValue"]);
assert_contains_all(
&document_header,
&[
"typedef NS_ENUM(NSInteger, PDFPrintScalingMode)",
"typedef NS_ENUM(NSUInteger, PDFSelectionGranularity);",
"withGranularity:(PDFSelectionGranularity)granularity",
],
);
assert_contains_all(
&page_header,
&[
"typedef NS_OPTIONS(NSInteger, PDFAreaOfInterest)",
"typedef NSString * PDFPageImageInitializationOption",
"initWithImage:(PDFKitPlatformImage *)image options:",
],
);
assert_contains_all(
&page_overlay_header,
&[
"@protocol PDFPageOverlayViewProvider <NSObject>",
"overlayViewForPage:(PDFPage*)page",
],
);
assert_contains_all(
&selection_header,
&["typedef NS_ENUM(NSUInteger, PDFSelectionGranularity)"],
);
assert_contains_all(
&thumbnail_header,
&["typedef NS_ENUM(NSInteger, PDFThumbnailLayoutMode)"],
);
assert_contains_all(
&view_header,
&[
"@property (nonatomic, weak, nullable) id< PDFViewDelegate > delegate;",
"@property (nonatomic, weak, nullable) id<PDFPageOverlayViewProvider> pageOverlayViewProvider",
"- (PDFAreaOfInterest)areaOfInterestForPoint:(PDFPoint)cursorLocation;",
"@protocol PDFViewDelegate< NSObject >",
],
);
let bridge = read_bridge();
assert_contains_all(
&bridge,
&[
"pdf_action_as_reset_form",
"pdf_action_reset_form_new",
"pdf_document_selection_from_pages_points_with_granularity",
"pdf_page_new_with_image_data",
"pdf_page_overlay_view_new",
"pdf_page_overlay_view_provider_new",
"pdf_view_set_delegate",
"pdf_view_set_page_overlay_view_provider",
"pdf_view_area_of_interest_for_point",
"pdf_view_delegate_new",
],
);
let action_reset_form_src = read_src("action_reset_form.rs");
assert_contains_all(
&action_reset_form_src,
&[
"pub struct PdfActionResetForm",
"pub fn fields(&self) -> Result<Vec<String>>",
"pub fn set_fields<I, S>(&self, fields: I) -> Result<()>",
],
);
let annotation_constants_src = read_src("annotation_constants.rs");
assert_contains_all(
&annotation_constants_src,
&[
"pub enum PdfAnnotationKey",
"pub enum PdfAnnotationSubtype",
"pub enum PdfAnnotationTextIconName",
"pub enum PdfAnnotationWidgetSubtype",
],
);
let page_src = read_src("page.rs");
assert_contains_all(
&page_src,
&[
"PdfPageImageInitializationOptions",
"pub fn from_image_data(",
],
);
let page_overlay_provider_src = read_src("page_overlay_view_provider.rs");
assert_contains_all(
&page_overlay_provider_src,
&[
"pub trait PdfPageOverlayViewProvider",
"pub struct PdfPageOverlayViewProviderHandle",
],
);
let types_src = read_src("types.rs");
assert_contains_all(
&types_src,
&[
"pub enum PdfTextAnnotationIconType",
"pub enum PdfWidgetCellState",
"pub struct PdfAreaOfInterest(u64);",
"pub enum PdfPrintScalingMode",
"pub enum PdfSelectionGranularity",
"pub enum PdfThumbnailLayoutMode",
"pub struct PdfPageImageInitializationOptions",
],
);
let view_src = read_src("view.rs");
assert_contains_all(
&view_src,
&[
"pub fn set_delegate(&self, delegate: Option<&PdfViewDelegateHandle>) -> Result<()>",
"pub fn set_page_overlay_view_provider(",
"pub fn area_of_interest_for_point(&self, point: PdfPoint) -> PdfAreaOfInterest",
],
);
let view_delegate_src = read_src("view_delegate.rs");
assert_contains_all(
&view_delegate_src,
&[
"pub trait PdfViewDelegate",
"pub struct PdfViewDelegateHandle",
"fn handle_link_click(&mut self, _view: PdfView, _url: &str) -> bool",
],
);
}
#[test]
fn accessibility_node_status_surface_is_present() {
let bridge = read_bridge();
assert_contains_all(
&bridge,
&[
"pdf_accessibility_node_public_api_available",
"pdf_accessibility_node_reason",
],
);
}