PSDraw.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. #
  2. # The Python Imaging Library
  3. # $Id$
  4. #
  5. # Simple PostScript graphics interface
  6. #
  7. # History:
  8. # 1996-04-20 fl Created
  9. # 1999-01-10 fl Added gsave/grestore to image method
  10. # 2005-05-04 fl Fixed floating point issue in image (from Eric Etheridge)
  11. #
  12. # Copyright (c) 1997-2005 by Secret Labs AB. All rights reserved.
  13. # Copyright (c) 1996 by Fredrik Lundh.
  14. #
  15. # See the README file for information on usage and redistribution.
  16. #
  17. from __future__ import annotations
  18. import sys
  19. from typing import TYPE_CHECKING
  20. from . import EpsImagePlugin
  21. ##
  22. # Simple PostScript graphics interface.
  23. class PSDraw:
  24. """
  25. Sets up printing to the given file. If ``fp`` is omitted,
  26. ``sys.stdout.buffer`` or ``sys.stdout`` is assumed.
  27. """
  28. def __init__(self, fp=None):
  29. if not fp:
  30. try:
  31. fp = sys.stdout.buffer
  32. except AttributeError:
  33. fp = sys.stdout
  34. self.fp = fp
  35. def begin_document(self, id: str | None = None) -> None:
  36. """Set up printing of a document. (Write PostScript DSC header.)"""
  37. # FIXME: incomplete
  38. self.fp.write(
  39. b"%!PS-Adobe-3.0\n"
  40. b"save\n"
  41. b"/showpage { } def\n"
  42. b"%%EndComments\n"
  43. b"%%BeginDocument\n"
  44. )
  45. # self.fp.write(ERROR_PS) # debugging!
  46. self.fp.write(EDROFF_PS)
  47. self.fp.write(VDI_PS)
  48. self.fp.write(b"%%EndProlog\n")
  49. self.isofont: dict[bytes, int] = {}
  50. def end_document(self) -> None:
  51. """Ends printing. (Write PostScript DSC footer.)"""
  52. self.fp.write(b"%%EndDocument\nrestore showpage\n%%End\n")
  53. if hasattr(self.fp, "flush"):
  54. self.fp.flush()
  55. def setfont(self, font: str, size: int) -> None:
  56. """
  57. Selects which font to use.
  58. :param font: A PostScript font name
  59. :param size: Size in points.
  60. """
  61. font_bytes = bytes(font, "UTF-8")
  62. if font_bytes not in self.isofont:
  63. # reencode font
  64. self.fp.write(
  65. b"/PSDraw-%s ISOLatin1Encoding /%s E\n" % (font_bytes, font_bytes)
  66. )
  67. self.isofont[font_bytes] = 1
  68. # rough
  69. self.fp.write(b"/F0 %d /PSDraw-%s F\n" % (size, font_bytes))
  70. def line(self, xy0: tuple[int, int], xy1: tuple[int, int]) -> None:
  71. """
  72. Draws a line between the two points. Coordinates are given in
  73. PostScript point coordinates (72 points per inch, (0, 0) is the lower
  74. left corner of the page).
  75. """
  76. self.fp.write(b"%d %d %d %d Vl\n" % (*xy0, *xy1))
  77. def rectangle(self, box: tuple[int, int, int, int]) -> None:
  78. """
  79. Draws a rectangle.
  80. :param box: A tuple of four integers, specifying left, bottom, width and
  81. height.
  82. """
  83. self.fp.write(b"%d %d M 0 %d %d Vr\n" % box)
  84. def text(self, xy: tuple[int, int], text: str) -> None:
  85. """
  86. Draws text at the given position. You must use
  87. :py:meth:`~PIL.PSDraw.PSDraw.setfont` before calling this method.
  88. """
  89. text_bytes = bytes(text, "UTF-8")
  90. text_bytes = b"\\(".join(text_bytes.split(b"("))
  91. text_bytes = b"\\)".join(text_bytes.split(b")"))
  92. self.fp.write(b"%d %d M (%s) S\n" % (xy + (text_bytes,)))
  93. if TYPE_CHECKING:
  94. from . import Image
  95. def image(
  96. self, box: tuple[int, int, int, int], im: Image.Image, dpi: int | None = None
  97. ) -> None:
  98. """Draw a PIL image, centered in the given box."""
  99. # default resolution depends on mode
  100. if not dpi:
  101. if im.mode == "1":
  102. dpi = 200 # fax
  103. else:
  104. dpi = 100 # grayscale
  105. # image size (on paper)
  106. x = im.size[0] * 72 / dpi
  107. y = im.size[1] * 72 / dpi
  108. # max allowed size
  109. xmax = float(box[2] - box[0])
  110. ymax = float(box[3] - box[1])
  111. if x > xmax:
  112. y = y * xmax / x
  113. x = xmax
  114. if y > ymax:
  115. x = x * ymax / y
  116. y = ymax
  117. dx = (xmax - x) / 2 + box[0]
  118. dy = (ymax - y) / 2 + box[1]
  119. self.fp.write(b"gsave\n%f %f translate\n" % (dx, dy))
  120. if (x, y) != im.size:
  121. # EpsImagePlugin._save prints the image at (0,0,xsize,ysize)
  122. sx = x / im.size[0]
  123. sy = y / im.size[1]
  124. self.fp.write(b"%f %f scale\n" % (sx, sy))
  125. EpsImagePlugin._save(im, self.fp, "", 0)
  126. self.fp.write(b"\ngrestore\n")
  127. # --------------------------------------------------------------------
  128. # PostScript driver
  129. #
  130. # EDROFF.PS -- PostScript driver for Edroff 2
  131. #
  132. # History:
  133. # 94-01-25 fl: created (edroff 2.04)
  134. #
  135. # Copyright (c) Fredrik Lundh 1994.
  136. #
  137. EDROFF_PS = b"""\
  138. /S { show } bind def
  139. /P { moveto show } bind def
  140. /M { moveto } bind def
  141. /X { 0 rmoveto } bind def
  142. /Y { 0 exch rmoveto } bind def
  143. /E { findfont
  144. dup maxlength dict begin
  145. {
  146. 1 index /FID ne { def } { pop pop } ifelse
  147. } forall
  148. /Encoding exch def
  149. dup /FontName exch def
  150. currentdict end definefont pop
  151. } bind def
  152. /F { findfont exch scalefont dup setfont
  153. [ exch /setfont cvx ] cvx bind def
  154. } bind def
  155. """
  156. #
  157. # VDI.PS -- PostScript driver for VDI meta commands
  158. #
  159. # History:
  160. # 94-01-25 fl: created (edroff 2.04)
  161. #
  162. # Copyright (c) Fredrik Lundh 1994.
  163. #
  164. VDI_PS = b"""\
  165. /Vm { moveto } bind def
  166. /Va { newpath arcn stroke } bind def
  167. /Vl { moveto lineto stroke } bind def
  168. /Vc { newpath 0 360 arc closepath } bind def
  169. /Vr { exch dup 0 rlineto
  170. exch dup 0 exch rlineto
  171. exch neg 0 rlineto
  172. 0 exch neg rlineto
  173. setgray fill } bind def
  174. /Tm matrix def
  175. /Ve { Tm currentmatrix pop
  176. translate scale newpath 0 0 .5 0 360 arc closepath
  177. Tm setmatrix
  178. } bind def
  179. /Vf { currentgray exch setgray fill setgray } bind def
  180. """
  181. #
  182. # ERROR.PS -- Error handler
  183. #
  184. # History:
  185. # 89-11-21 fl: created (pslist 1.10)
  186. #
  187. ERROR_PS = b"""\
  188. /landscape false def
  189. /errorBUF 200 string def
  190. /errorNL { currentpoint 10 sub exch pop 72 exch moveto } def
  191. errordict begin /handleerror {
  192. initmatrix /Courier findfont 10 scalefont setfont
  193. newpath 72 720 moveto $error begin /newerror false def
  194. (PostScript Error) show errorNL errorNL
  195. (Error: ) show
  196. /errorname load errorBUF cvs show errorNL errorNL
  197. (Command: ) show
  198. /command load dup type /stringtype ne { errorBUF cvs } if show
  199. errorNL errorNL
  200. (VMstatus: ) show
  201. vmstatus errorBUF cvs show ( bytes available, ) show
  202. errorBUF cvs show ( bytes used at level ) show
  203. errorBUF cvs show errorNL errorNL
  204. (Operand stargck: ) show errorNL /ostargck load {
  205. dup type /stringtype ne { errorBUF cvs } if 72 0 rmoveto show errorNL
  206. } forall errorNL
  207. (Execution stargck: ) show errorNL /estargck load {
  208. dup type /stringtype ne { errorBUF cvs } if 72 0 rmoveto show errorNL
  209. } forall
  210. end showpage
  211. } def end
  212. """