diff --git a/.Rbuildignore b/.Rbuildignore index c62c308..d3a3426 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -10,3 +10,5 @@ ^vignettes/articles$ ^doc$ ^Meta$ +^tools$ +^\.claude$ diff --git a/DESCRIPTION b/DESCRIPTION index 43d6025..108b56c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Type: Package Package: pikchr Title: R Wrapper for 'pikchr' (PIC) Diagram Language -Version: 1.1.0 -Date: 2026-04-07 +Version: 1.1.1 +Date: 2026-06-14 Authors@R: c( person("Andre", "Leite", email = "leite@castlab.org", role = c("aut", "cre")), person("Hugo", "Vaconcelos", email = "hugo.vasconcelos@ufpe.br", role = "aut"), diff --git a/NEWS.md b/NEWS.md index 0b005f7..8dee498 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,16 @@ +# pikchr 1.1.1 + +## Bug fixes + +- Fixed a memory leak in the C interface (`pikchr_c`): the SVG buffer returned by `pikchr()` was never freed, leaking on every diagram render. +- Guarded the C interface against a possible `NULL` return from `pikchr()` that could crash the R session. +- Fixed the `height` argument default in `pikchr()`, which never fell back to the SVG `viewBox` height and produced an empty `height:` CSS rule. +- Aligned the documented argument defaults of `pikchr()` (`width`, `height`, `fontSize`, `fontFamily`, `align`) with the actual function defaults. + +## Other + +- Regenerated the package logo with a geometrically regular hexagon (the previous SVG was horizontally squished) and fixed an invalid `` element (now ``) in the logo source. + # pikchr 1.1.0 ## New features diff --git a/R/pikchrWrappers.R b/R/pikchrWrappers.R index 34659e2..8af0f24 100644 --- a/R/pikchrWrappers.R +++ b/R/pikchrWrappers.R @@ -4,12 +4,12 @@ #' Pikchr is a diagram language inspired by PIC, and this function allows you to easily embed Pikchr diagrams as SVG images in your HTML or Markdown documents. #' #' @param code A character string containing the diagram code written in the Pikchr language. -#' @param width A character string representing the width of the rendered SVG. It accepts standard HTML values such as percentages ("75\%"), pixels ("300px"), or "auto". Defaults to "75\%". -#' @param height A character string representing the height of the rendered SVG. It accepts standard HTML values such as pixels ("300px") or "auto". Defaults to "auto". -#' @param fontSize A character string specifying the font size of the text within the SVG. It accepts standard HTML values such as percentages ("80\%"), pixels, or keywords ("large"). Defaults to "80\%". -#' @param fontFamily A character string specifying the font family for the text in the diagram. Common values include "inherit" (to use the page's default font), "Arial", "Times New Roman", etc. Defaults to "inherit". +#' @param width A character string representing the width of the rendered SVG. It accepts standard HTML values such as percentages ("75\%"), pixels ("300px"), or "auto". If NULL (default), the intrinsic width from the SVG viewBox is used. +#' @param height A character string representing the height of the rendered SVG. It accepts standard HTML values such as pixels ("300px") or "auto". If NULL (default), the intrinsic height from the SVG viewBox is used. +#' @param fontSize A character string specifying the font size of the text within the SVG. It accepts standard HTML values such as percentages ("80\%"), pixels, or keywords ("large"). Defaults to "100\%". +#' @param fontFamily A character string specifying the font family for the text in the diagram. Common values include "inherit" (to use the page's default font), "Arial", "Times New Roman", etc. Defaults to "Jost". #' @param class A character string specifying a CSS class to apply to the SVG. This can be used to style the SVG with external CSS rules. Defaults to "pikchr". -#' @param align A character string specifying the alignment of the SVG within its container. Options are "center" (default), "left", or "right". If set to "none", no alignment is applied. +#' @param align A character string specifying the alignment of the SVG within its container. Options are "center", "left", or "right". Defaults to "none", in which case no alignment wrapper is applied. #' @param css A character string containing extra CSS styles to apply directly to the SVG element. This allows additional customization beyond width, height, and font settings. If NULL, no extra styles are applied. #' @param margin A character string specifying the margin around the SVG element, formatted like a CSS margin rule (e.g., "10px 0 10px 0"). Defaults to "10px 0 10px 0". #' @param svgOnly Logical, whether to return only the raw SVG code (TRUE) or open the diagram in the Viewer window (FALSE, default). @@ -61,7 +61,7 @@ pikchr <- function(code, bbox_width <- bbox[3] - bbox[1] if (is.null(width)) width <- bbox_width - if (is.null(width)) width <- bbox_height + if (is.null(height)) height <- bbox_height styles = paste0("style='width:", width, ";height:", height, diff --git a/cran-comments.md b/cran-comments.md index 6f0f3d1..b14cc32 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -1,3 +1,12 @@ +## Resubmission + +This is a patch release (1.1.1) fixing bugs found during an internal audit: + +- Fixed a memory leak in the C interface (the SVG buffer was never freed). +- Guarded the C interface against a possible NULL return that could crash R. +- Fixed the `height` argument default in `pikchr()`. +- Aligned documented argument defaults with the actual defaults. + ## R CMD check results 0 errors | 0 warnings | 0 notes diff --git a/man/figures/logo.png b/man/figures/logo.png index 31d63df..cfd8b43 100644 Binary files a/man/figures/logo.png and b/man/figures/logo.png differ diff --git a/man/figures/pikchrHex.svg b/man/figures/pikchrHex.svg index a319cbb..3f3dea5 100644 --- a/man/figures/pikchrHex.svg +++ b/man/figures/pikchrHex.svg @@ -1,31 +1,30 @@ - - -Pikchr - - - - - - - - - - - - - - - - - - - - - - - - - - + + +Pikchr + + + + + + + + + + + + + + + + + + + + + + + + + + - diff --git a/man/pikchr.Rd b/man/pikchr.Rd index d7c8a3f..b37865a 100644 --- a/man/pikchr.Rd +++ b/man/pikchr.Rd @@ -20,17 +20,17 @@ pikchr( \arguments{ \item{code}{A character string containing the diagram code written in the Pikchr language.} -\item{width}{A character string representing the width of the rendered SVG. It accepts standard HTML values such as percentages ("75\%"), pixels ("300px"), or "auto". Defaults to "75\%".} +\item{width}{A character string representing the width of the rendered SVG. It accepts standard HTML values such as percentages ("75\%"), pixels ("300px"), or "auto". If NULL (default), the intrinsic width from the SVG viewBox is used.} -\item{height}{A character string representing the height of the rendered SVG. It accepts standard HTML values such as pixels ("300px") or "auto". Defaults to "auto".} +\item{height}{A character string representing the height of the rendered SVG. It accepts standard HTML values such as pixels ("300px") or "auto". If NULL (default), the intrinsic height from the SVG viewBox is used.} -\item{fontSize}{A character string specifying the font size of the text within the SVG. It accepts standard HTML values such as percentages ("80\%"), pixels, or keywords ("large"). Defaults to "80\%".} +\item{fontSize}{A character string specifying the font size of the text within the SVG. It accepts standard HTML values such as percentages ("80\%"), pixels, or keywords ("large"). Defaults to "100\%".} -\item{fontFamily}{A character string specifying the font family for the text in the diagram. Common values include "inherit" (to use the page's default font), "Arial", "Times New Roman", etc. Defaults to "inherit".} +\item{fontFamily}{A character string specifying the font family for the text in the diagram. Common values include "inherit" (to use the page's default font), "Arial", "Times New Roman", etc. Defaults to "Jost".} \item{class}{A character string specifying a CSS class to apply to the SVG. This can be used to style the SVG with external CSS rules. Defaults to "pikchr".} -\item{align}{A character string specifying the alignment of the SVG within its container. Options are "center" (default), "left", or "right". If set to "none", no alignment is applied.} +\item{align}{A character string specifying the alignment of the SVG within its container. Options are "center", "left", or "right". Defaults to "none", in which case no alignment wrapper is applied.} \item{css}{A character string containing extra CSS styles to apply directly to the SVG element. This allows additional customization beyond width, height, and font settings. If NULL, no extra styles are applied.} diff --git a/src/pikchr.c b/src/pikchr.c index 65a64b7..00f74f4 100644 --- a/src/pikchr.c +++ b/src/pikchr.c @@ -8281,13 +8281,16 @@ return exitCode ? EXIT_FAILURE : EXIT_SUCCESS; SEXP pikchr_c (SEXP code, SEXP class) { - const char *pcode = STRING_VALUE(code); - const char *pclass = STRING_VALUE(class); - char *zOut; - zOut = pikchr(pcode, pclass, 0, NULL, NULL); - SEXP result; - PROTECT(result = NEW_CHARACTER(1)); - SET_STRING_ELT(result, 0, mkChar(zOut)); + const char *pcode = CHAR(STRING_ELT(code, 0)); + const char *pclass = CHAR(STRING_ELT(class, 0)); + char *zOut = pikchr(pcode, pclass, 0, NULL, NULL); + SEXP result = PROTECT(allocVector(STRSXP, 1)); + if( zOut ){ + SET_STRING_ELT(result, 0, mkChar(zOut)); + free(zOut); /* pikchr() returns malloc'd memory */ + } else { + SET_STRING_ELT(result, 0, NA_STRING); + } UNPROTECT(1); return(result); } diff --git a/tools/generate_logo.py b/tools/generate_logo.py new file mode 100644 index 0000000..c50b2d0 --- /dev/null +++ b/tools/generate_logo.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +"""Generate a geometrically regular hexagon logo for the pikchr R package. + +The previous man/figures/pikchrHex.svg had a horizontally squished hexagon +(width/height ratio ~0.73 instead of the regular 0.866) and used an invalid + element instead of . This script rebuilds the same visual design +(filled hexagon + two concentric hexagonal rings of circles connected by +trimmed edges + two lens arcs + centered "Pikchr" text) using exact regular +pointy-top hexagon geometry. +""" +import math + +S3 = math.sqrt(3) / 2.0 # cos(30) -> regular hexagon horizontal factor +R_OUT = 39.34155 # outer circumradius (keeps original height) +R1 = 31.4736 # outer ring (light green) circumradius +R2 = 23.605 # inner ring (medium green) circumradius +CR = 1.44 # small circle radius / edge trim length +PAD = 1.44 # padding around the figure + +# Center: top vertex sits PAD below the top edge; left vertex PAD from left edge. +CX = R_OUT * S3 + PAD +CY = R_OUT + PAD +VB_W = 2 * R_OUT * S3 + 2 * PAD +VB_H = 2 * R_OUT + 2 * PAD + + +def hexagon(R): + """Pointy-top regular hexagon vertices (y grows downward), clockwise.""" + return [ + (CX, CY - R), # top + (CX + R * S3, CY - R / 2), # upper-right + (CX + R * S3, CY + R / 2), # lower-right + (CX, CY + R), # bottom + (CX - R * S3, CY + R / 2), # lower-left + (CX - R * S3, CY - R / 2), # upper-left + ] + + +def trim(p, q, d): + """Point d units from p toward q.""" + vx, vy = q[0] - p[0], q[1] - p[1] + n = math.hypot(vx, vy) + return (p[0] + vx / n * d, p[1] + vy / n * d) + + +def f(v): + return f"{v:.4f}".rstrip("0").rstrip(".") + + +COL_FILL = "rgb(39,174,96)" +COL_FILL_STROKE = "rgb(25,111,61)" +COL_R1 = "rgb(125,206,160)" +COL_R2 = "rgb(82,190,128)" + +parts = [] +parts.append( + "" + "" +) + +# Filled outer hexagon +hx = hexagon(R_OUT) +d = "M" + "L".join(f"{f(x)},{f(y)}" for x, y in hx) + "Z" +parts.append( + f'' +) + +# Centered text +parts.append( + f'Pikchr' +) + + +def ring(R, color): + verts = hexagon(R) + out = [] + for vx, vy in verts: + out.append( + f'' + ) + for i in range(6): + a, b = verts[i], verts[(i + 1) % 6] + s = trim(a, b, CR) + e = trim(b, a, CR) + out.append( + f'' + ) + return out, verts + + +r1_parts, _ = ring(R1, COL_R1) +parts.extend(r1_parts) +r2_parts, r2v = ring(R2, COL_R2) +parts.extend(r2_parts) + +# Two lens arcs on the inner ring (bulging beyond top/bottom vertices) +BULGE = 2.31 +c_bottom = (CX, CY + R2 + BULGE) +c_top = (CX, CY - R2 - BULGE) +lr, ll = r2v[2], r2v[4] # lower-right, lower-left +ur, ul = r2v[1], r2v[5] # upper-right, upper-left +for (p, q, c) in ((lr, ll, c_bottom), (ur, ul, c_top)): + s = trim(p, c, CR) + e = trim(q, c, CR) + parts.append( + f'' + ) + +parts.append("") + +with open("man/figures/pikchrHex.svg", "w") as fh: + fh.write("\n".join(parts) + "\n") + +print(f"viewBox 0 0 {f(VB_W)} {f(VB_H)} ratio={VB_W/VB_H:.4f}") +print(f"hex width/height ratio = {(2*R_OUT*S3)/(2*R_OUT):.4f} (regular = 0.8660)")