dotmax 0.1.9

High-performance terminal braille rendering for images, animations, and graphics
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
//! Prelude module for convenient imports.
//!
//! This module re-exports the most commonly used types from dotmax,
//! allowing you to import everything you need with a single statement:
//!
//! ```
//! use dotmax::prelude::*;
//! ```
//!
//! This follows the standard Rust convention used by libraries like
//! `std::prelude`, `tokio::prelude`, and `serde::prelude`.
//!
//! # What's Included
//!
//! ## Core Types
//!
//! - [`BrailleGrid`]: The fundamental braille rendering buffer
//! - [`Color`]: RGB color for cells and drawing operations
//! - [`TerminalRenderer`]: Handles terminal output and cursor management
//! - [`TerminalBackend`]: Trait for custom terminal backends
//! - [`TerminalCapabilities`]: Terminal feature detection
//! - [`DotmaxError`]: Error type for all operations
//! - [`Result`]: Convenience type alias (`Result<T, DotmaxError>`)
//!
//! ## Drawing Primitives
//!
//! - [`draw_line`], [`draw_line_colored`]: Line drawing (Bresenham algorithm)
//! - [`draw_circle`], [`draw_circle_colored`]: Circle drawing
//! - [`draw_rectangle`], [`draw_rectangle_colored`]: Rectangle drawing
//! - [`draw_polygon`], [`draw_polygon_colored`]: Polygon drawing
//!
//! ## Animation
//!
//! - [`AnimationLoop`], [`AnimationLoopBuilder`]: Animation loop management
//! - [`FrameBuffer`]: Double-buffered frame storage
//! - [`FrameTimer`]: Frame rate control and timing
//! - [`DifferentialRenderer`]: Efficient partial screen updates
//! - [`PrerenderedAnimation`]: Pre-computed animation frames
//!
//! ## Color System
//!
//! - [`ColorScheme`], [`ColorSchemeBuilder`]: Color scheme types
//! - [`ColorCapability`]: Terminal color capability detection
//! - [`detect_color_capability`]: Auto-detect terminal color support
//! - [`apply_color_scheme`], [`apply_colors_to_grid`]: Apply colors to grids
//! - Built-in schemes: [`heat_map`], [`rainbow`], [`grayscale`], [`monochrome`],
//!   [`blue_purple`], [`cyan_magenta`], [`green_yellow`]
//!
//! ## Universal Media (with `image` feature)
//!
//! - [`show_file`]: Display any media file (auto-detects format, plays animations)
//! - [`load_file`]: Load media into [`MediaContent`] for manual control
//! - [`MediaContent`]: Enum for static images vs animated content
//! - [`MediaPlayer`]: Trait for animated media playback (GIF, APNG, video)
//!
//! # Feature-Gated Exports
//!
//! With the `image` feature enabled, also includes:
//!
//! - `ImageRenderer`: High-level image-to-braille rendering
//! - `DitheringMethod`: Dithering algorithm selection
//! - `show_file`, `load_file`: Universal media display functions
//! - `MediaContent`, `MediaPlayer`: Media types for animation control
//!
//! # Note on `Result`
//!
//! This prelude exports `dotmax::Result<T>` which is an alias for
//! `std::result::Result<T, DotmaxError>`. If you need to use a different
//! error type (like `Box<dyn std::error::Error>`), use the fully qualified
//! `std::result::Result` instead.
//!
//! # Examples
//!
//! ## Basic Grid Usage
//!
//! ```
//! use dotmax::prelude::*;
//!
//! // Create a grid and draw some shapes
//! let mut grid = BrailleGrid::new(80, 24)?;
//! draw_line(&mut grid, 0, 0, 100, 50)?;
//! draw_circle(&mut grid, 80, 48, 30)?;
//! # Ok::<(), DotmaxError>(())
//! ```
//!
//! ## Animation Loop
//!
//! ```no_run
//! use dotmax::prelude::*;
//!
//! // AnimationLoop::new(width, height) returns an AnimationLoopBuilder
//! let animation = AnimationLoop::new(80, 24)
//!     .fps(30);
//! ```
//!
//! ## Color Schemes
//!
//! ```
//! use dotmax::prelude::*;
//!
//! let scheme = heat_map();
//! let capability = detect_color_capability();
//! # Ok::<(), DotmaxError>(())
//! ```
//!
//! ## Image Rendering (with `image` feature)
//!
//! ```ignore
//! use dotmax::prelude::*;
//! use std::path::Path;
//!
//! let grid = ImageRenderer::new()
//!     .load_from_path(Path::new("image.png"))?
//!     .resize_to_terminal()?
//!     .dithering(DitheringMethod::FloydSteinberg)
//!     .render()?;
//! ```
//!
//! ## Universal Media Display (with `image` feature)
//!
//! The easiest way to display any media file:
//!
//! ```ignore
//! use dotmax::prelude::*;
//!
//! // One function handles everything - images, GIFs, videos
//! show_file("photo.png")?;       // Static image
//! show_file("animation.gif")?;   // Plays animated GIF
//! show_file("movie.mp4")?;       // Video (requires 'video' feature)
//! ```
//!
//! For manual control over animated content:
//!
//! ```ignore
//! use dotmax::prelude::*;
//!
//! match load_file("animation.gif")? {
//!     MediaContent::Static(grid) => {
//!         // Single frame - render once
//!         show(&grid)?;
//!     }
//!     MediaContent::Animated(mut player) => {
//!         // Frame-by-frame control
//!         while let Some(Ok((frame, delay))) = player.next_frame() {
//!             // Custom rendering logic here
//!             std::thread::sleep(delay);
//!         }
//!     }
//! }
//! ```

