// After a transformation, produce new coordinate bounds. For paths that
// have been added, this is only an approximation since it takes the bounds of
// their transformed bounding box.
private void addTransformedCoords(coords2 dest, transform t,
coords2 point, coords2 min, coords2 max)
{
dest.push(t, point, point);
// Add in all 4 corner coords, to properly size rectangular pictures.
dest.push(t,min,min);
dest.push(t,min,max);
dest.push(t,max,min);
dest.push(t,max,max);
}
// Adds another sizing restriction to the coordinates, but only if it is
// maximal, that is, if under some scaling, this coordinate could be the
// largest.
private void addIfMaximal(coord[] coords, real user, real truesize) {
// TODO: Test promoting coordinates for efficiency.
for (coord c : coords)
if (user <= c.user && truesize <= c.truesize)
// Not maximal.
return;
// The coordinate is not dominated by any existing extreme, so it is
// maximal and will be added, but first remove any coords it now dominates.
int i=0;
while (i < coords.length) {
coord c=coords[i];
if (c.user <= user && c.truesize <= truesize)
coords.delete(i);
else
++i;
}
// Add the coordinate to the extremes.
coords.push(coord.build(user, truesize));
}
private void addIfMaximal(coord[] dest, coord[] src)
{
// This may be inefficient, as it rebuilds the coord struct when adding it.
for (coord c : src)
addIfMaximal(dest, c.user, c.truesize);
}
// Same as addIfMaximal, but testing for minimal coords.
private void addIfMinimal(coord[] coords, real user, real truesize) {
for (coord c : coords)
if (user >= c.user && truesize >= c.truesize)
return;
int i=0;
while (i < coords.length) {
coord c=coords[i];
if (c.user >= user && c.truesize >= truesize)
coords.delete(i);
else
++i;
}
coords.push(coord.build(user, truesize));
}
private void addIfMinimal(coord[] dest, coord[] src)
{
for (coord c : src)
addIfMinimal(dest, c.user, c.truesize);
}
// This stores a list of sizing bounds for picture data. If the object is
// frozen, then it cannot be modified further, and therefore can be safely
// passed by reference and stored in the sizing data for multiple pictures.
private struct freezableBounds {
restricted bool frozen=false;
void freeze() {
frozen=true;
}
// Optional links to further (frozen) sizing data.
private freezableBounds[] links;
// Links to (frozen) sizing data that is transformed when added here.
private static struct transformedBounds {
transform t;
freezableBounds link;
};
private transformedBounds[] tlinks;
// The sizing data. It cannot be modified once this object is frozen.
private coords2 point, min, max;
// A bound represented by a path. Using the path instead of the bounding
// box means it will be accurate after a transformation by coordinates.
private path[] pathBounds;
// A bound represented by a path and a pen.
// As often many paths use the same pen, we store an array of paths.
private static struct pathpen {
path[] g; pen p;
void operator init(path g, pen p) {
this.g.push(g);
this.p=p;
}
}
private static pathpen operator *(transform t, pathpen pp) {
// Should the pen be transformed?
pathpen newpp;
for (path g : pp.g)
newpp.g.push(t*g);
newpp.p=pp.p;
return newpp;
}
// WARNING: Due to crazy optimizations, if this array is changed between an
// empty and non-empty state, the assignment of a method to
// addPath(path,pen) must also change.
private pathpen[] pathpenBounds;
// Once frozen, the sizing is immutable, and therefore we can compute and
// store the extremal coordinates.
public static struct extremes {
coord[] left, bottom, right, top;
// Once frozen, getMutable returns a new object based on this one, which can
// be modified.
freezableBounds getMutable() {
assert(frozen);
var f=new freezableBounds;
f.links.push(this);
return f;
}
freezableBounds transformed(transform t) {
// Freeze these bounds, as we are storing a reference to them.
freeze();
var tlink=new transformedBounds;
tlink.t=t;
tlink.link=this;
var b=new freezableBounds;
b.tlinks.push(tlink);
return b;
}
void append(freezableBounds b) {
// Check that we can modify the object.
assert(!frozen);
//TODO: If b is "small", ie. a single tlink or cliplink, just copy the
//link.
// As we only reference b, we must freeze it to ensure it does not change.
b.freeze();
links.push(b);
}
// To squeeze out a bit more performance, this method is either assigned
// addPathToNonEmptyArray or addPathToEmptyArray depending on the state of
// the pathpenBounds array.
void addPath(path g, pen p);
// Test if the pens are equal or have the same bounds.
if (pp.p == p || (min(pp.p) == min(p) && max(pp.p) == max(p))) {
// If this path has the same pen as the last one, just add it to the
// array corresponding to that pen.
pp.g.push(g);
}
else {
// A different pen. Start a new bound and put it on the front. Put
// the old bound at the end of the array.
pathpenBounds[0]=pathpen(g,p);
pathpenBounds.push(pp);
}
}
void addPathToEmptyArray(path g, pen p) {
//assert(!frozen);
//assert(pathpenBounds.empty());
// Initial setting for addPath.
addPath=addPathToEmptyArray;
// Transform the sizing info by t then add the result to the coords
// structure.
private void accumulateCoords(transform t, coords2 coords) {
for (var link : links)
link.accumulateCoords(t, coords);
for (var tlink : tlinks)
tlink.link.accumulateCoords(t*tlink.t, coords);
for (var g : pathBounds) {
g=t*g;
coords.push(min(g), (0,0));
coords.push(max(g), (0,0));
}
for (var pp: pathpenBounds) {
pair pm=min(pp.p), pM=max(pp.p);
for (var g : pp.g) {
g=t*g;
coords.push(min(g), pm);
coords.push(max(g), pM);
}
}
}
// Add all of the sizing info to the given coords structure.
private void accumulateCoords(coords2 coords) {
for (var link : links)
link.accumulateCoords(coords);
for (var tlink : tlinks)
tlink.link.accumulateCoords(tlink.t, coords);
for (var g : pathBounds) {
coords.push(min(g), (0,0));
coords.push(max(g), (0,0));
}
for (var pp: pathpenBounds) {
pair pm=min(pp.p), pM=max(pp.p);
for (var g : pp.g) {
coords.push(min(g), pm);
coords.push(max(g), pM);
}
}
}
// Returns all of the coords that this sizing data represents.
private coords2 allCoords() {
coords2 coords;
accumulateCoords(coords);
return coords;
}
// Returns the extremal coordinates of the sizing data.
public extremes extremes() {
if (cachedExtremes == null) {
freeze();
extremes e;
addToExtremes(e);
cachedExtremes=e;
}
return cachedExtremes;
}
// Helper functions for computing the usersize bounds. usermin and usermax
// would be easily computable from extremes, except that the picture
// interface actually allows calls that manually change the usermin and
// usermax values. Therefore, we have to compute these values separately.
private static struct userbounds {
bool areSet=false;
pair min;
pair max;
}
private static struct boundsAccumulator {
pair[] mins;
pair[] maxs;
void push(pair m, pair M) {
mins.push(m);
maxs.push(M);
}
void push(userbounds b) {
if (b.areSet)
push(b.min, b.max);
}
void push(transform t, userbounds b) {
if (b.areSet) {
pair[] box={ t*(b.min.x,b.max.y), t*b.max,
t*b.min, t*(b.max.x,b.min.y) };
for (var z : box)
push(z,z);
}
}
// The user bounds already calculated for this data.
private userbounds storedUserBounds=null;
private void accumulateUserBounds(boundsAccumulator acc)
{
if (storedUserBounds != null) {
assert(frozen);
acc.push(storedUserBounds);
} else {
acc.pushUserCoords(point, point);
acc.pushUserCoords(min, max);
if (pathBounds.length > 0)
acc.push(min(pathBounds), max(pathBounds));
for (var pp : pathpenBounds)
if(size(pp.g) > 0)
acc.push(min(pp.g), max(pp.g));
for (var link : links)
link.accumulateUserBounds(acc);
// Transforms are handled as they were in the old system.
for (var tlink : tlinks) {
boundsAccumulator tacc;
tlink.link.accumulateUserBounds(tacc);
acc.push(tlink.t, tacc.collapse());
}
}
}
// userMin/userMax returns the minimal/maximal userspace coordinate of the
// sizing data. As coordinates for objects such as labels can have
// significant truesize dimensions, this userMin/userMax values may not
// correspond closely to the end of the screen, and are of limited use.
// userSetx and userSety determine if there is sizing data in order to even
// have userMin/userMax defined.
public bool userBoundsAreSet() {
return userBounds().areSet;
}
public pair userMin() {
return userBounds().min;
}
public pair userMax() {
return userBounds().max;
}
// To override the true userMin and userMax bounds, first compute the
// userBounds as they should be at this point, then change the values.
public void alterUserBound(string which, real val) {
// We are changing the bounds data, so it cannot be frozen yet. After the
// user bounds are set, however, the sizing data cannot change, so it will
// be frozen.
assert(!frozen);
computeUserBounds();
assert(frozen);
var b=storedUserBounds;
if (which == "minx")
b.min=(val, b.min.y);
else if (which == "miny")
b.min=(b.min.x, val);
else if (which == "maxx")
b.max=(val, b.max.y);
else {
assert(which == "maxy");
b.max=(b.max.x, val);
}
}
// A temporary measure. Stuffs all of the data from the links and paths
// into the coords.
private void flatten() {
assert(!frozen);
// First, compute the user bounds, taking into account any manual
// alterations.
computeUserBounds();
// Calculate all coordinates.
coords2 coords=allCoords();
// Erase all the old data.
point.erase();
min.erase();
max.erase();
pathBounds.delete();
pathpenBounds.delete();
addPath=addPathToEmptyArray;
links.delete();
tlinks.delete();
// Put all of the coordinates into point.
point=coords;
}
// Cap the userBounds.
userbounds b=storedUserBounds;
b.min=(b.min.x, max(Min, b.min.y));
b.max=(b.max.x, min(Max, b.max.y));
}
// Calculate the min for the final frame, given the coordinate transform.
pair min(transform t) {
extremes e=extremes();
if (e.left.length == 0)
return 0;
// Calculate the max for the final frame, given the coordinate transform.
pair max(transform t) {
extremes e=extremes();
if (e.right.length == 0)
return 0;
struct bounds {
private var base=new freezableBounds;
// We should probably put this back into picture.
bool exact=true;
// Called just before modifying the sizing data. It ensures base is
// non-frozen.
// Note that this is manually inlined for speed reasons in a couple often
// called methods below.
private void makeMutable() {
if (base.frozen)
base=base.getMutable();
//assert(!base.frozen); // Disabled for speed reasons.
}
void erase() {
// Just discard the old bounds.
base=new freezableBounds;
// We don't reset the 'exact' field, for backward compatibility.
}
bounds copy() {
// Freeze the underlying bounds and make a shallow copy.
base.freeze();
var b=new bounds;
b.base=this.base;
b.exact=this.exact;
return b;
}
void addPath(path g) {
//makeMutable(); // Manually inlined here for speed reasons.
if (base.frozen)
base=base.getMutable();
base.addPath(g);
}
void addPath(path[] g) {
//makeMutable(); // Manually inlined here for speed reasons.
if (base.frozen)
base=base.getMutable();
base.addPath(g);
}
void addPath(path g, pen p) {
//makeMutable(); // Manually inlined here for speed reasons.
if (base.frozen)
base=base.getMutable();
base.addPath(g, p);
}
public bool userBoundsAreSet() {
return base.userBoundsAreSet();
}
public pair userMin() {
return base.userMin();
}
public pair userMax() {
return base.userMax();
}
public void alterUserBound(string which, real val) {
makeMutable();
base.alterUserBound(which, val);
}
void xclip(real Min, real Max) {
makeMutable();
base.xclip(Min,Max);
}
void yclip(real Min, real Max) {
makeMutable();
base.yclip(Min,Max);
}
void clip(pair Min, pair Max) {
// TODO: If the user bounds have been manually altered, they may be
// incorrect after the clip.
xclip(Min.x,Max.x);
yclip(Min.y,Max.y);
}
pair min(transform t) {
return base.min(t);
}
pair max(transform t) {
return base.max(t);
}
transform scaling(real xsize, real ysize,
real xunitsize, real yunitsize,
bool keepAspect, bool warn) {
return base.scaling(xsize, ysize, xunitsize, yunitsize, keepAspect, warn);
}
}
bounds operator *(transform t, bounds b) {
return b.transformed(t);
}