R/rtf.R

Defines functions .start.rtf .add.font.table .add.color.table .add.page.numbers .add.paper.size .add.page.margins .add.header .get.TOC.level .start.paragraph .add.text .end.paragraph .end.rtf .add.table.row .get.space.before.after .add.merged.table.row .add.table.header.row .add.table .add.page.break .convert .add.newline .chunk.vector .add.png .rtf.plot .rtf.trellis.object .max.col.nchar .optimize.col.widths .hex2dec

##########################################################################################
## RTF Output Functions for R                                                            #
##                                                                                       # 
## Author: Michael E. Schaffer, Ph.D.                                                    #                                                                       #
##                                                                                       #
## Description:                                                                          #
## A set of R functions to output RTF files with high resolution graphics and tables.    #
## This is useful for reporting R results to an Microsoft Word-compatible report.  All   #
## graphics must be in a format supported by Word.  Currently the most compatible format #
## is PNG.                                                                               #
##                                                                                       #
## For details about the RTF format (a Microsoft format), see:                           #
## http://latex2rtf.sourceforge.net/rtfspec_7.html#rtfspec_paraforprop                   #
## http://www.pindari.com/rtf2.html                                                      #
##                                                                                       #
## For use as source file include: library(R.oo)                                         #
##########################################################################################



#########################################################################/**
# @RdocClass RTF
#
# @title "The RTF class"
#
# \description{
#	This is the class representing an RTF file output.
#
#	@classhierarchy
# }
#
# @synopsis
#
# \arguments{
# 	\item{file}{The path of the output file.}
# 	\item{width}{The width of the output page.}
# 	\item{height}{The width of the output page.}
# 	\item{omi}{A @vector representing the outer margins in inches (bottom, left, top, right).}
#	\item{font.size}{Default font size for the document in points.}
# 	\item{...}{Not used.}
# }
#
# \section{Fields and Methods}{
# 	@allmethods
# }
#
# \examples{
# \dontrun{
# output<-"test_RTF-class.doc"
# png.res<-300
#
# rtf<-RTF(output,width=8.5,height=11,font.size=10,omi=c(1,1,1,1))
# addHeader(rtf,title="Test",subtitle="2011-08-15\n")
# addPlot(rtf,plot.fun=plot,width=6,height=6,res=300, iris[,1],iris[,2])
# 
# # Try trellis plots
# if(require(lattice) & require(grid)) {
# 	# single page trellis objects
# 	addPageBreak(rtf, width=11,height=8.5,omi=c(0.5,0.5,0.5,0.5))
# 
# 	p <- histogram( ~ height | voice.part, data = singer, xlab="Height")
# 	addTrellisObject(rtf,trellis.object=p,width=10,height=7.5,res=png.res)
# 
# 	p <- densityplot( ~ height | voice.part, data = singer, xlab = "Height")
# 	addTrellisObject(rtf,trellis.object=p,width=10,height=7.5,res=png.res)
# 
# 	# multipage trellis object
# 	p2<-xyplot(uptake ~ conc | Plant, CO2, layout = c(2,2))
# 	addTrellisObject(rtf,trellis.object=p2,width=6,height=6,res=png.res)
# }
# 
# addPageBreak(rtf, width=6,height=10,omi=c(0.5,0.5,0.5,0.5))
# addTable(rtf,as.data.frame(head(iris)),font.size=10,row.names=FALSE,NA.string="-")
#
# addSessionInfo(rtf)
# 
# done(rtf)
# }
# }
#
# @author
#
# \seealso{
# 	@seeclass
# }
#
#*/#########################################################################
setConstructorS3("RTF", 
	function(file="",width=8.5,height=11,omi=c(1,1,1,1),font.size=10) {
	this <- extend(Object(), "RTF", 
		.rtf = .start.rtf(width,height,omi), 
		.file = file,
		.font.size = font.size,
		.indent = 0,
		.page.width = width,
		.page.height = height,
		.content.width = width - omi[2] - omi[4]
	);
	
	this;
});


###########################################################################/**
# @RdocMethod addTable
#
# @title "Insert a table into the RTF document"
#
# \description{
#	@get "title".
# }
#
# @synopsis
#
# \arguments{
# 	\item{this}{An RTF object.}
# 	\item{dat}{A matrix, data frame, or table.}
#	\item{col.widths}{A @vector of column widths in inches. \bold{optional}.}
#	\item{col.justify}{A single value or @vector of column justifications ('L', 'R', 'C', or 'J' for Left, Right, Center, and Justify, respectively). \bold{optional}.}
#	\item{header.col.justify}{A single value or @vector of table header column justifications ('L', 'R', 'C', or 'J' for Left, Right, Center, and Justify, respectively). \bold{optional}.}
#	\item{font.size}{Font size in points. \bold{optional}.}
#	\item{row.names}{Boolean argument to include row names in tables. \bold{optional}.}
#	\item{NA.string}{A character string to replace NA values in the table.}
#	\item{space.before}{Space before each row (in inches). \bold{optional}.}
#	\item{space.after}{Space after each row (in inches). \bold{optional}.}
# 	\item{...}{Not used.}
# }
#
# \examples{
# rtf<-RTF("test_addTable.doc",width=8.5,height=11,font.size=10,omi=c(1,1,1,1))
# addTable(rtf,as.data.frame(head(iris)),font.size=10,row.names=FALSE,NA.string="-",
#          col.widths=rep(1,5))
# 
# tab<-table(iris$Species,floor(iris$Sepal.Length))
# names(dimnames(tab))<-c("Species","Sepal Length")
# addTable(rtf,tab,font.size=10,row.names=TRUE,NA.string="-",col.widths=c(1,rep(0.5,4)) )
#
# done(rtf)
# }
#
# @author
#
# \seealso{
# 	@seeclass
# }
# 
# @keyword -internal
#
#*/###########################################################################
setMethodS3("addTable", "RTF", function(this,dat,col.widths=NULL,col.justify=NULL,header.col.justify=NULL,font.size=NULL,row.names=FALSE,NA.string="-", space.before=NULL, space.after=NULL, ...) {
	if(is.null(font.size)) {
		font.size = this$.font.size  # default
	}
	
	this$.rtf <- paste(this$.rtf, .add.table(dat,col.widths,col.justify,header.col.justify,font.size,row.names,indent=this$.indent,NA.string=NA.string,max.table.width=this$.content.width, space.before=space.before, space.after=space.after, ...),sep="")
})

#########################################################################/**
# @RdocMethod view
#
# @title "View encoded RTF"
#
# \description{
#	@get "title".
# }
#
# @synopsis
#
# \arguments{
# 	\item{this}{An RTF object.}
# 	\item{...}{Not used.}
# }
#
# \value{
# 	Output the content of the object as RTF code.
# }
#
# @author
#
# \seealso{
# 	@seeclass
# }
# 
# @keyword -internal
#*/#########################################################################
setMethodS3("view", "RTF", function(this, ...) {
	print(this$.rtf)
	print(this$.file)
})

#########################################################################/**
# @RdocMethod done
#
# @title "Write and close the RTF output"
#
# \description{
#	@get "title".
# }
#
# @synopsis
#
# \arguments{
# 	\item{this}{An RTF object.}
# 	\item{...}{Not used.}
# }
#
# @author
#
# \seealso{
# 	@seeclass
# }
# 
# @keyword -internal
#*/#########################################################################
setMethodS3("done", "RTF", function(this, ...) {
	#this$.rtf <- paste(this$.rtf,.end.rtf(),sep="")
	#write(this$.rtf,this$.file)
	
	# write the file, but don't close it out so that we can continue to 
	# add to the object and write it out again.
	write(paste(this$.rtf,.end.rtf(),sep=""),this$.file)
})


