home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / pascal / library / windows / printeps / epsprint.pas < prev    next >
Pascal/Delphi Source File  |  1994-06-06  |  16KB  |  555 lines

  1. unit EpsPrint;
  2.  
  3. {----------------------------------------------------------------------------}
  4. { Version 1.00 - 06 Jun 94 - S A Schafer                                     }
  5. {    original release                                                        }
  6. {----------------------------------------------------------------------------}
  7.  
  8. interface
  9.  
  10. uses
  11.   Objects,WinTypes,WinProcs,OPrinter,Strings;
  12.  
  13. type
  14.  
  15.   { tEpsPrinter is a simple descendant of tPrinter. The only change is the
  16.     addition of the IsPostScriptCapable method, which determines whether or
  17.     not the printer device context associated with the object is capable of
  18.     PostScript printing. }
  19.  
  20.   pEpsPrinter = ^tEpsPrinter;
  21.   tEpsPrinter = object (tPrinter)
  22.     public
  23.       function IsPostScriptCapable: Boolean;
  24.     end;
  25.  
  26.   { tEps encapsulates an EPS image at the file level. The only public
  27.     methods are the Init constructor and the Done destructor. tEps
  28.     automatically strips the preview header from an EPS image, if one is
  29.     present. }
  30.  
  31.   pEps = ^tEps;
  32.   tEps = object (tObject)
  33.     public
  34.       constructor Init (FileName: pChar);
  35.       destructor Done; virtual;
  36.     private
  37.       Xll,Yll,Xur,Yur: Integer;
  38.       Stream: pStream;
  39.       function GetBoundingBox: Boolean;
  40.       procedure GetLine (P: pChar; Length: Word);
  41.       procedure GetText (var Buf; Length: Word);
  42.       procedure Reset;
  43.       procedure SendBody (DC: hDC);
  44.       procedure SendText (DC: hDC; Text: pChar);
  45.     end;
  46.  
  47.   { tImage encapsulates a _transformed_ EPS image; that is, an EPS image
  48.     plus the information necessary to place the image on the page in the
  49.     desired location, size and orientation. aRotationAngle is given in
  50.     degrees, and is rounded to a multiple of 90 degrees. aLeft and aBottom
  51.     are given in inches, and set the desired position of the lower left
  52.     corner of the image, relative to the lower left corner of the page.
  53.     aWidth and aHeight set the desired width and height of the image. aLeft,
  54.     aBottom, aWidth and aHeight all refer to the image in its "normal"
  55.     orientation, _before_ rotation. For example, if the image is rotated 180
  56.     degrees, the image's "lower left" corner is located at the upper right.
  57.     A negative value for aWidth or aHeight will mirror the image about the
  58.     corresponding axis.
  59.  
  60.     Note: The purpose of having a separate tEps object, rather than simply
  61.     folding tEps into tImage, is that it allows one to place more than one
  62.     copy of an EPS image, possibly transformed in different ways, on a
  63.     single page, while maintaining only a single reference to the EPS file. }
  64.  
  65.   pImage = ^tImage;
  66.   tImage = object (tObject)
  67.     public
  68.       constructor Init (aEps: pEps; aRotationAngle: Integer; aLeft,aBottom,
  69.         aWidth,aHeight: Single);
  70.       procedure Send (DC: hDC);
  71.     private
  72.       RotationAngle: Single;
  73.       Left,Bottom,Width,Height: Single;
  74.       Eps: pEps;
  75.       procedure SendBody (DC: hDC);
  76.       procedure SendHeader (DC: hDC);
  77.       procedure SendTrailer (DC: hDC);
  78.       procedure SendTransformation (DC: hDC);
  79.     end;
  80.  
  81. implementation
  82.  
  83. const
  84.  
  85.   { These are some characters that need to be treated specially. }
  86.  
  87.   NUL = #00;
  88.   TAB = #09;
  89.   LF  = #10;
  90.   FF  = #12;
  91.   CR  = #13;
  92.   SP  = #32;
  93.  
  94.   { Build some useful sets of these special characters. }
  95.  
  96.   NewLine    = [LF,CR];
  97.   WhiteSpace = [NUL,TAB,LF,FF,CR,SP];
  98.  
  99.   { The following strings are sent to the printer driver at various times.
  100.     BeginEPS saves the state of the PostScript virtual machine, resets the
  101.     graphics state to the default, pushes a fresh copy of userdict on the
  102.     dictionary stack, and disables the showpage operator. EndEPS undoes all
  103.     of the changes made by BeginEPS. BeginDoc and EndDoc are comments which
  104.     are used to delimit the body of the EPS file; they are required by the
  105.     Adobe document structuring conventions, but are otherwise not
  106.     significant. }
  107.  
  108.   BeginEPS    = 'userdict /EPSPRINT save put initgraphics userdict begin ' +
  109.                 '/showpage {} def'+ CR + LF;
  110.   EndEPS      = 'end clear userdict /EPSPRINT get restore' + CR + LF;
  111.   BeginDoc    = '%%BeginDocument: EpsPrint 1.0' + CR + LF;
  112.   EndDoc      = '%%EndDocument' + CR + LF;
  113.  
  114.   { This is what the first part of a bounding box comment looks like; we
  115.     will use this when searching for the bounding box info. }
  116.  
  117.   BoundingBox = '%%BoundingBox: ';
  118.  
  119.   { Before sending an EPS image, we will reset the PostScript virtual
  120.     machine to the default state, which means that the unit of measure will
  121.     be an Adobe point, equal to 1/72 inch (approximately the same as a
  122.     printer's point). }
  123.  
  124.   DefaultScale = 72;
  125.  
  126.   { We will need a couple of buffers, of nominal size. }
  127.  
  128.   BufSize = 8192;
  129.  
  130. type
  131.  
  132.   { tBufRec is a general-purpose text buffer record whose fields are laid
  133.     out in the format that the PASSTHROUGH printer escape likes to see. }
  134.  
  135.   pBufRec = ^tBufRec;
  136.   tBufRec = record
  137.     Len: Word;
  138.     Buf: array[0..BufSize - 1] of Byte;
  139.     end;
  140.  
  141.   { StrDel is a simple text deletion routine; it will delete Length
  142.     characters from a pChar, starting at character number Start. No error-
  143.     checking is performed. }
  144.  
  145. procedure StrDel (Dest: pChar; Start,Length: Word);
  146.  
  147. begin
  148. Move (Dest[Start + Length],Dest[Start],StrLen (Dest) - (Start + Length) + 1);
  149. end;
  150.  
  151. { ParseInt parses an integer from the beginning of the pChar string P and
  152.   places the result in N. Leading whitespace is ignored, and the end of the
  153.   integer is signified by whitespace or end-of-string. ParseInt returns True
  154.   if the string is successfully parsed, and False otherwise. }
  155.  
  156. function ParseInt (P: pChar; var N: Integer): Boolean;
  157.  
  158. var
  159.   S: array[0..7] of Char;
  160.   I,ErrCode: Integer;
  161.  
  162. begin
  163. while P[0] in WhiteSpace do StrDel (P,0,1);
  164. I := 0;
  165. while (StrLen (P) > 0) and not (P[0] in WhiteSpace) and (I < 6) do
  166.   begin
  167.   S[I] := P[0];
  168.   StrDel (P,0,1);
  169.   Inc (I);
  170.   end;
  171. S[I] := NUL;
  172. Val (S,N,ErrCode);
  173. ParseInt := ErrCode = 0;
  174. end;
  175.  
  176. {--tEpsPrinter---------------------------------------------------------------}
  177.  
  178. { tEpsPrinter.IsPostScriptCapable returns True if the printer device context
  179.   associated with this tEpsPrinter object supports PostScript printing, and
  180.   False otherwise. It is assumed that the device context supports PostScript
  181.   printing if it supports the GETTECHNOLOGY and PASSTHROUGH escapes, and if
  182.   the technology string returned by the GETTECHNOLOGY escape contains the
  183.   substring "PostScript". }
  184.  
  185. function tEpsPrinter.IsPostScriptCapable: Boolean;
  186.  
  187. var
  188.   TechStr: array[0..79] of Char;
  189.   EscapeID: Integer;
  190.   DC: hDC;
  191.  
  192. begin
  193.  
  194. { Create a (temporary) DC. }
  195.  
  196. DC := GetDC;
  197. EscapeID := PASSTHROUGH;
  198. IsPostScriptCapable := (Escape (DC,GETTECHNOLOGY,0,nil,@TechStr) <> 0) and
  199.   (Escape (DC,QUERYESCSUPPORT,SizeOf (Integer),@EscapeID,nil) <> 0) and
  200.   (StrPos (TechStr,'PostScript') <> nil);
  201.  
  202. { Dispose of the temporary DC. }
  203.  
  204. DeleteDC (DC);
  205. end;
  206.  
  207. {--tEps----------------------------------------------------------------------}
  208.  
  209. { tEps.Init sets up a stream associated with the EPS file named in FileName.
  210.   It checks to see whether a preview header is present, and if so, copies
  211.   the PostScript portion of the file to a memory stream, discarding the
  212.   preview header and preview image. Finally, it obtains the bounding box of
  213.   the EPS image. }
  214.  
  215. constructor tEps.Init (FileName: pChar);
  216.  
  217. const
  218.  
  219. { An EPS file containing a preview header must begin with the following
  220.   4-byte signature. }
  221.  
  222.   EpsSig = $C6D3D0C5;
  223.  
  224. { tEpsHeader shows the layout of an EPS preview header. }
  225.  
  226. type
  227.   tEpsHeader = record
  228.     Signature: LongInt;
  229.     PsStart,PsLength: LongInt;
  230.     MfStart,MfLength: LongInt;
  231.     TfStart,TfLength: LongInt;
  232.     CheckSum: Word;
  233.     end;
  234.  
  235. var
  236.   Stripped: pStream;
  237.   Header: tEpsHeader;
  238.   Count: LongInt;
  239.   Buffer: Pointer;
  240.  
  241. begin
  242. inherited Init;
  243. Stream := New (pBufStream,Init (FileName,stOpenRead,BufSize));
  244. if Stream^.Status <> stOk then Fail
  245. else begin
  246.  
  247.   { Grab the preview header (assuming one is there). }
  248.  
  249.   Stream^.Read (Header,SizeOf (Header));
  250.  
  251.   { If the signature matches, then... }
  252.  
  253.   with Header do if Signature = EpsSig then
  254.     begin
  255.  
  256.     { Set up a memory stream. }
  257.  
  258.     Stripped := New (pMemoryStream,Init (PsLength,BufSize));
  259.  
  260.     { Copy the PostScript code from the original EPS file to the memory
  261.       stream. }
  262.  
  263.     Stream^.Seek (PsStart);
  264.     GetMem (Buffer,BufSize);
  265.     while PsLength > 0 do
  266.       begin
  267.       if PsLength > BufSize then Count := BufSize
  268.       else Count := PsLength;
  269.       Stream^.Read (Buffer^,Count);
  270.       Stripped^.Write (Buffer^,Count);
  271.       Dec (PsLength,Count);
  272.       end;
  273.     FreeMem (Buffer,BufSize);
  274.  
  275.     { Dispose of the original stream (i.e., close the EPS file). }
  276.  
  277.     Dispose (Stream,Done);
  278.  
  279.     { Set the Stream field to point to the memory stream instead. }
  280.  
  281.     Stream := Stripped;
  282.     end;
  283.  
  284.   { Locate and parse the %%BoundingBox comment from the PostScript code. }
  285.  
  286.   if not GetBoundingBox then Fail;
  287.   end;
  288. end;
  289.  
  290. destructor tEps.Done;
  291.  
  292. begin
  293. Dispose (Stream,Done);
  294. inherited Done;
  295. end;
  296.  
  297. { tEps.GetBoundingBox searches the EPS file for a valid %%BoundingBox
  298.   comment. If it finds one, it parses the comment and extracts the bounding
  299.   box information into the Xll, Yll, Xur and Yur fields. GetBoundingBox
  300.   returns True if a valid %%BoundingBox comment is found and successfully
  301.   parsed, and False otherwise. }
  302.  
  303. function tEps.GetBoundingBox: Boolean;
  304.  
  305. var
  306.   P: pChar;
  307.   Found: Boolean;
  308.  
  309. begin
  310. Reset;
  311. GetMem (P,256);
  312. Found := False;
  313. repeat
  314.  
  315.   { Retrieve a line from the EPS file. }
  316.  
  317.   GetLine (P,255);
  318.  
  319.   { If it contains the BoundingBox string, then... }
  320.  
  321.   if StrLComp (P,BoundingBox,Length (BoundingBox)) = 0 then
  322.     begin
  323.  
  324.     { Stript off the BoundingBox part }
  325.  
  326.     StrDel (P,0,Length (BoundingBox));
  327.  
  328.     { If the bounding box comment does _not_ contain the substring
  329.       '(atend)', we will assume that it is a valid bounding box comment, and
  330.       proceed to parse it. Otherwise, we'll move on to the next line. }
  331.  
  332.     if StrComp (P,'(atend)') <> 0 then
  333.       begin
  334.  
  335.       { The first number should be Xll. }
  336.  
  337.       Found := ParseInt (P,Xll);
  338.  
  339.       { And so on. }
  340.  
  341.       if Found then Found := ParseInt (P,Yll);
  342.       if Found then Found := ParseInt (P,Xur);
  343.       if Found then Found := ParseInt (P,Yur);
  344.       end;
  345.     end;
  346.  
  347.   { Keep trying until a valid bounding box is obtained or we reach end of
  348.     file. }
  349.  
  350.   until Found or (StrLen (P) = 0);
  351. FreeMem (P,256);
  352. Reset;
  353. GetBoundingBox := Found;
  354. end;
  355.  
  356. { tEps.GetLine reads characters from the Stream (i.e., the EPS file) and
  357.   builds a string representing one line of the original text file. The end
  358.   of a line is signified by the presence of a character from the NewLine
  359.   set. If the line is longer than Length, only the first Length characters
  360.   are returned; one or more subsequent calls to GetLine will return the
  361.   remainder of the line. GetLine strips leading whitespace and trailing
  362.   newline characters from the string. GetLine returns a null string if
  363.   Stream's file pointer is at end-of-file. }
  364.  
  365. procedure tEps.GetLine (P: pChar; Length: Word);
  366.  
  367. type
  368.   tStates = (sStart,sReading,sEnd,sDone);
  369.  
  370. var
  371.   C: Char;
  372.   I: Integer;
  373.   State: tStates;
  374.  
  375. begin
  376. I := 0;
  377. State := sStart;
  378. repeat
  379.   case State of
  380.  
  381.     { State = sStart: Strip leading whitespace. }
  382.  
  383.     sStart: begin
  384.       Stream^.Read (C,SizeOf (C));
  385.       if Stream^.Status <> stOk then State := sEnd
  386.       else if not (C in WhiteSpace) then State := sReading;
  387.       end;
  388.  
  389.     { State = sReading: Read characters from stream and append to the return
  390.       string. }
  391.  
  392.     sReading: begin
  393.       P[I] := C;
  394.       Inc (I);
  395.       if I >= Length then State := sEnd
  396.       else begin
  397.         Stream^.Read (C,SizeOf (C));
  398.         if (Stream^.Status <> stOk) or (C in NewLine) then State := sEnd;
  399.         end;
  400.       end;
  401.  
  402.     { State = sEnd: Add a trailing NUL character to the return string. }
  403.  
  404.     sEnd: begin
  405.       P[I] := NUL;
  406.       Stream^.Reset;
  407.       State := sDone;
  408.       end;
  409.     end;
  410.   until State = sDone;
  411. end;
  412.  
  413. { tEps.GetText reads Length characters from Stream and inserts them into
  414.   Buf. No error-checking is performed. }
  415.  
  416. procedure tEps.GetText (var Buf; Length: Word);
  417.  
  418. begin
  419. Stream^.Read (Buf,Length);
  420. end;
  421.  
  422. { tEps.Reset sets Stream's file pointer to zero (beginning of the file). }
  423.  
  424. procedure tEps.Reset;
  425.  
  426. begin
  427. Stream^.Seek (0);
  428. end;
  429.  
  430. { tEps.SendBody sends the full body of PostScript code to the printer
  431.   device context. }
  432.  
  433. procedure tEps.SendBody (DC: hDC);
  434.  
  435. var
  436.   Buffer: pBufRec;
  437.   Count,Size: LongInt;
  438.  
  439. begin
  440. SendText (DC,BeginDoc);
  441. New (Buffer);
  442. Reset;
  443. Size := Stream^.GetSize;
  444. while Size > 0 do
  445.   begin
  446.   if Size > BufSize then Count := BufSize
  447.   else Count := Size;
  448.   GetText (Buffer^.Buf,Count);
  449.   Buffer^.Len := Count;
  450.   Escape (DC,PASSTHROUGH,0,Buffer,nil);
  451.   Size := Size - Count;
  452.   end;
  453. Dispose (Buffer);
  454. SendText (DC,EndDoc);
  455. end;
  456.  
  457. { tEps.SendText sends the text specified in Text to the printer device
  458.   context. }
  459.  
  460. procedure tEps.SendText (DC: hDC; Text: pChar);
  461.  
  462. var
  463.   Buffer: pBufRec;
  464.  
  465. begin
  466. GetMem (Buffer,StrLen (Text) + SizeOf (Word));
  467. Buffer^.Len := StrLen (Text);
  468. Move (Text^,Buffer^.Buf,Buffer^.Len);
  469. Escape (DC,PASSTHROUGH,0,Buffer,nil);
  470. FreeMem (Buffer,StrLen (Text) + SizeOf (Word));
  471. end;
  472.  
  473. {--tImage--------------------------------------------------------------------}
  474.  
  475. constructor tImage.Init (aEps: pEps; aRotationAngle: Integer; aLeft,aBottom,
  476.   aWidth,aHeight: Single);
  477.  
  478. begin
  479. inherited Init;
  480. Eps := aEps;
  481.  
  482. { The rotation angle is rounded to the nearest multiple of 90 degrees;
  483.   rotation about an arbitrary angle is left as an exercise for the reader. }
  484.  
  485. RotationAngle := 90 * Round (aRotationAngle / 90);
  486. Left := aLeft;
  487. Bottom := aBottom;
  488. Width := aWidth;
  489. Height := aHeight;
  490. end;
  491.  
  492. { tImage.Send transmits the EPS data to the printer device context, along
  493.   with the necessary header, placement information and trailer. }
  494.  
  495. procedure tImage.Send (DC: hDC);
  496.  
  497. begin
  498. SendHeader (DC);
  499. SendTransformation (DC);
  500. SendBody (DC);
  501. SendTrailer (DC);
  502. end;
  503.  
  504. procedure tImage.SendBody (DC: hDC);
  505.  
  506. begin
  507. Eps^.SendBody (DC);
  508. end;
  509.  
  510. procedure tImage.SendHeader (DC: hDC);
  511.  
  512. begin
  513. Eps^.SendText (DC,BeginEps);
  514. end;
  515.  
  516. procedure tImage.SendTrailer (DC: hDC);
  517.  
  518. begin
  519. Eps^.SendText (DC,EndEps);
  520. end;
  521.  
  522. { tImage.SendTransformation calculates the translation, scaling and rotation
  523.   needed to transform the original EPS image to its final location, size and
  524.   orientation, and sends the PostScript commands required to perform this
  525.   transformation to the printer device context. }
  526.  
  527. procedure tImage.SendTransformation (DC: hDC);
  528.  
  529. var
  530.   L,B,W,H,A: String[10];
  531.   S: String[40];
  532.  
  533. begin
  534. with Eps^ do
  535.   begin
  536.   Str ((DefaultScale * Left):0:2,L);
  537.   Str ((DefaultScale * Bottom):0:2,B);
  538.   S := L + ' ' + B + ' translate ' + NUL;
  539.   SendText (DC,@S[1]);
  540.   Str (RotationAngle:0:0,A);
  541.   S := A + ' rotate ' + NUL;
  542.   SendText (DC,@S[1]);
  543.   Str ((DefaultScale * Width / (Xur - Xll)):0:2,W);
  544.   Str ((DefaultScale * Height / (Yur - Yll)):0:2,H);
  545.   S := W + ' ' + H + ' scale ' + NUL;
  546.   SendText (DC,@S[1]);
  547.   Str ((-1.0 * Xll):0:2,L);
  548.   Str ((-1.0 * Yll):0:2,B);
  549.   S := L + ' ' + B + ' translate' + CR + LF + NUL;
  550.   SendText (DC,@S[1]);
  551.   end;
  552. end;
  553.  
  554. end.
  555.