""" Android Print 组件 — PrintManager, PrintJob, PrintDocumentAdapter, PdfPrint 等 方法签名完全对齐 Android SDK (android.print 包) """ import os import time from typing import Any, Callable, Dict, List, Optional, Tuple from .._core import Stub, AndroidOnlyError # ═══════════════════════════════════════════════════════════════ # PrintAttributes # ═══════════════════════════════════════════════════════════════ class PrintAttributes(Stub): """PrintAttributes — 打印属性""" # 颜色模式 COLOR_MODE_MONOCHROME = 1 COLOR_MODE_COLOR = 2 # 方向 ORIENTATION_PORTRAIT = 1 ORIENTATION_LANDSCAPE = 2 # 媒质尺寸 ID MEDIA_SIZE_UNKNOWN = "unknown" MEDIA_SIZE_A4 = "A4" MEDIA_SIZE_A3 = "A3" MEDIA_SIZE_LETTER = "letter" MEDIA_SIZE_LEGAL = "legal" MEDIA_SIZE_ISO_A4 = "iso_a4" MEDIA_SIZE_ISO_A3 = "iso_a3" MEDIA_SIZE_NA_LETTER = "na_letter" def __init__(self): super().__init__() self._media_size: Optional["PrintAttributes.MediaSize"] = None self._resolution: Optional["PrintAttributes.Resolution"] = None self._min_margins: Optional["PrintAttributes.Margins"] = None self._color_mode: int = PrintAttributes.COLOR_MODE_COLOR self._orientation: int = PrintAttributes.ORIENTATION_PORTRAIT def getMediaSize(self) -> Optional["MediaSize"]: return self._media_size def getResolution(self) -> Optional["Resolution"]: return self._resolution def getMinMargins(self) -> Optional["Margins"]: return self._min_margins def getColorMode(self) -> int: return self._color_mode def getOrientation(self) -> int: return self._orientation def isPortrait(self) -> bool: return self._orientation == PrintAttributes.ORIENTATION_PORTRAIT def toString(self) -> str: return (f"PrintAttributes(colorMode={self._color_mode}, " f"orientation={self._orientation})") def hashCode(self) -> int: return hash((self._color_mode, self._orientation)) & 0x7FFFFFFF def equals(self, other) -> bool: if not isinstance(other, PrintAttributes): return False return (self._color_mode == other._color_mode and self._orientation == other._orientation) # ── 内部嵌套类 ── class MediaSize(Stub): """MediaSize — 媒质尺寸""" def __init__(self, id: str, label: str, width_mils: int, height_mils: int): super().__init__() self._id = id self._label = label self._width_mils = width_mils self._height_mils = height_mils def getId(self) -> str: return self._id def getLabel(self, package_manager: Any = None) -> str: return self._label def getWidthMils(self) -> int: return self._width_mils def getHeightMils(self) -> int: return self._height_mils def asPortrait(self) -> "PrintAttributes.MediaSize": if self._width_mils > self._height_mils: return PrintAttributes.MediaSize( self._id, self._label, self._height_mils, self._width_mils) return self def asLandscape(self) -> "PrintAttributes.MediaSize": if self._width_mils < self._height_mils: return PrintAttributes.MediaSize( self._id, self._label, self._height_mils, self._width_mils) return self def toString(self) -> str: return f"MediaSize({self._id}: {self._width_mils}x{self._height_mils}mils)" def hashCode(self) -> int: return hash(self._id) & 0x7FFFFFFF def equals(self, other) -> bool: if not isinstance(other, PrintAttributes.MediaSize): return False return self._id == other._id class Resolution(Stub): """Resolution — 打印分辨率""" def __init__(self, id: str, label: str, horizontal_dpi: int, vertical_dpi: int): super().__init__() self._id = id self._label = label self._horizontal_dpi = horizontal_dpi self._vertical_dpi = vertical_dpi def getId(self) -> str: return self._id def getLabel(self) -> str: return self._label def getHorizontalDpi(self) -> int: return self._horizontal_dpi def getVerticalDpi(self) -> int: return self._vertical_dpi def toString(self) -> str: return f"Resolution({self._id}: {self._horizontal_dpi}x{self._vertical_dpi}dpi)" class Margins(Stub): """Margins — 页边距""" NO_MARGINS: "PrintAttributes.Margins" = None def __init__(self, left_mils: int, top_mils: int, right_mils: int, bottom_mils: int): super().__init__() self._left_mils = left_mils self._top_mils = top_mils self._right_mils = right_mils self._bottom_mils = bottom_mils def getLeftMils(self) -> int: return self._left_mils def getTopMils(self) -> int: return self._top_mils def getRightMils(self) -> int: return self._right_mils def getBottomMils(self) -> int: return self._bottom_mils def toString(self) -> str: return f"Margins({self._left_mils},{self._top_mils},{self._right_mils},{self._bottom_mils})" class Builder(Stub): """Builder — PrintAttributes 构建器""" def __init__(self): super().__init__() self._attrs = PrintAttributes() def setMediaSize(self, media_size: "PrintAttributes.MediaSize") -> "PrintAttributes.Builder": self._attrs._media_size = media_size return self def setResolution(self, resolution: "PrintAttributes.Resolution") -> "PrintAttributes.Builder": self._attrs._resolution = resolution return self def setMinMargins(self, margins: "PrintAttributes.Margins") -> "PrintAttributes.Builder": self._attrs._min_margins = margins return self def setColorMode(self, color_mode: int) -> "PrintAttributes.Builder": self._attrs._color_mode = color_mode return self def setOrientation(self, orientation: int) -> "PrintAttributes.Builder": self._attrs._orientation = orientation return self def build(self) -> "PrintAttributes": return self._attrs PrintAttributes.Margins.NO_MARGINS = PrintAttributes.Margins(0, 0, 0, 0) # ═══════════════════════════════════════════════════════════════ # PrintJobInfo # ═══════════════════════════════════════════════════════════════ class PrintJobInfo(Stub): """PrintJobInfo — 打印任务信息""" # 状态常量 STATE_CREATED = 1 STATE_QUEUED = 2 STATE_STARTED = 3 STATE_BLOCKED = 4 STATE_COMPLETED = 5 STATE_FAILED = 6 STATE_CANCELED = 7 def __init__(self): super().__init__() self._label: str = "" self._id: Optional[Any] = None self._state: int = PrintJobInfo.STATE_CREATED self._attributes: Optional[PrintAttributes] = None self._copies: int = 1 self._creation_time: int = int(time.time() * 1000) self._canceling: bool = False self._pages: Optional[List[int]] = None self._has_advanced_options: bool = False def getLabel(self) -> str: return self._label def setLabel(self, label: str): self._label = label def getId(self) -> Optional[Any]: return self._id def getState(self) -> int: return self._state def setState(self, state: int): self._state = state def getAttributes(self) -> Optional[PrintAttributes]: return self._attributes def setAttributes(self, attributes: PrintAttributes): self._attributes = attributes def getCopies(self) -> int: return self._copies def setCopies(self, copies: int): self._copies = copies def getCreationTime(self) -> int: return self._creation_time def isCanceling(self) -> bool: return self._canceling def getPages(self) -> Optional[List[int]]: return self._pages def setPages(self, pages: Optional[List[int]]): self._pages = pages def hasAdvancedOptions(self) -> bool: return self._has_advanced_options def toString(self) -> str: return f"PrintJobInfo(label={self._label}, state={self._state}, copies={self._copies})" def hashCode(self) -> int: return hash(self._id) & 0x7FFFFFFF def equals(self, other) -> bool: if not isinstance(other, PrintJobInfo): return False return self._id == other._id # ── Builder ── class Builder(Stub): """Builder — PrintJobInfo 构建器""" def __init__(self, info: Optional["PrintJobInfo"] = None): super().__init__() self._info = PrintJobInfo() if info: self._info._label = info._label self._info._id = info._id self._info._attributes = info._attributes self._info._copies = info._copies def setCopies(self, copies: int) -> "PrintJobInfo.Builder": self._info._copies = copies return self def setAttributes(self, attributes: PrintAttributes) -> "PrintJobInfo.Builder": self._info._attributes = attributes return self def setLabel(self, label: str) -> "PrintJobInfo.Builder": self._info._label = label return self def setPages(self, pages: Optional[List[int]]) -> "PrintJobInfo.Builder": self._info._pages = pages return self def build(self) -> "PrintJobInfo": return self._info # ═══════════════════════════════════════════════════════════════ # PrintJob # ═══════════════════════════════════════════════════════════════ class PrintJob(Stub): """PrintJob — 打印任务""" def __init__(self, info: PrintJobInfo): super().__init__() self._info = info self._cancelled = False self._result: Optional[Any] = None def getId(self) -> Optional[Any]: return self._info.getId() def getInfo(self) -> PrintJobInfo: return self._info def isStarted(self) -> bool: return self._info.getState() >= PrintJobInfo.STATE_STARTED def isBlocked(self) -> bool: return self._info.getState() == PrintJobInfo.STATE_BLOCKED def isCompleted(self) -> bool: return self._info.getState() == PrintJobInfo.STATE_COMPLETED def isFailed(self) -> bool: return self._info.getState() == PrintJobInfo.STATE_FAILED def isCancelled(self) -> bool: return self._cancelled def cancel(self): """取消打印任务""" self._cancelled = True self._info.setState(PrintJobInfo.STATE_CANCELED) def restart(self): """重新启动打印任务""" self._cancelled = False self._info.setState(PrintJobInfo.STATE_QUEUED) def setResult(self, result: Any): self._result = result def getResult(self) -> Optional[Any]: return self._result def toString(self) -> str: return f"PrintJob(id={self.getId()}, state={self._info.getState()})" def hashCode(self) -> int: return hash(self.getId()) & 0x7FFFFFFF def equals(self, other) -> bool: if not isinstance(other, PrintJob): return False return self.getId() == other.getId() # ═══════════════════════════════════════════════════════════════ # PrintDocumentAdapter # ═══════════════════════════════════════════════════════════════ class PrintDocumentAdapter(Stub): """PrintDocumentAdapter — 打印文档适配器""" EXTRA_PRINT_PREVIEW = "EXTRA_PRINT_PREVIEW" def __init__(self): super().__init__() def onStart(self): """打印开始回调""" pass def onFinish(self): """打印结束回调""" pass def onLayout(self, old_attributes: PrintAttributes, new_attributes: PrintAttributes, cancellation_signal: Any, callback: "PrintDocumentAdapterLayoutCallback", metadata: Any): """布局回调 - 桌面实现直接完成""" if callback: callback.onLayoutFinished(self, new_attributes) def onWrite(self, pages: List[int], destination: Any, cancellation_signal: Any, callback: "PrintDocumentAdapterWriteCallback"): """写入回调 - 桌面实现直接完成""" if callback: callback.onWriteFinished(pages) class PrintDocumentAdapterLayoutCallback(Stub): """PrintDocumentAdapterLayoutCallback — 布局回调""" def __init__(self): super().__init__() def onLayoutFinished(self, adapter: PrintDocumentAdapter, attributes: PrintAttributes): """布局完成""" pass def onLayoutFailed(self, error: Optional[str] = None): """布局失败""" pass def onLayoutCancelled(self): """布局取消""" pass class PrintDocumentAdapterWriteCallback(Stub): """PrintDocumentAdapterWriteCallback — 写入回调""" def __init__(self): super().__init__() def onWriteFinished(self, pages: List[int]): """写入完成""" pass def onWriteFailed(self, error: Optional[str] = None): """写入失败""" pass def onWriteCancelled(self): """写入取消""" pass # ═══════════════════════════════════════════════════════════════ # PdfPrint # ═══════════════════════════════════════════════════════════════ class PdfPrint(Stub): """PdfPrint — PDF 打印辅助类""" def __init__(self, attributes: Optional[PrintAttributes] = None): super().__init__() self._attributes = attributes or PrintAttributes() def getAttributes(self) -> PrintAttributes: return self._attributes def setAttributes(self, attributes: PrintAttributes): self._attributes = attributes def print(self, print_adapter: PrintDocumentAdapter, output_path: str) -> bool: """执行 PDF 打印 (桌面实现: 写入文件)""" try: os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True) # 在桌面环境, PDF 打印由适配器的 onWrite 实现处理 callback = PrintDocumentAdapterWriteCallback() print_adapter.onWrite([1], output_path, None, callback) return True except Exception: return False def toString(self) -> str: return f"PdfPrint(attributes={self._attributes})" def hashCode(self) -> int: return hash(str(self._attributes)) & 0x7FFFFFFF def equals(self, other) -> bool: if not isinstance(other, PdfPrint): return False return str(self._attributes) == str(other._attributes) # ═══════════════════════════════════════════════════════════════ # PrintManager # ═══════════════════════════════════════════════════════════════ class PrintManager(Stub): """PrintManager — 打印管理器""" # 打印任务 ID 生成 _next_job_id = 1 def __init__(self, context: Any = None): super().__init__() self._context = context self._print_jobs: Dict[str, PrintJob] = {} def getPrintJobs(self) -> List[PrintJob]: """获取打印任务列表""" return list(self._print_jobs.values()) def print(self, job_name: str, document_adapter: PrintDocumentAdapter, attributes: Optional[PrintAttributes] = None) -> PrintJob: """提交打印任务""" info = PrintJobInfo() info.setLabel(job_name) info.setAttributes(attributes or PrintAttributes()) info.setState(PrintJobInfo.STATE_QUEUED) job_id = f"print_job_{PrintManager._next_job_id}" PrintManager._next_job_id += 1 job = PrintJob(info) self._print_jobs[job_id] = job # 自动触发布局和写入 cancellation_signal = type("CancellationSignal", (), {"isCanceled": lambda: False})() document_adapter.onStart() layout_cb = PrintDocumentAdapterLayoutCallback() document_adapter.onLayout(None, info.getAttributes(), cancellation_signal, layout_cb, None) info.setState(PrintJobInfo.STATE_COMPLETED) document_adapter.onFinish() return job def getPrintJobById(self, job_id: str) -> Optional[PrintJob]: """根据 ID 获取打印任务""" return self._print_jobs.get(job_id) def cancelPrintJob(self, job_id: str) -> bool: """取消打印任务""" job = self._print_jobs.get(job_id) if job: job.cancel() return True return False def toString(self) -> str: return f"PrintManager(jobs={len(self._print_jobs)})" def hashCode(self) -> int: return id(self) & 0x7FFFFFFF def equals(self, other) -> bool: return self is other # ═══════════════════════════════════════════════════════════════ # __all__ # ═══════════════════════════════════════════════════════════════ __all__ = [ "PrintManager", "PrintJob", "PrintJobInfo", "PrintDocumentAdapter", "PrintAttributes", "PrintDocumentAdapterLayoutCallback", "PrintDocumentAdapterWriteCallback", "PdfPrint", ]