FakeBooker/FakeBooker.mm

282 rader
7.0 KiB
Plaintext

#import <Foundation/Foundation.h>
#import <Quartz/Quartz.h>
#include <list>
#include <algorithm>
#include <stdio.h>
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<FBDoc *> 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 <index.idx>\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;
}