library(grid)

HersheyLabel <- function(x, y=unit(.5, "npc")) {
    lines <- strsplit(x, "\n")[[1]]
    if (!is.unit(y))
        y <- unit(y, "npc")
    n <- length(lines)
    if (n > 1) {
        y <- y + unit(rev(seq(n)) - mean(seq(n)), "lines")
    }
    grid.text(lines, y=y, gp=gpar(fontfamily="HersheySans"))
}

devMask <- function(aMask, lMask) {
    support <- dev.capabilities()$masks
    if (is.character(support)) {
        if ("alpha" %in% support) {
            aMask
        } else {
            if ("luminance" %in% support) {
                as.mask(lMask, type="luminance")
            } else {
                FALSE
            }
        }
    } else {
        FALSE
    }
}

################################################################################

## Simple mask
mask <- devMask(circleGrob(r=.3, gp=gpar(fill="black")),
                circleGrob(r=.3, gp=gpar(col="white", fill="white")))
grid.newpage()
pushViewport(viewport(mask=mask))
grid.rect(width=.5, gp=gpar(fill="black"))
popViewport()
HersheyLabel("solid black rectangle with circle mask", y=.1)

## VERY thin mask
mask <- devMask(circleGrob(r=.3, gp=gpar(fill=NA)),
                circleGrob(r=.3, gp=gpar(col="white", fill=NA)))
grid.newpage()
pushViewport(viewport(mask=mask))
grid.rect(width=.5, gp=gpar(fill="black"))
popViewport()
HersheyLabel("solid black rectangle with circle BORDER mask", y=.1)

## Multiple grobs mask
mask <- devMask(circleGrob(x=1:3/4, y=1:3/4, r=.1, gp=gpar(fill="black")),
                circleGrob(x=1:3/4, y=1:3/4, r=.1, gp=gpar(col="white",
                                                           fill="white")))
grid.newpage()
pushViewport(viewport(mask=mask))
grid.rect(width=.5, gp=gpar(fill="black"))
popViewport()
HersheyLabel("solid black rectangle with three-circle mask", y=.1)

## Mask with gradient on single grob
mask <- devMask(circleGrob(gp=gpar(col=NA,
                                   fill=radialGradient(c("black",
                                                         "transparent")))),
                circleGrob(gp=gpar(col=NA,
                                   fill=radialGradient(c("white",
                                                         "black")))))
grid.newpage()
pushViewport(viewport(mask=mask))
grid.rect(width=.5, gp=gpar(fill="black"))
popViewport()
HersheyLabel("solid black rectangle with radial gradient mask", y=.1)

## Mask with gradient on multiple grobs
grid.newpage()
pushViewport(viewport(mask=mask))
grid.rect(x=1:2/3, width=.2, gp=gpar(fill="black"))
popViewport()
HersheyLabel("two solid black rectangles with radial gradient mask", y=.1)

## Mask with clipping path
mask <- devMask(gTree(children=gList(rectGrob(gp=gpar(fill="black"))),
                      vp=viewport(clip=circleGrob(r=.4))),
                gTree(children=gList(rectGrob(gp=gpar(col="white",
                                                      fill="white"))),
                      vp=viewport(clip=circleGrob(r=.4))))
