[gs-cvs] rev 7999 - in trunk/gs: doc lib

alexcher at ghostscript.com alexcher at ghostscript.com
Wed May 23 13:27:43 PDT 2007


Author: alexcher
Date: 2007-05-23 13:27:42 -0700 (Wed, 23 May 2007)
New Revision: 7999

Modified:
   trunk/gs/doc/Use.htm
   trunk/gs/lib/pdf_draw.ps
   trunk/gs/lib/pdf_main.ps
Log:
Add support for /UserUnit. Also fix some related bugs that stayed
in the way while testing it. Patch from SaGS.
Bug 688124 from customet 870, bug 688359.

DIFFERENCES:
None

DETAILS:
(A) The basic implementation: scaling the PS user space

/UserUnit implementation is very similar to -dPDFFitPages.

- If -dPDFFitPages=true, then /UserUnit is ignored; no matter 
  how small or haw large is 1 PDF user space unit, it has to 
  be scaled so that the PDF page fits on the paper.
- Else, the /UserUnit scales the PS user space, exactly like 
  -dPDFFitPage does. This solves [almost] everything related 
  to drawing marks on the page.

This also means that all problems with -dPDFFitPages affect 
the implementation of /UserUnit. The biggest problem was to 
get rid of those, especially for PDF->PDF "conversion" where 
some elements of the source PDF (outlines, links...) needed 
to be preserved.

(B) Don't scale the border width in a border style dict
    (the change to pdf_draw.ps)

See comment in code. Note that a border width in a border style 
ARRAY is specified in user space units, so it grows with 
/UserUnit and the scaling of the PS user space suffices.

(C) No more /PAGES for /CropBox
    (pdf_main.ps hunk @@ -129,10 +129,6 @@ 
    and the "pget" instead of "knownoget" for /PAGE pdfmark)

/UserUnit, /Rotate and the translation due to non-(0,0) PDF page 
origin are "flattened" into the page; also -dPDFFitPage scales 
the page. This means the even if 2 source PDF pages had the same 
/CropBox, in the destination PDF these may need different 
/CropBox-es.

Example:

source:  page #1 /UserUnit 1 and /CropBox [0 0 100 100]
	 page #2 /UserUnit 2 and /CropBox [0 0 100 100] (the same)
becomes: page #1 no /UserUnit and /CropBox [0 0 100 100]
	 page #2 no /UserUnit and /CropBox [0 0 200 200] (differs)

so the /CropBox cannot be inherited anymore. The new code puts a 
/CropBox into each page that has or inherits one.

NOTE:
  There are 2 more places in pdf_main.ps that do a "knownget" 
  for /CropBox, one a few lines after "%****** DOESN'T HANDLE 
  COLOR TRANSFER YET ******" and one after "(Adobe Tech Note 
  5407, sec 9.2)". I think both should be using "pget".

(D) EXTRA: /CropBox-es in intermediate /Pages were ignored

Old code preserved only /CropBox-es that appeared in the root 
PDF /Pages object and in /Page objects, the ones in intermediate 
nodes of the /Pages tree being lost. In the new code, this 
gets fixed for PDF->PDF as a side effect of the implementation 
for (C). See also the note above for clipping of the marks 
drawn on the page.

(E) PDF->PS "default" user space transform

The new code computes a matrix that transforms the PDF default 
user space of a page to the PS default user space. This matrix 
accounts for the rotation (/Rotate), scaling (-dPDFFitPage or 
/UserUnit) and any translation needed to move the PDF 
lower-left corner the the lower-left corner of the paper.

- This is done in the new pdf_main.ps::pdf_PDF2PS_matrix, 
  which inherits, with the needed changes, almost all of the 
  code in the old .pdfshowpage_Install.

- The matrix is page-specific because different pages may have 
  different dimensions (so -dPDFFitPage scales them 
  differently), different /Rotate or /UserUnit.

- pdf_main.ps::pdf_cached_PDF2PS_matrix is a utility proc that 
  ensures the matrix for a given page is computed, caches it 
  in the PDF page dictionary under the key given by 
  pdf_main.ps::PDF2PS_matrix_key, then returns the matrix.

