Class PDF::TechBook
In: lib/pdf/techbook.rb
Parent: PDF::Writer
Transaction::Simple SimpleTable TechBook Complex Action Procset FontDescriptor FontEncoding Destination Info Catalog Encryption Contents Pages Outline Outlines Annotation Page Font ViewerPreferences Image Hash OHash QuickRef FontMetrics ARC4 StrokeStyle PolygonPoint ImageInfo StdDev lib/pdf/simpletable.rb lib/pdf/techbook.rb lib/pdf/writer.rb lib/pdf/quickref.rb Math lib/pdf/writer/fontmetrics.rb lib/pdf/writer/ohash.rb lib/pdf/writer/arc4.rb lib/pdf/writer/strokestyle.rb lib/pdf/writer/graphics.rb lib/pdf/writer/object.rb lib/pdf/writer/object/image.rb External EN Lang OffsetReader lib/pdf/writer/graphics/imageinfo.rb Graphics lib/pdf/writer/object/outlines.rb lib/pdf/writer/object/destination.rb lib/pdf/writer/object/viewerpreferences.rb lib/pdf/writer/object/fontencoding.rb lib/pdf/writer/object/page.rb lib/pdf/writer/object/contents.rb lib/pdf/writer/object/procset.rb lib/pdf/writer/object/pages.rb lib/pdf/writer/object/info.rb lib/pdf/writer/object/encryption.rb lib/pdf/writer/object/catalog.rb lib/pdf/writer/object/outline.rb lib/pdf/writer/object/fontdescriptor.rb lib/pdf/writer/object/action.rb lib/pdf/writer/object/font.rb lib/pdf/writer/object/annotation.rb Object Writer lib/pdf/charts/stddev.rb Charts PDF dot/m_33_0.png

PDF::TechBook

The TechBook class is a markup language interpreter. This will read a file containing the "TechBook" markukp, described below, and create a PDF document from it. This is intended as a complete document language, but it does have a number of limitations.

The TechBook markup language and class are used to format the PDF::Writer manual, represented in the distrubtion by the file "manual.pwd".

The TechBook markup language is primarily stream-oriented with awareness of lines. That is to say that the document will be read and generated from beginning to end in the order of the markup stream.

TechBook Markup

TechBook markup is relatively simple. The simplest markup is no markup at all (flowed paragraphs). This means that two lines separated by a single line separator will be treaed as part of the same paragraph and formatted appropriately by PDF::Writer. Paragaphs are terminated by empty lines, valid line markup directives, or valid headings.

Certain XML entitites will need to be escaped as they would in normal XML usage, that is, < must be written as <; > must be written as >; and & must be written as &.

Comments, headings, and directives are line-oriented where the first mandatory character is in the first column of the document and take up the whole line. Styling and callback tags may appear anywhere in the text.

Comments