#########################################################################/**
# @RdocMethod addTOC
#
# @title "Insert table of contents field"
#
# \description{
#	@get "title".
# }
#
# @synopsis
#
# \arguments{
# 	\item{this}{An RTF object.}
# 	\item{...}{Not used.}
# }
#
# @author
#
# \seealso{
#   @seeclass
# }
# 
# @keyword -internal
#
#*/#########################################################################
setMethodS3("addTOC", "RTF", function(this,...) {
	toc<-"{\\field\\flddirty\\fldedit{\\*\\fldinst TOC f h}{\\fldrslt Update Field (right-click in MS Word) to show Table of Contents}}\\line\\line"
	
	if(!is.null(this$.font.size)) {
		font.size = this$.font.size  # default
	}
	
	this$.rtf <- paste(this$.rtf,.start.paragraph(indent=this$.indent,font.size=font.size),sep="")
	this$.rtf <- paste(this$.rtf,toc,sep="")
	this$.rtf <- paste(this$.rtf,.end.paragraph(),sep="")
})


#########################################################################/**
# @RdocMethod addHeader
#
# @title "Insert a header into the RTF document"
#
# \description{
#	@get "title".
# }
#
# @synopsis
#
# \arguments{
# 	\item{this}{An RTF object.}
#   \item{title}{Header title text.}
#   \item{subtitle}{Header subtitle text. \bold{optional}.}
#	\item{font.size}{Font size in points. \bold{optional}.}
#	\item{TOC.level}{Indent level for table of contents. \bold{optional}.}
# 	\item{...}{Not used.}
# }
#
# @author
#
# \seealso{
#   @seeclass
# }
# 
# @keyword -internal
#
#*/#########################################################################
setMethodS3("addHeader", "RTF", function(this, title,subtitle=NULL,font.size=NULL,TOC.level=NULL,...) {
	if(is.null(font.size)) {
		font.size = this$.font.size  # default
	}
	
	this$.rtf <- paste(this$.rtf,.add.header(title,subtitle=subtitle,indent=this$.indent,font.size=font.size,TOC.level=TOC.level),sep="")
})



#########################################################################/**
# @RdocMethod addText
#
# @title "Insert text into the RTF document"
#
# \description{
#	@get "title".
# }
#
# @synopsis
#
# \arguments{
# 	\item{this}{An RTF object.}
# 	\item{bold}{Bold text. \bold{optional}.}
# 	\item{italic}{Italic text. \bold{optional}.}
# 	\item{...}{Any number of character strings to concatenate.}
# }
#
# @author
#
# \seealso{
#   @seeclass
# }
# 
# @keyword -internal
#
#*/#########################################################################
setMethodS3("addText", "RTF", function(this, ..., bold=FALSE, italic=FALSE) {
	text<-paste(... , sep="")
	if(bold) {
		text<-paste("\\b ",text,"\\b0",sep="")
	}
	if(italic){
		text<-paste("\\i ",text,"\\i0",sep="")
	}
	this$.rtf <- paste(this$.rtf,.add.text(text),sep="")
})

#########################################################################/**
# @RdocMethod addParagraph
#
# @title "Insert a paragraph into the RTF document"
#
# \description{
#	@get "title".
# }
#
# @synopsis
#
# \arguments{
# 	\item{this}{An RTF object.}
# 	\item{...}{A character @vector of text to add.}
# }
#
# @author
#
# \seealso{
#   @seeclass
# }
# 
# @keyword -internal
#
#*/#########################################################################
setMethodS3("addParagraph", "RTF", function(this, ...) {
	text<-paste(... , sep="")
	
	if(!is.null(this$.font.size)) {
		font.size = this$.font.size  # default
	}
	
	this$.rtf <- paste(this$.rtf,.start.paragraph(indent=this$.indent,font.size=font.size),sep="")
	this$.rtf <- paste(this$.rtf,.add.text(text),sep="")
	this$.rtf <- paste(this$.rtf,.end.paragraph(),sep="")
})

#########################################################################/**
# @RdocMethod startParagraph
#
# @title "Start a new paragraph in the RTF document"
#
# \description{
#	@get "title".
# }
#
# @synopsis
#
# \arguments{
# 	\item{this}{An RTF object.}
# 	\item{...}{Not used.}
# }
#
# @author
#
# \seealso{
#   @seeclass
# }
# 
# @keyword -internal
#
#*/#########################################################################
setMethodS3("startParagraph", "RTF", function(this, ...) {
	this$.rtf <- paste(this$.rtf,.start.paragraph(indent=this$.indent),sep="")
})

#########################################################################/**
# @RdocMethod endParagraph
#
# @title "End a paragraph in the RTF document"
#
# \description{
#	@get "title".
# }
#
# @synopsis
#
# \arguments{
# 	\item{this}{An RTF object.}
# 	\item{...}{Not used.}
# }
#
# @author
#
# \seealso{
#   @seeclass
# }
# 
# @keyword -internal
#
#*/#########################################################################
setMethodS3("endParagraph", "RTF", function(this, ...) {
	this$.rtf <- paste(this$.rtf,.end.paragraph(),sep="")
})

###########################################################################/**
# @RdocMethod addPageBreak
#
# @title "Insert a page break into the RTF document optionally specifying new page settings"
#
# \description{
#	@get "title".
# }
#
# @synopsis
#
# \arguments{
# 	\item{this}{An RTF object.}
# 	\item{width}{New page width in inches. \bold{optional}.}
# 	\item{height}{New page height in inches. \bold{optional}.}
# 	\item{omi}{A @vector of page margins (botton, left, top, right) \bold{optional}.}
# 	\item{...}{Not used.}
# }
#
# \examples{
# rtf<-RTF("test_addPageBreak.doc",width=8.5,height=11,font.size=10,omi=c(1,1,1,1))
# addPageBreak(rtf,width=11,height=8.5,omi=c(0.5,0.5,0.5,0.5))
# done(rtf)
# }
#
# @author
#
# \seealso{
# 	@seeclass
# }
# 
# @keyword -internal
#
#*/###########################################################################
setMethodS3("addPageBreak", "RTF", function(this, width=8.5,height=11,omi=c(1,1,1,1), ...) {
	this$.rtf <- paste(this$.rtf,.add.page.break(width=width,height=height,omi=omi),sep="")	
	this$.page.width = width
	this$.page.height = height
	this$.content.width = width - omi[2] - omi[4]
})

#########################################################################/**
# @RdocMethod addNewLine
#
# @title "Insert a new line into the RTF document"
#
# \description{
#	@get "title".
# }
#
# @synopsis
#
# \arguments{
# 	\item{this}{An RTF object.}
# 	\item{n}{Number of lines to add. Default is 1. \bold{optional}}
# 	\item{...}{Not used.}
# }
#
# @author
#
# \seealso{
#   @seeclass
# }
# 
# @keyword -internal
#
#*/#########################################################################
setMethodS3("addNewLine", "RTF", function(this, n=1, ...) {
	this$.rtf <- paste(this$.rtf,.add.newline(n=n,font.size=this$.font.size),sep="")
})

#########################################################################/**
# @RdocMethod increaseIndent
#
# @title "Increase RTF document indent"
#
# \description{
#	@get "title".
# }
#
# @synopsis
#
# \arguments{
# 	\item{this}{An RTF object.}
# 	\item{...}{Not used.}
# }
#
# @author
#
# \seealso{
#   @seeclass
# }
# 
# @keyword -internal
#
#*/#########################################################################
setMethodS3("increaseIndent", "RTF", function(this, ...) {
	this$.indent <- this$.indent + 720 # 1/2" increments
})

#########################################################################/**
# @RdocMethod decreaseIndent
#
# @title "Decrease RTF document indent"
#
# \description{
#	@get "title".
# }
#
# @synopsis
#
# \arguments{
# 	\item{this}{An RTF object.}
# 	\item{...}{Not used.}
# }
#
# @author
#
# \seealso{
#   @seeclass
# }
# 
# @keyword -internal
#
#*/#########################################################################
setMethodS3("decreaseIndent", "RTF", function(this, ...) {
	this$.indent <- max(0,this$.indent - 720) # 1/2" increments
})