- (The definition for PDF2PS_matrix_key exists only to allow 
  binding of a complicated name into pdf_cached_PDF2PS_matrix; 
  avoids doing "(complicated.name) cvn" at run-time.)

- This matrix is currently used:
  (E.1) by .pdfshowpage_Install (which is now reduced to 2 
	lines) for setting up the PS user space;
  (E.2) for transforming the /Crop- or /MediaBox in 
	pdfshowpage_setpage
  (E.3) to transform coordinates in view destinations (used 
	by outline entries, PDF links ...).

(F) /Orientation now always 0

pdfmark does not work correctly with /Orientation != 0 (long 
story). The old code used the /Orientation page device 
parameter to handle the /Rotate from PDF pages. The new 
code always sets /Orientation to 0 and handles /Rotate by 
explicitely doing a "rotate" (in pdf_PDF2PS_matrix).

- Avoids GS-specific hackery otherwise needed to work around 
  pdfmark problems when /Orientation != 0.

- Simplifies the code, because a single transformation matrix 
  needs to be computed both for setting up the PS user space 
  and for transforming varions coordinates used in pdfmarks
  (/CropBox, view destinations).

(G) PDF->PDF-migrated view destinations were wrong
    (pdf_main.ps hunks @@ -939,6 +935,45 @@ 
    and @@ -947,18 +982,30 @@)

Coordinates appearing in view destinations need to be recomputed, 
due the PS default user space, AS USED BY the pdfwrite driver, 
not being identical to the original PDF default user space. The 
list of causes included rotation due to /Rotate (implemented either 
with /Orientation or a simple "rotate"), translation due to 
non-(0,0) PDF page origin, and scaling due to -dPDFFitPages; now 
we add /UserUnit to this list.

(H) EXTRA: -dPDFFitPage now chooses portrait or landscape
    (pdf_main.ps, near the end of hunk @@ -1031,62 +1078,133 @@)

If -dPDFFitPage, the code after "% Preserve page size," chooses 
portrait or landscape orientation depending on the PDF page's 
width:height ratio. I consider this results in a better "fit to 
page". Example: PDF with mixed portrait + landscape letter 
pages, to be printed on A4 paper. Old code sometimes fitted 
landscape pages on portrait paper.

(I) EXTRA: better placement of imaged area

If -dPDFFitPage and the PDF page's width:height ratio differs 
from the paper's width:height, some unused space remains. With 
the old code, this extra space was placed at left/right/top/bottom 
depending on the page's /Rotate. New code always puts the extra 
space either at right or top (depending only on the PDF page 
being relatively "taller" or "wider" than the paper). This 
is mainly a side effect of not using /Orientation anymore.

(J) -dNoUserUnit

I added a new option that can be used to disable processing of 
/UserUnit. Named NoUserUnit, defaults to "false" meaning 
/UserUnit being taken into account. I implemented this default 
following Adobe Reader's 7.0.7/Windows default, but see note.

Note:
  I suggest to set the default to ignore UserUnit, and have a 
  -dDoUserUnit option to activate it. I can do this change 
  if desired.
  I'll explain the reson for such a choice through an example:
- I THINK that UserUnit was introduced by Adobe as part of 
  it [Adobe] entering the CAD world.
- Consider a floorplan plotted on a sheet of paper at a certain 
  scale, let's say 1:50.
- In this scenario, the PDF page corresponds to the plotted 
  paper, so it has a MediaBox of that size.
- If the scale is 1:50, set UserUnit = 50. This allows someone, 
  given a suitable UI, to easily and accurately MEASURE 
  various elements of the floorplan.
- Ghostscript does not have such a UI, and I think such a UI 
  is beyond GS's purpose.
- GS's role, however, is to PRINT that PDF in order to obtain 
  the "plotted" floorplan.
- To obtain the equivalent of the plotted paper, printing must 
  ignore UserUnit. Observing UserUnit for printing would 
  require a building-sized sheet of paper!

(K) TESTING DETAIL: "transform" returns reals

"<x> <y> <matrix> transform" returns 2 realtype objects, even 
when <x> <y> are integertype and the matrix is [1 0 0 1 0 0] 
(identity, containing only integers). This makes, for example, 
a Media- or CropBox of [0 0 612 792] in a source PDF to be 
written as [0 0 612.0 792] in the destination, which is 
annoying if comparing the output of unpatched and patched 
Ghostscript.


