1use crate::config::{PackerConfig, SkylineHeuristic};
2use crate::error::{Result, TexPackerError};
3use crate::geometry::PlacementGeometry;
4use crate::model::{Atlas, Frame, Meta, Page, Rect};
5use crate::runtime_placement::RuntimePage;
6
7#[derive(Debug, Clone)]
8pub enum RuntimeStrategy {
9 Guillotine,
10 Shelf(ShelfPolicy),
11 Skyline(SkylineHeuristic),
12}
13
14#[derive(Debug, Clone, Copy)]
15pub enum ShelfPolicy {
16 NextFit,
17 FirstFit,
18}
19
20#[derive(Debug, Clone)]
22pub struct RuntimeStats {
23 pub num_pages: usize,
25 pub num_textures: usize,
27 pub total_page_area: u64,
29 pub total_used_area: u64,
31 pub total_free_area: u64,
33 pub occupancy: f64,
35 pub num_free_rects: usize,
37}
38
39impl RuntimeStats {
40 pub fn summary(&self) -> String {
42 format!(
43 "Pages: {}, Textures: {}, Occupancy: {:.2}%, Free: {} px² ({} rects), Used: {} px²",
44 self.num_pages,
45 self.num_textures,
46 self.occupancy * 100.0,
47 self.total_free_area,
48 self.num_free_rects,
49 self.total_used_area,
50 )
51 }
52
53 pub fn fragmentation(&self) -> f64 {
57 if self.total_free_area == 0 {
58 0.0
59 } else {
60 (self.num_free_rects as f64) / ((self.total_free_area as f64) / 1000.0).max(1.0)
61 }
62 }
63
64 pub fn waste_percentage(&self) -> f64 {
66 if self.total_page_area > 0 {
67 (self.total_free_area as f64 / self.total_page_area as f64) * 100.0
68 } else {
69 0.0
70 }
71 }
72}
73
74pub struct AtlasSession {
75 pub(crate) cfg: PackerConfig,
76 _strategy: RuntimeStrategy,
77 pages: Vec<RuntimePage>,
78 next_id: usize,
79}
80
81impl AtlasSession {
82 pub fn new(cfg: PackerConfig, strategy: RuntimeStrategy) -> Self {
83 Self {
84 cfg,
85 _strategy: strategy,
86 pages: Vec::new(),
87 next_id: 0,
88 }
89 }
90
91 fn new_page(&mut self) -> RuntimePage {
92 let id = self.next_id;
93 self.next_id += 1;
94 RuntimePage::new(
95 id,
96 self.cfg.max_width,
97 self.cfg.max_height,
98 &self.cfg,
99 &self._strategy,
100 )
101 }
102
103 pub fn append(&mut self, key: String, w: u32, h: u32) -> Result<(usize, Frame<String>)> {
104 let geometry = PlacementGeometry::from_size(w, h, &self.cfg);
105 for idx in 0..self.pages.len() {
107 let (slot, rotated, id);
108 {
109 let p = &self.pages[idx];
110 if let Some((s, r)) = p.choose(geometry.reserved_w, geometry.reserved_h) {
111 slot = s;
112 rotated = r;
113 id = p.id;
114 } else {
115 continue;
116 }
117 }
118 let frame = geometry.frame(key.clone(), Rect::new(0, 0, w, h), &slot, rotated);
119 let p = &mut self.pages[idx];
120 p.place(&key, &slot, &frame, rotated);
121 return Ok((id, frame));
122 }
123 let mut page = self.new_page();
125 if let Some((slot, rotated)) = page.choose(geometry.reserved_w, geometry.reserved_h) {
126 let frame = geometry.frame(key.clone(), Rect::new(0, 0, w, h), &slot, rotated);
127 page.place(&key, &slot, &frame, rotated);
128 let id = page.id;
129 self.pages.push(page);
130 return Ok((id, frame));
131 }
132 Err(TexPackerError::OutOfSpace {
133 key,
134 width: w,
135 height: h,
136 pages_attempted: self.pages.len() + 1,
137 })
138 }
139
140 pub fn evict(&mut self, page_id: usize, key: &str) -> bool {
141 if let Some(p) = self.pages.iter_mut().find(|p| p.id == page_id)
142 && let Some((slot, _rot, _frame)) = p.used.remove(key)
143 {
144 p.add_free(slot);
145 return true;
146 }
147 false
148 }
149
150 pub fn snapshot_atlas(&self) -> Atlas<String> {
151 let mut pages: Vec<Page<String>> = Vec::new();
152 for p in &self.pages {
153 let mut frames: Vec<Frame<String>> = Vec::new();
154 for (_k, (_slot, _rot, f)) in p.used.iter() {
155 frames.push(f.clone());
156 }
157 pages.push(Page {
158 id: p.id,
159 width: p.width,
160 height: p.height,
161 frames,
162 });
163 }
164 let meta = Meta {
165 schema_version: "1".into(),
166 app: "tex-packer".into(),
167 version: env!("CARGO_PKG_VERSION").into(),
168 format: "RGBA8888".into(),
169 scale: 1.0,
170 power_of_two: self.cfg.power_of_two,
171 square: self.cfg.square,
172 max_dim: (self.cfg.max_width, self.cfg.max_height),
173 padding: (self.cfg.border_padding, self.cfg.texture_padding),
174 extrude: self.cfg.texture_extrusion,
175 allow_rotation: self.cfg.allow_rotation,
176 trim_mode: if self.cfg.trim { "trim" } else { "none" }.into(),
177 background_color: None,
178 };
179 Atlas { pages, meta }
180 }
181
182 pub fn get_frame(&self, key: &str) -> Option<(usize, &Frame<String>)> {
185 for page in &self.pages {
186 if let Some((_slot, _rot, frame)) = page.used.get(key) {
187 return Some((page.id, frame));
188 }
189 }
190 None
191 }
192
193 pub fn get_reserved_slot(&self, key: &str) -> Option<(usize, Rect)> {
195 for page in &self.pages {
196 if let Some((slot, _rot, _frame)) = page.used.get(key) {
197 return Some((page.id, *slot));
198 }
199 }
200 None
201 }
202
203 pub fn evict_by_key(&mut self, key: &str) -> bool {
206 for page in &mut self.pages {
207 if let Some((slot, _rot, _frame)) = page.used.remove(key) {
208 page.add_free(slot);
209 return true;
210 }
211 }
212 false
213 }
214
215 pub fn contains(&self, key: &str) -> bool {
217 self.pages.iter().any(|p| p.used.contains_key(key))
218 }
219
220 pub fn keys(&self) -> Vec<&str> {
222 self.pages
223 .iter()
224 .flat_map(|p| p.used.keys().map(|k| k.as_str()))
225 .collect()
226 }
227
228 pub fn texture_count(&self) -> usize {
230 self.pages.iter().map(|p| p.used.len()).sum()
231 }
232
233 pub fn stats(&self) -> RuntimeStats {
235 let num_pages = self.pages.len();
236 let num_textures = self.texture_count();
237
238 let mut total_used_area = 0u64;
239 let mut total_free_area = 0u64;
240 let mut num_free_rects = 0;
241
242 for page in &self.pages {
243 total_used_area += page.used_area();
244 let (free_area, free_rects) = page.free_area_and_rects();
245 total_free_area += free_area;
246 num_free_rects += free_rects;
247 }
248
249 let total_page_area = if num_pages > 0 {
250 (self.cfg.max_width as u64) * (self.cfg.max_height as u64) * (num_pages as u64)
251 } else {
252 0
253 };
254
255 let occupancy = if total_page_area > 0 {
256 total_used_area as f64 / total_page_area as f64
257 } else {
258 0.0
259 };
260
261 RuntimeStats {
262 num_pages,
263 num_textures,
264 total_page_area,
265 total_used_area,
266 total_free_area,
267 occupancy,
268 num_free_rects,
269 }
270 }
271}