From b795da189ef530321345784af52a6a1103e825d8 Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Sun, 25 Jan 2009 22:23:24 -0700 Subject: [PATCH] manual (html AND pdf) --- doc/html.rb | 192 +++++++++++++++++++++++++++++++++++ doc/include/basics.rb | 6 ++ doc/include/breaks.rb | 13 +++ doc/include/custom-tags.rb | 10 ++ doc/include/custom-tags2.rb | 2 + doc/include/indent.rb | 4 + doc/include/options.rb | 15 +++ doc/include/style-classes.rb | 5 + doc/manual.txt | 100 ++++++++++++++++++ doc/pdf.rb | 176 ++++++++++++++++++++++++++++++++ 10 files changed, 523 insertions(+) create mode 100644 doc/html.rb create mode 100644 doc/include/basics.rb create mode 100644 doc/include/breaks.rb create mode 100644 doc/include/custom-tags.rb create mode 100644 doc/include/custom-tags2.rb create mode 100644 doc/include/indent.rb create mode 100644 doc/include/options.rb create mode 100644 doc/include/style-classes.rb create mode 100644 doc/manual.txt create mode 100644 doc/pdf.rb diff --git a/doc/html.rb b/doc/html.rb new file mode 100644 index 0000000..c923a90 --- /dev/null +++ b/doc/html.rb @@ -0,0 +1,192 @@ +$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib" +require 'prawn/format/version' +require 'coderay' + +def process_style(document, style, content, line_number) + content = process_substitutions(content) + + case style + when "h1" then h1(document, content) + when "h2" then h2(document, content) + when "p" then paragraph(document, content) + when "fp" then paragraph(document, content, false) + when "ul" then start_list(document) + when "li" then list_item(document, content) + when "/ul" then end_list(document) + when "page" then new_page(document) + when "highlight" then highlight(document, content) + when "hr" then horiz_rule(document) + when "center" then center(document, content) + else warn "unknown style #{style.inspect}" + end + +rescue Exception => err + puts "[error occurred while processing line ##{line_number}]" + raise +end + +def process_substitutions(content) + content. + gsub(/%FORMAT:VERSION%/, Prawn::Format::Version::STRING). + gsub(/%NOW%/, Time.now.utc.strftime("%e %B %Y at %H:%M UTC")) +end + +def center(document, content) + document << "
#{content}
\n" +end + +def horiz_rule(document) + document << "
\n" +end + +def h1(document, content) + document << "

#{content}

\n" +end + +def h2(document, content) + document << "

#{content}

\n" +end + +def paragraph(document, content, indent=true) + document << "

#{content}