Modified: trunk/gs/doc/Use.htm
===================================================================
--- trunk/gs/doc/Use.htm	2007-05-23 18:51:55 UTC (rev 7998)
+++ trunk/gs/doc/Use.htm	2007-05-23 20:27:42 UTC (rev 7999)
@@ -630,6 +630,16 @@
 not known yet when it does.
 </dl>
 
+<dl>
+<dt><b><tt>-dNoUserUnit</tt></b>
+<dd>
+Ignore <b><tt>UserUnit</tt></b> parameter. This may be useful for backward
+compatibility with old versions of Ghostscript and Adobe Acrobat,
+or for processing files with large values of <b><tt>UserUnit</tt></b>
+that otherwise exceed implementation limits.
+</dl>
+
+
 <h3><a name="PDF_problems"></a>Problems interpreting a PDF file</h3>
 
 <p>

Modified: trunk/gs/lib/pdf_draw.ps
===================================================================
--- trunk/gs/lib/pdf_draw.ps	2007-05-23 18:51:55 UTC (rev 7998)
+++ trunk/gs/lib/pdf_draw.ps	2007-05-23 20:27:42 UTC (rev 7999)
@@ -1233,6 +1233,15 @@
     } if
     dup type /dicttype eq {
     dup /W knownoget not { 1 } if
+      % Per PDF1.6 Reference table 8.13, /W in the border style dictionary is
+      % expressed in points (an absolute unit), so compensate here for any
+      % scaling of the PostScript user space done due to /UserUnit.
+      % Scaling due to -dPDFFitPage is not undone, to keep the correct border width
+      % compared to the size of the surrounding marks.
+      //systemdict /NoUserUnit .knownget not { false } if not
+      //systemdict /PDFFitPage known not and {	% UserUnit is ignored if -dPDFFitPage
+        Page /UserUnit knownoget { div } if
+      } if
     [] 2 index /S knownoget {
       /D eq { 2 index /D knownoget not { [3] } if exch pop } if
     } if 3 -1 roll pop strokeborder

Modified: trunk/gs/lib/pdf_main.ps
===================================================================
--- trunk/gs/lib/pdf_main.ps	2007-05-23 18:51:55 UTC (rev 7998)
+++ trunk/gs/lib/pdf_main.ps	2007-05-23 20:27:42 UTC (rev 7999)
@@ -142,10 +142,6 @@
    GS_PDF_ProcSet begin
    pdfdict begin
    pdfopen begin
-   Trailer /Root oget /Pages oget /CropBox knownoget
-    { oforce_array normrect mark /CropBox 3 -1 roll /PAGES pdfmark
-    }
-   if
    /FirstPage where 
     { pop FirstPage dup pdfpagecount gt
       { (\nRequested FirstPage is greater than the number of pages in the file: ) print
@@ -1002,6 +998,45 @@
     } if
   } ifelse
 } bind def
