1 //------------------------------------------------------------------------------
2 // emPsRenderer.cpp
3 //
4 // Copyright (C) 2006-2011,2014,2017-2019 Oliver Hamann.
5 //
6 // Homepage: http://eaglemode.sourceforge.net/
7 //
8 // This program is free software: you can redistribute it and/or modify it under
9 // the terms of the GNU General Public License version 3 as published by the
10 // Free Software Foundation.
11 //
12 // This program is distributed in the hope that it will be useful, but WITHOUT
13 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 // FOR A PARTICULAR PURPOSE. See the GNU General Public License version 3 for
15 // more details.
16 //
17 // You should have received a copy of the GNU General Public License version 3
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
19 //------------------------------------------------------------------------------
20 
21 #include <emPs/emPsRenderer.h>
22 #include <emCore/emInstallInfo.h>
23 
24 
Acquire(emRootContext & rootContext)25 emRef<emPsRenderer> emPsRenderer::Acquire(emRootContext & rootContext)
26 {
27 	EM_IMPL_ACQUIRE_COMMON(emPsRenderer,rootContext,"");
28 }
29 
30 
StartJob(const emPsDocument & document,int pageIndex,emImage & outputImage,double priority,emEngine * listenEngine)31 emPsRenderer::JobHandle emPsRenderer::StartJob(
32 	const emPsDocument & document, int pageIndex,
33 	emImage & outputImage, double priority,
34 	emEngine * listenEngine
35 )
36 {
37 	Job * job;
38 
39 	job=new Job;
40 	job->Document=document;
41 	job->PageIndex=pageIndex;
42 	job->Image=&outputImage;
43 	job->Priority=priority;
44 	job->ListenEngine=listenEngine;
45 	job->State=JS_WAITING;
46 	job->Prev=NULL;
47 	job->Next=NULL;
48 	AddToJobList(job);
49 	PSPriorityValid=false;
50 	WakeUp();
51 	return job;
52 }
53 
54 
SetJobPriority(JobHandle jobHandle,double priority)55 void emPsRenderer::SetJobPriority(JobHandle jobHandle, double priority)
56 {
57 	Job * job;
58 
59 	job=(Job*)jobHandle;
60 	if (job->Priority!=priority) {
61 		job->Priority=priority;
62 		if (job->State==JS_WAITING) {
63 			PSPriorityValid=false;
64 			WakeUp();
65 		}
66 	}
67 }
68 
69 
CloseJob(JobHandle jobHandle)70 void emPsRenderer::CloseJob(JobHandle jobHandle)
71 {
72 	Job * job;
73 
74 	job=(Job*)jobHandle;
75 	if (job->State!=JS_SUCCESS && job->State!=JS_ERROR) {
76 		job->ListenEngine=NULL;
77 		SetJobState(job,JS_ERROR);
78 	}
79 	delete job;
80 }
81 
82 
emPsRenderer(emContext & context,const emString & name)83 emPsRenderer::emPsRenderer(emContext & context, const emString & name)
84 	: emModel(context,name),
85 	Timer(GetScheduler()),
86 	PSAgent(*this)
87 {
88 	SetMinCommonLifetime(5);
89 	PSPriorityValid=false;
90 	FirstJob=NULL;
91 	LastJob=NULL;
92 	MainState=COLD_WAIT_JOB;
93 	CurrentJob=NULL;
94 	CurrentPageIndex=0;
95 	AddWakeUpSignal(Timer.GetSignal());
96 }
97 
98 
~emPsRenderer()99 emPsRenderer::~emPsRenderer()
100 {
101 	while (FirstJob) CloseJob(FirstJob);
102 	if (CurrentJob) CloseJob(CurrentJob);
103 	Process.Terminate();
104 }
105 
106 
Cycle()107 bool emPsRenderer::Cycle()
108 {
109 	bool busy,readProceeded,writeProceeded;
110 	Job * job;
111 	int flags;
112 
113 	busy=false;
114 
115 	switch (MainState) {
116 
117 L_ENTER_COLD_WAIT_JOB:
118 		CurrentDocument.Clear();
119 		PSAgent.ReleaseAccess();
120 		MainState=COLD_WAIT_JOB;
121 	case COLD_WAIT_JOB:
122 		if (FirstJob) goto L_ENTER_COLD_WAIT_ACCESS;
123 		break;
124 
125 L_ENTER_COLD_WAIT_ACCESS:
126 		UpdatePSPriority();
127 		PSAgent.RequestAccess();
128 		MainState=COLD_WAIT_ACCESS;
129 	case COLD_WAIT_ACCESS:
130 		if (!FirstJob) goto L_ENTER_COLD_WAIT_JOB;
131 		if (PSAgent.HasAccess()) goto L_ENTER_PREPARE_PROCESS;
132 		UpdatePSPriority();
133 		break;
134 
135 L_ENTER_PREPARE_PROCESS:
136 		job=SearchBestJob();
137 		if (!job) goto L_ENTER_COLD_WAIT_JOB;
138 		CurrentDocument=job->Document;
139 		try {
140 			TryStartProcess();
141 		}
142 		catch (const emException & exception) {
143 			FailAllJobs(exception.GetText());
144 			goto L_ENTER_COLD_WAIT_JOB;
145 		}
146 		PrepareWritingStartup();
147 		PrepareReadingStartup();
148 		Timer.Start(12000);
149 		MainState=PREPARE_PROCESS;
150 	case PREPARE_PROCESS:
151 		if (!Process.IsRunning()) {
152 			FailDocJobs("PostScript interpretation failed: Interpreter exited.");
153 			goto L_ENTER_QUIT_PROCESS;
154 		}
155 		try {
156 			TryRead();
157 			TryWrite();
158 		}
159 		catch (const emException & exception) {
160 			FailDocJobs(exception.GetText());
161 			goto L_ENTER_QUIT_PROCESS;
162 		}
163 		if (IsReadingFinished()) goto L_ENTER_RUN_JOB;
164 		if (!Timer.IsRunning()) {
165 			FailDocJobs("PostScript interpretation failed: Start-up timed out.");
166 			goto L_ENTER_QUIT_PROCESS;
167 		}
168 		busy=true;
169 		break;
170 
171 L_ENTER_RUN_JOB:
172 		if (CurrentDocument.GetDataRefCount()<=1) goto L_ENTER_QUIT_PROCESS;
173 		job=SearchBestSameDocJob();
174 		if (!job) goto L_ENTER_QUIT_PROCESS;
175 		SetJobState(job,JS_RUNNING);
176 		CurrentPageIndex=CurrentJob->PageIndex;
177 		PrepareWritingPage();
178 		PrepareReadingPage();
179 		Timer.Start(8000);
180 		MainState=RUN_JOB;
181 	case RUN_JOB:
182 		if (!Process.IsRunning()) {
183 			FailDocJobs("PostScript interpretation failed: Interpreter exited.");
184 			goto L_ENTER_QUIT_PROCESS;
185 		}
186 		if (!Timer.IsRunning()) {
187 			FailDocJobs("PostScript interpretation failed: Page timed out.");
188 			goto L_ENTER_QUIT_PROCESS;
189 		}
190 		for (;;) {
191 			try {
192 				readProceeded=TryRead();
193 				writeProceeded=TryWrite();
194 			}
195 			catch (const emException & exception) {
196 				FailDocJobs(exception.GetText());
197 				goto L_ENTER_QUIT_PROCESS;
198 			}
199 			if (IsReadingFinished()) {
200 				if (CurrentJob) SetJobState(CurrentJob,JS_SUCCESS);
201 				goto L_ENTER_HOT_WAIT_JOB;
202 			}
203 			if (IsTimeSliceAtEnd()) break;
204 			if (!readProceeded && !writeProceeded) {
205 				flags=emProcess::WF_WAIT_STDOUT;
206 				if (!IsWritingFinished()) flags|=emProcess::WF_WAIT_STDIN;
207 				Process.WaitPipes(flags,10);
208 			}
209 		}
210 		busy=true;
211 		break;
212 
213 L_ENTER_HOT_WAIT_JOB:
214 		PSAgent.ReleaseAccess();
215 		Timer.Start(3000);
216 		MainState=HOT_WAIT_JOB;
217 	case HOT_WAIT_JOB:
218 		if (CurrentDocument.GetDataRefCount()<=1) goto L_ENTER_QUIT_PROCESS;
219 		if (FirstJob) goto L_ENTER_HOT_WAIT_ACCESS;
220 		if (!Timer.IsRunning()) goto L_ENTER_QUIT_PROCESS;
221 		busy=true;
222 		break;
223 
224 L_ENTER_HOT_WAIT_ACCESS:
225 		UpdatePSPriority();
226 		PSAgent.RequestAccess();
227 		MainState=HOT_WAIT_ACCESS;
228 	case HOT_WAIT_ACCESS:
229 		if (CurrentDocument.GetDataRefCount()<=1) goto L_ENTER_QUIT_PROCESS;
230 		if (!FirstJob) goto L_ENTER_QUIT_PROCESS;
231 		if (PSAgent.HasAccess()) goto L_ENTER_RUN_JOB;
232 		UpdatePSPriority();
233 		busy=true;
234 		break;
235 
236 L_ENTER_QUIT_PROCESS:
237 		CurrentDocument.Clear();
238 		PSAgent.ReleaseAccess();
239 		Process.CloseWriting();
240 		Process.CloseReading();
241 		Process.SendTerminationSignal();
242 		Timer.Start(10000);
243 		MainState=QUIT_PROCESS;
244 	case QUIT_PROCESS:
245 		if (!Process.IsRunning()) goto L_ENTER_COLD_WAIT_JOB;
246 		if (!Timer.IsRunning()) {
247 			FailAllJobs(
248 				"Failed to terminate PostScript interpreter after previous job."
249 			);
250 			Timer.Start(10000);
251 		}
252 		busy=true;
253 		break;
254 	}
255 
256 	return busy;
257 }
258 
259 
AddToJobList(Job * job)260 void emPsRenderer::AddToJobList(Job * job)
261 {
262 	job->Prev=LastJob;
263 	job->Next=NULL;
264 	if (LastJob) LastJob->Next=job; else FirstJob=job;
265 	LastJob=job;
266 }
267 
268 
RemoveFromJobList(Job * job)269 void emPsRenderer::RemoveFromJobList(Job * job)
270 {
271 	if (job->Prev) job->Prev->Next=job->Next;
272 	else FirstJob=job->Next;
273 	if (job->Next) job->Next->Prev=job->Prev;
274 	else LastJob=job->Prev;
275 	job->Prev=NULL;
276 	job->Next=NULL;
277 }
278 
279 
SearchBestJob()280 emPsRenderer::Job * emPsRenderer::SearchBestJob()
281 {
282 	Job * job, * bestJob;
283 	double bestPri;
284 
285 	bestJob=FirstJob;
286 	if (bestJob) {
287 		bestPri=bestJob->Priority;
288 		for (job=bestJob->Next; job; job=job->Next) {
289 			if (bestPri<job->Priority) {
290 				bestPri=job->Priority;
291 				bestJob=job;
292 			}
293 		}
294 	}
295 	return bestJob;
296 }
297 
298 
SearchBestSameDocJob()299 emPsRenderer::Job * emPsRenderer::SearchBestSameDocJob()
300 {
301 	Job * job, * bestJob;
302 
303 	for (bestJob=FirstJob; bestJob; bestJob=bestJob->Next) {
304 		if (CurrentDocument==bestJob->Document) break;
305 	}
306 	if (bestJob) {
307 		for (job=bestJob->Next; job; job=job->Next) {
308 			if (bestJob->Priority<job->Priority && bestJob->Document==job->Document) {
309 				bestJob=job;
310 			}
311 		}
312 	}
313 	return bestJob;
314 }
315 
316 
SetJobState(Job * job,JobState state,emString errorText)317 void emPsRenderer::SetJobState(Job * job, JobState state, emString errorText)
318 {
319 	switch (job->State) {
320 	case JS_WAITING:
321 		RemoveFromJobList(job);
322 		PSPriorityValid=false;
323 		WakeUp();
324 		break;
325 	case JS_RUNNING:
326 		CurrentJob=NULL;
327 		break;
328 	default:
329 		break;
330 	}
331 
332 	job->State=state;
333 	job->ErrorText=errorText;
334 	if (job->ListenEngine) job->ListenEngine->WakeUp();
335 
336 	switch (job->State) {
337 	case JS_WAITING:
338 		AddToJobList(job);
339 		PSPriorityValid=false;
340 		WakeUp();
341 		break;
342 	case JS_RUNNING:
343 		CurrentJob=job;
344 		break;
345 	default:
346 		break;
347 	}
348 }
349 
350 
FailCurrentJob(emString errorMessage)351 void emPsRenderer::FailCurrentJob(emString errorMessage)
352 {
353 	if (CurrentJob) SetJobState(CurrentJob,JS_ERROR,errorMessage);
354 }
355 
356 
FailDocJobs(emString errorMessage)357 void emPsRenderer::FailDocJobs(emString errorMessage)
358 {
359 	Job * * pJob;
360 	Job * job;
361 
362 	for (pJob=&FirstJob;;) {
363 		job=*pJob;
364 		if (!job) break;
365 		if (job->Document==CurrentDocument) {
366 			SetJobState(job,JS_ERROR,errorMessage);
367 		}
368 		else {
369 			pJob=&job->Next;
370 		}
371 	}
372 	if (CurrentJob) SetJobState(CurrentJob,JS_ERROR,errorMessage);
373 }
374 
375 
FailAllJobs(emString errorMessage)376 void emPsRenderer::FailAllJobs(emString errorMessage)
377 {
378 	while (FirstJob) SetJobState(FirstJob,JS_ERROR,errorMessage);
379 	if (CurrentJob) SetJobState(CurrentJob,JS_ERROR,errorMessage);
380 }
381 
382 
UpdatePSPriority()383 void emPsRenderer::UpdatePSPriority()
384 {
385 	Job * job;
386 	double pri;
387 
388 	if (!PSPriorityValid) {
389 		job=SearchBestJob();
390 		if (job) pri=job->Priority;
391 		else pri=0.0;
392 		PSAgent.SetAccessPriority(pri);
393 		PSPriorityValid=true;
394 	}
395 }
396 
397 
TryStartProcess()398 void emPsRenderer::TryStartProcess()
399 {
400 	emArray<emString> args;
401 
402 #if defined(_WIN32)
403 	args.Add(
404 		emGetChildPath(
405 			emGetInstallPath(EM_IDT_LIB,"emPs","emPs"),
406 			"emPsWinAdapterProc"
407 		)
408 	);
409 	const char * p=getenv("EM_DIR");
410 	if (!p) throw emException("emPsRenderer: EM_DIR not set.");
411 	emString gsPath=emString(p)+"\\thirdparty\\bin\\gs.exe";
412 	if (!emIsRegularFile(gsPath)) {
413 		// Otherwise we get a quite stupid error message.
414 		throw emException("emPsRenderer: Cannot not find %s",gsPath.Get());
415 	}
416 	args.Add(gsPath);
417 #else
418 	args.Add("gs");
419 #endif
420 	args.Add("-q");
421 	args.Add("-dNOPAUSE");
422 	args.Add("-dSAFER");
423 	args.Add("-sDEVICE=ppmraw");
424 	args.Add("-dTextAlphaBits=1");     // emPsPagePanel performs some
425 	args.Add("-dGraphicsAlphaBits=1"); // kind of anti-aliasing. Therefore
426 	args.Add("-dNOINTERPOLATE");       // it's disabled here.
427 	args.Add("-dAlignToPixels=0");
428 	args.Add("-r72.0x72.0"); // Dummy values (adapted for each page by commands).
429 	args.Add("-g612x792");   // Dummy values (adapted for each page by commands).
430 	args.Add("-sOutputFile=-");
431 	args.Add("-_"); // "-" or "-_"?
432 
433 	Process.TryStart(
434 		args,
435 		emArray<emString>(),
436 		NULL,
437 		emProcess::SF_PIPE_STDIN|
438 		emProcess::SF_PIPE_STDOUT|
439 		emProcess::SF_SHARE_STDERR|
440 		emProcess::SF_NO_WINDOW
441 	);
442 }
443 
444 
PrepareWritingStartup()445 void emPsRenderer::PrepareWritingStartup()
446 {
447 	WriterState=WRITING_STARTUP;
448 	WriterPos=0;
449 	WriteCommand.Clear();
450 }
451 
452 
PrepareWritingPage()453 void emPsRenderer::PrepareWritingPage()
454 {
455 	double rx,ry,rt;
456 	int w,h,t;
457 
458 	if (CurrentJob && CurrentJob->Image) {
459 		w=CurrentJob->Image->GetWidth();
460 		h=CurrentJob->Image->GetHeight();
461 	}
462 	else {
463 		w=10;
464 		h=10;
465 	}
466 	rx=w*72.0/CurrentDocument.GetPageWidth(CurrentPageIndex);
467 	ry=h*72.0/CurrentDocument.GetPageHeight(CurrentPageIndex);
468 	if (CurrentDocument.IsLandscapePage(CurrentPageIndex)) {
469 		rt=rx; rx=ry; ry=rt;
470 		t=w; w=h; h=t;
471 	}
472 	WriteCommand=emString::Format(
473 		"\nmark /HWSize [%d %d] /HWResolution [%f %f] currentdevice putdeviceprops pop\n",
474 		w,h,
475 		rx,ry
476 	);
477 	WriterState=WRITING_PAGE_SIZE;
478 	WriterPos=0;
479 }
480 
481 
TryWrite()482 bool emPsRenderer::TryWrite()
483 {
484 	const char * buf;
485 	int len;
486 
487 	switch (WriterState) {
488 	case WRITING_STARTUP:
489 		buf=CurrentDocument.GetStartupScriptPtr();
490 		len=CurrentDocument.GetStartupScriptLen();
491 		if (WriterPos>=len) goto L_ENTER_WRITING_SYNC;
492 		break;
493 
494 	case WRITING_PAGE_SIZE:
495 		buf=WriteCommand.Get();
496 		len=strlen(buf);
497 		if (WriterPos>=len) goto L_ENTER_WRITING_PAGE;
498 		break;
499 
500 L_ENTER_WRITING_PAGE:
501 		WriterPos=0;
502 		WriterState=WRITING_PAGE;
503 	case WRITING_PAGE:
504 		buf=CurrentDocument.GetPageScriptPtr(CurrentPageIndex);
505 		len=CurrentDocument.GetPageScriptLen(CurrentPageIndex);
506 		if (WriterPos>=len) goto L_ENTER_WRITING_SYNC;
507 		break;
508 
509 L_ENTER_WRITING_SYNC:
510 		WriteCommand=emString::Format(
511 			"\n(%s) print\nflush\n",
512 			SyncString
513 		);
514 		WriterPos=0;
515 		WriterState=WRITING_SYNC;
516 	case WRITING_SYNC:
517 		buf=WriteCommand.Get();
518 		len=strlen(buf);
519 		if (WriterPos>=len) WriterState=WRITING_FINISHED;
520 		break;
521 
522 	default:
523 		buf=NULL;
524 		len=0;
525 	}
526 
527 	len=Process.TryWrite(buf+WriterPos,len-WriterPos);
528 	if (len<0) {
529 		throw emException(
530 			"PostScript interpretation failed: Interpreter closed STDIN or exited."
531 		);
532 	}
533 	if (len==0) return false;
534 	WriterPos+=len;
535 	return true;
536 }
537 
538 
PrepareReadingStartup()539 void emPsRenderer::PrepareReadingStartup()
540 {
541 	ReaderState=READING_SYNC;
542 	ReadBufferFill=0;
543 	RdSyncSearchPos=0;
544 }
545 
546 
PrepareReadingPage()547 void emPsRenderer::PrepareReadingPage()
548 {
549 	ReaderState=READING_IMAGE_HEADER;
550 	ReadBufferFill=0;
551 	RdSyncSearchPos=0;
552 }
553 
554 
TryRead()555 bool emPsRenderer::TryRead()
556 {
557 	int len,syncLen,eat,r;
558 	bool syncFound;
559 	const char * p;
560 
561 	if (ReadBufferFill>=(int)sizeof(ReadBuffer)) {
562 		throw emException("PostScript interpretation failed: Read buffer too small.");
563 	}
564 	len=Process.TryRead(
565 		ReadBuffer+ReadBufferFill,
566 		sizeof(ReadBuffer)-ReadBufferFill
567 	);
568 	if (len<0) {
569 		throw emException(
570 			"PostScript interpretation failed: Interpreter closed STDOUT or exited."
571 		);
572 	}
573 	if (len==0) return false;
574 	ReadBufferFill+=len;
575 
576 	syncLen=strlen(SyncString);
577 	syncFound=false;
578 	while (RdSyncSearchPos+syncLen<=ReadBufferFill) {
579 		p=(const char*)memchr(
580 			ReadBuffer+RdSyncSearchPos,
581 			SyncString[0],
582 			ReadBufferFill-RdSyncSearchPos
583 		);
584 		if (!p) {
585 			RdSyncSearchPos=ReadBufferFill;
586 			break;
587 		}
588 		RdSyncSearchPos=p-ReadBuffer;
589 		if (RdSyncSearchPos+syncLen>ReadBufferFill) break;
590 		if (memcmp(ReadBuffer+RdSyncSearchPos,SyncString,syncLen)==0) {
591 			syncFound=true;
592 			break;
593 		}
594 		RdSyncSearchPos++;
595 	}
596 
597 	eat=0;
598 	len=RdSyncSearchPos;
599 	switch (ReaderState) {
600 	case READING_IMAGE_HEADER:
601 		while (len>0) {
602 			r=ParseImageHeader(ReadBuffer+eat,len-eat);
603 			if (r<0) {
604 				eat++;
605 			}
606 			else if (r==0) {
607 				if (len-eat>1024) eat++;
608 				else break;
609 			}
610 			else {
611 				eat+=r;
612 				goto L_ENTER_READING_IMAGE_DATA;
613 			}
614 		}
615 		break;
616 
617 L_ENTER_READING_IMAGE_DATA:
618 		RdImgX=0;
619 		RdImgY=0;
620 		RdImgDone=false;
621 		ReaderState=READING_IMAGE_DATA;
622 	case READING_IMAGE_DATA:
623 		while (len>0) {
624 			r=ParseImageData(ReadBuffer+eat,len-eat);
625 			if (r<0) {
626 				throw emException(
627 					"PostScript interpretation failed: Image data confusion."
628 				);
629 			}
630 			else if (r==0) {
631 				if (len-eat>1024) {
632 					throw emException(
633 						"PostScript interpretation failed: Image parser faulty."
634 					);
635 				}
636 				break;
637 			}
638 			else {
639 				eat+=r;
640 			}
641 			if (RdImgDone) goto L_ENTER_READING_SYNC;
642 		}
643 		break;
644 
645 L_ENTER_READING_SYNC:
646 		ReaderState=READING_SYNC;
647 	case READING_SYNC:
648 		if (syncFound) {
649 			ReaderState=READING_FINISHED;
650 			eat=ReadBufferFill;
651 		}
652 		else {
653 			eat=RdSyncSearchPos;
654 		}
655 		break;
656 
657 	default:
658 		eat=ReadBufferFill;
659 		break;
660 	}
661 
662 	if (syncFound && ReaderState!=READING_FINISHED) {
663 		throw emException(
664 			"PostScript interpretation failed: Unsupported document structure."
665 		);
666 	}
667 
668 	if (eat>0) {
669 		ReadBufferFill-=eat;
670 		RdSyncSearchPos-=eat;
671 		if (RdSyncSearchPos<0) RdSyncSearchPos=0;
672 		if (ReadBufferFill>0) {
673 			memmove(ReadBuffer,ReadBuffer+eat,ReadBufferFill);
674 		}
675 		else {
676 			ReadBufferFill=0;
677 		}
678 	}
679 
680 	return true;
681 }
682 
683 
ParseImageHeader(const char * buf,int len)684 int emPsRenderer::ParseImageHeader(const char * buf, int len)
685 {
686 	int i,r;
687 
688 	i=0;
689 
690 	if (i>=len) return 0;
691 	if (buf[i++]!='P') return -1;
692 
693 	if (i>=len) return 0;
694 	RdImgFormat=buf[i++]-'0';
695 	if (RdImgFormat<1 || RdImgFormat>6) return -1;
696 
697 	r=ParseImageDecimal(buf+i,len-i,&RdImgW);
698 	if (r<=0) return r;
699 	if (RdImgW<1) return -1;
700 	i+=r;
701 
702 	r=ParseImageDecimal(buf+i,len-i,&RdImgH);
703 	if (r<=0) return r;
704 	if (RdImgH<1) return -1;
705 	i+=r;
706 
707 	if (RdImgFormat!=1 && RdImgFormat!=4) {
708 		r=ParseImageDecimal(buf+i,len-i,&RdImgMaxVal);
709 		if (r<=0) return r;
710 		if (RdImgMaxVal<1 || RdImgMaxVal>65535) return -1;
711 		i+=r;
712 	}
713 	else {
714 		RdImgMaxVal=1;
715 	}
716 
717 	if (i>=len) return 0;
718 	if (buf[i++]!=0x0a) return -1;
719 	return i;
720 }
721 
722 
ParseImageDecimal(const char * buf,int len,int * pNumber)723 int emPsRenderer::ParseImageDecimal(
724 	const char * buf, int len, int * pNumber
725 )
726 {
727 	int i,c,n;
728 
729 	for (i=0;;) {
730 		if (i>=len) return 0;
731 		c=(unsigned char)buf[i++];
732 		if (c>='0' && c<='9') break;
733 		if (c=='#') {
734 			do {
735 				if (i>=len) return 0;
736 				c=(unsigned char)buf[i++];
737 			} while (c!=0x0a && c!=0x0d);
738 		}
739 		else if (c>0x20) return -1;
740 	}
741 	n=c-'0';
742 	for (;;) {
743 		if (i>=len) return 0;
744 		c=(unsigned char)buf[i++];
745 		if (c>='0' && c<='9') {
746 			n=n*10+(c-'0');
747 		}
748 		else {
749 			*pNumber=n;
750 			return i-1;
751 		}
752 	}
753 }
754 
755 
ParseImageData(const char * buf,int len)756 int emPsRenderer::ParseImageData(const char * buf, int len)
757 {
758 	emImage * img;
759 	int eat,w,d;
760 	bool landscape;
761 	emByte * p;
762 	const emByte * s, * se;
763 
764 	if (RdImgFormat!=6 || RdImgMaxVal!=255) return -1;
765 
766 	if (CurrentJob) {
767 		landscape=CurrentDocument.IsLandscapePage(CurrentPageIndex);
768 		img=CurrentJob->Image;
769 		if (img) {
770 			if (landscape) {
771 				if (img->GetWidth()!=RdImgH || img->GetHeight()!=RdImgW) {
772 					return -1;
773 				}
774 			}
775 			else {
776 				if (img->GetWidth()!=RdImgW || img->GetHeight()!=RdImgH) {
777 					return -1;
778 				}
779 			}
780 			if (img->GetChannelCount()!=3) {
781 				emFatalError("emPsRenderer: Output image must have 3 channels.");
782 			}
783 		}
784 	}
785 	else {
786 		img=NULL;
787 		landscape=false;
788 	}
789 
790 	for (eat=0;;) {
791 		w=(len-eat)/3;
792 		if (w>RdImgW-RdImgX) w=RdImgW-RdImgX;
793 		if (w<=0) break;
794 		if (img) {
795 			if (landscape) {
796 				s=(const emByte*)buf+eat;
797 				se=s+3*w;
798 				p=img->GetWritableMap()+(RdImgX*RdImgH+RdImgH-1-RdImgY)*3;
799 				d=RdImgH*3;
800 				do {
801 					p[0]=s[0];
802 					p[1]=s[1];
803 					p[2]=s[2];
804 					p+=d;
805 					s+=3;
806 				} while (s<se);
807 			}
808 			else {
809 				memcpy(
810 					img->GetWritableMap()+(RdImgY*RdImgW+RdImgX)*3,
811 					buf+eat,
812 					w*3
813 				);
814 			}
815 		}
816 		eat+=w*3;
817 		RdImgX+=w;
818 		if (RdImgX>=RdImgW) {
819 			RdImgX=0;
820 			RdImgY++;
821 			if (RdImgY>=RdImgH) {
822 				RdImgDone=true;
823 				break;
824 			}
825 		}
826 	}
827 	return eat;
828 }
829 
830 
PSAgentClass(emPsRenderer & interpreter)831 emPsRenderer::PSAgentClass::PSAgentClass(emPsRenderer & interpreter)
832 	: emPriSchedAgent(interpreter.GetRootContext(),"cpu"),
833 	Renderer(interpreter)
834 
835 {
836 }
837 
838 
GotAccess()839 void emPsRenderer::PSAgentClass::GotAccess()
840 {
841 	Renderer.WakeUp();
842 }
843 
844 
845 const char * const emPsRenderer::SyncString=
846 	"SYNC823JVG73LS0GJ7B2TX2M49GZWK2D" // Just random
847 ;
848