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/epdfs.rb ADDED
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: ASCII-8BIT
3
+ #
4
+ # Created by Brent Rowland on 2008-01-12.
5
+ # Copyright (c) 2008, Eidetic Software. All rights reserved.
6
+
7
+ # Eidetic PDF Support
8
+
9
+ class Numeric # :nodoc:
10
+ unless 1.respond_to?(:degrees)
11
+ def degrees
12
+ self * Math::PI / 180
13
+ end
14
+ end
15
+
16
+ unless 2.respond_to?(:even?)
17
+ def even?
18
+ self % 2 == 0
19
+ end
20
+ end
21
+
22
+ unless 1.respond_to?(:odd?)
23
+ def odd?
24
+ self % 2 != 0
25
+ end
26
+ end
27
+ end
28
+
29
+ module EideticPDF
30
+ ImageReadMode = "".respond_to?(:encoding) ? "rb:binary" : "rb"
31
+
32
+ module Statistics # :nodoc:
33
+ def sum
34
+ self.inject(0) { |total, obj| total + obj }
35
+ end
36
+
37
+ def mean
38
+ self.sum.quo(self.size)
39
+ end
40
+ end
41
+
42
+ class PropertyStack # :nodoc:
43
+ def initialize(obj, prop, &block)
44
+ @obj, @prop, @condition = obj, prop, block
45
+ @stack = []
46
+ end
47
+
48
+ def push(value)
49
+ @stack.push @obj.send(@prop)
50
+ @obj.send(@prop, value) if @condition.call(value)
51
+ end
52
+
53
+ def pop
54
+ value = @stack.pop
55
+ @obj.send(@prop, value) if @condition.call(value)
56
+ end
57
+ end
58
+
59
+ class ColorStack # :nodoc:
60
+ def initialize(obj, prop)
61
+ @obj, @prop = obj, prop
62
+ @stack = []
63
+ end
64
+
65
+ def push(color)
66
+ @stack.push @obj.send(@prop)
67
+ @obj.send(@prop, color) if color.respond_to?(:to_int) or color.respond_to?(:to_str)
68
+ end
69
+
70
+ def pop
71
+ color = @stack.pop
72
+ @obj.send(@prop, color) if color.respond_to?(:to_int) or color.respond_to?(:to_str)
73
+ end
74
+ end
75
+
76
+ module JpegInfo # :nodoc:
77
+ def jpeg?(image)
78
+ # image[0, 2] == "\xFF\xD8"
79
+ image[0, 2].hash == "\xFF\xD8".hash
80
+ end
81
+
82
+ def jpeg_dimensions(image)
83
+ raise ArgumentError, "Not a JPEG" unless jpeg?(image)
84
+ image = image.dup
85
+ image.slice!(0, 2) # delete jpeg marker
86
+ while marker = image.slice!(0, 4)
87
+ m, c, l = marker.unpack('aan')
88
+ raise "Bad JPEG" unless m == "\xFF"
89
+ if ["\xC0", "\xC1", "\xC2", "\xC3", "\xC5", "\xC6", "\xC7", "\xC9", "\xCA", "\xCB", "\xCD", "\xCE", "\xCF"].include?(c)
90
+ dims = image.slice(0, 6)
91
+ bits_per_component, height, width, components = dims.unpack('CnnC')
92
+ break
93
+ end
94
+ image.slice!(0, l - 2)
95
+ end
96
+ [width, height, components, bits_per_component]
97
+ end
98
+
99
+ module_function :jpeg?, :jpeg_dimensions
100
+ end
101
+ end
data/lib/epdfsw.rb ADDED
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: ASCII-8BIT
3
+ #
4
+ # Created by Brent Rowland on 2007-07-13.
5
+ # Copyright (c) 2007, 2008 Eidetic Software. All rights reserved.
6
+ #
7
+ # Eidetic PDF Stream Writers
8
+
9
+ require 'epdfo'
10
+
11
+ module EideticPDF
12
+ class BaseWriter # :nodoc:
13
+ def f(value, prec=2)
14
+ sprintf("%.*f", prec, value).sub(',','.')
15
+ end
16
+
17
+ def g(value, prec=4)
18
+ # prec must be >= 1 or gsub will strip significant trailing 0's.
19
+ sprintf("%.*f", prec, value).sub(',','.').gsub(/\.?0*$/,'')
20
+ end
21
+ end
22
+
23
+ class MiscWriter < BaseWriter # :nodoc:
24
+ def initialize(stream)
25
+ @stream = stream
26
+ end
27
+
28
+ def set_gray_fill(gray)
29
+ @stream << "%s g\n" % g(gray)
30
+ end
31
+
32
+ def set_gray_stroke(gray)
33
+ @stream << "%s G\n" % g(gray)
34
+ end
35
+
36
+ def set_cmyk_color_fill(c, m, y, k)
37
+ @stream << "%s %s %s %s k\n" % [g(c), g(m), g(y), g(k)]
38
+ end
39
+
40
+ def set_cmyk_color_stroke(c, m, y, k)
41
+ @stream << "%s %s %s %s K\n" % [g(c), g(m), g(y), g(k)]
42
+ end
43
+
44
+ def set_rgb_color_fill(red, green, blue)
45
+ @stream << "%s %s %s rg\n" % [g(red), g(green), g(blue)]
46
+ end
47
+
48
+ def set_rgb_color_stroke(red, green, blue)
49
+ @stream << "%s %s %s RG\n" % [g(red), g(green), g(blue)]
50
+ end
51
+
52
+ def set_color_space_fill(name)
53
+ @stream << "/#{name} cs\n"
54
+ end
55
+
56
+ def set_color_space_stroke(name)
57
+ @stream << "/#{name} CS\n"
58
+ end
59
+
60
+ def set_color_fill(colors)
61
+ @stream << "%s sc\n" % colors.map { |c| g(c) }.join(' ')
62
+ end
63
+
64
+ def set_color_stroke(colors)
65
+ @stream << "%s SC\n" % colors.map { |c| g(c) }.join(' ')
66
+ end
67
+
68
+ # xxx scn, SCN: patterns and separations
69
+ def set_color_rendering_intent(intent)
70
+ @stream << "/#{intent} ri\n"
71
+ end
72
+
73
+ def x_object(name)
74
+ @stream << "/#{name} Do\n"
75
+ end
76
+ end
77
+
78
+ class GraphWriter < BaseWriter # :nodoc:
79
+ def initialize(stream)
80
+ @stream = stream
81
+ end
82
+
83
+ def save_graphics_state
84
+ @stream << "q\n"
85
+ end
86
+
87
+ def restore_graphics_state
88
+ @stream << "Q\n"
89
+ end
90
+
91
+ def concat_matrix(a, b, c, d, x, y)
92
+ @stream << "%s %s %s %s %s %s cm\n" % [g(a), g(b), g(c), g(d), g(x), g(y)]
93
+ end
94
+
95
+ def set_flatness(flatness)
96
+ @stream << "%d i\n" % flatness
97
+ end
98
+
99
+ def set_line_cap_style(line_cap_style)
100
+ @stream << "%d J\n" % line_cap_style
101
+ end
102
+
103
+ def set_line_dash_pattern(line_dash_pattern)
104
+ @stream << "%s d\n" % line_dash_pattern
105
+ end
106
+
107
+ def set_line_join_style(line_join_style)
108
+ @stream << "%d j\n" % line_join_style
109
+ end
110
+
111
+ def set_line_width(line_width)
112
+ @stream << "%s w\n" % g(line_width)
113
+ end
114
+
115
+ def set_miter_limit(miter_limit)
116
+ @stream << "%s M\n" % g(miter_limit)
117
+ end
118
+
119
+ def move_to(x, y)
120
+ @stream << "%s %s m\n" % [g(x), g(y)]
121
+ end
122
+
123
+ def line_to(x, y)
124
+ @stream << "%s %s l\n" % [g(x), g(y)]
125
+ end
126
+
127
+ def curve_to(x1, y1, x2, y2, x3, y3)
128
+ @stream << "%s %s %s %s %s %s c\n" % [g(x1), g(y1), g(x2), g(y2), g(x3), g(y3)]
129
+ end
130
+
131
+ def rectangle(x, y, width, height)
132
+ @stream << "%s %s %s %s re\n" % [g(x), g(y), g(width), g(height)]
133
+ end
134
+
135
+ def close_path
136
+ @stream << "h\n"
137
+ end
138
+
139
+ def new_path
140
+ @stream << "n\n"
141
+ end
142
+
143
+ def stroke
144
+ @stream << "S\n"
145
+ end
146
+
147
+ def close_path_and_stroke
148
+ @stream << "s\n"
149
+ end
150
+
151
+ def fill
152
+ @stream << "f\n"
153
+ end
154
+
155
+ def eo_fill
156
+ @stream << "f*\n"
157
+ end
158
+
159
+ def fill_and_stroke
160
+ @stream << "B\n"
161
+ end
162
+
163
+ def close_path_fill_and_stroke
164
+ @stream << "b\n"
165
+ end
166
+
167
+ def eo_fill_and_stroke
168
+ @stream << "B*\n"
169
+ end
170
+
171
+ def close_path_eo_fill_and_stroke
172
+ @stream << "b*\n"
173
+ end
174
+
175
+ # use nonzero winding number rule
176
+ def clip
177
+ @stream << "W\n"
178
+ end
179
+
180
+ # use even-odd rule
181
+ def eo_clip
182
+ @stream << "W*\n"
183
+ end
184
+
185
+ def make_line_dash_pattern(pattern, phase)
186
+ "[%s] %d" % [pattern.join(' '), phase]
187
+ end
188
+ end
189
+
190
+ class TextWriter < BaseWriter # :nodoc:
191
+ def initialize(stream)
192
+ @stream = stream
193
+ end
194
+
195
+ def open
196
+ @stream << "BT\n"
197
+ end
198
+
199
+ def close
200
+ @stream << "ET\n"
201
+ end
202
+
203
+ def set_char_spacing(char_space)
204
+ @stream << "%s Tc\n" % g(char_space)
205
+ end
206
+
207
+ def set_word_spacing(word_space)
208
+ @stream << "%s Tw\n" % g(word_space)
209
+ end
210
+
211
+ def set_horiz_scaling(scale)
212
+ @stream << "%s Tz\n" % g(scale)
213
+ end
214
+
215
+ def set_leading(leading)
216
+ @stream << "%s TL\n" % g(leading)
217
+ end
218
+
219
+ def set_font_and_size(font_name, size)
220
+ @stream << "/%s %s Tf\n" % [font_name, g(size)]
221
+ end
222
+
223
+ def set_rendering_mode(render)
224
+ @stream << "%d Tr\n" % render
225
+ end
226
+
227
+ def set_rise(rise)
228
+ @stream << "%s Ts\n" % g(rise)
229
+ end
230
+
231
+ def move_by(tx, ty)
232
+ @stream << "%s %s Td\n" % [g(tx), g(ty)]
233
+ end
234
+
235
+ def move_by_and_set_leading(tx, ty)
236
+ @stream << "%s %s TD\n" % [g(tx), g(ty)]
237
+ end
238
+
239
+ def set_matrix(a, b, c, d, x, y)
240
+ @stream << "%s %s %s %s %s %s Tm\n" % [g(a), g(b), g(c), g(d), g(x), g(y)]
241
+ end
242
+
243
+ def next_line
244
+ @stream << "T*\n"
245
+ end
246
+
247
+ def show(s)
248
+ @stream << "(%s) Tj\n" % PdfObjects::PdfString.escape(s)
249
+ end
250
+
251
+ def show_wide(ws)
252
+ @stream << "(%s) Tj\n" % PdfObjects::PdfString.escape_wide(ws)
253
+ end
254
+
255
+ def next_line_show(s)
256
+ @stream << "(%s) '" % PdfObjects::PdfString.escape(s)
257
+ end
258
+
259
+ def set_spacing_next_line_show(char_space, word_space, s)
260
+ @stream << "%s %s (%s) \"\n" % [g(char_space), g(word_space), PdfObjects::PdfString.escape(s)]
261
+ end
262
+
263
+ def show_with_dispacements(elements)
264
+ @stream << "%sTJ\n" % elements
265
+ end
266
+ end
267
+ end
data/lib/epdft.rb ADDED
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: ASCII-8BIT
3
+ #
4
+ # Created by Brent Rowland on 2007-11-27.
5
+ # Copyright (c) 2007, Eidetic Software. All rights reserved.
6
+
7
+ module EideticPDF
8
+ module TextLine # :nodoc:
9
+ def height
10
+ @height ||= map { |p| 0.001 * p.font.height * p.font.size }.max
11
+ end
12
+
13
+ def ascent
14
+ @ascent ||= map { |p| 0.001 * p.font.ascent * p.font.size }.max
15
+ end
16
+
17
+ def descent
18
+ @descent ||= map { |p| 0.001 * p.font.descent * p.font.size }.min
19
+ end
20
+
21
+ def width
22
+ @width ||= inject(0) { |total, p| total + p.width }
23
+ end
24
+
25
+ def chars
26
+ @chars ||= inject(0) { |total, p| total + p.chars }
27
+ end
28
+
29
+ def tokens
30
+ @tokens ||= inject(0) { |total, p| total + p.tokens }
31
+ end
32
+ end
33
+
34
+ module PdfText # :nodoc: all
35
+ class TextPiece
36
+ attr_accessor :text, :width, :font, :color, :underline, :chars, :tokens
37
+
38
+ def initialize(text, width, font, color, underline, chars, tokens)
39
+ @text, @width, @font, @color, @underline, @chars, @tokens = text, width, font, color, underline, chars, tokens
40
+ end
41
+
42
+ def initialize_copy(other)
43
+ @text = @text.clone
44
+ end
45
+ end
46
+
47
+ class RichText
48
+ TOKEN_RE = /\n|\t|[ ]|[\S]+-+|[\S]+/
49
+ attr_reader :words
50
+
51
+ def initialize(text=nil, font=nil, options={})
52
+ @words = []
53
+ add(text, font, options) unless text.nil?
54
+ end
55
+
56
+ def initialize_copy(other)
57
+ @words = @words.map { |word| word.clone }
58
+ end
59
+
60
+ def add(text, font, options={})
61
+ fsize = font.size * 0.001
62
+ char_spacing, word_spacing = options[:char_spacing] || 0, options[:word_spacing] || 0
63
+ color, underlined = options[:color] || 0, options[:underline] || false
64
+ words = text.scan(TOKEN_RE).map do |token|
65
+ width = 0.0
66
+ token.each_byte do |b|
67
+ width += fsize * font.widths[b] + char_spacing
68
+ width += word_spacing if b == 32 # space
69
+ end
70
+ TextPiece.new(token, width, font, color, underlined, token.length, 1)
71
+ end
72
+ @words.concat(words)
73
+ end
74
+
75
+ # merge pieces with identical characteristics
76
+ def merge(text_pieces)
77
+ while !text_pieces.empty? and text_pieces.last.text == ' '
78
+ text_pieces.pop
79
+ end
80
+ text_pieces.inject([]) do |pieces, piece|
81
+ if pieces.empty? or [pieces.last.font, pieces.last.color, pieces.last.underline] != [piece.font, piece.color, piece.underline]
82
+ pieces << piece
83
+ else
84
+ pieces.last.text << piece.text
85
+ pieces.last.width += piece.width
86
+ pieces.last.chars += piece.chars
87
+ pieces.last.tokens += piece.tokens
88
+ end
89
+ pieces
90
+ end
91
+ end
92
+
93
+ def next(width)
94
+ # remove leading spaces
95
+ @words.shift while !@words.empty? and @words.first.text == ' '
96
+ return nil if @words.empty?
97
+ # measure how many words will fit
98
+ i, phrase_width = 0, 0.0
99
+ while i < @words.size and phrase_width + @words[i].width < width
100
+ phrase_width += @words[i].width
101
+ i += 1
102
+ break if @words[i-1].text == "\n"
103
+ end
104
+ merge(@words.slice!(0, [i,1].max)).extend(TextLine)
105
+ end
106
+
107
+ def lines(width)
108
+ rich_text = self.clone
109
+ result = []
110
+ while line = rich_text.next(width)
111
+ result << line
112
+ end
113
+ result
114
+ end
115
+
116
+ def empty?
117
+ @words.empty?
118
+ end
119
+
120
+ def ascent(width=nil)
121
+ return 0 if @words.empty?
122
+ if width.nil?
123
+ @words.clone.extend(TextLine).ascent
124
+ else
125
+ self.clone.next(width).ascent
126
+ end
127
+ end
128
+
129
+ def height(width=nil)
130
+ return 0 if @words.empty?
131
+ if width.nil?
132
+ @words.clone.extend(TextLine).height
133
+ # f = @words.first.font
134
+ # 0.001 * f.height * f.size
135
+ else
136
+ lines(width).inject(0) { |total, line| total + line.height }
137
+ end
138
+ end
139
+
140
+ def width(max_width)
141
+ lines(max_width).map { |line| line.width }.max
142
+ end
143
+ end
144
+ end
145
+ end