@amitkhare/capacitor-cat-printer 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -48,10 +48,13 @@ public class CatPrinterCore {
48
48
 
49
49
  private String connectedAddress;
50
50
  private int paperWidth = PrinterProtocol.WIDTH_58MM;
51
+ private boolean dryRunMode = false;
52
+ private int connectionTimeout = 10000; // Default 10 seconds
51
53
 
52
54
  private final AtomicBoolean isScanning = new AtomicBoolean(false);
53
55
  private final AtomicBoolean isPaused = new AtomicBoolean(false);
54
56
  private final AtomicBoolean isPrinting = new AtomicBoolean(false);
57
+ private final AtomicBoolean isCancelled = new AtomicBoolean(false);
55
58
 
56
59
  private final ConcurrentLinkedQueue<byte[]> writeQueue = new ConcurrentLinkedQueue<>();
57
60
  private final AtomicBoolean isWriting = new AtomicBoolean(false);
@@ -247,9 +250,21 @@ public class CatPrinterCore {
247
250
 
248
251
  // ==================== CONNECTION ====================
249
252
 
250
- public void connect(String address, int width, ConnectionCallback callback) {
253
+ private Runnable connectionTimeoutRunnable;
254
+
255
+ public void connect(String address, int width, int timeout, boolean dryRun, ConnectionCallback callback) {
251
256
  this.connectionCallback = callback;
252
257
  this.paperWidth = width;
258
+ this.connectionTimeout = timeout;
259
+ this.dryRunMode = dryRun;
260
+
261
+ // In dry-run mode, simulate connection
262
+ if (dryRun) {
263
+ Log.d(TAG, "Dry-run mode: simulating connection to " + address);
264
+ connectedAddress = address;
265
+ mainHandler.post(() -> callback.onConnected());
266
+ return;
267
+ }
253
268
 
254
269
  if (bluetoothAdapter == null) {
255
270
  callback.onError("Bluetooth not available");
@@ -267,10 +282,31 @@ public class CatPrinterCore {
267
282
  return;
268
283
  }
269
284
 
285
+ // Set up connection timeout
286
+ connectionTimeoutRunnable = () -> {
287
+ if (gatt != null && txCharacteristic == null) {
288
+ Log.e(TAG, "Connection timeout after " + timeout + "ms");
289
+ disconnect();
290
+ mainHandler.post(() -> callback.onError("Connection timeout"));
291
+ }
292
+ };
293
+ mainHandler.postDelayed(connectionTimeoutRunnable, timeout);
294
+
270
295
  gatt = device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE);
271
296
  }
272
297
 
298
+ // Overload for backward compatibility
299
+ public void connect(String address, int width, ConnectionCallback callback) {
300
+ connect(address, width, 10000, false, callback);
301
+ }
302
+
273
303
  public void disconnect() {
304
+ // Cancel connection timeout if pending
305
+ if (connectionTimeoutRunnable != null) {
306
+ mainHandler.removeCallbacks(connectionTimeoutRunnable);
307
+ connectionTimeoutRunnable = null;
308
+ }
309
+
274
310
  if (gatt != null) {
275
311
  gatt.disconnect();
276
312
  gatt.close();
@@ -278,14 +314,23 @@ public class CatPrinterCore {
278
314
  }
279
315
  txCharacteristic = null;
280
316
  connectedAddress = null;
317
+ dryRunMode = false;
281
318
  isPaused.set(false);
282
319
  writeQueue.clear();
283
320
  }
284
321
 
285
322
  public boolean isConnected() {
323
+ // In dry-run mode, we're always "connected" if address is set
324
+ if (dryRunMode && connectedAddress != null) {
325
+ return true;
326
+ }
286
327
  return gatt != null && txCharacteristic != null;
287
328
  }
288
329
 
330
+ public boolean isDryRunMode() {
331
+ return dryRunMode;
332
+ }
333
+
289
334
  public String getConnectedAddress() {
290
335
  return connectedAddress;
291
336
  }
@@ -319,6 +364,12 @@ public class CatPrinterCore {
319
364
  BluetoothGattCharacteristic rx = service.getCharacteristic(PrinterProtocol.RX_CHAR_UUID);
320
365
 
321
366
  if (tx != null && rx != null) {
367
+ // Cancel connection timeout
368
+ if (connectionTimeoutRunnable != null) {
369
+ mainHandler.removeCallbacks(connectionTimeoutRunnable);
370
+ connectionTimeoutRunnable = null;
371
+ }
372
+
322
373
  txCharacteristic = tx;
323
374
  connectedAddress = gatt.getDevice().getAddress();
324
375
 
@@ -390,6 +441,13 @@ public class CatPrinterCore {
390
441
  }
391
442
 
392
443
  private void processWriteQueue() {
444
+ // Check if cancelled - stop processing
445
+ if (isCancelled.get()) {
446
+ writeQueue.clear();
447
+ isWriting.set(false);
448
+ return;
449
+ }
450
+
393
451
  if (isPaused.get() || isWriting.get() || writeQueue.isEmpty()) {
394
452
  return;
395
453
  }
@@ -423,6 +481,12 @@ public class CatPrinterCore {
423
481
  int sleepInterval = 50;
424
482
 
425
483
  while ((!writeQueue.isEmpty() || isWriting.get()) && waited < maxWaitMs) {
484
+ // Check if cancelled - stop waiting
485
+ if (isCancelled.get()) {
486
+ writeQueue.clear();
487
+ return;
488
+ }
489
+
426
490
  try {
427
491
  Thread.sleep(sleepInterval);
428
492
  waited += sleepInterval;
@@ -445,7 +509,9 @@ public class CatPrinterCore {
445
509
  // ==================== PRINTING ====================
446
510
 
447
511
  public void printImage(String base64Image, float energy, int quality,
448
- int feedAfter, int threshold, boolean dither, PrintCallback callback) {
512
+ int feedAfter, int threshold, boolean dither,
513
+ boolean flipH, boolean flipV, boolean compress,
514
+ PrintCallback callback) {
449
515
  this.printCallback = callback;
450
516
 
451
517
  if (!isConnected()) {
@@ -459,11 +525,18 @@ public class CatPrinterCore {
459
525
  }
460
526
 
461
527
  isPrinting.set(true);
528
+ isCancelled.set(false); // Reset cancel flag
462
529
 
463
530
  new Thread(() -> {
464
531
  try {
465
532
  reportProgress(0, "processing", "Decoding image");
466
533
 
534
+ // Check for cancellation
535
+ if (isCancelled.get()) {
536
+ handleCancellation();
537
+ return;
538
+ }
539
+
467
540
  // Decode base64 image
468
541
  byte[] imageBytes = Base64.decode(base64Image, Base64.DEFAULT);
469
542
  Bitmap bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
@@ -476,6 +549,12 @@ public class CatPrinterCore {
476
549
 
477
550
  reportProgress(10, "processing", "Resizing image");
478
551
 
552
+ // Check for cancellation
553
+ if (isCancelled.get()) {
554
+ handleCancellation();
555
+ return;
556
+ }
557
+
479
558
  // Resize to paper width
480
559
  bitmap = ImageProcessor.resizeToWidth(bitmap, paperWidth);
481
560
 
@@ -492,12 +571,22 @@ public class CatPrinterCore {
492
571
  }
493
572
  int height = bitmap.getHeight();
494
573
 
495
- Log.d(TAG, "Printing " + height + " lines");
574
+ // Apply flipping if requested
575
+ if (flipH) {
576
+ reportProgress(25, "processing", "Flipping horizontally");
577
+ ImageProcessor.flipHorizontal(monoData, paperWidth, height);
578
+ }
579
+ if (flipV) {
580
+ reportProgress(27, "processing", "Flipping vertically");
581
+ ImageProcessor.flipVertical(monoData, paperWidth, height);
582
+ }
583
+
584
+ Log.d(TAG, "Printing " + height + " lines" + (dryRunMode ? " (DRY RUN)" : ""));
496
585
 
497
586
  bitmap.recycle();
498
587
 
499
- // Print the bitmap
500
- printBitmapData(monoData, paperWidth, height, energy, quality, feedAfter);
588
+ // Print the bitmap (with compression flag for newer printers)
589
+ printBitmapData(monoData, paperWidth, height, energy, quality, feedAfter, compress);
501
590
 
502
591
  } catch (Exception e) {
503
592
  Log.e(TAG, "Print error", e);
@@ -507,9 +596,16 @@ public class CatPrinterCore {
507
596
  }).start();
508
597
  }
509
598
 
599
+ // Overload for backward compatibility
600
+ public void printImage(String base64Image, float energy, int quality,
601
+ int feedAfter, int threshold, boolean dither, PrintCallback callback) {
602
+ printImage(base64Image, energy, quality, feedAfter, threshold, dither, false, false, false, callback);
603
+ }
604
+
510
605
  public void printText(String text, int fontSize, String align, boolean bold,
511
606
  float lineSpacing, float energy, int quality,
512
- int feedAfter, PrintCallback callback) {
607
+ int feedAfter, boolean wordWrap, boolean rtl,
608
+ PrintCallback callback) {
513
609
  this.printCallback = callback;
514
610
 
515
611
  if (!isConnected()) {
@@ -523,14 +619,21 @@ public class CatPrinterCore {
523
619
  }
524
620
 
525
621
  isPrinting.set(true);
622
+ isCancelled.set(false); // Reset cancel flag
526
623
 
527
624
  new Thread(() -> {
528
625
  try {
529
626
  reportProgress(0, "processing", "Rendering text");
530
627
 
531
- // Render text to bitmap
628
+ // Check for cancellation
629
+ if (isCancelled.get()) {
630
+ handleCancellation();
631
+ return;
632
+ }
633
+
634
+ // Render text to bitmap with word wrap and RTL support
532
635
  Bitmap bitmap = ImageProcessor.renderText(text, paperWidth, fontSize,
533
- align, bold, lineSpacing);
636
+ align, bold, lineSpacing, wordWrap, rtl);
534
637
 
535
638
  reportProgress(20, "processing", "Converting to monochrome");
536
639
 
@@ -538,10 +641,12 @@ public class CatPrinterCore {
538
641
  byte[] monoData = ImageProcessor.toMonochrome(bitmap, 127);
539
642
  int height = bitmap.getHeight();
540
643
 
644
+ Log.d(TAG, "Printing text: " + height + " lines" + (dryRunMode ? " (DRY RUN)" : ""));
645
+
541
646
  bitmap.recycle();
542
647
 
543
648
  // Print the bitmap
544
- printBitmapData(monoData, paperWidth, height, energy, quality, feedAfter);
649
+ printBitmapData(monoData, paperWidth, height, energy, quality, feedAfter, false);
545
650
 
546
651
  } catch (Exception e) {
547
652
  Log.e(TAG, "Print error", e);
@@ -551,13 +656,49 @@ public class CatPrinterCore {
551
656
  }).start();
552
657
  }
553
658
 
659
+ // Overload for backward compatibility
660
+ public void printText(String text, int fontSize, String align, boolean bold,
661
+ float lineSpacing, float energy, int quality,
662
+ int feedAfter, PrintCallback callback) {
663
+ printText(text, fontSize, align, bold, lineSpacing, energy, quality, feedAfter, true, false, callback);
664
+ }
665
+
554
666
  private void printBitmapData(byte[] data, int width, int height,
555
- float energy, int quality, int feedAfter) {
667
+ float energy, int quality, int feedAfter, boolean compress) {
556
668
  try {
557
669
  int bytesPerLine = width / 8;
558
670
  int totalLines = height;
559
671
 
560
- reportProgress(30, "printing", "Initializing printer");
672
+ reportProgress(30, "printing", dryRunMode ? "Simulating print..." : "Initializing printer");
673
+
674
+ // In dry-run mode, simulate the print process without sending data
675
+ if (dryRunMode) {
676
+ for (int line = 0; line < totalLines; line++) {
677
+ // Check for cancellation
678
+ if (isCancelled.get()) {
679
+ handleCancellation();
680
+ return;
681
+ }
682
+
683
+ // Simulate processing time
684
+ if (line % 100 == 0) {
685
+ Thread.sleep(10);
686
+ int progress = 30 + (int) ((line / (float) totalLines) * 60);
687
+ reportProgress(progress, "printing", "Simulating line " + line + "/" + totalLines);
688
+ }
689
+ }
690
+
691
+ reportProgress(90, "feeding", "Simulation complete");
692
+ Thread.sleep(100);
693
+
694
+ isPrinting.set(false);
695
+ reportProgress(100, "done", "Dry run complete");
696
+
697
+ if (printCallback != null) {
698
+ mainHandler.post(() -> printCallback.onComplete());
699
+ }
700
+ return;
701
+ }
561
702
 
562
703
  // Calculate energy and speed values
563
704
  // Reference: energy = int(args.energy * 0xffff)
@@ -565,12 +706,21 @@ public class CatPrinterCore {
565
706
  // Reference: speed = 4 * (quality + 5), quality 1-4 -> speed 24-36
566
707
  int speedValue = 4 * (quality + 5);
567
708
 
709
+ // Check if this is a new-kind printer that supports compression
710
+ String model = PrinterProtocol.detectModel(getConnectedDeviceName());
711
+ boolean isNewKind = PrinterProtocol.isNewKind(model);
712
+ boolean useCompression = compress && isNewKind;
713
+
568
714
  // === PREPARE PHASE (matches reference _prepare method) ===
569
715
  // 1. Get device state
570
716
  queueWrite(PrinterProtocol.cmdGetDeviceState());
571
717
 
572
- // 2. Start printing (use standard command, not new)
573
- queueWrite(PrinterProtocol.cmdStartPrinting());
718
+ // 2. Start printing (use new command for new-kind printers)
719
+ if (isNewKind) {
720
+ queueWrite(PrinterProtocol.cmdStartPrintingNew());
721
+ } else {
722
+ queueWrite(PrinterProtocol.cmdStartPrinting());
723
+ }
574
724
 
575
725
  // 3. Set DPI
576
726
  queueWrite(PrinterProtocol.cmdSetDpi200());
@@ -599,11 +749,22 @@ public class CatPrinterCore {
599
749
 
600
750
  // === PRINT BITMAP DATA ===
601
751
  for (int line = 0; line < totalLines; line++) {
752
+ // Check for cancellation
753
+ if (isCancelled.get()) {
754
+ handleCancellation();
755
+ return;
756
+ }
757
+
602
758
  int offset = line * bytesPerLine;
603
759
  byte[] lineData = new byte[bytesPerLine];
604
760
  System.arraycopy(data, offset, lineData, 0, bytesPerLine);
605
761
 
606
- queueWrite(PrinterProtocol.cmdDrawBitmap(lineData));
762
+ if (useCompression) {
763
+ // Use compressed bitmap command for newer printers
764
+ queueWrite(PrinterProtocol.cmdDrawCompressedBitmap(lineData));
765
+ } else {
766
+ queueWrite(PrinterProtocol.cmdDrawBitmap(lineData));
767
+ }
607
768
 
608
769
  // Report progress every 50 lines
609
770
  if (line % 50 == 0) {
@@ -653,9 +814,531 @@ public class CatPrinterCore {
653
814
  callback.onComplete();
654
815
  }
655
816
 
817
+ /**
818
+ * Cancel an ongoing print job
819
+ */
820
+ public void cancelPrint(PrintCallback callback) {
821
+ Log.d(TAG, "Cancel requested, isPrinting=" + isPrinting.get());
822
+
823
+ // Always set cancelled flag and clear queue, even if not currently printing
824
+ isCancelled.set(true);
825
+ writeQueue.clear();
826
+
827
+ if (isPrinting.get()) {
828
+ // Let the print loop handle the cancellation
829
+ Log.d(TAG, "Cancelling active print job...");
830
+ } else {
831
+ // Reset the flag if nothing was printing
832
+ isCancelled.set(false);
833
+ }
834
+
835
+ callback.onComplete();
836
+ }
837
+
838
+ public void retractPaper(int pixels, PrintCallback callback) {
839
+ if (!isConnected()) {
840
+ callback.onError("Not connected to printer");
841
+ return;
842
+ }
843
+
844
+ queueWrite(PrinterProtocol.cmdRetractPaper(pixels));
845
+ callback.onComplete();
846
+ }
847
+
848
+ public void getDeviceInfo(PrintCallback callback) {
849
+ if (!isConnected()) {
850
+ callback.onError("Not connected to printer");
851
+ return;
852
+ }
853
+
854
+ // Send device info command
855
+ queueWrite(PrinterProtocol.cmdGetDeviceInfo());
856
+ // Note: Response comes via onCharacteristicChanged, but we don't have
857
+ // a way to capture it yet. For now, just confirm command was sent.
858
+ callback.onComplete();
859
+ }
860
+
861
+ /**
862
+ * Get the connected device name for model detection
863
+ */
864
+ public String getConnectedDeviceName() {
865
+ if (gatt != null && gatt.getDevice() != null) {
866
+ return gatt.getDevice().getName();
867
+ }
868
+ return null;
869
+ }
870
+
871
+ /**
872
+ * Get detected printer model info
873
+ */
874
+ public PrinterModelInfo getPrinterModel() {
875
+ String deviceName = getConnectedDeviceName();
876
+ String model = PrinterProtocol.detectModel(deviceName);
877
+ boolean isNewKind = PrinterProtocol.isNewKind(model);
878
+ boolean hasFeedingProblems = PrinterProtocol.hasFeedingProblems(model);
879
+
880
+ return new PrinterModelInfo(
881
+ model,
882
+ isNewKind,
883
+ hasFeedingProblems,
884
+ paperWidth,
885
+ deviceName != null ? deviceName : ""
886
+ );
887
+ }
888
+
889
+ public static class PrinterModelInfo {
890
+ public String model;
891
+ public boolean isNewKind;
892
+ public boolean hasFeedingProblems;
893
+ public int paperWidth;
894
+ public String deviceName;
895
+
896
+ public PrinterModelInfo(String model, boolean isNewKind, boolean hasFeedingProblems,
897
+ int paperWidth, String deviceName) {
898
+ this.model = model;
899
+ this.isNewKind = isNewKind;
900
+ this.hasFeedingProblems = hasFeedingProblems;
901
+ this.paperWidth = paperWidth;
902
+ this.deviceName = deviceName;
903
+ }
904
+ }
905
+
656
906
  private void reportProgress(int percent, String status, String message) {
657
907
  if (printCallback != null) {
658
908
  mainHandler.post(() -> printCallback.onProgress(percent, status, message));
659
909
  }
660
910
  }
911
+
912
+ private void handleCancellation() {
913
+ Log.d(TAG, "Print job cancelled");
914
+ writeQueue.clear();
915
+ isWriting.set(false);
916
+ isPrinting.set(false);
917
+
918
+ // Try to send stop command to printer (best effort)
919
+ if (gatt != null && txCharacteristic != null && !dryRunMode) {
920
+ try {
921
+ byte[] stopCmd = PrinterProtocol.cmdEndLattice();
922
+ txCharacteristic.setValue(stopCmd);
923
+ txCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
924
+ gatt.writeCharacteristic(txCharacteristic);
925
+ } catch (Exception e) {
926
+ Log.w(TAG, "Failed to send stop command", e);
927
+ }
928
+ }
929
+
930
+ isCancelled.set(false);
931
+
932
+ reportProgress(0, "error", "Print cancelled");
933
+
934
+ if (printCallback != null) {
935
+ mainHandler.post(() -> printCallback.onError("Print cancelled by user"));
936
+ }
937
+ }
938
+
939
+ // ==================== NEW FEATURES ====================
940
+
941
+ // Auto-reconnect settings
942
+ private boolean autoReconnectEnabled = false;
943
+ private int autoReconnectMaxAttempts = 3;
944
+ private int autoReconnectDelayMs = 2000;
945
+ private int autoReconnectAttempts = 0;
946
+ private String lastConnectedAddress = null;
947
+ private int lastPaperWidth = PrinterProtocol.WIDTH_58MM;
948
+
949
+ /**
950
+ * Set auto-reconnect options
951
+ */
952
+ public void setAutoReconnect(boolean enabled, int maxAttempts, int delayMs) {
953
+ this.autoReconnectEnabled = enabled;
954
+ this.autoReconnectMaxAttempts = maxAttempts;
955
+ this.autoReconnectDelayMs = delayMs;
956
+ this.autoReconnectAttempts = 0;
957
+ }
958
+
959
+ /**
960
+ * Print text with full styling support
961
+ */
962
+ public void printTextStyled(String text, int fontSize, String align, boolean bold,
963
+ boolean italic, boolean underline, boolean strikethrough,
964
+ float lineSpacing, float energy, int quality,
965
+ int feedAfter, boolean wordWrap, boolean rtl,
966
+ String fontPath, PrintCallback callback) {
967
+ this.printCallback = callback;
968
+
969
+ if (!isConnected()) {
970
+ callback.onError("Not connected to printer");
971
+ return;
972
+ }
973
+
974
+ if (isPrinting.get()) {
975
+ callback.onError("Already printing");
976
+ return;
977
+ }
978
+
979
+ isPrinting.set(true);
980
+ isCancelled.set(false);
981
+
982
+ new Thread(() -> {
983
+ try {
984
+ reportProgress(0, "processing", "Rendering styled text");
985
+
986
+ if (isCancelled.get()) {
987
+ handleCancellation();
988
+ return;
989
+ }
990
+
991
+ Bitmap bitmap = ImageProcessor.renderText(text, paperWidth, fontSize,
992
+ align, bold, italic, underline, strikethrough,
993
+ lineSpacing, wordWrap, rtl, fontPath);
994
+
995
+ reportProgress(20, "processing", "Converting to monochrome");
996
+
997
+ byte[] monoData = ImageProcessor.toMonochrome(bitmap, 127);
998
+ int height = bitmap.getHeight();
999
+
1000
+ bitmap.recycle();
1001
+
1002
+ printBitmapData(monoData, paperWidth, height, energy, quality, feedAfter, false);
1003
+
1004
+ } catch (Exception e) {
1005
+ Log.e(TAG, "Print error", e);
1006
+ isPrinting.set(false);
1007
+ mainHandler.post(() -> callback.onError(e.getMessage()));
1008
+ }
1009
+ }).start();
1010
+ }
1011
+
1012
+ /**
1013
+ * Print rich text with mixed formatting (different styles per segment)
1014
+ */
1015
+ public void printRichText(String[] texts, boolean[] bolds, boolean[] italics,
1016
+ boolean[] underlines, boolean[] strikethroughs, int[] fontSizes,
1017
+ int defaultFontSize, String align, float lineSpacing,
1018
+ float energy, int quality, int feedAfter, boolean rtl,
1019
+ String fontPath, PrintCallback callback) {
1020
+ this.printCallback = callback;
1021
+
1022
+ if (!isConnected()) {
1023
+ callback.onError("Not connected to printer");
1024
+ return;
1025
+ }
1026
+
1027
+ if (isPrinting.get()) {
1028
+ callback.onError("Already printing");
1029
+ return;
1030
+ }
1031
+
1032
+ isPrinting.set(true);
1033
+ isCancelled.set(false);
1034
+
1035
+ new Thread(() -> {
1036
+ try {
1037
+ reportProgress(0, "processing", "Rendering rich text");
1038
+
1039
+ if (isCancelled.get()) {
1040
+ handleCancellation();
1041
+ return;
1042
+ }
1043
+
1044
+ Bitmap bitmap = ImageProcessor.renderRichText(texts, bolds, italics,
1045
+ underlines, strikethroughs, fontSizes, defaultFontSize,
1046
+ paperWidth, align, lineSpacing, rtl, fontPath);
1047
+
1048
+ reportProgress(20, "processing", "Converting to monochrome");
1049
+
1050
+ byte[] monoData = ImageProcessor.toMonochrome(bitmap, 127);
1051
+ int height = bitmap.getHeight();
1052
+
1053
+ bitmap.recycle();
1054
+
1055
+ printBitmapData(monoData, paperWidth, height, energy, quality, feedAfter, false);
1056
+
1057
+ } catch (Exception e) {
1058
+ Log.e(TAG, "Rich text print error", e);
1059
+ isPrinting.set(false);
1060
+ mainHandler.post(() -> callback.onError(e.getMessage()));
1061
+ }
1062
+ }).start();
1063
+ }
1064
+
1065
+ /**
1066
+ * Print image with brightness/contrast/invert adjustments
1067
+ */
1068
+ public void printImageAdjusted(String base64Image, float energy, int quality,
1069
+ int feedAfter, int threshold, boolean dither,
1070
+ boolean flipH, boolean flipV, boolean compress,
1071
+ int brightness, int contrast, boolean invert,
1072
+ PrintCallback callback) {
1073
+ this.printCallback = callback;
1074
+
1075
+ if (!isConnected()) {
1076
+ callback.onError("Not connected to printer");
1077
+ return;
1078
+ }
1079
+
1080
+ if (isPrinting.get()) {
1081
+ callback.onError("Already printing");
1082
+ return;
1083
+ }
1084
+
1085
+ isPrinting.set(true);
1086
+ isCancelled.set(false);
1087
+
1088
+ new Thread(() -> {
1089
+ try {
1090
+ reportProgress(0, "processing", "Decoding image");
1091
+
1092
+ if (isCancelled.get()) {
1093
+ handleCancellation();
1094
+ return;
1095
+ }
1096
+
1097
+ byte[] imageBytes = Base64.decode(base64Image, Base64.DEFAULT);
1098
+ Bitmap bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
1099
+
1100
+ if (bitmap == null) {
1101
+ throw new Exception("Failed to decode image");
1102
+ }
1103
+
1104
+ reportProgress(10, "processing", "Resizing image");
1105
+ bitmap = ImageProcessor.resizeToWidth(bitmap, paperWidth);
1106
+
1107
+ // Apply brightness/contrast adjustments
1108
+ if (brightness != 0 || contrast != 0) {
1109
+ reportProgress(15, "processing", "Adjusting brightness/contrast");
1110
+ bitmap = ImageProcessor.adjustBrightnessContrast(bitmap, brightness, contrast);
1111
+ }
1112
+
1113
+ // Apply invert
1114
+ if (invert) {
1115
+ reportProgress(17, "processing", "Inverting colors");
1116
+ bitmap = ImageProcessor.invertColors(bitmap);
1117
+ }
1118
+
1119
+ reportProgress(20, "processing", "Converting to monochrome");
1120
+
1121
+ byte[] monoData;
1122
+ if (dither) {
1123
+ monoData = ImageProcessor.toMonochromeDithered(bitmap, threshold);
1124
+ } else {
1125
+ monoData = ImageProcessor.toMonochrome(bitmap, threshold);
1126
+ }
1127
+ int height = bitmap.getHeight();
1128
+
1129
+ if (flipH) {
1130
+ ImageProcessor.flipHorizontal(monoData, paperWidth, height);
1131
+ }
1132
+ if (flipV) {
1133
+ ImageProcessor.flipVertical(monoData, paperWidth, height);
1134
+ }
1135
+
1136
+ bitmap.recycle();
1137
+
1138
+ printBitmapData(monoData, paperWidth, height, energy, quality, feedAfter, compress);
1139
+
1140
+ } catch (Exception e) {
1141
+ Log.e(TAG, "Print error", e);
1142
+ isPrinting.set(false);
1143
+ mainHandler.post(() -> callback.onError(e.getMessage()));
1144
+ }
1145
+ }).start();
1146
+ }
1147
+
1148
+ /**
1149
+ * Print a barcode
1150
+ */
1151
+ public void printBarcode(String data, String type, int height, boolean showText,
1152
+ String align, float energy, int feedAfter, PrintCallback callback) {
1153
+ this.printCallback = callback;
1154
+
1155
+ if (!isConnected()) {
1156
+ callback.onError("Not connected to printer");
1157
+ return;
1158
+ }
1159
+
1160
+ if (isPrinting.get()) {
1161
+ callback.onError("Already printing");
1162
+ return;
1163
+ }
1164
+
1165
+ isPrinting.set(true);
1166
+ isCancelled.set(false);
1167
+
1168
+ new Thread(() -> {
1169
+ try {
1170
+ reportProgress(0, "processing", "Generating barcode");
1171
+
1172
+ Bitmap bitmap = BarcodeGenerator.createAlignedBarcode(data, type, paperWidth,
1173
+ height, showText, align);
1174
+
1175
+ reportProgress(20, "processing", "Converting to monochrome");
1176
+
1177
+ byte[] monoData = ImageProcessor.toMonochrome(bitmap, 127);
1178
+ int bitmapHeight = bitmap.getHeight();
1179
+
1180
+ bitmap.recycle();
1181
+
1182
+ printBitmapData(monoData, paperWidth, bitmapHeight, energy, 3, feedAfter, false);
1183
+
1184
+ } catch (Exception e) {
1185
+ Log.e(TAG, "Barcode print error", e);
1186
+ isPrinting.set(false);
1187
+ mainHandler.post(() -> callback.onError(e.getMessage()));
1188
+ }
1189
+ }).start();
1190
+ }
1191
+
1192
+ /**
1193
+ * Print a QR code
1194
+ */
1195
+ public void printQRCode(String data, int size, String errorCorrection, String align,
1196
+ float energy, int feedAfter, PrintCallback callback) {
1197
+ this.printCallback = callback;
1198
+
1199
+ if (!isConnected()) {
1200
+ callback.onError("Not connected to printer");
1201
+ return;
1202
+ }
1203
+
1204
+ if (isPrinting.get()) {
1205
+ callback.onError("Already printing");
1206
+ return;
1207
+ }
1208
+
1209
+ isPrinting.set(true);
1210
+ isCancelled.set(false);
1211
+
1212
+ new Thread(() -> {
1213
+ try {
1214
+ reportProgress(0, "processing", "Generating QR code");
1215
+
1216
+ Bitmap bitmap = BarcodeGenerator.createAlignedQRCode(data, paperWidth, size,
1217
+ errorCorrection, align);
1218
+
1219
+ reportProgress(20, "processing", "Converting to monochrome");
1220
+
1221
+ byte[] monoData = ImageProcessor.toMonochrome(bitmap, 127);
1222
+ int height = bitmap.getHeight();
1223
+
1224
+ bitmap.recycle();
1225
+
1226
+ printBitmapData(monoData, paperWidth, height, energy, 3, feedAfter, false);
1227
+
1228
+ } catch (Exception e) {
1229
+ Log.e(TAG, "QR code print error", e);
1230
+ isPrinting.set(false);
1231
+ mainHandler.post(() -> callback.onError(e.getMessage()));
1232
+ }
1233
+ }).start();
1234
+ }
1235
+
1236
+ /**
1237
+ * Print a formatted table
1238
+ */
1239
+ public void printTable(String[][] rows, int[] columnWidths, String[] alignments,
1240
+ boolean[] boldRows, boolean showLines, String lineChar,
1241
+ int fontSize, float energy, int feedAfter, PrintCallback callback) {
1242
+ this.printCallback = callback;
1243
+
1244
+ if (!isConnected()) {
1245
+ callback.onError("Not connected to printer");
1246
+ return;
1247
+ }
1248
+
1249
+ if (isPrinting.get()) {
1250
+ callback.onError("Already printing");
1251
+ return;
1252
+ }
1253
+
1254
+ isPrinting.set(true);
1255
+ isCancelled.set(false);
1256
+
1257
+ new Thread(() -> {
1258
+ try {
1259
+ reportProgress(0, "processing", "Rendering table");
1260
+
1261
+ Bitmap bitmap = ImageProcessor.renderTable(rows, columnWidths, alignments,
1262
+ boldRows, paperWidth, fontSize, showLines, lineChar);
1263
+
1264
+ reportProgress(20, "processing", "Converting to monochrome");
1265
+
1266
+ byte[] monoData = ImageProcessor.toMonochrome(bitmap, 127);
1267
+ int height = bitmap.getHeight();
1268
+
1269
+ bitmap.recycle();
1270
+
1271
+ printBitmapData(monoData, paperWidth, height, energy, 3, feedAfter, false);
1272
+
1273
+ } catch (Exception e) {
1274
+ Log.e(TAG, "Table print error", e);
1275
+ isPrinting.set(false);
1276
+ mainHandler.post(() -> callback.onError(e.getMessage()));
1277
+ }
1278
+ }).start();
1279
+ }
1280
+
1281
+ /**
1282
+ * Get print preview as base64 image
1283
+ */
1284
+ public PreviewResult getPreview(String type, Object options) {
1285
+ try {
1286
+ Bitmap bitmap = null;
1287
+
1288
+ // Generate bitmap based on type (simplified - actual implementation would parse options)
1289
+ // This is a placeholder - the actual implementation would be in the plugin layer
1290
+
1291
+ if (bitmap != null) {
1292
+ String base64 = ImageProcessor.bitmapToBase64(bitmap);
1293
+ int width = bitmap.getWidth();
1294
+ int height = bitmap.getHeight();
1295
+ bitmap.recycle();
1296
+ return new PreviewResult(base64, width, height);
1297
+ }
1298
+ } catch (Exception e) {
1299
+ Log.e(TAG, "Preview error", e);
1300
+ }
1301
+ return null;
1302
+ }
1303
+
1304
+ public static class PreviewResult {
1305
+ public String imageBase64;
1306
+ public int width;
1307
+ public int height;
1308
+
1309
+ public PreviewResult(String imageBase64, int width, int height) {
1310
+ this.imageBase64 = imageBase64;
1311
+ this.width = width;
1312
+ this.height = height;
1313
+ }
1314
+ }
1315
+
1316
+ /**
1317
+ * Get printer status (battery, paper, firmware)
1318
+ * Note: Most cat printers don't report this info, so values may be null
1319
+ */
1320
+ public PrinterStatus getPrinterStatus() {
1321
+ PrinterStatus status = new PrinterStatus();
1322
+ status.connected = isConnected();
1323
+ status.paperWidth = paperWidth;
1324
+
1325
+ // These would need to be populated from device responses
1326
+ // Most cat printers don't report battery/paper status
1327
+ status.batteryLevel = null;
1328
+ status.isCharging = null;
1329
+ status.hasPaper = null;
1330
+ status.firmwareVersion = null;
1331
+
1332
+ return status;
1333
+ }
1334
+
1335
+ public static class PrinterStatus {
1336
+ public boolean connected;
1337
+ public Integer batteryLevel;
1338
+ public Boolean isCharging;
1339
+ public Boolean hasPaper;
1340
+ public String firmwareVersion;
1341
+ public int paperWidth;
1342
+ public String rawData;
1343
+ }
661
1344
  }