#########################################################################/**
# @RdocMethod setFontSize
#
# @title "Set RTF document font size"
#
# \description{
#	@get "title".
# }
#
# @synopsis
#
# \arguments{
# 	\item{this}{An RTF object.}
# 	\item{font.size}{New default font size in points.}
# 	\item{...}{Not used.}
# }
#
# @author
#
# \seealso{
#   @seeclass
# }
# 
# @keyword -internal
#
#*/#########################################################################
setMethodS3("setFontSize", "RTF", function(this, font.size, ...) {
	this$.font.size <- font.size
})

#########################################################################/**
# @RdocMethod addPlot
#
# @title "Insert a plot into the RTF document"
#
# \description{
#	@get "title".
# }
#
# @synopsis
#
# \arguments{
# 	\item{this}{An RTF object.}
# 	\item{plot.fun}{Plot function.}
# 	\item{width}{Plot output width in inches.}
# 	\item{height}{Plot output height in inches.}
# 	\item{res}{Output resolution in dots per inch.}
# 	\item{...}{Arguments for \code{plot.fun}.}
# }
#
# \details{
# 	Plots are added to the document as PNG objects.  This function will work with all
#   base graphics methods for plotting.  For more sophisticated plots, you may need to
#   wrap your plot code into a function, and then pass a reference to that function to
#   this method.  The parameters for the plot method would then get passed in as '...'
#   above.
#
#   To output a ggplot2 plot, simply assign the plot to a variable.  Then use 'print'
#   as the plot function and pass in the plot variable assigned above. 
# }
#
# \examples{
# rtf<-RTF("test_addPlot.doc",width=8.5,height=11,font.size=10,omi=c(1,1,1,1))
# addPlot(rtf,plot.fun=plot,width=6,height=6,res=300, iris[,1],iris[,2])
# done(rtf)
# }
#
# @author
#
# \seealso{
#   @seeclass
# }
# 
# @keyword -internal
#
#*/#########################################################################
setMethodS3("addPlot", "RTF", function(this,plot.fun=plot.fun,width=3.0,height=0.3,res=300, ...) {
	if(!is.null(this$.font.size)) {
		font.size = this$.font.size  # default
	}
	
	tmp.file<-tempfile("temp_rtf_plot")
	this$.rtf <- paste(this$.rtf,.start.paragraph(indent=this$.indent,font.size=font.size),sep="")
	this$.rtf <- paste(this$.rtf,.rtf.plot(plot.fun=plot.fun,tmp.file=tmp.file,width=width,height=height,res=res, ...),sep="")
	this$.rtf <- paste(this$.rtf,.end.paragraph(),sep="")
	
	if(file.exists(tmp.file) ) {
		unlink(tmp.file)
	}
})

#########################################################################/**
# @RdocMethod addPng
#
# @title "Insert an existing PNG image into the RTF document"
#
# \description{
#	@get "title".
# }
#
# @synopsis
#
# \arguments{
# 	\item{this}{An RTF object.}
# 	\item{file}{Image file path.}
# 	\item{width}{Plot output width in inches.}
# 	\item{height}{Plot output height in inches.}
# 	\item{...}{Not used.}
# }
#
# \details{
# 	Add existing PNG file to RTF document.
# }
#
# @author
#
# \seealso{
#   @seeclass
# }
# 
# @keyword -internal
#
#*/#########################################################################
setMethodS3("addPng", "RTF", function(this,file,width=3.0,height=0.3, ...) {
	if(!is.null(this$.font.size)) {
		font.size = this$.font.size  # default
	}
	
	this$.rtf <- paste(this$.rtf,.start.paragraph(indent=this$.indent,font.size=font.size),sep="")
	this$.rtf <- paste(this$.rtf,.add.png(file,width=width,height=height,verbose=FALSE),sep="")
	this$.rtf <- paste(this$.rtf,.end.paragraph(),sep="")
})

#########################################################################/**
# @RdocMethod addTrellisObject
#
# @title "Insert a trellis plot object into the RTF document"
#
# \description{
#	@get "title".
# }
#
# @synopsis
#
# \arguments{
# 	\item{this}{An RTF object.}
# 	\item{trellis.object}{The trellis plot object.}
# 	\item{width}{Plot output width in inches.}
# 	\item{height}{Plot output height in inches.}
# 	\item{res}{Output resolution in dots per inch.}
# 	\item{rotate}{Object rotation in degrees. \bold{optional}.}
# 	\item{...}{Not used.}
# }
#
# \details{
# 	Plots are added to the document as PNG objects.  Multi-page trellis objects are 
#	automatically split across multiple pages in the RTF output file.  To rotate the
#   object to landscape orientation within the RTF output, use rotate=90.  When using 
#   rotation, width and height still refer to the unrotated plot dimensions and not the 
#   rotated output dimensions on the RTF page.  An alternative to rotating the plot is
#   to rotate the entire page using a call to addPageBreak with suitable page width and
#   height dimensions.
# }
#
# \examples{
# \dontrun{
# rtf<-RTF("test_addTrellisObject.doc",width=8.5,height=11,font.size=10,omi=c(1,1,1,1))
# if(require(lattice) & require(grid)) {
# 	# multipage trellis object
# 	p2<-xyplot(uptake ~ conc | Plant, CO2, layout = c(2,2))
# 	addTrellisObject(rtf,trellis.object=p2,width=8,height=4,res=300, rotate=90)
# }
# done(rtf)
# }
# }
#
# @author
#
# \seealso{
#   @seeclass
# }
# 
# @keyword -internal
#
#*/#########################################################################
setMethodS3("addTrellisObject", "RTF", function(this,trellis.object,width=3.0,height=0.3,res=300, rotate=NULL, ...) {
	tmp.file<-tempfile("temp_rtf_trellis")
	this$.rtf <- paste(this$.rtf,.rtf.trellis.object(trellis.object=trellis.object,tmp.file=tmp.file,width=width,height=height,res=res,rotate=rotate),sep="")
	if(file.exists(tmp.file) ) {
		unlink(tmp.file)
	}
})


#########################################################################/**
# @RdocMethod addSessionInfo
#
# @title "Insert session information into the RTF document"
#
# \description{
#	@get "title".
# }
#
# @synopsis
#
# \arguments{
# 	\item{this}{An RTF object.}
#	\item{locale}{Output the locale.}
# 	\item{...}{Not used.}
# }
#
# \details{
# 	Exports session information to the RTF document in a similar
# }
#
# \examples{
# rtf<-RTF("test_addSessionInfo.doc",width=8.5,height=11,font.size=10,omi=c(1,1,1,1))
# addSessionInfo(rtf)
# done(rtf)
# }
#
# @author
#
# \seealso{
#   @seeclass, \code{\link{sessionInfo}}.
# }
# 
# @keyword -internal
#
#*/#########################################################################
setMethodS3("addSessionInfo", "RTF", function(this, locale = TRUE, ...) {
	
	si<-sessionInfo()
	opkgver <- sapply(si$otherPkgs, function(x) x$Version)
    nspkgver <- sapply(si$loadedOnly, function(x) x$Version)
	
	startParagraph(this)
	addText(this,"\\s1 Session Information",bold=TRUE)
	endParagraph(this)
	
	startParagraph(this)
	addText(this,si$R.version$version.string,bold=TRUE,italic=TRUE)
	endParagraph(this)
	
	increaseIndent(this)
	startParagraph(this)
	addText(this,"Platform: ")
    addText(this,si$R.version$platform,italic=TRUE)
    
	if (locale) {
		addText(this,"\nLocale: ")
        addText(this,si$locale,italic=TRUE)
    }
    endParagraph(this)
    decreaseIndent(this)
    
    startParagraph(this)
    addText(this,"Packages",bold=TRUE,italic=TRUE)
    endParagraph(this)
    
    increaseIndent(this)
    startParagraph(this)
    addText(this,"Base: ")
	addText(this, paste(sort(si$basePkgs), collapse = ", "),italic=TRUE)
	
	if (length(opkgver)) {
        opkgver <- opkgver[sort(names(opkgver))]
        addText(this,"\nOther: ")
        vers<-paste("(v",opkgver,")",sep="")
        addText(this, paste(names(opkgver), vers, sep = " ", collapse = ", "),italic=TRUE)
    }
    
    if (length(nspkgver)) {
        nspkgver <- nspkgver[sort(names(nspkgver))]
    	addText(this,"\nLoaded (not attached): ")
    	vers<-paste("(v",nspkgver,")",sep="")
    	addText(this, paste(names(nspkgver), vers, sep = " ", collapse = ", "),italic=TRUE)
    }
    endParagraph(this)
    decreaseIndent(this)
    
    startParagraph(this)
    addText(this,"Session Details",bold=TRUE,italic=TRUE)
    endParagraph(this)
    increaseIndent(this)
    startParagraph(this)
    addText(this,"Working directory: ")
	addText(this, getwd(),italic=TRUE)
	addText(this,"\nOutput file: ")
	addText(this, this$.file,italic=TRUE)
	endParagraph(this)
    decreaseIndent(this)
})

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