\n" +end + +def start_list(document) + document << "\n" +end + +def new_page(document) + document << "
\n" +end + +def highlight(document, content) + file, syntax = content.split(/,/) + analyzed = CodeRay.scan(File.read(File.join(File.dirname(__FILE__), file)), syntax.to_sym) + document << "
#{analyzed.html}
" +end + +File.open("prawn-format.html", "w") do |html| + html << "\n\n\n" + html << "\n" + File.open("#{File.dirname(__FILE__)}/manual.txt") do |source| + number = 0 + source.each_line do |line| + number += 1 + line.chomp! + next if line.length == 0 + + style, content = line.match(/^(\S+)\.\s*(.*)/)[1,2] + process_style(html, style, content, number) + end + end + html << "\n\n" +end + +__END__ +body { + margin-left: 15%; + margin-right: 15%; + margin-top: 1em; +} + +.highlight { + background-color: #f8f8f8; + border: 1px solid silver; + font-family: Courier, monospace; + font-size: 90%; + color: #100; + line-height: 1.1em; +} +pre.highlight { margin: 0px; padding: 1em; } + +.highlight .code { width: 100% } + +ol.highlight { font-size: 10pt } +ol.highlight li { white-space: pre } + +.highlight .code pre { width: 37em; overflow: auto } + +.highlight .af { color:#00C } +.highlight .an { color:#007 } +.highlight .av { color:#700 } +.highlight .aw { color:#C00 } +.highlight .bi { color:#509; font-weight:bold } +.highlight .c { color:#888 } + +.highlight .ch { color:#04D } +.highlight .ch .k { color:#04D } +.highlight .ch .dl { color:#039 } + +.highlight .cl { color:#B06; font-weight:bold } +.highlight .co { color:#036; font-weight:bold } +.highlight .cr { color:#0A0 } +.highlight .cv { color:#369 } +.highlight .df { color:#099; font-weight:bold } +.highlight .di { color:#088; font-weight:bold } +.highlight .dl { color:black } +.highlight .do { color:#970 } +.highlight .ds { color:#D42; font-weight:bold } +.highlight .e { color:#666; font-weight:bold } +.highlight .en { color:#800; font-weight:bold } +.highlight .er { color:#F00; background-color:#FAA } +.highlight .ex { color:#F00; font-weight:bold } +.highlight .fl { color:#60E; font-weight:bold } +.highlight .fu { color:#06B; font-weight:bold } +.highlight .gv { color:#d70; font-weight:bold } +.highlight .hx { color:#058; font-weight:bold } +.highlight .i { color:#00D; font-weight:bold } +.highlight .ic { color:#B44; font-weight:bold } + +.highlight .il { background: #eee } +.highlight .il .il { background: #ddd } +.highlight .il .il .il { background: #ccc } +.highlight .il .dl { font-weight: bold ! important; color: #888 ! important } + +.highlight .in { color:#B2B; font-weight:bold } +.highlight .iv { color:#33B } +.highlight .la { color:#970; font-weight:bold } +.highlight .lv { color:#963 } +.highlight .oc { color:#40E; font-weight:bold } +.highlight .on { color:#000; font-weight:bold } +.highlight .op { } +.highlight .pc { color:#038; font-weight:bold } +.highlight .pd { color:#369; font-weight:bold } +.highlight .pp { color:#579 } +.highlight .pt { color:#339; font-weight:bold } +.highlight .r { color:#080; font-weight:bold } + +.highlight .rx { background-color:#fff0ff } +.highlight .rx .k { color:#808 } +.highlight .rx .dl { color:#404 } +.highlight .rx .mod { color:#C2C } +.highlight .rx .fu { color:#404; font-weight: bold } + +.highlight .s { background-color:#fff0f0 } +.highlight .s .s { background-color:#ffe0e0 } +.highlight .s .s .s { background-color:#ffd0d0 } +.highlight .s .k { color:#D20 } +.highlight .s .dl { color:#710 } + +.highlight .sh { background-color:#f0fff0 } +.highlight .sh .k { color:#2B2 } +.highlight .sh .dl { color:#161 } + +.highlight .sy { color:#A60 } +.highlight .sy .k { color:#A60 } +.highlight .sy .dl { color:#630 } + +.highlight .ta { color:#070 } +.highlight .tf { color:#070; font-weight:bold } +.highlight .ts { color:#D70; font-weight:bold } +.highlight .ty { color:#339; font-weight:bold } +.highlight .v { color:#036 } +.highlight .xt { color:#444 } + diff --git a/doc/include/basics.rb b/doc/include/basics.rb new file mode 100644 index 0000000..fc8d938 --- /dev/null +++ b/doc/include/basics.rb @@ -0,0 +1,6 @@ +require 'prawn' +require 'prawn/format' + +Prawn::Document.generate('basics.pdf') do + text "Some bold and italic text" +end diff --git a/doc/include/breaks.rb b/doc/include/breaks.rb new file mode 100644 index 0000000..a5a5d40 --- /dev/null +++ b/doc/include/breaks.rb @@ -0,0 +1,13 @@ +# renders all on one line +text <<-TEXT + • Item one + • Item two + • Item three +TEXT + +# renders all on three separate lines +text <<-TEXT + • Item one
+ • Item two
+ • Item three +TEXT diff --git a/doc/include/custom-tags.rb b/doc/include/custom-tags.rb new file mode 100644 index 0000000..bbd9f31 --- /dev/null +++ b/doc/include/custom-tags.rb @@ -0,0 +1,10 @@ +Prawn::Document.generate('basics.pdf') do + tags :h1 => { :font_size => "3em", :font_weight => :bold }, + :h2 => { :font_size => "2em", :font_style => :italic }, + :product => { :color => "red", + :text_decoration => :underline } + + text "

