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/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