.start.rtf<-function(width=8.5,height=11,omi=c(1,1,1,1)) {
	paste("{\\rtf1\\ansi\n\\deff",.add.font.table(),.add.color.table(),.add.paper.size(width=width,height=height),"\n",.add.page.margins(omi),"\n",.add.page.numbers(),"\n",sep="")
}

.add.font.table<-function() {
	fonts<-character()
	fonts[1]<-"{\\f1\\fswiss\\fcharset0 Helvetica;}" # default
	fonts[2]<-"{\\f2\\ffroman\\charset0\\fprg2 Times New Roman;}"
	fonts[3]<-"{\\f3\\ffswiss\\charset0\\fprg2 Arial;}"
	fonts[4]<-"{\\f4\\fftech\\charset0\\fprg2 Symbol;}"
	fonts[5]<-"{\\f4\\ffroman\\charset0\\fprg2 Cambria;}"
	
	paste("{\\fonttbl",paste(fonts,collapse="\n"),"}",sep="\n")
}

.add.color.table<-function() {
	colors<-character()
	colors[1]<-"{\\red0\\green0\\blue0;}" # default
	colors[2]<-"{\\red255\\green0\\blue0;}"
	colors[3]<-"{\\red0\\green255\\blue0;}"
	colors[4]<-"{\\red0\\green0\\blue255;}"
	
	paste("{\\colortbl",paste(colors,collapse="\n"),"}",sep="\n")
}

.add.page.numbers<-function() {
	paste("\\titlepg\\headery720\\footery720","{\\footer {\\pard\\qc\\fi0\\li0 \\f2\\fs20 \\field{\\fldinst{page}} \\par}}",sep="\n")
}

.add.paper.size<-function(width=8.5,height=11) {
	paste("\\paperw",round(width*1440,0),"\\paperh",round(height*1440,0),"\\widowctrl\\ftnbj\\fet0\\sectd",if(width>height){"\\lndscpsxn"} else {""},"\\linex0",sep="")
}

.add.page.margins<-function(margins=c(1,1,1,1)) {
	paste("\\margl",round(margins[2]*1440,0),"\\margr",round(margins[4]*1440,0),"\\margt",margins[3]*1440,"\\margb",margins[1]*1440,sep="")
}

.add.header<-function(title,subtitle=NULL,indent=0,font.size=10,TOC.level=NULL) {
	if(is.null(subtitle)) {
		paste("{\\pard\\fi0\\li",indent,"\\f2\\fs",font.size*2,"\\b",.get.TOC.level(TOC.level)," ",.convert(title),"\\b0\\line\\par}\n",sep="")
	} else {
		paste("{\\pard\\fi0\\li",indent,"\\f2\\fs",font.size*2,"\\b",.get.TOC.level(TOC.level)," ",.convert(title),"\\b0\\par}\n{\\pard\\fi0\\f2\\fs",font.size*2," ",.convert(subtitle),"\\line\\par}\n",sep="")
	}
}

.get.TOC.level<-function(section.level) {
	ret<-""
	
	if(!is.null(section.level)) {
		ret<-paste("\\s",section.level,sep="")
	}
	
	ret
}

.start.paragraph<-function(indent=0,font.size=10) {
	paste("{\\pard\\fi0\\li",indent,"\\f2\\fs",font.size*2,"\n",sep="")
}

.add.text<-function(x) {
	paste(.convert(x),sep="")
}

.end.paragraph<-function() {
	paste("\\par}\n",sep="")
}

.end.rtf<-function() {
	paste("}",sep="")
}

.add.table.row<-function(col.data=c("c1","c2","c3"),col.widths=c(1.0,4.5,1.0),col.justify=NULL,font.size=10,last.row=FALSE,indent=0, border.top=FALSE, border.bottom=FALSE,space.before=NULL, space.after=NULL) {
	header<-paste("\\trowd\\trgaph100\\trleft",indent,sep="")  # trqc for centered
	
	if(length(col.data) != length(col.widths)) {
		stop(paste("The number of data columns (",length(col.data),") doesn't match the column widths (",length(col.widths),")!  Input data: ",col.data,sep=""))
	}
	
	justify<-vector()
	justify["L"]<-"\\ql"
	justify["R"]<-"\\qr"
	justify["C"]<-"\\qc"
	justify["J"]<-"\\qj"
	
	# Default: Left justify everything except numeric which are right justified
	justify.v<-rep(justify["L"],length(col.data))
	numeric.cols<-which(!is.na(suppressWarnings(as.numeric(col.data))))
	if(length(numeric.cols)>0) { justify.v[numeric.cols] <- justify["R"] }
	
	if(!is.null(col.justify)) {	
		if(length(col.justify)==1) {
			if(col.justify %in% names(justify)) {
				justify.v<-rep(justify[col.justify],length(col.data))
			} else {
				stop(paste("col.justify parameter not recognized: ",col.justify," (should be L, R, C, or J)",sep=""))
			}
		} else if(length(col.justify)==length(col.data)) {
			justify.v<-justify[col.justify]
		} else {
			stop(paste("The number of data columns (",length(col.data),") doesn't match the col.justify (",length(col.justify),") parameter!  Input data: ",paste(col.data,sep="",collapse=", "),sep=""))
		}
	}
	
	# TODO: allow decimal alignment: RTF code: \tqdec\tx810 (where the tx part is the tabbed position)
	
	btop<-""
	bbottom<-""
	
	if(border.top == TRUE) btop <- "\\clbrdrt\\brdrs\\brdrw15"
	if(last.row==TRUE | border.bottom==TRUE) bbottom <- "\\clbrdrb\\brdrs\\brdrw15"
	
	# Top vertical alignment for rows (clvertalt)
	cols.prefix<-paste("\\clvertalt\\clshdrawnil\\clwWidth",round(col.widths*1440,0),"\\clftsWidth3\\clheight260\\clpadl100\\clpadr100\\gaph",btop,bbottom ,"\\cellx",c(1:length(col.widths)),"\n",sep="",collapse="")
	cols<-paste("\\pard",justify.v, .get.space.before.after(space.before, space.after),"\\widctlpar\\intbl\\fi0\\f2\\fs",font.size*2," ", .convert(col.data),"\\cell\n",sep="",collapse="")
	end.row<-"\\widctlpar\\intbl\\row\n"
	paste(header,cols.prefix,cols,end.row,sep="")
}

.get.space.before.after<-function(space.before=NULL,space.after=NULL) {
	ret<-""
	
	# \sbN -- N twips of extra (vertical) space before this paragraph (default: 0)
	# \saN -- N twips of extra (vertical) space after this paragraph (default: 0)
	
	if(!is.null(space.before)) {
		ret<-paste(ret,"\\sb",(space.before*1440),sep="")
	}
	
	if(!is.null(space.after)) {
		ret<-paste(ret,"\\sa",(space.after*1440),sep="")
	}
	
	ret
}