// ============================================================================
// Core Types (AC: #2)
// ============================================================================

pub use crate::{
    BrailleGrid, Color, DotmaxError, Result, TerminalBackend, TerminalCapabilities,
    TerminalRenderer,
};

// ============================================================================
// Drawing Primitives (AC: #3)
// ============================================================================

pub use crate::primitives::{
    draw_circle, draw_circle_colored, draw_line, draw_line_colored, draw_polygon,
    draw_polygon_colored, draw_rectangle, draw_rectangle_colored,
};

// ============================================================================
// Animation Types (AC: #4)
// ============================================================================

pub use crate::{
    AnimationLoop, AnimationLoopBuilder, DifferentialRenderer, FrameBuffer, FrameTimer,
    PrerenderedAnimation,
};

// ============================================================================
// Color Types (AC: #5)
// ============================================================================

pub use crate::{
    apply_color_scheme, apply_colors_to_grid, blue_purple, cyan_magenta, detect_color_capability,
    grayscale, green_yellow, heat_map, monochrome, rainbow, ColorCapability, ColorScheme,
    ColorSchemeBuilder,
};

// ============================================================================
// Image Types - Feature-Gated (AC: #6)
// ============================================================================

#[cfg(feature = "image")]
pub use crate::image::{DitheringMethod, ImageRenderer};

// ============================================================================
// Quick Functions (Story 8.2)
// ============================================================================

pub use crate::quick::{grid, grid_sized, show};

#[cfg(feature = "image")]
pub use crate::quick::{load_file, load_image, load_image_sized, show_file, show_image};

// ============================================================================
// Media Types - Feature-Gated (Epic 9: Universal Media Rendering)
// ============================================================================

#[cfg(feature = "image")]
pub use crate::media::{MediaContent, MediaPlayer};

// ============================================================================
// Webcam Types - Feature-Gated (Story 9.6)
// ============================================================================

#[cfg(feature = "video")]
pub use crate::media::{
    list_webcams, WebcamDevice, WebcamDeviceId, WebcamPlayer, WebcamPlayerBuilder,
};

#[cfg(feature = "video")]
pub use crate::quick::{show_webcam, show_webcam_device};

