///|
/// Color palette for SVG rectangle rendering.
let svg_palette : Array[String] = [
"#4F46E5", "#06B6D4", "#10B981", "#F59E0B", "#EF4444", "#8B5CF6", "#EC4899", "#3B82F6",
"#14B8A6", "#84CC16", "#F97316", "#6366F1", "#0EA5E9", "#D946EF",
]
///|
/// Exports a packed rectangle layout to a self-contained, responsive SVG image.
pub fn to_svg(
bin_width : Int32,
bin_height : Int32,
rects : Array[Rect],
title~ : String = "moon_binpack Texture Atlas",
) -> String {
let mut total_used_area = 0
for r in rects {
total_used_area = total_used_area + r.area()
}
let total_area = bin_width * bin_height
let occupancy_pct = if total_area > 0 {
(total_used_area.to_double() * 1000.0 / total_area.to_double())
.to_int()
.to_double() /
10.0
} else {
0.0
}
let buf = StringBuilder()
buf.write_string("\n")
buf.to_string()
}
///|
/// Exports packed rectangles into a standard JSON texture atlas manifest format.
pub fn to_json(
bin_width : Int32,
bin_height : Int32,
rects : Array[Rect],
) -> String {
let buf = StringBuilder()
buf.write_string("{\n")
buf.write_string(" \"meta\": {\n")
buf.write_string(" \"width\": ")
buf.write_string(bin_width.to_string())
buf.write_string(",\n")
buf.write_string(" \"height\": ")
buf.write_string(bin_height.to_string())
buf.write_string(",\n")
buf.write_string(" \"count\": ")
buf.write_string(rects.length().to_string())
buf.write_string("\n },\n")
buf.write_string(" \"frames\": [\n")
for i = 0; i < rects.length(); i = i + 1 {
let r = rects[i]
buf.write_string(" {\n")
buf.write_string(" \"id\": ")
buf.write_string(r.id.to_string())
buf.write_string(",\n")
buf.write_string(" \"x\": ")
buf.write_string(r.x.to_string())
buf.write_string(",\n")
buf.write_string(" \"y\": ")
buf.write_string(r.y.to_string())
buf.write_string(",\n")
buf.write_string(" \"width\": ")
buf.write_string(r.width.to_string())
buf.write_string(",\n")
buf.write_string(" \"height\": ")
buf.write_string(r.height.to_string())
buf.write_string(",\n")
buf.write_string(" \"rotated\": ")
buf.write_string(if r.rotated { "true" } else { "false" })
buf.write_string(",\n")
buf.write_string(" \"bin\": ")
buf.write_string(r.bin_index.to_string())
buf.write_string("\n }")
if i + 1 < rects.length() {
buf.write_string(",")
}
buf.write_string("\n")
}
buf.write_string(" ]\n")
buf.write_string("}\n")
buf.to_string()
}
///|
/// Exports packed rectangles into standard CSS Sprite rules.
/// Generates a common base class and individual utility classes with width, height,
/// and negative background-position offsets.
pub fn to_css(
rects : Array[Rect],
class_prefix~ : String = "sprite",
image_url~ : String = "atlas.png",
) -> String {
let buf = StringBuilder()
buf.write_string("/* Generated by moon_binpack CSS Sprite Exporter */\n")
buf.write_string(".")
buf.write_string(class_prefix)
buf.write_string(" {\n")
buf.write_string(" display: inline-block;\n")
buf.write_string(" background-image: url('")
buf.write_string(image_url)
buf.write_string("');\n")
buf.write_string(" background-repeat: no-repeat;\n")
buf.write_string("}\n\n")
for i = 0; i < rects.length(); i = i + 1 {
let r = rects[i]
buf.write_string(".")
buf.write_string(class_prefix)
buf.write_string("-")
buf.write_string(r.id.to_string())
buf.write_string(" {\n")
buf.write_string(" width: ")
buf.write_string(r.width.to_string())
buf.write_string("px;\n")
buf.write_string(" height: ")
buf.write_string(r.height.to_string())
buf.write_string("px;\n")
buf.write_string(" background-position: -")
buf.write_string(r.x.to_string())
buf.write_string("px -")
buf.write_string(r.y.to_string())
buf.write_string("px;\n")
buf.write_string("}\n")
if i + 1 < rects.length() {
buf.write_string("\n")
}
}
buf.to_string()
}
///|
/// Exports packed rectangles into standard CSV format for CAD, CNC, and spreadsheet analysis.
pub fn to_csv(rects : Array[Rect]) -> String {
let buf = StringBuilder()
buf.write_string("id,x,y,width,height,rotated,bin,area\n")
for r in rects {
buf.write_string(r.id.to_string())
buf.write_string(",")
buf.write_string(r.x.to_string())
buf.write_string(",")
buf.write_string(r.y.to_string())
buf.write_string(",")
buf.write_string(r.width.to_string())
buf.write_string(",")
buf.write_string(r.height.to_string())
buf.write_string(",")
buf.write_string(if r.rotated { "true" } else { "false" })
buf.write_string(",")
buf.write_string(r.bin_index.to_string())
buf.write_string(",")
buf.write_string(r.area().to_string())
buf.write_string("\n")
}
buf.to_string()
}
///|
/// Exports packed rectangles into TexturePacker / Pixi.js / Phaser JSON Hash format.
pub fn to_pixi_json(
bin_width : Int32,
bin_height : Int32,
rects : Array[Rect],
image_name~ : String = "atlas.png",
) -> String {
let buf = StringBuilder()
buf.write_string("{\n")
buf.write_string(" \"frames\": {\n")
for i = 0; i < rects.length(); i = i + 1 {
let r = rects[i]
let orig_w = if r.rotated { r.height } else { r.width }
let orig_h = if r.rotated { r.width } else { r.height }
buf.write_string(" \"frame_")
buf.write_string(r.id.to_string())
buf.write_string("\": {\n")
buf.write_string(" \"frame\": {\"x\": ")
buf.write_string(r.x.to_string())
buf.write_string(", \"y\": ")
buf.write_string(r.y.to_string())
buf.write_string(", \"w\": ")
buf.write_string(r.width.to_string())
buf.write_string(", \"h\": ")
buf.write_string(r.height.to_string())
buf.write_string("},\n")
buf.write_string(" \"rotated\": ")
buf.write_string(if r.rotated { "true" } else { "false" })
buf.write_string(",\n")
buf.write_string(" \"trimmed\": false,\n")
buf.write_string(" \"spriteSourceSize\": {\"x\": 0, \"y\": 0, \"w\": ")
buf.write_string(orig_w.to_string())
buf.write_string(", \"h\": ")
buf.write_string(orig_h.to_string())
buf.write_string("},\n")
buf.write_string(" \"sourceSize\": {\"w\": ")
buf.write_string(orig_w.to_string())
buf.write_string(", \"h\": ")
buf.write_string(orig_h.to_string())
buf.write_string("}\n")
buf.write_string(" }")
if i + 1 < rects.length() {
buf.write_string(",")
}
buf.write_string("\n")
}
buf.write_string(" },\n")
buf.write_string(" \"meta\": {\n")
buf.write_string(" \"app\": \"moon_binpack\",\n")
buf.write_string(" \"version\": \"0.1.0\",\n")
buf.write_string(" \"image\": \"")
buf.write_string(image_name)
buf.write_string("\",\n")
buf.write_string(" \"format\": \"RGBA8888\",\n")
buf.write_string(" \"size\": {\"w\": ")
buf.write_string(bin_width.to_string())
buf.write_string(", \"h\": ")
buf.write_string(bin_height.to_string())
buf.write_string("},\n")
buf.write_string(" \"scale\": \"1\"\n")
buf.write_string(" }\n")
buf.write_string("}\n")
buf.to_string()
}
///|
/// Exports packed rectangles into a standalone, interactive HTML visual inspection report.
pub fn to_html(
bin_width : Int32,
bin_height : Int32,
rects : Array[Rect],
title~ : String = "moon_binpack Texture Atlas Report",
) -> String {
let mut total_used_area = 0
for r in rects {
total_used_area = total_used_area + r.area()
}
let total_area = bin_width * bin_height
let occupancy_pct = if total_area > 0 {
(total_used_area.to_double() * 1000.0 / total_area.to_double())
.to_int()
.to_double() /
10.0
} else {
0.0
}
let svg_content = to_svg(bin_width, bin_height, rects, title~)
let buf = StringBuilder()
buf.write_string("\n")
buf.write_string("\n\n")
buf.write_string(" \n")
buf.write_string(
" \n",
)
buf.write_string(" ")
buf.write_string(title)
buf.write_string(" \n")
buf.write_string(" \n\n\n")
buf.write_string(" \n")
buf.write_string(" \n")
buf.write_string(" ")
buf.write_string(title)
buf.write_string("
\n")
buf.write_string(
" Interactive Texture Atlas Inspection Report generated by moon_binpack
\n",
)
buf.write_string(" \n\n")
// Metrics cards
buf.write_string(" \n")
buf.write_string(
" Atlas Dimensions",
)
buf.write_string(bin_width.to_string())
buf.write_string(" × ")
buf.write_string(bin_height.to_string())
buf.write_string(" px\n")
buf.write_string(
" Occupancy Rate",
)
buf.write_string(occupancy_pct.to_string())
buf.write_string("%\n")
buf.write_string(
" Packed Items",
)
buf.write_string(rects.length().to_string())
buf.write_string("\n")
buf.write_string(
" Used Surface Area",
)
buf.write_string(total_used_area.to_string())
buf.write_string(" px²\n")
buf.write_string(" \n\n")
// SVG Preview
buf.write_string(" \n")
buf.write_string(svg_content)
buf.write_string("\n \n\n")
// Details Table
buf.write_string(" \n")
buf.write_string(
" Frame Details\n",
)
buf.write_string(" \n")
buf.write_string(" \n")
buf.write_string(" \n")
buf.write_string(
" # ID X Y Width Height Rotated Bin \n",
)
buf.write_string(" \n")
buf.write_string(" \n")
buf.write_string(" \n")
for i = 0; i < rects.length(); i = i + 1 {
let r = rects[i]
buf.write_string(" \n")
buf.write_string(" ")
buf.write_string((i + 1).to_string())
buf.write_string(" \n")
buf.write_string(" #")
buf.write_string(r.id.to_string())
buf.write_string(" \n")
buf.write_string(" ")
buf.write_string(r.x.to_string())
buf.write_string(" \n")
buf.write_string(" ")
buf.write_string(r.y.to_string())
buf.write_string(" \n")
buf.write_string(" ")
buf.write_string(r.width.to_string())
buf.write_string(" \n")
buf.write_string(" ")
buf.write_string(r.height.to_string())
buf.write_string(" \n")
buf.write_string(" ")
if r.rotated {
buf.write_string("Yes")
} else {
buf.write_string("No")
}
buf.write_string(" \n")
buf.write_string(" ")
buf.write_string(r.bin_index.to_string())
buf.write_string(" \n")
buf.write_string(" \n")
}
buf.write_string(" \n")
buf.write_string("
\n")
buf.write_string(" \n\n")
buf.write_string(" \n")
buf.write_string(" \n\n\n")
buf.to_string()
}