.add.merged.table.row<-function(col.data=c("c1","c2","c3"),col.widths=c(1.0,4.5,1.0),justify="LEFT",font.size=10,last.row=FALSE,indent=0, border.top=FALSE, border.bottom=FALSE) {
	header<-paste("\\trowd\\trgaph100\\trleft",indent,sep="")  # trqc for centered
	
	justify.q<-"\\ql"
	if(justify=="LEFT") justify.q<-"\\ql"
	if(justify=="RIGHT") justify.q<-"\\qr"
	if(justify=="CENTER") justify.q<-"\\qc"
	if(justify=="JUSTIFY") justify.q<-"\\qj"
	
	btop<-""
	bbottom<-""
	
	if(border.top == TRUE) btop <- "\\clbrdrt\\brdrs\\brdrw15"
	if(last.row==TRUE | border.bottom==TRUE) bbottom <- "\\clbrdrb\\brdrs\\brdrw15"
	
	merged<-c("","\\clmgf",rep("\\clmrg",length(col.data)-2))
	
	cols.prefix<-paste("\\clvertalc \\clshdrawnil \\clwWidth",round(col.widths*1440,0),"\\clftsWidth3 \\clheight260 \\clpadl100 \\clpadr100 \\gaph",btop," ",bbottom ,merged,"\\cellx",c(1:length(col.widths)),"\n",sep="",collapse="")
	cols<-paste("\\pard",justify.q,"\\widctlpar\\intbl\\fi0\\f2\\fs",font.size*2," ",.convert(col.data),"\\cell\n",sep="",collapse="")
	end.row<-"\\widctlpar\\intbl \\row \n\n"
	paste(header,cols.prefix,cols,end.row,sep="")
}

.add.table.header.row<-function(col.data=c("c1","c2","c3"),col.widths=c(1.0,4.5,1.0),col.justify=NULL,font.size=10,repeat.header=FALSE,indent=0) {
	header<-paste("\\trowd\\trgaph100\\trleft",indent,sep="")  # trqc for centered

		if(length(col.data) != length(col.widths)) {
		stop(paste("The number of data columns (",length(col.data),") doesn't match the column widths (",length(col.widths),")!  Input data: ",col.data,sep=""))
	}
	
	justify<-vector()
	justify["L"]<-"\\ql"
	justify["R"]<-"\\qr"
	justify["C"]<-"\\qc"
	justify["J"]<-"\\qj"
	
	# Default: Left justify everything
	justify.v<-rep(justify["L"],length(col.data))
	
	if(!is.null(col.justify)) {	
		if(length(col.justify)==1) {
			if(col.justify %in% names(justify)) {
				justify.v<-rep(justify[col.justify],length(col.data))
			} else {
				stop(paste("header.col.justify parameter not recognized: ",col.justify," (should be L, R, C, or J)",sep=""))
			}
		} else if(length(col.justify)==length(col.data)) {
			justify.v<-justify[col.justify]
		} else {
			stop(paste("The number of data columns (",length(col.data),") doesn't match the header.col.justify (",length(col.justify),") parameter!  Input data: ",paste(col.data,sep="",collapse=", "),sep=""))
		}
	}

	if(repeat.header==TRUE) header<-paste(header,"\\trhdr")
	
	# Bottom vertical alignment for headers (clvertalb)
	cols.prefix<-paste("\\clvertalb\\clshdrawnil\\clwWidth",round(col.widths*1440,0),"\\clftsWidth3\\clheight260\\clpadl100\\clpadr100\\gaph\\clbrdrt\\brdrs\\brdrw15\\clbrdrb\\brdrs\\brdrw15\\cellx",c(1:length(col.widths)),"\n",sep="",collapse="")
	cols<-paste("\\pard",justify.v,"\\widctlpar\\intbl\\fi0\\f2\\fs",font.size*2,"\\b ",.convert(col.data),"\\b0\\cell\n",sep="",collapse="")
	end.row<-"\\widctlpar\\intbl\\row\n\n"
	paste(header,cols.prefix,cols,end.row,sep="")
}


