a580e12000-09-27Fredrik Hübinette (Hubbe) #pike __REAL_VERSION__
a20af62000-09-26Fredrik Hübinette (Hubbe) 
6562532002-12-14Martin Nilsson //! @appears Image.PS
4cd7152002-11-15Martin Nilsson //! Codec for the Adobe page description language PostScript.
df5f072006-11-14Tor Edvardsson //! Uses Ghostscript for decoding or built-in support.
4cd7152002-11-15Martin Nilsson 
9eaf1d2008-06-28Martin Nilsson protected string find_in_path( string file )
d7bd251999-04-15Per Hedbor { string path=getenv("PATH"); foreach(path ? path/":" : ({}) , path) if(file_stat(path=combine_path(path,file))) return path; }
4cd7152002-11-15Martin Nilsson //! Decodes the postscript @[data] into an image object //! using Ghostscript. //! @param options //! Optional decoding parameters. //! @mapping //! @member int "dpi" //! The resolution the image should be rendered in. //! Defaults to 100. //! @member string "device" //! The selected Ghostscript device. Defaults to "ppmraw". //! @member string "binary" //! Path to the Ghostscript binary to be used. If missing //! the environment paths will be searched for a file "gs" //! to be used instead.
df5f072006-11-14Tor Edvardsson //! @member int(0..1) "force_gs" //! Forces use of Ghostscript for EPS files instead //! of Pikes native support. //! @member int(0..1) "eps_crop" //! Use -dEPSCrop option to Ghostscript to crop the //! BoundingBox for a EPS file. //! @member int(0..1) "cie_color" //! Use -dUseCIEColor option to Ghostscript for
f45fea2007-05-24Henrik Grubbström (Grubba) //! mapping color values through a CIE color space. //! @member string "file" //! Filename to read. If this is specified, it will be //! passed along to the @tt{gs@} binary, so that it can //! read the file directly. If this is specified @[data] //! may be set to @expr{0@} (zero).
4cd7152002-11-15Martin Nilsson //! @endmapping
f45fea2007-05-24Henrik Grubbström (Grubba) //! //! @note //! Some versions of @tt{gs@} on MacOS X have problems with //! reading files on stdin. If this occurrs, try writing to //! a plain file and specifying the @tt{file@} option. //! //! @note //! @tt{gs@} versions 7.x and earlier don't support rendering
8a28ac2007-06-01Martin Bähr //! of EPSes if they are specified with the @tt{file@} option.
f45fea2007-05-24Henrik Grubbström (Grubba) //! If this is a problem, upgrade to @tt{gs@} version 8.x or //! later.
d7bd251999-04-15Per Hedbor object decode( string data, mapping|void options ) {
f45fea2007-05-24Henrik Grubbström (Grubba)  if(!options) options = ([]); if (data) { if(has_prefix(data, "\xc5\xd0\xd3\xc6")) { // DOS EPS Binary Header. int ps_start, ps_len, meta_start, meta_len, tiff_start, tiff_len, sum; sscanf(data, "%*4c%-4c%-4c%-4c%-4c%-4c%-4c%-2c", ps_start, ps_len, meta_start, meta_len, tiff_start, tiff_len, sum);
402b802006-09-13Henrik Grubbström (Grubba) #if constant(Image.TIFF.decode)
f45fea2007-05-24Henrik Grubbström (Grubba)  if (tiff_start && tiff_len) { return Image.TIFF.decode(data[tiff_start..tiff_start + tiff_len-1]); }
402b802006-09-13Henrik Grubbström (Grubba) #endif
f45fea2007-05-24Henrik Grubbström (Grubba)  data = data[ps_start..ps_start+ps_len-1]; } if(data[0..3] != "%!PS") error("This is not a postscript file!\n"); if(!options->force_gs) { if (has_prefix(data, "%!PS-Adobe-3.0 EPSF-3.0")) { int width, height, bits, ncols; int nbws, width2, encoding; string init_tag; if ((sscanf(data, "%*s%%ImageData:%*[ ]%d%*[ ]%d%*[ ]%d%*[ ]%d%" "*[ ]%d%*[ ]%d%*[ ]%d%*[ ]\"%s\"",
096bd82009-12-09Tor Edvardsson  width, height, bits, ncols,
f45fea2007-05-24Henrik Grubbström (Grubba)  nbws, width2, encoding, init_tag) > 7) && (width == width2) && (width > 0) && (height > 0) && (bits == 8) && (< 1, 2 >)[encoding]) { // Image data present. int len; string term; string raw; if ((sscanf(data, "%*s%%%%BeginBinary:%*[ ]%d%[\r\n]%s", len, term, raw) == 5) && (len>0) && has_prefix(raw, init_tag + term)) {
df5f072006-11-14Tor Edvardsson  raw = raw[sizeof(init_tag+term)..len-1]; if (encoding == 2) { raw = String.hex2string(raw - term); } if (sizeof(raw) == width*height*(ncols+nbws)) { array(string) rows = raw/width; if ((<3,4>)[ncols]) { // RGB or CMYK image. array(string) channels = allocate(ncols, ""); int c; for (c = 0; c < ncols; c++) { int i; for (i = c; i < sizeof(rows); i += ncols+nbws) { channels[c] += rows[i]; } } if (ncols == 3) { return Image.Image(width, height, "rgb", @channels); } return Image.Image(width, height, "adjusted_cmyk", @channels); } string grey = "";
4481cf2006-09-12Henrik Grubbström (Grubba)  int i;
df5f072006-11-14Tor Edvardsson  for(i = ncols; i < sizeof(rows); i += ncols+nbws) { grey += rows[i];
4481cf2006-09-12Henrik Grubbström (Grubba)  }
df5f072006-11-14Tor Edvardsson  return Image.Image(width, height, "rgb", grey, grey, grey);
4481cf2006-09-12Henrik Grubbström (Grubba)  } }
f45fea2007-05-24Henrik Grubbström (Grubba)  }
4481cf2006-09-12Henrik Grubbström (Grubba)  } }
df5f072006-11-14Tor Edvardsson  }
4481cf2006-09-12Henrik Grubbström (Grubba) 
4cd7152002-11-15Martin Nilsson  Stdio.File fd = Stdio.File(); object fd2 = fd->pipe();
d7bd251999-04-15Per Hedbor 
4cd7152002-11-15Martin Nilsson  Stdio.File fd3 = Stdio.File(); object fd4 = fd3->pipe();
d7bd251999-04-15Per Hedbor  array command = ({
4cd7152002-11-15Martin Nilsson  options->binary||find_in_path("gs")||("/bin/sh -c gs "),
d7bd251999-04-15Per Hedbor  "-quiet", "-sDEVICE="+(options->device||"ppmraw"),
3044d11999-04-22Per Hedbor  "-r"+(options->dpi||100),
f45fea2007-05-24Henrik Grubbström (Grubba)  "-dBATCH",
df5f072006-11-14Tor Edvardsson  "-dNOPAUSE"});
f45fea2007-05-24Henrik Grubbström (Grubba) 
df5f072006-11-14Tor Edvardsson  if(options->eps_crop) command += ({"-dEPSCrop"});
096bd82009-12-09Tor Edvardsson 
df5f072006-11-14Tor Edvardsson  if(options->cie_color) command += ({"-dUseCIEColor"});
096bd82009-12-09Tor Edvardsson 
f45fea2007-05-24Henrik Grubbström (Grubba)  command += ({ "-sOutputFile=-", options->file || "-", "-c", "quit", }); Process.Process pid = Process.create_process( command, ([
d7bd251999-04-15Per Hedbor  "stdin":fd,
daba052006-10-05Henrik Grubbström (Grubba)  "stdout":fd4, "stderr":fd4,
d7bd251999-04-15Per Hedbor  ]));
f45fea2007-05-24Henrik Grubbström (Grubba)  fd->close(); fd4->close();
7a5f332007-12-12Henrik Grubbström (Grubba)  // Backend to use. Pike.Backend be = Pike.Backend();
f45fea2007-05-24Henrik Grubbström (Grubba) 
daba052006-10-05Henrik Grubbström (Grubba)  // Kill the gs binary after 30 seconds in case it hangs.
7a5f332007-12-12Henrik Grubbström (Grubba)  mixed co = be->call_out(lambda(Process.Process pid) { if (!pid->status()) { pid->kill(9); } }, 30, pid);
f45fea2007-05-24Henrik Grubbström (Grubba)  if(!options->file) { if(!has_value(data, "showpage")) data += "\nshowpage\n";
7a5f332007-12-12Henrik Grubbström (Grubba)  be->add_file(fd2);
f45fea2007-05-24Henrik Grubbström (Grubba)  Stdio.sendfile(({ data }), 0, 0, sizeof(data), 0, fd2, lambda(int n) { fd2->close(); }); } else { fd2->close(); } string output = "";
7a5f332007-12-12Henrik Grubbström (Grubba)  be->add_file(fd3);
f45fea2007-05-24Henrik Grubbström (Grubba)  fd3->set_nonblocking(lambda(mixed ignored, string s) { output += s; }, 0, lambda() { fd3->close(); destruct(fd3); }); mixed err = catch { while (fd3) {
7a5f332007-12-12Henrik Grubbström (Grubba)  be(1.0);
f45fea2007-05-24Henrik Grubbström (Grubba)  } }; #if 0 if (err) { werror("Backend failed: %s\n", describe_backtrace(err)); } #endif
7a5f332007-12-12Henrik Grubbström (Grubba)  be->remove_call_out(co);
f45fea2007-05-24Henrik Grubbström (Grubba)  int ret_code = pid->wait(); if(ret_code) error("Ghostscript failed with exit code: %O:\n%s\n", ret_code, output); object i= Image.PNM.decode( output );
daba052006-10-05Henrik Grubbström (Grubba) 
f45fea2007-05-24Henrik Grubbström (Grubba)  if (data) {
7a5f332007-12-12Henrik Grubbström (Grubba)  if(data && sscanf(data, "%*s\n%%%%BoundingBox: %s\n", string bbox) == 2 && !options->eps_crop)
f45fea2007-05-24Henrik Grubbström (Grubba)  { int x0,x1,y0,y1; sscanf(bbox, "%d %d %d %d", x0,y0,x1,y1 ); int llx = (int)((x0/72.0) * (options->dpi||100)+0.01); int lly = (int)((y0/72.0) * (options->dpi||100)+0.01); int urx = (int)((x1/72.0) * (options->dpi||100)+0.01); int ury = (int)((y1/72.0) * (options->dpi||100)+0.01); if(urx && ury) i = i->mirrory()->copy(llx,lly,urx,ury)->mirrory(); } }
d7bd251999-04-15Per Hedbor  return i; }
4cd7152002-11-15Martin Nilsson //! Calls decode and returns the image in the "image" //! index of the mapping. This method is present for //! API reasons.
d7bd251999-04-15Per Hedbor mapping _decode( string data, mapping|void options ) { return ([ "image":decode( data,options ) ]); }
3044d11999-04-22Per Hedbor 
4cd7152002-11-15Martin Nilsson //! Encodes the image object @[img] as a postscript 3.0 file. //! @param options //! Optional extra encoding parameters. //! @mapping //! @member int "dpi" //! The resolution of the encoded image. Defaults to 100. //! @member int(0..1) "eps" //! If the resulting image should be an eps instead of ps. //! Defaults to 0, no. //! @endmapping
3044d11999-04-22Per Hedbor string encode( object img, mapping|void options ) { int w = img->xsize(), h = img->ysize(); string i = (string)img; float scl = 72.0 / ((options&&options->dpi)||100); img = 0; string res; res =("%!PS-Adobe-3.0\n" "%%DocumentData: Clean8Bit\n" "%%BoundingBox: 0 0 "+(int)ceil(w*scl)+" "+(int)ceil(h*scl)+"\n" "%%EndComments\n" "%%BeginProlog\n" "30 dict begin\n" "/tmpstr 256 string def\n" "/dnl{currentfile tmpstr readline pop pop}bind def\n" "/di{dnl gsave scale 2 copy pop string/pxdat exch def\n" " 2 copy 8 [4 2 roll dup 0 0 4 2 roll neg 0 3 2 roll] {currentfile\n" " pxdat readstring pop}false 3 colorimage grestore}bind def\n" "%%EndProlog\n"); res += sprintf("%d %d %f %f di\n", w, h, scl*w, scl*h); res +="%%BeginData "+sizeof(i)+" Binary Bytes\n" + i +"\n%%EndData\n"; if( !options || !options->eps ) res += "showpage\n"; res += "%%Trailer\n" "end\n" "%%EOF\n"; return res; }
4cd7152002-11-15Martin Nilsson //! Same as encode. Present for API reasons.
3044d11999-04-22Per Hedbor function _encode = encode;
096bd82009-12-09Tor Edvardsson  //! Decodes the header of the postscript @[data] into a mapping. //! //! @mapping //! @member int "xsize" //! @member int "ysize" //! Size of image //! @member string "type" //! File type information as MIME type. Always "application/postscript". //! @member string "color_space" //! Color space of image. "GRAYSCALE", "LAB", RGB", "CMYK" or "UNKNOWN" //! @endmapping //! public mapping decode_header(string data) { if(has_prefix(data, "\xc5\xd0\xd3\xc6")) { // DOS EPS Binary Header. int ps_start, ps_len, meta_start, meta_len, tiff_start, tiff_len, sum; sscanf(data, "%*4c%-4c%-4c%-4c%-4c%-4c%-4c%-2c", ps_start, ps_len, meta_start, meta_len, tiff_start, tiff_len, sum); #if constant(Image.TIFF.decode_header) if (tiff_start && tiff_len) { return Image.TIFF.decode_header(data[tiff_start..tiff_start + tiff_len-1]); } #endif data = data[ps_start..ps_start+ps_len-1]; } if (has_prefix(data, "%!PS-Adobe-3.0 EPSF-3.0")) { int width, height, bits, ncols; int nbws, width2, encoding; string init_tag; if ((sscanf(data, "%*s%%ImageData:%*[ ]%d%*[ ]%d%*[ ]%d%*[ ]%d%" "*[ ]%d%*[ ]%d%*[ ]%d%*[ ]\"%s\"", width, height, bits, ncols, nbws, width2, encoding, init_tag) > 7) && (width == width2) && (width > 0) && (height > 0) && (bits == 8) && (< 1, 2 >)[encoding]) { string color_space; switch (ncols) { case 1: color_space = "GRAYSCALE"; break; case 2: color_space = "LAB"; break; case 3: color_space = "RGB"; break; case 4: color_space = "CMYK"; break; default: color_space = "UNKNOWN"; break; } return ([ "type": "application/postscript", "xsize": width, "ysize": height, "color_space": color_space ]); } } return 0; }