Package 'easeling'

Title: Minimal 'DrawingML' Graphics Device for 'openxlsx2'
Description: A base R graphics device that writes plots directly as 'OOXML' 'DrawingML' vector shapes (ECMA-376, <https://ecma-international.org/publications-and-standards/standards/ecma-376/>), for adding editable graphics to spreadsheets with 'openxlsx2' (via its wb_add_drawing() function). Has no dependency on 'cairo', 'FreeType', 'fontconfig', or 'xml2'; real font metrics are used when the 'systemfonts' package is available.
Authors: Jan Marvin Garbuszus [aut, cre]
Maintainer: Jan Marvin Garbuszus <[email protected]>
License: MIT + file LICENSE
Version: 0.4.0
Built: 2026-11-06 15:19:54 UTC
Source: https://github.com/JanMarvin/easeling

Help Index


Open a DrawingML graphics device for openxlsx2

Description

Draws directly to a standalone DrawingML XML file containing 'xdr:sp' shapes, suitable for 'openxlsx2::wb_add_drawing(xml = file)'. No dependency on Cairo, FreeType, fontconfig, or xml2.

Usage

easel_dev(
  file = tempfile(fileext = ".xml"),
  width = 6,
  height = 6,
  units = "in",
  pointsize = 12,
  fontname = "Calibri",
  bg = NA,
  symbolfamily = "",
  underline = FALSE,
  strikeout = FALSE,
  dims = NULL,
  wb = NULL,
  sheet = 1,
  text_voff = 0.35,
  metrics = NULL
)

Arguments

file

Path of the XML file to write. Defaults to a temp file. For a plain string instead of a file, see [easel_xml()].

width, height

Device size, in 'units'.

units

Unit for 'width' and 'height': '"in"' (default), '"cm"', '"mm"' or '"px"'. Pixels are 96 per inch, the value spreadsheet applications use. Ignored when 'dims' is given, since a cell region already fixes the size.

pointsize

Default font pointsize.

fontname

Default font typeface (matches ‘openxlsx2::wb_add_font()'’s 'name' argument), e.g. '"Calibri"', '"Arial"'. Used whenever R itself doesn't request a specific family — i.e. whenever a plot's own ‘family'/'fontfamily' is unset or is one of R’s generic aliases ('"sans"', '"serif"', '"mono"', '"symbol"', or '""'). If a plot sets an actual font name (e.g. 'par(family = "Georgia")' or 'theme_minimal(base_family = "Georgia")'), that takes priority over this default.

bg

Background colour drawn behind the plot. The default, 'NA', leaves the drawing transparent so the sheet shows through.

symbolfamily

Typeface asked for on plotmath symbols. The glyphs are written as Unicode either way, so this only matters when 'fontname' lacks them: '"Cambria Math"' is the usual choice on Windows. Empty (the default) keeps symbols in 'fontname'.

underline, strikeout

Apply underline/strikeout to all text on this device. Unlike 'fontname', these have no per-call R equivalent (base graphics has no underline/strikeout concept), so they're a device-wide setting.

dims

Optionally, a cell range such as '"A1:G15"'. If given, 'width' and 'height' are ignored and computed from the region via [easel_size()], so the plot fills that region exactly when later anchored to the same 'dims'.

wb, sheet

Passed to [easel_size()] when 'dims' is given: the 'openxlsx2' workbook (and sheet) to read actual column widths and row heights from.

text_voff

Vertical text calibration in em: text boxes are centre-anchored, and the baseline is placed 'text_voff' em below the box centre. The default ‘0.35' was calibrated against Excel’s line layout for Calibri; LibreOffice's optimum is around 0.24, so text there sits ~0.1 em low. Increase to shift rendered text up, decrease to shift it down, if your spreadsheet application's line layout places it visibly off; see 'system.file("examples", "calibrate_text.R", package = "easeling")'.

metrics

Font metrics used for text layout (string widths, vertical centring, margins). 'NULL' (default): use real metrics for 'fontname' via the 'systemfonts' package when it is installed, otherwise the built-in Calibri-like table. 'FALSE': always use the built-in table. Or a list with numeric components 'widths', 'ascents', 'descents', each of length 95 giving em fractions for the ASCII characters 32..126. Metrics only affect what R computes - rendering is always done by the spreadsheet application with the real font - but better metrics mean legend boxes, margins, and centring are sized for the text that will actually appear.

The machine writing the file need not have the font it will be opened with, and 'systemfonts' substitutes without saying so; [font_match()] reports which font was measured. To get real metrics for a font this machine lacks, install a replacement built to match it (Carlito for Calibri, Liberation Sans for Arial, Liberation Serif for Times New Roman), point 'systemfonts' at the actual file with 'systemfonts::register_font("Calibri", plain = "calibri.ttf")' before opening the device, or pass the numbers in 'metrics' yourself.

Value

The output file path, invisibly.

Examples

f <- easel_dev(width = 6, height = 4, fontname = "Georgia")
plot(1:10, (1:10)^2, type = "b")
dev.off()

Read a drawing back into R

Description

Parses a DrawingML drawing into a list of shapes that can be redrawn with [plot()]. The drawing can come from a file written by [easel_dev()], or from a workbook that contains one, including a workbook that has been saved again by a spreadsheet application. This closes the loop plot -> drawing -> workbook -> plot, without access to the original plot. The result is a redraw of the picture, not of the original plot: axes, legends and points come back as the paths and text runs they were written as.

Usage

easel_read(
  file,
  sheet = 1L,
  n = 1L,
  what = c("drawing", "comments"),
  all = FALSE
)

## S3 method for class 'easel_drawing'
plot(x, family = "", text_voff = 0.35, pad = 0, ...)

## S3 method for class 'easel_drawings'
print(x, ...)

## S3 method for class 'easel_drawings'
plot(x, ncol = NULL, ...)

Arguments

file

Path of an XML file written by [easel_dev()], of a drawing XML file, or of an '.xlsx', '.xlsm', '.xlsb' or '.xls' workbook, or an openxlsx2 'wbWorkbook' object. Binary workbooks ('.xlsb', '.xls') are converted with openxlsx2 (> 1.30) when it is installed. Without it, '.xlsb' files are read directly, but comments and slicers are not available and shapes without a size are sized from the sheet's records; '.xls' files cannot be read.

sheet

For a workbook, the sheet (name or position) holding the drawing.

n

Which drawing on the sheet to read, in the order of the drawing's anchors, or which comment, in the order of the cells.

what

'"drawing"' reads a drawing. '"comments"' reads a note or a threaded comment of a worksheet and returns it as the box a spreadsheet application shows for it.

all

If 'TRUE', reads every drawing of every sheet of a workbook (or every drawing of an XML file, or every note and threaded comment with 'what = "comments"') and returns them as one object that 'plot()' draws as a grid of panels, each labeled with its sheet and position. 'sheet' and 'n' are ignored.

x

An 'easel_drawing' object, or the list of them returned with 'all = TRUE'.

family

Font family used for text. The typefaces named in the file are not used, since a graphics device rarely knows them.

text_voff

Baseline offset in em below the center of a text box. The file does not record it, so it must match the 'text_voff' the drawing was written with (see [easel_dev()]).

pad

Extra margin in points around the drawing, so that thick strokes at the edge are not cut off. The default of 0 reproduces the drawing's frame exactly.

...

Ignored by the plot method of a single drawing; the plot method of several drawings passes them on to it.

ncol

Number of panels per row when plotting all drawings. The default is 3 or fewer.

Details

Understood are most of the common preset shapes (basic shapes, polygons, stars, block arrows, flowchart symbols, callouts, 'donut', 'frame', 'cube', 'can', 'heart', 'cloud', 'wave' and more; shadings and 3-D lighting are not reproduced and presets not in the list are drawn as their bounding box), shapes without a transform (sized from their anchor), theme and style colors, 'custGeom' paths made of 'moveTo', 'lnTo', 'cubicBezTo', 'quadBezTo' and 'close', solid, linear and radial gradient fills (R >= 4.1 for gradients, older versions use the first stop color), line properties, shape rotation and flips, nested groups, and text. Embedded PNG and JPEG pictures are drawn when the png or jpeg package is installed, including cropping, rotation and flips. Charts are drawn with the encharter package (with openxlsx2) when both are installed. Slicers of pivot tables are drawn with their items and which of them are selected; for any other object, such as a slicer of a table, the placeholder shape stored for older readers is drawn. Diagrams (SmartArt) are drawn from the shapes Office stores with them. Pictures that cannot be decoded, such as EMF or GIF, are drawn as an empty gray frame. Equations (Office Math in text) are drawn for fractions, scripts, roots, sums, integrals, brackets and accents. Arcs, underlines and strike-through are ignored.

Value

'easel_read()' returns an object of class 'easel_drawing', a list with the drawing 'width' and 'height' in points and a list of 'shapes'. With 'all = TRUE' it returns a named list of such objects of class 'easel_drawings'. 'plot()' returns 'x' invisibly.

Examples

f <- easel_dev(width = 4, height = 3)
plot(1:10, main = "round trip")
dev.off()

d <- easel_read(f)
plot(d)

Compute the device size for a spreadsheet cell region

Description

Translates a cell range like '"A1:G15"' into a device size in inches, so that a plot drawn at that size fills the region exactly when anchored to the same 'dims' — the dims-first workflow: pick the region, size the device from it, then plot. Because the device is opened at the final size, R lays out text, margins, and legends for that size; nothing needs to be rescaled afterwards (DrawingML cannot rescale text in groups).

Usage

easel_size(dims, wb = NULL, sheet = 1)

Arguments

dims

A cell range such as '"A1:G15"' (both corners inclusive).

wb

Optionally, an 'openxlsx2' workbook ('wbWorkbook') to read the actual column widths and row heights from.

sheet

Sheet index in 'wb'. Default '1'.

Details

Column widths and row heights are taken from 'wb' when given, including custom widths/heights and the sheet defaults stored in its ‘sheetFormatPr'. Without 'wb', Excel’s defaults are assumed (Calibri 11: 8.43 character column width = 64px, 15pt row height = 20px). The character-to-pixel conversion uses Excel's formula for a maximum digit width of 7px; workbooks with a different base font will be off by the ratio of their digit widths.

Value

Named numeric vector 'c(width = , height = )' in inches.

Examples

easel_size("A1:G15")

Render plotting code straight to a DrawingML string

Description

Evaluates 'code' on an in-memory easeling device and returns the resulting drawing as a single character string - no file is involved. The device is opened before and closed after 'code', also on error.

Usage

easel_xml(code, ...)

Arguments

code

Plotting code; a braced block for multiple statements. Remember that ggplot2/tmap/lattice objects only draw when printed, so wrap them in 'print()'.

...

Passed to [easel_dev()] (everything except 'file').

Value

The DrawingML as a length-one character vector, ready for 'openxlsx2::wb_add_drawing(xml = )'.

Examples

xml <- easel_xml(plot(1:10), width = 4, height = 3)

Which font is actually measured for a typeface

Description

The machine writing a drawing need not have the font the drawing will be opened with. 'systemfonts' substitutes without saying so, and the plot is then laid out with one font's widths while the drawing asks the spreadsheet application for another. Often that costs nothing, because some substitutes exist to match: Carlito for Calibri, Liberation Sans for Arial, Liberation Serif for Times New Roman. Others are unrelated fonts of quite different width. The device does not check, so call this when a drawing's layout looks wrong and you want to know which font produced it.

Usage

font_match(fontname)

Arguments

fontname

Typeface the drawing will name.

Value

A list with 'requested', 'matched' and 'substituted', or 'NULL' when 'systemfonts' is not installed.

Examples

font_match("Calibri")