#import #import #include #include #include struct FBDoc { NSString * fDocName; CGPDFDocumentRef fDocument; NSString * fLabel; size_t fStartPage; size_t fNumPages; bool fKeepWithPrev; bool fKeepWithNext; FBDoc(NSString * line); FBDoc(NSString * docName, CGPDFDocumentRef doc, NSString * line = nil); ~FBDoc(); }; FBDoc::FBDoc(NSString * line) : fDocName(nil), fDocument(nil), fLabel([line retain]) { } FBDoc::FBDoc(NSString * name, CGPDFDocumentRef doc, NSString * line) : fDocName(name), fDocument(doc), fLabel(line), fStartPage(0), fKeepWithPrev(false), fKeepWithNext(false) { fNumPages = CGPDFDocumentGetNumberOfPages(doc); } typedef std::list FBDocList; size_t gNextPage = 1; FBDocList gIndexList; FILE * gIndex; CGContextRef gBookContext; CFURLRef gBookURL; void AddVerbatimLine(NSString * line) { gIndexList.push_back(new FBDoc(line)); } void AddDocument(NSString * docPath, NSString * label, bool keepWithPrev) { CGPDFDocumentRef doc = nil; if (docPath) { NSURL * url = [NSURL fileURLWithPath:docPath]; doc = CGPDFDocumentCreateWithURL((CFURLRef)url); if (!doc) { fprintf(stderr, "Can't read PDF document %s\n", [docPath UTF8String]); return; } } FBDoc * fbDoc = new FBDoc(docPath, doc, label); if (keepWithPrev) if (FBDoc * prev = gIndexList.back()) prev->fKeepWithNext = fbDoc->fKeepWithPrev = true; gIndexList.push_back(fbDoc); } void SelectDocument(FBDoc * doc) { const float kFontSize = 20.0f; const float kXROff = -40.0f; const float kXLOff = 20.0f; const float kYOff = 20.0f; if (!gBookContext) { CGRect mediaBox = CGPDFDocumentGetMediaBox(doc->fDocument, 1); mediaBox.origin.x = mediaBox.origin.y = 0.0f; gBookContext = CGPDFContextCreateWithURL(gBookURL, &mediaBox, NULL); } doc->fStartPage = gNextPage; for (size_t i = 0; i++ < doc->fNumPages; ) { CGRect pageRect = CGPDFDocumentGetMediaBox(doc->fDocument, i); CGContextBeginPage(gBookContext, &pageRect); CGContextDrawPDFDocument(gBookContext, pageRect, doc->fDocument, i); if (gNextPage > 1) { CGContextSetTextMatrix(gBookContext, CGAffineTransformIdentity); CGContextSelectFont(gBookContext, "MarkerFelt-Thin", kFontSize, kCGEncodingMacRoman); char pageNum[5]; sprintf(pageNum, "%d", gNextPage); const float xPos = pageRect.origin.x + (gNextPage&1) ? (pageRect.size.width+kXROff) : kXLOff; const float yPos = pageRect.origin.y+kYOff; CGContextShowTextAtPoint(gBookContext, xPos, yPos, pageNum, strlen(pageNum)); } ++gNextPage; CGContextEndPage(gBookContext); } } void AssembleBook() { FBDocList::iterator i = gIndexList.begin(); while (i != gIndexList.end()) { if ((*i)->fStartPage || !(*i)->fNumPages) { ++i; // Already mapped, skip } else if (gNextPage & 1) { // // Odd-numbered page, search for next unmapped single page song // for (FBDocList::iterator j=i; j != gIndexList.end(); ++j) if (!(*j)->fStartPage && (*j)->fNumPages == 1 && !(*j)->fKeepWithNext && !(*j)->fKeepWithPrev ) { SelectDocument(*j); if (j==i) ++i; goto found_odd_song; } abort(); found_odd_song: ; } else { // // Even-numbered page, search for multi-page songs or at least two // single page ones. // if ((*i)->fNumPages == 1) { FBDocList::iterator j = i; while (++j != gIndexList.end()) if ((*j)->fStartPage || !(*j)->fNumPages) { continue; // Already used, try next one } else if ((*j)->fNumPages > 1 || (*j)->fKeepWithNext) { break; // Found multi-page, fail } else { // // Found two single page documents to combine // SelectDocument(*i); ++i; SelectDocument(*j); if (j==i) ++j; goto found_even_song; } if (j == gIndexList.end()) { // // At end, use single page // SelectDocument(*i); ++i; goto found_even_song; } } for (FBDocList::iterator j=i; j != gIndexList.end(); ++j) if (!(*j)->fStartPage && ((*j)->fNumPages > 1 || (*j)->fKeepWithNext) ) { SelectDocument(*j); if (j==i) ++i; while (++j != gIndexList.end() && (*j)->fKeepWithPrev) SelectDocument(*j); goto found_even_song; } abort(); found_even_song: ; } } if (gBookContext) CGContextRelease(gBookContext); } void PrintIndexLine(FBDoc * line) { if (line->fLabel) { NSString * s; if (line->fDocument) s = [NSString stringWithFormat:line->fLabel, line->fStartPage]; else s = line->fLabel; NSData * d = [s dataUsingEncoding:NSUTF8StringEncoding]; fwrite([d bytes], 1, [d length], gIndex); fputc('\n', gIndex); } } void PrintIndex() { std::for_each(gIndexList.begin(), gIndexList.end(), PrintIndexLine); } void ProcessIndex(NSString * inIdx) { bool inPrePostLude = true; NSEnumerator * lines = [[inIdx componentsSeparatedByString:@"\n"] objectEnumerator]; NSCharacterSet * nonBlank = [[NSCharacterSet whitespaceCharacterSet] invertedSet]; while (NSString * line = [lines nextObject]) { NSScanner * lineScanner = [NSScanner scannerWithString:line]; if ([lineScanner scanString:@"%%" intoString:nil] && [lineScanner isAtEnd] ) { // // Separate with %% lines // inPrePostLude = !inPrePostLude; } else if (inPrePostLude) { // // Verbatim copy to output // AddVerbatimLine(line); } else { // // In index proper // NSString * doc; NSString * label = nil; bool keepWithPrev = false; [lineScanner setScanLocation:0]; if ([lineScanner scanString:@"%" intoString:nil]) { continue; // Comment line, skip } else if ([lineScanner scanString:@"-" intoString:nil]) { doc = nil; // No document } else { if ([lineScanner scanString:@"+" intoString:nil]) keepWithPrev = true; if (![lineScanner scanCharactersFromSet:nonBlank intoString:&doc]) continue; // Blank line, skip } label = [line substringFromIndex:[lineScanner scanLocation]]; AddDocument(doc, label, keepWithPrev); } } AssembleBook(); PrintIndex(); } int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; if (!argv[1]) { fprintf(stderr, "Usage: FakeBooker \n"); exit(1); } NSString * index = [NSString stringWithUTF8String:argv[1]]; NSString * idxBase = [index stringByDeletingPathExtension]; NSString * idx = [NSString stringWithContentsOfFile:index]; if (!idx) { fprintf(stderr, "Index file `%s' can't be read.\n", argv[1]); exit(2); } NSString * toc = [idxBase stringByAppendingString:@"_index.pdf"]; if ([[NSFileManager defaultManager] fileExistsAtPath:toc]) AddDocument(toc, nil, false); gIndex = fopen([[[toc stringByDeletingPathExtension] stringByAppendingPathExtension:@"tex"] UTF8String], "w"); gBookURL = (CFURLRef)[NSURL fileURLWithPath: [idxBase stringByAppendingPathExtension:@"pdf"]]; ProcessIndex(idx); [pool release]; return 0; }