+
+% Procedures to do the necessary transformations of view destinations
+% <PDF2PS_matrix> <rot> <view> -- <view'>
+/viewdestprocs 8 dict dup begin
+    /Fit  { exch pop exch pop } bind def
+    /FitH {
+	aload pop
+	0 4 -1 roll 1 and 0 eq { exch } if
+	4 -1 roll transform exch pop
+	2 array astore
+    } bind def
+    /FitV {
+	aload pop
+	0 4 -1 roll 1 and 0 ne { exch } if
+	4 -1 roll transform pop
+	2 array astore
+    } bind def
+    /FitB  /Fit  load def
+    /FitBH /FitH load def
+    /FitBV /FitV load def
+    /XYZ  {
+	aload pop
+	3 1 roll
+	2 copy 7 -1 roll 1 and 0 ne { exch } if	4 2 roll    % odd rotation switches x<->y
+	2 { dup null eq { pop 0 } if exch } repeat	    % replace nulls with 0
+	7 -1 roll transform				    % transform coordinates
+	2 { 3 -1 roll null eq { pop null } if exch } repeat % put the nulls back
+	3 -1 roll
+	4 array astore
+    } bind def
+    /FitR {
+	exch pop
+	aload pop
+	2 { 5 index transform 4 2 roll } repeat normrect_elems
+	5 array astore
+	exch pop
+    } bind def
+end readonly def
+
 /linkdest {		% <link|outline> linkdest
 			%   ([/Page <n>] /View <view> | ) <link|outline>
   dup /Dest knownoget
@@ -1010,18 +1045,30 @@
       dup null eq
        { pop }
        { dup 0 oget
-         dup type /dicttype eq {
-           dup /Type knownoget {
+	 false % don't have a page# and transformation matrix (yet)
+	 1 index type /dicttype eq {
+	   1 index /Type knownoget {
              /Page eq {
+	       pop % the "false" flag
+	       dup pdf_cached_PDF2PS_matrix exch
+	       dup /Rotate pget not { 0 } if 90 idiv exch
                pdfpagenumber
+               true % now we have a page# and a transformation matrix
              } if
            } if
          } if
-         dup type /integertype ne 
-           { pop }
-           { /Page exch 4 -2 roll }
-         ifelse
-	 dup length 1 sub 1 exch getinterval /View exch 3 -1 roll
+	 % stack: <link|outline> <dest>	( <PDF2PS_matrix> <rot>	<page#>	true  |	 <page> false )
+	 {
+	   /Page exch 6	2 roll
+	   % stack: [/Page <page#>] <link|outline> <dest> <PDF2PS_matrix> <rot>
+	   3 -1 roll dup length 1	sub 1 exch getinterval /View 4 1 roll
+	   % stack: [/Page <page#>] <link|outline> /View <PDF2PS_matrix> <rot> <view>
+	   //viewdestprocs 1 index 0 get get exec
+	   3 -1 roll
+	 } { 
+	   pop
+	   dup length 1 sub 1 exch getinterval /View exch 3 -1 roll
+	 } ifelse
        }
       ifelse
     }
@@ -1102,67 +1149,140 @@
   } if
 } bind def
 
-/.pdfshowpage_Install {	% <pagedict> [<prevproc>] .pdfshowpage_Install -
-  exch
-	% We would like to clip to the CropBox here, but the subsequent
-	% initgraphics would override it.  Instead, we have to handle it
-	% in graphicsbeginpage.
-  dup /CropBox pget dup {exch pop} if systemdict /UseCropBox known and {
-    /CropBox exch /CropBox pget pop
+% Compute the matrix that transforms the PDF->PS "default" user space
+/pdf_PDF2PS_matrix {	% <pdfpagedict> -- matrix
+  matrix currentmatrix matrix setmatrix exch
+  % stack: savedCTM <pdfpagedict>
+  dup /CropBox pget dup {exch pop} if //systemdict /UseCropBox known and {
+    /CropBox 2 copy pget pop
   } {
-    /MediaBox exch get_media_box
+    /MediaBox 1 index get_media_box
   } ifelse
-  % stack: [<prevproc>] /Crop|MediaBox <Crop|Media Box>
+  % stack: savedCTM <pdfpagedict> /Crop|MediaBox <Crop|Media Box>
   oforce_elems normrect_elems fix_empty_rect_elems 4 array astore
-  systemdict /PDFFitPage known {
+  //systemdict /PDFFitPage known {
     PDFDEBUG { (Fiting PDF to imageable area of the page.) = flush } if
     currentpagedevice /.HWMargins get aload pop
     currentpagedevice /PageSize get aload pop
+    % Adjust PageSize and .HWMargins for the page portrait/landscape orientation
+    2 copy gt
+    7 index aload pop 3 -1 roll sub 3 1 roll exch sub exch
+    10 index /Rotate pget not { 0 } if 90 idiv 1 and 0 ne { exch } if
+    gt
+    ne {
+      2 copy ne {
+        % rotate the .HWMargins
+        2 copy lt {
+	  6 2 roll 4 -1 roll 6 -2 roll
+        } {
+	  6 2 roll 4  1 roll 6 -2 roll
+        } ifelse
+        % rotate the page dimensions
+        exch
+      } if
+    } if
     3 -1 roll sub 3 1 roll exch sub exch
-    % stack: [<prevproc>] <pagedict> <Crop|Media Box> Xmin Ymin Xmax Ymax
+    % stack: savedCTM <pdfpagedict> <Crop|Media Box> Xmin Ymin Xmax Ymax
     PDFDEBUG { (    Translate up by [ ) print 3 index =print (, ) print 2 index =print ( ]) = flush } if
     3 index 3 index translate		% move origin up to imageable area
     2 index sub exch 3 index sub exch 4 2 roll pop pop
-	    % stack: [Box] XImageable YImageable
+	    % stack: savedCTM <pdfpagedict> [Box] XImageable YImageable
     2 index aload pop 2 index sub exch 3 index sub exch 4 2 roll pop pop
-	    % stack: [Box] XImageable YImageable XBox YBox
+    5 index /Rotate pget not { 0 } if 90 idiv 1 and 0 ne { exch } if
+	    % stack: savedCTM <pdfpagedict> [Box] XImageable YImageable XBox YBox
     3 -1 roll exch div 3 1 roll div .min
     PDFDEBUG { (    Scale by ) print dup = flush } if
-    dup scale
-  } if
+  } {
+    //systemdict /NoUserUnit .knownget not { false } if {
+      1
+    } {
+      1 index /UserUnit knownoget {
+        PDFDEBUG { (Scaling due to UserUnit by ) print dup = flush } if
+      } {
+        1
+      } ifelse
+    } ifelse
+  } ifelse
+  % stack: savedCTM <pdfpagedict> [Box] scale
+  dup scale
+  % Rotate according to /Rotate
+  aload pop boxrect
+  {
+    {     pop pop }
+    { -90 rotate pop neg 0 translate }
+    { 180 rotate neg exch neg exch translate }
+    {  90 rotate neg 0 exch translate pop }
+  }
+  5 index /Rotate pget not { 0 } if
+  PDFDEBUG { dup 0 ne { (Rotating by ) print dup =print ( degrees.) = flush } if } if
+  90 idiv 3 and get exec
   % Now translate to the origin given in the Crop|Media Box
-  dup 0 get neg exch 1 get neg translate
-  0 get
-  exec
+  exch neg exch neg translate
+  % stack: savedCTM <pdfpagedict>
+  pop
+  matrix currentmatrix exch setmatrix
 } bind def
 
+% Cache the matrix that transforms the PDF->PS "default" user space
+% into <pdfpagedict> under the key //PDF2PS_matrix_key, then return it
+/PDF2PS_matrix_key (PDF->PS matrix) cvn def
+/pdf_cached_PDF2PS_matrix {  % <pdfpagedict> -- <PDF2PS_matrix>
+  dup //PDF2PS_matrix_key .knownget {
+    exch pop
+  } {
+    dup dup pdf_PDF2PS_matrix //PDF2PS_matrix_key exch put
+    //PDF2PS_matrix_key get
+  } ifelse
+} bind def
+currentdict /PDF2PS_matrix_key undef
+
+/.pdfshowpage_Install {	% <pagedict> [<prevproc>] .pdfshowpage_Install -
+  exch pdf_cached_PDF2PS_matrix concat
+  0 get exec
+} bind def
+
 /pdfshowpage_setpage {	% <pagedict> pdfshowpage_setpage <pagedict>
   5 dict begin		% for setpagedevice
-	% Stack: pagedict
+	% Stack: pdfpagedict
   % UseCIEColor is always true for PDF; see the comment in runpdf above
   /UseCIEColor true def
-  currentpagedevice /Orientation 2 index /Rotate pget not { 0 } if 90 idiv
-	% Rotate specifies *clockwise* rotation!
-    neg 3 and def
-	% Stack: pagedict currentpagedict
-  1 index /CropBox pget dup {exch pop} if systemdict /UseCropBox known and {
-			% Set the page size.
-    1 index /CropBox pget pop /CropBox exch oforce_elems normrect_elems
-    fix_empty_rect_elems boxrect
-    2 array astore /PageSize exch def pop pop
+  /Orientation 0 def
+  currentpagedevice
+	% Stack: pdfpagedict currentpagedevicedict
+  1 index /CropBox pget dup {exch pop} if //systemdict /UseCropBox known and {
+    /CropBox 2 index /CropBox pget % will use the CropBox
   } {
-    1 index /MediaBox pget {
-			% Set the page size.
-      /MediaBox exch oforce_elems normrect_elems fix_empty_rect_elems boxrect
-      2 array astore /PageSize exch def pop pop
-    } if
+    /MediaBox 2 index get_media_box true % will use the MediaBox
   } ifelse
-  % Don't change the page size if we are going to fit the PDF to the page
-  systemdict /PDFFitPage known { currentdict /PageSize undef } if
+  {
+    oforce_elems normrect_elems fix_empty_rect_elems boxrect 4 2 roll pop pop
+    3 index /Rotate pget not { 0 } if 90 idiv 1 and 0 ne { exch } if
+    % stack: pdfpagedict currentpagedevicedict boxwidth boxheight
+    //systemdict /PDFFitPage known {
+      % Preserve page size,
+      % but choose portrait/landscape depending on box width:height ratio
+      % (if box width == height, select portrait orientation)
+      gt
+      1 index /PageSize get aload pop
+      2 copy gt
+      4 -1 roll ne { exch } if
+    } {
+      % Set the page size.
+      //systemdict /NoUserUnit .knownget not { false } if not {
+        3 index /UserUnit knownoget {
+          dup 4 -1 roll mul 3 1 roll mul
+        } if
+      } if
+    } ifelse
+    2 array astore /PageSize exch def
+  } {
+    pop % pops /Crop|MediaBox
+  } ifelse
   % Determine the number of spot colors used on the page.  Note: This searches
   % the pages resources.  It may be high if a spot color is in a resource but
   % is not actually used on the page.
   << /PageSpotColors 3 index countspotcolors >> setpagedevice