.add.table<-function(dat,col.widths=NULL,col.justify=NULL,header.col.justify=NULL,font.size=10,row.names=FALSE,indent=0,NA.string="-",max.table.width=NULL, space.before=NULL, space.after=NULL) {
	ret<-"{\\pard\n"
	
	if("table" %in% class(dat)) {
		if(length(dim(dat))==1) {
		
			varnames<-names(dimnames(dat))[1]
			nc<-2
			nr<-length(dimnames(dat)[[1]])
			
			if(is.null(col.widths)){ col.widths<-rep(6.5/nc,nc)}
			
			ret<-paste(ret,.add.table.header.row(c(names(dimnames(dat))[1]," "),col.widths,header.col.justify,font.size=font.size,repeat.header=TRUE,indent=indent),sep="")
			
			if(nrow(dat)>1) {
				for(i in 1:(nrow(dat)-1) ) {
					rn<-rownames(dat)[i]
					ret<-paste(ret,.add.table.row(c(rn,as.character(dat[i])),col.widths,col.justify,font.size=font.size,indent=indent, space.before=space.before, space.after=space.after),sep="")
				}
			}
			
			rn<-rownames(dat)[nrow(dat)]
			ret<-paste(ret,.add.table.row(c(rn,as.character(dat[nrow(dat)])),col.widths,col.justify,font.size=font.size,indent=indent,border.bottom=TRUE, space.before=space.before, space.after=space.after),sep="")
		
		} else if(length(dim(dat))==2) {
			varnames<-names(dimnames(dat))
			nc<-ncol(dat)+1
			nr<-nrow(dat)
			
			if(is.null(col.widths)){ col.widths<-rep(6.5/nc,nc)}
			
			# ret<-paste(ret,.add.table.header.row(c(" ",colnames(dat)),col.widths,font.size=font.size,repeat.header=TRUE,indent=indent),sep="")
			
			ret<-paste(ret,.add.merged.table.row(c(" ",paste("\\b ",varnames[2]," \\b0",sep=""),rep(" ",nc-2)),col.widths,font.size=font.size,indent=indent,border.top=TRUE),sep="")
			ret<-paste(ret,.add.table.row(c(paste("\\b ",varnames[1]," \\b0",sep=""),colnames(dat)),col.widths,col.justify,font.size=font.size,indent=indent,border.bottom=TRUE),sep="")
			
			if(nrow(dat)>1) {
				for(i in 1:(nrow(dat)-1) ) {
					rn<-rownames(dat)[i]
					ret<-paste(ret,.add.table.row(c(rn,as.character(dat[i,])),col.widths,col.justify,font.size=font.size,indent=indent, space.before=space.before, space.after=space.after),sep="")
				}
			}
			
			rn<-rownames(dat)[nrow(dat)]
			ret<-paste(ret,.add.table.row(c(rn,as.character(dat[nrow(dat),])),col.widths,col.justify,font.size=font.size,indent=indent,border.bottom=TRUE, space.before=space.before, space.after=space.after),sep="")
		
		} else {
			stop("Table dimensions can't be written")
		}
	
	} else if("xtab" %in% class(dat)) {
		
		nc<-ncol(dat$counts)+2
		nr<-nrow(dat$counts)
		
		if(is.null(col.widths)){ col.widths<-rep(6.5/nc,nc)}
		
		# ret<-paste(ret,.add.table.header.row(c(" ",colnames(dat)),col.widths,font.size=font.size,repeat.header=TRUE,indent=indent),sep="")
		
		ret<-paste(ret,.add.merged.table.row(c(" ",paste("\\b ",dat$varnames[2]," \\b0",sep=""),rep(" ",nc-2)),col.widths,font.size=font.size,indent=indent,border.top=TRUE),sep="")
		ret<-paste(ret,.add.table.row(c(paste("\\b ",dat$varnames[1]," \\b0",sep=""),colnames(dat$counts),"Total"),col.widths,col.justify,font.size=font.size,indent=indent,border.bottom=TRUE),sep="")
		grand.total<-sum(dat$col.margin)
		
		if(nrow(dat$counts)>1) {
			for(i in 1:(nrow(dat$counts)) ) {
				rn<-rownames(dat$counts)[i]
				ret<-paste(ret,.add.table.row(c(rn,as.character(dat$counts[i,]),paste( dat$row.margin[i]," (",sprintf("%0.1f",dat$row.margin[i]/grand.total*100),"%)" ,sep="")),col.widths,col.justify,font.size=font.size,indent=indent, space.before=space.before, space.after=space.after),sep="")
				ret<-paste(ret,.add.table.row(c(" ",paste("(",sprintf("%0.1f",dat$counts[i,]/dat$row.margin[i]*100),"% R)",sep="")," "),col.widths,col.justify,font.size=font.size,indent=indent, space.before=space.before, space.after=space.after),sep="")
				ret<-paste(ret,.add.table.row(c(" ",paste("(",sprintf("%0.1f",dat$counts[i,]/dat$col.margin*100),"% C)",sep="")," "),col.widths,col.justify,font.size=font.size,indent=indent, space.before=space.before, space.after=space.after),sep="")
				ret<-paste(ret,.add.table.row(rep(" ",nc),col.widths,col.justify,font.size=font.size,indent=indent, space.before=space.before, space.after=space.after),sep="")
			}
		}
		
		# Total rows
		ret<-paste(ret,.add.table.row(c("Total",paste(as.character(dat$col.margin),paste(" (",sprintf("%0.1f",dat$col.margin/grand.total*100),"%)",sep="")),as.character(grand.total)),col.widths,font.size=font.size,last.row=TRUE,indent=indent, space.before=space.before, space.after=space.after),sep="")
		
	# handle etables (etables are just matrices with a 'start cell' defined)
	} else if ("matrix" %in% class(dat) & !is.null(attributes(dat)$"start cell")) { 
		start.row<-attributes(dat)$"start cell"[1]
		
		# convert matrix to data frame
		dat<-as.data.frame(dat,stringsAsFactors=FALSE)
		dat[is.na(dat)] <- NA.string
		dat[dat=="NA"] <- NA.string
		
		# if no column widths are specified, then calculate optimal sizes that fit the page
		if(is.null(col.widths) & !is.null(max.table.width)) {
			col.widths <- .optimize.col.widths(dat,include.row.names=row.names,max.table.width=max.table.width,font.size=font.size)
		}
		
		# render the header
		nc<-ncol(dat)
		if(is.null(col.widths)){ col.widths<-rep(6.5/nc,nc)}
		
		if(nrow(dat)>1) {
			for(i in 1:(nrow(dat)-1)) {
				if(i<start.row) {
					border.top=FALSE
					border.bottom=FALSE
					if (i==1) { border.top=TRUE }
					if (i==(start.row-1)) { border.bottom=TRUE }
					ret<-paste(ret,.add.table.row(paste("\\b ",as.character(dat[i,])," \\b0",sep=""),col.widths,col.justify,font.size=font.size,indent=indent,border.top=border.top,border.bottom=border.bottom,space.before=space.before, space.after=space.after),sep="")

				} else {
					ret<-paste(ret,.add.table.row(as.character(dat[i,]),col.widths,col.justify,font.size=font.size,indent=indent, space.before=space.before, space.after=space.after),sep="")
				}
			}
			ret<-paste(ret,.add.table.row(as.character(dat[nrow(dat),]),col.widths,col.justify,font.size=font.size,last.row=TRUE,indent=indent, space.before=space.before, space.after=space.after),sep="")
		}
	
	} else if ("data.frame" %in% class(dat) || "matrix" %in% class(dat)) {
		# convert matrix to data frame
		if("matrix" %in% class(dat)) {
			dat<-as.data.frame(dat)
		}
		
		# convert factor columns in a data frame to characters
		rnames<-rownames(dat)
		is.na(dat) <- is.na(dat) # handle NaN values by converting to NAs since this throws an error: dat[is.nan(dat)] <- NA.string
		dat<-data.frame(lapply(dat,as.character),stringsAsFactors=FALSE,check.names=FALSE)
		dat[is.na(dat)] <- NA.string
		dat[dat=="NA"] <- NA.string
		rownames(dat)<-rnames
		
		# if no column widths are specified, then calculate optimal sizes that fit the page
		if(is.null(col.widths) & !is.null(max.table.width)) {
			col.widths <- .optimize.col.widths(dat,include.row.names=row.names,max.table.width=max.table.width,font.size=font.size)
		}
		
		# render the header
		nc<-ncol(dat)
		if(row.names==TRUE){ nc<-nc+1 }
		
		if(is.null(col.widths)){ col.widths<-rep(6.5/nc,nc)}
		
		
		if(row.names==TRUE){
			ret<-paste(ret,.add.table.header.row(c(" ",colnames(dat)),col.widths,header.col.justify,font.size=font.size,repeat.header=TRUE,indent=indent),sep="")
		} else {
			ret<-paste(ret,.add.table.header.row(colnames(dat),col.widths,header.col.justify,font.size=font.size,repeat.header=TRUE,indent=indent),sep="")
		}
		
		# render the rows
		if(nrow(dat)>1) {
			for(i in 1:(nrow(dat)-1)) {
				if(row.names==TRUE){
					rn<-rownames(dat)[i]
					ret<-paste(ret,.add.table.row(c(rn,as.character(dat[i,])),col.widths,col.justify,font.size=font.size,indent=indent, space.before=space.before, space.after=space.after),sep="")
				} else {
					ret<-paste(ret,.add.table.row(as.character(dat[i,]),col.widths,col.justify,font.size=font.size,indent=indent, space.before=space.before, space.after=space.after),sep="")
				}
			}
		}
		
		if(row.names==TRUE){
			rn<-rownames(dat)[nrow(dat)]
			ret<-paste(ret,.add.table.row(c(rn,as.character(dat[nrow(dat),])),col.widths,col.justify,font.size=font.size,last.row=TRUE,indent=indent, space.before=space.before, space.after=space.after),sep="")
		} else {
			ret<-paste(ret,.add.table.row(as.character(dat[nrow(dat),]),col.widths,col.justify,font.size=font.size,last.row=TRUE,indent=indent, space.before=space.before, space.after=space.after),sep="")
		}
		
	} else {
		warning("No suitable RTF converter for object class!")
	}
	
	ret<-paste(ret,"}\n\n",sep="")
	ret
}

.add.page.break<-function(width=8.5,height=11,omi=c(1,1,1,1)) {
	#	"\\pard {\\f1 \\sect } \\sectd \\lndscpsxn\\pgwsxn16840\\pghsxn11907\\left\\widctlpar\\fi0\\f2\\fs18 \\par"
	# previous: "\\pard {\\f1 \\column }\\left\\widctlpar\\fi0\\f2\\fs18 \\par"
	paste("\\pard{\\f1\\sect}\\sectd",.add.paper.size(width=width,height=height),.add.page.margins(omi),"\\left\\widctlpar\\fi0\\f2\\fs18",sep="")
}

