SKPDFSynchronizer.m 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. //
  2. // SKPDFSynchronizer.m
  3. // Skim
  4. //
  5. // Created by Christiaan Hofman on 4/21/07.
  6. /*
  7. This software is Copyright (c) 2007-2018
  8. Christiaan Hofman. All rights reserved.
  9. Redistribution and use in source and binary forms, with or without
  10. modification, are permitted provided that the following conditions
  11. are met:
  12. - Redistributions of source code must retain the above copyright
  13. notice, this list of conditions and the following disclaimer.
  14. - Redistributions in binary form must reproduce the above copyright
  15. notice, this list of conditions and the following disclaimer in
  16. the documentation and/or other materials provided with the
  17. distribution.
  18. - Neither the name of Christiaan Hofman nor the names of any
  19. contributors may be used to endorse or promote products derived
  20. from this software without specific prior written permission.
  21. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  22. "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  23. LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  24. A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  25. OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  26. SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  27. LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  28. DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  29. THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  30. (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  31. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  32. */
  33. #import "SKPDFSynchronizer.h"
  34. #import <libkern/OSAtomic.h>
  35. #import "SKPDFSyncRecord.h"
  36. //#import "NSCharacterSet_SKExtensions.h"
  37. //#import "NSScanner_SKExtensions.h"
  38. //#import <CoreFoundation/CoreFoundation.h>
  39. //#import "NSFileManager_SKExtensions.h"
  40. #define PDFSYNC_TO_PDF(coord) ((CGFloat)coord / 65536.0)
  41. // Offset of coordinates in PDFKit and what pdfsync tells us. Don't know what they are; is this implementation dependent?
  42. static NSPoint pdfOffset = {0.0, 0.0};
  43. #define SKPDFSynchronizerPdfsyncExtension @"pdfsync"
  44. static NSArray *SKPDFSynchronizerTexExtensions = nil;
  45. static BOOL caseInsensitiveStringEqual(const void *item1, const void *item2, NSUInteger (*size)(const void *item));
  46. static NSUInteger caseInsensitiveStringHash(const void *item, NSUInteger (*size)(const void *item));
  47. #pragma mark -
  48. @implementation SKPDFSynchronizer
  49. //@synthesize delegate;
  50. //@dynamic fileName, shouldKeepRunning;
  51. + (void)initialize {
  52. // SKINITIALIZE;
  53. SKPDFSynchronizerTexExtensions = [[NSArray alloc] initWithObjects:@"tex", @"ltx", @"latex", @"ctx", @"lyx", @"rnw", nil];
  54. }
  55. - (id)init {
  56. self = [super init];
  57. if (self) {
  58. queue = NULL;
  59. lockQueue = dispatch_queue_create("net.sourceforge.skim-app.lockQueue.SKPDFSynchronizer", NULL);
  60. syncFileName = nil;
  61. isPdfsync = YES;
  62. pages = nil;
  63. lines = nil;
  64. filenames = nil;
  65. scanner = NULL;
  66. shouldKeepRunning = 1;
  67. // it is not safe to use the defaultManager on background threads
  68. fileManager = [[NSFileManager alloc] init];
  69. }
  70. return self;
  71. }
  72. - (void)dealloc {
  73. // SKDISPATCHDESTROY(queue);
  74. // SKDISPATCHDESTROY(lockQueue);
  75. // SKDESTROY(fileManager);
  76. // SKDESTROY(pages);
  77. // SKDESTROY(lines);
  78. // SKDESTROY(filenames);
  79. // SKDESTROY(fileName);
  80. // SKDESTROY(syncFileName);
  81. // SKDESTROY(lastModDate);
  82. // if (scanner) synctex_scanner_free(scanner);
  83. // scanner = NULL;
  84. // [super dealloc];
  85. }
  86. - (void)terminate {
  87. // make sure we're not calling our delegate
  88. delegate = nil;
  89. // set the stop flag immediately, so any running task may stop in its tracks
  90. OSAtomicCompareAndSwap32Barrier(1, 0, (int32_t *)&shouldKeepRunning);
  91. }
  92. #pragma mark Thread safe accessors
  93. - (BOOL)shouldKeepRunning {
  94. OSMemoryBarrier();
  95. return shouldKeepRunning == 1;
  96. }
  97. - (void)setFileName:(NSString *)newFileName {
  98. // we compare filenames in canonical form throughout, so we need to make sure fileName also is in canonical form
  99. newFileName = [[newFileName stringByResolvingSymlinksInPath] stringByStandardizingPath];
  100. _fileName = newFileName;
  101. }
  102. // this should only be used from the server thread
  103. - (void)setSyncFileName:(NSString *)newSyncFileName {
  104. dispatch_async(lockQueue, ^{
  105. syncFileName = newSyncFileName;
  106. _lastModDate = (syncFileName ? [[fileManager attributesOfItemAtPath:syncFileName error:NULL] fileModificationDate] : nil);
  107. });
  108. }
  109. #pragma mark Support
  110. - (NSString *)sourceFileForFileName:(NSString *)file isTeX:(BOOL)isTeX removeQuotes:(BOOL)removeQuotes {
  111. if (removeQuotes && [file length] > 2 && [file characterAtIndex:0] == '"' && [file characterAtIndex:[file length] - 1] == '"')
  112. file = [file substringWithRange:NSMakeRange(1, [file length] - 2)];
  113. if ([file isAbsolutePath] == NO)
  114. file = [[[self fileName] stringByDeletingLastPathComponent] stringByAppendingPathComponent:file];
  115. if (isTeX && [fileManager fileExistsAtPath:file] == NO && [SKPDFSynchronizerTexExtensions containsObject:[[file pathExtension] lowercaseString]] == NO) {
  116. for (NSString *extension in SKPDFSynchronizerTexExtensions) {
  117. NSString *tryFile = [file stringByAppendingPathExtension:extension];
  118. if ([fileManager fileExistsAtPath:tryFile]) {
  119. file = tryFile;
  120. break;
  121. }
  122. }
  123. }
  124. // the docs say -stringByStandardizingPath uses -stringByResolvingSymlinksInPath, but it doesn't
  125. return [[file stringByResolvingSymlinksInPath] stringByStandardizingPath];
  126. }
  127. - (NSString *)defaultSourceFile {
  128. NSString *file = [[self fileName] stringByDeletingPathExtension];
  129. for (NSString *extension in SKPDFSynchronizerTexExtensions) {
  130. NSString *tryFile = [file stringByAppendingPathExtension:extension];
  131. if ([fileManager fileExistsAtPath:tryFile])
  132. return tryFile;
  133. }
  134. return [file stringByAppendingPathExtension:[SKPDFSynchronizerTexExtensions firstObject]];
  135. }
  136. #pragma mark PDFSync
  137. static inline SKPDFSyncRecord *recordForIndex(NSMapTable *records, NSInteger recordIndex) {
  138. SKPDFSyncRecord *record = (__bridge SKPDFSyncRecord *)(NSMapGet(records, (const void *)recordIndex));
  139. if (record == nil) {
  140. record = [[SKPDFSyncRecord alloc] initWithRecordIndex:recordIndex];
  141. NSMapInsert(records, (const void *)recordIndex, CFBridgingRetain(record));
  142. }
  143. return record;
  144. }
  145. - (BOOL)loadPdfsyncFile:(NSString *)theFileName {
  146. if (pages)
  147. [pages removeAllObjects];
  148. else
  149. pages = [[NSMutableArray alloc] init];
  150. if (lines) {
  151. [lines removeAllObjects];
  152. } else {
  153. NSPointerFunctions *keyPointerFunctions = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPersonality];
  154. [keyPointerFunctions setIsEqualFunction:&caseInsensitiveStringEqual];
  155. [keyPointerFunctions setHashFunction:&caseInsensitiveStringHash];
  156. NSPointerFunctions *valuePointerFunctions = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPersonality];
  157. lines = [[NSMapTable alloc] initWithKeyPointerFunctions:keyPointerFunctions valuePointerFunctions:valuePointerFunctions capacity:0];
  158. }
  159. [self setSyncFileName:theFileName];
  160. isPdfsync = YES;
  161. NSString *pdfsyncString = [NSString stringWithContentsOfFile:theFileName encoding:NSUTF8StringEncoding error:NULL];
  162. BOOL rv = NO;
  163. if ([pdfsyncString length]) {
  164. NSMapTable *records = NSCreateMapTable(NSIntegerMapKeyCallBacks, NSObjectMapValueCallBacks, 0);
  165. NSMutableArray *files = [[NSMutableArray alloc] init];
  166. NSString *file;
  167. NSInteger recordIndex, line, pageIndex;
  168. double x, y;
  169. SKPDFSyncRecord *record;
  170. NSMutableArray *array;
  171. unichar ch;
  172. NSScanner *sc = [[NSScanner alloc] initWithString:pdfsyncString];
  173. NSCharacterSet *newlines = [NSCharacterSet newlineCharacterSet];
  174. [sc setCharactersToBeSkipped:[NSCharacterSet whitespaceCharacterSet]];
  175. if ([sc scanUpToCharactersFromSet:newlines intoString:&file] &&
  176. [sc scanCharactersFromSet:newlines intoString:NULL]) {
  177. file = [self sourceFileForFileName:file isTeX:YES removeQuotes:YES];
  178. [files addObject:file];
  179. array = [[NSMutableArray alloc] init];
  180. [lines setObject:array forKey:file];
  181. // we ignore the version
  182. if ([sc scanString:@"version" intoString:NULL] && [sc scanInteger:NULL]) {
  183. [sc scanCharactersFromSet:newlines intoString:NULL];
  184. while ([self shouldKeepRunning] && [self scanCharacter:sc ch:&ch]) {
  185. switch (ch) {
  186. case 'l':
  187. if ([sc scanInteger:&recordIndex] && [sc scanInteger:&line]) {
  188. // we ignore the column
  189. [sc scanInteger:NULL];
  190. record = recordForIndex(records, recordIndex);
  191. [record setFile:file];
  192. [record setLine:line];
  193. [[lines objectForKey:file] addObject:record];
  194. }
  195. break;
  196. case 'p':
  197. // we ignore * and + modifiers
  198. if ([sc scanString:@"*" intoString:NULL] == NO)
  199. [sc scanString:@"+" intoString:NULL];
  200. if ([sc scanInteger:&recordIndex] && [sc scanDouble:&x] && [sc scanDouble:&y]) {
  201. record = recordForIndex(records, recordIndex);
  202. [record setPageIndex:[pages count] - 1];
  203. [record setPoint:NSMakePoint(PDFSYNC_TO_PDF(x) + pdfOffset.x, PDFSYNC_TO_PDF(y) + pdfOffset.y)];
  204. [[pages lastObject] addObject:record];
  205. }
  206. break;
  207. case 's':
  208. // start of a new page, the scanned integer should always equal [pages count]+1
  209. if ([sc scanInteger:&pageIndex] == NO) pageIndex = [pages count] + 1;
  210. while (pageIndex > (NSInteger)[pages count]) {
  211. array = [[NSMutableArray alloc] init];
  212. [pages addObject:array];
  213. }
  214. break;
  215. case '(':
  216. // start of a new source file
  217. if ([sc scanUpToCharactersFromSet:newlines intoString:&file]) {
  218. file = [self sourceFileForFileName:file isTeX:YES removeQuotes:YES];
  219. [files addObject:file];
  220. if ([lines objectForKey:file] == nil) {
  221. array = [[NSMutableArray alloc] init];
  222. [lines setObject:array forKey:file];
  223. }
  224. }
  225. break;
  226. case ')':
  227. // closing of a source file
  228. if ([files count]) {
  229. [files removeLastObject];
  230. file = [files lastObject];
  231. }
  232. break;
  233. default:
  234. // shouldn't reach
  235. break;
  236. }
  237. [sc scanUpToCharactersFromSet:newlines intoString:NULL];
  238. [sc scanCharactersFromSet:newlines intoString:NULL];
  239. }
  240. NSSortDescriptor *lineSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"line" ascending:YES];
  241. NSSortDescriptor *xSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"x" ascending:YES];
  242. NSSortDescriptor *ySortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"y" ascending:NO];
  243. NSArray *lineSortDescriptors = [NSArray arrayWithObjects:lineSortDescriptor, nil];
  244. for (array in [lines objectEnumerator])
  245. [array sortUsingDescriptors:lineSortDescriptors];
  246. [pages makeObjectsPerformSelector:@selector(sortUsingDescriptors:)
  247. withObject:[NSArray arrayWithObjects:ySortDescriptor, xSortDescriptor, nil]];
  248. rv = [self shouldKeepRunning];
  249. }
  250. }
  251. NSFreeMapTable(records);
  252. }
  253. return rv;
  254. }
  255. - (BOOL)scanCharacter:(NSScanner *)scan ch:(unichar *)ch {
  256. NSInteger location, length = [[scan string] length];
  257. unichar character = 0;
  258. BOOL success = NO;
  259. for (location = [scan scanLocation]; success == NO && location < length; location++) {
  260. character = [[scan string] characterAtIndex:location];
  261. success = [[scan charactersToBeSkipped] characterIsMember:character] == NO;
  262. }
  263. if (success) {
  264. if (ch != 0)
  265. *ch = character;
  266. [scan setScanLocation:location];
  267. }
  268. return success;
  269. }
  270. - (BOOL)pdfsyncFindFileLine:(NSInteger *)linePtr file:(NSString **)filePtr forLocation:(NSPoint)point inRect:(NSRect)rect pageBounds:(NSRect)bounds atPageIndex:(NSUInteger)pageIndex {
  271. BOOL rv = NO;
  272. if (pageIndex < [pages count]) {
  273. SKPDFSyncRecord *record = nil;
  274. SKPDFSyncRecord *beforeRecord = nil;
  275. SKPDFSyncRecord *afterRecord = nil;
  276. NSMutableDictionary *atRecords = [NSMutableDictionary dictionary];
  277. for (record in [pages objectAtIndex:pageIndex]) {
  278. if ([record line] == 0)
  279. continue;
  280. NSPoint p = [record point];
  281. if (p.y > NSMaxY(rect)) {
  282. beforeRecord = record;
  283. } else if (p.y < NSMinY(rect)) {
  284. afterRecord = record;
  285. break;
  286. } else if (p.x < NSMinX(rect)) {
  287. beforeRecord = record;
  288. } else if (p.x > NSMaxX(rect)) {
  289. afterRecord = record;
  290. break;
  291. } else {
  292. [atRecords setObject:record forKey:[NSNumber numberWithDouble:fabs(p.x - point.x)]];
  293. }
  294. }
  295. record = nil;
  296. if ([atRecords count]) {
  297. NSNumber *nearest = [[[atRecords allKeys] sortedArrayUsingSelector:@selector(compare:)] objectAtIndex:0];
  298. record = [atRecords objectForKey:nearest];
  299. } else if (beforeRecord && afterRecord) {
  300. NSPoint beforePoint = [beforeRecord point];
  301. NSPoint afterPoint = [afterRecord point];
  302. if (beforePoint.y - point.y < point.y - afterPoint.y)
  303. record = beforeRecord;
  304. else if (beforePoint.y - point.y > point.y - afterPoint.y)
  305. record = afterRecord;
  306. else if (beforePoint.x - point.x < point.x - afterPoint.x)
  307. record = beforeRecord;
  308. else if (beforePoint.x - point.x > point.x - afterPoint.x)
  309. record = afterRecord;
  310. else
  311. record = beforeRecord;
  312. } else if (beforeRecord) {
  313. record = beforeRecord;
  314. } else if (afterRecord) {
  315. record = afterRecord;
  316. }
  317. if (record) {
  318. *linePtr = [record line];
  319. *filePtr = [record file];
  320. rv = YES;
  321. }
  322. }
  323. if (rv == NO)
  324. NSLog(@"PDFSync was unable to find file and line.");
  325. return rv;
  326. }
  327. - (BOOL)pdfsyncFindPage:(NSUInteger *)pageIndexPtr location:(NSPoint *)pointPtr forLine:(NSInteger)line inFile:(NSString *)file {
  328. BOOL rv = NO;
  329. NSArray *theLines = [lines objectForKey:file];
  330. if (theLines) {
  331. SKPDFSyncRecord *record = nil;
  332. SKPDFSyncRecord *beforeRecord = nil;
  333. SKPDFSyncRecord *afterRecord = nil;
  334. SKPDFSyncRecord *atRecord = nil;
  335. for (record in theLines) {
  336. if ([record pageIndex] == NSNotFound)
  337. continue;
  338. NSInteger l = [record line];
  339. if (l < line) {
  340. beforeRecord = record;
  341. } else if (l > line) {
  342. afterRecord = record;
  343. break;
  344. } else {
  345. atRecord = record;
  346. break;
  347. }
  348. }
  349. if (atRecord) {
  350. record = atRecord;
  351. } else if (beforeRecord && afterRecord) {
  352. NSInteger beforeLine = [beforeRecord line];
  353. NSInteger afterLine = [afterRecord line];
  354. if (beforeLine - line > line - afterLine)
  355. record = afterRecord;
  356. else
  357. record = beforeRecord;
  358. } else if (beforeRecord) {
  359. record = beforeRecord;
  360. } else if (afterRecord) {
  361. record = afterRecord;
  362. }
  363. if (record) {
  364. *pageIndexPtr = [record pageIndex];
  365. *pointPtr = [record point];
  366. rv = YES;
  367. }
  368. }
  369. if (rv == NO)
  370. NSLog(@"PDFSync was unable to find location and page.");
  371. return rv;
  372. }
  373. #pragma mark SyncTeX
  374. - (BOOL)loadSynctexFileForFile:(NSString *)theFileName {
  375. BOOL rv = NO;
  376. if (scanner)
  377. synctex_scanner_free(scanner);
  378. scanner = synctex_scanner_new_with_output_file([theFileName UTF8String], NULL, 1);
  379. if (scanner) {
  380. const char *fileRep = synctex_scanner_get_synctex(scanner);
  381. [self setSyncFileName:[self sourceFileForFileName:[NSString stringWithUTF8String:fileRep] isTeX:NO removeQuotes:NO]];
  382. if (filenames) {
  383. NSResetMapTable(filenames);
  384. } else {
  385. NSPointerFunctions *keyPointerFunctions = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPersonality];
  386. [keyPointerFunctions setIsEqualFunction:&caseInsensitiveStringEqual];
  387. [keyPointerFunctions setHashFunction:&caseInsensitiveStringHash];
  388. NSPointerFunctions *valuePointerFunctions = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsMallocMemory | NSPointerFunctionsCStringPersonality | NSPointerFunctionsCopyIn];
  389. filenames = [[NSMapTable alloc] initWithKeyPointerFunctions:keyPointerFunctions valuePointerFunctions:valuePointerFunctions capacity:0];
  390. }
  391. synctex_node_p node = synctex_scanner_input(scanner);
  392. do {
  393. if ((fileRep = synctex_scanner_get_name(scanner, synctex_node_tag(node)))) {
  394. NSMapInsert(filenames, CFBridgingRetain([self sourceFileForFileName:[NSString stringWithUTF8String:fileRep] isTeX:YES removeQuotes:NO]), fileRep);
  395. }
  396. } while ((node = synctex_node_next(node)));
  397. isPdfsync = NO;
  398. rv = [self shouldKeepRunning];
  399. }
  400. return rv;
  401. }
  402. - (BOOL)synctexFindFileLine:(NSInteger *)linePtr file:(NSString **)filePtr forLocation:(NSPoint)point inRect:(NSRect)rect pageBounds:(NSRect)bounds atPageIndex:(NSUInteger)pageIndex {
  403. BOOL rv = NO;
  404. if (synctex_edit_query(scanner, (int)pageIndex + 1, point.x, NSMaxY(bounds) - point.y) > 0) {
  405. synctex_node_p node;
  406. const char *file;
  407. while (rv == NO && (node = synctex_scanner_next_result(scanner))) {
  408. if ((file = synctex_scanner_get_name(scanner, synctex_node_tag(node)))) {
  409. *linePtr = MAX(synctex_node_line(node), 1) - 1;
  410. *filePtr = [self sourceFileForFileName:[NSString stringWithUTF8String:file] isTeX:YES removeQuotes:NO];
  411. rv = YES;
  412. }
  413. }
  414. }
  415. if (rv == NO)
  416. NSLog(@"SyncTeX was unable to find file and line.");
  417. return rv;
  418. }
  419. - (BOOL)synctexFindPage:(NSUInteger *)pageIndexPtr location:(NSPoint *)pointPtr forLine:(NSInteger)line inFile:(NSString *)file {
  420. BOOL rv = NO;
  421. char *filename = NSMapGet(filenames, CFBridgingRetain(file)) ?: NSMapGet(filenames, CFBridgingRetain([[file stringByResolvingSymlinksInPath] stringByStandardizingPath]));
  422. if (filename == NULL) {
  423. for (NSString *fn in filenames) {
  424. if ([[fn lastPathComponent] caseInsensitiveCompare:[file lastPathComponent]] == NSOrderedSame) {
  425. filename = NSMapGet(filenames, CFBridgingRetain(file));
  426. break;
  427. }
  428. }
  429. if (filename == NULL)
  430. filename = (char *)[[file lastPathComponent] UTF8String];
  431. }
  432. if (synctex_display_query(scanner, filename, (int)line + 1, 0, -1) > 0) {
  433. synctex_node_p node = synctex_scanner_next_result(scanner);
  434. if (node) {
  435. NSUInteger page = synctex_node_page(node);
  436. *pageIndexPtr = MAX(page, 1ul) - 1;
  437. *pointPtr = NSMakePoint(synctex_node_visible_h(node), synctex_node_visible_v(node));
  438. rv = YES;
  439. }
  440. }
  441. if (rv == NO)
  442. NSLog(@"SyncTeX was unable to find location and page.");
  443. return rv;
  444. }
  445. #pragma mark Generic
  446. - (BOOL)loadSyncFileIfNeeded {
  447. NSString *theFileName = [self fileName];
  448. BOOL rv = NO;
  449. if (theFileName) {
  450. NSString *theSyncFileName = syncFileName;
  451. if (theSyncFileName && [fileManager fileExistsAtPath:theSyncFileName]) {
  452. NSDate *modDate = [[fileManager attributesOfItemAtPath:theFileName error:NULL] fileModificationDate];
  453. NSDate *currentModDate = self.lastModDate;
  454. if (currentModDate && [modDate compare:currentModDate] != NSOrderedDescending)
  455. rv = YES;
  456. else if (isPdfsync)
  457. rv = [self loadPdfsyncFile:theSyncFileName];
  458. else
  459. rv = [self loadSynctexFileForFile:theFileName];
  460. } else {
  461. rv = [self loadSynctexFileForFile:theFileName];
  462. if (rv == NO) {
  463. theSyncFileName = [[theFileName stringByDeletingPathExtension] stringByAppendingPathExtension:SKPDFSynchronizerPdfsyncExtension];
  464. if ([fileManager fileExistsAtPath:theSyncFileName])
  465. rv = [self loadPdfsyncFile:theSyncFileName];
  466. }
  467. }
  468. }
  469. if (rv == NO)
  470. NSLog(@"Unable to find or load synctex or pdfsync file.");
  471. return rv;
  472. }
  473. #pragma mark Queue
  474. - (dispatch_queue_t)queue {
  475. if (queue == NULL)
  476. queue = dispatch_queue_create("net.sourceforge.skim-app.queue.SKPDFSynchronizer", NULL);
  477. return queue;
  478. }
  479. #pragma mark Finding API
  480. - (void)findFileAndLineForLocation:(NSPoint)point inRect:(NSRect)rect pageBounds:(NSRect)bounds atPageIndex:(NSUInteger)pageIndex {
  481. dispatch_async([self queue], ^{
  482. if ([self shouldKeepRunning] && [self loadSyncFileIfNeeded]) {
  483. NSInteger foundLine = 0;
  484. NSString *foundFile = nil;
  485. BOOL success = NO;
  486. if (isPdfsync)
  487. success = [self pdfsyncFindFileLine:&foundLine file:&foundFile forLocation:point inRect:rect pageBounds:bounds atPageIndex:pageIndex];
  488. else
  489. success = [self synctexFindFileLine:&foundLine file:&foundFile forLocation:point inRect:rect pageBounds:bounds atPageIndex:pageIndex];
  490. if (success && [self shouldKeepRunning]) {
  491. dispatch_async(dispatch_get_main_queue(), ^{
  492. [delegate synchronizer:self foundLine:foundLine inFile:foundFile];
  493. });
  494. }
  495. }
  496. });
  497. }
  498. - (void)findPageAndLocationForLine:(NSInteger)line inFile:(NSString *)file options:(NSInteger)options {
  499. if (file == nil)
  500. file = [self defaultSourceFile];
  501. dispatch_async([self queue], ^{
  502. if (file && [self shouldKeepRunning] && [self loadSyncFileIfNeeded]) {
  503. NSUInteger foundPageIndex = NSNotFound;
  504. NSPoint foundPoint = NSZeroPoint;
  505. NSInteger foundOptions = options;
  506. BOOL success = NO;
  507. NSString *fixedFile = [self sourceFileForFileName:file isTeX:YES removeQuotes:NO];
  508. if (isPdfsync)
  509. success = [self pdfsyncFindPage:&foundPageIndex location:&foundPoint forLine:line inFile:fixedFile];
  510. else
  511. success = [self synctexFindPage:&foundPageIndex location:&foundPoint forLine:line inFile:fixedFile];
  512. if (success && [self shouldKeepRunning]) {
  513. if (isPdfsync)
  514. foundOptions &= ~SKPDFSynchronizerFlippedMask;
  515. else
  516. foundOptions |= SKPDFSynchronizerFlippedMask;
  517. dispatch_async(dispatch_get_main_queue(), ^{
  518. [delegate synchronizer:self foundLocation:foundPoint atPageIndex:foundPageIndex options:foundOptions];
  519. });
  520. }
  521. }
  522. });
  523. }
  524. @end
  525. #pragma mark -
  526. #define STACK_BUFFER_SIZE 256
  527. static BOOL caseInsensitiveStringEqual(const void *item1, const void *item2, NSUInteger (*size)(const void *item)) {
  528. return CFStringCompare(item1, item2, kCFCompareCaseInsensitive | kCFCompareNonliteral) == kCFCompareEqualTo;
  529. }
  530. static NSUInteger caseInsensitiveStringHash(const void *item, NSUInteger (*size)(const void *item)) {
  531. if(item == NULL) return 0;
  532. NSUInteger hash = 0;
  533. CFAllocatorRef allocator = CFGetAllocator(item);
  534. CFIndex len = CFStringGetLength(item);
  535. // use a generous length, in case the lowercase changes the number of characters
  536. UniChar *buffer, stackBuffer[STACK_BUFFER_SIZE];
  537. if (len + 10 >= STACK_BUFFER_SIZE)
  538. buffer = (UniChar *)CFAllocatorAllocate(allocator, (len + 10) * sizeof(UniChar), 0);
  539. else
  540. buffer = stackBuffer;
  541. CFStringGetCharacters(item, CFRangeMake(0, len), buffer);
  542. // If we create the string with external characters, CFStringGetCharactersPtr is guaranteed to succeed; since we're going to call CFStringGetCharacters anyway in fastHash if CFStringGetCharactsPtr fails, let's do it now when we lowercase the string
  543. CFMutableStringRef mutableString = CFStringCreateMutableWithExternalCharactersNoCopy(allocator, buffer, len, len + 10, (buffer != stackBuffer ? allocator : kCFAllocatorNull));
  544. CFStringLowercase(mutableString, NULL);
  545. hash = [(id)CFBridgingRelease(mutableString) hash];
  546. // if we used the allocator, this should free the buffer for us
  547. CFRelease(mutableString);
  548. return hash;
  549. }