eideticpdf 0.9.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. data/fonts/AntiqueOlive-Bold.afm +406 -0
  2. data/fonts/AntiqueOlive-Bold.inf +27 -0
  3. data/fonts/AntiqueOlive-Compact.afm +401 -0
  4. data/fonts/AntiqueOlive-Compact.inf +27 -0
  5. data/fonts/AntiqueOlive-Italic.afm +401 -0
  6. data/fonts/AntiqueOlive-Italic.inf +27 -0
  7. data/fonts/AntiqueOlive-Roman.afm +409 -0
  8. data/fonts/AntiqueOlive-Roman.inf +27 -0
  9. data/fonts/AvantGarde-Book.afm +667 -0
  10. data/fonts/AvantGarde-Book.inf +26 -0
  11. data/fonts/AvantGarde-BookOblique.afm +667 -0
  12. data/fonts/AvantGarde-BookOblique.inf +26 -0
  13. data/fonts/AvantGarde-Demi.afm +673 -0
  14. data/fonts/AvantGarde-Demi.inf +26 -0
  15. data/fonts/AvantGarde-DemiOblique.afm +673 -0
  16. data/fonts/AvantGarde-DemiOblique.inf +26 -0
  17. data/fonts/Bookman-Demi.afm +669 -0
  18. data/fonts/Bookman-Demi.inf +25 -0
  19. data/fonts/Bookman-DemiItalic.afm +669 -0
  20. data/fonts/Bookman-DemiItalic.inf +25 -0
  21. data/fonts/Bookman-Light.afm +643 -0
  22. data/fonts/Bookman-Light.inf +25 -0
  23. data/fonts/Bookman-LightItalic.afm +620 -0
  24. data/fonts/Bookman-LightItalic.inf +25 -0
  25. data/fonts/Clarendon-Bold.afm +412 -0
  26. data/fonts/Clarendon-Bold.inf +27 -0
  27. data/fonts/Clarendon-Light.afm +410 -0
  28. data/fonts/Clarendon-Light.inf +27 -0
  29. data/fonts/Clarendon.afm +410 -0
  30. data/fonts/Clarendon.inf +27 -0
  31. data/fonts/CooperBlack-Italic.afm +421 -0
  32. data/fonts/CooperBlack-Italic.inf +26 -0
  33. data/fonts/CooperBlack.afm +427 -0
  34. data/fonts/CooperBlack.inf +26 -0
  35. data/fonts/Coronet-Regular.afm +327 -0
  36. data/fonts/Coronet-Regular.inf +25 -0
  37. data/fonts/Courier-Bold.afm +342 -0
  38. data/fonts/Courier-Bold.inf +26 -0
  39. data/fonts/Courier-BoldOblique.afm +342 -0
  40. data/fonts/Courier-BoldOblique.inf +26 -0
  41. data/fonts/Courier-Oblique.afm +342 -0
  42. data/fonts/Courier-Oblique.inf +26 -0
  43. data/fonts/Courier.afm +342 -0
  44. data/fonts/Courier.inf +26 -0
  45. data/fonts/Eurostile.afm +419 -0
  46. data/fonts/Eurostile.inf +27 -0
  47. data/fonts/Goudy-ExtraBold.afm +412 -0
  48. data/fonts/Goudy-ExtraBold.inf +27 -0
  49. data/fonts/Helvetica-Bold.afm +2827 -0
  50. data/fonts/Helvetica-Bold.inf +26 -0
  51. data/fonts/Helvetica-BoldOblique.afm +2827 -0
  52. data/fonts/Helvetica-BoldOblique.inf +26 -0
  53. data/fonts/Helvetica-Condensed-Bold.afm +623 -0
  54. data/fonts/Helvetica-Condensed-Bold.inf +25 -0
  55. data/fonts/Helvetica-Condensed-BoldObl.afm +623 -0
  56. data/fonts/Helvetica-Condensed-BoldObl.inf +25 -0
  57. data/fonts/Helvetica-Condensed-Oblique.afm +528 -0
  58. data/fonts/Helvetica-Condensed-Oblique.inf +25 -0
  59. data/fonts/Helvetica-Condensed.afm +528 -0
  60. data/fonts/Helvetica-Condensed.inf +25 -0
  61. data/fonts/Helvetica-Narrow-Bold.afm +1439 -0
  62. data/fonts/Helvetica-Narrow-Bold.inf +26 -0
  63. data/fonts/Helvetica-Narrow-BoldOblique.afm +1439 -0
  64. data/fonts/Helvetica-Narrow-BoldOblique.inf +26 -0
  65. data/fonts/Helvetica-Narrow-Oblique.afm +1556 -0
  66. data/fonts/Helvetica-Narrow-Oblique.inf +26 -0
  67. data/fonts/Helvetica-Narrow.afm +1556 -0
  68. data/fonts/Helvetica-Narrow.inf +26 -0
  69. data/fonts/Helvetica-Oblique.afm +3051 -0
  70. data/fonts/Helvetica-Oblique.inf +26 -0
  71. data/fonts/Helvetica.afm +3051 -0
  72. data/fonts/Helvetica.inf +26 -0
  73. data/fonts/LetterGothic-Bold.afm +443 -0
  74. data/fonts/LetterGothic-Bold.inf +26 -0
  75. data/fonts/LetterGothic-BoldSlanted.afm +443 -0
  76. data/fonts/LetterGothic-BoldSlanted.inf +26 -0
  77. data/fonts/LetterGothic-Slanted.afm +443 -0
  78. data/fonts/LetterGothic-Slanted.inf +26 -0
  79. data/fonts/LetterGothic.afm +443 -0
  80. data/fonts/LetterGothic.inf +26 -0
  81. data/fonts/LubalinGraph-Book.afm +351 -0
  82. data/fonts/LubalinGraph-Book.inf +25 -0
  83. data/fonts/LubalinGraph-BookOblique.afm +351 -0
  84. data/fonts/LubalinGraph-BookOblique.inf +25 -0
  85. data/fonts/LubalinGraph-Demi.afm +351 -0
  86. data/fonts/LubalinGraph-Demi.inf +25 -0
  87. data/fonts/LubalinGraph-DemiOblique.afm +351 -0
  88. data/fonts/LubalinGraph-DemiOblique.inf +25 -0
  89. data/fonts/Marigold.afm +491 -0
  90. data/fonts/Marigold.inf +27 -0
  91. data/fonts/MonaLisa-Recut.afm +663 -0
  92. data/fonts/MonaLisa-Recut.inf +27 -0
  93. data/fonts/NewCenturySchlbk-Bold.afm +886 -0
  94. data/fonts/NewCenturySchlbk-Bold.inf +25 -0
  95. data/fonts/NewCenturySchlbk-BoldItalic.afm +1521 -0
  96. data/fonts/NewCenturySchlbk-BoldItalic.inf +26 -0
  97. data/fonts/NewCenturySchlbk-Italic.afm +1113 -0
  98. data/fonts/NewCenturySchlbk-Italic.inf +26 -0
  99. data/fonts/NewCenturySchlbk-Roman.afm +1067 -0
  100. data/fonts/NewCenturySchlbk-Roman.inf +26 -0
  101. data/fonts/Optima-Bold.afm +515 -0
  102. data/fonts/Optima-Bold.inf +27 -0
  103. data/fonts/Optima-BoldItalic.afm +712 -0
  104. data/fonts/Optima-BoldItalic.inf +26 -0
  105. data/fonts/Optima-Italic.afm +737 -0
  106. data/fonts/Optima-Italic.inf +26 -0
  107. data/fonts/Optima.afm +501 -0
  108. data/fonts/Optima.inf +27 -0
  109. data/fonts/Palatino-Bold.afm +675 -0
  110. data/fonts/Palatino-Bold.inf +26 -0
  111. data/fonts/Palatino-BoldItalic.afm +702 -0
  112. data/fonts/Palatino-BoldItalic.inf +26 -0
  113. data/fonts/Palatino-Italic.afm +700 -0
  114. data/fonts/Palatino-Italic.inf +26 -0
  115. data/fonts/Palatino-Roman.afm +718 -0
  116. data/fonts/Palatino-Roman.inf +26 -0
  117. data/fonts/StempelGaramond-Bold.afm +412 -0
  118. data/fonts/StempelGaramond-Bold.inf +27 -0
  119. data/fonts/StempelGaramond-BoldItalic.afm +413 -0
  120. data/fonts/StempelGaramond-BoldItalic.inf +27 -0
  121. data/fonts/StempelGaramond-Italic.afm +413 -0
  122. data/fonts/StempelGaramond-Italic.inf +27 -0
  123. data/fonts/StempelGaramond-Roman.afm +412 -0
  124. data/fonts/StempelGaramond-Roman.inf +27 -0
  125. data/fonts/Symbol.afm +213 -0
  126. data/fonts/Symbol.inf +27 -0
  127. data/fonts/Times-Bold.afm +2588 -0
  128. data/fonts/Times-Bold.inf +26 -0
  129. data/fonts/Times-BoldItalic.afm +2384 -0
  130. data/fonts/Times-BoldItalic.inf +26 -0
  131. data/fonts/Times-Italic.afm +2667 -0
  132. data/fonts/Times-Italic.inf +26 -0
  133. data/fonts/Times-Roman.afm +2419 -0
  134. data/fonts/Times-Roman.inf +26 -0
  135. data/fonts/Univers-Bold.afm +712 -0
  136. data/fonts/Univers-Bold.inf +27 -0
  137. data/fonts/Univers-BoldOblique.afm +712 -0
  138. data/fonts/Univers-BoldOblique.inf +27 -0
  139. data/fonts/Univers-Condensed.afm +403 -0
  140. data/fonts/Univers-Condensed.inf +27 -0
  141. data/fonts/Univers-CondensedOblique.afm +403 -0
  142. data/fonts/Univers-CondensedOblique.inf +27 -0
  143. data/fonts/Univers-Light.afm +737 -0
  144. data/fonts/Univers-Light.inf +27 -0
  145. data/fonts/Univers-LightOblique.afm +737 -0
  146. data/fonts/Univers-LightOblique.inf +27 -0
  147. data/fonts/Univers-Oblique.afm +656 -0
  148. data/fonts/Univers-Oblique.inf +27 -0
  149. data/fonts/Univers.afm +656 -0
  150. data/fonts/Univers.inf +27 -0
  151. data/fonts/ZapfChancery-MediumItalic.afm +842 -0
  152. data/fonts/ZapfChancery-MediumItalic.inf +26 -0
  153. data/fonts/ZapfDingbats.afm +225 -0
  154. data/fonts/ZapfDingbats.inf +26 -0
  155. data/lib/epdfafm.rb +334 -0
  156. data/lib/epdfdw.rb +710 -0
  157. data/lib/epdfk.rb +3142 -0
  158. data/lib/epdfo.rb +882 -0
  159. data/lib/epdfpw.rb +1749 -0
  160. data/lib/epdfs.rb +101 -0
  161. data/lib/epdfsw.rb +267 -0
  162. data/lib/epdft.rb +145 -0
  163. data/lib/epdftt.rb +458 -0
  164. data/test/pdf_tests.rb +16 -0
  165. data/test/test.rb +816 -0
  166. data/test/test_epdfafm.rb +202 -0
  167. data/test/test_epdfdw.rb +369 -0
  168. data/test/test_epdfk.rb +47 -0
  169. data/test/test_epdfo.rb +762 -0
  170. data/test/test_epdfpw.rb +129 -0
  171. data/test/test_epdfs.rb +54 -0
  172. data/test/test_epdfsw.rb +314 -0
  173. data/test/test_epdft.rb +198 -0
  174. data/test/test_epdftt.rb +34 -0
  175. data/test/test_helpers.rb +18 -0
  176. data/test/testimg.jpg +0 -0
  177. metadata +247 -0
