eideticpdf 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
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