File: example.rb

package info (click to toggle)
ruby-prawn-manual-builder 0.2.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 5,428 kB
  • sloc: ruby: 411; makefile: 2
file content (393 lines) | stat: -rw-r--r-- 11,561 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# encoding: UTF-8

module Prawn
  module ManualBuilder
    # The Prawn::ManualBuilder::Example class holds all the helper methods 
    # used to generate manuals.
    #
    # The overall structure is to have single example files grouped by package
    # folders. Each package has a package builder file (with the same name as the
    # package folder) that defines the inner structure of subsections and
    # examples. The manual is then built by loading all the packages and some
    # standalone pages.
    #
    # To see one of the examples check manual/basic_concepts/cursor.rb
    #
    # To see one of the package builders check
    # manual/basic_concepts/basic_concepts.rb
    #
    # To see how the manual is built check manual/manual/manual.rb (Yes that's a
    # whole load of manuals)
    class Example < Prawn::ManualBuilder.document_class

      # Values used for the manual design:

      # This is the default value for the margin box
      #
      BOX_MARGIN   = 36

      # Additional indentation to keep the line measure with a reasonable size
      #
      INNER_MARGIN = 30

      # Vertical Rhythm settings
      #
      RHYTHM  = 10
      LEADING = 2

      # Colors
      #
      BLACK      = "000000"
      LIGHT_GRAY = "F2F2F2"
      GRAY       = "DDDDDD"
      DARK_GRAY  = "333333"
      BROWN      = "A4441C"
      ORANGE     = "F28157"
      LIGHT_GOLD = "FBFBBE"
      DARK_GOLD  = "EBE389"
      BLUE       = "0000D0"

      # Used to generate the url for the example files
      #
      MANUAL_URL = "http://github.com/prawnpdf/prawn/tree/master/manual"


      # Loads a package. Used on the manual.
      #
      def load_package(package)
        load_file(package, package)
      end

      # Loads a page with outline support. Used on the manual.
      #
      def load_page(package, page)
        load_file(package, page)

        outline.define do
          section(page.gsub("_", " ").capitalize, :destination => page_number)
        end
      end

      # Opens a file in a given package and evals the source
      #
      def load_file(package, file)
        start_new_page
        example = ExampleFile.new(package, "#{file}.rb")
        eval example.generate_block_source
      end


      # Creates a new ExamplePackage object and yields it to a block in order for
      # it to be populated with examples, sections and some introduction text.
      # Used on the package files.
      #
      def package(package, &block)
        ep = ExamplePackage.new(package)
        ep.instance_eval(&block)
        ep.render(self)
      end

      # Renders an ExamplePackage cover page.
      #
      # Starts a new page and renders the package introduction text.
      #
      def render_package_cover(package)
        header(package.name)
        instance_eval &(package.intro_block)

        outline.define do
          section(package.name, :destination => page_number, :closed => true)
        end
      end

      # Add the ExampleSection to the document outline within the appropriate
      # package.
      #
      def render_section(section)
        outline.add_subsection_to(section.package_name) do
          outline.section(section.name, :closed => true)
        end
      end

      # Renders an ExampleFile.
      #
      # Starts a new page and renders an introductory text, the example source and
      # evaluates the example source inline whenever that is appropriate according
      # to the ExampleFile directives.
      #
      def render_example(example)
        start_new_page

        outline.add_subsection_to(example.parent_name) do
          outline.page(:destination => page_number, :title => example.name)
        end

        example_header(example.parent_folder_name, example.filename)

        prose(example.introduction_text)

        code(example.source)

        if example.eval?
          eval_code(example.source)
        else
          source_link(example)
        end

        reset_settings
      end

      # Render the example header. Used on the example pages of the manual
      #
      def example_header(package, example)
        header_box do
          register_fonts
          font('DejaVu', :size => 18) do
            formatted_text([ { :text => package, :color => BROWN  },
                             { :text => "/",     :color => BROWN  },
                             { :text => example, :color => ORANGE }
                           ], :valign => :center)
          end
        end
      end

      # Register fonts used on the manual
      #
      def register_fonts
        kai_file = "/usr/share/fonts/truetype/arphic-gkai00mp/gkai00mp.ttf"
        font_families["Kai"] = {
          :normal => { :file => kai_file, :font => "Kai" }
        }

        dejavu_file = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
        font_families["DejaVu"] = {
          :normal => { :file => dejavu_file, :font => "DejaVu" }
        }
      end

      # Render a block of text after processing code tags and URLs to be used with
      # the inline_format option.
      #
      # Used on the introducory text for example pages of the manual and on
      # package pages intro
      #
      def prose(str)

        # Process the <code> tags
        str.gsub!(/<code>([^<]+?)<\/code>/,
            "<font name='Courier'><b>\\1<\/b><\/font>")

        # Process the links
        str.gsub!(/(https?:\/\/\S+)/,
                    "<color rgb='#{BLUE}'><link href=\"\\1\">\\1</link></color>")

        inner_box do
          font("Helvetica", :size => 11) do
            str.split(/\n\n+/).each do |paragraph|

              text(paragraph.gsub(/\s+/," "),
                   :align         => :justify,
                   :inline_format => true,
                   :leading       => LEADING,
                   :color         => DARK_GRAY)

              move_down(RHYTHM)
            end
          end
        end

        move_down(RHYTHM)
      end

      # Render a code block. Used on the example pages of the manual
      #
      def code(str)
        pre_text = str.gsub(' ', Prawn::Text::NBSP)
        pre_text = ::CodeRay.scan(pre_text, :ruby).to_prawn

        font('Courier', :size => 9.5) do
          colored_box(pre_text, :fill_color => DARK_GRAY)
        end
      end

      # Renders a dashed line and evaluates the code inline
      #
      def eval_code(source)
        move_down(RHYTHM)

        dash(3)
        stroke_color(BROWN)
        stroke_horizontal_line(-BOX_MARGIN, bounds.width + BOX_MARGIN)
        stroke_color(BLACK)
        undash

        move_down(RHYTHM*3)
        begin
          eval(source)
        rescue => e
          puts "Error evaluating example: #{e.message}"
          puts
          puts "---- Source: ----"
          puts source
        end
      end

      # Renders a box with the link for the example file
      #
      def source_link(example)
        url = "#{MANUAL_URL}/#{example.parent_folder_name}/#{example.filename}"

        reason = [{ :text  => "This code snippet was not evaluated inline. " +
                              "You may see its output by running the " +
                              "example file located here:\n",
                    :color => DARK_GRAY },

                  { :text   => url,
                    :color  => BLUE,
                    :link   => url}
                 ]

        font('Helvetica', :size => 9) do
          colored_box(reason,
                      :fill_color   => LIGHT_GOLD,
                      :stroke_color => DARK_GOLD,
                      :leading      => LEADING*3)
        end
      end

      # Render a page header. Used on the manual lone pages and package
      # introductory pages
      #
      def header(str)
        header_box do
          register_fonts
          font('DejaVu', :size => 24) do
            text(str, :color  => BROWN, :valign => :center)
          end
        end
      end

      # Render the arguments as a bulleted list. Used on the manual package
      # introductory pages
      #
      def list(*items)
        move_up(RHYTHM)

        inner_box do
          font("Helvetica", :size => 11) do
            items.each do |li|
              float { text("•", :color => DARK_GRAY) }
              indent(RHYTHM) do
                text(li.gsub(/\s+/," "),
                     :inline_format => true,
                     :color         => DARK_GRAY,
                     :leading       => LEADING)
              end

              move_down(RHYTHM)
            end
          end
        end
      end

      # Renders the page-wide headers
      #
      def header_box(&block)
        bounding_box([-bounds.absolute_left, cursor + BOX_MARGIN],
                     :width  => bounds.absolute_left + bounds.absolute_right,
                     :height => BOX_MARGIN*2 + RHYTHM*2) do

          fill_color LIGHT_GRAY
          fill_rectangle([bounds.left, bounds.top],
                          bounds.right,
                          bounds.top - bounds.bottom)
          fill_color BLACK

          indent(BOX_MARGIN + INNER_MARGIN, &block)
        end

        stroke_color GRAY
        stroke_horizontal_line(-BOX_MARGIN, bounds.width + BOX_MARGIN, :at => cursor)
        stroke_color BLACK

        move_down(RHYTHM*3)
      end

      # Renders a Bounding Box for the inner margin
      #
      def inner_box(&block)
        bounding_box([INNER_MARGIN, cursor],
                     :width => bounds.width - INNER_MARGIN*2,
                     &block)
      end

      # Renders a Bounding Box with some background color and the formatted text
      # inside it
      #
      def colored_box(box_text, options={})
        options = { :fill_color   => DARK_GRAY,
                    :stroke_color => nil,
                    :text_color   => LIGHT_GRAY,
                    :leading      => LEADING
                  }.merge(options)

        register_fonts
        text_options = { :leading        => options[:leading],
                         :fallback_fonts => ["DejaVu", "Kai"]
                       }

        box_height = height_of_formatted(box_text, text_options)

        bounding_box([INNER_MARGIN + RHYTHM, cursor],
                     :width => bounds.width - (INNER_MARGIN+RHYTHM)*2) do

          fill_color   options[:fill_color]
          stroke_color options[:stroke_color] || options[:fill_color]
          fill_and_stroke_rounded_rectangle(
              [bounds.left - RHYTHM, cursor],
              bounds.left + bounds.right + RHYTHM*2,
              box_height + RHYTHM*2,
              5
          )
          fill_color   BLACK
          stroke_color BLACK

          pad(RHYTHM) do
            formatted_text(box_text, text_options)
          end
        end

        move_down(RHYTHM*2)
      end

      # Draws X and Y axis rulers beginning at the margin box origin. Used on
      # examples.
      #
      def stroke_axis(options={})
        super({:height => (cursor - 20).to_i}.merge(options))
      end

      # Reset some of the Prawn settings including graphics and text to their
      # defaults.
      #
      # Used after rendering examples so that each new example starts with a clean
      # slate.
      #
      def reset_settings

        # Text settings
        font("Helvetica", :size => 12)
        default_leading 0
        self.text_direction = :ltr

        # Graphics settings
        self.line_width = 1
        self.cap_style  = :butt
        self.join_style = :miter
        undash
        fill_color   BLACK
        stroke_color BLACK
      end
    end
  end
end