bug-groff
[Top][All Lists]

## [bug #65910] [pic] some dashed ellipse sizes produce irregular dashes

 From: G. Branden Robinson Subject: [bug #65910] [pic] some dashed ellipse sizes produce irregular dashes Date: Wed, 10 Jul 2024 12:23:12 -0400 (EDT)

```Follow-up Comment #5, bug #65910 (group groff):

I'm not yet convinced that deep mathematical sophistication is required here.
(If it is, I'll try to recover what meager allotment of that I may once have

The GNU _pic_ function that breaks up a dashed ellipse in to a bunch of
elliptical arcs is called `ellipse_arc()`.  I crudely instrumented it as
follows.

diff --git a/src/preproc/pic/common.cpp b/src/preproc/pic/common.cpp
index 6a4a93eb9..e72c67807 100644
--- a/src/preproc/pic/common.cpp
+++ b/src/preproc/pic/common.cpp
@@ -1,4 +1,3 @@
-// -*- C++ -*-
/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
Written by James Clark (jjc@jclark.com)

@@ -172,8 +171,21 @@ void common_output::dashed_ellipse(const position &cent,
const distance &dim,
// and use it to get the exact value on the ellipse
double psi = atan2(zdot.y / dim_y, zdot.x / dim_x);
zdot = position(dim_x * cos(psi), dim_y * sin(psi));
-    if ((i % 2 == 0) && (i > 1))
+    if ((i % 2 == 0) && (i > 1)) {
+      fprintf(stderr, "GBR: ellipse_arc("
+         "cent=(%g, %g); "
+         "zpre=(%5.2f,%5.2f); "
+         "zdot=(%5.2f,%5.2f); "
+         "dim/2=(%g, %g); "
+         "line type=%d)\n",
+         cent.x, cent.y,
+         zpre.x, zpre.y,
+         zdot.x, zdot.y,
+         (dim.x / 2.0), (dim.y / 2.0),
+         slt.type);
+      fflush(stderr);
ellipse_arc(cent, zpre, zdot, dim / 2, slt);
+    }
}
}

I tuned the floating point output formats deliberately.  We don't want to be
blitzed with numerals; we want to sanity check the values at something
approximating a glance.

I don't have mastery yet of what this function does but the rough concept
seems to be pretty straightforward: a dashed ellipse is like a regular ellipse
except you chop it up into N elliptical arcs, and you lift and lower the pen
each time you draw one of those arcs, in alternation.  That's what the
expression "(i % 2 == 0)" does for us--we draw only the "even" arcs, not the
odd ones.

There's some business about deriving the placement of the arcs from the
"affine circle".  I once could rattle off with ease what an affine transform
is.  I seem to remember that it's a linear transform with the origin
preserved.[1]  (A linear transform is mapping between linear spaces.  Linear
spaces are mathematical structures where a dot product is defined.  I think.
It's been over a decade since I used linear algebra in anger, and that makes
me a little sad.  I *loved* linear algebra.)

Anyway.

This function's parameters are:

1. Coordinates of the ellipse's center.
2. Coordinates of the previous location of "dot" (a pen-up/pen-down
location);
3. Coordinates of the current location of "dot";
4. The halved dimensions of the ellipse, a.k.a. the semi-major and semi-minor
axis lengths; and
5. The line type, which just a C++ enum for "invisible, solid, dotted,
dashed".  The line type here is always "solid" because I guess we can't count
on the output device to draw a dashed arc, so we construct it ourselves--with
this very function.

Here is the output.

\$ ./build/pic -t EXPERIMENTS/dashed-ellipse-8x3.ptex >|
EXPERIMENTS/dashed-ellipse-8x3.tex
GBR: ellipse_arc(cent=(4, 0); zpre=( 3.89, 0.35); zdot=( 3.73, 0.54);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=( 3.53, 0.71); zdot=( 3.24, 0.88);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=( 3.09, 0.95); zdot=( 2.83, 1.06);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=( 2.62, 1.13); zdot=( 2.35, 1.21);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=( 2.13, 1.27); zdot=( 1.82, 1.34);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=( 1.63, 1.37); zdot=( 1.24, 1.43);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=( 1.13, 1.44); zdot=( 0.63, 1.48);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=( 0.63, 1.48); zdot=( 0.00, 1.50);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=( 0.00, 1.50); zdot=(-0.13, 1.50);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=(-0.63, 1.48); zdot=(-0.63, 1.48);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=(-1.24, 1.43); zdot=(-1.24, 1.43);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=(-1.39, 1.41); zdot=(-1.82, 1.34);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=(-1.89, 1.32); zdot=(-2.35, 1.21);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=(-2.38, 1.21); zdot=(-2.83, 1.06);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=(-2.86, 1.05); zdot=(-3.24, 0.88);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=(-3.32, 0.83); zdot=(-3.56, 0.68);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=(-3.73, 0.54); zdot=(-3.89, 0.35);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=(-3.99, 0.11); zdot=(-3.98,-0.13);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=(-3.88,-0.36); zdot=(-3.72,-0.55);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=(-3.52,-0.71); zdot=(-3.24,-0.88);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=(-3.08,-0.96); zdot=(-2.83,-1.06);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=(-2.61,-1.14); zdot=(-2.35,-1.21);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=(-2.12,-1.27); zdot=(-1.82,-1.34);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=(-1.62,-1.37); zdot=(-1.24,-1.43);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=(-1.12,-1.44); zdot=(-0.63,-1.48);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=(-0.62,-1.48); zdot=(-0.00,-1.50);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=(-0.00,-1.50); zdot=( 0.14,-1.50);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=( 0.63,-1.48); zdot=( 0.64,-1.48);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=( 1.24,-1.43); zdot=( 1.24,-1.43);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=( 1.40,-1.40); zdot=( 1.82,-1.34);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=( 1.90,-1.32); zdot=( 2.35,-1.21);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=( 2.39,-1.20); zdot=( 2.83,-1.06);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=( 2.87,-1.04); zdot=( 3.24,-0.88);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=( 3.33,-0.83); zdot=( 3.56,-0.68);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=( 3.74,-0.53); zdot=( 3.90,-0.34);
dim/2=(4, 1.5); line type=1)
GBR: ellipse_arc(cent=(4, 0); zpre=( 3.99,-0.10); zdot=( 3.98, 0.14);
dim/2=(4, 1.5); line type=1)

So what we should be seeing here is a fairly regular change in x and y
coordinates.[2]  In fact I think if you graphed the foregoing you would get a
polygon with dashed sides.  The arc lengths between each adjacent pair of
vertices should be close to uniform, and that's what we're **NOT** seeing in
the visible output.

These numbers look okay to me but the human brain is a poor device for
analyzing such things.  Because we're drawing a curve, the changes in neither
the x nor the y coordinates will be constant.

But they will, or should, have a uniform property: the arc length.

The nonuniformity of the arc length is what jumps out at us and prompted the
filing of this bug.  A dashed ellipse should have uniform dashes.

We can compute the arc length the way they taught us in second-semester
calculus: ds=dy/dx, except we don't care about signs so we'd take the absolute
value.  More prosaically, one can use the "distance formula" (square root of
((y2 - y1) over (x2 - x1))--again we won't care about signs.

That's what I think my next step is: to compute and report the arc length at
each stage.  If it remains consistent within a certain tolerance, we'll know
that bug lies somewhere other than `ellipse_arc()`, possibly in the logic that
translates these arcs to _troff_ or TeX commands.

It's really interesting to me that the same bug affects both output formats,
though.  That's why I started here.

[1] Or maybe it's the other way around: an affine transform is the general
case and a linear transform keeps the origin the same(?).  I remember learning
that the meaning of "linear" we were taught as non-math majors was actually
"affine" to true mathematicians.  This made me angry.

[2] I say "regular".  The magnitudes of the changes to x and y coordinates
actually would be periodic, I think.  I'd bet a shot of whiskey that they're
sinusoidal.  What they should **not** be is, technically, "bonkers".

_______________________________________________________

<https://savannah.gnu.org/bugs/?65910>

_______________________________________________
Message sent via Savannah
https://savannah.gnu.org/
```

signature.asc
Description: PGP signature