// ============================================================================
// Tests (AC: #7)
// ============================================================================

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    // The nested `returns_result` fn exists *because* it always returns `Ok`: it proves the
    // re-exported `Result<T>` alias is usable as a return type. Unwrapping it defeats the test.
    #[allow(clippy::unnecessary_wraps)]
    fn test_core_types_accessible() {
        // Test BrailleGrid
        let grid = BrailleGrid::new(10, 5).unwrap();
        assert_eq!(grid.width(), 10);

        // Test Color
        let color = Color::rgb(255, 128, 0);
        assert_eq!(color.r, 255);

        // Test Result type alias compiles
        fn returns_result() -> Result<()> {
            Ok(())
        }
        returns_result().unwrap();
    }

    #[test]
    fn test_primitives_accessible() {
        let mut grid = BrailleGrid::new(20, 10).unwrap();

        // All primitives should be callable
        draw_line(&mut grid, 0, 0, 10, 10).unwrap();
        draw_circle(&mut grid, 20, 20, 5).unwrap();
        draw_rectangle(&mut grid, 0, 0, 10, 10).unwrap();
        draw_polygon(&mut grid, &[(0, 0), (10, 0), (5, 10)]).unwrap();

        // Colored variants (with proper arguments)
        let color = Color::rgb(255, 0, 0);
        draw_line_colored(&mut grid, 0, 0, 5, 5, color, None).unwrap();
        draw_circle_colored(&mut grid, 15, 15, 3, color, false).unwrap();
        draw_rectangle_colored(&mut grid, 5, 5, 10, 10, color, false).unwrap();
        draw_polygon_colored(&mut grid, &[(0, 0), (5, 0), (2, 5)], color, true).unwrap();
    }

    #[test]
    fn test_animation_types_accessible() {
        // Test FrameBuffer
        let fb = FrameBuffer::new(10, 5);
        assert_eq!(fb.width(), 10);

        // Test FrameTimer
        let timer = FrameTimer::new(30);
        assert_eq!(timer.target_fps(), 30);

        // Test AnimationLoopBuilder (created via AnimationLoop::new(w, h))
        let _builder = AnimationLoop::new(80, 24).fps(60);

        // Test DifferentialRenderer (takes no args)
        let _diff = DifferentialRenderer::new();

        // Test PrerenderedAnimation (takes frame_rate)
        let mut prerendered = PrerenderedAnimation::new(30);
        prerendered.add_frame(BrailleGrid::new(10, 5).unwrap());
        assert_eq!(prerendered.frame_rate(), 30);
    }

    #[test]
    // These bindings are the assertion: they only have to *name* and type-check each re-export.
    // Having no runtime effect is the point, so the effect-free-binding lint is expected here.
    #[allow(clippy::no_effect_underscore_binding)]
    fn test_color_types_accessible() {
        // Test ColorCapability
        let _cap = ColorCapability::TrueColor;

        // Test detect_color_capability function
        let _detected = detect_color_capability();

        // Test ColorScheme via built-in functions
        let _scheme1 = heat_map();
        let _scheme2 = rainbow();
        let _scheme3 = grayscale();
        let _scheme4 = monochrome();
        let _scheme5 = blue_purple();
        let _scheme6 = cyan_magenta();
        let _scheme7 = green_yellow();

        // Test ColorSchemeBuilder
        let _builder = ColorSchemeBuilder::new("test");
    }

    #[test]
    #[cfg(feature = "image")]
    // Effect-free by design: the binding exists only to prove the re-export type-checks.
    #[allow(clippy::no_effect_underscore_binding)]
    fn test_image_types_accessible() {
        // Test ImageRenderer
        let _renderer = ImageRenderer::new();

        // Test DitheringMethod
        let _method = DitheringMethod::FloydSteinberg;
    }

    #[test]
    #[cfg(feature = "image")]
    // Effect-free by design: the fn-pointer bindings only assert the re-exported signatures.
    #[allow(clippy::no_effect_underscore_binding)]
    fn test_media_types_accessible() {
        // Test MediaContent enum variants are accessible
        // We can't easily construct them without real files, but we can verify
        // the types are exported and pattern matching compiles

        fn _pattern_match_media_content(content: MediaContent) {
            match content {
                MediaContent::Static(_grid) => {}
                MediaContent::Animated(_player) => {}
            }
        }

        // Test MediaPlayer trait is accessible as a trait bound
        fn _accepts_media_player<T: MediaPlayer>(_player: T) {}

        // Test show_file and load_file functions are accessible
        // (just verify they exist, don't call them without real files)
        let _show_file_fn: fn(&str) -> crate::Result<()> = |path| show_file(path);
        let _load_file_fn: fn(&str) -> crate::Result<MediaContent> = |path| load_file(path);
    }

    #[test]
    #[cfg(feature = "video")]
    // Effect-free by design: the bindings only assert the re-exported types/signatures exist.
    #[allow(clippy::no_effect_underscore_binding)]
    fn test_webcam_types_accessible() {
        // Test WebcamDevice
        let device = WebcamDevice::new("/dev/video0", "Test Camera", "Test description");
        assert_eq!(device.id, "/dev/video0");

        // Test WebcamDeviceId conversions
        let _id1: WebcamDeviceId = 0.into();
        let _id2: WebcamDeviceId = "/dev/video0".into();
        let _id3 = WebcamDeviceId::Default;

        // Test list_webcams exists and returns Vec
        let _devices: Vec<WebcamDevice> = list_webcams();

        // Test WebcamPlayerBuilder chain methods compile
        let _builder = WebcamPlayerBuilder::new()
            .device(0)
            .resolution(1280, 720)
            .fps(30);

        // Test show_webcam and show_webcam_device functions exist
        // (just verify they exist as function pointers, don't call them)
        let _show_fn: fn() -> crate::Result<()> = show_webcam;
        let _show_device_fn: fn(usize) -> crate::Result<()> = |idx| show_webcam_device(idx);
    }

    #[test]
    // `_use_result_alias` must return `Ok` unconditionally: it is a compile-time check that the
    // `Result<T>` alias coexists with every other re-export without a naming conflict.
    #[allow(clippy::unnecessary_wraps)]
    fn test_no_naming_conflicts() {
        // This test verifies that all re-exported items can be used together
        // without naming conflicts. If this compiles, there are no conflicts.

        let mut grid = BrailleGrid::new(20, 10).unwrap();
        let color = Color::rgb(128, 128, 128);
        let scheme = grayscale();
        let _cap = detect_color_capability();

        draw_line(&mut grid, 0, 0, 10, 10).unwrap();
        draw_line_colored(&mut grid, 5, 5, 15, 15, color, None).unwrap();

        let fb = FrameBuffer::new(20, 10);
        let _timer = FrameTimer::new(30);

        // If this function compiles, there are no conflicts
        fn _use_result_alias() -> Result<()> {
            Ok(())
        }

        // Verify scheme methods work (sample takes f32 intensity 0.0-1.0)
        let _sampled_color = scheme.sample(0.5);

        // Verify we can access buffer dimensions
        assert_eq!(fb.width(), 20);
        assert_eq!(fb.height(), 10);
    }
}