| devtools::load_all()
|
|
|
|
|
| puzzle_structure <- generate_puzzle_core(
|
| seed = 9999,
|
| grid = c(6, 4),
|
| size = c(1200, 600),
|
| unit = "mm",
|
| dpi = 300,
|
| tabsize = 15,
|
| jitter = 2
|
| )
|
|
|
|
|
| puzzle_complete <- generate_jigsaw_svg(
|
| seed = 9999,
|
| tabsize = 15,
|
| jitter = 2,
|
| width = 1200,
|
| height = 600,
|
| unit = "mm",
|
| dpi = 300,
|
| radius = 5.0,
|
| xn = 4,
|
| yn = 6
|
| )
|
|
|
|
|
| dir.create("output", showWarnings = FALSE)
|
|
|
|
|
| save_jigsaw_svg(puzzle_complete, "output/custom_jigsaw_complete.svg")
|
|
|
|
|
| kerf_offset <- 100
|
|
|
| separated_puzzle <- generate_separated_puzzle_svg(
|
| puzzle_structure = puzzle_structure,
|
| offset = kerf_offset,
|
| colors = "black",
|
| stroke_width = 1.5,
|
| background = "none"
|
| )
|
|
|
|
|
| writeLines(separated_puzzle, "output/custom_jigsaw_separated.svg")
|
|
|
|
|
| original_area <- puzzle_structure$size[1] * puzzle_structure$size[2]
|
| separated_width <- puzzle_structure$size[1] + (puzzle_structure$grid[2] - 1) * kerf_offset
|
| separated_height <- puzzle_structure$size[2] + (puzzle_structure$grid[1] - 1) * kerf_offset
|
| separated_area <- separated_width * separated_height
|
|
|
|
|
| cat(sprintf(
|
| "Puzzle: %dx%d grid (%d pieces), %.0fx%.0f%s, %d DPI, offset=%d%s\n",
|
| puzzle_structure$grid[2], puzzle_structure$grid[1],
|
| puzzle_structure$grid[1] * puzzle_structure$grid[2],
|
| puzzle_structure$size[1], puzzle_structure$size[2], puzzle_structure$unit,
|
| puzzle_structure$dpi, kerf_offset,
|
| if (puzzle_structure$unit == "px") "px" else "mm"
|
| ))
|
| cat(sprintf(
|
| "Layout: %.0fx%.0f%s → %.0fx%.0f%s (%.1f%% increase)\n",
|
| puzzle_structure$size[1], puzzle_structure$size[2], puzzle_structure$unit,
|
| separated_width, separated_height, puzzle_structure$unit,
|
| (separated_area - original_area) / original_area * 100
|
| ))
|
|
|
|
|
| generate_text_overlay_svg <- function(puzzle_structure,
|
| label_text = NULL,
|
| label_size = 20,
|
| label_color = "black",
|
| label_font = "Times New Roman",
|
| add_label_background = FALSE,
|
| label_bg_opacity = 0.8,
|
| label_bg_color = "white") {
|
| xn <- puzzle_structure$grid[2]
|
| yn <- puzzle_structure$grid[1]
|
|
|
|
|
| puzzle_width <- puzzle_structure$size_mm[1]
|
| puzzle_height <- puzzle_structure$size_mm[2]
|
|
|
|
|
| svg_content <- sprintf('<?xml version="1.0" encoding="UTF-8"?>
|
| <svg xmlns="http://www.w3.org/2000/svg" version="1.1"
|
| width="%.0f" height="%.0f" viewBox="0 0 %.0f %.0f">
|
| ', puzzle_width, puzzle_height, puzzle_width, puzzle_height)
|
|
|
|
|
| for (yi in 0:(yn - 1)) {
|
| for (xi in 0:(xn - 1)) {
|
|
|
| center_x <- xi * puzzle_structure$piece_width + puzzle_structure$piece_width / 2
|
| center_y <- yi * puzzle_structure$piece_height + puzzle_structure$piece_height / 2
|
|
|
|
|
| current_label <- if (is.matrix(label_text)) {
|
| label_text[yi + 1, xi + 1]
|
| } else if (is.vector(label_text) && length(label_text) > 1) {
|
| piece_index <- yi * xn + xi + 1
|
| if (piece_index <= length(label_text)) label_text[piece_index] else sprintf("%d-%d", xi, yi)
|
| } else if (!is.null(label_text)) {
|
| as.character(label_text)
|
| } else {
|
| sprintf("%d-%d", xi, yi)
|
| }
|
|
|
|
|
| if (add_label_background) {
|
|
|
| text_width <- nchar(current_label) * label_size * 0.6
|
| text_height <- label_size * 1.2
|
| bg_x <- center_x - text_width / 2
|
| bg_y <- center_y - text_height / 2
|
|
|
| svg_content <- paste0(svg_content, sprintf(
|
| ' <rect x="%.2f" y="%.2f" width="%.2f" height="%.2f" fill="%s" fill-opacity="%.2f" stroke="none"/>\n',
|
| bg_x, bg_y, text_width, text_height, label_bg_color, label_bg_opacity
|
| ))
|
| }
|
|
|
|
|
| svg_content <- paste0(svg_content, sprintf(
|
| ' <text x="%.2f" y="%.2f" font-family="%s" font-size="%d" fill="%s" text-anchor="middle" dominant-baseline="central">%s</text>\n',
|
| center_x, center_y, label_font, as.integer(label_size), label_color, current_label
|
| ))
|
| }
|
| }
|
|
|
|
|
| svg_content <- paste0(svg_content, "</svg>")
|
|
|
| return(svg_content)
|
| }
|
|
|
|
|
| save_individual_piece_svgs <- function(puzzle_structure,
|
| output_dir = "individual_pieces",
|
| stroke_width = 1.5,
|
| stroke_color = "black",
|
| fill_color = "transparent",
|
| background_color = "transparent",
|
| use_viridis = FALSE,
|
| add_labels = FALSE,
|
| label_text = NULL,
|
| label_size = 20,
|
| label_color = "black",
|
| label_font = "Times New Roman",
|
| add_label_background = FALSE,
|
| label_bg_opacity = 0.8,
|
| text_overlay_file = NULL) {
|
|
|
|
|
| dir.create(output_dir, showWarnings = FALSE, recursive = TRUE)
|
|
|
| xn <- puzzle_structure$grid[2]
|
| yn <- puzzle_structure$grid[1]
|
|
|
|
|
| clean_hex_color <- function(color) {
|
| if (is.character(color) && nchar(color) == 9 && substr(color, 1, 1) == "#") {
|
|
|
| return(substr(color, 1, 7))
|
| }
|
| return(color)
|
| }
|
|
|
|
|
| if (use_viridis && is.character(fill_color) && length(fill_color) == 1 &&
|
| (fill_color == "transparent" || fill_color == "none")) {
|
| if (!requireNamespace("viridis", quietly = TRUE)) {
|
| stop("viridis package is required for use_viridis = TRUE")
|
| }
|
| n_pieces <- xn * yn
|
| viridis_colors <- viridis::viridis(n_pieces)
|
|
|
| viridis_colors <- sapply(viridis_colors, clean_hex_color)
|
| fill_color <- matrix(viridis_colors, nrow = yn, ncol = xn, byrow = TRUE)
|
| }
|
|
|
|
|
| if (is.matrix(stroke_color)) {
|
| if (!all(dim(stroke_color) == c(yn, xn))) {
|
| stop(sprintf(
|
| "stroke_color matrix dimensions (%dx%d) must match grid (%dx%d)",
|
| nrow(stroke_color), ncol(stroke_color), yn, xn
|
| ))
|
| }
|
| }
|
| if (is.matrix(fill_color)) {
|
| if (!all(dim(fill_color) == c(yn, xn))) {
|
| stop(sprintf(
|
| "fill_color matrix dimensions (%dx%d) must match grid (%dx%d)",
|
| nrow(fill_color), ncol(fill_color), yn, xn
|
| ))
|
| }
|
| }
|
| if (is.matrix(background_color)) {
|
| if (!all(dim(background_color) == c(yn, xn))) {
|
| stop(sprintf(
|
| "background_color matrix dimensions (%dx%d) must match grid (%dx%d)",
|
| nrow(background_color), ncol(background_color), yn, xn
|
| ))
|
| }
|
| }
|
| if (is.matrix(label_text)) {
|
| if (!all(dim(label_text) == c(yn, xn))) {
|
| stop(sprintf(
|
| "label_text matrix dimensions (%dx%d) must match grid (%dx%d)",
|
| nrow(label_text), ncol(label_text), yn, xn
|
| ))
|
| }
|
| }
|
|
|
|
|
| for (yi in 0:(yn - 1)) {
|
| for (xi in 0:(xn - 1)) {
|
|
|
| piece_path <- generate_single_piece(xi, yi, puzzle_structure)
|
|
|
|
|
| current_stroke <- if (is.matrix(stroke_color)) stroke_color[yi + 1, xi + 1] else stroke_color
|
| current_fill <- if (is.matrix(fill_color)) fill_color[yi + 1, xi + 1] else fill_color
|
| current_bg <- if (is.matrix(background_color)) background_color[yi + 1, xi + 1] else background_color
|
|
|
|
|
| current_stroke <- clean_hex_color(current_stroke)
|
| current_fill <- clean_hex_color(current_fill)
|
| current_bg <- clean_hex_color(current_bg)
|
|
|
|
|
| piece_width <- puzzle_structure$piece_width * 1.5
|
| piece_height <- puzzle_structure$piece_height * 1.5
|
| x_offset <- xi * puzzle_structure$piece_width - puzzle_structure$piece_width * 0.25
|
| y_offset <- yi * puzzle_structure$piece_height - puzzle_structure$piece_height * 0.25
|
|
|
|
|
| background_rect <- ""
|
| if (current_bg != "transparent" && current_bg != "none" && current_bg != "") {
|
| background_rect <- sprintf(
|
| ' <rect x="%.2f" y="%.2f" width="%.0f" height="%.0f" fill="%s"/>\n',
|
| x_offset, y_offset, piece_width, piece_height, current_bg
|
| )
|
| }
|
|
|
|
|
| label_element <- ""
|
| if (add_labels) {
|
|
|
| center_x <- xi * puzzle_structure$piece_width + puzzle_structure$piece_width / 2
|
| center_y <- yi * puzzle_structure$piece_height + puzzle_structure$piece_height / 2
|
|
|
|
|
| current_label <- if (is.matrix(label_text)) {
|
| label_text[yi + 1, xi + 1]
|
| } else if (is.vector(label_text) && length(label_text) > 1) {
|
| piece_index <- yi * xn + xi + 1
|
| if (piece_index <= length(label_text)) label_text[piece_index] else sprintf("%d-%d", xi, yi)
|
| } else if (!is.null(label_text)) {
|
| as.character(label_text)
|
| } else {
|
| sprintf("%d-%d", xi, yi)
|
| }
|
|
|
|
|
| label_bg <- ""
|
| if (add_label_background) {
|
|
|
| text_width <- nchar(current_label) * label_size * 0.6
|
| text_height <- label_size * 1.2
|
| bg_x <- center_x - text_width / 2
|
| bg_y <- center_y - text_height / 2
|
|
|
| label_bg <- sprintf(
|
| ' <rect x="%.2f" y="%.2f" width="%.2f" height="%.2f" fill="white" fill-opacity="%.2f" stroke="none"/>\n',
|
| bg_x, bg_y, text_width, text_height, label_bg_opacity
|
| )
|
| }
|
|
|
|
|
| label_element <- sprintf(
|
| '%s <text x="%.2f" y="%.2f" font-family="%s" font-size="%d" fill="%s" text-anchor="middle" dominant-baseline="central">%s</text>\n',
|
| label_bg, center_x, center_y, label_font, as.integer(label_size), label_color, current_label
|
| )
|
| }
|
|
|
|
|
| svg_content <- sprintf(
|
| '<?xml version="1.0" encoding="UTF-8"?>
|
| <svg xmlns="http://www.w3.org/2000/svg" version="1.1"
|
| width="%.0f" height="%.0f" viewBox="%.2f %.2f %.0f %.0f">
|
| %s <path d="%s" fill="%s" stroke="%s" stroke-width="%.1f" stroke-linecap="round" stroke-linejoin="round"/>
|
| %s</svg>',
|
| piece_width, piece_height,
|
| x_offset, y_offset, piece_width, piece_height,
|
| background_rect,
|
| piece_path, current_fill, current_stroke, stroke_width,
|
| label_element
|
| )
|
|
|
|
|
| filename <- sprintf("%s/piece_%02d_%02d.svg", output_dir, xi, yi)
|
| writeLines(svg_content, filename)
|
| }
|
| }
|
|
|
| cat(sprintf("\nSaved %d individual piece files to %s/\n", xn * yn, output_dir))
|
|
|
|
|
| if (!is.null(text_overlay_file)) {
|
| overlay_content <- generate_text_overlay_svg(
|
| puzzle_structure = puzzle_structure,
|
| label_text = label_text,
|
| label_size = label_size,
|
| label_color = label_color,
|
| label_font = label_font,
|
| add_label_background = add_label_background,
|
| label_bg_opacity = label_bg_opacity
|
| )
|
|
|
|
|
| if (!grepl("^output/", text_overlay_file)) {
|
| text_overlay_file <- file.path("output", text_overlay_file)
|
| }
|
|
|
| writeLines(overlay_content, text_overlay_file)
|
| cat(sprintf("Saved text overlay for PowerPoint editing: %s\n", text_overlay_file))
|
| }
|
| }
|
|
|
|
|
| save_individual_piece_svgs(puzzle_structure,
|
| output_dir = "output/individual_pieces",
|
| stroke_width = 1.5,
|
| stroke_color = "black"
|
| )
|
|
|
|
|
| save_individual_piece_svgs(puzzle_structure,
|
| output_dir = "output/individual_pieces_colored",
|
| stroke_width = 2.0,
|
| stroke_color = "darkblue",
|
| fill_color = "#E3F2FD",
|
| background_color = "white"
|
| )
|
|
|
|
|
| save_individual_piece_svgs(puzzle_structure,
|
| output_dir = "output/individual_pieces_viridis",
|
| stroke_width = 1.5,
|
| stroke_color = "white",
|
| use_viridis = TRUE
|
| )
|
|
|
|
|
| if (requireNamespace("viridis", quietly = TRUE)) {
|
|
|
| n_pieces <- puzzle_structure$grid[1] * puzzle_structure$grid[2]
|
| custom_colors <- viridis::viridis(n_pieces, option = "plasma")
|
|
|
| custom_colors <- sapply(custom_colors, function(color) {
|
| if (is.character(color) && nchar(color) == 9 && substr(color, 1, 1) == "#") {
|
| return(substr(color, 1, 7))
|
| }
|
| return(color)
|
| })
|
| color_matrix <- matrix(custom_colors,
|
| nrow = puzzle_structure$grid[1],
|
| ncol = puzzle_structure$grid[2],
|
| byrow = TRUE
|
| )
|
|
|
| save_individual_piece_svgs(puzzle_structure,
|
| output_dir = "output/individual_pieces_plasma",
|
| stroke_width = 2.0,
|
| stroke_color = "white",
|
| fill_color = color_matrix
|
| )
|
| }
|
|
|
|
|
| save_individual_piece_svgs(puzzle_structure,
|
| output_dir = "output/individual_pieces_labeled",
|
| stroke_width = 2.0,
|
| stroke_color = "darkblue",
|
| fill_color = "#F0F8FF",
|
| add_labels = TRUE,
|
| label_size = 24,
|
| label_color = "darkblue"
|
| )
|
|
|
|
|
| if (requireNamespace("viridis", quietly = TRUE)) {
|
|
|
| label_matrix <- matrix(paste("P", 1:(puzzle_structure$grid[1] * puzzle_structure$grid[2])),
|
| nrow = puzzle_structure$grid[1],
|
| ncol = puzzle_structure$grid[2],
|
| byrow = TRUE
|
| )
|
|
|
| save_individual_piece_svgs(puzzle_structure,
|
| output_dir = "output/individual_pieces_numbered",
|
| stroke_width = 1.5,
|
| stroke_color = "white",
|
| use_viridis = TRUE,
|
| add_labels = TRUE,
|
| label_text = label_matrix,
|
| label_size = 20,
|
| label_color = "white",
|
| add_label_background = TRUE,
|
| label_bg_opacity = 0.7
|
| )
|
| }
|
|
|
|
|
| save_individual_piece_svgs(puzzle_structure,
|
| output_dir = "output/powerpoint_pieces",
|
| stroke_width = 1.5,
|
| stroke_color = "black",
|
| fill_color = "transparent",
|
| add_labels = FALSE,
|
| text_overlay_file = "powerpoint_text_overlay.svg"
|
| )
|
|
|
|
|
| if (requireNamespace("viridis", quietly = TRUE)) {
|
|
|
| custom_labels <- matrix(paste("Piece", 1:(puzzle_structure$grid[1] * puzzle_structure$grid[2])),
|
| nrow = puzzle_structure$grid[1],
|
| ncol = puzzle_structure$grid[2],
|
| byrow = TRUE
|
| )
|
|
|
| save_individual_piece_svgs(puzzle_structure,
|
| output_dir = "output/powerpoint_pieces_colored",
|
| stroke_width = 1.5,
|
| stroke_color = "white",
|
| use_viridis = TRUE,
|
| add_labels = FALSE,
|
| text_overlay_file = "powerpoint_custom_text_overlay.svg",
|
| label_text = custom_labels,
|
| label_size = 18,
|
| label_color = "darkblue",
|
| add_label_background = TRUE,
|
| label_bg_opacity = 0.9
|
| )
|
| }
|
|
|
| cat("All files saved to output/ directory\n")
|
| cat("\n=== PowerPoint Workflow Instructions ===\n")
|
| cat("1. Import puzzle pieces (e.g., output/powerpoint_pieces/piece_*.svg)\n")
|
| cat("2. Import text overlay (e.g., output/powerpoint_text_overlay.svg)\n")
|
| cat("3. Align the text overlay exactly over the puzzle pieces\n")
|
| cat("4. Right-click text overlay → 'Convert to Shape' → Ungroup\n")
|
| cat("5. Individual text boxes are now editable in PowerPoint!\n")
|
| cat("6. Tip: Lock puzzle pieces layer to prevent accidental movement\n")
|
|
|