Comments begin with the hash-mark (’#’) at the beginning of the line. Comment lines are ignored.

Styling and Callback Tags

Within normal, preserved, or code text, or in headings, HTML-like markup may be used for bold (&lt;b&gt;) and italic (&lt;i&gt;) text. TechBook supports standard PDF::Writer callback tags (<c:alink>, <c:ilink>, <C:bullet/>, and <C:disc/>) and adds two new ones (<r:xref/>, <C:tocdots/>).

&lt;r:xref/>:Creates an internal document link to the named cross-reference destination. Works with the heading format (see below). See tag_xref_replace for more information.
&lt;C:tocdots/>:This is used internally to create and display a row of dots between a table of contents entry and the page number to which it refers. This is used internally by TechBook.

Directives

Directives begin with a period (’.’) and are followed by a letter (‘a’..’z’) and then any combination of word characters (‘a’..’z’, ‘0’..’9’, and ‘_’). Directives are case-insensitive. A directive may have arguments; if there are arguments, they must follow the directive name after whitespace. After the arguments for a directive, if any, all other text is ignored and may be considered a comment.

.newpage [force]

The .newpage directive starts a new page. If multicolumn mode is on, a new column will be started if the current column is not the last column. If the optional argument force follows the .newpage directive, a new page will be started even if multicolumn mode is on.

  .newpage
  .newpage force

.pre, .endpre

The .pre and .endpre directives enclose a block of text with preserved newlines. This is similar to normal text, but the lines in the .pre block are not flowed together. This is useful for poetic forms or other text that must end when each line ends. .pre blocks may not be nested in any other formatting block. When an .endpre directive is encountered, the text format will be returned to normal (flowed text) mode.

  .pre
  The Way that can be told of is not the eternal Way;
  The name that can be named is not the eternal name.
  The Nameless is the origin of Heaven and Earth;
  The Named is the mother of all things.
  Therefore let there always be non-being,
    so we may see their subtlety,
  And let there always be being,
    so we may see their outcome.
  The two are the same,
  But after they are produced,
    they have different names.
  .endpre

.code, .endcode

The .code and .endcode directives enclose a block of text with preserved newlines. In addition, the font is changed from the normal techbook_textfont to techbook_codefont. The techbook_codefont is normally a fixed pitched font and defaults to Courier. At the end of the code block, the text state is restored to its prior state, which will either be .pre or normal.

  .code
  require 'pdf/writer'
  PDF::Writer.prepress # US Letter, portrait, 1.3, prepress
  .endcode

.blist, .endblist

These directives enclose a bulleted list block. Lists may be nested within other text states. If lists are nested, each list will be appropriately indented. Each line in the list block will be treated as a single list item with a bullet inserted in front using either the <C:bullet/> or <C:disc/> callbacks. Nested lists are successively indented. .blist directives accept one optional argument, the name of the type of bullet callback desired (e.g., ‘bullet’ for <C:bullet/> and ‘disc’ for <C:disc/>).

  .blist
  Item 1
  .blist disc
  Item 1.1
  .endblist
  .endblist

.eval, .endeval

With these directives, the block enclosed will collected and passed to Ruby‘s Kernel#eval. .eval blocks may be present within normal text, .pre, .code, and .blist blocks. No other block may be embedded within an .eval block.

  .eval
  puts "Hello"
  .endeval

.columns

Multi-column output is controlled with this directive, which accepts one or two parameters. The first parameter is mandatory and is either the number of columns (2 or more) or the word ‘off’ (turning off multi-column output). When starting multi-column output, a second parameter with the gutter size may be specified.

  .columns 3
  Column 1
  .newpage
  Column 2
  .newpage
  Column 3
  .columns off

.toc

This directive is used to tell TechBook to generate a table of contents after the first page (assumed to be a title page). If this is not present, then a table of contents will not be generated.

.author, .title, .subject, .keywords

Sets values in the PDF information object. The arguments — to the end of the line — are used to populate the values.

.done

Stops the processing of the document at this point.

Headings

Headings begin with a number followed by the rest of the heading format. This format is "#<heading-text>" or "#<heading-text>xref_name". TechBook supports five levels of headings. Headings may include markup, but should not exceed a single line in size; those headings which have boxes as part of their layout are not currently configured to work with multiple lines of heading output. If an xref_name is specified, then the &lt;r:xref> tag can use this name to find the target for the heading. If xref_name is not specified, then the "name" associated with the heading is the index of the order of insertion. The xref_name is case sensitive.

  1<Chapter>xChapter
  2<Section>Section23
  3<Subsection>
  4<Subsection>
  5<Subsection>

Heading Level 1

First level headings are generally chapters. As such, the standard implementation of the heading level 1 method (__heading1), will be rendered as "chapter#. heading-text" in centered white on a black background, at 26 point (H1_STYLE). First level headings are added to the table of contents.

Heading Level 2

Second level headings are major sections in chapters. The headings are rendered by default as black on 80% grey, left-justified at 18 point (H2_STYLE). The text is unchanged (__heading2). Second level headings are added to the table of contents.

Heading Level 3, 4, and 5

The next three heading levels are used for varying sections within second level chapter sections. They are rendered by default in black on the background (there is no bar) at 18, 14, and 12 points, respectively (H3_STYLE, H4_STYLE, and H5_STYLE). Third level headings are bold-faced (__heading3); fourth level headings are italicised (__heading4), and fifth level headings are underlined (__heading5).

Methods

Classes and Modules

Class PDF::TechBook::TagTocDots
Class PDF::TechBook::TagXref

Constants

H1_STYLE = { :background => Color::RGB::Black, :foreground => Color::RGB::White, :justification => :center, :font_size => 26, :bar => true
H2_STYLE = { :background => Color::RGB::Grey80, :foreground => Color::RGB::Black, :justification => :left, :font_size => 18, :bar => true
H3_STYLE = { :background => Color::RGB::White, :foreground => Color::RGB::Black, :justification => :left, :font_size => 18, :bar => false
H4_STYLE = { :background => Color::RGB::White, :foreground => Color::RGB::Black, :justification => :left, :font_size => 14, :bar => false
H5_STYLE = { :background => Color::RGB::White, :foreground => Color::RGB::Black, :justification => :left, :font_size => 12, :bar => false
LIST_ITEM_STYLES = %w(bullet disc)

Attributes

chapter_number  [RW] 
table_of_contents  [RW] 
techbook_codefont  [RW] 
techbook_encoding  [RW] 
techbook_fontsize  [RW] 
techbook_source_dir  [RW] 
techbook_textfont  [RW] 
xref_table  [R] 

Public Class methods

[Source]

     # File lib/pdf/techbook.rb, line 793
793:   def self.run(args)
794:     config = OpenStruct.new
795:     config.regen      = false
796:     config.cache      = true
797:     config.compressed = false
798: 
799:     opts = OptionParser.new do |opt|
800:       opt.banner    = PDF::Writer::Lang[:techbook_usage_banner] % [ File.basename($0) ]
801:       PDF::Writer::Lang[:techbook_usage_banner_1].each do |ll|
802:         opt.separator "  #{ll}"
803:       end
804:       opt.on('-f', '--force-regen', *PDF::Writer::Lang[:techbook_help_force_regen]) { config.regen = true }
805:       opt.on('-n', '--no-cache', *PDF::Writer::Lang[:techbook_help_no_cache]) { config.cache = false }
806:       opt.on('-z', '--compress', *PDF::Writer::Lang[:techbook_help_compress]) { config.compressed = true }
807:       opt.on_tail ""
808:       opt.on_tail("--help", *PDF::Writer::Lang[:techbook_help_help]) { $stderr << opt; exit(0) }
809:     end
810:     opts.parse!(args)
811: 
812:     config.document = args[0]
813: 
814:     unless config.document
815:       config.document = "manual.pwd"
816:       unless File.exist?(config.document)
817:         dirn = File.dirname(__FILE__)
818:         config.document = File.join(dirn, File.basename(config.document))
819:         unless File.exist?(config.document)
820:           dirn = File.join(dirn, "..")
821:           config.document = File.join(dirn, File.basename(config.document))
822:           unless File.exist?(config.document)
823:             dirn = File.join(dirn, "..")
824:             config.document = File.join(dirn,
825:                                         File.basename(config.document))
826:             unless File.exist?(config.document)
827:               $stderr.puts PDF::Writer::Lang[:techbook_cannot_find_document]
828:               exit(1)
829:             end
830:           end
831:         end
832:       end
833: 
834:       $stderr.puts PDF::Writer::Lang[:techbook_using_default_doc] % config.document
835:     end
836: 
837:     dirn = File.dirname(config.document)
838:     extn = File.extname(config.document)
839:     base = File.basename(config.document, extn)
840: 
841:     files = {
842:       :document => config.document,
843:       :cache    => "#{base}._mc",
844:       :pdf      => "#{base}.pdf"
845:     }
846: 
847:     unless config.regen
848:       if File.exist?(files[:cache])
849:         _tm_doc = File.mtime(config.document)
850:         _tm_prg = File.mtime(__FILE__)
851:         _tm_cch = File.mtime(files[:cache])
852:         
853:           # If the cached file is newer than either the document or the
854:           # class program, then regenerate.
855:         if (_tm_doc < _tm_cch) and (_tm_prg < _tm_cch)
856:           $stderr.puts PDF::Writer::Lang[:techbook_using_cached_doc] % File.basename(files[:cache])
857:           pdf = File.open(files[:cache], "rb") { |cf| Marshal.load(cf.read) }
858:           pdf.save_as(files[:pdf])
859:           File.open(files[:pdf], "wb") { |pf| pf.write pdf.render }
860:           exit(0)
861:         else
862:           $stderr.puts PDF::Writer::Lang[:techbook_regenerating]
863:         end
864:       end
865:     else
866:       $stderr.puts PDF::Writer::Lang[:techbook_ignoring_cache] if File.exist?(files[:cache])
867:     end
868: 
869:       # Create the manual object.
870:     pdf = PDF::TechBook.new
871:     pdf.compressed = config.compressed
872:     pdf.techbook_source_dir = File.expand_path(dirn)
873: 
874:     document = open(files[:document]) { |io| io.read.split($/) }
875:     progress = ProgressBar.new(base.capitalize, document.size)
876:     pdf.techbook_parse(document, progress)
877:     progress.finish
878: 
879:     if pdf.generate_table_of_contents?
880:       progress = ProgressBar.new("TOC", pdf.table_of_contents.size)
881:       pdf.techbook_toc(progress)
882:       progress.finish
883:     end
884: 
885:     if config.cache
886:       File.open(files[:cache], "wb") { |f| f.write Marshal.dump(pdf) }
887:     end
888: 
889:     pdf.save_as(files[:pdf])
890:   end

Public Instance methods

[Source]

     # File lib/pdf/techbook.rb, line 433
433:   def __heading1(heading)
434:     @chapter_number ||= 0
435:     @chapter_number = @chapter_number.succ
436:     "#{chapter_number}. #{heading}"
437:   end

[Source]

     # File lib/pdf/techbook.rb, line 438
438:   def __heading2(heading)
439:     heading
440:   end

[Source]

     # File lib/pdf/techbook.rb, line 441
441:   def __heading3(heading)
442:     "<b>#{heading}</b>"
443:   end

[Source]

     # File lib/pdf/techbook.rb, line 444
444:   def __heading4(heading)
445:     "<i>#{heading}</i>"
446:   end

[Source]

     # File lib/pdf/techbook.rb, line 447
447:   def __heading5(heading)
448:     "<c:uline>#{heading}</c:uline>"
449:   end

[Source]

     # File lib/pdf/techbook.rb, line 787
787:   def generate_table_of_contents?
788:     @gen_toc
789:   end

[Source]

     # File lib/pdf/techbook.rb, line 746
746:   def techbook_directive_author(args)
747:     info.author = args
748:   end

[Source]

     # File lib/pdf/techbook.rb, line 764
764:   def techbook_directive_blist(args)
765:     __render_paragraph
766:     sm = /^(\w+).*$/o.match(args)
767:     style = sm.captures[0] if sm
768:     style = "bullet" unless LIST_ITEM_STYLES.include?(style)
769: 
770:     @blist_factor = @left_margin * 0.10 if @blist_info.empty?
771: 
772:     info = {
773:       :left_margin  => @left_margin,
774:       :style        => style
775:     }
776:     @blist_info << info
777:     @left_margin += @blist_factor
778: 
779:     @techbook_lastmode, @techbook_mode = @techbook_mode, :blist if :blist != @techbook_mode
780:   end

Code: .code

[Source]

     # File lib/pdf/techbook.rb, line 661
661:   def techbook_directive_code(args)
662:     __render_paragraph
663:     select_font @techbook_codefont, @techbook_encoding
664:     @techbook_lastmode, @techbook_mode = @techbook_mode, :code
665:     @techbook_textopt  = { :justification => :left, :left => 20, :right => 20 }
666:     @techbook_fontsize = 10
667:   end

Columns. .columns <number-of-columns>|off

[Source]

     # File lib/pdf/techbook.rb, line 719
719:   def techbook_directive_columns(args)
720:     av = /^(\d+|off)(?: (\d+))?(?: .*)?$/o.match(args)
721:     unless av
722:       $stderr.puts PDF::Writer::Lang[:techbook_bad_columns_directive] % args
723:       raise ArgumentError
724:     end
725:     cols = av.captures[0]
726: 
727:       # Flush the paragraph cache.
728:     __render_paragraph
729: 
730:     if cols == "off" or cols.to_i < 2
731:       stop_columns
732:     else
733:       if av.captures[1]
734:         start_columns(cols.to_i, av.captures[1].to_i)
735:       else
736:         start_columns(cols.to_i)
737:       end
738:     end
739:   end

Done. Stop parsing: .done

[Source]

     # File lib/pdf/techbook.rb, line 709
709:   def techbook_directive_done(args)
710:     unless @techbook_code.empty?
711:       $stderr.puts PDF::Writer::Lang[:techbook_code_not_empty]
712:       $stderr.puts @techbook_code
713:     end
714:     __render_paragraph
715:     :break
716:   end

[Source]

     # File lib/pdf/techbook.rb, line 782
782:   def techbook_directive_endblist(args)
783:     self.left_margin = @blist_info.pop[:left_margin]
784:     @techbook_lastmode, @techbook_mode = @techbook_mode, @techbook_lastmode if @blist_info.empty?
785:   end

End Code: .endcode

[Source]

     # File lib/pdf/techbook.rb, line 670
670:   def techbook_directive_endcode(args)
671:     select_font @techbook_textfont, @techbook_encoding
672:     @techbook_lastmode, @techbook_mode = @techbook_mode, @techbook_lastmode
673:     @techbook_textopt  = { :justification => :full }
674:     @techbook_fontsize = 12
675:   end

End Eval: .endeval

[Source]

     # File lib/pdf/techbook.rb, line 684
684:   def techbook_directive_endeval(args)
685:     save_state
686: 
687:     thread = Thread.new do
688:       begin
689:         @techbook_code.untaint
690:         pdf = self
691:         eval @techbook_code
692:       rescue Exception => ex
693:         err = PDF::Writer::Lang[:techbook_eval_exception]
694:         $stderr.puts err % [ @techbook_line__, ex, ex.backtrace.join("\n") ]
695:         raise ex
696:       end
697:     end
698:     thread.abort_on_exception = true
699:     thread.join
700: 
701:     restore_state
702:     select_font @techbook_textfont, @techbook_encoding
703: 
704:     @techbook_code = ""
705:     @techbook_mode, @techbook_lastmode = @techbook_lastmode, @techbook_mode
706:   end

End preserved newlines: .endpre

[Source]

     # File lib/pdf/techbook.rb, line 656
656:   def techbook_directive_endpre(args)
657:     @techbook_mode = :normal
658:   end

Eval: .eval

[Source]

     # File lib/pdf/techbook.rb, line 678
678:   def techbook_directive_eval(args)
679:     __render_paragraph
680:     @techbook_lastmode, @techbook_mode = @techbook_mode, :eval
681:   end

[Source]

     # File lib/pdf/techbook.rb, line 758
758:   def techbook_directive_keywords(args)
759:     info.keywords = args
760:   end

Start a new page: .newpage

[Source]

     # File lib/pdf/techbook.rb, line 639
639:   def techbook_directive_newpage(args)
640:     __render_paragraph
641: 
642:     if args =~ /^force/
643:       start_new_page true
644:     else
645:       start_new_page
646:     end
647:   end

Preserved newlines: .pre

[Source]

     # File lib/pdf/techbook.rb, line 650
650:   def techbook_directive_pre(args)
651:     __render_paragraph
652:     @techbook_mode = :preserved
653:   end

[Source]

     # File lib/pdf/techbook.rb, line 754
754:   def techbook_directive_subject(args)
755:     info.subject  = args
756:   end

[Source]

     # File lib/pdf/techbook.rb, line 750
750:   def techbook_directive_title(args)
751:     info.title  = args
752:   end

[Source]

     # File lib/pdf/techbook.rb, line 741
741:   def techbook_directive_toc(args)
742:     @toc_title  = args unless args.empty?
743:     @gen_toc    = true
744:   end

[Source]

     # File lib/pdf/techbook.rb, line 517
517:   def techbook_parse(document, progress = nil)
518:     @table_of_contents = []
519: 
520:     @toc_title          = "Table of Contents"
521:     @gen_toc            = false
522:     @techbook_code      = ""
523:     @techbook_para      = ""
524:     @techbook_fontsize  = 12
525:     @techbook_textopt   = { :justification => :full }
526:     @techbook_lastmode  = @techbook_mode = :normal
527: 
528:     @techbook_textfont  = "Times-Roman"
529:     @techbook_codefont  = "Courier"
530: 
531:     @blist_info         = []
532: 
533:     @techbook_line__    = 0
534: 
535:     __build_xref_table(document)
536: 
537:     document.each do |line|
538:     begin
539:       progress.inc if progress
540:       @techbook_line__ += 1
541: 
542:       next if line =~ %r{^#}o
543: 
544:       directive, args = techbook_find_directive(line)
545:       if directive
546:           # Just try to call the method/directive. It will be far more
547:           # common to *find* the method than not to.
548:         res = __send__("techbook_directive_#{directive}", args) rescue nil
549:         break if :break == res 
550:         next
551:       end
552: 
553:       case @techbook_mode
554:       when :eval
555:         @techbook_code << line << "\n"
556:         next
557:       when :code
558:         techbook_text(line)
559:         next
560:       when :blist
561:         line = "<C:#{@blist_info[-1][:style]}/>#{line}"
562:         techbook_text(line)
563:         next
564:       end
565: 
566:       next if techbook_heading(line)
567: 
568:       if :preserved == @techbook_mode
569:         techbook_text(line)
570:         next
571:       end
572: 
573:       line.chomp!
574: 
575:       if line.empty?
576:         __render_paragraph
577:         techbook_text("\n")
578:       else
579:         @techbook_para << " " unless @techbook_para.empty?
580:         @techbook_para << line
581:       end
582:     rescue Exception => ex
583:       $stderr.puts PDF::Writer::Lang[:techbook_exception] % [ ex, @techbook_line ]
584:       raise
585:     end
586:     end
587:   end

[Source]

     # File lib/pdf/techbook.rb, line 892
892:   def techbook_text(line)
893:     opt = @techbook_textopt.dup
894:     opt[:font_size] = @techbook_fontsize
895:     text(line, opt)
896:   end

[Source]

     # File lib/pdf/techbook.rb, line 589
589:   def techbook_toc(progress = nil)
590:     insert_mode :on
591:     insert_position :after
592:     insert_page 1
593:     start_new_page
594: 
595:     style = H1_STYLE
596:     save_state
597: 
598:     if style[:bar]
599:       fill_color    style[:background]
600:       fh = font_height(style[:font_size]) * 1.01
601:       fd = font_descender(style[:font_size]) * 1.01
602:       x = absolute_left_margin
603:       w = absolute_right_margin - absolute_left_margin
604:       rectangle(x, y - fh + fd, w, fh).fill
605:     end
606: 
607:     fill_color  style[:foreground]
608:     text(@toc_title, :font_size => style[:font_size],
609:          :justification => style[:justification])
610: 
611:     restore_state
612: 
613:     self.y += font_descender(style[:font_size])#* 0.5
614: 
615:     right = absolute_right_margin
616: 
617:       # TODO -- implement tocdots as a replace tag and a single drawing tag.
618:     @table_of_contents.each do |entry|
619:       progress.inc if progress
620: 
621:       info =  "<c:ilink dest='#{entry[:xref]}'>#{entry[:title]}</c:ilink>"
622:       info << "<C:tocdots level='#{entry[:level]}' page='#{entry[:page]}' xref='#{entry[:xref]}'/>"
623: 
624:       case entry[:level]
625:       when 1
626:         text info, :font_size => 16, :absolute_right => right
627:       when 2
628:         text info, :font_size => 12, :left => 50, :absolute_right => right
629:       end
630:     end
631:   end

Private Instance methods

[Source]

     # File lib/pdf/techbook.rb, line 355
355:   def __build_xref_table(data)
356:     headings = data.grep(HEADING_FORMAT_RE)
357: 
358:     @xref_table = {}
359: 
360:     headings.each_with_index do |text, idx|
361:       level, label, name = HEADING_FORMAT_RE.match(text).captures
362: 
363:       xref = "xref#{idx}"
364: 
365:       name ||= idx.to_s
366:       @xref_table[name] = {
367:         :title  => __send__("__heading#{level}", label),
368:         :page   => nil,
369:         :level  => level.to_i,
370:         :xref   => xref
371:       }
372:     end
373:   end

[Source]

     # File lib/pdf/techbook.rb, line 376
376:   def __render_paragraph
377:     unless @techbook_para.empty?
378:       techbook_text(@techbook_para.squeeze(" "))
379:       @techbook_para.replace ""
380:     end
381:   end

[Source]

     # File lib/pdf/techbook.rb, line 386
386:   def techbook_find_directive(line)
387:     directive = nil
388:     arguments = nil
389:     dmatch = LINE_DIRECTIVE_RE.match(line)
390:     if dmatch
391:       directive = dmatch.captures[0].downcase.chomp
392:       arguments = dmatch.captures[1]
393:     end
394:     [directive, arguments]
395:   end

[Source]

     # File lib/pdf/techbook.rb, line 453
453:   def techbook_heading(line)
454:     head = HEADING_FORMAT_RE.match(line)
455:     if head
456:       __render_paragraph
457: 
458:       @heading_num ||= -1
459:       @heading_num += 1
460: 
461:       level, heading, name = head.captures
462:       level = level.to_i
463: 
464:       name ||= @heading_num.to_s
465:       heading = @xref_table[name]
466: 
467:       style   = self.class.const_get("H#{level}_STYLE")
468: 
469:       start_transaction(:heading_level)
470:       ok = false
471: 
472:       loop do # while not ok
473:         break if ok
474:         this_page = pageset.size
475: 
476:         save_state
477: 
478:         if style[:bar]
479:           fill_color style[:background]
480:           fh = font_height(style[:font_size]) * 1.01
481:           fd = font_descender(style[:font_size]) * 1.01
482:           x = absolute_left_margin
483:           w = absolute_right_margin - absolute_left_margin
484:           rectangle(x, y - fh + fd, w, fh).fill
485:         end
486: 
487:         fill_color style[:foreground]
488:         text(heading[:title], :font_size => style[:font_size],
489:              :justification => style[:justification])
490: 
491:         restore_state
492: 
493:         if (pageset.size == this_page)
494:           commit_transaction(:heading_level)
495:           ok = true
496:         else
497:             # We have moved onto a new page. This is bad, as the background
498:             # colour will be on the old one.
499:           rewind_transaction(:heading_level)
500:           start_new_page
501:         end
502:       end
503: 
504:       heading[:page] = which_page_number(current_page_number)
505: 
506:       case level
507:       when 1, 2
508:         @table_of_contents << heading
509:       end
510: 
511:       add_destination(heading[:xref], 'FitH', @y + font_height(style[:font_size]))
512:     end
513:     head
514:   end

[Validate]