///|
pub fn fixture_name(kind : FixtureKind) -> String {
match kind {
SpriteSheet => "sprite-sheet"
UiPanels => "ui-panels"
DocumentBlocks => "document-blocks"
MixedIcons => "mixed-icons"
StressSlivers => "stress-slivers"
}
}
///|
pub fn fixture_description(kind : FixtureKind) -> String {
match kind {
SpriteSheet =>
"game-like sprite assets with square icons and medium animation frames"
UiPanels => "wide interface panels, badges, tabs, and compact buttons"
DocumentBlocks =>
"document layout blocks with mixed headings, paragraphs, and figures"
MixedIcons => "small icon-like rectangles with deliberate size variation"
StressSlivers => "thin rectangles that stress row and split decisions"
}
}
///|
fn fixture_count(count : Int) -> Int {
if count <= 0 {
1
} else {
count
}
}
///|
fn cyclic(value : Int, modulo : Int) -> Int {
if modulo <= 0 {
0
} else {
value % modulo
}
}
///|
fn sprite_fixture_rect(index : Int) -> Rect {
let phase = cyclic(index, 6)
if phase == 0 {
Rect::make("sprite-idle-\{index}", 16, 16)
} else if phase == 1 {
Rect::make("sprite-run-\{index}", 24, 18)
} else if phase == 2 {
Rect::make("sprite-jump-\{index}", 18, 28)
} else if phase == 3 {
Rect::make("sprite-hit-\{index}", 12, 14)
} else if phase == 4 {
Rect::make("sprite-fx-\{index}", 32, 12)
} else {
Rect::make("sprite-shadow-\{index}", 20, 8)
}
}
///|
fn ui_panel_fixture_rect(index : Int) -> Rect {
let phase = cyclic(index, 7)
if phase == 0 {
Rect::make("panel-card-\{index}", 40, 18)
} else if phase == 1 {
Rect::make("panel-toolbar-\{index}", 52, 10)
} else if phase == 2 {
Rect::make("panel-tab-\{index}", 18, 8)
} else if phase == 3 {
Rect::make("panel-button-\{index}", 16, 9)
} else if phase == 4 {
Rect::make("panel-modal-\{index}", 36, 24)
} else if phase == 5 {
Rect::make("panel-badge-\{index}", 9, 7)
} else {
Rect::make("panel-column-\{index}", 14, 34)
}
}
///|
fn document_block_fixture_rect(index : Int) -> Rect {
let phase = cyclic(index, 8)
if phase == 0 {
Rect::make("doc-title-\{index}", 54, 9)
} else if phase == 1 {
Rect::make("doc-paragraph-\{index}", 48, 14)
} else if phase == 2 {
Rect::make("doc-figure-\{index}", 32, 26)
} else if phase == 3 {
Rect::make("doc-caption-\{index}", 28, 6)
} else if phase == 4 {
Rect::make("doc-sidebar-\{index}", 16, 38)
} else if phase == 5 {
Rect::make("doc-table-\{index}", 42, 20)
} else if phase == 6 {
Rect::make("doc-note-\{index}", 22, 12)
} else {
Rect::make("doc-rule-\{index}", 56, 4)
}
}
///|
fn mixed_icon_fixture_rect(index : Int) -> Rect {
let phase = cyclic(index, 10)
if phase == 0 {
Rect::make("icon-xs-\{index}", 6, 6)
} else if phase == 1 {
Rect::make("icon-sm-\{index}", 8, 8)
} else if phase == 2 {
Rect::make("icon-md-\{index}", 12, 12)
} else if phase == 3 {
Rect::make("icon-lg-\{index}", 18, 18)
} else if phase == 4 {
Rect::make("icon-wide-\{index}", 20, 8)
} else if phase == 5 {
Rect::make("icon-tall-\{index}", 8, 20)
} else if phase == 6 {
Rect::make("icon-pill-\{index}", 24, 10)
} else if phase == 7 {
Rect::make("icon-chip-\{index}", 10, 5)
} else if phase == 8 {
Rect::make("icon-emblem-\{index}", 14, 16)
} else {
Rect::make("icon-mark-\{index}", 9, 11)
}
}
///|
fn stress_sliver_fixture_rect(index : Int) -> Rect {
let phase = cyclic(index, 9)
if phase == 0 {
Rect::make("sliver-hairline-\{index}", 48, 2)
} else if phase == 1 {
Rect::make("sliver-rail-\{index}", 3, 42)
} else if phase == 2 {
Rect::make("sliver-strip-\{index}", 36, 4)
} else if phase == 3 {
Rect::make("sliver-post-\{index}", 4, 30)
} else if phase == 4 {
Rect::make("sliver-block-\{index}", 12, 12)
} else if phase == 5 {
Rect::make("sliver-cap-\{index}", 18, 5)
} else if phase == 6 {
Rect::make("sliver-riser-\{index}", 5, 18)
} else if phase == 7 {
Rect::make("sliver-plate-\{index}", 30, 6)
} else {
Rect::make("sliver-pin-\{index}", 2, 24)
}
}
///|
fn fixture_rect_at(kind : FixtureKind, index : Int) -> Rect {
match kind {
SpriteSheet => sprite_fixture_rect(index)
UiPanels => ui_panel_fixture_rect(index)
DocumentBlocks => document_block_fixture_rect(index)
MixedIcons => mixed_icon_fixture_rect(index)
StressSlivers => stress_sliver_fixture_rect(index)
}
}
///|
/// Generate deterministic sample rectangles for examples and benchmarks.
pub fn fixture_rects(kind : FixtureKind, count : Int) -> Array[Rect] {
let rects : Array[Rect] = []
let limit = fixture_count(count)
for i in 0.. PackOptions {
match kind {
SpriteSheet =>
PackOptions::{
bin: Bin::make(128, 128),
padding: Padding::make(1, 1),
sort: AreaDesc,
algorithm: GuillotineBestArea,
allow_rotate: true,
}
UiPanels =>
PackOptions::{
bin: Bin::make(160, 96),
padding: Padding::make(2, 2),
sort: WidthDesc,
algorithm: SkylineBottomLeft,
allow_rotate: false,
}
DocumentBlocks =>
PackOptions::{
bin: Bin::make(144, 160),
padding: Padding::make(2, 2),
sort: HeightDesc,
algorithm: ShelfFirstFit,
allow_rotate: false,
}
MixedIcons =>
PackOptions::{
bin: Bin::make(96, 96),
padding: Padding::make(1, 1),
sort: AreaDesc,
algorithm: GuillotineBestArea,
allow_rotate: true,
}
StressSlivers =>
PackOptions::{
bin: Bin::make(96, 96),
padding: Padding::make(1, 1),
sort: PerimeterDesc,
algorithm: SkylineBottomLeft,
allow_rotate: true,
}
}
}
///|
fn write_rect_spec_line(out : StringBuilder, rect : Rect) -> Unit {
out.write_string("rect ")
out.write_string(rect.id)
out.write_char(' ')
out.write_string("\{rect.width}")
out.write_char(' ')
out.write_string("\{rect.height}")
out.write_char('\n')
}
///|
fn write_options_spec(out : StringBuilder, options : PackOptions) -> Unit {
out.write_string("bin \{options.bin.width} \{options.bin.height}\n")
out.write_string(
"padding \{options.padding.outer} \{options.padding.inner}\n",
)
out.write_string("algorithm ")
out.write_string(algorithm_name(options.algorithm))
out.write_char('\n')
out.write_string("sort ")
out.write_string(sort_name(options.sort))
out.write_char('\n')
out.write_string("rotate ")
out.write_string(bool_name(options.allow_rotate))
out.write_char('\n')
}
///|
/// Export any rectangle list as a rectloom text spec.
pub fn rects_to_spec(rects : ArrayView[Rect], options : PackOptions) -> String {
let out = StringBuilder()
write_options_spec(out, options)
for rect in rects {
write_rect_spec_line(out, rect)
}
out.to_string()
}
///|
/// Generate a ready-to-pack spec for a fixture family.
pub fn fixture_spec(kind : FixtureKind, count : Int) -> String {
rects_to_spec(fixture_rects(kind, count), fixture_options(kind))
}
///|
/// Pack a fixture with its recommended options.
pub fn pack_fixture(
kind : FixtureKind,
count : Int,
) -> Result[Layout, PackError] {
pack(fixture_rects(kind, count), fixture_options(kind))
}
///|
/// Run automatic comparison on a fixture family.
pub fn compare_fixture(
kind : FixtureKind,
count : Int,
) -> Result[PackComparison, PackError] {
let options = fixture_options(kind)
compare_packers(fixture_rects(kind, count), options)
}
///|
fn write_fixture_catalog_row(out : StringBuilder, kind : FixtureKind) -> Unit {
let options = fixture_options(kind)
out.write_string("- ")
out.write_string(fixture_name(kind))
out.write_string(": ")
out.write_string(fixture_description(kind))
out.write_string(" default=")
out.write_string(options_key(options))
out.write_char('\n')
}
///|
/// Human-readable list of built-in fixtures.
pub fn fixture_catalog() -> String {
let out = StringBuilder()
out.write_string("rectloom fixture catalog\n")
write_fixture_catalog_row(out, SpriteSheet)
write_fixture_catalog_row(out, UiPanels)
write_fixture_catalog_row(out, DocumentBlocks)
write_fixture_catalog_row(out, MixedIcons)
write_fixture_catalog_row(out, StressSlivers)
out.to_string()
}