grid.newpage()
pushViewport(viewport(mask=mask))
grid.rect(width=.5, gp=gpar(fill="grey"))
popViewport()
HersheyLabel("rect is half width and filled grey
mask is full rect with circle clipping path
result is half width rect with rounded top and bottom", y=.1)

## Mask with a mask
mask <- devMask(gTree(children=gList(rectGrob(gp=gpar(fill="black"))),
                      vp=viewport(mask=circleGrob(r=.4,
                                                  gp=gpar(fill="black")))),
                gTree(children=gList(rectGrob(gp=gpar(col="white",
                                                      fill="white"))),
                      vp=viewport(mask=as.mask(circleGrob(r=.4,
                                                          gp=gpar(col="white",
                                                                  fill="white")),
                                               type="luminance"))))
grid.newpage()
pushViewport(viewport(mask=mask))
grid.rect(width=.5, gp=gpar(fill="grey"))
popViewport()
HersheyLabel("rect is half width and filled grey
mask is full rect with circle mask
result is half width rect with rounded top and bottom", y=.1)

## A mask from two grobs, with ONE grob making use of a clipping path 
grid.newpage()
mask <- devMask(gTree(children=gList(rectGrob(x=.25, width=.3, height=.8,
                                              gp=gpar(fill="black"),
                                              vp=viewport(clip=circleGrob(r=.4))),
                                     rectGrob(x=.75, width=.3, height=.8,
                                              gp=gpar(fill="black")))),
                gTree(children=gList(rectGrob(x=.25, width=.3, height=.8,
                                              gp=gpar(col="white",
                                                      fill="white"),
                                              vp=viewport(clip=circleGrob(r=.4))),
                                     rectGrob(x=.75, width=.3, height=.8,
                                              gp=gpar(col="white",
                                                      fill="white")))))
pushViewport(viewport(mask=mask))
grid.rect(gp=gpar(fill="grey"))
popViewport()
HersheyLabel("mask is two grobs, ONE with its own (circle) clip path
push mask
rect
result is one slice of circle and one rectangle")

## A mask that is equivalent to ...
## A clipping path that itself makes use of a clipping path !?
grid.newpage()
mask <- devMask(rectGrob(gp=gpar(fill="black"),
                         vp=viewport(width=.5, height=.5, clip=circleGrob())),
                rectGrob(gp=gpar(col="white", fill="white"),
                         vp=viewport(width=.5, height=.5, clip=circleGrob())))
pushViewport(viewport(mask=mask))
grid.rect(gp=gpar(fill="grey"))
HersheyLabel("mask includes clip path
(clip path is circle)
push mask
rect
small grey circle")

## A mask that is equivalent to ...
## A clipping path that itself makes use of a rectangular clipping !?
grid.newpage()
mask <- devMask(circleGrob(r=.6,
                           gp=gpar(fill="black"),
                           vp=viewport(width=.5, height=.5, clip=TRUE)),
                circleGrob(r=.6,
                           gp=gpar(col="white", fill="white"),
                           vp=viewport(width=.5, height=.5, clip=TRUE)))
pushViewport(viewport(mask=mask))
grid.rect(gp=gpar(fill="grey"))
HersheyLabel("mask includes clip rect
(mask is squared circle)
push mask
rect
grey squared circle")

## Inheriting masks (between viewports)
grid.newpage()
pushViewport(viewport(mask=devMask(circleGrob(gp=gpar(fill="black")),
                                   circleGrob(gp=gpar(col="white",
                                                      fill="white")))))
pushViewport(viewport())
grid.rect(gp=gpar(fill="grey"))
HersheyLabel("push mask
push again (inherit mask)
rect
grey circle")

## Restoring masks (between viewports)
grid.newpage()
pushViewport(viewport(mask=devMask(circleGrob(gp=gpar(fill="black")),
                                   circleGrob(gp=gpar(col="white",
                                                      fill="white")))))
pushViewport(viewport())
pushViewport(viewport())
upViewport()
grid.rect(gp=gpar(fill="grey"))
HersheyLabel("push mask
push again (inherit mask)
push again (inherit mask)
up (restore inherited mask)
rect
grey circle")

## Revisiting mask on a viewport
## upViewport()
grid.newpage()
pushViewport(viewport(mask=devMask(circleGrob(gp=gpar(fill="black")),
                                   circleGrob(gp=gpar(col="white",
                                                      fill="white")))))
grid.rect(gp=gpar(fill="grey"))
upViewport()
grid.rect(gp=gpar(fill=rgb(0,0,1,.2)))
HersheyLabel("push mask
grey circle
upViewport
page all (translucent) blue")

## downViewport()
grid.newpage()
pushViewport(viewport(mask=devMask(circleGrob(gp=gpar(fill="black")),
                                   circleGrob(gp=gpar(col="white",
                                                      fill="white"))),
                      name="vp"))
grid.rect(height=.5, gp=gpar(fill="grey"))
upViewport()
downViewport("vp")
grid.rect(gp=gpar(fill=rgb(0,0,1,.2)))
HersheyLabel("push mask
rounded rect
upViewport
downViewport
blue (translucent) circle")

######################################
## Replaying the graphics display list

## Resizing device
grid.newpage()
pushViewport(viewport(mask=devMask(circleGrob(gp=gpar(fill="black")),
                                   circleGrob(gp=gpar(col="white",
                                                      fill="white")))))
grid.rect(gp=gpar(fill="grey"))
HersheyLabel("push mask
rect
grey circle
(for resizing)")

## Record and replay
grid.newpage()
pushViewport(viewport(mask=devMask(circleGrob(gp=gpar(fill="black")),
                                   circleGrob(gp=gpar(col="white",
                                                      fill="white")))))
grid.rect(gp=gpar(fill="grey"))
x <- recordPlot()
HersheyLabel("push mask
rect
grey circle
(for recording)")
print(x)
HersheyLabel("push mask
rect
record plot
replay plot
grey circle")

######################################
## Test of 'grid' display list

## Grabbing a grob with mask
## (replaying the 'grid' display list)
grid.newpage()
pushViewport(viewport(mask=devMask(circleGrob(gp=gpar(fill="black")),
                                   circleGrob(gp=gpar(col="white",
                                                      fill="white")))))
grid.rect(gp=gpar(fill="grey"))
x <- grid.grab()
HersheyLabel("push mask
rect
grey circle
(for grid.grab)")
grid.newpage()
grid.draw(x)
HersheyLabel("push mask
rect
grey circle
grid.grab
grid.draw
grey circle")

## A mask from two grobs, with ONE grob making use of a mask
grid.newpage()
mask <- devMask(gTree(children=gList(rectGrob(x=.25, width=.3, height=.8,
                                              gp=gpar(fill="black"),
                                              vp=viewport(mask=circleGrob(r=.4,
                                                                          gp=gpar(fill="black")))),
                                     rectGrob(x=.75, width=.3, height=.8,
                                              gp=gpar(fill="black")))),
                gTree(children=gList(rectGrob(x=.25, width=.3, height=.8,
                                              gp=gpar(col="white",
                                                      fill="white"),
                                              vp=viewport(mask=as.mask(circleGrob(r=.4,
                                                                                  gp=gpar(col="white", fill="white")),
                                                                       type="luminance"))),
                                     rectGrob(x=.75, width=.3, height=.8,
                                              gp=gpar(col="white",
                                                      fill="white")))))
pushViewport(viewport(mask=mask))
grid.rect(gp=gpar(fill="grey"))
popViewport()
HersheyLabel("mask is two grobs, ONE with its own (circle) mask
push mask
rect
result is one slice of circle and one rectangle")

## A mask within a makeContent() method
grid.newpage()
g <- gTree(cl="test")
makeContent.test <- function(x) {
    mask <- devMask(circleGrob(gp=gpar(fill="black")),
                    circleGrob(gp=gpar(col="white", fill="white")))
    setChildren(x, gList(rectGrob(gp=gpar(fill="grey"),
                                  vp=viewport(mask=mask))))
}
grid.draw(g)
HersheyLabel("custom grob class with makeContent() method
makeContent() adds rectangle with viewport
viewport has circle mask
result is grey circle")

## A mask that makes use of makeContent() method
grid.newpage()
mask <- devMask(gTree(cl="test"), gTree(cl="testLuminance"))
makeContent.test <- function(x) {
    setChildren(x, gList(circleGrob(gp=gpar(fill="black"))))
}
makeContent.testLuminance <- function(x) {
    setChildren(x, gList(circleGrob(gp=gpar(col="white", fill="white"))))
}
pushViewport(viewport(mask=mask))
grid.rect(gp=gpar(fill="grey"))
popViewport()
HersheyLabel("push viewport with mask
mask is grob with makeContent() method
makeContent() adds circle
draw rect
result is grey circle")

######################
## Check resource exhaustion
grid.newpage()
for (i in 1:65) {
    pushViewport(viewport(mask=devMask(circleGrob(gp=gpar(fill="black")),
                                       circleGrob(gp=gpar(col="white",
                                                          fill="white")))))
    grid.rect(gp=gpar(fill="grey"))
    HersheyLabel(paste0("viewport ", i, " with mask
result is grey circle"))
    popViewport()
}

## Bug from 4.1.0 (mask should NOT be applied to pattern)
grid.newpage()
pat <- pattern(circleGrob(r=.1),
               width=.17, height=.17,
               extend="repeat")
mask <- devMask(rectGrob(0:1/2, 0:1/2, width=.5, height=.5,
                         just=c("left", "bottom"),
                         gp=gpar(fill=rgb(0,0,0,1:2/2))),
                rectGrob(0:1/2, 0:1/2, width=.5, height=.5,
                         just=c("left", "bottom"),
                         gp=gpar(col="white", fill=grey(1:2/2))))
pushViewport(viewport(mask=mask))
grid.rect(gp=gpar(fill=pat))

## Mask from text
grid.newpage()
mask <- devMask(textGrob("test", gp=gpar(cex=10)),
                textGrob("test", gp=gpar(col="white", cex=10)))
pushViewport(viewport(mask=mask))
grid.rect(width=.5, height=.5, gp=gpar(fill=linearGradient()))
popViewport()
HersheyLabel("rect filled with linear gradient
masked by text", y=.8)
    
## Text being masked
grid.newpage()
mask <- devMask(rectGrob(width=.5, height=.5,
                         gp=gpar(fill=linearGradient(c("black",
                                                       "transparent")))),
                rectGrob(width=.5, height=.5,
                         gp=gpar(fill=linearGradient(c("white",
                                                       "black")))))
grid.segments(gp=gpar(col=2, lwd=50))
pushViewport(viewport(mask=mask))
grid.text("test", gp=gpar(cex=10))
popViewport()
HersheyLabel("text with mask
mask is rect with semitransparent linear gradient", y=.8)

## Mask from raster
grid.newpage()
mask <- devMask(rasterGrob(matrix(rgb(0,0,0,1:3/4), nrow=1), interpolate=FALSE),
                rasterGrob(matrix(grey(1:3/4), nrow=1), interpolate=FALSE))
grid.segments(gp=gpar(col=2, lwd=100))
pushViewport(viewport(mask=mask))
grid.circle(r=.4, gp=gpar(fill="black"))
popViewport()
HersheyLabel("circle with mask
mask is semitransparent raster", y=.8)
    
## Raster being masked
grid.newpage()
mask <- devMask(circleGrob(r=.4, gp=gpar(col=NA, fill=rgb(0,0,0,.5))),
                circleGrob(r=.4, gp=gpar(col=NA, fill=grey(.5))))
grid.segments(gp=gpar(col=2, lwd=100))
pushViewport(viewport(mask=mask))
grid.raster(matrix(1:3/4, nrow=1), interpolate=FALSE)
popViewport()
HersheyLabel("raster with mask
mask is semitransparent circle", y=.8)
    

################################################################################
## Need to test ...