diff --git a/utils/pdftoppm.cc b/utils/pdftoppm.cc index 04a0dfb..3eea765 100644 --- a/utils/pdftoppm.cc +++ b/utils/pdftoppm.cc @@ -35,6 +35,9 @@ #ifdef _WIN32 #include // for O_BINARY #include // for setmode +#else +#include +#include #endif #include #include @@ -193,6 +196,9 @@ static void savePageSlice(PDFDoc *doc, 0, !useCropBox, gFalse, gFalse, x, y, w, h +#ifdef USE_THREADS + , NULL, NULL, NULL, NULL, gTrue +#endif ); SplashBitmap *bitmap = splashOut->getBitmap(); @@ -237,6 +243,454 @@ static int numberOfCharacters(unsigned int n) charNum++; return charNum; } +#ifdef USE_THREADS +typedef struct { + PDFDoc *doc; /* the PDF document */ + double resolution; + double x_resolution; + double y_resolution; + int pg; + int pg_num_len; + char *ppmRoot; +#ifdef _WIN32 + HANDLE thread_h; /* a handle to the thread being executed */ + DWORD thread_id; /* contains the thread number */ +#else + pthread_t thread_h; /* a handle to the thread being executed */ + pthread_t thread_id; /* contains the thread number */ +#endif +} page_info; + +static page_info * threads; + +#define MAX_THREADS 5 + +void ThreadTerminate(page_info * thread); + +void processPage(page_info * pageInfo) { + SplashColor paperColor; + SplashOutputDev *splashOut; + double pg_w, pg_h, tmp; + char *ppmFile; +#if SPLASH_CMYK + if (jpegcmyk || overprint) { + globalParams->setOverprintPreview(gTrue); + paperColor[0] = 0; + paperColor[1] = 0; + paperColor[2] = 0; + paperColor[3] = 0; + } else +#endif + { + paperColor[0] = 255; + paperColor[1] = 255; + paperColor[2] = 255; + } + splashOut = new SplashOutputDev(mono ? splashModeMono1 : + gray ? splashModeMono8 : +#if SPLASH_CMYK + (jpegcmyk || overprint) ? splashModeCMYK8 : +#endif + splashModeRGB8, 4, + gFalse, paperColor); + splashOut->startDoc(pageInfo->doc); + if (useCropBox) { + pg_w = pageInfo->doc->getPageCropWidth(pageInfo->pg); + pg_h = pageInfo->doc->getPageCropHeight(pageInfo->pg); + } else { + pg_w = pageInfo->doc->getPageMediaWidth(pageInfo->pg); + pg_h = pageInfo->doc->getPageMediaHeight(pageInfo->pg); + } + if (scaleTo != 0) { + pageInfo->resolution = (72.0 * scaleTo) / (pg_w > pg_h ? pg_w : pg_h); + pageInfo->x_resolution = pageInfo->y_resolution = pageInfo->resolution; + } else { + if (x_scaleTo > 0) { + pageInfo->x_resolution = (72.0 * x_scaleTo) / pg_w; + if (y_scaleTo == -1) + pageInfo->y_resolution = pageInfo->x_resolution; + } + if (y_scaleTo > 0) { + pageInfo->y_resolution = (72.0 * y_scaleTo) / pg_h; + if (x_scaleTo == -1) + pageInfo->x_resolution = pageInfo->y_resolution; + } + } + pg_w = pg_w * (pageInfo->x_resolution / 72.0); + pg_h = pg_h * (pageInfo->y_resolution / 72.0); + if ((pageInfo->doc->getPageRotate(pageInfo->pg) == 90) || (pageInfo->doc->getPageRotate(pageInfo->pg) == 270)) { + tmp = pg_w; + pg_w = pg_h; + pg_h = tmp; + } + if (pageInfo->ppmRoot != NULL) { + const char *ext = png ? "png" : (jpeg || jpegcmyk) ? "jpg" : tiff ? "tif" : mono ? "pbm" : gray ? "pgm" : "ppm"; + if (singleFile) { + ppmFile = new char[strlen(pageInfo->ppmRoot) + 1 + strlen(ext) + 1]; + sprintf(ppmFile, "%s.%s", pageInfo->ppmRoot, ext); + } else { + ppmFile = new char[strlen(pageInfo->ppmRoot) + 1 + pageInfo->pg_num_len + 1 + strlen(ext) + 1]; + sprintf(ppmFile, "%s-%0*d.%s", pageInfo->ppmRoot, pageInfo->pg_num_len, pageInfo->pg, ext); + } + savePageSlice(pageInfo->doc, splashOut, pageInfo->pg, x, y, w, h, pg_w, pg_h, ppmFile); + delete[] ppmFile; + } else { + savePageSlice(pageInfo->doc, splashOut, pageInfo->pg, x, y, w, h, pg_w, pg_h, NULL); + } + delete splashOut; + pageInfo->thread_h = NULL; + ThreadTerminate(pageInfo); +} + +#ifdef _WIN32 +SECURITY_ATTRIBUTES sa; +SECURITY_DESCRIPTOR sd; +PACL acl; +DWORD aclsize; +PSID sid; + +/*------------------------------------------------------------------------*\ + GetUserSid +\*------------------------------------------------------------------------*/ +// This function is used on Windows NT to obtain security information +// for the currently logged in user. +// This user profile is need in order to change access rigth (ACL) on +// threads so that status querying is allowed. + +PSID GetUserSid() +{ + HANDLE hToken; + DWORD cbBuffer, cbRequired; + PTOKEN_USER pUserInfo; + PSID pUserSid; + + // The User's SID can be obtained from the process token + // + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) + return NULL; + + // Set buffer size to 0 for first call to determine + // the size of buffer we need. + // + cbBuffer = 0; + if (GetTokenInformation(hToken, TokenUser, NULL, cbBuffer, &cbRequired)) + return NULL; + + // Allocate a buffer for our token user data + // + cbBuffer = cbRequired; + pUserInfo = (PTOKEN_USER) HeapAlloc(GetProcessHeap(), 0, cbBuffer); + if (NULL == pUserInfo) { + fprintf(stderr, "HeapAlloc failed for UserInfo.\n"); + fflush(stderr); + return NULL; + } + + // Make the "real" call + // + if (!GetTokenInformation(hToken, TokenUser, pUserInfo, cbBuffer, &cbRequired)) + return NULL; + + // Make another copy of the SID for the return value + // + cbBuffer = GetLengthSid(pUserInfo->User.Sid); + + pUserSid = (PSID) HeapAlloc(GetProcessHeap(), 0, cbBuffer); + if (NULL == pUserSid) { + fprintf(stderr, "HeapAlloc failed for UserSid.\n"); + fflush(stderr); + return NULL; + } + + if (! CopySid(cbBuffer, pUserSid, pUserInfo->User.Sid)) + return NULL; + + if (!HeapFree(GetProcessHeap(), 0, pUserInfo)) { + fprintf(stderr, "Warning ! - HeapFree failed for UserInfo.\n"); + fflush(stderr); + } + + return pUserSid; +} + +//------------------------------------------------------------------------ +// Initialize +//------------------------------------------------------------------------ +GBool Initialize() { + + // In order to handle threads properly, so that we e.g. can ask for + // progress status, if a thread is still running and so on within + // WINDOWS we have to assign a Security attribut when creating a new + // thread. + + // Get the security information for the current user + sid = GetUserSid(); + + if (sid == NULL) { + fprintf(stderr,"Unable to obtain security information for current user\n"); + fflush(stderr); + return(0); + } + + // Now we have to create an access control list (ACL), which allows us to + // query the threads for their status, first we determine the size to the + // ACL and then allocate memory for it. After that the ACL is initialized. + aclsize = GetLengthSid(sid) + sizeof(ACL) + sizeof(THREAD_QUERY_INFORMATION); + + if((acl = (PACL) malloc(aclsize)) == NULL) + return(0); + + if (!InitializeAcl(acl,aclsize,ACL_REVISION)) { + fprintf(stderr,"Unable to initialize access control list object\n"); + fflush(stderr); + return(0); + } + + // An this point we add an access control entry which allows us to query + // the threads to the ACL in the context of the security information for + // the current user + if (AddAccessAllowedAce(acl,ACL_REVISION,THREAD_QUERY_INFORMATION,sid )) { + fprintf(stderr,"Unable to Add new access control list object\n"); + fflush(stderr); + return(0); + } + + // The extended ACL has to be assigned to a security descriptor, so first we + // have to initialize a security descriptor and the add the ACL to this + // security descriptor. + if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) { + fprintf(stderr,"Unable to Initialize Security Descriptor\n"); + fflush(stderr); + return(0); + } + + if (!SetSecurityDescriptorDacl(&sd,TRUE, acl, FALSE)) { + fprintf(stderr,"Unable to Add new access control list object to Security Descriptor\n"); + fflush(stderr); + return(0); + } + + // Finally the security descriptor is added to a security attribute, and we + // allow the security attribute to be inherited. That is needed because otherwise + // the threads will be created with default security attribute, which doesn't allow + // querying threads. + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = &sd; + sa.bInheritHandle = TRUE; + + return TRUE; +} + +/*------------------------------------------------------------------------*\ + ThreadTerminate +\*------------------------------------------------------------------------*/ + +void ThreadTerminate(page_info * thread) +{ + ExitThread(0); +} + +//------------------------------------------------------------------------ +// FinishAllThreads +//------------------------------------------------------------------------ + +void FinishAllThreads(page_info ** thread) +// wait until all threads in the global array threads has terminated. +{ + for (int i = 0; i < MAX_THREADS; i++) { + if (threads[i].thread_h != NULL) { + DWORD exitcode; + while (GetExitCodeThread(threads[i].thread_h,&exitcode) + && (exitcode == STILL_ACTIVE)) + Sleep(100); + } + } +} + +/*------------------------------------------------------------------------*\ + spawnPageThread +\*------------------------------------------------------------------------*/ +void spawnPageThread(page_info ** pageInfo) +// wait until all threads in the global array threads has terminated. +{ + + (*pageInfo)->thread_h = CreateThread(&sa, 0, (LPTHREAD_START_ROUTINE)processPage, + *pageInfo,0,&(*pageInfo)->thread_id); + // In order to obtain better performance we change the priority of the + // thread to THREAD_PRIORITY_TIME_CRITICAL, in order to get as much CPU + // time as possible. + SetThreadPriority((*pageInfo)->thread_h,(DWORD)THREAD_PRIORITY_TIME_CRITICAL); +} + +//------------------------------------------------------------------------ +// findFinishThread +//------------------------------------------------------------------------ + +GBool findFinishThread(page_info ** thread) +// Find first non active entry i Global array of threads +{ + int i; + DWORD exitcode; + + *thread = NULL; + + for (i = 0; i < MAX_THREADS; i++) { + if (threads[i].thread_h == NULL) { + *thread = &threads[i]; + fprintf(stderr,"Starting thread no '%d'\n",i); + return TRUE; + } + else if (GetExitCodeThread(threads[i].thread_h,&exitcode) + && (exitcode != STILL_ACTIVE)) { + if (!CloseHandle(threads[i].thread_h)) { + fprintf(stderr,"Error, unable to close thread handle\n"); + fflush(stderr); + return FALSE; + } else { + fprintf(stderr,"Closed thread handle '%d'\n",i); + fflush(stderr); + } + fprintf(stderr,"Starting thread no '%d'\n",i); + fflush(stderr); + *thread = &threads[i]; + return TRUE; + } + } + + return FALSE; +} +#else +long noOfThreads; +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +//------------------------------------------------------------------------ +// Initialize +//------------------------------------------------------------------------ +GBool Initialize() { + // The mutex object has to be initialized along with the global + // varible noOfThreads, which indicates the currently number of + // running threads. + pthread_mutexattr_t mtype; + + noOfThreads = 0; + + pthread_mutexattr_init(&mtype); + pthread_mutexattr_settype(&mtype, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mutex, &mtype); + + return gTrue; +} + +/*------------------------------------------------------------------------*\ + findFinishThread +\*------------------------------------------------------------------------*/ + +GBool findFinishThread(page_info ** thread) +// Find first non active entry i Global array of threads +{ + int i; + + *thread = NULL; + + if (pthread_mutex_lock(&mutex) == 0) { + if (noOfThreads >= MAX_THREADS ) { + pthread_mutex_unlock(&mutex); + return gFalse; + } else { + noOfThreads++; + for (i = 0; i < MAX_THREADS; i++) { + if (threads[i].thread_id == 0) { + *thread = &threads[i]; + fprintf(stderr,"Starting thread no '%d'\n",i); + fflush(stderr); + break; + } + } + pthread_mutex_unlock(&mutex); + return gTrue; + } + + } else + return gFalse; +} + +/*------------------------------------------------------------------------*\ + ThreadTerminate +\*------------------------------------------------------------------------*/ + +void ThreadTerminate(page_info * thread) +{ + int status = 0; + pthread_mutex_lock(&mutex); + + fprintf(stdout,"Terminating thread_id '%ld'\n", + (long) thread->thread_id); + fflush(stdout); + + noOfThreads--; + thread->thread_id = 0; + + pthread_mutex_unlock(&mutex); + pthread_exit(&status); +} + +//------------------------------------------------------------------------ +// FinishAllThreads +//------------------------------------------------------------------------ + +void FinishAllThreads(page_info ** thread) +// wait until all threads in the global array threads has terminated. +{ + int i; + int th_id; + + for (i = 0; i < MAX_THREADS; i++) { + if (threads[i].thread_id != 0) { + fprintf(stderr,"Stopping thread no '%d'\n",i); + fflush(stderr); + do { + sleep(1); + pthread_mutex_lock(&mutex); + th_id = threads[i].thread_id; + pthread_mutex_unlock(&mutex); + } while(th_id != 0); + + } + } +} + +/*------------------------------------------------------------------------*\ + spawnPageThread +\*------------------------------------------------------------------------*/ +void spawnPageThread(page_info ** pageInfo) +{ + int retval; + pthread_attr_t pth_attr; + + pthread_mutex_lock(&mutex); + + retval = pthread_attr_init (&pth_attr); + if (retval != 0) { + fprintf (stderr, "pthread_attr_init() returns %d (errno: %d)\n", retval, errno); + exit (0); + } + retval = pthread_attr_setdetachstate (&pth_attr, PTHREAD_CREATE_DETACHED); + if (retval != 0) { + fprintf (stderr, "pthread_attr_setdetachstate() returns %d (errno: %d)\n", retval, errno); + exit (0); + } + retval = pthread_create(&(*pageInfo)->thread_id, &pth_attr, (void* (*)(void*))processPage, *pageInfo); + if (retval != 0) { + fprintf(stderr, "pthread_create() returns %d (errno: %d)\n", retval, errno); + exit(0); + } + pthread_attr_destroy(&pth_attr); + pthread_mutex_unlock(&mutex); + +} + +#endif +#endif int main(int argc, char *argv[]) { PDFDoc *doc; @@ -250,7 +704,15 @@ int main(int argc, char *argv[]) { int exitCode; int pg, pg_num_len; double pg_w, pg_h, tmp; +#ifdef USE_THREADS + page_info * thread = NULL; + Initialize(); + // Allocate space for structure dealing with threads + // and then initialize it. + threads = (page_info *)malloc(MAX_THREADS * sizeof(page_info)); + memset(threads, 0, MAX_THREADS * sizeof(page_info)); +#endif exitCode = 99; // parse args @@ -349,6 +811,7 @@ int main(int argc, char *argv[]) { lastPage = firstPage; } +#ifndef USE_THREADS // write PPM files #if SPLASH_CMYK if (jpegcmyk || overprint) { @@ -372,11 +835,29 @@ int main(int argc, char *argv[]) { splashModeRGB8, 4, gFalse, paperColor); splashOut->startDoc(doc); +#endif if (sz != 0) w = h = sz; pg_num_len = numberOfCharacters(doc->getNumPages()); for (pg = firstPage; pg <= lastPage; ++pg) { if (printOnlyEven && pg % 2 == 0) continue; if (printOnlyOdd && pg % 2 == 1) continue; +#ifdef USE_THREADS + while (!findFinishThread(&thread)) +#ifdef _WIN32 + Sleep(100); +#else + sleep(1); +#endif + thread->doc = doc; // the PDF document + thread->resolution = resolution; + thread->x_resolution = x_resolution; + thread->y_resolution = y_resolution; + thread->pg = pg; + thread->pg_num_len = pg_num_len; + thread->ppmRoot = ppmRoot; + + spawnPageThread(&thread); +#else if (useCropBox) { pg_w = doc->getPageCropWidth(pg); pg_h = doc->getPageCropHeight(pg); @@ -421,8 +902,13 @@ int main(int argc, char *argv[]) { } else { savePageSlice(doc, splashOut, pg, x, y, w, h, pg_w, pg_h, NULL); } +#endif } +#ifdef USE_THREADS + FinishAllThreads(&thread); +#else delete splashOut; +#endif exitCode = 0;