123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637 |
- #import "SKPDFSynchronizer.h"
- #import <libkern/OSAtomic.h>
- #import "SKPDFSyncRecord.h"
- #define PDFSYNC_TO_PDF(coord) ((CGFloat)coord / 65536.0)
- static NSPoint pdfOffset = {0.0, 0.0};
- #define SKPDFSynchronizerPdfsyncExtension @"pdfsync"
- static NSArray *SKPDFSynchronizerTexExtensions = nil;
- static BOOL caseInsensitiveStringEqual(const void *item1, const void *item2, NSUInteger (*size)(const void *item));
- static NSUInteger caseInsensitiveStringHash(const void *item, NSUInteger (*size)(const void *item));
- #pragma mark -
- @implementation SKPDFSynchronizer
- + (void)initialize {
- SKPDFSynchronizerTexExtensions = [[NSArray alloc] initWithObjects:@"tex", @"ltx", @"latex", @"ctx", @"lyx", @"rnw", nil];
- }
- - (id)init {
- self = [super init];
- if (self) {
- queue = NULL;
- lockQueue = dispatch_queue_create("net.sourceforge.skim-app.lockQueue.SKPDFSynchronizer", NULL);
-
- syncFileName = nil;
- isPdfsync = YES;
-
- pages = nil;
- lines = nil;
-
- filenames = nil;
- scanner = NULL;
-
- shouldKeepRunning = 1;
-
-
- fileManager = [[NSFileManager alloc] init];
- }
- return self;
- }
- - (void)dealloc {
- }
- - (void)terminate {
-
- delegate = nil;
-
- OSAtomicCompareAndSwap32Barrier(1, 0, (int32_t *)&shouldKeepRunning);
- }
- #pragma mark Thread safe accessors
- - (BOOL)shouldKeepRunning {
- OSMemoryBarrier();
- return shouldKeepRunning == 1;
- }
- - (void)setFileName:(NSString *)newFileName {
-
- newFileName = [[newFileName stringByResolvingSymlinksInPath] stringByStandardizingPath];
- _fileName = newFileName;
- }
- - (void)setSyncFileName:(NSString *)newSyncFileName {
- dispatch_async(lockQueue, ^{
- syncFileName = newSyncFileName;
- _lastModDate = (syncFileName ? [[fileManager attributesOfItemAtPath:syncFileName error:NULL] fileModificationDate] : nil);
- });
- }
- #pragma mark Support
- - (NSString *)sourceFileForFileName:(NSString *)file isTeX:(BOOL)isTeX removeQuotes:(BOOL)removeQuotes {
- if (removeQuotes && [file length] > 2 && [file characterAtIndex:0] == '"' && [file characterAtIndex:[file length] - 1] == '"')
- file = [file substringWithRange:NSMakeRange(1, [file length] - 2)];
- if ([file isAbsolutePath] == NO)
- file = [[[self fileName] stringByDeletingLastPathComponent] stringByAppendingPathComponent:file];
- if (isTeX && [fileManager fileExistsAtPath:file] == NO && [SKPDFSynchronizerTexExtensions containsObject:[[file pathExtension] lowercaseString]] == NO) {
- for (NSString *extension in SKPDFSynchronizerTexExtensions) {
- NSString *tryFile = [file stringByAppendingPathExtension:extension];
- if ([fileManager fileExistsAtPath:tryFile]) {
- file = tryFile;
- break;
- }
- }
- }
-
- return [[file stringByResolvingSymlinksInPath] stringByStandardizingPath];
- }
- - (NSString *)defaultSourceFile {
- NSString *file = [[self fileName] stringByDeletingPathExtension];
- for (NSString *extension in SKPDFSynchronizerTexExtensions) {
- NSString *tryFile = [file stringByAppendingPathExtension:extension];
- if ([fileManager fileExistsAtPath:tryFile])
- return tryFile;
- }
- return [file stringByAppendingPathExtension:[SKPDFSynchronizerTexExtensions firstObject]];
- }
- #pragma mark PDFSync
- static inline SKPDFSyncRecord *recordForIndex(NSMapTable *records, NSInteger recordIndex) {
- SKPDFSyncRecord *record = (__bridge SKPDFSyncRecord *)(NSMapGet(records, (const void *)recordIndex));
- if (record == nil) {
- record = [[SKPDFSyncRecord alloc] initWithRecordIndex:recordIndex];
- NSMapInsert(records, (const void *)recordIndex, CFBridgingRetain(record));
- }
- return record;
- }
- - (BOOL)loadPdfsyncFile:(NSString *)theFileName {
- if (pages)
- [pages removeAllObjects];
- else
- pages = [[NSMutableArray alloc] init];
- if (lines) {
- [lines removeAllObjects];
- } else {
- NSPointerFunctions *keyPointerFunctions = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPersonality];
- [keyPointerFunctions setIsEqualFunction:&caseInsensitiveStringEqual];
- [keyPointerFunctions setHashFunction:&caseInsensitiveStringHash];
- NSPointerFunctions *valuePointerFunctions = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPersonality];
- lines = [[NSMapTable alloc] initWithKeyPointerFunctions:keyPointerFunctions valuePointerFunctions:valuePointerFunctions capacity:0];
- }
-
- [self setSyncFileName:theFileName];
- isPdfsync = YES;
-
- NSString *pdfsyncString = [NSString stringWithContentsOfFile:theFileName encoding:NSUTF8StringEncoding error:NULL];
- BOOL rv = NO;
-
- if ([pdfsyncString length]) {
-
- NSMapTable *records = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSObjectMapValueCallBacks, 0);
- NSMutableArray *files = [[NSMutableArray alloc] init];
- NSString *file;
- NSInteger recordIndex, line, pageIndex;
- double x, y;
- SKPDFSyncRecord *record;
- NSMutableArray *array;
- unichar ch;
- NSScanner *sc = [[NSScanner alloc] initWithString:pdfsyncString];
- NSCharacterSet *newlines = [NSCharacterSet newlineCharacterSet];
-
- [sc setCharactersToBeSkipped:[NSCharacterSet whitespaceCharacterSet]];
-
- if ([sc scanUpToCharactersFromSet:newlines intoString:&file] &&
- [sc scanCharactersFromSet:newlines intoString:NULL]) {
-
- file = [self sourceFileForFileName:file isTeX:YES removeQuotes:YES];
- [files addObject:file];
-
- array = [[NSMutableArray alloc] init];
- [lines setObject:array forKey:file];
-
-
- if ([sc scanString:@"version" intoString:NULL] && [sc scanInteger:NULL]) {
-
- [sc scanCharactersFromSet:newlines intoString:NULL];
-
- while ([self shouldKeepRunning] && [self scanCharacter:sc ch:&ch]) {
-
- switch (ch) {
- case 'l':
- if ([sc scanInteger:&recordIndex] && [sc scanInteger:&line]) {
-
- [sc scanInteger:NULL];
- record = recordForIndex(records, recordIndex);
- [record setFile:file];
- [record setLine:line];
- [[lines objectForKey:file] addObject:record];
- }
- break;
- case 'p':
-
- if ([sc scanString:@"*" intoString:NULL] == NO)
- [sc scanString:@"+" intoString:NULL];
- if ([sc scanInteger:&recordIndex] && [sc scanDouble:&x] && [sc scanDouble:&y]) {
- record = recordForIndex(records, recordIndex);
- [record setPageIndex:[pages count] - 1];
- [record setPoint:NSMakePoint(PDFSYNC_TO_PDF(x) + pdfOffset.x, PDFSYNC_TO_PDF(y) + pdfOffset.y)];
- [[pages lastObject] addObject:record];
- }
- break;
- case 's':
-
- if ([sc scanInteger:&pageIndex] == NO) pageIndex = [pages count] + 1;
- while (pageIndex > (NSInteger)[pages count]) {
- array = [[NSMutableArray alloc] init];
- [pages addObject:array];
- }
- break;
- case '(':
-
- if ([sc scanUpToCharactersFromSet:newlines intoString:&file]) {
- file = [self sourceFileForFileName:file isTeX:YES removeQuotes:YES];
- [files addObject:file];
- if ([lines objectForKey:file] == nil) {
- array = [[NSMutableArray alloc] init];
- [lines setObject:array forKey:file];
- }
- }
- break;
- case ')':
-
- if ([files count]) {
- [files removeLastObject];
- file = [files lastObject];
- }
- break;
- default:
-
- break;
- }
-
- [sc scanUpToCharactersFromSet:newlines intoString:NULL];
- [sc scanCharactersFromSet:newlines intoString:NULL];
- }
-
- NSSortDescriptor *lineSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"line" ascending:YES];
- NSSortDescriptor *xSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"x" ascending:YES];
- NSSortDescriptor *ySortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"y" ascending:NO];
- NSArray *lineSortDescriptors = [NSArray arrayWithObjects:lineSortDescriptor, nil];
-
- for (array in [lines objectEnumerator])
- [array sortUsingDescriptors:lineSortDescriptors];
- [pages makeObjectsPerformSelector:@selector(sortUsingDescriptors:)
- withObject:[NSArray arrayWithObjects:ySortDescriptor, xSortDescriptor, nil]];
-
- rv = [self shouldKeepRunning];
- }
- }
-
- NSFreeMapTable(records);
- }
-
- return rv;
- }
- - (BOOL)scanCharacter:(NSScanner *)scan ch:(unichar *)ch {
- NSInteger location, length = [[scan string] length];
- unichar character = 0;
- BOOL success = NO;
- for (location = [scan scanLocation]; success == NO && location < length; location++) {
- character = [[scan string] characterAtIndex:location];
- success = [[scan charactersToBeSkipped] characterIsMember:character] == NO;
- }
- if (success) {
- if (ch != 0)
- *ch = character;
- [scan setScanLocation:location];
- }
- return success;
- }
- - (BOOL)pdfsyncFindFileLine:(NSInteger *)linePtr file:(NSString **)filePtr forLocation:(NSPoint)point inRect:(NSRect)rect pageBounds:(NSRect)bounds atPageIndex:(NSUInteger)pageIndex {
- BOOL rv = NO;
- if (pageIndex < [pages count]) {
-
- SKPDFSyncRecord *record = nil;
- SKPDFSyncRecord *beforeRecord = nil;
- SKPDFSyncRecord *afterRecord = nil;
- NSMutableDictionary *atRecords = [NSMutableDictionary dictionary];
-
- for (record in [pages objectAtIndex:pageIndex]) {
- if ([record line] == 0)
- continue;
- NSPoint p = [record point];
- if (p.y > NSMaxY(rect)) {
- beforeRecord = record;
- } else if (p.y < NSMinY(rect)) {
- afterRecord = record;
- break;
- } else if (p.x < NSMinX(rect)) {
- beforeRecord = record;
- } else if (p.x > NSMaxX(rect)) {
- afterRecord = record;
- break;
- } else {
- [atRecords setObject:record forKey:[NSNumber numberWithDouble:fabs(p.x - point.x)]];
- }
- }
-
- record = nil;
- if ([atRecords count]) {
- NSNumber *nearest = [[[atRecords allKeys] sortedArrayUsingSelector:@selector(compare:)] objectAtIndex:0];
- record = [atRecords objectForKey:nearest];
- } else if (beforeRecord && afterRecord) {
- NSPoint beforePoint = [beforeRecord point];
- NSPoint afterPoint = [afterRecord point];
- if (beforePoint.y - point.y < point.y - afterPoint.y)
- record = beforeRecord;
- else if (beforePoint.y - point.y > point.y - afterPoint.y)
- record = afterRecord;
- else if (beforePoint.x - point.x < point.x - afterPoint.x)
- record = beforeRecord;
- else if (beforePoint.x - point.x > point.x - afterPoint.x)
- record = afterRecord;
- else
- record = beforeRecord;
- } else if (beforeRecord) {
- record = beforeRecord;
- } else if (afterRecord) {
- record = afterRecord;
- }
-
- if (record) {
- *linePtr = [record line];
- *filePtr = [record file];
- rv = YES;
- }
- }
- if (rv == NO)
- NSLog(@"PDFSync was unable to find file and line.");
- return rv;
- }
- - (BOOL)pdfsyncFindPage:(NSUInteger *)pageIndexPtr location:(NSPoint *)pointPtr forLine:(NSInteger)line inFile:(NSString *)file {
- BOOL rv = NO;
- NSArray *theLines = [lines objectForKey:file];
- if (theLines) {
-
- SKPDFSyncRecord *record = nil;
- SKPDFSyncRecord *beforeRecord = nil;
- SKPDFSyncRecord *afterRecord = nil;
- SKPDFSyncRecord *atRecord = nil;
-
- for (record in theLines) {
- if ([record pageIndex] == NSNotFound)
- continue;
- NSInteger l = [record line];
- if (l < line) {
- beforeRecord = record;
- } else if (l > line) {
- afterRecord = record;
- break;
- } else {
- atRecord = record;
- break;
- }
- }
-
- if (atRecord) {
- record = atRecord;
- } else if (beforeRecord && afterRecord) {
- NSInteger beforeLine = [beforeRecord line];
- NSInteger afterLine = [afterRecord line];
- if (beforeLine - line > line - afterLine)
- record = afterRecord;
- else
- record = beforeRecord;
- } else if (beforeRecord) {
- record = beforeRecord;
- } else if (afterRecord) {
- record = afterRecord;
- }
-
- if (record) {
- *pageIndexPtr = [record pageIndex];
- *pointPtr = [record point];
- rv = YES;
- }
- }
- if (rv == NO)
- NSLog(@"PDFSync was unable to find location and page.");
- return rv;
- }
- #pragma mark SyncTeX
- - (BOOL)loadSynctexFileForFile:(NSString *)theFileName {
- BOOL rv = NO;
- if (scanner)
- synctex_scanner_free(scanner);
- scanner = synctex_scanner_new_with_output_file([theFileName UTF8String], NULL, 1);
- if (scanner) {
- const char *fileRep = synctex_scanner_get_synctex(scanner);
- [self setSyncFileName:[self sourceFileForFileName:[NSString stringWithUTF8String:fileRep] isTeX:NO removeQuotes:NO]];
- if (filenames) {
- NSResetMapTable(filenames);
- } else {
- NSPointerFunctions *keyPointerFunctions = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPersonality];
- [keyPointerFunctions setIsEqualFunction:&caseInsensitiveStringEqual];
- [keyPointerFunctions setHashFunction:&caseInsensitiveStringHash];
- NSPointerFunctions *valuePointerFunctions = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsMallocMemory | NSPointerFunctionsCStringPersonality | NSPointerFunctionsCopyIn];
- filenames = [[NSMapTable alloc] initWithKeyPointerFunctions:keyPointerFunctions valuePointerFunctions:valuePointerFunctions capacity:0];
- }
- synctex_node_p node = synctex_scanner_input(scanner);
- do {
- if ((fileRep = synctex_scanner_get_name(scanner, synctex_node_tag(node)))) {
- NSMapInsert(filenames, CFBridgingRetain([self sourceFileForFileName:[NSString stringWithUTF8String:fileRep] isTeX:YES removeQuotes:NO]), fileRep);
- }
- } while ((node = synctex_node_next(node)));
- isPdfsync = NO;
- rv = [self shouldKeepRunning];
- }
- return rv;
- }
- - (BOOL)synctexFindFileLine:(NSInteger *)linePtr file:(NSString **)filePtr forLocation:(NSPoint)point inRect:(NSRect)rect pageBounds:(NSRect)bounds atPageIndex:(NSUInteger)pageIndex {
- BOOL rv = NO;
- if (synctex_edit_query(scanner, (int)pageIndex + 1, point.x, NSMaxY(bounds) - point.y) > 0) {
- synctex_node_p node;
- const char *file;
- while (rv == NO && (node = synctex_scanner_next_result(scanner))) {
- if ((file = synctex_scanner_get_name(scanner, synctex_node_tag(node)))) {
- *linePtr = MAX(synctex_node_line(node), 1) - 1;
- *filePtr = [self sourceFileForFileName:[NSString stringWithUTF8String:file] isTeX:YES removeQuotes:NO];
- rv = YES;
- }
- }
- }
- if (rv == NO)
- NSLog(@"SyncTeX was unable to find file and line.");
- return rv;
- }
- - (BOOL)synctexFindPage:(NSUInteger *)pageIndexPtr location:(NSPoint *)pointPtr forLine:(NSInteger)line inFile:(NSString *)file {
- BOOL rv = NO;
- char *filename = NSMapGet(filenames, CFBridgingRetain(file)) ?: NSMapGet(filenames, CFBridgingRetain([[file stringByResolvingSymlinksInPath] stringByStandardizingPath]));
- if (filename == NULL) {
- for (NSString *fn in filenames) {
- if ([[fn lastPathComponent] caseInsensitiveCompare:[file lastPathComponent]] == NSOrderedSame) {
- filename = NSMapGet(filenames, CFBridgingRetain(file));
- break;
- }
- }
- if (filename == NULL)
- filename = (char *)[[file lastPathComponent] UTF8String];
- }
- if (synctex_display_query(scanner, filename, (int)line + 1, 0, -1) > 0) {
- synctex_node_p node = synctex_scanner_next_result(scanner);
- if (node) {
- NSUInteger page = synctex_node_page(node);
- *pageIndexPtr = MAX(page, 1ul) - 1;
- *pointPtr = NSMakePoint(synctex_node_visible_h(node), synctex_node_visible_v(node));
- rv = YES;
- }
- }
- if (rv == NO)
- NSLog(@"SyncTeX was unable to find location and page.");
- return rv;
- }
- #pragma mark Generic
- - (BOOL)loadSyncFileIfNeeded {
- NSString *theFileName = [self fileName];
- BOOL rv = NO;
-
- if (theFileName) {
- NSString *theSyncFileName = syncFileName;
-
- if (theSyncFileName && [fileManager fileExistsAtPath:theSyncFileName]) {
- NSDate *modDate = [[fileManager attributesOfItemAtPath:theFileName error:NULL] fileModificationDate];
- NSDate *currentModDate = self.lastModDate;
-
- if (currentModDate && [modDate compare:currentModDate] != NSOrderedDescending)
- rv = YES;
- else if (isPdfsync)
- rv = [self loadPdfsyncFile:theSyncFileName];
- else
- rv = [self loadSynctexFileForFile:theFileName];
- } else {
- rv = [self loadSynctexFileForFile:theFileName];
- if (rv == NO) {
- theSyncFileName = [[theFileName stringByDeletingPathExtension] stringByAppendingPathExtension:SKPDFSynchronizerPdfsyncExtension];
- if ([fileManager fileExistsAtPath:theSyncFileName])
- rv = [self loadPdfsyncFile:theSyncFileName];
- }
- }
- }
- if (rv == NO)
- NSLog(@"Unable to find or load synctex or pdfsync file.");
- return rv;
- }
- #pragma mark Queue
- - (dispatch_queue_t)queue {
- if (queue == NULL)
- queue = dispatch_queue_create("net.sourceforge.skim-app.queue.SKPDFSynchronizer", NULL);
- return queue;
- }
- #pragma mark Finding API
- - (void)findFileAndLineForLocation:(NSPoint)point inRect:(NSRect)rect pageBounds:(NSRect)bounds atPageIndex:(NSUInteger)pageIndex {
- dispatch_async([self queue], ^{
- if ([self shouldKeepRunning] && [self loadSyncFileIfNeeded]) {
- NSInteger foundLine = 0;
- NSString *foundFile = nil;
- BOOL success = NO;
-
- if (isPdfsync)
- success = [self pdfsyncFindFileLine:&foundLine file:&foundFile forLocation:point inRect:rect pageBounds:bounds atPageIndex:pageIndex];
- else
- success = [self synctexFindFileLine:&foundLine file:&foundFile forLocation:point inRect:rect pageBounds:bounds atPageIndex:pageIndex];
-
- if (success && [self shouldKeepRunning]) {
- dispatch_async(dispatch_get_main_queue(), ^{
- [delegate synchronizer:self foundLine:foundLine inFile:foundFile];
- });
- }
- }
- });
- }
- - (void)findPageAndLocationForLine:(NSInteger)line inFile:(NSString *)file options:(NSInteger)options {
- if (file == nil)
- file = [self defaultSourceFile];
- dispatch_async([self queue], ^{
- if (file && [self shouldKeepRunning] && [self loadSyncFileIfNeeded]) {
- NSUInteger foundPageIndex = NSNotFound;
- NSPoint foundPoint = NSZeroPoint;
- NSInteger foundOptions = options;
- BOOL success = NO;
- NSString *fixedFile = [self sourceFileForFileName:file isTeX:YES removeQuotes:NO];
-
- if (isPdfsync)
- success = [self pdfsyncFindPage:&foundPageIndex location:&foundPoint forLine:line inFile:fixedFile];
- else
- success = [self synctexFindPage:&foundPageIndex location:&foundPoint forLine:line inFile:fixedFile];
-
- if (success && [self shouldKeepRunning]) {
- if (isPdfsync)
- foundOptions &= ~SKPDFSynchronizerFlippedMask;
- else
- foundOptions |= SKPDFSynchronizerFlippedMask;
- dispatch_async(dispatch_get_main_queue(), ^{
- [delegate synchronizer:self foundLocation:foundPoint atPageIndex:foundPageIndex options:foundOptions];
- });
- }
- }
- });
- }
- @end
- #pragma mark -
- #define STACK_BUFFER_SIZE 256
- static BOOL caseInsensitiveStringEqual(const void *item1, const void *item2, NSUInteger (*size)(const void *item)) {
- return CFStringCompare(item1, item2, kCFCompareCaseInsensitive | kCFCompareNonliteral) == kCFCompareEqualTo;
- }
- static NSUInteger caseInsensitiveStringHash(const void *item, NSUInteger (*size)(const void *item)) {
- if(item == NULL) return 0;
-
- NSUInteger hash = 0;
- CFAllocatorRef allocator = CFGetAllocator(item);
- CFIndex len = CFStringGetLength(item);
-
-
- UniChar *buffer, stackBuffer[STACK_BUFFER_SIZE];
- if (len + 10 >= STACK_BUFFER_SIZE)
- buffer = (UniChar *)CFAllocatorAllocate(allocator, (len + 10) * sizeof(UniChar), 0);
- else
- buffer = stackBuffer;
- CFStringGetCharacters(item, CFRangeMake(0, len), buffer);
-
-
- CFMutableStringRef mutableString = CFStringCreateMutableWithExternalCharactersNoCopy(allocator, buffer, len, len + 10, (buffer != stackBuffer ? allocator : kCFAllocatorNull));
- CFStringLowercase(mutableString, NULL);
- hash = [(id)CFBridgingRelease(mutableString) hash];
-
- CFRelease(mutableString);
- return hash;
- }
|