You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

282 lines
7.0 KiB

  1. #import <Foundation/Foundation.h>
  2. #import <Quartz/Quartz.h>
  3. #include <list>
  4. #include <algorithm>
  5. #include <stdio.h>
  6. struct FBDoc {
  7. NSString * fDocName;
  8. CGPDFDocumentRef fDocument;
  9. NSString * fLabel;
  10. size_t fStartPage;
  11. size_t fNumPages;
  12. bool fKeepWithPrev;
  13. bool fKeepWithNext;
  14. FBDoc(NSString * line);
  15. FBDoc(NSString * docName, CGPDFDocumentRef doc, NSString * line = nil);
  16. ~FBDoc();
  17. };
  18. FBDoc::FBDoc(NSString * line)
  19. : fDocName(nil), fDocument(nil), fLabel([line retain])
  20. {
  21. }
  22. FBDoc::FBDoc(NSString * name, CGPDFDocumentRef doc, NSString * line)
  23. : fDocName(name), fDocument(doc), fLabel(line),
  24. fStartPage(0), fKeepWithPrev(false), fKeepWithNext(false)
  25. {
  26. fNumPages = CGPDFDocumentGetNumberOfPages(doc);
  27. }
  28. typedef std::list<FBDoc *> FBDocList;
  29. size_t gNextPage = 1;
  30. FBDocList gIndexList;
  31. FILE * gIndex;
  32. CGContextRef gBookContext;
  33. CFURLRef gBookURL;
  34. void AddVerbatimLine(NSString * line)
  35. {
  36. gIndexList.push_back(new FBDoc(line));
  37. }
  38. void AddDocument(NSString * docPath, NSString * label, bool keepWithPrev)
  39. {
  40. CGPDFDocumentRef doc = nil;
  41. if (docPath) {
  42. NSURL * url = [NSURL fileURLWithPath:docPath];
  43. doc = CGPDFDocumentCreateWithURL((CFURLRef)url);
  44. if (!doc) {
  45. fprintf(stderr, "Can't read PDF document %s\n", [docPath UTF8String]);
  46. return;
  47. }
  48. }
  49. FBDoc * fbDoc = new FBDoc(docPath, doc, label);
  50. if (keepWithPrev)
  51. if (FBDoc * prev = gIndexList.back())
  52. prev->fKeepWithNext = fbDoc->fKeepWithPrev = true;
  53. gIndexList.push_back(fbDoc);
  54. }
  55. void SelectDocument(FBDoc * doc)
  56. {
  57. const float kFontSize = 20.0f;
  58. const float kXROff = -40.0f;
  59. const float kXLOff = 20.0f;
  60. const float kYOff = 20.0f;
  61. if (!gBookContext) {
  62. CGRect mediaBox = CGPDFDocumentGetMediaBox(doc->fDocument, 1);
  63. mediaBox.origin.x = mediaBox.origin.y = 0.0f;
  64. gBookContext = CGPDFContextCreateWithURL(gBookURL, &mediaBox, NULL);
  65. }
  66. doc->fStartPage = gNextPage;
  67. for (size_t i = 0; i++ < doc->fNumPages; ) {
  68. CGRect pageRect = CGPDFDocumentGetMediaBox(doc->fDocument, i);
  69. CGContextBeginPage(gBookContext, &pageRect);
  70. CGContextDrawPDFDocument(gBookContext, pageRect, doc->fDocument, i);
  71. if (gNextPage > 1) {
  72. CGContextSetTextMatrix(gBookContext, CGAffineTransformIdentity);
  73. CGContextSelectFont(gBookContext, "MarkerFelt-Thin", kFontSize,
  74. kCGEncodingMacRoman);
  75. char pageNum[5];
  76. sprintf(pageNum, "%d", gNextPage);
  77. const float xPos = pageRect.origin.x
  78. + (gNextPage&1) ? (pageRect.size.width+kXROff) : kXLOff;
  79. const float yPos = pageRect.origin.y+kYOff;
  80. CGContextShowTextAtPoint(gBookContext, xPos, yPos,
  81. pageNum, strlen(pageNum));
  82. }
  83. ++gNextPage;
  84. CGContextEndPage(gBookContext);
  85. }
  86. }
  87. void AssembleBook()
  88. {
  89. FBDocList::iterator i = gIndexList.begin();
  90. while (i != gIndexList.end()) {
  91. if ((*i)->fStartPage || !(*i)->fNumPages) {
  92. ++i; // Already mapped, skip
  93. } else if (gNextPage & 1) {
  94. //
  95. // Odd-numbered page, search for next unmapped single page song
  96. //
  97. for (FBDocList::iterator j=i; j != gIndexList.end(); ++j)
  98. if (!(*j)->fStartPage && (*j)->fNumPages == 1
  99. && !(*j)->fKeepWithNext && !(*j)->fKeepWithPrev
  100. ) {
  101. SelectDocument(*j);
  102. if (j==i)
  103. ++i;
  104. goto found_odd_song;
  105. }
  106. abort();
  107. found_odd_song:
  108. ;
  109. } else {
  110. //
  111. // Even-numbered page, search for multi-page songs or at least two
  112. // single page ones.
  113. //
  114. if ((*i)->fNumPages == 1) {
  115. FBDocList::iterator j = i;
  116. while (++j != gIndexList.end())
  117. if ((*j)->fStartPage || !(*j)->fNumPages) {
  118. continue; // Already used, try next one
  119. } else if ((*j)->fNumPages > 1 || (*j)->fKeepWithNext) {
  120. break; // Found multi-page, fail
  121. } else {
  122. //
  123. // Found two single page documents to combine
  124. //
  125. SelectDocument(*i);
  126. ++i;
  127. SelectDocument(*j);
  128. if (j==i)
  129. ++j;
  130. goto found_even_song;
  131. }
  132. if (j == gIndexList.end()) {
  133. //
  134. // At end, use single page
  135. //
  136. SelectDocument(*i);
  137. ++i;
  138. goto found_even_song;
  139. }
  140. }
  141. for (FBDocList::iterator j=i; j != gIndexList.end(); ++j)
  142. if (!(*j)->fStartPage
  143. && ((*j)->fNumPages > 1 || (*j)->fKeepWithNext)
  144. ) {
  145. SelectDocument(*j);
  146. if (j==i)
  147. ++i;
  148. while (++j != gIndexList.end() && (*j)->fKeepWithPrev)
  149. SelectDocument(*j);
  150. goto found_even_song;
  151. }
  152. abort();
  153. found_even_song:
  154. ;
  155. }
  156. }
  157. if (gBookContext)
  158. CGContextRelease(gBookContext);
  159. }
  160. void PrintIndexLine(FBDoc * line)
  161. {
  162. if (line->fLabel) {
  163. NSString * s;
  164. if (line->fDocument)
  165. s = [NSString stringWithFormat:line->fLabel, line->fStartPage];
  166. else
  167. s = line->fLabel;
  168. NSData * d = [s dataUsingEncoding:NSUTF8StringEncoding];
  169. fwrite([d bytes], 1, [d length], gIndex);
  170. fputc('\n', gIndex);
  171. }
  172. }
  173. void PrintIndex()
  174. {
  175. std::for_each(gIndexList.begin(), gIndexList.end(), PrintIndexLine);
  176. }
  177. void ProcessIndex(NSString * inIdx)
  178. {
  179. bool inPrePostLude = true;
  180. NSEnumerator * lines = [[inIdx componentsSeparatedByString:@"\n"]
  181. objectEnumerator];
  182. NSCharacterSet * nonBlank = [[NSCharacterSet whitespaceCharacterSet]
  183. invertedSet];
  184. while (NSString * line = [lines nextObject]) {
  185. NSScanner * lineScanner = [NSScanner scannerWithString:line];
  186. if ([lineScanner scanString:@"%%" intoString:nil]
  187. && [lineScanner isAtEnd]
  188. ) {
  189. //
  190. // Separate with %% lines
  191. //
  192. inPrePostLude = !inPrePostLude;
  193. } else if (inPrePostLude) {
  194. //
  195. // Verbatim copy to output
  196. //
  197. AddVerbatimLine(line);
  198. } else {
  199. //
  200. // In index proper
  201. //
  202. NSString * doc;
  203. NSString * label = nil;
  204. bool keepWithPrev = false;
  205. [lineScanner setScanLocation:0];
  206. if ([lineScanner scanString:@"%" intoString:nil]) {
  207. continue; // Comment line, skip
  208. } else if ([lineScanner scanString:@"-" intoString:nil]) {
  209. doc = nil; // No document
  210. } else {
  211. if ([lineScanner scanString:@"+" intoString:nil])
  212. keepWithPrev = true;
  213. if (![lineScanner scanCharactersFromSet:nonBlank intoString:&doc])
  214. continue; // Blank line, skip
  215. }
  216. label = [line substringFromIndex:[lineScanner scanLocation]];
  217. AddDocument(doc, label, keepWithPrev);
  218. }
  219. }
  220. AssembleBook();
  221. PrintIndex();
  222. }
  223. int main (int argc, const char * argv[])
  224. {
  225. NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  226. if (!argv[1]) {
  227. fprintf(stderr, "Usage: FakeBooker <index.idx>\n");
  228. exit(1);
  229. }
  230. NSString * index = [NSString stringWithUTF8String:argv[1]];
  231. NSString * idxBase = [index stringByDeletingPathExtension];
  232. NSString * idx = [NSString stringWithContentsOfFile:index];
  233. if (!idx) {
  234. fprintf(stderr, "Index file `%s' can't be read.\n", argv[1]);
  235. exit(2);
  236. }
  237. NSString * toc = [idxBase stringByAppendingString:@"_index.pdf"];
  238. if ([[NSFileManager defaultManager] fileExistsAtPath:toc])
  239. AddDocument(toc, nil, false);
  240. gIndex = fopen([[[toc stringByDeletingPathExtension]
  241. stringByAppendingPathExtension:@"tex"]
  242. UTF8String], "w");
  243. gBookURL = (CFURLRef)[NSURL fileURLWithPath:
  244. [idxBase stringByAppendingPathExtension:@"pdf"]];
  245. ProcessIndex(idx);
  246. [pool release];
  247. return 0;
  248. }