Skip to main content

ql_label/
printer.rs

1use log::{debug, error, info, warn};
2use rusb::{Context, Device, DeviceDescriptor, DeviceHandle, Direction, TransferType, UsbContext};
3use std::time::Duration;
4
5use crate::{
6    error::{Error, PrinterError},
7    media::Media,
8    model::Model,
9    utils::TwoColorMatrix,
10    Matrix,
11};
12
13// Vendoer id of Brother Industries, Ltd
14const VENDOR_ID: u16 = 0x04f9;
15
16#[derive(Debug, Clone, Copy)]
17#[allow(dead_code)]
18struct Endpoint {
19    config: u8,
20    iface: u8,
21    setting: u8,
22    address: u8,
23}
24
25pub struct Printer {
26    handle: Box<DeviceHandle<Context>>,
27    endpoint_out: Endpoint,
28    endpoint_in: Endpoint,
29    config: Config,
30}
31
32impl Printer {
33    /// Create a new printer instance with the specified configuration.
34    ///
35    /// This constructor handles USB device enumeration, connection, and initialization.
36    /// It will search for a Brother P-Touch printer matching the model and serial number
37    /// specified in the configuration.
38    ///
39    /// # Arguments
40    /// * `config` - Printer configuration containing model, serial, media, and print settings
41    ///
42    /// # Returns
43    /// * `Ok(Printer)` - Successfully connected printer instance
44    /// * `Err(Error)` - Connection failed, device not found, or USB error
45    ///
46    /// # Example
47    /// ```rust,no_run
48    /// # use ptouch::{Config, Model, Media, ContinuousType, Printer};
49    /// let config = Config::new(Model::QL820NWB, "E8N117P02180".to_string(), 
50    ///                         Media::Continuous(ContinuousType::Continuous62));
51    /// let printer = Printer::new(config)?;
52    /// # Ok::<(), ptouch::Error>(())
53    /// ```
54    pub fn new(config: Config) -> Result<Self, Error> {
55        // rusb::set_log_level(rusb::LogLevel::Debug);
56        match Context::new() {
57            Ok(mut context) => {
58                match Self::open_device(&mut context, config.model.pid(), config.serial.clone()) {
59                    Ok((mut device, device_desc, handle)) => {
60                        handle.reset()?;
61
62                        let endpoint_in = match Self::find_endpoint(
63                            &mut device,
64                            &device_desc,
65                            Direction::In,
66                            TransferType::Bulk,
67                        ) {
68                            Some(endpoint) => endpoint,
69                            None => return Err(Error::MissingEndpoint),
70                        };
71
72                        let endpoint_out = match Self::find_endpoint(
73                            &mut device,
74                            &device_desc,
75                            Direction::Out,
76                            TransferType::Bulk,
77                        ) {
78                            Some(endpoint) => endpoint,
79                            None => return Err(Error::MissingEndpoint),
80                        };
81
82                        // QL-800では`has_kernel_driver`が`true`となる
83                        // QL-820NWBでは`has_kernel_driver`が`false`となる
84                        // `has_kernel_driver`が`true`の場合に、カーネルドライバーをデタッチしないとエラーとなる
85                        //
86                        handle.set_auto_detach_kernel_driver(true)?;
87                        let has_kernel_driver = match handle.kernel_driver_active(0) {
88                            Ok(true) => {
89                                handle.detach_kernel_driver(0).ok();
90                                true
91                            }
92                            _ => false,
93                        };
94                        info!(" Kernel driver support is {}", has_kernel_driver);
95                        handle.set_active_configuration(1)?;
96                        handle.claim_interface(0)?;
97                        handle.set_alternate_setting(0, 0)?;
98
99                        Ok(Printer {
100                            handle: Box::new(handle),
101                            endpoint_out,
102                            endpoint_in,
103                            config,
104                        })
105                    }
106                    Err(err) => {
107                        debug!("Device connection failed: {:?}", err);
108                        Err(Error::DeviceOffline)
109                    }
110                }
111            }
112            Err(err) => Err(Error::UsbError(err)),
113        }
114    }
115
116    /// Cancel current print job and reset printer state.
117    ///
118    /// Sends an initialization command to cancel any ongoing print job
119    /// and reset the printer to a ready state.
120    ///
121    /// # Returns
122    /// * `Ok(())` - Cancel command sent successfully
123    /// * `Err(Error)` - Communication error
124    ///
125    /// # Example
126    /// ```rust,no_run
127    /// # use ptouch::{Config, Model, Media, ContinuousType, Printer};
128    /// # let config = Config::new(Model::QL820NWB, "serial".to_string(), 
129    /// #                         Media::Continuous(ContinuousType::Continuous62));
130    /// let printer = Printer::new(config)?;
131    /// printer.cancel()?; // Cancel any ongoing job
132    /// # Ok::<(), ptouch::Error>(())
133    /// ```
134    pub fn cancel(&self) -> Result<(), Error> {
135        let buf = self.initialize();
136        self.write(buf)?;
137        Ok(())
138    }
139
140    /// Read current printer status including media type, errors, and phase.
141    ///
142    /// This method is convenient for inspection when a new media is added
143    /// or to check for printer errors before starting a print job.
144    ///
145    /// # Returns
146    /// * `Ok(Status)` - Current printer status information
147    /// * `Err(Error)` - Communication error or timeout
148    ///
149    /// # Example
150    /// ```rust,no_run
151    /// # use ptouch::{Config, Model, Media, ContinuousType, Printer};
152    /// # let config = Config::new(Model::QL820NWB, "serial".to_string(), 
153    /// #                         Media::Continuous(ContinuousType::Continuous62));
154    /// let printer = Printer::new(config)?;
155    /// match printer.check_status() {
156    ///     Ok(status) => println!("Printer ready: {:?}", status),
157    ///     Err(e) => eprintln!("Printer error: {:?}", e),
158    /// }
159    /// # Ok::<(), ptouch::Error>(())
160    /// ```
161    pub fn check_status(&self) -> Result<Status, Error> {
162        self.request_status()?;
163        self.read_status()
164    }
165
166    /// Print single-color labels.
167    ///
168    /// This method prints labels using black ink only. For two-color printing,
169    /// use `print_two_color()` method instead.
170    ///
171    /// # Arguments
172    /// * `images` - Iterator of `Matrix` (`Vec<Vec<u8>>`) containing 1-bit bitmap data
173    ///
174    /// # Returns
175    /// * `Ok(())` - Print job completed successfully
176    /// * `Err(Error)` - Printer error, communication error, or media mismatch
177    ///
178    /// # Image Format
179    /// - Width: 720 pixels (90 bytes) for normal printers, 1296 pixels for wide printers
180    /// - Height: Variable, depends on label length
181    /// - Format: 1-bit bitmap packed into bytes (8 pixels per byte)
182    ///
183    /// # Example
184    /// ```rust,no_run
185    /// # use ptouch::{Config, Model, Media, ContinuousType, Printer, Matrix};
186    /// let config = Config::new(Model::QL820NWB, "serial".to_string(), 
187    ///                         Media::Continuous(ContinuousType::Continuous62));
188    /// let printer = Printer::new(config)?;
189    /// 
190    /// // Create simple black and white pattern
191    /// let image_data: Matrix = vec![vec![0xFF; 90]; 300]; // 300 lines of solid black
192    /// 
193    /// printer.print(vec![image_data].into_iter())?;
194    /// # Ok::<(), ptouch::Error>(())
195    /// ```
196    pub fn print(&self, images: impl Iterator<Item = Matrix>) -> Result<(), Error> {
197        info!("Requesting printer status before print job");
198
199        self.request_status()?;
200
201        match self.read_status() {
202            Ok(status) => {
203                info!("Verifying correct media is installed");
204                status.check_media(self.config.media)?;
205
206                info!("Starting print job");
207                self.print_label(images)?;
208                Ok(())
209            }
210            Err(err) => {
211                error!("Failed to read printer status: {:?}", err);
212                Err(err)
213            }
214        }
215    }
216
217    /// Print two-color labels using black and red colors.
218    ///
219    /// This method is specifically designed for QL-820NWB printers with
220    /// red/black tape installed. The configuration must have `two_colors(true)`
221    /// enabled for this method to work.
222    ///
223    /// # Arguments
224    /// * `images` - Iterator of `TwoColorMatrix` containing black and red image data
225    ///
226    /// # Returns
227    /// * `Ok(())` - Print job completed successfully
228    /// * `Err(Error)` - Printer error, communication error, or invalid configuration
229    ///
230    /// # Example
231    /// ```rust,no_run
232    /// # use ptouch::{Config, Model, Media, ContinuousType, Printer, TwoColorMatrix};
233    /// let config = Config::new(Model::QL820NWB, "serial".to_string(), 
234    ///                         Media::Continuous(ContinuousType::Continuous62Red))
235    ///     .two_colors(true);
236    /// let printer = Printer::new(config)?;
237    /// 
238    /// // Create two-color image data
239    /// let black_data = vec![vec![0u8; 90]; 300];
240    /// let red_data = vec![vec![0u8; 90]; 300];
241    /// let two_color = TwoColorMatrix::new(black_data, red_data)?;
242    /// 
243    /// printer.print_two_color(vec![two_color].into_iter())?;
244    /// # Ok::<(), Box<dyn std::error::Error>>(())
245    /// ```
246    pub fn print_two_color(&self, images: impl Iterator<Item = TwoColorMatrix>) -> Result<(), Error> {
247        if !self.config.two_colors {
248            return Err(Error::InvalidConfig("Two-color printing not enabled in config".to_string()));
249        }
250
251        info!("Requesting printer status before two-color print job");
252
253        self.request_status()?;
254
255        match self.read_status() {
256            Ok(status) => {
257                info!("Verifying correct media is installed");
258                status.check_media(self.config.media)?;
259
260                info!("Starting two-color print job");
261                let alternating_images = images.map(|two_color| two_color.to_alternating_matrix());
262                self.print_label(alternating_images)?;
263                Ok(())
264            }
265            Err(err) => {
266                error!("Failed to read printer status: {:?}", err);
267                Err(err)
268            }
269        }
270    }
271
272    // Private helper methods
273
274    fn open_device(
275        context: &mut Context,
276        pid: u16,
277        serial: String,
278    ) -> Result<(Device<Context>, DeviceDescriptor, DeviceHandle<Context>), Error> {
279        let devices = context.devices()?;
280
281        if devices.is_empty() {
282            warn!("Unable to enumerate USB devices");
283            return Err(Error::DeviceListNotReadable);
284        }
285        for device in devices.iter() {
286            let device_desc = match device.device_descriptor() {
287                Ok(d) => d,
288                Err(err) => {
289                    debug!("{:#?}", err);
290                    continue;
291                }
292            };
293            debug!(
294                "vender_id: {:x},  product_id: {:x}",
295                device_desc.vendor_id(),
296                device_desc.product_id()
297            );
298            if device_desc.vendor_id() == VENDOR_ID && device_desc.product_id() == pid {
299                match device.open() {
300                    Ok(handle) => {
301                        let timeout = Duration::from_secs(1);
302                        let languages = handle.read_languages(timeout)?;
303
304                        if languages.len() > 0 {
305                            let language = languages[0];
306                            match handle.read_serial_number_string(language, &device_desc, timeout)
307                            {
308                                Ok(s) => {
309                                    if s == serial {
310                                        info!("Connected to printer (serial: {})", serial);
311                                        return Ok((device, device_desc, handle));
312                                    } else {
313                                        continue;
314                                    }
315                                }
316                                Err(err) => {
317                                    debug!("Cannot read device serial number: {:?}", err);
318                                    continue;
319                                }
320                            }
321                        } else {
322                            continue;
323                        }
324                    }
325                    Err(err) => {
326                        debug!("Unable to open USB device: {:?}", err);
327                        continue;
328                    }
329                }
330            }
331        }
332        error!("No printer found with serial number: {}", serial);
333        Err(Error::DeviceOffline)
334    }
335
336    fn find_endpoint(
337        device: &mut Device<Context>,
338        device_desc: &DeviceDescriptor,
339        direction: Direction,
340        transfer_type: TransferType,
341    ) -> Option<Endpoint> {
342        for n in 0..device_desc.num_configurations() {
343            let config_desc = match device.config_descriptor(n) {
344                Ok(c) => c,
345                Err(_) => continue,
346            };
347            for interface in config_desc.interfaces() {
348                for interface_desc in interface.descriptors() {
349                    for endpoint_desc in interface_desc.endpoint_descriptors() {
350                        if endpoint_desc.direction() == direction
351                            && endpoint_desc.transfer_type() == transfer_type
352                        {
353                            return Some(Endpoint {
354                                config: config_desc.number(),
355                                iface: interface_desc.interface_number(),
356                                setting: interface_desc.setting_number(),
357                                address: endpoint_desc.address(),
358                            });
359                        }
360                    }
361                }
362            }
363        }
364        None
365    }
366
367    fn write(&self, buf: Vec<u8>) -> Result<(), Error> {
368        // 動的タイムアウト計算
369        // - ベースタイムアウト: 5秒
370        // - データサイズ依存: 1MB/sの転送速度を仮定
371        // - 安全マージン: 2倍
372        let base_timeout_secs = 5;
373        let transfer_rate_bytes_per_sec = 1_000_000; // 1MB/s
374        let safety_margin = 2.0;
375
376        let data_dependent_timeout =
377            (buf.len() as f64 / transfer_rate_bytes_per_sec as f64) * safety_margin;
378        let total_timeout_secs = base_timeout_secs as f64 + data_dependent_timeout;
379
380        // 最小10秒、最大60秒の範囲でクランプ
381        let timeout_secs = total_timeout_secs.max(10.0).min(60.0);
382        let timeout = Duration::from_secs(timeout_secs as u64);
383
384        debug!(
385            "USB transfer timeout set to {:.1}s for {} bytes",
386            timeout_secs,
387            buf.len()
388        );
389        let result = self
390            .handle
391            .write_bulk(self.endpoint_out.address, &buf, timeout);
392        match result {
393            Ok(n) => {
394                if n == buf.len() {
395                    debug!(
396                        "Successfully wrote {} bytes to endpoint {:#x}",
397                        n, self.endpoint_out.address
398                    );
399                    Ok(())
400                } else {
401                    warn!(
402                        "USB write incomplete: {} of {} bytes transferred (possible timeout)",
403                        n,
404                        buf.len()
405                    );
406                    Err(Error::InvalidResponse(n))
407                }
408            }
409            Err(e) => Err(Error::UsbError(e)),
410        }
411    }
412
413    fn read_status(&self) -> Result<Status, Error> {
414        self.read_status_with_timeout(Duration::from_millis(1000))
415    }
416
417    fn read_status_with_timeout(&self, timeout: Duration) -> Result<Status, Error> {
418        let mut buf: [u8; 32] = [0x00; 32];
419        let mut counter = 0;
420
421        debug!("reading from endpoint_in {:#?}", self.endpoint_in);
422        while counter < 100000 {
423            match self
424                .handle
425                .read_bulk(self.endpoint_in.address, &mut buf, timeout)
426            {
427                // TODO: Check the first 4bytes match to [0x80, 0x20, 0x42, 0x34]
428                // TODO: Check the error status
429                //
430                // buf is pouplated with 32 bytes of data
431                Ok(32) => {
432                    let status = Status::from_buf(buf);
433                    debug!("Raw status code: {:X?}", buf);
434                    debug!("Parsed Status struct: {:?}", status);
435                    return Ok(status);
436                }
437                Ok(x) => {
438                    debug!("Waiting {counter} {x}");
439                    std::thread::sleep(std::time::Duration::from_millis(50));
440                }
441                Err(e) => return Err(Error::UsbError(e)),
442            };
443            counter = counter + 1;
444        }
445        Err(Error::ReadStatusTimeout)
446    }
447
448    fn wait_for_print_completion(&self) -> Result<(), Error> {
449        let mut attempts = 0;
450        const MAX_ATTEMPTS: u32 = 100; // 約5秒のタイムアウト
451
452        debug!("Waiting for print completion...");
453
454        loop {
455            let status = self.read_status_with_timeout(Duration::from_millis(1000))?;
456            debug!(
457                "Print completion check: status_type={:?}, phase={:?}, error={:?}",
458                status.status_type, status.phase, status.error
459            );
460
461            // エラー状態の即座検出
462            if !status.error.is_no_error() {
463                error!("Print operation failed: {:?}", status.error);
464                return Err(Error::PrinterError(status.error));
465            }
466
467            match (status.status_type, status.phase) {
468                // エラー状態の即座検出
469                (StatusType::Error, _) => {
470                    error!("Printer reported error status");
471                    return Err(Error::PrinterError(status.error));
472                }
473
474                // 印刷完了 -> 受信待機への遷移を待つ
475                (StatusType::Completed, Phase::Printing) => {
476                    info!("Print finished, verifying printer state");
477                    // 完了後、受信状態への遷移を確認
478                    std::thread::sleep(Duration::from_millis(100));
479                    let final_status = self.read_status_with_timeout(Duration::from_millis(500))?;
480                    if matches!(final_status.phase, Phase::Receiving) {
481                        info!("Print completed, printer ready for next job");
482                        return Ok(());
483                    }
484                    debug!(
485                        "Still waiting for transition to receiving state, current phase: {:?}",
486                        final_status.phase
487                    );
488                }
489
490                // 既に受信状態に戻っている(即座完了)
491                (StatusType::PhaseChange, Phase::Receiving) => {
492                    info!("Printer ready (already in receiving state)");
493                    return Ok(());
494                }
495
496                // まだ印刷中
497                (StatusType::PhaseChange, Phase::Printing) => {
498                    debug!("Print in progress, continuing to monitor");
499                    // 短い待機で継続監視
500                    std::thread::sleep(Duration::from_millis(50));
501                }
502
503                // 予期しない状態
504                _ => {
505                    debug!("Unexpected status during print completion: {:#?}", status);
506                    std::thread::sleep(Duration::from_millis(100));
507                }
508            }
509
510            attempts += 1;
511            if attempts >= MAX_ATTEMPTS {
512                error!(
513                    "Print completion timed out after {} attempts ({}s)",
514                    attempts,
515                    attempts * 50 / 1000
516                );
517                return Err(Error::PrintTimeout);
518            }
519        }
520    }
521
522    fn initialize(&self) -> Vec<u8> {
523        let mut buf: Vec<u8> = Vec::new();
524        buf.append(&mut [0x00; 400].to_vec());
525        buf.append(&mut [0x1B, 0x40].to_vec());
526        buf
527    }
528
529    fn set_media(&self, buf: &mut std::vec::Vec<u8>, raster_count: u32) {
530        buf.extend_from_slice(&[0x1B, 0x69, 0x7A]); // ESC i z
531
532        // n1: 有効フラグ (用紙種類+幅+長さ+ラスター数)
533        let valid_flags = 0x02 | 0x04 | 0x08 | 0x40;
534        buf.push(valid_flags);
535
536        // n2: 用紙種類 (長尺:0x0A, ダイカット:0x0C)
537        let media_type = match self.config.media {
538            Media::Continuous(_) => 0x0A,
539            Media::DieCut(_) => 0x0B,
540        };
541        buf.push(media_type);
542
543        // n3, n4: 用紙幅・長さ (mm)
544        let spec = self.config.media.spec();
545        buf.push(spec.width_mm());
546        buf.push(spec.length_mm());
547
548        // n5-n8: ラスター数 (リトルエンディアン)
549        let raster_bytes = raster_count.to_le_bytes();
550        buf.extend_from_slice(&raster_bytes);
551
552        // n9: 先頭ページフラグ (0=先頭ページ)
553        buf.push(0x00);
554
555        // n10: 固定値
556        buf.push(0x00);
557    }
558
559    fn print_label(&self, images: impl Iterator<Item = Matrix>) -> Result<(), Error> {
560        let mut preamble: Vec<u8> = self.initialize();
561        preamble.append(&mut [0x1B, 0x69, 0x61, 0x01].to_vec()); // Set raster command mode
562        preamble.append(&mut [0x1B, 0x69, 0x21, 0x00].to_vec()); // Set auto status notificatoin mode
563                                                                 //
564                                                                 // Apply config values
565        match self.config.clone().build() {
566            Ok(mut buf) => preamble.append(&mut buf),
567            Err(err) => return Err(err),
568        }
569
570        // QL-800では圧縮モードがサポートされていないため、常に非圧縮とする
571        let use_compression = if matches!(self.config.model, Model::QL800) && self.config.compress {
572            warn!("QL-800 does not support compression mode, using uncompressed mode instead");
573            false
574        } else {
575            self.config.compress
576        };
577        
578        if use_compression {
579            preamble.append(&mut [0x4D, 0x02].to_vec()); // Set to pack bits compression mode
580        } else {
581            preamble.append(&mut [0x4D, 0x00].to_vec()); // Set to no compression mode
582        }
583
584        debug!("{:?}", self.config);
585
586        let mut start_flag: bool = true;
587        let mut color = false;
588
589        let mut iter = images.into_iter().peekable();
590
591        loop {
592            let mut buf: Vec<u8> = Vec::new();
593
594            match iter.next() {
595                Some(image) => {
596                    if start_flag {
597                        buf.append(&mut preamble);
598                    }
599
600                    // ESC i z 印刷情報司令
601                    let raster_count = if self.config.two_colors {
602                        (image.len() / 2) as u32
603                    } else {
604                        image.len() as u32
605                    };
606                    self.set_media(&mut buf, raster_count);
607                    if start_flag {
608                        buf.append(&mut [0x00, 0x00].to_vec());
609                        start_flag = false;
610                    } else {
611                        buf.append(&mut [0x01, 0x00].to_vec());
612                    }
613
614                    // Add raster line image data
615                    if self.config.two_colors {
616                        for mut row in image {
617                            if color {
618                                // Black raster line (color code 0x01)
619                                buf.append(&mut [0x77, 0x01, 90].to_vec());
620                                buf.append(&mut row);
621                                color = !color;
622                            } else {
623                                // Red raster line (color code 0x02)
624                                buf.append(&mut [0x77, 0x02, 90].to_vec());
625                                buf.append(&mut row);
626                                color = !color;
627                            }
628                        }
629                    } else {
630                        if use_compression {
631                            for row in image {
632                                let mut packed = Self::pack_bits(&row);
633                                let len = packed.len() as u8;
634                                buf.append(&mut [0x67, 0x00, len].to_vec());
635                                buf.append(&mut packed);
636                            }
637                        } else {
638                            for mut row in image {
639                                buf.append(&mut [0x67, 0x00, 90].to_vec());
640                                buf.append(&mut row);
641                            }
642                        }
643                    }
644
645                    if iter.peek().is_some() {
646                        buf.push(0x0C); // FF : Print
647                        self.write(buf)?;
648                        info!("Print command sent, waiting for completion...");
649
650                        // 改善されたステータス待機(中間ページ)
651                        self.wait_for_print_completion()?;
652                        info!("Page printed successfully");
653                    } else {
654                        buf.push(0x1A); // Control-Z : Print then Eject
655                        self.write(buf)?;
656                        info!("Final print command sent, ejecting media...");
657
658                        // 改善されたステータス待機
659                        self.wait_for_print_completion()?;
660                        info!("Print job completed successfully");
661
662                        self.invalidate()?;
663                    }
664                }
665                None => {
666                    break;
667                }
668            }
669        }
670        Ok(())
671    }
672
673    /// TIFF PackBits圧縮アルゴリズム(Brother QL仕様準拠)
674    ///
675    /// 仕様:
676    /// - 同一データ連続:個数-1を負数で指定 + データ1バイト
677    /// - 異なるデータ連続:個数-1を正数で指定 + 全データ
678    /// - 90バイト超過時は非圧縮として91バイト送信
679    fn pack_bits(data: &[u8]) -> Vec<u8> {
680        // 入力データが90バイト固定でない場合はそのまま返す
681        if data.len() != 90 {
682            return data.to_vec();
683        }
684
685        let mut packed = Vec::new();
686        let mut i = 0;
687
688        while i < data.len() {
689            // Run-length encoding (RLE)のチェック
690            let mut run_length = 1;
691            let run_value = data[i];
692
693            // 同じ値の連続をカウント(最大128個まで)
694            while i + run_length < data.len()
695                && run_length < 128
696                && data[i + run_length] == run_value
697            {
698                run_length += 1;
699            }
700
701            // RLEが効果的な場合(2個以上の連続)
702            if run_length >= 2 {
703                // 負数で圧縮指示: -(count-1)
704                packed.push((-(run_length as i8 - 1)) as u8);
705                packed.push(run_value);
706                i += run_length;
707            } else {
708                // リテラル実行のチェック
709                let start_pos = i;
710                let mut literal_length = 1;
711
712                // リテラル実行の最適な長さを決定
713                while i + literal_length < data.len() && literal_length < 128 {
714                    // 次の位置で2個以上同じ値が続く場合は、ここでリテラル実行を終了
715                    if i + literal_length + 1 < data.len()
716                        && data[i + literal_length] == data[i + literal_length + 1]
717                    {
718                        break;
719                    }
720                    literal_length += 1;
721                }
722
723                // リテラル実行: 正数で非圧縮指示
724                packed.push((literal_length - 1) as u8);
725                packed.extend_from_slice(&data[start_pos..start_pos + literal_length]);
726                i += literal_length;
727            }
728        }
729
730        // 重要な最適化: 90バイト超過時は非圧縮として91バイト返す
731        if packed.len() > 90 {
732            warn!(
733                "Data compression ineffective, sending uncompressed ({} bytes)",
734                data.len()
735            );
736            let mut result = Vec::with_capacity(91);
737            result.push(89); // 90-1 = 89(90バイトの非圧縮指示)
738            result.extend_from_slice(data);
739            result
740        } else {
741            debug!(
742                "Compression reduced data from {} to {} bytes ({:.1}% reduction)",
743                data.len(),
744                packed.len(),
745                (1.0 - packed.len() as f64 / data.len() as f64) * 100.0
746            );
747            packed
748        }
749    }
750
751    fn request_status(&self) -> Result<(), Error> {
752        let mut buf: Vec<u8> = self.initialize();
753        buf.append(&mut [0x1b, 0x69, 0x53].to_vec());
754        self.write(buf)
755    }
756
757    fn invalidate(&self) -> Result<(), Error> {
758        let buf: Vec<u8> = self.initialize();
759        self.write(buf)
760    }
761}
762
763#[cfg(test)]
764mod tests {
765    use super::*;
766
767    #[test]
768    fn test_pack_bits_compression() {
769        // テスト1: 効果的な圧縮(同一データ連続)
770        let all_zeros = vec![0u8; 90];
771        let compressed = Printer::pack_bits(&all_zeros);
772        println!(
773            "All zeros: {} -> {} bytes",
774            all_zeros.len(),
775            compressed.len()
776        );
777        assert!(compressed.len() < all_zeros.len(), "圧縮が効果的でない");
778
779        // テスト2: 非効果的な圧縮(ランダムデータ)
780        let random_data: Vec<u8> = (0..90).map(|i| (i * 37 + 17) as u8).collect();
781        let compressed_random = Printer::pack_bits(&random_data);
782        println!(
783            "Random data: {} -> {} bytes",
784            random_data.len(),
785            compressed_random.len()
786        );
787
788        // テスト3: 91バイト制限の確認
789        if compressed_random.len() > 90 {
790            println!("91バイト制限により非圧縮データが返される");
791            assert_eq!(compressed_random.len(), 91); // 89 + 90バイトの元データ
792            assert_eq!(compressed_random[0], 89); // 非圧縮指示
793        }
794
795        // テスト4: 混合パターン(部分的な圧縮効果)
796        let mut mixed_data = vec![0u8; 30];
797        mixed_data.extend(vec![255u8; 30]);
798        mixed_data.extend((0..30).map(|i| i as u8));
799        let compressed_mixed = Printer::pack_bits(&mixed_data);
800        println!(
801            "Mixed data: {} -> {} bytes",
802            mixed_data.len(),
803            compressed_mixed.len()
804        );
805    }
806
807    #[test]
808    fn test_pack_bits_edge_cases() {
809        // エッジケース1: 空のデータ
810        let empty_data = vec![];
811        let compressed_empty = Printer::pack_bits(&empty_data);
812        assert_eq!(compressed_empty, empty_data);
813
814        // エッジケース2: 90バイト以外のサイズ
815        let wrong_size = vec![42u8; 50];
816        let compressed_wrong = Printer::pack_bits(&wrong_size);
817        assert_eq!(compressed_wrong, wrong_size);
818
819        // エッジケース3: 単一バイトの繰り返し(最大圧縮)
820        let single_byte = vec![42u8; 90];
821        let compressed_single = Printer::pack_bits(&single_byte);
822        assert_eq!(compressed_single.len(), 2); // 長さ指示 + データ
823        assert_eq!(compressed_single[0], (-(90i8 - 1)) as u8); // -89
824        assert_eq!(compressed_single[1], 42);
825    }
826}
827
828///
829/// Status received from the printer encoded to Rust friendly type.
830///
831#[derive(Debug)]
832#[allow(dead_code)]
833pub struct Status {
834    model: Model,
835    error: PrinterError,
836    media: Option<Media>,
837    mode: u8,
838    status_type: StatusType,
839    phase: Phase,
840    notification: Notification,
841    id: u8,
842}
843
844impl Status {
845    fn from_buf(buf: [u8; 32]) -> Self {
846        Status {
847            model: Model::from_code(buf[4]),
848            error: PrinterError::from_buf(buf),
849            media: Media::from_buf(buf),
850            mode: buf[15],
851            status_type: StatusType::from_code(buf[18]),
852            phase: Phase::from_buf(buf),
853            notification: Notification::from_code(buf[22]),
854            id: buf[14],
855        }
856    }
857
858    pub fn check_media(self, expected_media: Media) -> Result<(), Error> {
859        match self.media {
860            Some(actual_media) => {
861                if actual_media == expected_media {
862                    Ok(())
863                } else {
864                    Err(Error::MediaMismatch {
865                        expected: expected_media,
866                        actual: actual_media,
867                    })
868                }
869            }
870            None => Err(Error::NoMediaInstalled),
871        }
872    }
873}
874
875// StatusType
876
877#[derive(Debug, PartialEq, Clone, Copy)]
878enum StatusType {
879    ReplyToRequest,
880    Completed,
881    Error,
882    Offline,
883    Notification,
884    PhaseChange,
885    Unknown,
886}
887
888impl StatusType {
889    fn from_code(code: u8) -> StatusType {
890        match code {
891            0x00 => Self::ReplyToRequest,
892            0x01 => Self::Completed,
893            0x02 => Self::Error,
894            0x04 => Self::Offline,
895            0x05 => Self::Notification,
896            0x06 => Self::PhaseChange,
897            _ => Self::Unknown,
898        }
899    }
900}
901// Phase
902
903#[derive(Debug, PartialEq, Clone, Copy)]
904pub enum Phase {
905    Receiving,
906    Printing,
907    Waiting(u16),
908    // Printing(u16),
909}
910
911impl Phase {
912    fn from_buf(buf: [u8; 32]) -> Self {
913        match buf[19] {
914            0x00 => Self::Receiving,
915            0x01 => Self::Printing,
916            _ => Self::Waiting(0),
917        }
918    }
919}
920
921// Notification
922
923#[derive(Debug)]
924enum Notification {
925    NotAvailable,
926    CoolingStarted,
927    CoolingFinished,
928}
929
930impl Notification {
931    fn from_code(code: u8) -> Self {
932        match code {
933            0x03 => Self::CoolingStarted,
934            0x04 => Self::CoolingFinished,
935            _ => Self::NotAvailable,
936        }
937    }
938}
939
940/// Config
941///
942#[derive(Debug, Clone, Copy)]
943enum AutoCut {
944    Enabled(u8),
945    Disabled,
946}
947
948#[derive(Debug, Clone)]
949pub struct Config {
950    model: Model,
951    serial: String,
952    media: Media,
953    auto_cut: AutoCut,
954    two_colors: bool,
955    cut_at_end: bool,
956    high_resolution: bool,
957    feed: u16,
958    compress: bool,
959}
960
961impl Config {
962    /// Initialize configuration data with default values.
963    ///
964    /// This method receives model and media.  They are not modifiable after the initialization.
965    ///
966    /// # Example
967    ///
968    /// ```rust,no_run
969    /// use ptouch::{Config, ContinuousType, Media, Model};
970    /// 
971    /// let media = Media::Continuous(ContinuousType::Continuous29);
972    /// let model = Model::QL800;
973    /// let config = Config::new(model, "serial".to_string(), media);
974    /// ```
975    ///
976    pub fn new(model: Model, serial: String, media: Media) -> Config {
977        Config {
978            model,
979            serial,
980            media,
981            auto_cut: AutoCut::Enabled(1),
982            two_colors: false,
983            cut_at_end: true,
984            high_resolution: false,
985            feed: media.get_default_feed_dots(),
986            compress: false,
987        }
988    }
989
990    /// Enable auto cut after printing specified number of labels.
991    ///
992    /// # Arguments
993    /// * `size` - Number of labels to print before auto-cutting (1-255)
994    ///
995    /// # Example
996    /// ```rust,no_run
997    /// # use ptouch::{Config, Model, Media, ContinuousType};
998    /// let config = Config::new(Model::QL820NWB, "serial".to_string(), 
999    ///                         Media::Continuous(ContinuousType::Continuous62))
1000    ///     .enable_auto_cut(3); // Cut after every 3 labels
1001    /// ```
1002    pub fn enable_auto_cut(self, size: u8) -> Self {
1003        Config {
1004            auto_cut: AutoCut::Enabled(size),
1005            ..self
1006        }
1007    }
1008
1009    /// Disable automatic cutting of labels.
1010    ///
1011    /// When disabled, labels will need to be manually torn or cut.
1012    ///
1013    /// # Example
1014    /// ```rust,no_run
1015    /// # use ptouch::{Config, Model, Media, ContinuousType};
1016    /// let config = Config::new(Model::QL820NWB, "serial".to_string(), 
1017    ///                         Media::Continuous(ContinuousType::Continuous62))
1018    ///     .disable_auto_cut();
1019    /// ```
1020    pub fn disable_auto_cut(self) -> Self {
1021        Config {
1022            auto_cut: AutoCut::Disabled,
1023            ..self
1024        }
1025    }
1026
1027    /// Control whether to cut the tape at the end of a print job.
1028    ///
1029    /// # Arguments
1030    /// * `flag` - `true` to cut at end, `false` to leave uncut
1031    ///
1032    /// # Example
1033    /// ```rust,no_run
1034    /// # use ptouch::{Config, Model, Media, ContinuousType};
1035    /// let config = Config::new(Model::QL820NWB, "serial".to_string(), 
1036    ///                         Media::Continuous(ContinuousType::Continuous62))
1037    ///     .cut_at_end(true); // Cut at the end of job
1038    /// ```
1039    pub fn cut_at_end(self, flag: bool) -> Self {
1040        Config {
1041            cut_at_end: flag,
1042            ..self
1043        }
1044    }
1045
1046    /// Enable or disable high resolution printing.
1047    ///
1048    /// High resolution doubles the vertical resolution from 300 DPI to 600 DPI.
1049    /// When enabled, image height should be doubled accordingly.
1050    ///
1051    /// # Arguments
1052    /// * `high` - `true` for 600 DPI, `false` for 300 DPI
1053    ///
1054    /// # Example
1055    /// ```rust,no_run
1056    /// # use ptouch::{Config, Model, Media, ContinuousType};
1057    /// let config = Config::new(Model::QL820NWB, "serial".to_string(), 
1058    ///                         Media::Continuous(ContinuousType::Continuous62))
1059    ///     .high_resolution(true); // Enable 600 DPI
1060    /// ```
1061    pub fn high_resolution(self, high: bool) -> Self {
1062        Config {
1063            high_resolution: high,
1064            ..self
1065        }
1066    }
1067
1068    /// Set the feeding length in dots.
1069    ///
1070    /// Controls how much tape is fed before printing starts.
1071    /// Different media types have different valid ranges.
1072    ///
1073    /// # Arguments
1074    /// * `feed` - Feed length in dots
1075    ///
1076    /// # Example
1077    /// ```rust,no_run
1078    /// # use ptouch::{Config, Model, Media, ContinuousType};
1079    /// let config = Config::new(Model::QL820NWB, "serial".to_string(), 
1080    ///                         Media::Continuous(ContinuousType::Continuous62))
1081    ///     .set_feed_in_dots(150); // Set feed to 150 dots
1082    /// ```
1083    pub fn set_feed_in_dots(self, feed: u16) -> Self {
1084        Config { feed, ..self }
1085    }
1086
1087    /// Enable or disable two-color printing (black and red).
1088    ///
1089    /// Only supported on QL-820NWB with compatible red/black tape.
1090    /// When enabled, use `print_two_color()` method instead of `print()`.
1091    ///
1092    /// # Arguments
1093    /// * `two_colors` - `true` to enable two-color printing
1094    ///
1095    /// # Example
1096    /// ```rust,no_run
1097    /// # use ptouch::{Config, Model, Media, ContinuousType};
1098    /// let config = Config::new(Model::QL820NWB, "serial".to_string(), 
1099    ///                         Media::Continuous(ContinuousType::Continuous62Red))
1100    ///     .two_colors(true); // Enable red and black printing
1101    /// ```
1102    pub fn two_colors(self, two_colors: bool) -> Self {
1103        Config { two_colors, ..self }
1104    }
1105
1106    /// Enable or disable data compression.
1107    ///
1108    /// Uses PackBits compression to reduce USB transfer size.
1109    /// Automatically disabled for QL-800 model due to hardware limitations.
1110    ///
1111    /// # Arguments
1112    /// * `flag` - `true` to enable compression
1113    ///
1114    /// # Example
1115    /// ```rust,no_run
1116    /// # use ptouch::{Config, Model, Media, ContinuousType};
1117    /// let config = Config::new(Model::QL820NWB, "serial".to_string(), 
1118    ///                         Media::Continuous(ContinuousType::Continuous62))
1119    ///     .compress(true); // Enable compression
1120    /// ```
1121    pub fn compress(self, flag: bool) -> Self {
1122        Config {
1123            compress: flag,
1124            ..self
1125        }
1126    }
1127
1128    fn build(self) -> Result<Vec<u8>, Error> {
1129        let mut buf: Vec<u8> = Vec::new();
1130
1131        // Set feeding values in dots
1132        {
1133            match self.media.check_feed_value(self.feed) {
1134                Ok(feed) => {
1135                    buf.append(&mut [0x1B, 0x69, 0x64].to_vec());
1136                    buf.append(&mut feed.to_vec());
1137                }
1138                Err(msg) => return Err(Error::InvalidConfig(msg)),
1139            }
1140        }
1141        // Set auto cut settings
1142        {
1143            let mut various_mode: u8 = 0b0000_0000;
1144            let mut auto_cut_num: u8 = 1;
1145
1146            if let AutoCut::Enabled(n) = self.auto_cut {
1147                various_mode = various_mode | 0b0100_0000;
1148                auto_cut_num = n;
1149            }
1150
1151            debug!("Auto-cut mode configured: {:#04x}", various_mode);
1152            debug!("Auto-cut frequency: {} pages", auto_cut_num);
1153
1154            buf.append(&mut [0x1B, 0x69, 0x4D, various_mode].to_vec()); // ESC i M : Set various mode
1155            buf.append(&mut [0x1B, 0x69, 0x41, auto_cut_num].to_vec()); // ESC i A : Set auto cut number
1156        }
1157        // Set expanded mode
1158        {
1159            let mut expanded_mode: u8 = 0b00000000;
1160
1161            if self.two_colors {
1162                expanded_mode = expanded_mode | 0b0000_0001;
1163            }
1164
1165            if self.cut_at_end {
1166                expanded_mode = expanded_mode | 0b0000_1000;
1167            };
1168
1169            if self.high_resolution {
1170                expanded_mode = expanded_mode | 0b0100_0000;
1171            }
1172
1173            debug!("Print mode settings: {:#04x}", expanded_mode);
1174
1175            buf.append(&mut [0x1B, 0x69, 0x4B, expanded_mode].to_vec()); // ESC i K : Set expanded mode
1176        }
1177        Ok(buf)
1178    }
1179}