blob: bd532e8b12efef630c4e6a6cfbd1dbf3cb0bfaab [file] [log] [blame]
/*
* R : A Computer Language for Statistical Data Analysis
* Copyright (C) 2001-3 Paul Murrell
* 2003-2013 The R Core Team
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, a copy is available at
* https://www.R-project.org/Licenses/
*/
#include "grid.h"
/* This stuff always returns an LViewportLocation in "npc" units
*/
int layoutNRow(SEXP l) {
return INTEGER(VECTOR_ELT(l, LAYOUT_NROW))[0];
}
int layoutNCol(SEXP l) {
return INTEGER(VECTOR_ELT(l, LAYOUT_NCOL))[0];
}
SEXP layoutWidths(SEXP l) {
return VECTOR_ELT(l, LAYOUT_WIDTHS);
}
SEXP layoutHeights(SEXP l) {
return VECTOR_ELT(l, LAYOUT_HEIGHTS);
}
int layoutRespect(SEXP l) {
return INTEGER(VECTOR_ELT(l, LAYOUT_VRESPECT))[0];
}
int* layoutRespectMat(SEXP l) {
return INTEGER(VECTOR_ELT(l, LAYOUT_MRESPECT));
}
double layoutHJust(SEXP l) {
return REAL(VECTOR_ELT(l, LAYOUT_VJUST))[0];
}
double layoutVJust(SEXP l) {
return REAL(VECTOR_ELT(l, LAYOUT_VJUST))[1];
}
Rboolean relativeUnit(SEXP unit, int index,
pGEDevDesc dd) {
return pureNullUnit(unit, index, dd);
}
void findRelWidths(SEXP layout, int *relativeWidths,
pGEDevDesc dd)
{
int i;
SEXP widths = layoutWidths(layout);
for (i=0; i<layoutNCol(layout); i++)
relativeWidths[i] = relativeUnit(widths, i, dd);
}
void findRelHeights(SEXP layout, int *relativeHeights,
pGEDevDesc dd)
{
int i;
SEXP heights = layoutHeights(layout);
for (i=0; i<layoutNRow(layout); i++)
relativeHeights[i] = relativeUnit(heights, i, dd);
}
void allocateKnownWidths(SEXP layout,
int *relativeWidths,
double parentWidthCM, double parentHeightCM,
LViewportContext parentContext,
const pGEcontext parentgc,
pGEDevDesc dd,
double *npcWidths, double *widthLeftCM)
{
int i;
SEXP widths = layoutWidths(layout);
for (i=0; i<layoutNCol(layout); i++)
if (!relativeWidths[i]) {
npcWidths[i] = transformWidth(widths, i, parentContext,
parentgc,
parentWidthCM, parentHeightCM,
0, 0, dd)*2.54;
*widthLeftCM -= npcWidths[i];
}
}
void allocateKnownHeights(SEXP layout,
int *relativeHeights,
double parentWidthCM, double parentHeightCM,
LViewportContext parentContext,
const pGEcontext parentgc,
pGEDevDesc dd,
double *npcHeights, double *heightLeftCM)
{
int i;
SEXP heights = layoutHeights(layout);
for (i=0; i<layoutNRow(layout); i++)
if (!relativeHeights[i]) {
npcHeights[i] = transformHeight(heights, i, parentContext,
parentgc,
parentWidthCM, parentHeightCM,
0, 0, dd)*2.54;
*heightLeftCM -= npcHeights[i];
}
}
int colRespected(int col, SEXP layout) {
int i;
int result = 0;
int respect = layoutRespect(layout);
int *respectMat = layoutRespectMat(layout);
if (respect == 1)
result = 1;
else
for (i=0; i<layoutNRow(layout); i++)
if (respectMat[col*layoutNRow(layout) + i] != 0)
result = 1;
return result;
}
int rowRespected(int row, SEXP layout) {
int i;
int result = 0;
int respect = layoutRespect(layout);
int *respectMat = layoutRespectMat(layout);
if (respect == 1)
result = 1;
else
for (i=0; i<layoutNCol(layout); i++)
if (respectMat[i*layoutNRow(layout) + row] != 0)
result = 1;
return result;
}
/*
* These sum up ALL relative widths and heights (unit = "null")
* Some effort is made to find all truly null units
* (e.g., including a grobwidth unit where the grob's width is null)
*/
double totalWidth(SEXP layout, int *relativeWidths,
LViewportContext parentContext,
const pGEcontext parentgc,
pGEDevDesc dd)
{
int i;
SEXP widths = layoutWidths(layout);
double totalWidth = 0;
for (i=0; i<layoutNCol(layout); i++)
if (relativeWidths[i])
totalWidth += transformWidth(widths, i, parentContext,
parentgc,
/*
* NOTE: 0, 0, here is ok
* because we are only
* obtaining "null" units
*/
0, 0, 1, 0, dd);
return totalWidth;
}
double totalHeight(SEXP layout, int *relativeHeights,
LViewportContext parentContext,
const pGEcontext parentgc,
pGEDevDesc dd)
{
int i;
SEXP heights = layoutHeights(layout);
double totalHeight = 0;
for (i=0; i<layoutNRow(layout); i++)
if (relativeHeights[i])
totalHeight += transformHeight(heights, i, parentContext,
parentgc,
/*
* NOTE: 0, 0, here is ok
* because we are only
* obtaining "null" units
*/
0, 0, 1, 0, dd);
return totalHeight;
}
void allocateRespected(SEXP layout,
int *relativeWidths, int *relativeHeights,
double *reducedWidthCM, double *reducedHeightCM,
LViewportContext parentContext,
const pGEcontext parentgc,
pGEDevDesc dd,
double *npcWidths, double *npcHeights)
{
int i;
SEXP widths = layoutWidths(layout);
SEXP heights = layoutHeights(layout);
int respect = layoutRespect(layout);
double sumWidth = totalWidth(layout, relativeWidths, parentContext,
parentgc, dd);
double sumHeight = totalHeight(layout, relativeHeights, parentContext,
parentgc, dd);
double denom, mult;
double tempWidthCM = *reducedWidthCM;
double tempHeightCM = *reducedHeightCM;
if (respect > 0) {
/* Determine whether aspect ratio of available space is
* bigger or smaller than aspect ratio of layout
*/
// NB: widths could be zero
// if ((tempHeightCM / tempWidthCM) > (sumHeight / sumWidth)) {
if ( tempHeightCM * sumWidth > sumHeight * tempWidthCM) {
denom = sumWidth;
mult = tempWidthCM;
}
else {
denom = sumHeight;
mult = tempHeightCM;
}
/* Allocate respected widths
*/
for (i=0; i<layoutNCol(layout); i++)
if (relativeWidths[i])
if (colRespected(i, layout)) {
/*
* Special case of respect, but sumHeight = 0.
* Action is to allocate widths as if unrespected.
* Ok to test == 0 because will only be 0 if
* all relative heights are actually exactly 0.
*/
if (sumHeight == 0) {
denom = sumWidth;
mult = tempWidthCM;
}
/* Build a unit SEXP with a single value and no data
*/
npcWidths[i] = pureNullUnitValue(widths, i) /
denom*mult;
*reducedWidthCM -= npcWidths[i];
}
/* Allocate respected heights
*/
for (i=0; i<layoutNRow(layout); i++)
if (relativeHeights[i])
if (rowRespected(i, layout)) {
/*
* Special case of respect, but sumWidth = 0.
* Action is to allocate widths as if unrespected.
* Ok to test == 0 because will only be 0 if
* all relative heights are actually exactly 0.
*/
if (sumWidth == 0) {
denom = sumHeight;
mult = tempHeightCM;
}
npcHeights[i] = pureNullUnitValue(heights, i) /
denom*mult;
*reducedHeightCM -= npcHeights[i];
}
}
}
void setRespectedZero(SEXP layout,
int *relativeWidths, int *relativeHeights,
double *npcWidths, double *npcHeights)
{
int i;
for (i=0; i<layoutNCol(layout); i++)
if (relativeWidths[i])
if (colRespected(i, layout))
npcWidths[i] = 0;
for (i=0; i<layoutNRow(layout); i++)
if (relativeHeights[i])
if (rowRespected(i, layout))
npcHeights[i] = 0;
}
/* These sum up unrespected relative widths and heights (unit = "null")
*/
double totalUnrespectedWidth(SEXP layout, int *relativeWidths,
LViewportContext parentContext,
const pGEcontext parentgc,
pGEDevDesc dd)
{
int i;
SEXP widths = layoutWidths(layout);
double totalWidth = 0;
for (i=0; i<layoutNCol(layout); i++)
if (relativeWidths[i])
if (!colRespected(i, layout))
totalWidth += transformWidth(widths, i, parentContext,
parentgc,
/*
* NOTE: 0, 0, here is ok
* because we are only
* obtaining "null" units
*/
0, 0, 1, 0, dd);
return totalWidth;
}
double totalUnrespectedHeight(SEXP layout, int *relativeHeights,
LViewportContext parentContext,
const pGEcontext parentgc,
pGEDevDesc dd)
{
int i;
SEXP heights = layoutHeights(layout);
double totalHeight = 0;
for (i=0; i<layoutNRow(layout); i++)
if (relativeHeights[i])
if (!rowRespected(i, layout))
totalHeight += transformHeight(heights, i, parentContext,
parentgc,
/*
* NOTE: 0, 0, here is ok
* because we are only
* obtaining "null" units
*/
0, 0, 1, 0, dd);
return totalHeight;
}
void setRemainingWidthZero(SEXP layout,
int *relativeWidths,
double *npcWidths)
{
int i;
for (i=0; i<layoutNCol(layout); i++)
if (relativeWidths[i])
if (!colRespected(i, layout))
npcWidths[i] = 0;
}
void allocateRemainingWidth(SEXP layout, int *relativeWidths,
double remainingWidthCM,
LViewportContext parentContext,
const pGEcontext parentgc,
pGEDevDesc dd,
double *npcWidths)
{
int i;
SEXP widths = layoutWidths(layout);
double sumWidth;
sumWidth = totalUnrespectedWidth(layout, relativeWidths,
parentContext, parentgc, dd);
if (sumWidth > 0) {
for (i=0; i<layoutNCol(layout); i++)
if (relativeWidths[i])
if (!colRespected(i, layout))
npcWidths[i] = remainingWidthCM*
transformWidth(widths, i, parentContext, parentgc,
/*
* NOTE: 0, 0, here is ok
* because we are only
* obtaining "null" units
*/
0, 0, 1, 0, dd)/
sumWidth;
} else {
/*
* If ALL relative widths are zero then they all get
* allocated zero width
*/
setRemainingWidthZero(layout, relativeWidths, npcWidths);
}
}
void setRemainingHeightZero(SEXP layout,
int *relativeHeights,
double *npcHeights)
{
int i;
for (i=0; i<layoutNRow(layout); i++)
if (relativeHeights[i])
if (!rowRespected(i, layout))
npcHeights[i] = 0;
}
void allocateRemainingHeight(SEXP layout, int *relativeHeights,
double remainingHeightCM,
LViewportContext parentContext,
const pGEcontext parentgc,
pGEDevDesc dd,
double *npcHeights)
{
int i;
SEXP heights = layoutHeights(layout);
double sumHeight;
sumHeight = totalUnrespectedHeight(layout, relativeHeights,
parentContext, parentgc, dd);
if (sumHeight > 0) {
for (i=0; i<layoutNRow(layout); i++)
if (relativeHeights[i])
if (!rowRespected(i, layout))
npcHeights[i] = remainingHeightCM*
transformHeight(heights, i, parentContext, parentgc,
/*
* NOTE: 0, 0, here is ok
* because we are only
* obtaining "null" units
*/
0, 0, 1, 0, dd)/
sumHeight;
} else {
/*
* If ALL relative heights are zero then they all get
* allocated zero height
*/
setRemainingHeightZero(layout, relativeHeights, npcHeights);
}
}
static double sumDims(double dims[], int from, int to)
{
int i;
double s = 0;
for (i = from; i < to + 1; i++)
s = s + dims[i];
return s;
}
static void subRegion(SEXP layout,
int minrow, int maxrow, int mincol, int maxcol,
double widths[], double heights[],
double parentWidthCM, double parentHeightCM,
double *left, double *bottom,
double *width, double *height)
{
double hjust = layoutHJust(layout);
double vjust = layoutVJust(layout);
double totalWidth = sumDims(widths, 0, layoutNCol(layout) - 1);
double totalHeight = sumDims(heights, 0, layoutNRow(layout) - 1);
*width = sumDims(widths, mincol, maxcol);
*height = sumDims(heights, minrow, maxrow);
/* widths and heights are in CM */
*left = parentWidthCM*hjust - totalWidth*hjust +
sumDims(widths, 0, mincol - 1);
*bottom = parentHeightCM*vjust + (1 - vjust)*totalHeight -
sumDims(heights, 0, maxrow);
/*
* From when hjust and vjust were enums
*
switch (layoutHJust(layout)) {
case L_LEFT:
*left = sumDims(widths, 0, mincol - 1);
break;
case L_RIGHT:
*left = 1 - sumDims(widths, mincol, layoutNCol(layout) - 1);
break;
case L_CENTRE:
case L_CENTER:
*left = (0.5 - totalWidth/2) + sumDims(widths, 0, mincol - 1);
break;
}
switch (layoutVJust(layout)) {
case L_BOTTOM:
*bottom = totalHeight - sumDims(heights, 0, maxrow);
break;
case L_TOP:
*bottom = 1 - sumDims(heights, 0, maxrow);
break;
case L_CENTRE:
case L_CENTER:
*bottom = (0.5 - totalHeight/2) + totalHeight
- sumDims(heights, 0, maxrow);
}
*/
}
Rboolean allocationRemaining(double initial, double remaining)
{
if (initial == 0) {
return TRUE;
} else if (initial > 0) {
return remaining > 0;
} else {
return remaining < 0;
}
}
void calcViewportLayout(SEXP viewport,
double parentWidthCM,
double parentHeightCM,
LViewportContext parentContext,
const pGEcontext parentgc,
pGEDevDesc dd)
{
int i;
SEXP currentWidths, currentHeights;
SEXP layout = viewportLayout(viewport);
double *npcWidths = (double *) R_alloc(layoutNCol(layout), sizeof(double));
double *npcHeights = (double *) R_alloc(layoutNRow(layout),
sizeof(double));
int *relativeWidths = (int *) R_alloc(layoutNCol(layout), sizeof(int));
int *relativeHeights = (int *) R_alloc(layoutNRow(layout), sizeof(int));
double reducedWidthCM = parentWidthCM;
double reducedHeightCM = parentHeightCM;
/* Figure out which rows and cols have relative heights and widths
*/
findRelWidths(layout, relativeWidths, dd);
findRelHeights(layout, relativeHeights, dd);
/* For any width or height which has a unit other than "null"
* we can immediately figure out its physical size.
* We do this and return the widthCM and heightCM
* remaining after these widths and heights have been allocated
*/
allocateKnownWidths(layout, relativeWidths,
parentWidthCM, parentHeightCM,
parentContext, parentgc,
dd, npcWidths,
&reducedWidthCM);
allocateKnownHeights(layout, relativeHeights,
parentWidthCM, parentHeightCM,
parentContext, parentgc,
dd, npcHeights,
&reducedHeightCM);
/* Now allocate respected widths and heights and return
* widthCM and heightCM remaining
*/
if (allocationRemaining(parentWidthCM, reducedWidthCM) ||
allocationRemaining(parentHeightCM, reducedHeightCM)) {
allocateRespected(layout, relativeWidths, relativeHeights,
&reducedWidthCM, &reducedHeightCM,
parentContext, parentgc, dd,
npcWidths, npcHeights);
} else {
/*
* IF EITHER we started with ZERO widthCM and heightCM
* OR we've used up all the widthCM and heightCM
* THEN all respected widths/heights get ZERO
*/
setRespectedZero(layout, relativeWidths, relativeHeights,
npcWidths, npcHeights);
}
/* Now allocate relative widths and heights (unit = "null")
* in the remaining space
*/
if (allocationRemaining(parentWidthCM, reducedWidthCM)) {
allocateRemainingWidth(layout, relativeWidths,
reducedWidthCM,
parentContext, parentgc, dd, npcWidths);
} else {
/*
* IF EITHER we started with ZERO width
* OR we've used up all the width
* THEN any relative widths get ZERO
*/
setRemainingWidthZero(layout, relativeWidths, npcWidths);
}
if (allocationRemaining(parentHeightCM, reducedHeightCM)) {
allocateRemainingHeight(layout, relativeHeights,
reducedHeightCM,
parentContext, parentgc, dd, npcHeights);
} else {
/*
* IF EITHER we started with ZERO height
* OR we've used up all the height
* THEN any relative heights get ZERO
*/
setRemainingHeightZero(layout, relativeHeights, npcHeights);
}
/* Record the widths and heights in the viewport
*/
PROTECT(currentWidths = allocVector(REALSXP, layoutNCol(layout)));
PROTECT(currentHeights = allocVector(REALSXP, layoutNRow(layout)));
for (i=0; i<layoutNCol(layout); i++) {
/* Layout widths are stored in CM
*/
REAL(currentWidths)[i] = npcWidths[i];
}
for (i=0; i<layoutNRow(layout); i++) {
/* Layout heights are stored in CM
*/
REAL(currentHeights)[i] = npcHeights[i];
}
SET_VECTOR_ELT(viewport, PVP_WIDTHS, currentWidths);
SET_VECTOR_ELT(viewport, PVP_HEIGHTS, currentHeights);
UNPROTECT(2);
}
Rboolean checkPosRowPosCol(SEXP vp, SEXP parent)
{
int ncol = layoutNCol(viewportLayout(parent));
int nrow = layoutNRow(viewportLayout(parent));
if (!isNull(viewportLayoutPosRow(vp)) &&
(INTEGER(viewportLayoutPosRow(vp))[0] < 1 ||
INTEGER(viewportLayoutPosRow(vp))[1] > nrow))
error(_("invalid 'layout.pos.row'"));
if (!isNull(viewportLayoutPosCol(vp)) &&
(INTEGER(viewportLayoutPosCol(vp))[0] < 1 ||
INTEGER(viewportLayoutPosCol(vp))[1] > ncol))
error(_("invalid 'layout.pos.col'"));
return TRUE;
}
void calcViewportLocationFromLayout(SEXP layoutPosRow,
SEXP layoutPosCol,
SEXP parent,
LViewportLocation *vpl)
{
int minrow, maxrow, mincol, maxcol;
double x, y, width, height;
SEXP vpx, vpy, vpwidth, vpheight;
SEXP layout = viewportLayout(parent);
/* It is possible for ONE of layoutPosRow and layoutPosCol to
* be NULL; this is interpreted as "occupy all rows/cols"
* NOTE: The " - 1" is there because R is 1-based and C is zero-based
*/
if (isNull(layoutPosRow)) {
minrow = 0;
maxrow = layoutNRow(layout) - 1;
} else {
minrow = INTEGER(layoutPosRow)[0] - 1;
maxrow = INTEGER(layoutPosRow)[1] - 1;
}
if (isNull(layoutPosCol)) {
mincol = 0;
maxcol = layoutNCol(layout) - 1;
} else {
mincol = INTEGER(layoutPosCol)[0] - 1;
maxcol = INTEGER(layoutPosCol)[1] - 1;
}
/* Put the relevant values into vpl */
subRegion(viewportLayout(parent), minrow, maxrow, mincol, maxcol,
REAL(viewportLayoutWidths(parent)),
REAL(viewportLayoutHeights(parent)),
REAL(viewportWidthCM(parent))[0],
REAL(viewportHeightCM(parent))[0],
&x, &y, &width, &height);
/* Layout widths and heights are stored in CM
*/
PROTECT(vpx = unit(x, L_CM));
vpl->x = vpx;
PROTECT(vpy = unit(y, L_CM));
vpl->y = vpy;
PROTECT(vpwidth = unit(width, L_CM));
vpl->width = vpwidth;
PROTECT(vpheight = unit(height, L_CM));
vpl->height = vpheight;
vpl->hjust = 0;
vpl->vjust = 0;
/* Question: Is there any chance that these newly-allocated
* unit SEXPs will get corrupted after this unprotect ??
*/
UNPROTECT(4);
}