Initial commit of Roster
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# SPDX-FileCopyrightText: 2024 geisserml <[email protected]>
|
||||
# SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
|
||||
from pypdfium2.internal.bases import *
|
||||
from pypdfium2.internal.consts import *
|
||||
from pypdfium2.internal.utils import *
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,108 @@
|
||||
# SPDX-FileCopyrightText: 2024 geisserml <[email protected]>
|
||||
# SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
|
||||
__all__ = ("AutoCastable", "AutoCloseable", "DEBUG_AUTOCLOSE", "LIBRARY_AVAILABLE")
|
||||
|
||||
import os
|
||||
import sys
|
||||
import ctypes
|
||||
import weakref
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# mutable bools
|
||||
DEBUG_AUTOCLOSE = ctypes.c_bool(False)
|
||||
LIBRARY_AVAILABLE = ctypes.c_bool(False) # set to true on library init
|
||||
|
||||
STATE_INVALID = -1
|
||||
STATE_AUTO = 0
|
||||
STATE_EXPLICIT = 1
|
||||
STATE_BYPARENT = 2
|
||||
|
||||
|
||||
class AutoCastable:
|
||||
|
||||
@property
|
||||
def _as_parameter_(self):
|
||||
return self.raw
|
||||
|
||||
|
||||
def _close_template(close_func, raw, obj_repr, state, parent, *args, **kwargs):
|
||||
|
||||
if DEBUG_AUTOCLOSE:
|
||||
desc = {STATE_AUTO: "auto", STATE_EXPLICIT: "explicit", STATE_BYPARENT: "by parent"}[state.value]
|
||||
# use os.write() rather than print() to avoid "reentrant call" exceptions on shutdown (see https://stackoverflow.com/q/75367828/15547292)
|
||||
os.write(sys.stderr.fileno(), f"Close ({desc}) {obj_repr}\n".encode())
|
||||
|
||||
if not LIBRARY_AVAILABLE:
|
||||
os.write(sys.stderr.fileno(), f"-> Cannot close object, library is destroyed. This may cause a memory leak!\n".encode())
|
||||
return
|
||||
|
||||
assert (parent is None) or not parent._tree_closed()
|
||||
close_func(raw, *args, **kwargs)
|
||||
|
||||
|
||||
class AutoCloseable (AutoCastable):
|
||||
|
||||
def __init__(self, close_func, *args, obj=None, needs_free=True, **kwargs):
|
||||
|
||||
# NOTE proactively prevent accidental double initialization
|
||||
assert not hasattr(self, "_finalizer")
|
||||
|
||||
self._close_func = close_func
|
||||
self._obj = self if obj is None else obj
|
||||
self._uuid = uuid.uuid4()
|
||||
self._ex_args = args
|
||||
self._ex_kwargs = kwargs
|
||||
self._autoclose_state = ctypes.c_int8(STATE_AUTO) # mutable int
|
||||
|
||||
self._finalizer = None
|
||||
self._kids = []
|
||||
if needs_free:
|
||||
self._attach_finalizer()
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{type(self).__name__} uuid:{str(self._uuid)[:8]}>"
|
||||
|
||||
|
||||
def _attach_finalizer(self):
|
||||
# NOTE this function captures the value of the `parent` property at finalizer installation time - if it changes, detach the old finalizer and create a new one
|
||||
assert self._finalizer is None
|
||||
self._finalizer = weakref.finalize(self._obj, _close_template, self._close_func, self.raw, repr(self), self._autoclose_state, self.parent, *self._ex_args, **self._ex_kwargs)
|
||||
|
||||
def _detach_finalizer(self):
|
||||
self._finalizer.detach()
|
||||
self._finalizer = None
|
||||
|
||||
def _tree_closed(self):
|
||||
if self.raw is None:
|
||||
return True
|
||||
if (self.parent is not None) and self.parent._tree_closed():
|
||||
return True
|
||||
return False
|
||||
|
||||
def _add_kid(self, k):
|
||||
self._kids.append( weakref.ref(k) )
|
||||
|
||||
|
||||
def close(self, _by_parent=False):
|
||||
|
||||
if not self.raw or not self._finalizer:
|
||||
return False
|
||||
|
||||
for k_ref in self._kids:
|
||||
k = k_ref()
|
||||
if k and k.raw:
|
||||
k.close(_by_parent=True)
|
||||
|
||||
self._autoclose_state.value = STATE_BYPARENT if _by_parent else STATE_EXPLICIT
|
||||
self._finalizer()
|
||||
self._autoclose_state.value = STATE_INVALID
|
||||
self.raw = None
|
||||
self._finalizer = None
|
||||
self._kids.clear()
|
||||
|
||||
return True
|
||||
@@ -0,0 +1,154 @@
|
||||
# SPDX-FileCopyrightText: 2024 geisserml <[email protected]>
|
||||
# SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
|
||||
import pypdfium2.raw as pdfium_c
|
||||
from pypdfium2.version import PDFIUM_INFO
|
||||
|
||||
|
||||
class _fallback_dict (dict):
|
||||
|
||||
def get(self, key, default_prefix="Unhandled constant"):
|
||||
return dict.get(self, key, f"{default_prefix} {key}")
|
||||
|
||||
|
||||
#: Convert a rotation value in degrees to a PDFium constant.
|
||||
RotationToConst = {
|
||||
0: 0,
|
||||
90: 1,
|
||||
180: 2,
|
||||
270: 3,
|
||||
}
|
||||
|
||||
#: Convert a PDFium rotation constant to a value in degrees. Inversion of :data:`.RotationToConst`.
|
||||
RotationToDegrees = {v: k for k, v in RotationToConst.items()}
|
||||
|
||||
|
||||
#: Get the number of channels for a PDFium bitmap format. (:attr:`FPDFBitmap_Unknown` is deliberately not handled.)
|
||||
BitmapTypeToNChannels = {
|
||||
pdfium_c.FPDFBitmap_Gray: 1,
|
||||
pdfium_c.FPDFBitmap_BGR: 3,
|
||||
pdfium_c.FPDFBitmap_BGRA: 4,
|
||||
pdfium_c.FPDFBitmap_BGRx: 4,
|
||||
}
|
||||
|
||||
#: Convert a PDFium bitmap format to string, assuming BGR byte order. (:attr:`FPDFBitmap_Unknown` is deliberately not handled.)
|
||||
BitmapTypeToStr = {
|
||||
pdfium_c.FPDFBitmap_Gray: "L",
|
||||
pdfium_c.FPDFBitmap_BGR: "BGR",
|
||||
pdfium_c.FPDFBitmap_BGRA: "BGRA",
|
||||
pdfium_c.FPDFBitmap_BGRx: "BGRX",
|
||||
}
|
||||
|
||||
#: Convert a PDFium bitmap format to string, assuming RGB byte order. (:attr:`FPDFBitmap_Unknown` is deliberately not handled.)
|
||||
BitmapTypeToStrReverse = {
|
||||
pdfium_c.FPDFBitmap_Gray: "L",
|
||||
pdfium_c.FPDFBitmap_BGR: "RGB",
|
||||
pdfium_c.FPDFBitmap_BGRA: "RGBA",
|
||||
pdfium_c.FPDFBitmap_BGRx: "RGBX",
|
||||
}
|
||||
|
||||
#: Convert a string to PDFium bitmap format, assuming BGR byte order. Inversion of :data:`BitmapTypeToStr`.
|
||||
BitmapStrToConst = {v: k for k, v in BitmapTypeToStr.items()}
|
||||
|
||||
#: Convert a string to PDFium bitmap format, assuming RGB byte order. Inversion of :data:`BitmapTypeToStrReverse`.
|
||||
BitmapStrReverseToConst = {v: k for k, v in BitmapTypeToStrReverse.items()}
|
||||
|
||||
#: Convert a PDFium form type (:attr:`FORMTYPE_*`) to string.
|
||||
FormTypeToStr = _fallback_dict({
|
||||
pdfium_c.FORMTYPE_NONE: "None",
|
||||
pdfium_c.FORMTYPE_ACRO_FORM: "AcroForm",
|
||||
pdfium_c.FORMTYPE_XFA_FULL: "XFA",
|
||||
pdfium_c.FORMTYPE_XFA_FOREGROUND: "XFAF",
|
||||
})
|
||||
|
||||
#: Convert a PDFium color space constant (:attr:`FPDF_COLORSPACE_*`) to string.
|
||||
ColorspaceToStr = _fallback_dict({
|
||||
pdfium_c.FPDF_COLORSPACE_UNKNOWN: "?",
|
||||
pdfium_c.FPDF_COLORSPACE_DEVICEGRAY: "DeviceGray",
|
||||
pdfium_c.FPDF_COLORSPACE_DEVICERGB: "DeviceRGB",
|
||||
pdfium_c.FPDF_COLORSPACE_DEVICECMYK: "DeviceCMYK",
|
||||
pdfium_c.FPDF_COLORSPACE_CALGRAY: "CalGray",
|
||||
pdfium_c.FPDF_COLORSPACE_CALRGB: "CalRGB",
|
||||
pdfium_c.FPDF_COLORSPACE_LAB: "Lab",
|
||||
pdfium_c.FPDF_COLORSPACE_ICCBASED: "ICCBased",
|
||||
pdfium_c.FPDF_COLORSPACE_SEPARATION: "Separation",
|
||||
pdfium_c.FPDF_COLORSPACE_DEVICEN: "DeviceN",
|
||||
pdfium_c.FPDF_COLORSPACE_INDEXED: "Indexed", # i. e. palettized
|
||||
pdfium_c.FPDF_COLORSPACE_PATTERN: "Pattern",
|
||||
})
|
||||
|
||||
#: Convert a PDFium view mode constant (:attr:`PDFDEST_VIEW_*`) to string.
|
||||
ViewmodeToStr = _fallback_dict({
|
||||
pdfium_c.PDFDEST_VIEW_UNKNOWN_MODE: "?",
|
||||
pdfium_c.PDFDEST_VIEW_XYZ: "XYZ",
|
||||
pdfium_c.PDFDEST_VIEW_FIT: "Fit",
|
||||
pdfium_c.PDFDEST_VIEW_FITH: "FitH",
|
||||
pdfium_c.PDFDEST_VIEW_FITV: "FitV",
|
||||
pdfium_c.PDFDEST_VIEW_FITR: "FitR",
|
||||
pdfium_c.PDFDEST_VIEW_FITB: "FitB",
|
||||
pdfium_c.PDFDEST_VIEW_FITBH: "FitBH",
|
||||
pdfium_c.PDFDEST_VIEW_FITBV: "FitBV",
|
||||
})
|
||||
|
||||
#: Convert a PDFium object type constant (:attr:`FPDF_PAGEOBJ_*`) to string.
|
||||
ObjectTypeToStr = _fallback_dict({
|
||||
pdfium_c.FPDF_PAGEOBJ_UNKNOWN: "?",
|
||||
pdfium_c.FPDF_PAGEOBJ_TEXT: "text",
|
||||
pdfium_c.FPDF_PAGEOBJ_PATH: "path",
|
||||
pdfium_c.FPDF_PAGEOBJ_IMAGE: "image",
|
||||
pdfium_c.FPDF_PAGEOBJ_SHADING: "shading",
|
||||
pdfium_c.FPDF_PAGEOBJ_FORM: "form",
|
||||
})
|
||||
|
||||
#: Convert an object type string to a PDFium constant. Inversion of :data:`.ObjectTypeToStr`.
|
||||
ObjectTypeToConst = {v: k for k, v in ObjectTypeToStr.items()}
|
||||
|
||||
#: Convert a PDFium page mode constant (:attr:`PAGEMODE_*`) to string.
|
||||
PageModeToStr = _fallback_dict({
|
||||
pdfium_c.PAGEMODE_UNKNOWN: "?",
|
||||
pdfium_c.PAGEMODE_USENONE: "None",
|
||||
pdfium_c.PAGEMODE_USEOUTLINES: "Outline",
|
||||
pdfium_c.PAGEMODE_USETHUMBS: "Thumbnails",
|
||||
pdfium_c.PAGEMODE_FULLSCREEN: "Full-screen",
|
||||
pdfium_c.PAGEMODE_USEOC: "Layers",
|
||||
pdfium_c.PAGEMODE_USEATTACHMENTS: "Attachments",
|
||||
})
|
||||
|
||||
#: Convert a PDFium error constant (:attr:`FPDF_ERR_*`) to string.
|
||||
ErrorToStr = _fallback_dict({
|
||||
pdfium_c.FPDF_ERR_SUCCESS: "Success",
|
||||
pdfium_c.FPDF_ERR_UNKNOWN: "Unknown error",
|
||||
pdfium_c.FPDF_ERR_FILE: "File access error",
|
||||
pdfium_c.FPDF_ERR_FORMAT: "Data format error",
|
||||
pdfium_c.FPDF_ERR_PASSWORD: "Incorrect password error",
|
||||
pdfium_c.FPDF_ERR_SECURITY: "Unsupported security scheme error",
|
||||
pdfium_c.FPDF_ERR_PAGE: "Page not found or content error",
|
||||
})
|
||||
|
||||
|
||||
# known implication: causes eager evaluation of pdfium version
|
||||
if "XFA" in PDFIUM_INFO.flags:
|
||||
#: [V8/XFA builds only] Convert a PDFium XFA error constant (:attr:`FPDF_ERR_XFA*`) to string.
|
||||
XFAErrorToStr = _fallback_dict({
|
||||
pdfium_c.FPDF_ERR_XFALOAD: "Load error",
|
||||
pdfium_c.FPDF_ERR_XFALAYOUT: "Layout error",
|
||||
})
|
||||
|
||||
#: Convert a PDFium unsupported constant (:attr:`FPDF_UNSP_*`) to string.
|
||||
UnsupportedInfoToStr = _fallback_dict({
|
||||
pdfium_c.FPDF_UNSP_DOC_XFAFORM: "XFA form",
|
||||
pdfium_c.FPDF_UNSP_DOC_PORTABLECOLLECTION: "Portable collection",
|
||||
pdfium_c.FPDF_UNSP_DOC_ATTACHMENT: "Attachment (incomplete support)", # https://crbug.com/pdfium/1945
|
||||
pdfium_c.FPDF_UNSP_DOC_SECURITY: "Security",
|
||||
pdfium_c.FPDF_UNSP_DOC_SHAREDREVIEW: "Shared review",
|
||||
pdfium_c.FPDF_UNSP_DOC_SHAREDFORM_ACROBAT: "Shared form (acrobat)",
|
||||
pdfium_c.FPDF_UNSP_DOC_SHAREDFORM_FILESYSTEM: "Shared form (filesystem)",
|
||||
pdfium_c.FPDF_UNSP_DOC_SHAREDFORM_EMAIL: "Shared form (email)",
|
||||
pdfium_c.FPDF_UNSP_ANNOT_3DANNOT: "3D annotation",
|
||||
pdfium_c.FPDF_UNSP_ANNOT_MOVIE: "Movie annotation",
|
||||
pdfium_c.FPDF_UNSP_ANNOT_SOUND: "Sound annotation",
|
||||
pdfium_c.FPDF_UNSP_ANNOT_SCREEN_MEDIA: "Screen media annotation",
|
||||
pdfium_c.FPDF_UNSP_ANNOT_SCREEN_RICHMEDIA: "Screen rich media annotation",
|
||||
pdfium_c.FPDF_UNSP_ANNOT_ATTACHMENT: "Attachment annotation",
|
||||
pdfium_c.FPDF_UNSP_ANNOT_SIG: "Signature annotation",
|
||||
})
|
||||
@@ -0,0 +1,91 @@
|
||||
# SPDX-FileCopyrightText: 2024 geisserml <[email protected]>
|
||||
# SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
|
||||
|
||||
import ctypes
|
||||
import pypdfium2.raw as pdfium_c
|
||||
|
||||
|
||||
def color_tohex(color, rev_byteorder):
|
||||
|
||||
if len(color) != 4:
|
||||
raise ValueError("Color must consist of exactly 4 values.")
|
||||
if not all(0 <= c <= 255 for c in color):
|
||||
raise ValueError("Color value exceeds boundaries.")
|
||||
|
||||
# different color interpretation with FPDF_REVERSE_BYTE_ORDER might be a bug? at least it's not documented.
|
||||
r, g, b, a = color
|
||||
channels = (a, b, g, r) if rev_byteorder else (a, r, g, b)
|
||||
|
||||
c_color = 0
|
||||
shift = 24
|
||||
for c in channels:
|
||||
c_color |= c << shift
|
||||
shift -= 8
|
||||
|
||||
return c_color
|
||||
|
||||
|
||||
def set_callback(struct, fname, callback):
|
||||
setattr(struct, fname, type( getattr(struct, fname) )(callback))
|
||||
|
||||
|
||||
def is_buffer(buf, spec="r"):
|
||||
methods = []
|
||||
assert set(spec).issubset( set("rw") )
|
||||
if "r" in spec:
|
||||
methods += ["seek", "tell", "read", "readinto"]
|
||||
if "w" in spec:
|
||||
methods += ["write"]
|
||||
return all(callable(getattr(buf, a, None)) for a in methods)
|
||||
|
||||
|
||||
class _buffer_reader:
|
||||
|
||||
def __init__(self, buffer):
|
||||
self.buffer = buffer
|
||||
|
||||
def __call__(self, _, position, p_buf, size):
|
||||
c_buf = ctypes.cast(p_buf, ctypes.POINTER(ctypes.c_char * size))
|
||||
self.buffer.seek(position)
|
||||
self.buffer.readinto(c_buf.contents)
|
||||
return 1
|
||||
|
||||
|
||||
class _buffer_writer:
|
||||
|
||||
def __init__(self, buffer):
|
||||
self.buffer = buffer
|
||||
|
||||
def __call__(self, _, data, size):
|
||||
block = ctypes.cast(data, ctypes.POINTER(ctypes.c_ubyte * size))
|
||||
self.buffer.write(block.contents)
|
||||
return 1
|
||||
|
||||
|
||||
def get_bufreader(buffer):
|
||||
|
||||
file_len = buffer.seek(0, 2)
|
||||
buffer.seek(0)
|
||||
|
||||
reader = pdfium_c.FPDF_FILEACCESS()
|
||||
reader.m_FileLen = file_len
|
||||
set_callback(reader, "m_GetBlock", _buffer_reader(buffer))
|
||||
reader.m_Param = None
|
||||
|
||||
to_hold = (reader.m_GetBlock, )
|
||||
|
||||
return reader, to_hold
|
||||
|
||||
|
||||
def get_bufwriter(buffer):
|
||||
writer = pdfium_c.FPDF_FILEWRITE(version=1)
|
||||
set_callback(writer, "WriteBlock", _buffer_writer(buffer))
|
||||
return writer
|
||||
|
||||
|
||||
def pages_c_array(pages):
|
||||
if not pages:
|
||||
return None, 0
|
||||
count = len(pages)
|
||||
c_array = (pdfium_c.FPDF_PAGE * count)(*[p.raw for p in pages])
|
||||
return c_array, count
|
||||
Reference in New Issue
Block a user