PSDraw.py 6.8 KB

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