# --- latex_grob creation and structure --- test_that("latex_grob creates valid grob and returns correct dimensions", { g <- latex_grob("\\frac{x^{2}+1}{\\sqrt{y}}") expect_s3_class(g, "latexgrob") expect_true(nrow(g$layout_df) > 0) expect_true(g$bbox_w > 0) # render_mode stored correctly g_path <- latex_grob("x^2", render_mode = "path") g_type <- latex_grob("x^2", render_mode = "typeface") expect_equal(g_path$render_mode, "path") expect_null(g_path$path_layout_df) expect_s3_class(g_type$path_layout_df, "data.frame") # latex_dims with fontsize scaling dims <- latex_dims("\\frac{a}{b}", render_mode = "path") expect_true(grid::convertWidth(dims$width, "points", valueOnly = TRUE) > 0) w_small <- grid::convertWidth(latex_dims("x^2", gp = grid::gpar(fontsize = 10))$width, "bigpts", valueOnly = TRUE) w_large <- grid::convertWidth(latex_dims("x^2", gp = grid::gpar(fontsize = 40))$width, "bigpts", valueOnly = TRUE) expect_true(w_large > w_small) }) # --- latex_grob parameters --- test_that("latex_grob parameters work correctly", { # Rotation expect_equal(latex_grob("x^2", rot = 45)$vp$angle, 45) # max_width g_mw <- latex_grob("x^2 + y^2 = z^2", max_width = 50, render_mode = "path") expect_true(grid::convertWidth(grid::grobWidth(g_mw), "bigpts", valueOnly = TRUE) > 0) # makeContent builds children g_mc <- grid::makeContent(latex_grob("\\frac{a}{b}", render_mode = "path")) expect_true(length(g_mc$children) > 0) # width/height details positive g <- latex_grob("\\frac{a}{b}", render_mode = "path") expect_true(grid::convertWidth(grid::widthDetails(g), "bigpts", valueOnly = TRUE) > 0) expect_true(grid::convertHeight(grid::heightDetails(g), "bigpts", valueOnly = TRUE) > 0) }) # --- device support and typeface rendering --- test_that("device support detection and typeface fallback work", { # pdf reports glyphs=TRUE tf_pdf <- tempfile(fileext = ".pdf") grDevices::pdf(tf_pdf) expect_true(gridmicrotex:::.device_supports_typeface_glyphs()) # PDF: renders without fallback warning expect_no_warning({ g <- latex_grob("\\frac{a}{b}", render_mode = "typeface", gp = grid::gpar(fontsize = 20)) grid::grid.newpage() grid::grid.draw(g) }) grDevices::dev.off() unlink(tf_pdf) # Postscript: falls back with warning tf_ps <- tempfile(fileext = ".ps") grDevices::postscript(tf_ps) on.exit({ grDevices::dev.off(); unlink(tf_ps) }, add = TRUE) expect_false(gridmicrotex:::.device_supports_typeface_glyphs()) expect_warning( expect_no_error({ g <- latex_grob("\\frac{a}{b}", render_mode = "typeface", gp = grid::gpar(fontsize = 20)) grid::grid.newpage() grid::grid.draw(g) }), "falling back to path mode" ) }) # --- edge cases: empty and invalid input --- test_that("latex_grob handles empty input", { expect_error(latex_grob("")) }) test_that("latex_grob handles invalid LaTeX commands gracefully", { # MicroTeX silently ignores unknown commands; verify it doesn't crash g <- latex_grob("\\notavalidcommand{x}") expect_s3_class(g, "latexgrob") }) # --- editGrob: re-parse on parse-affecting fields --- test_that("editGrob re-parses when tex changes", { g <- latex_grob("x", render_mode = "path") g2 <- grid::editGrob(g, tex = "x^{2} + y^{2} + z^{2}") expect_equal(g2$tex, "x^{2} + y^{2} + z^{2}") expect_true(g2$bbox_w > g$bbox_w) expect_true(nrow(g2$layout_df) > nrow(g$layout_df)) # viewport width/height tracked bbox expect_equal( as.numeric(g2$vp$width), as.numeric(grid::unit(g2$bbox_w, "bigpts")) ) }) test_that("editGrob re-parses when gp (fontsize) changes", { g20 <- latex_grob("x^2", render_mode = "path", gp = grid::gpar(fontsize = 20)) g40 <- grid::editGrob(g20, gp = grid::gpar(fontsize = 40)) expect_equal(g40$fontsize, 40) # 2x font -> ~2x bbox expect_equal(g40$bbox_w / g20$bbox_w, 2, tolerance = 0.05) # gp is re-stripped after parse (fontsize not carried on the gTree) expect_null(g40$gp$fontsize) }) test_that("editGrob re-parses when math_font / tex_style / render_mode change", { g <- latex_grob("\\frac{a}{b}", render_mode = "path", math_font = "lete") g2 <- grid::editGrob(g, math_font = "stix") expect_false(identical(g$layout_df, g2$layout_df)) g3 <- latex_grob("\\sum_{i=1}^{n} i", render_mode = "path", tex_style = "text") g4 <- grid::editGrob(g3, tex_style = "display") expect_true(g4$bbox_h > g3$bbox_h) # display makes \sum taller g5 <- latex_grob("x^2", render_mode = "path") g6 <- grid::editGrob(g5, render_mode = "typeface") expect_equal(g6$render_mode, "typeface") expect_s3_class(g6$path_layout_df, "data.frame") # fallback layout generated }) test_that("editGrob on non-parse fields does not re-parse", { g <- latex_grob("\\frac{a}{b}", render_mode = "path") orig_layout <- g$layout_df g2 <- grid::editGrob(g, debug = TRUE) expect_true(isTRUE(g2$debug)) expect_identical(g2$layout_df, orig_layout) }) test_that("ascentDetails + descentDetails sum to heightDetails", { g <- latex_grob("\\frac{a}{b}", render_mode = "path", gp = grid::gpar(fontsize = 24)) asc <- grid::convertHeight(grid::ascentDetails(g), "bigpts", valueOnly = TRUE) desc <- grid::convertHeight(grid::descentDetails(g), "bigpts", valueOnly = TRUE) h <- grid::convertHeight(grid::heightDetails(g), "bigpts", valueOnly = TRUE) expect_equal(asc + desc, h, tolerance = 1e-6) expect_true(asc > 0) expect_true(desc >= 0) # Descent matches the bbox_d field exposed for grob-to-grob alignment expect_equal(desc, g$bbox_d, tolerance = 1e-6) }) test_that("editGrob keeps viewport just in sync with hjust/vjust", { g <- latex_grob("x", render_mode = "path", hjust = 0.5, vjust = 0.5) g2 <- grid::editGrob(g, hjust = 0, vjust = 1) expect_equal(g2$hjust, 0) expect_equal(g2$vjust, 1) expect_equal(as.numeric(g2$vp$valid.just), c(0, 1)) }) test_that("latex_dims respects math_font parameter", { expr <- "\\int_0^1 f(x)\\,dx + x + y" dims_lete <- latex_dims(expr, math_font = "lete", gp = grid::gpar(fontsize = 20)) dims_stix <- latex_dims(expr, math_font = "stix", gp = grid::gpar(fontsize = 20)) w_lete <- grid::convertWidth(dims_lete$width, "bigpts", valueOnly = TRUE) w_stix <- grid::convertWidth(dims_stix$width, "bigpts", valueOnly = TRUE) expect_true(w_lete > 0) expect_true(w_stix > 0) expect_false(w_lete == w_stix) })