rorientationrtf

Combine several RTF files with different page orientation in R


I am trying to assemble rtf with r2rtf package in R. But it doesn't support different page orientation. If there is other opportunity? I have multiple RTF files which already oriented correctly. They have different headers, footers ect. I just need to assemble them into 1 RTF file with correct orientation and page numbers.

I tried

assemble_rtf(
  input = file,
  output = output,
  landscape = FALSE
)

file - stores paths to my temp separate RTFs output - path to output RTF

consider I have orientation specified for each separate file: rtf_page(orientation = "landscape") or rtf_page(orientation = "portrait")

but in assembled RTF all pages were landscape


Solution

  • I'll use the example from r2rtf::assemble_rtf since you haven't provided any. Before we start, here it is, creating a portrait & a landscape document and merging those:

    library(r2rtf)
    
    file <- replicate(2, tempfile(fileext = ".rtf"))
    file1 <- head(iris) |>
      rtf_body() |>
      rtf_encode() |>
      write_rtf(file[1])
    file2 <- head(cars) |>
      rtf_page(orientation = "landscape") |>
      rtf_body() |>
      rtf_encode() |>
      write_rtf(file[2])
    output <- tempfile(fileext = ".rtf")
    
    assemble_rtf(
      input = file,
      output = output
    )
    

    This very example suffers from the same problem you have: it comes out as a landscape document. Aside, the landscape= argument of assemble_rtf v1.1.4 does nothing other than being the subject of some input validation, here's the entire source reference for it:

    assemble_rtf <- function (input, output, landscape = FALSE)  {
       check_args(landscape, "logical", length = 1)
       ## ..... and nothing else??
    }
    

    Anyway, the major issue with the entire package is that they don't use sections which allow different page sizes & orientations, they only use properties that target the entire document at once. Here is a version of assemble_rtf that makes this distinction, I've labelled the changes:

    my_assemble_rtf <- function(input, output) {
       input <- normalizePath(input)
       n_input <- length(input)
       missing_input <- input[!file.exists(input)]
       
       if (length(missing_input) > 0) {
          warning("Missing files: \n", paste(missing_input, collapse = "\n"))
          input <- setdiff(input, missing_input)
       }
       
       rtf <- lapply(seq_along(input), \(i) {
          ## Change 1: use \LNDSCPSXN (landscape section)
          ##           rather than \LANDSCAPE (whole document)
          lines <- readLines(input[i]) |>
            stringi::stri_replace_all_regex("\\landscape", "\\lndscpsxn")
    
          ## Change 2: use section-specific page sizes & margins
          ##           after the first (which will still define the global default)
          if (i > 1) {
             lines <- stringi::stri_replace_all_regex(
                        lines,
                        "\\\\paper(w|h)(?=[^s])",
                        "\\\\pg$1sxn"
                      )
             lines <- stringi::stri_replace_all_regex(
                        lines,
                        "\\\\marg(b|l|t|r)(?=[^s])",
                        "\\\\marg$1sxn"
                      )
          }
          return(lines)
       })
       
       n <- length(rtf)
       start <- c(
         1,
         vapply(rtf[-1], \(x) max(grep("fcharset", rtf[[1]])) + 2, numeric(1))
       )
       end <- vapply(rtf, length, numeric(1))
       end[-n] <- end[-n] - 1
       for (i in seq_len(n)) {
          rtf[[i]] <- rtf[[i]][start[i]:end[i]]
          
          ## Change 3: start a new *section* rather than a new *page*
          ##           after each respective document
          if (i < n)  rtf[[i]] <- c(rtf[[i]], "\n\\pard\n\\sect\n")
       }
       
       rtf <- do.call(c, rtf)
       r2rtf::write_rtf(rtf, output)
    }
    

    I should add that this solution is really quite bare-bones because r2rtf apparently creates & assumes very basic RTF structure -- for example that there isn't anything but a fontmap in the header. Still, the principle applies and would require detailed knowledge of your actual RTF structure to further generalize.