Manual

" + text "

Learning to do stuff

" + text "Prawn is a PDF generator for Ruby" +end diff --git a/doc/include/custom-tags2.rb b/doc/include/custom-tags2.rb new file mode 100644 index 0000000..4f3bd67 --- /dev/null +++ b/doc/include/custom-tags2.rb @@ -0,0 +1,2 @@ +tags[:em][:color] = "red" +text "This really matters!" diff --git a/doc/include/indent.rb b/doc/include/indent.rb new file mode 100644 index 0000000..3265cde --- /dev/null +++ b/doc/include/indent.rb @@ -0,0 +1,4 @@ +tags[:indent] = { :width => "2em" } + +text "Once upon a time, far, far away..." +text "But the prince was not discouraged! ..." diff --git a/doc/include/options.rb b/doc/include/options.rb new file mode 100644 index 0000000..ae8800d --- /dev/null +++ b/doc/include/options.rb @@ -0,0 +1,15 @@ +# force unformatted display +text "Show this verbatim", :plain => true + +# force formatted display for full justification +text "I want this to be fully justified", :plain => false, + :align => :justify + +# assume all text is blue +text "Blue text", :plain => false, + :default_style => { :color => "blue" } + +# one-off tag and style definitions +text "Nifty!", + :tags => { :nifty => { :font_weight => :bold } }, + :styles => { :orange => { :color => "#fa0" } } diff --git a/doc/include/style-classes.rb b/doc/include/style-classes.rb new file mode 100644 index 0000000..df54990 --- /dev/null +++ b/doc/include/style-classes.rb @@ -0,0 +1,5 @@ +styles :product => { :color => "navy" }, + :ruby => { :color => "red", :font_family => "Courier", + :white_space => :pre } +text "In Ruby, you can write " + + "1.upto(5) { |x| puts x }" diff --git a/doc/manual.txt b/doc/manual.txt new file mode 100644 index 0000000..e2e9fc9 --- /dev/null +++ b/doc/manual.txt @@ -0,0 +1,100 @@ +h1. Prawn::Format %FORMAT:VERSION% +fp. Prawn::Format is an extension library for the Prawn PDF generation library for Ruby, allowing you to easily embed formatting tags in your documents. It features: +ul. +li. Out-of-the-box support for many common HTML tags +li. Intra-document hyperlinks +li. Easily add your own tag definitions +li. Style classes +/ul. +p. Sound good? It's not all roses, though. Prawn::Format currently has the following known warts: +ul. +li. No support for block-level tags (paragraphs, divs, tables, etc.) +li. No support for inline images +/ul. +p. If that doesn't deter you, read on! This manual currently has information about the following topics: +ul. +li. Basic usage +li. Custom tags +li. Style classes +li. text() options +/ul. +p. Prawn::Format was written by Jamis Buck (jamis@jamisbuck.org). Although there are not currently plans to extend the functionality much beyond the current incarnation, Jamis is definitely amenable to patches. What this means is that if you've got a pet feature you'd like to see in Prawn::Format, your best bet is to get to work and implement it! +p. Bug reports are welcome, and may be submitted at: +center. http://prawn.lighthouseapp.com/projects/23476 +p. And if you're inclined to help out with patches and the like, the code is hosted at (where else?) GitHub: +center. http://github.com/jamis/prawn-format +p. Thanks! +hr. +fp. This manual generated for Prawn::Format version %FORMAT:VERSION% on %NOW%. + +page. +h2. Basic usage +fp. For most uses, adding text to your documents is exactly the same as it is in Prawn. The only difference is that you now add formatting tags (HTML-ish in nature) to the strings: +highlight. include/basics.rb,ruby +p. Prawn::Format does not support even the majority of HTML tags by default, but it does include support for the most common ones (and you can easily add others if you so desire). The supported tags are: +ul. +li. a — for hyperlinks (the name attribute is for hyperlink anchors, and the href attribute is for the actual links, just like in HTML) +li. b — bold-faced text (but only if the currently selected font family supports bold text) +li. br — line break +li. code — switch to monospaced font (Courier, by default) +li. em — italicized text (but only if the currently selected font family supports italicized text) +li. font — change font attributes, include face (to change font family), color, and size +li. i — italicized text (see em) +li. pre — "verbatim" text (preserving whitespace and newlines). Unlike the HTML pre tag, this is not a block tag +li. span — has no formatting, by default, but is recognized so that you can apply style classes to the tag +li. strong — bold-faced text (see b) +li. sub — subscript text +li. sup — superscript text +li. tt — monospaced font (see code) +li. u — underlined text +/ul. +p. You've likely noticed, already, the conspicuous absence of such common HTML tags as p, div, and table. The reason these are missing is simple: Prawn::Format has no real box model. Turns out, implementing such a beast is a really, really complicated thing to do right. However, you can work around this limitation without too much effort. In fact, this entire manual is formatted using Prawn::Format! (The source code for the manual is included in the Prawn::Format distribution; see doc/manual.rb. Also, examples/document.rb has a simpler demonstration of wholesale document formatting.) +p. One particularly important thing to note about Prawn::Format tags: these are XML tags, and not HTML tags! The distinction is moot in most cases, but what this means in practical terms is that you cannot have unclosed tags in Prawn::Format. Even tags like <br> (which HTML allows to exist unclosed) must be closed. However, Prawn::Format allows you to use the abbreviated "open/close" XML syntax, e.g., <br/>. +p. Lastly, Prawn::Format will ignore whitespace, just like HTML. (Unless you're operating in verbatim mode, usually introduced via the <pre> tag.) This means that newlines in your text will not translate to newlines in your output; you need to use the line-break tag <br/> to put line-breaks in your output. +highlight. include/breaks.rb,ruby +p. Got all that? Great! Time to move on to more tricky stuff. Next up: custom tags! + +page. +h2. Custom tags +fp. Prawn::Format makes it very easy to define your own tags, either for clearer semantic markup or to add support for HTML tags that are not supported out-of-the-box. +highlight. include/custom-tags.rb,ruby +p. If you want to tweak the definition of an existing tag, you can reach right into the tags hash directly and add or modify existing styles: +highlight. include/custom-tags2.rb,ruby +p. The supported style options are: +ul. +li. font_size +li. font_family (this may be any font name that Prawn's Document#font method will support) +li. font_style — either :normal or :italic +li. font_weight — either :normal or :bold +li. color — may be any valid HTML color definition +li. vertical_align — either :super or :sub, or a number to indicate the vertical offset from the baseline +li. text_decoration — either :none or :underline +li. white_space — either :normal, or :pre (for verbatim text, where whitespace is preserved) +li. width — a number, used primarily to fake text indents at paragraph starts. Any tag with this attribute will have a width of that value (in addition to any contents of the tag) +li. kerning — either true or false, to indicate whether kerning should be done on the text +/ul. +p. As you can see, for the most part these follow the same naming and value schemes as CSS3, though of course the similarity is only superficial. Prawn::Format doesn't support cascading styles, or specifying style classes by anything other than a bare class name. (Maybe someday this will change. If it does, it'll be thanks to some enterprising programmer who submits a patch.) +p. Still, even with the limitations, you can do quite a bit. For example, suppose you want indent the first line of a paragraph in your own document. Prawn::Format doesn't support this out of the box, but it's easy to do; just define an <indent> tag and give it a width the size of the text indent; then prepend that tag to every paragraph: +highlight. include/indent.rb,ruby +p. Oh, and did you catch that? Just like in CSS, you can specify most measurements in ems (e.g. "2em"), picas ("3pc"), inches ("0.5in"), or PDF "points" (14). There are 72 PDF points to an inch, and 6 picas to an inch. Also, like CSS, you can specify relative measurements, using percentages, in which case it is relative to some appropriate value. (Setting width to "25%" will set it to 25% of the width of the line. Setting the font size to "80%" will set it to 80% of the current font size.) + +page. +h2. Style classes +fp. Adding a new tag for every different style in your document can get to be pretty cumbersome. For that reason, you can also define style classes. This is simply a style definition (just like you would use for defining a new tag). To apply the style to a tag, do just like you would in HTML: specify the style class in the class attribute of the tag: +highlight. include/style-classes.rb,ruby +p. As mentioned before, Prawn::Format doesn't support cascading styles (i.e. you can't specify the format of an element by the chain of elements and styles in it's ancestor elements). And sadly, in practice, this does limit the ease with which you can apply complex styles. However, with judicious use of both style classes and custom tags you can do quite a bit. + +page. +h2. text() options +fp. Prawn::Format overloads the existing functionality of Prawn's Document#text method. In fact, unless formatting tags or XML entities are detected in the text, Prawn::Format's text method will simply fall back to the original. (Why? Because if you don't need the formatting, it's much faster to skip all the extra work that Prawn::Format does.) +p. What this means is that Prawn::Format tries very hard to support all of the options that Prawn's text method accepts. +p. Prawn::Format does add several new options to text, though. They are as follows: +ul. +li. :plain — a boolean indicating whether the text is "plain" or not. Plain text is processed by the original text method. By default, Prawn::Format analyzes the text to determine plainness, but this option lets you force it one way or the other. +li. :align => :justify — the original text method understood several text alignment methods, but not full-justification. Prawn::Format will understand all the original methods, and also :justify, for full justification. +li. :tags — a hash of tag definitions to use for this one call. Prawn::Format will always use the tags defined via Document#tags, but anything you specify via the :tags option takes precedent. +li. :styles — a hash of style class definitions to use for this one call. Prawn::Format will always use the style classes defined via Document#styles, but anything you specify via the :styles option takes precedent. +li. :default_style — a hash of style definitions that defines the default style to use for any otherwise-unstyled text. This defaults to the current font and color settings on the document, but you can set something else via this option. +/ul. +p. Here are a few examples using these options: +highlight. include/options.rb,ruby diff --git a/doc/pdf.rb b/doc/pdf.rb new file mode 100644 index 0000000..faac523 --- /dev/null +++ b/doc/pdf.rb @@ -0,0 +1,176 @@ +$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib" +require 'prawn' +require 'prawn/format' +require 'prawn/format/version' +require 'coderay' + +def process_style(document, style, content, line_number) + content = process_substitutions(content) + + case style + when "h1" then h1(document, content) + when "h2" then h2(document, content) + when "p" then paragraph(document, content) + when "fp" then paragraph(document, content, false) + when "ul" then start_list(document) + when "li" then list_item(document, content) + when "/ul" then end_list(document) + when "page" then new_page(document) + when "highlight" then highlight(document, content) + when "hr" then horiz_rule(document) + when "center" then center(document, content) + else warn "unknown style #{style.inspect}" + end + +rescue Exception => err + puts "[error occurred while processing line ##{line_number}]" + raise +end + +def process_substitutions(content) + content. + gsub(/%FORMAT:VERSION%/, Prawn::Format::Version::STRING). + gsub(/%NOW%/, Time.now.utc.strftime("%e %B %Y at %H:%M UTC")) +end + +def center(document, content) + document.y -= document.font_size + document.text(content, :plain => false, :align => :center) + document.y -= document.font_size +end + +def horiz_rule(document) + document.y -= document.font_size + document.stroke_color "000000" + document.stroke_horizontal_rule + document.y -= document.font_size +end + +def h1(document, content) + document.text "