data/lib/epdfpw.rb ADDED
@@ -0,0 +1,1749 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: ASCII-8BIT
3
+ #
4
+ # Created by Brent Rowland on 2007-07-13.
5
+ # Copyright (c) 2007, Eidetic Software. All rights reserved.
6
+
7
+ require 'epdfo'
8
+ require 'epdfsw'
9
+ require 'epdfk'
10
+ require 'epdft'
11
+ require 'epdfafm'
12
+ require 'epdftt'
13
+ require 'epdfs'
14
+
15
+ module EideticPDF
16
+ Font = Struct.new(:name, :size, :style, :color, :encoding, :sub_type, :widths, :ascent, :descent, :height,
17
+ :underline_position, :underline_thickness)
18
+ Location = Struct.new(:x, :y)
19
+ Signs = Struct.new(:x, :y)
20
+ Bullet = Struct.new(:name, :width, :proc)
21
+
22
+ SIGNS = [ Signs.new(1, -1), Signs.new(-1, -1), Signs.new(-1, 1), Signs.new(1, 1) ]
23
+ UNIT_CONVERSION = { :pt => 1, :in => 72, :cm => 28.35 }
24
+ LINE_PATTERNS = { :solid => [], :dotted => [1, 2], :dashed => [4, 2] }
25
+ LINE_CAP_STYLES = [:butt_cap, :round_cap, :projecting_square_cap].freeze
26
+ IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0].freeze
27
+
28
+ class PageStyle # :nodoc:
29
+ attr_reader :page_size, :crop_size, :orientation, :landscape, :rotate
30
+
31
+ PORTRAIT = 0
32
+ LANDSCAPE = 270
33
+
34
+ def initialize(options={})
35
+ page_size = options[:page_size] || :letter
36
+ crop_size = options[:crop_size] || page_size
37
+ @orientation = options[:orientation] || :portrait
38
+ @page_size = make_size_rectangle(page_size, @orientation)
39
+ @crop_size = make_size_rectangle(crop_size, @orientation)
40
+ @landscape = (@orientation == :landscape)
41
+ @rotate = ROTATIONS[options[:rotate] || :portrait]
42
+ end
43
+
44
+ SIZES = {
45
+ :letter => [612, 792].freeze,
46
+ :legal => [612, 1008].freeze,
47
+ :A4 => [595, 842].freeze,
48
+ :B5 => [499, 708].freeze,
49
+ :C5 => [459, 649].freeze
50
+ }
51
+ ROTATIONS = { :portrait => PORTRAIT, :landscape => LANDSCAPE }.freeze
52
+
53
+ protected
54
+ def make_size_rectangle(size, orientation)
55
+ w, h = SIZES[size]
56
+ w, h = h, w if orientation == :landscape
57
+ PdfObjects::Rectangle.new(0, 0, w, h)
58
+ end
59
+ end
60
+
61
+ class PageWriter # :nodoc:
62
+ include JpegInfo
63
+
64
+ private
65
+ def iconv_encoding(encoding)
66
+ case encoding
67
+ when 'WinAnsiEncoding' then 'CP1252'
68
+ else encoding
69
+ end
70
+ end
71
+
72
+ def pdf_encoding(encoding, font_name)
73
+ return 'StandardEncoding' if ['Symbol','ZapfDingbats'].include?(font_name)
74
+ case encoding.upcase
75
+ when 'CP1252' then 'WinAnsiEncoding'
76
+ else encoding
77
+ end
78
+ end
79
+
80
+ def to_points(units, measurement)
81
+ UNIT_CONVERSION[units] * measurement
82
+ end
83
+
84
+ def from_points(units, measurement)
85
+ measurement.quo(UNIT_CONVERSION[units])
86
+ end
87
+
88
+ def make_loc(x, y)
89
+ Location.new(x, y)
90
+ end
91
+
92
+ def translate(x, y)
93
+ Location.new(x, page_height - y)
94
+ end
95
+
96
+ def translate_p(p)
97
+ Location.new(p.x, page_height - p.y)
98
+ end
99
+
100
+ def convert_units(loc, from_units, to_units)
101
+ Location.new(
102
+ loc.x * UNIT_CONVERSION[from_units].quo(UNIT_CONVERSION[to_units]),
103
+ loc.y * UNIT_CONVERSION[from_units].quo(UNIT_CONVERSION[to_units]))
104
+ end
105
+
106
+ def get_quadrant_bezier_points(quadrant, x, y, rx, ry=nil)
107
+ ry = rx if ry.nil?
108
+ a = 4.0 / 3.0 * (Math.sqrt(2) - 1.0)
109
+ bp = []
110
+ if quadrant.odd? # quadrant is odd
111
+ # (1,0)
112
+ bp << make_loc(x + (rx * SIGNS[quadrant - 1].x), y)
113
+ # (1,a)
114
+ bp << make_loc(bp[0].x, y + (a * ry * SIGNS[quadrant - 1].y))
115
+ # (a,1)
116
+ bp << make_loc(x + (a * rx * SIGNS[quadrant - 1].x), y + (ry * SIGNS[quadrant - 1].y))
117
+ # (0,1)
118
+ bp << make_loc(x, bp[2].y)
119
+ else # quadrant is even
120
+ # (0,1)
121
+ bp << make_loc(x, y + (ry * SIGNS[quadrant - 1].y))
122
+ # (a,1)
123
+ bp << make_loc(x + (a * rx * SIGNS[quadrant - 1].x), bp[0].y)
124
+ # (1,a)
125
+ bp << make_loc(x + (rx * SIGNS[quadrant - 1].x), y + (a * ry * SIGNS[quadrant - 1].y))
126
+ # (1,0)
127
+ bp << make_loc(bp[2].x, y)
128
+ end
129
+ bp
130
+ end
131
+
132
+ def rotate_xy_coordinate(x, y, angle)
133
+ theta = angle.degrees
134
+ r_cos = Math::cos(theta)
135
+ r_sin = Math::sin(theta)
136
+ x_rot = (r_cos * x) - (r_sin * y)
137
+ y_rot = (r_sin * x) + (r_cos * y)
138
+ [x_rot, y_rot]
139
+ end
140
+
141
+ def rotate_point(loc, angle)
142
+ x, y = rotate_xy_coordinate(loc.x, loc.y, angle)
143
+ make_loc(x, y)
144
+ end
145
+
146
+ def add_vector(point, angle, distance)
147
+ theta = angle.degrees
148
+ Location.new(point.x + Math::cos(theta) * distance, point.y + Math::sin(theta) * distance)
149
+ end
150
+
151
+ def rotate_points(mid, points, angle)
152
+ theta = angle.degrees
153
+ r_cos = Math::cos(theta)
154
+ r_sin = Math::sin(theta)
155
+ points.map do |p|
156
+ x, y = p.x - mid.x, p.y - mid.y
157
+ x_rot = (r_cos * x) - (r_sin * y)
158
+ y_rot = (r_sin * x) + (r_cos * y)
159
+ make_loc(x_rot + mid.x, y_rot + mid.y)
160
+ end
161
+ end
162
+
163
+ def calc_arc_small(r, mid_theta, half_angle, ccwcw)
164
+ half_theta = half_angle.abs.degrees
165
+ v_cos = Math::cos(half_theta)
166
+ v_sin = Math::sin(half_theta)
167
+
168
+ x0 = r * v_cos
169
+ y0 = -ccwcw * r * v_sin
170
+ x1 = r * (4.0 - v_cos) / 3.0
171
+ x2 = x1
172
+ y1 = r * ccwcw * (1.0 - v_cos) * (v_cos - 3.0) / (3.0 * v_sin)
173
+ y2 = -y1
174
+ x3 = r * v_cos
175
+ y3 = ccwcw * r * v_sin
176
+
177
+ x0, y0 = rotate_xy_coordinate(x0, y0, mid_theta)
178
+ x1, y1 = rotate_xy_coordinate(x1, y1, mid_theta)
179
+ x2, y2 = rotate_xy_coordinate(x2, y2, mid_theta)
180
+ x3, y3 = rotate_xy_coordinate(x3, y3, mid_theta)
181
+
182
+ [x0, y0, x1, y1, x2, y2, x3, y3]
183
+ end
184
+
185
+ def points_for_arc_small(x, y, r, mid_theta, half_angle, ccwcw)
186
+ x0, y0, x1, y1, x2, y2, x3, y3 = calc_arc_small(r, mid_theta, half_angle, ccwcw)
187
+ [make_loc(x+x0, y-y0), make_loc(x+x1, y-y1), make_loc(x+x2, y-y2), make_loc(x+x3, y-y3)]
188
+ end
189
+
190
+ def arc_small(x, y, r, mid_theta, half_angle, ccwcw, move_to0)
191
+ x0, y0, x1, y1, x2, y2, x3, y3 = calc_arc_small(r, mid_theta, half_angle, ccwcw)
192
+ line_to(x+x0, y-y0) unless move_to0
193
+ curve(x+x0, y-y0, x+x1, y-y1, x+x2, y-y2, x+x3, y-y3)
194
+ end
195
+
196
+ def set_text_angle(angle, x, y)
197
+ theta = angle.degrees
198
+ v_cos = Math::cos(theta)
199
+ v_sin = Math::sin(theta)
200
+ @tw.set_matrix(v_cos, v_sin, -v_sin, v_cos, to_points(@units, x), to_points(@units, y))
201
+ @text_angle = angle
202
+ end
203
+
204
+ def rgb_from_color(color)
205
+ color = named_colors[color] || 0 if color.respond_to? :to_str
206
+ color ||= 0
207
+ b = color & 0xFF
208
+ g = (color >> 8) & 0xFF
209
+ r = (color >> 16) & 0xFF
210
+ [r, g, b]
211
+ end
212
+
213
+ def color_from_rgb(r, g, b)
214
+ (r << 16) | (g << 8) | b
215
+ end
216
+ protected
217
+ def start_text
218
+ raise Exception.new("Already in text.") if @in_text
219
+ raise Exception.new("Not in page.") unless @doc.in_page
220
+ end_graph if @in_graph
221
+ @last_loc = Location.new(0, 0)
222
+ @in_text = true
223
+ @tw = TextWriter.new(@stream)
224
+ @tw.open
225
+ @tw
226
+ end
227
+
228
+ def end_text
229
+ raise Exception.new("Not in text.") unless @in_text
230
+ @tw.close
231
+ @in_text = false
232
+ end
233
+
234
+ def start_graph
235
+ raise Exception.new("Already in graph") if @in_graph
236
+ end_text if @in_text
237
+ @last_loc = Location.new(0, 0)
238
+ @in_graph = true
239
+ @gw = GraphWriter.new(@stream)
240
+ end
241
+
242
+ def end_path
243
+ gw.stroke if @auto_path
244
+ @in_path = false
245
+ end
246
+
247
+ def end_graph
248
+ raise Exception.new("Not in graph") unless @in_graph
249
+ end_path if @in_path
250
+ @gw = nil
251
+ @in_graph = false
252
+ end
253
+
254
+ def start_misc
255
+ raise Exception.new("Already in misc") if @in_misc
256
+ @in_misc = true
257
+ @mw = MiscWriter.new(@stream)
258
+ end
259
+
260
+ def end_misc
261
+ raise Exception.new("Not in misc") unless @in_misc
262
+ @mw = nil
263
+ @in_misc = false
264
+ end
265
+
266
+ def tw
267
+ @tw ||= start_text
268
+ end
269
+
270
+ def gw
271
+ @gw ||= start_graph
272
+ end
273
+
274
+ def mw
275
+ @mw ||= start_misc
276
+ end
277
+
278
+ def end_margins
279
+ end_path if @in_path
280
+ gw.restore_graphics_state
281
+ end
282
+
283
+ def end_sub_page
284
+ end_path if @in_path
285
+ gw.restore_graphics_state
286
+ end
287
+
288
+ def sub_orientation(pages_across, pages_down)
289
+ @page_width / pages_across > @page_height / pages_down ? :landscape : :portrait
290
+ end
291
+
292
+ # font methods
293
+ def set_default_font
294
+ font(@default_font[:name], @default_font[:size], @default_font)
295
+ @font
296
+ end
297
+
298
+ def check_set_font
299
+ set_default_font if @font.nil?
300
+ if (@last_page_font != @page_font) or (@last_font != @font)
301
+ @tw.set_font_and_size(@page_font, @font.size)
302
+ check_set_v_text_align(true)
303
+ @last_page_font = @page_font
304
+ @last_font = @font
305
+ end
306
+ end
307
+
308
+ def check_set_v_text_align(force=false)
309
+ if force or @last_v_text_align != @v_text_align
310
+ @v_text_align_pts = case @v_text_align
311
+ when :above then -@font.height * 0.001 * @font.size
312
+ when :top then -@font.ascent * 0.001 * @font.size
313
+ when :middle then -@font.ascent * 0.001 * @font.size / 2.0
314
+ when :below then -@font.descent * 0.001 * @font.size
315
+ else 0.0 # :base
316
+ end
317
+ @tw.set_rise(@v_text_align_pts)
318
+ @last_v_text_align = @v_text_align
319
+ end
320
+ end
321
+
322
+ def check_set_spacing
323
+ unless @word_spacing == @last_word_spacing
324
+ tw.set_word_spacing(@word_spacing)
325
+ @last_word_spacing = @word_spacing
326
+ end
327
+ unless @char_spacing == @last_char_spacing
328
+ tw.set_char_spacing(@char_spacing)
329
+ @last_char_spacing = @char_spacing
330
+ end
331
+ end
332
+
333
+ def check_set_scale
334
+ unless @scale == @last_scale
335
+ tw.set_horiz_scaling(@scale * 100)
336
+ @last_scale = @scale
337
+ end
338
+ end
339
+
340
+ def text_rendering_mode(options)
341
+ if options[:fill] and options[:stroke]
342
+ @text_rendering_mode = 2
343
+ elsif options[:stroke]
344
+ @text_rendering_mode = 1
345
+ elsif options[:fill]
346
+ @text_rendering_mode = 0
347
+ elsif options[:invisible]
348
+ @text_rendering_mode = 3 # Why is this an option in PDF?
349
+ else
350
+ @text_rendering_mode = 0
351
+ end
352
+ end
353
+
354
+ def text_clipping_mode(options)
355
+ if options[:fill] and options[:stroke]
356
+ @text_rendering_mode = 6
357
+ elsif options[:stroke]
358
+ @text_rendering_mode = 5
359
+ elsif options[:fill]
360
+ @text_rendering_mode = 4
361
+ else
362
+ @text_rendering_mode = 7
363
+ end
364
+ end
365
+
366
+ def check_set_text_rendering_mode
367
+ unless @text_rendering_mode == @last_text_rendering_mode
368
+ tw.set_rendering_mode(@text_rendering_mode)
369
+ @last_text_rendering_mode = @text_rendering_mode
370
+ end
371
+ end
372
+
373
+ # color methods
374
+ def check_set_line_color
375
+ unless @line_color == @last_line_color
376
+ r, g, b = rgb_from_color(@line_color)
377
+ if @in_path and @auto_path
378
+ gw.stroke
379
+ @in_path = false
380
+ end
381
+ if @in_misc
382
+ mw.set_rgb_color_stroke(r / 255.0, g / 255.0, b / 255.0)
383
+ @last_line_color = @line_color
384
+ end
385
+ end
386
+ end
387
+
388
+ def check_set_fill_color
389
+ unless @fill_color == @last_fill_color
390
+ r, g, b = rgb_from_color(@fill_color)
391
+ if @in_path and @auto_path
392
+ gw.stroke
393
+ @in_path = false
394
+ end
395
+ if @in_misc
396
+ mw.set_rgb_color_fill(r / 255.0, g / 255.0, b / 255.0)
397
+ @last_fill_color = @fill_color
398
+ end
399
+ end
400
+ end
401
+
402
+ def check_set_font_color
403
+ unless @font_color == @last_fill_color
404
+ r, g, b = rgb_from_color(@font_color)
405
+ if @in_path and @auto_path
406
+ gw.stroke
407
+ @in_path = false
408
+ end
409
+ if @in_misc
410
+ mw.set_rgb_color_fill(r / 255.0, g / 255.0, b / 255.0)
411
+ @last_fill_color = @font_color
412
+ end
413
+ end
414
+ end
415
+
416
+ def check_set_line_dash_pattern
417
+ unless [@line_dash_pattern, @line_cap_style] == [@last_line_dash_pattern, @last_line_cap_style]
418
+ if @in_path and @auto_path
419
+ gw.stroke
420
+ @in_path = false
421
+ end
422
+
423
+ if @line_dash_pattern.is_a?(Symbol)
424
+ dashes = (LINE_PATTERNS[@line_dash_pattern] || []).map { |p| p * @line_width.round }
425
+ pattern = gw.make_line_dash_pattern(dashes, 0)
426
+ else
427
+ pattern = @line_dash_pattern.to_s
428
+ end
429
+
430
+ gw.set_line_dash_pattern(pattern)
431
+ @last_line_dash_pattern = @line_dash_pattern
432
+
433
+ gw.set_line_cap_style(LINE_CAP_STYLES.index(@line_cap_style) || 0)
434
+ @last_line_cap_style = @line_cap_style
435
+ end
436
+ end
437
+
438
+ def check_set_line_width
439
+ unless @line_width == @last_line_width
440
+ if @in_path and @auto_path
441
+ gw.stroke
442
+ @in_path = false
443
+ end
444
+
445
+ gw.set_line_width(@line_width)
446
+ @last_line_width = @line_width
447
+ @last_line_dash_pattern = nil if @last_line_dash_pattern.is_a?(Symbol)
448
+ end
449
+ end
450
+
451
+ def check_set(*options)
452
+ check_set_line_color if options.include?(:line_color)
453
+ check_set_fill_color if options.include?(:fill_color)
454
+ check_set_line_width if options.include?(:line_width)
455
+ check_set_line_dash_pattern if options.include?(:line_dash_pattern)
456
+ check_set_font if options.include?(:font)
457
+ check_set_font_color if options.include?(:font_color)
458
+ check_set_v_text_align if options.include?(:v_text_align)
459
+ check_set_spacing if options.include?(:spacing)
460
+ check_set_scale if options.include?(:scale)
461
+ check_set_text_rendering_mode if options.include?(:text_rendering_mode)
462
+ end
463
+
464
+ def line_colors
465
+ @line_colors ||= ColorStack.new(self, :line_color)
466
+ end
467
+
468
+ def fill_colors
469
+ @fill_colors ||= ColorStack.new(self, :fill_color)
470
+ end
471
+
472
+ def auto_stroke_and_fill(options)
473
+ if @auto_path
474
+ gw.clip if options[:clip]
475
+ if (options[:stroke] and options[:fill])
476
+ gw.fill_and_stroke
477
+ elsif options[:stroke] then
478
+ gw.stroke
479
+ elsif options[:fill] then
480
+ gw.fill
481
+ else
482
+ gw.new_path
483
+ end
484
+ @in_path = false
485
+ end
486
+ end
487
+
488
+ # protected drawing methods
489
+ def draw_rounded_rectangle(x, y, width, height, options)
490
+ corners = options[:corners] || []
491
+ if corners.size == 1
492
+ xr1 = yr1 = xr2 = yr2 = xr3 = yr3 = xr4 = yr4 = corners[0]
493
+ elsif corners.size == 2
494
+ xr1 = yr1 = xr2 = yr2 = corners[0]
495
+ xr3 = yr3 = xr4 = yr4 = corners[1]
496
+ elsif corners.size == 4
497
+ xr1 = yr1 = corners[0]
498
+ xr2 = yr2 = corners[1]
499
+ xr3 = yr3 = corners[2]
500
+ xr4 = yr4 = corners[3]
501
+ elsif corners.size == 8
502
+ xr1, yr1, xr2, yr2, xr3, yr3, xr4, yr4 = corners
503
+ else
504
+ xr1 = yr1 = xr2 = yr2 = xr3 = yr3 = xr4 = yr4 = 0
505
+ end
506
+
507
+ q2p = get_quadrant_bezier_points(2, x + xr1, y + yr1, xr1, yr1)
508
+ q1p = get_quadrant_bezier_points(1, x + width - xr2, y + yr2, xr2, yr2)
509
+ q4p = get_quadrant_bezier_points(4, x + width - xr3, y + height - yr3, xr3, yr3)
510
+ q3p = get_quadrant_bezier_points(3, x + xr4, y + height - yr4, xr4, yr4)
511
+
512
+ qpa = [q1p, q2p, q3p, q4p]
513
+ if options[:reverse]
514
+ qpa.reverse!
515
+ qpa.each { |qp| qp.reverse! }
516
+ end
517
+
518
+ curve_points(qpa[0]); line_to(qpa[1][0].x, qpa[1][0].y)
519
+ curve_points(qpa[1]); line_to(qpa[2][0].x, qpa[2][0].y)
520
+ curve_points(qpa[2]); line_to(qpa[3][0].x, qpa[3][0].y)
521
+ curve_points(qpa[3]); line_to(qpa[0][0].x, qpa[0][0].y)
522
+ end
523
+
524
+ def draw_rectangle_path(x, y, width, height, options)
525
+ move_to(x, y)
526
+ if options[:reverse]
527
+ line_to(x, y + height)
528
+ line_to(x + width, y + height)
529
+ line_to(x + width, y)
530
+ else
531
+ line_to(x + width, y)
532
+ line_to(x + width, y + height)
533
+ line_to(x, y + height)
534
+ end
535
+ line_to(x, y)
536
+ end
537
+
538
+ def draw_underline(pos1, pos2, position, thickness, angle)
539
+ # position and thickness are in points
540
+ if @units != :pt
541
+ pos1, pos2 = convert_units(pos1, @units, :pt), convert_units(pos2, @units, :pt)
542
+ end
543
+ save_units = units(:pt)
544
+ save_line_width = line_width(thickness)
545
+ off_x, off_y = rotate_xy_coordinate(0, position - @v_text_align_pts, angle)
546
+ move_to(pos1.x - off_x, pos1.y + off_y)
547
+ line_to(pos2.x - off_x, pos2.y + off_y)
548
+ line_width(save_line_width)
549
+ units(save_units)
550
+ end
551
+
552
+ public
553
+ DEFAULT_FONT = { :name => 'Helvetica', :size => 12 }
554
+
555
+ attr_reader :doc, :page
556
+ attr_reader :stream, :annotations
557
+ attr_reader :auto_path
558
+
559
+ def initialize(doc, options)
560
+ # doc: PdfDocumentWriter
561
+ @doc = doc
562
+ @options = options
563
+ @page_style = PageStyle.new(options)
564
+ @units = options[:units] || :pt
565
+ @v_text_align = options[:v_text_align] || :top
566
+ @page_width = @page_style.page_size.x2
567
+ @page_height = @page_style.page_size.y2
568
+ if @page = options[:_page]
569
+ @reused_page = true
570
+ else
571
+ @page = PdfObjects::PdfPage.new(@doc.next_seq, 0, @doc.catalog.pages)
572
+ @page.media_box = @page_style.page_size.clone
573
+ @page.crop_box = @page_style.crop_size.clone
574
+ @page.rotate = @page_style.rotate
575
+ @page.resources = @doc.resources
576
+ @doc.file.body << @page
577
+ end
578
+ @stream = ''
579
+ @annotations = []
580
+ @char_spacing = @word_spacing = 0.0
581
+ @last_char_spacing = @last_word_spacing = 0.0
582
+ @last_scale = @scale = 1.0
583
+ @last_text_rendering_mode = @text_rendering_mode = 0
584
+ @default_font = options[:font] || DEFAULT_FONT
585
+ @font_color = @default_font[:color] || 0
586
+ @fill_color = options[:fill_color] || 0xFFFFFF
587
+ @line_color = options[:line_color] || 0
588
+ @line_height = options[:line_height] || 1.7
589
+ line_width(options[:line_width] || 1.0, :pt)
590
+ @text_angle = 0.0
591
+ @auto_path = true
592
+ @underline = false
593
+ start_misc
594
+ sub_page(*options[:sub_page] + Array(options[:unscaled])) if options[:sub_page]
595
+ margins(options[:margins] || 0)
596
+ text_encoding(options[:text_encoding])
597
+ @indent = 0
598
+ end
599
+
600
+ def close
601
+ end_margins unless @matrix.nil?
602
+ end_sub_page unless @sub_page.nil?
603
+ end_text if @in_text
604
+ end_graph if @in_graph
605
+ end_misc if @in_misc
606
+ pdf_stream = if @options[:compress]
607
+ require 'zlib'
608
+ zipper = Zlib::Deflate.new
609
+ zstream = zipper.deflate(@stream, Zlib::FINISH)
610
+ zpdf_stream = PdfObjects::PdfStream.new(@doc.next_seq, 0, zstream)
611
+ zpdf_stream.filter = 'FlateDecode'
612
+ zpdf_stream
613
+ else
614
+ PdfObjects::PdfStream.new(@doc.next_seq, 0, @stream)
615
+ end
616
+ @doc.file.body << pdf_stream
617
+ @page.annots = @annotations if @annotations.size.nonzero?
618
+ @page.contents << pdf_stream
619
+ @doc.catalog.pages.kids << @page unless @reused_page
620
+ @stream = nil
621
+ end
622
+
623
+ def closed?
624
+ @stream.nil?
625
+ end
626
+
627
+ def units(units=nil)
628
+ return @units if units.nil? or units == @units
629
+ @loc = convert_units(@loc, @units, units)
630
+ @last_loc = convert_units(@last_loc, @units, units) unless @last_loc.nil?
631
+ prev_units, @units = @units, units
632
+ prev_units
633
+ end
634
+
635
+ def margins(*margins)
636
+ @margins ||= [0] * 4
637
+ return @margins.map { |m| from_points(@units, m) } unless [4,2,1].include?(margins.size)
638
+ margins = margins.first if margins.first.is_a?(Array)
639
+ @margins = case margins.size
640
+ when 4 then margins
641
+ when 2 then margins * 2
642
+ when 1 then margins * 4
643
+ else @margins
644
+ end.map { |m| to_points(@units, m) }
645
+ @margin_top, @margin_right, @margin_bottom, @margin_left = @margins
646
+ if (@matrix || IDENTITY_MATRIX)[4..5] != [@margin_left, -@margin_top]
647
+ if @matrix.nil?
648
+ @matrix = IDENTITY_MATRIX.dup
649
+ else
650
+ gw.restore_graphics_state
651
+ end
652
+ @matrix[4..5] = [@margin_left, -@margin_top]
653
+ gw.save_graphics_state
654
+ gw.concat_matrix(*@matrix)
655
+ end
656
+ @canvas_width = @page_width - @margin_left - @margin_right
657
+ @canvas_height = @page_height - @margin_top - @margin_bottom
658
+ move_to(0, 0)
659
+ nil
660
+ end
661
+
662
+ def page_width
663
+ from_points(@units, @page_width)
664
+ end
665
+
666
+ def page_height
667
+ from_points(@units, @page_height)
668
+ end
669
+
670
+ def margin_top
671
+ from_points(@units, @margin_top)
672
+ end
673
+
674
+ def margin_right
675
+ from_points(@units, @margin_right)
676
+ end
677
+
678
+ def margin_bottom
679
+ from_points(@units, @margin_bottom)
680
+ end
681
+
682
+ def margin_left
683
+ from_points(@units, @margin_left)
684
+ end
685
+
686
+ def canvas_width
687
+ from_points(@units, @canvas_width)
688
+ end
689
+
690
+ def canvas_height
691
+ from_points(@units, @canvas_height)
692
+ end
693
+
694
+ def tabs(tabs=nil)
695
+ return @tabs if tabs.nil?
696
+ return @tabs = nil if tabs == false or tabs.empty?
697
+ tabs = tabs.split(',') if tabs.respond_to?(:to_str)
698
+ @tabs = tabs.map { |stop| stop.to_f }.select { |stop| stop > 0 }.sort
699
+ end
700
+
701
+ def tab(&block)
702
+ return if @tabs.nil?
703
+ p = pen_pos
704
+ x = @tabs.detect { |stop| stop > p.x }
705
+ if x.nil?
706
+ dy = block_given? ? yield : height
707
+ move_to(@tabs.first, p.y + dy)
708
+ else
709
+ move_to(x, p.y)
710
+ end
711
+ end
712
+
713
+ def vtabs(tabs=nil)
714
+ return @vtabs if tabs.nil?
715
+ return @vtabs = nil if tabs == false or tabs.empty?
716
+ tabs = tabs.split(',') if tabs.respond_to?(:to_str)
717
+ @vtabs = tabs.map { |stop| stop.to_f }.select { |stop| stop > 0 }.sort
718
+ end
719
+
720
+ def vtab(&block)
721
+ return if @vtabs.nil?
722
+ p = pen_pos
723
+ y = @vtabs.detect { |stop| stop > p.y }
724
+ if y.nil?
725
+ move_to(p.x + yield, @vtabs.first) if block_given?
726
+ else
727
+ move_to(p.x, y)
728
+ end
729
+ end
730
+
731
+ def indent(value=nil, absolute=false)
732
+ return @indent if value.nil?
733
+ prev_indent, @indent = @indent, absolute ? value : @indent + value
734
+ @loc.x = @indent
735
+ prev_indent
736
+ end
737
+
738
+ def sub_page(x, pages_across, y, pages_down, unscaled=false)
739
+ unless @matrix.nil?
740
+ gw.restore_graphics_state
741
+ @matrix = nil
742
+ end
743
+ gw.restore_graphics_state unless @sub_page.nil?
744
+ return unless x && pages_across && y && pages_down
745
+ if unscaled
746
+ @canvas_width = @page_width.quo(pages_across)
747
+ @canvas_height = @page_height.quo(pages_down)
748
+ @sub_page = IDENTITY_MATRIX.dup
749
+ @sub_page[4] = @canvas_width * x
750
+ @sub_page[5] = @canvas_height * (pages_down - 1 - y)
751
+ else
752
+ ps = PageStyle.new(@options.merge(:orientation => sub_orientation(pages_across, pages_down)))
753
+ width = ps.page_size.x2
754
+ height = ps.page_size.y2
755
+
756
+ ratio_w = @page_width.quo(pages_across * width)
757
+ ratio_h = @page_height.quo(pages_down * height)
758
+ ratio = [ratio_w, ratio_h].min
759
+ @sub_page = IDENTITY_MATRIX.dup
760
+ @sub_page[0] = ratio
761
+ @sub_page[3] = ratio
762
+ @sub_page[4] = @page_width.quo(pages_across) * x
763
+ @sub_page[5] = @page_height.quo(pages_down) * (pages_down - 1 - y)
764
+ if ratio_w >= ratio_h
765
+ @page_width, @page_height = width, height
766
+ else
767
+ @page_width, @page_height = width, width / ratio_w
768
+ end
769
+ end
770
+
771
+ gw.save_graphics_state
772
+ gw.concat_matrix(*@sub_page)
773
+ @last_font = @last_page_font = nil
774
+ end
775
+
776
+ def move_to(x, y)
777
+ @loc = translate(x, y)
778
+ nil
779
+ end
780
+
781
+ def pen_pos(x=nil, y=nil)
782
+ return translate(@loc.x, @loc.y) if x.nil?
783
+ prev_loc = translate(@loc.x, @loc.y)
784
+ move_to(x, y)
785
+ prev_loc
786
+ end
787
+
788
+ def move_by(dx, dy)
789
+ p = pen_pos
790
+ move_to(p.x + dx, p.y + dy)
791
+ end
792
+
793
+ def line_to(x, y)
794
+ unless @last_loc == @loc
795
+ gw.stroke if @in_path and @auto_path
796
+ @in_path = false
797
+ end
798
+
799
+ check_set(:line_color, :line_width, :line_dash_pattern)
800
+
801
+ gw.move_to(to_points(@units, @loc.x), to_points(@units, @loc.y)) unless @in_path
802
+ move_to(x, y)
803
+ gw.line_to(to_points(@units, @loc.x), to_points(@units, @loc.y))
804
+ @in_path = true
805
+ @last_loc = @loc.clone
806
+ nil
807
+ end
808
+
809
+ def line(x, y, angle, length)
810
+ lx, ly = rotate_xy_coordinate(1, 0, angle)
811
+ move_to(x, y)
812
+ line_to(x + lx * length, y - ly * length)
813
+ end
814
+
815
+ def rectangle(x, y, width, height, options={}, &block)
816
+ border = options[:border].nil? ? true : options[:border]
817
+ fill = options[:fill].nil? ? false : options[:fill]
818
+ clip = options[:clip].nil? ? false : options[:clip] && block_given?
819
+ gw.stroke if @in_path and @auto_path
820
+
821
+ line_colors.push(border)
822
+ fill_colors.push(fill)
823
+ check_set(:line_color, :line_width, :line_dash_pattern, :fill_color)
824
+
825
+ if options[:corners]
826
+ draw_rounded_rectangle(x, y, width, height, options)
827
+ elsif options[:path] or options[:reverse]
828
+ draw_rectangle_path(x, y, width, height, options)
829
+ else
830
+ gw.rectangle(
831
+ to_points(@units, x),
832
+ @page_height - to_points(@units, y + height),
833
+ to_points(@units, width),
834
+ to_points(@units, height))
835
+ end
836
+
837
+ gw.save_graphics_state if clip
838
+ auto_stroke_and_fill(:stroke => border, :fill => fill, :clip => clip)
839
+ yield if block_given?
840
+ gw.restore_graphics_state if clip
841
+ line_colors.pop
842
+ fill_colors.pop
843
+ move_to(x + width, y)
844
+ nil
845
+ end
846
+
847
+ def curve(x0, y0, x1, y1, x2, y2, x3, y3)
848
+ move_to(x0, y0)
849
+ unless @last_loc == @loc
850
+ if @in_path and @auto_path
851
+ gw.stroke
852
+ @in_path = false
853
+ end
854
+ end
855
+ check_set(:line_color, :line_width, :line_dash_pattern)
856
+
857
+ gw.move_to(to_points(@units, @loc.x), to_points(@units, @loc.y)) unless @in_path
858
+ gw.curve_to(
859
+ to_points(@units, x1),
860
+ @page_height - to_points(@units, y1),
861
+ to_points(@units, x2),
862
+ @page_height - to_points(@units, y2),
863
+ to_points(@units, x3),
864
+ @page_height - to_points(@units, y3))
865
+ move_to(x3, y3)
866
+ @last_loc = @loc.clone
867
+ @in_path = true
868
+ end
869
+
870
+ def curve_points(points)
871
+ raise Exception.new("Need at least 4 points for curve") if points.size < 4
872
+ move_to(points[0].x, points[0].y)
873
+ unless @last_loc == @loc
874
+ if @in_path and @auto_path
875
+ gw.stroke
876
+ @in_path = false
877
+ end
878
+ end
879
+
880
+ check_set(:line_color, :line_width, :line_dash_pattern)
881
+
882
+ gw.move_to(to_points(@units, @loc.x), to_points(@units, @loc.y)) unless (@loc == @last_loc) and @in_path
883
+ i = 1
884
+ while i + 2 < points.size
885
+ gw.curve_to(
886
+ to_points(@units, points[i].x),
887
+ @page_height - to_points(@units, points[i].y),
888
+ to_points(@units, points[i+1].x),
889
+ @page_height - to_points(@units, points[i+1].y),
890
+ to_points(@units, points[i+2].x),
891
+ @page_height - to_points(@units, points[i+2].y)
892
+ )
893
+ move_to(points[i+2].x, points[i+2].y)
894
+ @last_loc = @loc.clone
895
+ i += 3
896
+ end
897
+ @in_path = true
898
+ end
899
+
900
+ # def curve_to(points)
901
+ # end
902
+
903
+ def points_for_circle(x, y, r)
904
+ points = (1..4).inject([]) { |points, q| points + get_quadrant_bezier_points(q, x, y, r) }
905
+ [12,8,4].each { |i| points.delete_at(i) }
906
+ points
907
+ end
908
+
909
+ def circle(x, y, r, options={}, &block)
910
+ border = options[:border].nil? ? true : options[:border]
911
+ fill = options[:fill].nil? ? false : options[:fill]
912
+ clip = options[:clip].nil? ? false : options[:clip] && block_given?
913
+
914
+ line_colors.push(border)
915
+ fill_colors.push(fill)
916
+ check_set(:line_color, :line_width, :line_dash_pattern, :fill_color)
917
+
918
+ points = points_for_circle(x, y, r)
919
+ points.reverse! if options[:reverse]
920
+ curve_points(points)
921
+
922
+ gw.save_graphics_state if clip
923
+ auto_stroke_and_fill(:stroke => border, :fill => fill, :clip => clip)
924
+ yield if block_given?
925
+ gw.restore_graphics_state if clip
926
+ line_colors.pop
927
+ fill_colors.pop
928
+ nil
929
+ end
930
+
931
+ def points_for_ellipse(x, y, rx, ry)
932
+ points = (1..4).inject([]) { |points, q| points + get_quadrant_bezier_points(q, x, y, rx, ry) }
933
+ [12,8,4].each { |i| points.delete_at(i) }
934
+ points
935
+ end
936
+
937
+ def ellipse(x, y, rx, ry, options={}, &block)
938
+ rotation = options[:rotation] || 0
939
+ border = options[:border].nil? ? true : options[:border]
940
+ fill = options[:fill].nil? ? false : options[:fill]
941
+ clip = options[:clip].nil? ? false : options[:clip] && block_given?
942
+
943
+ line_colors.push(border)
944
+ fill_colors.push(fill)
945
+ check_set(:line_color, :line_width, :line_dash_pattern, :fill_color)
946
+
947
+ points = points_for_ellipse(x, y, rx, ry)
948
+ points = rotate_points(make_loc(x, y), points, -rotation)
949
+ points.reverse! if options[:reverse]
950
+ curve_points(points)
951
+
952
+ gw.save_graphics_state if clip
953
+ auto_stroke_and_fill(:stroke => border, :fill => fill, :clip => clip)
954
+ yield if block_given?
955
+ gw.restore_graphics_state if clip
956
+ line_colors.pop
957
+ fill_colors.pop
958
+ nil
959
+ end
960
+
961
+ def points_for_arc(x, y, r, start_angle, end_angle)
962
+ return nil if start_angle == end_angle
963
+
964
+ num_arcs = 1
965
+ ccwcw = 1.0
966
+ arc_span = end_angle - start_angle
967
+ if end_angle < start_angle
968
+ ccwcw = -1.0
969
+ end
970
+ while arc_span.abs.quo(num_arcs) > 90.0
971
+ num_arcs += 1
972
+ end
973
+ angle_bump = arc_span.quo(num_arcs)
974
+ half_bump = 0.5 * angle_bump
975
+ cur_angle = start_angle + half_bump
976
+ points = []
977
+ num_arcs.times do |i|
978
+ points << points_for_arc_small(x, y, r, cur_angle, half_bump, ccwcw)
979
+ points.last.shift if i > 0
980
+ cur_angle = cur_angle + angle_bump
981
+ end
982
+ points.flatten
983
+ end
984
+
985
+ def arc(x, y, r, start_angle, end_angle, move_to0=false)
986
+ return if start_angle == end_angle
987
+
988
+ move_to0 = true unless @in_path
989
+ num_arcs = 1
990
+ ccwcw = 1.0
991
+ arc_span = end_angle - start_angle
992
+ if end_angle < start_angle
993
+ ccwcw = -1.0
994
+ end
995
+ while arc_span.abs.quo(num_arcs) > 90.0
996
+ num_arcs += 1
997
+ end
998
+ angle_bump = arc_span.quo(num_arcs)
999
+ half_bump = 0.5 * angle_bump
1000
+ cur_angle = start_angle + half_bump
1001
+ num_arcs.times do
1002
+ arc_small(x, y, r, cur_angle, half_bump, ccwcw, move_to0)
1003
+ move_to0 = false
1004
+ cur_angle = cur_angle + angle_bump
1005
+ end
1006
+ end
1007
+
1008
+ def pie(x, y, r, start_angle, end_angle, options={})
1009
+ border = options[:border].nil? ? true : options[:border]
1010
+ fill = options[:fill].nil? ? false : options[:fill]
1011
+ clip = options[:clip].nil? ? false : options[:clip] && block_given?
1012
+ unless @last_loc == @loc
1013
+ gw.stroke if @in_path and @auto_path
1014
+ @in_path = false
1015
+ end
1016
+
1017
+ line_colors.push(border)
1018
+ fill_colors.push(fill)
1019
+ check_set(:line_color, :line_width, :line_dash_pattern, :fill_color)
1020
+
1021
+ move_to(x, y)
1022
+ gw.move_to(to_points(@units, @loc.x), to_points(@units, @loc.y))
1023
+ @last_loc = @loc.clone
1024
+ @in_path = true
1025
+ start_angle, end_angle = end_angle, start_angle if options[:reverse]
1026
+ arc(x, y, r, start_angle, end_angle)
1027
+ line_to(x, y)
1028
+
1029
+ gw.save_graphics_state if clip
1030
+ auto_stroke_and_fill(:stroke => border, :fill => fill, :clip => clip)
1031
+ yield if block_given?
1032
+ gw.restore_graphics_state if clip
1033
+ line_colors.pop
1034
+ fill_colors.pop
1035
+ nil
1036
+ end
1037
+
1038
+ def arch(x, y, r1, r2, start_angle, end_angle, options={}, &block)
1039
+ return if start_angle == end_angle
1040
+ start_angle, end_angle = end_angle, start_angle if options[:reverse]
1041
+ border = options[:border].nil? ? true : options[:border]
1042
+ fill = options[:fill].nil? ? false : options[:fill]
1043
+ clip = options[:clip].nil? ? false : options[:clip] && block_given?
1044
+ unless @last_loc == @loc
1045
+ gw.stroke if @in_path and @auto_path
1046
+ @in_path = false
1047
+ end
1048
+
1049
+ line_colors.push(border)
1050
+ fill_colors.push(fill)
1051
+ check_set(:line_color, :line_width, :line_dash_pattern, :fill_color)
1052
+ arc1 = points_for_arc(x, y, r1, start_angle, end_angle)
1053
+ arc2 = points_for_arc(x, y, r2, end_angle, start_angle)
1054
+ move_to(arc1.first.x, arc1.first.y)
1055
+ gw.move_to(to_points(@units, @loc.x), to_points(@units, @loc.y))
1056
+ curve_points(arc1)
1057
+ line_to(arc2.first.x, arc2.first.y)
1058
+ curve_points(arc2)
1059
+ line_to(arc1.first.x, arc1.first.y)
1060
+
1061
+ gw.save_graphics_state if clip
1062
+ auto_stroke_and_fill(:stroke => border, :fill => fill, :clip => clip)
1063
+ yield if block_given?
1064
+ gw.restore_graphics_state if clip
1065
+ line_colors.pop
1066
+ fill_colors.pop
1067
+ nil
1068
+ end
1069
+
1070
+ def points_for_polygon(x, y, r, sides, options={})
1071
+ step = 360.0 / sides
1072
+ angle = step / 2 + 90
1073
+ points = (0..sides).collect do
1074
+ px, py = rotate_xy_coordinate(1, 0, angle)
1075
+ angle += step
1076
+ make_loc(x + px * r, y + py * r)
1077
+ end
1078
+ rotation = options[:rotation] || 0
1079
+ points = rotate_points(make_loc(x, y), points, -rotation) unless rotation.zero?
1080
+ points.reverse! if options[:reverse]
1081
+ points
1082
+ end
1083
+
1084
+ def polygon(x, y, r, sides, options={}, &block)
1085
+ border = options[:border].nil? ? true : options[:border]
1086
+ fill = options[:fill].nil? ? false : options[:fill]
1087
+ clip = options[:clip].nil? ? false : options[:clip] && block_given?
1088
+ unless @last_loc == @loc
1089
+ gw.stroke if @in_path and @auto_path
1090
+ @in_path = false
1091
+ end
1092
+
1093
+ points = points_for_polygon(x, y, r, sides, options)
1094
+ line_colors.push(border)
1095
+ fill_colors.push(fill)
1096
+ check_set(:line_color, :line_width, :line_dash_pattern, :fill_color)
1097
+
1098
+ points.each_with_index do |point, i|
1099
+ if i == 0
1100
+ move_to(point.x, point.y)
1101
+ else
1102
+ line_to(point.x, point.y)
1103
+ end
1104
+ end
1105
+ gw.save_graphics_state if clip
1106
+ auto_stroke_and_fill(:stroke => border, :fill => fill, :clip => clip)
1107
+ yield if block_given?
1108
+ gw.restore_graphics_state if clip
1109
+ line_colors.pop
1110
+ fill_colors.pop
1111
+ nil
1112
+ end
1113
+
1114
+ def star(x, y, r1, r2, points, options={}, &block)
1115
+ return if points < 5
1116
+ border = options[:border].nil? ? true : options[:border]
1117
+ fill = options[:fill].nil? ? false : options[:fill]
1118
+ clip = options[:clip].nil? ? false : options[:clip] && block_given?
1119
+ unless @last_loc == @loc
1120
+ gw.stroke if @in_path and @auto_path
1121
+ @in_path = false
1122
+ end
1123
+
1124
+ rotation = options[:rotation] || 0
1125
+ r2 ||= (points - 2).quo(points * 1.5)
1126
+ vertices1 = points_for_polygon(x, y, r1, points, options)
1127
+ vertices2 = points_for_polygon(x, y, r2, points, options.merge(:rotation => rotation + (360.0 / points / 2)))
1128
+ line_colors.push(border)
1129
+ fill_colors.push(fill)
1130
+ check_set(:line_color, :line_width, :line_dash_pattern, :fill_color)
1131
+
1132
+ move_to(vertices2[0].x, vertices2[0].y)
1133
+ points.times do |i|
1134
+ line_to(vertices1[i].x, vertices1[i].y)
1135
+ line_to(vertices2[i+1].x, vertices2[i+1].y)
1136
+ end
1137
+ gw.save_graphics_state if clip
1138
+ auto_stroke_and_fill(:stroke => border, :fill => fill, :clip => clip)
1139
+ yield if block_given?
1140
+ gw.restore_graphics_state if clip
1141
+ line_colors.pop
1142
+ fill_colors.pop
1143
+ nil
1144
+ end
1145
+
1146
+ def path(options={}, &block)
1147
+ check_set(:line_color, :line_width, :line_dash_pattern, :fill_color)
1148
+ stroke = options[:stroke].nil? ? false : options[:stroke]
1149
+ fill = options[:fill].nil? ? false : options[:fill]
1150
+ line_colors.push(stroke)
1151
+ fill_colors.push(fill)
1152
+ @auto_path = false
1153
+ if block_given?
1154
+ yield
1155
+ if options[:fill] and options[:stroke]
1156
+ gw.fill_and_stroke
1157
+ elsif options[:stroke]
1158
+ gw.stroke
1159
+ elsif options[:fill]
1160
+ gw.fill
1161
+ else
1162
+ gw.new_path
1163
+ end
1164
+ line_colors.pop
1165
+ fill_colors.pop
1166
+ @in_path = false
1167
+ @auto_path = true
1168
+ end
1169
+ end
1170
+
1171
+ def fill
1172
+ raise Exception.new("Not in graph") unless @in_graph
1173
+ raise Exception.new("Not in path") unless @in_path
1174
+
1175
+ check_set(:fill_color)
1176
+ gw.fill
1177
+ line_colors.pop
1178
+ fill_colors.pop
1179
+ @in_path = false
1180
+ @auto_path = true
1181
+ end
1182
+
1183
+ def stroke
1184
+ raise Exception.new("Not in graph") unless @in_graph
1185
+ raise Exception.new("Not in path") unless @in_path
1186
+
1187
+ check_set(:line_color)
1188
+ gw.stroke
1189
+ line_colors.pop
1190
+ fill_colors.pop
1191
+ @in_path = false
1192
+ @auto_path = true
1193
+ end
1194
+
1195
+ def fill_and_stroke
1196
+ raise Exception.new("Not in graph") unless @in_graph
1197
+ raise Exception.new("Not in path") unless @in_path
1198
+
1199
+ check_set(:fill_color,:line_color)
1200
+ gw.fill_and_stroke
1201
+ line_colors.pop
1202
+ fill_colors.pop
1203
+ @in_path = false
1204
+ @auto_path = true
1205
+ end
1206
+
1207
+ def clip(options={}, &block)
1208
+ gw.save_graphics_state
1209
+ if @in_path
1210
+ gw.clip
1211
+ if options[:fill] and options[:stroke]
1212
+ gw.fill_and_stroke
1213
+ elsif options[:stroke]
1214
+ gw.stroke
1215
+ elsif options[:fill]
1216
+ gw.fill
1217
+ else
1218
+ gw.new_path
1219
+ end
1220
+ end
1221
+ save_text_rendering_mode = @text_rendering_mode
1222
+ text_clipping_mode(options)
1223
+ yield if block_given?
1224
+ # gw.clip
1225
+ gw.restore_graphics_state
1226
+ @text_rendering_mode = save_text_rendering_mode
1227
+ @in_path = false
1228
+ @auto_path = true
1229
+ end
1230
+
1231
+ def line_cap_style(style=nil)
1232
+ return @line_cap_style || :butt_cap if style.nil?
1233
+ prev_line_cap_style, @line_cap_style = @line_cap_style, style.to_sym if LINE_CAP_STYLES.include?(style.to_sym)
1234
+ prev_line_cap_style
1235
+ end
1236
+
1237
+ def line_dash_pattern(pattern=nil)
1238
+ return @line_dash_pattern if pattern.nil?
1239
+ prev_line_dash_pattern, @line_dash_pattern = @line_dash_pattern, pattern
1240
+ prev_line_dash_pattern
1241
+ end
1242
+
1243
+ def line_width(value=nil, units=nil)
1244
+ return from_points(@units, @line_width || 0) if value.nil?
1245
+ return from_points(value, @line_width || 0) if value.is_a?(Symbol)
1246
+ prev_line_width = @line_width || 0
1247
+ if !units.nil?
1248
+ u, value = units.to_sym, value.to_f
1249
+ elsif value.respond_to?(:to_str) and value =~ /\D+/
1250
+ u, value = $&.to_sym, value.to_f
1251
+ else
1252
+ u = @units
1253
+ end
1254
+ @line_width = to_points(u, value)
1255
+ from_points(@units, prev_line_width)
1256
+ end
1257
+
1258
+ def line_height(height=nil)
1259
+ return @line_height if height.nil?
1260
+ prev_line_height, @line_height = @line_height, height
1261
+ prev_line_height
1262
+ end
1263
+
1264
+ def named_colors
1265
+ @doc.named_colors
1266
+ end
1267
+
1268
+ def line_color(color=nil)
1269
+ return @line_color if color.nil?
1270
+ if color.is_a?(Array)
1271
+ r, g, b = color
1272
+ prev_line_color, @line_color = @line_color, color_from_rgb(r, g, b)
1273
+ else
1274
+ prev_line_color, @line_color = @line_color, color
1275
+ end
1276
+ prev_line_color
1277
+ end
1278
+
1279
+ def fill_color(color=nil)
1280
+ return @fill_color if color.nil?
1281
+ if color.is_a?(Array)
1282
+ r, g, b = color
1283
+ prev_fill_color, @fill_color = @fill_color, color_from_rgb(r, g, b)
1284
+ else
1285
+ prev_fill_color, @fill_color = @fill_color, color
1286
+ end
1287
+ prev_fill_color
1288
+ end
1289
+
1290
+ def print(text, options={}, &block)
1291
+ text = text.to_s
1292
+ return if text.empty?
1293
+ text = @ic.iconv(text) unless @ic.nil?
1294
+ align = options[:align]
1295
+ angle = options[:angle] || 0.0
1296
+ @scale = options[:scale] || 1.0
1297
+ prev_underline = underline(options[:underline]) unless options[:underline].nil?
1298
+ prev_v_text_align = v_text_align(options[:v_text_align]) unless options[:v_text_align].nil?
1299
+ clip = options[:clip] && block_given?
1300
+ if clip
1301
+ gw.save_graphics_state
1302
+ text_clipping_mode(options)
1303
+ end
1304
+ start_text unless @in_text
1305
+ check_set(:font, :font_color, :line_color, :v_text_align, :spacing, :scale, :text_rendering_mode)
1306
+ ds = width(text, true)
1307
+ if align
1308
+ prev_loc = @loc.clone
1309
+ @loc = case align
1310
+ when :left then @loc
1311
+ when :center then add_vector(@loc, angle + 180, ds.quo(2))
1312
+ when :right then add_vector(@loc, angle + 180, ds)
1313
+ end
1314
+ end
1315
+ if (@text_angle != angle) or (angle != 0.0)
1316
+ set_text_angle(angle, @loc.x, @loc.y)
1317
+ elsif @loc != @last_loc
1318
+ tw.move_by(to_points(@units, @loc.x - @last_loc.x), to_points(@units, @loc.y - @last_loc.y))
1319
+ end
1320
+ tw.show(text)
1321
+ # if @ic.nil?
1322
+ # tw.show(text)
1323
+ # else
1324
+ # text = @ic.iconv(text)
1325
+ # tw.show_wide(text)
1326
+ # end
1327
+ @last_loc = @loc.clone
1328
+ new_loc = (angle == 0.0) ? Location.new(@loc.x + ds, @loc.y) : add_vector(@loc, angle, ds)
1329
+ draw_underline(translate_p(@last_loc), translate_p(new_loc), @font.underline_position, @font.underline_thickness, angle) if @underline
1330
+ underline(prev_underline) unless options[:underline].nil?
1331
+ v_text_align(prev_v_text_align) unless options[:v_text_align].nil?
1332
+ @loc = align ? prev_loc : new_loc
1333
+ if clip
1334
+ yield
1335
+ gw.restore_graphics_state
1336
+ end
1337
+ nil
1338
+ end
1339
+
1340
+ def print_xy(x, y, text, options={}, &block)
1341
+ move_to(x, y)
1342
+ print(text, options, &block)
1343
+ end
1344
+
1345
+ def puts(text='', options={}, &block)
1346
+ # if it's not a real string, assume it's an enumeration of strings
1347
+ unless text.respond_to?(:to_str)
1348
+ text.each { |t| puts(t, options) }
1349
+ else
1350
+ prev_loc = @loc.clone
1351
+ print(text, options, &block)
1352
+ @loc = options[:indent] ? Location.new(prev_loc.x, prev_loc.y - height) : Location.new(@indent, prev_loc.y - height)
1353
+ end
1354
+ nil
1355
+ end
1356
+
1357
+ def puts_xy(x, y, text, options={}, &block)
1358
+ move_to(x, y)
1359
+ prev_indent = indent(x, true)
1360
+ puts(text, options, &block)
1361
+ indent(prev_indent, true)
1362
+ nil
1363
+ end
1364
+
1365
+ def new_line(count=1)
1366
+ @loc = Location.new(@indent, @loc.y - height * count)
1367
+ nil
1368
+ end
1369
+
1370
+ def width(text, encoded=false)
1371
+ set_default_font if @font.nil?
1372
+ result = 0.0
1373
+ fsize = @font.size * 0.001
1374
+ text = @ic.iconv(text) unless @ic.nil? or encoded
1375
+ text.each_byte do |b|
1376
+ result += fsize * @font.widths[b] + @char_spacing
1377
+ result += @word_spacing if b == 32 # space
1378
+ end
1379
+ from_points(@units, result - @char_spacing)
1380
+ end
1381
+
1382
+ def wrap(text, width)
1383
+ re = /\n|\t|[ ]|[\S]+-+|[\S]+/
1384
+ words = text.scan(re)
1385
+ word_tuples = words.map { |word| [width(word), word] }
1386
+ line_length = 0
1387
+ lines = word_tuples.inject(['']) do |lines, tuple|
1388
+ if tuple[1] == "\n"
1389
+ lines << ''
1390
+ line_length = 0
1391
+ elsif line_length == 0
1392
+ unless tuple[1] == ' '
1393
+ lines.last << tuple[1]
1394
+ line_length += tuple[0]
1395
+ end
1396
+ elsif line_length + tuple[0] > width
1397
+ lines << ''
1398
+ line_length = 0
1399
+ redo
1400
+ else
1401
+ lines.last << tuple[1]
1402
+ line_length += tuple[0]
1403
+ end
1404
+ lines
1405
+ end
1406
+ end
1407
+
1408
+ def text_ascent(units=nil)
1409
+ units ||= @units
1410
+ set_default_font if @font.nil?
1411
+ 0.001 * @font.ascent * @font.size.quo(UNIT_CONVERSION[units])
1412
+ end
1413
+
1414
+ def text_height(units=nil)
1415
+ units ||= @units
1416
+ set_default_font if @font.nil?
1417
+ 0.001 * @font.height * @font.size.quo(UNIT_CONVERSION[units])
1418
+ end
1419
+
1420
+ def height(text='', units=nil)
1421
+ units ||= @units
1422
+ set_default_font if @font.nil?
1423
+ if text.respond_to?(:to_str)
1424
+ text_height(units) * @line_height
1425
+ else
1426
+ text.inject(0) { |total, line| total + height(line, units) }
1427
+ end
1428
+ end
1429
+
1430
+ def paragraph(text, options={})
1431
+ width = options[:width] || canvas_width - pen_pos.x
1432
+ height = options[:height] || canvas_height - pen_pos.y
1433
+ if bul = bullet(options[:bullet])
1434
+ save_loc = pen_pos
1435
+ bul.proc.call(self)
1436
+ move_to(save_loc.x + from_points(units, bul.width), save_loc.y)
1437
+ width -= from_points(units, bul.width)
1438
+ end
1439
+ prev_underline, ul = @underline, options[:underline].nil? ? @underline : options[:underline]
1440
+ unless text.is_a?(PdfText::RichText)
1441
+ text = PdfText::RichText.new(text, font, :color => @font_color,
1442
+ :char_spacing => @char_spacing, :word_spacing => @word_spacing,
1443
+ :underline => ul)
1444
+ end
1445
+ dy = 0
1446
+ while dy + from_points(@units, text.height) < height and line = text.next(to_points(@units, width))
1447
+ save_loc = pen_pos
1448
+ line_dy = line.height.quo(UNIT_CONVERSION[units]) * @line_height
1449
+ case options[:align]
1450
+ when :center then move_to(save_loc.x + (width - from_points(@units, line.width)) / 2.0, save_loc.y)
1451
+ when :right then move_to(save_loc.x + width - from_points(@units, line.width), save_loc.y)
1452
+ when :justify then
1453
+ width_pt = to_points(@units, width)
1454
+ delta_pt = width_pt - line.width
1455
+ words = (line.tokens / 2) + 1
1456
+ if delta_pt.abs.quo(width_pt) < 0.4
1457
+ if words == 1
1458
+ @word_spacing = 0
1459
+ @char_spacing = delta_pt.quo(line.chars - 1)
1460
+ elsif delta_pt.abs / words > 3
1461
+ @word_spacing = 3
1462
+ delta_pt -= (words - 1) * @word_spacing
1463
+ @char_spacing = delta_pt.quo(line.chars - 1)
1464
+ else
1465
+ @word_spacing = delta_pt.quo(words - 1)
1466
+ @char_spacing = 0
1467
+ end
1468
+ end
1469
+ end
1470
+ while piece = line.shift
1471
+ font(piece.font)
1472
+ font_color piece.color
1473
+ underline piece.underline
1474
+ print(piece.text)
1475
+ end
1476
+ @word_spacing = @char_spacing = 0.0 if options[:align] == :justify
1477
+ dy += line_dy
1478
+ move_to(save_loc.x, save_loc.y + line_dy)
1479
+ end
1480
+ underline(prev_underline)
1481
+ move_by(-from_points(units, bul.width), 0) unless bul.nil?
1482
+ return text.empty? ? nil : text
1483
+ end
1484
+
1485
+ def paragraph_xy(x, y, text, options={})
1486
+ move_to(x, y)
1487
+ paragraph(text, options)
1488
+ end
1489
+
1490
+ def v_text_align(vta=nil)
1491
+ return @v_text_align if vta.nil?
1492
+ prev_vta, @v_text_align = @v_text_align, vta
1493
+ prev_vta
1494
+ end
1495
+
1496
+ def underline(underline=nil)
1497
+ return @underline if underline.nil?
1498
+ prev_underline, @underline = @underline, underline
1499
+ prev_underline
1500
+ end
1501
+
1502
+ def type1_font_names
1503
+ @doc.type1_font_names
1504
+ end
1505
+
1506
+ def truetype_font_names
1507
+ @doc.truetype_font_names
1508
+ end
1509
+
1510
+ def select_font(name, size, options={})
1511
+ unless @ic.nil?
1512
+ @ic.close
1513
+ @ic = nil
1514
+ end
1515
+ weight = options[:weight] || ''
1516
+ style = weight + (options[:style] || '')
1517
+ font = Font.new(name, size, style, options[:color], pdf_encoding(options[:encoding] || 'WinAnsiEncoding', name))
1518
+ font.sub_type = options[:sub_type] || 'Type1'
1519
+ punc = (font.sub_type == 'TrueType') ? ',' : '-'
1520
+ full_name = name.gsub(' ','')
1521
+ full_name << punc << font.style unless font.style.empty?
1522
+ if @options[:built_in_fonts]
1523
+ if font.sub_type == 'Type1'
1524
+ metrics = PdfK::font_metrics(full_name)
1525
+ elsif font.sub_type == 'TrueType'
1526
+ metrics = PdfTT::font_metrics(full_name)
1527
+ end
1528
+ else
1529
+ if font.sub_type == 'Type1'
1530
+ weight = 'Bold' if weight.empty? and /Bold/i =~ style
1531
+ afm = AFM::find_font(name, weight, style)
1532
+ full_name = afm.font_name unless afm.nil?
1533
+ metrics = AFM::font_metrics(full_name, :encoding => font.encoding)
1534
+ elsif font.sub_type == 'TrueType'
1535
+ raise Exception.new("Non-built-in TrueType fonts not supported yet.")
1536
+ elsif font.sub_type == 'Type0'
1537
+ # metrics = AFM::font_metrics(full_name, :encoding => :unicode)
1538
+ # require 'iconv'
1539
+ # @ic = Iconv.new('UCS-2BE', iconv_encoding(font.encoding))
1540
+ else
1541
+ raise Exception.new("Unsupported subtype #{font.sub_type}.")
1542
+ end
1543
+ end
1544
+ unless text_encoding.nil? or (font.encoding == 'StandardEncoding') or (font.encoding == text_encoding)
1545
+ @ic = Iconv.new(iconv_encoding(font.encoding)+'//IGNORE', iconv_encoding(text_encoding))
1546
+ end
1547
+ font.widths, font.ascent, font.descent = metrics.widths, metrics.ascent, metrics.descent
1548
+ font.height = font.ascent + font.descent.abs
1549
+ font.underline_position = metrics.underline_position * -0.001 * font.size
1550
+ font.underline_thickness = metrics.underline_thickness * 0.001 * font.size
1551
+ font_key = "#{full_name}/#{font.encoding}-#{font.sub_type}"
1552
+ page_font = @doc.fonts[font_key]
1553
+ unless page_font
1554
+ widths = nil
1555
+ if metrics.needs_descriptor
1556
+ descriptor = PdfObjects::PdfFontDescriptor.new(@doc.next_seq, 0, full_name, metrics.flags, metrics.b_box,
1557
+ metrics.missing_width, metrics.stem_v, metrics.stem_h, metrics.italic_angle,
1558
+ metrics.cap_height, metrics.x_height, metrics.ascent, metrics.descent, metrics.leading,
1559
+ metrics.max_width, metrics.avg_width)
1560
+ @doc.file.body << descriptor
1561
+ widths = PdfObjects::IndirectObject.new(@doc.next_seq, 0, PdfObjects::PdfInteger.ary(metrics.widths))
1562
+ @doc.file.body << widths
1563
+ else
1564
+ descriptor = nil
1565
+ widths = nil
1566
+ end
1567
+ page_font = "F#{@doc.fonts.size}"
1568
+ f = PdfObjects::PdfFont.new(@doc.next_seq, 0, font.sub_type, full_name, 0, 255, widths, descriptor)
1569
+ @doc.file.body << f
1570
+ if PdfObjects::PdfFont.standard_encoding?(font.encoding)
1571
+ f.encoding = font.encoding
1572
+ else
1573
+ encoding = @doc.encodings[font.encoding]
1574
+ if encoding.nil?
1575
+ encoding = PdfObjects::PdfFontEncoding.new(@doc.next_seq, 0, 'WinAnsiEncoding', metrics.differences)
1576
+ @doc.encodings[font.encoding] = encoding
1577
+ @doc.file.body << encoding
1578
+ end
1579
+ f.encoding = encoding.reference_object
1580
+ # raise Exception.new("Unsupported encoding #{font.encoding}")
1581
+ end
1582
+ @doc.resources.fonts[page_font] = f.reference_object
1583
+ @doc.fonts[font_key] = page_font
1584
+ end
1585
+ font_color(options[:color]) if options[:color]
1586
+ text_rendering_mode(options)
1587
+ [font, page_font]
1588
+ end
1589
+
1590
+ def font(name=nil, size=nil, options={})
1591
+ return @font || set_default_font if name.nil?
1592
+ prev_font = @font
1593
+ if name.is_a?(Font)
1594
+ @font = name
1595
+ name, size = @font.name, @font.size
1596
+ options.update(:style => @font.style, :color => @font.color, :encoding => @font.encoding, :sub_type => @font.sub_type)
1597
+ end
1598
+ size ||= @font.nil? ? @default_font[:size] : @font.size
1599
+ @font, @page_font = select_font(name, size, options)
1600
+ prev_font
1601
+ end
1602
+
1603
+ def font_style(style=nil)
1604
+ set_default_font if @font.nil?
1605
+ return @font.style if style.nil?
1606
+ prev_style = @font.style
1607
+ font(@font.name, @font.size, :style => style, :color => @font.color, :encoding => @font.encoding, :sub_type => @font.sub_type)
1608
+ prev_style
1609
+ end
1610
+
1611
+ def font_size(size=nil)
1612
+ set_default_font if @font.nil?
1613
+ return @font.size if size.nil?
1614
+ prev_size = @font.size
1615
+ font(@font.name, size, :style => @font.style, :color => @font.color, :encoding => @font.encoding, :sub_type => @font.sub_type)
1616
+ prev_size
1617
+ end
1618
+
1619
+ def font_color(color=nil)
1620
+ return @font_color if color.nil?
1621
+ if color.is_a?(Array)
1622
+ r, g, b = color
1623
+ prev_font_color, @font_color = @font_color, color_from_rgb(r, g, b)
1624
+ else
1625
+ prev_font_color, @font_color = @font_color, color
1626
+ end
1627
+ @font.color = @font_color unless @font.nil?
1628
+ prev_font_color
1629
+ end
1630
+
1631
+ def font_encoding(encoding=nil)
1632
+ set_default_font if @font.nil?
1633
+ return @font.encoding if encoding.nil?
1634
+ prev_encoding = @font.encoding
1635
+ font(@font.name, @font.size, :style => @font.style, :color => @font.color, :encoding => encoding, :sub_type => @font.sub_type)
1636
+ prev_encoding
1637
+ end
1638
+
1639
+ def load_image(image_file_name, stream=nil)
1640
+ image, name = @doc.images[image_file_name]
1641
+ return [image, name] unless image.nil?
1642
+ stream ||= open(image_file_name, ImageReadMode) { |io| io.read }
1643
+ image = PdfObjects::PdfImage.new(@doc.next_seq, 0, stream)
1644
+ image.width, image.height, components, image.bits_per_component = jpeg_dimensions(stream)
1645
+ image.color_space = { 1 => 'DeviceGray', 3 => 'DeviceRGB', 4 => 'DeviceCMYK' }[components]
1646
+ image.filter = 'DCTDecode'
1647
+ name = "Im#{@doc.images.size}"
1648
+ @doc.file.body << image
1649
+ @doc.resources.x_objects[name] = image.reference_object
1650
+ @doc.images[image_file_name] = [image, name]
1651
+ [image, name]
1652
+ end
1653
+
1654
+ def print_image_file(image_file_name, x=nil, y=nil, width=nil, height=nil)
1655
+ image, name = load_image(image_file_name)
1656
+ x ||= pen_pos.x
1657
+ y ||= pen_pos.y
1658
+ if width.nil? and height.nil?
1659
+ width, height = image.width, image.height
1660
+ elsif width.nil?
1661
+ height = to_points(@units, height)
1662
+ width = height * image.width.quo(image.height)
1663
+ elsif height.nil?
1664
+ width = to_points(@units, width)
1665
+ height = width * image.height.quo(image.width)
1666
+ else
1667
+ width, height = to_points(@units, width), to_points(@units, height)
1668
+ end
1669
+ end_path if @in_path
1670
+ gw.save_graphics_state
1671
+ gw.concat_matrix(width, 0, 0, height, to_points(@units, x), to_points(@units, page_height - y) - height)
1672
+ mw.x_object(name)
1673
+ gw.restore_graphics_state
1674
+ [from_points(@units, width), from_points(@units, height)]
1675
+ end
1676
+
1677
+ def print_image(data, x=nil, y=nil, width=nil, height=nil)
1678
+ image_file_name = data.hash.to_s
1679
+ image, name = load_image(image_file_name, data)
1680
+ print_image_file(image_file_name, x, y, width, height)
1681
+ end
1682
+
1683
+ def print_link(s, uri)
1684
+ # TODO
1685
+ end
1686
+
1687
+ def bullet(name, options={}, &block)
1688
+ return nil if name.nil?
1689
+ return @doc.bullets[name.to_sym] unless block_given?
1690
+ if width = options[:width]
1691
+ units = options[:units] || self.units
1692
+ width = to_points(units, width)
1693
+ end
1694
+ @doc.bullets[name.to_sym] = Bullet.new(name.to_s, width || 36, block)
1695
+ end
1696
+
1697
+ def rotate(angle, x, y, &block)
1698
+ return unless block_given?
1699
+ end_path if @in_path
1700
+ check_set(:line_color, :line_width, :line_dash_pattern, :fill_color)
1701
+
1702
+ theta = angle.degrees
1703
+ v_cos = Math::cos(theta)
1704
+ v_sin = Math::sin(theta)
1705
+
1706
+ xx, xy = to_points(@units, x), @page_height - to_points(@units, y)
1707
+ rot_x, rot_y = rotate_xy_coordinate(xx, xy, angle)
1708
+ gw.save_graphics_state
1709
+ gw.concat_matrix(v_cos, v_sin, -v_sin, v_cos, xx - rot_x, xy - rot_y)
1710
+ @last_page_font = nil
1711
+ yield
1712
+ gw.restore_graphics_state
1713
+ @last_page_font = nil
1714
+ end
1715
+
1716
+ def scale(x, y, scale_x, scale_y, &block)
1717
+ return unless x && scale_x && y && scale_y && block_given?
1718
+ end_path if @in_path
1719
+ check_set(:line_color, :line_width, :line_dash_pattern, :fill_color)
1720
+ sub_area = IDENTITY_MATRIX.dup
1721
+ # a: Sx (horizontal scaling, 1 unit in new is x units in old)
1722
+ # b:
1723
+ # c:
1724
+ # d: Sy (vertical scaling, 1 unit in new is y units in old)
1725
+ # e: Tx (horizontal translation of the origin)
1726
+ # f: Ty (vertical translation of the origin)
1727
+ sub_area[0] = scale_x
1728
+ sub_area[3] = scale_y
1729
+ sub_area[4] = to_points(@units, x)
1730
+ sub_area[5] = to_points(@units, -y)
1731
+ save_page_height = @page_height
1732
+ @page_height = save_page_height.quo(scale_y)
1733
+ gw.save_graphics_state
1734
+ gw.concat_matrix(*sub_area)
1735
+ @last_page_font = nil
1736
+ yield
1737
+ end_text if @in_text
1738
+ end_graph if @in_graph
1739
+ gw.restore_graphics_state
1740
+ @last_page_font = nil
1741
+ @page_height = save_page_height
1742
+ end
1743
+
1744
+ def text_encoding(value=nil)
1745
+ return @text_encoding if value.nil?
1746
+ @text_encoding = value
1747
+ end
1748
+ end
1749
+ end