alphasights-prawn 0.10.0

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 (244) hide show
  1. data/COPYING +340 -0
  2. data/HACKING +50 -0
  3. data/LICENSE +56 -0
  4. data/README +141 -0
  5. data/Rakefile +52 -0
  6. data/data/encodings/win_ansi.txt +29 -0
  7. data/data/fonts/Action Man.dfont +0 -0
  8. data/data/fonts/Activa.ttf +0 -0
  9. data/data/fonts/Chalkboard.ttf +0 -0
  10. data/data/fonts/Courier-Bold.afm +342 -0
  11. data/data/fonts/Courier-BoldOblique.afm +342 -0
  12. data/data/fonts/Courier-Oblique.afm +342 -0
  13. data/data/fonts/Courier.afm +342 -0
  14. data/data/fonts/DejaVuSans.ttf +0 -0
  15. data/data/fonts/Dustismo_Roman.ttf +0 -0
  16. data/data/fonts/Helvetica-Bold.afm +2827 -0
  17. data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  18. data/data/fonts/Helvetica-Oblique.afm +3051 -0
  19. data/data/fonts/Helvetica.afm +3051 -0
  20. data/data/fonts/MustRead.html +19 -0
  21. data/data/fonts/Symbol.afm +213 -0
  22. data/data/fonts/Times-Bold.afm +2588 -0
  23. data/data/fonts/Times-BoldItalic.afm +2384 -0
  24. data/data/fonts/Times-Italic.afm +2667 -0
  25. data/data/fonts/Times-Roman.afm +2419 -0
  26. data/data/fonts/ZapfDingbats.afm +225 -0
  27. data/data/fonts/comicsans.ttf +0 -0
  28. data/data/fonts/gkai00mp.ttf +0 -0
  29. data/data/images/16bit.alpha +0 -0
  30. data/data/images/16bit.dat +0 -0
  31. data/data/images/16bit.png +0 -0
  32. data/data/images/arrow.png +0 -0
  33. data/data/images/arrow2.png +0 -0
  34. data/data/images/barcode_issue.png +0 -0
  35. data/data/images/dice.alpha +0 -0
  36. data/data/images/dice.dat +0 -0
  37. data/data/images/dice.png +0 -0
  38. data/data/images/dice_interlaced.png +0 -0
  39. data/data/images/fractal.jpg +0 -0
  40. data/data/images/letterhead.jpg +0 -0
  41. data/data/images/page_white_text.alpha +0 -0
  42. data/data/images/page_white_text.dat +0 -0
  43. data/data/images/page_white_text.png +0 -0
  44. data/data/images/pigs.jpg +0 -0
  45. data/data/images/rails.dat +0 -0
  46. data/data/images/rails.png +0 -0
  47. data/data/images/ruport.png +0 -0
  48. data/data/images/ruport_data.dat +0 -0
  49. data/data/images/ruport_transparent.png +0 -0
  50. data/data/images/ruport_type0.png +0 -0
  51. data/data/images/stef.jpg +0 -0
  52. data/data/images/tru256.bmp +0 -0
  53. data/data/images/web-links.dat +1 -0
  54. data/data/images/web-links.png +0 -0
  55. data/data/pdfs/complex_template.pdf +0 -0
  56. data/data/pdfs/contains_ttf_font.pdf +0 -0
  57. data/data/pdfs/encrypted.pdf +0 -0
  58. data/data/pdfs/hexagon.pdf +61 -0
  59. data/data/pdfs/indirect_reference.pdf +86 -0
  60. data/data/pdfs/nested_pages.pdf +118 -0
  61. data/data/pdfs/resources_as_indirect_object.pdf +83 -0
  62. data/data/pdfs/two_hexagons.pdf +90 -0
  63. data/data/pdfs/version_1_6.pdf +61 -0
  64. data/data/shift_jis_text.txt +1 -0
  65. data/examples/bounding_box/bounding_boxes.rb +43 -0
  66. data/examples/bounding_box/indentation.rb +34 -0
  67. data/examples/bounding_box/russian_boxes.rb +36 -0
  68. data/examples/bounding_box/stretched_nesting.rb +67 -0
  69. data/examples/builder/simple.rb +28 -0
  70. data/examples/example_helper.rb +4 -0
  71. data/examples/general/background.rb +23 -0
  72. data/examples/general/canvas.rb +15 -0
  73. data/examples/general/context_sensitive_headers.rb +37 -0
  74. data/examples/general/float.rb +11 -0
  75. data/examples/general/margin.rb +36 -0
  76. data/examples/general/measurement_units.rb +51 -0
  77. data/examples/general/metadata-info.rb +16 -0
  78. data/examples/general/multi_page_layout.rb +18 -0
  79. data/examples/general/outlines.rb +50 -0
  80. data/examples/general/page_geometry.rb +31 -0
  81. data/examples/general/page_numbering.rb +15 -0
  82. data/examples/general/repeaters.rb +47 -0
  83. data/examples/general/stamp.rb +41 -0
  84. data/examples/general/templates.rb +13 -0
  85. data/examples/graphics/basic_images.rb +23 -0
  86. data/examples/graphics/chunkable.rb +38 -0
  87. data/examples/graphics/cmyk.rb +12 -0
  88. data/examples/graphics/curves.rb +11 -0
  89. data/examples/graphics/hexagon.rb +13 -0
  90. data/examples/graphics/image_fit.rb +15 -0
  91. data/examples/graphics/image_flow.rb +37 -0
  92. data/examples/graphics/image_position.rb +17 -0
  93. data/examples/graphics/line.rb +32 -0
  94. data/examples/graphics/png_types.rb +22 -0
  95. data/examples/graphics/polygons.rb +16 -0
  96. data/examples/graphics/remote_images.rb +12 -0
  97. data/examples/graphics/rounded_polygons.rb +19 -0
  98. data/examples/graphics/rounded_rectangle.rb +20 -0
  99. data/examples/graphics/ruport_style_helpers.rb +19 -0
  100. data/examples/graphics/stroke_bounds.rb +20 -0
  101. data/examples/graphics/stroke_cap_and_join.rb +45 -0
  102. data/examples/graphics/stroke_dash.rb +42 -0
  103. data/examples/graphics/transformations.rb +52 -0
  104. data/examples/graphics/transparency.rb +26 -0
  105. data/examples/m17n/chinese_text_wrapping.rb +17 -0
  106. data/examples/m17n/euro.rb +15 -0
  107. data/examples/m17n/sjis.rb +28 -0
  108. data/examples/m17n/utf8.rb +13 -0
  109. data/examples/m17n/win_ansi_charset.rb +54 -0
  110. data/examples/security/hello_foo.rb +8 -0
  111. data/examples/table/bill.rb +53 -0
  112. data/examples/table/cell.rb +12 -0
  113. data/examples/table/checkerboard.rb +22 -0
  114. data/examples/table/header.rb +14 -0
  115. data/examples/table/inline_format_table.rb +12 -0
  116. data/examples/table/multi_page_table.rb +9 -0
  117. data/examples/table/simple_table.rb +24 -0
  118. data/examples/table/subtable.rb +12 -0
  119. data/examples/table/widths.rb +20 -0
  120. data/examples/text/alignment.rb +18 -0
  121. data/examples/text/character_spacing.rb +12 -0
  122. data/examples/text/dfont.rb +48 -0
  123. data/examples/text/family_based_styling.rb +24 -0
  124. data/examples/text/font_calculations.rb +91 -0
  125. data/examples/text/font_size.rb +33 -0
  126. data/examples/text/hyphenation.rb +45 -0
  127. data/examples/text/indent_paragraphs.rb +22 -0
  128. data/examples/text/inline_format.rb +103 -0
  129. data/examples/text/kerning.rb +30 -0
  130. data/examples/text/rotated.rb +98 -0
  131. data/examples/text/shaped_text_box.rb +31 -0
  132. data/examples/text/simple_text.rb +17 -0
  133. data/examples/text/simple_text_ttf.rb +17 -0
  134. data/examples/text/text_box.rb +88 -0
  135. data/examples/text/text_box_returning_excess.rb +51 -0
  136. data/examples/text/text_flow.rb +67 -0
  137. data/lib/prawn.rb +27 -0
  138. data/lib/prawn/canvas.rb +119 -0
  139. data/lib/prawn/chunkable.rb +37 -0
  140. data/lib/prawn/compatibility.rb +51 -0
  141. data/lib/prawn/core.rb +85 -0
  142. data/lib/prawn/core/annotations.rb +61 -0
  143. data/lib/prawn/core/byte_string.rb +9 -0
  144. data/lib/prawn/core/chunk.rb +36 -0
  145. data/lib/prawn/core/destinations.rb +90 -0
  146. data/lib/prawn/core/document_state.rb +78 -0
  147. data/lib/prawn/core/literal_string.rb +16 -0
  148. data/lib/prawn/core/name_tree.rb +165 -0
  149. data/lib/prawn/core/object_store.rb +236 -0
  150. data/lib/prawn/core/page.rb +179 -0
  151. data/lib/prawn/core/pdf_object.rb +108 -0
  152. data/lib/prawn/core/reference.rb +112 -0
  153. data/lib/prawn/core/text.rb +140 -0
  154. data/lib/prawn/core/text/formatted/arranger.rb +266 -0
  155. data/lib/prawn/core/text/formatted/line_wrap.rb +127 -0
  156. data/lib/prawn/core/text/formatted/wrap.rb +112 -0
  157. data/lib/prawn/core/text/line_wrap.rb +209 -0
  158. data/lib/prawn/core/text/wrap.rb +80 -0
  159. data/lib/prawn/document.rb +573 -0
  160. data/lib/prawn/document/bounding_box.rb +425 -0
  161. data/lib/prawn/document/graphics_state.rb +48 -0
  162. data/lib/prawn/document/internals.rb +170 -0
  163. data/lib/prawn/document/page_geometry.rb +136 -0
  164. data/lib/prawn/document/snapshot.rb +87 -0
  165. data/lib/prawn/document_builder.rb +51 -0
  166. data/lib/prawn/document_builder/command.rb +38 -0
  167. data/lib/prawn/document_builder/constructs.rb +2 -0
  168. data/lib/prawn/document_builder/constructs/flowing_text_construct.rb +18 -0
  169. data/lib/prawn/document_builder/constructs/path_construct.rb +9 -0
  170. data/lib/prawn/document_builder/layout.rb +25 -0
  171. data/lib/prawn/document_builder/modifications.rb +2 -0
  172. data/lib/prawn/document_builder/modifications/layout_modification.rb +9 -0
  173. data/lib/prawn/document_builder/modifications/path_modification.rb +9 -0
  174. data/lib/prawn/encoding.rb +121 -0
  175. data/lib/prawn/errors.rb +94 -0
  176. data/lib/prawn/font.rb +341 -0
  177. data/lib/prawn/font/afm.rb +225 -0
  178. data/lib/prawn/font/dfont.rb +42 -0
  179. data/lib/prawn/font/ttf.rb +350 -0
  180. data/lib/prawn/graphics.rb +325 -0
  181. data/lib/prawn/graphics/cap_style.rb +38 -0
  182. data/lib/prawn/graphics/color.rb +205 -0
  183. data/lib/prawn/graphics/dash.rb +71 -0
  184. data/lib/prawn/graphics/join_style.rb +38 -0
  185. data/lib/prawn/graphics/transformation.rb +156 -0
  186. data/lib/prawn/graphics/transparency.rb +99 -0
  187. data/lib/prawn/images.rb +348 -0
  188. data/lib/prawn/images/jpg.rb +46 -0
  189. data/lib/prawn/images/png.rb +226 -0
  190. data/lib/prawn/measurement_extensions.rb +46 -0
  191. data/lib/prawn/measurements.rb +71 -0
  192. data/lib/prawn/outline.rb +278 -0
  193. data/lib/prawn/repeater.rb +129 -0
  194. data/lib/prawn/security.rb +262 -0
  195. data/lib/prawn/security/arcfour.rb +51 -0
  196. data/lib/prawn/stamp.rb +126 -0
  197. data/lib/prawn/table.rb +421 -0
  198. data/lib/prawn/table/accessors.rb +180 -0
  199. data/lib/prawn/table/cell.rb +350 -0
  200. data/lib/prawn/table/cell/in_table.rb +27 -0
  201. data/lib/prawn/table/cell/subtable.rb +65 -0
  202. data/lib/prawn/table/cell/text.rb +125 -0
  203. data/lib/prawn/text.rb +449 -0
  204. data/lib/prawn/text/box.rb +392 -0
  205. data/lib/prawn/text/formatted.rb +4 -0
  206. data/lib/prawn/text/formatted/box.rb +228 -0
  207. data/lib/prawn/text/formatted/fragment.rb +181 -0
  208. data/lib/prawn/text/formatted/parser.rb +213 -0
  209. data/spec/annotations_spec.rb +90 -0
  210. data/spec/bounding_box_spec.rb +190 -0
  211. data/spec/cell_spec.rb +348 -0
  212. data/spec/destinations_spec.rb +15 -0
  213. data/spec/document_spec.rb +473 -0
  214. data/spec/font_spec.rb +324 -0
  215. data/spec/formatted_text_arranger_spec.rb +426 -0
  216. data/spec/formatted_text_box_spec.rb +756 -0
  217. data/spec/formatted_text_fragment_spec.rb +211 -0
  218. data/spec/graphics_spec.rb +446 -0
  219. data/spec/images_spec.rb +96 -0
  220. data/spec/inline_formatted_text_parser_spec.rb +502 -0
  221. data/spec/jpg_spec.rb +25 -0
  222. data/spec/line_wrap_spec.rb +341 -0
  223. data/spec/measurement_units_spec.rb +23 -0
  224. data/spec/name_tree_spec.rb +112 -0
  225. data/spec/object_store_spec.rb +160 -0
  226. data/spec/outline_spec.rb +269 -0
  227. data/spec/pdf_object_spec.rb +170 -0
  228. data/spec/png_spec.rb +237 -0
  229. data/spec/reference_spec.rb +82 -0
  230. data/spec/repeater_spec.rb +96 -0
  231. data/spec/security_spec.rb +120 -0
  232. data/spec/snapshot_spec.rb +138 -0
  233. data/spec/spec_helper.rb +26 -0
  234. data/spec/stamp_spec.rb +108 -0
  235. data/spec/stroke_styles_spec.rb +163 -0
  236. data/spec/table_spec.rb +598 -0
  237. data/spec/template_spec.rb +158 -0
  238. data/spec/text_at_spec.rb +119 -0
  239. data/spec/text_box_spec.rb +742 -0
  240. data/spec/text_spacing_spec.rb +75 -0
  241. data/spec/text_spec.rb +333 -0
  242. data/spec/text_with_inline_formatting_spec.rb +193 -0
  243. data/spec/transparency_spec.rb +89 -0
  244. metadata +331 -0
