2 implements a pdf output device (OutputDev).
4 This file is part of swftools.
6 Swftools is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 Swftools is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with swftools; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
26 #include "../../config.h"
31 #ifdef HAVE_SYS_STAT_H
34 #ifdef HAVE_FONTCONFIG
35 #include <fontconfig.h>
52 #include "OutputDev.h"
55 #include "CharCodeToUnicode.h"
56 #include "NameToUnicodeTable.h"
57 #include "GlobalParams.h"
58 #include "FoFiType1C.h"
59 #include "FoFiTrueType.h"
61 #include "GFXOutputDev.h"
63 //swftools header files
65 #include "../gfxdevice.h"
66 #include "../gfxtools.h"
67 #include "../gfxfont.h"
68 #include "../devices/record.h"
69 #include "../devices/ops.h"
70 #include "../devices/arts.h"
71 #include "../devices/render.h"
73 #include "../art/libart.h"
74 #include "../devices/artsutils.c"
81 typedef struct _fontfile
88 static fontfile_t fonts[2048];
89 static int fontnum = 0;
93 static char* lastfontdir = 0;
104 {"Times-Roman", "n021003l", n021003l_afm, n021003l_afm_len, n021003l_pfb, n021003l_pfb_len},
105 {"Times-Italic", "n021023l", n021023l_afm, n021023l_afm_len, n021023l_pfb, n021023l_pfb_len},
106 {"Times-Bold", "n021004l", n021004l_afm, n021004l_afm_len, n021004l_pfb, n021004l_pfb_len},
107 {"Times-BoldItalic", "n021024l", n021024l_afm, n021024l_afm_len, n021024l_pfb, n021024l_pfb_len},
108 {"Helvetica", "n019003l", n019003l_afm, n019003l_afm_len, n019003l_pfb, n019003l_pfb_len},
109 {"Helvetica-Oblique", "n019023l", n019023l_afm, n019023l_afm_len, n019023l_pfb, n019023l_pfb_len},
110 {"Helvetica-Bold", "n019004l", n019004l_afm, n019004l_afm_len, n019004l_pfb, n019004l_pfb_len},
111 {"Helvetica-BoldOblique", "n019024l", n019024l_afm, n019024l_afm_len, n019024l_pfb, n019024l_pfb_len},
112 {"Courier", "n022003l", n022003l_afm, n022003l_afm_len, n022003l_pfb, n022003l_pfb_len},
113 {"Courier-Oblique", "n022023l", n022023l_afm, n022023l_afm_len, n022023l_pfb, n022023l_pfb_len},
114 {"Courier-Bold", "n022004l", n022004l_afm, n022004l_afm_len, n022004l_pfb, n022004l_pfb_len},
115 {"Courier-BoldOblique", "n022024l", n022024l_afm, n022024l_afm_len, n022024l_pfb, n022024l_pfb_len},
116 {"Symbol", "s050000l", s050000l_afm, s050000l_afm_len, s050000l_pfb, s050000l_pfb_len},
117 {"ZapfDingbats", "d050000l", d050000l_afm, d050000l_afm_len, d050000l_pfb, d050000l_pfb_len}};
120 static int verbose = 0;
121 static int dbgindent = 0;
122 static void dbg(const char*format, ...)
129 va_start(arglist, format);
130 vsprintf(buf, format, arglist);
133 while(l && buf[l-1]=='\n') {
138 int indent = dbgindent;
148 typedef struct _feature
151 struct _feature*next;
153 feature_t*featurewarnings = 0;
155 void GFXOutputDev::showfeature(const char*feature,char fully, char warn)
157 feature_t*f = featurewarnings;
159 if(!strcmp(feature, f->string))
163 f = (feature_t*)malloc(sizeof(feature_t));
164 f->string = strdup(feature);
165 f->next = featurewarnings;
168 msg("<warning> %s not yet %ssupported!",feature,fully?"fully ":"");
169 if(this->config_break_on_warning) {
170 msg("<fatal> Aborting conversion due to unsupported feature");
174 msg("<notice> File contains %s",feature);
177 void GFXOutputDev::warnfeature(const char*feature,char fully)
179 showfeature(feature,fully,1);
181 void GFXOutputDev::infofeature(const char*feature)
183 showfeature(feature,0,0);
186 GFXOutputState::GFXOutputState() {
188 this->createsoftmask = 0;
189 this->transparencygroup = 0;
191 this->grouprecording = 0;
195 GBool GFXOutputDev::interpretType3Chars()
200 typedef struct _drawnchar
218 chars = (drawnchar_t*)malloc(sizeof(drawnchar_t)*buf_size);
219 memset(chars, 0, sizeof(drawnchar_t)*buf_size);
224 free(chars);chars = 0;
231 chars = (drawnchar_t*)realloc(chars, sizeof(drawnchar_t)*buf_size);
235 void addChar(int charid, gfxcoord_t x, gfxcoord_t y, gfxcolor_t color)
238 chars[num_chars].x = x;
239 chars[num_chars].y = y;
240 chars[num_chars].color = color;
241 chars[num_chars].charid = charid;
245 char* writeOutStdFont(fontentry* f)
250 char* tmpFileName = mktmpname(namebuf1);
252 sprintf(namebuf2, "%s.afm", tmpFileName);
253 fi = fopen(namebuf2, "wb");
256 fwrite(f->afm, 1, f->afmlen, fi);
259 sprintf(namebuf2, "%s.pfb", tmpFileName);
260 fi = fopen(namebuf2, "wb");
263 fwrite(f->pfb, 1, f->pfblen, fi);
265 return strdup(namebuf2);
267 void unlinkfont(char* filename)
272 msg("<verbose> Removing temporary font file %s", filename);
275 if(!strncmp(&filename[l-4],".afm",4)) {
276 memcpy(&filename[l-4],".pfb",4); unlink(filename);
277 memcpy(&filename[l-4],".pfa",4); unlink(filename);
278 memcpy(&filename[l-4],".afm",4);
281 if(!strncmp(&filename[l-4],".pfa",4)) {
282 memcpy(&filename[l-4],".afm",4); unlink(filename);
283 memcpy(&filename[l-4],".pfa",4);
286 if(!strncmp(&filename[l-4],".pfb",4)) {
287 memcpy(&filename[l-4],".afm",4); unlink(filename);
288 memcpy(&filename[l-4],".pfb",4);
294 GFXGlobalParams::GFXGlobalParams()
297 //setupBaseFonts(char *dir); //not tested yet
299 GFXGlobalParams::~GFXGlobalParams()
301 msg("<verbose> Performing cleanups");
303 for(t=0;t<sizeof(pdf2t1map)/sizeof(fontentry);t++) {
304 if(pdf2t1map[t].fullfilename) {
305 unlinkfont(pdf2t1map[t].fullfilename);
309 DisplayFontParam *GFXGlobalParams::getDisplayFont(GString *fontName)
311 msg("<verbose> looking for font %s in global params\n", fontName->getCString());
313 char*name = fontName->getCString();
314 /* see if it is a pdf standard font */
316 for(t=0;t<sizeof(pdf2t1map)/sizeof(fontentry);t++) {
317 if(!strcmp(name, pdf2t1map[t].pdffont)) {
318 if(!pdf2t1map[t].fullfilename) {
319 pdf2t1map[t].fullfilename = writeOutStdFont(&pdf2t1map[t]);
320 if(!pdf2t1map[t].fullfilename) {
321 msg("<error> Couldn't save default font- is the Temp Directory writable?");
323 msg("<verbose> Storing standard PDF font %s at %s", name, pdf2t1map[t].fullfilename);
326 DisplayFontParam *dfp = new DisplayFontParam(new GString(fontName), displayFontT1);
327 dfp->t1.fileName = new GString(pdf2t1map[t].fullfilename);
331 for(t=0;t<fontnum;t++) {
332 if(strstr(fonts[t].filename, name)) {
333 DisplayFontParam *dfp = new DisplayFontParam(new GString(fontName), displayFontT1);
334 dfp->t1.fileName = new GString(fonts[t].filename);
338 return GlobalParams::getDisplayFont(fontName);
341 GFXOutputDev::GFXOutputDev(parameter_t*p)
344 this->textmodeinfo = 0;
347 this->type3active = 0;
350 this->substitutepos = 0;
351 this->type3Warning = 0;
352 this->user_movex = 0;
353 this->user_movey = 0;
356 this->user_clipx1 = 0;
357 this->user_clipy1 = 0;
358 this->user_clipx2 = 0;
359 this->user_clipy2 = 0;
360 this->current_text_stroke = 0;
361 this->current_text_clip = 0;
362 this->outer_clip_box = 0;
364 this->pagebuflen = 0;
366 this->config_use_fontconfig=1;
367 this->config_break_on_warning=0;
368 this->config_remapunicode=0;
369 this->config_transparent=0;
370 this->config_extrafontdata = 0;
372 this->parameters = p;
374 this->gfxfontlist = gfxfontlist_create();
376 memset(states, 0, sizeof(states));
378 /* configure device */
380 setParameter(p->name, p->value);
385 void GFXOutputDev::setParameter(const char*key, const char*value)
387 if(!strcmp(key,"breakonwarning")) {
388 this->config_break_on_warning = atoi(value);
389 } else if(!strcmp(key,"fontconfig")) {
390 this->config_use_fontconfig = atoi(value);
391 } else if(!strcmp(key,"remapunicode")) {
392 this->config_remapunicode = atoi(value);
393 } else if(!strcmp(key,"transparent")) {
394 this->config_transparent = atoi(value);
395 } else if(!strcmp(key,"extrafontdata")) {
396 this->config_extrafontdata = atoi(value);
400 void GFXOutputDev::setDevice(gfxdevice_t*dev)
402 parameter_t*p = this->parameters;
404 /* pass parameters to output device */
408 this->device->setparameter(this->device, p->name, p->value);
414 void GFXOutputDev::setMove(int x,int y)
416 this->user_movex = x;
417 this->user_movey = y;
420 void GFXOutputDev::setClip(int x1,int y1,int x2,int y2)
422 if(x2<x1) {int x3=x1;x1=x2;x2=x3;}
423 if(y2<y1) {int y3=y1;y1=y2;y2=y3;}
425 this->user_clipx1 = x1;
426 this->user_clipy1 = y1;
427 this->user_clipx2 = x2;
428 this->user_clipy2 = y2;
431 static char*getFontName(GfxFont*font)
434 GString*gstr = font->getName();
435 char* fname = gstr==0?0:gstr->getCString();
439 sprintf(buf, "UFONT%d", r->num);
440 fontid = strdup(buf);
442 fontid = strdup(fname);
446 char* plus = strchr(fontid, '+');
447 if(plus && plus < &fontid[strlen(fontid)-1]) {
448 fontname = strdup(plus+1);
450 fontname = strdup(fontid);
456 static void dumpFontInfo(const char*loglevel, GfxFont*font);
457 static int lastdumps[1024];
458 static int lastdumppos = 0;
463 static void showFontError(GfxFont*font, int nr)
467 for(t=0;t<lastdumppos;t++)
468 if(lastdumps[t] == r->num)
472 if(lastdumppos<sizeof(lastdumps)/sizeof(int))
473 lastdumps[lastdumppos++] = r->num;
475 msg("<warning> The following font caused problems:");
477 msg("<warning> The following font caused problems (substituting):");
479 msg("<warning> The following Type 3 Font will be rendered as graphics:");
480 dumpFontInfo("<warning>", font);
483 static void dumpFontInfo(const char*loglevel, GfxFont*font)
485 char* id = getFontID(font);
486 char* name = getFontName(font);
487 Ref* r=font->getID();
488 msg("%s=========== %s (ID:%d,%d) ==========\n", loglevel, name, r->num,r->gen);
490 GString*gstr = font->getTag();
492 msg("%s| Tag: %s\n", loglevel, id);
494 if(font->isCIDFont()) msg("%s| is CID font\n", loglevel);
496 GfxFontType type=font->getType();
498 case fontUnknownType:
499 msg("%s| Type: unknown\n",loglevel);
502 msg("%s| Type: 1\n",loglevel);
505 msg("%s| Type: 1C\n",loglevel);
508 msg("%s| Type: 3\n",loglevel);
511 msg("%s| Type: TrueType\n",loglevel);
514 msg("%s| Type: CIDType0\n",loglevel);
517 msg("%s| Type: CIDType0C\n",loglevel);
520 msg("%s| Type: CIDType2\n",loglevel);
525 GBool embedded = font->getEmbeddedFontID(&embRef);
527 if(font->getEmbeddedFontName()) {
528 embeddedName = font->getEmbeddedFontName()->getCString();
531 msg("%s| Embedded id: %s id: %d\n",loglevel, FIXNULL(embeddedName), embRef.num);
533 gstr = font->getExtFontFile();
535 msg("%s| External Font file: %s\n", loglevel, FIXNULL(gstr->getCString()));
537 // Get font descriptor flags.
538 if(font->isFixedWidth()) msg("%s| is fixed width\n", loglevel);
539 if(font->isSerif()) msg("%s| is serif\n", loglevel);
540 if(font->isSymbolic()) msg("%s| is symbolic\n", loglevel);
541 if(font->isItalic()) msg("%s| is italic\n", loglevel);
542 if(font->isBold()) msg("%s| is bold\n", loglevel);
548 //void GFXOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, GBool inlineImg) {printf("void GFXOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, GBool inlineImg) \n");}
549 //void GFXOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, GBool inlineImg) {printf("void GFXOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, GBool inlineImg) \n");}
551 void dump_outline(gfxline_t*line)
554 if(line->type == gfx_moveTo) {
555 msg("<debug> | moveTo %.2f %.2f", line->x,line->y);
556 } else if(line->type == gfx_lineTo) {
557 msg("<debug> | lineTo %.2f %.2f", line->x,line->y);
558 } else if(line->type == gfx_splineTo) {
559 msg("<debug> | splineTo (%.2f %.2f) %.2f %.2f", line->sx,line->sy, line->x, line->y);
565 gfxline_t* gfxPath_to_gfxline(GfxState*state, GfxPath*path, int closed, int user_movex, int user_movey)
567 int num = path->getNumSubpaths();
570 double lastx=0,lasty=0,posx=0,posy=0;
573 msg("<warning> empty path");
577 gfxdrawer_target_gfxline(&draw);
579 for(t = 0; t < num; t++) {
580 GfxSubpath *subpath = path->getSubpath(t);
581 int subnum = subpath->getNumPoints();
582 double bx=0,by=0,cx=0,cy=0;
584 for(s=0;s<subnum;s++) {
587 state->transform(subpath->getX(s),subpath->getY(s),&x,&y);
592 if(closed && needsfix && (fabs(posx-lastx)+fabs(posy-lasty))>0.001) {
593 draw.lineTo(&draw, lastx, lasty);
595 draw.moveTo(&draw, x,y);
600 } else if(subpath->getCurve(s) && cpos==0) {
604 } else if(subpath->getCurve(s) && cpos==1) {
612 draw.lineTo(&draw, x,y);
614 gfxdraw_cubicTo(&draw, bx,by, cx,cy, x,y, 0.05);
621 /* fix non-closed lines */
622 if(closed && needsfix && (fabs(posx-lastx)+fabs(posy-lasty))>0.001) {
623 draw.lineTo(&draw, lastx, lasty);
625 gfxline_t*result = (gfxline_t*)draw.result(&draw);
627 gfxline_optimize(result);
632 GBool GFXOutputDev::useTilingPatternFill()
634 infofeature("tiled patterns");
638 GBool GFXOutputDev::useShadedFills()
640 infofeature("shaded fills");
644 GBool GFXOutputDev::useDrawForm()
646 infofeature("forms");
649 void GFXOutputDev::drawForm(Ref id)
651 msg("<error> drawForm not implemented");
653 GBool GFXOutputDev::needNonText()
657 void GFXOutputDev::endPage()
659 msg("<verbose> endPage");
662 #define STROKE_FILL 1
663 #define STROKE_CLIP 2
664 void GFXOutputDev::strokeGfxline(GfxState *state, gfxline_t*line, int flags)
666 int lineCap = state->getLineCap(); // 0=butt, 1=round 2=square
667 int lineJoin = state->getLineJoin(); // 0=miter, 1=round 2=bevel
668 double miterLimit = state->getMiterLimit();
669 double width = state->getTransformedLineWidth();
672 double opaq = state->getStrokeOpacity();
674 state->getFillRGB(&rgb);
676 state->getStrokeRGB(&rgb);
678 col.r = colToByte(rgb.r);
679 col.g = colToByte(rgb.g);
680 col.b = colToByte(rgb.b);
681 col.a = (unsigned char)(opaq*255);
683 gfx_capType capType = gfx_capRound;
684 if(lineCap == 0) capType = gfx_capButt;
685 else if(lineCap == 1) capType = gfx_capRound;
686 else if(lineCap == 2) capType = gfx_capSquare;
688 gfx_joinType joinType = gfx_joinRound;
689 if(lineJoin == 0) joinType = gfx_joinMiter;
690 else if(lineJoin == 1) joinType = gfx_joinRound;
691 else if(lineJoin == 2) joinType = gfx_joinBevel;
694 double dashphase = 0;
696 state->getLineDash(&ldash, &dashnum, &dashphase);
700 if(dashnum && ldash) {
701 float * dash = (float*)malloc(sizeof(float)*(dashnum+1));
703 msg("<trace> %d dashes", dashnum);
704 msg("<trace> | phase: %f", dashphase);
705 for(t=0;t<dashnum;t++) {
707 msg("<trace> | d%-3d: %f", t, ldash[t]);
710 if(getLogLevel() >= LOGLEVEL_TRACE) {
714 line2 = gfxtool_dash_line(line, dash, dashphase);
717 msg("<trace> After dashing:");
720 if(getLogLevel() >= LOGLEVEL_TRACE) {
721 msg("<trace> stroke width=%f join=%s cap=%s dashes=%d color=%02x%02x%02x%02x\n",
723 lineJoin==0?"miter": (lineJoin==1?"round":"bevel"),
724 lineCap==0?"butt": (lineJoin==1?"round":"square"),
726 col.r,col.g,col.b,col.a
731 if(flags&STROKE_FILL) {
732 ArtSVP* svp = gfxstrokeToSVP(line, width, capType, joinType, miterLimit);
733 gfxline_t*gfxline = SVPtogfxline(svp);
734 if(getLogLevel() >= LOGLEVEL_TRACE) {
735 dump_outline(gfxline);
738 msg("<warning> Empty polygon (resulting from stroked line)");
740 if(flags&STROKE_CLIP) {
741 device->startclip(device, gfxline);
742 states[statepos].clipping++;
744 device->fill(device, gfxline, &col);
749 if(flags&STROKE_CLIP)
750 msg("<error> Stroke&clip not supported at the same time");
751 device->stroke(device, line, width, &col, capType, joinType, miterLimit);
758 gfxcolor_t getFillColor(GfxState * state)
761 double opaq = state->getFillOpacity();
762 state->getFillRGB(&rgb);
764 col.r = colToByte(rgb.r);
765 col.g = colToByte(rgb.g);
766 col.b = colToByte(rgb.b);
767 col.a = (unsigned char)(opaq*255);
771 void GFXOutputDev::fillGfxLine(GfxState *state, gfxline_t*line)
773 gfxcolor_t col = getFillColor(state);
775 if(getLogLevel() >= LOGLEVEL_TRACE) {
776 msg("<trace> fill %02x%02x%02x%02x\n", col.r, col.g, col.b, col.a);
779 device->fill(device, line, &col);
782 void GFXOutputDev::clipToGfxLine(GfxState *state, gfxline_t*line)
784 if(getLogLevel() >= LOGLEVEL_TRACE) {
785 msg("<trace> clip\n");
789 device->startclip(device, line);
790 states[statepos].clipping++;
793 void GFXOutputDev::clip(GfxState *state)
795 GfxPath * path = state->getPath();
796 gfxline_t*line = gfxPath_to_gfxline(state, path, 1, user_movex + clipmovex, user_movey + clipmovey);
797 clipToGfxLine(state, line);
801 void GFXOutputDev::eoClip(GfxState *state)
803 GfxPath * path = state->getPath();
804 gfxline_t*line = gfxPath_to_gfxline(state, path, 1, user_movex + clipmovex, user_movey + clipmovey);
806 if(getLogLevel() >= LOGLEVEL_TRACE) {
807 msg("<trace> eoclip\n");
811 device->startclip(device, line);
812 states[statepos].clipping++;
815 void GFXOutputDev::clipToStrokePath(GfxState *state)
817 GfxPath * path = state->getPath();
818 gfxline_t*line= gfxPath_to_gfxline(state, path, 0, user_movex + clipmovex, user_movey + clipmovey);
820 if(getLogLevel() >= LOGLEVEL_TRACE) {
821 msg("<trace> cliptostrokepath\n");
825 strokeGfxline(state, line, STROKE_FILL|STROKE_CLIP);
829 void GFXOutputDev::endframe()
832 device->endclip(device);
837 void GFXOutputDev::finish()
841 device->endclip(device);
847 GFXOutputDev::~GFXOutputDev()
852 free(this->pages); this->pages = 0;
855 gfxfontlist_free(this->gfxfontlist);
857 GBool GFXOutputDev::upsideDown()
861 GBool GFXOutputDev::useDrawChar()
866 const char*renderModeDesc[]= {"fill", "stroke", "fill+stroke", "invisible",
867 "clip+fill", "stroke+clip", "fill+stroke+clip", "clip"};
869 #define RENDER_FILL 0
870 #define RENDER_STROKE 1
871 #define RENDER_FILLSTROKE 2
872 #define RENDER_INVISIBLE 3
873 #define RENDER_CLIP 4
875 static char tmp_printstr[4096];
876 char* makeStringPrintable(char*str)
878 int len = strlen(str);
893 tmp_printstr[len++] = '.';
894 tmp_printstr[len++] = '.';
895 tmp_printstr[len++] = '.';
897 tmp_printstr[len] = 0;
900 #define INTERNAL_FONT_SIZE 1024.0
901 void GFXOutputDev::updateFontMatrix(GfxState*state)
903 double* ctm = state->getCTM();
904 double fontSize = state->getFontSize();
905 double*textMat = state->getTextMat();
907 /* taking the absolute value of horizScaling seems to be required for
908 some italic fonts. FIXME: SplashOutputDev doesn't need this- why? */
909 double hscale = fabs(state->getHorizScaling());
911 // from xpdf-3.02/SplashOutputDev:updateFont
912 double mm11 = textMat[0] * fontSize * hscale;
913 double mm12 = textMat[1] * fontSize * hscale;
914 double mm21 = textMat[2] * fontSize;
915 double mm22 = textMat[3] * fontSize;
917 // multiply with ctm, like state->getFontTransMat() does
918 this->current_font_matrix.m00 = (ctm[0]*mm11 + ctm[2]*mm12) / INTERNAL_FONT_SIZE;
919 this->current_font_matrix.m01 = (ctm[1]*mm11 + ctm[3]*mm12) / INTERNAL_FONT_SIZE;
920 this->current_font_matrix.m10 = (ctm[0]*mm21 + ctm[2]*mm22) / INTERNAL_FONT_SIZE;
921 this->current_font_matrix.m11 = (ctm[1]*mm21 + ctm[3]*mm22) / INTERNAL_FONT_SIZE;
922 this->current_font_matrix.tx = 0;
923 this->current_font_matrix.ty = 0;
926 void GFXOutputDev::beginString(GfxState *state, GString *s)
928 int render = state->getRender();
929 if(current_text_stroke) {
930 msg("<error> Error: Incompatible change of text rendering to %d while inside cliptext", render);
933 msg("<trace> beginString(%s) render=%d", makeStringPrintable(s->getCString()), render);
936 static gfxline_t* mkEmptyGfxShape(double x, double y)
938 gfxline_t*line = (gfxline_t*)malloc(sizeof(gfxline_t));
939 line->x = x;line->y = y;line->type = gfx_moveTo;line->next = 0;
943 static char isValidUnicode(int c)
945 if(c>=32 && c<0x2fffe)
950 void GFXOutputDev::drawChar(GfxState *state, double x, double y,
951 double dx, double dy,
952 double originX, double originY,
953 CharCode charid, int nBytes, Unicode *_u, int uLen)
955 if(!current_fontinfo || (unsigned)charid >= current_fontinfo->num_glyphs || !current_fontinfo->glyphs[charid]) {
956 msg("<error> Invalid charid %d for font %s", charid, current_font_id);
960 CharCode glyphid = current_fontinfo->glyphs[charid]->glyphid;
962 int render = state->getRender();
963 gfxcolor_t col = getFillColor(state);
965 // check for invisible text -- this is used by Acrobat Capture
966 if (render == RENDER_INVISIBLE) {
970 GfxFont*font = state->getFont();
972 if(font->getType() == fontType3) {
973 /* type 3 chars are passed as graphics */
974 msg("<debug> type3 char at %f/%f", x, y);
978 Unicode u = uLen?(_u[0]):0;
979 msg("<debug> drawChar(%f,%f,c='%c' (%d), u=%d <%d>) CID=%d render=%d glyphid=%d\n",x,y,(charid&127)>=32?charid:'?', charid, u, uLen, font->isCIDFont(), render, glyphid);
981 gfxmatrix_t m = this->current_font_matrix;
982 state->transform(x, y, &m.tx, &m.ty);
983 m.tx += user_movex + clipmovex;
984 m.ty += user_movey + clipmovey;
986 if(render == RENDER_FILL || render == RENDER_INVISIBLE) {
987 device->drawchar(device, current_gfxfont, glyphid, &col, &m);
989 msg("<debug> Drawing glyph %d as shape", charid);
991 msg("<notice> Some texts will be rendered as shape");
994 gfxline_t*glyph = current_gfxfont->glyphs[glyphid].line;
995 gfxline_t*tglyph = gfxline_clone(glyph);
996 gfxline_transform(tglyph, &m);
997 if((render&3) != RENDER_INVISIBLE) {
998 gfxline_t*add = gfxline_clone(tglyph);
999 current_text_stroke = gfxline_append(current_text_stroke, add);
1001 if(render&RENDER_CLIP) {
1002 gfxline_t*add = gfxline_clone(tglyph);
1003 current_text_clip = gfxline_append(current_text_clip, add);
1004 if(!current_text_clip) {
1005 current_text_clip = mkEmptyGfxShape(m.tx, m.ty);
1008 gfxline_free(tglyph);
1012 void GFXOutputDev::endString(GfxState *state)
1014 int render = state->getRender();
1015 msg("<trace> endString() render=%d textstroke=%08x", render, current_text_stroke);
1017 if(current_text_stroke) {
1018 /* fillstroke and stroke text rendering objects we can process right
1019 now (as there may be texts of other rendering modes in this
1020 text object)- clipping objects have to wait until endTextObject,
1022 device->setparameter(device, "mark","TXT");
1023 if((render&3) == RENDER_FILL) {
1024 fillGfxLine(state, current_text_stroke);
1025 gfxline_free(current_text_stroke);
1026 current_text_stroke = 0;
1027 } else if((render&3) == RENDER_FILLSTROKE) {
1028 fillGfxLine(state, current_text_stroke);
1029 strokeGfxline(state, current_text_stroke,0);
1030 gfxline_free(current_text_stroke);
1031 current_text_stroke = 0;
1032 } else if((render&3) == RENDER_STROKE) {
1033 strokeGfxline(state, current_text_stroke,0);
1034 gfxline_free(current_text_stroke);
1035 current_text_stroke = 0;
1037 device->setparameter(device, "mark","");
1041 void GFXOutputDev::endTextObject(GfxState *state)
1043 int render = state->getRender();
1044 msg("<trace> endTextObject() render=%d textstroke=%08x clipstroke=%08x", render, current_text_stroke, current_text_clip);
1046 if(current_text_clip) {
1047 device->setparameter(device, "mark","TXT");
1048 clipToGfxLine(state, current_text_clip);
1049 device->setparameter(device, "mark","");
1050 gfxline_free(current_text_clip);
1051 current_text_clip = 0;
1055 /* the logic seems to be as following:
1056 first, beginType3Char is called, with the charcode and the coordinates.
1057 if this function returns true, it already knew about the char and has now drawn it.
1058 if the function returns false, it's a new char, and type3D0 and/or type3D1 might be
1059 called with some parameters.
1060 Afterwards, all draw operations until endType3Char are part of the char (which in this moment is
1061 at the position first passed to beginType3Char). the char ends with endType3Char.
1063 The drawing operations between beginType3Char and endType3Char are somewhat different to
1064 the normal ones. For example, the fillcolor equals the stroke color. (Because the stroke
1065 color determines the color of a font)
1068 GBool GFXOutputDev::beginType3Char(GfxState *state, double x, double y, double dx, double dy, CharCode charid, Unicode *u, int uLen)
1070 msg("<debug> beginType3Char %d u=%d", charid, uLen?u[0]:0);
1073 if(config_extrafontdata && current_fontinfo) {
1075 gfxmatrix_t m = this->current_font_matrix;
1076 state->transform(0, 0, &m.tx, &m.ty);
1077 m.m00*=INTERNAL_FONT_SIZE;
1078 m.m01*=INTERNAL_FONT_SIZE;
1079 m.m10*=INTERNAL_FONT_SIZE;
1080 m.m11*=INTERNAL_FONT_SIZE;
1081 m.tx += user_movex + clipmovex;
1082 m.ty += user_movey + clipmovey;
1084 if(!current_fontinfo || (unsigned)charid >= current_fontinfo->num_glyphs || !current_fontinfo->glyphs[charid]) {
1085 msg("<error> Invalid charid %d for font %s", charid, current_font_id);
1088 gfxcolor_t col={0,0,0,0};
1089 CharCode glyphid = current_fontinfo->glyphs[charid]->glyphid;
1090 device->drawchar(device, current_gfxfont, glyphid, &col, &m);
1094 /* the character itself is going to be passed using the draw functions */
1095 return gFalse; /* gTrue= is_in_cache? */
1098 void GFXOutputDev::type3D0(GfxState *state, double wx, double wy) {
1100 void GFXOutputDev::type3D1(GfxState *state, double wx, double wy, double llx, double lly, double urx, double ury) {
1103 void GFXOutputDev::endType3Char(GfxState *state)
1106 msg("<debug> endType3Char");
1109 void GFXOutputDev::startFrame(int width, int height)
1111 if(outer_clip_box) {
1112 device->endclip(device);
1116 device->startpage(device, width, height);
1117 this->width = width;
1118 this->height = height;
1121 void GFXOutputDev::startPage(int pageNum, GfxState *state, double crop_x1, double crop_y1, double crop_x2, double crop_y2)
1123 this->currentpage = pageNum;
1125 int rot = doc->getPageRotate(1);
1128 gfxline_t clippath[5];
1130 white.r = white.g = white.b = white.a = 255;
1132 /* state->transform(state->getX1(),state->getY1(),&x1,&y1);
1133 state->transform(state->getX2(),state->getY2(),&x2,&y2);
1134 Use CropBox, not MediaBox, as page size
1141 state->transform(crop_x1,crop_y1,&x1,&y1); //x1 += user_movex; y1 += user_movey;
1142 state->transform(crop_x2,crop_y2,&x2,&y2); //x2 += user_movex; y2 += user_movey;
1144 if(x2<x1) {double x3=x1;x1=x2;x2=x3;}
1145 if(y2<y1) {double y3=y1;y1=y2;y2=y3;}
1147 this->clipmovex = -(int)x1;
1148 this->clipmovey = -(int)y1;
1150 /* apply user clip box */
1151 if(user_clipx1|user_clipy1|user_clipx2|user_clipy2) {
1152 /*if(user_clipx1 > x1)*/ x1 = user_clipx1;
1153 /*if(user_clipx2 < x2)*/ x2 = user_clipx2;
1154 /*if(user_clipy1 > y1)*/ y1 = user_clipy1;
1155 /*if(user_clipy2 < y2)*/ y2 = user_clipy2;
1156 msg("<verbose> Using user clip box %f/%f/%f/%f",x1,y1,x2,y2);
1159 //msg("<verbose> Bounding box is (%f,%f)-(%f,%f) [shifted by %d/%d]", x1,y1,x2,y2, user_movex, user_movey);
1161 msg("<notice> processing PDF page %d (%dx%d:%d:%d) (move:%d:%d)", pageNum, (int)x2-(int)x1,(int)y2-(int)y1, (int)x1, (int)y1, user_movex + clipmovex, user_movey + clipmovey);
1163 msg("<verbose> page is rotated %d degrees\n", rot);
1165 clippath[0].type = gfx_moveTo;clippath[0].x = x1; clippath[0].y = y1; clippath[0].next = &clippath[1];
1166 clippath[1].type = gfx_lineTo;clippath[1].x = x2; clippath[1].y = y1; clippath[1].next = &clippath[2];
1167 clippath[2].type = gfx_lineTo;clippath[2].x = x2; clippath[2].y = y2; clippath[2].next = &clippath[3];
1168 clippath[3].type = gfx_lineTo;clippath[3].x = x1; clippath[3].y = y2; clippath[3].next = &clippath[4];
1169 clippath[4].type = gfx_lineTo;clippath[4].x = x1; clippath[4].y = y1; clippath[4].next = 0;
1170 device->startclip(device, clippath); outer_clip_box = 1;
1171 if(!config_transparent) {
1172 device->fill(device, clippath, &white);
1177 void GFXOutputDev::processLink(Link *link, Catalog *catalog)
1179 double x1, y1, x2, y2, w;
1180 gfxline_t points[5];
1183 msg("<debug> drawlink\n");
1185 link->getRect(&x1, &y1, &x2, &y2);
1186 cvtUserToDev(x1, y1, &x, &y);
1187 points[0].type = gfx_moveTo;
1188 points[0].x = points[4].x = x + user_movex + clipmovex;
1189 points[0].y = points[4].y = y + user_movey + clipmovey;
1190 points[0].next = &points[1];
1191 cvtUserToDev(x2, y1, &x, &y);
1192 points[1].type = gfx_lineTo;
1193 points[1].x = x + user_movex + clipmovex;
1194 points[1].y = y + user_movey + clipmovey;
1195 points[1].next = &points[2];
1196 cvtUserToDev(x2, y2, &x, &y);
1197 points[2].type = gfx_lineTo;
1198 points[2].x = x + user_movex + clipmovex;
1199 points[2].y = y + user_movey + clipmovey;
1200 points[2].next = &points[3];
1201 cvtUserToDev(x1, y2, &x, &y);
1202 points[3].type = gfx_lineTo;
1203 points[3].x = x + user_movex + clipmovex;
1204 points[3].y = y + user_movey + clipmovey;
1205 points[3].next = &points[4];
1206 cvtUserToDev(x1, y1, &x, &y);
1207 points[4].type = gfx_lineTo;
1208 points[4].x = x + user_movex + clipmovex;
1209 points[4].y = y + user_movey + clipmovey;
1212 msg("<trace> drawlink %.2f/%.2f %.2f/%.2f %.2f/%.2f %.2f/%.2f\n",
1213 points[0].x, points[0].y,
1214 points[1].x, points[1].y,
1215 points[2].x, points[2].y,
1216 points[3].x, points[3].y);
1218 LinkAction*action=link->getAction();
1221 const char*type = "-?-";
1224 msg("<trace> drawlink action=%d\n", action->getKind());
1225 switch(action->getKind())
1229 LinkGoTo *ha=(LinkGoTo *)link->getAction();
1230 LinkDest *dest=NULL;
1231 if (ha->getDest()==NULL)
1232 dest=catalog->findDest(ha->getNamedDest());
1233 else dest=ha->getDest();
1235 if (dest->isPageRef()){
1236 Ref pageref=dest->getPageRef();
1237 page=catalog->findPage(pageref.num,pageref.gen);
1239 else page=dest->getPageNum();
1240 sprintf(buf, "%d", page);
1247 LinkGoToR*l = (LinkGoToR*)action;
1248 GString*g = l->getFileName();
1250 s = strdup(g->getCString());
1252 /* if the GoToR link has no filename, then
1253 try to find a refernce in the *local*
1255 GString*g = l->getNamedDest();
1257 s = strdup(g->getCString());
1263 LinkNamed*l = (LinkNamed*)action;
1264 GString*name = l->getName();
1266 s = strdup(name->lowerCase()->getCString());
1267 named = name->getCString();
1270 if(strstr(s, "next") || strstr(s, "forward"))
1272 page = currentpage + 1;
1274 else if(strstr(s, "prev") || strstr(s, "back"))
1276 page = currentpage - 1;
1278 else if(strstr(s, "last") || strstr(s, "end"))
1280 if(pages && pagepos>0)
1281 page = pages[pagepos-1];
1283 else if(strstr(s, "first") || strstr(s, "top"))
1291 case actionLaunch: {
1293 LinkLaunch*l = (LinkLaunch*)action;
1294 GString * str = new GString(l->getFileName());
1295 GString * params = l->getParams();
1297 str->append(params);
1298 s = strdup(str->getCString());
1305 LinkURI*l = (LinkURI*)action;
1306 GString*g = l->getURI();
1308 url = g->getCString();
1313 case actionUnknown: {
1315 LinkUnknown*l = (LinkUnknown*)action;
1320 msg("<error> Unknown link type!\n");
1325 if(!s) s = strdup("-?-");
1327 msg("<trace> drawlink s=%s\n", s);
1329 if(!linkinfo && (page || s))
1331 msg("<notice> File contains links");
1339 for(t=1;t<=pagepos;t++) {
1340 if(pages[t]==page) {
1349 sprintf(buf, "page%d", lpage);
1350 device->drawlink(device, points, buf);
1354 device->drawlink(device, points, s);
1357 msg("<verbose> \"%s\" link to \"%s\" (%d)\n", type, FIXNULL(s), page);
1361 void GFXOutputDev::saveState(GfxState *state) {
1362 dbg("saveState");dbgindent+=2;
1364 msg("<trace> saveState\n");
1367 msg("<error> Too many nested states in pdf.");
1371 states[statepos].createsoftmask = states[statepos-1].createsoftmask;
1372 states[statepos].transparencygroup = states[statepos-1].transparencygroup;
1373 states[statepos].clipping = 0;
1376 void GFXOutputDev::restoreState(GfxState *state) {
1377 dbgindent-=2; dbg("restoreState");
1380 msg("<error> Invalid restoreState");
1383 msg("<trace> restoreState%s%s", states[statepos].softmask?" (end softmask)":"",
1384 states[statepos].clipping?" (end clipping)":"");
1385 if(states[statepos].softmask) {
1386 clearSoftMask(state);
1389 while(states[statepos].clipping) {
1390 device->endclip(device);
1391 states[statepos].clipping--;
1396 void GFXOutputDev::updateLineWidth(GfxState *state)
1398 double width = state->getTransformedLineWidth();
1399 //swfoutput_setlinewidth(&device, width);
1402 void GFXOutputDev::updateLineCap(GfxState *state)
1404 int c = state->getLineCap();
1407 void GFXOutputDev::updateLineJoin(GfxState *state)
1409 int j = state->getLineJoin();
1412 void GFXOutputDev::updateFillColor(GfxState *state)
1415 double opaq = state->getFillOpacity();
1416 state->getFillRGB(&rgb);
1418 void GFXOutputDev::updateFillOpacity(GfxState *state)
1421 double opaq = state->getFillOpacity();
1422 state->getFillRGB(&rgb);
1423 dbg("update fillopaq %f", opaq);
1425 void GFXOutputDev::updateStrokeOpacity(GfxState *state)
1427 double opaq = state->getFillOpacity();
1428 dbg("update strokeopaq %f", opaq);
1430 void GFXOutputDev::updateFillOverprint(GfxState *state)
1432 double opaq = state->getFillOverprint();
1433 dbg("update filloverprint %f", opaq);
1435 void GFXOutputDev::updateStrokeOverprint(GfxState *state)
1437 double opaq = state->getStrokeOverprint();
1438 dbg("update strokeoverprint %f", opaq);
1440 void GFXOutputDev::updateTransfer(GfxState *state)
1442 dbg("update transfer");
1446 void GFXOutputDev::updateStrokeColor(GfxState *state)
1449 double opaq = state->getStrokeOpacity();
1450 state->getStrokeRGB(&rgb);
1453 void GFXOutputDev::setXRef(PDFDoc*doc, XRef *xref)
1459 gfxfont_t* createGfxFont(GfxFont*xpdffont, FontInfo*src)
1461 gfxfont_t*font = (gfxfont_t*)malloc(sizeof(gfxfont_t));
1462 memset(font, 0, sizeof(gfxfont_t));
1464 font->glyphs = (gfxglyph_t*)malloc(sizeof(gfxglyph_t)*src->num_glyphs);
1465 memset(font->glyphs, 0, sizeof(gfxglyph_t)*src->num_glyphs);
1466 font->id = strdup(getFontID(xpdffont));
1468 double quality = (INTERNAL_FONT_SIZE * 0.05) / src->max_size;
1470 //printf("%d glyphs\n", font->num_glyphs);
1471 font->num_glyphs = 0;
1472 for(t=0;t<src->num_glyphs;t++) {
1473 if(src->glyphs[t]) {
1474 SplashPath*path = src->glyphs[t]->path;
1475 int len = path?path->getLength():0;
1476 //printf("glyph %d) %08x (%d line segments)\n", t, path, len);
1477 gfxglyph_t*glyph = &font->glyphs[font->num_glyphs];
1478 src->glyphs[t]->glyphid = font->num_glyphs;
1479 glyph->unicode = src->glyphs[t]->unicode;
1480 if(glyph->unicode >= font->max_unicode)
1481 font->max_unicode = glyph->unicode+1;
1483 gfxdrawer_target_gfxline(&drawer);
1487 for(s=0;s<len;s++) {
1490 path->getPoint(s, &x, &y, &f);
1493 if(f&splashPathFirst) {
1494 drawer.moveTo(&drawer, x*scale, y*scale);
1496 if(f&splashPathCurve) {
1498 path->getPoint(++s, &x2, &y2, &f);
1499 if(f&splashPathCurve) {
1501 path->getPoint(++s, &x3, &y3, &f);
1502 gfxdraw_cubicTo(&drawer, x*scale, y*scale, x2*scale, y2*scale, x3*scale, y3*scale, quality);
1504 drawer.splineTo(&drawer, x*scale, y*scale, x2*scale, y2*scale);
1507 drawer.lineTo(&drawer, x*scale, y*scale);
1509 // printf("%f %f %s %s\n", x, y, (f&splashPathCurve)?"curve":"",
1510 // (f&splashPathFirst)?"first":"",
1511 // (f&splashPathLast)?"last":"");
1513 glyph->line = (gfxline_t*)drawer.result(&drawer);
1514 glyph->advance = xmax*scale; // we don't know the real advance value, so this'll have to do
1518 font->unicode2glyph = (int*)malloc(sizeof(int)*font->max_unicode);
1519 memset(font->unicode2glyph, -1, sizeof(int)*font->max_unicode);
1520 for(t=0;t<font->num_glyphs;t++) {
1521 if(font->glyphs[t].unicode>0 && font->glyphs[t].unicode<font->max_unicode) {
1522 font->unicode2glyph[font->glyphs[t].unicode] = t;
1526 msg("<trace> %d glyphs.", t, font->num_glyphs);
1530 void GFXOutputDev::updateFont(GfxState *state)
1532 GfxFont* gfxFont = state->getFont();
1536 char*id = getFontID(gfxFont);
1537 msg("<verbose> Updating font to %s", id);
1538 if(gfxFont->getType() == fontType3) {
1539 infofeature("Type3 fonts");
1540 if(!config_extrafontdata) {
1545 msg("<error> Internal Error: FontID is null");
1549 this->current_fontinfo = this->info->getFont(id);
1550 if(!this->current_fontinfo->seen) {
1551 dumpFontInfo("<verbose>", gfxFont);
1554 gfxfont_t*font = gfxfontlist_findfont(this->gfxfontlist,id);
1556 font = createGfxFont(gfxFont, current_fontinfo);
1557 gfxfontlist_addfont(this->gfxfontlist, font);
1558 device->addfont(device, font);
1560 current_gfxfont = font;
1563 updateFontMatrix(state);
1566 #define SQR(x) ((x)*(x))
1568 unsigned char* antialize(unsigned char*data, int width, int height, int newwidth, int newheight, int palettesize)
1570 if((newwidth<2 || newheight<2) ||
1571 (width<=newwidth || height<=newheight))
1573 unsigned char*newdata;
1575 newdata= (unsigned char*)malloc(newwidth*newheight);
1577 double fx = (double)(width)/newwidth;
1578 double fy = (double)(height)/newheight;
1580 int blocksize = (int)(8192/(fx*fy));
1581 int r = 8192*256/palettesize;
1582 for(x=0;x<newwidth;x++) {
1583 double ex = px + fx;
1584 int fromx = (int)px;
1586 int xweight1 = (int)(((fromx+1)-px)*256);
1587 int xweight2 = (int)((ex-tox)*256);
1589 for(y=0;y<newheight;y++) {
1590 double ey = py + fy;
1591 int fromy = (int)py;
1593 int yweight1 = (int)(((fromy+1)-py)*256);
1594 int yweight2 = (int)((ey-toy)*256);
1597 for(xx=fromx;xx<=tox;xx++)
1598 for(yy=fromy;yy<=toy;yy++) {
1599 int b = 1-data[width*yy+xx];
1601 if(xx==fromx) weight = (weight*xweight1)/256;
1602 if(xx==tox) weight = (weight*xweight2)/256;
1603 if(yy==fromy) weight = (weight*yweight1)/256;
1604 if(yy==toy) weight = (weight*yweight2)/256;
1607 //if(a) a=(palettesize-1)*r/blocksize;
1608 newdata[y*newwidth+x] = (a*blocksize)/r;
1616 #define IMAGE_TYPE_JPEG 0
1617 #define IMAGE_TYPE_LOSSLESS 1
1619 static void drawimage(gfxdevice_t*dev, gfxcolor_t* data, int sizex,int sizey,
1620 double x1,double y1,
1621 double x2,double y2,
1622 double x3,double y3,
1623 double x4,double y4, int type)
1625 gfxcolor_t*newpic=0;
1627 double l1 = sqrt((x4-x1)*(x4-x1) + (y4-y1)*(y4-y1));
1628 double l2 = sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1));
1630 gfxline_t p1,p2,p3,p4,p5;
1631 p1.type=gfx_moveTo;p1.x=x1; p1.y=y1;p1.next=&p2;
1632 p2.type=gfx_lineTo;p2.x=x2; p2.y=y2;p2.next=&p3;
1633 p3.type=gfx_lineTo;p3.x=x3; p3.y=y3;p3.next=&p4;
1634 p4.type=gfx_lineTo;p4.x=x4; p4.y=y4;p4.next=&p5;
1635 p5.type=gfx_lineTo;p5.x=x1; p5.y=y1;p5.next=0;
1637 {p1.x = (int)(p1.x*20)/20.0;
1638 p1.y = (int)(p1.y*20)/20.0;
1639 p2.x = (int)(p2.x*20)/20.0;
1640 p2.y = (int)(p2.y*20)/20.0;
1641 p3.x = (int)(p3.x*20)/20.0;
1642 p3.y = (int)(p3.y*20)/20.0;
1643 p4.x = (int)(p4.x*20)/20.0;
1644 p4.y = (int)(p4.y*20)/20.0;
1645 p5.x = (int)(p5.x*20)/20.0;
1646 p5.y = (int)(p5.y*20)/20.0;
1653 m.m00 = (p4.x-p1.x)/sizex; m.m10 = (p2.x-p1.x)/sizey;
1654 m.m01 = (p4.y-p1.y)/sizex; m.m11 = (p2.y-p1.y)/sizey;
1659 img.data = (gfxcolor_t*)data;
1663 if(type == IMAGE_TYPE_JPEG)
1664 /* TODO: pass image_dpi to device instead */
1665 dev->setparameter(dev, "next_bitmap_is_jpeg", "1");
1667 dev->fillbitmap(dev, &p1, &img, &m, 0);
1670 void drawimagejpeg(gfxdevice_t*dev, gfxcolor_t*mem, int sizex,int sizey,
1671 double x1,double y1, double x2,double y2, double x3,double y3, double x4,double y4)
1673 drawimage(dev,mem,sizex,sizey,x1,y1,x2,y2,x3,y3,x4,y4, IMAGE_TYPE_JPEG);
1676 void drawimagelossless(gfxdevice_t*dev, gfxcolor_t*mem, int sizex,int sizey,
1677 double x1,double y1, double x2,double y2, double x3,double y3, double x4,double y4)
1679 drawimage(dev,mem,sizex,sizey,x1,y1,x2,y2,x3,y3,x4,y4, IMAGE_TYPE_LOSSLESS);
1683 void GFXOutputDev::drawGeneralImage(GfxState *state, Object *ref, Stream *str,
1684 int width, int height, GfxImageColorMap*colorMap, GBool invert,
1685 GBool inlineImg, int mask, int*maskColors,
1686 Stream *maskStr, int maskWidth, int maskHeight, GBool maskInvert, GfxImageColorMap*maskColorMap)
1688 double x1,y1,x2,y2,x3,y3,x4,y4;
1689 ImageStream *imgStr;
1694 unsigned char* maskbitmap = 0;
1697 ncomps = colorMap->getNumPixelComps();
1698 bits = colorMap->getBits();
1703 unsigned char buf[8];
1704 maskbitmap = (unsigned char*)malloc(maskHeight*maskWidth);
1706 ImageStream*imgMaskStr = new ImageStream(maskStr, maskWidth, maskColorMap->getNumPixelComps(), maskColorMap->getBits());
1707 imgMaskStr->reset();
1708 unsigned char pal[256];
1709 int n = 1 << colorMap->getBits();
1714 maskColorMap->getGray(pixBuf, &gray);
1715 pal[t] = colToByte(gray);
1717 for (y = 0; y < maskHeight; y++) {
1718 for (x = 0; x < maskWidth; x++) {
1719 imgMaskStr->getPixel(buf);
1720 maskbitmap[y*maskWidth+x] = pal[buf[0]];
1725 ImageStream*imgMaskStr = new ImageStream(maskStr, maskWidth, 1, 1);
1726 imgMaskStr->reset();
1727 for (y = 0; y < maskHeight; y++) {
1728 for (x = 0; x < maskWidth; x++) {
1729 imgMaskStr->getPixel(buf);
1731 maskbitmap[y*maskWidth+x] = (buf[0]^1)*255;
1739 imgStr = new ImageStream(str, width, ncomps,bits);
1742 if(!width || !height || (height<=1 && width<=1))
1744 msg("<verbose> Ignoring %d by %d image", width, height);
1745 unsigned char buf[8];
1747 for (y = 0; y < height; ++y)
1748 for (x = 0; x < width; ++x) {
1749 imgStr->getPixel(buf);
1757 state->transform(0, 1, &x1, &y1); x1 += user_movex + clipmovex; y1 += user_movey + clipmovey;
1758 state->transform(0, 0, &x2, &y2); x2 += user_movex + clipmovex; y2 += user_movey + clipmovey;
1759 state->transform(1, 0, &x3, &y3); x3 += user_movex + clipmovex; y3 += user_movey + clipmovey;
1760 state->transform(1, 1, &x4, &y4); x4 += user_movex + clipmovex; y4 += user_movey + clipmovey;
1762 if(!pbminfo && !(str->getKind()==strDCT)) {
1764 msg("<notice> file contains pbm pictures %s",mask?"(masked)":"");
1768 msg("<verbose> drawing %d by %d masked picture\n", width, height);
1770 if(!jpeginfo && (str->getKind()==strDCT)) {
1771 msg("<notice> file contains jpeg pictures");
1777 unsigned char buf[8];
1779 unsigned char*pic = new unsigned char[width*height];
1780 gfxcolor_t pal[256];
1782 state->getFillRGB(&rgb);
1784 memset(pal,255,sizeof(pal));
1785 pal[0].r = (int)(colToByte(rgb.r)); pal[1].r = 0;
1786 pal[0].g = (int)(colToByte(rgb.g)); pal[1].g = 0;
1787 pal[0].b = (int)(colToByte(rgb.b)); pal[1].b = 0;
1788 pal[0].a = 255; pal[1].a = 0;
1791 int realwidth = (int)sqrt(SQR(x2-x3) + SQR(y2-y3));
1792 int realheight = (int)sqrt(SQR(x1-x2) + SQR(y1-y2));
1793 for (y = 0; y < height; ++y)
1794 for (x = 0; x < width; ++x)
1796 imgStr->getPixel(buf);
1799 pic[width*y+x] = buf[0];
1802 /* the size of the drawn image is added to the identifier
1803 as the same image may require different bitmaps if displayed
1804 at different sizes (due to antialiasing): */
1807 unsigned char*pic2 = 0;
1810 pic2 = antialize(pic,width,height,realwidth,realheight,numpalette);
1819 height = realheight;
1823 /* make a black/white palette */
1825 float r = 255/(numpalette-1);
1827 for(t=0;t<numpalette;t++) {
1828 pal[t].r = colToByte(rgb.r);
1829 pal[t].g = colToByte(rgb.g);
1830 pal[t].b = colToByte(rgb.b);
1831 pal[t].a = (unsigned char)(t*r);
1835 gfxcolor_t*pic2 = new gfxcolor_t[width*height];
1836 for (y = 0; y < height; ++y) {
1837 for (x = 0; x < width; ++x) {
1838 pic2[width*y+x] = pal[pic[y*width+x]];
1841 drawimagelossless(device, pic2, width, height, x1,y1,x2,y2,x3,y3,x4,y4);
1845 if(maskbitmap) free(maskbitmap);
1851 if(colorMap->getNumPixelComps()!=1 || str->getKind()==strDCT) {
1852 gfxcolor_t*pic=new gfxcolor_t[width*height];
1853 for (y = 0; y < height; ++y) {
1854 for (x = 0; x < width; ++x) {
1855 imgStr->getPixel(pixBuf);
1856 colorMap->getRGB(pixBuf, &rgb);
1857 pic[width*y+x].r = (unsigned char)(colToByte(rgb.r));
1858 pic[width*y+x].g = (unsigned char)(colToByte(rgb.g));
1859 pic[width*y+x].b = (unsigned char)(colToByte(rgb.b));
1860 pic[width*y+x].a = 255;//(U8)(rgb.a * 255 + 0.5);
1862 pic[width*y+x].a = maskbitmap[(y*maskHeight/height)*maskWidth+(x*maskWidth/width)];
1866 if(str->getKind()==strDCT)
1867 drawimagejpeg(device, pic, width, height, x1,y1,x2,y2,x3,y3,x4,y4);
1869 drawimagelossless(device, pic, width, height, x1,y1,x2,y2,x3,y3,x4,y4);
1872 if(maskbitmap) free(maskbitmap);
1875 gfxcolor_t*pic=new gfxcolor_t[width*height];
1876 gfxcolor_t pal[256];
1877 int n = 1 << colorMap->getBits();
1879 for(t=0;t<256;t++) {
1881 colorMap->getRGB(pixBuf, &rgb);
1883 {/*if(maskColors && *maskColors==t) {
1884 msg("<notice> Color %d is transparent", t);
1885 if (imgData->maskColors) {
1887 for (i = 0; i < imgData->colorMap->getNumPixelComps(); ++i) {
1888 if (pix[i] < imgData->maskColors[2*i] ||
1889 pix[i] > imgData->maskColors[2*i+1]) {
1904 pal[t].r = (unsigned char)(colToByte(rgb.r));
1905 pal[t].g = (unsigned char)(colToByte(rgb.g));
1906 pal[t].b = (unsigned char)(colToByte(rgb.b));
1907 pal[t].a = 255;//(U8)(rgb.b * 255 + 0.5);
1910 for (y = 0; y < height; ++y) {
1911 for (x = 0; x < width; ++x) {
1912 imgStr->getPixel(pixBuf);
1913 pic[width*y+x] = pal[pixBuf[0]];
1915 pic[width*y+x].a = maskbitmap[(y*maskHeight/height)*maskWidth+(x*maskWidth/width)];
1919 drawimagelossless(device, pic, width, height, x1,y1,x2,y2,x3,y3,x4,y4);
1923 if(maskbitmap) free(maskbitmap);
1928 void GFXOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
1929 int width, int height, GBool invert,
1932 dbg("drawImageMask %dx%d, invert=%d inline=%d", width, height, invert, inlineImg);
1933 msg("<verbose> drawImageMask %dx%d, invert=%d inline=%d", width, height, invert, inlineImg);
1934 drawGeneralImage(state,ref,str,width,height,0,invert,inlineImg,1, 0, 0,0,0,0, 0);
1937 void GFXOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
1938 int width, int height, GfxImageColorMap *colorMap,
1939 int *maskColors, GBool inlineImg)
1941 dbg("drawImage %dx%d, %s, %s, inline=%d", width, height,
1942 colorMap?"colorMap":"no colorMap",
1943 maskColors?"maskColors":"no maskColors",
1945 msg("<verbose> drawImage %dx%d, %s, %s, inline=%d", width, height,
1946 colorMap?"colorMap":"no colorMap",
1947 maskColors?"maskColors":"no maskColors",
1950 msg("<verbose> colorMap pixcomps:%d bits:%d mode:%d\n", colorMap->getNumPixelComps(),
1951 colorMap->getBits(),colorMap->getColorSpace()->getMode());
1952 drawGeneralImage(state,ref,str,width,height,colorMap,0,inlineImg,0,maskColors, 0,0,0,0, 0);
1955 void GFXOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str,
1956 int width, int height,
1957 GfxImageColorMap *colorMap,
1958 Stream *maskStr, int maskWidth, int maskHeight,
1961 dbg("drawMaskedImage %dx%d, %s, %dx%d mask", width, height,
1962 colorMap?"colorMap":"no colorMap",
1963 maskWidth, maskHeight);
1964 msg("<verbose> drawMaskedImage %dx%d, %s, %dx%d mask", width, height,
1965 colorMap?"colorMap":"no colorMap",
1966 maskWidth, maskHeight);
1968 msg("<verbose> colorMap pixcomps:%d bits:%d mode:%d\n", colorMap->getNumPixelComps(),
1969 colorMap->getBits(),colorMap->getColorSpace()->getMode());
1970 drawGeneralImage(state,ref,str,width,height,colorMap,0,0,0,0, maskStr, maskWidth, maskHeight, maskInvert, 0);
1973 void GFXOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str,
1974 int width, int height,
1975 GfxImageColorMap *colorMap,
1977 int maskWidth, int maskHeight,
1978 GfxImageColorMap *maskColorMap)
1980 dbg("drawSoftMaskedImage %dx%d, %s, %dx%d mask", width, height,
1981 colorMap?"colorMap":"no colorMap",
1982 maskWidth, maskHeight);
1983 msg("<verbose> drawSoftMaskedImage %dx%d, %s, %dx%d mask", width, height,
1984 colorMap?"colorMap":"no colorMap",
1985 maskWidth, maskHeight);
1987 msg("<verbose> colorMap pixcomps:%d bits:%d mode:%d\n", colorMap->getNumPixelComps(),
1988 colorMap->getBits(),colorMap->getColorSpace()->getMode());
1989 drawGeneralImage(state,ref,str,width,height,colorMap,0,0,0,0, maskStr, maskWidth, maskHeight, 0, maskColorMap);
1992 void GFXOutputDev::stroke(GfxState *state)
1996 GfxPath * path = state->getPath();
1997 gfxline_t*line= gfxPath_to_gfxline(state, path, 0, user_movex + clipmovex, user_movey + clipmovey);
1998 strokeGfxline(state, line, 0);
2002 void GFXOutputDev::fill(GfxState *state)
2004 gfxcolor_t col = getFillColor(state);
2005 dbg("fill %02x%02x%02x%02x",col.r,col.g,col.b,col.a);
2007 GfxPath * path = state->getPath();
2008 gfxline_t*line= gfxPath_to_gfxline(state, path, 1, user_movex + clipmovex, user_movey + clipmovey);
2009 fillGfxLine(state, line);
2013 void GFXOutputDev::eoFill(GfxState *state)
2015 gfxcolor_t col = getFillColor(state);
2016 dbg("eofill %02x%02x%02x%02x",col.r,col.g,col.b,col.a);
2018 GfxPath * path = state->getPath();
2019 gfxline_t*line= gfxPath_to_gfxline(state, path, 1, user_movex + clipmovex, user_movey + clipmovey);
2020 fillGfxLine(state, line);
2025 static const char* dirseparator()
2034 void addGlobalFont(const char*filename)
2037 memset(&f, 0, sizeof(fontfile_t));
2038 f.filename = filename;
2039 if(fontnum < sizeof(fonts)/sizeof(fonts[0])) {
2040 msg("<notice> Adding font \"%s\".", filename);
2041 fonts[fontnum++] = f;
2043 msg("<error> Too many external fonts. Not adding font file \"%s\".", filename);
2047 void addGlobalLanguageDir(const char*dir)
2049 msg("<notice> Adding %s to language pack directories", dir);
2053 char* config_file = (char*)malloc(strlen(dir) + 1 + sizeof("add-to-xpdfrc") + 1);
2054 strcpy(config_file, dir);
2055 strcat(config_file, dirseparator());
2056 strcat(config_file, "add-to-xpdfrc");
2058 fi = fopen(config_file, "rb");
2060 msg("<error> Could not open %s", config_file);
2063 globalParams->parseFile(new GString(config_file), fi);
2067 void addGlobalFontDir(const char*dirname)
2069 #ifdef HAVE_DIRENT_H
2070 msg("<notice> Adding %s to font directories", dirname);
2071 lastfontdir = strdup(dirname);
2072 DIR*dir = opendir(dirname);
2074 msg("<warning> Couldn't open directory %s\n", dirname);
2079 ent = readdir (dir);
2083 char*name = ent->d_name;
2089 if(!strncasecmp(&name[l-4], ".pfa", 4))
2091 if(!strncasecmp(&name[l-4], ".pfb", 4))
2093 if(!strncasecmp(&name[l-4], ".ttf", 4))
2096 char*fontname = (char*)malloc(strlen(dirname)+strlen(name)+2);
2097 strcpy(fontname, dirname);
2098 strcat(fontname, dirseparator());
2099 strcat(fontname, name);
2100 addGlobalFont(fontname);
2105 msg("<warning> No dirent.h- unable to add font dir %s", dir);
2109 void GFXOutputDev::preparePage(int pdfpage, int outputpage)
2115 this->pagebuflen = 1024;
2116 this->pages = (int*)malloc(this->pagebuflen*sizeof(int));
2117 memset(this->pages, -1, this->pagebuflen*sizeof(int));
2119 while(pdfpage >= this->pagebuflen)
2121 int oldlen = this->pagebuflen;
2122 this->pagebuflen+=1024;
2123 this->pages = (int*)realloc(this->pages, this->pagebuflen*sizeof(int));
2124 memset(&this->pages[oldlen], -1, (this->pagebuflen-oldlen)*sizeof(int));
2127 this->pages[pdfpage] = outputpage;
2128 if(pdfpage>this->pagepos)
2129 this->pagepos = pdfpage;
2135 double width,height;
2138 BBox mkBBox(GfxState*state, double*bbox, double width, double height)
2140 double xMin, yMin, xMax, yMax, x, y;
2141 double tx, ty, w, h;
2142 // transform the bbox
2143 state->transform(bbox[0], bbox[1], &x, &y);
2146 state->transform(bbox[0], bbox[3], &x, &y);
2149 } else if (x > xMax) {
2154 } else if (y > yMax) {
2157 state->transform(bbox[2], bbox[1], &x, &y);
2160 } else if (x > xMax) {
2165 } else if (y > yMax) {
2168 state->transform(bbox[2], bbox[3], &x, &y);
2171 } else if (x > xMax) {
2176 } else if (y > yMax) {
2179 tx = (int)floor(xMin);
2182 } else if (tx > width) {
2185 ty = (int)floor(yMin);
2188 } else if (ty > height) {
2191 w = (int)ceil(xMax) - tx + 1;
2192 if (tx + w > width) {
2198 h = (int)ceil(yMax) - ty + 1;
2199 if (ty + h > height) {
2213 void GFXOutputDev::beginTransparencyGroup(GfxState *state, double *bbox,
2214 GfxColorSpace *blendingColorSpace,
2215 GBool isolated, GBool knockout,
2218 const char*colormodename = "";
2219 BBox rect = mkBBox(state, bbox, this->width, this->height);
2221 if(blendingColorSpace) {
2222 colormodename = GfxColorSpace::getColorSpaceModeName(blendingColorSpace->getMode());
2224 dbg("beginTransparencyGroup %.1f/%.1f/%.1f/%.1f %s isolated=%d knockout=%d forsoftmask=%d", bbox[0],bbox[1],bbox[2],bbox[3], colormodename, isolated, knockout, forSoftMask);
2225 dbg("using clipping rect %f/%f/%f/%f\n", rect.posx,rect.posy,rect.width,rect.height);
2226 msg("<verbose> beginTransparencyGroup %.1f/%.1f/%.1f/%.1f %s isolated=%d knockout=%d forsoftmask=%d", bbox[0],bbox[1],bbox[2],bbox[3], colormodename, isolated, knockout, forSoftMask);
2228 states[statepos].createsoftmask |= forSoftMask;
2229 states[statepos].transparencygroup = !forSoftMask;
2230 states[statepos].isolated = isolated;
2232 states[statepos].olddevice = this->device;
2233 this->device = (gfxdevice_t*)rfx_calloc(sizeof(gfxdevice_t));
2235 gfxdevice_record_init(this->device);
2237 /*if(!forSoftMask) { ////???
2238 state->setFillOpacity(0.0);
2243 void GFXOutputDev::endTransparencyGroup(GfxState *state)
2246 dbg("endTransparencyGroup");
2247 msg("<verbose> endTransparencyGroup");
2249 gfxdevice_t*r = this->device;
2251 this->device = states[statepos].olddevice;
2253 if(states[statepos].createsoftmask) {
2254 states[statepos-1].softmaskrecording = r->finish(r);
2256 states[statepos-1].grouprecording = r->finish(r);
2259 states[statepos].createsoftmask = 0;
2260 states[statepos].transparencygroup = 0;
2264 void GFXOutputDev::paintTransparencyGroup(GfxState *state, double *bbox)
2266 const char*blendmodes[] = {"normal","multiply","screen","overlay","darken", "lighten",
2267 "colordodge","colorburn","hardlight","softlight","difference",
2268 "exclusion","hue","saturation","color","luminosity"};
2270 dbg("paintTransparencyGroup blend=%s softmaskon=%d", blendmodes[state->getBlendMode()], states[statepos].softmask);
2271 msg("<verbose> paintTransparencyGroup blend=%s softmaskon=%d", blendmodes[state->getBlendMode()], states[statepos].softmask);
2273 if(state->getBlendMode() == gfxBlendNormal)
2274 infofeature("transparency groups");
2277 sprintf(buffer, "%s blended transparency groups", blendmodes[state->getBlendMode()]);
2278 warnfeature(buffer, 0);
2281 gfxresult_t*grouprecording = states[statepos].grouprecording;
2283 if(state->getBlendMode() == gfxBlendNormal) {
2285 gfxdevice_ops_init(&ops, this->device, (unsigned char)(state->getFillOpacity()*255));
2286 gfxresult_record_replay(grouprecording, &ops);
2289 grouprecording->destroy(grouprecording);
2291 states[statepos].grouprecording = 0;
2294 void GFXOutputDev::setSoftMask(GfxState *state, double *bbox, GBool alpha, Function *transferFunc, GfxColor *rgb)
2296 /* alpha = 1: retrieve mask values from alpha layer
2297 alpha = 0: retrieve mask values from luminance */
2298 dbg("setSoftMask %.1f/%.1f/%.1f/%.1f alpha=%d backdrop=%02x%02x%02x",
2299 bbox[0], bbox[1], bbox[2], bbox[3], alpha, colToByte(rgb->c[0]), colToByte(rgb->c[1]), colToByte(rgb->c[2]));
2300 msg("<verbose> setSoftMask %.1f/%.1f/%.1f/%.1f alpha=%d backdrop=%02x%02x%02x",
2301 bbox[0], bbox[1], bbox[2], bbox[3], alpha, colToByte(rgb->c[0]), colToByte(rgb->c[1]), colToByte(rgb->c[2]));
2303 infofeature("soft masks");
2305 warnfeature("soft masks from alpha channel",0);
2307 states[statepos].olddevice = this->device;
2308 this->device = (gfxdevice_t*)rfx_calloc(sizeof(gfxdevice_t));
2309 gfxdevice_record_init(this->device);
2311 dbg("softmaskrecording is %08x at statepos %d\n", states[statepos].softmaskrecording, statepos);
2313 states[statepos].softmask = 1;
2314 states[statepos].softmask_alpha = alpha;
2317 static inline Guchar div255(int x) {
2318 return (Guchar)((x + (x >> 8) + 0x80) >> 8);
2321 static unsigned char clampU8(unsigned char c, unsigned char min, unsigned char max)
2323 if(c < min) c = min;
2324 if(c > max) c = max;
2328 void GFXOutputDev::clearSoftMask(GfxState *state)
2330 if(!states[statepos].softmask)
2332 states[statepos].softmask = 0;
2333 dbg("clearSoftMask statepos=%d", statepos);
2334 msg("<verbose> clearSoftMask");
2336 if(!states[statepos].softmaskrecording || strcmp(this->device->name, "record")) {
2337 msg("<error> Error in softmask/tgroup ordering");
2341 gfxresult_t*mask = states[statepos].softmaskrecording;
2342 gfxresult_t*below = this->device->finish(this->device);
2343 this->device = states[statepos].olddevice;
2345 /* get outline of all objects below the soft mask */
2346 gfxdevice_t uniondev;
2347 gfxdevice_union_init(&uniondev, 0);
2348 gfxresult_record_replay(below, &uniondev);
2349 gfxline_t*belowoutline = gfxdevice_union_getunion(&uniondev);
2350 uniondev.finish(&uniondev);
2352 gfxbbox_t bbox = gfxline_getbbox(belowoutline);
2354 this->device->startclip(this->device, belowoutline);
2355 gfxresult_record_replay(below, this->device);
2356 gfxresult_record_replay(mask, this->device);
2357 this->device->endclip(this->device);
2358 gfxline_free(belowoutline);
2361 int width = (int)bbox.xmax,height = (int)bbox.ymax;
2362 if(width<=0 || height<=0)
2365 gfxdevice_t belowrender;
2366 gfxdevice_render_init(&belowrender);
2367 if(states[statepos+1].isolated) {
2368 belowrender.setparameter(&belowrender, "fillwhite", "1");
2370 belowrender.setparameter(&belowrender, "antialize", "2");
2371 belowrender.startpage(&belowrender, width, height);
2372 gfxresult_record_replay(below, &belowrender);
2373 belowrender.endpage(&belowrender);
2374 gfxresult_t* belowresult = belowrender.finish(&belowrender);
2375 gfximage_t* belowimg = (gfximage_t*)belowresult->get(belowresult,"page0");
2376 //writePNG("below.png", (unsigned char*)belowimg->data, belowimg->width, belowimg->height);
2378 gfxdevice_t maskrender;
2379 gfxdevice_render_init(&maskrender);
2380 maskrender.startpage(&maskrender, width, height);
2381 gfxresult_record_replay(mask, &maskrender);
2382 maskrender.endpage(&maskrender);
2383 gfxresult_t* maskresult = maskrender.finish(&maskrender);
2384 gfximage_t* maskimg = (gfximage_t*)maskresult->get(maskresult,"page0");
2386 if(belowimg->width != maskimg->width || belowimg->height != maskimg->height) {
2387 msg("<fatal> Internal error in mask drawing");
2392 for(y=0;y<height;y++) {
2393 gfxcolor_t* l1 = &maskimg->data[maskimg->width*y];
2394 gfxcolor_t* l2 = &belowimg->data[belowimg->width*y];
2395 for(x=0;x<width;x++) {
2397 if(states[statepos].softmask_alpha) {
2400 alpha = (77*l1->r + 151*l1->g + 28*l1->b) >> 8;
2403 l2->a = div255(alpha*l2->a);
2405 /* DON'T premultiply alpha- this is done by fillbitmap,
2406 depending on the output device */
2407 //l2->r = div255(alpha*l2->r);
2408 //l2->g = div255(alpha*l2->g);
2409 //l2->b = div255(alpha*l2->b);
2415 gfxline_t*line = gfxline_makerectangle(0,0,width,height);
2418 matrix.m00 = 1.0; matrix.m10 = 0.0; matrix.tx = 0.0;
2419 matrix.m01 = 0.0; matrix.m11 = 1.0; matrix.ty = 0.0;
2421 this->device->fillbitmap(this->device, line, belowimg, &matrix, 0);
2423 mask->destroy(mask);
2424 below->destroy(below);
2425 maskresult->destroy(maskresult);
2426 belowresult->destroy(belowresult);
2427 states[statepos].softmaskrecording = 0;
2432 // public: ~MemCheck()
2434 // delete globalParams;globalParams=0;
2435 // Object::memCheck(stderr);
2436 // gMemReport(stderr);