
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"))
}

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

## Simple mask
mask <- circleGrob(r=.3, gp=gpar(fill="black"))
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 <- circleGrob(r=.3, gp=gpar(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 <- circleGrob(x=1:3/4, y=1:3/4, r=.1, gp=gpar(fill="black"))
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 <- circleGrob(gp=gpar(col=NA,
                           fill=radialGradient(c("black", "transparent"))))
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 <- gTree(children=gList(rectGrob(gp=gpar(fill="black"))),
              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 <- gTree(children=gList(rectGrob(gp=gpar(fill="black"))),
              vp=viewport(mask=circleGrob(r=.4, gp=gpar(fill="black"))))
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 <- 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"))))
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 <- rectGrob(gp=gpar(fill="black"),
                 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 <- circleGrob(r=.6,
                   gp=gpar(fill="black"),
                   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=circleGrob(gp=gpar(fill="black"))))
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=circleGrob(gp=gpar(fill="black"))))
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=circleGrob(gp=gpar(fill="black"))))
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=circleGrob(gp=gpar(fill="black")), 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=circleGrob(gp=gpar(fill="black"))))
grid.rect(gp=gpar(fill="grey"))
HersheyLabel("push mask
rect
grey circle
(for resizing)")

## Record and replay
grid.newpage()
pushViewport(viewport(mask=circleGrob(gp=gpar(fill="black"))))
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=circleGrob(gp=gpar(fill="black"))))
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 <- 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"))))
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) {
    setChildren(x, gList(rectGrob(gp=gpar(fill="grey"),
                                  vp=viewport(mask=circleGrob(gp=gpar(fill="black"))))))
}
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 <- gTree(cl="test")
makeContent.test <- function(x) {
    setChildren(x, gList(circleGrob(gp=gpar(fill="black"))))
}
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=circleGrob(gp=gpar(fill="black"))))
    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 <- 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)))
pushViewport(viewport(mask=mask))
grid.rect(gp=gpar(fill=pat))

## Mask from text
grid.newpage()
mask <- textGrob("test", gp=gpar(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 <- rectGrob(width=.5, height=.5,
                 gp=gpar(fill=linearGradient(c("black", "transparent"))))
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 <- rasterGrob(matrix(rgb(0,0,0,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 <- circleGrob(r=.4, gp=gpar(col=NA, fill=rgb(0,0,0,.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 ...

