PSDraw.py 6.7 KB

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