utils.py 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. # SPDX-FileCopyrightText: 2026 geisserml <geisserml@gmail.com>
  2. # SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
  3. import os
  4. import ctypes
  5. import pypdfium2.raw as pdfium_c
  6. def color_tohex(color, rev_byteorder):
  7. if len(color) != 4:
  8. raise ValueError("Color must consist of exactly 4 values.")
  9. if not all(0 <= c <= 255 for c in color):
  10. raise ValueError("Color value exceeds boundaries.")
  11. # different color interpretation with FPDF_REVERSE_BYTE_ORDER might be a bug? at least it's not documented.
  12. r, g, b, a = color
  13. channels = (a, b, g, r) if rev_byteorder else (a, r, g, b)
  14. c_color = 0
  15. shift = 24
  16. for c in channels:
  17. c_color |= c << shift
  18. shift -= 8
  19. return c_color
  20. def set_callback(struct, fname, callback):
  21. setattr(struct, fname, type( getattr(struct, fname) )(callback))
  22. def is_stream(buf, spec="r"):
  23. methods = []
  24. assert set(spec).issubset( set("rw") )
  25. if "r" in spec:
  26. methods += ["seek", "tell", "read", "readinto"]
  27. if "w" in spec:
  28. methods += ["write"]
  29. return all(callable(getattr(buf, a, None)) for a in methods)
  30. def get_buffer(ptr, size):
  31. obj = ptr.contents
  32. return (type(obj) * size).from_address( ctypes.addressof(obj) )
  33. class _buffer_reader:
  34. def __init__(self, py_buffer):
  35. self.py_buffer = py_buffer
  36. def __call__(self, _, position, p_buf_first, size):
  37. c_buffer = get_buffer(p_buf_first, size)
  38. self.py_buffer.seek(position)
  39. self.py_buffer.readinto(c_buffer)
  40. return 1
  41. class _buffer_writer:
  42. def __init__(self, py_buffer):
  43. self.py_buffer = py_buffer
  44. def __call__(self, _, p_data_first, size):
  45. # c_void_p has no .contents, need to cast
  46. p_data_first = ctypes.cast(p_data_first, ctypes.POINTER(ctypes.c_ubyte))
  47. c_buffer = get_buffer(p_data_first, size)
  48. self.py_buffer.write(c_buffer)
  49. return 1
  50. def get_bufreader(buffer):
  51. file_len = buffer.seek(0, os.SEEK_END)
  52. buffer.seek(0)
  53. reader = pdfium_c.FPDF_FILEACCESS()
  54. reader.m_FileLen = file_len
  55. set_callback(reader, "m_GetBlock", _buffer_reader(buffer))
  56. reader.m_Param = None
  57. to_hold = (reader.m_GetBlock, )
  58. return reader, to_hold
  59. def get_bufwriter(buffer):
  60. writer = pdfium_c.FPDF_FILEWRITE(version=1)
  61. set_callback(writer, "WriteBlock", _buffer_writer(buffer))
  62. return writer
  63. def pages_c_array(pages):
  64. if not pages:
  65. return None, 0
  66. count = len(pages)
  67. c_array = (pdfium_c.FPDF_PAGE * count)(*[p.raw for p in pages])
  68. return c_array, count