feat: Android-pyApi v1.0 - 全面翻译 Android SDK API 为 Python
52 个模块覆盖 Android + Java + org.json 标准库
This commit is contained in:
commit
bfe27fdc08
104 files changed
+58750
No files matched your search
@@ -0,0 +1,335 @@
|
||||
"""
|
||||
Android Graphics PDF 组件 — PdfDocument, PdfRenderer, ParcelFileDescriptor
|
||||
方法签名完全对齐 Android SDK
|
||||
"""
|
||||
|
||||
import os
|
||||
import io
|
||||
import tempfile
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from .._core import Stub, AndroidOnlyError, android_context, HAS_PIL, HAS_PYMUPDF, HAS_PDF2IMAGE, resolve_android_path
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ParcelFileDescriptor
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class ParcelFileDescriptor(Stub):
|
||||
"""ParcelFileDescriptor — 文件描述符"""
|
||||
|
||||
MODE_READ_ONLY = 0x10000000
|
||||
MODE_WRITE_ONLY = 0x20000000
|
||||
MODE_READ_WRITE = 0x30000000
|
||||
MODE_CREATE = 0x08000000
|
||||
MODE_TRUNCATE = 0x04000000
|
||||
MODE_APPEND = 0x02000000
|
||||
|
||||
def __init__(self, fd: int = -1, file_path: str = ""):
|
||||
super().__init__()
|
||||
self._fd = fd
|
||||
self._file_path = file_path
|
||||
self._closed = False
|
||||
self._file_obj = None
|
||||
if file_path and fd < 0:
|
||||
resolved = resolve_android_path(file_path)
|
||||
self._file_path = resolved
|
||||
|
||||
@staticmethod
|
||||
def open(file_path: str, mode: int) -> "ParcelFileDescriptor":
|
||||
resolved = resolve_android_path(file_path)
|
||||
fmode = "rb"
|
||||
if mode & ParcelFileDescriptor.MODE_WRITE_ONLY:
|
||||
fmode = "wb"
|
||||
elif mode & ParcelFileDescriptor.MODE_READ_WRITE:
|
||||
fmode = "r+b"
|
||||
if mode & ParcelFileDescriptor.MODE_CREATE:
|
||||
if not os.path.exists(os.path.dirname(resolved)):
|
||||
os.makedirs(os.path.dirname(resolved), exist_ok=True)
|
||||
if not os.path.exists(resolved):
|
||||
open(resolved, "w").close()
|
||||
return ParcelFileDescriptor(-1, file_path)
|
||||
|
||||
@staticmethod
|
||||
def parseFromFd(fd: int) -> "ParcelFileDescriptor":
|
||||
return ParcelFileDescriptor(fd)
|
||||
|
||||
@staticmethod
|
||||
def parseFromString(path: str) -> "ParcelFileDescriptor":
|
||||
resolved = resolve_android_path(path)
|
||||
return ParcelFileDescriptor(-1, resolved)
|
||||
|
||||
@staticmethod
|
||||
def adoptFd(fd: int) -> "ParcelFileDescriptor":
|
||||
return ParcelFileDescriptor(fd)
|
||||
|
||||
@staticmethod
|
||||
def createPipe() -> List["ParcelFileDescriptor"]:
|
||||
r, w = os.pipe()
|
||||
return [ParcelFileDescriptor(r), ParcelFileDescriptor(w)]
|
||||
|
||||
def getFileDescriptor(self) -> int:
|
||||
return self._fd
|
||||
|
||||
def getStatSize(self) -> int:
|
||||
if self._file_path and os.path.exists(self._file_path):
|
||||
return os.path.getsize(self._file_path)
|
||||
return 0
|
||||
|
||||
def getFd(self) -> int:
|
||||
return self._fd
|
||||
|
||||
def close(self):
|
||||
if not self._closed:
|
||||
self._closed = True
|
||||
if self._file_obj:
|
||||
self._file_obj.close()
|
||||
elif self._fd >= 0:
|
||||
try:
|
||||
os.close(self._fd)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def getFilePath(self) -> str:
|
||||
return self._file_path
|
||||
|
||||
def toString(self) -> str:
|
||||
return f"ParcelFileDescriptor(fd={self._fd})"
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PdfDocument
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PdfDocument(Stub):
|
||||
"""PdfDocument — PDF 文档"""
|
||||
|
||||
class Page(Stub):
|
||||
"""PdfDocument.Page — PDF 页面"""
|
||||
|
||||
def __init__(self, page_number: int, width: int, height: int):
|
||||
super().__init__()
|
||||
self._page_number = page_number
|
||||
self._width = width
|
||||
self._height = height
|
||||
self._canvas = None
|
||||
|
||||
def getCanvas(self) -> "Canvas":
|
||||
from .graphics import Canvas
|
||||
if self._canvas is None:
|
||||
self._canvas = Canvas(self._width, self._height)
|
||||
return self._canvas
|
||||
|
||||
def getWidth(self) -> int:
|
||||
return self._width
|
||||
|
||||
def getHeight(self) -> int:
|
||||
return self._height
|
||||
|
||||
def start(self):
|
||||
pass
|
||||
|
||||
def finish(self):
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._pages: List[PdfDocument.Page] = []
|
||||
|
||||
def startPage(self, page_size: "Page") -> "Canvas":
|
||||
width = page_size.getWidth() if hasattr(page_size, "getWidth") else 595
|
||||
height = page_size.getHeight() if hasattr(page_size, "getHeight") else 842
|
||||
page = PdfDocument.Page(len(self._pages), width, height)
|
||||
self._pages.append(page)
|
||||
return page.getCanvas()
|
||||
|
||||
def finishPage(self, page: "Page"):
|
||||
page.finish()
|
||||
|
||||
def getPages(self) -> List["Page"]:
|
||||
return list(self._pages)
|
||||
|
||||
def writeTo(self, out_stream, close: bool = True):
|
||||
if HAS_PYMUPDF:
|
||||
import fitz
|
||||
doc = fitz.open()
|
||||
for p in self._pages:
|
||||
page = doc.new_page(width=p._width, height=p._height)
|
||||
if HAS_PIL and hasattr(p._canvas, "_image") and p._canvas._image:
|
||||
img_bytes = io.BytesIO()
|
||||
p._canvas._image.save(img_bytes, format="PNG")
|
||||
img_bytes.seek(0)
|
||||
rect = page.rect
|
||||
page.insert_image(rect, stream=img_bytes.read())
|
||||
doc.save(out_stream)
|
||||
doc.close()
|
||||
elif HAS_PIL:
|
||||
from PIL import Image
|
||||
images = []
|
||||
for p in self._pages:
|
||||
if hasattr(p._canvas, "_image") and p._canvas._image:
|
||||
images.append(p._canvas._image.convert("RGB"))
|
||||
if images:
|
||||
images[0].save(out_stream, format="PDF", save_all=True,
|
||||
append_images=images[1:])
|
||||
else:
|
||||
blank = Image.new("RGB", (595, 842), (255, 255, 255))
|
||||
blank.save(out_stream, format="PDF")
|
||||
else:
|
||||
if close:
|
||||
out_stream.close()
|
||||
|
||||
def close(self):
|
||||
self._pages.clear()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.close()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PdfRenderer
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
class PdfRenderer(Stub):
|
||||
"""PdfRenderer — PDF 渲染器"""
|
||||
|
||||
RENDER_MODE_FOR_DISPLAY = 1
|
||||
RENDER_MODE_FOR_PRINT = 2
|
||||
RENDER_MODE_FOR_ALL = 3
|
||||
|
||||
class Page(Stub):
|
||||
"""PdfRenderer.Page — PDF 渲染页面"""
|
||||
|
||||
def __init__(self, renderer: "PdfRenderer", index: int):
|
||||
super().__init__()
|
||||
self._renderer = renderer
|
||||
self._index = index
|
||||
self._opened = True
|
||||
if renderer._doc:
|
||||
self._fitz_page = renderer._doc[index]
|
||||
rect = self._fitz_page.rect
|
||||
self._width = int(rect.width)
|
||||
self._height = int(rect.height)
|
||||
else:
|
||||
self._fitz_page = None
|
||||
self._width = 595
|
||||
self._height = 842
|
||||
|
||||
def getWidth(self) -> int:
|
||||
return self._width
|
||||
|
||||
def getHeight(self) -> int:
|
||||
return self._height
|
||||
|
||||
def getIndex(self) -> int:
|
||||
return self._index
|
||||
|
||||
def render(self, canvas: "Canvas", destLeft: int, destTop: int,
|
||||
destRight: int, destBottom: int, flags: int = 0):
|
||||
if not self._opened:
|
||||
return
|
||||
if HAS_PYMUPDF and self._fitz_page:
|
||||
from .graphics import Bitmap
|
||||
mat = fitz.Matrix(2, 2) # 2x zoom for quality
|
||||
pix = self._fitz_page.get_pixmap(matrix=mat)
|
||||
img_data = pix.tobytes("png")
|
||||
if HAS_PIL and hasattr(canvas, "_image") and canvas._image:
|
||||
from PIL import Image
|
||||
import io as _io
|
||||
img = Image.open(io.BytesIO(img_data))
|
||||
img = img.resize((destRight - destLeft, destBottom - destTop))
|
||||
canvas._image.paste(img, (destLeft, destTop))
|
||||
elif HAS_PDF2IMAGE and HAS_PIL and hasattr(canvas, "_image"):
|
||||
from pdf2image import convert_from_bytes
|
||||
if self._renderer._pdf_bytes:
|
||||
images = convert_from_bytes(
|
||||
self._renderer._pdf_bytes,
|
||||
first_page=self._index + 1,
|
||||
last_page=self._index + 1
|
||||
)
|
||||
if images:
|
||||
img = images[0].resize((destRight - destLeft, destBottom - destTop))
|
||||
canvas._image.paste(img.convert("RGBA"), (destLeft, destTop))
|
||||
|
||||
def start(self):
|
||||
self._opened = True
|
||||
|
||||
def finish(self):
|
||||
self._opened = False
|
||||
|
||||
def close(self):
|
||||
self._opened = False
|
||||
|
||||
def __init__(self, pfd: ParcelFileDescriptor, closePfd: bool = True):
|
||||
super().__init__()
|
||||
self._pfd = pfd
|
||||
self._close_pfd = closePfd
|
||||
self._page_count = 0
|
||||
self._pdf_bytes = None
|
||||
self._doc = None
|
||||
|
||||
file_path = pfd.getFilePath()
|
||||
if file_path and os.path.exists(file_path):
|
||||
with open(file_path, "rb") as f:
|
||||
self._pdf_bytes = f.read()
|
||||
elif pfd._fd >= 0:
|
||||
try:
|
||||
self._pdf_bytes = os.read(pfd._fd, 10 * 1024 * 1024)
|
||||
except OSError:
|
||||
self._pdf_bytes = None
|
||||
|
||||
if HAS_PYMUPDF and self._pdf_bytes:
|
||||
import fitz
|
||||
self._doc = fitz.open(stream=self._pdf_bytes, filetype="pdf")
|
||||
self._page_count = len(self._doc)
|
||||
elif self._pdf_bytes:
|
||||
self._page_count = 1 # fallback
|
||||
else:
|
||||
self._page_count = 0
|
||||
|
||||
def openPage(self, index: int) -> "Page":
|
||||
if 0 <= index < self._page_count:
|
||||
return PdfRenderer.Page(self, index)
|
||||
raise IndexError(f"Page index {index} out of range (0-{self._page_count - 1})")
|
||||
|
||||
def close(self):
|
||||
if self._doc and HAS_PYMUPDF:
|
||||
self._doc.close()
|
||||
self._doc = None
|
||||
if self._close_pfd and self._pfd:
|
||||
self._pfd.close()
|
||||
self._pdf_bytes = None
|
||||
|
||||
def getPageCount(self) -> int:
|
||||
return self._page_count
|
||||
|
||||
def shouldScale(self) -> bool:
|
||||
return False
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.close()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PdfDocumentPage (alias for PdfDocument.Page)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
PdfDocumentPage = PdfDocument.Page
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# __all__
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
|
||||
__all__ = [
|
||||
"PdfDocument",
|
||||
"PdfDocumentPage",
|
||||
"PdfRenderer",
|
||||
"ParcelFileDescriptor",
|
||||
]
|
||||
Reference in new issue
Block a user