This peculiar bug is triggered only when the clip operator is executed against a perfectly horizontal single line path, where the Y coordinate is an integer, or has a fracional component of 0.00196 or less. The bug is particularly severe for my application, coz I use "clip clippath" to basically reduce the current path to the effective path after consulting with the existing clip region, and then I print out the resulting path with coordinates. So when this bug is triggerd, my application prints out a null path. The bug has been triggered by a real customer PS document, but I'm only producing a few lines of sample code to demonstrate the bug visually in Ghostscript. To reproduce the bug visually, copy the following lines of PS into a file (say clipbug.ps): ------------------------------------------- clipsave newpath 20 300.01 moveto 700 300.01 lineto clip stroke cliprestore clipsave newpath 20 200.00196 moveto 700 200.00196 lineto % to demonstrate the bug, change 200.00196 to 200.00197 in the above two lines clip stroke cliprestore showpage ------------------------------------------- Then run gs from the commandline like so: gs -r72 clipbug.ps You will notice just one line on the display instead of two. Now if you change 200.00196 to 200.00197 as indicated in the comment in that file, and then run gs again, you will see that both lines are displayed as they should be. Is there a simple fix for this?
Added May 24th: The bug is also triggered when the line is perfectly vertical and the X co'ds are very close to the integer. I'm attaching a Ps file that demonstrates both the vertical and horizontal line bug. I've also confirmed that the bug occurs when the value of the constant co-ordinate is close to the integer from both sides. (i.e. 200.00197 will trigger it as well as 199.99804 ) This bug is occuring for us in documents containing tables which use vertical hairline-rules to seperate columns and delimit the table boundries. Copy the following lines into ps file and run it with gs. Instructions on how to change values and trigger the bug are in PS comments. --------------------------------------------------------------------- clipsave % Demo of the bug in a Vertical situation newpath 200.00195 400 moveto 200.00195 600 lineto % in the present form no vertical line is displayed. % change 200.00194 to 200.00195 or more => the vertical line appears % if you change the value to 199.99804 no vertical line will be displayed % change 199.99804 to 199.99803 or less => the vertical line appears clip stroke cliprestore clipsave % Demo of the bug in a Horizontal situation newpath 100 200.00196 moveto 300 200.00196 lineto % in the present form, no horizontal line is displayed. % change 200.00196 to 200.00197 or more => the horizontal line appears % change the value to 199.99806 and the horizontal line will not be displayed % change 199.99806 to 199.99805 or less => the horizontal line appears clip stroke cliprestore showpage ----------------------------------------------------------------------
As a result of further experimentation, here is the most accurate test case and numbers that we can come up with so far. Based on the new findings, I'll redefine the conditions when the problem happens: 1) The path contains a perfectly horizontal or perfectly vertical line. The path is a single moveto followed by one or more linetos with any after the first one continuing forward in the same direction. AND 2) The constant coordinate (Y for horizontal lines, X for vertical lines) falls exactly on an integral pixel position in user space. AND 3) The line starts out inside the clip region and extends beyond the clip region AND 4) The clip operator is used to get the intersection of the current clip region with the current path. Here's the "script" listing from the series of commands I ran. It also includes the test file that I used. The bug can be seen on the lines that end with "M 0.0 0.0" If the test script is modified to do a "1.5 1.5 scale" just before drawing the lines, then the bug still occurs on the same lines, indicating that it's the user space pixel values that matter and not device space. ------------------------------------------------------------------------------ GPL Ghostscript 8.57 (2007-05-11) Copyright (C) 2007 artofcode LLC, Benicia, CA. All rights reserved. [rep@dinar bin]$ ARGS='-sDEVICE=bbox -dBATCH -dNOPAUSE -q /tmp/cliplinesbug.ps' [rep@dinar bin]$ cat /tmp/cliplinesbug.ps % stdout reporting routines /std (%stdout) (w) file def /sw { std exch writestring } def % str write /aw { 30 string cvs sw } def % any write /nl { (\n) sw } def % newline % report resolution currentpagedevice /HWResolution get {} forall pop /res exch def (RESOLUTION: ) sw res aw nl (POINTS -> PIXELS: PATH .... ) sw nl 0 0 600 600 rectclip /ds{ 200 add dup aw ( -> ) sw % display Y coord dup res 72 div mul aw (:) sw % and equiv in pixels clipsave newpath dup 100 exch moveto 700 exch lineto clip gsave clippath { ( M ) sw exch aw ( ) sw aw } { ( L ) sw exch aw ( ) sw aw } { (curveto: not reached) sw } { ( C ) sw } pathforall nl grestore stroke cliprestore } def % /incr comes from the commandline e.g. -Dincr=0.2 0 25 { dup ds incr add } repeat pop showpage [rep@dinar bin]$ ./gs -r72 -Dincr=0.1 $ARGS 2>/dev/null RESOLUTION: 72.0 POINTS -> PIXELS: PATH .... 200 -> 200.0: M 0.0 0.0 200.1 -> 200.1: M 100.0 200.0 L 100.0 201.0 L 600.0 201.0 L 600.0 200.0 C 200.2 -> 200.2: M 100.0 200.0 L 100.0 201.0 L 600.0 201.0 L 600.0 200.0 C 200.3 -> 200.3: M 100.0 200.0 L 100.0 201.0 L 600.0 201.0 L 600.0 200.0 C 200.4 -> 200.4: M 100.0 200.0 L 100.0 201.0 L 600.0 201.0 L 600.0 200.0 C 200.5 -> 200.5: M 100.0 200.0 L 100.0 201.0 L 600.0 201.0 L 600.0 200.0 C 200.6 -> 200.6: M 100.0 200.0 L 100.0 201.0 L 600.0 201.0 L 600.0 200.0 C 200.7 -> 200.7: M 100.0 200.0 L 100.0 201.0 L 600.0 201.0 L 600.0 200.0 C 200.8 -> 200.8: M 100.0 200.0 L 100.0 201.0 L 600.0 201.0 L 600.0 200.0 C 200.9 -> 200.9: M 100.0 200.0 L 100.0 201.0 L 600.0 201.0 L 600.0 200.0 C 201.0 -> 201.0: M 0.0 0.0 201.1 -> 201.1: M 100.0 201.0 L 100.0 202.0 L 600.0 202.0 L 600.0 201.0 C 201.2 -> 201.2: M 100.0 201.0 L 100.0 202.0 L 600.0 202.0 L 600.0 201.0 C 201.3 -> 201.3: M 100.0 201.0 L 100.0 202.0 L 600.0 202.0 L 600.0 201.0 C 201.4 -> 201.4: M 100.0 201.0 L 100.0 202.0 L 600.0 202.0 L 600.0 201.0 C 201.5 -> 201.5: M 100.0 201.0 L 100.0 202.0 L 600.0 202.0 L 600.0 201.0 C 201.6 -> 201.6: M 100.0 201.0 L 100.0 202.0 L 600.0 202.0 L 600.0 201.0 C 201.7 -> 201.7: M 100.0 201.0 L 100.0 202.0 L 600.0 202.0 L 600.0 201.0 C 201.8 -> 201.8: M 100.0 201.0 L 100.0 202.0 L 600.0 202.0 L 600.0 201.0 C 201.9 -> 201.9: M 100.0 201.0 L 100.0 202.0 L 600.0 202.0 L 600.0 201.0 C 202.0 -> 202.0: M 0.0 0.0 202.1 -> 202.1: M 100.0 202.0 L 100.0 203.0 L 600.0 203.0 L 600.0 202.0 C 202.2 -> 202.2: M 100.0 202.0 L 100.0 203.0 L 600.0 203.0 L 600.0 202.0 C 202.3 -> 202.3: M 100.0 202.0 L 100.0 203.0 L 600.0 203.0 L 600.0 202.0 C 202.4 -> 202.4: M 100.0 202.0 L 100.0 203.0 L 600.0 203.0 L 600.0 202.0 C [rep@dinar bin]$ ./gs -r144 -Dincr=0.1 $ARGS 2>/dev/null RESOLUTION: 144.0 POINTS -> PIXELS: PATH .... 200 -> 400.0: M 0.0 0.0 200.1 -> 400.2: M 100.0 200.0 L 100.0 200.5 L 600.0 200.5 L 600.0 200.0 C 200.2 -> 400.4: M 100.0 200.0 L 100.0 200.5 L 600.0 200.5 L 600.0 200.0 C 200.3 -> 400.6: M 100.0 200.0 L 100.0 200.5 L 600.0 200.5 L 600.0 200.0 C 200.4 -> 400.8: M 100.0 200.0 L 100.0 200.5 L 600.0 200.5 L 600.0 200.0 C 200.5 -> 401.0: M 0.0 0.0 200.6 -> 401.2: M 100.0 200.5 L 100.0 201.0 L 600.0 201.0 L 600.0 200.5 C 200.7 -> 401.4: M 100.0 200.5 L 100.0 201.0 L 600.0 201.0 L 600.0 200.5 C 200.8 -> 401.6: M 100.0 200.5 L 100.0 201.0 L 600.0 201.0 L 600.0 200.5 C 200.9 -> 401.8: M 100.0 200.5 L 100.0 201.0 L 600.0 201.0 L 600.0 200.5 C 201.0 -> 402.0: M 0.0 0.0 201.1 -> 402.2: M 100.0 201.0 L 100.0 201.5 L 600.0 201.5 L 600.0 201.0 C 201.2 -> 402.4: M 100.0 201.0 L 100.0 201.5 L 600.0 201.5 L 600.0 201.0 C 201.3 -> 402.6: M 100.0 201.0 L 100.0 201.5 L 600.0 201.5 L 600.0 201.0 C 201.4 -> 402.8: M 100.0 201.0 L 100.0 201.5 L 600.0 201.5 L 600.0 201.0 C 201.5 -> 403.0: M 0.0 0.0 201.6 -> 403.2: M 100.0 201.5 L 100.0 202.0 L 600.0 202.0 L 600.0 201.5 C 201.7 -> 403.4: M 100.0 201.5 L 100.0 202.0 L 600.0 202.0 L 600.0 201.5 C 201.8 -> 403.6: M 100.0 201.5 L 100.0 202.0 L 600.0 202.0 L 600.0 201.5 C 201.9 -> 403.8: M 100.0 201.5 L 100.0 202.0 L 600.0 202.0 L 600.0 201.5 C 202.0 -> 404.0: M 0.0 0.0 202.1 -> 404.2: M 100.0 202.0 L 100.0 202.5 L 600.0 202.5 L 600.0 202.0 C 202.2 -> 404.4: M 100.0 202.0 L 100.0 202.5 L 600.0 202.5 L 600.0 202.0 C 202.3 -> 404.6: M 100.0 202.0 L 100.0 202.5 L 600.0 202.5 L 600.0 202.0 C 202.4 -> 404.8: M 100.0 202.0 L 100.0 202.5 L 600.0 202.5 L 600.0 202.0 C [rep@dinar bin]$ ./gs -r720 -Dincr=0.1 $ARGS 2>/dev/null RESOLUTION: 720.0 POINTS -> PIXELS: PATH .... 200 -> 2000.0: M 0.0 0.0 200.1 -> 2001.0: M 0.0 0.0 200.2 -> 2002.0: M 0.0 0.0 200.3 -> 2003.0: M 0.0 0.0 200.4 -> 2004.0: M 0.0 0.0 200.5 -> 2005.0: M 0.0 0.0 200.6 -> 2006.0: M 0.0 0.0 200.7 -> 2007.0: M 0.0 0.0 200.8 -> 2008.0: M 0.0 0.0 200.9 -> 2009.0: M 0.0 0.0 201.0 -> 2010.0: M 0.0 0.0 201.1 -> 2011.0: M 0.0 0.0 201.2 -> 2012.0: M 0.0 0.0 201.3 -> 2013.0: M 0.0 0.0 201.4 -> 2014.0: M 0.0 0.0 201.5 -> 2015.0: M 0.0 0.0 201.6 -> 2016.0: M 0.0 0.0 201.7 -> 2017.0: M 0.0 0.0 201.8 -> 2018.0: M 0.0 0.0 201.9 -> 2019.0: M 0.0 0.0 202.0 -> 2020.0: M 0.0 0.0 202.1 -> 2021.0: M 0.0 0.0 202.2 -> 2022.0: M 0.0 0.0 202.3 -> 2023.0: M 0.0 0.0 202.4 -> 2024.0: M 0.0 0.0 [rep@dinar bin]$ ./gs -r720 -Dincr=0.01 $ARGS 2>/dev/null RESOLUTION: 720.0 POINTS -> PIXELS: PATH .... 200 -> 2000.0: M 0.0 0.0 200.01 -> 2000.1: M 100.0 200.0 L 100.0 200.1 L 600.0 200.1 L 600.0 200.0 C 200.02 -> 2000.2: M 100.0 200.0 L 100.0 200.1 L 600.0 200.1 L 600.0 200.0 C 200.03 -> 2000.3: M 100.0 200.0 L 100.0 200.1 L 600.0 200.1 L 600.0 200.0 C 200.04 -> 2000.4: M 100.0 200.0 L 100.0 200.1 L 600.0 200.1 L 600.0 200.0 C 200.05 -> 2000.5: M 100.0 200.0 L 100.0 200.1 L 600.0 200.1 L 600.0 200.0 C 200.06 -> 2000.6: M 100.0 200.0 L 100.0 200.1 L 600.0 200.1 L 600.0 200.0 C 200.07 -> 2000.7: M 100.0 200.0 L 100.0 200.1 L 600.0 200.1 L 600.0 200.0 C 200.08 -> 2000.8: M 100.0 200.0 L 100.0 200.1 L 600.0 200.1 L 600.0 200.0 C 200.09 -> 2000.9: M 100.0 200.0 L 100.0 200.1 L 600.0 200.1 L 600.0 200.0 C 200.1 -> 2001.0: M 0.0 0.0 200.11 -> 2001.1: M 100.0 200.1 L 100.0 200.2 L 600.0 200.2 L 600.0 200.1 C 200.12 -> 2001.2: M 100.0 200.1 L 100.0 200.2 L 600.0 200.2 L 600.0 200.1 C 200.13 -> 2001.3: M 100.0 200.1 L 100.0 200.2 L 600.0 200.2 L 600.0 200.1 C 200.14 -> 2001.4: M 100.0 200.1 L 100.0 200.2 L 600.0 200.2 L 600.0 200.1 C 200.15 -> 2001.5: M 100.0 200.1 L 100.0 200.2 L 600.0 200.2 L 600.0 200.1 C 200.16 -> 2001.6: M 100.0 200.1 L 100.0 200.2 L 600.0 200.2 L 600.0 200.1 C 200.17 -> 2001.7: M 100.0 200.1 L 100.0 200.2 L 600.0 200.2 L 600.0 200.1 C 200.18 -> 2001.8: M 100.0 200.1 L 100.0 200.2 L 600.0 200.2 L 600.0 200.1 C 200.19 -> 2001.9: M 100.0 200.1 L 100.0 200.2 L 600.0 200.2 L 600.0 200.1 C 200.2 -> 2002.0: M 0.0 0.0 200.21 -> 2002.1: M 100.0 200.2 L 100.0 200.3 L 600.0 200.3 L 600.0 200.2 C 200.22 -> 2002.2: M 100.0 200.2 L 100.0 200.3 L 600.0 200.3 L 600.0 200.2 C 200.23 -> 2002.3: M 100.0 200.2 L 100.0 200.3 L 600.0 200.3 L 600.0 200.2 C 200.24 -> 2002.4: M 100.0 200.2 L 100.0 200.3 L 600.0 200.3 L 600.0 200.2 C [rep@dinar bin]$ ./gs -r143 -Dincr=0.179 $ARGS 2>/dev/null RESOLUTION: 143.0 POINTS -> PIXELS: PATH .... 200 -> 397.222: M 99.6923 199.888 L 99.6923 200.392 L 600.168 200.392 L 600.168 199.888 C 200.179 -> 397.578: M 99.6923 199.888 L 99.6923 200.392 L 600.168 200.392 L 600.168 199.888 C 200.358 -> 397.933: M 99.6923 199.888 L 99.6923 200.392 L 600.168 200.392 L 600.168 199.888 C 200.537 -> 398.289: M 99.6923 200.392 L 99.6923 200.895 L 600.168 200.895 L 600.168 200.392 C 200.716 -> 398.644: M 99.6923 200.392 L 99.6923 200.895 L 600.168 200.895 L 600.168 200.392 C 200.895 -> 399.0: M 0.0 0.0 201.074 -> 399.355: M 99.6923 200.895 L 99.6923 201.399 L 600.168 201.399 L 600.168 200.895 C 201.253 -> 399.711: M 99.6923 200.895 L 99.6923 201.399 L 600.168 201.399 L 600.168 200.895 C 201.432 -> 400.066: M 99.6923 201.399 L 99.6923 201.902 L 600.168 201.902 L 600.168 201.399 C 201.611 -> 400.422: M 99.6923 201.399 L 99.6923 201.902 L 600.168 201.902 L 600.168 201.399 C 201.79 -> 400.777: M 99.6923 201.399 L 99.6923 201.902 L 600.168 201.902 L 600.168 201.399 C 201.969 -> 401.133: M 99.6923 201.902 L 99.6923 202.406 L 600.168 202.406 L 600.168 201.902 C 202.148 -> 401.488: M 99.6923 201.902 L 99.6923 202.406 L 600.168 202.406 L 600.168 201.902 C 202.327 -> 401.844: M 99.6923 201.902 L 99.6923 202.406 L 600.168 202.406 L 600.168 201.902 C 202.506 -> 402.199: M 99.6923 202.406 L 99.6923 202.909 L 600.168 202.909 L 600.168 202.406 C 202.685 -> 402.555: M 99.6923 202.406 L 99.6923 202.909 L 600.168 202.909 L 600.168 202.406 C 202.864 -> 402.91: M 99.6923 202.406 L 99.6923 202.909 L 600.168 202.909 L 600.168 202.406 C 203.043 -> 403.266: M 99.6923 202.909 L 99.6923 203.413 L 600.168 203.413 L 600.168 202.909 C 203.222 -> 403.621: M 99.6923 202.909 L 99.6923 203.413 L 600.168 203.413 L 600.168 202.909 C 203.401 -> 403.977: M 99.6923 202.909 L 99.6923 203.413 L 600.168 203.413 L 600.168 202.909 C 203.58 -> 404.333: M 99.6923 203.413 L 99.6923 203.916 L 600.168 203.916 L 600.168 203.413 C 203.759 -> 404.688: M 99.6923 203.413 L 99.6923 203.916 L 600.168 203.916 L 600.168 203.413 C 203.938 -> 405.044: M 99.6923 203.916 L 99.6923 204.42 L 600.168 204.42 L 600.168 203.916 C 204.117 -> 405.399: M 99.6923 203.916 L 99.6923 204.42 L 600.168 204.42 L 600.168 203.916 C 204.296 -> 405.755: M 99.6923 203.916 L 99.6923 204.42 L 600.168 204.42 L 600.168 203.916 C [rep@dinar bin]$ exit Script done on Thu 14 Jun 2007 11:04:41 AM EDT
Reassigning path and fill problems to Robin Watts.
I've investigated this a bit, and the problem is related to our use of fill_adjust. The clip operation works by clipping the current path against the current clipping path. This is implemented in ghostscript by plotting the clipping path through a device that clips against the clipping path. Unfortunately this uses the current values of fill_adjust. To give consistent results, I suspect we should override the values of fill_adjust for the duration of the call. If we take fill adjust figures of 0,0 then completely horizontal/vertical paths clip to give completely empty paths irrespective of pixel positions. This is, I think, a valid implementation of the spec, as the enclosed area of the path is empty (i.e. the same). Unfortunately this does not match the results that Adobe gives. Adobe clips these paths so as to give the same completely horizontal/vertical paths, just restricted to being within the same area. We can get the same results as this (almost) by using fill adjust figures of 0.5,0.5. The only difference occurs when the path is precisely aligned to pixel boundaries, whereupon we get the same empty path as given by 0,0. I believe the difference here is due to the fact that we treat 0.5,0.5 as a special value meaning left/bottom adjust of -.5 and a top/right adjust of 0.49999 so as to give an open region.
Created attachment 5786 [details] clip.ps A file to show the problem.
Created attachment 5802 [details] patch A patch to solve this problem. fill_adjust already has one special case value (-1, meaning 0,0,0,0 for the adjustments). This introduces another such value, -2, meaning the closed 0.5,0.5,0.5,0.5 region, rather than the more usual half open region. This patch cures the problem with clip.ps. Regression testing gives results which differ slightly (as you'd expect). They aren't a complete match, but them *seem* like an improvement or neutral. I know this propogates (and further complicates) the hated fill_adjust, but uploading here for comments.
It has been suggested that an alternative (simpler) fix for this would simply be to enable the existing code protected by #ifdef FILL_ZERO_WIDTH. That code was introduced by Ray a while ago in response to a customer bug. It seems like a tempting solution, as mathematically I think it will do the right thing. Sadly, testing has revealed some problems with it, in that it exposes a mismatch between different output devices. I'll attach a file to show this problem up.
Created attachment 5817 [details] out.pdf A cutdown file that shows distinct differences when rendered at 300dpi using the display device and the bmp16m device. I suspect the problem is to do with the fact that the bmp16m device vertically flips the output.
Reassigning to new email address.
This seems to have been fixed as of 9.10. The initial example, at least shows two lines now.
All these problems were fixed in v.9.22 but the bug report was not updated.