+
   % Let the device know if we will be using PDF 1.4 transparency.
   % The clist logic may need to adjust the size of bands.
   1 index pageusestransparency /PageUsesTransparency exch def
@@ -1198,32 +1318,13 @@
   .writepdfmarks {
 
 	% Copy the crop box.
-    dup /CropBox knownoget {
-      oforce_array normrect
-
-        % .pdfshowpage_Install translates the origin -
+    dup /CropBox pget {
+        % .pdfshowpage_Install transforms the user space -
         % do same here with the CropBox.
-
-      1 index /CropBox pget dup {exch pop} if systemdict /UseCropBox known and {
-        1 index /CropBox pget pop /CropBox exch
-      } {
-        1 index get_media_box /MediaBox exch
-      } ifelse
-      oforce_elems normrect_elems fix_empty_rect_elems 4 array astore
-      dup 0 get exch 1 get   % [] tx ty
-      2 index 0 get 2 index sub 3 index exch 0 exch put
-      2 index 2 get 2 index sub 3 index exch 2 exch put
-      2 index 1 get 1 index sub 3 index exch 1 exch put
-      2 index 3 get 1 index sub 3 index exch 3 exch put
-      pop pop
-
-	% If the page has been rotated, rotate the CropBox.
+      oforce_elems
+      2 { Page pdf_cached_PDF2PS_matrix transform 4 2 roll } repeat
+      normrect_elems /CropBox 5 1 roll fix_empty_rect_elems 4 array astore
       mark /CropBox 3 -1 roll
-      3 index /Rotate pget {
-	90 idiv 1 and 0 ne {
-	  aload pop 4 -2 roll exch 4 2 roll exch 4 array astore
-	} if
-      } if
       /PAGE pdfmark
     } if
 
@@ -1246,7 +1347,7 @@
 	%****** DOESN'T HANDLE COLOR TRANSFER YET ******
    /TRDefault currenttransfer def
   matrix currentmatrix 2 dict
-  2 index /CropBox knownoget {
+  2 index /CropBox pget {
     oforce_elems normrect_elems boxrect
     4 array astore 1 index /ClipRect 3 -1 roll put
   } if
@@ -1263,7 +1364,7 @@
       % If the page has a Group, enclose contents in transparency group.
       % (Adobe Tech Note 5407, sec 9.2)
       dup /Group knownoget {
-	1 index /CropBox knownoget {
+	1 index /CropBox pget {
           /CropBox exch
         } {
 	  1 index get_media_box /MediaBox exch



More information about the gs-cvs mailing list