@@ -0,0 +1,449 @@
1
+ # encoding: utf-8
2
+
3
+ # text.rb : Implements PDF text primitives
4
+ #
5
+ # Copyright May 2008, Gregory Brown. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+ require "prawn/core/text"
9
+ require "prawn/core/text/wrap"
10
+ require "prawn/text/box"
11
+ require "prawn/text/formatted"
12
+ require "zlib"
13
+
14
+ module Prawn
15
+ module Text
16
+
17
+ include Prawn::Core::Text
18
+ include Prawn::Text::Formatted
19
+
20
+ Prawn::Text::NBSP = " "
21
+
22
+ # If you want text to flow onto a new page or between columns, this is the
23
+ # method to use. If, instead, if you want to place bounded text outside of
24
+ # the flow of a document (for captions, labels, charts, etc.), use Text::Box
25
+ # or its convenience method text_box.
26
+ #
27
+ # Draws text on the page. Prawn attempts to wrap the text to fit within your
28
+ # current bounding box (or margin_box if no bounding box is being used).
29
+ # Text will flow onto the next page when it reaches the bottom of the
30
+ # bounding box. Text wrap in Prawn does not re-flow linebreaks, so if you
31
+ # want fully automated text wrapping, be sure to remove newlines before
32
+ # attempting to draw your string.
33
+ #
34
+ # == Examples
35
+ #
36
+ # pdf.text "Will be wrapped when it hits the edge of your bounding box"
37
+ # pdf.text "This will be centered", :align => :center
38
+ # pdf.text "This will be right aligned", :align => :right
39
+ # pdf.text "This <i>includes <b>inline</b></i> <font size='24'>" +
40
+ # "formatting</font>", :inline_format => true
41
+ #
42
+ # If your font contains kerning pair data that Prawn can parse, the
43
+ # text will be kerned by default. You can disable kerning by including
44
+ # a false <tt>:kerning</tt> option. If you want to disable kerning on an
45
+ # entire document, set default_kerning = false for that document
46
+ #
47
+ # === Text Positioning Details
48
+ #
49
+ # The text is positioned at font.ascender below the baseline,
50
+ # making it easy to use this method within bounding boxes and spans.
51
+ #
52
+ # == Encoding
53
+ #
54
+ # Note that strings passed to this function should be encoded as UTF-8.
55
+ # If you get unexpected characters appearing in your rendered document,
56
+ # check this.
57
+ #
58
+ # If the current font is a built-in one, although the string must be
59
+ # encoded as UTF-8, only characters that are available in WinAnsi
60
+ # are allowed.
61
+ #
62
+ # If an empty box is rendered to your PDF instead of the character you
63
+ # wanted it usually means the current font doesn't include that character.
64
+ #
65
+ # == Options (default values marked in [])
66
+ #
67
+ # <tt>:inline_format</tt>::
68
+ # <tt>boolean</tt>. If true, then the string parameter is interpreted
69
+ # as a HTML-esque string that recognizes the following tags:
70
+ # <tt>\<b></b></tt>:: bold
71
+ # <tt>\<i></i></tt>:: italic
72
+ # <tt>\<u></u></tt>:: underline
73
+ # <tt>\<strikethrough></strikethrough></tt>:: strikethrough
74
+ # <tt>\<sub></sub></tt>:: subscript
75
+ # <tt>\<sup></sup></tt>:: superscript
76
+ # <tt>\<font></font></tt>::
77
+ # with the following attributes (using double or single quotes)
78
+ # <tt>size="24"</tt>::
79
+ # attribute for setting size
80
+ # <tt>character_spacing="2.5"</tt>::
81
+ # attribute for setting character spacing
82
+ # <tt>name="Helvetica"</tt>::
83
+ # attribute for setting the font. The font name must be an
84
+ # AFM font with the desired faces or must be a font that is
85
+ # already registered using Prawn::Document#font_families
86
+ # <tt>\<color></color></tt>::
87
+ # with the following attributes
88
+ # <tt>rgb="ffffff" or rgb="#ffffff"</tt>::
89
+ # <tt>c="100" m="100" y="100" k="100"</tt>::
90
+ # <tt>\<link></link></tt>::
91
+ # with the following attributes
92
+ # <tt>href="http://example.com"</tt>:: an external link
93
+ # <tt>anchor="ToC"</tt>::
94
+ # where the value of the anchor attribute is the name of a
95
+ # destination that has already been or will be registered
96
+ # using Prawn::Core::Destinations#add_dest. A clickable link
97
+ # will be created to that destination.
98
+ # Note that you must explicitly underline and color using the
99
+ # appropriate tags if you which to draw attention to the link
100
+ #
101
+ # <tt>:kerning</tt>:: <tt>boolean</tt>. Whether or not to use kerning (if it
102
+ # is available with the current font)
103
+ # [value of default_kerning?]
104
+ # <tt>:size</tt>:: <tt>number</tt>. The font size to use. [current font
105
+ # size]
106
+ # <tt>:character_spacing</tt>:: <tt>number</tt>. The amount of space to add
107
+ # to or remove from the default character
108
+ # spacing. [0]
109
+ # <tt>:style</tt>:: The style to use. The requested style must be part of
110
+ # the current font familly. [current style]
111
+ # <tt>:indent_paragraphs</tt>:: <tt>number</tt>. The amount to indent the
112
+ # first line of each paragraph. Omit this
113
+ # option if you do not want indenting
114
+ # <tt>:align</tt>:: <tt>:left</tt>, <tt>:center</tt>, <tt>:right</tt>, or
115
+ # <tt>:justify</tt> Alignment within the bounding box [:left]
116
+ # <tt>:valign</tt>:: <tt>:top</tt>, <tt>:center</tt>, or <tt>:bottom</tt>.
117
+ # Vertical alignment within the bounding box [:top]
118
+ # <tt>:leading</tt>:: <tt>number</tt>. Additional space between lines [0]
119
+ # <tt>:final_gap</tt>:: <tt>boolean</tt>. If true, then the space between
120
+ # each line is included below the last line;
121
+ # otherwise, document.y is placed just below the
122
+ # descender of the last line printed [true]
123
+ #
124
+ # == Exceptions
125
+ #
126
+ # Raises <tt>ArgumentError</tt> if <tt>:at</tt> option included
127
+ #
128
+ # Raises <tt>Prawn::Errrors::CannotFit</tt> if not wide enough to print
129
+ # any text
130
+ #
131
+ def text(string, options={})
132
+ # we modify the options. don't change the user's hash
133
+ options = options.dup
134
+
135
+ if options[:inline_format]
136
+ options.delete(:inline_format)
137
+ array = Text::Formatted::Parser.to_array(string)
138
+ formatted_text(array, options)
139
+ return
140
+ end
141
+
142
+ inspect_options_for_text(options)
143
+
144
+ if @indent_paragraphs
145
+ string.split("\n").each do |paragraph|
146
+ options[:skip_encoding] = false
147
+ remaining_text = draw_indented_line(paragraph, options)
148
+ options[:skip_encoding] = true
149
+ if remaining_text == paragraph
150
+ # we were too close to the bottom of the page to print even one line
151
+ @bounding_box.move_past_bottom
152
+ remaining_text = draw_indented_line(paragraph, options)
153
+ end
154
+ remaining_text = fill_text_box(remaining_text, options)
155
+ draw_remaining_text_on_new_pages(remaining_text, options)
156
+ end
157
+ else
158
+ remaining_text = fill_text_box(string, options)
159
+ options[:skip_encoding] = true
160
+ draw_remaining_text_on_new_pages(remaining_text, options)
161
+ end
162
+ end
163
+
164
+
165
+ # Draws formatted text to the page.
166
+ # Formatted text is comprised of an array of hashes, where each hash defines
167
+ # text and format information. See Text::Formatted#formatted_text_box for
168
+ # more information on the structure of this array
169
+ #
170
+ # == Example
171
+ #
172
+ # text([{ :text => "hello" },
173
+ # { :text => "world",
174
+ # :size => 24,
175
+ # :style => [:bold, :italic] }])
176
+ #
177
+ # == Options
178
+ #
179
+ # Accepts the same options as #text
180
+ #
181
+ # == Exceptions
182
+ #
183
+ # Same as for #text
184
+ #
185
+ def formatted_text(array, options={})
186
+ # we modify the options. don't change the user's hash
187
+ options = options.dup
188
+
189
+ inspect_options_for_text(options)
190
+
191
+ if @indent_paragraphs
192
+ Text::Formatted::Parser.array_paragraphs(array).each do |paragraph|
193
+ options[:skip_encoding] = false
194
+ remaining_text = draw_indented_formatted_line(paragraph, options)
195
+ options[:skip_encoding] = true
196
+ if remaining_text == paragraph
197
+ # we were too close to the bottom of the page to print even one line
198
+ @bounding_box.move_past_bottom
199
+ remaining_text = draw_indented_formatted_line(paragraph, options)
200
+ end
201
+ remaining_text = fill_formatted_text_box(remaining_text, options)
202
+ draw_remaining_formatted_text_on_new_pages(remaining_text, options)
203
+ end
204
+ else
205
+ remaining_text = fill_formatted_text_box(array, options)
206
+ options[:skip_encoding] = true
207
+ draw_remaining_formatted_text_on_new_pages(remaining_text, options)
208
+ end
209
+ end
210
+
211
+ # Draws text on the page, beginning at the point specified by the :at option
212
+ # the string is assumed to be pre-formatted to properly fit the page.
213
+ #
214
+ # pdf.draw_text "Hello World", :at => [100,100]
215
+ # pdf.draw_text "Goodbye World", :at => [50,50], :size => 16
216
+ #
217
+ # If your font contains kerning pair data that Prawn can parse, the
218
+ # text will be kerned by default. You can disable kerning by including
219
+ # a false <tt>:kerning</tt> option. If you want to disable kerning on an
220
+ # entire document, set default_kerning = false for that document
221
+ #
222
+ # === Text Positioning Details:
223
+ #
224
+ # Prawn will position your text by the left-most edge of its baseline, and
225
+ # flow along a single line. (This means that :align will not work)
226
+ #
227
+ # == Rotation
228
+ #
229
+ # Text can be rotated before it is placed on the canvas by specifying the
230
+ # <tt>:rotate</tt> option with a given angle. Rotation occurs counter-clockwise.
231
+ #
232
+ # == Encoding
233
+ #
234
+ # Note that strings passed to this function should be encoded as UTF-8.
235
+ # If you get unexpected characters appearing in your rendered document,
236
+ # check this.
237
+ #
238
+ # If the current font is a built-in one, although the string must be
239
+ # encoded as UTF-8, only characters that are available in WinAnsi
240
+ # are allowed.
241
+ #
242
+ # If an empty box is rendered to your PDF instead of the character you
243
+ # wanted it usually means the current font doesn't include that character.
244
+ #
245
+ # == Options (default values marked in [])
246
+ #
247
+ # <tt>:at</tt>:: <tt>[x, y]</tt>(required). The position at which to start the text
248
+ # <tt>:kerning</tt>:: <tt>boolean</tt>. Whether or not to use kerning (if it
249
+ # is available with the current font)
250
+ # [value of default_kerning?]
251
+ # <tt>:size</tt>:: <tt>number</tt>. The font size to use. [current font
252
+ # size]
253
+ # <tt>:style</tt>:: The style to use. The requested style must be part of
254
+ # the current font familly. [current style]
255
+ #
256
+ # <tt>:rotate</tt>:: <tt>number</tt>. The angle to which to rotate text
257
+ #
258
+ # == Exceptions
259
+ #
260
+ # Raises <tt>ArgumentError</tt> if <tt>:at</tt> option omitted
261
+ #
262
+ # Raises <tt>ArgumentError</tt> if <tt>:align</tt> option included
263
+ #
264
+ def draw_text(text, options)
265
+ # we modify the options. don't change the user's hash
266
+ options = options.dup
267
+ inspect_options_for_draw_text(options)
268
+ # dup because normalize_encoding changes the string
269
+ text = text.to_s.dup
270
+ save_font do
271
+ process_text_options(options)
272
+ font.normalize_encoding!(text) unless @skip_encoding
273
+ font_size(options[:size]) { draw_text!(text, options) }
274
+ end
275
+ end
276
+
277
+ # Gets height of text in PDF points.
278
+ # Same options as #text, except as noted.
279
+ # Not compatible with :indent_paragraphs option
280
+ #
281
+ # ==Example
282
+ #
283
+ # height_of("hello\nworld")
284
+ #
285
+ # == Exceptions
286
+ #
287
+ # Raises <tt>NotImplementedError</tt> if <tt>:indent_paragraphs</tt>
288
+ # option included
289
+ #
290
+ # Raises <tt>Prawn::Errrors::CannotFit</tt> if not wide enough to print
291
+ # any text
292
+ #
293
+ def height_of(string, options={})
294
+ if options[:indent_paragraphs]
295
+ raise NotImplementedError, ":indent_paragraphs option not available" +
296
+ "with height_of"
297
+ end
298
+ process_final_gap_option(options)
299
+ box = Text::Box.new(string,
300
+ options.merge(:height => 100000000,
301
+ :document => self))
302
+ printed = box.render(:dry_run => true)
303
+
304
+ height = box.height - (box.line_height - box.ascender)
305
+ height += box.line_height + box.leading - box.ascender if @final_gap
306
+ height
307
+ end
308
+
309
+ # Gets height of formatted text in PDF points.
310
+ # See documentation for #height_of.
311
+ #
312
+ # ==Example
313
+ #
314
+ # height_of_formatted([{ :text => "hello" },
315
+ # { :text => "world",
316
+ # :size => 24,
317
+ # :style => [:bold, :italic] }])
318
+ #
319
+ def height_of_formatted(array, options={})
320
+ if options[:indent_paragraphs]
321
+ raise NotImplementedError, ":indent_paragraphs option not available" +
322
+ "with height_of"
323
+ end
324
+ process_final_gap_option(options)
325
+ box = Text::Formatted::Box.new(array,
326
+ options.merge(:height => 100000000,
327
+ :document => self))
328
+ printed = box.render(:dry_run => true)
329
+
330
+ height = box.height - (box.line_height - box.ascender)
331
+ height += box.line_height + box.leading - box.ascender if @final_gap
332
+ height
333
+ end
334
+
335
+ private
336
+
337
+ def draw_remaining_text_on_new_pages(remaining_text, options)
338
+ while remaining_text.length > 0
339
+ @bounding_box.move_past_bottom
340
+ previous_remaining_text = remaining_text
341
+ remaining_text = fill_text_box(remaining_text, options)
342
+ break if remaining_text == previous_remaining_text
343
+ end
344
+ end
345
+
346
+ def draw_indented_line(string, options)
347
+ indent(@indent_paragraphs) do
348
+ fill_text_box(string, options.dup.merge(:single_line => true))
349
+ end
350
+ end
351
+
352
+ def fill_text_box(text, options)
353
+ merge_text_box_positioning_options(options)
354
+
355
+ box = Text::Box.new(text, options)
356
+ remaining_text = box.render
357
+
358
+ self.y -= box.height - (box.line_height - box.ascender)
359
+ if @final_gap
360
+ self.y -= box.line_height + box.leading - box.ascender
361
+ end
362
+ remaining_text
363
+ end
364
+
365
+
366
+
367
+
368
+ def draw_remaining_formatted_text_on_new_pages(remaining_text, options)
369
+ while remaining_text.length > 0
370
+ @bounding_box.move_past_bottom
371
+ previous_remaining_text = remaining_text
372
+ remaining_text = fill_formatted_text_box(remaining_text, options)
373
+ break if remaining_text == previous_remaining_text
374
+ end
375
+ end
376
+
377
+ def draw_indented_formatted_line(string, options)
378
+ indent(@indent_paragraphs) do
379
+ fill_formatted_text_box(string, options.dup.merge(:single_line => true))
380
+ end
381
+ end
382
+
383
+ def fill_formatted_text_box(text, options)
384
+ merge_text_box_positioning_options(options)
385
+ box = Text::Formatted::Box.new(text, options)
386
+ remaining_text = box.render
387
+
388
+ self.y -= box.height - (box.line_height - box.ascender)
389
+ if @final_gap
390
+ self.y -= box.line_height + box.leading - box.ascender
391
+ end
392
+ remaining_text
393
+ end
394
+
395
+
396
+
397
+ def merge_text_box_positioning_options(options)
398
+ bottom = @bounding_box.stretchy? ? @margin_box.absolute_bottom :
399
+ @bounding_box.absolute_bottom
400
+
401
+ options[:height] = y - bottom
402
+ options[:width] = bounds.width
403
+ options[:at] = [@bounding_box.left_side - @bounding_box.absolute_left,
404
+ y - @bounding_box.absolute_bottom]
405
+ end
406
+
407
+ def inspect_options_for_draw_text(options)
408
+ if options[:at].nil?
409
+ raise ArgumentError, "The :at option is required for draw_text"
410
+ elsif options[:align]
411
+ raise ArgumentError, "The :align option does not work with draw_text"
412
+ end
413
+ if options[:kerning].nil? then
414
+ options[:kerning] = default_kerning?
415
+ end
416
+ valid_options = Prawn::Core::Text::VALID_OPTIONS + [:at, :rotate]
417
+ Prawn.verify_options(valid_options, options)
418
+ end
419
+
420
+ def inspect_options_for_text(options)
421
+ if options[:at]
422
+ raise ArgumentError, ":at is no longer a valid option with text." +
423
+ "use draw_text or text_box instead"
424
+ end
425
+ process_final_gap_option(options)
426
+ process_indent_paragraphs_option(options)
427
+ options[:document] = self
428
+ end
429
+
430
+ def process_final_gap_option(options)
431
+ @final_gap = options[:final_gap].nil? || options[:final_gap]
432
+ options.delete(:final_gap)
433
+ end
434
+
435
+ def process_indent_paragraphs_option(options)
436
+ @indent_paragraphs = options[:indent_paragraphs]
437
+ options.delete(:indent_paragraphs)
438
+ end
439
+
440
+ def move_text_position(dy)
441
+ bottom = @bounding_box.stretchy? ? @margin_box.absolute_bottom :
442
+ @bounding_box.absolute_bottom
443
+
444
+ @bounding_box.move_past_bottom if (y - dy) < bottom
445
+
446
+ self.y -= dy
447
+ end
448
+ end
449
+ end
@@ -0,0 +1,392 @@
1
+ # encoding: utf-8
2
+
3
+ # text/rectangle.rb : Implements text boxes
4
+ #
5
+ # Copyright November 2009, Daniel Nelson. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+ #
9
+
10
+ module Prawn
11
+ module Text
12
+
13
+ # Draws the requested text into a box. When the text overflows
14
+ # the rectangle, you can display ellipses, shrink to fit, or
15
+ # truncate the text. Text boxes are independent of the document
16
+ # y position.
17
+ #
18
+ # == Encoding
19
+ #
20
+ # Note that strings passed to this function should be encoded as UTF-8.
21
+ # If you get unexpected characters appearing in your rendered document,
22
+ # check this.
23
+ #
24
+ # If the current font is a built-in one, although the string must be
25
+ # encoded as UTF-8, only characters that are available in WinAnsi
26
+ # are allowed.
27
+ #
28
+ # If an empty box is rendered to your PDF instead of the character you
29
+ # wanted it usually means the current font doesn't include that character.
30
+ #
31
+ # == Options (default values marked in [])
32
+ #
33
+ # <tt>:kerning</tt>:: <tt>boolean</tt>. Whether or not to use kerning (if it
34
+ # is available with the current font)
35
+ # [value of document.default_kerning?]
36
+ # <tt>:size</tt>:: <tt>number</tt>. The font size to use. [current font
37
+ # size]
38
+ # <tt>:character_spacing</tt>:: <tt>number</tt>. The amount of space to add
39
+ # to or remove from the default character
40
+ # spacing. [0]
41
+ # <tt>:style</tt>:: The style to use. The requested style must be part of
42
+ # the current font familly. [current style]
43
+ #
44
+ # <tt>:at</tt>::
45
+ # <tt>[x, y]</tt>. The upper left corner of the box
46
+ # [@document.bounds.left, @document.bounds.top]
47
+ # <tt>:width</tt>::
48
+ # <tt>number</tt>. The width of the box [@document.bounds.right - @at[0]]
49
+ # <tt>:height</tt>::
50
+ # <tt>number</tt>. The height of the box [@at[1] - @document.bounds.bottom]
51
+ # <tt>:align</tt>::
52
+ # <tt>:left</tt>, <tt>:center</tt>, <tt>:right</tt>, or
53
+ # <tt>:justify</tt> Alignment within the bounding box [:left]
54
+ # <tt>:valign</tt>::
55
+ # <tt>:top</tt>, <tt>:center</tt>, or <tt>:bottom</tt>. Vertical
56
+ # alignment within the bounding box [:top]
57
+ #
58
+ # <tt>:rotate</tt>::
59
+ # <tt>number</tt>. The angle to rotate the text
60
+ # <tt>:rotate_around</tt>::
61
+ # <tt>:center</tt>, <tt>:upper_left</tt>, <tt>:upper_right</tt>,
62
+ # <tt>:lower_right</tt>, or <tt>:lower_left</tt>. The point around which
63
+ # to rotate the text [:upper_left]
64
+ # <tt>:leading</tt>::
65
+ # <tt>number</tt>. Additional space between lines [0]
66
+ # <tt>:single_line</tt>::
67
+ # <tt>boolean</tt>. If true, then only the first line will be drawn [false]
68
+ # <tt>:skip_encoding</tt>::
69
+ # <tt>boolean</tt> [false]
70
+ # <tt>:overflow</tt>::
71
+ # <tt>:truncate</tt>, <tt>:shrink_to_fit</tt>, <tt>:expand</tt>, or
72
+ # <tt>:ellipses</tt>. This controls the behavior when the amount of text
73
+ # exceeds the available space. <tt>:ellipses</tt> [:truncate]
74
+ # <tt>:min_font_size</tt>::
75
+ # <tt>number</tt>. The minimum font size to use when :overflow is set to
76
+ # :shrink_to_fit (that is the font size will not be reduced to less than
77
+ # this value, even if it means that some text will be cut off). [5]
78
+ #
79
+ # == Returns
80
+ #
81
+ # Returns any text that did not print under the current settings.
82
+ #
83
+ # NOTE: if an AFM font is used, then the returned text is encoded in
84
+ # WinAnsi. Subsequent calls to text_box that pass this returned text back
85
+ # into text box must include a :skip_encoding => true option. This is
86
+ # unnecessary when using TTF fonts because those operate on UTF-8 encoding.
87
+ #
88
+ # == Exceptions
89
+ #
90
+ # Raises <tt>Prawn::Errrors::CannotFit</tt> if not wide enough to print
91
+ # any text
92
+ #
93
+ def text_box(string, options)
94
+ Text::Box.new(string, options.merge(:document => self)).render
95
+ end
96
+
97
+ # Generally, one would use the Prawn::Text#text_box convenience
98
+ # method. However, using Text::Box.new in conjunction with
99
+ # #render(:dry_run=> true) enables one to do look-ahead calculations prior
100
+ # to placing text on the page, or to determine how much vertical space was
101
+ # consumed by the printed text
102
+ #
103
+ class Box
104
+ include Prawn::Core::Text::Wrap
105
+
106
+ def valid_options
107
+ Prawn::Core::Text::VALID_OPTIONS + [:at, :height, :width,
108
+ :align, :valign,
109
+ :rotate, :rotate_around,
110
+ :overflow, :min_font_size,
111
+ :leading, :character_spacing,
112
+ :single_line,
113
+ :skip_encoding,
114
+ :document]
115
+ end
116
+
117
+ # The text that was successfully printed (or, if <tt>dry_run</tt> was
118
+ # used, the test that would have been successfully printed)
119
+ attr_reader :text
120
+ # The upper left corner of the text box
121
+ attr_reader :at
122
+ # The line height of the last line printed
123
+ attr_reader :line_height
124
+ # The height of the ascender of the last line printed
125
+ attr_reader :ascender
126
+ # The height of the descender of the last line printed
127
+ attr_reader :descender
128
+ # The leading used during printing
129
+ attr_reader :leading
130
+
131
+
132
+ # Extend Prawn::Text::Box
133
+ #
134
+ # Example (see Prawn::Text::Core::Wrap for what is required
135
+ # of the wrap method if you want to override the default
136
+ # wrapping algorithm):
137
+ #
138
+ # module MyWrap
139
+ #
140
+ # def wrap
141
+ # @text = nil
142
+ # @line_height = @document.font.height
143
+ # @descender = @document.font.descender
144
+ # @ascender = @document.font.ascender
145
+ # @baseline_y = -@ascender
146
+ # draw_line("all your base are belong to us")
147
+ # ""
148
+ # end
149
+ #
150
+ # end
151
+ #
152
+ # Prawn::Text::Box.extensions << MyWrap
153
+ #
154
+ # box = Prawn::Text::Box.new('hello world')
155
+ # box.render('why can't I print anything other than' +
156
+ # '"all your base are belong to us"?')
157
+ #
158
+ #
159
+ def self.extensions
160
+ @extensions ||= []
161
+ end
162
+
163
+ def self.inherited(base) #:nodoc:
164
+ extensions.each { |e| base.extensions << e }
165
+ end
166
+
167
+ # See Prawn::Text#text_box for valid options
168
+ #
169
+ def initialize(text, options={})
170
+ @inked = false
171
+ Prawn.verify_options(valid_options, options)
172
+ options = options.dup
173
+
174
+ self.class.extensions.reverse_each { |e| extend e }
175
+
176
+ @overflow = options[:overflow] || :truncate
177
+
178
+ self.original_text = text
179
+ @text = nil
180
+
181
+ @document = options[:document]
182
+ @at = options[:at] ||
183
+ [@document.bounds.left, @document.bounds.top]
184
+ @width = options[:width] ||
185
+ @document.bounds.right - @at[0]
186
+ @height = options[:height] ||
187
+ @at[1] - @document.bounds.bottom
188
+ @align = options[:align] || :left
189
+ @vertical_align = options[:valign] || :top
190
+ @leading = options[:leading] || @document.default_leading?
191
+ @character_spacing = options[:character_spacing] ||
192
+ @document.character_spacing
193
+ @rotate = options[:rotate] || 0
194
+ @rotate_around = options[:rotate_around] || :upper_left
195
+ @single_line = options[:single_line]
196
+ @skip_encoding = options[:skip_encoding] || @document.skip_encoding
197
+
198
+ if @overflow == :expand
199
+ # if set to expand, then we simply set the bottom
200
+ # as the bottom of the document bounds, since that
201
+ # is the maximum we should expand to
202
+ @height = @at[1] - @document.bounds.bottom
203
+ @overflow = :truncate
204
+ end
205
+ @min_font_size = options[:min_font_size] || 5
206
+ if options[:kerning].nil? then
207
+ options[:kerning] = @document.default_kerning?
208
+ end
209
+ @options = { :kerning => options[:kerning],
210
+ :size => options[:size],
211
+ :style => options[:style] }
212
+
213
+ super(text, options)
214
+ end
215
+
216
+ # Render text to the document based on the settings defined in initialize.
217
+ #
218
+ # In order to facilitate look-ahead calculations, <tt>render</tt> accepts
219
+ # a <tt>:dry_run => true</tt> option. If provided, then everything is
220
+ # executed as if rendering, with the exception that nothing is drawn on
221
+ # the page. Useful for look-ahead computations of height, unprinted text,
222
+ # etc.
223
+ #
224
+ # Returns any text that did not print under the current settings
225
+ #
226
+ def render(flags={})
227
+ unprinted_text = ''
228
+ @document.save_font do
229
+ @document.character_spacing(@character_spacing) do
230
+ process_options
231
+
232
+ if @skip_encoding
233
+ text = original_text
234
+ else
235
+ text = normalize_encoding
236
+ end
237
+
238
+ @document.font_size(@font_size) do
239
+ shrink_to_fit(text) if @overflow == :shrink_to_fit
240
+ process_vertical_alignment(text)
241
+ @inked = true unless flags[:dry_run]
242
+ if @rotate != 0 && @inked
243
+ unprinted_text = render_rotated(text)
244
+ else
245
+ unprinted_text = wrap(text)
246
+ end
247
+ @inked = false
248
+ end
249
+ end
250
+ end
251
+
252
+ unprinted_text
253
+ end
254
+
255
+ # The height actually used during the previous <tt>render</tt>
256
+ #
257
+ def height
258
+ return 0 if @baseline_y.nil? || @descender.nil?
259
+ # baseline is already pushed down one line below the current
260
+ # line, so we need to subtract line line_height and leading,
261
+ # but we need to add in the descender since baseline is
262
+ # above the descender
263
+ @baseline_y.abs - @ascender - @leading
264
+ end
265
+
266
+ # The width available at this point in the box
267
+ #
268
+ def available_width
269
+ @width
270
+ end
271
+
272
+ def draw_line(line_to_print, line_width=0, word_spacing=0, include_ellipses=false) #:nodoc:
273
+ insert_ellipses(line_to_print) if include_ellipses
274
+
275
+ case(@align)
276
+ when :left, :justify
277
+ x = @at[0]
278
+ when :center
279
+ x = @at[0] + @width * 0.5 - line_width * 0.5
280
+ when :right
281
+ x = @at[0] + @width - line_width
282
+ end
283
+
284
+ y = @at[1] + @baseline_y
285
+
286
+ if @inked
287
+ if @align == :justify
288
+ @document.word_spacing(word_spacing) {
289
+ @document.character_spacing(@character_spacing) {
290
+ @document.draw_text!(line_to_print, :at => [x, y],
291
+ :kerning => @kerning)
292
+ }
293
+ }
294
+ else
295
+ @document.character_spacing(@character_spacing) {
296
+ @document.draw_text!(line_to_print, :at => [x, y],
297
+ :kerning => @kerning)
298
+ }
299
+ end
300
+ end
301
+
302
+ line_to_print
303
+ end
304
+
305
+ private
306
+
307
+ def normalize_encoding
308
+ @document.font.normalize_encoding(@original_string)
309
+ end
310
+
311
+ def original_text
312
+ @original_string
313
+ end
314
+
315
+ def original_text=(string)
316
+ @original_string = string.dup
317
+ end
318
+
319
+ def process_vertical_alignment(text)
320
+ return if @vertical_align == :top
321
+ wrap(text)
322
+ case @vertical_align
323
+ when :center
324
+ @at[1] = @at[1] - (@height - height) * 0.5
325
+ when :bottom
326
+ @at[1] = @at[1] - (@height - height)
327
+ end
328
+ @height = height
329
+ end
330
+
331
+ # Decrease the font size until the text fits or the min font
332
+ # size is reached
333
+ def shrink_to_fit(text)
334
+ while (unprinted_text = wrap(text)).length > 0 &&
335
+ @font_size > @min_font_size
336
+ @font_size -= 0.5
337
+ @document.font_size = @font_size
338
+ end
339
+ end
340
+
341
+ def process_options
342
+ # must be performed within a save_font bock because
343
+ # document.process_text_options sets the font
344
+ @document.process_text_options(@options)
345
+ @font_size = @options[:size]
346
+ @kerning = @options[:kerning]
347
+ end
348
+
349
+ def render_rotated(text)
350
+ unprinted_text = ''
351
+
352
+ case @rotate_around
353
+ when :center
354
+ x = @at[0] + @width * 0.5
355
+ y = @at[1] - @height * 0.5
356
+ when :upper_right
357
+ x = @at[0] + @width
358
+ y = @at[1]
359
+ when :lower_right
360
+ x = @at[0] + @width
361
+ y = @at[1] - @height
362
+ when :lower_left
363
+ x = @at[0]
364
+ y = @at[1] - @height
365
+ else
366
+ x = @at[0]
367
+ y = @at[1]
368
+ end
369
+
370
+ @document.rotate(@rotate, :origin => [x, y]) do
371
+ unprinted_text = wrap(text)
372
+ end
373
+ unprinted_text
374
+ end
375
+
376
+ def last_line?
377
+ @baseline_y.abs + @descender > @height - @line_height
378
+ end
379
+
380
+ def insert_ellipses(line_to_print)
381
+ if @document.width_of(line_to_print + "...",
382
+ :kerning => @kerning) < available_width
383
+ line_to_print.insert(-1, "...")
384
+ else
385
+ line_to_print[-3..-1] = "..." if line_to_print.length > 3
386
+ end
387
+ end
388
+
389
+ end
390
+
391
+ end
392
+ end