.convert<-function(x) {
	# http://www.ssec.wisc.edu/~tomw/java/unicode.html
	#x<-gsubfn("\\u(\\d+)", .hex2dec, x, engine="R")     # format UTF-8 characters from hex to dec
	#x<-gsub("\\u(\\d+)","\\\\u\\1\\\\3",x)  # format UTF-8 characters from hex to dec
	
	x<-gsub("\\n"," \\\\line ",x)         # convert new line to RTF \line
	#x<-gsub("\\t"," \\\\tab ",x)         # convert tab to RTF \tab
	x<-gsub("<=","\\\\u8804\\\\3",x)      # convert <= to RTF symbol
	x<-gsub(">=","\\\\u8805\\\\3",x)      # convert >= to RTF symbol
	
# 	x<-gsub(":delta:","\\\\u0916\\\\3",x) # convert :delta: to uppercase Greek delta
# 	
# 	x<-gsub("&alpha;","\\\\u0945\\\\3",x) # convert &alpha; to lowercase Greek alpha
# 	x<-gsub("&beta;","\\\\u0946\\\\3",x)  # convert &beta; to lowercase Greek beta
# 	x<-gsub("&gamma;","\\\\u0947\\\\3",x) # convert &gamma; to lowercase Greek gamma
# 	x<-gsub("&delta;","\\\\u0948\\\\3",x) # convert &delta; to lowercase Greek delta
# 	x<-gsub("&epsilon;","\\\\u0949\\\\3",x) # convert &epsilon; to lowercase Greek epsilon
# 	x<-gsub("&theta;","\\\\u0952\\\\3",x) # convert &theta; to lowercase Greek theta
# 	x<-gsub("&kappa;","\\\\u0954\\\\3",x) # convert &kappa; to lowercase Greek kappa
# 	x<-gsub("&lambda;","\\\\u0955\\\\3",x) # convert &lambda; to lowercase Greek lambda
# 	x<-gsub("&mu;","\\\\u0956\\\\3",x)    # convert &mu; to lowercase Greek lambda
# 	
# 	x<-gsub("&Alpha;","\\\\u0913\\\\3",x) # convert &Alpha; to uppercase Greek alpha
# 	x<-gsub("&Beta;","\\\\u0914\\\\3",x)  # convert &Beta; to uppercase Greek beta
# 	x<-gsub("&Gamma;","\\\\u0915\\\\3",x) # convert &Gamma; to uppercase Greek gamma
# 	x<-gsub("&Delta;","\\\\u0916\\\\3",x) # convert &Delta; to uppercase Greek delta
# 	x<-gsub("&Epsilon;","\\\\u0917\\\\3",x) # convert &Epsilon; to uppercase Greek epsilon
# 	x<-gsub("&Theta;","\\\\u0920\\\\3",x) # convert &Theta; to uppercase Greek theta
# 	x<-gsub("&Kappa;","\\\\u0922\\\\3",x) # convert &Kappa; to lowercase Greek kappa
# 	x<-gsub("&Lambda;","\\\\u0923\\\\3",x) # convert &Lambda; to lowercase Greek lambda
# 	x<-gsub("&Mu;","\\\\u0924\\\\3",x)     # convert &Mu; to lowercase Greek lambda
	
	# convert HTML characters
	x<-gsub("&gt;",">",x)
	x<-gsub("&lt;","<",x)
	
	# convert uppercase and lowercase Greek letters
	x<-gsub("&Alpha;","\\\\u0913\\\\3",x)
	x<-gsub("&Beta;","\\\\u0914\\\\3",x)
	x<-gsub("&Gamma;","\\\\u0915\\\\3",x)
	x<-gsub("&Delta;","\\\\u0916\\\\3",x)
	x<-gsub("&Epsilon;","\\\\u0917\\\\3",x)
	x<-gsub("&Zeta;","\\\\u0918\\\\3",x)
	x<-gsub("&Eta;","\\\\u0919\\\\3",x)
	x<-gsub("&Theta;","\\\\u0920\\\\3",x)
	x<-gsub("&Iota;","\\\\u0921\\\\3",x)
	x<-gsub("&Kappa;","\\\\u0922\\\\3",x)
	x<-gsub("&Lambda;","\\\\u0923\\\\3",x)
	x<-gsub("&Mu;","\\\\u0924\\\\3",x)
	x<-gsub("&Nu;","\\\\u0925\\\\3",x)
	x<-gsub("&Xi;","\\\\u0926\\\\3",x)
	x<-gsub("&Omicron;","\\\\u0927\\\\3",x)
	x<-gsub("&Pi;","\\\\u0928\\\\3",x)
	x<-gsub("&Rho;","\\\\u0929\\\\3",x)
	x<-gsub("&Sigma;","\\\\u0931\\\\3",x)
	x<-gsub("&Tau;","\\\\u0932\\\\3",x)
	x<-gsub("&Upsilon;","\\\\u0933\\\\3",x)
	x<-gsub("&Phi;","\\\\u0934\\\\3",x)
	x<-gsub("&Chi;","\\\\u0935\\\\3",x)
	x<-gsub("&Psi;","\\\\u0936\\\\3",x)
	x<-gsub("&Omega;","\\\\u0937\\\\3",x)
	x<-gsub("&alpha;","\\\\u0945\\\\3",x)
	x<-gsub("&beta;","\\\\u0946\\\\3",x)
	x<-gsub("&gamma;","\\\\u0947\\\\3",x)
	x<-gsub("&delta;","\\\\u0948\\\\3",x)
	x<-gsub("&epsilon;","\\\\u0949\\\\3",x)
	x<-gsub("&zeta;","\\\\u0950\\\\3",x)
	x<-gsub("&eta;","\\\\u0951\\\\3",x)
	x<-gsub("&theta;","\\\\u0952\\\\3",x)
	x<-gsub("&iota;","\\\\u0953\\\\3",x)
	x<-gsub("&kappa;","\\\\u0954\\\\3",x)
	x<-gsub("&lambda;","\\\\u0955\\\\3",x)
	x<-gsub("&mu;","\\\\u0956\\\\3",x)
	x<-gsub("&nu;","\\\\u0957\\\\3",x)
	x<-gsub("&xi;","\\\\u0958\\\\3",x)
	x<-gsub("&omicron;","\\\\u0959\\\\3",x)
	x<-gsub("&pi;","\\\\u0960\\\\3",x)
	x<-gsub("&rho;","\\\\u0961\\\\3",x)
	x<-gsub("&sigmaf;","\\\\u0962\\\\3",x)
	x<-gsub("&sigma;","\\\\u0963\\\\3",x)
	x<-gsub("&tau;","\\\\u0964\\\\3",x)
	x<-gsub("&upsilon;","\\\\u0965\\\\3",x)
	x<-gsub("&phi;","\\\\u0966\\\\3",x)
	x<-gsub("&chi;","\\\\u0967\\\\3",x)
	x<-gsub("&psi;","\\\\u0968\\\\3",x)
	x<-gsub("&omega;","\\\\u0969\\\\3",x)
	
	x<-gsub("TRUE","Yes",x)
	x<-gsub("FALSE","No",x)
	x
}

.add.newline<-function(n=NULL, font.size=10) {
	# return("\\line ")

	ret<-paste("{\\pard\\fi0\\f2\\fs",(font.size*2),sep="")
	
	if(is.null(n)) {
		if(n>=2) {
			ret<-paste(ret,paste(rep("\\line",n),"\n",collapse="",sep=""),sep="")
		}
	}
	paste(ret,"\\par}",sep="")
}

.chunk.vector<-function(tokens,n=10) {
 	nlines<-as.integer(length(tokens)/n)+1
	ntokens.line <- n #ceiling(length(tokens) / nlines) # tokens per line
	token.list <- split(tokens, rep( 1:ntokens.line, each=ntokens.line, len=length(tokens)))
	munged<-lapply(token.list,paste,collapse="")
	do.call(paste,list(munged,collapse="\n"))
}

# width and height are in inches
.add.png<-function(file,width=3,height=3,verbose=FALSE) {
	# return a hexadecimal version of a file
	max.bytes<-50000000  # maximum file size in bytes (~50MB)
	dat<-readBin(file, what="raw", size=1, signed=TRUE, endian="little",n=max.bytes);
	if(verbose) {
		cat(paste(length(dat),"bytes read\n"))
	}
	paste("{\\rtf1\\ansi\\deff0{\\pict\\pngblip\\picwgoal",round(width*1440),"\\pichgoal",round(height*1440)," ",paste(dat,collapse=""),"}}",sep="")
	# paste("{\\rtf1\\ansi\\deff0{\\pict\\pngblip\\picwgoal",round(width*1440),"\\pichgoal",round(height*1440)," \n",.chunk.vector(dat),"}}",sep="")
}

.rtf.plot<-function(plot.fun,tmp.file="temp.png",width=3.0,height=0.3,res=300, ...) {
	width.px<-round(width*res)
	height.px<-round(height*res)
	#png(tmp.file,width=width.px,height=height.px,units="px",pointsize=8,bg = "white",res=res)
	png(tmp.file,width=width.px,height=height.px,units="px",pointsize=8,bg = "transparent",res=res)
	plot.fun(...)
	dev.off()
	.add.png(tmp.file,width=width,height=height)
}