#{content}

" + document.stroke_color "000080" + document.stroke_horizontal_rule + document.y -= document.font_size * 2 +end + +def h2(document, content) + document.text "

#{content}

" + document.stroke_color "000080" + document.stroke_horizontal_rule + document.y -= document.font_size +end + +def paragraph(document, content, indent=true) + indent_tag = indent ? "" : "" + document.text "#{indent_tag}#{content}", :align => :justify, :plain => false +end + +def start_list(document) + document.y -= document.font_size +end + +def list_item(document, content) + indent_b = document.font_size * 3 + indent = document.font_size * 4 + + document.start_new_page if document.y < document.font_size + y = document.y - document.bounds.absolute_bottom + document.text "•", :at => [indent_b, y - document.font.ascender] + document.layout(content, :align => :justify) do |helper| + while !helper.done? + y = helper.fill(indent, y, document.bounds.width-indent, :height => document.y) + if helper.done? + document.y = y + document.bounds.absolute_bottom + else + document.start_new_page + y = document.y - document.bounds.absolute_bottom + end + end + end + + document.y -= document.font_size / 4 +end + +def end_list(document) + document.y -= document.font_size * 3 / 4 +end + +def new_page(document) + document.start_new_page +end + +def highlight(document, content) + file, syntax = content.split(/,/) + analyzed = CodeRay.scan(File.read(File.join(File.dirname(__FILE__), file)), syntax.to_sym) + html = "
" + analyzed.html + "
" + + document.y -= document.font_size * 2 + y = document.y - document.bounds.absolute_bottom + start_y = y + document.font_size + + indent = document.font_size * 2 + + document.layout(html) do |helper| + while !helper.done? + y = helper.fill(indent, y, document.bounds.width-indent*2, :height => document.y) + if helper.done? + document.y = y + document.bounds.absolute_bottom + else + document.start_new_page + y = document.y - document.bounds.absolute_bottom + end + end + end + + document.stroke_color "a0a0a0" + document.rectangle [document.font_size, start_y], bounds.width - document.font_size*2, (start_y - y) + document.stroke + + document.y -= document.font_size +end + +SERIF_FONT = "/Library/Fonts/Baskerville.dfont" + +Prawn::Document.generate("prawn-format.pdf", :compress => true) do + if File.exists?(SERIF_FONT) + font_families["Baskerville"] = { + :normal => { :file => SERIF_FONT, :font => 1 }, + :italic => { :file => SERIF_FONT, :font => 2 }, + :bold => { :file => SERIF_FONT, :font => 4 }, # semi-bold, not bold + :bold_italic => { :file => SERIF_FONT, :font => 3 } + } + font "Baskerville", :size => 14 + else + warn "Baskerville font is preferred for the manual, but could not be found. Using Times-Roman." + font "Times-Roman", :size => 14 + end + + tags :h1 => { :font_size => "2em", :font_weight => :bold, :color => "navy" }, + :h2 => { :font_size => "1.5em", :font_weight => :bold, :color => "navy" }, + :indent => { :width => "2em" }, + :about => { :font_size => "80%", :color => "808080", :font_style => :italic } + + styles :no => { :color => "gray" }, + :c => { :color => "#666" }, + :s => { :color => "#d20" }, + :dl => { :color => "black" }, + :co => { :color => "#036", :font_weight => :bold }, + :pc => { :color => "#038", :font_weight => :bold }, + :sy => { :color => "#A60" }, + :r => { :color => "#080" }, + :i => { :color => "#00D", :font_weight => :bold }, + :idl => { :color => "#888", :font_weight => :bold }, + :dl => { :color => "#840", :font_weight => :bold } + + File.open("#{File.dirname(__FILE__)}/manual.txt") do |source| + number = 0 + source.each_line do |line| + number += 1 + line.chomp! + next if line.length == 0 + + style, content = line.match(/^(\S+)\.\s*(.*)/)[1,2] + process_style(self, style, content, number) + end + end +end