.rtf.trellis.object<-function(trellis.object,tmp.file="temp.png",width=3.0,height=0.3,res=300,rotate=NULL, ...) {
	if(class(trellis.object) != "trellis") {
		stop("Not a trellis object!")
	}
	
	ret<-""
	
# 	if(is.null(trellis.object$layout)) {
# 		# single page
# 		width.px<-round(width*res)
# 		height.px<-round(height*res)
# 		png(tmp.file,width=width.px,height=height.px,units="px",pointsize=8,bg = "white",res=res)
# 		print(trellis.object)
# 		dev.off()
# 		
# 		if(!is.null(rotate)) {
# 			system(paste("convert -rotate ",rotate," '",tmp.file,"' '",tmp.file,"'",sep=""))
# 			ret<-.add.png(tmp.file,width=height,height=width) # swap width and height
# 		} else {
# 			ret<-.add.png(tmp.file,width=width,height=height)
# 		}
# 		
# 	} else {
# 		plot.cnt<-dim(trellis.object)
# 		per.page<-trellis.object$layout[1]*trellis.object$layout[2]
# 		pages<-floor((plot.cnt-1)/per.page)+1
# 		
# 		for(pg in 1:pages) {
# 			plot.start<-(pg-1)*per.page+1
# 			plot.end<-min(plot.cnt,pg*per.page)
# 			
# 			width.px<-round(width*res)
# 			height.px<-round(height*res)
# 			png(tmp.file,width=width.px,height=height.px,units="px",pointsize=8,bg = "white",res=res)
# 			print(trellis.object[plot.start:plot.end])
# 			dev.off()
# 			
# 			if(!is.null(rotate)) {
# 				system(paste("convert -rotate ",rotate," '",tmp.file,"' '",tmp.file,"'",sep=""))
# 				ret<-paste(ret,.add.png(tmp.file,width=height,height=width),sep="\n") # swap width and height
# 			} else {
# 				ret<-paste(ret,.add.png(tmp.file,width=width,height=height),sep="\n")
# 			}
# 		}
# 	}
	
	if(is.null(trellis.object$layout)) {
		# single page
		width.px<-round(width*res)
		height.px<-round(height*res)
		
		if(!is.null(rotate)) {
			
			png(tmp.file,width=height.px,height=width.px,units="px",pointsize=8,bg = "transparent",res=res)
			#grid::grid.newpage()
			grid::pushViewport(grid::viewport(width=grid::unit(width,"inches"),height=grid::unit(height,"inches"),angle = rotate))
			print(trellis.object, newpage = FALSE)
			#grid::upViewport()
			dev.off()
			
			ret<-.add.png(tmp.file,width=height,height=width) # swap width and height
			
		} else {
			png(tmp.file,width=width.px,height=height.px,units="px",pointsize=8,bg = "transparent",res=res)
			print(trellis.object)
			dev.off()
			
			ret<-.add.png(tmp.file,width=width,height=height)
		}
		
	} else {
		plot.cnt<-dim(trellis.object)
		per.page<-trellis.object$layout[1]*trellis.object$layout[2]
		pages<-floor((plot.cnt-1)/per.page)+1
		
		for(pg in 1:pages) {
			plot.start<-(pg-1)*per.page+1
			plot.end<-min(plot.cnt,pg*per.page)
			
			width.px<-round(width*res)
			height.px<-round(height*res)
			
			if(!is.null(rotate)) {
				png(tmp.file,width=height.px,height=width.px,units="px",pointsize=8,bg = "transparent",res=res)
				#grid::grid.newpage()
				grid::pushViewport(grid::viewport(width=grid::unit(width,"inches"),height=grid::unit(height,"inches"),angle = rotate))
				print(trellis.object[plot.start:plot.end], newpage = FALSE)
				#grid::upViewport()
				dev.off()
				ret<-paste(ret,.add.png(tmp.file,width=height,height=width),sep="\n") # swap width and height
			} else {
				png(tmp.file,width=width.px,height=height.px,units="px",pointsize=8,bg = "transparent",res=res)
				print(trellis.object[plot.start:plot.end])
				dev.off()
				
				ret<-paste(ret,.add.png(tmp.file,width=width,height=height),sep="\n")
			}
		}
	}
	
	ret
}

.max.col.nchar<-function(x,include.row.names=FALSE,wrap.headers=TRUE) {
	# for each column, returns the maximum width (in characters) of either the 
	# largest header word or largest entire column field.  This allows 
	# headers to be longer and have word wrapping while attempting
	# to keep each field of table data on a single line.
	
	contents<-apply(x,2,function(x){max(nchar(x))} )
	col.nchar<-c()
	
	if(wrap.headers==TRUE) {
		# find maximum word width in each header (split on spaces)
		headers<-sapply(names(x), function(x){ max(nchar(strsplit(x," ")[[1]])) } )
		col.nchar<-mapply(max,contents,headers)
	} else {
		headers<-nchar(names(x))
		col.nchar<-mapply(max,contents,headers)
	}
	
	if(include.row.names==TRUE) {
		row.names.nchar<-max(sapply(rownames(x),nchar))
		col.nchar<-c(row.names.nchar,col.nchar)
	}
	
	col.nchar
}

# TODO this code is not so great.  Can we take advantage of R's strwidth function
# and plug in the font faces and size in points to estimate the width. E.g.
# 
# strwidth("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.?,;:'\"!@#$%^&*()-=+_[]{}|\\",units="inches",family="Times",ps=12)
# 
# where face = (1=plain, 2=bold, 3=italic, 4=bold-italic)
# see also : ps.options()

.optimize.col.widths<-function(x,include.row.names=FALSE,max.table.width=6.5,font.size=9,col.padding=0.1) {
	letter.width<-font.size * 1/144    # font point size to width (roughly 1/144 inch)
	letter.width<-letter.width + 0.03  # fine tuning to account for all caps columns or bold
	
	max.nchars <- .max.col.nchar(x,include.row.names,wrap.headers=TRUE)
	col.widths <- max.nchars*letter.width+2*col.padding
	
	# This could also be tweaked with a more formal character width analysis:
	# http://stephensite.net/WordPressSS/2008/02/19/how-to-calculate-the-character-width-accross-fonts-and-points/

	# If table is still too wide, resize each column proportionally to the content to
	# fit the maximum table width allowed
	if(sum(col.widths) > max.table.width) {
		col.widths<-col.widths/sum(col.widths) * max.table.width
	}
	
	col.widths
}

.hex2dec <- function(hexadecimal) {
	hexdigits <- c(0:9, LETTERS[1:6])
	hexadecimal <- toupper(hexadecimal)    # treat upper/lower case the same
	decimal <- rep(0, length(hexadecimal))
	for (i in 1:length(hexadecimal)) {
		digits <- match(strsplit(hexadecimal[i],"")[[1]], hexdigits) - 1
		decimal[i] <- sum(digits * 16^((length(digits)-1):0))
	}
	return(decimal)
}



# Functions for pivoting data tables
# this is especially useful for pivoting data frames created with ddply.
# Additional options for including gaps between repeated categories
#
#
# tab<-cast(stats, Marker + Visit + TRT ~ stat)
# split.cols<-c("Marker","Visit")
# 
# removedups<-function(x){ x[which(as.character(x) == c(tail(as.character(x),-1),NA))+1]<-NA; x }
# removedups_previous<-function(x,y){ x[ setdiff(which(as.character(x) == c(tail(as.character(x),-1),NA))+1,which(!is.na(y))) ]<-NA; x }
# 
# tab[,split.cols[1]]<-removedups(tab[,split.cols[1]])
# for(i in 2:length(split.cols)) {
# 	tab[,split.cols[i]]<-removedups_previous(tab[,split.cols[i]],tab[,split.cols[i-1]])
# }
# 
# # Insert section breaks
# idx<-order( c( 1:nrow(tab), which(apply(!is.na(tab[,split.cols]), 1, any))-0.5  )) # break at previous row
# idx[which(idx>nrow(tab))]<-NA
# tab<-tab[idx,]
# colnames(tab)<-c("Marker","Visit","Trt","N","Mean Change From Baseline","Diff. Estimate (95CI)","P")

Try the rtf package in your browser

Any scripts or data that you put into this service are public.

rtf documentation built on March 26, 2020, 7:40 p.m.